From 1d51704dad807a8b8c8c26c9a1ee46eca2a93e0c Mon Sep 17 00:00:00 2001 From: kzlin Date: Wed, 6 May 2026 21:30:11 +0800 Subject: [PATCH] docs(kvc): agentic-fit analysis, refactor plan, validation report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three new docs covering the structural-fit investigation: - AGENTIC_FIT_ANALYSIS_ZH.md: §1-§7 of structural design issues that surface KVC vs vanilla DP gap on real agentic workloads (SWE 50sess). Quantifies session pinning, LRU shortfall, P-side imbalance, time-scale distortion, etc., with code citations and N=3 rerun data. - REFACTOR_PLAN_ZH.md: KISS-edition refactor plan. After verifying the original "estimate inflation" and "resident_blocks aging" claims were not real bugs, scope shrinks to one code change (backpressure) plus a 4-run smoke sweep within an 8h budget. - STRUCTURAL_VALIDATION_REPORT_ZH.md: validates §1-§7 claims using existing v5 baseline rerun data + 8DP CA baseline. Each claim labeled fully-supported / indirect / retracted with the data source. Notes that backpressure E2E validation is pending GPU smoke run. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/AGENTIC_FIT_ANALYSIS_ZH.md | 434 ++++++++++++++++++++++++ docs/REFACTOR_PLAN_ZH.md | 123 +++++++ docs/STRUCTURAL_VALIDATION_REPORT_ZH.md | 304 +++++++++++++++++ 3 files changed, 861 insertions(+) create mode 100644 docs/AGENTIC_FIT_ANALYSIS_ZH.md create mode 100644 docs/REFACTOR_PLAN_ZH.md create mode 100644 docs/STRUCTURAL_VALIDATION_REPORT_ZH.md diff --git a/docs/AGENTIC_FIT_ANALYSIS_ZH.md b/docs/AGENTIC_FIT_ANALYSIS_ZH.md new file mode 100644 index 0000000..be667af --- /dev/null +++ b/docs/AGENTIC_FIT_ANALYSIS_ZH.md @@ -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 实验 diff --git a/docs/REFACTOR_PLAN_ZH.md b/docs/REFACTOR_PLAN_ZH.md new file mode 100644 index 0000000..555ca8f --- /dev/null +++ b/docs/REFACTOR_PLAN_ZH.md @@ -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 → 写报告。 diff --git a/docs/STRUCTURAL_VALIDATION_REPORT_ZH.md b/docs/STRUCTURAL_VALIDATION_REPORT_ZH.md new file mode 100644 index 0000000..f68cd81 --- /dev/null +++ b/docs/STRUCTURAL_VALIDATION_REPORT_ZH.md @@ -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 才是)。