Skip to content

Add MXFP4 training support via a dequantize-on-the-fly fallback - #47464

Closed
rexminnis wants to merge 2 commits into
huggingface:mainfrom
rexminnis:mxfp4-training-dequant-fallback
Closed

Add MXFP4 training support via a dequantize-on-the-fly fallback#47464
rexminnis wants to merge 2 commits into
huggingface:mainfrom
rexminnis:mxfp4-training-dequant-fallback

Conversation

@rexminnis

@rexminnis rexminnis commented Jul 21, 2026

Copy link
Copy Markdown

CI

Add MXFP4 training support via a dequantize-on-the-fly fallback

Fixes #40170 (partially — see "Relation to backward kernels" below), closes the gap behind #40236.

What this does

Enables LoRA/adapter fine-tuning of MXFP4-quantized models (gpt-oss-20b/120b) with the quantized weights kept packed, on hardware where the dequantized-bf16 recipe cannot fit:

  • Mxfp4HfQuantizer.is_trainableTrue (with a warning_once explaining the fallback + recommending gradient checkpointing).
  • mlp_forward gains a grad-aware branch: when torch.is_grad_enabled() and gradients are required, the MoE block runs the eager reference math (top-k router + GptOssExperts-style expert loop) over weights dequantized on the fly; otherwise the existing triton kernel path runs unchanged. Inference performance is untouched.
  • Mxfp4GptOssExperts gains _dequantize_weights() (layout-safe: convert_layout(..., StridedLayout) before upcast_from_mxfp_torch, per-expert chunking to bound the fp32 padding intermediates) and forward_dequantized() (the autograd-visible expert forward, mirroring the eager GptOssExperts.forward math).

The packed MXFP4 storage is never modified. The dequantized tensors are transients owned by the autograd graph; with gradient checkpointing enabled, at most ~one decoder layer's worth (~1.7 GB bf16 for gpt-oss-20b) is alive at a time.

Why (the silent-gradient trap)

The triton MXFP4 kernels implement forward only. If a user lifts the is_trainable gate today (one-line patch, circulating in gists), training appears to work — losses go down, grad norms look healthy — but the expert outputs have grad_fn=None, so gradients flow through residual connections alone. Every LoRA trained that way learns from biased gradients with no error or warning. We verified this empirically before writing this PR:

EXPERT model.layers.0.mlp.experts {'out_grad_fn': None, 'out_requires_grad': False, 'in_requires_grad': True}
VERDICT: DISCONNECTED — residual-only gradients

This PR replaces that trap with a correct (if slower) path.

Validation (gpt-oss-20b, single 24 GB NVIDIA L4, SM89)

The exact code in this PR (injected over a transformers 5.14.1 install) was validated end to end, not a prototype variant:

  • Numerical equivalence: patched eager-dequant MLP output vs the triton kernel forward on the same input: cosine similarity 0.999992, relative error 0.4% (bf16 dequant math vs kernel-internal upcasting).
  • Gradient connectivity: expert outputs carry grad_fn; gradient reaches the embedding output (norm ≈ 2.86) with all base parameters frozen.
  • End-to-end LoRA fine-tune (axolotl, r=8 on q/k/v/o, seq 256, gradient checkpointing): 5/5 steps, grad_norm 13–44, peak 14.9 GiB (13 GB packed weights + transients), ~7.4 s/step at micro-batch 1.
  • Adapter round-trip: saved adapter (lora_B abs-sum 702.7 ≠ 0) loads onto the quantized base via PeftModel.from_pretrained, greedy generation visibly shifts vs base; adapter also serves via vLLM (merged into checkpoint, and dynamically on vLLM ≥ 0.25.1).

Hardware note: validated on SM89 where the triton weight layout is already StridedLayout; _dequantize_weights calls convert_layout(..., StridedLayout) explicitly so swizzled layouts (SM90+) should round-trip, but we could not validate on Hopper/Blackwell — reviewer confirmation welcome.

