Skip to content

NVFP4 RL - #1261

Merged
yueming-yuan merged 40 commits into
radixark:mainfrom
zianglih:fp4
Jul 24, 2026
Merged

NVFP4 RL#1261
yueming-yuan merged 40 commits into
radixark:mainfrom
zianglih:fp4

Conversation

@zianglih

@zianglih zianglih commented May 29, 2026

Copy link
Copy Markdown
Contributor

@HumansAnd

Blog post: https://humansand.ai/blog/nvfp4-rl

Summary

This PR adds NVFP4 rollout/train support for Blackwell Miles with TE-compatible NVFP4 checkpoint conversion, Megatron-to-HF weight export, Qwen3 launch-script wiring, and GLM5.2 e2e coverage.

The current production quantization path is TE-direct rather than a FlashInfer/reference wrapper: Miles constructs transformer_engine.pytorch.tensor.nvfp4_tensor.NVFP4Quantizer in rowwise-only mode and extracts the rowwise NVFP4 payload fields needed by ModelOpt/SGLang (weight, weight_scale, weight_scale_2). FlashInfer remains the serving backend path via SGLang TRTLLM routed MoE, and Miles forwards relevant FLASHINFER_* and NVTE_* env vars so precision behavior is controlled by the backend libraries rather than new Miles CLI flags.

Implementation

  • Adds a shared TE-backed NVFP4 quantization/export path for HF checkpoint conversion and Megatron-to-HF live weight update.
  • Keeps the scope focused on MoE expert weights, while preserving BF16 carve-outs for configured first/last layers and extra high-precision layer patterns.
  • Handles gated MoE pairs as one quantization unit so gate/up weights share scale semantics, with a storage-aware fast path that avoids an extra full-size copy when possible.
  • Adds Qwen3 rollout/train NVFP4 launch wiring, backend precision env forwarding for NVTE_* / FLASHINFER_*, and disabled-by-default GLM5.2 Blackwell e2e coverage.

Validation

  • Fast-gpu test after syncing this branch:

    • python3 -m pytest tests/fast-gpu/test_nvfp4_quantizer.py::test_nvfp4_quantize_params_omits_static_input_scale -q
    • Result: 1 passed, 25 warnings in 4.48s
  • Qwen3-30B-A3B B200 NVFP4 after syncing this branch:

    • Step 1 quality sanity: train/train_rollout_logprob_abs_diff=0.030812, train/kl_loss=0.007849
  • Earlier branch validation before the latest cleanup/rebase series:

    • NVFP4 quantizer bitwise tests were expanded to cover single tensors and paired gate/up tensors across precision-env variants.
    • GLM NVFP4 e2e coverage was brought up on B200 before being moved to the current GLM5.2 test file.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces comprehensive support for NVFP4 quantization, including utility functions for environment synchronization, integration with FlashInfer and TransformerEngine, and updates to conversion tools, run scripts, and end-to-end tests. The review feedback highlights two important improvements in miles/utils/nvfp4.py: first, adding a check to fall back to the TransformerEngine reference implementation when weights are on the CPU to prevent FlashInfer from crashing; second, moving the environment synchronization call inside the try block of the context manager to ensure that environment variables are properly restored even if synchronization fails.

Comment thread miles/utils/nvfp4.py Outdated
Comment thread miles/utils/nvfp4.py Outdated
@zianglih

Copy link
Copy Markdown
Contributor Author

Root cause update for the NVFP4 4over6 bitwise mismatch:

The mismatch is not coming from the Miles quantizer plumbing. Miles is comparing FlashInfer NVFP4 quantization against the TE/FourOverSix-style reference. TE and FourOverSix compute the 4over6 candidate error by dequantizing with the original tensor amax expression, effectively:

e2m1_value * e4m3_scale * amax / (E2M1_MAX * E4M3_MAX_FOUROVERSIX)

The current FlashInfer installation computes the same error-domain quantity through the global decode scale path instead. That is algebraically equivalent, but not bitwise identical. On near-tie blocks this changes the strict 4-vs-6 candidate selection, so the packed NVFP4 values and scale bytes can differ from the TE reference.

Because of that, test_nvfp4_quantize_matches_te_reference_bitwise is not expected to pass with current FlashInfer until the FlashInfer-side fix in flashinfer-ai/flashinfer#3448 is available in the installed FlashInfer build. After installing a FlashInfer build with that PR, the Miles bitwise test passes on the devbox (416 passed).

@ziang-and
ziang-and force-pushed the fp4 branch 4 times, most recently from def88c0 to 59716d8 Compare June 22, 2026 21:42
@zianglih zianglih changed the title Add FlashInfer-backed NVFP4 quantization support NVFP4 RL Jun 23, 2026
nanjiangwill added a commit to modal-projects/miles that referenced this pull request Jul 11, 2026
Squash of zianglih:fp4 @ f9da769 onto main: TE-direct NVFP4 quantizer with
paired gate/up amax handling, the nvfp4 dispatch in quantize_params, flashinfer
env forwarding, conversion tool updates, and the GLM5.2 nvfp4 e2e. Carried on
this branch until the PR merges upstream.

Co-authored-by: Ziang Li <ziangli@umich.edu>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ucT9vHENjhVatxSvsT1Mq
jvmncs added a commit to modal-projects/stitch that referenced this pull request Jul 20, 2026
* Remove dead and speculative code from the core library

