FS-EEND.AXERA

LS-EEND(FS-EEND 的在线版本)说话人日志模型 Axera AX650N 推理 demo: 输入音频,输出 RTTM 说话人时序标签。

  • Python 推理(axengine,python/example.py
  • C++ 可执行文件(bin/ls_eend_ax650
  • 一键运行脚本(run_ax650.sh

源码(模型转换 + C++ 源码)见 GitHub: FS-EEND.AXERA

上游模型: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.pyDiarizationErrorRate(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 板端)

# 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

手动调用:

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/

作为库调用

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}')

逐帧流式(真实实时场景):

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: 8000feature.pysr = 8000 硬编码)。 其他采样率的音频会被自动带限重采样到 8 kHz(Python 用 librosa kaiser_best,C++ 用 同参数的 Kaiser 窗 sinc)。上游 AMI / DIHARD2 / DIHARD3 也是先降到 8 kHz 再训练和评测的。
  • max_speakers 上限 8。
  • 量化退化随录音长度增长,几乎全落在 confusion(说话人指派):1417 min 录音上 Δconfusion 仅 +0.50.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 权重的许可以其原仓库为准。

Downloads last month
68
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support