What this unlocks for users

  • gpt-oss-20b fine-tuning on 24 GB cards (4090/L4/Colab-class) instead of 80 GB; gpt-oss-120b (~60 GB packed) plausibly on a single 80 GB device instead of multi-node bf16 (~240 GB).
  • Training against the official checkpoint bytes — no third-party requantized copies, no 42 GB dequantized saves; adapters come out vLLM-serving-compatible.
  • Axolotl/TRL/PEFT recipes work without custom launchers or monkey-patches.

Relation to the backward triton kernels (#40170) and to #40180

This is deliberately a pure-Python fallback, not the fused MXFP4 backward kernels the issue ultimately wants: ~2–4× slower per step than a fused implementation would be, since the backward recomputes dequantization. It is intended to (a) make training correct and possible now, and (b) serve as the numerical reference implementation to validate the real backward kernels against when they land — at which point the grad-aware branch can dispatch to them instead.

To be explicit about scope, in the terms raised on #40180 ("isn't this MXFP4 weight storage and computation in a fallback dtype?"): yes — by design, and that split is the contract. The storage half is where the memory win lives and it is fully preserved — the packed weights are never materialized in bf16 (that's the 14.9 GiB peak above, vs ~42 GB for dequantized-bf16 weights alone). The compute half is an eager fallback that is correct, which is sufficient for the LoRA/adapter use case: the frozen base weights never need grad_weight — gradients only need to flow through the expert math to the adapters, which the autograd-visible dequant path provides. Native MXFP4 compute remains #40170's fused kernels; #40180 was deferred pending those kernels, and this PR intentionally does not presume them — it is the reference they can be validated against.

Tests

  • test_mxfp4_expert_output_requires_grad — quantized load, forward under grad; asserts expert output has grad_fn and backward reaches inputs (GPU + triton kernels required).
  • test_mxfp4_dequant_forward_matches_kernel — cosine similarity ≥ 0.999 between the two paths on random inputs.

The triton MXFP4 kernels are forward-only, so lifting the is_trainable
gate alone trains on silently biased residual-only gradients (expert
outputs are autograd constants). Instead, mlp_forward now branches when
gradients are required: the eager reference math (top-k router + expert
loop) runs over weights dequantized on the fly per expert, keeping the
packed MXFP4 storage untouched and the kernel path for inference.

With gradient checkpointing the dequantized transients are bounded to
~one decoder layer, which puts gpt-oss-20b LoRA fine-tuning at ~15GB
peak on a single 24GB GPU (validated end to end on an L4: cos=0.999992
vs kernel forward, true gradient flow, 5-step LoRA run + adapter
round-trip through PEFT and vLLM serving).
@rexminnis

Copy link
Copy Markdown
Author

cc @Rocketknight1 @salmanmohammadi — you both reviewed #40180 (native MXFP4 training, deferred pending backward kernels). This PR takes the complementary route: weights stay packed MXFP4 (15 GB peak for gpt-oss-20b LoRA on a single 24 GB L4, vs 42 GB for bf16 weights alone), and the backward runs eager reference math over per-expert on-the-fly dequantization — so gradients through the expert path are real (cosine 0.999992 vs the Triton forward), avoiding the silent-gradient issue. Scoped to LoRA/adapter training, where the frozen base never needs grad_weight; it's intended as the eager reference implementation for validating the eventual #40170 backward kernels rather than a substitute for them. Review would be much appreciated.

@github-actions

Copy link
Copy Markdown
Contributor

[For maintainers] Suggested jobs to run (before merge)

run-slow: mxfp4

@github-actions

Copy link
Copy Markdown
Contributor

CI recap

Dashboard: View test results in Grafana
Latest run: 30191479162:2
Result: success | Jobs: 16 | Tests: 169,042 | Failures: 0 | Duration: 16h 14m

@Rocketknight1

Copy link
Copy Markdown
Member

Sorry, no code agent PRs from first-time contributors!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add MXFP4 MoE/attention backward kernels

2 participants