File size: 8,373 Bytes
1022d04 d2f59ca 1022d04 d2f59ca cabd803 d2f59ca 148d2be d2f59ca 148d2be d2f59ca 148d2be a9e00af d2f59ca a9e00af d2f59ca cabd803 a9e00af d2f59ca 148d2be d2f59ca 148d2be d2f59ca 148d2be d2f59ca 148d2be d2f59ca 148d2be d2f59ca 148d2be d2f59ca 148d2be d2f59ca cabd803 a9e00af d2f59ca | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | ---
license: apache-2.0
library_name: axera
pipeline_tag: audio-classification
tags:
- speaker-diarization
- ls-eend
- fs-eend
- end-to-end-neural-diarization
- rttm
- speech
- axera
- ax650n
---
# FS-EEND.AXERA
LS-EEND(FS-EEND 的在线版本)说话人日志模型 Axera AX650N 推理 demo:
输入音频,输出 RTTM 说话人时序标签。
- [x] Python 推理(axengine,`python/example.py`)
- [x] C++ 可执行文件(`bin/ls_eend_ax650`)
- [x] 一键运行脚本(`run_ax650.sh`)
源码(模型转换 + C++ 源码)见 GitHub:
[FS-EEND.AXERA](https://github.com/ZY-2012/FS-EEND.AXERA)
上游模型:[Audio-WestlakeU/FS-EEND](https://github.com/Audio-WestlakeU/FS-EEND)
## 支持模型
上游为每个数据集单独训练了模型,`max_speakers` 不同导致输出通道数不同。本仓提供
两个量化变体:
| 变体 | 来源 checkpoint | 适用场景 | max_speakers | 输出通道 | axmodel |
|---|---|---|---|---|---|
| `models/simu/` | `ls_eend_1-8spk_16_25_avg_model` | 通用 / 电话带宽 | 8 | 10 | 12.5 MB |
| `models/ami/` | `ls_eend_ami_allspk_model` | 会议室(AMI 域) | 4 | 6 | 12.5 MB |
接口:`feat [1,1,345]` + 10 路状态 → `pred [1,1,C]` + 11 路状态,U16 激活 / S8 权重。
8 kHz 输入,每帧 0.1 s;通道布局为 ch0 静音、ch1..C-2 说话人、ch(C-1) 非说话人。
两个 SDK 都在加载时从模型读取通道数,切换变体不用改代码。
CALLHOME(7→9 通道)与 DIHARD2/3(10→12 通道)未提供量化产物,可用 GitHub 仓的
`model_convert/` 自行量化(`LS_EEND_CONF` 切换配置)。
## 精度与速度
测试录音 `samples/mix_0000176.wav`(192.02 s / 1921 帧 / 4 人),
参考 `samples/ground_truth_4spk_mix176.rttm`,DER 用 `collar=0.25`。
| 路径 | vs 原生 cosine | DER(±0.25 s,上游口径) | DER(±0.125 s) | ms/帧 | RTF |
|---|---|---|---|---|---|
| 原生 PyTorch 逐帧 | — | **0.4411%** | 1.2156% | — | — |
| FP32 ONNX 流式循环 | **1.0000000** | 0.8672% | 1.6489% | — | — |
| **AX650N Python** | **0.9985337** | **1.1214%** | 1.9512% | 7.29 | 0.073 |
| **AX650N C++** | 0.9985337 | **1.1214%** | 1.9512% | **2.79** | **0.0279** |
上游 `metrics.py` 用 `DiarizationErrorRate(collar=50)`,其 `Segment` 单位是 10 ms 帧
(注释写明 "250ms tolerance"),等效 pyannote 的 `collar=0.5`(±0.25 s,NIST 惯例)。
两条路径 confusion 均为 0%,说话人指派完全正确,DER 完全相同。C++ 比 Python 快约 2.6 倍
(主机端 FP32 状态累加在 numpy 里开销较大);55 段里仅 1 段边界差一帧,来自前端
2.3e-05 的浮点残差翻转了一个接近阈值的帧,在 collar 内。
真实会议数据(AMI Eval,用上游 AMI finetune checkpoint 另行量化):4 场加权
原生 26.50% → 板端 30.18%,板端 2.2 ms/帧。
## 目录结构
```
FS-EEND.AXERA/
├── models/
│ ├── simu/ # 仿真数据模型(10 通道,默认)
│ │ ├── streaming_step.axmodel
│ │ └── model_meta.json
│ └── ami/ # AMI finetune 模型(6 通道)
│ ├── streaming_step.axmodel
│ └── model_meta.json
├── python/
│ ├── example.py # 推理入口
│ └── ls_eend_sdk/ # 前端 / 流式循环 / 后处理
├── bin/ls_eend_ax650 # 预编译 C++ 可执行文件(aarch64)
├── samples/ # 演示音频 + 参考 RTTM
├── run_ax650.sh # 一键运行
└── requirements.txt
```
## 运行(AX650N 板端)
```bash
# C++(默认,最快),仿真模型
bash run_ax650.sh
bash run_ax650.sh cpp your.wav
# Python
bash run_ax650.sh python your.wav
# 会议室场景用 AMI finetune 模型(第三个参数选变体)
bash run_ax650.sh cpp your_meeting.wav ami
```
手动调用:
```bash
export LD_LIBRARY_PATH=/soc/lib:$LD_LIBRARY_PATH
./bin/ls_eend_ax650 --model models/simu/streaming_step.axmodel \
--wav samples/mix_0000176.wav \
--rttm out.rttm --max-speakers 4
python3 python/example.py --model models/ami/streaming_step.axmodel \
--wav your_meeting.wav \
--rttm out.rttm --max-speakers 4 --median 1
```
参数:`--max-speakers`(保留 8 个说话人通道中的前 N 个)、`--threshold`(默认 0.5)、
`--median`(中值滤波帧数,默认 11,与上游一致)。
C++ 源码与编译方式见 GitHub 仓库的 `cpp/`。
## 作为库调用
```python
import sys; sys.path.insert(0, 'python')
from ls_eend_sdk import diarize
result = diarize('meeting.wav', 'models/ami/streaming_step.axmodel', max_speakers=4)
print(result['rttm'], result['speakers'], result['rtf'])
for start, end, spk in result['segments']:
print(f'{start:.2f}-{end:.2f} speaker_{spk}')
```
逐帧流式(真实实时场景):
```python
from ls_eend_sdk import StreamingDiarizer, extract_features, load_audio
runner = StreamingDiarizer('models/simu/streaming_step.axmodel')
audio, _ = load_audio('meeting.wav')
for frame in extract_features(audio):
logits = runner.step(frame) # 前 9 帧返回 None(卷积 warmup)
if logits is not None:
... # (10,) logits
```
## 模型接口
单帧图,14 输入 / 12 输出,全 FP32 边界(量化只在图内部)。
**输入**
| 名称 | shape | 说明 |
|---|---|---|
| `feat` | `1×1×345` | 8 kHz `logmel23_cummn`,23 mel × ±7 帧拼接,10 倍下采样 |
| `inv_count` | `1×4×1×1` | `1/t`,encoder retention 均值混合系数,由调用方给 |
| `dec_inv_count` | `1×4×1×1` | `1/t_dec`,decoder 同上 |
| `conv_cache` | `1×256×18` | 输出端因果 CNN 的 cache(kernel 19) |
| `enc{0..3}_kv` | `1×4×64×64` | encoder 各层 retention 运行均值 |
| `enc{0..3}_conv` | `1×256×15` | encoder Conformer 卷积 cache |
| `dec{0..1}_kv` | `10×4×64×64` | decoder 各层 retention 运行均值 |
**输出**
| 名称 | shape | 说明 |
|---|---|---|
| `pred` | `1×1×10` | 10 通道 logit |
| `enc{0..3}_inc` | `1×4×64×64` | 本帧增量 `k_t·v_t`(**不是**更新后的状态) |
| `enc{0..3}_conv_out` | `1×256×15` | 卷积 cache 更新 |
| `conv_cache_out` | `1×256×18` | 输出卷积 cache 更新 |
| `dec{0..1}_inc` | `10×4×64×64` | decoder 本帧增量 |
**调用方必须实现两件事**(`python/ls_eend_sdk/session.py` 已实现,自行改写不能省):
1. **FP32 主机端累加**——图只返回有界的单帧增量,主机侧用 FP32 维护
`mean += (inc - mean)/t`。把量化后的状态直接喂回会让误差沿递推累积
(DER 1.95% → 7.4%)。
2. **Warmup 门控**——上游 `StreamingConv1d` 前 9 帧不输出,原生循环此时完全不调用
decoder。前 9 帧要喂真实特征让 encoder 状态前进,但丢弃 `pred` 且保持 decoder
状态不变(漏掉这步 DER 会到 71.8%)。
后处理:`sigmoid` → 阈值 0.5 → 11 帧中值滤波 → 段合并 → RTTM。
## 已知限制
- **尾部 0.9 s 不输出。** 原生 flush 把零 *embedding* 直接推进输出卷积、绕过 encoder,
融合的单帧图表达不了。
- **前端有状态。** `logmel23_cummn` 用累积均值归一化,跨调用不是无状态的。
- **模型是 8 kHz 的**(上游配置 `sample_rate: 8000`,`feature.py` 里 `sr = 8000` 硬编码)。
其他采样率的音频会被自动带限重采样到 8 kHz(Python 用 librosa `kaiser_best`,C++ 用
同参数的 Kaiser 窗 sinc)。上游 AMI / DIHARD2 / DIHARD3 也是先降到 8 kHz 再训练和评测的。
- `max_speakers` 上限 8。
- **量化退化随录音长度增长**,几乎全落在 confusion(说话人指派):14~17 min 录音上
Δconfusion 仅 +0.5~0.8 pp,36 min 会议上可达 +1.4~5.9 pp。原因是 `inv_count = 1/t`
在 2 万帧时降到 5e-5、跌出校准范围。长会议场景请用同等长度录音重做校准
(见 GitHub 仓 `model_convert/`)。
- 量化时把 retention 状态改成了有界的运行均值形式,前提是本 checkpoint
`decay == 1`。换成 per-head decay < 1 的权重需要重新导出。
## License
Apache-2.0。上游 FS-EEND 与 LS-EEND 权重的许可以其原仓库为准。
|