Repository navigation
cuda : prevent premature eviction of graphs about to be used - #30662
frank-2077 wants to merge 1 commit into
Conversation
|
llama.cpp natively supports single-weight multi-context architecture, enabling natural concurrency handling. However, in high-concurrency scenarios, both CUDA graph capture and instantiate operations exhibit significant non-linear latency growth due to CUDA driver contention. Under these conditions, unnecessarily recreating graphs becomes extremely wasteful — avoiding premature eviction of graphs that are about to be reused is critical for maintaining throughput. |
|
Hi @frank-2077, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
|
@am17an Hi, This addresses one of the issues we've encountered. Other problems may require modifications to the model's graph construction logic. Regarding this specific issue, the fix does resolve the core pain point: when a CUDA graph has been idle for more than 10 seconds and is then requested again, it would previously be evicted, recreated, and instantiated — a completely wasteful process. By excluding the currently requested graph from the cleanup loop, we avoid this unnecessary overhead. |
|
We should instead introduce a new variable Something like this: |
|
FYI I used this PR as an example of modification to trigger full CUDA rebuild. #11582 (comment) |
|
@am17an I’ve run into a few details that I’d like to discuss with you. I considered your proposal and this is the current local implementation. I have not pushed it yet because there are still some open questions about the behavior. The main concern is that a graph that is never used again may still be kept alive if all graphs are considered idle when another graph is requested. Also, checking all graphs on every call adds some overhead. Does this approach match your intended behavior, or would you recommend moving the eviction logic to a higher-level graph-compute entry point? |
Overview
The current CUDA graph cleanup mechanism in PR #21611 introduces a timing issue that causes unnecessary performance degradation in high-concurrency scenarios.
Current behavior in
ggml-cuda/common.cuh:1467-1488:The issue:
If a graph's
last_used_timeis >10s ago, it gets deleted even when we're about to use it in the same function call. This forces expensive re-capture operations (warmup → capture → instantiate).Solution
Exclude the currently requested graph from the cleanup sweep:
This simple check prevents deleting the graph we're actively requesting. The graph's
last_used_timegets updated later in the function, so subsequent cleanups will preserve it correctly.Code Change
File:
ggml/src/ggml-cuda/common.cuh1 line changed - adds a check to skip the currently requested graph during cleanup.
Impact on Production Deployments
Our deployment characteristics make this fix critical:
Profiling Data - Capture & Instantiate Latency Growth (High Concurrency)
Under concurrent load (1→8 concurrent requests), re-capture costs become prohibitive:
1→8 concurrent
1→8 concurrent
(p50: 0.95 → 1.18)
(p50: 3.1)
(p95: 549ms)
Key observation: Some modules see 10-27× latency increase under concurrency, likely due to CUDA driver contention. Avoiding unnecessary re-captures is essential for maintaining throughput.
Benefits
Testing
This fix applies to any high-concurrency deployment where graph access patterns have occasional >10s gaps. We observed this in production serving multimodal workloads with bursty request patterns.
Note: Our workload uses a custom model architecture (Omni multimodal model) on a forked llama.cpp, but this fix applies to any high-concurrency deployment where graph access patterns have occasional >10s gaps.
Requirements