Files
live-caption/docs/phase2-streaming.md
T
blumia 18dbc9c8d3 feat: add real-time caption CLI prototype with Qwen3-ASR
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 验证文档。
2026-10-12 11:40:59 +08:00

53 lines
3.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 越贵;滑窗是必须的,不是优化项。