Skip to content

Commit 6d2ba65

Browse files
ZhaoChaoqunclaude
andcommitted
docs: update benchmark results and add investigation reports
- Benchmark test: align flush strategy with product code - benchmark-report-swift.md: latest results (Qwen3 streaming CER 0.0519) - Add streaming params guide appendix (mel spectrogram) - Add investigation reports: UTF-8 mojibake bug, memory analysis, streaming decoder degeneracy, long-audio optimization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c2c32c9 commit 6d2ba65

8 files changed

Lines changed: 1486 additions & 121 deletions

TypelessTests/ASRPipelineBenchmarkTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,11 @@ class ASRPipelineBenchmarkTests: XCTestCase {
191191
_ = recognizer.pushAudio(samples: chunk, finalize: false)
192192
offset = end
193193
}
194-
// 直接取 stable + unfixed 作为最终结果,与产品代码 QwenASREngine.flush() 一致
195-
// 避免推送 silence+finalize 导致 decoder 在静音上 hallucinate 重复文本
196-
let stableText = recognizer.getResult()
197-
let unfixedText = recognizer.getUnfixed() ?? ""
198-
return stableText + unfixedText
194+
// 极少量 silence(0.1s)+ finalize:commit rollback tokens + 处理尾部音频
195+
// 不用 1s silence——长音频上 decoder hallucinate 重复文本
196+
let minimalSilence = [Float](repeating: 0.0, count: 1600)
197+
_ = recognizer.pushAudio(samples: minimalSilence, finalize: true)
198+
return recognizer.getResult()
199199
}
200200

201201
Self.allResults.append(PipelineResult(pipelineName: "Qwen3-ASR (流式)", results: results))

docs/benchmark-report-swift.md

Lines changed: 79 additions & 96 deletions
Large diffs are not rendered by default.

docs/benchmark-report.md

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# ASR Pipeline 量化对比评估报告
22

3-
*生成时间:2026-03-02 11:33*
4-
*测试集:1 条音频(corpus.json + real_manifest.json)*
5-
*Pipeline:Paraformer Pipeline*
3+
*生成时间:2026-03-03 13:29*
4+
*测试集:3 条音频(corpus.json + real_manifest.json)*
5+
*Pipeline:Qwen3-ASR (流式)*
66

77
**CER 计算方式**:保留标点符号,仅做 lower + 去空格后计算字符错误率。
88

@@ -12,51 +12,55 @@
1212

1313
| Pipeline | 平均 CER | CER=0 条数 | CER≤0.10 | CER≤0.20 | CER>0.20 | 总推理时长 | RTF |
1414
|----------|:-------:|:---------:|:-------:|:-------:|:-------:|:---------:|:---:|
15-
| Paraformer Pipeline | 0.2647 | 0/1 | 0 | 0 | 1 | 0.2s | 0.036x |
15+
| Qwen3-ASR (流式) | 0.4978 | 1/3 | 1 | 1 | 2 | 520.8s | 4.101x |
1616

1717
## 2. 按数据集/类别的平均 CER
1818

19-
| 类别 | N | Paraformer Pip | 最佳 |
19+
| 类别 | N | Qwen3-ASR | 最佳 |
2020
|------|:-:|:------:|------|
21-
| real_ascend_codeswitching | 1 | **0.265** | Paraformer Pip |
22-
| **OVERALL** | 1 | **0.2647** | **Paraformer Pip** |
21+
| chinese_long | 1 | **0.000** | Qwen3-ASR |
22+
| long_audio | 2 | **0.747** | Qwen3-ASR |
23+
| **OVERALL** | 3 | **0.4978** | **Qwen3-ASR** |
2324

2425
## 3. 逐条 CER 对比
2526

