Skip to content

[ROCm] Make the Python NFP8 dtype selection architecture-aware - #167

Open
aryaman-gupta wants to merge 3 commits into
aryaman/upstreamfrom
aryaman/fix-nfp8-arch-aware-dtype
Open

[ROCm] Make the Python NFP8 dtype selection architecture-aware#167
aryaman-gupta wants to merge 3 commits into
aryaman/upstreamfrom
aryaman/fix-nfp8-arch-aware-dtype

Conversation

@aryaman-gupta

@aryaman-gupta aryaman-gupta commented Aug 19, 2026

Copy link
Copy Markdown

Problem

FP8 e4m3 has two encodings: fnuz on gfx90a/gfx94x (MI300), and OCP fn on gfx950 and CUDA.

The C++ side already selects per-arch via getNFP8ScalarType(), but Python selected on "is this a ROCm build" and always returned fnuz. On gfx950 that labels the weight tensor fnuz while the device decodes OCP fn, so every PyTorch-native read of those bytes (casting, printing, serialization) is off by one exponent bias — a factor of two.

Fix

Add _nfp8_is_fnuz() / nfp8_dtype(), mirroring the C++ arch check, and use them at the dtype-selection sites.

No new kernel instantiations. relabel_nfp8_for_dispatch() already views an fn-labelled tensor as fnuz at the host boundary, so the single fnuz kernel instantiation still serves both archs. An earlier attempt at this problem emitted both FP8 kernel variants on ROCm and was rolled back over build time; this approach avoids that entirely.

as_dtype() resolves NFP8 ahead of its lookup table rather than inside it, because the table is rebuilt on every call and would otherwise run a device query for every dtype lookup.

Testing (MI350X / gfx950, ROCm 7.1)

  • test_forward_gpu_nfp8_format_matches_host_decode: fails (max abs err 26) → passes
  • forward_test.py: 14 passed, 0 failed
  • Additionally ran the NFP8 tests currently skipped on ROCm (no_cache_fp8, uvm_cache_fp8, backward_adagrad_fp8_pmSUM) — all pass, which exercises the forward, cache, backward and optimizer relabel paths. Those skips are left untouched in this PR.

aryaman-gupta and others added 2 commits August 19, 2026 09:05
The C++ side picks the FP8 e4m3 encoding per device arch
(getNFP8ScalarType: fnuz on gfx94x/gfx90a, OCP fn elsewhere), but Python still
selected on "is this a ROCm build" and always returned fnuz. On gfx950 that
labels the weight buffer fnuz while the device decodes OCP fn, so every
PyTorch-native read of those bytes is off by one exponent bias - a factor of
two.

Add _nfp8_is_fnuz()/nfp8_dtype(), mirroring the C++ arch check, and use them in
sparse_type_int_to_dtype, SparseType.as_dtype, the NFP8 weight init, and the
two test-harness fp8_dtype globals.

No new kernel instantiations: relabel_nfp8_for_dispatch() already views an
fn-labeled tensor as fnuz at the host boundary, so the single fnuz
instantiation still serves both archs.

as_dtype() resolves NFP8 before its lookup table rather than inside it, because
the table is rebuilt per call and would otherwise run a device query for every
dtype lookup.

Fixes test_forward_gpu_nfp8_format_matches_host_decode on gfx950.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Shorten the _nfp8_is_fnuz docstring and the as_dtype note, and invert the guard
to an early return so it is evident that non-ROCm builds never reach the device
query. Behaviour is unchanged - the `and` already short-circuited.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread fbgemm_gpu/fbgemm_gpu/split_embedding_configs.py Outdated
The ty == 9 branch duplicated nfp8_dtype()'s body. Call it instead, leaving
_nfp8_is_fnuz() with a single caller.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants