Skip to content

fix(rocm): clear the nine gfx1151 test failures in verify-rocm - #2613

Merged
inureyes merged 6 commits into
mainfrom
fix/issue-2608-rocm-test-failures
Oct 26, 2026
Merged

inureyes merged 6 commits into
mainfrom
fix/issue-2608-rocm-test-failures

Conversation

@inureyes

@inureyes inureyes commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes the nine make verify-rocm failures on gfx1151 from #2608. Reproduced on main at 773aa55. On macOS (Nightly verify run 39703647781 at 414ae12) all nine pass, so every failure is ROCm-only. Hosted CI on main runs no test jobs right now, so there is no CUDA data point.

Root causes and resolution

Group B: compiled real / imag of complex64 (mlxcel-core), fixed.

  • compiled_dtype_cast_tests::compiled_real_of_complex64_matches_eager
  • compiled_dtype_cast_tests::compiled_imag_of_complex64_matches_eager

Cause: the hiprtc preamble (g_jit_includes in overlays/rocm/mlx/backend/rocm/compiled.cpp) defined no complex64_t, although the shared codegen names complex64 values that way and emits complex64_t(re, im) constants and static_cast for AsType. Every fused kernel touching complex64 failed to JIT ("unknown type name 'complex64_t'"). Real, Imag and Conjugate were also identity or zero stubs for real inputs.

Fix: the preamble defines complex64_t (hipFloatComplex layout, explicit real conversions, arithmetic and comparison operators) with complex64 overloads for Add, Subtract, Multiply, Divide, Square, Abs, Real, Imag and Conjugate. complex64_t to bool is true when either part is nonzero, and NaNEqual has a complex64 overload, both matching MLX CPU and CUDA (added during review: the generic real-part conversion would otherwise have made a fused astype(z, bool) and array_equal(..., equal_nan=True) silently wrong). Float-promoting op templates with no complex overload now static_assert on complex64 (MLX_ROCM_REAL_ONLY), so an unsupported complex graph fails to build with a message instead of silently computing on the real part. Recorded as LOCAL_FIXES.md item 59. It is a backend file, so CORE_RESIDUAL.diff is unchanged.

Group A, NVFP4 loads (mlxcel): confirmed backend gap, gated.

  • models::switch_layers::global_scale_tests::native_global_scale_matches_dense_dequantized_reference
  • models::switch_layers::global_scale_tests::per_expert_sidecars_stack_into_the_output_multiplier
  • models::switch_layers::global_scale_tests::a_plane_whose_family_left_the_modelopt_sidecars_unstacked_is_refused
  • loading::vlm::gemma_unified::tests::unified_nvfp4_fused_experts_keep_their_native_global_scale
  • models::mistral4::modelopt_stack_tests::modelopt_expert_sidecars_stack_into_the_output_multiplier
  • models::phimoe::sanitize_tests::modelopt_expert_sidecars_stack_into_the_output_multiplier

