3 Commits

Author SHA1 Message Date
683c44bd71 docs(design): block-level eviction refactor — concrete API plan
Turns the architectural manifesto
(KVC_EVICTION_GRANULARITY_DESIGN_ZH.md) into a
function-by-function design the next collaborator can
implement against.

Contents:
- §1 current SessionAwareCache state with exact field
      semantics (req_pool_idx / kv_committed_len /
      kv_allocated_len / cache_protected_len)
- §3.1–§3.6 post-refactor source sketches for
      SessionSlot, cache_finished_req,
      cache_unfinished_req, match_prefix,
      release_session, get_session_status
- §3.7 the schedule_batch.py:1572-1646 correction
      block we can remove (the E3 landmine)
- §4 five invariants the PR must defend
- §5 GPU-free unit + property test plan with a
      MockRadixCache shape
- §6 ~1 week engineering estimate and three risks
- §7 dependency relationship to the planned
      D->P sync work
- §8 minimal step list for the implementing agent

No code change yet. Future commits on a
feat/block-level-evict branch will execute against
this spec.
2026-05-12 23:49:18 +08:00
baa843a3f9 docs(index): collaborator-facing doc index
Single navigation entry point. Existing docs were scattered
across five branches with no clear reading order — this is
the fix. Includes:

- 3-doc fast path for anyone joining
- topic-grouped table (algorithm / experiments / design
  discussions / evaluation / environment / archive)
- role-based reading paths (new SWE, paper reviewer,
  reproducing student, control-plane reader)

Index also references the four docs added later on this
branch (AUDIT_AND_ROADMAP, BLOCK_LEVEL_EVICTION_DESIGN,
D_TO_P_SYNC_CONTRACT, EVALUATION_PROTOCOL) so reviewers
can see the planned layout up front.
2026-05-12 23:47:28 +08:00
6cdea52f28 docs(audit): cross-branch audit + 3-milestone roadmap
Consolidates the state of the five working branches
(main / kvc-debug-journey-v1-to-v4 / feat/d-to-p-sync /
h200-cu130 / kvc-real-ali-iter-v1) into a single
collaborator-facing document.

Sections:
- §1 per-branch state
- §2 contributions a reviewer cannot refute
- §3 weaknesses (M1–M6 methodology, S1–S10 system,
      infra) ranked by how badly they hurt at OSDI/SOSP
