“解码卓越——深入探索WeNet智能语音识别源码”系列报道:解码引擎篇之流式引擎解码中的缓存机制

科技   2024-12-11 20:02   黑龙江  

DECODER

解码引擎

流式引擎中的缓存机制



接上篇深入探索WeNet智能语音识别源码之掩码篇,继续此专题,探索WeNet框架的底层解码引擎。




为什么要使用缓存?什么是缓存?


● 在流式推理中,输入为一个个陆续到来的 chunk。就第 i 个 chunk 而言,在计算第 k 层网络的输出时,因网络结构对左侧上下文存在依赖,所以需要依赖第 k - 1 层网络中在 i 之前的一些 chunks 的输出。如果针对当前到来的 chunk,把它与所依赖的 chunk 序列(例如有 10 层自注意力层,每层依赖左侧 4 个 chunk,那么累积起来就需要依赖左侧 40 个 chunk)拼接起来作为网络输入进行前向传播,其计算量会很大。对于那些已经计算过的 chunk,可以把在计算下一个 chunk 的输出时所需的中间量保存下来,以此减少重复计算。这种方式被称为 cache。

此外,wenet 的网络在设计时,对因果卷积和自注意力的左侧上下文均采用有限长度,所以无论序列多长,每次缓存(cache)的大小都是固定不变的(不会增长)

仅仅编码器部分在涉及 chunk 计算时才会用到缓存机制。对于连接时序分类解码器(CTC decoder)来说,由于它是线性层,所以不需要 cache。而对于集束搜索解码器(AED decoder),是在计算完整个序列的编码器输出后进行重新评分,并不涉及 chunk。





             流式引擎缓存代码分析

● BaseEncoder.forward_chunk()分析:

forward_chunk()是对单个chunk进行前向计算的核心函数。下面从该函数的内容来了解cache的实现。

图片中源码来自于github开源项目Wenet wenet/transformer/encoder.py


xs是当前的chunk输入,由于对于单个chunk的前向计算,需要之前的chunk的计算得到的信息,因此这里需要传入相关的三个cache信息。


subsampling_cache:torch.Tensor subsampling输出的cache。即第一个conformer block的输入。


● elayers_output_cache:List[torch.Tensor] 第1个到最后1个conformer block的输出的cache。也就是第2个conformer block的输入和CTC层的输入。


● conformer_cnn_cache:List[torch.Tensor] conformer block里的conv层的左侧依赖的输入cache。


(1)cache的大小:

● subsampling_cache和elayers_output_cache的大小由self-attention是对左侧的依赖长度required_cache_size决定。

● decoding_chunk_size是解码帧级别的chunk大小, num_decoding_left_chunks是self-attention依赖的左侧chunk数。

图片中源码来自于github开源项目Wenet wenet/transformer/encoder.py


● conformer_cnn_cache值的大小与函数内参数required_cache_size无关,由casual网络的左侧上下文lorder决定。


(2)offset:

● 当按chunk进行输入时,不能直接得到chunk在序列中的位置,需要传入offset给出该chunk在整个序列里的偏移,用于计算positional encoding。

图片中源码来自于github开源项目Wenet wenet/transformer/encoder.py


● subsampling内部的计算虽然存在冗余,但是不进行cache。一个是其实现比较复杂,另一个原因是subsampling的计算量占比不大。


(3)subsampling_cache:

● subsampling的输出的cache。即第一个conformer block的输入。

图片中源码来自于github开源项目Wenet wenet/transformer/encoder.py


(4)elayers_output_cache:

● 第1个到最后1个conformer block的输出的cache。也就是第2个conformer block的输入和CTC层的输入。

图片中源码来自于github开源项目Wenet wenet/transformer/encoder.py


注意,此处的xs不是当前的chunk,而是当前chunk+cache输入,所以其长度不是chunk_size, 而是 

chunk_size + required_cache_size。

图片中源码来自于github开源项目Wenet wenet/transformer/encoder.py


● layer()对应着

wenet/transformer/encoder_layer.py中的

ConformerEncoderLayer.forward()。

下面是其具体过程。

图片中源码来自于github开源项目Wenet wenet/transformer/encoder_layer.py


注意,self-attention之前的一些前向计算其实仍然存在冗余,如果对attention层的输入进行cache,而不是对conformer block层的输入cache,可以进一步降低计算量。


(5)conformer_cnn_cache:

● conformer block里的conv层的左侧依赖的输入cache。

● conformer_cnn_cache大小为lorder,即因果卷积左侧依赖。

图片中源码来自于github开源项目Wenet wenet/transformer/convolution.py


● 缓存工作流程如下图。



●以上便是使用WeNet框架中模型实现时使用的缓存技巧代码解读的全部内容了。希望通过本文的学习,读者能够更深入地了解WeNet框架。同时,也鼓励大家继续探索更多深度学习框架和模型优化技术,以推动人工智能领域的不断发展和进步。

下一期我们会从模型推理方面继续探索WeNet源码,“我们下期见!”


注:文章图片中源码均来自于github开源项目Wenet



图文 | 智慧客服项目组 张禹豪

编辑 | 智慧客服项目组 张禹豪

审核 | 王涛 于向丽 刘小菁

校对 | 党委办公室(办公室)

联通哈尔滨软件研究院
专业的软件研发机构
 最新文章