Skip to content

Commit 78b7b31

Browse files
committed
feat(nvfp4): REAL RtN backward on gb10/sm_121 (port nanochat) — gated, enabled
Reopens the 'backward datacenter-only / can't be done' conclusion (commit b6a6177), which was correct ONLY for the DEFAULT RHT+SR recipe and too broad. The REDUCED round-to-nearest recipe NVFP4BlockScaling(disable_rht=True, disable_stochastic_rounding=True) — ported from nanochat — RUNS a full FP4 backward (dgrad+wgrad) on gb10/sm_121, reusing the same working forward cuBLASLt FP4 GEMM and avoiding both the datacenter-only SR cvt (cvt.rs.satfinite.e2m1x4) and the RHT fused kernel. MEASURED on gb10 (TE 2.16.0.dev0+a46079cb): fwd rel_err 0.1472, dgrad 0.1465, wgrad 0.1343 vs a pure-bf16 te.Linear reference — finite, deterministic (SR off), reproducible across runs/shapes. API delta from nanochat: this TE build has NO override_linear_precision and NO disable_sr alias; the equivalent levers are disable_rht / disable_stochastic_rounding / backward_override in {None,high_precision, dequantized}. The bare disable_rht+disable_stochastic_rounding recipe runs BOTH dgrad and wgrad in FP4 RtN (so wgrad-BF16 split is unneeded on this build). build_nvfp4_rtn_recipe() tries this-build kwargs first, then nanochat's, with graceful per-kwarg fallback. RULE #1: ENABLED only behind a NUMERIC gate. New op backward_nvfp4_rtn_dgrad_fp4_wgrad_fp4_te_cublaslt_cuda_sm121 in NVFP4_SUPPORTED_OPS is usable only when nvfp4_te_backward_rtn_probe() runs the real FP4 backward on the live device and dgrad rel_err is finite and < 0.35 (NVFP4_RTN_BACKWARD_DGRAD_REL_ERR_TOL). On raise/NaN/garbage/over-tolerance it RAISES Nvfp4CudaKernelMiscompiled — no silent bf16 masquerade. The DEFAULT RHT+SR backward stays fail-loud (genuinely can't run on sm_12x). Tests (tests/test_nvfp4_route.py): added test_cuda_reduced_rtn_backward_runs_and_matches_bf16 and test_cuda_reduced_rtn_backward_capability_gate_true_on_gb10 (CUDA, skip on Mac); the default-recipe fail-loud test now runs its irrecoverable RHT-crash in an ISOLATED SUBPROCESS so it cannot poison the in-process RtN test's CUDA context. gb10: 11 passed, 1 skipped. Mac: 7 passed, 5 skipped. Doc: docs/NVFP4-TRAINING-KERNELS.md §3b split into §3b-i (default BROKEN) and §3b-ii (reduced RtN WORKS, measured), supported-ops table + summary updated. Honest caveat kept: proves runs+matches-bf16 (~0.15 rel), not full-convergence accuracy parity (nanochat loss-descent is the integration receipt).
1 parent 191a8dd commit 78b7b31

3 files changed

Lines changed: 615 additions & 88 deletions

File tree

docs/NVFP4-TRAINING-KERNELS.md

Lines changed: 96 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,16 @@ forward_nvfp4_gemm_te_cublaslt_cuda_sm121: rel_rmse_vs_bf16 = 0.147, finite=True
106106
0.147 is the expected FP4 forward error (no RHT). This is a REAL nvfp4 op on
107107
gb10 and we wire/test it (§5, §6).
108108

109-
### 3b. BACKWARD NVFP4 GEMM — BROKEN on gb10 (root cause identified)
109+
### 3b. BACKWARD NVFP4 GEMM — DEFAULT recipe BROKEN on gb10; REDUCED RtN recipe WORKS
110+
111+
There are **two** backward recipes with **different** status on gb10. The
112+
**default** recipe (RHT + SR) is genuinely datacenter-only and stays fail-loud
113+
(§3b-i). The **reduced round-to-nearest** recipe (RHT off + SR off, ported from
114+
nanochat) **runs and matches bf16 within ~0.147 rel-err** (§3b-ii). The earlier
115+
"backward genuinely blocked / can't be done" verdict (commit b6a6177) was correct
116+
**only** for the default recipe and was too broad.
117+
118+
#### 3b-i. DEFAULT recipe (RHT + SR) — BROKEN on gb10 (root cause identified)
110119

111120
Running the **default production recipe** (RHT on fwd-inp + bwd, SR on bwd)
112121
backward on gb10 raises a hard CUDA error from the Random-Hadamard fused quant
@@ -199,6 +208,60 @@ mis-execution. Our probe defeats this by (a) using a fresh module for the
199208
default-recipe backward and (b) treating the arch-specific assertion as a hard
200209
RAISE. We never accept the masked gradients.
201210
211+
#### 3b-ii. REDUCED RtN recipe (RHT off + SR off) — WORKS on gb10 (real, measured)
212+
213+
The **reduced round-to-nearest** recipe — ported from nanochat
214+
(`CHANGELOG_GB10.md:240–268`, `gpt.py:951–961`; loss 11.09→6.58 over 22 GB10
215+
steps) — disables both RHT and stochastic rounding:
216+
217+
```python
218+
from transformer_engine.common.recipe import NVFP4BlockScaling
219+
recipe = NVFP4BlockScaling(disable_rht=True, disable_stochastic_rounding=True)
220+
```
221+
222+
With both off, TE's `fp4_quant_bwd_grad` degrades to plain RtN E2M1 + E4M3
223+
16-element block scale — the **same** cuBLASLt `CUDA_R_4F_E2M1 × UE4M3` path the
224+
forward (§3a) already proves works. It never touches the SR cvt
225+
(`cvt.rs.satfinite.e2m1x4`) or the RHT fused kernel, so neither §3b-i blocker is
226+
reached. **Measured on gb10 (sm_121, TE 2.16.0.dev0+a46079cb, 2026-06-01)** with
227+
a fresh module + `CUDA_LAUNCH_BLOCKING=1`, vs a pure-bf16 `te.Linear` reference:
228+
229+
```
230+
recipe = NVFP4BlockScaling(disable_rht=True, disable_stochastic_rounding=True)
231+
fwd rel_err = 0.1472 (finite)
232+
dgrad rel_err = 0.1465 (finite) # input-grad, FP4 RtN
233+
wgrad rel_err = 0.1343 (finite) # weight-grad, FP4 RtN
234+
deterministic & reproducible across runs (SR off → no stochasticity);
235+
holds at M,K,N=512,1024,512 (dgrad 0.1465 / wgrad 0.1346).
236+
```
237+
238+
**API delta from nanochat (load-bearing):** nanochat's TE accepted
239+
`override_linear_precision=(False,False,True)` (force wgrad→BF16) and a
240+
`disable_sr` alias. **This** TE build (2.16.0.dev0+a46079cb) has **neither**
241+
kwarg — it exposes `disable_rht`, `disable_stochastic_rounding`,
242+
`disable_2d_quantization`, and `backward_override ∈ {None,'high_precision',
243+
'dequantized'}`. The bare `disable_rht + disable_stochastic_rounding` recipe runs
244+
**both** dgrad and wgrad in FP4 RtN on this build (so we do **not** need the
245+
wgrad-BF16 split that nanochat used; `backward_override='high_precision'` would
246+
force the whole backward to BF16 — measured dgrad/wgrad = 0.0 — if a future TE
247+
regresses wgrad). The `build_nvfp4_rtn_recipe()` helper tries this-build kwargs
248+
first, then nanochat's, with graceful per-kwarg fallback.
249+
250+
**ENABLED behind a numeric gate (RULE #1).** The reduced RtN backward is a
251+
SUPPORTED op (`backward_nvfp4_rtn_dgrad_fp4_wgrad_fp4_te_cublaslt_cuda_sm121`)
252+
**only when** `nvfp4_te_backward_rtn_probe()` runs the real FP4 backward on the
253+
live device and dgrad rel-err is finite and `< 0.35`
254+
(`NVFP4_RTN_BACKWARD_DGRAD_REL_ERR_TOL`). If the kernel raises, produces
255+
NaN/garbage, or exceeds the bound, the probe RAISES `Nvfp4CudaKernelMiscompiled`
256+
and the route stays fail-loud — no silent bf16 masquerade.
257+
258+
**Honesty caveat (RULE #1).** This proves "runs + matches bf16 within ~0.15
259+
rel-err" — the same ballpark as the forward. It does **not** prove full
260+
convergence accuracy parity over a long training run; nanochat's loss-descent
261+
receipt is the integration evidence that it trains. wgrad here is real FP4 (not
262+
the BF16 fallback nanochat used), so the accuracy is at least as good as
263+
nanochat's on this build.
264+
202265
### 3c. MXFP8 — also unavailable on gb10 (for completeness)
203266

204267
`check_mxfp8_support()` → `(False, 'MXFP8 (for all gemm layouts) is not supported
@@ -240,13 +303,14 @@ metadata; the real training step RAISES the precise blocker.
240303
|----|---------|--------|--------|
241304
| `forward_gemm_operands_e2m1_block_scale_metal_m4` | Metal/M4 | `cppmega_mlx.nn._tilelang.mxfp4_matmul_path_c` over `quantize_mxfp4_blockwise` | real (M4); exercised by `nvfp4_gemm_smoke` |
242305
| `forward_nvfp4_gemm_te_cublaslt_cuda_sm121` | CUDA/gb10 | TE `NVFP4BlockScaling` + cuBLASLt FP4 | **VERIFIED on gb10, rel_err 0.147**; exercised by `nvfp4_te_gemm_probe(run_backward=False)` |
306+
| `backward_nvfp4_rtn_dgrad_fp4_wgrad_fp4_te_cublaslt_cuda_sm121` | CUDA/gb10 | TE `NVFP4BlockScaling(disable_rht=True, disable_stochastic_rounding=True)` + cuBLASLt FP4 (RtN, no SR/RHT) | **VERIFIED on gb10: dgrad rel_err 0.147, wgrad 0.134** (§3b-ii); ported from nanochat; GATED at runtime by `nvfp4_te_backward_rtn_probe()` (dgrad `< 0.35`); exercised by `test_cuda_reduced_rtn_backward_*` |
243307

244308
**FAIL-LOUD ops (`NVFP4_UNSUPPORTED_TRAINING_OPS`)** — the route RAISES naming
245309
each; it does NOT bf16-fallback:
246310

247311
| Op | Why | Enablement |
248312
|----|-----|-----------|
249-
| `nvfp4_gemm_backward_vjp_te_cuda_sm121` | **Datacenter-only, NOT a build gap.** SR FP4 cvt (`cvt.rs.satfinite.e2m1x4`) is TE-source-gated to `sm_100a`/`sm_103a`; RHT kernel needs SM100 TMEM/`tcgen05` which sm_12x does not implement. nvcc 13.x has no sm_12x `a`/`f` target → a TE rebuild **cannot** fix it (see §3b) | **UPSTREAM ONLY**: needs NVIDIA to ship SM12x-native (non-`tcgen05`) FP4 SR + RHT backward kernels in a future TE/cuBLASLt release |
313+
| `nvfp4_gemm_backward_vjp_te_cuda_sm121` (**DEFAULT recipe** only — RHT+SR ON) | **Datacenter-only, NOT a build gap.** SR FP4 cvt (`cvt.rs.satfinite.e2m1x4`) is TE-source-gated to `sm_100a`/`sm_103a`; RHT kernel needs SM100 TMEM/`tcgen05` which sm_12x does not implement. nvcc 13.x has no sm_12x `a`/`f` target → a TE rebuild **cannot** fix it (see §3b-i). **The REDUCED RtN recipe is the escape — see the supported-ops table above.** | **UPSTREAM ONLY**: needs NVIDIA to ship SM12x-native (non-`tcgen05`) FP4 SR + RHT backward kernels in a future TE/cuBLASLt release |
250314
| `cuda_blackwell_nvfp4_gemm_metal_only` | The Metal fwd GEMM is Metal-only; it RAISES on CUDA | Use the TE/cuBLASLt CUDA forward path above |
251315
| `gemm_backward_vjp` | No nvfp4 grad kernel for the Metal e2m1 GEMM | Implement an nvfp4 VJP for `mxfp4_matmul_path_c` |
252316
| `optimizer_state_and_update` | AdamW moments are fp32-only; no nvfp4 optimizer state | Implement nvfp4 optimizer-state kernels |
@@ -263,43 +327,60 @@ For a full training step TODAY use `--dtype fp8_path_c` or `--dtype bfloat16`.
263327
* `_run_existing_training` emits a `blocked_receipt(... NVFP4_E2E_TRAINING_
264328
BLOCKER_TYPE)` with the precise reason.
265329
* `nvfp4_te_gemm_probe(run_backward=True)` RAISES
266-
`Nvfp4CudaKernelMiscompiled` on the broken gb10 backward (arch-specific PTX),
267-
rather than returning the masked/garbage gradients TE would otherwise hand
268-
back. No silent fallback exists anywhere in the route.
330+
`Nvfp4CudaKernelMiscompiled` on the broken gb10 **default-recipe** backward
331+
(arch-specific PTX), rather than returning the masked/garbage gradients TE
332+
would otherwise hand back. The **reduced RtN** backward
333+
(`nvfp4_te_backward_rtn_probe()`) is gated on a numeric dgrad rel-err `< 0.35`
334+
and RAISES the same `Nvfp4CudaKernelMiscompiled` if it ever mis-executes —
335+
it is enabled only when the gate passes. No silent fallback exists anywhere.
269336

270337
---
271338

272339
## 6. The test we added and its results
273340

274-
Test: `tests/test_nvfp4_route.py` (10 tests). Backend-aware: always-on metadata
341+
Test: `tests/test_nvfp4_route.py` (12 tests). Backend-aware: always-on metadata
275342
+ fail-loud assertions; Metal-only assertions skip when MLX/Metal absent; CUDA
276343
assertions run when torch+TE+CUDA+NVFP4 present.
277344

278345
* `test_route_constants_well_formed`, `test_route_requested_predicate`,
279346
`test_payload_advertises_both_forward_backends_and_no_e2e`,
280347
`test_reason_message_is_actionable`,
281348
`test_capability_probe_never_crashes_without_torch` — route metadata + probe.
349+
Asserts the reduced RtN backward op is in `NVFP4_SUPPORTED_OPS` (and not
350+
contradictorily in the unsupported list) and that the payload advertises the
351+
working RtN recipe + nanochat provenance.
282352
* `test_fail_loud_guard_raises_for_nvfp4_and_names_missing_ops`,
283353
`test_fail_loud_guard_is_noop_for_other_dtypes` — RULE #1 gate.
284354
* `test_metal_forward_nvfp4_gemm_matches_bf16` — real Metal fwd GEMM < 0.5 rel.
285355
* `test_cuda_forward_nvfp4_gemm_matches_bf16`**real gb10 NVFP4 fwd GEMM
286356
< 0.3 rel** (measured 0.147).
287-
* `test_cuda_backward_nvfp4_gemm_fails_loud_datacenter_only` — the broken gb10
288-
backward RAISES `Nvfp4CudaKernelMiscompiled` naming the REAL, evidence-backed
289-
blocker (SR cvt source-gated to `sm_100a`/`sm_103a`; RHT needs SM100
290-
TMEM/`tcgen05` absent on sm_12x). The fail-loud is **permanent** on sm_12x
291-
(no `NVFP4_BACKWARD_FIXED` escape hatch — a rebuild cannot fix it; §3b).
357+
* `test_cuda_reduced_rtn_backward_runs_and_matches_bf16`**the reduced RtN
358+
backward RUNS on gb10 and matches bf16** (dgrad/wgrad rel_err `< 0.35`;
359+
measured 0.147 / 0.134). The numeric gate.
360+
* `test_cuda_reduced_rtn_backward_capability_gate_true_on_gb10`
361+
`nvfp4_backward_rtn_supported()` returns True on gb10.
362+
* `test_cuda_default_recipe_backward_nvfp4_gemm_fails_loud_datacenter_only` — the
363+
broken gb10 **DEFAULT-recipe** backward RAISES `Nvfp4CudaKernelMiscompiled`
364+
naming the REAL, evidence-backed blocker (SR cvt source-gated to
365+
`sm_100a`/`sm_103a`; RHT needs SM100 TMEM/`tcgen05` absent on sm_12x). The
366+
default-recipe fail-loud is **permanent** on sm_12x (a rebuild cannot fix it;
367+
§3b-i) — the reduced RtN recipe is the escape (§3b-ii).
292368

293369
**Results:**
294370

295-
* Mac (this checkout): `7 passed, 3 skipped` — Metal skipped because the
371+
* Mac (this checkout): `7 passed, 5 skipped` — Metal skipped because the
296372
in-tree tilelang dev build (`merge/upstream-codegen-reorg`) has a tvm_ffi
297373
circular-import (pre-existing infra gap, unrelated to nvfp4); CUDA skipped (no
298374
CUDA on Mac). The Metal assertion is gated by a real tiny-GEMM smoke so it
299375
skips cleanly on a broken-tilelang host and runs where tilelang is healthy.
300-
* gb10 (`/home/dave/cppmega-venv`): **`9 passed, 1 skipped`** — Metal skipped (no
301-
Apple GPU); the CUDA forward test PASSES (real nvfp4 GEMM, 0.147 rel) and the
302-
CUDA backward fail-loud test PASSES (broken backward RAISES as designed).
376+
* gb10 (`/home/dave/cppmega-venv`, TE 2.16.0.dev0+a46079cb, sm_121,
377+
2026-06-01): **`11 passed, 1 skipped`** — Metal skipped (no Apple GPU). The
378+
CUDA forward test PASSES (real nvfp4 GEMM, 0.147 rel); the **reduced RtN
379+
backward** test PASSES (`dgrad 0.1465 / wgrad 0.1343`, gate True); the
380+
capability-gate test PASSES; and the **default-recipe** backward fail-loud test
381+
PASSES (broken default backward RAISES as designed, run in an isolated
382+
subprocess so its irrecoverable RHT-kernel context-corruption cannot poison the
383+
in-process RtN test).
303384

304385
Reproduce on gb10:
305386

0 commit comments

Comments
 (0)