perf: accelerate colocated and non-colocated vLLM weight updates#340
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a pipeline parallel (PP) broadcast fast path for weight updates, allowing raw-mode pipeline stages to be converted in parallel and broadcast as packed HF chunks across colocated trainer ranks before CUDA IPC transfer. It adds new CLI arguments (--vllm-weight-update-max-inflight and --vllm-colocate-pp-broadcast), updates the weight update logic to support packed metadata, and renames variables in rollout.py for clarity. Feedback on the changes highlights a critical issue in the PP broadcast implementation where dist.broadcast is called with a global rank instead of the relative group rank for the src parameter, which could lead to out-of-bounds errors or incorrect broadcasting.
| for source_rank in pp_ranks: | ||
| for chunk_index, metadata in enumerate(metadata_by_global_rank[source_rank]): | ||
| if rank == source_rank: | ||
| _, packed_tensor = local_chunks[chunk_index] | ||
| local_chunks[chunk_index] = None | ||
| else: | ||
| packed_tensor = torch.empty( | ||
| sum(metadata["tensor_sizes"]), | ||
| dtype=torch.uint8, | ||
| device=torch.cuda.current_device(), | ||
| ) | ||
| dist.broadcast(packed_tensor, src=source_rank, group=pp_group) |
There was a problem hiding this comment.
In PyTorch distributed, when calling collective operations like dist.broadcast with a custom process group (group=pp_group), the src argument must be the relative rank (group rank) of the source process within that process group, not its global rank.
Using the global rank (source_rank) will cause out-of-bounds rank errors or incorrect broadcasting behavior if the pipeline parallel group ranks do not start at 0 or are not contiguous global ranks.
Since pp_ranks is the list of global ranks ordered by their rank within pp_group, the index of source_rank in pp_ranks is exactly its group rank. We can use enumerate(pp_ranks) to get both the group rank (src_group_rank) and the global rank (source_rank), and pass src=src_group_rank to dist.broadcast.
| for source_rank in pp_ranks: | |
| for chunk_index, metadata in enumerate(metadata_by_global_rank[source_rank]): | |
| if rank == source_rank: | |
| _, packed_tensor = local_chunks[chunk_index] | |
| local_chunks[chunk_index] = None | |
| else: | |
| packed_tensor = torch.empty( | |
| sum(metadata["tensor_sizes"]), | |
| dtype=torch.uint8, | |
| device=torch.cuda.current_device(), | |
| ) | |
| dist.broadcast(packed_tensor, src=source_rank, group=pp_group) | |
| for src_group_rank, source_rank in enumerate(pp_ranks): | |
| for chunk_index, metadata in enumerate(metadata_by_global_rank[source_rank]): | |
| if rank == source_rank: | |
| _, packed_tensor = local_chunks[chunk_index] | |
| local_chunks[chunk_index] = None | |
| else: | |
| packed_tensor = torch.empty( | |
| sum(metadata["tensor_sizes"]), | |
| dtype=torch.uint8, | |
| device=torch.cuda.current_device(), | |
| ) | |
| dist.broadcast(packed_tensor, src=src_group_rank, group=pp_group) |
2337eb0 to
281886f
Compare
bee245a to
fcf4740
Compare
Signed-off-by: aoshen02 <aoshen@inferact.ai>
fcf4740 to
93c589a
Compare
Summary
This branch is based on current
main, including the vLLM 0.25.1 / CUDA 13 image update in #353, and contains one focused commit. The stale rollout naming and Qwen3-Next quantization changes previously carried by this PR have been removed.Design
For colocated CUDA IPC, a vLLM worker selects its handle by physical GPU UUID. Every trainer rank in a colocated engine slot therefore needs to expose the same complete HF metadata stream; sending only the local PP stage is not correct unless inference PP is explicitly aligned with training PP.
The raw iterator preserves the general path: PP broadcasts restore stage-owned parameters, EP broadcasts restore expert shards, and TP gathers reconstruct tensor shards before HF conversion. TP tensors are gathered in group/dtype buckets. EP tensors are coalesced by source, with the mapping from original global source rank to the current EP group's local root computed once from PP group membership. This supports mixed TP/PP/EP/DP topologies without topology-specific branches in the hot path.
Each converted chunk is packed into one byte tensor and one CUDA IPC handle per trainer GPU. The historical packed/non-packed CLI switch and legacy IPC payload are removed; transport format is now an internal invariant, matching Slime’s unconditional
FlattenedTensorBucketcolocated path. Up to four chunk updates remain in flight internally; the window is implementation backpressure rather than a user-facing tuning parameter. Tensors remain alive until vLLM has closed the IPC handles.The non-colocated path keeps its existing per-PP-stage ownership and now uses packed vLLM transfer for both dense and expert chunks. Expert parameters are bucketed by parent layer so converter calls remain atomic.
H200 validation
All tests used
vllm/vime:nightly-dev-20260715c(vLLM 0.25.1, PyTorch 2.11.0+cu130) on one 8xH200 node.Performance A/B
Moonlight-16B-A3B FP8, trainer TP4/EP2, four rollout engines at TP2/EP2:
main(ee16693cd, code-equivalent baseline at6cefd8463)All 240
/update_weightsrequests returned HTTP 200, rollout completed, and training completed. The optimized timing matches the original PR implementation while using a smaller, general design.The baseline and this PR both later hit the same Transformer Engine FP8 checkpoint-save issue (
extra_fp8_variables=None), after training and independently of weight transfer. FP8 train/rollout logprob differences are also large on both revisions, so BF16 was used for the correctness checks below.Correctness and topology
train_rollout_logprob_abs_diff = 0.013537; job succeeded.train_rollout_logprob_abs_diff = 0.0188007; job succeeded.Both logprob differences are below the CI threshold of 0.1. The 30B run specifically exercises PP-to-EP source remapping with real MoE shards.
Buildkite A/B
This compares the vLLM 0.25.1 image PR (Buildkite #669,
fcdd0db8) with this PR (Buildkite #685,93c589ade). Update timings use onlyTimer update_weights end, excluding queueing, model downloads, and initialization. Tests with multiple training steps report their mean.Across the 56 directly comparable weight updates, total time fell from 751.2 s to 308.4 s (-58.9%, 2.44x faster). All 19 common tests that emit an update timer were non-regressing. Across 33 common logprob samples, mean
train_rollout_logprob_abs_diffstayed effectively unchanged (0.0161984to0.0161861), while the maximum decreased from0.0526013to0.0496524.Two suites are new in #685 and have no #669 baseline:
Every logprob result remains well below the CI threshold of 0.1.
Validation
tests/utils: 165 passedtorch.distributed._broadcast_coalescedexpects a group-local root rankgit diff --check: passed93c589adeDiff reduction
Related work
AI assistance was used. The submitter reviewed every changed line and ran the listed tests and model evaluations.