# Qwen3-ASR 0.6B · 3 实例(A40 48GB)并发压测方案

> 版本:v1.3(2026-09-14)· 配套理论报告:课程 `0012-asr-load-testing.html`
> v1.3 变更:主阶梯显式关闭前缀缓存(`--no-enable-prefix-caching`)——同带回放使 KV 复用率随并发增长(档内同相位组后到者整段命中先到者),高档位 TTFT 被人为压低、N\* 虚高;新增 `gpu_prefix_cache_hit_rate` 采集验证关闭生效(§3.2 规则 6、§4、§6.1)
> v1.2 变更:① 新增 §3.2「节目带」策略(Tape-A/Tape-B + manifest 冻结 + 流间相位错开)——解决档间内容一致性,使 KV 压力成为 N 的确定函数;② KV 从「算」改「测」:新增 kv_pool_p95 / preempt_delta 指标与抢占红线;③ 档间由「重启」改为「排空验证」+ 阶梯末重复性检验(§6.4 / TC-08);④ 修正章节编号重复(原有两个 §1)及对应交叉引用
> v1.1 变更:① 新增 §0 方法论(参考基准正名 + 四设计要点);② 修正 RTFx/RTF 互为倒数的口径;③ 修正"空载基线"错误——质量/延迟基线取**单流最低负荷**,0 路(空载)只用于显存标定
> 被测拓扑:**1 × A40 48GB,3 个 vLLM 实例(gpu-memory-utilization 0.30/实例)**,流式通话转写
> 本方案目标:实测 3 实例拓扑下的**最大并发通话路数 N\***,并验证稳定性与故障表现

---

## 0. 方法论:为什么这样测

### 0.1 参考基准的正名:Riva 是"事实标准",MLPerf 才是官方基准

先澄清概念,避免口径争议:

- **"Riva 金标准"不是任何标准化组织发布的认证**。它指 NVIDIA Riva(前身 Jarvis)SDK 官方文档自带的流式性能压测方法与配套工具(`riva_streaming_asr_client`,容器内开放、可复现)。因为这套方法足够贴近真实流式业务、指标口径清晰,语音/流式推理圈普遍照搬它来定义自己的压测口径——它是**事实上的参考基准(de facto reference)**。
- **真正官方的开放基准是 MLPerf Inference**(有 ASR 任务),但它测的是**离线吞吐**,不建模流式服务的延迟 SLO。所以做流式压测,业内仍回到 Riva 这套方法。本方案即按 Riva 方法论执行,并在 §0.3 把它的指标翻译成本方案用的名字。

背景 30 秒:Riva 是 NVIDIA 的语音 AI 推理 SDK(流式/离线 ASR、TTS、NMT),容器交付,底层跑在 Triton Inference Server 上,对外 gRPC 流式接口;官方文档按 GPU/精度发布"最大并发流数 + RTFx + 延迟分位数"表格(其测试硬件清单含 T4/A100/L4 等)。

### 0.2 四个设计要点(本方案全部遵循)

**要点 1:负载单位 = 并发流,不是 QPS。**
流式语音没有"请求结束"的短事务概念。每条流模拟一个持续说话的用户,音频按固定小块(本方案 100ms)持续推送,容量表述为"3 实例可承载 N 路并发流"。

**要点 2:实时喂音——整个方法的灵魂。**
每条流按**真实语速**逐块推音频(1x 实时节拍),而不是把文件一次性灌进去:

- 不实时喂音 → 服务端看到的是离线批量任务 → 测出的只是吞吐,排队/调度/算力争抢全部失真;
- 实时喂音 → 服务端承受的到达模式与线上一致 → 排队、调度、争抢真实呈现。

理论报告 0011 里"0.6B ≈ 2000 路"的天花板数字,对应的是**不实时喂音的离线口径**;Riva 方法论坚持测的正是**实时喂音**下的数字——这也解释了为什么官方 Table 2 不能直接当容量结论。

**要点 3:指标体系(Riva 口径 → 本方案口径)。**

| Riva 指标 | 定义 | 本方案对应 |
|---|---|---|
| **RTFx** | 音频时长 ÷ 纯推理处理时长,**>1 即快于实时**;"x"表不含 I/O 等待 | 与本方案 RTF **互为倒数**(见 §0.3) |
| **First Chunk Latency** | 第一个音频块发出 → 首个识别结果返回 | 即 TTFT(首字延迟) |
| **Final Latency 分位数** | 末块发出 → 最终结果返回,报 p50/p90/p95/p99 | 最终延迟 SLO 的撞线依据 |
| **并发流数 / 失败率** | 负载水平 + 各延迟档位下的失败计数 | 稳定性判据 |

工具输出 CSV,按并发数逐行给出上述指标,可直接绘图——本方案的 `ladder.csv`(§7)同构。

**要点 4:判定方式 = 并发扫描 + 双条件收敛。**
对并发流数爬坡扫描(本方案 3→6→12→…→90,每档预热 + 稳态窗口),**同时满足两条**才算该档通过:

1. **每路都跟得上实时**(本方案 RTF p95 ≤ 0.5,等价 Riva 口径 RTFx ≥ 2,比 1 更严、留余量);
2. **延迟分位数不撞 SLO 红线**(TTFT p95 ≤ 1s 等)。

通过的最大档位 = 3 实例工程容量 N\*——这就是"TTFT SLO 撞线处"的正式化定义。

### 0.3 术语澄清(两处易混,全文统一)

**① "空载(0 路)"只用于显存标定,不用于任何质量/延迟指标。**
0 路负荷时没有音频流,不存在转写结果,自然没有 WER/TTFT 可测;但**显存占用是可测的**(实例存活、无流,量出权重+固定开销)。所以:

- 显存固定开销基线 = 0 路(实例存活、无流)——合法且必要(TC-02);
- 质量/延迟基线 = **单流最低负荷(1~2 路)**——它是"质量不随负载变化"的参考点(TC-01/TC-04)。

**② RTF 与 RTFx 互为倒数,读别人数据时先看方向。**

- 本方案与多数文献:RTF = 处理耗时 ÷ 音频时长,**< 1 才跟得上实时**(越小越好);
- Riva:RTFx = 音频时长 ÷ 纯推理处理时长,**> 1 即快于实时**(越大越好),且不含 I/O 等待;
- 换算:RTFx = 1 / RTF。本方案 loadgen 统计的是**端到端** RTF(含网络与调度等待),比 Riva 的纯推理 RTFx **更保守**——用它做 SLO 只会更安全。
- 实例:官方报告 0.6B 并发 1 时 RTF=0.0094、吞吐=106.4 秒音频/秒,两者恰互为倒数(1/0.0094=106.4);报告中"吞吐"列就是聚合 RTFx 口径,"RTF"列 = 并发数 ÷ 吞吐(每路流的实际倍速)。

---

## 1. SLO 与判定标准(先定标准再开测)

| 指标 | SLO 阈值 | 说明 |
|---|---|---|
| TTFT p95(首字延迟) | **≤ 1.0 s** | 通话体验核心;超此值即认为该档过载 |
| RTF p95 | **≤ 0.5** | 端到端口径,较 Riva 纯推理 RTFx ≥ 2 更严 |
| 最终结果延迟 p95 | ≤ 2.0 s | 尾点/结束帧出结果的延迟 |
| WER/CER 劣化 | ≤ 1.0 个点 | 与**单流基线**比(带标注子集) |
| KV 池健康 | **抢占 = 0**(preempt_delta),kv_pool_p95 < 0.98 | 硬红线:抢占即 KV 池打满,先于延迟判据判该档不合格 |
| 稳定性 | 浸泡 4h 无重启、显存曲线平稳、无错误累积 | 泄漏/碎片检查 |

**产出定义**:N\* = 阶梯加压中"所有 SLO 仍满足"的最大总并发路数(短话单口径,播 Tape-A;含长话单口径由 TC-03b 给出)。
**部署容量结论** = N\*(3 实例合计)× 安全系数 0.7(写入值班手册的数字)。

---

## 2. 环境与被测系统

### 2.1 环境信息采集(测试前记录,填入附录 A)

```bash
nvidia-smi -q | head -40                  # 驱动、CUDA、显存
pip show vllm qwen-asr torch | grep -E "Name|Version"
python3 -c "import torch; print(torch.cuda.get_device_name(0), torch.version.cuda)"
```

### 2.2 三个实例的启动命令

按 0.92 ÷ 3 ≈ 0.30 分配(vLLM 该参数为**每实例独立限额**,官方文档语义):

