Repository navigation
[Perf][Qwen4Exp] Route the PLE shard copy through copy_weight_ - #65692
Conversation
|
👋 Hi! Thank you for contributing to the vLLM project. 💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment Once the PR is approved or has the If you have any questions, please reach out to us on Slack at https://slack.vllm.ai. Agent GuidelinesIMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban. 🚀 |
Willian-Zhang
left a comment
There was a problem hiding this comment.
Thanks @JimmyToluene! Loading the PLE table on GB10 dropping from ~177 s to ~11 s is exactly what we hoped unified memory could do once the page faults moved out of the driver copy. Great to see it land end to end. 🚀
Nit: the old line wasn't a plain copy_ either. .to(device=…) made a temporary GPU tensor first, so this also drops that on discrete GPUs. Worth a sentence in the description.
LGTM once test_ple.py passes in CI.
PLEVocabParallelEmbedding._copy_embedding_shard_ copies each checkpoint shard with `target.copy_(source.to(device=...))`, straight from the safetensors mmap view. With a device-resident table on an integrated GPU (the packed-NVFP4 table from vllm-project#56273 with `cpu_offload: false` on a DGX Spark), the page faults land inside the driver copy and the 28.8 GB table loads at ~0.2 GB/s. copy_weight_ from vllm-project#58868 faults the pages in on the CPU first; elsewhere it is the same copy_. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: jimmyjia <jimmytoluene@gmail.com>
437e4ed to
6605fa9
Compare
|
/ci run |
|
❌ @JimmyToluene, A reviewer with write access must run |
Thanks @Willian-Zhang! I added the temporary GPU tensor point to the description; for a same-dtype source the temporary is gone, and a dtype-converting copy now stages through the CPU. I also rebased onto
|
|
/ci run |
|
✅ Triggered Buildkite CI #100119 for commit ℹ️ This PR is 1 commit behind upstream |
|
✅ @JimmyToluene, CI is now available for this PR.
|
|
Correction to my comment above: I measured this on a GB10, and the temporary GPU tensor is gone with or without a dtype conversion. The CPU staging isn't new. I updated the description. Thanks @Willian-Zhang and @gau-nernst for the reviews and for getting this merged! |
Overview
Routes the Qwen4Exp PLE shard copy through
copy_weight_, the follow-up named in #58868. Fixes #58726.@Willian-Zhang, as you asked in #58868.
Not a duplicate: when this was opened, no other open PR mentioned #58726 or changed
ple.py. #65713 now applies the same swap to other loaders and doesn't touch this file. The open PLE storage PRs (#58439, #58815, #62847) avoid or stage this copy for offloaded tables; this one speeds up the existing device-resident path.Claims
cpu_offload: false), mainLoading weights tookfalls from about 213 s to about 76 s from a cold page cache, and start to/healthfrom about 320 s to about 184 s.copy_weight_is a plaincopy_. A dtype-converting copy skips the touch..to(device=…)before copying. This drops it, with or without a dtype conversion: in a small GB10 test, extra peak GPU memory went from the target's size to 0. I did not measure discrete GPUs.Validation
End to end, this branch.
vllm serveon one GB10,Qwen3.8-Flash-Next-NVFP4-LIL-qad5500, local NVMe, TP=1, four boots in the order old, new, new, old. Onlyple.pydiffered. The checkpoint was evicted withposix_fadvisebefore each boot, andmincorereported 0% resident.Loading weights took/healthcopy_weight_, run 1copy_weight_, run 2Output. Against the code it replaces, per
tests/AGENTS.md: 16 fixed prompts withtemperature=0andseed=0. All 566 prompt-token logprobs and all 16 48-token greedy completions were exactly equal across all four boots.Model evaluation not run.
tests/evals/qwen4_exptargets Qwen3.8-Flash-Next-FP8 with--tensor-parallel-size 4on H200/B200, which I don't have. This change does not alter the loaded weights, as the exact comparison above and the table check below show.Table copy alone, this branch. The 128
weightand 128weight_scaleshards (28.8 GB), each copied by the real_copy_embedding_shard_into a GPU tensor of the shard's dtype, cold cache, two runs per arm. The old line took 176.8 s and 178.9 s;copy_weight_took 11.4 s and 10.2 s. Both tables compared equal withtorch.equalafter every run.Rebase. The boots and table copy above ran just before a final rebase onto current
main, which changed neitherple.pynormodel_executor/utils.py. The tests below were rerun after it.Earlier result. The 160 s → 14 s figure for the table within a stock nightly boot is in this comment on #58868.
Tests, from
.venvperAGENTS.md, on the GB10 with its GPU:test_ple.py: 127 passed, 2 skipped.test_utils.py: 32 passed.pre-commit: passed.Serve command and environment
The host has no
nvcc, so serving ran invllm/vllm-openai:nightly-e4a5b5537344(torch 2.14.0+cu130) with this tree onPYTHONPATH. The baseline bind-mounted the previousple.pyover it.Autotune was off because, with the model loaded, it took this machine down to 1 GiB available.
Details
Root cause:
_copy_embedding_shard_copied each shard withtarget.copy_(source.to(device=target.device, dtype=target.dtype)), straight from the safetensors mmap view. For a same-dtype source, the page faults land inside the driver copy, which #58726 measured as several times slower than faulting on the CPU.Scope:
load_weightsinnvidia/ngram_embedding.pysends split table shards throughweight_loaderto this copy. Disk-offloaded tables takeaccept_checkpoint_shardinstead. Thebuffer.copy_in the same function only handles the small hash buffers, so I left it alone.Other backends:
amd/ple_layer.pyreaches the same copy.copy_weight_only touches pages whenis_integrated_gpuis true, and otherwise runs a plaincopy_. I only ran this on a GB10.Tradeoffs: this is load-time only, so steady-state inference and CPU/GPU synchronization are untouched. The touch reads the pages it is about to copy, and the lowest available memory during boot was 4–5 GiB in both arms.
CI coverage: CI has no integrated GPU, so it can't exercise the touch path here.
test_ple.pyruns on H200 and MI355 throughmodels_basic.yaml, and #58868 tests the helper with the gate forced on. I added no test because this is one line of wiring. Validation outside CI is above.AI assistance (Claude) was used for the measurements and this write-up; I reviewed the change and the results.
Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.