Files
agentic-kvc/analysis/characterization/window_1_results.md
Gahow Wang 0881942cf3 Window 1 results: recompute with fixed metrics + reframe limitations
After the B3 audit bug fixes (joined_analysis hotspot median +
b3_analyze percentile interp), regenerate b3_policy_comparison.json
and the per-policy hotspot_index.json from the same raw run on
dash0 and re-render the three affected figures (apc-vs-hotspot,
latency-bars, per-worker TTFT).

Key number changes in window_1_results.md:
- hotspot_index magnitudes corrected (all five policies; lmetric
  smallest delta at +0.7%, sticky largest at +16.1%)
- "capped reduces hotspot 13%" -> "~10% (2.253 -> 2.020)"
- TTFT/E2E/TPOT percentiles shift by <1% from floor->interp
  (unified TTFT p90 7.24 -> 7.35 s)

Restructured "Caveats" into "Limitations (read this before quoting
B3 numbers)":
1. Agentic dispatch coupling is by design — promoted from caveat
   to top-level methodology framing, tied to
   agentic_dispatch_coupling.md
2. B3 interference_index is binary (not size-graded) — added
3. Hot-sweep cache contamination (<1%) — kept
4. Unified interference unrecoverable — kept with explicit warning
   not to read unified's failure attribution as causal
5. w600 is a sample, not full trace — kept
6. Reuse decomposition is per-token in expectation — added

current_results/characterization_claim_matrix.md updates:
- The "heavy-tail not sole cause" claim now cites the corrected
  ~10% drop with the median bug noted
- New supported claim: "B3 saturated-replay latency gaps include an
  agentic dispatch-coupling feedback term, which is intentional and
  matches production"; cited against agentic_dispatch_coupling.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 01:08:55 +08:00

17 KiB
Raw Blame History

Window 1 Results: B1' + B2 + B3

Status: Window 1 complete (CPU + 2 dash0 GPU windows on 2026-05-25) Sweep: outputs/b3_sweep_20260525_095043 (B3) + outputs/b2_microbench/ (B2) Trace: traces/w600_r0.0015_st30.jsonl (1214 requests / 274 sessions / 53.3 M input tokens) Model: Qwen3-Coder-30B-A3B-Instruct (TP1 × 8 instances on H20)

Per-policy artifacts under window_1_results/. Figures under window_1_results/figures/.

Headline

Claim Status Evidence
Agentic workload reuse is overwhelmingly intra-session supported 93.2% of cached_tokens are intra-session (real); theoretical any-session APC ceiling 80.3% vs intra-session ceiling 79.6% → < 1pp gap
LMetric leaves 23 pp of APC on the table supported lmetric achieved 56.9% vs intra-session ceiling 79.6% (theoretical)
Hard session affinity recovers the locality lost by LMetric supported sticky APC 77.2% = 97% of theoretical ceiling
Hard affinity inflates same-worker prefill-decode interference supported sticky interference_index 13.65 vs lmetric 6.53
Hybrid affinity (Unified) breaks the locality-vs-latency tradeoff supported unified hits 79.4% APC and TTFT p90 7.35 s (lmetric 15.67 s) simultaneously
Same-worker prefill-decode interference is causal, not correlation supported different-worker control idx≈1.0; same-worker idx scales monotonically with prefill size
Heavy-tail sessions are a contributor to hot-spot, not the sole cause supported cap=8 truncated trace cuts 37% of work; hotspot drops only ~10% (2.253→2.020)
The agentic dispatch coupling amplifies policy gaps under saturation supported, framed as feature Slow policy → longer session lifetime → more concurrent in-flight → harder system. B3 measures the combined policy + feedback effect, which is what an agentic operator experiences. See agentic_dispatch_coupling.md.

B1' Workload characterization

Per-request KV footprint (Qwen3-Coder-30B-A3B)

kv_bytes_per_token = 2 × num_layers × num_kv_heads × head_dim × dtype_bytes = 2 × 48 × 4 × 128 × 2 = 98304 B

Full GLM-5.1 trace (2.11 M requests, 1.31 M sessions):

p50 p90 p95 p99 max
KV per request 1.83 GiB 8.04 GiB 9.59 GiB 11.49 GiB 18.5 GiB

H20 has ~95 GiB usable per GPU. A single p99 request occupies 12% of a single H20's HBM purely for KV. Multi-request batching is bounded by this.

Figure: figures/fig_kv_footprint_cdf.png.

Real reuse decomposition (from lmetric run on w600 trace)

class tokens fraction
intra-session 28.3 M 93.2%
cross-session 1.72 M 5.7%
shared / system-prefix 0.34 M 1.1%
unclassified 0 0.0%

→ session-affinity routing covers >99% of the reuse signal. There is no meaningful "system prompt" in this trace.

