CoolFace
Modelpublic

JazerJu/qwen3-asr-ctc-aiinfra

sourceHugging Faceapache-2.0updated 8d agoView on Hugging Face
0likes26downloads
Model Card

Qwen3-ASR-CTC-aiinfra

一个 85.4M 参数的 Self-conditioned CTC 头,接在冻结的 Qwen3-ASR-1.7B 音频 编码器后面,做首遍转写和强制对齐。编码器不含在本仓库,运行时从 `Qwen/Qwen3-ASR-1.7B` 加载。

相对 `JazerJu/qwen3-asr-ctc`(v1, 48.3M,非 self-conditioned),本模型多做了两件事:

  1. 1.换成 Self-conditioned CTC(arXiv 2104.02724)+ Intermediate CTC 辅助损失;
  2. 2.在此基础上续训了 372 小时 AI 基础设施领域的中英混读语料,专门补 cann / Ascend C / 昇腾 / SGLang / HBM 这类术语。

领域续训没有以通用能力为代价。 FLEURS 11 个语种没有一个退步,15 个标准 测试集也全部持平或更好 —— 数字在下面。

和 v1 的架构差异:self_cond 必须跟着权重走

conditioning_layer(72468 → 512)参与推理前向,不是训练期的辅助结构: 它把第 2、4 个 block 之后的中间预测 softmax 后投影回主干、加到下一层输入上。

python
for i, block in enumerate(self.blocks):
    x = block(x)
    if self.self_cond and i in self.inter_layers:      # (1, 3)
        inter = self.ctc_lo(self.layer_norm(x))
        x = x + self.conditioning_layer(F.softmax(inter.float(), -1).to(x.dtype))
return self.ctc_lo(self.layer_norm(x))

照 v1 的结构建模型会静默出错:权重的键全都在,load_state_dict 少两个 unexpected key 就能过(把 strict 关掉的话),但少了这条通路,输出是错的。 config.json 里的 self_cond: true 和 inter_layers: [1, 3] 必须被读到。 本仓库的 modeling_ctc.py 已经按配置建图,直接用即可。

int4 量化后是 44.0 MB,不是 v1 的 25.1 MB —— 多出来的就是 conditioning_layer。

领域术语

自留的 955 段测试数据,47 个术语共出现 1,158 次(贪心解码,统计的是术语被 正确写出的次数):

基座**本模型**
47 个术语合计403(35%)940(81%)

单个术语(次数 ≥ 14):

术语次数基座**本模型**
cann1050%80%
昇腾730%84%
openai10819%84%
sglang240%67%
hbm147%100%
ascend210%95%
ascend c180%72%
zero5186%33%

zero 是唯一退步的:语料里 ZeRO(DeepSpeed 的显存优化)和数字 0 同音, 续训后模型更倾向写成 ZeRO。这是真实的退步,没有修。

通用能力:11 个语种一个不退

FLEURS 全量 7,876 句,基座与本模型同一次运行、同一套解码:

语种指标n基座**本模型**差
en_usWER64717.8916.98−0.92
cmnhanscnCER94510.7910.24−0.55
ko_krCER38218.9015.85−3.06
ja_jpCER65019.2218.25−0.98
yuehanthkCER81926.1225.54−0.58
de_deWER86238.5136.74−1.76
fr_frWER67643.4542.15−1.30
es_419WER90831.4530.56−0.89
it_itWER86546.2444.39−1.85
nl_nlWER36448.1346.82−1.30
pl_plWER75874.8873.03−1.85
微平均30.3929.14−1.24
宏平均34.1432.78−1.37

韩语那 −3.06 不是领域数据的功劳,是修标签修出来的:紧凑词表里有一类 「空格 + 半截 UTF-8 字节」的 token,训练时把它拆成独立空格 + 重新分词, 韩语含 U+FFFD 的句子从 280 句降到 38 句。

标准测试集(同环境):

语料指标基座**本模型**
ASCEND test(中英混说)MER13.9112.43
ReazonSpeech ja testCER21.3119.28
LibriSpeech test-cleanWER5.915.55
AISHELL-1 devCER4.123.91
jalocal1kCER12.8812.06

长音频:可以直接按 30 秒切

同一段连续音频按固定窗口切开送进去的 CER:

切段基座**本模型**
30 秒28.1%18.4%
20 秒28.7%17.5%
15 秒27.6%17.8%
8 秒27.3%20.6%

中文语料几乎没有长句(AISHELL-1 超过 12 秒的占 0.01%、WenetSpeech 0.08%, 而 LibriSpeech 有 72.3%),只喂短段训出来的模型在 30 秒输入上会崩。 解决办法是把相邻字幕按 ≤0.6 秒的间隔合并成 8–20 秒的长段再训一遍 —— 不拼接音频,只把时间窗口拉长,中间的停顿原样保留。

