## 一、暂定结论（TL;DR）

指标 | 原目标 P95 | 实测（预连接后） | 暂定新目标
--- | --- | --- | ---
`first_partial`（按键→首字） | <500ms | P50 942ms · P95 950ms（极稳定） | P95 < 1.2s
`release_to_commit`（松键→上屏） | <300ms | 短句 ~170ms · 8s 会话 350~500ms | ≤3s 会话 <300ms；长会话 <600ms

为什么 500ms 结构性不可达：first_partial = max(握手, DashScope 音频块阈值 ~850ms) + 推理 ~90ms。 地板是 DashScope 的音频块阈值，与网关代码无关（直连同样 ~1s）。要破地板只能换模型（见第四节）。
体感修正：前导静音实验证明阈值按「音频量」而非「语音量」计——400ms 静音只使 first_partial 推迟 ~100ms。 用户按键后自然停顿 ~300ms 再开口时，从开口算的首字延迟仅 ~640ms，体感优于指标数字。

## 二、延迟分解（实测）

环节 | 耗时 | 处置
--- | --- | ---
网关预检（限流 / 配额 / 设备槽） | ~10ms | 无需优化
DashScope dial + task-started 握手 | 暖 ~130ms · 抖动 60ms~3.7s | 已消除 spare 预连接（#24）
DashScope 音频块阈值 | ~850ms（地板） | 换模型才能降（paraformer ~520ms）
推理 + 回传 | ~90ms | 无手段
final flush（松键后） | 短句 ~170ms · 8s 会话 350~540ms | 随内容量增长，属 provider 物理时间

### 测量口径（对齐桌面端 dictation.rs）

- `first_partial`：start 帧发出 → 首个 partial（不含麦克风启动，桌面另有 `audio.start_ms`）
- `release_to_commit`：stop 帧发出 → 尾部 final（不含注入耗时 <10ms）
- 工具：`go run ./cmd/latencyprobe -wav xx.wav -n 12`（单持久连接 + 100ms/帧实时推流，复刻桌面行为）

## 三、本轮落地的修复与优化

### #23 stop 后周期 usage 帧抢跑尾部 final（已修，服务端根治）

- 症状：8s 会话 4/4 复现 usage 先于尾部 final 到达，客户端以 partial 文本提前上屏、丢失 final 修正（iOS 21A 修过客户端侧，桌面端仍暴露）
- 根因：finish 等尾部 final 期间（最长 3s）usageLoop 仍在 tick
- 修复：finish 先冻结会话并停 usageLoop 再 flush——stop 后唯一的 usage 是结算帧且必在全部 final 之后，五端客户端「final 或 usage 即提交」逻辑自动变安全，无协议改动。修复后 6/6 归零
- 桌面端收尾兜底 350ms → 800ms（final flush 实测 350~540ms，350ms 会截丢尾 final）

### #24 gummy spare 预连接（已实现）

- 常备一条已完成 run-task 握手的 DashScope 会话，start 直取、异步补位、40s 定期换新
- 依据：DashScope 空闲 60s 存活、120s 报 Idle timeout（实测），留余量 45s
- 效果：网关 start 处理 120~670ms → 3~4ms；dial 抖动（60ms~3.7s）移出关键路径，否则 P95 可达 1.5~4.5s

### 顺手修复

- 滑动窗口 Lua「只查不记」与实现不符：val=0 的检查不再写入 0 值成员污染窗口 ZSET
- gummycheck 增加 `-model / -pace / -idle` 参数，支持模型对比与闲置存活实验

## 四、待决策：要不要换 paraformer

| gummy-realtime-v1（现用） | paraformer-realtime-v2
--- | --- | ---
音频块阈值（首字地板） | ~850ms | ~520ms
partial 刷新节奏 | ~1s / 次 | ~500ms / 次（更跟手）
识别质量（单样本 TTS 实测） | 全对 | 「各自模块的进度汇报」→「各字文案者进入汇报」
partial 时间戳（计量交叉校验用） | 每条带 end_time | 仅 final 带

决策前置条件：用 eval 框架（`eval/`）跑真实数据集对比两模型 WER，再决定是否为 ~300ms 首字收益换质量。 切换成本低：`GummyProvider.Model` 一行配置。

## 五、复测方法

    # 起服务端（pg/redis 容器已在跑）
    cd server && ./run-dev.sh

    # 全链路延迟实测（另开终端）
    go run ./cmd/latencyprobe -wav /path/to/16k-mono.wav -n 12

    # 直连模型对比 / 闲置存活实验
    rbw get dashscope-api-key | go run ./cmd/gummycheck -wav xx.wav -pace 100 -model paraformer-realtime-v2
    rbw get dashscope-api-key | go run ./cmd/gummycheck -wav xx.wav -pace 100 -idle 60

- 注意设备 30 分钟滑动窗口上限 30 次会话，probe 换 `-device` 规避
- 观测日志：`gummy: session ready`（dial/task-started 耗时）、`gummy: session from spare`、`gateway: start handled`