Skip to content

[Elastic EP] Reuse CUDA graphs across reconfiguration - #54985

Merged
tlrmchlsmth merged 4 commits into
vllm-project:mainfrom
itayalroy:eep_cuda_graph_reuse
Sep 12, 2026
Merged

tlrmchlsmth merged 4 commits into
vllm-project:mainfrom
itayalroy:eep_cuda_graph_reuse

Conversation

@itayalroy

@itayalroy itayalroy commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Async preparation and eager-mode optimizations moved most Elastic EP steps out of the blocking commit path, but CUDA graph mode still blocked serving during kernel warmups and CUDA graph capture on every reconfiguration. This PR removes that work from commit as follows:

  1. Existing ranks reuse their fused-MoE runtime and CUDA graphs. NIXL EP preserves its graph entries across reconfiguration, while graph-facing tensors and buffers (such as EPLB mapping and dispatch output buffer) keep the same memory addresses.
  2. New ranks warm up and capture during preparation. Existing ranks keep serving. During new-rank warmup and capture, NIXL EP dispatch and combine mask all remote peers, DP batch coordination is skipped, and FlashInfer tuning results are copied from existing ranks.

CUDA graph reuse is currently only enabled for NIXL EP. Other backends keep the existing recapture path.

With this PR, we consider Elastic EP reconfiguration downtime solved.

Results

We tested full-size deepseek-ai/DeepSeek-V3 with native FP8 on 24 GB200 GPUs across six nodes, scaling DP/EP 16 -> 24 -> 16 under heavy traffic. The test used vLLM defaults for model length, maximum sequences, and CUDA graph capture. Each revision ran once to warm its torch compile caches and once for measurement on the same allocation. Accuracy passed before and after both reconfigurations.

Downtime is the interval in which existing requests do not progress and new requests are rejected.

Direction Upstream downtime This PR Reduction
16 -> 24 40.266 s 1.019 s 97.5%
24 -> 16 38.320 s 1.895 s 95.1%

Total reconfiguration time covers background preparation and commit.

Direction Upstream total This PR Reduction
16 -> 24 79.571 s 72.324 s 9.1%
24 -> 16 45.531 s 9.406 s 79.3%

Effect of Max-Capacity Input on Expert Kernel Performance

To keep CUDA graphs stable, the dispatch output buffer is sized for the maximum EP capacity. Padding exists without Elastic EP, but reserving maximum capacity increases the amount of padding. DeepGEMM and FlashInfer CuteDSL avoid padded work and are effectively unaffected. Batched Triton and CUTLASS process more of the padded buffer (for example, Triton does not support masked activations), so Elastic EP amplifies their inefficiency. With active EP fixed at 16, the table compares capacity 64 against capacity 16:

Expert implementation Decode throughput change Prefill throughput change
Batched DeepGEMM 0.08% higher 0.97% lower
FlashInfer CuteDSL 1.21% higher 0.89% lower
Batched Triton 17.58% lower 13.08% higher
Batched CUTLASS FP8 77.49% lower 63.90% lower

cc @tlrmchlsmth @SageMoore @ilmarkov

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: 29c712df-8047-4ac2-aa54-6441be304521

📥 Commits

Reviewing files that changed from the base of the PR and between 07cafd8 and 1bf454e.

📒 Files selected for processing (2)
  • vllm/v1/worker/dp_utils.py
  • vllm/v1/worker/gpu/dp_utils.py

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Summary

Summary by CodeRabbit

  • New Features

    • Added --elastic-ep-max-dp-size to configure the maximum data-parallel size for elastic expert parallelism.
    • Elastic expert parallelism now supports dynamic expert capacity and smoother worker scaling.
    • CUDA graphs, fused MoE kernels, and FlashInfer tuning data can be reused or synchronized during NIXL scaling.
  • Bug Fixes

    • Added validation to prevent scaling beyond the configured maximum.
    • Improved expert mapping and buffer handling during reconfiguration.
  • Configuration

    • Removed the legacy NIXL maximum-ranks environment variable; use the new command-line option instead.

Walkthrough

Elastic EP now accepts a configured maximum data-parallel size. NIXL EP derives capacity from this value, stores EPLB state in preallocated buffers, and reuses fused MoE kernels and CUDA graphs during compatible rank changes.

Changes

Elastic EP reconfiguration

Layer / File(s) Summary
Elastic EP capacity configuration
requirements/kv_connectors.txt, vllm/config/parallel.py, vllm/engine/arg_utils.py, vllm/envs.py, vllm/model_executor/layers/fused_moe/config.py, vllm/model_executor/layers/fused_moe/layer.py, vllm/model_executor/layers/fused_moe/utils.py, vllm/v1/engine/core_client.py
Adds and validates elastic_ep_max_dp_size, exposes it through the CLI, propagates it to MoE configuration, and removes the NIXL environment-variable path.
Storage-backed EPLB and NIXL lifecycle
vllm/distributed/eplb/eplb_state.py, vllm/distributed/device_communicators/*, vllm/model_executor/layers/fused_moe/all2all_utils.py, vllm/model_executor/layers/fused_moe/eep_reconfigure.py, vllm/model_executor/layers/fused_moe/modular_kernel.py, vllm/model_executor/layers/fused_moe/prepare_finalize/nixl_ep.py, tests/distributed/test_eplb_utils.py
Uses preallocated EPLB storage and adds EP-size staging, commit, rank masking, and configuration-derived NIXL capacity.
Kernel reuse and scaling flow
vllm/distributed/elastic_ep/elastic_execute.py, vllm/distributed/elastic_ep/elastic_state.py, vllm/model_executor/warmup/flashinfer_autotune_cache.py, vllm/v1/worker/dp_utils.py, vllm/v1/worker/gpu/dp_utils.py, tests/distributed/test_elastic_ep.py
Reuses fused MoE kernels during NIXL EP scaling, synchronizes FlashInfer autotune state, updates worker warmup, and expands scaling test coverage.

Estimated code review effort: 4 (Complex) | ~60 minutes

Merge Risk: ⚪ Minimal · up to 1bf45

The change improves Elastic EP reconfiguration by reusing NIXL CUDA execution state, with no substantiated merge-blocking correctness or availability issue remaining.

Sequence Diagram(s)

sequenceDiagram
  participant DPLBAsyncMPClient
  participant ElasticEPScalingExecutor
  participant NixlEPAll2AllManager
  participant EplbState
  participant FlashInferAutotune
  DPLBAsyncMPClient->>ElasticEPScalingExecutor: prepare_elastic_ep(new_data_parallel_size)
  ElasticEPScalingExecutor->>NixlEPAll2AllManager: stage_ep_size()
  ElasticEPScalingExecutor->>FlashInferAutotune: sync_flashinfer_autotune_cache()
  ElasticEPScalingExecutor->>EplbState: reconfigure_physical_expert_slots()
  ElasticEPScalingExecutor->>NixlEPAll2AllManager: commit_ep_size()
  ElasticEPScalingExecutor->>ElasticEPScalingExecutor: warmup_new_worker()
Loading

Suggested reviewers: theepicdolphin

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.83% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 72 functions across 19 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the CUDA graph reuse changes, the NIXL EP scope, the preparation and commit behavior, performance results, and capacity trade-offs.
Title check ✅ Passed The title clearly identifies the main change: reusing CUDA graphs across Elastic EP reconfiguration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mergify

mergify Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @itayalroy.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Sep 5, 2026
@itayalroy
itayalroy force-pushed the eep_cuda_graph_reuse branch from 7ce6dc8 to 07cafd8 Compare September 5, 2026 13:00
@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@mergify mergify Bot removed the needs-rebase label Sep 5, 2026
Signed-off-by: Itay Alroy <ialroy@nvidia.com>
Signed-off-by: Itay Alroy <ialroy@nvidia.com>
@almogtavor

Copy link
Copy Markdown
Contributor

@itayalroy hi looks great, just one more thing when reading it through, I think this can go OOB with round-robin after scale-up. E.g. if before scaling global_to_physical has entries for physical expert IDs [0..7], then EP scales up and the new mapping adds experts [8..15], but the reused NIXL object still holds the old 8-entry tensor.

If EPLB now returns physical expert ID 10, NIXL does self.global_to_physical[10] on that old tensor and that’s an OOB access, wdyt?

@itayalroy

itayalroy commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

@itayalroy hi looks great, just one more thing when reading it through, I think this can go OOB with round-robin after scale-up. E.g. if before scaling global_to_physical has entries for physical expert IDs [0..7], then EP scales up and the new mapping adds experts [8..15], but the reused NIXL object still holds the old 8-entry tensor.

If EPLB now returns physical expert ID 10, NIXL does self.global_to_physical[10] on that old tensor and that’s an OOB access, wdyt?

Elastic EP requires EPLB, which does not support round-robin expert placement, so global_to_physical is always None

@SageMoore SageMoore left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @itayalroy!

Comment thread vllm/v1/worker/gpu/dp_utils.py Outdated
tensor[4][dp_rank] = int(allow_ubatching)
tensor[5][dp_rank] = num_reqs
dist.all_reduce(tensor, group=group)
if is_uniform_dp_batch():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check this in coordinate_batch_across_dp and short-circuit there instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For MRv1, we already short-circuit inside coordinate_batch_across_dp. MRv2 does not use that function, its equivalent is sync_cudagraph_and_dp_padding, which is the current function, so we need to short-circuit here as well.

MRV2 does not support EEP yet, but support will be added soon with #53934, so I have already tested CUDA graph reuse on top of that PR to ensure we will not need a follow-up PR to support MRv2 + CUDA graph reuse.

Comment thread vllm/distributed/elastic_ep/elastic_execute.py Outdated
Comment thread vllm/v1/worker/dp_utils.py Outdated
if new_dp_size > old_dp_size and self._can_reuse_fused_moe_kernel():
target_world_group = get_standby_world_group()
assert target_world_group is not None
sync_flashinfer_autotune_cache(self.worker.model_runner, target_world_group)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to call this on all ranks or just new ones?

@itayalroy itayalroy Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This must run on all ranks because it broadcasts over the target world group, which includes both existing and new ranks. We could create a separate group or p2p flow containing only the new ranks, but that would add quite a bit of complexity, and I don’t think avoiding a small CPU broadcast to existing ranks during preparation is worth it

Comment thread requirements/kv_connectors.txt
Comment thread vllm/distributed/eplb/eplb_state.py
Comment thread vllm/distributed/eplb/eplb_state.py Outdated
Comment thread vllm/distributed/eplb/eplb_state.py Outdated
Comment thread vllm/distributed/eplb/eplb_state.py
Signed-off-by: Itay Alroy <ialroy@nvidia.com>
@itayalroy
itayalroy requested a review from SageMoore September 11, 2026 14:01
@github-project-automation github-project-automation Bot moved this to Ready in NVIDIA Sep 11, 2026
@tlrmchlsmth

Copy link
Copy Markdown
Member

Great work!

@tlrmchlsmth

Copy link
Copy Markdown
Member

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #88396 for commit 8fc61e3c1562.

@itayalroy

Copy link
Copy Markdown
Contributor Author

/ci retry

@github-actions

Copy link
Copy Markdown

✅ The previous CI build is still running: https://buildkite.com/vllm/ci/builds/88396

@itayalroy

Copy link
Copy Markdown
Contributor Author

/ci retry

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #88494 for commit 4068e19f3793, running 9 failed step(s) from Buildkite CI #88396.

@tlrmchlsmth
tlrmchlsmth merged commit 658c813 into vllm-project:main Sep 12, 2026
33 checks passed
@github-project-automation github-project-automation Bot moved this from Ready to Done in NVIDIA Sep 12, 2026
almogtavor added a commit to almogtavor/vllm that referenced this pull request Sep 12, 2026
Existing NIXL EP ranks keep their CUDA graphs across a reconfigure after
vllm-project#54985 and no longer re-warm at commit, so MRV2 only drains when the
backend re-warms. warm_and_capture keeps the NIXL warmup contexts and the
runner serving state context manager in one with statement.

Signed-off-by: almogtavor <almogtavor@gmail.com>
Shreya-gaur pushed a commit to Shreya-gaur/vllm_private that referenced this pull request Sep 14, 2026
ItsRoy69 pushed a commit to ItsRoy69/vllm that referenced this pull request Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/build mrv2 Model Runner V2 specific nvidia

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants