# KV-cache centric P/D routing 当前进展

本文记录当前原型在 SGLang xPyD 上围绕 session-aware / KV-cache-aware P/D routing 的实现、实验结果和阶段性结论。实验日期为 2026-04-24 至 2026-04-25。

## 目标和核心假设

目标是在单机 8 GPU xPyD 环境中验证：针对 agentic coding workload，session-aware / KV-cache-aware P/D routing 是否能提升端到端延迟。

当前重点假设：

1. 在 PD-disaggregation 下，P 节点和 D 节点可能同时保留同一个 session 的 prefix KV，形成 P/D duplicate。
2. 如果预测某个 session 后续会 direct-to-D，那么 P 侧在 radix/prefix cache eviction 时可以优先淘汰这部分 prefix cache。
3. 这样可以给 P 节点释放 cache 空间，提高 P 侧 prefix cache reuse。
4. 如果 P 侧 reuse 提升后 D 侧开始成为瓶颈，可以通过增加 xPyD 中 D 的配比，也就是增加 y，缓解 decode 侧压力。

实验结果表明：第 2、3 点在 P cache 高压 workload 下成立；第 4 点只部分成立，因为瓶颈会从 decode prealloc 转移到 P->D transfer/bootstrap pipeline。

## 已实现机制

### 1. Trace profile 和 paired comparison

新增 `agentic_pd_hybrid profile` 子命令和 `src/agentic_pd_hybrid/profile.py`。

能力：

- 统计 trace 的 session 数、turn2+ 数、append tokens、overlap ratio、direct-to-D eligible turn。
- 对比 baseline/candidate metrics，输出 paired E2E latency delta。
- 用于解释 micro-benchmark 与 Ali filtered workload 的差异。

### 2. P 侧 priority eviction 支持

修改 SGLang server args，允许：

```bash
--radix-eviction-policy priority
```

router/replay 支持内部字段：

- `smg_prefill_priority`
- `smg_decode_priority`

router 会将内部字段剥离，只把标准 `priority` 分别传给 P/D backend。这样可以让 direct-to-D predicted session 在 P 侧使用更低优先级，例如 `-100`，普通请求使用 `100`。

### 3. Kvcache seed/direct admission 控制

新增多种 seed/reseed 过滤：

- `kvcache_seed_max_resident_tokens`
- `kvcache_seed_max_output_tokens`
- `kvcache_seed_min_turn_id`
- `kvcache_seed_only_multiturn_sessions`
- `kvcache_seed_max_inflight_decode`

新增 P streaming session backup 策略：

- `release-after-transfer`：P->D transfer 后释放 P 侧 session backup。
- `capacity-backup`：容量允许时保留 P 侧 backup。

当前主实验使用 `release-after-transfer`。

### 4. 稳定性修复

之前 worker-admission 实验会在 replay 尾部卡住，不写出 metrics。原因是 benchmark 的长 `timeout_s=3600` 同时用于：

- SGLang stack 启动等待
- replay client 单请求
- router 到 P/D backend 的单请求

修复后新增：

- `BenchmarkConfig.request_timeout_s`
- CLI `benchmark-live --request-timeout-s`
- launch plan `router_request_timeout_s`

当前做法：

- `timeout_s=3600` 继续用于 SGLang 启动和整体 stack 等待。
- `request_timeout_s=180` 用于 replay client 和 router 到 P/D backend 的单请求。
- control-plane probe/open/close session 使用 2s timeout，fail closed。

修复效果：worker-admission 从“尾部卡死不落盘”变为“卡住请求记录为 ReadTimeout，整轮实验完成并写 metrics”。

## 关键实验结果

### P cache pressure 下 priority eviction 是否提升 P 侧 reuse

配置：2P2D，P `--max-total-tokens 90000`，micro workload 316 requests / 58 sessions。

| 配置 | ok/total | mean E2E | p99 | request cached tokens | P log cached tokens | P new-token total |
|---|---:|---:|---:|---:|---:|---:|
| LRU | 314/316 | 28.171s | 43.409s | 8.204M | 7.783M | 2.236M |
| Priority | 314/316 | 28.165s | 41.935s | 8.401M | 7.981M | 2.039M |

结论：

- 在 P cache eviction 高频触发时，priority eviction 确实提高 P 侧 prefix reuse。
- cached tokens 增加约 197k，new prefill tokens 减少约 197k。
- 但 mean E2E 基本不变，说明性能瓶颈转移到 D decode/transfer。

### 增加 D 配比前的 D 侧瓶颈证据