```bash
# 实例 1/2/3:端口 9001/9002/9003,其余参数完全一致
for i in 1 2 3; do
  port=$((9000 + i))
  qwen-asr-serve Qwen/Qwen3-ASR-0.6B \
    --dtype bfloat16 \
    --gpu-memory-utilization 0.30 \
    --no-enable-prefix-caching \
    --port $port \
    > /var/log/asr-inst$i.log 2>&1 &
done
# --no-enable-prefix-caching:容量口径必须关(§3.2 规则 6);若直接 vllm serve,参数名一致
# 若不用官方包而直接 vllm serve,参数名一致;启动日志里记录
# "Available KV cache memory" 一行 —— 这是实测 KV 池大小,填回附录 A
```

- 三个实例的 **KV 池大小(GB)务必从启动日志抄下来**,对照理论推算(每实例 ≈ 0.30×48 − 1.8 权重 − 开销)。
- 前置校验:三进程就位后,**0 路负荷下的 `nvidia-smi memory.used`** 之和即固定开销实测值(替换压测报告 0012 推导表里的 ~2GB 估计)——这是"空载"唯一合法的用途。

### 2.3 负载均衡

三个实例前放一个轮询入口(nginx stream / HAProxy / 自写),**按连接分发、连接期间粘滞**(通话流不能中途换实例)。压测打 LB 地址;TC-06 单独直打单实例。

### 2.4 流式网关核对

官方包流式接口为进程内 API(`init_streaming_state / streaming_transcribe / finish_streaming_transcribe`,vLLM 后端,2 秒块、`unfixed_chunk_num=2`)。
- 若你已有 WebSocket 网关:核对它逐块转发 + 增量返回即可;
- 若没有:先用 OpenAI 兼容 `/v1/audio/transcriptions` 端点压**整段转写**模式(测吞吐/RTF 有效,TTFT 语义为整段首字),流式压测需按官方示例补一个最小 WS 网关(入=二进制 PCM 块,出=JSON 增量/最终帧,end 帧收尾)。

---

## 3. 测试数据准备

### 3.1 语料池(6000 条真实通话)

| 项 | 要求 |
|---|---|
| 规模 | ≥ 6000 条真实通话录音,8k μ-law/wav,涵盖安静办公室/车载/移动网络/方言口音 |
| 时长分层 | 全池按时长分桶(30s / 1min / 3min / 5min / 10min …),各桶数量写入 manifest——选带按桶配额抽,后续扩带同样 |
| 带标注子集 | 8 条(覆盖各时长桶),人工核对出参考文本,用于 WER-负载曲线 |

重采样(一次性,6000 条量大,8 并行防磁盘打满):

```bash
find calls -name '*.wav' -print0 | xargs -0 -P 8 -I{} sh -c \
  'sox "$1" -r 16000 -c 1 "pcm16/$(basename "$1")"' _ {}
```

### 3.2 节目带:档间内容一致性的实现(v1.2 核心修订)

**为什么不用「循环换语料」**:TTFT/RTF/最终延迟不只取决于并发,还取决于内容——时长分布决定 prefill 长度与 KV 占用,语音密度决定输出词元数(decode 步数)。每档随机换语料,「随并发的劣化」就混入「随内容的波动」,曲线无法归因。6000 条的正确用法是**选材池**:从中冻出固定带子,所有档、所有流播同一条。

**Tape-A「容量带」——阶梯主用(480s = 8min,9 通)**

| 构成 | 数量 | 作用 |
|---|---|---|
| 30s | ×6 | prefill/TTFT 样本密集 |
| 60s | ×2 | 中位时长覆盖 |
| 180s | ×1 | 制造 KV 峰值相位与长 prefill |

**Tape-B「长话单带」——N\* 定位后补跑两档(660s ≈ 11min,6 通)**

| 构成 | 数量 | 作用 |
|---|---|---|
| 300s | ×1 | KV 压力主源(单路峰值 ≈ 590MB,即 0.59GB/路) |
| 180s | ×1 | 次级 KV 峰(≈ 355MB/路) |
| 60s | ×2 | 过渡 |
| 30s | ×2 | 保持 TTFT 样本 |

> 为什么必须 Tape-B:每路 KV 随通话时长线性增长(112KB/token;30s≈62MB、60s≈122MB、180s≈355MB、300s≈590MB)。Tape-A 最长 180s,会**系统性低估** KV 压力——容量结论必须双口径(TC-03b)。

**冻结与回放规则**

1. **选带**:按桶配额分层抽样,RNG 固定 seed 一次;产出 manifest(文件清单 + 播放顺序 + md5 + 各桶配额),git 提交冻结。此后任何档、任何流、任何一轮都播同一条带。
2. **相位错开**:流 k 从带内第 `k mod 带内通话数` 通电话开始播——内容集合完全一致,壁钟时间上 decorrelate(避免 N 路同时进入 180s 通话的同步 KV 尖峰——那是带子造成的假象,不是真实负载);相位映射固定 → 完全可复现。
3. **跨界剔除**:只统计「在稳态窗口内完整开始且完整结束」的通话;相位固定 → 每档剔除的是同一批通话,不引入档间偏差。
4. **预热同带**:流在预热期即起播,cudagraph/编译缓存被同一负载填热——预热与稳态看到同一稳态(前缀缓存已关,见规则 6)。
5. **复用不虚高**:服务端没有音频内容缓存(客户端推裸 PCM,嵌入现算、词元现解),同 9 条循环本身不会让性能虚高。
6. **关掉前缀缓存(容量口径的前提)**:同一条带意味着任意时刻同一通电话有 ~N/9 路在播。vLLM 的自动前缀缓存按 token 块(+多模态输入哈希)复用 KV——**同一音频 → 同一哈希 → 同相位组的后到者整段命中先到者的 prefill**。命中率随并发增大(N=90 时档内 10 路一组,9/10 可命中;N=3 时无重复),高档位 TTFT 被人为压低——而 TTFT p95 恰是容量红线,曲线必在拐点处失真、N\* 虚高。真实通话两路音频完全相同的概率 ≈ 0,该命中是 tape 造成的假象。处理:主阶梯全部实例加 `--no-enable-prefix-caching`,并用 `gpu_prefix_cache_hit_rate` 实测验证 ≈ 0(不靠默认值假设);若引擎对多模态强制开启关不掉,fallback 把带扩到 ≥90 通(≈80min)、相位 = k mod 带长——每流任意时刻占据带内唯一位置,并发重复从设计上消失。生产若开缓存,收益上限只是模板前缀 ~30 token,量级极小;关掉测得的 N\* 偏保守,方向安全。

### 3.3 其余语料分工与注意事项

| 用途 | 取材 | 方式 |
|---|---|---|
| Tape-A 容量带 | 9 条 | 固定 seed 分层抽样,manifest 冻结 |
| Tape-B 长话单带 | 6 条 | 同上 |
| WER 标注子集 | 8 条 | 覆盖各时长桶 |
| TC-05 浸泡 | 全池随机轮换 | 真实分布优先,不求档间可比 |
| 稳健性复检(可选) | N\* 档加跑一轮「每流从全池固定 seed 随机抽 8min」 | 验证 Tape-A 结论不是内容巧合 |

- 回放源统一 16kHz mono s16le(loadgen 内置校验);
- 客户端与 GPU 服务器**分机部署**;90 路 × 100ms × 3.2kB ≈ 23 Mbps,普通内网即可,压测前确认客户端 CPU 余量(loadgen 进程 < 50%)。

---

## 4. 测试工具

| 工具 | 来源 | 用途 |
|---|---|---|
| `asr_loadgen.py` | 课程 0012 代码实例(已验证语法/CLI),本期需扩展 | N 路并发、1x 实时喂音、TTFT/RTF 统计;**新增:按 manifest 节目带播放(`--tape`)、流 k 相位偏移(k mod 带内通话数)、通话级起止时间戳(剔除跨界通话)** |
| `nvidia-smi` 采样 | §6.2 | GPU 利用率/显存/功耗曲线 |
| vLLM `/metrics` 采集 | 各实例端口(9001-9003) | `gpu_cache_usage_perc`(→kv_pool_p95)、`num_preemptions_total`(→preempt_delta/抢占红线)、`num_requests_running/waiting`(→排空验证)、`gpu_prefix_cache_hit_rate`(→验证前缀缓存已关,期望 ≈ 0);指标名随 vLLM 版本略异,以附录 A 实录为准 |
| `wer.py`(编辑距离) | 课程 0001 同款实现 | 带标注子集的 WER/CER |
| `sox` | 系统包 | 8k→16k 重采样 |

---

## 5. 测试用例矩阵

> 阶梯档位全部取 **3 的倍数**(3 实例均分后每档每实例负载为整数)。

### TC-01 单流基线(质量与延迟的参考点)

- 负载:**1 路流**(最低负荷,不是 0 路——0 路没有音频,不存在转写指标)
- 3 条不同时长音频各跑 3 遍
- 记录:RTF₁、TTFT、最终延迟、WER(标注子集)
- **产出:RTF₁ 与单流 TTFT/单流 WER,是全案的两个锚点**;TC-04 的"劣化"全部相对这里的单流 WER

### TC-02 显存足迹标定(唯一使用"0 路"的用例)

- 0 路点:三实例存活、无任何流 → `nvidia-smi memory.used` 之和 = **固定开销实测**(权重 + CUDA context + 图池)
- 负载点:依次 1 / 8 / 24 / 48 路(合计),每档稳态 5 分钟,记录稳态显存均值
- **产出:固定开销实测值 + 每路流增量** → 替换 0012 第四节推算表的估计值,重算实例数上限表

### TC-03 阶梯加压找拐点(核心用例,全档播 Tape-A)

- 档位:N_total = 3, 6, 12, 18, 24, 30, 36, 48, 60, 75, 90
- 每档:预热 2 分钟(同带起播,填热编译/图缓存;前缀缓存已关,§3.2 规则 6)→ 稳态测量 8 分钟 → 排空验证(§6.1 第 4 步)。**档位 3 与 6 的稳态延长至 24 分钟**(低档通话样本太少,p95≈max 不可信;带循环 3 遍,内容密度不变,跨档可比性不受影响)
- 统计口径:只计窗口内**完整开始且完整结束**的通话(跨界剔除,§3.2)
- 每档记录(§7 模板):TTFT p50/p95/max、RTF p50/p95/max、错误流数、kv_pool_p95、preempt_delta、calls_clean、GPU util 均值/峰值、显存
- **抢占红线(先于延迟判据)**:preempt_delta > 0 或 kv_pool_p95 ≥ 0.98 → 该档直接不合格(KV 池打满,与 TTFT 达标与否无关)
- **中止条件**:TTFT p95 > 5s、错误流 > 5%、或显存逼近 `gpu-memory-utilization` 上限
- **档间动作:不重启**(§6.4);120s 排不空才走重启分支
- **产出:N\*(短话单口径)= 最后一档「抢占红线 + 双条件(RTF 与延迟)」同时满足的总并发数**(§0.2 要点 4)

### TC-03b 长话单校验(N\* 定位后补跑,播 Tape-B)

- 负载:0.5×N\* 与 1.0×N\* 两档,其余协议同 TC-03
- 依据:单路 KV 随通话时长线性增长,Tape-A 最长 180s 会系统性低估 KV 压力。理论均值:Tape-A ≈ 187MB/路、Tape-B ≈ 393MB/路(附录 B)→ 每实例占用 ≈ 均值 × (N/3)。若 Tape-B @ N\* 的估算超过 KV 池实测值(启动日志),预期出现抢占、N\*(含长话单口径)< N\*(短话单口径)
- **产出:N\*(含长话单口径)。两个口径的差值 = 业务通话时长分布对容量的修正量,写入交付结论**

### TC-04 WER-负载曲线

- 负载点:**单流基线(1~2 路)**、0.5×N\*、0.8×N\*、1.0×N\* ——每个点把 6 条标注子集混入回放流
- 记录每点 CER/WER,对比**单流基线**(不是空载——0 路没有转写结果)
- **产出:负载-WER 曲线;劣化 > 1 点即为隐性劣化告警**

### TC-05 稳定性浸泡

- 前置:**重启三实例**取干净基线(§6.4——泄漏测量不能站在上一档的状态上)
- 负载:0.8 × N\*,持续 4 小时(全池随机轮换回放,含静音段;若 TC-03b 显示 KV 紧张,改播 Tape-B 以覆盖长话单)
- 监控:显存时间序列(泄漏判定:线性增长不回落)、错误计数、实例存活
- **产出:稳定性结论 + 显存-时间图**

### TC-06 故障注入

- 负载:满载(1.0 × N\*)时,`kill` 实例 2
- 观察:LB 是否把新连接导到 1/3 实例、存量通话是否被断、TTFT 恢复时间
- **产出:过载持续时间(判定 < 30s)、是否需要重试逻辑**

### TC-07 拓扑对照(复核 3 实例决策,可选但推荐)

- 同一总压(0.8 × N\*),三种拓扑各跑一轮:3×0.30(现方案)、2×0.46、1×0.92
- 对比:聚合吞吐、TTFT p95、错误率
- **产出:确认 3 实例是否真优;若 1 实例大池更好,回改部署并重跑 TC-03**

### TC-08 重复性检验(判定「不重启策略」是否成立)

- 阶梯走完、N\* 定位后,回退到中档(建议 24),按 TC-03 同协议(Tape-A)原样重跑一次
- 对比两次 24 档的 TTFT p95 / RTF p95 / kv_pool_p95:偏差在噪声内(建议阈值 ±15%)→ 「排空验证」策略成立,阶梯曲线可信
- 若漂移超阈值 → 存在热漂移/状态残留,升级为**每档重启 + 重新预热**,重跑全阶梯
- **产出:可复现性结论(入交付物);重复档数据入 ladder.csv(notes 标 repeat)**

---

## 6. 执行流程

### 6.1 每档标准动作(以 TC-03 某档为例)

```bash
# 1) 前置检查:3 实例健康、GPU 温度 < 85°C、磁盘余量
curl -s http://gpu-host:9001/health && curl -s http://gpu-host:9002/health && curl -s http://gpu-host:9003/health

# 2) 启动监控(后台,整个压测期间不中断):GPU 曲线 + vLLM 指标(5s 采样)
nvidia-smi --query-gpu=timestamp,utilization.gpu,memory.used,power.draw,temperature.gpu \
    --format=csv -l 5 > results/gpu_N48.csv &
while true; do
  ts=$(date +%s)
  for p in 9001 9002 9003; do
    curl -s http://gpu-host:$p/metrics \
      | grep -E "gpu_cache_usage_perc|num_preemptions_total|num_requests_(running|waiting)|prefix_cache_hit_rate" \
      | sed "s/^/$ts inst$p /" >> results/metrics_N48.log
  done
  sleep 5
done &

# 3) 压测(错峰爬坡 1s/流;--tape 按 manifest 播放,流 k 相位 = k mod 带内通话数)
python3 asr_loadgen.py --url ws://gpu-host:9000/lb \
    --tape manifests/tapeA.json --streams 48 --ramp 0.5 \
    | tee results/ladder_N48.log

# 4) 排空验证(全部流 finish 后执行;期望两指标归零 + 显存回落 0 路基线)
for p in 9001 9002 9003; do
  curl -s http://gpu-host:$p/metrics | grep -E "num_requests_(running|waiting)"
done
# 120s 内排不空 → 走 §6.4 重启分支(本身即泄漏/挂死信号,drain_s 记入 ladder.csv)

# 5) 冷却 60s 再进下一档
sleep 60
```

### 6.2 中止与安全

- 任一实例 OOM/崩溃 → 立即停止该档,记录崩溃前最后 5 分钟曲线;重启三实例后该档作废重跑(§6.4)
- kv_pool_p95 ≥ 0.98 或出现抢占 → 停止升档(该档已超 KV 容量;数据照记,作为红线证据)
- GPU 温度 > 87°C → 暂停,检查散热
- 每档结束确认三实例仍存活(health check)+ 排空验证(§6.1 第 4 步)再进下一档

### 6.3 时间估算

TC-01 ~0.5h;TC-02 ~1h;TC-03 常规 9 档 × ~13min(预热2+稳态8+排空2+冷却1)≈ 2h,档位 3/6 各延长 16min ≈ +0.5h;TC-03b 两档 ≈ 0.5h;TC-08 一档 ≈ 0.25h;TC-04 并入 ~0.5h;TC-05 4h(挂机);TC-06 ~0.5h;TC-07 ~1.5h。**合计约 2.5 个工作日**(含数据准备与整理)。

### 6.4 排空验证与重启策略(档间「干净」从哪来)

vLLM 的 KV 池在**启动时全额预分配**(`gpu-memory-utilization` 划走),PagedAttention 分页管理、请求结束块即归还、无外部碎片;主阶梯又已显式关闭前缀缓存(§3.2 规则 6)——上一档排空后,引擎不存在任何跨请求状态残留,与冷启动严格等价。因此**档间不重启**,以排空验证替代:

| 时点 | 动作 |
|---|---|
| 会话开始 | 全新启动 3 实例 + 预热(TC-01/02/03 共用同批实例) |
| **每档之间** | **不重启**:排空验证(§6.1 第 4 步)+ 60s 冷却 |
| 某档异常中止(OOM/抢占风暴) | 重启 3 实例 + 重新预热,该档作废重跑 |
| TC-05 浸泡前 | 重启(泄漏测量需要干净基线) |
| TC-07 | 每种拓扑必然重启(改了 gpu-memory-utilization) |
| TC-08 重复性检验失败 | 升级为每档重启 + 重新预热,重跑全阶梯 |

