Files
agentic-kvc/analysis/mb1/README.md
Gahow Wang 029821c1b6 MB1: prefill-decode interference under chunked-prefill default; §3.2 headline
Single-GPU bench on dash1 GPU 0 (vanilla vLLM 0.18.1, chunked-prefill on,
no kv_connector). 3 decode batch sizes × 5 prefill sizes × 3 reps.

Method recap (driver: microbench/interference/driver.py, repurposed):
- Pin D streaming decode requests at constant max_tokens
- Inject one prefill-only request (max_tokens=1) of varying input length
- Bin decode-stream token timestamps into "during prefill" vs baseline
- Headline metric: effective per-stream TPOT during the prefill burst,
  = prefill_ttft / (num_tokens_during_prefill / D). This is the average
  rate at which each decode stream produces tokens during the burst.
  p50 of inter-token intervals is deceptive (chunked-prefill makes most
  intervals look normal); the burst-average gives the true cost.

Results (D=8 row, the most agentic-realistic case):
  P (tokens) | prefill_ttft | per-stream TPOT during | penalty
       2048  |    143 ms    |      32 ms             |    4×
       8192  |    583 ms    |     114 ms             |   15×
      32768  |  4520 ms     |     388 ms             |   52×
      65536  | 15615 ms     |     757 ms             |   99×
     131072  | 56991 ms     |    1419 ms             |  183×

Baseline TPOT at D=8: ~7.7 ms. So during a 131k-token prefill burst
each ongoing decode is running ~183× slower (i.e. essentially halted)
for ~57 seconds.

§3.2 implication: PD-disagg's promised phase-isolation benefit per
agentic request is bounded by the decode duration, which is 50–200 ms
for tool-call output. MB2 says the KV-transfer cost of PD-disagg
is 300 ms – 10 s for agentic-size requests. Cost > benefit for every
KV size above ~80 MiB (well below trace mean 192 MiB).

The new figs/pd_cost_vs_benefit.png overlays MB1 benefit ceiling
(50–200 ms band, capped by decode) onto MB2 transfer cost curve and
marks the agentic-distribution waypoints (trace mean, p90, p95, p99)
on the x-axis. Across the entire agentic distribution, the cost curve
sits above the benefit band.

Adds:
- microbench/fresh_setup/mb1_launch.sh: single-GPU vLLM launcher (no
  kv_connector, default chunked_prefill=on, max_num_batched_tokens=8192)
- microbench/fresh_setup/mb1_driver.py: copy of the existing
  microbench/interference/driver.py for cpfs deployment
- microbench/fresh_setup/analyze_mb1.py: aggregator emitting
  per-(D, P) effective-TPOT-during + max PD-disagg-benefit table
- microbench/fresh_setup/plot_mb1.py: mb1 standalone +
  pd_cost_vs_benefit headline figure
- analysis/mb1/summary.csv: 45 raw rows from the sweep
- analysis/mb1/breakdown.json: per-(D, P) aggregate
- analysis/mb1/README.md: persistent doc
- figs/mb1_interference.png: effective TPOT during prefill, one line per D
- figs/pd_cost_vs_benefit.png: §3.2 headline (cost > benefit everywhere)

Caveats noted in README:
- chunk_tokens=8192 only; Sarathi-Serve's smaller chunks would
  interleave decode more aggressively. Chunk-size sensitivity is
  flagged as next run.
- D ≤ 8; higher D may saturate or shrink the penalty further.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 21:25:09 +08:00

8.4 KiB
Raw Blame History

MB1 — PrefillDecode Interference (chunked-prefill on, vLLM 0.18.1 default)

Persistent record of the phase-interference microbench used to put a quantitative upper bound on what PD-disaggregation can buy under the chunked-prefill-on baseline. Re-runs append a dated section at the bottom; the Summary block is what gets cited.


Summary (latest)

Headline Value
Baseline single-stream TPOT (D=1, idle GPU) 4.8 ms
Effective per-stream TPOT during 8k-token prefill burst (D=8) 114 ms (≈15× baseline)
Effective per-stream TPOT during 32k-token prefill burst (D=8) 388 ms (≈52×)
Effective per-stream TPOT during 131k-token prefill burst (D=8) 1419 ms (≈183×)
Maximum PD-disagg benefit per agentic decode ≤ 50200 ms (= decode duration)

