[Perf][Model Loader] Reload layout-preserving weights directly - #48382
Closed
aoshen02 wants to merge 8 commits into
Closed
[Perf][Model Loader] Reload layout-preserving weights directly#48382aoshen02 wants to merge 8 commits into
aoshen02 wants to merge 8 commits into
Conversation
aoshen02
force-pushed
the
codex/layerwise-direct-reload-main
branch
from
July 12, 2026 17:44
f07a6bc to
03acbc5
Compare
Contributor
|
This pull request has merge conflicts that must be resolved before it can be |
aoshen02
force-pushed
the
codex/layerwise-direct-reload-main
branch
from
July 19, 2026 01:45
03acbc5 to
cc4582f
Compare
Assisted-by: OpenAI Codex Signed-off-by: aoshen02 <aoshen@inferact.ai>
Assisted-by: OpenAI Codex Signed-off-by: aoshen02 <aoshen@inferact.ai>
aoshen02
force-pushed
the
codex/layerwise-direct-reload-main
branch
from
July 19, 2026 09:44
cc4582f to
f341478
Compare
aoshen02
force-pushed
the
codex/layerwise-direct-reload-main
branch
from
July 19, 2026 09:49
f341478 to
6e8f63b
Compare
Signed-off-by: aoshen02 <aoshen@inferact.ai>
aoshen02
force-pushed
the
codex/layerwise-direct-reload-main
branch
from
July 19, 2026 09:59
6e8f63b to
768db50
Compare
aoshen02
marked this pull request as ready for review
July 19, 2026 10:17
aoshen02
requested review from
22quinn,
AndreasKaratzas,
mgoin,
pavanimajety,
robertgshaw2-redhat,
tlrmchlsmth,
yewentao256 and
zyongye
as code owners
July 19, 2026 10:17
Signed-off-by: aoshen02 <aoshen@inferact.ai>
Signed-off-by: aoshen02 <aoshen@inferact.ai>
Signed-off-by: aoshen02 <aoshen@inferact.ai>
Signed-off-by: aoshen02 <aoshen@inferact.ai>
Signed-off-by: aoshen02 <aoshen@inferact.ai>
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.
Summary
This PR removes redundant reconstruction work from online weight reload while
preserving the runtime tensor storage used by kernels and CUDA graphs.
It adds two fail-closed fast paths:
exactly the same shape, stride, and dtype, the original weight loader writes
directly into the existing runtime storage.
method may expose checkpoint-layout views backed by already transformed
runtime storage. The loader writes through those views, and finalization only
restores the original
Parameterobjects.The initial runtime-view implementation covers compressed-tensors WNA16 Triton
MoE weights and scales. Unsupported methods, unverified layouts, and failed atomic bindings keep
using the existing layerwise materialize/process/copy fallback. Attention
wrappers may use direct/runtime-view loading when their layouts pass the same
contract, but all attention variants still share one deferred post-load
finalization stage.
The important result is that this PR does not skip the original weight
loader, change the inference backend, replace Triton with Marlin, or rebuild
CUDA-graph-visible kernel tensors. It removes the temporary layer lifecycle
that is unnecessary when the final storage can be safely targeted directly.
Motivation
The existing layerwise reload path is necessary when
process_weights_after_loading()changes the checkpoint representation into adifferent runtime representation. However, it applies the same reconstruction
lifecycle to layers whose final storage is already known and safely writable.
Before this PR, each arriving checkpoint tensor could go through:
This is correct, but redundant for two common cases:
same storage.
After this PR:
Design
1. One non-mutating format-owner hook
Quantization methods expose one opt-in contract:
The default returns
None. An implementation must not mutatelayer; it eitherreturns a complete checkpoint-name-to-runtime-storage mapping or declines the
fast path. This replaces the earlier boolean
supports_direct_weight_reload()plus mutating
bind_runtime_weight_reload()pair. One mapping describes bothidentity and nontrivial runtime-view cases, so capability and destination cannot
disagree.
Modules without a quantization method may use the generic identity mapping when
the complete tensor layout matches. A quantized method must opt in explicitly;
generic code never guesses that a packed or transformed quantized layout is
safe.
2. Complete-set direct-layout validation
Identity/direct reload requires the complete checkpoint-facing parameter and
buffer key sets to match the reloadable runtime key sets. Every tensor must
match in shape, stride, and dtype. Lazy/uninitialized tensors fail closed. A
matching name, shape alone, or equal byte count is insufficient.
Attention,MLAAttention, andMMEncoderAttentionuse the same deferredpost-load stage because their scales and runtime state have ordering requirements
beyond a local identity copy. Deferring finalization no longer forces their
checkpoint-compatible tensors onto the layerwise reconstruction path.
3. Explicit reusable reload plans
Every layer receives exactly one cached mode:
DIRECT: the original loader targets the identical runtime tensor;RUNTIME_VIEW: the format owner supplies a checkpoint-layout view over theexisting runtime storage;
LAYERWISE: the existing materialize/process/copy-back fallback.The plan records signatures for all runtime parameters and buffers: shape,
stride, dtype, device, data pointer, storage pointer, storage offset, and storage
size. Before every update, a stale plan is discarded if any signature changed.
DIRECT and RUNTIME_VIEW also re-confirm the current quant-method mapping on every
update, so replacing a quant method cannot silently reuse an old capability.
Finalization verifies the signature again and raises if reload changed runtime
storage or layout.
4. Atomic runtime-view binding validation
A custom mapping is accepted only after the entire mapping passes validation:
through the custom mapping.
Only after all entries pass does generic code bind the complete mapping to the
layer. A detached clone, partial mapping, mismatched view, or unsupported method
falls back without partially rebinding the live layer. This atomicity applies to
structural binding; it does not claim rollback of weight bytes after a transport
failure has already partially written an update.
5. Preserve loader metadata without retaining layer cycles
Direct/runtime-view loading still invokes the original weight loader, preserving
its slicing, TP/PP/EP sharding, stacked-parameter, and copy semantics. Loader
metadata is restored onto the live destination from a fresh meta copy.
Layer-bound references are restored only on that copy: the stored metadata
remains sanitized, avoids a layer-reference cycle, and cannot leave a loader
bound to the sanitizer sentinel. A real H200 run exposed this sentinel-bound
method case; the implementation and a bound-loader regression test cover it.
6. WNA16 Triton runtime views
Compressed-tensors WNA16 uses different checkpoint and runtime representations:
uint8storage, while thecheckpoint-facing loader expects an
int32view with checkpoint axis order;The WNA16 method owns this transform. It returns
int32views over the completepacked
uint8storage and checkpoint-facing views of the transposed scales,copies loader metadata to temporary Parameters, and clears
is_transposedforthe loader-facing view. Generic code proves that these views cover the exact
live storage before binding them. Finalization restores the original runtime
Parameter objects without transposing, repacking, or copying weight data.
7. Conservative fallback
The existing layerwise path remains for:
shape/stride/dtype/storage mismatch.
Initial opt-ins
UnquantizedLinearMethodon every platform; generic key/shape/stride/dtypevalidation rejects CPU optimized layouts that removed or replaced
weight;Fp8LinearMethodwithCutlassFp8BlockScaledMMKernel;Fp8MoEMethodwith Triton or Batched Triton;CompressedTensorsWNA16MoEMethodcheckpoint-layout runtimeviews.
Unified post-load and CPU layout update
The latest head removes two remaining policy special cases.
Attention finalizes once, independently of load mode
Attention,MLAAttention, andMMEncoderAttentionare classified by one_POST_LOAD_ATTENTION_TYPEStuple. Their checkpoint-compatible tensors may nowuse the same direct/runtime-view planner as other layers. Regardless of whether
a layer selected direct, runtime-view, or legacy layerwise loading, it is queued
for the same final stage after all incoming tensors have arrived:
layer, or restore/reload attention scales for a layerwise layer;
layer.process_weights_after_loading(model_config.dtype)exactly once;This separates two independent questions: where bytes can safely be written, and
when module-wide derived state must be finalized.
CPU Linear uses the generic contract
UnquantizedLinearMethod.get_runtime_weight_reload_mapping()now returns theruntime parameters on every platform. That is only a candidate mapping. Generic
code still requires the complete checkpoint-facing and runtime key sets to match
in shape, stride, and dtype.
Therefore:
weightand can reloaddirectly;
weightno longer match and automatically fall back;The same simplification is deliberately not applied to arbitrary quantized
methods. Equal shape/dtype cannot prove that packed or transformed runtime
storage is checkpoint-compatible; the format owner must explicitly opt in.
Broad local A/B validation
A second validation layer was run on exact vLLM v0.25.1 using the same container
and the same prerequisite model-support/backend fixes on both sides:
48c2ba006277e50331580c789a56ba623aa52494;eda369098c21eaea99fe0e4329815b0f6c1f85eb;55da21d9604334f6ff418630d8aef8a153b44740;sha256:267dab8accfcdbbb31aa09ce8b74a31e13a9aaec3d7968164b3dad7ae865dd2c;h200-0andh200-1, each with 8 H200 GPUs.Each paired process used eager execution, prefix caching disabled, a fixed
prompt, greedy generation of eight tokens, and three same-checkpoint reloads.
The table reports the median of reloads 2 and 3. A and B ran concurrently on
separate GPUs where possible.
All 21 pairs passed. For every model, all three post-reload outputs matched the
pre-reload token IDs, text, and selected-token logprobs exactly. Baseline and
candidate token IDs and selected-token logprobs also matched exactly (maximum
cross-variant selected-logprob absolute difference:
0.0).Small negative timing deltas on mostly-layerwise formats are within the observed
NFS/page-cache variance and have no corresponding output difference. This disk
matrix is a breadth/correctness regression test; the VIME/NCCL results below
remain the end-to-end transport performance evidence.
Three attempted fixtures failed identically on A and B and are not counted:
DeepSeek-V2-Lite had an incomplete offline remote-code cache; GPT-OSS MXFP4 hit
the pre-existing
w13_weightmeta-restore collision; and the local GPT-OSS FP8checkpoint did not match the model's
w2_biasparameter set.Benchmark methodology
The benchmark was designed to isolate this PR from both sender-side
optimizations and inference-backend changes.
Fixed environment
h200-0andh200-1, each with 8 NVIDIAH200 141 GB GPUs.
sha256:267dab8accfcdbbb31aa09ce8b74a31e13a9aaec3d7968164b3dad7ae865dd2c.0.25.1, commit752a3a504485790a2e8491cacbb35c137339ad34.b41b854964a162ada035f1875e055b949fc82796.vllm-project/vime#340; sender optimization is therefore held constant.Same-backend requirement and #49065
Both A and B explicitly use
--vllm-moe-backend triton.Both sides also include the behavior from #49065: WNA16 may select Marlin only
when
moe_backend in ("auto", "marlin"); an explicit Triton selection remainson
CompressedTensorsWNA16MoEMethod.This is important because an earlier Qwen3.6 comparison accidentally used
Marlin for A and Triton for B. That number was invalid as a reload-only A/B and
was removed. The results below rerun both sides on Triton. Logs from every Qwen
run contain
Using CompressedTensorsWNA16MoEMethodand contain no Marlinselection.
#49065 is not part of this PR. It only holds backend selection constant so that
the measured delta belongs to this reload change.
A/B definition
PR.
The profiling code, transport instrumentation, model checkpoint, VIME code,
container, topology, sender settings, and backend selection are identical
between A and B.
Topologies
Timing definitions
Three nested timing levels are reported and are never added across parent/child
boundaries:
Timer update_weights; this is theuser-visible end-to-end time.
start_weight_update()tofinish_weight_update()interval. All additivestages in a row come from that same worker.
The receiver hierarchy is:
transfer.receivecontainsmodel.load_weights; those values must not beadded. Similarly,
loader.trigger_processcontains the six_layerwise_process()sub-blocks.For attribution runs, transport and callback boundaries synchronize CUDA so
the callback values are CUDA-complete. The per-weight direct-loader timer is
CPU-only to avoid a synchronization per tensor.
The matrix measures the initial update and stops only after:
The final
KeyboardInterruptin these controlled logs is the orchestratorstopping the job after the measured update, not an update failure.
End-to-end performance
The low-instrumentation/reference outer results are the appropriate production
performance comparison:
The detailed profiler adds synchronization and Python timing overhead, so its
absolute outer values are not substituted for the table above. Its purpose is
to identify where the time went:
model.load_weightsA / BDetailed breakdown: Moonlight-16B-A3B FP8
Receiver critical path
transfer.initializetransfer.receive(60 calls)transfer.finalizeMoonlight uses colocated IPC:
model.load_weightsA: old callback blocks
inspect.Signature.bindand defaultsBoundArgumentsand count numelThese blocks total approximately 1.513 s. The remaining approximately 0.606 s
inside
model.load_weightsis model traversal, original-loader work outsidethe wrapper, and CUDA completion.
The 0.459 s trigger block contains:
_layerwise_process()sub-blockB: direct and fallback work
0.0008 s and trigger time is approximately 0.0016 s.
and wrap loaders: 0.0001 / 0.0005 / 0.0021 s.
An independent h200-1 repeat reproduced outer 5.0 -> 3.4 s, receiver
4.145 -> 2.683 s, and
model.load_weights2.088 -> 0.921 s. The A-sidebuffer_and_counthotspot remained 0.826 s.Detailed breakdown: Qwen3-Coder-Next FP8
Receiver critical path
transfer.initializetransfer.receive(56 calls)transfer.finalizeCoder uses packed NCCL.
broadcast_and_unpackincludes the callback:model.load_weightscallbacksA: old callback blocks
123,807 wrapped loader calls are measured; 122,880 come from
RoutedExperts.The five blocks total approximately 18.247 s. The remaining approximately
19.302 s is model traversal and original-loader/CUDA work outside the wrapper;
it is reported as residual rather than assigned to a function that was not
directly timed.
The trigger block contains:
_layerwise_process()sub-blockB: direct and fallback work
RoutedExperts.0.020 s.
0.00004 / 0.00047 / 0.00177 s.
The direct and old wrapper call counts are not expected to match exactly. The
old wrapper returns early after a layer has already been processed, while the
direct timer records every original-loader invocation. The authoritative
comparison is the CUDA-complete callback boundary: 37.549 -> 19.885 s.
An independent h200-1 repeat reproduced outer 50.0 -> 34.0 s, receiver
49.946 -> 33.988 s, and
model.load_weights34.008 -> 19.338 s. Of the15.958 s receiver reduction, 14.670 s is again inside the callback.
Detailed breakdown: Qwen3.6-35B-A3B INT4
Qwen is the most important same-backend result because the old path exposes a
pathological interaction between checkpoint ordering and layerwise buffer
accounting.
h200-0 same-host receiver critical path
transfer.initializetransfer.receive(50 calls)transfer.finalizeNCCL broadcast/unpack is 6479.108 -> 9.064 s and includes 50 CUDA-complete
model.load_weightscallbacks totaling 6478.328 -> 8.621 s.The four B workers are tightly grouped at 23.0900-23.0914 s, so the critical
worker is not an outlier.
A: exact old-path hotspot
The critical worker enters 92,773 wrapped loader calls; 92,160 come from
RoutedExperts.BoundArgumentsand count numelThe device-buffer accounting block alone is 97.32% of the profiled outer time;
its
RoutedExpertscontribution alone is 96.84%.The five mutually exclusive blocks total 6459.470 s. The remaining 18.857 s
inside
model.load_weightscontains traversal,can_load/early-return paths,original-loader work outside the timed wrapper blocks, and CUDA completion.
463 layers process immediately. Another 40 partially loaded WNA16
RoutedExpertsprocess during finalization in 2.755 s. Across both paths,503
_layerwise_process()calls contain:_layerwise_process()sub-blockThese six sub-blocks total approximately 2.843 s. Therefore the 6000+ second
result is not caused by Triton quantization or repacking; it is caused by the
per-weight Python accounting/warning block.
Why accounting becomes pathological
For every arriving device weight, the old wrapper:
LOADING_LAYERS;get_info_size(...);logger.warning_oncewith the computed memory and layer-name list.The list, sort, and sum execute before entering the logger. In addition,
warning_onceis backed by@lru_cachewith(logger, message, *args)as thekey. Both
mem_usedand the layer-name list change as moreRoutedExpertsaccumulate, producing new cache keys and actual warning writes.
The h200-0 attribution run emits exactly 179,828 such warnings (about 111 MB of
log); B emits zero. During the slow buckets, all eight GPUs show 0% compute
utilization while the four vLLM TP workers use approximately 90-98% CPU. The
GPU is waiting for Python accounting and logging, not executing a slow Triton
kernel.
B: WNA16 direct/runtime-view path
Initialization sees 799 layers:
RoutedExpertsbind successfully;Measured initialization/finalization cost:
The loader side contains:
RoutedExpertscalls in 2.036 s;The fallback trigger contains 0.00221 s materialize, 0.00066 s replay, and
0.00055 s copy/restore; all other sub-blocks total less than 0.00006 s.
No WNA16
RoutedExpertslayer is materialized, repacked, and copied back in B.The original loader writes through the runtime-backed checkpoint view into the
existing kernel storage.
Independent Qwen repeat
h200-1 independently reproduces the same result:
model.load_weightsbuffer_and_countThis is a 272.3x same-host speedup. The h200-1 A run also emits exactly 179,828
accounting warnings, and 5595.712 s of its accounting time is specifically
attributed to
RoutedExperts.The two absolute profiled A times differ because both long runs overlap on a
shared NFS log path and profiling changes synchronization/interleaving. Their
call counts, warning counts, hotspot, and B results agree. Production speedup is
therefore reported from the lower-instrumentation 6075.7 -> 20.6 s result, while
the synchronized profiles are used only for code-block attribution.
What remains after the fast path
The following table locates remaining B cost. Columns are nested; they must not
be added horizontally.
model.load_weightsThe callback residual is inclusive callback time minus direct-loader CPU and
the tiny fallback trigger. It includes model traversal, original-loader CUDA
work, and callback-end CUDA completion; it is not labeled as pure NCCL or pure
kernel time.
The largest remaining top-level component is generally lifecycle uncovered
time: sender/chunk arrival, HTTP/Ray scheduling, and inter-chunk gaps. This PR
does not change transport scheduling.
Profiling caveats
perf_counterblocks per wrapped loader call.perturbs the result and was excluded.
unmeasured function.
profiles are used for attribution.
occupied, so their absolute logging cost is intentionally treated as
profiling-disturbed.
Despite the instrumentation overhead, the direction and attribution are
stable. For example, Coder's profiled speedup is 67.2 / 42.4 = 1.58x versus
49.4 / 32.0 = 1.54x without detailed profiling.
Validation
Static and unit validation
ce0cf2e2a944cb366fb43de37aa7642f676a786d;14c2444aed42705873fc8b614e702c38ed9218a6;forbidden-import, configuration, and sign-off checks;
py_compile.Tests cover exact key/layout acceptance and rejection, lazy parameters and
buffers, detached mapping rejection before mutation, bound-method metadata,
repeated runtime-view validation, quant-method replacement, storage replacement
detection, model cleanup, alias/non-persistent buffer preservation, WNA16
checkpoint views, and identity mappings in the presence of runtime-only MoE
tensors.
Final-head three-model validation
Only the PR side was rerun; the pre-change measurements above were reused. All
three runs used the same H200 host, pinned image, explicit Triton MoE backend,
and exact final code.
PR48382_CASE_DONEPR48382_CASE_DONEPR48382_CASE_DONEThe final revalidation caught and fixed an intermediate fail-closed regression:
a runtime-only MoE tensor made an otherwise identity mapping look non-identity,
which skipped restoration of FP8 scale loader metadata. Identity is now checked
against the checkpoint-reloadable runtime tensor set, and the focused test
includes that exact boundary. No final log contains a weight-update traceback,
endpoint failure, storage/layout validation error, NaN, or invalid MoE scale
quant method.
Cross-framework design validation
The design was checked against 12 locally cloned inference or inference-adjacent
frameworks: SGLang, LMDeploy, TensorRT-LLM, xLLM, LightLLM, TGI, DeepSpeed,
Megatron-LM, llama.cpp, MLC-LLM, FlexFlow, and Dynamo. The common requirements
are: quiesce inference, plan destinations before mutation, let the format owner
define transforms, preserve runtime storage, finalize exactly once, and fail
closed when support cannot be proved.
CUDA graph and transaction boundary
This PR preserves and verifies registered Parameter/Buffer storage used by the
fast path. It deliberately does not recursively walk arbitrary backend object
graphs. RFC #48478 owns explicit registration and a ModelRunner-level completion
gate for graph-visible backend tensors; draft #48902 is useful as an audit
prototype, not the production discovery contract. PR #48908 owns the common
prepare/finish/abort lifecycle.
This is a zero-copy refit, not a full value rollback. If transport fails after
some bytes have been written, correctness requires the worker to remain
paused/unhealthy until full reload or restart; successful binding validation
cannot reconstruct the previous model without retaining a second copy.
Scope and non-goals
selection correction.
kernel or transport cost.
Related work
vllm-project/vime#340optimizes sender collectives and packed transfer; it ispresent on both benchmark sides.
storage while removing redundant reload work.
layers inside quantized models.
model.load_weightssafe to invoke on already-initialized model #42823 routes repeated raw loading through layerwise reload; this PR optimizesthat pipeline without changing its entry points.
AI assistance was used. The submitter reviewed every changed line and ran the
listed validations.