- generate_rollout compatibility wrapper and the unreachable per-request
  version-pinning branch (the live hook contract never supplies rollout_id);
  the request hook now does only what runs: retry budget + session affinity
- test-only helpers: transition_artifact_paths, parse_extra_key_version,
  validate_policy (redundant with request_context)
- register_routes extension point nothing passes
- version error helpers inlined into evaluate_version_policy
- unused top-level re-export facade in stitch/__init__.py

* Remove dangling cookbook artifacts

- unreachable per-request version-pin branch in the standalone slime hook
  (the hook contract never supplies rollout_id; announce_and_wait already
  gates staleness) + its helper, config knob, and tests
- megatron_r3_num_out_tokens.patch: never applied, the fix is baked inline

* Dedup the slime/miles twin adapter modules

- bulletin_hooks reads generic DELTA_APP_NAME / DELTA_SERVER_CLS_NAME env
  fallbacks; per-recipe hooks.py become pure re-export shims kept only
  because configs reference them by dotted string
- per-recipe helpers.py shrink to the real per-recipe axes (sidecar module,
  model-script attr, wake mode) + one-line wrappers; miles-only
  materialize_node_local_yaml and start_host_mem_monitor move out of the
  shared trainer_helpers into miles_disagg/helpers
- serving wrappers take the shared trainer_repo_* kwarg names; the shared
  builder computes the cookbook mount dir itself

* De-noise prose: drop provenance labels, war stories, and docstring essays

Keep the behavioral fact, drop the provenance: external PR/issue labels
(slime PR #5, radixark/miles#1261, P0.1), incident anecdotes, milestone
roadmap prose (M1/M2/M3), and layout/design essays compressed to what the
code needs the reader to know. Pinned SHAs stay; their changelog trailers go.

* Trim READMEs and packaging

- root README: document all four cookbook recipes; fix the nonexistent
  slime extra claim
- miles_disagg README: compress the fork changelog and bring-up checklist,
  fold the GLM-4.5-Air section into a variant note, point at the fork for
  the convert tools, document _spawn_into_deployed
- standalone_rollouts README: compress the compatibility spec-dump and the
  SLIME-harness tail
- slime_disagg README: merge the redundant Protocol notes into the run
  section
- pyproject: drop uvicorn from the sglang extra (packaged code never
  imports it; Modal images install it explicitly)
Yueming Yuan added 2 commits July 23, 2026 18:00
- transformer_engine / transformer_engine_cu13 / transformer_engine_torch 2.12.0 -> 2.17.0
- update te_fa2_sm103_whitelist.patch for TE 2.17 source layout
- add te_dequantized_backward_override.patch (hot fix from
  NVIDIA/TransformerEngine#3141; drop after TE v2.18)
@yueming-yuan
yueming-yuan changed the base branch from main to bump-te-2.17 July 24, 2026 05:31
Yueming Yuan added 2 commits July 23, 2026 22:33
# Conflicts:
#	docker/patch/cu13/te_dequantized_backward_override.patch
#	docker/patch/cu13/te_fa2_sm103_whitelist.patch
torch_memory_saver 0.0.9.post1 ships CUDA-major-suffixed preload
binaries (torch_memory_saver_hook_mode_preload_cu13.abi3.so), but
actor_factory hardcoded the old unsuffixed name and asserted it exists,
breaking every offload_train megatron run. Use the package's
get_binary_path_from_package helper to resolve the right variant.
@Zhichenzzz
Zhichenzzz deleted the branch radixark:main July 24, 2026 07:01
@Zhichenzzz Zhichenzzz closed this Jul 24, 2026
@yueming-yuan yueming-yuan reopened this Jul 24, 2026
@yueming-yuan
yueming-yuan changed the base branch from bump-te-2.17 to main July 24, 2026 07:31
@yueming-yuan yueming-yuan added the bypass-fastfail Disable CI fast-fail: run all stages and all tests to surface every failure label Jul 24, 2026
Yueming Yuan and others added 2 commits July 24, 2026 01:56
The NVFP4 quantize kernels are gated to sm_100+ (Blackwell); the test is
registered on stage-b-2-gpu-h200 (Hopper) where tex.quantize hits
NVTE_DEVICE_ERROR("sm_100 or higher is required"). Mark disabled, matching
the GLM5.2 NVFP4 e2e test, until miles CI has a B-card suite.
@yueming-yuan
yueming-yuan merged commit 1fca8f8 into radixark:main Jul 24, 2026
33 checks passed
@zianglih

Copy link
Copy Markdown
Contributor Author

For accurate reproduction, need to make sure the flashinfer is after flashinfer-ai/flashinfer#3448 to include the bit-exact 4over6 NVFP4 quantization. That is at least https://github.com/flashinfer-ai/flashinfer/releases/tag/v0.6.13rc1 .

# Replace FlashInfer with v0.6.13rc2:

   python3 -m pip uninstall -y flashinfer flashinfer-python flashinfer-cubin flashinfer-jit-cache
   python3 -m pip install --pre 'flashinfer-python[cu13]==0.6.13rc2' 'flashinfer-cubin==0.6.13rc2'
   python3 -m pip install --pre 'flashinfer-jit-cache==0.6.13rc2' \
     --index-url https://flashinfer.ai/whl/cu130
   flashinfer show-config

# Restore cudnn version after FlashInfer reinstall:

    pip install --no-deps nvidia-cudnn-cu13==9.16.0.29

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

Labels

bypass-fastfail Disable CI fast-fail: run all stages and all tests to surface every failure run-ci-megatron

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants