ASR-demo/realtime_asr_optimization_demo/README.md

145 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Realtime ASR WebSocket Optimization Demo
这是一个独立的实时 ASR WebSocket 验证项目,放在模型部署项目 `demo` 下,但不导入、不启动、也不调用仓库根目录的原始 `app/`
## 验证目标
- 浏览器麦克风或按实时速度发送的 PCM/WAV 音频通过本项目 WebSocket 发送。
- 同一 `sentence_id` 的 partial、final 和后续更新覆盖同一条 raw segment。
- raw segment 与前端 display block 分离,避免把物理切段直接等同于展示换行。
- 小于 1.6 秒且带有 `short_attach` / `embedding_attach` 策略的实名结果降级为 pending。
- 不把 embedding 字段写入 demo 状态池。
- 只有相邻且身份可信的 segment 才合并A→B→A 保持时间顺序。
- 记录 partial 首次延迟、final 延迟、partial 修订次数和服务端返回时间范围。
- 每个实时 turn 单独提交声纹特征,由辅助服务维护本 WebSocket session 的在线聚类中心。
本次修复、诊断状态和部署验收步骤见 [FIXES.md](FIXES.md)。ASR 继续使用已部署的独立 vLLM已有 vLLM 服务时无需重复启动或下载模型。
当前 VLLM 端点提供的是同步 OpenAI 音频转写接口,没有暴露原生
`create_stream/feed_stream/finish_stream`。因此本项目仍然是真实 WebSocket
音频流:麦克风 PCM 到达后立即进入 VAD按窗口调用 VLLM 生成 partial它不会
等整段音频结束。`native_partial_supported=false` 只表示模型 HTTP 接口本身
不是原生 ASR stream不伪造不存在的能力。
## 启动
需要启动两个模型服务和一个 WebSocket 页面VLLM 只负责 ASR辅助服务负责
VAD、CAM++ 声纹模型及在线聚类WebSocket 只做音频流编排,不导入原项目代码。
先在 `demo` 目录下载 ASR 和辅助模型,并启动 VLLM
```powershell
cd D:\github-project\ASR\Qwen-Asr\demo
python scripts\download_models.py
python scripts\serve.py
```
另一个终端启动辅助模型服务:
```powershell
cd D:\github-project\ASR\Qwen-Asr\demo
pip install -r requirements-auxiliary.txt
python scripts\auxiliary_server.py
```
另开一个终端启动 WebSocket 页面:
```powershell
cd D:\github-project\ASR\Qwen-Asr\demo\realtime_asr_optimization_demo
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
python server.py --no-browser
```
页面服务默认监听 `0.0.0.0:8082`,端口在 `server.py` 顶部的 `WEB_PORT` 内部变量中维护。
VLLM 默认地址为 `http://127.0.0.1:9950/v1`,辅助服务默认地址为
`http://127.0.0.1:8010`。可通过环境变量切换到远程服务:
```powershell
$env:MODEL_SERVICE_URL = 'http://127.0.0.1:9950/v1'
$env:AUXILIARY_SERVICE_URL = 'http://127.0.0.1:8010'
python server.py --no-browser
```
如果服务器端口已通过 VS Code Remote/端口转发映射到本机,保持上述两个
`127.0.0.1` 地址即可:本地 WebSocket 只负责编排ASR 和 VAD/CAM++ 推理仍在
服务器 GPU 服务中完成。先访问 `http://127.0.0.1:9950/v1/models`
`http://127.0.0.1:8010/health`,分别确认 VLLM 模型和辅助模型服务可达且 `ready=true`
服务器部署时使用 `--no-browser`,然后在客户端浏览器访问 `http://服务器IP:8082`。如需让启动日志显示服务器域名或 IP可设置 `WEB_DISPLAY_HOST`;它只影响提示文本,不改变监听地址。
辅助服务启动后可用 `http://服务器IP:8010/health` 检查模型状态。WebSocket
收到聚类服务错误时仍会继续输出 ASR但对应片段会显示“未知说话人”详细的
`speaker_reason` 可将鼠标悬停在标签上查看,事件日志仍会显示 `speaker_warning`
便于区分“模型未归类”和“ASR 失败”。
声纹服务的实时路径必须通过 ModelScope pipeline 的公开接口提取 embedding
`pipeline([wav_path], output_emb=True)`。不能绕过 pipeline 预处理后直接调用
`pipeline.model`,否则采样率、声道和 waveform 预处理不会执行,部分 ModelScope
版本会直接抛异常WebSocket 仍会继续输出 ASR 并把说话人保留为 pending。
更新辅助服务代码后需要重启 `python scripts/auxiliary_server.py`,仅重启页面
服务不会替换已经驻留在 GPU 中的旧辅助服务进程。
## 页面操作
1. 选择 Mic 或 File。
2. 点击开始,浏览器通过 `/ws` 建立本项目 WebSocket。
3. 页面展示腾讯 Demo 风格的气泡;麦克风或 PCM/WAV 文件按流式方式输入partial 会在讲话过程中实时刷新。
4. VAD 检测到静音、或活跃 turn 内声纹窗口比对检出换人时提交当前 turn先返回 pending再异步更新说话人。
5. 停止会发送 `stop`,服务端完成当前 turn 和 speaker 队列后再发送 `end`
6. `abort` 只取消会话,不提交当前片段。
## WebSocket 消息
客户端首条消息:
```json
{
"type": "start",
"source": "mic",
"model_service_url": "http://127.0.0.1:9950/v1",
"model": "Qwen/Qwen3-ASR-0.6B",
"speaker_diarization": 1,
"sentence_strategy": 0,
"enable_native_partial_stream": true,
"partial_interval_ms": 1200,
"max_segment_sec": 12,
"display_merge": true,
"speaker_window_ms": 1000,
"speaker_switch_similarity": 0.5
}
```
`speaker_window_ms``speaker_switch_similarity` 可省略,默认读取环境变量
`SPEAKER_TURN_WINDOW_MS`1000`SPEAKER_SWITCH_SIMILARITY`0.5)。
随后持续发送 16kHz、单声道、PCM16 二进制音频;文件模式仅支持 PCM/WAV结束发送 `{"type":"eof"}`,停止发送
`{"type":"stop"}`,取消发送 `{"type":"abort"}`。每个已完成 turn 会向辅助服务
发送一次 `/v1/speaker/resolve`,只包含当前 turn 音频和 session_id不会重复上传整段会话。
换人强制切段产生的边界段会附带 `speaker_verified=0`:辅助服务仍返回匹配标签,
但不更新簇质心、不建立新簇。活跃 turn 内另有窗口比对请求 `/v1/speaker/embedding`
multipart 音频,返回 `{"ok": true, "embedding": [...]}`),只出向量、零聚类副作用。
辅助服务 `speaker_protocol_version` 为 3旧版辅助服务缺少窗端点时 WebSocket 侧
探测一次即自动停用切换感知切段并发送 `speaker_warning`,不影响 ASR。
服务端会发送 `start`、`sentences`、`display_state`、`metrics`、`speaker_warning`、`draining`、`end` 和 `error`
页面用带 `revision``display_state` 渲染,以 `block_id` 标识展示块;`sentences` 保留原始片段及诊断状态。
停止后必须等待 `end`,其中包含完整 `sentences``display_blocks`,不能提前关闭连接。
`sentences``sentence_type=0` 是 partial`sentence_type=1` 是 final同一个
`sentence_id` 必须覆盖更新而不是追加。`sentence_strategy=0` 使用约 800ms
静音切句,`sentence_strategy=1` 使用约 1400ms 静音切段,更适合段落模式。
静音阈值之外还有说话人切换感知切段:活跃 turn 内每积累约 1 秒有声窗口就与
辅助服务比对一次声纹,与当前 turn 参考向量余弦低于阈值时在“停顿≥200ms 后的
首个起音”处强制切开,头段 final 的 `commit_reason``speaker_change`。这覆盖
上一位尾句与下一位间隔不足静音阈值(常见 200~600ms 换话停顿)被合成一句的问题。
## 目录
- `server.py`:本地 HTTP 页面和 WebSocket 会话编排。
- `model_service.py`:独立 VLLM OpenAI 音频接口适配层。
- `auxiliary_service.py`:独立辅助模型 HTTP 接口适配层。
- `speaker_assembler.py`raw segment、speaker evidence 和 display block 状态机。
- `static/`:麦克风/文件测试页面。
- `tests/`:只测试本项目状态合并和音频转换逻辑。