Cause: each test loads an MLX-native NVFP4 SwitchLinear / SwitchGLU plane. ROCm has no NVFP4 kernel: GpuBackendKind::quant_mode_support marks it ConvertTo(Affine) and validate_quantization_mode_runnable refuses such a plane at load by design (#1806). Only ModelOpt checkpoints are converted. The ROCm GatherQMM also has no input for the per-expert global scale, so this is a missing kernel, not a regression.

Fix: a new test helper, switch_layers::expect_native_nvfp4_load, returns the layer where NVFP4 is native. On other backends it asserts that the refusal names the plane and nvfp4, prints a skip that names the gap and #1806, and returns None, so the test stops before the parts that need the loaded layer. The parts that do not depend on the backend still run everywhere: sidecar stacking into inv_global_scale, the "left unstacked" refusal, and, in the gemma-unified test, the split's per-expert native scale, now also checked directly on the weight map. Other tests already branch on quant_mode_support(Nvfp4) the same way.

Group A, PhiMoE fused expert kernel: test reference fixed.

  • models::phimoe_tests::fused_expert_kernel_keeps_sparsemixer_scores

Cause: the test compared the fused kernel against the bf16 graph path with a 2% bound. With MLXCEL_FUSED_MOE_PARITY_CHECK=1 on gfx1151: fused vs all-f32 truth 0.17%, gather_qmm graph chain vs truth 2.2%, fused vs graph 2.2%. The fixture cancels hard in the down projection (|activation| about 176 against |output| about 12). Rounding the activation to bf16 alone moves the output 0.7%, and the bf16 gate/up/activation chain moves it several percent. A single quantized_matmul matches f32 to bf16 rounding (0.16%), so neither the ROCm kernel nor the fused kernel is wrong. The bf16 reference was simply on the bound.

Fix: the reference runs the graph path in f32 (x cast to f32, moe_weighted_sum to f32). A kernel that renormalized the sparsemixer scores (sum 1.5025) would still be 1.5x away and fail.

Verification (gfx1151, all GPU runs under scripts/rocm_gpu_guard.sh --hold)

  • mlxcel-core lib compiled_dtype_cast_tests: 7/7 pass (2 failed before). The broader compiled filter: 42/42 pass in mlxcel-core and 8 pass (1 ignored) in mlxcel, re-run after the review commit.
  • mlxcel lib models::switch_layers 57 pass (1 ignored, needs a checkpoint), models::phimoe 14/14, loading::vlm::gemma_unified 9/9, models::mistral4 2/2
  • cargo clippy --profile test-fast --features rocm -p mlxcel -p mlxcel-core --lib --tests -- -D warnings: clean. cargo fmt --all -- --check: clean
  • The steps of verify-rocm-overlay: rocm_overlay.py records OK (59 entries), rocm_overlay_test.sh OK, allocator_ownership_test.sh and rocm_runtime_dir_test.sh pass, rocm_overlay.py drift OK
  • The full make verify-rocm-held gate runs centrally before merge

Closes #2608

@inureyes inureyes added type:bug Bug fixes, error corrections, or issue resolutions priority:medium Medium priority status:review Under review status:done Completed and removed status:review Under review labels Oct 26, 2026
The hiprtc preamble of the ROCm compiled-kernel builder defined no complex64_t, although the shared codegen names complex64 values that way and emits complex64_t constants and casts. Every compiled graph that touched complex64 failed to build ("unknown type name 'complex64_t'"), so compiled real(z) and imag(z) threw where eager evaluation worked. Real, Imag and Conjugate were also identity or zero stubs written for real inputs.

The preamble now defines complex64_t with the hipFloatComplex layout, explicit conversions to and from real types, arithmetic and comparison operators, and complex64 overloads for Add, Subtract, Multiply, Divide, Square, Abs, Real, Imag and Conjugate. Float-promoting op templates without a complex overload assert on complex64, so an unsupported complex graph fails to build loudly instead of computing on the real part. Recorded as LOCAL_FIXES.md item 59.
Six tests loaded an MLX-native NVFP4 expert plane and failed on ROCm because the load is refused there by design: the capability table marks NVFP4 as ConvertTo(Affine) on ROCm, and only ModelOpt checkpoints are converted at load (#1806). A new expect_native_nvfp4_load test helper returns the layer where NVFP4 is native and, elsewhere, asserts that the refusal names the plane and the mode, prints the skip, and stops the test before the parts that need the loaded layer. The backend-independent assertions (sidecar stacking, the split's per-expert scale on the weight map) still run on every backend.

fused_expert_kernel_keeps_sparsemixer_scores compared the fused kernel against the bf16 graph path. The fixture cancels hard in the down projection, so the graph path's bf16 rounding of gate, up and the activation alone puts it about 2% from the all-f32 result, on the test's 2% bound and over it on ROCm (2.2%), while the fused kernel is within 0.2%. The reference now runs the graph path in f32; a renormalizing kernel would still be 1.5x away.
The complex64_t added to the hiprtc preamble converted to every real type through one templated explicit conversion that keeps the real part. For bool that differs from MLX CPU and CUDA, where a complex value is true when either part is nonzero, so a fused astype(z, bool) or logical_not(z) returned false for 0+1j. NaNEqual had no complex64 overload and its float template compared real parts only, so a compiled array_equal(..., equal_nan=True) ignored the imaginary part. Before #2608 both graphs failed to build; with the new type they returned wrong results silently.

complex64_t gains a non-template explicit operator bool, which overload resolution prefers over the template, and NaNEqual gains a complex64 overload with the MLX CUDA and Metal rule. LOCAL_FIXES.md item 59 records both.

Validated by compiling the extracted preamble device-only for gfx1151 with hipcc (real-typed overload results unchanged, unsupported ops still hit MLX_ROCM_REAL_ONLY) and a host build of the type and NaNEqual checking the bool and NaN cases. No GPU run.

Refs #2608
@inureyes
inureyes force-pushed the fix/issue-2608-rocm-test-failures branch from 4aeee28 to 5e803e5 Compare October 26, 2026 06:15
MLX's abs() builds an Abs node whose dtype stays complex64 and casts it to float32 afterwards, so the codegen declares the Abs result as complex64_t. The complex overload returned float, and the explicit complex64_t(float) constructor made every fused abs of a complex64 fail to JIT. It now returns complex64_t(|z|, 0), as MLX CUDA does, and LOCAL_FIXES item 59 says so.

A new compile probe graph (abs(z) * 2 + a) and compiled_abs_of_complex64_matches_eager cover it: on gfx1151 the test fails with the previous overlay and passes with this one, and the 43 mlxcel-core compiled tests pass.

Refs #2608
@inureyes

Copy link
Copy Markdown
Member Author

Pre-merge follow-up from the orchestrator review:

  • 61204c56: the complex64 Abs overload returned float, but MLX's Abs node keeps the complex64 dtype (an AsType to float32 follows), so every fused abs of a complex64 still failed to JIT. It now returns complex64_t(|z|, 0) as MLX CUDA does. New probe graph 11 and compiled_abs_of_complex64_matches_eager cover it: on gfx1151 the test fails with the previous overlay and passes with the fix, and all 43 mlxcel-core compiled tests pass. LOCAL_FIXES.md item 59 and the technical report are updated.
  • Rebased on main after fix: read LLAMA_ARG_* toggle pair variables after parsing #2612; verify-llama-compat, verify-env-var-docs, verify-kernel-port-dispatch, verify-rocm-overlay and cargo fmt --check pass. The full make verify-rocm-held gate runs on main after the merge.

@inureyes
inureyes marked this pull request as ready for review October 26, 2026 06:25
@inureyes
inureyes merged commit 1c520eb into main Oct 26, 2026
1 check passed
@inureyes
inureyes deleted the fix/issue-2608-rocm-test-failures branch October 26, 2026 06:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority:medium Medium priority status:done Completed type:bug Bug fixes, error corrections, or issue resolutions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: 9 tests fail under ROCm (gfx1151) in make verify-rocm

1 participant