Skip to content

cuda: grow the CUDA graph cache cap while it evicts graphs still in use - #238

Open
LeoBorcherding wants to merge 2 commits into
unslothai:mtp/qwen4exp-nextnfrom
LeoBorcherding:fix/cuda-graph-cap-grow
Open

LeoBorcherding wants to merge 2 commits into
unslothai:mtp/qwen4exp-nextnfrom
LeoBorcherding:fix/cuda-graph-cap-grow

Conversation

@LeoBorcherding

@LeoBorcherding LeoBorcherding commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Fixes the tensor split decode regression in unslothai/unsloth#12468 (2.4x to 4.3x slower since b10715-mix-86bd2d3).

max_cuda_graphs = 64 from #144 is below the working set in --split-mode tensor. That mode needs about 2 x n_layers + 1 graphs per device per shape, so any model past ~31 layers evicts every graph before its next use and decode runs with no graph reuse. Single GPU and layer split need 2 to 4 graphs and are unaffected.

Instead of a new fixed number, the cap starts at 64 and doubles, up to 2048, whenever the LRU victim was used within the last second. That only happens when the cap is below the working set. The existing 10 s sweep still drops stale entries, and the hard limit keeps the many-shapes case #144 added the cap for bounded.

The patch and every number below are floewe's, from unslothai/unsloth#12468. The only change from their diff is static constexpr on the limit, because std::min takes it by reference and a static const member without an out-of-class definition can fail to link at -O0.

Measured by floewe on this head (5df4722)

2x RTX 5070 Ti 16 GB, WSL2 Ubuntu 24.04, driver 616.92, CUDA 13.3, built from this branch. Baseline is the b10715-mix-86bd2d3 prebuilt. Three 600-token requests per row (full log in unslothai/unsloth#12468).

model mode b10715 this PR
Qwen3.8-27B, MTP tensor split 34 / 32 / 29 94 / 102 / 90
Qwen3.8-27B tensor split 14 / 14 / 14 61 / 67 / 67
Qwen3.8-27B, MTP layer split 77 / 78 / 83 80 / 85 / 84
Qwen3.8-27B, MTP single GPU 89 / 87 / 97 92 / 86 / 85
gemma-4-31B-it tensor split 18 / 17 / 17 57 / 66 / 62
gemma-4-31B-it layer split 45 / 45 / 46 45 / 45 / 47
gemma-4-31B-it single GPU 47 / 48 / 46 47 / 47 / 46

With a counter build: the cap grew to 512 for Qwen and 1024 for Gemma (727 live graphs at the prompt-to-decode transition), and stayed at 64 for layer split and single GPU. No graph used within the last second was ever evicted. The 2048 limit was never reached, so the warning didn't fire.

Relation to #232

#232 raises the fixed cap to 512. That covers steady-state decode for the three models above, but not Gemma's 727 live graphs at the prompt-to-decode transition, and a deeper model with MTP will outgrow it the same way 64 was outgrown.

Not verified

  • Only one multi-GPU rig (2x 5070 Ti). Not run on more than two GPUs, or on cards other than Blackwell.
  • Nobody has measured many distinct shapes inside one second, for example high concurrency with varied prompt lengths. That grows the cap to 2048, and the cap doesn't shrink back afterwards. Only the sweep frees the entries.
  • The 1 s threshold and the 2048 limit are picked, not tuned.
  • Shipping it in the prebuilts also needs this commit added to scripts/unsloth/pr-set.json after MTP for Qwen3.8-Flash-Next #144.

Tensor split needs ~2*n_layers+1 graphs per device per shape, so the fixed
cap of 64 evicted every graph before its next use and decode ran without
graph reuse. Start at 64 and double, up to 2048, whenever the LRU victim
was used within the last second. Patch by floewe (unslothai/unsloth#12468).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant