Files
agentic-pd-hybrid/docs/KVC_ROUTER_ALGORITHM.md
kzlin 9ccd853066 docs(kvc): correct reseed cost decomposition + flag D->P sync gap
After an independent Opus-agent forensic audit, the previous "(c) 增量
fetch (工程量较大,未实现)" line in V2_DEEP_ANALYSIS §4.2 was understating
the gap. The audit confirmed:

- No D->P KV transfer code exists in the framework at any layer
  (agentic_pd_hybrid orchestration, vendored SGLang disaggregation,
  or mooncake transport).
- Mooncake MooncakeKVManager has a hard role split: PREFILL = sender,
  DECODE = receiver-only loop. `add_transfer_request` asserts the
  disaggregation_mode is PREFILL.
- The BaseKVSender / BaseKVReceiver abstraction has no bidirectional slot.
- session_aware_cache.release_session only calls kv_pool_allocator.free()
  on eviction -- no serialization, no outbound network call.
- _commit_prefill_backup_residency is only called from the seed/reseed
  path (_invoke_kvcache_seeded_router). direct-to-D path never updates
  P-side backup state.
- "capacity-backup" policy semantics: it only skips the close on P after
  reseed -- the backup is the seed-time static snapshot, never refreshed
  by D-side append-prefill activity.

V2_DEEP_ANALYSIS §4.2:
- Decomposed the 3-7s reseed cost into the P-side re-prefill segment
  (1.5-3s, dominant) and the P->D mooncake transfer segment (1.5-4s).
- Quantified the realistic effect of enabling RDMA: only the transfer
  segment shrinks, reseed reduces to 1.7-3.2s, TTFT p99 ~0.7s, still
  loses to DP's 0.43s.
- Replaced the throwaway "(c) incremental fetch" line with a full
  paragraph explaining what D->P sync would require, why it's the
  largest engineering gap, and that the blocker is SGLang's radix-tree
  single-producer assumption, not the network layer.

KVC_ROUTER_ALGORITHM §9:
- Refined Open Question 3 (RDMA) to clarify it only helps the transfer
  segment, not the re-prefill segment.
- Added Open Question 4: D->P incremental KV sync as the central
  future-work contribution gap, with cited evidence for why it doesn't
  currently exist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 22:07:14 +08:00

