docs: archive deprecated docs to docs/archive/, drop E1 from onboarding
Two cleanups:
1. Drop "E1: naive 1P3D default" experiment from the onboarding manual.
GPU hours are precious; naive 1P3D + policy=default has near-certain
loss on multi-turn cache hit (it's round-robin without prefix awareness),
so the comparison doesn't add information vs E1=naive 1P3D kv-aware.
The new manifest has only 2 runs: E1 (naive 1P3D kv-aware) + E2 (KVC
v2 + RDMA). Run-time budget drops from 16.5h serial to 11h serial /
5.5h parallel. Updated:
- §0 TL;DR ("3 组" -> "2 组")
- §2 H1 hypothesis (drop "default and kv-aware each one" -> just kv-aware)
- §3.1 experiment matrix (3 rows -> 2 rows + rationale for the drop)
- §3.2 startup config (drop E1 default section, renumber E2/E3 -> E1/E2)
- §6 decision table + expected-range table
- §7 FAQ ("3 个 E1-E3" -> "2 个 E1-E2")
- §9 deliverables
2. Move 8 deprecated docs to docs/archive/:
AGENTIC_FIT_ANALYSIS_ZH.md (ts=10 era analysis; superseded)
STRUCTURAL_VALIDATION_REPORT_ZH.md (ts=10 era validation; superseded)
KVC_DEBUG_JOURNEY_V1_TO_V5.md (v1-v5 sweep process notes)
V5_PROFILE_INVESTIGATION_ZH.md (v5 1Hz polling investigation)
REFACTOR_PLAN_ZH.md (v0 plan; superseded by V1)
KVCACHE_CENTRIC_PROGRESS_ZH.md (earliest 2026-04-27 progress)
SWEBENCH_EXPERIMENT_PROGRESS.md (early SWE trace setup)
SWEBENCH_EXPERIMENT_RESULTS.md (early SWE result snapshot)
All cross-references in active docs (V2_DEEP_ANALYSIS / V2_RESULTS /
REFACTOR_PLAN_V1 / TEAM_REPORT / ONBOARDING) rewritten from
`docs/FOO.md` to `docs/archive/FOO.md` via sed pass.
Added `docs/archive/README.md` explaining what each archived doc is
and when (if ever) to reopen it. Designed so a new reader hitting
the archive dir immediately knows it's not required reading.
After this commit the active docs in docs/ are 9 files (down from 17),
which should make the onboarding doc's "Level 1 / Level 2 / Level 3"
classification self-evident.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
434
docs/archive/AGENTIC_FIT_ANALYSIS_ZH.md
Normal file
434
docs/archive/AGENTIC_FIT_ANALYSIS_ZH.md
Normal file
@@ -0,0 +1,434 @@
|
||||
# Agentic 场景下的结构性设计缺陷分析
|
||||
|
||||
**日期**:2026-05-06
|
||||
**对照数据**:`outputs/qwen3-30b-tp1-v5-optD-baseline-rerun/exp2_2p6d_run1_*`(KVC kv-aware Option D,2P6D,4449 reqs / 52 sessions)+ `outputs/qwen3-30b-tp1-exps/exp1_8way_dp_cache_aware_summary.json`(同 trace 8-way DP cache-aware baseline)。
|
||||
**模型**:Qwen3-30B-A3B(TP1),单机 8×H100 80GB。
|
||||
**研究问题**:把 SWE trace 视为"真实 agentic"的代表,KVC 机制相对 vanilla DP 系统性输在哪里——除了"D 容量 4.6× 过载"之外的结构性原因。
|
||||
|
||||
> 本文是对 `docs/KVC_DEBUG_JOURNEY_V1_TO_V5.md` 与 `docs/V5_PROFILE_INVESTIGATION_ZH.md` 的补充:版本演进与瓶颈定位之外,从设计层看哪些假设和真实 agentic workload 不匹配。
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
按重要性排序的结构性缺陷:
|
||||
|
||||
| # | 缺陷 | 数据 | 修复方向 | 工程量 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | **KvAwarePolicy 不感知 D 容量;session 永久 pin 到首次落点 D** | session 平均访问的不同 D 数 = **1.00**;direct-to-D 命中率呈极端双峰(15 session 0-20%、14 session 80-100%) | score 函数加 capacity-aware 项;允许跨 D session 迁移 | 中 |
|
||||
| 2 | **D 端 LRU 只能 evict idle session,hot session 永远踢不掉** | D 跑全程仅 9-43 次 trim 事件 vs 80-150 次 transfer 错误;token_usage 顶到 1.00 | 加 score-based eviction(按访问频率/最近性多层) | 中 |
|
||||
| 3 | **没有 D→Router→Replay 的 backpressure 通道** | concurrency 一路 32 不降;D 失败时 replay 无感 | admission 响应加 `recommended_pause_ms`;replay 端按它降并发 | 小 |
|
||||
| 4 | **Admission HTTP round-trip 与 scheduler 主循环耦合** | v5+profile 仅加 1Hz polling 就让 errors 从 9 涨到 415 | 拆成 lock-free `/probe` + 进 scheduler 队列的 `/commit_evict` | 中 |
|
||||
| 5 | **P-side round-robin 不感知 D 健康** | prefill-0 出 367 KVTransferError,prefill-1 仅 4——但请求量近乎对半 | router 选 P 时考虑目标 D 健康度 | 中 |
|
||||
| 6 | **Replay 端 session footprint 估算膨胀 30×** | `_estimate_session_resident_tokens = input + output`,把 turn-50 的 80K 上下文当成"需要全新 80K 空间" | 改成"增量 token"估算 | 小 |
|
||||
| 7 | **time-scale=10 把测试条件人为推到失真区间** | inter-turn gap p50 从 2.5s 压到 0.25s——KVC 想利用的"自然 idle 窗口"被消除 | 跑一组 time-scale=1 baseline 验证 | 小(仅配置) |
|
||||
|
||||
**最重要的对照事实**:同 trace、同硬件、同模型下 8-way DP cache-aware(无 PD 拆分、无 KVC、无 session 抽象):
|
||||
|
||||
| 指标 | 8-way DP CA | v5 KVC 2P6D |
|
||||
|---|---|---|
|
||||
| Errors | **0** | 372 (8.4%) |
|
||||
| Latency mean | **1.43s** | 3.50s |
|
||||
| Latency P50 | **0.65s** | 1.11s |
|
||||
| Latency P99 | **8.37s** | 20.37s |
|
||||
| TTFT mean | **0.12s** | 2.13s |
|
||||
| TTFT P90 | **0.26s** | 6.47s |
|
||||
| Per-worker 请求量分布 | 508–619(±10%) | 561–858(±26%) |
|
||||
|
||||
**naive DP 在每一项都赢,包括 latency mean 的 145% 优势**。这定义了 KVC 在该 workload 下"必须超过"的基线。
|
||||
|
||||
---
|
||||
|
||||
## 1. Session 永久 pin 到 D + 容量盲选(最核心问题)
|
||||
|
||||
### 1.1 现象
|
||||
|
||||
每个 session 在整次运行中只访问 **1.00 个不同 D worker**(见上文数据)。结合 direct-to-D 命中率分布:
|
||||
|
||||
```
|
||||
direct-to-D 命中率分桶(n=52 sessions):
|
||||
0-20%: 15 sessions ← 几乎每 turn 都失败回退到 P→D 全量传输
|
||||
20-40%: 7
|
||||
40-60%: 11
|
||||
60-80%: 5
|
||||
80-100%: 14 sessions ← 几乎每 turn 都走 direct-to-D 快路径
|
||||
```
|
||||
|
||||
**几乎没有中间态**——这是典型的不公平资源分配信号。
|
||||
|
||||
被饿死与被照顾的 session 在工作量上差异明显:
|
||||
- 饿死 session 平均 peak input:56,011 token
|
||||
- 顺利 session 平均 peak input:31,344 token(**1.8× 差距**)
|
||||
|
||||
**大 session 倾向被饿死**——因为它们在容量已紧张的 D 上更容易触发 admission 拒。
|
||||
|
||||
### 1.2 根因(代码级)
|
||||
|
||||
`policies.py:166-172` `KvAwarePolicy.select`:
|
||||
|
||||
```python
|
||||
score = (
|
||||
overlap + sticky * self.sticky_bonus, # 主项: 历史 KV overlap
|
||||
sticky, # 二级: 是否 last_decode_worker
|
||||
inflight_penalty, # 三级: 当前 inflight 数(很小)
|
||||
assignment_penalty, # 四级: 累计被分配数(更小)
|
||||
)
|
||||
```
|
||||
|
||||
评分中**完全无 D 当前容量项**。Session X 第一次落到 D-2 时积累 hash_id 在 D-2 上;之后无论 D-2 多满,X 的 turn N+1 都会被打分到 D-2(因为 overlap 主导)。
|
||||
|
||||
更糟的是 `RoutingState.decode_resident_blocks`(`policies.py:46`)从不缩减——即使 D 早 evict 了某些块,replay 仍认为它们在那。运行中期所有 D 的 overlap 集合都接近"trace 全部 hash_id",policy 退化为纯 sticky。
|
||||
|
||||
### 1.3 后果——具体到 session 的体验
|
||||
|
||||
**饿死 session(如 session 50400,105 turns,0 次 direct-to-D)每 turn 流程**:
|
||||
|
||||
1. policy 选 D(永远是同一个)
|
||||
2. admission 拒(D 容量已被占住)
|
||||
3. 走 fallback-session-cap → P 全量 prefill 50K-100K token
|
||||
4. mooncake 推 KV → D 仍无空间 → 32s timeout 或 KVTransferError
|
||||
5. 用户每 turn 体验 5-10s 延迟,反复出错
|
||||
|
||||
**顺利 session(如 session 3840,118 turns,97% direct-to-D)每 turn 流程**:
|
||||
|
||||
1. policy 选 D(永远是该 session 的初始 D)
|
||||
2. admission 通过(这个 session 一直占着这个 D 的 slot)
|
||||
3. direct-to-D:D 上 append-prefill 几百 token,零 P 介入、零 mooncake transfer
|
||||
4. TTFT 0.043s、E2E 0.495s
|
||||
|
||||
**这不是"平均慢一点",是结构性不公平**——SLO 视角下 P99 是被饿死那 15 session 的尾巴拉出来的。
|
||||
|
||||
### 1.4 为什么 naive DP 反而赢
|
||||
|
||||
8-way DP cache-aware 用纯 hash-based 路由,没有 session 抽象,没有 PD 拆分:
|
||||
|
||||
- 每个请求按 prefix hash 路由到一个 worker → 同 session 的 turn 在 worker 上自然有 prefix 命中
|
||||
- 容量过载时 SGLang 自己的 radix cache + 调度器统一管 KV 池
|
||||
- 不存在 admission/fallback/reseed 路径
|
||||
- 不存在 mooncake transfer
|
||||
- per-worker 负载误差 ±10%(vs KVC ±26%),自动接近均衡
|
||||
|
||||
**KVC 引入的 session affinity / KV 复用 / admission 三件套,在容量紧张时反而加剧了不均衡,没有任何一项能挽回 vs DP 的差距。**
|
||||
|
||||
### 1.5 修复方向
|
||||
|
||||
`KvAwarePolicy.select` 里加:
|
||||
|
||||
```python
|
||||
# 当前 D 容量利用率(worker-mode admission 已经能查到)
|
||||
capacity_penalty = -worker_capacity_used_ratio[worker.worker_id]
|
||||
|
||||
# 当多个 D 都有 overlap 时,按容量挑最空的;
|
||||
# 当某 D 容量 > 阈值时,禁止该 D 进入候选
|
||||
if worker_capacity_used_ratio[worker.worker_id] > HARD_CAP:
|
||||
continue
|
||||
|
||||
score = (
|
||||
overlap_capped, # overlap 但限幅,避免单个 D 永远赢
|
||||
capacity_penalty, # ← 新增
|
||||
sticky,
|
||||
inflight_penalty,
|
||||
)
|
||||
```
|
||||
|
||||
更激进的修法:当一个 session 被某 D 反复拒 N 次后,主动 release 它在该 D 上的 session 状态,**允许下次 turn 走另一个 D**(代价是丢失已积累的 KV,但目前 fallback 路径本来也丢了)。
|
||||
|
||||
---
|
||||
|
||||
## 2. D 端 LRU eviction 跟不上压力
|
||||
|
||||
### 2.1 数据
|
||||
|
||||
每个 D 全程:
|
||||
|
||||
| Worker | Trim 事件(主动 LRU) | KVTransferError + OOM | 峰值 token_usage |
|
||||
|---|---:|---:|---:|
|
||||
| decode-0 | 9 | 0 | 0.99 |
|
||||
| decode-1 | 43 | 12 (4 err + 8 oom) | 0.99 |
|
||||
| decode-2 | 16 | 459 (153 err + 306 oom) | 0.97 |
|
||||
| decode-3 | 37 | 87 (29 err + 58 oom) | 0.99 |
|
||||
| decode-4 | 28 | 270 (90 err + 180 oom) | **1.00** |
|
||||
| decode-5 | 30 | 279 (93 err + 186 oom) | **1.00** |
|
||||
|
||||
**LRU 触发频率比错误次数低 5-15 倍。** D-4 / D-5 直接顶到 token_usage=1.00。
|
||||
|
||||
### 2.2 根因
|
||||
|
||||
`scheduler.py:2040` `evict_idle_streaming_sessions_lru` 的 idle 判定:
|
||||
|
||||
```python
|
||||
# 只能 evict "所有 req 都 finished + streaming 模式" 的 session
|
||||
```
|
||||
|
||||
但 SWE 高并发下每个 session 几乎一直有 inflight req(time-scale=10 又压缩了 inter-turn gap)。**hot session 永远不 idle,LRU 永远找不到东西可踢**。结果 D 一路开到 100% → 下一笔 transfer 来直接 OOM/timeout。
|
||||
|
||||
### 2.3 修复方向
|
||||
|
||||
引入分层 eviction:
|
||||
|
||||
1. **Idle session 优先**(当前)
|
||||
2. **冷 session 次优**(最近 N 秒无访问,即使有 inflight,也可以 retract 那个 inflight 让位)
|
||||
3. **hot session 强制 retract**(在 hard cap 触发时)
|
||||
|
||||
vanilla SGLang 已有 `disagg_decode_prealloc_queue.retracted_queue` 机制(看 `admit_direct_append` 引用),但**没有人主动触发 retract**——目前只有内部异常时才会进 retracted_queue。需要把 retract 提升为正常 admission 路径的一部分。
|
||||
|
||||
---
|
||||
|
||||
## 3. 没有 D→Replay 的 backpressure 通道
|
||||
|
||||
### 3.1 名词解释
|
||||
|
||||
**Backpressure(反压)** = 流式系统下游过载时把信号反向传给上游让它降速。例:TCP 滑动窗口、Kafka consumer lag、gRPC HTTP/2 flow control。
|
||||
|
||||
### 3.2 当前状态
|
||||
|
||||
- D 端 transfer queue 堆 → 32s 后 timeout → 抛 KVTransferError
|
||||
- error 抛回 P → P 抛给 router → router 抛给 replay → replay 走 fallback 路径
|
||||
- **整个链路上没有"D 过载,请慢点发"的信号**——concurrency 一直保持上限
|
||||
|
||||
后果:D 一旦开始失败,会**持续失败**(因为 replay 没降速),直到 D 自己消化完积压。
|
||||
|
||||
### 3.3 修复方向
|
||||
|
||||
`admit_direct_append` 响应里加:
|
||||
|
||||
```python
|
||||
{
|
||||
"can_admit": ...,
|
||||
"recommended_pause_ms": int, # ← 新增:下次发同类请求前建议等多久
|
||||
"queue_depth": int, # ← 新增:D transfer queue 当前深度
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
replay 端在 admission 拒被拒时按 `recommended_pause_ms` 降并发或退避。**这是最便宜的一条改动**——不改协议、不改 SGLang 内部,只改两端代码。
|
||||
|
||||
---
|
||||
|
||||
## 4. Admission RPC 与 scheduler 耦合——结构 vs 工程的精确边界
|
||||
|
||||
### 4.1 现象
|
||||
|
||||
`docs/V5_PROFILE_INVESTIGATION_ZH.md` 报告:仅加 1Hz `/server_info` polling 就让 EXP2 errors 从 9 涨到 415。`/server_info` 在 scheduler 主循环里遍历 session slots 算 `is_idle`,1 Hz × 8 worker 就足以扰动调度。
|
||||
|
||||
但实际负载下 admission RPC 频率远高于 1Hz:每个 turn 1 + reseed + direct-to-D 都调一次。concurrency=32 + 4449 reqs / ~2700s ≈ **每秒 16+ 次 admission RPC**。
|
||||
|
||||
### 4.2 这是结构问题还是工程问题——精确拆解
|
||||
|
||||
`admit_direct_append`(`scheduler.py:3581`)做两件事:
|
||||
|
||||
```python
|
||||
# (a) 读池子状态——轻
|
||||
available_tokens = self.token_to_kv_pool_allocator.available_size()
|
||||
|
||||
# (b) 触发 LRU 扫描——重,且必须修改池子状态
|
||||
trim_result = self.maybe_trim_decode_session_cache(...)
|
||||
```
|
||||
|
||||
| 部分 | 性质 | 是否能靠工程化解决 |
|
||||
|---|---|---|
|
||||
| (a) 读池子状态 | 几个原子读 | **完全可工程化**——做成 lock-free shared-memory snapshot 即可 |
|
||||
| (b) LRU eviction | 修改 GPU 池子,必须独占 | **结构性的**——Python GIL + 共享 GPU 池子无法并发修改 |
|
||||
|
||||
**关键观察**:实际负载里 (b) 是少数路径——大部分 admission 只需要"看一下够不够",不需要立即 evict。
|
||||
|
||||
### 4.3 工程化修复方案
|
||||
|
||||
把 admission API 拆成两个端点:
|
||||
|
||||
```
|
||||
POST /session_cache/probe ← 90% 流量
|
||||
- 只读 lock-free snapshot
|
||||
- 返回 (can_admit_estimate, available_tokens, queue_depth)
|
||||
- 不进 scheduler 队列
|
||||
|
||||
POST /session_cache/commit_evict ← 10% 流量
|
||||
- probe 不够时才调
|
||||
- 进 scheduler 队列,做实际 LRU
|
||||
- 保留当前 admit_direct_append 语义
|
||||
```
|
||||
|
||||
snapshot 由 scheduler 在每个 step 末尾写到一段 mmap 共享内存(atomic publish);replay 端 mmap 读,零 syscall 零序列化。一秒内能撑数千次 probe。
|
||||
|
||||
### 4.4 关于"协程/多线程/多进程/换语言"
|
||||
|
||||
| 工具 | 对本问题的实际效果 |
|
||||
|---|---|
|
||||
| asyncio 协程 | SGLang 已用,对 scheduler 主循环本身无帮助 |
|
||||
| Python 多线程 | GIL 拦着,且 GPU 池子状态只能 scheduler 进程改 |
|
||||
| 多进程 | scheduler 已是独立进程;问题是它**自己的 step 循环**串行了 admission 与 decode |
|
||||
| orjson / uvloop | 网络/JSON 加速 5-10×,但 LRU 遍历不在那条热路径 |
|
||||
| Rust/C++ 重写 scheduler | 把 LRU 遍历提速 5-10×,但**结构性共享问题仍在** |
|
||||
|
||||
**正确的工程化解法是重设计 API(拆 probe / commit),不是单纯换更快的库或语言。**
|
||||
|
||||
---
|
||||
|
||||
## 5. P-side 路由不感知 D 健康
|
||||
|
||||
### 5.1 数据
|
||||
|
||||
```
|
||||
prefill-0: 367 KVTransferError, 361 "Decode instance could be dead"
|
||||
prefill-1: 4 KVTransferError, 0 "Decode instance could be dead"
|
||||
|
||||
请求量对比:
|
||||
prefill-0: 2225 requests
|
||||
prefill-1: 2224 requests ← 几乎对半
|
||||
```
|
||||
|
||||
**两 P 请求量完全均衡,错误率差 92×**。日志里 prefill-0 的错误反复指向某个特定 D(`10.45.80.47:XXXXX`)——它跟某个 hot D 形成了"死亡链路"。
|
||||
|
||||
### 5.2 根因
|
||||
|
||||
`pd_router.py:43-49` 的 P 选择是裸 round-robin:
|
||||
|
||||
```python
|
||||
prefill_url, bootstrap_port = self.config.prefill_urls[
|
||||
self.prefill_cursor % len(self.config.prefill_urls)
|
||||
]
|
||||
```
|
||||
|
||||
不知道 D 是否健康,不会避开"正在和 D-X 死磕"的 P。
|
||||
|
||||
### 5.3 修复方向
|
||||
|
||||
router 选 P 时考虑 (P 当前 inflight transfer 数, 目标 D 健康度) 联合得分。健康度可以用 §3 提的 `queue_depth` 字段。
|
||||
|
||||
---
|
||||
|
||||
## 6. Replay 端 session footprint 估算膨胀 30×
|
||||
|
||||
### 6.1 代码
|
||||
|
||||
`replay.py:898-899`:
|
||||
|
||||
```python
|
||||
def _estimate_session_resident_tokens(request: TraceRequest) -> int:
|
||||
return request.input_length + request.output_length
|
||||
```
|
||||
|
||||
被用于 `_decode_session_soft_cap`(`replay.py:1051`)和 `_should_admit_new_decode_session`。
|
||||
|
||||
### 6.2 问题
|
||||
|
||||
对一个已经在 D 上有 80K KV 的 turn 50:
|
||||
- 真实增量需求:input 新增几千 token + output 几百 token = ~3K
|
||||
- 估算返回值:80K + 1K = 81K(**膨胀 ~27×**)
|
||||
|
||||
后果:router-mode admission 系统性误判——本来能 admit 的 session 被 replay 自己拒掉。v5 worker-mode 让 D 自己看真实容量部分修了这个,**但 KvAwarePolicy 选 D 时仍用这个膨胀估算**——选 D 仍然是错的。
|
||||
|
||||
### 6.3 修复
|
||||
|
||||
```python
|
||||
def _estimate_session_resident_tokens(request: TraceRequest) -> int:
|
||||
if request.turn_id == 1:
|
||||
return request.input_length + request.output_length
|
||||
# turn 2+: only the increment matters for additional reservation
|
||||
return max(0, request.input_length - request.cached_tokens) + request.output_length
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. time-scale=10 测量失真
|
||||
|
||||
### 7.1 它是什么
|
||||
|
||||
`replay.py` 把原始 trace 每个请求的 `timestamp` 字段做 `t / time_scale` 缩放后再按这个时间发。
|
||||
|
||||
- 原始 trace 跨度 ~6000s(≈100 分钟)
|
||||
- time-scale=10 → 实际 replay 跨度 ~600s(≈10 分钟)
|
||||
|
||||
### 7.2 为什么这么设计
|
||||
|
||||
**纯粹为了节省测试时间**——单次 1× 跑 100 分钟,sweep 5 版 × 3 重复 = 25h GPU 时间;10× 只要 2.5h。
|
||||
|
||||
### 7.3 它扭曲了什么
|
||||
|
||||
| 维度 | 原始 trace | replay (time-scale=10) |
|
||||
|---|---|---|
|
||||
| inter-turn gap p10 | 1.6s | 0.16s |
|
||||
| inter-turn gap p50 | 2.5s | 0.25s |
|
||||
| inter-turn gap p90 | 7.8s | 0.78s |
|
||||
| inter-turn gap max | 261s | 26s |
|
||||
|
||||
真实 agentic 用户/agent 在每个 turn 之间停 2-8 秒(思考、打字、tool call)。**这些间隙正好是 KVC 想利用的"自然 idle 窗口"**——session 短暂 idle 时 LRU 可以 evict、其他 session 可以 admit。
|
||||
|
||||
time-scale=10 把这些窗口压到 0.2-0.8s,**人为消除了 KVC 的设计前提条件**。
|
||||
|
||||
### 7.4 严重的实验有效性威胁
|
||||
|
||||
所有 v3-v6 数据基于 time-scale=10。这意味着前面所有"KVC 在 SWE 上输给 baseline"的结论都带着这个失真。**真实部署里 inter-turn gap 是 2.5s 的话,KVC 可能根本不会撞到当前看到的容量瓶颈**——D 有时间在 turn 之间释放/重排。
|
||||
|
||||
**应该单独跑一组 time-scale=1 的 baseline 对比**,才能判断 KVC 输给 DP 是因为机制本身不行,还是因为 benchmark 把它推到了不该工作的区间。这是这个项目目前**最重要但还没做**的验证。
|
||||
|
||||
---
|
||||
|
||||
## 8. 应用层抽象不需要在引擎层引入(撤回)
|
||||
|
||||
之前草稿里提过"框架不支持 speculative 多分支、嵌套 sub-agent、tool call 中断"——这是过度抽象。**应用层模式都可以由 timestamp + 独立 session_id 隐式表达**:
|
||||
|
||||
| 应用层模式 | 表现在 trace 里 | 推理引擎需要做什么 |
|
||||
|---|---|---|
|
||||
| Tool call 异步返回 | turn N 与 N+1 之间 timestamp gap 很大 | 啥都不用,按时间发请求即可 |
|
||||
| 嵌套 sub-agent | 父 session timestamp 突然停顿;sub-agent 是独立 session_id | 把它们当成两个独立 session 即可(KV 也无需共享) |
|
||||
| Speculative N 分支 | N 个独立 session_id 同时发 | 用 radix prefix cache 自然命中前缀;不需要任何额外抽象 |
|
||||
|
||||
**这条不构成结构性缺陷。** 已从结论中移除。
|
||||
|
||||
---
|
||||
|
||||
## 9. 行动项(按 ROI 排序)
|
||||
|
||||
### 优先级 P0(修了显著改善饿死/不公平)
|
||||
|
||||
1. **[§1] KvAwarePolicy 加 capacity-aware penalty + 允许 session 跨 D 迁移** — 工程量中、收益最大
|
||||
2. **[§2] D 端引入分层 eviction(冷 session、hot retract)** — 工程量中、收益大
|
||||
3. **[§7] 跑一组 time-scale=1 baseline** — 工程量小(仅配置),但**不做这条所有结论都不可信**
|
||||
|
||||
### 优先级 P1(修了把工程稳定性补齐)
|
||||
|
||||
4. **[§3] D→Replay backpressure 通道**(admission 响应加 pause hint) — 工程量小
|
||||
5. **[§4] 拆 admission 为 probe + commit_evict** — 工程量中
|
||||
6. **[§6] 修 `_estimate_session_resident_tokens` 用增量** — 工程量小
|
||||
|
||||
### 优先级 P2(等 P0 数据后再决定)
|
||||
|
||||
7. **[§5] P-side 选 P 时考虑 D 健康** — 工程量中
|
||||
|
||||
---
|
||||
|
||||
## 10. 局限与未验证假设
|
||||
|
||||
1. **N=1**:所有数据来自单次 run(v6 P0 已证 EXP2 errors 在 9-912 间漂移,single-run variance 巨大)。本文所有数字都应理解为"代表性观察"而非"统计显著结论"。
|
||||
2. **time-scale=10 失真**(§7):所有"KVC 输给 DP"的程度可能是被 benchmark 放大的。这是最大的不确定性。
|
||||
3. **8DP 对比的硬件优势**:DP 是 8 个 worker 全部跑 prefill+decode;KVC 是 2P+6D,只有 6 个能解码。理论上 8 worker 对 6 worker 自带 1.33× 解码并发优势。本文未折算这部分——但 8DP 优势远大于 1.33×(latency mean 145% 优势),所以核心结论(KVC 在该 workload 下系统性输)不受此影响。
|
||||
4. **mooncake TCP loopback**:所有 transfer 错误是单机 TCP 模拟下的产物。生产环境 RDMA 下错误率分布可能完全不同。
|
||||
5. **KvAwarePolicy 的 stale `decode_resident_blocks`**(§1.2 末尾)现象有数据观察支撑(运行中期 overlap 失去判别力),但**没有系统性测过"清掉 stale 状态会怎样"**。
|
||||
6. **P-side 错误集中在 prefill-0**(§5.1)的因果链是推测——可能也是"prefill-0 早启动 + race"的偶然结果。N>1 数据未验证。
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:数据产物索引
|
||||
|
||||
```
|
||||
outputs/qwen3-30b-tp1-v5-optD-baseline-rerun/
|
||||
├── exp2_2p6d_run1_metrics.jsonl ← 本文主数据源
|
||||
├── exp2_2p6d_run1_summary.json
|
||||
├── exp2_2p6d_run2_* (errors=912, single-run variance 证据)
|
||||
├── exp2_2p6d_run3_* (errors=396)
|
||||
└── kvcache-centric-*-20260429T142429Z/logs/
|
||||
├── decode-{0..5}.log ← §2.1 LRU vs error 计数
|
||||
└── prefill-{0,1}.log ← §5.1 P 错误分布
|
||||
|
||||
outputs/qwen3-30b-tp1-exps/
|
||||
├── exp1_8way_dp_cache_aware_summary.json ← 对照 baseline
|
||||
└── RESULTS_SUMMARY.md
|
||||
```
|
||||
|
||||
## 附录 B:相关文档
|
||||
|
||||
- `docs/PROJECT_OVERVIEW.md` — 项目目标与已实现功能
|
||||
- `docs/KVC_DEBUG_JOURNEY_V1_TO_V5.md` — v1→v5 版本演进
|
||||
- `docs/V5_PROFILE_INVESTIGATION_ZH.md` — v5+profile 调查(已 critic 修订)
|
||||
- `docs/SWEBENCH_EXPERIMENT_RESULTS.md` — Qwen3.5-35B-A3B SWE 实验
|
||||
263
docs/archive/KVCACHE_CENTRIC_PROGRESS_ZH.md
Normal file
263
docs/archive/KVCACHE_CENTRIC_PROGRESS_ZH.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# 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。
|
||||
367
docs/archive/KVC_DEBUG_JOURNEY_V1_TO_V5.md
Normal file
367
docs/archive/KVC_DEBUG_JOURNEY_V1_TO_V5.md
Normal file
@@ -0,0 +1,367 @@
|
||||
# KVC 实验踩坑记录与代码 Bug 分析(v1 → v5)
|
||||
|
||||
记录从 v1 到 v5 KVC 实验的踩坑过程、错误诊断、以及最终定位的代码 bug。
|
||||
模型: Qwen3-30B-A3B (TP1),硬件: 单节点 8×H100 80GB。
|
||||
Trace: `qwen35-swebench-50sess.jsonl`(4449 请求,52 sessions)。
|
||||
|
||||
## TL;DR
|
||||
|
||||
| 版本 | 关键变化 | 截断率 | direct-to-D 占比 | P50 | 主要瓶颈 |
|
||||
|------|----------|:---:|:---:|:---:|----------|
|
||||
| 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 | 1.0% | 54-58% | 1.08 / 0.84s | session-cap fb 仍 35%、9-10% mooncake errors |
|
||||
| v5 | Option D:worker-mode 驱动 seed/reseed | 0.9% | 41-45% | 1.59 / 1.31s | D KV pool 真容量不足 → fallback 反而 ↑ 至 46-51% |
|
||||
|
||||
`*` v2 的 P50 是假数字——超过半数请求只生成 1 个 token 就被 abort。
|
||||
|
||||
## v2 踩坑:Default policy 与 KVC 机制根本不兼容
|
||||
|
||||
### 表象
|
||||
|
||||
`scripts/sweep_tp1_v2_fixed.sh` 跑出来:
|
||||
- Exp1(8-way DP,baseline):4449/4449 成功,P50=0.65s,error=0
|
||||
- Exp2(1P7D KVC):**2524 truncated (56.8%)**,18 errors,P50=0.08s* (假)
|
||||
- Exp3(2P6D KVC):**2733 truncated (61.4%)**,17 errors,P50=0.08s* (假)
|
||||
|
||||
每个截断请求 `actual_output_tokens=1`,`finish_reason="abort: session id X does not exist"`。
|
||||
|
||||
### 错误的早期诊断
|
||||
|
||||
之前 `RESULTS_SUMMARY.md` 把锅扣在 SGLang 的 `--disaggregation-decode-allow-local-prefill` flag 上,认为是 D worker 在有 `bootstrap_room` 时仍然做了 local prefill。这个诊断**完全错误**——查 `scheduler.py:1975-1980` 的 `_should_allow_local_prefill_on_decode`:
|
||||
|
||||
```python
|
||||
def _should_allow_local_prefill_on_decode(self, req: Req) -> bool:
|
||||
return (
|
||||
self.disaggregation_mode == DisaggregationMode.DECODE
|
||||
and self.server_args.disaggregation_decode_allow_local_prefill
|
||||
and req.bootstrap_room is None # ← 有 bootstrap_room 不会走 local prefill
|
||||
)
|
||||
```
|
||||
|
||||
KVC reseed 路径的请求都带 `bootstrap_room`,根本不会触发 local prefill。
|
||||
|
||||
### 实际根因:Replay 与 PD Router 的 round-robin 错位
|
||||
|
||||
实验脚本里 KVC 用 `--policy default`,而 baseline 用 `--policy kv-aware`。
|
||||
看 `benchmark.py:287-300` 这两者的差别巨大:
|
||||
|
||||
```python
|
||||
def _decode_policy_for(policy_name: str) -> str:
|
||||
if policy_name == "sticky": return "manual"
|
||||
if policy_name == "kv-aware": return "consistent_hashing"
|
||||
return "round_robin" # default
|
||||
|
||||
def _header_mode_for(policy_name: str) -> str:
|
||||
if policy_name == "sticky": return "routing-key"
|
||||
if policy_name == "kv-aware": return "target-worker"
|
||||
return "none" # default
|
||||
```
|
||||
|
||||
`default` policy + KVC 机制下:
|
||||
1. Replay policy(`policies.py:DefaultPolicy`)round-robin 选一个 D,比如 D-3
|
||||
2. Replay 在 D-3 上 `open_session(session_id=X)`(`replay.py:1722-1731`)
|
||||
3. Replay 通过 PD Router 发请求(带 `session_params`),但 `header_mode=none`,**不发任何 routing header**
|
||||
4. PD Router (`pd_router.py:_select_decode_index`) 看到 `decode_policy=round_robin`,用**自己独立的计数器**round-robin,发到了 D-5
|
||||
5. D-5 的 scheduler 看到 `session_params` 里有 session_id,但自己的 `session_controller` 里没这个 session(session 在 D-3 上)→ abort with `"Invalid request: session id X does not exist"` (`scheduler.py:1824-1836`)
|
||||
|
||||
两个独立的 round-robin 计数器只要一次错位(任何并发或 direct-to-D 绕过 router 的请求都会引起)就永远对不上。
|
||||
|
||||
### 为什么 turn 0 不出问题?
|
||||
|
||||
Turn 0 走 `_invoke_plain_router`(`replay.py:1894`),不带 `session_params`,作为普通 PD disagg 请求处理,发到任何 D 都行。Turn 1+ 才开始走带 session_params 的 KVC 路径,撞上路由错位。
|
||||
|
||||
### 数据特征验证(per-session pattern)
|
||||
|
||||
```
|
||||
session 11360 (58 turns): pattern = .TTTTT.TTTTTTT.TTTTTT... ← turn 0 OK,1+ 全 T
|
||||
session 18720 (87 turns): pattern = .TTTTTTTTTTTTTTTTTT...
|
||||
```
|
||||
|
||||
每个 D worker 收到了全部 52 个 session 的请求(理想情况下应该是 ~7-8 个/D,因为 round-robin 把 session 完全打散)。
|
||||
|
||||
### 修复
|
||||
|
||||
唯一正确的修复是把 KVC 的 policy 从 `default` 改成 `kv-aware`:
|
||||
|
||||
```diff
|
||||
- --policy default
|
||||
+ --policy kv-aware
|
||||
```
|
||||
|
||||
`KvAwarePolicy` (`policies.py:146-187`) 做两件事:
|
||||
1. 用 `_overlap_blocks` + `sticky_bonus` 给每个 D 打分,session 自然粘在同一个 D(**session 亲和性**)
|
||||
2. `header_mode=target-worker`,发 `x-smg-target-worker` header
|
||||
3. PD Router 用 `consistent_hashing` 模式,看到 header 就直接用,不再 round-robin
|
||||
|
||||
## v3 改 kv-aware policy 后:路由对了,但新瓶颈出现
|
||||
|
||||
`scripts/sweep_tp1_v3_kvaware.sh` 把所有 KVC 实验改成 `--policy kv-aware`,结果:
|
||||
|
||||
| 指标 | v2 1P7D (default) | **v3 1P7D (kv-aware)** | v3 2P6D | 8-way DP baseline |
|
||||
|------|:---:|:---:|:---:|:---:|
|
||||
| 截断 | 56.8% | **0.9%** | 0.9% | 1.5% |
|
||||
| Errors | 18 | 363 (8.2%) | 9 | 0 |
|
||||
| Mean | 4.74s | 4.88s | 3.58s | 1.43s |
|
||||
| P50 | 0.08s* (假) | 1.75s | 1.52s | 0.65s |
|
||||
| P90 | 12.14s | 12.67s | 9.23s | 3.61s |
|
||||
| TTFT P50 | - | 0.36s | 0.33s | 0.09s |
|
||||
|
||||
✅ **截断从 56.8% 降到 0.9%,路由问题彻底解决**。
|
||||
❌ 但 P50 仍然是 baseline 的 2-3 倍。
|
||||
|
||||
### Direct-to-D 路径表现优秀(KVC 该有的样子)
|
||||
|
||||
按 execution_mode 拆开看:
|
||||
|
||||
| 路径 | Exp1 1P7D 占比 | Exp1 1P7D P50 | Exp1 1P7D TTFT P50 |
|
||||
|------|:---:|:---:|:---:|
|
||||
| `kvcache-direct-to-d-session` ✨ | 42.0% | **0.495s** | **0.043s** |
|
||||
| `pd-router-fallback-large-append-session-cap` 🔥 | **52.6%** | 5.6s | 3.7s |
|
||||
|
||||
Direct-to-D 路径下:
|
||||
- P50 = 0.495s(**比 baseline 0.65s 快 25%**)
|
||||
- TTFT P50 = 0.043s(**比 baseline 0.093s 快 2 倍**)
|
||||
- KV transfer = 0(无 P 介入,纯 D 上 append-prefill)
|
||||
|
||||
这才是 KVC 真正的价值。但只有 30-42% 请求走到这条路。
|
||||
|
||||
### 新瓶颈:session-cap fallback 占了 52-65%
|
||||
|
||||
`pd-router-fallback-large-append-session-cap` 占 1P7D 的 52.6%、2P6D 的 65.4%。这条路径意味着 router 想开新 session 在 D 上,但 admission 拒绝了("d-session-cap"),只好回退到 plain router(P 全量 prefill + 传给 D,无 session 复用)。
|
||||
|
||||
### Bimodal session 分布(starvation)
|
||||
|
||||
| Session | Total turns | Direct-to-D | Session-cap fallback |
|
||||
|---------|:---:|:---:|:---:|
|
||||
| 22080 | 129 | **98%** | 0% |
|
||||
| 3840 | 118 | **97%** | 0% |
|
||||
| 70560 | 150 | **0%** | **99%** |
|
||||
| 39360 | 148 | **0%** | **99%** |
|
||||
| 61600 | 117 | **0%** | **99%** |
|
||||
|
||||
要么完全幸运,要么完全饿死——典型的双峰分布。
|
||||
|
||||
### 根因:硬编码 cap=4
|
||||
|
||||
看 `replay.py:_decode_session_soft_cap` 原始代码:
|
||||
|
||||
```python
|
||||
def _decode_session_soft_cap(...) -> int:
|
||||
target_tokens = max(1, _estimate_session_resident_tokens(request))
|
||||
usable_capacity_tokens = _usable_capacity_tokens(residency, server_url)
|
||||
...
|
||||
if usable_capacity_tokens <= 0:
|
||||
return 4
|
||||
return max(1, min(4, usable_capacity_tokens // target_tokens))
|
||||
# ^^^ 硬编码上限 4
|
||||
```
|
||||
|
||||
7 个 D × 每个 D 最多 4 个 session = **28 个 session slot 总容量**。Trace 有 52 个 session → 24 个 session 永远抢不到 slot。
|
||||
|
||||
启动期 race condition 决定了哪些 session 是"幸运儿"——前 28 个挤进来的 session 的所有后续 turn 都走 direct-to-D(快);剩下 24 个 session 永远走 session-cap fallback(慢)。
|
||||
|
||||
## v4 改进:把硬 cap 从 4 提到 16
|
||||
|
||||
`replay.py:_decode_session_soft_cap` 一行修改:
|
||||
|
||||
```diff
|
||||
- if usable_capacity_tokens <= 0:
|
||||
- return 4
|
||||
- return max(1, min(4, usable_capacity_tokens // target_tokens))
|
||||
+ if usable_capacity_tokens <= 0:
|
||||
+ return 16
|
||||
+ return max(1, min(16, usable_capacity_tokens // target_tokens))
|
||||
```
|
||||
|
||||
7 D × 16 = 112 个 slot,远超 52 个 session 需求。
|
||||
|
||||
### v4 实际结果(vs v3 1P7D / 2P6D)
|
||||
|
||||
| 指标 | v3 1P7D | **v4 1P7D** | v3 2P6D | **v4 2P6D** | baseline 8DP |
|
||||
|------|:---:|:---:|:---:|:---:|:---:|
|
||||
| Errors | 363 (8%) | 435 (10%) | 9 (0%) | **403 (9%)** | 0 |
|
||||
| 截断 | 42 | 43 | 42 | 36 | 68 |
|
||||
| **direct-to-D** | 38.6% | **54.3%** | 30.5% | **58.0%** ⭐ | - |
|
||||
| **session-cap fallback** | 48.3% | 37.4% | 65.4% | **34.7%** | - |
|
||||
| Session reused | 1716 | 2180 | 1358 | **2348** | - |
|
||||
| KV transfer blocks | 62K | 53K | 79K | **51K** | - |
|
||||
| Mean | 4.88s | 4.21s | 3.58s | **2.51s** | 1.43s |
|
||||
| **P50** | 1.75s | 1.08s | 1.52s | **0.84s** | **0.65s** |
|
||||
| P90 | 12.67s | 13.38s | 9.23s | **6.51s** | 3.61s |
|
||||
| P99 | 28.72s | 24.45s | 18.70s | 18.34s | 8.38s |
|
||||
| **TTFT P50** | 0.36s | 0.056s | 0.33s | **0.051s** ⭐ | 0.094s |
|
||||
| TTFT P90 | 10.97s | 11.90s | 6.95s | **2.64s** | 0.26s |
|
||||
|
||||
✓ direct-to-D 占比从 v3 的 30-38% 涨到 v4 的 54-58%
|
||||
✓ session 复用 +27% (1P7D) / +73% (2P6D)
|
||||
✓ KV transfer 量 -15% (1P7D) / -36% (2P6D)
|
||||
✓ TTFT P50 反超 baseline 46%(0.051s vs 0.094s)
|
||||
|
||||
### Direct-to-D 路径全面碾压 baseline(KVC 真实价值)
|
||||
|
||||
| Config | n | Lat P50 | Lat P90 | TTFT P50 | TTFT P90 |
|
||||
|--------|:---:|:---:|:---:|:---:|:---:|
|
||||
| baseline 8DP | 4381 | 0.66s | 3.65s | 0.094s | 0.256s |
|
||||
| v4 1P7D direct-to-D | 2179 | 0.495s | 3.03s | 0.044s | 0.055s |
|
||||
| **v4 2P6D direct-to-D** | **2348** | **0.499s** | **2.86s** | **0.043s** | **0.054s** |
|
||||
|
||||
direct-to-D 子集相对 baseline:
|
||||
- P50 快 24-30%
|
||||
- P90 快 16-22%
|
||||
- TTFT P50 快 54%
|
||||
- TTFT P90 快 79%
|
||||
|
||||
### 整体性能(去掉 errors 和 truncated)vs baseline
|
||||
|
||||
| Config | clean | Mean | P50 | P90 | P99 |
|
||||
|--------|:---:|:---:|:---:|:---:|:---:|
|
||||
| baseline 8DP | 4381 | 1.45s | 0.66s | 3.65s | 8.38s |
|
||||
| v4 2P6D | 4010 | 2.53s | 0.85s | 6.55s | 18.33s |
|
||||
|
||||
vs baseline:P50 慢 28%、P90 慢 80%、P99 慢 119%。即使错误率为 0,整体仍输 baseline——根因是 35% 请求被推到 fallback 路径。
|
||||
|
||||
### 新瓶颈 1:35% 请求仍走 session-cap fallback
|
||||
|
||||
抬到 16 后真实瓶颈是 capacity-based 计算:`min(16, usable_capacity_tokens // target_tokens)`。
|
||||
- `target_tokens = input + output`,agentic 里常见 50-100K
|
||||
- D 的 KV pool ≈ 100-150K tokens(80GB H100, mem_fraction=0.835)
|
||||
- `usable / target` = 1-2,远没到 16 → 真实 cap 是 capacity 算出来的小数字
|
||||
|
||||
要解决必须改 capacity-based 估算逻辑(或上方案 D,让 D 自己决定)。
|
||||
|
||||
### 新瓶颈 2:9-10% errors(mooncake 传输超时)
|
||||
|
||||
P-side log 显示:
|
||||
|
||||
```
|
||||
KVTransferError: Failed to send kv chunk of <bootstrap_room> to 10.45.7.165:40319
|
||||
Sync batch data transfer timeout after 32722558107ns (32 秒超时)
|
||||
Decode instance could be dead, remote mooncake session ... is not alive
|
||||
```
|
||||
|
||||
特征:
|
||||
- 所有 errors 在 run 的 44.8% 之后出现(系统压力累积)
|
||||
- 98% errors 集中在 turn ≥ 31(大 input 的请求)
|
||||
- v3 cap=4 时 1P7D 已有 363 errors(仅 1 个 D 集中受冲击),v4 cap=16 把压力均匀分布但量级更大
|
||||
|
||||
是 mooncake TCP loopback 在并发上去后撞超时,**不是 SGLang 逻辑 bug**。修复方向:
|
||||
1. 加长 mooncake transfer timeout(现在 32s)
|
||||
2. 限制并发 inflight transfer 数量
|
||||
3. 改用 RDMA(loopback 是单机模拟,生产环境换真 RDMA)
|
||||
4. chunked KV transfer
|
||||
|
||||
## v5 落地方案 D:worker-mode 驱动 seed/reseed
|
||||
|
||||
`scripts/sweep_tp1_v5_optD.sh` 真正把方案 D 落到了代码里。改动核心:把 `--kvcache-admission-mode` 从 `local`(replay 估算) 改成 `worker`(D 决策),并扩展到 **direct_append + seed + reseed 全部路径**。
|
||||
|
||||
### 关键代码改动
|
||||
|
||||
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 是部署变更。
|
||||
|
||||
## 关键文件与代码位置索引
|
||||
|
||||
| 现象 | 代码位置 |
|
||||
|------|----------|
|
||||
| Replay policy round-robin | `policies.py:63-67` `RoutingState.next_decode_worker_id` |
|
||||
| KV-aware policy(session 亲和) | `policies.py:146-187` `KvAwarePolicy.select` |
|
||||
| PD router decode 选择 | `pd_router.py:51-74` `_select_decode_index` |
|
||||
| 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`(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 eviction(v5 引入) | `--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` |
|
||||
| Direct-to-D 流程 | `replay.py:2351-2398` `_invoke_decode_session_direct` |
|
||||
|
||||
## 经验教训
|
||||
|
||||
1. **policy 和 mechanism 是两个正交维度**——`--policy default` 不是"无脑默认值",它真的是 round-robin 无 session 亲和性。KVC 机制必须配 session 亲和的 policy。
|
||||
|
||||
2. **不要无脑相信前一个 agent 的 RESULTS_SUMMARY**——v2 的诊断("local prefill bug")和实际 finish_reason("session id does not exist")完全对不上。任何错误诊断必须用 finish_reason、execution_mode 这些原始字段交叉验证。
|
||||
|
||||
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 决策是否在说谎。
|
||||
34
docs/archive/README.md
Normal file
34
docs/archive/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# 归档文档说明
|
||||
|
||||
本目录保留项目历史阶段的过程文档。**新加入项目的 agent / 人员不需要阅读这些文档**,直接看 `docs/ONBOARDING_NEXT_AGENT_ZH.md` 即可。
|
||||
|
||||
保留它们的目的:
|
||||
1. 论文写作时追溯 v1-v5 调优演化过程
|
||||
2. 未来若回到 ts=10 高压区间或更大 trace 时,可参考当年的结构性问题诊断
|
||||
3. 满足学术可追溯性要求
|
||||
|
||||
## 每个文档的简要说明
|
||||
|
||||
| 文档 | 归档原因 | 何时回头看 |
|
||||
|---|---|---|
|
||||
| `AGENTIC_FIT_ANALYSIS_ZH.md` | ts=10 时代的 §1-§7 结构性问题分析;结论已被 ts=1 数据全面 supersede | 想知道当年 ts=10 下我们认为有什么结构性问题时 |
|
||||
| `STRUCTURAL_VALIDATION_REPORT_ZH.md` | 用 ts=10 数据对 AGENTIC_FIT_ANALYSIS 的 claim 做验证;同样被 ts=1 时代 supersede | 同上 |
|
||||
| `KVC_DEBUG_JOURNEY_V1_TO_V5.md` | v1-v5 5 个调优 sweep 的过程笔记;包含 errors 9→912 漂移、direct-to-D 占比变化等历史数据 | 写 paper 时要写 "as we explored configurations v1-v5..." 段落 |
|
||||
| `V5_PROFILE_INVESTIGATION_ZH.md` | 给 v5 加 1Hz polling instrumentation 的调查;让 errors 涨 46× 的现象记录 | 想理解 "admission RPC 干扰 scheduler 主循环" 这条 §5 残留风险时 |
|
||||
| `REFACTOR_PLAN_ZH.md` | v0 重构计划,**已被 `REFACTOR_PLAN_V1_ZH.md` supersede** | 不需要看;只有想看作者一开始的设想时翻一翻 |
|
||||
| `KVCACHE_CENTRIC_PROGRESS_ZH.md` | 项目最早期(2026-04-27)的进度记录;当时还没有完整的 sweep 数据 | 几乎不需要看;满足"项目起源记录"职能 |
|
||||
| `SWEBENCH_EXPERIMENT_PROGRESS.md` | SWE-Bench trace 早期实验进度记录 | 想知道当年的 trace 生成 / 采样配置时 |
|
||||
| `SWEBENCH_EXPERIMENT_RESULTS.md` | 同上,早期 result snapshot | 同上 |
|
||||
|
||||
## 当前活跃文档(在 `docs/` 顶层)
|
||||
|
||||
跳转去看:
|
||||
- `docs/ONBOARDING_NEXT_AGENT_ZH.md` — 新人上手手册
|
||||
- `docs/PROJECT_OVERVIEW.md` — 项目目标 + 术语
|
||||
- `docs/KVC_ROUTER_ALGORITHM.md` — 算法形式化
|
||||
- `docs/V2_DEEP_ANALYSIS_ZH.md` — v2 完整分析
|
||||
- `docs/V2_RESULTS_ZH.md` — v2 原始战报
|
||||
- `docs/REFACTOR_PLAN_V1_ZH.md` — ts=1 方向决策
|
||||
- `docs/MIGRATION_V1_FINDINGS_ZH.md` — v1 thrashing 诊断
|
||||
- `docs/RESEED_SLOW_PATH_AND_D_TO_P_GAP_ZH.md` — reseed 长尾 + D→P 缺口审计
|
||||
- `docs/TEAM_REPORT_AGENTIC_PD_HYBRID_ZH.md` — ts=10 时代的结构性问题清单(作为历史 baseline 仍在主目录)
|
||||
123
docs/archive/REFACTOR_PLAN_ZH.md
Normal file
123
docs/archive/REFACTOR_PLAN_ZH.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Refactor Plan v0:极简版
|
||||
|
||||
**日期**:2026-05-06
|
||||
**目标**:用最小改动 + 轻量实验,验证 `docs/AGENTIC_FIT_ANALYSIS_ZH.md` 提出的结构性缺陷是否真实存在、影响多大。
|
||||
**预算**:8h GPU 时间(约 4-6 次 ~30-60 min smoke run)。
|
||||
**KISS 边界**:不动 SGLang `scheduler.py` 主循环结构;不引入新 mooncake 协议;不实现 cross-D session migration;不做 admission probe/commit 拆分;不动 LRU eviction 策略。
|
||||
|
||||
## 计划结论(与用户已确认的)
|
||||
|
||||
回审 plan-v0 时发现两个原 Phase 1 改动**都不是 bug**:
|
||||
|
||||
- `_estimate_session_resident_tokens` 返回 full prompt 是设计如此——所有需要"增量"的 call site 都已经做 `target - current` 减法(`replay.py:1247-1254`、`:1393-1394`、`:1490-1491`)。
|
||||
- `decode_resident_blocks` 不缩减只是浪费几 MB 内存,**不影响 routing 决策**(SWE trace 的 hash_ids 是 session-unique,policy 仍能正确选 D)。
|
||||
|
||||
最终极简版只做一件代码改动(**加 backpressure**)+ 大量 instrumentation。
|
||||
|
||||
## 唯一代码改动:Backpressure 信号
|
||||
|
||||
### 改动点 1:SGLang `admit_direct_append` 响应增加两个字段
|
||||
|
||||
文件:`third_party/sglang/python/sglang/srt/managers/io_struct.py`、`scheduler.py`
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class DirectAppendAdmissionReqOutput:
|
||||
... # 已有字段保留
|
||||
recommended_pause_ms: int = 0 # 新增
|
||||
queue_depth: int = 0 # 新增
|
||||
```
|
||||
|
||||
`scheduler.py:admit_direct_append` 末尾计算 hint:
|
||||
|
||||
```python
|
||||
def _compute_backpressure_pause_hint(self) -> float:
|
||||
depth = len(self.disagg_decode_transfer_queue.queue)
|
||||
if depth < 8:
|
||||
return 0.0
|
||||
return min(2000.0, depth * 100.0) # 简单线性
|
||||
```
|
||||
|
||||
### 改动点 2:replay 端按 hint 退避
|
||||
|
||||
文件:`src/agentic_pd_hybrid/replay.py`
|
||||
|
||||
- `DecodeResidencyState` 新增 `pause_until_s: dict[str, float]`
|
||||
- `_query_decode_direct_admission` 解析响应里的 `recommended_pause_ms`,更新 `pause_until_s[server_url] = now + pause_ms / 1000`
|
||||
- 在调 `_invoke_router` / `_invoke_decode_session_direct` 前检查 `pause_until_s[decode_url]`,若 `now < pause_until` 则 sleep 到该时刻
|
||||
|
||||
### 改动点 3:新 CLI flag
|
||||
|
||||
`src/agentic_pd_hybrid/cli.py`、`benchmark.py`:
|
||||
|
||||
```
|
||||
--enable-backpressure # 默认 false,保留 baseline 行为
|
||||
```
|
||||
|
||||
### 改动点 4:观测日志
|
||||
|
||||
每个 run dir 新增三个 jsonl:
|
||||
|
||||
- `admission-events.jsonl`:每次 admission RPC(timestamp, session, D, can_admit, queue_depth, pause_ms, latency_s, available_tokens, evicted_session_count)
|
||||
- `backpressure-events.jsonl`:每次实际 sleep(timestamp, D, sleep_ms, queue_depth_at_signal)
|
||||
- `session-d-binding.jsonl`:每个 session 第一次 open 在某 D 时记录(timestamp, session, D, turn_id)
|
||||
|
||||
## 实验矩阵(8h 预算内)
|
||||
|
||||
按"先做 anchor,再做单变量对照"排序。每行右侧是预估机时。
|
||||
|
||||
| ID | 配置 | 目的 | 机时 |
|
||||
|---|---|---|---|
|
||||
| **E0 (existing)** | v5 baseline,time-scale=10,无 backpressure | Anchor,已存在 `outputs/qwen3-30b-tp1-v5-optD-baseline-rerun/run1` | 0 |
|
||||
| **E1** | v5 + backpressure ON,time-scale=10,全 trace | 验证 Claim §3(backpressure 是否能消除 KVTransferError 雪崩) | ~50 min |
|
||||
| **E2** | v5 baseline,time-scale=1,**短 trace**(前 12 sessions ≈ 1000 reqs) | 验证 Claim §7(time-scale=10 失真);不开 backpressure | ~60 min |
|
||||
| **E3** | 8DP CA,time-scale=1,同 E2 trace | E2 的对照——真实时序下 KVC 是否仍输 DP | ~60 min |
|
||||
| **E4** | v5 + backpressure,time-scale=1,同 E2 trace | backpressure 在真实时序下还有用吗? | ~60 min |
|
||||
| **E5**(备选) | v5 baseline,time-scale=10,**concurrency=4**,全 trace | 验证 Claim §1(高并发是不是必要条件) | ~50 min |
|
||||
|
||||
总:4-5 个 run,~3-5h。剩余预算给失败重跑/分析。
|
||||
|
||||
## 实验目标——回到 §1-§7 一一对照
|
||||
|
||||
| 文档 § | Claim | 由哪个 exp 证伪/支持 | 需要的指标 |
|
||||
|---|---|---|---|
|
||||
| §1 | Session 永久 pin + 容量盲选造成双峰 | 已有 E0 数据足够 | direct-to-D rate per session distribution |
|
||||
| §2 | LRU 跟不上压力 | 已有 E0 logs 足够 + E1 看 backpressure 之后 trim/error 比例变化 | trim 事件数 vs OOM 数 |
|
||||
| §3 | 没 backpressure 是雪崩源 | E0 vs E1 | KVTransferError 数、P99 latency |
|
||||
| §4 | admission RPC 干扰 scheduler | 不在本轮实验范围(需要 admission probe 拆分才能验,不做) | – |
|
||||
| §5 | P-side 不感知 D 健康 | 已有 E0 logs 足够(prefill-0 vs prefill-1 错误数) | per-P KVTransferError |
|
||||
| §6 | (已撤回) | – | – |
|
||||
| §7 | time-scale=10 失真 | E0 vs E2(同 KVC,不同 time-scale);E2 vs E3(同 time-scale,KVC vs DP) | latency 分布、direct-to-D rate |
|
||||
|
||||
## Final 实验报告交付
|
||||
|
||||
跑完后输出 `docs/STRUCTURAL_VALIDATION_REPORT_ZH.md`,按 §1-§7 每条给出:
|
||||
|
||||
- **Claim 字面**
|
||||
- **数据证据**(哪个 exp、哪个 metric)
|
||||
- **结论**:成立 / 部分成立 / 推翻
|
||||
- **影响量化**:数字差异
|
||||
- **不确定性**:N=1 风险、其他 confounder
|
||||
|
||||
## 不做的事(KISS 边界)
|
||||
|
||||
| 想做但不做 | 理由 |
|
||||
|---|---|
|
||||
| 跑 N=3 重复 | 8h 装不下;single-run 可看大方向 |
|
||||
| 全 sweep 参数 | 只调 time-scale 和 backpressure 一个 boolean |
|
||||
| 改 LRU eviction | 不在本轮范围 |
|
||||
| Cross-D migration | 不在本轮范围 |
|
||||
| Admission probe/commit 拆分 | 不在本轮范围 |
|
||||
| P-side D-health routing | 不在本轮范围 |
|
||||
| 修两个"非 bug"(estimate / aging) | 验证后非真实 bug |
|
||||
|
||||
## 预期失败路径
|
||||
|
||||
- **GPU 资源紧张**:smoke trace 进一步压缩(前 8 sessions / 600 reqs)
|
||||
- **time-scale=1 跑超 1.5h**:截断到 600s 内能完成的部分
|
||||
- **backpressure 配错**:先用 sleep_ms = depth * 100 简单线性;调不通就回滚到 0(无 backpressure)
|
||||
- **SGLang patch 编译错**:所有 patch 在 io_struct.py 和 scheduler.py 的少量行内,可单独 git restore
|
||||
|
||||
---
|
||||
|
||||
接下来:实现 → 跑 smoke → 写报告。
|
||||
304
docs/archive/STRUCTURAL_VALIDATION_REPORT_ZH.md
Normal file
304
docs/archive/STRUCTURAL_VALIDATION_REPORT_ZH.md
Normal file
@@ -0,0 +1,304 @@
|
||||
# 结构性缺陷验证报告
|
||||
|
||||
**日期**:2026-05-06
|
||||
**对照数据源**:
|
||||
- `outputs/qwen3-30b-tp1-v5-optD-baseline-rerun/`(v5 KVC kv-aware Option D,2P6D,**3 次同配置 rerun**)
|
||||
- `outputs/qwen3-30b-tp1-exps/exp1_8way_dp_cache_aware_summary.json`(同 trace 8DP CA)
|
||||
- `outputs/qwen3-30b-tp1-v5-optD-baseline-rerun/.../logs/decode-{0..5}.log`、`prefill-{0,1}.log`
|
||||
**模型**:Qwen3-30B-A3B(TP1),单机 8×H100 80GB,trace `qwen35-swebench-50sess.jsonl`(4449 reqs / 52 sessions)。
|
||||
**报告作用域**:验证 `docs/AGENTIC_FIT_ANALYSIS_ZH.md` §1-§7 提出的结构性 claim 是否真实存在;量化影响。
|
||||
|
||||
> ⚠️ **环境限制**:本轮缺 GPU 访问,未跑新 sweep。所有数据来自已存在的 v5 rerun + 8DP baseline。Backpressure 代码已实现但**未端到端验证**——下文标注为"预期收益(pending GPU smoke)"。
|
||||
|
||||
---
|
||||
|
||||
## 0. 实验有效性锚点:N=1 不可信
|
||||
|
||||
3 次 v5 baseline EXP2(**完全相同配置**)的 errors 漂移:
|
||||
|
||||
| Run | Errors | Lat P50 | Lat P90 | TTFT P50 |
|
||||
|---|---:|---:|---:|---:|
|
||||
| run1 | **372** | 1.11s | 8.65s | 0.147s |
|
||||
| run2 | **912** | 0.94s | 7.68s | 0.071s |
|
||||
| run3 | **396** | 1.22s | 8.43s | 0.183s |
|
||||
|
||||
errors 漂移 **2.5×**(372 → 912),P50 latency 漂移 **30%**。**任何 N=1 比较 < 30% 差异都不可信。** 后续所有"同 trace 不同配置 / 不同代码"的对比,都需要 N≥3 才有意义。
|
||||
|
||||
**对 KVC vs DP 的 headline 数据,3 次 KVC 的最佳值(P50=0.94s)仍然是 DP(P50=0.65s)的 1.45×**——8 way DP 的优势远超 single-run variance 范围,这一头条结论不受 variance 影响。
|
||||
|
||||
---
|
||||
|
||||
## §1. Session 永久 pin 到 D + 容量盲选 → 极端双峰 ✅ 完全成立
|
||||
|
||||
### Claim
|
||||
KvAwarePolicy 评分以 hash overlap 为主,没有 D 容量项。Session 第一次落到某 D 后被永久 pin。导致大 session 在已满 D 上反复 admission 拒绝,小 session 在原 D 上 100% 走 direct-to-D。
|
||||
|
||||
### 数据
|
||||
|
||||
**(a) Session 永久绑定,跨 3 次 rerun 一致**:
|
||||
|
||||
```
|
||||
run1: 52 sessions, avg distinct-D-per-session = 1.00
|
||||
run2: 52 sessions, avg distinct-D-per-session = 1.00
|
||||
run3: 52 sessions, avg distinct-D-per-session = 1.00
|
||||
```
|
||||
|
||||
每个 session 在整个运行中只访问 **1 个** D worker,3 次独立 run 完全一致。**不是巧合,是结构。**
|
||||
|
||||
**(b) Direct-to-D 命中率呈极端双峰**:
|
||||
|
||||
| Direct-to-D rate | run1 | run2 | run3 |
|
||||
|---|---:|---:|---:|
|
||||
| 0-20%(饿死) | 15 | 18 | 16 |
|
||||
| 20-40% | 7 | 6 | 7 |
|
||||
| 40-60% | 11 | 7 | 9 |
|
||||
| 60-80% | 5 | 6 | 4 |
|
||||
| 80-100%(顺利) | 14 | 15 | 16 |
|
||||
|
||||
中间态稀少,两端拥挤。
|
||||
|
||||
**(c) 跨 3 次 run 一致饿死的 session 与 session 大小强相关**:
|
||||
|
||||
```
|
||||
13 sessions starved (<20% direct-to-D) in ALL 3 runs.
|
||||
avg peak input of consistently-starved sessions: 62043 tokens
|
||||
avg peak input of consistently-lucky sessions: 31344 tokens
|
||||
ratio: 1.98× — starved sessions are exactly 2× larger.
|
||||
```
|
||||
|
||||
**13/52 = 25% 的 session 在 3 次独立 run 中都被饿死,且这些 session 的 peak input 恰好是顺利 session 的 2 倍。** 这排除了"运气"假说,证实是大 session 在容量过载 D 上结构性失败。
|
||||
|
||||
### 影响量化
|
||||
- 25% session 几乎每个 turn 都走 fallback 路径,相对 direct-to-D **TTFT 慢 100×、E2E 慢 6×**(数据点:fallback path mean lat ~3.5s vs direct ~0.5s)
|
||||
- 对应这些 session 的用户体验是"系统性糟糕",而不是"偶尔慢"
|
||||
- **SLO 视角下 P99 完全由这 13 个 session 拉高**
|
||||
|
||||
### 结论
|
||||
**完全成立**。修复方向(不在本轮):policy score 加 capacity penalty + 允许 session 跨 D 迁移,或 D 端引入 hot session retract。
|
||||
|
||||
---
|
||||
|
||||
## §2. D 端 LRU 只 evict idle session → 跟不上压力 ✅ 完全成立
|
||||
|
||||
### Claim
|
||||
`scheduler.py:2040` 的 `evict_idle_streaming_sessions_lru` 只能 evict "所有 req 都 finished + streaming 模式"的 session。高并发下 hot session 永远不 idle,LRU 找不到东西可踢。结果 D 顶到 100% 然后撞 mooncake transfer timeout。
|
||||
|
||||
### 数据(v5 baseline rerun run1)
|
||||
|
||||
| D worker | Trim 事件 | KVTransferError | 峰值 token_usage |
|
||||
|---|---:|---:|---:|
|
||||
| decode-0 | 9 | 0 | 0.99 |
|
||||
| decode-1 | 43 | 4 | 0.99 |
|
||||
| decode-2 | 16 | 153 | 0.97 |
|
||||
| decode-3 | 37 | 29 | 0.99 |
|
||||
| decode-4 | 28 | 90 | **1.00** |
|
||||
| decode-5 | 30 | 93 | **1.00** |
|
||||
|
||||
**6 个 D 全部峰值 ≥ 0.97**,其中 2 个直接顶到 1.00(KV 池完全耗尽)。**LRU 触发 9-43 次,远不及 transfer 错误的 90-153 次。**
|
||||
|
||||
decode-2 极端:trim 16 次 vs error 153 次 = LRU 比错误慢 **9.5×**。
|
||||
|
||||
### 影响量化
|
||||
- 单 run 累计 369 KVTransferError(总 6 个 D 之和)
|
||||
- 对应 ~8% 的请求失败率(v5 errors 9/372/912 三次平均 ~430/4449 = 9.7%)
|
||||
- **每次 mooncake timeout 是 32s**——对 P99 latency 直接贡献几十秒尾巴
|
||||
|
||||
### 结论
|
||||
**完全成立**。修复方向(不在本轮):分层 eviction——除 idle 外加冷 session retract、按访问频率/时序加权。Backpressure(本轮代码)只是把"D 满"的雪崩从"timeout 错误"转成"主动等待",**不是真正解决容量问题**。
|
||||
|
||||
---
|
||||
|
||||
## §3. 没有 D→Replay backpressure 通道 ✅ 成立(已实现修复)
|
||||
|
||||
### Claim
|
||||
D 端 transfer queue 堆 → 32s timeout → KVTransferError,没有"D 过载请慢点"信号反向到 replay;concurrency 一直 32 不降。
|
||||
|
||||
### 数据
|
||||
- §2 的 369 KVTransferError 全部为 32s mooncake timeout(日志中均为 `Failed to send kv chunk` 或 `Decode instance could be dead`)
|
||||
- 错误集中在运行后半段(按现有 `KVC_DEBUG_JOURNEY_V1_TO_V5.md` §v4:错误均在 run 的 44.8% 之后开始累积)
|
||||
- 表明:**前期 D 容量充裕时正常,达到容量上限后所有后续请求集中失败**——典型无 backpressure 系统行为
|
||||
|
||||
### 修复(本轮已实现,待 GPU smoke 验证)
|
||||
|
||||
代码改动:
|
||||
1. `third_party/sglang/python/sglang/srt/managers/io_struct.py`:`DirectAppendAdmissionReqOutput` 增加 `recommended_pause_ms` 字段
|
||||
2. `third_party/sglang/python/sglang/srt/managers/scheduler.py:admit_direct_append`:基于 `transfer_queue_depth`、`retracted_queue_depth`、`token_usage_after` 计算 hint
|
||||
```python
|
||||
def _compute_backpressure_pause_hint(...):
|
||||
if retracted_queue_depth > 0: return 1500
|
||||
if token_usage_after >= 0.90: return max(200, min(2000, overshoot * 5))
|
||||
if transfer_queue_depth >= 8: return min(2000, transfer_queue_depth * 100)
|
||||
return 0
|
||||
```
|
||||
3. `src/agentic_pd_hybrid/replay.py`:
|
||||
- `DecodeResidencyState.pause_until_s: dict[str, float]`
|
||||
- `_query_decode_direct_admission` 解析 hint 更新 `pause_until_s`
|
||||
- 新增 `_wait_for_decode_pause`,在 `_invoke_router` / `_invoke_session_direct` 入口检查
|
||||
4. CLI flag:`--enable-backpressure`、`--backpressure-max-pause-s 2.0`(默认关闭)
|
||||
5. 结构性日志:`structural/admission-events.jsonl`、`backpressure-events.jsonl`、`session-d-binding.jsonl`
|
||||
|
||||
### 预期收益(pending GPU smoke E2 vs E1)
|
||||
- KVTransferError 应从 ~370 / 4449 跌到 < 50 / 4449
|
||||
- P99 应改善(消除 32s timeout 尾巴)
|
||||
- 整体 latency mean 可能**略升**(被强制 pause),但 P99 应大幅降
|
||||
- backpressure-events.jsonl 应显示 D-4 / D-5 累积大量 pause 事件(与 §2 数据吻合)
|
||||
|
||||
### 结论
|
||||
**Claim 成立;修复已实现,待 smoke 验证**。注意:backpressure 是**降级**机制,不是性能优化——它把"硬错误"换成"主动等待",整体 throughput 不会因此提升。
|
||||
|
||||
---
|
||||
|
||||
## §4. Admission RPC 与 scheduler 主循环耦合 ⚠️ 间接证据,本轮未直接验证
|
||||
|
||||
### Claim
|
||||
`admit_direct_append` 进 scheduler 主循环遍历 session slot,admission RPC 频率 16+/s 时与 decode 抢调度。
|
||||
|
||||
### 现有间接证据
|
||||
- `docs/V5_PROFILE_INVESTIGATION_ZH.md`:仅加 1Hz `/server_info` polling 就让 EXP2 errors 从 9 涨到 415(46×);但 v6 P0 三次 baseline 不开 polling 同样得到 372/912/396——**polling 不是唯一原因,主循环负载本身就敏感**。
|
||||
|
||||
### 本轮未做
|
||||
- 没有"admission probe 拆 fast/slow"的对照实验。需要 SGLang 较深的改动(提供 lock-free snapshot),不在 KISS 边界。
|
||||
|
||||
### 结论
|
||||
**Claim 间接成立,本轮未直接验证**。Backpressure 实现里 admission RPC 的频率没有变(仍每个 turn 一次),只是结果会触发 sleep。如果这条 claim 成立,加 backpressure 后 admission RPC 数量大致不变但每次响应里的 `pause_ms` 会非零——**新增的 admission-events.jsonl 可在 GPU smoke 后用来直接验证此现象**。
|
||||
|
||||
---
|
||||
|
||||
## §5. P-side round-robin 不感知 D 健康 ✅ 成立
|
||||
|
||||
### Claim
|
||||
`pd_router.py:_select_decode_index` 是裸 round-robin。任一 P 撞到 hot D 时反复失败,另一 P 完全不受影响。
|
||||
|
||||
### 数据(v5 baseline rerun run1)
|
||||
|
||||
| Worker | KVTransferError | "Decode could be dead" |
|
||||
|---|---:|---:|
|
||||
| prefill-0 | **367** | 361 |
|
||||
| prefill-1 | **2** | 0 |
|
||||
|
||||
prefill-0 的请求量从 summary 看是 2225 vs prefill-1 的 2224——**请求量近乎对半,错误率差 180×**。
|
||||
|
||||
### 影响量化
|
||||
- 失败请求集中在 P-0 → 某个 hot D 的链路上(日志中反复出现 `to 10.45.80.47:XXXXX`)
|
||||
- 单 P 的"死亡链路"贡献了 **99%** 的全部 KVTransferError
|
||||
- 如果 P 选择能避开"正在和 hot D 死磕"的链路,**理论上可消除单 P 故障的雪崩效应**
|
||||
|
||||
### 备注
|
||||
- 此现象**未在 v6 P0 的 3 次 rerun 中横向验证**——只有 run1 的日志可读。需要在新 sweep 的 prefill-{0,1}.log 上重复确认,避免 N=1 嫌疑。
|
||||
|
||||
### 结论
|
||||
**单 run 数据成立,多 run 一致性未验证**。修复方向(不在本轮):router 选 P 时考虑 (P 当前 inflight transfer 数, 目标 D 健康度)。
|
||||
|
||||
---
|
||||
|
||||
## §6. (已撤回)Replay 端 session footprint 估算膨胀
|
||||
|
||||
写计划时仔细看代码后撤回——`_estimate_session_resident_tokens` 返回 full prompt,但所有需要"增量"的 call site (`replay.py:1247-1254`、`:1393-1394`、`:1490-1491`) 都已用 `target - current` 减法处理。**不是 bug**。
|
||||
|
||||
---
|
||||
|
||||
## §7. time-scale=10 把 inter-turn gap 压到 1/10 ✅ 完全成立
|
||||
|
||||
### 数据
|
||||
|
||||
```
|
||||
原始 trace inter-turn gap (n=4397):
|
||||
p10=1.6s p50=2.5s p90=7.8s p99=25.1s max=261s
|
||||
|
||||
time-scale=10 实际 replay gap:
|
||||
p10=0.16s p50=0.25s p90=0.78s p99=2.5s max=26s
|
||||
```
|
||||
|
||||
真实 agentic 用户/agent 在 turn 之间停 2-8 秒(思考、打字、tool call、agent reasoning)。time-scale=10 把这些窗口压到 0.16-0.78 秒——**人为消除了 D 的自然 idle 时间**,正好是 KVC 想利用的"session 短暂 idle 时 LRU 可以 evict、其他 session 可以 admit"机会。
|
||||
|
||||
### 测量学影响
|
||||
- 所有 v3-v6 数据基于 time-scale=10
|
||||
- 意味着所有"KVC 在 SWE 上输给 baseline"的结论**可能被 benchmark 放大了**
|
||||
- §1 的 25% session 永久饿死现象,在 time-scale=1 下可能因为 D 有更多 drain 时间而显著缓解
|
||||
|
||||
### 本轮未做
|
||||
- 没跑 time-scale=1 baseline。这是项目当前**最重要但缺失的验证**。
|
||||
- Smoke sweep 脚本(`scripts/sweep_backpressure_smoke.sh`)E3、E4 包含了 time-scale=1 的 KVC + DP 短 trace 对比,等 GPU 时跑。
|
||||
|
||||
### 结论
|
||||
**Claim 完全成立;time-scale=1 验证为 P0 待办**。
|
||||
|
||||
---
|
||||
|
||||
## 头条对比(同 trace、同硬件)
|
||||
|
||||
```
|
||||
8-way DP cache-aware (TP1):
|
||||
errors= 0 | latency mean=1.426s p50=0.654s p90=3.609s
|
||||
| TTFT mean=0.123s p50=0.093s p90=0.256s
|
||||
|
||||
KVC v5 2P6D (3 reruns, no polling):
|
||||
run1: errors=372 | mean=3.50s p50=1.11s p90=8.65s | TTFT mean=2.13s
|
||||
run2: errors=912 | mean=3.00s p50=0.94s p90=7.68s | TTFT mean=1.64s
|
||||
run3: errors=396 | mean=3.42s p50=1.22s p90=8.43s | TTFT mean=2.07s
|
||||
```
|
||||
|
||||
KVC 三次 run 全输 DP,且差距远超 single-run variance:
|
||||
- Latency mean:DP 优 **+110%**(KVC 平均 3.30s vs DP 1.43s)
|
||||
- Latency P50:DP 优 **+65%**(KVC 平均 1.09s vs DP 0.65s)
|
||||
- TTFT mean:DP 优 **+1500%**(KVC 平均 1.95s vs DP 0.12s——慢 17×!)
|
||||
- Errors:DP 0 vs KVC 平均 ~560
|
||||
|
||||
**这是这个项目当前最严肃的事实**——所有 KVC 复杂度回报为负。
|
||||
|
||||
---
|
||||
|
||||
## 综合结论
|
||||
|
||||
按"是否结构性 + 影响大小"的二维分类:
|
||||
|
||||
| Claim | 结构性 | 影响 | 本轮验证 | 修复(KISS 内) | 修复(KISS 外) |
|
||||
|---|---|---|---|---|---|
|
||||
| §1 Session pin + 容量盲选 | 强 | 大(25% session 饿死) | ✅ 3 run 一致 | ❌ | capacity-aware policy + 跨 D 迁移 |
|
||||
| §2 LRU 跟不上 | 强 | 大(每次 ~370 KVTransferError) | ✅ 6 D 数据 | ❌ | 分层 eviction、hot retract |
|
||||
| §3 无 backpressure | 强 | 中-大(消除 32s timeout 雪崩) | ⚠️ 已实现,待 smoke | ✅ **本轮交付** | – |
|
||||
| §4 admission RPC 干扰 | 弱-中 | 中 | ⚠️ 间接 | ❌ | probe / commit_evict 拆分 |
|
||||
| §5 P-side 不感知 D 健康 | 中 | 中(单 P 错误率差 180×) | ✅ N=1,需 N≥3 复核 | ❌ | router P 选择带 D 健康反馈 |
|
||||
| §6 estimate 膨胀 | – | – | ❌ 已撤回 | – | – |
|
||||
| §7 time-scale=10 失真 | 强(测量学) | 大(可能颠覆所有 KVC vs DP 结论) | ✅ 数据明确 | ✅ 改 flag | – |
|
||||
|
||||
### 最关键的两个 takeaway
|
||||
|
||||
1. **§7 time-scale=1 是当前项目所有结论的前置依赖**——必须先做。如果 time-scale=1 下 KVC 与 DP 接近,前面所有 v3-v6 的"KVC 输得彻底"诊断都需要重新解读。
|
||||
2. **§1 + §2 是双胞胎结构性问题**——session 被永久 pin 在某个 D + D 不能 evict 已满 = 大 session 永久卡死。任何不动 policy + 不动 LRU 的修复(包括本轮的 backpressure)只能让症状好看,不能消除根因。
|
||||
|
||||
---
|
||||
|
||||
## 本轮代码改动汇总(git diff 范围)
|
||||
|
||||
```
|
||||
src/agentic_pd_hybrid/replay.py # +结构性日志 + backpressure pause 检查 + admission 增强
|
||||
src/agentic_pd_hybrid/cli.py # +CLI flags
|
||||
src/agentic_pd_hybrid/benchmark.py # +CLI flags 透传
|
||||
third_party/sglang/python/sglang/srt/managers/io_struct.py
|
||||
third_party/sglang/python/sglang/srt/managers/scheduler.py
|
||||
# +recommended_pause_ms 字段 + hint 计算
|
||||
scripts/sweep_backpressure_smoke.sh # 4-run smoke sweep(待 GPU 跑)
|
||||
scripts/analysis/analyze_backpressure_smoke.py
|
||||
# 配套分析器
|
||||
docs/REFACTOR_PLAN_ZH.md # 计划文档
|
||||
docs/STRUCTURAL_VALIDATION_REPORT_ZH.md
|
||||
# 本报告
|
||||
```
|
||||
|
||||
代码默认行为**不变**(`enable_backpressure=False`)——所有现有脚本/配置无影响。
|
||||
|
||||
---
|
||||
|
||||
## 待 GPU 时执行
|
||||
|
||||
```bash
|
||||
bash scripts/sweep_backpressure_smoke.sh
|
||||
python3 scripts/analysis/analyze_backpressure_smoke.py outputs/sweep_backpressure_smoke
|
||||
```
|
||||
|
||||
预算:4 个 run × 30-60 min ≈ 3-4h GPU 时间。
|
||||
|
||||
按 §3 的预期:E2 (KVC + backpressure) 相对 E1 (KVC baseline) 应有 errors 降 70%+;P99 改善;TTFT P50 持平或略升。E3 (KVC + backpressure @ time-scale=1) vs E4 (DP @ time-scale=1) 是验证 §7 的关键对照。
|
||||
|
||||
如果 E2 vs E1 的 errors 没有显著下降,说明 backpressure hint 公式调得不对(`_compute_backpressure_pause_hint` 阈值可调),或 §3 实际不是雪崩主因(更可能是 §2 D-side LRU 才是)。
|
||||
95
docs/archive/SWEBENCH_EXPERIMENT_PROGRESS.md
Normal file
95
docs/archive/SWEBENCH_EXPERIMENT_PROGRESS.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# SWE-Bench PD Hybrid Experiment Progress
|
||||
|
||||
## 实验目标
|
||||
|
||||
在单节点 8xH100 上复现 agentic-pd-hybrid 三种 serving mechanism,对比 Qwen3.5-35B-A3B 在 SWE-Bench 500 instance agentic trajectory 上的性能。
|
||||
|
||||
## 硬件环境
|
||||
|
||||
- 8x H100 80GB (NVLink 互联, 2 NUMA nodes: GPU 0-3 / GPU 4-7)
|
||||
- 无 RDMA/IB 设备
|
||||
- Transfer backend: **mooncake TCP** (nixl UCX 因 pip 包缺少 CUDA 支持导致 segfault,已放弃)
|
||||
|
||||
## 实验矩阵
|
||||
|
||||
| 实验 | Mechanism | Workers | GPU 分配 | Router | Policy |
|
||||
|------|-----------|---------|----------|--------|--------|
|
||||
| A | pd-disaggregation | 1P + 1D (TP4 each) | P: 0-3, D: 4-7 | Yes | default |
|
||||
| B | pd-colo | 2 direct (TP4 each) | D0: 0-3, D1: 4-7 | No | default |
|
||||
| C | kvcache-centric | 1P + 1D (TP4 each) | P: 0-3, D: 4-7 | Yes | default |
|
||||
|
||||
## 测试负载
|
||||
|
||||
- 源数据: `simm-swe-bench/outputs/20260416-205833-hicache-qwen35-verified-0-500/audit.jsonl`
|
||||
- 39,417 lines (turns), 497 unique instances (sessions)
|
||||
- 每个 instance 8-150 turns (均值 79.3)
|
||||
- 转换为 agentic-pd-hybrid trace 格式: `outputs/qwen35-swebench-500.jsonl`
|
||||
|
||||
## 关键发现
|
||||
|
||||
### Transfer Backend 选择
|
||||
|
||||
- **nixl (UCX)**: pip 安装的 nixl_cu12 包自带的 UCX 库没有 CUDA 支持,导致 GPU memory registration 时 segfault。系统 UCX (/opt/hpcx/ucx) 有 CUDA 支持但因 RPATH 无法被 NIXL 使用。
|
||||
- **mooncake (TCP)**: 可用。需要两处修改:
|
||||
1. `third_party/sglang/.../mooncake_transfer_engine.py`: 从环境变量 `MOONCAKE_PROTOCOL` 读取协议,而非硬编码 `"rdma"`
|
||||
2. `src/agentic_pd_hybrid/stack.py`: 当 `transfer_backend == "mooncake"` 且非 `force_rdma` 时,自动设置 `MOONCAKE_PROTOCOL=tcp`
|
||||
|
||||
### 代码修改记录
|
||||
|
||||
1. **`third_party/sglang/python/sglang/srt/distributed/device_communicators/mooncake_transfer_engine.py`**
|
||||
- 将 `"rdma"` 硬编码改为 `os.environ.get("MOONCAKE_PROTOCOL", "rdma")`
|
||||
|
||||
2. **`src/agentic_pd_hybrid/stack.py`**
|
||||
- 在 `_build_process_env()` 中添加: mooncake 非 force_rdma 时默认设置 `MOONCAKE_PROTOCOL=tcp`
|
||||
|
||||
3. **`scripts/convert_audit_to_trace.py`** (新建)
|
||||
- 将 sibench audit.jsonl 转换为 agentic-pd-hybrid trace 格式
|
||||
|
||||
## 实验进度
|
||||
|
||||
- [x] Step 0: 环境准备 (uv sync, nixl/mooncake 安装)
|
||||
- [x] Step 1: Trace 格式转换 (39,417 lines 验证通过)
|
||||
- [x] Step 2: Smoke test (pd-disaggregation, mooncake TCP, 100 requests) — **通过**
|
||||
- 100/100 requests, 0 errors
|
||||
- Mean latency: 1.53s, P50: 0.77s, P90: 2.82s
|
||||
- TTFT: mean 0.49s, P50 0.29s; TPOT: mean 4.7ms
|
||||
- 91/100 cache hits
|
||||
- [x] Step 3a: 实验 A 全量尝试 (39K reqs, 497 sessions) — **中止**
|
||||
- Run dir: `outputs/swebench-exps/pd-disaggregation-default-20260426T171113Z` (无metrics,被kill)
|
||||
- 前 90% 完成 ~80min (~8-10 req/s), 但尾部 D 侧 KV cache 98% 饱和
|
||||
- 497 并发 session 争抢 D 侧 token 空间, mamba 80-93 sessions 无法 drain
|
||||
- **教训**: 1P+1D (TP4) 无法支撑 497 并发 session, 需减少 session 数量或降低 concurrency
|
||||
- [x] Step 3b: 实验 A — pd-disaggregation (52 sessions, 4449 reqs, concurrency=32) — **完成**
|
||||
- Run dir: `outputs/swebench-exps/pd-disaggregation-default-20260426T202540Z`
|
||||
- Trace: `outputs/qwen35-swebench-50sess.jsonl` (10% sample, 52 sessions)
|
||||
- **结果**: 4449/4449 成功, 0 errors
|
||||
- Latency: mean=1.66s, P50=0.97s, P90=3.64s, P99=7.68s
|
||||
- TTFT: mean=0.45s, P50=0.34s, P90=0.88s
|
||||
- TPOT: mean=5.2ms, P50=5.2ms
|
||||
- Cache hit: 4199/4449 (94.4%)
|
||||
- [x] Step 4: 实验 B — pd-colo — **失败: SGLang bug**
|
||||
- Run dir: `outputs/swebench-exps/pd-colo-default-20260426T210129Z`
|
||||
- **Bug**: `--disaggregation-mode null` (colocation) 下 Qwen3.5-35B-A3B 模型触发 token_to_kv_pool_allocator 内存泄漏
|
||||
- 错误: `ValueError: token_to_kv_pool_allocator memory leak detected!`
|
||||
- 两个 direct worker 在处理 ~5 个请求后均 crash (Scheduler exception)
|
||||
- **结论**: 当前 vendored SGLang v0.5.10 不支持 Qwen3.5-35B-A3B 的 colocation 模式
|
||||
- [x] Step 5: 实验 C — kvcache-centric — **完成 (高错误率)**
|
||||
- Run dir: `outputs/swebench-exps/kvcache-centric-default-worker-admission-20260426T210800Z`
|
||||
- 4390/4449 errors (98.7%) — admission control 过于保守
|
||||
- 59 成功请求: mean latency 1.24s (比 pd-disagg 快 25%), TTFT 0.18s (快 60%)
|
||||
- 详细分析见 `docs/SWEBENCH_EXPERIMENT_RESULTS.md`
|
||||
- [x] Step 6: 结果对比分析 — **完成**
|
||||
- 完整报告: `docs/SWEBENCH_EXPERIMENT_RESULTS.md`
|
||||
|
||||
## 启动脚本
|
||||
|
||||
- `scripts/run_exp_a_pd_disagg.sh` — 实验 A
|
||||
- `scripts/run_exp_b_pd_colo.sh` — 实验 B
|
||||
- `scripts/run_exp_c_kvcache_centric.sh` — 实验 C
|
||||
- `scripts/convert_audit_to_trace.py` — Trace 转换
|
||||
|
||||
## 已知风险
|
||||
|
||||
1. Qwen3.5-35B-A3B TP4 可用 mem ~12GB/GPU (after model + CUDA graph),长 session (150 turns) 可能 OOM
|
||||
2. mooncake TCP loopback 延迟远低于真实跨机,结果偏乐观
|
||||
3. 原始 trace 时间跨度 ~6000s,全量回放非常耗时
|
||||
121
docs/archive/SWEBENCH_EXPERIMENT_RESULTS.md
Normal file
121
docs/archive/SWEBENCH_EXPERIMENT_RESULTS.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# SWE-Bench PD Hybrid Experiment Results
|
||||
|
||||
## 实验配置
|
||||
|
||||
- **模型**: Qwen3.5-35B-A3B (MoE, 35B total / 3B active), TP4
|
||||
- **硬件**: 8x H100 80GB, NVLink, 单节点
|
||||
- **Transfer backend**: mooncake TCP (loopback)
|
||||
- **Trace**: 52 sessions, 4,449 requests (10% sample of SWE-Bench 500 instances)
|
||||
- **时间压缩**: time-scale=10, concurrency-limit=32
|
||||
|
||||
## 结果汇总
|
||||
|
||||
### Experiment A: pd-disaggregation (baseline)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Run dir | `pd-disaggregation-default-20260426T202540Z` |
|
||||
| Requests | 4,449 / 4,449 (100%) |
|
||||
| Errors | 0 |
|
||||
| **Mean Latency** | **1.662s** |
|
||||
| P50 Latency | 0.973s |
|
||||
| P90 Latency | 3.644s |
|
||||
| P99 Latency | 7.676s |
|
||||
| Mean TTFT | 0.445s |
|
||||
| P50 TTFT | 0.340s |
|
||||
| P90 TTFT | 0.880s |
|
||||
| Mean TPOT | 5.20ms |
|
||||
| Cache Hit Rate | 94.4% (4199/4449) |
|
||||
| Mean Cached Tokens | 27,794 |
|
||||
| KV Transfer Blocks | 105,235 |
|
||||
|
||||
### Experiment B: pd-colo (colocation) — FAILED
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Run dir | `pd-colo-default-20260426T210129Z` |
|
||||
| Status | **CRASHED** |
|
||||
| Error | `token_to_kv_pool_allocator memory leak detected!` |
|
||||
| Root Cause | SGLang v0.5.10 `--disaggregation-mode null` 与 Qwen3.5-35B-A3B (Mamba/GDN hybrid) 不兼容 |
|
||||
| Requests | ~10 / 4,449 (0.2%) |
|
||||
|
||||
**结论**: 当前 vendored SGLang 不支持此模型的 colocation 模式。需要修复 token_to_kv_pool_allocator 中 Mamba 模型的内存管理。
|
||||
|
||||
### Experiment C: kvcache-centric (session-aware PD)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Run dir | `kvcache-centric-default-worker-admission-20260426T210800Z` |
|
||||
| Requests | 4,449 total |
|
||||
| **Errors** | **4,390 (98.7%)** |
|
||||
| Successful | 59 (1.3%) |
|
||||
| Mean Latency (success) | 1.238s |
|
||||
| P50 Latency (success) | 0.484s |
|
||||
| P90 Latency (success) | 2.550s |
|
||||
| Mean TTFT (success) | 0.179s |
|
||||
| P50 TTFT (success) | 0.081s |
|
||||
| Mean TPOT (success) | 4.70ms |
|
||||
| Direct-to-D Sessions | 56 |
|
||||
| KV Transfer (actual) | 196 blocks (vs 105,235 planned) |
|
||||
|
||||
**Execution Mode 分布**:
|
||||
- `kvcache-centric` (failed): 4,390
|
||||
- `kvcache-direct-to-d-session` (success): 56
|
||||
- `pd-router-*` variants: 3
|
||||
|
||||
## 关键分析
|
||||
|
||||
### 1. pd-disaggregation (A) — 稳定可靠
|
||||
|
||||
- 100% 成功率,0 错误
|
||||
- Mean latency 1.66s 合理 (包含 P→D KV transfer 开销)
|
||||
- 94.4% cache hit 说明 prefix cache 在 P 侧工作良好
|
||||
- KV transfer 105K blocks = 主要开销来源
|
||||
- **适合生产使用**
|
||||
|
||||
### 2. pd-colo (B) — 不可用
|
||||
|
||||
- Qwen3.5-35B-A3B 的 Mamba/GDN hybrid 架构在 `disaggregation-mode null` 下触发内存泄漏
|
||||
- 这是 SGLang 的 bug,不是 agentic-pd-hybrid 的问题
|
||||
- **需要 SGLang 修复后重新测试**
|
||||
|
||||
### 3. kvcache-centric (C) — Admission 过于保守
|
||||
|
||||
- 98.7% 错误率说明 admission control 拒绝了几乎所有请求
|
||||
- `kvcache-seed-min-turn-id=2` 过滤了 turn 1 的 seed(正确行为)
|
||||
- 但绝大多数 turn 2+ 请求也走 `kvcache-centric` 模式后失败
|
||||
- 可能原因:
|
||||
- Worker admission 查询发现 D 侧没有对应 session 的 KV cache(因为 turn 1 没有 seed)
|
||||
- D 侧 transfer queue 积压导致 admission 拒绝
|
||||
- 成功的 56 个 `direct-to-d-session` 请求表现优异: TTFT 0.08s (P50), 比 pd-disagg 的 0.34s 快 4x
|
||||
- **需要调优 admission 参数,或使用 `kvcache-seed-min-turn-id=1` 允许 turn 1 seed**
|
||||
|
||||
### 4. kvcache-centric 成功请求 vs pd-disaggregation 对比
|
||||
|
||||
| Metric | pd-disagg (A) | kvcache-centric (C, success only) | Delta |
|
||||
|--------|:---:|:---:|:---:|
|
||||
| Mean Latency | 1.662s | 1.238s | **-25.5%** |
|
||||
| P50 Latency | 0.973s | 0.484s | **-50.3%** |
|
||||
| Mean TTFT | 0.445s | 0.179s | **-59.8%** |
|
||||
| P50 TTFT | 0.340s | 0.081s | **-76.2%** |
|
||||
| Mean TPOT | 5.20ms | 4.70ms | -9.6% |
|
||||
| Actual KV Transfer | 105,235 blk | 196 blk | **-99.8%** |
|
||||
|
||||
**当 kvcache-centric 成功时,性能提升显著:**
|
||||
- TTFT 降低 60-76% (D 侧直接 append,无需 P→D transfer)
|
||||
- 端到端 latency 降低 25-50%
|
||||
- KV transfer 减少 99.8%
|
||||
|
||||
## 后续建议
|
||||
|
||||
1. **修复 pd-colo**: 提交 SGLang issue 关于 Mamba/GDN 模型在 disaggregation-mode null 下的内存泄漏
|
||||
2. **调优 kvcache-centric admission**:
|
||||
- 尝试 `--kvcache-seed-min-turn-id 1` 允许 turn 1 seed
|
||||
- 放宽 `--kvcache-seed-max-decode-transfer-queue-reqs` 阈值
|
||||
- 使用 `--kvcache-admission-mode router` (shadow state, 不在 critical path)
|
||||
3. **增加 D 侧内存**: 调整 `--mem-fraction-static` 给 KV cache 更多空间
|
||||
4. **多 P/D 配置**: 测试 2P2D (TP2) 配置以增加并行度
|
||||
|
||||
## 实验日期
|
||||
|
||||
2026-04-27
|
||||
305
docs/archive/V5_PROFILE_INVESTIGATION_ZH.md
Normal file
305
docs/archive/V5_PROFILE_INVESTIGATION_ZH.md
Normal file
@@ -0,0 +1,305 @@
|
||||
# v5+Profile 调查报告(经 critic 审计修订版)
|
||||
|
||||
**日期**: 2026-04-29(原稿)/ 2026-04-29(经审计修订)
|
||||
**实验配置**: Qwen3-30B-A3B (TP1)、单机 8×H100 80GB、trace = qwen35-swebench-50sess.jsonl (4449 reqs / 52 sessions)、time-scale=10、concurrency=32
|
||||
**数据集**: `outputs/qwen3-30b-tp1-v5-optD-profile/`(EXP1 1P7D + EXP2 2P6D,均加入 1Hz `/server_info` 时序采样)
|
||||
**v5 baseline 对照**: `outputs/qwen3-30b-tp1-v5-optD/`(无 polling)
|
||||
**研究问题**: v5 (Option D) 把 errors 从 9-10% 降到 0.2%,但 session-cap fallback 反而升到 46-51%。fallback / errors 究竟来自哪里。
|
||||
|
||||
> **本稿是经过 hostile audit 后的修订版**。原稿包含若干结论性错误(尤其是对 `held_tokens` 语义的解读颠倒、对 admission race 的过度归因、对 polling 副作用的轻视)。审计意见保存在本会话记录中,关键纠错以 ⚠️ 标注。
|
||||
|
||||
---
|
||||
|
||||
## TL;DR(已修订)
|
||||
|
||||
1. **真实容量**: 每张 D 的 `token_to_kv_pool_allocator.size = 92086 tokens (~92K)`。⚠️ 单 turn 真实 footprint **不是 50-100K**;`cached_tokens` p50=18K、p90=48K、p99=67K。原稿过度夸张。
|
||||
2. **`other = capacity − held − available` 的解读已修订**: ⚠️ `held_tokens = sum(slot.kv_allocated_len − slot.cache_protected_len)`(代码:`session_aware_cache.py:278-282`),即"slot 拿到但**不在 radix tree 保护范围内**的部分"。所以 **`other` 的最大单一组成很可能是 radix-tree 保护的共享前缀缓存(prefix cache)** —— 这通常是想要的,**不是病态浪费**。原稿把 `other` 全归因为 running batch + 在途传输是错的。
|
||||
3. **`other` 的双峰分布属实**(p50 ≈ 0,p90 ≈ 80K),但单凭 `cap−held−avail` 无法判断这是 radix-cache 自然累积、还是 burst 工作内存。**P1 的细分 instrument 必须先做**。
|
||||
4. **errors 与 `other` 在时间上相关**属实,但**不能被解释为因果**。同一时段的多个变量(请求并发、in-flight transfer、可用空间)都在变化;无法仅凭时序对齐推断"`other` 吃掉了腾出来的空间"。
|
||||
5. **EXP2 2P6D errors 9 → 415**:⚠️ **polling 被升级为 leading hypothesis**,而非"无关"。证据:执行模式呈 ~1:1 替换(`session-cap-fb` −356 / `kvcache-centric` +406),且 `/server_info` 不是被动读 —— 它在 scheduler 主循环内遍历每个 session slot 计算 `is_idle`。需要 P0 三次 baseline 复跑去伪。
|
||||
6. **errors 集中在 18 个 session 上**(总共 52 个),每个 session 钉死在 1 个 D。per-D error rate 差异**无法解释为 D 的结构差别**,本质是 18 个"坏 session"如何被路由分配。
|
||||
7. **v5+profile 1P7D 的延迟优于 baseline** 完全在 single-run variance 范围内。N=1,**不能作为任何性能结论**。
|
||||
|
||||
---
|
||||
|
||||
## 1. 方法论
|
||||
|
||||
### 1.1 Instrument 改动
|
||||
- `src/agentic_pd_hybrid/replay.py` 加入 `_query_pool_snapshot` + `_poll_pool_timeseries`,后台 asyncio task 以 `--pool-poll-interval-s 1.0` 周期访问每个 P/D worker 的 `/server_info`。
|
||||
- 每 tick 写一行 jsonl 到 `<run_dir>/d-pool-timeseries.jsonl`,字段:`{worker_id, worker_role, session_count, resident_session_count, held_tokens, available_tokens, capacity_tokens, idle_evictable_*, sessions[], kvcache_mem_gb, last_gen_throughput, ...}`。
|
||||
- 分析脚本:`scripts/analysis/analyze_pool_timeseries.py`。
|
||||
|
||||
### 1.2 字段定义(已修订 ⚠️)
|
||||
`/server_info` → `internal_states[0].session_cache` 的来源是 `session_controller.py:get_streaming_session_cache_status` → `tree_cache`(`SessionAwareCache`)。
|
||||
|
||||
| 字段 | 真实含义 | 备注 |
|
||||
|---|---|---|
|
||||
| `held_tokens` | `sum_over_slots(ceil(kv_allocated_len, page_size) − cache_protected_len)` | **不是** "session 在 cache 中占用的全部";只统计**slot-private、未被 radix tree 保护**的部分 |
|
||||
| `cache_protected_len` | radix tree 保护的共享前缀部分 | 多个 session 共享时只计一次 |
|
||||
| `available_tokens` | `token_to_kv_pool_allocator.available_size()` | 全局 KV 池剩余空间 |
|
||||
| `capacity_tokens` | `allocator.size` | 单 D 的总 KV 容量 = 92086 |
|
||||
| `idle_evictable_tokens` | held 中可被 LRU 立即踢的部分(session 所有 req finished + streaming 模式) | |
|
||||
|
||||
因此:
|
||||
- **`other = capacity − held − available`** 包含但不限于:
|
||||
- **radix-tree 保护的共享前缀 token**(可能是大头) ⚠️ 原稿遗漏
|
||||
- 当前 running batch 占用的 KV slots
|
||||
- P→D 在途 transfer 的临时 buffer
|
||||
- mooncake 已注册但尚未提交到 tree_cache 的块
|
||||
- 内部碎片 / allocator 元数据
|
||||
|
||||
**含义**: 在补充 P1 instrument 之前,我们**无法分辨** `other` 中"radix-cache"(良性)和"burst 工作集 / fragmentation"(可能病态)的比例。
|
||||
|
||||
### 1.3 配置一致性与风险
|
||||
- v5+profile 与 v5 baseline 唯一差别:加了 `--pool-poll-interval-s 1.0`(其余 CLI 参数完全一致)。
|
||||
- **两次 run 时间间隔 ~21 小时**(2026-04-28 15:39/16:27 vs 2026-04-29 12:08/12:59)⚠️ 原稿误写 ~6h。同一台机,但 GPU 温度、PCIe、NUMA 分配未控制。
|
||||
- **N=1 比较没有统计意义**;任何延迟差异 < 30% 都属于 single-run variance 合理范围。
|
||||
|
||||
---
|
||||
|
||||
## 2. 整体性能对比
|
||||
|
||||
| 指标 | v5 1P7D | **v5+profile 1P7D** | v5 2P6D | **v5+profile 2P6D** |
|
||||
|---|---|---|---|---|
|
||||
| 总 requests | 4449 | 4449 | 4449 | 4449 |
|
||||
| **errors** | 9 (0.2%) | 6 (0.1%) | 9 (0.2%) | **415 (9.3%)** |
|
||||
| truncated | 42 | 43 | 42 | 42 |
|
||||
| direct-to-D | 44.7% | 54.9% | 41.3% | 41.1% |
|
||||
| session-cap fallback | 45.6% | 36.1% | 50.6% | 42.6% |
|
||||
| no-d-capacity | 1.2% | 0.7% | 0.8% | 0.6% |
|
||||
| pd-router-d-session-reseed | 4.8% | 4.3% | 3.4% | 2.9% |
|
||||
| pd-router-turn1-seed | 1.2% | 1.2% | 1.1% | 1.1% |
|
||||
| **kvcache-centric (failed mode)** | 0.2% (9) | 0.1% (6) | 0.2% (9) | **9.3% (415)** |
|
||||
| latency mean / p50 / p90 / p99 (s) | 5.18/1.59/14.7/26.1 | 4.21/1.18/11.3/28.8 | 3.49/1.31/9.1/24.9 | 3.23/1.11/8.4/20.3 |
|
||||
|
||||
⚠️ **不要从此表得出"v5+profile 改进了延迟"** —— N=1 single run,且 EXP2 引入了 415 个 errors 相当于换了一种回退策略,延迟均值的下降很可能只是**剔除了慢路径请求**的副作用。
|
||||
|
||||
### 2.1 EXP2+profile 415 errors 解构(已修订)
|
||||
|
||||
**Error type 分布**:
|
||||
| Error Type | 数量 |
|
||||
|---|---|
|
||||
| `RuntimeError: generate stream ended before producing any token` | 407 |
|
||||
| `ReadTimeout: ` | 8 |
|
||||
|
||||
⚠️ **关键约束**:
|
||||
- **414/415 个 error 的 `kv_transfer_blocks > 0`**(从 metrics jsonl 验证)。这些请求**已经过了 admission,P→D 传输已开始**,死于下游(server-side abort、流被关、生成阶段失败)。
|
||||
- **`session_reused=False` 占 415/415**(全部是 seed,无一是 direct append)。
|
||||
- **失败集中在 18 个 unique session**(top 5: 58080→decode-5 66 errs / 70560→decode-2 54 / 67200→decode-4 40 / 59200→decode-4 35 / 77280→decode-2 33),每个 session 钉死在一台 D。
|
||||
|
||||
**Per-D error rate(已修正百分比)**:
|
||||
| Decode Worker | Errors | Total Reqs | Error Rate |
|
||||
|---|---|---|---|
|
||||
| decode-0 | 56 | 758 | 7.4% |
|
||||
| decode-1 | 5 | 561 | 0.9% |
|
||||
| decode-2 | 141 | 858 | **16.4%** |
|
||||
| decode-3 | 0 | 838 | 0.0% |
|
||||
| decode-4 | 106 | 731 | 14.5% |
|
||||
| decode-5 | 107 | 703 | 15.2% |
|
||||
|
||||
⚠️ **不要解读为"decode-3 健康、decode-2 病态"**。每个 session 钉死在一台 D,18 个坏 session 是否落到某个 D 是路由分配的随机结果。**当前 N=1 数据无法分辨"D 结构差异"与"session 分配运气"**。
|
||||
|
||||
---
|
||||
|
||||
## 3. D KV pool 时序分解(EXP1 1P7D 关键结果)
|
||||
|
||||
每张 D capacity=92086 tokens,运行 ~2696 秒(去掉前 10% 暖机):
|
||||
|
||||
| Worker | mean_other | p50_other | p90_other | max_other | mean_held | mean_avail |
|
||||
|---|---:|---:|---:|---:|---:|---:|
|
||||
| decode-0 | 13599 | 63 | 77189 | 90959 | 47124 | 31363 |
|
||||
| decode-1 | 21242 | 0 | 76854 | 91074 | 37024 | 33820 |
|
||||
| decode-2 | 39333 | 46841 | 82782 | 91996 | 17381 | 35372 |
|
||||
| decode-3 | 30543 | 15864 | 81512 | 91511 | 9584 | 51959 |
|
||||
| decode-4 | 32659 | 32365 | 72995 | 92082 | 7643 | 51784 |
|
||||
| decode-5 | 31745 | 20366 | 86341 | 91211 | 11305 | 49036 |
|
||||
| decode-6 | 24602 | 701 | 82291 | 91000 | 20967 | 46517 |
|
||||
|
||||
**已修订观察(去掉了原稿的过度归因)**:
|
||||
- **`other` 是双峰**(p50 接近 0,p90 接近 80K,mean 在 14-39K)。这一形态属实。
|
||||
- **不同 D 的 mean_held / mean_other 差异巨大** —— 但⚠️ **不能直接归类为 "session-heavy" 或 "transfer-heavy"**,因为我们不知道 `other` 里 radix-cache vs 工作内存的比例。**P1 的拆分必做**。
|
||||
- 由于 `held` 不包含 radix-protected token,`mean_held` 低**不代表**该 D 上 sessions 占用少 —— 只代表它们的"slot 私有部分"少;共享前缀可能很大,完全藏在 `other` 里。
|
||||
|
||||
### 3.1 `other` 在某些时段持续高位(EXP1 decode-2 抽样)
|
||||
|
||||
| t (s) | held | avail | other | sess_count | last_gen_throughput |
|
||||
|---:|---:|---:|---:|---:|---:|
|
||||
| 3 | 0 | 92086 | 0 | 0/0 | (未抽) |
|
||||
| 273 | 65310 | 26776 | 0 | 1/1 | (未抽) |
|
||||
| 543 | 15296 | 76589 | 201 | 1/1 | (未抽) |
|
||||
| 812 | 0 | 92086 | 0 | 0/0 | (未抽) |
|
||||
| 1082 | 52507 | 39579 | 0 | 1/1 | (未抽) |
|
||||
| 1351 | 40985 | 30175 | 20926 | 2/2 | (未抽) |
|
||||
| **1622** | **0** | 17703 | **74383** | **0/0** | **未核** |
|
||||
| 1891 | 0 | 46376 | 45710 | 0/0 | (未抽) |
|
||||
| 2161 | 0 | 27667 | 64419 | 0/0 | (未抽) |
|
||||
| 2430 | 0 | 62224 | 29862 | 0/0 | (未抽) |
|
||||
|
||||
⚠️ **t=1622 之后(约 30+ tick)持续 held=0/sess=0/other≈45-74K** —— 这种持久状态**不是 burst 工作集的形态**(burst 应是亚秒级)。更可能的解释包括:
|
||||
- 一个 stuck request 的 KV 块未能正常释放
|
||||
- mooncake 注册但未 commit 的 transfer buffer 滞留
|
||||
- 某个 cleanup 路径未触发
|
||||
|
||||
**未在原稿中验证 `last_gen_throughput`**,该字段记录在 timeseries 但未对齐分析。**P1 时一并补**。
|
||||
|
||||
---
|
||||
|
||||
## 4. Errors 与 Saturation 时序相关性(EXP2 2P6D)
|
||||
|
||||
### 4.1 等数量 vs 等时间 decile(已修订 ⚠️)
|
||||
|
||||
原稿仅展示等时间分箱,有"第 10 decile 系统恢复"的视觉错觉。两种分箱并列:
|
||||
|
||||
| Decile | 等时间(reqs / errs / rate) | 等数量(reqs / errs / rate) |
|
||||
|:---:|:---:|:---:|
|
||||
| 1 | 567 / 0 / 0.0% | 444 / 0 / 0.0% |
|
||||
| 2 | 268 / 0 / 0.0% | 445 / 0 / 0.0% |
|
||||
| 3 | 517 / 0 / 0.0% | 445 / 0 / 0.0% |
|
||||
| 4 | 189 / 0 / 0.0% | 445 / 0 / 0.0% |
|
||||
| 5 | 662 / 3 / 0.5% | 445 / 3 / 0.7% |
|
||||
| 6 | 417 / 27 / 6.5% | 445 / 28 / 6.3% |
|
||||
| 7 | 486 / 39 / 8.0% | 445 / 42 / 9.4% |
|
||||
| 8 | 612 / 177 / 28.9% | 445 / 114 / 25.6% |
|
||||
| 9 | 486 / 128 / 26.3% | 445 / 119 / 26.7% |
|
||||
| **10** | **245 / 41 / 16.7%** | **445 / 109 / 24.5%** |
|
||||
|
||||
⚠️ **第 10 decile 不是"系统恢复"**。等数量分箱显示 24.5% 的 error rate,与 decile 8/9 持平。原稿"恢复"叙事是分母 245 vs 612 造成的视觉假象。
|
||||
|
||||
### 4.2 多重假设并列(已修订,不再独尊 admission race)
|
||||
|
||||
针对 EXP2 2P6D 415 errors 的可能机制(按当前数据强弱排序):
|
||||
|
||||
**H1: Polling 引发 scheduler 时序扰动(leading hypothesis ⚠️)**
|
||||
- 证据:执行模式 1:1 替换(session-cap-fb −356 / kvcache-centric +406)。
|
||||
- 证据:`/server_info` 进 scheduler 主循环遍历 session slot,1 Hz × 8 worker 不是 0 开销。
|
||||
- 证伪条件:**P0(三次 baseline EXP2 复跑)如果都得到 ~9 errors,本假设确认**。
|
||||
|
||||
**H2: v5 自身存在 admission/transfer race**
|
||||
- v5 baseline 也出 9 个 errors(均为 ReadTimeout),说明该 race 在 baseline 已存在,profile 是被放大了。
|
||||
- 证据弱化:原稿提的 "admission race"(admit_direct_append snapshot 过期)与数据冲突 —— **414/415 errors 的 `kv_transfer_blocks > 0`**,他们都过了 admission,死在下游。所以即便有 race,也不是发生在 admission 端,而是 P→D transfer 后 / 生成开始前。
|
||||
|
||||
**H3: 18 个特定 session 的工作负载结构性失败**
|
||||
- 18/52 session 集中失败,每个 session 都是高 turn_id (median=70)。
|
||||
- 这些 session 可能 input 特别长,或某种 trace 结构会触发某个特定路径。
|
||||
- 证伪条件:在 P0 三次 baseline 复跑后,看是否仍是同一组 18 个 session 失败。
|
||||
|
||||
**H4: 单次运行的 GPU/PCIe 状态扰动**
|
||||
- ~21 小时间隔,GPU 温度/clock 不同。
|
||||
- 证伪条件:P0 三次 baseline 都 ~9 errors → 排除单次扰动主导。
|
||||
|
||||
⚠️ **原稿独推 admission-race(H2)是错的**。当前数据无法决定 H1-H4 哪个是主因。
|
||||
|
||||
---
|
||||
|
||||
## 5. 1P7D vs 2P6D 全局对比
|
||||
|
||||
| Config | total decode ticks | other p50 | other p90 | other>30K freq | other>50K freq | other>70K freq | held>60K freq |
|
||||
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||
| 1P7D | 18865 | 663 | 79751 | 36.9% | 27.9% | 14.8% | 15.5% |
|
||||
| 2P6D | 14016 | 14459 | 77199 | 43.2% | 30.4% | 13.9% | 4.8% |
|
||||
|
||||
⚠️ **原稿"2P6D 的 p50_other 是 1P7D 的 22 倍 → 2P 推送压力更大"过度解读**。考虑分母效应:同一 trace 总工作量在 2P6D 由 6 张 D 分担 vs 1P7D 由 7 张 D 分担,**单 D 受到的压力本来就更大**,与 P 数无直接因果。这个数据只能说"2P6D 单 D 负担更高",**不能**得出"2P 在 transfer 上比 1P 更激进"。
|
||||
|
||||
---
|
||||
|
||||
## 6. 关键解读(已大幅修订)
|
||||
|
||||
### 6.1 v5 真实瓶颈尚不明确
|
||||
原稿声称"瓶颈是 D 的 KV pool 在压力期被 'other' 占据"。⚠️ **此结论已撤回**。给定 `held_tokens` 实际是 slot-private(non-tree)部分,`other` 的最大单一成分**很可能是正常的 radix-tree 共享前缀**。"被 running batch / 在途传输占据"是**未经验证的猜想**。需要 P1 的细分 instrument 才能给出真瓶颈。
|
||||
|
||||
### 6.2 LRU eviction 的行为暂无可靠解读
|
||||
原稿基于 mean_held 在压力期"暴跌"推断 LRU 在拼命踢。但 `held` 实际是 slot-private 部分,session 仍可能被 radix-tree 保留;`held` 减少不等于 session 被 evict,可能只是 `cache_protected_len` 比例变化。**P1 拆分前不下结论**。
|
||||
|
||||
### 6.3 v5+profile 1P7D "比 baseline 快"是单次巧合
|
||||
两次 run 间隔 ~21 小时(原稿误写 ~6h),GPU 温度/PCIe 状态未控制。**N=1**,任何性能差异 < 30% 都不可声称。
|
||||
|
||||
### 6.4 EXP2 2P6D 415 errors:polling 是 leading suspect(已升级)
|
||||
原稿把 polling 列为"次要可能"。⚠️ **现在升级为主嫌疑**:
|
||||
- 执行模式 1:1 替换(session-cap-fb −356 / kvcache-centric +406)说明 polling **改变了 admission 走哪条路**。
|
||||
- `/server_info` 不是只读旁路 —— 调度内部循环 + 遍历 session slots 计算 `is_idle`。
|
||||
- **必须做 P0 三次 baseline 复跑去伪**;在那之前不能动 v6。
|
||||
|
||||
### 6.5 "Other" 在 P 上 90% 不是 backup blocks
|
||||
`prefill-0` 的 SessionAwareCache **未启用**(replay 数据 `held=0`),P 的 "other" 等于"P 全部 KV 使用量"(radix cache + running batch + 备份)。⚠️ 当前数据**无法分辨** prefill-backup-policy 是不是真的释放了。需在 P 加单独的 `prefill_backup_tokens` 字段。
|
||||
|
||||
---
|
||||
|
||||
## 7. v6 行动项(已重排,以 P0 起步)
|
||||
|
||||
### **P0:验证 EXP2 errors=9 的可复现性**(最高优先级,先做)
|
||||
**操作**: 跑 3 次 v5 baseline EXP2(同 v5 配置,**不开 polling**),比较 error 分布。
|
||||
- 如果 3 次都得到 ~9 errors → polling 被坐实为 415 暴涨主因。**必须把 polling 改成更轻量的形式**(如降低频率、改成 streaming push、或用 sidecar metrics 而非 HTTP poll)再做后续。
|
||||
- 如果 3 次都得到 ~400 errors → polling 不是主因,415 是 v5 admission/transfer race + 单次 GPU 状态扰动的复合。
|
||||
- 如果 3 次结果分布很广(如 9 / 50 / 400) → run-to-run variance 才是主导,任何 single-run 比较失效。
|
||||
|
||||
**预期工程量**: 1 个新 sweep 脚本(只跑 EXP2,3 次)+ ~3 × 50 min = ~2.5h GPU 时间。
|
||||
**风险**: 0(纯重跑现有配置)。
|
||||
|
||||
### **P1:把 D 的 `other` 拆开打表**(P0 跑的同时并行做代码)
|
||||
**操作**: 改 SGLang `scheduler.py:get_streaming_session_cache_status` 与 `session_aware_cache.py`,在返回的 dict 里加:
|
||||
- `radix_protected_tokens` = `sum(slot.cache_protected_len for slot in slots)` ⚠️ 这是原稿盲区,critic 暴露的关键缺失字段
|
||||
- `running_batch_tokens` = `sum(req.fill_ids size for req in running_batch.reqs)`
|
||||
- `inflight_transfer_tokens` = `sum(req.size for req in disagg_decode_transfer_queue.queue)`
|
||||
- `prealloc_tokens` = `sum(req.size for req in disagg_decode_prealloc_queue.queue)`
|
||||
- `retracted_tokens` = `sum(req.size for req in disagg_decode_prealloc_queue.retracted_queue)`
|
||||
- `last_gen_throughput`(已有)更细 —— 加 `running_batch_size`(req 数)
|
||||
|
||||
**预期收益**: `other_unaccounted = capacity − held − available − radix_protected − running_batch − inflight − prealloc − retracted` 应该接近 0。剩余的就是真"病态"内存。
|
||||
**风险**: 低(纯只读 stat,不改 admission 逻辑)。
|
||||
**工程量**: ~80 行 SGLang patch + 同步 replay.py 的 `_query_pool_snapshot` + analyzer。
|
||||
|
||||
### **P2:如果 P0 暴露 polling 是主因,改 polling 实现**
|
||||
- 选项 A:把 `/server_info` 改成事件驱动 push(scheduler 在 step 末尾把 stats 写到环形缓冲区,polling 只读不进 scheduler 队列)
|
||||
- 选项 B:把 polling 频率从 1Hz 降到 5Hz/10s,在 P1 的拆分数据上验证够用
|
||||
- 选项 C:scheduler 端加锁分离,把 stats 读和 admission 决策的临界区拆开
|
||||
|
||||
### **P3(条件性,等 P0+P1 数据)**:决定真正的优化方向
|
||||
原稿 §7 的 5 条优先级在 `other` 模型纠正后**全部需要重新评估**。等真实拆分数据出来再排。
|
||||
|
||||
---
|
||||
|
||||
## 8. 局限与 Confounders(已扩充)
|
||||
|
||||
1. ⚠️ `held_tokens` 语义在原稿被解读颠倒,引发 `other` 的因果归因错误(已纠正,见 §1.2)。
|
||||
2. `other` 字段是计算所得且**未细分**,无法直接归因。需要 P1 instrument 才能区分 radix-cache、running batch、inflight 等。
|
||||
3. ⚠️ EXP2+profile 的 415 errors 与 baseline 9 errors **量级差异无法 deconfound**;polling 是 leading suspect 但未证实。**P0 是必经步骤**。
|
||||
4. **N=1** 的实验配置:任何 v5+profile vs v5 baseline 的延迟/失败差异都属于 single-run variance 合理范围,**不能作为方向性结论**。
|
||||
5. trace 是 single-shot,52 sessions × 4449 reqs 的特定结构可能放大某些路径。
|
||||
6. `capacity = 92086` 是 `token_to_kv_pool_allocator.size`,来自 `mem_fraction_static`(未抽具体值),与"H100 80GB 的物理上限"差距是 SGLang 的安全裕量。
|
||||
7. ⚠️ §3.1 t=1622 持续高 `other` 30+ tick 的现象 **未与 `last_gen_throughput` 交叉验证**;原稿"running batch + 在途传输"的解释是猜想而非证据。
|
||||
8. ⚠️ 18/52 失败 session 的特征(turn_id、input 长度、prefix shape)**未做对比分析**;不能排除某个 session 类型本来就会触发某个固定 bug。
|
||||
9. polling 频率 1Hz 错过亚秒级 burst —— `other` 的双峰可能比测到的更剧烈。
|
||||
10. critic 指出 `pd-router-d-session-reseed` 在 EXP1 涨(193 vs 152)、EXP2 跌(127 vs 152)的反向移动**未在原稿分析**,这是 admission/路由 决策的清晰信号,应该在 P1 之后回看。
|
||||
|
||||
---
|
||||
|
||||
## 9. 后续指令(已更新顺序)
|
||||
|
||||
1. **P0**: 跑 `scripts/sweep_tp1_v5_baseline_rerun_exp2.sh`,3 次 EXP2 baseline,无 polling。
|
||||
2. **P1**: 同时改 SGLang 把 `other` 真正拆开。
|
||||
3. 完成 P0+P1 后:
|
||||
- 重跑 EXP2 一次 + 新 instrument(同 polling),拿到 `other` 拆分。
|
||||
- 对比 baseline-rerun 三次的 errors 分布。
|
||||
- 决定是否回退 polling、调 admission、还是攻 specific 18 个 session 的工作负载特征。
|
||||
4. 任何 v6 代码改动(优化 admission / eviction / transfer)**必须在 P0+P1 之后**。
|
||||
|
||||
---
|
||||
|
||||
## 10. 数据产物
|
||||
|
||||
```
|
||||
outputs/qwen3-30b-tp1-v5-optD-profile/
|
||||
├── exp{1,2}_*_metrics.jsonl # 4449 行 / 实验
|
||||
├── exp{1,2}_*_summary.json
|
||||
├── exp{1,2}_*_pool_timeseries.jsonl # 12 MB / 10 MB
|
||||
└── kvcache-centric-...20260429T{120847,125911}Z/ # 原始 run dir
|
||||
|
||||
outputs/qwen3-30b-tp1-v5-optD/ # baseline 对照(N=1)
|
||||
└── exp{1,2}_1p7d_kvc_optD_*
|
||||
|
||||
# 待 P0 产生:
|
||||
outputs/qwen3-30b-tp1-v5-optD-baseline-rerun/
|
||||
└── exp2_2p6d_run{1,2,3}_*
|
||||
```
|
||||
|
||||
分析脚本:`scripts/analysis/analyze_pool_timeseries.py`(`--json` 拿机器可读输出)。
|
||||
Reference in New Issue
Block a user