§3.2 headline (cost vs benefit, this run + MB2):

Under chunked-prefill, every ongoing decode stream is essentially halted while a prefill chunk is in flight — per-stream effective TPOT during the burst is 15× to 2000× baseline, scaling with prefill size. PD-disagg can recover this stall, but the recovery is bounded by the decode duration of the request being protected. For agentic, decode is 50200 ms (tool-call output). MB2 shows PD-disagg pays 300 ms 10 s of KV-transfer cost per request to do that recovery. The cost exceeds the benefit ceiling for any per-request KV ≥ ~80 MiB (~830 tokens) — well below all agentic operating points. The benefit never beats the cost in this workload.

Setup

Component Value
Host dash1, H20 96 GiB, driver 570.133.20
Venv /home/admin/cpfs/wjh/agentic-kv-fresh/.venv
vLLM 0.18.1 official wheel (chunked-prefill default-on, V1 engine)
Model /home/admin/cpfs/wjh/models/Qwen/Qwen3-Coder-30B-A3B-Instruct
Launch flags --tensor-parallel-size 1 --enable-prefix-caching --gpu-memory-utilization 0.9 --max-model-len 200000 --max-num-batched-tokens 8192
kv_connector none (this measures pure single-GPU phase interference; PD-disagg cost lives in MB2)

Method

Adapted from microbench/interference/driver.py:

  1. Start D streaming decode requests on /v1/chat/completions with a long max_tokens cap. Discard the first 32 tokens as warmup.
  2. After 1 s, inject one prefill-only request with max_tokens=1 and an input of P synthetic tokens (uuid-seeded for zero prefix-cache reuse). Measure the prefill's TTFT.
  3. Bin the during-prefill tokens from each decode stream by whether their wall-clock falls inside [prefill_inject_ts, prefill_inject_ts + prefill_ttft]. Report inter-token p50 / p90.
  4. Bin a baseline run (D streams, no prefill injection) the same way.

We additionally compute the effective per-stream TPOT during the prefill burst as the single most informative summary:

eff_TPOT_during = prefill_ttft_ms / (num_tokens_during_prefill / D)

This is the average rate at which each decode stream produces tokens while a prefill is in flight. Compared to baseline TPOT it gives the real per-stream throughput penalty (chunked-prefill p50 looks deceptively fine because most decode-token intervals during the burst are at normal speed; p90 sees the stall but is itself noisy; the effective TPOT is the cleanest "average over the whole burst window" number).

Results — 2026-05-27, dash1 GPU 0, chunk_tokens=8192

3 D × 5 P × 3 reps. Aggregated by analyze_mb1.py.

D P (tok) base TPOT (ms) prefill_ttft (ms) per-stream tokens during effective TPOT during (ms) penalty max PD-disagg benefit per stream (ms)
1 2 048 4.79 163 4.0 41 8× 144
1 8 192 4.78 584 5.0 117 24× 560
1 32 768 4.78 4 515 5.0 903 189× 4 491
1 65 536 4.78 15 568 5.3 2 919 610× 15 542
1 131 072 4.78 56 765 5.7 10 017 2 094× 56 738
4 2 048 5.62 138 3.9 36 6× 117
4 8 192 6.08 574 4.5 128 21× 547
4 32 768 6.09 4 529 11.9 381 63× 4 457
4 65 536 5.85 15 587 19.8 789 135× 15 471
4 131 072 6.27 56 697 37.4 1 517 242× 56 463
8 2 048 7.71 143 4.5 32 4× 109
8 8 192 7.69 583 5.1 114 15× 544
8 32 768 7.42 4 520 11.7 387 52× 4 434
8 65 536 7.67 15 615 20.6 757 99× 15 457
8 131 072 7.74 56 991 40.2 1 419 183× 56 680