训练

编码器Qwen3-ASR-1.7B audio tower,全程冻结
CTC 头2048→2048→512,5 层 Transformer block(8 头,FFN 128),72,468 类,self-conditioned
参数量85,448,596
起点多语种 self-cond 基座(step 260,503)
续训数据AI 基础设施领域中英混读 372 小时 / 545 个视频(硬字幕提取)+ 合并长段 + 英文 + TTS 合成,叠加原有 26 个 manifest 的基座语料
硬件8× 昇腾 910B3
配方batch 32/卡(合计 256)、grad_accum 1、2 个 epoch、余弦 LR 自 8e-5 续、InterCTC 0.3、SpecAugment
步数 / 时长339,707 步(本轮增量 10 小时 30 分)
最终 val loss通用 0.5321 / 领域 1.0815

vocab_compact.json 是从 Qwen3 原生的 151,705 词表压出来的紧凑词表(72,468 类, 含 blank 与 unk),与 v1 同一份。

用法

bash
pip install torch transformers qwen-asr safetensors soundfile
python
from modeling_ctc import Qwen3CtcAsr

asr = Qwen3CtcAsr(".", device="cuda")        # 会自动拉 Qwen/Qwen3-ASR-1.7B
print(asr.transcribe([waveform_16k_float32]))

离线环境把本地编码器目录给 QWEN3_ASR_ENCODER。完整示例(含 CTC 强制对齐出 字级时间戳)见 example.py:

bash
python example.py audio.wav

三个会让你静默拿到错误结果的坑

前两个和 v1 一样,第三个是本模型新增的:

  1. 1.编码器输入是时间维拼接的 2D 张量 [128, ΣT_mel] 加 feature_lens, 不是 [B, 128, T]。传 3D 会报 split_with_sizes 的错。
  1. 1.帧率是 13 fps(76.9 ms/帧),不是 Whisper/GLM 的 50 fps。 编码器用 3 层 stride-2 conv2d 做 8 倍降采样,但官方长度公式是「每 100 个 mel 帧出 13 帧」 (qwen3_output_lengths())。沿用 50 fps 的算法会把可用帧数高估 4 倍 —— CTC 以为容量充足,实际 log_probs 只有 1/4 长,loss 看着能降但对齐全是错的。
  1. 1.必须打 attention mask 补丁。 qwen-asr 0.0.6 里 _prepare_attention_mask 定义了却从来没被调用,cu_seq_lens_q/k 只有 flashattention2 后端认。 不打补丁时同一条音频单条推理 vs 批推理的余弦相似度只有 0.81–0.88,打完 0.9998+。CUDA 上同样中招,不是昇腾特有的问题。patch_qwen3_attention_mask() 是幂等的,Qwen3CtcAsr 会自动调用。
  1. 1.`self_cond` 必须建进图里,见上文。

已知局限

  • —`zero`(ZeRO)退步,基座 86% → 本模型 33%,见上表。
  • —首遍写对的 `cann`,LLM 二遍会改回「看」。 在「CTC 首遍 + LLM 二遍」的 流水线里实测:首遍 105 次里写对 84 次(89%),二遍只剩约 21%。同一条链路上 ascend 95%、昇腾 84%、sglang 67% 都保得住,只有 cann 掉,因为它和高频字 「看」完全同音。要在最终文本里保住,得把热词匹配算出的改写直接应用到二遍 文本上,而不是只塞进 prompt。
  • —贪心解码偶尔会吐出半个汉字(U+FFFD)。155 段自留测试集里有 1 段。加上 UTF-8 合法性约束的束搜索可以清零,同时 CER 从 0.1449 降到 0.1426。
  • —训练语料里 TALCS / MAGICDATA / CS-Dialogue 的官方 test split 混进了训练集, 这三个 test 上的数字不能当泛化指标。上面列的测试集都实测确认干净。
  • —中文时间戳没有真值可校,沿用 v1 的结论:CTC 尖峰式发射导致词起始系统性 偏晚约 100 ms、结束偏早约 80 ms,是常数,减掉即可。

ONNX

导出好的 ONNX(fp16 / q4 / q4f16)在 `JazerJu/glm-asr-ctc-bench` 的 qwen-ctc-aiinfra4/ 下,配套编码器在同仓库 qwen-ctc/。 导出与量化脚本:<https://github.com/JazerJu/Qwen3-ASR-CTC-GGUF>

许可

CTC 头权重按基础模型 Qwen3-ASR-1.7B 的 Apache-2.0 发布。训练语料各自的许可 归各自所有者,本仓库不含任何语料数据。