Repository navigation
Quantized weight-update fixes: Qwen3.5 draft KV budget, UE8M0 partial blocks, mxfp8 fused indexer, NVFP4 input scales - #43251
Merged
Conversation
…dden_layers The target's KV pool sizing adds the EAGLE draft's layers to the per-token cost, taking the draft's depth from num_nextn_predict_layers. Qwen3.5 names it mtp_num_hidden_layers, so with the target's own MTP layer as draft the depth read as none, and the draft's pool, sized to the target's token count, was allocated outside mem_fraction_static: about a tenth more KV than budgeted on Qwen3.5-35B-A3B, and an out-of-memory at startup on a 4-layer cut of it, whose one full-attention layer makes the draft's pool as large as the target's (127.6 GB with 82 GB left at 0.7 on GB300).
The packed UE8M0 layout holds one scale row per weight row, so a weight of 2624 rows (GLM-5's fused q_a + kv_a) ends in a partial block. The inverse viewed the rows as whole blocks of 128 and raised `shape '[20, 128, 48]' is invalid for input of size 125952`, which made the weight checker fail on every GLM-5 fp8 update on Blackwell. It now takes the first row of each block and checks every row of a block against it, the partial one included.
…heir UE8M0 scales On CUDA the DSA indexer loads wk and weights_proj into one bf16 param, dequantizing quantized shards with block_quant_dequant, which multiplies by the scale. mxfp8 checkpoints store 1x32 UE8M0 scales as uint8 exponents, so an mxfp8 GLM-5.x checkpoint with a quantized wk (as miles' converter makes) started with indexer keys scaled by the raw integers: off by up to 5e4 against the bf16 the trainer's first update writes. Shards with uint8 scales now go through dequant_mxfp8_2d_to_bf16.
w13_input_scale and w2_input_scale are calibrated activation scales, like the KV cache scales: no weight update carries them (miles' NVFP4 quantizer emits weight, weight_scale and weight_scale_2 only). The checker reset them to random before an update and compared them after it, so every NVFP4 MoE update failed the check on them alone, whatever it wrote. They are now marked _skip_weight_check, as the KV cache scales are.
yueming-yuan
requested review from
Alisehen,
AniZpZ,
BBuf,
Edwardf0t1,
FlamingoPg,
Fridge003,
HaiShaw,
OrangeRedeng,
Ying1123,
b8zhong,
ch-wan,
fzyzcjy,
hnyls2002,
ispobock,
merrymercy and
mmangkad
as code owners
October 9, 2026 00:48
Both rebuild model-specific derived state after a write that bypasses the model's own loader. #43251 carries them with the other model- and format-specific fixes p2p needs, so this PR keeps the general engine support.
loader.post_load_weights calls model.post_load_weights() with no arguments and relies on NextN subclasses to pin is_nextn=True, as DeepseekV3ForCausalLMNextN does. Glm4MoeLiteForCausalLMNextN did not, so the main model's branch ran on the draft and raised AttributeError: 'Glm4MoeLiteModelNextN' object has no attribute 'start_layer'. It hits every weight-update session that writes without load_weights and covers the draft (p2p on GLM-4.7-Flash with EAGLE).
GemmaRMSNorm's weight loader derives gemma_weight = weight + 1, which fused allreduce kernels read. Loaders and weight-update sessions that write params without load_weights (p2p, remote instance, sharded state, dummy) left it at its old value.
post_load_weights now walks the model's modules to refresh gemma_weight, which an instance made with __new__ alone does not have.
The post_load_weights util lives in loader.py, whose tests already have this file and its fixtures; a separate file only adds an interpreter import to the CPU gate.
post_load_weights refreshed GemmaRMSNorm's derived gemma_weight only in sessions where no load ran. A `direct` or custom load writes params without the norm's own loader yet marks the session loaded, so those sessions kept the old gemma_weight.
Zhichenzzz
approved these changes
Oct 12, 2026
nv-hwoo
pushed a commit
to nv-hwoo/sglang
that referenced
this pull request
Oct 20, 2026
…V budget, UE8M0 partial blocks, mxfp8 fused indexer, NVFP4 input scales (sgl-project#43251)
nanjiangwill
pushed a commit
to modal-projects/sglang
that referenced
this pull request
Oct 21, 2026
…V budget, UE8M0 partial blocks, mxfp8 fused indexer, NVFP4 input scales (sgl-project#43251) (cherry picked from commit 6ac15f4, partial: tests that need 56bef33's test files dropped) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDwTQn4gGFgVhzGXFs4aJx
Rockdu
pushed a commit
that referenced
this pull request
Oct 30, 2026
…V budget, UE8M0 partial blocks, mxfp8 fused indexer, NVFP4 input scales (#43251)
Rockdu
pushed a commit
that referenced
this pull request
Oct 30, 2026
…V budget, UE8M0 partial blocks, mxfp8 fused indexer, NVFP4 input scales (#43251)
Rockdu
pushed a commit
that referenced
this pull request
Oct 31, 2026
…V budget, UE8M0 partial blocks, mxfp8 fused indexer, NVFP4 input scales (#43251)
Rockdu
pushed a commit
that referenced
this pull request
Oct 31, 2026
…V budget, UE8M0 partial blocks, mxfp8 fused indexer, NVFP4 input scales (#43251)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #42820. The model- and format-specific engine fixes miles' p2p weight update needs; #42820 keeps the general engine support.
Derived state after writes that bypass a model's loader (moved from #42820, plus one gap the review found):
gemma_weightgemma_weightgemma_weight = weight + 1, and p2p, remote-instance, sharded-state and dummy loads write the weight without it;post_load_weightsnow refreshes itgemma_weightat every session enddirector custom load wrote the norm weight kept the oldgemma_weightend_weight_updateskippedpost_load_weights; it now refreshesgemma_weighteither waypost_load_weightsran the target's path on the draft; the NextN draft now pinsis_nextn=TrueQuantized checkpoints. Four fixes found by running miles' p2p weight-update e2e with quantized rollout checkpoints on GB300 (fp8 block, mxfp8, nvfp4 on GLM-5.2 cut to 5 layers; fp8 on Qwen3.5-35B-A3B cut to 4 layers with its MTP draft). None is specific to p2p: each also hits sglang's own weight update or its startup. One commit each.
mem_fraction_static; OOM at startup on the 4-layer cut (127.6 GB draft pool, 82 GB left at 0.7)_resolve_eagle_aux_hidden_statereads the draft depth fromnum_nextn_predict_layers; Qwen3.5 names itmtp_num_hidden_layers, so the target's per-token cost counted no draft layerRuntimeError: shape '[20, 128, 48]' is invalid for input of size 125952in the weight checker on every GLM-5 fp8 update on Blackwell_inverse_transform_scale_ue8m0_implviewed the per-row packed scales as whole 128-row blocks; GLM-5's fused q_a + kv_a has 2624 rowswk_weights_projoff by up to 5e4 after loading an mxfp8 checkpoint with a quantizedwk_load_fused_indexer_wkdequantized withblock_quant_dequant, multiplying by the uint8 UE8M0 exponents as integersw13_input_scale/w2_input_scaleonlyweight,weight_scale,weight_scale_2); the checker reset them to random before the update. Now_skip_weight_check, as the KV cache scalesOn the draft budget: with Qwen3.5-35B-A3B's 10 full-attention layers the unbudgeted draft is a tenth of the KV pool, which fits at 0.6–0.7 and so went unnoticed; the 4-layer cut makes it as large as the target's.
Tests
unit/model_loader/test_default_model_loader.py:gemma_weightfollows a norm weight written without its loader (CPU).unit/model_executor/model_runner_components/test_weight_updater_published_storage.py: a session whose load bypassed the norm's loader still refreshesgemma_weight(CPU); fails on its parent.unit/models/test_glm4_moe_lite_nextn_post_load.py: the GLM-lite NextN draft runs the NextN post-load (CPU).unit/model_executor/model_runner_components/test_spec_aux_hidden_state.py: an MTP depth namedmtp_num_hidden_layerscounts as the EAGLE draft's layers (CPU).unit/layers/quantization/test_fp8_utils.py: the UE8M0 round trip also covers GLM-5's (2624, 6144) (CUDA).unit/models/test_deepseek_fused_indexer_mxfp8.py: mxfp8wkandweights_projreach the fused param dequantized with their UE8M0 scales (CPU).unit/layers/quantization/test_modelopt_nvfp4_moe_weight_check.py: the checker skips the NVFP4 MoE input scales and still checks the weight scales (CPU).On an H200 devbox (
radixark/miles:dev), all changed test files of this branch and #42820 pass (35 passed, 1 skipped: an SM100-only case). On #42820's head each quantized case fails for its cause:None == 1, the[20, 128, 48]view error, the fusedwkmismatch,w13_input_scalenot skipped.End to end on GB300 with the UE8M0 fix applied: GLM-5.2 5-layer fp8 compares every fp8 tensor within quantization ULP,
fused_qkv_a_proj_with_mqaincluded. What remains there is on miles' side: its fp8 / mxfp8 converters quantize the interleaved indexer'swk, which sglang keeps in bf16 and the trainer sends in bf16.CI States
Latest PR Test (Base): ❌ Run #38300076576
Latest PR Test (Extra): ❌ Run #38300075941
Latest PR Test (AMD ROCm 10): ❌ Run #38300076381