[Misc] Fixes and Optimizations for DeepEP + DeepGEMM combination. - #19298
Conversation
|
👋 Hi! Thank you for contributing to the vLLM project. 💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels. Just a reminder: PRs would not trigger full CI run by default. Instead, it would only run Once the PR is approved and ready to go, your PR reviewer(s) can run CI to test the changes comprehensively before merging. To run CI, PR reviewers can either: Add 🚀 |
There was a problem hiding this comment.
Hello @varun-sundar-rabindranath, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
Summary of Changes
Hello! Gemini here, providing a summary of this pull request to help everyone quickly understand the changes and context.
This PR addresses two specific issues encountered when using the DeepEP and DeepGEMM backends, particularly with high data parallelism and the DeepseekR1 model. The first issue involved Out-of-Memory (OOM) errors during profile runs with dummy zero inputs, which led to unbalanced token distribution across experts and GPUs. The second issue was an assertion failure related to RDMA buffer size when using the low-latency DeepEP backend with high data parallelism. To fix these, the PR introduces randomization for dummy run input IDs to balance expert load, optimizes memory usage in DeepGEMM by reusing preallocated workspaces, and replaces expensive PyTorch operations with potentially more efficient custom ops in the DeepEP high-throughput finalize function.
Highlights
- DeepGEMM Memory Optimization: Modifies the DeepGEMM MOE layer to reuse preallocated workspaces for intermediate results, specifically for the activation output, the second quantization step's output, and the final output after inverse permutation. This reduces overall memory footprint.
- DeepEP High Throughput Optimization: Replaces the
torch.sum(dim=1)operation in the DeepEP high-throughput finalize function with a customops.moe_sumkernel. This aims to improve performance and potentially reduce memory overhead during the final combination step. - New Environment Variable: Adds a new environment variable
VLLM_RANDOMIZE_DP_DUMMY_INPUTSto control whether dummy inputs should be randomized during data parallel dummy runs.
Changelog
Click here to see the changelog
- vllm/envs.py
- Added
VLLM_RANDOMIZE_DP_DUMMY_INPUTSboolean environment variable (default: False) at line 113. - Added logic to parse the
VLLM_RANDOMIZE_DP_DUMMY_INPUTSenvironment variable (checking for '1') at line 765.
- Added
- vllm/model_executor/layers/fused_moe/deep_gemm_moe.py
- Adjusted the calculation for
workspace2size inworkspace_shapestoM_sum * max(N, K)fromM_sum * Nat line 87. - Modified the
applymethod to reuseworkspace13formm1_outandquant_out(viewed as float8) andworkspace2foract_outandmm2_outat lines 140-145. - Replaced the tensor indexing
workspace3[inv_perm, ...]withtorch.index_select(mm2_out, 0, inv_perm, out=out)to perform the inverse permutation directly into the preallocatedouttensor at line 161.
- Adjusted the calculation for
- vllm/model_executor/layers/fused_moe/deepep_ht_prepare_finalize.py
- Imported
vllm._custom_ops as opsat line 8. - Replaced
fused_expert_output.sum(dim=1).to(output_dtype)with creating an output tensoroutand callingops.moe_sum(fused_expert_output, out)at lines 208-211.
- Imported
- vllm/model_executor/layers/quantization/utils/fp8_utils.py
- Added an optional
out_q: Optional[torch.Tensor] = Noneparameter to theper_token_group_quant_fp8function signature at line 314. - Updated the docstring to describe the new
out_qparameter at line 326. - Modified the function to use the provided
out_qtensor if available, otherwise create a new one, at lines 341-344.
- Added an optional
- vllm/v1/worker/gpu_model_runner.py
- Imported
contextmanagerfromcontextlibat line 8. - Imported
vllm.envsat line 16. - Added a new context manager
maybe_randomize_inputsat lines 1726-1753, which randomizesinput_idsifVLLM_RANDOMIZE_DP_DUMMY_INPUTSis true and DP size > 1, and resets them to zeros upon exiting the context. - Applied the
maybe_randomize_inputscontext manager around the_dummy_runcall at line 1835.
- Imported
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Code Review
This pull request introduces crucial fixes and optimizations for DeepEP and DeepGEMM, addressing OOM errors and enhancing memory efficiency. The randomization of dummy inputs is a key fix for expert load balancing, and the memory optimizations in both DeepGEMM and DeepEP modules are well-implemented. The code is clear and the changes are well-justified by the PR description.
Summary of Findings
- Correctness of Workspace Sizing: In
deep_gemm_moe.py, theworkspace2sizing was updated. While the new sizingM_sum * max(N, K)correctly fixes issues whenK > N, a slightly tighter bound could beM_sum * max(N // 2, K)based on its direct usages foract_outandmm2_out. The current approach is safe, however. - Memory Optimizations: Significant memory optimizations were made by reusing tensors (e.g.,
workspace13,workspace2in DeepGEMM,out_qin quantization) and using in-place operations or custom ops (e.g.,mul_andops.moe_sumin DeepEP HT finalize). These are excellent for performance and reducing OOM risks. - Dummy Run Input Randomization: The new
VLLM_RANDOMIZE_DP_DUMMY_INPUTSflag and themaybe_randomize_inputscontext manager effectively address the expert load imbalance OOM during dummy/profile runs. The implementation is clear and robust.
Merge Readiness
The pull request introduces important fixes and optimizations. The changes are well-implemented and address the described issues effectively. After considering the feedback, I believe this PR is in good shape for merging. I am unable to approve the pull request myself, so please ensure it is reviewed and approved by other maintainers before merging.
There was a problem hiding this comment.
The in-place multiplication fused_expert_output.mul_(topk_weights.view(m_x_topk, -1, 1)) is a good optimization, replacing the previous type casting and out-of-place multiplication. This assumes that the dtypes of fused_expert_output and topk_weights are compatible for in-place multiplication, or that PyTorch's type promotion handles it correctly without unexpected side effects. Given this is for performance, this is likely fine, but it's good to be mindful of the dtypes involved. Could you confirm if fused_expert_output's dtype (coming from DeepEP kernels) is consistently compatible with topk_weights (usually float/bfloat16) for this in-place operation?
There was a problem hiding this comment.
topk_weights is float32 - and fused_expert_output is bfloat16 - the multiplication relies on type promotion/demotion.
There was a problem hiding this comment.
The addition of the out_q parameter to allow for in-place quantization is a good memory optimization. The assertion assert out_q is None or out_q.shape == x.shape and the conditional assignment x_q = out_q if out_q is not None else torch.empty_like(...) are correctly implemented. This enhances flexibility and efficiency.
There was a problem hiding this comment.
The maybe_randomize_inputs context manager correctly randomizes input_ids for dummy runs when the VLLM_RANDOMIZE_DP_DUMMY_INPUTS flag is set. The use of torch.randint_like and slicing ensures appropriate random token IDs are generated and copied.
Restoring input_ids to zeros with input_ids.fill_(0) after the yield is crucial for ensuring that subsequent operations or dummy runs (if randomization is off or for other purposes) start with a clean, expected state. This is well handled.
There was a problem hiding this comment.
It is incorrect to do this when we are doing profile runs -- during profile runs, we do want the system to be stress tested (i.e. all tokens reaching the same set of GPU ranks).
However, at the moment, without this we are not able to run large scale DP -- things OOM during profile_run.
#19168 should fix the OOM - then we can remove this logic for the profile run case.
There was a problem hiding this comment.
This could be optimized. We dont have to fill the input_ids and then set it to zeros every time. For eager-mode runs (i.e. batch size > 512) - we could just use the rand tensor in the place of input-ids .. I plan to do this in a follow up PR.
Head branch was pushed to by a user without write access
There was a problem hiding this comment.
memory optimization to prevent inv_perm from making a brand-new tensor.
There was a problem hiding this comment.
Is DP important here? I think you would want this for any EP case, so maybe just VLLM_RANDOMIZE_DUMMY_INPUTS
There was a problem hiding this comment.
Investigated it for a bit and I think it is better to call out DP in the name. It is only in the context of DP that some DP Ranks execute dummy-runs so we can synchronize with the DP Ranks that run the model with actual tokens.
— The other way we could do expert parallel is with DP=1 and TP > 1 - with this, all the ranks run with actual data (the input data is replicated across all ranks)
also, I have this statement in code,
randomize_inputs = envs.VLLM_RANDOMIZE_DP_DUMMY_INPUTS and dp_size > 1
But I see what you are saying, we could do,
VLLM_RANDOMIZE_DUMMY_INPUTS -> VLLM_RANDOMIZE_DUMMY_INPUTS and randomize_inputs = envs.VLLM_RANDOMIZE_DP_DUMMY_INPUTS and randomize if the env var is just set.
let's do it when more use cases for randomizing dummy runs come up ? What do you think ?
Signed-off-by: Varun <vsundarr@redhat.com>
Signed-off-by: Varun <vsundarr@redhat.com>
Signed-off-by: Varun <vsundarr@redhat.com>
Signed-off-by: Varun <vsundarr@redhat.com>
Signed-off-by: Varun <vsundarr@redhat.com>
Signed-off-by: Varun <vsundarr@redhat.com>
Signed-off-by: Varun <vsundarr@redhat.com>
Signed-off-by: Varun <vsundarr@redhat.com>
a35027a to
edd27de
Compare
|
^ rebase on to main |
…lm-project#19298) Signed-off-by: Varun <vsundarr@redhat.com> Co-authored-by: Varun <vsundarr@redhat.com>
…lm-project#19298) Signed-off-by: Varun <vsundarr@redhat.com> Co-authored-by: Varun <vsundarr@redhat.com>
…lm-project#19298) Signed-off-by: Varun <vsundarr@redhat.com> Co-authored-by: Varun <vsundarr@redhat.com>
Purpose
Issue1:
The engine fails to initialize with DeepseekR1 + data-parallel-size 32 + expert-parallel + VLLM_ALL2ALL_BACKEND="deepep_high_throughput" + VLLM_USE_DEEP_GEMM=1
Cause:
During profile runs we execute model forward pass on all GPUs with maximum batch size. We do this with dummy input_ids all set to zeros. This has an effect of routing all tokens from all GPUs to a specific set of experts / GPU Ranks in the All2All dispatch call. The GPU receiving all the tokens has an enormous batch-size to process. The GPUs OOM as a result.
Issue2:
The engine fails to initialize with DeepseekR1 + data-parallel-size 128 + expert-parallel + VLLM_ALL2ALL_BACKEND="deepep_low_latency" + VLLM_USE_DEEP_GEMM=1
Cause:
RuntimeError: Failed: Assertion error /mnt/data/home/smo/vllm/tools/ep_kernels/ep_kernels_workspace/DeepEP/csrc/deep_ep.cpp:1040 'layout.total_bytes <= num_rdma_bytes'Issue 3:
CUDA illegal memory access in FP8 block-quant triton kernel
Cause:
Integer overflow issues in the block-quant triton kernel.
Changes / Fixes:
num_rdma_byteshintTest Plan
Tested locally with :
and
Test Result