跳至主要内容
返回文章列表
约 12 分钟阅读

流式语音识别中的 Chunk 分块掩码:Conformer、Emformer、Zipformer 与 U2/U2++

从 Chunk-based Masking 出发,理解流式语音识别如何限制未来信息、复用历史缓存,并比较 Conformer、Emformer、U2/U2++ 和 Zipformer 的延迟与精度取舍。

博客目录 →

实时字幕、语音输入法、会议转写和车载语音交互都有一个共同要求:用户还没有说完,系统就要开始出字。若每次都等到整句话结束后再把完整音频送进模型,识别准确率可能不错,但交互延迟会明显变长。

面试里常见的两个问题——“实时字幕一两秒才出字,怎么压到几百毫秒?”和“把语音转写延迟降下来,精度不能明显掉”——背后都指向同一个设计:让模型只看当前 chunk 和受控的历史上下文,同时把未来信息屏蔽掉。 这就是流式语音识别中的 Chunk-based Masking,中文常称分块掩码或分块注意力。

本文先把分块掩码讲清楚,再把它放进 Conformer、Emformer、U2/U2++ 和 Zipformer 这些常见方案中比较,最后给出延迟评估和工程落地时真正需要调的参数。

一、流式 ASR 要解决的是什么

语音识别模型通常接收一串声学特征,例如经过梅尔滤波器组提取的帧序列。非流式模型可以等待整段语音到齐,再使用完整的双向上下文;流式模型则必须在音频不断到达时持续计算,并且尽早给出部分结果。

这会带来三个约束:

  • 不能依赖未来帧。 当前输出不能偷看尚未到达的音频。
  • 历史信息不能无限增长。 否则每个新 chunk 都要重新处理整段历史,计算和缓存都会越来越大。
  • 边界要稳定。 新的音频到来后,之前显示的文字最好不要频繁大幅修改。

原始的全序列自注意力会让每个位置和所有位置交互,长度为 (T) 时,注意力部分的计算量通常随 (T^2) 增长。Conformer 在语音识别上效果很好,但它的标准全上下文配置并不天然满足上面的流式约束。需要说明的是,Conformer 并不是不能做流式识别;工程上通常通过分块掩码、键值缓存和卷积状态缓存,把它改造成流式版本。

二、Chunk 分块掩码:只把注意力放在可用上下文里

1. 一个 chunk 是什么

设输入特征序列被切成多个 chunk,每个 chunk 包含 (C) 个特征帧。实际项目里常见的配置可能是 16 或 18 个特征帧,但这个数字必须结合前端下采样倍率理解:它有时表示下采样后的时间步,而不是原始音频中的 16 个 10 毫秒帧。

处理当前 chunk 时,模型通常允许三类位置参与注意力:

  1. 当前 chunk 内的所有位置,常见做法是块内双向可见;
  2. 左侧有限数量的历史 chunk,作为历史上下文;
  3. 可选的少量右侧帧,也就是 lookahead 或 right context。

更远的历史,以及尚未到达的未来 chunk,不参与当前输出。下面的示意图把当前正在处理的第 3 个 chunk 标成橙色,并假设左侧历史窗口为 2 个 chunk。

处理第三个语音块时,当前块和两个历史块可见,未来块被分块掩码屏蔽
图:Chunk-based Masking 的可见范围。原创示意图;蓝色表示历史上下文,橙色表示当前 chunk,灰色表示被屏蔽的未来。

2. 掩码矩阵如何限制未来信息

假设一段语音被切成 5 个 chunk,每个 chunk 有 10 个特征帧。当模型处理第 3 个 chunk 时:

  • 第 1、2 个 chunk 属于历史窗口,可以被当前 chunk 读取;
  • 第 3 个 chunk 内的 10 帧可以互相读取;
  • 第 4、5 个 chunk 属于未来,注意力分数会被掩码置为不可见。

如果把左侧窗口从 2 个 chunk 调成 1 个 chunk,第 3 个 chunk 就只能读取第 2 个 chunk 的历史。窗口越大,模型能利用的上下文越完整,但缓存和计算开销也越高。

可以把每个查询位置允许读取的集合写成一个直观的形式:

[ \mathcal{A}_k = {\text{当前 chunk } k} \cup {\text{左侧 } L \text{ 个 chunk}} \cup {\text{右侧 } R \text{ 个 lookahead 帧}}. ]

注意力掩码把不在 (\mathcal{A}_k) 中的位置排除。训练时,每次前向传播都使用同样的上下文约束,模型从一开始就适应“看不到未来”的条件;推理时则逐 chunk 执行,并将历史所需的键值、卷积状态或记忆向量缓存起来。

3. 延迟和复杂度如何变化

若每个 chunk 的大小 (C)、左侧上下文 (L) 和右侧上下文 (R) 都固定,那么每个 chunk 只在一个有界范围内做注意力。整段长度为 (T) 时,注意力部分可以近似写成:

[ O\bigl(T(C+L+R)\bigr), ]

