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>
357 lines
22 KiB
Markdown
357 lines
22 KiB
Markdown
# 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` — orchestration:admission 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 index(0 = 首轮)
|
||
- `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`) | 8192(v2) | 走 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 做完整 prefill,KV 通过 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` 以 block(24 tokens)为单位时,**任何一次 hash 命中都压制纯 sticky 的候选**。
|
||
- 第 1–4 行的 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` 当作 Direct(append-only)服务,还是必须改走 P 路径。
|
||
|
||
```
|
||
输入:D worker d,请求 r,d 上本地常驻的 session 集合 M_d,KV 池预算 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-success(v2)
|
||
9. Σ.pin[s(r)] ← d*
|
||
10. Σ.inflight[d*] ← Σ.inflight[d*] + 1
|
||
11. if resp.mode = Direct :
|
||
12. 在 d* 上完整执行 r(append-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 2(fast-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 RTT(loopback 亚毫秒,跨机数据中心约 1 ms)。
|
||
|
||
---
|
||
|
||
## 5. 与 baseline 的对比
|
||
|
||
| 性质 | Vanilla pd-disagg | DP(cache-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, 3(1P3D 配置) | 单机 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-patterns(KVC **不**是什么)
|
||
|
||
1. **KVC 不仅仅是 kv-aware routing**。DP 和 KVC 都可以跑 `kv-aware` policy;KVC 在此之上加了三件事:(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 代价**:本次评测的 3–7 s reseed 延迟由两段组成——P 端 re-prefill(1.5-3s)+ P→D mooncake transfer(1.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 1–3 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` 第 5–11 行 | `policies.py:189–202` | `KvAwarePolicy.select` 内层循环 |
|
||
| `Route` 第 1–4 行(blacklist 过滤 + 退化分支) | `policies.py:182–187, 204–211` | `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` |
|