Figure: figures/fig_reuse_decomposition.png.

Theoretical APC ceilings on w600

Computed by building a block-level trie of hash_ids per session (intra-session) or globally (any-session), then walking each request's hash_ids to count its longest prefix-match against previously-seen prefixes.

variant upper bound hit requests
any-session (perfect global cache) 80.3% 961 / 1214
intra-session only 79.6% 914 / 1214
shared-prefix only (pos 0, ≥8 sessions) 0.10% 107 / 1214

Gap "any intra" is 0.7 pp → no meaningful cross-session sharing in this trace.

B3 5-policy routing sweep

8 vLLM instances on TP1, w600 trace, --enable-prompt-tokens-details so cached_tokens is reported per request.

policy TTFT p50/p90/p99 TPOT p50/p90/p99 ms E2E p50/p90/p99 APC interference hotspot n_slow
lmetric 0.94 / 15.67 / 53.57 8.9 / 21.2 / 176.9 2.75 / 24.82 / 79.83 56.9% 6.53 2.253 295
load_only 1.26 / 20.20 / 52.84 9.2 / 26.9 / 320.7 3.59 / 33.46 / 93.93 54.1% 9.16 1.294 379
sticky 0.54 / 18.02 / 74.09 8.9 / 36.4 / 357.2 2.08 / 34.63 / 134.36 77.2% 13.65 2.728 234
unified 0.50 / 7.35 / 42.34 8.1 / 17.1 / 118.3 1.75 / 18.03 / 68.43 79.4% n/a* 3.667 189
capped 1.20 / 12.83 / 46.62 7.2 / 16.0 / 101.7 2.59 / 21.25 / 73.79 31.6% 6.33 2.020 185

*unified engine_state was overwritten by my analyzer's slice step before the b3_analyze.sh fix landed; vLLM and the patch worked correctly. The B2 microbench provides a cleaner interference proof.

Methodology note (read before interpreting latency comparisons): B3 uses session-sequential trace dispatch — turn N+1 fires the instant turn N completes when the trace timestamp has already passed. This is the right model of agentic workloads (tool-call driven, no user think-time), but it means under saturation each policy's effective in-flight session count is a function of its own per-turn latency (slower policy → longer mean session lifetime → more concurrent in-flight). The reported gaps are therefore "policy + agentic-feedback-amplification", which is what a production agentic operator would experience when switching policies. See agentic_dispatch_coupling.md for the full argument. B4 will report the orthogonal "fixed-λ open-loop" measurement.

Mechanism indices

  • interference_index = TPOT_p90(decode overlapping same-worker prefill) / TPOT_p90(clean)
  • hotspot_index = max(worker TTFT p90) / median(worker TTFT p90)

Figures: fig_b3_latency_bars.png, fig_b3_apc_vs_upper.png, fig_b3_apc_vs_hotspot.png, fig_b3_per_worker_ttft_p90.png, fig_b3_failure_breakdown.png.

Per-policy reading

  • lmetric is the cache-aware baseline. APC 56.9% achieves only 71% of the intra-session ceiling — the missing 23 pp is the locality opportunity unified picks up.
  • load_only strips cache awareness. Hot-spot drops to 1.294 (best), but APC only drops 3 pp because the picker's min(num_requests) tie-break to instance 0 creates accidental stickiness at low concurrency.
  • sticky locks each session to one worker. APC climbs to 77.2% (97% of ceiling) but interference doubles to 13.65 and TPOT p99 hits 345 ms.
  • unified is the hybrid — affinity gate (cache_ratio>0.5 AND num_req ≤ 2×avg) keeps locality where it pays and drops it where it would hurt. The result is APC 79.4% and TTFT p90 cut in half from lmetric. The one bad worker (engine_4 at 37.7s p90) drives hotspot_index=3.667, but the other seven workers are all under 18 s.
  • capped runs lmetric on a turn-capped trace (max 8 turns/session). Removes 37% of requests but APC also crashes to 31.6% and hotspot only improves by ~10% (2.253 → 2.020). This is the session-mass ablation: heavy sessions are a contributor to hot-spot but not the sole cause.

Slow-request cause breakdown (from joined_analysis.label_slow_requests)

policy n_slow same-worker overlap hot worker queue cache miss large append unknown
lmetric 295 69 (23%) 68 (23%) 94 (32%) 64 (22%)
load_only 379 108 (29%) 33 (9%) 151 (40%) 87 (23%)
sticky 234 134 (57%) 51 (22%) 20 (9%) 29 (12%)
unified 189 0 (no engine_state) 116 (61%) 18 (10%) 55 (29%)
capped 185 45 (24%) 66 (36%) 60 (32%) 14 (8%)