相对于完整序列的 (O(T^2)),它在固定窗口下随序列长度近似线性增长。更准确地说,是每个 chunk 的计算范围被限制为常数级,而不是整段序列的总计算量变成常数。

chunk 越小,理论上的等待时间越短,但边界更频繁,调度和缓存开销可能变大;chunk 越大,上下文更充分,却需要等待更多音频到达。实际延迟还要加上右侧 lookahead、特征提取、编码器计算、解码器搜索和端点判定,不能只看 chunk 大小。

三、从音频到部分结果:推理时到底缓存什么

流式识别不是“每次截一小段音频,然后重新从头跑一遍”。高效实现一般会把处理过程组织成下面的循环:

音频流经过分块、特征提取、带缓存的流式编码器和解码器,持续产生部分结果并在端点输出最终结果
图:流式 ASR 的基本推理循环。原创示意图;缓存用于避免历史上下文重复计算。
  1. 从麦克风或音频流中收集一小段新帧;
  2. 将新帧做特征提取和必要的下采样,组成当前 chunk;
  3. 用分块掩码计算当前 chunk 的编码结果;
  4. 复用历史的 attention K/V cache、Conformer 卷积 cache,或其他形式的 memory;
  5. 通过 CTC、RNN-T 或 attention decoder 更新部分结果;
  6. 根据静音和端点策略决定继续等待,还是提交最终结果。

这里有一个经常被忽略的细节:当前 chunk 内部可以双向,并不代表系统看到了真正的未来。 它只是等当前 chunk 的音频收齐后,在这段有限窗口内部做双向计算。因此 chunk 本身就会带来一部分等待时间;如果再加入右侧 lookahead,准确率可能提高,但延迟也会增加。

四、四种常见路线放在一起看

这几种方案并不完全处在同一层级:Chunk Conformer 主要描述注意力范围,Emformer 强调历史记忆压缩,U2/U2++ 强调统一解码和重打分,Zipformer 则把高效编码器与动态上下文训练结合起来。

Chunk Conformer、Emformer、U2/U2++ 和 Zipformer 在历史记忆、未来上下文与解码方式上的对比
图:四种流式路线的关注点对比。原创示意图;具体效果取决于模型规模、数据、解码器和部署配置。
路线主要机制历史上下文未来上下文典型取舍
Chunk Conformer分块掩码 + cache左侧 chunk 或有限窗口固定或可选 lookahead机制直观,参数容易解释
EmformerAugmented Memory Bank + cache压缩成少量 memory entries按 segment 处理长历史成本更低,但存在信息压缩
U2 / U2++流式首遍 + 非流式重打分由共享编码器和解码器维护第二遍可利用更多上下文在线响应与最终精度兼顾,但第二遍有额外计算
Zipformer高效编码器 + dynamic chunk / right-context 训练由 chunk 和 cache 控制训练覆盖多种右上下文一个模型适配多档延迟,训练和部署更复杂

1. Chunk Conformer:先把注意力范围限制住

Conformer 将自注意力和卷积模块结合起来,能够同时建模较长的依赖和局部声学模式。流式版本的关键不是把网络完全换掉,而是让每一层都遵守同一套上下文边界:当前块可以读取左侧缓存,未来块被掩码,卷积模块也只读取已经到达的局部状态。

它的优点是容易理解和调参。chunk_size 决定一次处理多少时间步,left_context 或 left_chunk 决定保留多少历史,right_context 决定允许多少 lookahead。缺点是固定窗口对不同场景不一定都合适:短命令可能不需要很长历史,长句子又可能受益于更大的上下文。

2. Emformer:用记忆向量承接更远的历史

Emformer(Efficient Memory Transformer)针对长历史上下文设计了增强记忆库。它不会把所有历史帧原样放进每次注意力,而是将较早的 segment 压缩成少量 memory entries,后续 segment 通过这些记忆读取更远的上下文;同时缓存注意力中的键和值,避免重复计算。

因此,Emformer 的核心问题不是“能不能看历史”,而是“怎样用较小的记忆成本保留历史中有用的信息”。压缩记忆适合长语音和低延迟场景,但压缩本身是有损的,记忆容量、segment 大小和缓存策略都会影响最终效果。原论文在 LibriSpeech 上报告了不同延迟预算下的 WER 结果,例如约 960 毫秒延迟时 test-clean 2.50%、test-other 5.62%,压缩到约 80 毫秒时 test-clean 为 3.01%;这些数字对应特定模型和实验设置,不能直接当作所有实现的固定上限。

3. U2 / U2++:在线先出字,离线再修正

U2(Unified Two-pass)把流式和非流式解码放进同一套模型体系:第一遍使用分块约束快速产生初步结果,第二遍利用更完整的上下文进行重打分或修正。这样,用户可以先看到低延迟的部分结果,系统在一句话结束后再提交更稳定的最终结果。

U2++ 在 U2 的基础上强化了双向注意力解码器或重打分路径,常见实现可以在第一遍保持在线响应,再用第二遍提高精度。WeNet 提供了 U2/U2++ 的完整工程实现;公开实验中,AISHELL-1 上的 U2++ CER 常见约为 4.23%,但同样会随数据增强、解码参数和版本而变化。