2P2D priority pressure 下：

- decode `#queue-req`：max/mean/p90 = 0/0/0
- decode token usage：max 0.98，mean 0.842，p90 0.96
- decode `#transfer-req`：max 7，mean 4.61，p90 7
- decode `#prealloc-req`：max 21，mean 11.6，p90 21

解释：

- D 侧不是普通 waiting queue 堆积。
- 真正压力在 token usage、transfer queue 和 prealloc queue。

### D scaling：router admission 旧结果

| 配置 | ok/total | mean | p50 | p90 | p99 | error |
|---|---:|---:|---:|---:|---:|---:|
| 2P2D | 314/316 | 28.165s | 30.576s | 38.267s | 41.935s | 2 |
| 2P3D | 290/316 | 29.915s | 31.428s | 40.856s | 45.964s | 26 |
| 2P4D | 285/316 | 30.566s | 32.823s | 40.566s | 44.838s | 31 |

结论：

- 直接增加 D 不稳定。
- 2P3D/2P4D 的错误主要来自 `kvcache-centric` seed/direct 路径。
- 日志显示 decode 侧出现 `WaitingForInput` timeout 和 `KVTransferError`。

### D scaling：worker admission + request timeout 修复后

配置：P `--max-total-tokens 90000`，priority eviction，worker admission，`request_timeout_s=180`。

| 配置 | ok/total | mean | p50 | p90 | p99 | error |
|---|---:|---:|---:|---:|---:|---:|
| 2P2D | 313/316 | 29.838s | 30.742s | 39.641s | 52.506s | 3 |
| 2P3D | 299/316 | 29.349s | 30.569s | 42.161s | 46.113s | 17 |
| 2P4D | 312/316 | 26.442s | 27.759s | 38.197s | 47.970s | 4 |

对应 decode log 摘要：

| 配置 | decode usage mean | decode transfer mean | decode prealloc mean |
|---|---:|---:|---:|
| 2P2D | 0.859 | 4.86 | 11.3 |
| 2P3D | 0.877 | 5.70 | 6.92 |
| 2P4D | 0.809 | 5.31 | 3.46 |

结论：

- 2P4D + worker admission + request timeout 是当前最好的 D scaling 配置。
- 相比旧 2P4D，成功率从 285/316 提升到 312/316，mean 从 30.566s 降到 26.442s。
- 但 p99 仍未稳定改善，tail 仍由 P->D transfer/bootstrap timeout 主导。
- 2P3D 不稳定，错误 17 个，不适合作为当前推荐配置。

### 与默认 PD-disaggregation 的同配置对比

为了避免和旧 2P2D/no-pressure 基线混比，补跑了同一 workload、同一 2P4D、同一 P `--max-total-tokens 90000`、同一 `time_scale=50`、同一 `request_timeout_s=180` 的默认 PD-disaggregation 基线。

| 策略 | ok/total | mean | p50 | p90 | p99 | cached total | direct-to-D |
|---|---:|---:|---:|---:|---:|---:|---:|
| 默认 PD-disaggregation 2P4D | 316/316 | 29.210s | 28.940s | 47.434s | 52.605s | 8.165M | 0 |
| KVC 2P4D latency-best | 312/316 | 26.442s | 27.759s | 38.197s | 47.970s | 7.882M | 11 |
| KVC 2P4D seed-min2 | 316/316 | 26.729s | 25.840s | 43.589s | 50.426s | 8.337M | 3 |

结论：

- 如果只看成功请求延迟，KVC 2P4D latency-best 相比默认 PD：
  - mean 改善约 9.5%；
  - p50 改善约 4.1%；
  - p90 改善约 19.5%；
  - p99 改善约 8.8%。
- 但 latency-best 有 4 个 `ReadTimeout`，全部来自 turn1 大 seed 的 P->D transfer/bootstrap timeout。
- `kvcache_seed_min_turn_id=2` 可以消除这些错误，达到 316/316 成功，同时 mean/p50/p90/p99 仍然优于默认 PD。
- 因此当前推荐分成两档：
  - 追求最低 mean/p90：使用 KVC 2P4D latency-best。
  - 追求稳定性：使用 KVC 2P4D seed-min2。

### 进一步优化尝试

在当前最佳配置基础上尝试了三个优化方向：

