Repository navigation
Conversation
…ss_weights_after_loading process_weights_after_loading no longer unregisters a loaded param or leaves it without its data, except where keeping the data would cost real memory: - Renames go through the new move_parameter helper, which registers the same param object under the runtime name too, so the loader writes through the checkpoint name and kernels read the runtime name of one storage. - Loaded params no kernel reads (consumed per-tensor scales, input scales of weight-only Marlin/Humming MoE backends) keep their data and are marked retired; the MoE quant configs get None for unused input scales instead of the layer attribute being set to None. - Large released sources (weights repacked into a different layout, KV-cache scale placeholders) stay registered with empty storage. kernel_parameters/get_live_param give the kernel view (live params, each object once under its runtime name) to the Humming conversion, and the Triton MXFP4 MoE wrapper moves to a plain <name>_triton attribute. Signed-off-by: aoshen02 <aoshen@inferact.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Marlin/CPU/zentorch/conch: a loaded zero-point param that the config does not use (symmetric GPTQ qzeros) is emptied in place again, as on main, instead of keeping its data (~1/group_size of the int4 weight). - Revert the unreachable deepseek_v4 XPU and dots3 vision changes. - Tests that asserted deleted or renamed params now assert the param is still registered (renamed ones alias the runtime name); drop the blanket non-empty check, since Triton MXFP4 release empties by design. Signed-off-by: aoshen02 <aoshen@inferact.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
replace_parameter now copies a new tensor that is smaller than the param into the param's own storage (a prefix view) instead of rebinding .data to a new, smaller allocation, so a loaded param never ends up with fewer bytes than its checkpoint tensor (e.g. ModelOpt NVFP4 MoE w13_weight_scale_2 and w13_input_scale [E,2] -> [E], merged qkv per-tensor scales [3] -> scalar). Online quantization (uses_meta_device) still frees its high-precision source. The mixed-precision CUTLASS FP8 scale conversion and the K3 gfx942 MXFP4 repack now go through replace_parameter instead of rebinding or emptying .data directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
The Triton MXFP4 MoE path emptied w13/w2_weight and their scales after swizzling them into triton_kernels tensors. The params now hold the swizzled storage and the triton tensors point at it, so the loaded params keep their bytes without a second copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
A converted tensor under a new name is now registered as the leftover loaded param it came from (same object under both names, as move_parameter does) when it lives in that param's storage, or takes the place of a large leftover. This removes the second Parameter object that block-FP8 MoE w13/w2_weight_scale had over w13/w2_weight_scale_inv, and the emptied CT WNA16 *_weight_packed params. Other leftovers keep their data, retired. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
- Unused zero points (Marlin, Conch, CPU, Zentorch), dynamic 4-bit scales and INC ARK qzeros/scales keep their data, retired. - Zentorch WOQ/DA8W4, the DeepSeek-V4 CPU wo_a pack, CPU AWQ repack, CT W4A8 int8 MoE and CT WNA8O8 int weights put the repacked tensor into the loaded param (the same object is registered under the packed name where one exists). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
…put scales Humming block-FP8 MoE registered the runtime w13/w2_weight_scale as a second Parameter over the storage of the loaded w13/w2_weight_scale_inv, and the Humming NVFP4 linear did the same for input_scale_2 over input_global_scale. Both are now one object registered under both names (move_parameter). Signed-off-by: aoshen02 <aoshen@inferact.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: aoshen02 <aoshen@inferact.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…irect-reload base Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
Post-load processing reruns on every weight reload, while captured CUDA graphs and kernels keep reading the tensors at their startup addresses. Tensors derived from the weights that live outside the parameters (fused DFlash/DSpark K/V and norm stacks, GLM-5-next indexer gate and KDA merged conv weight, Gemma4 MTP gathered lm_head, NVFP4 MoE activation gscales and TRTLLM constants, host KV scales) were rebuilt as new tensors, so a reload left graphs reading the old values. `replace_parameter` now also handles buffers and plain tensor attributes, by the same rule as parameters (copy in place when the layout matches, else into the existing storage when it fits, else rebind), and keeps their kind: a buffer stays a buffer and an attribute never becomes a parameter. It returns the tensor now held under the name. The derived tensors above go through it; attributes first built during post-load processing are declared (None) beforehand so they stay plain attributes. GLM-5-next and its MTP rebuild their caches from the model-level post-load hook after every load; TRTLLM NVFP4 registers its folded constants through `replace_parameter` instead of `register_parameter`. Tests: tests/model_executor/test_utils.py (kind kept and in-place refill for parameter, buffer, attribute and declared attribute). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
…fter reload - Kimi K3: the specialized decode keeps copies derived from the weights (packed MoE front, padded TP16 dense down projection, KDA conv weight and FP32 norm); the model-level post-load hook refreshes them in place after a reload. The MLA reciprocal q/k scales are refilled in their buffers. - Muse Spark: the norm-preserving residual connection refills its alpha/beta buffers in place when the coefficients are rebuilt. Captured CUDA graphs keep the startup addresses of all of these. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
`materialize_inference_buffers` runs after every load. Refill the attention bias, fused edge projection and factor input splits in place instead of allocating new tensors (captured CUDA graphs keep the startup addresses); the rank constants depend only on topk and are built once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
…n reload Quant methods build a new experts object in `process_weights_after_loading`, so every weight reload builds one, and the constant tensors it allocates in its constructor (TRTLLM MXFP4 SwiGLU `gemm1_alpha/beta/clamp_limit`) are new tensors. Captured CUDA graphs keep reading the tensors of the first object: after a reload they read freed memory. An experts class now names those tensors in `kept_constants`; `keep_constants(layer)`, called wherever an FP8/MXFP4/NVFP4 MoE kernel is built, keeps them on the layer as plain attributes through `replace_parameter` (refilled in place with the fresh values) and hands them back to the new object. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
… addresses Continuation for the remaining kernels whose post-load processing allocates new tensors on every weight reload while captured CUDA graphs keep reading the first ones: - TRTLLM FP8 per-tensor experts: `_g1_alphas`, `_g2_alphas`, `_g1_scale_c` (they change with the weight and activation scales); - FlashInfer CUTLASS experts (FP8, NVFP4, MXFP4): the SwiGLU `gemm1_alpha/beta/clamp_limit` and MXFP8 `fake_input_scale`; - CuteDSL NVFP4 experts: the per-token `per_token_global_scale`; - FP8 per-tensor FlashInfer CUTLASS/HPC quant config: `g1_alphas` and `g2_alphas` were registered as new parameters on every run, now refilled through `replace_parameter`; the inverse activation scales `a1_gscale` and `a2_gscale` are kept on the layer as plain attributes. Tests: tests/quantization/test_keep_param_objects.py (a rebuilt experts object of each class gets the first object's tensors with the new values, no parameter added; FP8 FlashInfer CUTLASS scales refilled in place). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
…er load The NVIDIA MuseSpark1xMoE folded config.output_multiplier into the router weight and then dropped the multiplier, so a weight reload left the raw router weight unscaled while routing no longer applied the multiplier. Keep the multiplier and fold once per load of the router weight, like the AMD model: the router weight's loader resets the folded flag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
…heir startup addresses on reload Every post-load rerun of MuseSparkModelOptNvFp4FusedMoE built new g1_alphas, g1_scale_c, g2_alphas and a1/a2 gscale tensors and re-registered g1_scale_c as a new Parameter. Captured CUDA graphs kept reading the freed first tensors, so a same-weight direct reload left every weight bitwise equal yet dropped GSM8K from 0.84 to 0.75. Keep them as layer attributes written through replace_parameter, which refills them in place on a reload, and build the quant config from those tensors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
…o the direct-reload base Signed-off-by: aoshen <aoshen@inferact.ai>
replace_parameter goes back to handling parameters only. Weight-derived buffers and plain attributes that kernels read go through a separate refill_derived(obj, name, tensor): copy in place when shape, dtype and device match (captured CUDA graphs keep the address), else replace. Call sites no longer pre-declare the attribute to keep it from becoming a parameter. Drop the TRT-LLM NVFP4 unfolded-constant refill: kernels read the folded parameters, which replace_parameter already refills in place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen <aoshen524@gmail.com>
- TRT-LLM FP8: keep gemm1_alpha/beta/clamp_limit, allocated per experts object and read by the kernels. - TRT-LLM NVFP4: keep g1_scale_c and gemm1_* from the constructor. Quark NVFP4 never runs the experts post-load step, so they moved on every rerun; methods that run it still register the folded values afterwards. - Muse Spark: fold the router output multiplier in its weight loader, dropping the fold flag. - Drop the attention host KV scale refill (host tensors, read by name) and a dead GLM-5-next check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen <aoshen524@gmail.com>
CutlassExpertsFp8Base and CutlassExpertsW4A8Fp8 allocate the grouped-GEMM stride tensors in their constructor; the object is rebuilt on every post-load rerun, so captured CUDA graphs read freed memory after a reload (illegal memory access on Qwen3.5-35B-A3B with online FP8 per-channel). Name them in kept_constants like the other experts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen <aoshen524@gmail.com>
…result _in_own_storage only reused the loaded storage for a strictly smaller result, so a repack to the same byte count (FP8 weight -> Marlin int32, a transposed copy) was rebound to fresh memory and moved the parameter's address on every post-load rerun; captured CUDA graphs keep reading the old one. An equal-size result fits exactly: copy it in place. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SeFSSrpw7yTpLz2C2sHDKA Signed-off-by: Aaron Hao <aaronhao@inferact.ai>
…aded storage `replace_parameter` now writes a smaller result into the existing parameter's storage instead of rebinding `.data`. Two tests relied on the old behaviour: - `test_online_int8_moe_w2_scale_matches_unsharded` built the layer's parameters directly from the test's own `w13`/`w2` tensors, so the int8 result overwrote expert 0 of `w2` before the test reused it for the unsharded amax and the TP shard. Clone the inputs. - `test_humming_replace_keeps_loaded_params` asserted the kept storage size with a hard-coded float32 element size; under a bfloat16 default dtype (as on the Intel XPU runner) the loaded storage is 1024 bytes, which the test misread as shrunk. Use the parameter's element size. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SeFSSrpw7yTpLz2C2sHDKA Signed-off-by: Aaron Hao <aaronhao@inferact.ai>
…_after_loading `replace_parameter` now writes a same-size result into the loaded parameter's own storage. The repack test computed its expected scales / zero points from the checkpoint tensors after processing, when that storage already held the transposed result. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SeFSSrpw7yTpLz2C2sHDKA Signed-off-by: Aaron Hao <aaronhao@inferact.ai>
FallbackExperts (Triton-or-CUTLASS, Triton-or-DeepGEMM) holds the experts that own the kernel constants; keep_constants on the wrapper now reaches both of them, under their own prefix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen <aoshen524@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen <aoshen524@gmail.com>
… wu/stack/direct-base
… into wu/stack/direct-base
# Conflicts: # vllm/models/glm5next/common/kda.py
|
Gap to cover later: DeepSeek-V4 ROCm compressor GEMM fusion (found while auditing storage sharing for vllm-project#64336)
fused_weight = torch.cat((main_weight, indexer_weight), dim=0)
main_weight.set_(fused_weight[:main_size])
indexer_weight.set_(fused_weight[main_size:])so two checkpoint params end up on one storage, and the indexer weight moves to offset
The forward ( fused = torch.cat((main_weight, indexer_weight), dim=0)
refill_derived(self, "_fused_compressor_weight", fused) # in place on reload
self._fused_compressor_split_sizes = (main_size, indexer_size)
# drop the set_() calls and the early returnCost: one extra copy of the C4 compressor weights per layer (the Posted by Claude Code on behalf of Aaron Hao. |
Stack PR 5 on the current PR 1 and PR 4 heads. Laguna DFlash and LiLiCorr changes are dropped here: PR 1 leaves those models to the F9 follow-up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen524@gmail.com>
The V2 speculator captures the context K/V step in its FULL CUDA graph, so the stacked K/V and K-norm weights rebuilt by the post-load hook must keep their addresses. Split layerwise reload, proto-beam-early-0923-NVFP4 TP4 with the DFlash2 drafter: draft acceptance after the reload was 7.03 -> 4.89 with the buffers re-created, 7.02 -> 7.03 refilled in place (7.02 -> 7.04 with --enforce-eager). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen524@gmail.com>
Problem
process_weights_after_loadingreruns on every weight reload (layerwise and direct). Many quant methods, kernels and models build tensors derived from the weights in that step: kernel constants, activation global scales, decode-path copies, fused drafter buffers. They allocate them as new tensors each time. Captured CUDA graphs and kernels keep reading the tensors from startup, so after a reload they read freed memory.The weights themselves are reloaded correctly (diff 0 against cold start), yet accuracy drops or collapses. The symptom is model-specific and silent: no error, only wrong output.
Fix
Write weight-derived tensors back into their startup storage on every rerun instead of allocating new ones.
replace_parameter, which already refills in place; it now returns the kept parameter.refill_derived(obj, name, tensor): copy in place when shape, dtype and device match, else replace. It returns the held tensor.kept_constants.keep_constants(layer)keeps them on the layer throughrefill_derivedand hands them back to the new object.Sites:
gemm1_alpha/beta/clamp_limit._g1_alphas/_g2_alphas/_g1_scale_cand SwiGLUgemm1_alpha/beta/clamp_limit.fake_input_scale.per_token_global_scale.g1/g2_alphas.a1/a2_gscale.g1_scale_candgemm1_*(registered by the experts' post-load step; kept from the constructor for Quark NVFP4, which skips that step).ab/c_stridesand CUTLASS W4A8a/b/c/s_strides(onlinefp8_per_channelMoE on Qwen3.5-35B-A3B: illegal memory access after a reload before, GSM8K 0.644 → 0.623 with oracle unchanged after).FallbackExperts: Triton-or-CUTLASS, Triton-or-DeepGEMM) passeskeep_constantsto both experts it holds.g1_alphas,g1_scale_c,g2_alphas,a1/a2_gscale).Tests
Unit tests:
tests/model_executor/test_utils.py:refill_derivedrefills buffers and attributes in place and never makes a parameter.tests/quantization/test_keep_param_objects.py: a rebuilt experts object keeps the first object's constant addresses, for TRTLLM MXFP4/FP8/NVFP4, FlashInfer CUTLASS, CuteDSL, CUTLASS FP8/W4A8 and aFallbackExpertswrapper. Also covers the FP8 FlashInfer CUTLASS scales.tests/models/test_muse_spark_logits_local.py: the router fold happens once per load; the NVFP4 MoE constants keep their addresses across reruns.All pass on GB200 (slurm 39929, 40053, 40106).
E2E on GB200: same-weight NUPD≥2 reloads. GSM8K is compared before and after, and every tensor is fingerprinted against cold start.
Reload in place (direct mode), stacked on vllm-project#57733 + vllm-project#62407 + vllm-project#58382 (+ fork #179):
Layerwise reload, CuteDSL NVFP4 MoE, Qwen3-30B-A3B NVFP4:
Dependencies
Stacked on vllm-project#62407 (keep param objects across post-load processing) and vllm-project#57733 (post-load placement). The base branch of this PR is their merge,
5e23af126d. Follow-up to vllm-project#54477.🤖 Generated with Claude Code