PD-colo failures are mixed-mechanism: lmetric has no single dominant cause. Sticky concentrates failures into same-worker overlap (locality is on, cache misses are gone, but interference takes over).

B2 PD-colo interference microbench

Setup: 2 vLLM instances on GPU 0 (decode endpoint) and GPU 1 (prefill endpoint). A continuous 4 req/s short-prompt decode load runs against GPU 0 for 60 s per cell. 4 large-prompt one-token "prefill injections" fire every 12 s, targeted at either the same instance (same) or the paired one (different). Decode requests are labeled overlap iff their [t_first_token, t_finish] intersects any injection window. We compare TPOT p90 (overlap vs clean) per cell.

variant prefill n_overlap n_clean TPOT idx TTFT idx
different 2k65k 12126 114228 0.921.02 0.961.00
same 2k 12 228 1.16 2.15
same 8k 19 221 1.90 12.1×
same 16k 37 203 3.37 30.8×
same 32k 67 173 7.89 94.6×
same 65k 130 110 2.26* 218×

*65k TPOT idx is non-monotone — see §"TPOT idx peaks at 32k, not 65k" below.

Figures: fig_b2_tpot_vs_prefill.png, fig_b2_ttft_vs_prefill.png.

Why this matters

  • The different-worker control sits at idx ≈ 1.0 across 32× variation in prefill size. This is the cleanest possible disproof of "any prefill anywhere hurts decode": prefill on a different worker is invisible to the decode worker.
  • The same-worker TTFT curve is monotone in prefill size all the way to 218× at 65k. TPOT p90 is monotone only up to 32k (7.89×), then drops at 65k — this is not "interference relaxing", it is the cost regime shifting from TPOT to TTFT (see below).
  • This is the mechanism behind the B3 sticky interference jump (13.65) and unified's single hot worker (engine_4 at 37.7 s TTFT p90).

TPOT idx peaks at 32k, not 65k — regime shift, not relief

The naïve reading of the table is "interference gets worse up to 32k then drops at 65k". That is wrong; the cost is shifting from per-token rate (TPOT) to first-token wait (TTFT), and p90 / clean happens to compress the visible cost. Three superimposed effects.

Same-variant detail across the regime boundary:

                          32k         65k       change
n_overlap                  67         130       +94%  (most decodes now overlap)
n_clean                   173         110       -37%
TPOT p50 overlap (ms)    12.2        20.1       +1.6x
TPOT p90 overlap (ms)    54.8        21.7       -2.5x  <- "improves"
TPOT p99 overlap (ms)    59.0       169.5       +2.9x  <- tail explodes
TTFT p90 overlap  (s)    4.17       14.06       +3.4x
TPOT p90 clean   (ms)     6.9         9.6       +40%

Mechanism 1 — Cost shifts from TPOT to TTFT. TPOT is measured only after a request starts emitting tokens. A 32 k prefill (~5 s on H20) is short enough that vLLM's chunked-prefill scheduler keeps interleaving decode steps; overlapping decodes trickle tokens out at painfully slow per-token rates → p90 TPOT 54.8 ms. A 65 k prefill (~10 s) is long enough that many overlapping decodes get zero tokens for nearly the whole prefill window; when they finally break through, the injection is winding down so subsequent decode iterations are unobstructed. The cost goes onto the TTFT clock (14 s) instead of inflating TPOT.

Mechanism 2 — Bimodal TPOT distribution hides under p90. At 65 k overlap, two populations of decodes coexist:

  • decodes blocked the entire prefill (high TTFT, then normal per-token rate)
  • decodes that did trickle slowly through prefill chunks (low TTFT, high TPOT)
  • The p99 jump 59 → 169.5 ms shows the second population is worse at 65 k. p90 happens to fall on the first (fast-after-block) population.

Mechanism 3 — "Clean" stops being clean. With 4 × ~10 s injections spread across 60 s (40 s of injection time, 20 s of gaps), there are very few moments where the worker is truly idle. The 110 "clean" decodes at 65 k are squeezed into 2-3 s pockets where the system is recovering from the previous injection or about to be hit by the next. TPOT p90 clean rises 6.9 → 9.6 ms (the denominator of the idx ratio drifts up by 40%).

Reading rule for B2: TTFT idx is the headline interference metric — it is monotone and reflects user-visible "no tokens for N seconds" latency. TPOT p99 is the right tail-sensitivity indicator (also monotone). TPOT p90 is non-monotone across regime shifts and should not be used alone. This has direct implications for SLO design: TTFT and TPOT cannot share the same violation threshold under PD-colo interference, because they measure costs from different points in the request lifecycle and the cost migration between them is workload-dependent.

This is also a finding the paper should call out: once same-worker prefill grows beyond a TTFT-block threshold, overlapping decodes "give up" their per-token rate complaint and pay the cost in queueing instead. The system looks faster on per-token metrics; users experience longer waits.