26-
| ID | 类别 | Paraformer | 最佳 |
27+
| ID | 类别 | Qwen3-ASR (流 | 最佳 |
2728
|-----|------|:-----:|------|
28-
| ascend_cs_003 | real_ascend_codeswitching | **0.265** | Paraformer |
29+
| zh_long_01 | chinese_long | **0** | Qwen3-ASR |
30+
| long_30s_01 | long_audio | **0.675** | Qwen3-ASR |
31+
| long_60s_01 | long_audio | **0.818** | Qwen3-ASR |
2932

3033
## 4. 推理速度对比
3134

3235
| Pipeline | 总音频 | 总推理 | RTF | 平均/条 |
3336
|----------|:-----:|:-----:|:---:|:------:|
34-
| Paraformer Pipeline | 5s | 0.2s | 0.036x | 0.16s |
37+
| Qwen3-ASR (流式) | 127s | 520.8s | 4.101x | 173.60s |
3538

3639
## 5. 各 Pipeline 识别错误案例详细分析
3740

38-
### 5.1 Paraformer Pipeline(1 条不准确)
41+
### 5.1 Qwen3-ASR (流式)(2 条不准确)
3942

4043
| # | ID | CER | 期望文本 | 识别结果 | 错误类型 |
4144
|:-:|-----|:---:|---------|---------|---------|
42-
| 1 | ascend_cs_003 | 0.265 | 深圳啊,或者是上海这种比较大的城市,会有更多opportunity。 | 深圳啊,或者是上海这种表达城市会有更opportun。 | 英文词丢失: opportunity |
43-
深圳啊,或者上海这种比较大的城市,会有更多的 opportunity。深圳啊,或者上海这种比较大的城市,会有更多的 opportunity。深圳或者上海这种比较大的城市会有更多的opportunity。深圳或者上海这种比较大的城市会有更多的opportunity。
44-
深圳或者上海这种深圳或者上海这种比较大的城市会有更多的opportunity。
45+
| 1 | long_60s_01 | 0.818 | 软件工程是一门研究用工程化方法构建和维护有效的实用的和高质量的软件的学科。它涉及… | 软件工程是一门研究用工程化方法构建和维护有效的、实用的和高质量的软件的学科。它涉… | 英文词丢失: git,docker,devops, 数字/量词, 截断 |
46+
| 2 | long_30s_01 | 0.675 | 人工智能技术在过去10年中取得了巨大的进步。深度学习算法使得计算机能够处理和理解… | 人工智能技术在过去十年中取得了巨大的进步。深度学习算法使得计算机能够处理和理解自… | 数字/量词, 截断 |
47+
4548
## 6. 综合分析与建议
4649

4750
### 各场景最佳 Pipeline 推荐
4851

4952
| 场景 | 推荐 Pipeline | CER |
5053
|------|--------------|:---:|
51-
| real_ascend_codeswitching | Paraformer Pipeline | 0.265 |
54+
| chinese_long | Qwen3-ASR (流式) | 0.000 |
55+
| long_audio | Qwen3-ASR (流式) | 0.747 |
5256

5357
### Pipeline 特点总结
5458

55-
**Paraformer Pipeline**
56-
- 平均 CER: 0.2647
57-
- 完美识别 (CER=0): 0/1
58-
- 不准确 (CER>5%): 1/1
59-
- RTF: 0.036x
59+
**Qwen3-ASR (流式)**
60+
- 平均 CER: 0.4978
61+
- 完美识别 (CER=0): 1/3
62+
- 不准确 (CER>5%): 2/3
63+
- RTF: 4.101x
6064

6165
---
6266

docs/guides/streaming-params-guide.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,3 +624,181 @@ macOS 麦克风默认采集 48kHz,Typeless 在 `RecordingManager.swift` 中将
624624
语音识别只需要单声道——人说话的内容不分左右。
625625
立体声输入时,通常取两路平均: S = (L + R) / 2
626626
```
627+
628+
---
629+
630+
## 附录:Mel Spectrogram(梅尔频谱图)
631+
632+
Qwen3-ASR 的 encoder 输入不是原始 PCM,而是从 PCM 计算出的 **mel spectrogram**。这是所有现代 ASR 模型(Whisper、Qwen3-ASR、Paraformer、SenseVoice)共用的音频前端表示。
633+
634+
### 为什么不直接用 PCM?
635+
636+
PCM 是时域信号——每个采样点表示某一瞬间的气压值。但人耳和大脑处理声音靠的是**频率**,不是瞬时气压。
637+
638+
```
639+
PCM 波形(时域):
640+
+1 ┤ ╱╲ ╱╲
641+
│ ╱ ╲ ╱ ╲ ← 看不出"这是什么声音"
642+
0 ├───╱────╲──╱────╲───
643+
│ ╲╱
644+
-1 ┤
645+
646+
Mel Spectrogram(频域+时域):
647+
│ ██ ← 低频能量强(元音共振峰)
648+
频率 │ ░░██ ← 中频
649+
│ ░░░░░░██ ← 高频能量弱
650+
└────────────→ 时间 ← 每列就是一帧,能看出"哪个频率在哪个时刻响"
651+
```
652+
653+
语音的本质信息(元音/辅音区分、声调、语义)藏在**频率分布随时间的变化**中,而不是原始波形的形状中。
654+
655+
### 三步计算过程
656+
657+
Mel spectrogram 的计算分三步,对应 `audio.rs:273``mel_spectrogram()` 函数:
658+
659+
```
660+
原始 PCM STFT Mel 滤波 + 取对数
661+
(时域) ──→ (频域) ──→ (Mel 频域)
662+
分帧 + 加窗 + FFT 三角滤波器组 + log
663+
664+
[16000 个采样/秒] [201 频率 × N 帧] [128 mel bins × N 帧]
665+
```
666+
667+
#### Step 1: 分帧 + 加窗
668+
669+
把连续的 PCM 信号切成一段段重叠的"帧":
670+
671+
```
672+
PCM 信号: ════════════════════════════════════════════════════
673+
|← 帧0 (400点) →|
674+
|← 帧1 (400点) →|
675+
|← 帧2 (400点) →|
676+
|← 帧3 →|
677+
|←160→|←160→|←160→|
678+
hop hop hop
679+
680+
窗口大小 (WINDOW_SIZE) = 400 采样 = 25ms
681+
步进大小 (HOP_LENGTH) = 160 采样 = 10ms
682+
重叠长度 = 400 - 160 = 240 采样 = 15ms (60% 重叠)
683+
```
684+
685+
每帧乘以 **Hann 窗函数**(两端平滑衰减为零),避免帧边界处的频谱泄漏:
686+
687+
```
688+
Hann 窗:
689+
1.0 ┤ ╱──╲
690+
│ ╱ ╲
691+
0.5 ┤ ╱ ╲
692+
│ ╱ ╲
693+
0.0 ├──╱──────────╲──
694+
0 400
695+
```
696+
697+
#### Step 2: FFT → 功率谱
698+
699+
对每帧加窗后的 400 个采样做**离散傅里叶变换(DFT/FFT)**,得到 201 个频率分量(`N_FFT/2 + 1 = 201`)的功率:
700+
701+
```
702+
一帧 PCM (400 点):
703+
[0.1, -0.3, 0.5, 0.7, ...] ← 时域:看不出频率信息
704+
705+
↓ FFT
706+
707+
功率谱 (201 点):
708+
频率: 0Hz 80Hz 160Hz ... 4kHz ... 8kHz
709+
功率: 0.01 0.15 0.82 ... 0.03 ... 0.001
710+
711+
这个频率最强 → 说明当前帧有 160Hz 的声音成分
712+
```
713+
714+
每个频率 bin 的间隔 = 采样率 / N_FFT = 16000 / 400 = **40 Hz**
715+
716+
#### Step 3: Mel 滤波 + 取对数
717+
718+
将 201 个线性频率 bin 通过 **128 个三角形 Mel 滤波器**映射为 128 个 Mel bin:
719+
720+
```
721+
线性频率轴 (Hz):
722+
0 500 1k 2k 3k 4k 5k 6k 7k 8k
723+
|───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───|
724+
725+
Mel 滤波器组(128 个三角滤波器):
726+
低频段: ╱╲ ╱╲ ╱╲ ╱╲ ╱╲ ╱╲ ← 窄,频率分辨率高
727+
高频段: ╱──╲ ╱────╲ ╱──────╲ ← 宽,频率分辨率低
728+
```
729+
730+
**为什么用 Mel 尺度?** 人耳对低频的分辨能力远高于高频——你能轻松区分 200Hz 和 300Hz,但很难区分 7000Hz 和 7100Hz。Mel 尺度模拟了这种非线性感知:
731+
732+
```
733+
Mel 尺度 vs 线性频率:
734+
735+
Mel │ 3000 ┤ ╱
736+
│ │ ╱╱
737+
│ 2000 ┤ ╱╱╱
738+
│ │ ╱╱╱
739+
│ 1000 ┤ ╱╱╱╱
740+
│ │ ╱╱╱╱
741+
│ 0 ├╱──────┼──────┼──────┼
742+
0 2000 4000 8000 Hz
743+
744+
低频段:100Hz 的差异 ≈ 大量 Mel 值变化 → 分配更多滤波器
745+
高频段:100Hz 的差异 ≈ 很小 Mel 值变化 → 分配更少滤波器
746+
```
747+
748+
最后取 **log**(对数),因为人耳感知到的"响度"与能量的对数成正比(10 倍能量 ≈ 感知到的 2 倍响度):
749+
750+
```
751+
mel_value = log(max(mel_filtered_power, 1e-10))
752+
```
753+
754+
### Qwen3-ASR 的具体参数
755+
756+
| 参数 || 定义位置 | 含义 |
757+
|------|-----|---------|------|
758+
| `MEL_BINS` | 128 | `config.rs:4` | Mel 滤波器数量(输出维度) |
759+
| `N_FFT` | 400 | `audio.rs:6` | FFT 窗口大小(400 采样 = 25ms) |
760+
| `WINDOW_SIZE` | 400 | `config.rs:5` | Hann 窗大小 |
761+
| `HOP_LENGTH` | 160 | `config.rs:5` | 帧步进(160 采样 = 10ms) |
762+
763+
对应关系:**1 帧 = 10ms 音频**,每帧产生一个 128 维向量。
764+
765+
### 帧数计算
766+
767+
```
768+
n_frames = (n_samples + padding - N_FFT) / HOP_LENGTH + 1
769+
770+
例:2 秒音频 (32000 采样):
771+
n_frames ≈ 32000 / 160 = 200 帧
772+
773+
输出 shape: [128, 200] → 128 个 Mel bin × 200 帧
774+
= 25600 个 f32 值 ≈ 100 KB
775+
```
776+
777+
### 与 Encoder 的连接
778+
779+
```
780+
PCM 音频 (16kHz, f32)
781+
↓ mel_spectrogram()
782+
Mel Spectrogram [128 × n_frames] ← 每 10ms 一帧,128 维
783+
↓ Encoder (audio tower)
784+
↓ 3 层卷积: stride ≈ 4x 下采样
785+
↓ 18/24 层 Transformer
786+
Encoder Output [n_tokens × 896/1024] ← 每 ~40ms 一个 token
787+
788+
嵌入到 Decoder 的 input_embeds 中
789+
```
790+
791+
800 帧(8 秒音频)经过 encoder 后压缩为约 200 个 encoder token,然后拼接 prefix/suffix tokens 一起送入 LLM decoder 做自回归解码。
792+
793+
### 与 Whisper 的对比
794+
795+
Qwen3-ASR 的 mel 计算与 Whisper 几乎相同(因为 audio tower 源自 Whisper 架构),唯一区别是 mel bin 数:
796+
797+
| | Whisper | Qwen3-ASR |
798+
|--|---------|-----------|
799+
| Mel bins | 80 | **128** |
800+
| N_FFT | 400 | 400 |
801+
| HOP_LENGTH | 160 | 160 |
802+
| 采样率 | 16kHz | 16kHz |
803+
804+
Qwen3-ASR 用 128 维而非 80 维,保留了更多高频细节信息。

0 commit comments

Comments
 (0)