docs: add v5 chapter (Option D worker-mode admission) and rename to V1_TO_V5

v5 sweep (sweep_tp1_v5_optD.sh) lands the previously-deferred Option D:
worker admission_mode authoritative for direct_append + seed + reseed,
bypassing replay's local _decode_session_soft_cap.

Key findings now documented:
- errors collapse from 9-10% to 0.2% (mooncake timeouts gone)
- session-cap fallback rises 33-35% -> 46-51% — D's true KV pool is the
  binding constraint, not replay's estimator; v4's "low fallback" was
  hiding capacity overruns as transfer-timeout errors
- direct-to-D subset latency unchanged from v4 (admission overhead negligible)
- new bottleneck: D's physical KV pool — points v6 at prefill backup release
  timing, priority eviction tuning, chunked seed, cross-D session migration,
  and real RDMA

Also adds a 5th lesson on errors-vs-fallback reciprocity and updates the
code index with the v5 endpoint extension and new CLI knobs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kzlin
2026-04-29 16:13:25 +08:00
parent 6e5ed8da80
commit 6572d7f3f4

View File

@@ -1,6 +1,6 @@
# KVC 实验踩坑记录与代码 Bug 分析v1 → v4
# KVC 实验踩坑记录与代码 Bug 分析v1 → v5
记录从 v1 到 v4 KVC 实验的踩坑过程、错误诊断、以及最终定位的代码 bug。
记录从 v1 到 v5 KVC 实验的踩坑过程、错误诊断、以及最终定位的代码 bug。
模型: Qwen3-30B-A3B (TP1),硬件: 单节点 8×H100 80GB。
Trace: `qwen35-swebench-50sess.jsonl`4449 请求52 sessions
@@ -11,7 +11,8 @@ Trace: `qwen35-swebench-50sess.jsonl`4449 请求52 sessions
| v1 (smoke / 早期) | mechanism 跑通 | - | - | - | - |
| v2 | KVC + `--policy default` | **56.8% / 61.4%** | <0.1% | 0.08s* | Routing 错位默认策略 |
| v3 | KVC + `--policy kv-aware` | **0.9%** | 30-42% | 1.5-1.8s | session-cap fallback (52-65%) |
| v4 | v3 + soft_cap 4→16 | (待数据) | (待数据) | (待数据) | (待数据) |
| v4 | v3 + soft_cap 4→16 | 1.0% | 54-58% | 1.08 / 0.84s | session-cap fb 仍 35%、9-10% mooncake errors |
| v5 | Option Dworker-mode 驱动 seed/reseed | 0.9% | 41-45% | 1.59 / 1.31s | D KV pool 真容量不足 → fallback 反而 ↑ 至 46-51% |
`*` v2 的 P50 是假数字——超过半数请求只生成 1 个 token 就被 abort。
@@ -251,16 +252,88 @@ Decode instance could be dead, remote mooncake session ... is not alive
3. 改用 RDMAloopback 是单机模拟,生产环境换真 RDMA
4. chunked KV transfer
## 后续可以考虑的更深方案:让 D 自己决定 admission方案 D
## v5 落地方案 Dworker-mode 驱动 seed/reseed
v4 的硬 cap 抬高只是把数字调大,实际容量管理还是 replay 自己估算。代码里 `replay.py:_decode_session_soft_cap` `target_tokens = input + output`(基于当前请求的 size估算每个 session footprint
- agentic context 越攒越长target_tokens 动态增长cap 会随之缩小
- 多个并发请求查询时 replay 视图会过期
- replay 自己写了 LRU eviction 逻辑(`_reserve_decode_session_capacity_from_router_state`),与 SGLang 内部的 `maybe_trim_decode_session_cache` 重复且永远滞后
`scripts/sweep_tp1_v5_optD.sh` 真正把方案 D 落到了代码里。改动核心:把 `--kvcache-admission-mode` `local`(replay 估算) 改成 `worker`(D 决策),并扩展到 **direct_append + seed + reseed 全部路径**
SGLang 已经提供 `/session_cache/admit_direct_append` 端点(`scheduler.py:3497`D worker 自己回答能不能 admit并且查询时**主动调用 LRU eviction**。但这个端点只在 direct-to-D 路径用seed/reseed 路径用的是 replay 自己估算的 soft_cap。
### 关键代码改动
理想方案是扩展端点支持 `seed_new` / `reseed` 模式replay 完全交给 D 决策——但这需要 SGLang 侧 patch + replay 重构(~200 行),工程量比 v4 大得多
1. SGLang 侧:`scheduler.py``admit_direct_append` 端点新增 `mode` 字段,支持 `direct_append | seed`seed 模式会触发 D 真正去 reserve KV pool 块并主动调用 `maybe_trim_decode_session_cache` 做 LRU
2. Replay 侧:`replay.py` 中 reseed / turn-1 seed / large-append-reseed 都改走同一个 admit endpoint`_decode_session_soft_cap` 在 worker mode 下被完全 bypass。
3. 新增运行参数:`--kvcache-admission-mode worker``--kvcache-seed-min-turn-id 1``--kvcache-seed-max-inflight-decode -1``--kvcache-prefill-backup-policy release-after-transfer``--kvcache-prefill-priority-eviction`
### 假设
- v4 的 35% session-cap fallback 来自 replay 视图过期 + capacity-based 计算保守 → 让 D 自己看 KV pool 应该把这 35% 救回来。
- D 主动 LRU eviction 比 replay 自己写的 reservation 更准确,**应该**让更多 session 能 seed 进来。
### v5 实际结果vs v4 同配置)
| 指标 | v4 1P7D | **v5 1P7D** | v4 2P6D | **v5 2P6D** | baseline 8DP |
|------|:---:|:---:|:---:|:---:|:---:|
| Errors | 435 (10%) | **9 (0.2%)** ⭐ | 403 (9%) | **9 (0.2%)** ⭐ | 0 |
| 截断 | 43 | 42 | 36 | 42 | 68 |
| direct-to-D | 54.3% | 44.7% ↓ | 58.0% | 41.3% ↓ | - |
| **session-cap fallback** | 37.4% | **45.6%** ↑ | 34.7% | **50.6%** ↑ | - |
| no-d-capacity fallback | 0.3% | 1.2% | 0.2% | 0.8% | - |
| pd-router-turn1-seed (新可见) | - | 1.2% | - | 1.1% | - |
| pd-router-d-session-reseed (新可见) | - | 4.8% | - | 3.4% | - |
| pd-router-large-append-reseed (新可见) | - | 1.0% | - | 1.0% | - |
| Session reused | 2180 | 1990 | 2348 | 1837 | - |
| KV transfer blocks | 53K | 66K | 51K | 69K | - |
| Mean | 4.21s | 5.18s | 2.51s | 3.49s | 1.45s |
| **P50** | 1.08s | 1.59s | 0.84s | 1.31s | 0.66s |
| P90 | 13.38s | 14.67s | 6.51s | 9.09s | 3.65s |
| P99 | 24.45s | 26.09s | 18.34s | 24.92s | 8.38s |
| TTFT P50 | 0.056s | 0.21s | 0.051s | 0.24s | 0.094s |
| TTFT P90 | 11.90s | 13.06s | 2.64s | 6.90s | 0.26s |
**可靠性大幅提升**mooncake 传输超时 errors 从 9-10% 跌到 0.2%。D 真容量决策避免了 v4 那种"乐观 admit → 30s 后超时"的死亡链路。
✅ reseed / turn1-seed 路径首次显式出现,证明 admission 端点对 seed 模式确实生效了。
**session-cap fallback 不降反升**37→46% 与 35→51%)。说明 v4 的本地 soft_cap 实际上**比 D 真实容量更乐观**——admit 进来后转身就 OOM统计成了 error 而不是 fallback。
❌ 直接结果:**direct-to-D 占比下降、整体延迟全面变差**。P50/P90/P99 与 TTFT 都退步。
### Direct-to-D 子集还是稳的KVC 真实价值仍在)
| Config | n | Lat P50 | Lat P90 | TTFT P50 | TTFT P90 |
|--------|:---:|:---:|:---:|:---:|:---:|
| baseline 8DP | 4381 | 0.66s | 3.65s | 0.094s | 0.256s |
| v4 2P6D direct-to-D | 2348 | 0.499s | 2.86s | 0.043s | 0.054s |
| **v5 1P7D direct-to-D** | 1990 | 0.475s | 3.04s | 0.043s | 0.055s |
| **v5 2P6D direct-to-D** | 1837 | 0.483s | 3.04s | 0.043s | 0.054s |
direct-to-D 的尾延迟和 TTFT 与 v4 几乎完全一致(端点决策开销可忽略),**v5 的回退不是路径本身变慢,而是更多请求被赶到 fallback**。
### Fallback 路径反而比 v4 更糟
| Config | n | Lat P50 | Lat P90 | TTFT P50 |
|--------|:---:|:---:|:---:|:---:|
| v5 1P7D session-cap fallback | 2027 | 6.38s | 17.47s | 4.49s |
| v5 2P6D session-cap fallback | 2253 | 3.13s | 11.25s | 0.89s |
由于 fallback 占比上升、且这条路径本身就比 direct-to-D 慢一个数量级,整体均值被拖累得更厉害。
### v5 真正暴露的瓶颈D 的 KV pool 物理容量
把 admission 决策权交给 D 之后,瓶颈从"replay 估得太死"变成"D 真的装不下"
- 80GB H100 × `mem_fraction_static=0.835` → D 单卡 KV pool ≈ 100-150K tokens
- agentic 长 context session 单 turn footprint 50-100K
- 单 D 上能并存的 session 数量本就 2-3 个 → 7 个 D 装 50 session 基本不可能
v4 的 cap=16 之所以"看起来好",部分是因为本地 soft_cap 没真的查 D 的 free pool开了一堆**最终会失败**的 session统计成 errors 而非 fallback。v5 把这部分洗成了"诚实的拒绝"——可靠性跃升的代价是看见了真实容量上限。
### v6 应该针对什么
把 D 物理容量管理打开,而不是再调 replay
1. **prefill backup 提早 release**(已经加了 `release-after-transfer` 但可能还不够及时) → 让 P 上的 backup blocks 不要长期占用 KV pool。
2. **priority eviction 策略调优**(已开 `--kvcache-prefill-priority-eviction`):当前 LRU 可能把 hot session 误踢;需要按 session 命中频率/最近访问做加权。
3. **chunked / streamed seed**:不要一次 reserve 整个 prompt 的容量,按 chunk 分摊。
4. **跨 D 的 session migration**:当一个 D 满了但隔壁 D 空时主动迁移,而不是直接 fallback 到 P。
5. **真正的多机 RDMA**:单机 mooncake loopback 是 errors 的根因之一;上多机 + RDMA 才能让 prefill backup release 后的 KV transfer 真的稳。
工程量1-3 是 SGLang 内部改 (`scheduler.py` + `session_controller.py`)4 需要 router 协议扩展5 是部署变更。
## 关键文件与代码位置索引
@@ -272,7 +345,10 @@ SGLang 已经提供 `/session_cache/admit_direct_append` 端点(`scheduler.py:
| Header 构建 | `replay.py:2407-2424` `_build_headers` |
| Policy → router config 映射 | `benchmark.py:287-300` `_decode_policy_for/_header_mode_for` |
| Session admission 软 cap | `replay.py:889-905` `_decode_session_soft_cap` |
| 已有的 D 侧 admission 端点 | `scheduler.py:3497-3580` `admit_direct_append` |
| 已有的 D 侧 admission 端点 | `scheduler.py:3497-3580` `admit_direct_append`v5 扩展支持 `mode=seed` |
| Worker-mode admission 调用方 | `replay.py` reseed / turn1-seed / large-append-reseed 路径 |
| Prefill backup 释放策略v5 引入) | `--kvcache-prefill-backup-policy release-after-transfer` |
| Prefill priority evictionv5 引入) | `--kvcache-prefill-priority-eviction` |
| Session 在 D 上找不到的报错 | `scheduler.py:1824-1836` |
| `_should_allow_local_prefill_on_decode` | `scheduler.py:1975-1980` |
| Reseed 流程入口 | `replay.py:1665-1809` `_invoke_kvcache_seeded_router` |
@@ -287,3 +363,5 @@ SGLang 已经提供 `/session_cache/admit_direct_append` 端点(`scheduler.py:
3. **bimodal 分布是 starvation 的强信号**——v3 数据里某些 session 100% 走快路径、某些 100% 走慢路径,几乎肯定是某种"先到先得"的资源竞争。看到这种模式立刻去找硬编码 cap 或全局共享资源。
4. **测量要看分组而非整体均值**——v3 整体 P50=1.5s 看似比 baseline 慢,但拆开看 direct-to-D 子集 P50=0.495s 已经反超 baseline。整体均值被 fallback 路径拖累,但 KVC 的核心价值是真实存在的。
5. **errors 与 fallback 是同一类资源压力的两副面孔**——v4 的"低 fallback 率 + 高 error 率"不是更优解,是把容量超限的失败从"显式拒绝"伪装成"超时失败"。v5 把决策权交给真容量后fallback 升、errors 降,这是更诚实的指标,不要被 v4 的 fallback 数字误导。当看到错误率和 fallback 率呈反相关时,要警惕 admission 决策是否在说谎。