Files
agentic-kvc/v2/README.md
Gahow Wang ad754cfe0b v2 exp(b): GPU KV-capacity APC/latency knee + writeup
Sweeps GPU KV-cache capacity (--num-gpu-blocks-override) under a closed-loop
replay (concurrency 4) of a controlled multi-turn workload (cumulative
intra-session prefix, gen_synth_trace.py), measuring realized APC
(prefix_cache hits/queries delta) and latency per capacity.

Result: a sharp knee at 3.6 GB = exactly the active working set
(4 sessions x 0.91 GB). APC rises 7->12->36->80% then saturates at the
~71% intra-session ceiling; TTFT p90 collapses 13.0 s -> 0.53 s at the same
point; dead flat to 14.5 GB, 100% completion throughout. So only the active
working set needs HBM; capacity beyond it -- and the CPU/storage tier built
to chase the reuse tail -- buys ~0. Knee scales linearly with concurrency
= cluster GPU count.

README.md ties exp(a)+exp(b) into the section-2.2 GPU-hit-first argument
with tables, conclusions, and caveats. Raw per-request dumps gitignored;
summary/m0/m1 deltas kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 11:23:31 +08:00

100 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# v2 — Evidence for the GPU-hit-first principle (§2.2)
Two experiments that turn "**Hits on GPU > hits on CPU**" + "**GPU is enough to
hold most of the *valuable* KV reuse**" from assertion into measurement.
Hardware: dash0, 1× NVIDIA H20 (97 GB) per experiment, Qwen3-Coder-30B-A3B-Instruct,
vLLM 0.18.1 (V1, prefix caching, enforce-eager). KV = 96 KiB/token (1 GiB = 10,923 tok).
## Exp (a) — three-tier hit latency (`exp_a_tier_latency/`)
TTFT of serving a reused prefix of length L from each tier:
- **miss** — fresh unique prompt → full prefill (recompute)
- **GPU hit** — re-request → HBM prefix cache
- **CPU hit** — warm → evict to CPU offload tier (`--kv-offloading-size`) → re-request → DRAM fetch
- **PCIe floor** — direct pinned-memory H2D transfer cost for the same KV size (backstop)
Tier of each measured request is *verified* via `vllm:prefix_cache_hits` vs
`vllm:external_prefix_cache_hits` deltas, not assumed.
Run: `GPU=0 bash v2/exp_a_tier_latency/run.sh` then `.venv/bin/python v2/exp_a_tier_latency/plot.py`.
## Exp (b) — capacity → APC → latency knee (`exp_b_capacity_knee/`)
Replay a fixed agentic trace at several GPU KV pool sizes
(`--num-gpu-blocks-override`); measure realized APC + TTFT p90 per capacity.
The knee = the GPU capacity beyond which more HBM buys ~no extra reuse.
Run: `GPU=1 bash v2/exp_b_capacity_knee/run_sweep.sh` then
`.venv/bin/python v2/exp_b_capacity_knee/analyze_and_plot.py`.
## Results (dash0, 2026-05-30)
### Exp (a) — GPU hit ≫ CPU hit ≫ miss (`figs/exp_a_tier_latency.png`)
TTFT (s, p50 over reps) to serve a reused prefix of length L. CPU-tier hits were
100% verified via `vllm:external_prefix_cache_hits`.
| prefix L | miss (recompute) | CPU-tier hit | GPU-tier hit | miss/CPU | **CPU/GPU** |
|---:|---:|---:|---:|---:|---:|
| 1k | 0.078 | 0.057 | 0.042 | 1.4× | 1.4× |
| 4k | 0.261 | 0.064 | 0.046 | 4.1× | 1.4× |
| 8k | 0.588 | 0.076 | 0.053 | 7.7× | 1.4× |
| 16k | 1.547 | 0.105 | 0.063 | 14.8× | 1.7× |
| 32k | 4.604 | 0.158 | 0.080 | 29.2× | 2.0× |
| **64k** | **15.230** | **0.272** | **0.111** | **56.0×** | **2.4×** |
- **GPU hit is ~flat** (42→111 ms over 1k→64k): a hit returns the whole prefix from
HBM, only the last token is recomputed.
- **miss grows superlinearly** (→15.2 s at 64k): a miss pays the full prefill.
- **CPU hit grows transfer-bound** (PCIe H2D measured **~54 GB/s**); CPU-hit TTFT ≈
GPU-hit + KV/PCIe + ~0.15 s connector overhead (the dashed PCIe floor sits just
under the orange curve, confirming the decomposition).
- **Takeaway:** among hits, **GPU beats CPU by 1.42.5×** and the gap widens with
context. A CPU hit is a useful backstop (up to 56× better than recompute) but is
strictly worse than keeping the prefix resident in HBM.
### Exp (b) — APC and latency knee at small GPU capacity (`figs/exp_b_capacity_knee.png`)
Closed-loop replay (concurrency 4) of a controlled multi-turn workload (24 sessions
× 6 turns, cumulative intra-session prefix, per-session working set **0.91 GB**,
intra-session APC ceiling 71%), sweeping GPU KV capacity.
| GPU KV (GB) | realized APC | TTFT p50 | TTFT p90 | E2E p90 | completion |
|---:|---:|---:|---:|---:|---:|
| 1.2 | 7.4% | 8.32 | 13.00 | 16.54 | 100% |
| 1.6 | 12.2% | 4.02 | 8.90 | 12.41 | 100% |
| 2.4 | 36.3% | 0.47 | 4.62 | 8.66 | 100% |
| **3.6** | **80.3%** | **0.41** | **0.53** | **4.33** | 100% |
| 4.8 | 72.9% | 0.49 | 0.65 | 4.27 | 100% |
| 7.2 | 72.9% | 0.49 | 0.64 | 4.25 | 100% |
| 9.7 | 72.9% | 0.49 | 0.65 | 4.19 | 100% |
| 14.5| 72.9% | 0.49 | 0.65 | 4.25 | 100% |
- **Sharp knee at 3.6 GB** = exactly the active working set (4 sessions × 0.91 GB).
APC saturates at the ~71% ceiling; **TTFT p90 collapses 13.0 s → 0.53 s** at the
same point. Beyond the knee, **more HBM buys nothing** (dead flat to 14.5 GB).
- Below the knee, sessions evict each other between turns → cache misses →
recompute → 13 s TTFT. The knee is where the working set becomes GPU-resident.
## Conclusion (for §2.2)
1. **Hits on GPU > hits on CPU** is now measured, not asserted: a GPU(HBM) hit is
1.42.5× faster than a CPU(DRAM-offload) hit and 14137× faster than recompute,
with the GPU advantage growing in context length (Exp a).
2. **You only need to hold the *active working set* on GPU.** Realized APC and
latency saturate once HBM covers the concurrent sessions' working set (3.6 GB
here); past that, extra capacity — and the entire CPU/storage tier built to chase
the long reuse tail — adds ~0 (Exp b). The knee scales linearly with concurrency,
i.e. with **cluster GPU count**, which the production cluster already provides.
3. Together: maximize GPU residency of the active working set (colocation + affinity
routing + dedup-migration); the CPU tier is a fallback, not the primary path.
## Caveats
- Exp (b) uses a controlled multi-turn workload (the production trace is 90%
single-turn with huge per-request contexts that thrash a single instance — see
C1/f2c); it isolates the capacity→APC→latency mechanism. Knee *position* scales
with concurrency × per-session working set.
- Single H20; PCIe H2D ~54 GB/s is intra-node (cf. 9.7 GB/s Mooncake inter-node RDMA).
- The 80.3% point at the knee slightly exceeds the 71% intra-session ceiling
(transient full residency / generated-token reuse); steady state is 72.9%.