Warn once when float32 ops silently run at TF32 precision#3883
Warn once when float32 ops silently run at TF32 precision#3883pierre427 wants to merge 3 commits into
Conversation
fp32 matmul-family ops default to TF32-class reduced precision (MLX_ENABLE_TF32=1) on both CUDA and Metal (M5 NAX), undocumented (ml-explore#3860). Add env::tf32_active_for_fp32(): same value as env::enable_tf32(), but prints a one-line warning, once per process, only when MLX_ENABLE_TF32 is unset. Explicitly setting =0 or =1 keeps it silent. Called exactly where an fp32 op engages the reduced-precision route, so matvec-only, non-fp32, and opted-out processes never print. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Running the sm_120 pass this week — source build on WSL2, results with numbers to follow. One design question I'd like to resolve before merge, since it's the item I most want hardware to answer: the Metal gates sit inside the NAX dispatch (shape-aware), so matvec never reaches the helper — consistent with your 0-warning row. The CUDA main gate is Same question likely applies to the cuDNN conv path: if an fp32-conv-only workload warns, the message text ("matmul-family ops") won't help the reader locate it. |
Review feedback on ml-explore#3883: the fixed "matmul-family ops" text is misleading when the convolution or attention path fires first. The helper now takes the op family from the call site (default "matmul") and the once-per-process message names whichever family actually engaged first: matmul, quantized matmul, attention, convolution, or grouped matmul. No behavioral change to when the warning fires. M5-verified: GEMM-first warns "float32 matmul ops", NAX-shape quantized-matmul-first warns "float32 quantized matmul ops", gemv and non-NAX qmm shapes stay silent, explicit MLX_ENABLE_TF32=0/1 stay silent; test_blas / test_quantized / test_fast_sdpa green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Both points are well taken. On the gemv/matvec asymmetry: you've read the structure exactly right — the Metal gates live inside the NAX dispatch, which is shape-aware, so matvec (and any shape that routes to a non-NAX kernel) never reaches the helper; that's where the 0-warning gemv row comes from. The CUDA gate at On the message text: agreed, and fixed now rather than waiting — |
|
Ran the CUDA half on real sm_120 hardware (RTX 5070 Laptop, WSL2 One asymmetry vs the M5 measurement: fp32 matvec fires the warning Minor observation for the discussion: under pytest's default capture |
…sitives On CUDA the once-per-process TF32 warning was emitted from dtype_to_compute_type(), which is dtype-only. An fp32 matvec whose layout misses the dedicated GEMV path falls through to cuBLAS and triggers the warning, even though cuBLASLt leaves the rank-1 shape numerically exact (rel err ~1e-7 measured on sm_120) -- a false positive, since Metal routes those shapes to a separate GEMV kernel and never reaches the gate. Move the warning into the CublasGemm constructor, where the output dims are already known (M_ = a_rows, N_ = b_cols, set by init_base), and only warn for a genuine matrix-matrix product (M_ > 1 && N_ > 1). dtype_to_compute_type() goes back to silent enable_tf32() for compute-type selection. The constructor is the single point every cuBLAS fp32 entry -- plain matmul, both AddMM constructors, and the GEMM-conv fallbacks -- funnels through, so warning coverage matches the previous gate while dropping the matvec noise.
|
Confirmed — thanks for the hardware run. The 9.7e-08 vs 2.9e-04 split is consistent with cuBLASLt not selecting a TF32 tensor-core kernel for that rank-1 shape while the compute descriptor still requests (The Metal half stays silent on matvec for a different reason than I first implied: vector shapes are routed to a dedicated Fix pushed ( To be precise about what the guard buys: CUDA is still compile-only from me, so would you mind re-checking on the 5070: (1) your scenario-2 matvec now warns 0×, (2) a GEMM-first workload still warns exactly once (family "matmul"), (3) an |
|
Re-ran the matrix on real sm_120 hardware against 57466a7
The shape gate resolves the matvec false positive from my earlier |
Addresses the diagnostic half of #3860.
float32 matmul-family ops default to TF32-class reduced precision (
MLX_ENABLE_TF32defaults to 1) on both backends on current hardware — CUDA tensor cores and the M5 Metal NAX route — and the default is undocumented. #3860 has two independent downstream reports of the resulting failure mode: op-level tests stay green while downstream results shift (near-tie argmax flips on sm_120; mlx-lm'stest_ssmstate comparison on M5), and the bisection points at hardware/environment rather than any commit. This repo's own test harness already pinsMLX_ENABLE_TF32=0(python/tests/mlx_tests.py) for the same reason.This adds
env::tf32_active_for_fp32(): returns exactlyenv::enable_tf32(), but prints a one-line warning to stderr, once per process, and only whenMLX_ENABLE_TF32is unset — anyone who set=1or=0explicitly has already made the choice and never sees it:It is called at exactly the points where a float32 op takes the reduced-precision route, by reordering the existing short-circuit
(env::enable_tf32() || dtype != float32)→(dtype != float32 || env::tf32_active_for_fp32()), so processes that never engage TF32 (matvec-only, non-fp32, opted out) never print:No behavior change beyond the log — every gate computes the same boolean as before. The
complex64cuBLAS case (alsoFAST_TF32today) is intentionally left untouched; probably worth folding into the docs follow-up discussed in #3860.Testing
On M5 Max (macOS 26.5), source build with NAX kernels active:
MLX_ENABLE_TF32=0, fp32 GEMMMLX_ENABLE_TF32=1, fp32 GEMMNumerics unchanged: default fp32 GEMM max rel err vs an fp64 reference is 8.3e-4 (TF32 signature), and 2.5e-7 with
MLX_ENABLE_TF32=0.test_blas.py(24 tests),test_quantized.py(32), andtest_fast_sdpa.py(16) all pass.The CUDA side compiles the same helper but I have no NVIDIA hardware to run it — @stoyoda0012-cyber offered an sm_120 review in #3860.
🤖 Generated with Claude Code