| 策略 | ok/total | mean | p50 | p90 | p99 | 结论 |
|---|---:|---:|---:|---:|---:|---|
| KVC 2P4D latency-best | 312/316 | 26.442s | 27.759s | 38.197s | 47.970s | 延迟最优，但有 4 个 turn1 seed timeout |
| transfer-cap4 | 304/316 | 27.961s | 29.785s | 38.730s | 45.192s | 不可取，实时 transfer queue snapshot 滞后，错误更多 |
| disable-failed-session | 285/316 | 29.179s | 30.878s | 42.794s | 46.411s | 不可取，失败 session 降级会放大后端异常状态 |
| seed-min2 | 316/316 | 26.729s | 25.840s | 43.589s | 50.426s | 稳定性最优 |
| inflight0 | 316/316 | 29.497s | 31.186s | 43.498s | 48.022s | 太保守，几乎关闭 KVC 收益 |

从错误明细看，latency-best 的 4 个错误全部是 turn1 seed timeout，每个都是约 1250 KV blocks 的大 seed。`seed-min2` 跳过 turn1 seed 后，错误全部消失，说明当前主要稳定性问题是启动阶段 seed 风暴，而不是后续 direct append 本身。

transfer-cap4 没有效果，说明仅依赖 D worker 的实时 `decode_transfer_queue_reqs` 不够；并发请求可能同时读取到尚可的 snapshot，然后一起进入 P->D transfer，导致 backlog 在 admission 后形成。

## Ali filtered 当前状态

Ali filtered small-append trace：

- 81 requests
- 28 sessions
- 53 turn2+
- max input 18901
- max output 1925
- span 5414s

PD baseline：

- ok 81/81
- mean 9.072s
- p50 7.086s
- p90 21.761s
- p99 26.813s

Kvcache-centric 在 Ali filtered 上曾出现 58-67 个 router 200 后挂住、无 metrics 的问题。当前 request timeout 和 control-plane timeout 修复后，应重新跑 Ali filtered；在未重跑前，不把 Ali filtered 纳入最终性能结论。

## 当前结论

1. kvcache centric 可以提高 KV reuse，但需要满足 workload 条件：
   - session 有多 turn；
   - turn2+ append 较小；
   - prefix overlap 高；
   - P 侧 cache 有 eviction pressure；
   - D 侧 seed/direct admission 不把 transfer pipeline 打爆。

2. 不适合的 workload：
   - 单 turn 或 session 间隔过长；
   - turn2+ append 很大，direct-to-D 不能省掉多少 prefill；
   - prefix overlap 低；
   - P cache 没有 eviction pressure；
   - D transfer/prealloc 已经高压。

3. 用户关于 P/D duplicate 的假设部分成立：
   - 如果 D session 已经 resident，P 侧对应 streaming session backup 可以视为 duplicate。
   - `release-after-transfer` 可以避免长期保留 P/D 两份 session KV。
   - priority eviction 进一步让 P 在必须 eviction 时优先淘汰 direct-to-D predicted session prefix。

4. 但当前机制还没有完全解决性能问题：
   - P 侧 reuse 提升后，E2E 不一定改善。
   - 主要原因是 D 侧 transfer/bootstrap pipeline 成为瓶颈。
   - 增加 D 可以降低 prealloc，但不能自动降低 transfer backlog。
   - 当前同配置下 KVC 已经可以优于默认 PD，但必须在 latency-best 和 stable 两种策略之间取舍。

## 下一步优化方向

1. transfer-aware admission：
   - seed/direct 不只看 D token capacity，也要看 `decode_transfer_queue_reqs`、`decode_prealloc_queue_reqs`、`decode_retracted_queue_reqs`。
   - 当 transfer queue 高时，应该主动走 PD fallback。
   - 当前实验显示，单纯使用实时 transfer queue threshold 不够，需要配合本地 reservation。

2. per-D transfer budget：
   - 对每个 D 设置 seed/reseed 并发上限。
   - 不能只按 session residency 或 token headroom 判断。
   - 特别要限制 turn1 大 seed 的并发，避免启动阶段 seed 风暴。

3. P/D ratio 联合调度：
   - 2P4D 当前最好，但 P queue 也随 D 增加而上升。
   - 后续需要测试 3P3D、3P4D、4P4D 等组合，确认 P transfer source 是否成为瓶颈。

4. Ali filtered 重跑：
   - 使用 request timeout 修复后的版本重新跑 Ali filtered。
   - 如果仍然没有收益，需要按 session gap、append size、overlap ratio 分桶分析。

5. 更严格的成功率指标：
   - 当前不能只看成功请求的 mean/p90。
   - 必须同时报告 ok/total、timeout/error 类型和 tail latency。
