Repository navigation
Conversation
aoshen02
force-pushed
the
wu/register-derived-tensors
branch
5 times, most recently
from
September 20, 2026 01:10
1e2e129 to
4b6419e
Compare
aoshen02
force-pushed
the
wu/register-derived-tensors
branch
4 times, most recently
from
September 20, 2026 02:35
136367d to
2c6b860
Compare
11 tasks
Open
1 task done
This was referenced Sep 28, 2026
[Weight Reload] Add module-owned runtime buffers and staged reload lifecycle
vllm-project/vllm#53509
Draft
Draft
Draft
aoshen02
force-pushed
the
wu/register-derived-tensors
branch
from
October 5, 2026 03:21
2c6b860 to
ab1fcb1
Compare
…or buffer Sleep mode level 2 saves and restores only named_buffers(); CUDA-graph address checks and weight reload also see only parameters and buffers. Any persistent device tensor kept as a plain attribute, inside a quant method, kernel, MoE quant config or attention impl, or in a module-level cache is invisible to them and comes back stale or freed. Register all of them. Framework (vllm/model_executor/utils.py): - set_derived_buffer: fill a None placeholder registered in __init__, then copy later values in place; shape/dtype changes raise. - register_constant_buffer: a non-persistent constant no checkpoint provides, so layerwise reload does not wait for it. - persistent_tensors() on non-module holders (quant methods, linear kernels, fused-MoE experts / kernels / prepare-finalize / quant configs, attention impls, schemes); held_tensors and dataclass_tensors build it. - register_held_tensors(model), run after process_weights_after_loading and after layerwise reload, registers each declared tensor on the module holding the object as _held_<attr>_<name>, deduplicated per storage view and through traceable wrapper subclasses. Loader and reload: - MoERunner builds the fused router/shared-expert gate at post-load (under device_loading_context) instead of on the first forward, and again after layerwise reload. - Layerwise reload points derived buffers at their kernel storage before reprocessing a layer, so set_derived_buffer refills it in place, and keeps a derived buffer that processing rebinds instead of re-placing the stale one. The sharded RDT engine shares the same preparation. - DeepGEMM warmup reads the layer's MoE quant config instead of building a second one, which re-registered g1_alphas/g2_alphas and orphaned the tensors the kernel holds (FlashInfer CUTLASS per-tensor FP8 MoE). Models and layers: attention impls (sinks, alibi slopes, MLA absorbed weights, static sink K/V, TurboQuant constants), MoE experts and quant configs (FP8/NVFP4/MXFP4/W4A8 scales, strides, TRT-LLM constants, triton kernels storage, b12x packed weights, Humming locks), Hadamard shared weights, RMSNorm without weight, linear-attention slopes, DFlash / DSpark / LiLiCorr fused buffers, Gemma4 MTP, GLM5-next, Inkling, Kimi-K3, DSv4/V4.1 (NVIDIA, ROCm, XPU, CPU), dots3 audio STFT constants, pixtral/OpenPangu-VL rotary, Granite Speech, Sentence-Transformers projector weights under DispatchPooler. Tests: test_initialization asserts that no unregistered device tensor remains (tests/models/utils.py::find_unregistered_tensors, with runtime state exempted by attribute name and reason), plus unit tests for set_derived_buffer, register_held_tensors and layerwise reload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
aoshen02
force-pushed
the
wu/register-derived-tensors
branch
from
October 7, 2026 04:41
ab1fcb1 to
08f95fe
Compare
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.
Purpose
Sleep mode level 2 saves and restores only
named_buffers(), and CUDA-graph address checks and weight reload only see parameters and buffers. A persistent device tensor stored any other way comes back stale or freed after sleep/wake or a weight update. That covers plain attributes, tensors held by quant methods, kernels, MoE quant configs or attention impls, and module-level caches.This PR makes it a rule that every persistent device tensor is a parameter or a buffer, and enforces it in CI. Earlier versions of this PR registered weight-derived tensors only; this version covers every persistent device tensor, constants included, under the contract below.
Contract
state_dict()and checkpoints are unchanged.Nonein__init__and filled withset_derived_buffer. Later values are copied in place, and a shape or dtype change raises.register_constant_buffer, so layerwise reload does not wait for them.persistent_tensors(). This covers quant methods, linear kernels, fused-MoE experts / kernels / prepare-finalize / quant configs, attention impls and schemes.register_held_tensors(model)registers each of them on the module that holds the object, as_held_<attr>_<name>. It runs afterprocess_weights_after_loadingand after layerwise reload, and deduplicates per storage view, through traceable wrapper subclasses such as TorchAO.__init__,create_weightsor post-load, never on the first forward.tests/models/utils.py::RUNTIME_STATE_ATTRS: KV caches, per-step scratch, workspaces, compile artifacts.Changes
vllm/model_executor/utils.py(set_derived_buffer,register_constant_buffer,register_held_tensors,held_tensors,dataclass_tensors,storage_views), called frommodel_loader/utils.py::process_weights_after_loading.reload/layerwise.pypoints derived buffers at their kernel storage before a layer is reprocessed, so values are refilled in place. A buffer that processing rebinds is kept instead of the stale one. The MoE runner hook and held-tensor registration also run after reload. The sharded RDT engine shares the same preparation.device_loading_context, instead of lazily on the first forward.g1_alphas/g2_alphasand orphaned the tensors the kernel holds. It now reads the layer's existing config.DispatchPoolertest_initialization.py::can_initializeasserts thatfind_unregistered_tensorsfinds nothing. Unit tests coverset_derived_buffer,register_held_tensorsand the layerwise reload path.Not covered (known, out of scope)
lru_cache, module globals) are runtime state.Test Plan / Results
All GPU checks ran on GB200. The image was
nightly-dev-arm64-cu130-ac9126e58aa7, overlaid with this change file by file, with SHA-256 gates. Base armIis the same image with no overlay. Determinism checks ran withkernel_config.enable_flashinfer_autotune=false, because autotune alone makes several models differ from run to run.Unregistered-tensor oracle (
find_unregistered_tensors):test_initializationover all 396 registered architectures, dummy weightsdesc_act, W4A8 scheme.On main the oracle finds, for example: FlashInfer CUTLASS FP8 MoE scales computed in the quant config, MXFP4 TRT-LLM constants, CUTLASS strides, attention sinks, Sentence-Transformers projector weights and MoE runner fused gates.
Sleep level 2 round trip. Generate, then
sleep(2),wake_up(),reload_weights(), then generate again. The second run must be bit-identical (48 greedy tokens and top-5 logprobs).DeepSeek-V4, Kimi-K3 and DeepSeek-Coder-V2-Lite already differ between two generations in one process without sleeping, on main and on this PR alike, so this check cannot decide them.
Outputs unchanged (main vs this PR, warm caches, same GPU). Greedy tokens and top-5 logprobs are bit-identical for every model that is deterministic from run to run: gpt-oss-20b, Qwen3-30B-A3B FP8 and NVFP4, Qwen3-0.6B (bf16 and FP8), Qwen3-Next-80B-FP8, Ministral-3-3B, Gemma-3-4B, DeepSeek-Coder-V2-Lite-FP8, DeepSeek-V4 4L and Kimi-K3 6L real weights.
Throughput is unchanged (
vllm bench throughput, 1024 in / 256 out, 500 prompts, 3 reps): gpt-oss-20b 106.05 vs 106.39 req/s; Qwen3-30B-A3B-FP8 75.71 vs 75.77 req/s.Unit tests:
tests/model_executor/test_utils.py,tests/quantization/test_held_tensors.py,tests/model_executor/model_loader/,tests/models/test_deepseek_v4_mega_moe.pyandtests/kernels/core/test_layernorm.pyshow the same failures as on main, plus the new tests. The failures on both are local_Cand flashinfer build mismatches.pre-commitpasses.Not a duplicate
I searched open PRs for
register buffer sleep mode,named_buffers sleep,derived weight buffer,unregistered tensorsandpersistent tensors buffer. Related work:These PRs make individual derived tensors reload-correct. This PR defines one registration contract for every persistent device tensor and enforces it with a CI oracle over every architecture. Some sites overlap: the Gemma4 MTP LM head, MegaMoE buffers, DeepSeek hyper-connection broadcast, FlashInfer epilogue alphas and Humming locks. I will rebase onto whichever of those lands first.
AI assistance
Written with Claude Code. Fable 5.1, Codex (gpt-6-astra) and Kimi K3 each audited the change in two rounds, and every finding was fixed or is listed above. The submitter reviewed every changed line and ran the tests and GPU checks above.
🤖 Generated with Claude Code