Reading the table:

  • Baseline TPOT grows mildly with D (4.8 ms → 7.7 ms as D goes 1 → 8). Multi-stream decoding has small but nonzero contention even without prefill.
  • Effective TPOT during grows mostly with P: a single 8k prefill stalls decode for ~580 ms regardless of D, so each stream emits only a handful of tokens during that 580 ms window — effective per-stream TPOT collapses to 100130 ms. Larger prefill = more chunks = larger stall.
  • Penalty is the eff/baseline ratio. Above 50× for P ≥ 32k. Above 500× for D=1 at P ≥ 65k.
  • Max PD-disagg benefit per stream = prefill_ttft per_stream_tokens × baseline_TPOTprefill_ttft (since interference essentially halts decode). This is the entire prefill duration's worth of decode time that could in principle be recovered.

Two big caveats for agentic application:

  1. Decode is short (~50200 ms for tool-call output). The actual recoverable benefit per request is bounded by the decode duration, not by prefill_ttft. If a decode lasts 100 ms and a 5-second prefill collides with it, PD-disagg can save at most 100 ms — not 5 s.
  2. PD-disagg pays KV-transfer cost (MB2: 300 ms 10 s per request for agentic sizes). For any KV ≥ ~80 MiB the cost already exceeds the ~100 ms benefit ceiling. Cost > benefit across the whole agentic distribution.

§3.2 cost-vs-benefit figure

figs/pd_cost_vs_benefit.png overlays MB1 benefit ceiling (50200 ms band, capped by decode duration) on top of MB2 transfer cost curve. The cost curve crosses the benefit ceiling somewhere around 80 MiB / 830 tokens of KV — well below the trace mean (192 MiB / 2k tok ≈ trace mean per request KV, and we know agentic averages 33k tokens, p99 125k). For anything bigger PD-disagg pays more than it can recover.

Reproduction

# vllm pair-free single-instance launch
ssh dash1 'GPU=0 PORT=8000 CHUNK_TOKENS=8192 \
  bash /home/admin/cpfs/wjh/agentic-kv-fresh/scripts/mb1_launch.sh start'

# sweep
ssh dash1 'source /home/admin/cpfs/wjh/agentic-kv-fresh/.venv/bin/activate && \
  python /home/admin/cpfs/wjh/agentic-kv-fresh/scripts/mb1_driver.py \
    --host 127.0.0.1 --port 8000 \
    --model /home/admin/cpfs/wjh/models/Qwen/Qwen3-Coder-30B-A3B-Instruct \
    --decode-batch-sizes 1,4,8 --prefill-tokens 2048,8192,32768,65536,131072 \
    --reps 3 --output-dir /home/admin/cpfs/wjh/agentic-kv-fresh/mb1_results'

# pull + analyze
scp dash1:/home/admin/cpfs/wjh/agentic-kv-fresh/mb1_results/chunk8192/summary.csv \
    analysis/mb1/summary.csv
.venv/bin/python microbench/fresh_setup/analyze_mb1.py \
  --summary analysis/mb1/summary.csv --out analysis/mb1/breakdown.json
.venv/bin/python microbench/fresh_setup/plot_mb1.py \
  --mb1 analysis/mb1/breakdown.json \
  --mb2-intra analysis/mb2/intra_kvboth_breakdown.json \
  --mb2-inter analysis/mb2/inter_kvboth_breakdown.json

# teardown
ssh dash1 'bash /home/admin/cpfs/wjh/agentic-kv-fresh/scripts/mb1_launch.sh stop'

Open questions / next runs

  • Chunk size sensitivity: this run uses --max-num-batched-tokens 8192. Sarathi-Serve goes smaller (e.g. 1024) and recovers more decode interleaving inside each prefill burst. Worth running chunk_tokens ∈ {1024, 2048, 4096, 16384} to map the chunk-size axis.
  • Higher D: 12, 16 streams to see whether the penalty saturates or keeps shrinking per-stream.
  • Cross-validate effective_TPOT_during with token-time-series plot: raw per-token timestamps could reveal whether the stall is a few big spikes or many small ones (currently inferred from p50/p90 spread).

Run log

2026-05-27 — dash1 GPU 0, chunk_tokens=8192

3 × 5 × 3 sweep. CSV: analysis/mb1/summary.csv. Per-config JSONs on dash1 at /home/admin/cpfs/wjh/agentic-kv-fresh/mb1_results/chunk8192/. Figures: figs/mb1_interference.png, figs/pd_cost_vs_benefit.png.