Repository navigation
test(rocm): prove mxfp8 end to end, incl. FP8 block checkpoints - #2071
Merged
Merged
Conversation
3 tasks done
Member
Author
Implementation Review SummaryIntent
Findings Addressed
Remaining Items
Verification
|
Adds host-reference tests for the block-float matmuls mlxcel runs, through SwitchLinear::forward (gather_qmm) and UnifiedLinear::forward (quantized_matmul). The reference decodes codes and E8M0 scales in Rust and accumulates in f64; a separate test pins the decoder to MLX dequantize. The gather cases reach every branch mxfp4/mxfp8 can take in the ROCm GatherQMM::eval_gpu (all dtype and bit arms, sorted and unsorted schedules, M = 1 and 4). With LOCAL_FIXES item 10 reverted both gather tests fail on gfx1151. The same matrix runs on the CPU device to show the bounds are not fitted to one GPU. Building the CPU-device reference for a vendor FP8 block checkpoint needed two fixes: examples/logit_trace now resolves MLXCEL_DEVICE through initialize_runtime_checked and records the device in its header, and the ROCm overlay's ScaledDotProductAttention::use_fallback now takes the fallback on a non-GPU stream (LOCAL_FIXES item 23). Before it, MLXCEL_DEVICE=cpu aborted every attention model with NYI on a ROCm build; tests/cpu_device_sdpa.rs aborts with the guard reverted. fp8_block.rs documents why load-time requantization stays on the default device, and the hardware.rs capability table cites the new evidence. Refs #1807
Adds the results for issue #1807 and the traces behind them. A vendor FP8 block checkpoint (Lazarus-Ai/ReAligned-Qwen3.5-0.8B-FP8 @ db97e6a7), requantized to mxfp8 at load, traced on ROCm against the CPU device (Metal is not available on this host): 0 of 50 decided positions disagree at w32, largest gap at a disagreement 0.125. GPU-quantized against CPU-quantized weights, both computed on the GPU: 0 of 346 decided positions disagree across w1, w8 and w256, so load-time quantization stays on the default device. Also updates the stale mxfp8 gap line in the 2026-09-12 correctness doc and the ROCm status table in installation.md (FP8 block checkpoints, and the CPU device on a ROCm build, which now runs but slowly). Refs #1807
The sorted shared-activation case gathered one row against 16 slots over 8 experts. The ROCm GatherQMM sorted-rhs schedule needs B / E >= 4, so that case ran the plain lhs-index path and the stride-0 implicit-lhs branch the test doc claimed was never exercised. It now uses 32 slots, four per expert. The f32 bound of 1e-5 would fail on CUDA sm80 and later: the sorted prefill case (B = 128 >= 8 * E) takes MLX's grouped GEMM there, which with MLX_ENABLE_TF32 on by default rounds f32 activations to a 10-bit mantissa. The bound is now 2e-3, still far below the item 10 defect (errors of 1.0 and above). Docs: the quantization-stream comparison names its reference arm (rocm_cpuquant), which the quoted 346 decided positions and 0.375 gap depend on; METADATA no longer gives the MLX pin as the mlxcel base; measured op-test maxima refreshed (f16 2.2e-4 on ROCm, f32 1.2e-7 on CPU). Verified on gfx1151: models::switch_layers::mxfp_tests 5 passed, models::fp8_block 11 passed, cpu_device_sdpa and dead_doc_pointers pass, clippy -D warnings on the mlxcel lib and tests, verify-fmt. Refs #1807
The CPU arm of the mxfp matmul tests ran the full matrix on MLX's scalar fp_qmm_t: about 38 s under test-fast (56 s dev) on every backend, holding lock_default_device the whole time. It now runs both modes with the unsorted prefill, sorted shared-activation and multi-row gather cases and the dense M = 1 and M = 4 cases, in bf16 and f16, on a 64-wide output (2.5 s). The GPU arms are unchanged. Re-measured CPU maxima on the reduced matrix: 8.2e-3 (bf16) and 1.0e-3 (f16), still well under the 2e-2 and 4e-3 bounds. The test module doc, the bound comment and the gfx1151 benchmark note now describe the reduced arm and quote both the reduced and the earlier full-matrix maxima. Also add SAFETY comments on the null-pointer dequantize call and the null-mask fast_scaled_dot_product_attention call. Validation: - cargo test --features rocm --lib models::switch_layers::mxfp_tests -- --test-threads=1: 5 passed - cargo test --features rocm --test cpu_device_sdpa: passed - cargo clippy -p mlxcel --features rocm --lib --tests --examples -- -D warnings: clean Refs #1807
inureyes
force-pushed
the
feature/issue-1807-mxfp8-rocm-e2e
branch
from
September 30, 2026 03:31
15f3640 to
9d1f570
Compare
Closed
6 tasks
inureyes
added a commit
that referenced
this pull request
Sep 30, 2026
Part of #1809 Brings in the Apple M5 Max Metal traces for the five checkpoints PR #2059 traced on ROCm (commit 9830bd6 from `data/issue-1809-metal-traces`, cherry-picked with its authorship) and completes every row of `docs/benchmark_results/rocm-correctness-gfx1151-2026-09-30.md` against them, Metal as the reference, with the 2026-09-12 criterion (zero mismatches on positions decided at `--decided 2.0`). It also adds two `w1ctx512` rows that cover Metal's fused SSM update kernel (commits d2043c9 and c570666 from the same branch for the Metal half, plus new ROCm traces). ## Result | Pair | Decided mismatches | Verdict | |---|---|---| | qwen2.5-7b-instruct w1 / w8 / w256 | 0/19, 0/274, 0/204 | pass | | gemma-3-4b-it w1 / w8 / w256 / w8ctx1536 | 0/2, 0/320, 0/204, 0/189 | pass | | granite-4.0-h-tiny w1 / w8 / w256 | 0/112, 0/260, 0/208 | pass | | nemotron-3-nano-30b-a3b w1 | 0/0 | inconclusive (no decided position) | | nemotron-3-nano-30b-a3b w8 / w256 | 0/287, 0/216 | pass | | qwen2.5-vl-3b-instruct w1 / w8 / w256 | 0/49, 0/198, 0/154 | pass | | granite-4.0-h-tiny w1ctx512 | 0/60 | pass | | nemotron-3-nano-30b-a3b w1ctx512 | 0/71 | pass | No pair fails. Over the first sixteen pairs top-1 disagrees at 155 of 6720 positions, 0 of 2696 decided; the largest reference gap at a disagreement is 1.250 (Nemotron-H w256, rank 2). The doc also has the logit delta percentiles, perplexity deltas, a 0.5/1.0/2.0 threshold sweep and the rank distribution. ## The w1ctx512 rows The earlier caveat "w1: Metal runs the fused SSM update kernel, ROCm runs the SSD graph" was wrong. The fused step needs `seq_len == 1`, `ssm_kernel_available()` and an existing `ssm_state`, and `logit_trace` builds fresh caches per chunk, so w1 without prefill runs the graph on both backends: w1 is graph against graph. `w1ctx512` (`1 128 8 512`, `MLXCEL_TRACE_START_TOKEN=512`) prefills 512 tokens before each single-token forward, so Metal runs `ssm_update_kernel` (inside `fused_mamba2_forward` for Nemotron-H) and ROCm runs the SSD graph with state. - Both halves are at 3c9edea (`metal_m5_3c9edea0/`, `rocm_gfx1151_3c9edea0/`), a later commit than the other sixteen pairs. The ROCm binary (sha256 `00686315...`) was built on this branch at 8e65051, which differs from 3c9edea only in data and docs. The Metal d112826 w1ctx512 traces have byte-identical data rows. - granite: top-1 4/128, 0/60 decided, largest gap 0.125, perplexity 7.090 Metal vs 7.234 ROCm (+2.0%, about 1 standard error per position). nemotron: 7/128, 0/71, largest gap 0.250, 7.489 vs 7.489. Both are zero at 0.5 and 1.0 too. - The doc lists the ROCm fixes merged between the two commits and where they sit relative to this path (#2070, #2076, #2079, #2071). Apple has no runtime switch to force the graph path, so fused vs graph could not be A/B tested on Metal. ## Not like-for-like (stated in the doc) - The reference is an M5 (generation 17, NAX kernels for multi-token matmul and prefill attention), not the M1 Ultra of the first run, so the two runs' bounds are against different Metal arithmetic. - Nemotron-H compares Metal `fused_moe_forward` with ROCm `forward_nonfused` at every width. - The VLM rows trace the language model only. ## Findings for follow-up - `src/models/nemotron_h.rs` prints five loader lines to stdout, so every Nemotron-H trace (both backends) makes `compare_logit_traces.py` raise `ValueError`. Those pairs were compared on copies filtered with `grep -v '^\[NemotronH\] '`; committed traces are unchanged. The `# device` header line of newer traces is read as metadata and needs no filter. - granite w1 perplexity is +4.345% over 128 no-context positions (about 2.5 standard errors per position) with every top-1 in agreement. ## Verification On the gfx1151 host: `sha256sum -c SHA256SUMS` in all three new or updated trace directories; `cargo build --release --features rocm --example logit_trace` at 8e65051 and the two w1ctx512 ROCm traces (exit 0, 128 data rows, no NaN), each started with no other process in `/sys/class/kfd/kfd/proc`; all 18 comparisons run; `make verify-versions verify-kernel-dtype-keys verify-kernel-port-dispatch verify-llama-compat verify-rocm-overlay verify-fmt verify-binary-assets` pass; `cargo test --features rocm --test dead_doc_pointers` pass (2 tests). Not verified: Metal and CUDA are not available on this host. The Metal traces were produced on the M5 Max and are taken on their METADATA.txt, RUNS.txt and checksums. No code on the Metal or CUDA paths changes.
This was referenced Sep 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SwitchLinear::forward(gather_qmm) andUnifiedLinear::forward(quantized_matmul), against a host-decoded f64 reference (src/models/switch_layers_mxfp_tests.rs). The gather cases hit every branch a block-float mode reaches in the ROCmGatherQMM::eval_gpu: all specialised paths there are affine-only, so mxfp4/mxfp8 reach only the item-10gather_qmv_kernel<T, uint8_t, BITS, 32, false>launch. The cases cover bf16/f16/f32, 8 and 4 bits, unsorted and sorted-rhs schedules (activation strideKand 0), andM = 1and4. This is the default path. The opt-in expert-batched kernel (item 9) is affine-only and unreachable. A reduced matrix also runs on the CPU device (both modes, unsorted and sorted gather, multi-row, denseM = 1and4, bf16 and f16, 64-wide output), which takes 2.5 s instead of the 38 s the full matrix needed on MLX's scalar CPUfp_qmm_t.docs/benchmark_results/rocm-fp8-block-gfx1151-2026-09-30.mdand traces inbenchmarks/logit_traces/fp8_block_gfx1151/. Getting that reference took two fixes.examples/logit_tracenow honoursMLXCEL_DEVICE. The ROCm overlay'sScaledDotProductAttention::use_fallbacknow takes the fallback on a CPU stream (LOCAL_FIXES.mditem 23). Before it,MLXCEL_DEVICE=cpuaborted every attention model withNYIon a ROCm build.tests/cpu_device_sdpa.rsguards it.fp8_block.rs. No new round-trip test was needed because the quantize stream did not change. Thehardware.rsevidence comment now cites these results.Verification
All on gfx1151:
cargo test --features rocm --lib models::switch_layers::mxfp_tests models::fp8_block -- --test-threads=1: 16 passed, including the existing round trip. Worst relative L2 error on ROCm: 1.8e-3 (bf16), 2.2e-4 (f16), 2.9e-7 (f32). On the CPU device, reduced matrix: 8.2e-3 (bf16), 1.0e-3 (f16); the earlier full-matrix run gave 7.9e-3, 1.0e-3, 1.2e-7. Bounds are 2e-2, 4e-3, 2e-3 (the f32 bound covers CUDA's default TF32 grouped GEMM, which the sorted prefill case reaches on sm80 and later).LOCAL_FIXESitem 10 reverted and the overlay rebuilt, both gather tests failed with non-finite output. With the SDPA guard reverted,cargo test --features rocm --test cpu_device_sdpaaborted withNYI. Both pass restored.Lazarus-Ai/ReAligned-Qwen3.5-0.8B-FP8@db97e6a7(dense). CPU vs ROCm atw32(32 8 8 0): 0/50 decided disagreements, largest gap 0.125. ROCm traces are byte-identical on rerun and on the rebased build.mlxcel generateon ROCm: 132 tensors requantized in 565 ms, 56 to 62 tok/s, coherent text ("The capital of France is Paris ... The Eiffel Tower comes to mind immediately. The Louvre Museum is another major landmark.").make verify-versions verify-kernel-dtype-keys verify-kernel-port-dispatch verify-llama-compat verify-fmt,cargo test --test dead_doc_pointers, and clippy-D warningsonmlxcel(lib, tests, examples) andmlxcel-core(lib, tests): pass.Not verified: Metal and CUDA are not available on this host. On those paths the change touches
examples/logit_trace(now callsinitialize_runtime_checked, which also readsMLXCEL_DEVICEthere and applies the default wired limit and the arch-compatibility refusal the CLI already applies) and addstests/cpu_device_sdpa.rsand the mxfp tests, which run on every backend. Neither the SDPA edit (ROCm overlay only) nor any library code path changes there. Also not verified: the official Qwen3.5 FP8 MoE and 27B releases (too large for this host and for a CPU reference), and CPU-reference arms atw1,w8andw256(the CPU backend needs about 2.7 minutes per token for this checkpoint).Closes #1807