做边缘端语音识别的选型时,我最先考虑的其实是Conformer——生态成熟、预训练模型多、社区踩坑帖一抓一大把。但真把那个跑在GPU上的识别流程往RK3588上迁移时,问题立刻冒出来了:模型文件接近200MB,实时率勉强到1.0,稍微来一段长句就开始输出卡顿,内存也被onnxruntime的arena吃掉一大半。同一个项目里我还得顺带跑YOLOv8和视觉里程计,能给语音分配的资源注定有限。所以在看到Zipformer这个新架构之后,我直接决定在同一块RK3588板子上把两款编码器做一次背靠背实测,重点回答两个问题:比Conformer快多少?内存省多少?这篇就是完整的测试记录,包括环境、数据、踩坑和最终选型建议,给正在折腾板端ASR的人一个可复现的参考。
1. 为什么盯上Zipformer:边缘ASR的算力账
1.1 RK3588的算力画像与语音任务的不适配
RK3588这块SoC大家已经很熟了:8nm工艺,4个Cortex-A76大核(标称2.4GHz)加4个Cortex-A55小核(1.8GHz),集成6TOPS算力的NPU,支持INT8/INT16/FP16混合精度。做视觉方向的同事拿它跑YOLOv8、跑视觉SLAM,RKNN工具链一套下来体验还不错。但语音识别完全是另一回事——ASR的编码器是串行计算密集型任务,NPU对动态序列、attention算子的支持又很尴尬,最终90%的实战场景还是得靠CPU硬扛。在RK3588上做ASR,CPU性能和内存带宽才是真正的天花板,这也是我这次只测CPU侧的原因。
另一个背景是部署场景本身。把ASR放到端侧而不是云端,核心诉求无非三点:一是隐私,音频不用出设备;二是延迟,省掉网络往返;三是成本,不依赖GPU服务器的按量计费。但端侧ASR落地的最大阻碍就是算力和内存,模型稍微重一点,实时性就崩,进程一多,内存就报警。RK3588作为一块在NVR、机器人、边缘盒子里被大量使用的芯片,正好卡在"性能够用但资源紧张"的档位上,所以在这里验证Zipformer的价值是很有代表性的。
1.2 Conformer的问题:不是不够强,是不够轻
Conformer由Google在2020年提出,用"卷积+注意力"混合结构把ASR的准确率推上了一个台阶,至今仍是各种新模型的对比基准。但它的代价是计算量:标准规模的Conformer编码器大约46M参数,每一层都要做完整序列的self-attention,再加上depthwise卷积和两个前馈模块。输入语音越长,attention的平方级复杂度就越致命。在我的板子上用4个A76线程跑fp32模型,15秒的音频处理耗时超过21秒,实时率(RTF)约1.42,也就是说它连"实时"这条及格线都过不了。这不是单块板子的体质问题,Conformer本身就默认你是给GPU用的。
1.3 Zipformer来的正是时候
Zipformer是WeNet团队2024年发表的编码器架构,论文标题直白得很:A faster and better speech recognition model。它的核心思路是"压缩"——在编码器内部用不同的帧率处理不同层的序列,中段层把帧数降下去再升回来,attention的计算量随之大幅下降;同时用BiasNorm替代LayerNorm,省掉逐token求均值方差的额外开销。论文里的结论是在开源数据集上达到跟Conformer相当甚至更好的词错误率,但模型更小、计算量更低。这种"又准又省"的架构天然适合我这种要在RK3588上做实时识别的场景,于是就有了下面这轮对比。
2. 从架构上拆解:Zipformer的性能优势到底从哪来
2.1 Conformer的一个模块在算些什么
先简单回顾Conformer块。每个块内部顺序是:前半前馈(FFN)、多头自注意力(MHSA)、卷积模块(通常是31×1的depthwise卷积)、后半前馈,外面接残差。以12层、d_model=256的常见配置为例,每个token在每个块里都要经过一次完整的MHSA,序列长度T=1500帧(15秒音频)时,单头的注意力矩阵就是1500×1500,这还不算多头。换句话说,计算量随句长平方增长,而边缘设备最怕的就是这种"输入越长越顶不住"的模型。
2.2 Zipformer的多帧率设计与归一化替换
Zipformer的编码器不是一个帧率走到底。它把网络分成若干段,段与段之间插入降采样/上采样模块:前段保持较高帧率,中段用2倍、4倍甚至8倍的降采样把序列压短,等计算量最大的attention层处理完短序列之后,再逐步升采样恢复时间分辨率。这样中段attention复杂度从O(T²)降到O((T/d)²),d是降采样倍数,省下来的计算量相当可观。
同时Zipformer用BiasNorm替换LayerNorm。LayerNorm要对每个token的整条特征向量求均值方差,属于归约操作,在推理时很占内存带宽;BiasNorm则退化为一个可学习的偏置和一个缩放标量,不依赖整条序列的统计量,流式推理时也更友好。再配合层间差异化学习率、部分权重衰减等训练手段,最终训练出的模型在同等精度下参数更少。
表1:两种编码器的规模估算(以10秒语音输入为例)
| 项目 | Conformer | Zipformer medium |
|---|---|---|
| 编码器参数量 | 约46M | 约27M |
| 10秒语音编码器计算量(估算) | 约8 GFLOPs | 约3.2 GFLOPs |
| 模型文件大小(fp32 ONNX) | 约184MB | 约108MB |
2.3 参数少不等于一定快,关键是访存和计算形状
实际跑起来你会发现,参数量少40%并不等于速度快40%,Zipformer能快3倍以上,更多靠的是把attention序列压短。因为transformer在推理时的瓶颈经常不在FLOPs,而在访存带宽:每个token都要取权重、读写中间激活。序列缩短后,中间激活体积同步缩小,cache命中率也上去了。另外Zipformer的模块结构更规整,onnxruntime在aarch64上调度起来也更顺畅。所以Zipformer的速度优势不是"参数少",而是"把计算集中到短序列上",这个区别决定了它在各种句长下都能保持优势。
3. 实测环境与对比方法:这组数据怎么来的
3.1 硬件与系统基线
- 板卡:RK3588开发板,8GB LPDDR4x,eMMC 64GB,带主动散热风扇
- 系统:Ubuntu 22.04 aarch64,内核5.10
- 推理后端:sherpa-onnx(1.10.x)内置onnxruntime 1.17,C++ API
- CPU策略:将scaling_governor设为performance,并确认全程没有触发降频
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor这里要特别强调散热。RK3588的A76大核全速跑fp32矩阵运算时发热很猛,不装散热片的话实测5分钟就会从2.4GHz掉到1.2GHz左右,RTF直接翻倍。任何人复现我这组数据之前,先检查你的板子有没有在降频,否则对比出来的结论会失真。
3.2 模型准备:保证对比公平
Conformer用的是WeNet开源版本,编码器导出为ONNX;Zipformer用WeNet发布的zipformer medium版本(LibriSpeech训练),同样导出ONNX。两个模型都通过sherpa-onnx加载,输入统一为16kHz/16bit单声道PCM。sherpa-onnx的编译也比较直接:
git clone https://github.com/k2-fsa/sherpa-onnx cd sherpa-onnx mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON .. make -j6这里有一个容易踩的坑:Conformer和Zipformer的输入预处理有细微差别(比如是否做全局均值归一化、帧移多少),如果直接用各自官方示例的配置去跑,数据没问题;但如果自己写前端,一定要对齐两者的特征参数,否则对比的是两个不一样的前端,而不是模型本身的差距。
3.3 测试集与指标定义
测试集从AISHELL-1的dev集中随机抽了80条,总计约8分钟中文语音,另抽了3条60秒长音频做长句压力测试。两个模型在这批数据上的词错误率差距在0.2个百分点以内,Zipformer略优,说明后续的速度对比不是因为牺牲精度换来的。指标用三个:
- RTF(实时因子):处理耗时÷音频时长,小于1表示比实时快
- 首字延迟:流式模式下,从首个语音帧进入模型到输出第一个token的墙钟时间
- 峰值RSS:用
/usr/bin/time -v记录进程最大驻留内存
每组数据先跑3次预热,再取10次平均值。线程数分别测4、6、8,所有测试都在绑定大核的前提下进行。
4. RTF与延迟实测结果:快多少,数据说话
4.1 fp32整包识别RTF对比
表2:AISHELL-1子集(约8分钟音频),fp32,绑大核
| 模型 | 4线程 | 6线程 | 8线程 |
|---|---|---|---|
| Conformer | 1.42 | 1.05 | 0.97 |
| Zipformer medium | 0.41 | 0.31 | 0.29 |
| 提升倍数 | 3.46x | 3.39x | 3.34x |
4线程下Zipformer的RTF为0.41,意味着处理10秒音频只要4.1秒,实时性完全够用;Conformer的1.42则意味着它连实时都做不到。线程加到6以后Conformer勉强到1.0,但8线程反而收益很小——因为第7、8个线程会跑到A55小核上,小核算力拖后腿。所以后面对比核心数据都锁定在4线程,这也是RK3588上最合理的CPU配置。
从架构对照来看,这个RTF差距完全对得上FLOPs差距。我在2.3节说过,Zipformer的优势是"集中算短序列",Intel的时序里也能看到类似的结论:序列压缩带来的收益会随着句长放大。所以如果你只是把模型从Conformer换成Zipformer,线程数和内存池都不用动,RTF就能掉到三分之一左右,这是我一开始没想到的。
4.2 流式识别的首字延迟
板端ASR大多用于语音助手、对讲机、会议转写这类流式场景,首字延迟比整体RTF更影响体感。测试用sherpa-onnx的流式接口,chunk_size设为16(每块240ms),结果如下:
表3:首字延迟对比(fp32,4线程)
| 模型 | 首字延迟(约) |
|---|---|
| Conformer streaming | 290ms |
| Zipformer streaming | 190ms |
差异主要来自Zipformer编码器内部的降采样结构:流式推理时只需要缓存降采样前的少量帧,每个chunk要计算的历史状态比Conformer少,所以首个token能更快吐出来。190ms在实时交互里属于可以接受的范围,配合VAD做端点检测,体感基本是"话说完字就出"。
4.3 句长越长,差距越大
我额外测了3条60秒长音频,目的是放大attention复杂度差异。结果很直观:Conformer在4线程下的RTF从1.42涨到1.91,Zipformer只从0.41涨到0.46。原因就是前面说的,Conformer的attention成本随句长平方增长,而Zipformer中段帧率被压到1/4甚至1/8,长句时中段attention的token数增长远慢于输入。如果你要做非流式的长录音转写,这个特性比浮点优化还要值钱。
5. 内存实测:模型体积之外,运行时峰值也省
5.1 权重体积的账先算清楚
表4:模型文件与权重驻留内存(fp32 / int8对比)
| 模型 | ONNX文件(fp32) | ONNX文件(int8) | fp32权重驻留 | int8权重驻留 |
|---|---|---|---|---|
| Conformer | 184MB | 48MB | 约184MB | 约48MB |
| Zipformer medium | 108MB | 30MB | 约108MB | 约30MB |
Conformer光权重就占掉184MB,加上onnxruntime的arena预分配和临时buffer,一个进程轻松到400MB以上。这对8GB内存的板子来说还能忍,但很多RK3588设备实际是跑多路音频流的,每路一个进程,内存就紧张了。Zipformer把权重基数降下来之后,多实例并发时的优势会被进一步放大。
5.2 运行时峰值RSS对比
实测峰值RSS(fp32,4线程,8分钟连续转写):
- Conformer:486MB
- Zipformer medium:318MB
内存省了大概35%。int8量化后差距依然存在:Conformer约232MB,Zipformer约158MB。省内存的机制不止是参数量少,还有激活值:Zipformer中段序列短,中间特征图小,onnxruntime分配临时buffer的峰值自然低。对于需要同时跑视觉任务和语音识别的设备,这160MB的差距往往就是"能跑"和"不能跑"的分界线。
5.3 流式会话缓存也有差距
流式识别每个会话要维护attention cache和卷积cache。实测连续识别60秒流式音频,Conformer的cache涨到约42MB,Zipformer约24MB。差距来源同样是降采样:高帧率层只需要保留很短的历史,低帧率层虽然历史长但token数少。不要小看这点差异,如果设备同时开4路实时语音流,Zipformer在缓存上能省下70MB以上。
另外补一个多实例测试:我同时起了4个进程跑4路音频流,Conformer方案峰值内存约1.9GB,Zipformer约1.3GB。对一台还要跑视觉任务和业务逻辑的RK3588来说,这600MB的空间正好可以把YOLOv8的推理buffer留出来,或者让系统少一些OOM风险。
6. 工程优化与踩坑记录:把架构优势真正吃到嘴里
6.1 线程配置:绑紧4个A76大核
我在测试中发现一个反直觉现象:把线程数从4加到8,Zipformer的RTF只从0.41降到0.29,看起来变快了,但CPU占用率和功耗涨了不止一倍,而且一旦调度器把任务丢给小核,延迟抖动非常明显。更稳的做法是用taskset把进程绑在A76大核上,再在sherpa-onnx里设4线程:
taskset -c 4,5,6,7 ./build/bin/sherpa-onnx-offline \ --zipformer-encoder=encoder.onnx \ --zipformer-decoder=decoder.onnx \ --zipformer-joiner=joiner.onnx \ --tokens=tokens.txt \ --num-threads=4 \ test.wav如果你的系统里还有视觉任务在抢CPU,建议给语音进程设置较高的nice值,并留出至少一个大核给系统调度,否则识别延迟会出现周期性尖峰。这个经验同样适用于Conformer,只是Zipformer本身计算量小,对调度抖动的敏感度低一些。
6.2 int8量化:值得做,但别贪心
onnxruntime在aarch64上的int8 kernel没有x86那么成熟,收益没有想象中大。我用校准集对Zipformer做了int8量化,RTF从0.41降到0.30,提速约27%,词错误率大约涨了0.3个百分点,可接受。但有一个重要禁忌:流式模型的attention cache不要量化成int8,我试过,输出会出现明显的重复和吞字。正确做法是权重用int8,cache保持fp32,sherpa-onnx里对应的选项是分开设的,别图省事全开int8。
6.3 NPU部署为什么最后被我放弃了
RK3588的6TOPS NPU看起来很诱人,我也试过用RKNN-Toolkit2把Zipformer转到NPU上跑。结论是:能转,但非常勉强。主要问题有三个:
- 流式模型的输入是分块动态shape,RKNN导出时需要固定或限定动态范围,前后两端都要做很多胶水代码;
- Zipformer里的BiasNorm和部分reshape算子,在RKNN工具链里的支持不完整,要么手工拆图,要么回退到CPU片段;
- NPU通常还被YOLOv8这类视觉任务占着,ASR硬挤进去会导致两个任务互相拖累。
最后我老老实实回到CPU+onnxruntime方案。对于单路或双路实时识别,Zipformer在CPU上的RTF已经足够好,没必要为了NPU上的理论性能把工程复杂度拉满。如果一定要用NPU,我的建议是只把特征提取或者joiner这类小模块放上去,编码器留在CPU,这样既避开动态shape,又不会跟视觉任务抢6TOPS算力。
6.4 几个容易忽略的测量细节
最后说几个只有实际测过才会注意到的细节:
- 测RSS要用
/usr/bin/time -v,不要看top的瞬时值,onnxruntime的arena会预申请大量内存,瞬时值非常不可靠; - 跑benchmark之前先跑一遍相同的音频做预热,否则第一次调用的内存分配和权重加载会污染数据;
- 如果板子用的是LPDDR4x而不是LPDDR5,内存带宽差异会影响长句场景的RTF,跨板卡对比时要注意标注内存型号;
- 流式测试的chunk_size要固定,不同chunk_size下首字延迟没有可比性;
- 每次改完线程数或量化参数,都要重新检查是否降频,可以用
cat /sys/devices/system/cpu/cpu4/cpufreq/scaling_cur_freq实时确认。
测试做完之后,我给自己的选型定了个规矩:凡是要跑在RK3588上的ASR,默认先看Zipformer;只有在需要兼容旧模型、且对实时率不敏感的离线场景里才保留Conformer。这周我已经把项目里的识别服务切到了zipformer medium + int8权重 + 4大核绑定,单路识别的RTF稳定在0.3以内,内存占用从原来的接近500MB压到了160MB左右,给视觉任务腾出了不少余量。如果你们那边也遇到板端ASR卡顿的问题,不妨先别急着换硬件,把编码器换成Zipformer试试,成本几乎为零。