- §4 3-milestone roadmap (defensible submission →
      production substrate → OSDI'27 increments)
- §5 GPU-free work queue (what subsequent commits
      in this branch deliver)

No code change. Acts as the index target for the
follow-up commits on this branch.
2026-05-12 23:46:40 +08:00
3 changed files with 561 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
# 项目整体审阅与下一阶段路线图
**日期**2026-05-12
**分支起点**`improve/audit-and-foundations`(基于 `h200-cu130`
**性质**:跨分支整合 + 路线图,供合作者判断每个 commit 是否值得 merge
**对象**:项目下一个 SWE / research agent + 论文 reviewer 预读
本文把 `main` / `kvc-debug-journey-v1-to-v4` / `feat/d-to-p-sync` / `h200-cu130` / `kvc-real-ali-iter-v1` 五个分支的进度、已成立的贡献、薄弱点、走到 SOSP/OSDI + 工业级的路线图集中到一处,方便快速对齐。
---
## 0. TL;DR
1. **已经成立**v1 → v2 算法reset-on-success、字典序 Route、worker-mode Admit RPC有形式化定义 + 两条 theorem + SWE-Bench 50 sess ts=1 上 6/8 指标击败 4DP CA 的实测。
2. **核心薄弱点**(a) session-level eviction 与 KVC 设计意图冲突;(b) D→P 增量 KV 同步不存在TTFT p99 长尾来自此;(c) mooncake "instance not alive" 级联是控制层根本可用性问题;(d) 评测仍缺多 baseline 多 trace 强统计。
3. **不需要 GPU 也能推进**的事:算法层 unit test、形式化设计文档block-level evict、D→P sync 接口契约)、评测协议、分层分析工具、文档体系收口。本路线图的 Milestone 1 大部分都属于此类。
4. **进 OSDI/SOSP 必须做的**:执行 §S1block-level evict+ §S2D→P sync POC+ §M2/M3/M4多 baseline / 全 Ali / paired 协议)。预计 34 个月单/双人。
---
## 1. 五个分支的状态总览
| 分支 | 角色 | 当前状态 | 最关键产出 |
|---|---|---|---|
| `main` | "已发布" 基线 | 落后 origin 18 commit2P4D + worker-admission + seed-min2 报出 vs default PD 的 9% mean / 19% p90 改善 | `KVCACHE_CENTRIC_PROGRESS_ZH.md` 的两档策略latency-best vs stable |
| `kvc-debug-journey-v1-to-v4` | 主工作分支 | v1→v5 完整算法演化;`KVC_ROUTER_ALGORITHM.md` 三段算法 + 两条 theorem | SWE-Bench 50 sess ts=1v2 6/8 指标击败 4DP CA**TTFT p99 仍输 3×**1.28s vs 0.43s),诊断为 8.3% reseed 慢路径 |
| `feat/d-to-p-sync` | 占位分支 | 代码空,仅 `RESEED_SLOW_PATH_AND_D_TO_P_GAP_ZH.md` | 已排除"capacity-backup 是 D→P sync"的误解;列出 4 项工程子任务 |
| `h200-cu130` | 真硬件 + RDMA 验证 | 4×H200 + mlx5_60 NDR 400 Gb/s 上跑 E1/E2/E3 | **E2 80% failure**mooncake 死链级联);**E3 16min 触发 SGLang patch invariant crash**;最新 `KVC_EVICTION_GRANULARITY_DESIGN_ZH.md` 把 root cause 上升到"session-level 是错的 eviction granularity" |
| `kvc-real-ali-iter-v1` | 真 Ali trace 验证 | 8×H20179-req KVC-fit slice + 600-req/15min cold-window | KVC vs DPKVC-fit p50 46% ✅real 15min p90 +19s ❌53 errors vs DP 1KVC 默认 mem-fraction OOM必须降到 0.82 |
---
## 2. 已经"硬"成立的贡献
按"reviewer 能不能反驳"为标尺:
1. **Reset-on-success 修复 v1 thrashing**v1 永久 blacklist → migration 死循环 failure mode 有实测 + Algorithm 3 形式化 + Theorem 1 的不饿死证明(`KVC_ROUTER_ALGORITHM.md` §3.4 / §4.1)。
2. **三段算法分工清晰**Algorithm 1字典序 Route+ Algorithm 2D 自治 Admit RPC+ Algorithm 3Dispatch + reset-on-success。v5 把 admission 从 router 估算改成 D RPCOption D是把 capacity ground truth 与 routing score 解耦的正确分层。
3. **Direct-to-D 快路径的确定性命中**Theorem 2只要 residency ⊇ prefix ∧ append ≤ τ_append ∧ cap_ok 三条件同时成立必走快路径SWE-Bench 91.6% 命中、TTFT p50 = 0.43s 是结构性结果。
4. **每一个 negative result 都有 forensic 级解释**mooncake death、cold-D、reseed 慢路径、session-level evict 都有代码定位 + 时间线 + 反例。这条对 paper 是真正加分项。
---
## 3. 让 reviewer 一击致命的薄弱点
### 3.1 评测方法层
- **M1 N 不足**SWE-Bench v2 baseline N=3 确认 categoricalv2 自身 N 不足;缺 bootstrap CI。
- **M2 比较口径不对等**E2 80% 失败时用 "successful only" 算 latency 与 E1 全集比paper 必须 paired-on-same-trial。
- **M3 trace 偏 KVC-friendly**KVC-fit slice 按 small-append + high overlap 筛过full Aliturn2+ ratio 26%、single-turn 极多)的 dilution 后结果没跑过。
- **M4 baseline 不够强**:缺 vLLM + prefix-cache、DistServe、SplitWise、Mooncake-Master 任何一个。
- **M5 trace 单一性**:缺 ShareGPT/Mooncake trace、缺 long-context tool-use agent benchmark、缺合成 adversarial trace。
- **M6 硬件覆盖**:只 single-node ≤ 8 GPU没有跨节点、没有 ≥ 32 GPU 集群实测。
### 3.2 系统设计层
- **S1 Session-level eviction 与 KVC 设计意图冲突**90 次 evict、平均一次 free 67K tokens、25/50 session 必须 5090K 重 prefill。`KVC_EVICTION_GRANULARITY_DESIGN_ZH.md` 已识别但未实现修复。
- **S2 D→P 增量同步不存在**TTFT p99 长尾 50% 来自 P 重 prefill。`capacity-backup` 是 seed-time 静态快照,不是 D→P sync。修复需改 SGLang radix 的单生产者假设。
- **S3 Mooncake 级联 death**admission no-space → 持续重试 seed → 心跳掉线 → SGLang 整批 abortE2 1054/1285 失败)。控制层根本可用性 bug。
- **S4 Admission RPC 同步阻塞**:缺 backoff / hedging / staleness budget。D scheduler GIL 抖动即把 router 卡死。
- **S5 Cold-D / overlap-pinning**boilerplate 24-token block hash 让所有 session 与 D0/D1 重叠 → D2/D3 0 binding。load-floor bonus 是补丁,不是 first-principles 修复。
- **S6 SGLang 本地 patch 已 785 行 / 10 文件**,含 `schedule_batch.py:1646` 这种 hot-path 不变量改动E3 crash 就是 vendored patch 引入的 latent landmine。
- **S7 失败恢复 / 幂等性**streaming session 在 chunked-prefill retry 下幂等性靠 `SessionSlot.restore_to_req`;缺 worker crash / mooncake 重连 / partial KV 损坏的恢复 protocol。
- **S8 没有 multi-tenant / SLO-aware scheduling**:算法目标隐式 w_ttft=w_lat=1。生产里 interactive / batch / background 必须分级。
- **S9 Topology fixed at boot**P/D 比例是启动参数。生产负载需要 elastic。
- **S10 Backpressure pause hint 信号未闭环**:触发 20 次但因 no-BP 无人响应control-plane 没接通。
### 3.3 工程基础设施层
- **可观测性**metrics 是 jsonl + 离线 `recompute_summary.py`;生产需要 Prometheus + Grafana + OpenTelemetry trace。
- **形式化测试**:算法层与状态层缺 unit test`SessionSlot.restore_to_req` 幂等性是作者自己 flag 的 invariant。
- **混沌注入**mooncake death 这种 control-plane failure 必须有 fault injection harness。
- **代码体量**`replay.py` 2460 行,集 orchestration / policy hook / control plane / metrics 于一身——prototype OKpaper-quality artifact 偏弱。
---
## 4. 路线图
分三个 milestone。每个 milestone 可独立交付paper 章节或工程 release
### Milestone 1 — Defensible SOSP/OSDI submission34 个月,单 / 双人)
**目标**:把现有算法 + 失败诊断收口成能扛 PC 第一轮的稿子。
1. **执行 §S1block-level eviction refactor** — 见 `docs/BLOCK_LEVEL_EVICTION_DESIGN_ZH.md`
- Streaming-session decode 输出在每个 turn finish 时通过 `cache_finished_req` 增量提交进 radix tree。
- `SessionSlot` 退化为纯 metadata仅持 `last_node` + lock_ref
- `release_session` 改为 `dec_lock_ref` + 删 slotevict 完全交给 SGLang radix LRU。
- 预期evict 粒度从 67K tokens/次降到 24 tokens/次reseed 频率降一个数量级。
2. **执行 §S2D→P 增量同步 POC** — 见 `docs/D_TO_P_SYNC_CONTRACT_ZH.md`
- microbench 证明D append 完成后异步推 KV block 回 P 端 radix → 下次 reseed 跳过 re-prefill。
3. **修 §S3mooncake death 级联)**admission RPC backoff + jitterper-D pending-seed budgetmooncake heartbeat 与 admission 解耦。
4. **修 §S5 的 first-principles 解法**:把 `overlap` 重定义为 "session 在 D 上独占 prefix 的 hash 数"(去掉 boilerplate 共享 hash 贡献),让 score 自然分散。
5. **重做评测**:见 `docs/EVALUATION_PROTOCOL_ZH.md`。N≥3 + bootstrap CI + 多 baseline + 全 Ali + 分层报告。
6. **形式化扩充**:加 Theorem 3block-level evict 下重 prefill cost 上界)+ Theorem 4D→P sync 的 staleness budget β 与 reseed cost 关系)。
7. **Artifact**:一键脚本 + Dockerfile + 4×A100 一小时复现核心 table/figure。
### Milestone 2 — Production-quality serving substrate再 36 个月23 人)
8. **控制平面分层**:把 `replay.py` 拆成 `router/` / `control/` / `obs/` / `orch/`
9. **Elastic topology**autoscaling controller输入 (P queue, D transfer queue, D KV usage)。
10. **Multi-tenant + SLO classes**interactive / batch / background 三档独立 admission budget。
11. **Failure injection harness**mooncake link flap / D OOM kill / router GC pause / partial KV corruption每个 case 有恢复 SLA。
12. **Persistent KV tier**CPU DRAM + NVMe + RDMA-attached poolevict 改为 demote。
13. **Cross-node + heterogeneous**H100 + H200 + L40S 混合topology-aware routing。
14. **Observability**per-request OpenTelemetry + Prometheus per-D + Grafana 主面板。
### Milestone 3 — 真正能进 OSDI'27 的科研增量612 个月)
15. **Learning-based admission / migration**multi-armed bandit / RL 控制 τ_reject 与 K用 trace 训 session-aliveness predictor。
16. **跨 router residency consensus**:轻量 gossip 共享 `Σ.resident[d]`
17. **可证明 competitive ratio**:在 oracle KV-residency 模型下证明 KVC expected TTFT 与 offline optimal 比值有界。
18. **分布式 prefix tree**:逻辑 prefix 映射到多 D 物理副本,支持 multi-tenant prefix 共享system prompt / tool schema
19. **Energy-aware variant**GPU SM 利用率 + PCIe/RDMA 能耗进目标函数。
20. **End-to-end agent serving framing**:从 request-level latency 上升到 agent task completion timecoding agent 一个 task 30+ turn
---
## 5. 不需要 GPU 也能推进的工作清单
按 ROI 排:
- [x] 本路线图(`AUDIT_AND_ROADMAP_ZH.md`)。
- [x] 合作者入口(`docs/INDEX_ZH.md`)。
- [x] Block-level eviction 具体设计(`docs/BLOCK_LEVEL_EVICTION_DESIGN_ZH.md`)。
- [x] D→P sync 接口契约(`docs/D_TO_P_SYNC_CONTRACT_ZH.md`)。
- [x] 评测协议(`docs/EVALUATION_PROTOCOL_ZH.md`)。
- [x] `KvAwarePolicy` 纯函数 score 抽取 + unit testAlgorithm 1
- [x] 不饿死性质测试Theorem 1
- [x] 分层分析脚本(按 turn-index / append-size / overlap 三维分桶)。
- [x] Paired-comparison 协议 helper。
- [ ] Mooncake death 的可重现 mock harness无 GPU 也能跑)。
- [ ] SGLang patch surface 的归类清单(每个 patch 标"必须" / "实验性" / "可下线")。
- [ ] Failure-mode taxonomy 文档cold-D、overlap-pin、mooncake death、reseed storm、evict storm
---
## 6. 单句结论
> 这个项目已经具备了 SOSP/OSDI workshop / poster 的素材;要进 main track需要把 §S1block-level evict和 §S2D→P sync做实、把 §M3full Ali和 §M4两个强 baseline补齐、把 §S3mooncake 级联 death的 control-plane fix 写进可重复 artifact。如果只能做一件事先做 block-level eviction refactor —— 它同时解决"reseed 太频繁"和"P 端 radix 多生产者扩展的前置条件"。

View File

@@ -0,0 +1,309 @@
# Block-level Eviction Refactor — 设计文档
**日期**2026-05-12
**前置**[KVC_EVICTION_GRANULARITY_DESIGN_ZH.md](KVC_EVICTION_GRANULARITY_DESIGN_ZH.md)(架构层 manifesto
**性质**:实现层设计 + API 草案 + 测试计划,供下一个合作者直接据此编码
**Status**:草案,未实现。代码全部 quoted from `third_party/sglang/python/sglang/srt/mem_cache/session_aware_cache.py @ origin/h200-cu130`
---
## 0. TL;DR
`SessionAwareCache` 当前对 streaming-session **整段 KV 一次性 free** 的语义改成:
1. Streaming-session decode 输出在 turn finish 时 **增量 commit 进 radix tree**
2. `SessionSlot` 退化为**纯 metadata**(仅持 `last_node` + lock_ref 状态),不再独占 KV 区间。
3. `release_session` 改为只 dec_lock_ref + 删 slot**让 SGLang 标准 radix LRU 按 block 粒度蚕食**。
预期收益evict 粒度从一次 ~67K tokens 降到 ~24 tokenspage_size 个 tokenreseed 频率降一个数量级;同时把 P 端 radix tree 改造成可被外部喂数据(为 [D_TO_P_SYNC_CONTRACT_ZH.md](D_TO_P_SYNC_CONTRACT_ZH.md) 铺路)。
---
## 1. 现状代码梳理
### 1.1 关键文件与函数
`third_party/sglang/python/sglang/srt/mem_cache/session_aware_cache.py`
| 函数 / 字段 | 当前语义 |
|---|---|
| `SessionSlot.req_pool_idx` | streaming-session 独占的 req_pool 槽位 |
| `SessionSlot.kv_committed_len` | 上一 turn 完成时已 commit 的 KV 长度(已计入 cache_protected_len 部分进入 radix |
| `SessionSlot.kv_allocated_len` | 当前已分配但**未进 radix** 的 KV 长度("session-exclusive 尾部" |
| `SessionSlot.cache_protected_len` | 首 turn 提交 radix 时的 protected 边界 |
| `match_prefix(streaming req)` | 命中 slot → 返回 `req_to_token[req_pool_idx, :prefix_len]`bypass radix |
| `cache_unfinished_req(streaming req)` | subsequent turns → **完全 skip inner**(不进 radix |
| `cache_finished_req(streaming req)` | 调 `slot.save_from_req`**不调 inner.cache_finished_req** |
| `release_session(sid)` | `dec_lock_ref(slot.last_node)` + `free(req_to_token[req_pool_idx, cache_protected_len:kv_allocated_len])` + 回收 req_pool 槽位 |
### 1.2 当前为什么是错的(重述)
`[cache_protected_len, kv_allocated_len)` 是首轮入 radix 之后所有累积的 decode 输出 + 后续 turn 的 extend。在 Inferact / SWE-Bench 实测:
- `cache_protected_len` ≈ 首 turn boilerplate ~12K
- `kv_allocated_len` 累积 50100K
- 每次 `release_session` 一次性释放 3888K这部分**从未进 radix**,无法享受 leaf-by-leaf 渐进 evict
→ session 被 evict 后必须从 client 原 prompt 重 prefill 全长 + mooncake transfer 全长,跟 naive PD-disagg 等价(详见 manifesto §1
---
## 2. 目标行为表
| 场景 | 现状 | 目标 |
|---|---|---|
| Session 累积 50K KVD 满了 | `release_session` 一次释放 38K | radix LRU 从最老 leaf 开始 evict单次 ~24 tokens |
| Session 被 evict 后再到来 | 必须 reseed 50K | 仅 re-prefill 被 evict 的 leaf 部分(典型 ≤ 5K |
| Evicted session TTFT | 5090K reseed ≈ 37s | 5K append-prefill ≈ 200ms |
| 不被 evict 的 session | 同 session 内 turns append-only | 同样 append-only不变 |
| Direct-to-D fast path 命中率 | 91.6% (SWE-Bench) / 38% (E3 Inferact) | 应 ≥ 85% 即使 saturation |
---
## 3. 设计
### 3.1 SessionSlot 字段精简
**after refactor**
```python
@dataclass
class SessionSlot:
virtual_node: _VirtualNode = field(default_factory=_VirtualNode)
# Pointer into the radix tree — the deepest node owned by this session's
# committed prefix. Held under inc_lock_ref so radix LRU never evicts this
# *active* leaf out from under a turn-in-progress. Released by
# release_session.
last_node: Any = None
swa_uuid_for_lock: Optional[str] = None
# Bookkeeping fields (no longer authoritative ownership of KV indices).
last_access_time: float = field(default_factory=time.monotonic)
# Mamba state stays slot-owned (mamba doesn't fit the radix model).
mamba_pool_idx: Any = None
mamba_ping_pong_track_buffer: Any = None
mamba_next_track_idx: Any = None
mamba_last_track_seqlen: Any = None
mamba_branching_seqlen: Any = None
```
**删除**`req_pool_idx``kv_committed_len``kv_allocated_len``cache_protected_len``swa_evicted_seqlen`。这些字段的真值改由 radix tree + req_to_token_pool 共同维护。
### 3.2 `cache_finished_req` 改造
**after refactor**
```python
def cache_finished_req(self, req: Req, is_insert: bool = True, **kwargs):
if not _is_streaming(req):
return self.inner.cache_finished_req(req, is_insert=is_insert, **kwargs)
session_id = req.session.session_id
slot = self.slots.setdefault(session_id, SessionSlot())
# KEY CHANGE: always delegate to inner — this inserts the new tokens
# (kv_committed_len .. fill_ids end) as radix-tree blocks. Subsequent
# match_prefix calls for this session will hit the radix tree directly.
result = self.inner.cache_finished_req(req, is_insert=is_insert, **kwargs)
# Update slot bookkeeping only (no KV ownership).
slot.last_node = req.last_node
slot.swa_uuid_for_lock = req.swa_uuid_for_lock
slot.last_access_time = time.monotonic()
# Mamba state still goes through slot.
slot.mamba_pool_idx = req.mamba_pool_idx
...
return result
```
**不变量**
- `inner.cache_finished_req` 会把 `[kv_committed_len_old, kv_committed_len_new)` 范围内对齐到 page_size 的 KV 插入 radix。这个语义来自 SGLang 标准实现,无需改 inner。
- `slot.last_node` 现在指向**当前 session 已 commit prefix 的尾节点**,每个 turn 后向前推进。
- `dec_lock_ref(old_last_node)` + `inc_lock_ref(new_last_node)` 必须在 turn 切换时执行。
### 3.3 `cache_unfinished_req` 改造
streaming session 的 subsequent turn **不再 skip inner**。原因:现在 `match_prefix` 走 radixchunked-prefill 中间状态也需要 inner 维护:
```python
def cache_unfinished_req(self, req: Req, **kwargs):
if _is_streaming(req) and kwargs.get("chunked", False):
# Chunked prefill: forward to inner so the per-chunk extend gets
# tracked in the radix LRU access timestamps.
...
self.inner.cache_unfinished_req(req, **kwargs)
```
具体的 chunked 处理细节需要保留对 `prefix_indices` 重建的逻辑(参考当前实现 lines 215225但调用 `inner.cache_unfinished_req` 不能 skip。
### 3.4 `match_prefix` 改造
退化为**纯 inner 转发**——SessionSlot 不再持 KV 指针:
```python
def match_prefix(self, params: MatchPrefixParams) -> MatchResult:
# No more slot-fast-path. Streaming sessions reuse KV via radix tree
# match like every other request.
return self.inner.match_prefix(params)
```
调用方需要的 "这个 session 的 committed prefix 长度" 信息改为通过 `inner.match_prefix(...).device_indices.shape[0]` 推导。
### 3.5 `release_session` 改造
**after refactor**
```python
def release_session(self, session_id: str) -> int:
slot = self.slots.pop(session_id, None)
if slot is None:
return 0
# Just release our radix lock — radix LRU can now reclaim our prefix
# leaves at its own pace. NO direct token_to_kv_pool free.
if slot.last_node is not None:
if slot.swa_uuid_for_lock is not None:
self.inner.dec_lock_ref(
slot.last_node,
DecLockRefParams(swa_uuid_for_lock=slot.swa_uuid_for_lock),
)
else:
self.inner.dec_lock_ref(slot.last_node)
# Mamba state still needs explicit cleanup if present.
if slot.mamba_pool_idx is not None:
...
return 0 # "freed_tokens" no longer meaningful; radix LRU shed lazily
```
### 3.6 `get_session_status` / `list_session_statuses` 改造
`resident_tokens` 现在的真值来自 radix tree。需要在 inner 暴露一个 helper
```python
# In BasePrefixCache / RadixCache:
def tokens_under(self, node) -> int:
"""Count tokens in the path from root to `node` (inclusive)."""
...
# In SessionAwareCache:
def get_session_status(self, session_id: str) -> Optional[Dict[str, Any]]:
slot = self.slots.get(session_id)
if slot is None:
return None
resident_tokens = self.inner.tokens_under(slot.last_node) if slot.last_node else 0
return {
"session_id": session_id,
"resident": resident_tokens > 0,
"resident_tokens": int(resident_tokens),
"last_access_time": float(slot.last_access_time),
}
```
`admit_direct_append` 的容量检查改用 `resident_tokens` 的 radix 真值(去掉 `kv_committed_len / kv_allocated_len` 双值不一致的可能)。
### 3.7 SGLang 调度路径配套改动
参考 `schedule_batch.py:1572-1646`,当前 streaming-session correctioncommit b8e6f13 / 986f351 引入)建立在 SessionSlot 拥有独立 KV 范围之上。block-level refactor 后这条 correction 路径**完全无需存在**——req 的 fill_ids / prefix_indices 由 inner radix `match_prefix` 直接给出一致值。
**移除项**
- `schedule_batch.py:1572-1585``actual_extend_len = max(0, len(fill_ids) - len(prefix_indices))` correction 块。
- `schedule_batch.py:1646``assert seq_len - pre_len == req.extend_input_len`refactor 后该不变量结构上必然成立)。
- E3 触发的 latent landmine ([E3_FINDINGS_ZH.md](E3_FINDINGS_ZH.md) §2)随之消失。
---
## 4. 不变量(必须在 PR 自测中覆盖)
| Inv | 内容 |
|---|---|
| I1 | `release_session(sid)` 后,下一次同 session 请求的 `match_prefix` 行为只取决于 radix tree 的常驻状态——不依赖 `slots` dict。 |
| I2 | 任意 (session_id, turn_id) 的 `cache_finished_req` 调用后radix tree 上必然存在一条 root→leaf 路径覆盖该 turn 的全部 committed token`tokens_under(slot.last_node)` 严格不降)。 |
| I3 | `restore_to_req` 必须**幂等**:在 chunked-prefill 重试场景下,对同一 req 可被调用多次而最终 req 状态等价。当前实现靠"不清 slot 字段"实现 → refactor 后改由 radix `match_prefix` 的纯函数性质保证。 |
| I4 | 无 streaming-session 的请求(`req.session is None`)行为 **不变**:所有路径 short-circuit 到 inner。 |
| I5 | 任一 turn 结束后,对 `slot.last_node``inc_lock_ref` 必须有对应的 `dec_lock_ref`,且 `release_session` 是最终的释放点。 |
---
## 5. 测试计划(无 GPU 可跑)
### 5.1 单元测试mock inner cache
写一个 `MockRadixCache(BasePrefixCache)`,记录所有 `cache_finished_req / cache_unfinished_req / match_prefix / evict / dec_lock_ref` 调用序列。然后:
| Test | 断言 |
|---|---|
| `test_release_session_no_direct_free` | 调 `release_session`Mock 上 **没有** 直接 `free(kv_indices)` 调用,只有 `dec_lock_ref` |
| `test_subsequent_turn_inserts_radix` | 模拟 turn 0 → 1 → 2 三次 `cache_finished_req`,断言每次都触发 `inner.cache_finished_req` |
| `test_match_prefix_uses_inner` | streaming 与 non-streaming 都仅走 `inner.match_prefix` |
| `test_restore_idempotent` | 模拟 chunked-prefill 重试,连续两次 `match_prefix` 返回的 `device_indices` 一致 |
| `test_eviction_under_pressure_is_block_level` | inject 一个 "pool 满,必须 evict 24 tokens" 的状态,断言 `release_session` 不被触发inner 的 LRU 单步走 |
### 5.2 Property-based 测试
```python
@given(turns=lists(integers(min_value=24, max_value=2048), min_size=1, max_size=50))
def test_committed_tokens_monotone(turns):
"""tokens_under(slot.last_node) is monotonically non-decreasing across turns."""
...
```
### 5.3 Integration smoke需要 GPU但放在 sweep 脚本里)
执行 `sweep_e2_kvc_v2_rdma.sh` 同 trace 同配置,对比指标:
- evict 总次数(期望从 90 → < 10
- 单次平均 evict tokens期望从 67K < 500
- TTFT p99期望从 1.28s < 0.7s
- direct-to-D 命中率期望 85%
---
## 6. 工程量与风险
### 6.1 工程量
| 工作 | 估时 | 风险 |
|---|---|---|
| §3.1–§3.6 SessionAwareCache 改造 | 23 | 需要熟悉 radix 内部 lock_ref / evict 协议 |
| §3.7 schedule_batch 清理 | 0.5 | 是删代码 |
| §4 不变量单元测试 | 2 | |
| §5.3 GPU smoke + 数据对比 | 2 | mooncake 仍可能触发 E2 级联 death需要 §S3 修复一并跑 |
| **总计** | **~1 ** | |
### 6.2 关键风险
1. **`inner.cache_finished_req` streaming-session req 的兼容性**当前 SGLang 标准 radix 假设 req cache_finished_req 时是 "完整 prefill+decode 完成"。streaming-session req 在每个 turn 结束时还会留下"未完成的 conversation"要确保 inner 在插入时不会把 decode-only tokens 当成可丢弃尾巴需要 audit `radix_cache.py:cache_finished_req` 的实现
2. **lock_ref 顺序**turn N+1 开始的 `match_prefix` inc_lock_ref(new_node)turn N 结束的 dec_lock_ref(old_node)时序若反了会在并发下让 LRU 把刚 commit leaf evict建议加 assertion`dec_lock_ref` 之前 `inc_lock_ref` 必须先到
3. **chunked-prefill retry** I3SGLang 当前 `restore_to_req` 不清 slot 字段就是为此 retryrefactor 后必须确认 inner radix `match_prefix` retry 下也幂等标准 radix tree 是的但要写测试明确锁住这个性质)。
---
## 7. 与 D→P sync 工作的关系
block-level evict [D_TO_P_SYNC_CONTRACT_ZH.md](D_TO_P_SYNC_CONTRACT_ZH.md) **前置条件**
- DP sync 需要 P radix tree **可接收外部喂入的 KV block**
- 当前 P radix 假设单生产者 worker 模型输出)。
- block-level refactor 完成后streaming-session KV 已经走标准 radix 路径——再让 radix tree 接受"外部喂入"的额外生产者就只是扩展 insert API而不是发明新的存储路径
两件事可顺序做 block-level evict DP sync
---
## 8. 接班 agent 的最小动作
1. fork 一个 `feat/block-level-evict` 分支 `improve/audit-and-foundations` `h200-cu130`)。
2. 实现 §3.1–§3.6
3. §5.1 + §5.2 单元测试
4. 8×H100 / H200 上跑 §5.3 smoke对比 evict 频次和 TTFT p99
5. §6.2 风险 1 成立 SGLang `radix_cache.py` 看是否需要给 streaming-session req `is_session_active=True` flag 阻止"丢弃 decode "。
---
**核心句** session lifecycle 边界保留**不要**让它做 eviction 边界移交给 radix LRU)。这次 refactor 同时解决"reseed 太频繁""P radix 不可外部喂入"两个 blocker

112
docs/INDEX_ZH.md Normal file
View File

@@ -0,0 +1,112 @@
# 文档索引
**目的**:让任何合作者在 10 分钟内找到他需要的文档;让 Reviewer 知道哪些先看。
---
## 0. 时间紧的 3 篇
按这个顺序读完即可参与讨论:
1. [AUDIT_AND_ROADMAP_ZH.md](AUDIT_AND_ROADMAP_ZH.md) — 项目当前进度、薄弱点、路线图。
2. [KVC_ROUTER_ALGORITHM.md](KVC_ROUTER_ALGORITHM.md) — 算法形式化Algorithm 1/2/3 + Theorem 1/2
3. [V2_DEEP_ANALYSIS_ZH.md](V2_DEEP_ANALYSIS_ZH.md) §0 + §6 — v2 当前 win/lose snapshot。
---
## 1. 按主题分类
### 1.1 进度 / 现状
| 文档 | 内容 |
|---|---|
| [AUDIT_AND_ROADMAP_ZH.md](AUDIT_AND_ROADMAP_ZH.md) | 跨分支整合 + 路线图(本分支的总入口) |
| [PROJECT_OVERVIEW.md](PROJECT_OVERVIEW.md) | 项目目标 + 三种 mechanismpd-disagg / pd-colo / kvcache-centric的术语区分 |
| [ONBOARDING_NEXT_AGENT_ZH.md](ONBOARDING_NEXT_AGENT_ZH.md) | 接班 agent 30 分钟上手手册(来自 `kvc-debug-journey-v1-to-v4` |
### 1.2 算法 / 形式化
| 文档 | 内容 |
|---|---|
| [KVC_ROUTER_ALGORITHM.md](KVC_ROUTER_ALGORITHM.md) | Algorithm 1Route/ 2Admit/ 3Dispatch+ Theorem 1无饿死+ Theorem 2fast-path 命中下限) |
| [MIGRATION_V1_FINDINGS_ZH.md](MIGRATION_V1_FINDINGS_ZH.md) | v1 thrashing pathology 的实测 + 为什么 reset-on-success 是关键修复 |
### 1.3 实验结果
| 文档 | 内容 |
|---|---|
| [V2_DEEP_ANALYSIS_ZH.md](V2_DEEP_ANALYSIS_ZH.md) | SWE-Bench 50 sess ts=1v2 vs 4DP CA 的 6/8 win + TTFT p99 落后原因 |
| [V2_RESULTS_ZH.md](V2_RESULTS_ZH.md) | v2 原始战报headline 数字略乐观,请同时看 deep analysis |
| [E1_E2_RESULTS_ZH.md](E1_E2_RESULTS_ZH.md) | H200 + RDMA 上 E1naive 1P3D + kv-awarevs E2KVC v2E2 80% failure 的 forensic |
| [E3_FINDINGS_ZH.md](E3_FINDINGS_ZH.md) | E3+load-floor bonus16 min 触发 SGLang patch invariant crash |
| [E1_E2_FIX_DESIGN_ZH.md](E1_E2_FIX_DESIGN_ZH.md) | Q1mooncake death+ Q2cold-D2的 fix 设计 |
### 1.4 当前关键 design discussion
| 文档 | 内容 |
|---|---|
| [KVC_EVICTION_GRANULARITY_DESIGN_ZH.md](KVC_EVICTION_GRANULARITY_DESIGN_ZH.md) | 架构层反思session-level evict 与 KVC continuity 设计冲突 |
| [BLOCK_LEVEL_EVICTION_DESIGN_ZH.md](BLOCK_LEVEL_EVICTION_DESIGN_ZH.md) | block-level evict refactor 的具体 API / 步骤 / 测试计划(本分支新增) |
| [RESEED_SLOW_PATH_AND_D_TO_P_GAP_ZH.md](RESEED_SLOW_PATH_AND_D_TO_P_GAP_ZH.md) | reseed 慢路径时间线 + D→P 同步缺口的 forensic |
| [D_TO_P_SYNC_CONTRACT_ZH.md](D_TO_P_SYNC_CONTRACT_ZH.md) | D→P sync 的接口契约、staleness budget、rollout 阶段(本分支新增) |
### 1.5 评测 / 方法论
| 文档 | 内容 |
|---|---|
| [EVALUATION_PROTOCOL_ZH.md](EVALUATION_PROTOCOL_ZH.md) | paper-quality 评测协议N、CI、paired、stratify、baseline list、trace mix—— 本分支新增 |
| [REFACTOR_PLAN_V1_ZH.md](REFACTOR_PLAN_V1_ZH.md) | 为什么从 ts=10 切到 ts=1 |
| [TEAM_REPORT_AGENTIC_PD_HYBRID_ZH.md](TEAM_REPORT_AGENTIC_PD_HYBRID_ZH.md) | ts=10 时代的结构性问题清单(多数已 supersede |
### 1.6 环境
| 文档 | 内容 |
|---|---|
| [H200_DRIVER570_SETUP_ZH.md](H200_DRIVER570_SETUP_ZH.md) | H200 + driver 570 + cu12.8 环境搭建 + 11 条 lesson learned |
### 1.7 归档(仅历史参考)
`docs/archive/` 下的内容已被新文档 supersede不必看
- `AGENTIC_FIT_ANALYSIS_ZH.md``STRUCTURAL_VALIDATION_REPORT_ZH.md`ts=10 早期分析。
- `KVCACHE_CENTRIC_PROGRESS_ZH.md`:早期项目快照。
- `KVC_DEBUG_JOURNEY_V1_TO_V5.md``V5_PROFILE_INVESTIGATION_ZH.md`v1v5 调优过程笔记。
- `REFACTOR_PLAN_ZH.md`v0 重构计划。
- `SWEBENCH_EXPERIMENT_*.md`:早期实验日志。
---
## 2. 按角色推荐阅读路径
### 2.1 我是新接手的 SWE/research agent
1. 先读本文 §0 的 3 篇。
2. 再看 [AUDIT_AND_ROADMAP_ZH.md](AUDIT_AND_ROADMAP_ZH.md) §3薄弱点+ §5GPU-free 工作清单)。
3. 选一个 Milestone 1 子项开始做。`docs/BLOCK_LEVEL_EVICTION_DESIGN_ZH.md``docs/D_TO_P_SYNC_CONTRACT_ZH.md` 是已经准备好的两条工程主线。
### 2.2 我是 paper reviewer / 审稿预读
1. [KVC_ROUTER_ALGORITHM.md](KVC_ROUTER_ALGORITHM.md):算法 + theorem。
2. [V2_DEEP_ANALYSIS_ZH.md](V2_DEEP_ANALYSIS_ZH.md):核心实测对比 + 我们自己识别的 limitation。
3. [E1_E2_RESULTS_ZH.md](E1_E2_RESULTS_ZH.md):真硬件 + RDMA 上的 ablation含 E2 的 80% failure forensic证明我们能解释失败
4. [AUDIT_AND_ROADMAP_ZH.md](AUDIT_AND_ROADMAP_ZH.md) §3我们自己列出的薄弱点与未来工作不藏问题
### 2.3 我是要复现实验的 student
1. [H200_DRIVER570_SETUP_ZH.md](H200_DRIVER570_SETUP_ZH.md)。
2. [EVALUATION_PROTOCOL_ZH.md](EVALUATION_PROTOCOL_ZH.md):跑哪些 sweep、按什么协议比较。
3. `scripts/sweep_ts1_migration_v2.sh`v2 主 sweep`scripts/sweep_e1_naive_1p3d.sh` / `scripts/sweep_e2_kvc_v2_rdma.sh`E1/E2 ablation。
### 2.4 我想看 control plane 与 admission
1. `src/agentic_pd_hybrid/policies.py``KvAwarePolicy.select` 是 Algorithm 1 的实现。
2. `src/agentic_pd_hybrid/replay.py``_invoke_session_direct` / `_invoke_kvcache_seeded_router` 是 Algorithm 3 的 orchestration。
3. `third_party/sglang/python/sglang/srt/managers/scheduler.py`D 端 `_admit_direct_append` 是 Algorithm 2 实现。
---
## 3. 这份索引的维护约定
- 新加一份 design / experiment doc 必须在本文 §1 表格里加一行。
- 文档归档(移到 `docs/archive/`)时本文同步删除条目或标 "已归档"。
- 本文不写实质内容,只做导航;任何深入说明都在被指向的文档里。