Repository navigation
[Bugfix][Memory] Accurately profile CUDA graph memory for quantized/F… - #60673
Aravind0403 wants to merge 1 commit into
Conversation
…P8 KV caches (vllm-project#60350) When using quantized KV caches (such as fp8, int8), the block table and dequantization scale allocations per CUDA graph allocate additional memory during capture. Previously, _extrapolate_full_graph_memory used a 1 MiB per-graph floor, which under-budgeted full graph capture overhead by ~0.8 GiB across ~30 graphs and triggered out-of-memory errors during engine startup capture on 24GB GPUs. This commit: 1. Introduces _MIN_QUANTIZED_PER_GRAPH_BYTES = 16 << 20 (16 MiB) floor when is_quantized_kv_cache(kv_cache_dtype) is true. 2. Propagates kv_cache_dtype from profile_cudagraph_memory to _extrapolate_full_graph_memory. 3. Adds regression unit tests covering quantized vs non-quantized extrapolation in test_gpu_model_runner_v2_cudagraph_profiling.py. Fixes vllm-project#60350. Signed-off-by: aravindsundaresan <aravindsharma20@gmail.com>
|
👋 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. 🚀 |
lucamotz
left a comment
There was a problem hiding this comment.
I’m not sure this is the right approach to fixing the underlying issue. If I understand correctly, increasing the minimum estimated memory per CUDA graph to 16 MiB would permanently reduce the available KV cache allocation for quantized KV cache configurations, even if the actual CUDA graphs don’t require that much additional memory.
My main concern is that we’re introducing a fairly arbitrary buffer here rather than fixing the underlying memory profiling. This effectively trades KV cache capacity for additional safety margins, which I don’t think is a great solution, especially since the actual overhead likely depends on the configuration.
I’d rather understand why we’re underestimating CUDA graph memory in the first place and fix the profiling to account for the actual allocations. Otherwise we’re potentially wasting a significant amount of GPU memory without knowing whether the 16 MiB is even sufficient or necessary.
Is there a reason we can’t address this in the profiling itself?
|
Thanks @lucamotz, fair critique. I measured on an RTX 4090 (24 GB, CUDA 13.0, PyTorch 2.13) with
On this model, the 1 MiB floor under-budgets for both dtypes, not just FP8. That supports your point: a static 16 MiB floor is an arbitrary constant and doesn't address why the samples collapse. Two ways forward:
If you prefer Option 1, I'm happy to rework this PR into that and validate it thoroughly on the RTX 4090. Let me know which direction you'd like! |
lucamotz
left a comment
There was a problem hiding this comment.
I don't think those are the right approaches. #56492 already covers this long term, and if we really wanted a fix today, I think this would be the way to go:
--- a/vllm/v1/worker/gpu/cudagraph_utils.py
+++ b/vllm/v1/worker/gpu/cudagraph_utils.py
@@ -488,4 +488,6 @@
if self._capture_mem_samples is not None:
torch.accelerator.synchronize()
+ # torch.cuda.graph() empties the cache on entry.
+ torch.accelerator.empty_cache()
free_before = torch.accelerator.get_memory_info()[0]
with (torch.cuda.graph() calls empty_cache() on entry, so the memory cached by the warmup forward is currently released inside the sampled window. That would explain the negative samples, for BF16 as well. MRV1 reads the free memory before the warmup and doesn't have this.
But in general, I'm not sure this issue even exists in the way it's being described. It was run on v0.28.0, where MRV2 didn't reserve any CUDA graph memory yet (#53306 added that in v0.29.0), and it seems to be a bit of an edge case anyways. I would want to see the OOM reproduced on current main first. Otherwise I think closing this in favour of #56492 is the right way to go here.
|
Thanks @lucamotz! I just verified your hypothesis on the 24GB RTX 4090:
Given that the original issue was tied to v0.28.0 before graph memory reservation existed, and #56492 (extensible KV cache) dynamically manages graph and cache allocation, I'll close this in favor of #56492. Thanks for the great review and the deep dive into the allocator mechanics! |
[Bugfix][Memory] Accurately profile CUDA graph memory for quantized/FP8 KV caches (#60350)
When using quantized KV caches (such as fp8, int8), the block table and dequantization scale allocations per CUDA graph allocate additional memory during capture. Previously, _extrapolate_full_graph_memory used a 1 MiB per-graph floor, which under-budgeted full graph capture overhead by ~0.8 GiB across ~30 graphs and triggered out-of-memory errors during engine startup capture on 24GB GPUs.
This commit:
Fixes #60350.
Overview
Accurately budgets CUDA graph memory for quantized/FP8 KV caches during memory profiling, preventing startup OOM errors during
capture_model(). Fixes #60350.Claims
_extrapolate_full_graph_memoryacross both standard and quantized (fp8,fp8_e4m3) cache dtypes.Validation
Details
Root Cause
During worker initialization, profile_cudagraph_memory() samples the memory of the largest FULL CUDA graphs into a throwaway pool and extrapolates the total capture memory with _extrapolate_full_graph_memory().
Previously, _extrapolate_full_graph_memory floored per-graph overhead at _MIN_PER_GRAPH_BYTES = 1 << 20 (1 MiB). While sufficient for 16-bit unquantized KV caches, quantized caches (fp8, int8) hold smaller element sizes, creating
2×2× more total blocks. During graph capture, allocating auxiliary scale factors and binding larger block tables consumes ~16–30 MiB per captured graph. For a typical batch schedule with ~30 full graphs, the 1 MiB floor underestimated graph capture memory by ~0.8 GiB, leaving insufficient headroom and triggering torch.OutOfMemoryError during the real capture_model() phase.
Design Decisions
_MIN_QUANTIZED_PER_GRAPH_BYTES = 16 << 20 (16 MiB): Applied when is_quantized_kv_cache(kv_cache_dtype) evaluates to true.
Plumbing: profile_cudagraph_memory extracts kv_cache_dtype from runner.cache_config.cache_dtype and passes it to _extrapolate_full_graph_memory.
Pull Request Checklist
I used vLLM's /pr-checklist skill. (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.