Skip to content

[NVFP4 4over6] Add the dense four-over-six linear converter - #13

Draft
wolfcomos wants to merge 1 commit into
mainfrom
4over6/titan1-dense
Draft

[NVFP4 4over6] Add the dense four-over-six linear converter#13
wolfcomos wants to merge 1 commit into
mainfrom
4over6/titan1-dense

Conversation

@wolfcomos

@wolfcomos wolfcomos commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Stack (emulated ghstack, oldest at bottom):

Companion torchao PR providing torchao.prototype.moe_training.nvfp4_training.four_over_six: wolfcomos/ao#12.

Summary

Adds NVFP4FourOverSixLinear and a recipe selector on the existing NVFP4LinearConverter, wiring TorchAO's NVFP4 four-over-six training prototype (torchao.prototype.moe_training.nvfp4_training.four_over_six) into the quantization converter registry. Four-over-six encodes every 16-value FP4 block twice — the standard map-to-6 block scale and a 1.5x-expanded map-to-4 scale — and stores the candidate with the lower dequantization error. The recipe uses no random Hadamard transform and no stochastic rounding; it targets RL and post-training, where TransformerEngine ships the same recipe (NVTE_NVFP4_4OVER6=1).

Also included: _validate_four_over_six_knobs (config-time knob validation shared with the follow-up grouped-experts converter), the llama3_debugmodel_nvfp4_four_over_six debug recipe, a "Four-over-six Recipe" subsection in torchtitan/components/quantization/nvfp4.md, and CPU unit tests.

Design notes

  • Single NVFP4LinearConverter with a recipe selector, per review feedback (an earlier revision shipped a sibling NVFP4FourOverSixLinearConverter class): callers pick a quantization recipe, not a converter class, so the converter takes recipe="default" (torchao's stock NVFP4 recipe: random Hadamard transform + stochastic rounding) or recipe="four_over_six". The module implementations stay separate: the default recipe's NVFP4Linear hard-inherits torchao's stateful RHT/SR module (it registers sr_seed / rht_sign_vector buffers and shards _sr_seed), while NVFP4FourOverSixLinear stays stateless (no RHT, no SR). The five four-over-six knobs live on the consolidated Config and are read only under recipe="four_over_six"; a non-default value under recipe="default" is rejected at converter build with one ValueError naming every offending knob, rather than being silently ignored.
  • Knobs validated at config build, per converter convention: _validate_four_over_six_knobs checks all five knobs, including err_mode and e4m3_scale_bound, so a bad recipe fails when the config tree is built rather than on the first forward; the converter's __init__ also fail-fasts under recipe="four_over_six", so a bad recipe errors even when no Linear matches the fqns. In particular backward_override="quantized" + row_scaled_activation=True is rejected: a row-scaled four-over-six tensor has no columnwise form for the quantized wgrad operand.
  • TP status: like MXFP8Linear, this is a pure leaf swap; TP wiring for the four-over-six linear is not included (the class docstring says so).
  • err_mode defaults to "mae", matching TransformerEngine's library default (NVTE_NVFP4_4OVER6_ERR_MODE="MAE"); recipes mirroring the miles RL setup set err_mode="mse" explicitly.

Numerics evidence

Numerics live in the companion torchao PRs; parity results are maintained in a local TE harness (not in-tree): pure-torch quantizer 384/384 bitwise vs TE 2.17 kernels; CuTe DSL kernel 1920/1920 bitwise vs the torch oracle plus 12/12 vs TE; re-verified 2026-08-24 vs TE main.

Test plan

CPU-only (container without --gpus; requires a torchao build providing the four-over-six prototype on PYTHONPATH, otherwise the new tests skip):

python -m pytest tests/unit_tests/cpu/test_quantization.py -q
33 passed, 14 warnings in 12.73s

Import checks of every touched module pass (torchtitan.components.quantization{,.nvfp4,.utils}, torchtitan.models.llama3.config_registry). Lint: ufmt (black 22.12.0 / usort 1.0.5), flake8 with the repo plugin pins, pydoclint, and codespell all clean on the changed files.

GPU results come from the assembled stack tip on GB200 (RL-parity phases A-D2 plus a 6-arm Qwen3 recipe ablation), since CI has no SM100 runner for the four-over-six kernels.

Draft on the fork to stage the upstream submission; supersedes the exploratory stack (#8)

Review pass (2026-08-30)

  • _validate_four_over_six_knobs now validates err_mode and e4m3_scale_bound (config-build time), with matching test cases; the grouped-only parameter moved up the stack to the grouped-converter PR where it is used.
  • Converter __init__ fail-fasts on invalid four-over-six knobs even when fqns match nothing.
  • has_quantization now counts NVFP4FourOverSixLinear.Config, with a dense-converter test (moved into this PR from the grouped one, where it belonged by scope).
  • The four_over_six ImportError and nvfp4.md now name the actual module (nvfp4_training.four_over_six); md em-dash consistency fix.
  • CPU suite re-run in the vLLM 26.08 container: 34 passed / 0 failed.

Add NVFP4FourOverSixLinear and a recipe selector on NVFP4LinearConverter,
wiring TorchAO's four-over-six training prototype
(torchao.prototype.moe_training.nvfp4_training.four_over_six) into the
existing converter. Four-over-six encodes every FP4 block twice (the
standard map-to-6 scale and a 1.5x-expanded map-to-4 scale) and keeps the
candidate with the lower dequantization error; no RHT and no stochastic
rounding, targeting RL and post-training.

Callers pick a quantization recipe, not a converter class, so the recipe
lives on NVFP4LinearConverter: recipe="default" keeps torchao's NVFP4
recipe (random Hadamard transform + stochastic rounding, stateful per-rank
buffers) and recipe="four_over_six" selects the stateless adaptive
block-scale recipe. The two module implementations stay separate --
the upstream NVFP4Linear hard-inherits torchao's stateful RHT/SR module
(sr_seed / rht_sign_vector buffers), which four-over-six rejects. The
five four-over-six knobs (err_mode, e4m3_scale_bound,
row_scaled_activation, backward_override, weight_block) are read only
under recipe="four_over_six"; a non-default value under recipe="default"
raises one ValueError naming every offending knob, so a mis-paired
recipe fails at converter build instead of being silently ignored.

Knob combinations are validated when the config tree is built via
_validate_four_over_six_knobs, per converter convention, so a bad recipe
fails at config time instead of on the first forward.

Also add the llama3_debugmodel_nvfp4_four_over_six debug recipe, a
Four-over-six subsection in the NVFP4 doc, and CPU unit tests for knob
validation, converter targeting, and the recipe-surface rejections.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@wolfcomos
wolfcomos force-pushed the 4over6/titan1-dense branch from 7652f2d to 75ecab9 Compare August 31, 2026 02:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant