Add a C++/Qt6/CMake CLI prototype that captures the default sink monitor through libpulse and transcribes it in-process with Qwen3-ASR via libllama/libmtmd. Add phase 0/1 verification docs. 新增实时字幕命令行原型:libpulse 采集默认输出 monitor,进程内用 libllama/libmtmd 跑 Qwen3-ASR 推理。附阶段 0/1 验证文档。
53 lines
3.3 KiB
Markdown
53 lines
3.3 KiB
Markdown
# Phase 2 计划:原生流式解码
|
||
|
||
目标:把「窗口重转写 + 事后取最长公共前缀」换成模型官方的流式解码 —— **前缀回滚(prefix rollback)**,消除 Phase 1 里「窗口在词中间被切断时模型自己编结尾」的问题。
|
||
|
||
实现方式:**在 libllama / libmtmd 上自己实现**(只保留一个推理运行时,复用已有的 CMake、模型管理、采集与解析代码)。
|
||
参考实现:`antirez/qwen-asr`(纯 C,已实现 `--stream` 的前缀回滚与滑窗),其 MODEL.md 逐行记录了官方 Python 实现 `qwen_asr/inference/qwen3_asr.py` 的流式算法,用作对齐参数的对照。
|
||
|
||
## 为什么不需要 vLLM
|
||
|
||
官方文档写「streaming 只支持 vLLM 后端」,容易误解为依赖 vLLM 特性。实际上流式只是一套**解码策略**:
|
||
|
||
1. 音频按 2s 累积;
|
||
2. 每次触发时把**已累积的全部音频重新过一遍编码器**(O(n²),被 2s 节奏限制住);
|
||
3. 解码器 prompt 里带上**上一轮转写文本(去掉最后 5 个 token)**作为 assistant 前缀,从那里继续生成;
|
||
4. 前 `unfixed_chunk_num` 个 chunk 不带前缀(冷启动);
|
||
5. 输出 = 前缀 + 新生成部分。
|
||
|
||
## 参数(官方代码默认值)
|
||
|
||
| 参数 | 值 | 说明 |
|
||
|---|---|---|
|
||
| `chunk_size_sec` | 2.0 s | 音频累积粒度 |
|
||
| `unfixed_token_num` | 5 | 前缀回滚:丢掉最后 5 个 token 再续写 |
|
||
| `unfixed_chunk_num` | 2 | 冷启动不开前缀;论文写 4,取保守值 |
|
||
| 滑窗上限 | ~16 s | 丢弃「文本已提交」对应的音频,把成本从 O(n²) 压到 O(n·窗口) |
|
||
|
||
## 与 Phase 1 原型的差距
|
||
|
||
| 已有 | 缺 |
|
||
|---|---|
|
||
| 2s 节奏的窗口重转写 | 把「上一轮文本 − 最后 5 token」拼进 assistant 前缀 |
|
||
| mtmd tokenize + eval 的完整路径 | 按 **token** 而非字符回滚(需跟踪生成的 token 序列) |
|
||
| 语言识别、输出解析、稳定前缀显示 | 滑窗:丢弃已提交文本对应的音频 |
|
||
| monitor 采集 + 重采样 | 用「已提交 token 数」定义稳定边界,替换事后取交集 |
|
||
|
||
> 注意:Phase 1 的「字符最长公共前缀」是错的替代品,官方的定义是「丢最后 5 个 token」。
|
||
|
||
## 顺带简化:去掉 VAD
|
||
|
||
官方明确不使用 VAD(模型自己处理静音;只有非流式长音频才用「在切点 ±5s 内找 100ms 最低能量点」切段)。流式模式下可以整个删掉能量 VAD,少一个需要调参的组件。
|
||
|
||
## Spike(先验证,再改产品代码)
|
||
|
||
1. **效果**:同一段音频,对比「离线转写」与「带前缀回喂的流式」输出,确认流式结果连贯、不再编结尾。
|
||
2. **延迟**:实测 2s 节奏下的单步耗时,重点看窗口涨到 16s 时是否仍跟得上(每步预算 < 2s)。
|
||
3. 以上两条通过后,才把它落进 `Qwen3Asr`,并补一个流式模式的实机测试脚本。
|
||
|
||
## 风险
|
||
|
||
- **成本**:每步重编码累积音频,窗口越大越慢;若 16s 窗口跟不上,需改为「复用 KV cache + 只编码新 chunk」,并用 `llama_memory_seq_rm` 做回滚。
|
||
- **前缀注入方式**:需要确认把文本前缀拼在 chat template 的 assistant 头之后,mtmd 能否正确切分(预期:`[text]<media>[text]` 三段,前缀属于第三段)。
|
||
- **吞吐**:前缀越长,每步 prefill 越贵;滑窗是必须的,不是优化项。
|