> 不推荐每档重启:重启引入 ~3-5min × 3 实例的冷启动方差(cudagraph 重捕获、编译缓存)× 11 档 ≈ 1h,换来的「干净」排空验证已等价给出——只有 TC-08 检验失败时它才成为必要手段。

---

## 7. 记录模板

每档一行,存 `results/ladder.csv`(tape 列标 A/B,重复档在 notes 标 repeat):

```csv
N_total, tape, ttft_p50, ttft_p95, ttft_max, rtf_p50, rtf_p95, rtf_max, err_streams, kv_pool_p95, preempt_delta, calls_clean, drain_s, gpu_util_avg, gpu_util_max, vram_used_gb, cer, notes
3,   A, 0.31, 0.52, 0.71, 0.012, 0.019, 0.031, 0, 0.21, 0, 71, 4, 34, 61, 14.2, 0.041, 稳态24min(低档加长)
6,   A, ...
75,  B, ...
```

结果判读规则:

- `N*` = 最大满足 [TTFT p95 ≤ 1 ∧ RTF p95 ≤ 0.5 ∧ 抢占 = 0 ∧ 错误 = 0] 的 N_total;Tape-A 行 = 短话单口径,Tape-B 行 = 含长话单口径,结论双标
- **抢占红线先行**:preempt_delta > 0 或 kv_pool_p95 ≥ 0.98 → 该档直接不合格,与延迟无关
- 只计窗口内完整开始/结束的通话(calls_clean 为样本数;p95 置信度随样本量,低档已加长窗口)
- 拐点前后各多测一档细化(如 30 与 36 之间插 33)
- 把实测 N\*、固定开销、每流 KV 代回 0012 第四节,校准理论表(实测替换估计)

---

## 8. 最终交付物

1. `ladder.csv` + 曲线图(TTFT-并发、RTF-并发、WER-并发、显存-并发、**KV 池利用率-并发**)
2. **部署容量结论(双口径)**:短话单口径 N\*(Tape-A)与含长话单口径 N\*(Tape-B);值班手册容量 = 0.7 × N\*(按业务通话时长分布选口径)
3. 校准后的显存推算表(实测固定开销/每流 KV 替换估计值)
4. 拓扑结论(3 实例 vs 1/2 实例的对照数据)
5. 稳定性结论:排空耗时序列、TC-08 重复性偏差、泄漏判定
6. 风险清单(温度、泄漏、故障转移表现)

---

## 附录 A:待填环境清单

| 项 | 值 |
|---|---|
| GPU | A40 48GB(驱动版本 ___,CUDA ___) |
| vLLM / qwen-asr / torch 版本 | ___ / ___ / ___ |
| 模型 revision(HF commit) | ___ |
| 每实例 KV 池(启动日志) | ___ / ___ / ___ |
| 固定开销实测(0 路基线,仅显存口径) | ___ GB |
| 每路流增量实测(TC-02) | ___ MB/路 |
| LB 方式 | ___ |
| 节目带 manifest(Tape-A/B 清单+顺序+md5) | ___(git 冻结) |
| prefix caching 开关(主阶梯必须 `--no-enable-prefix-caching`) | ___ / gpu_prefix_cache_hit_rate 实测:___(期望 ≈ 0) |

## 附录 B:对照口径

- 理论参考:报告 Table 2(0.6B,BF16,vLLM 0.14,**GPU 型号未公开**,多并发整段提交口径):RTF 0.0094@1 → 0.064@128,TTFT p95 105ms@1 → 6195ms@128;吞吐列即聚合 RTFx。实时喂音的流式口径必须以本方案实测为准。
- 推导依据:每路流 KV ≈ 112KB/token;5 分钟通话 ≈ 0.59GB/路(课程 0012 第四节,替换实测值后以实测为准)。
- 节目带单路 KV 均值(理论,时间加权):Tape-A ≈ 187MB/路(= 0.375×62 + 0.25×122 + 0.375×355),Tape-B ≈ 393MB/路(= 0.45×590 + 0.27×355 + 0.18×122 + 0.09×62)。每实例占用 ≈ 均值 × (N/3) 路,对照 KV 池实测值(启动日志)预判抢占;最终以 kv_pool_p95 实测为准。