这类方案的关键不只是“用了两遍”,还包括如何处理两遍结果之间的边界:部分结果可以被修订多少、什么时候提交最终文本、第二遍是否阻塞后续音频,都会影响用户感知的延迟。

4. Zipformer:让模型适配不同的右上下文预算

Zipformer 是 k2-fsa / icefall 生态中常见的高效语音编码器。它的流式实践经常结合 dynamic chunk training 或 dynamic right-context:训练时让不同 batch 使用不同数量的右侧上下文,使同一个模型学习多种延迟条件;推理时则根据设备和业务的延迟预算选择合适的 lookahead。

这样做的好处是,不必为每一个延迟档位都训练一套模型。加入少量右上下文后,流式结果可以更接近非流式配置。代价是训练掩码、缓存逻辑和部署配置都更复杂,评测时也要明确每个延迟档位到底使用了多少未来帧。

五、只看 WER 不够:流式系统怎样评估延迟

流式 ASR 的目标是让“正确结果尽早出现”,所以 WER 或 CER 只是其中一部分。常见指标包括:

  • 首字延迟(first-token latency):从系统获得有效语音,到第一次输出可见 token 或文字所需的时间。不同系统对起点的定义可能不同,报告时要写清楚;
  • 部分结果延迟(partial latency):用户仍在说话时,当前音频和屏幕上最新稳定 partial result 之间的时间差;
  • 端点延迟(endpoint latency):用户停止说话后,系统判定一句话结束并提交最终结果所需的时间;
  • 实时因子(RTF):处理耗时除以音频时长。RTF 小于 1 说明平均计算速度快于实时,但它不代表首字延迟或尾部延迟一定好。

延迟最好同时报告平均值、50 分位和 90 分位。平均值容易掩盖少数特别慢的请求,而 p90 更接近用户遇到的尾部体验。评测时还应固定网络、音频缓存、线程数、设备型号和端点策略,否则不同实验的延迟数字很难比较。

六、FastEmit:让 RNN-T 更早发射标签

分块掩码限制了模型能看到的上下文,但解码器仍可能倾向于“多等一会儿再确认”。RNN-T 的目标函数通常并不直接惩罚标签发射得太晚,只要最终序列正确即可。因此,模型可能为了提高置信度而等待更多未来声学信息。

FastEmit 在训练目标中加入序列级发射正则,鼓励模型更早发射标签。论文在 Conformer-Transducer 上报告过把延迟从约 220 毫秒降到约 27 毫秒、WER 仅增加约 0.7% 的结果。这个例子说明,延迟优化不只发生在注意力掩码层,也可以从训练目标和解码器行为入手。

实际使用时要关注一个边界:更早发射可能让部分结果更快,但如果没有稳定性策略,屏幕上的文字也可能更容易回滚。低延迟和低抖动需要同时设计。

七、数据集与快速上手路径

流式语音识别论文中最常见的两个开源基准是:

  • LibriSpeech:约 960 小时的英文朗读语音,采样率为 16 kHz,常用于英文 ASR 的 WER 对比;
  • AISHELL-1:约 178 小时的普通话朗读语音,由约 400 位说话人录制,常用于中文 ASR 的 CER 对比。

如果想快速实践,可以按下面的顺序推进:

  1. 先用 WeNet 跑通 U2/U2++ 示例,熟悉训练、流式解码和两遍重打分;
  2. 明确 chunk_size、left_context / left_chunk 和 right_context 的单位,确认它们是在下采样后的时间轴上还是原始声学帧上;
  3. 训练时使用和推理一致的分块掩码分布,避免训练看到了未来、推理却完全看不到;
  4. 在推理代码中正确缓存 attention K/V、卷积状态或 memory entries,避免每个 chunk 从头计算;
  5. 同时记录 WER/CER、首字延迟、部分结果延迟、端点延迟、p50/p90 和 RTF;
  6. 部署时可以导出 ONNX,再结合 TensorRT 或其他硬件后端做推理加速,但要重新验证数值一致性和端到端延迟;
  7. 最后在真实音频上检查专有名词、数字、噪声、打断和端点误判,不能只依赖干净测试集。

八、最后记住这几句话

Chunk 分块掩码的核心不是简单地“把输入切小”,而是把每个时间点允许读取的上下文明确写进注意力规则。它带来的收益可以概括为:

  • 当前 chunk 内做有限范围的计算,避免全序列注意力的平方级增长;
  • 用左侧 cache、memory bank 或卷积状态保留有用的历史;
  • 用 right context 换取一部分精度,同时把额外等待时间纳入预算;
  • 用两遍解码、动态上下文或更早发射训练,把在线响应和最终精度分开优化。

如果只能记住一个工程判断,可以用这句话:先定义允许的延迟预算,再决定 chunk、历史窗口、右上下文和解码策略;不要只追求更小的 chunk,也不要只看最终 WER。

延伸阅读

打开原图