357 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# KVC-Router面向 Agentic 多轮 LLM Serving 的 Session-Aware 调度算法
**性质**:论文级形式化规范——用于团队内部对齐 + 外部读者 onboarding。
**对象**:项目团队(统一术语);论文 reviewer算法定义
**最近更新**2026-05-11。
本文给出本项目所开发的 **KVCache-Centric Router**(以下简称 "KVC-Router")调度算法的形式化、与实现无关的定义。本文设计为可直接被论文引用,并作为"KVC 到底在谈论什么调度算法"的标准回答。
对应的参考实现位于:
- `src/agentic_pd_hybrid/policies.py``KvAwarePolicy``RoutingState`
- `src/agentic_pd_hybrid/replay.py` — orchestrationadmission RPC、reset-on-success、fallback chain
- `third_party/sglang/python/sglang/srt/managers/scheduler.py` — D-worker 端的 admission 决策
---
## 1. 问题定义
我们要服务一群多轮 agentic LLM session如 Claude Code、Codex、Cursor 等 coding agent底层是异构 worker 池,分成:
- **Prefill workers**`P`GPU 常驻的模型副本,针对长输入 prompt 的 batched prefill 做了优化。
- **Decode workers**`D`GPU 常驻的模型副本,配备 session-aware KV cache"SessionAwareCache"),具备:(i) 跨 turn 保留 session 的 KV 状态;(ii) 在本地已缓存的 prefix 上做 append-prefill无需绕回 `P`
在一个 agent turn 内,请求 `r` 到达时其对话 prefix 已经从前序 turn 累积;**新增**的 tokens工具输出、用户消息等构成小规模 **append**。驱动 KVC 设计的根本观察是:
> 当 prefix KV **已经驻留在将要解码该请求的 D worker 上**,请求的 first-token 延迟仅由 *append* 大小决定(典型 O(10²10³) tokens而非完整 prompt 大小(典型 O(10⁴10⁵) tokens
Router 的工作就是最大化满足上述条件的请求占比,同时尊重容量约束、不造成 session 无限饿死。
### 1.1 优化目标
给定来自 `S` 个 session 的请求流 `R = (r_1, r_2, ...)`,最小化 SLO 加权的 TTFT 与端到端延迟混合:
```
minimize E[ w_ttft · TTFT(r) + w_lat · E2E_Latency(r) ]
subject to capacity[d] ≤ K_d 对任意 D worker d 在任意时刻 t,
没有 session 被永久拒绝服务.
```
参考实现中通过 measurement 隐式取 `w_ttft = 1, w_lat = 1`per-D KV 池预算 `K_d` 取 SGLang 启动时上报的 `max_total_num_tokens`
---
## 2. 系统模型与记号
### 2.1 集合
| 符号 | 含义 |
|---|---|
| `P = {p₁, …, p_|P|}` | Prefill worker 池 |
| `D = {d₁, …, d_|D|}` | Decode worker 池 |
| `S` | Session 标识符集合(由上游 agent runtime 分配) |
| `H` | KV block hash 的全集(本实现中每 `BLOCK_TOKEN_BUDGET = 24` tokens 对应一个 hash |
### 2.2 请求
一个请求 `r` 是一个元组:
```
r = ⟨ s(r), t(r), prefix_hashes(r), append_len(r), input_len(r) ⟩
```
其中:
- `s(r) ∈ S` — session id
- `t(r) ∈ ` — 该 session 内的 turn index0 = 首轮)
- `prefix_hashes(r) ⊂ H` — 覆盖请求输入 prefix 的 block hash 集合
- `append_len(r) ∈ ` — 新到达、**不在** `prefix_hashes(r)` 中的 token 数
- `input_len(r) = (|prefix_hashes(r)| · 24) + append_len(r)` — 总 token 数
### 2.3 Router 状态 (`Σ`)
Router 跨请求维护的全局状态:
| 字段 | 类型 | 语义 |
|---|---|---|
| `resident[d]` | `set[H]` | Router 估计的 D `d` 当前 SessionAwareCache 中常驻的 block hash 集合router 端估计,真值在 worker 上) |
| `pin[s]` | `D {⊥}` | Session `s` 最近一次成功服务的 D`⊥` 表示从未见过 |
| `inflight[d]` | `` | 当前已派发给 `d` 但尚未完成的请求数 |
| `assigned[d]` | `` | 累计派发到 `d` 的路由决策次数(负载 tie-breaker |
| `rejects[s,d]` | `` | per-(session, D) 的 admission 拒绝计数v2 引入的 migration 机制) |
### 2.4 超参数
| 符号 | 默认值 | 描述 |
|---|---|---|
| `α``sticky_bonus` | 1 | 匹配 `pin[s]` 的 D 在评分中获得的 bonus |
| `τ_reject``migration_reject_threshold` | 3 | (s, d) 被拒绝达此次数后d 对 s 进入 blacklist |
| `τ_append``kvcache_direct_max_uncached_tokens` | 8192v2 | 走 Direct-to-D 路径允许的最大 append 长度 |
| `K_d` | 取自 SGLang `max_total_num_tokens` | per-D 的 KV 池预算 |
| `ρ` | 0.95 | 容量高水位线(隐式由 SGLang 强制) |
| `ε`(最大 fallback 重试数) | `|D| - 1` | router 在退化到 vanilla PD-disagg 之前最多探测几个 D |
### 2.5 路由结果
路由决策 `δ(r)` 取以下四种之一:
| Mode | 含义 | KV transfer |
|---|---|---|
| `Direct(d)` | r 完全在 D `d` 上执行D 在其常驻 KV 上做 append | **无**(快路径) |
| `Seed(d)` | Session 首轮P 做完整 prefillKV 通过 mooncake 传到 `d` | 完整 input |
| `Reseed(d)` | Session 之前在某个 D' 上,但已不再常驻;按 Seed 处理 | 完整 input |
| `Fallback(p, d)` | Vanilla pd-disagg 路径(其它 D 均被 blacklist 或拒绝) | 完整 input |
---
## 3. 算法
KVC-Router 由三个相互配合的过程组成:
- **Algorithm 1 (`Route`)**router 端基于评分的候选选择。
- **Algorithm 2 (`Admit`)**D-worker 端的 admission 决策(在 D scheduler 中执行,非 router
- **Algorithm 3 (`Dispatch`)**:端到端 orchestration把 Route + Admit + reset-on-success 串起来。
### 3.1 Algorithm 1`Route(r, Σ)` — 基于评分的候选选择
```
输入:请求 r状态 Σ
输出:候选 d* ∈ D若所有 D 都被过滤后仍无候选,退化分支兜底返回最少被拒的 D
1. blacklisted ← { d ∈ D : Σ.rejects[s(r), d] ≥ τ_reject }
2. C ← D blacklisted // 候选 D 集合
3. if C = ∅ : // 退化
4. return argmin_{d ∈ D} Σ.rejects[s(r), d] // 选最少被拒的 D
5. for each d ∈ C :
6. overlap(d) ← |prefix_hashes(r) ∩ Σ.resident[d]|
7. sticky(d) ← 1 if Σ.pin[s(r)] = d else 0
8. infl(d) ← Σ.inflight[d]
9. assn(d) ← Σ.assigned[d]
10. score(d) ← ⟨ overlap(d) + α·sticky(d), // 主项
sticky(d), // tie-1
infl(d), // tie-2负载小者占优
assn(d) ⟩ // tie-3
11. return argmax_{d ∈ C} score(d) // 按字典序最大
```
**说明**
- 评分是 **4 元组按字典序比较**,不是单个标量——这样避免在不同维度之间调权重。
- 第 10 行的主项 `overlap + α·sticky` 同时奖励 KV 复用与 session stickiness。取 `α=1``overlap` 以 block24 tokens为单位时**任何一次 hash 命中都压制纯 sticky 的候选**。
- 第 14 行的 blacklist 过滤防止永久绑死在已饱和的 D 上;与 Algorithm 3 的 reset-on-success 配合,限定了 migration 频率。
### 3.2 Algorithm 2`Admit(d, r, M, K)` — D-worker admission 决策
在 D worker 自己的 scheduler 内部执行(非 router这是 **KVC 的机制核心**:每个 D 自治判断能否把 `r` 当作 Directappend-only服务还是必须改走 P 路径。
```
输入D worker d请求 rd 上本地常驻的 session 集合 M_dKV 池预算 K_d
输出⟨can_admit ∈ {True, False}, mode ∈ {Direct, Seed, Reseed, ⊥}, reason⟩
1. used_tokens ← Σ_{s' ∈ M_d} resident_tokens(s', d) // D 自己的 bookkeeping
2. cap_ok ← (used_tokens + input_len(r)) ≤ ρ · K_d // 高水位线 ρ ≈ 0.95
3. if s(r) ∈ M_d : // session 在 d 上有常驻
4. if append_len(r) ≤ τ_append and cap_ok :
5. return ⟨True, Direct, ∅⟩ // → 快路径
6. elif append_len(r) > τ_append :
7. return ⟨False, ⊥, "real-large-append"⟩
8. else :
9. return ⟨False, ⊥, "no-d-capacity"⟩
10. else : // session 在 d 上无常驻
11. if cap_ok :
12. mode ← Seed if t(r) = 0 else Reseed
13. return ⟨True, mode, ∅⟩ // → 经 P 做 KV seeding
14. else :
15. return ⟨False, ⊥, "session-not-resident-no-capacity"⟩
```
**说明**
- 该过程通过同步 HTTP RPC`/admit_direct_append`)从 router 调用。RPC 阻塞直到 D scheduler 给出权威答复——这是 v5 引入的 **"worker-mode admission"**,替换了更早的 router-端容量估算(系统性偏乐观)。
- reason 字符串被回传给 router用于(i) 在 Algorithm 3 中驱动 fallback chain(ii) 标注 `execution_mode` 字段便于分析。
### 3.3 Algorithm 3`Dispatch(r, Σ)` — 端到端 orchestration
```
输入:请求 r状态 Σ
输出:执行模式 μ ∈ {Direct, Seed, Reseed, Fallback}
1. retries ← 0
2. tried ← ∅
3. while retries < ε :
4. d* ← Route(r, Σ \ {对 tried 中的 d 已 bump 过的 rejects})
5. if d* = ⊥ : break // 无候选
6. resp ← Admit(d*, r) // RPC 到 D scheduler
7. if resp.can_admit :
8. Σ.rejects[s(r), d*] ← 0 // ◀ reset-on-successv2
9. Σ.pin[s(r)] ← d*
10. Σ.inflight[d*] ← Σ.inflight[d*] + 1
11. if resp.mode = Direct :
12. 在 d* 上完整执行 rappend-prefill + decode
13. return Direct
14. else : // Seed 或 Reseed
15. p ← round_robin_next(Σ, P)
16. 在 p 上做 r 的 prefill
17. 经 mooncake 把 KV(r) 从 p 传到 d*
18. 在 d* 上 decode r
19. return resp.mode
20. else :
21. Σ.rejects[s(r), d*] ← Σ.rejects[s(r), d*] + 1
22. tried ← tried {d*}
23. retries ← retries + 1
24.
25. // ε 次重试耗尽——退化 Fallback 到 vanilla pd-disagg
26. p ← round_robin_next(Σ, P)
27. d ← round_robin_next(Σ, D)
28. 通过 ⟨p, d⟩ 走 pd-disagg(r)
29. return Fallback
```
**维持的关键不变量**
1. **不会静默过载**:一个 D 永不接受会让 `used_tokens > ρ · K_d` 的请求Algorithm 2 第 2 行)。
2. **不存在永久饿死**:对任意 session `s`,只要曾在某 D `d*` 上成功过一次,之后 `Σ.rejects[s, d*] = 0`Algorithm 3 第 8 行)。因此 blacklist 计数器不会对仍在某处成功获得服务的 session 累积——这阻止了 **v1 的 thrashing 病理**:原本 blacklist 计数器单调增长 + 退化 fallback 形成自放大的 round-robin 死循环。
3. **migration 有界**:一个 session 从 D `a` 迁移到 D `b` 必须经过连续 `τ_reject` 次在 `a` 上失败、期间无任何成功。每个 session 生命周期内的最坏 migration 次数 ≤ `(|D| 1) · τ_reject`
### 3.4 Reset-on-success为什么这是关键修复v1 → v2 演化)
v1 实现**省略了** Algorithm 3 第 8 行——一旦 `(s, d)` 累积 `τ_reject` 次拒绝d 对该 session **整个 run 永久 blacklist**。实测Migration v1`docs/MIGRATION_V1_FINDINGS_ZH.md`)触发了自放大的失效模式:
```
session s 在 d 上稳定服务 70 个 turn
↓ 瞬时 burst 让 d 短暂饱和
3 次到 d 的 admission 被拒 → rejects[s,d] = 3 → d 对 s 永久 blacklist
↓ s 迁到 d'd' 也在负载中 → 被拒 → blacklist
↓ d'' 同理
所有 D 都 blacklist → 退化 fallback round-robin → 每次重试都 bump 一次计数器
→ s 永远在 D 之间 thrashing每次都丢失 KV residency
```
reset-on-success 关上了这个回路:只要 `s` 在任一 d 上真正完成一次 Direct针对该 session 的 blacklist 立刻清零。该机制只对**持续性**(不是瞬时性)容量压力触发。
---
## 4. 性质
### 4.1 Theorem 1在有界 ε 下无永久饿死)
*假设 `τ_reject ≥ 1` 且每个 D worker 的容量非零。则对任意能在 admission 时容下的 session `s`Algorithm 3 在至多 `|D| · τ_reject` 次重试内返回 `{Direct, Seed, Reseed}` 之一;之后任意一次 Direct 成功即可清空 `s` 的所有 blacklist。*
**证明概要**每次循环要么成功return、要么恰好让某个 `rejects[s, d]` 计数器 +1第 21 行)。经过 `|D| · τ_reject` 次迭代后,每个 D 要么对 `s` 已被 blacklist`Route` 第 1 行会过滤),要么已成功(已终止)。在所有 D 都被 blacklist 的饱和点,`Route` 第 3 行返回最少被拒的 D打破对称性强制取得进展。∎
### 4.2 Theorem 2fast-path 命中下限)
*假设 session `s` 在 D `d` 上已积累 KV residency `R_s ⊂ H`,且在某 turn `t > 0` 提交的请求 `r` 满足 `prefix_hashes(r) ⊆ R_s`、`append_len(r) ≤ τ_append` 且 admission 容量充足。则 Algorithm 3 将 `r` 路由为 Direct(d)。*
**证明概要**:由 Algorithm 1`overlap(d) = |R_s|` 取得最大值;结合 `α·sticky(d) ≥ 1`d 的字典序得分严格高于任何 `prefix_hashes(r) ⊈ R_{s,d'}` 的 d'。故 `Route` 返回 d。`Admit(d, r)` 进入 `s ∈ M_d ∧ append ≤ τ_append ∧ cap_ok` 分支,返回 Direct。∎
这是 **支持架构设计的机制级保证**:只要 residency、append 大小、容量三者同时成立,快路径就被**确定性地**选中KVC 在典型场景下的 TTFT 优势是结构性属性,不是概率性。
### 4.3 复杂度
每个请求:
- `Route``O(|D|)`(每个候选 D 算一次 score。生产规模下 `|D| ≤ 8`,主要开销在 Python 层,≪ 1 ms。
- `Admit`D scheduler 内部 O(1)(查自己的 bookkeeping无全局锁
- Router 层的单请求总开销:`O(|D|)` 计算 + 1 次到目标 D 的 HTTP RTTloopback 亚毫秒,跨机数据中心约 1 ms
---
## 5. 与 baseline 的对比
| 性质 | Vanilla pd-disagg | DPcache-aware | **KVC-Router**(本文) |
|---|---|---|---|
| P/D 分离 | 是(`|P| + |D|` GPU | 否(每个 worker fused P+D | 是 |
| 跨 turn cache locality | 无(每个请求都 P→D 传 KV | 仅在单 fused worker 内部走 hash prefix 路由 | session 钉在某 D 上,本地 append-prefill |
| 同 session cache 集中度 | 无 | 散到 `|D|` 个 worker每个占 1/|D| | 集中在一个 D整段常驻 |
| 最坏 turn-2 prefill 工作量 | 完整 input 经 P→mooncake→D | 在目标 worker 上做完整 prefill带 prefix cache 命中) | 本地 `append_len ≤ τ_append` tokens |
| 容量感知 admission | 无router 盲发) | 隐式靠 worker 队列深度 | 显式的 per-D `Admit()` 决策 |
| Migration 机制 | N/A | N/A | 带 reset-on-success 的 reject-counter blacklist |
| Idle prefill 成本 | 是——P 永远在算 | 否 | 是——P 只在 cache miss 时启用(本工作 SWE-Bench 评测下约 8% 请求) |
KVC 的关键架构权衡:**用 P 端 GPU 闲置换 D 端 TTFT 稳定性**。在 per-session cache 复用率高的 agentic workload 上Inferact 的 Codex trace 报告 94.2% cache hit我们的 SWE-Bench replay 实测 91.6% Direct 命中),这个交换显著有利。在 session 短或 cache hit 低的 workload 上权衡反转、DP 胜出。
---
## 6. 符号速查表
| 符号 | 含义 |
|---|---|
| `P, D` | Prefill / Decode worker 池 |
| `s(r), t(r)` | 请求 r 的 session id 与 turn index |
| `prefix_hashes(r)` | r 输入 prefix 的 KV block hash |
| `append_len(r)` | r 中新增(未缓存)部分的 token 数 |
| `Σ.resident[d]` | Router 对 d 缓存 block 集合的估计 |
| `Σ.pin[s]` | session s 最近一次成功的 D |
| `Σ.rejects[s,d]` | per-(s,d) 的 admission 拒绝计数 |
| `α` | sticky bonus 权重(默认 1 |
| `τ_reject` | migration 阈值(默认 3 |
| `τ_append` | Direct 路径允许的 max append 大小v2 默认 8192 |
| `K_d` | D worker d 的 KV 池预算 |
| `ρ` | 容量高水位(默认 0.95 |
| `ε` | fallback 重试上限(默认 `|D| 1` |
| `δ(r)` | 路由决策:`Direct(d)` / `Seed(d)` / `Reseed(d)` / `Fallback(p, d)` |
---
## 7. 本工作评测中实际使用的默认参数
| 参数 | 取值 | 说明 |
|---|---|---|
| `|P|, |D|` | 1, 31P3D 配置) | 单机 4× H100 80GB |
| `α` | 1 | |
| `τ_reject` | 3 | |
| `τ_append` | 8192 | v2 调优后取值v0/v1 用 2048 |
| `K_d` | 92104 tokens | SGLang 按 `mem_fraction_static=0.835` 自动算出 |
| `ρ` | 隐式 ~0.95 | 由 SGLang 的 `max_total_num_tokens` 强制 |
| `ε` | 2 | `|D| 1 = 2` |
| 每次 run 的 session 数 | 52 | SWE-Bench 50sess trace |
| 总请求数 | 4449 | |
| Time-scale | 1.0(真实 trace 时序) | |
| 并发 | 32 | |
---
## 8. Anti-patternsKVC **不**是什么)
1. **KVC 不仅仅是 kv-aware routing**。DP 和 KVC 都可以跑 `kv-aware` policyKVC 在此之上加了三件事:(i) session 钉定,(ii) worker 端 admission(iii) 带 reset-on-success 的 migration。如果在比较 "KVC vs DP" 时缺这三个要素的任何一个,**测的就不是 KVC 与 DP 的差异**。
2. **KVC 在 policy 项里不直接感知容量**`Route` 不查 per-D 容量;容量感知完全经由 `Admit` 拒绝来传导。我们刻意做了这层分层——把容量判断放进 `Route` 会引入"换 D"的决策空间,导致 orphan KV 滞留问题。
3. **KVC 不保证 load balance**。一个 session 若能舒服地装在某个 D 上,可能永远钉在那里,而其它 D 大部分时间空闲。在低容量压力下这是设计意图;高压力下 Theorem 1 的 migration 会触发再均衡。
4. **`Fallback` 不是"降级路径"**。它和 vanilla pd-disagg 请求结构性等价延迟特征相同。KVC 的价值在于让 Fallback 占比在典型 agentic workload 下 ≪ 10%。
---
## 9. 公开问题reviewer 关注点)
以下问题在当前评测中尚未解决,主动列出以保持透明:
1. **Session 钉定相对于纯 P/D disaggregation 的边际贡献是多少?** 需要 `naive 1P3D` 对照实验vanilla SGLang xPyD不带 KVC 层)——仓库当前缺失(见 `docs/V2_DEEP_ANALYSIS_ZH.md §4.7`)。
2. **Algorithm 3 在更高压下行为如何**(例如 ts=10 加速、session 数 ≫ |D|·K_d/peak_input当前 ts=1 评测对应真实 agentic 区间,但算法在更高负载下的鲁棒性未经实验验证。
3. **真 RDMA 下的 reseed 代价**:本次评测的 37 s reseed 延迟由两段组成——P 端 re-prefill1.5-3s+ P→D mooncake transfer1.5-4s。当前 sweep 用的是 TCP loopback启用 IB/RoCE节点有 mlx5_0/_1 @ 200 Gb/s × 2 active需在 sweep 加 `--force-rdma --ib-device mlx5_0`)只能压缩 transfer 段到 ~200ms**不动 re-prefill 段**。预期 TTFT p99 从 1.28s 降到 ~0.7s(仍输 DP 0.43s)。待独立验证。
4. **D→P 增量 KV 同步(核心 future-work 缺口)**reseed 长尾的真正消除需要让 P 端 backup 跟上 D 的 direct-to-D append 增长。经独立 forensic 审查,**当前代码、vendored SGLang、mooncake 三层均无 D→P KV transfer 实现**mooncake `MooncakeKVManager` 是 PREFILL=sender / DECODE=receiver 的硬角色分支(`add_transfer_request` 上有 `assert disaggregation_mode == PREFILL` 硬约束),`BaseKVSender` / `BaseKVReceiver` 抽象无 bidirectional slot`session_aware_cache.release_session` 在驱逐时只调 `kv_pool_allocator.free()` 无出站,`_commit_prefill_backup_residency` 唯一 caller 是 seed/reseed 路径;`capacity-backup` policy 的真实语义只是"reseed 完不关 P streaming session"——backup 是 seed-time 的静态快照,不随 direct-to-D append 同步。要实现 D→P 增量同步,工程量 ~1-2 周,最难的不是 mooncake 加 D-sender / P-receiver 角色(~400 LOC而是 **SGLang radix tree 改成允许从外部 worker 喂数据**——radix cache 当前假设单一生产者(本 worker model 输出)。这是论文里最值得做的 contribution 之一。
5. **v2 代码路径下的确定性**v0 代码库的 ts=1 N=3 categorical 确定性已经证实;新增的 reset-on-success 分支与 threshold=8192 路径未被独立 re-validate。两个额外的 N=1 run 即可解决。
---
## 10. 论文引用建议
论文中提到本算法时建议表述:
> "We use the KVC-Router scheduling algorithm (Algorithms 13 of [our paper], formally defined in our supplementary materials). The router selects a decode worker by lexicographic scoring on `(overlap+α·sticky, sticky, inflight, assigned)` (Algorithm 1), defers the admission decision to the chosen worker via a synchronous RPC (Algorithm 2), and maintains a per-(session, decode worker) rejection counter that is reset on every successful Direct admission (Algorithm 3). This last detail — reset-on-success — is what distinguishes our v2 from the unstable v1 implementation that exhibits self-amplifying session thrashing."
---
**附录 A — 算法步骤到代码实现的对照**
| 算法步骤 | 文件 | 符号 |
|---|---|---|
| `Route` 第 511 行 | `policies.py:189202` | `KvAwarePolicy.select` 内层循环 |
| `Route` 第 14 行blacklist 过滤 + 退化分支) | `policies.py:182187, 204211` | `migration_reject_threshold``select` 的 fallback |
| `Admit` | `third_party/sglang/python/sglang/srt/managers/scheduler.py` | `handle_admit_direct_append_request` |
| `Dispatch` 第 8 行reset-on-success | `replay.py: _run_request` | finish 路径中的 reset |
| `Dispatch` 第 21 行(记录 reject | `replay.py: _run_request` | `state.record_admission_reject(...)` |
| 超参数 `τ_append` | CLI flag | `--kvcache-direct-max-uncached-tokens` |
| 超参数 `τ_reject` | CLI flag | `--kvcache-migration-reject-threshold` |