What Window 1 does not answer

These need Window 2 (B4 SRR sweep + B5 failure attribution near SRR boundary):

  1. Sustainable arrival rate (SRR) per policy under SLO. B3 was driven by trace timestamps with strict session sequentiality; when 8 instances cannot keep up, requests pile up and the effective dispatch window stretches (lmetric: trace claims 600 s, actual replay 49 min). We measured saturated behavior but not the saturation point. B4 needs the A4 open-loop Poisson loadgen with per-class SLO thresholds.
  2. Failure breakdown at the SRR boundary. B5 will rerun each policy at 0.9× / 1.0× / 1.1× of its SRR_max and label each SLO-violating request — gives the paper its causal failure-attribution table.

Optional / paper-polish runs (not blocking the story):

  1. unified isolated rerun to capture interference_index (B2 already provides cleaner causal proof; skip unless reviewer asks).
  2. B2 with the proxy in path — measure whether the production cache_aware routing actually pushes prefill and decode onto different workers in practice.
  3. KV-occupancy timeline per worker — needs polling vllm:gpu_cache_usage during B3 reruns; useful for "KV pressure drives cache miss" subsection.

Limitations (read this before quoting B3 numbers)

  1. Agentic dispatch coupling is by design. B3 is the "production-replay under captured agentic load" experiment, not the "controlled-load envelope" experiment. Latency p90 reflects both per-request policy effect AND the agentic feedback amplification (slow policy → longer mean session lifetime → more concurrent in-flight). Both contributions are real and visible to a production operator; the paper must report both, not subtract one. See agentic_dispatch_coupling.md. The orthogonal "fixed-λ Poisson" measurement is B4.

  2. B3 interference_index is a binary indicator. A decode is labeled "overlap" iff any other request's prefill exists on the chosen worker during [t_first_token, t_finish], regardless of prefill size. B2's per-prefill-size cells (2k = 1.16×, 65k = 2.26×) cannot be directly read off B3's aggregate numbers (lmetric 6.53, sticky 13.65). The B3 numbers are size-weighted averages of the per-cell signal, valid for within-B3 cross-policy comparison but not for direct cross-batch numerical comparison with B2.

  3. Hot-sweep cache contamination (low): lmetric ran from cold; load_only and sticky ran on the same 8 vLLMs without restart. First-turn cached_tokens verification puts empirical contamination at < 1% APC, well below the cross-policy gaps. unified and capped were rerun cold-start specifically to remove any residual concern.

  4. Unified's interference_index is missing. The original b3_analyze.sh unconditionally truncate-wrote sliced engine_state files; isolated runs that wrote engine_state into their own per-policy directory were overwritten. Fixed in commit df32499; capped was the first run to benefit and survived. Implication: unified's slow-request mechanism breakdown (rows 0 / 116 / 18 / 55 for same-worker overlap / hot worker queue / cache miss / unknown) has the "same-worker overlap" label unrecoverable and forced into the catch-all buckets — do not read unified's failure attribution as causal.

  5. w600 is not the full GLM-5.1 trace (1214 req vs 2.11 M). All B3/B2 percentiles are on the sample. The full-trace KV-footprint stats are on the full trace.

  6. Reuse decomposition (intra/cross/shared/unclassified) is per-cached-token only in expectationjoined_analysis.py distributes a request's cached_tokens count uniformly across its hash_ids and classifies block-by-block. For the w600 trace with <1% cross-session sharing the qualitative split is robust; for workloads with mixed-class hashes within a single request the classifier should be revisited.

Reproduction commands

# B3 5-policy sweep
bash scripts/b3_sweep.sh                                   # lmetric, load_only, sticky (hot-cache)
bash scripts/b3_isolated_policy.sh unified <trace> <dir>   # isolated cold-start
bash scripts/b3_isolated_policy.sh lmetric <capped> <dir>  # capped variant

bash scripts/b3_analyze.sh outputs/b3_sweep_<TS>
python3 scripts/render_b3_report.py --sweep-dir outputs/b3_sweep_<TS>

# B2 interference microbench
# (launch 2 vLLM on ports 8100/8101 with --enable-prompt-tokens-details first)
python3 scripts/b2_interference.py \
    --decode-endpoint http://127.0.0.1:8100 \
    --prefill-endpoint http://127.0.0.1:8101 \
    --model <model> \
    --out-dir outputs/b2_microbench/sweep
python3 analysis/characterization/b2_sweep_analysis.py --sweep-dir outputs/b2_microbench/sweep

# Figures
python3 analysis/characterization/render_window1_figures.py \
    --results-dir analysis/characterization/window_1_results \
    --out-dir analysis/characterization/window_1_results/figures