Kaldi 是 WFST 语音识别的参考实现,2015 年落到 GitHub 上,至今 15.4k star、5.3k fork,Apache-2.0 协议。它有个和别的项目都不一样的地方:仓库里一个 release tag 都没有,版本号只写在 src/.version 这个文本文件里,内容就两个字符——5.5。Daniel Povey 一个人贡献了近 4000 次提交,然后把精力转向了别处;主线最后一次 push 停在 2025 年 9 月。但它没有被归档,Vosk(15k star,仍在更新)这类落地项目今天仍然直接跑在它上面。
所以搜「Kaldi 本地部署」的人,第一步得先确认自己说的是哪个 Kaldi。主线 kaldi-asr/kaldi 是 C++ 写的 WFST 工具箱,给你 GMM 强制对齐、HCLG 解码图、lattice 后处理、CUDA 批量解码器。Next-gen Kaldi 是另一套东西:k2 提供可微的 FSA/FST 算子,lhotse 管数据,icefall 放训练 recipe,sherpa-onnx 负责端侧推理——最新一版 v1.13.6 发在 2026 年 8 月 18 日,节奏跟主线完全是两回事。两条线共用一个名字,但代码没有一行是共享的。
至于显存,Kaldi 跟大模型那套换算完全不搭界。它的 chain 模型是 17 层 TDNN-F,dim 1536、bottleneck 160,落盘几十 MB,训练时单卡 8GB 都算宽裕。真正的资源墙有三道:特征抽取和对齐要横向 fan-out 吃 CPU 核;建 HCLG 图和 dump egs 吃内存与磁盘;只有 GPU 批量解码这一环是真吃显存——上游 cudadecoder/README 写得很直白,16GB 卡大约支撑 200 路并发,而且显存随控制线程数和 batch size 线性增长。按并发选卡,不要按参数量选卡。
01 —
Kaldi 家族对照表:你要装的到底是哪一个
主线 C++ 工具箱、Next-gen 训练栈、跨端推理运行时,三条线各有各的显存账。
| 版本 | 参数量 | 显存 | 上下文 | 说明 |
|---|---|---|---|---|
| Kaldi 主线(kaldi-asr/kaldi,src/.version = 5.5) | chain TDNN-F:17 层 × dim 1536 / bottleneck 160 | 训练单卡 8GB 起步已宽裕;CUDA 解码 16GB ≈ 200 路并发 | WFST 全句解码,没有上下文窗口这个概念 | GitHub 上没有任何 tag 发布,版本号只在 src/.version 里;最后一次 push 是 2025-09-22。要 WFST 解码图、GMM 对齐、lattice 后处理,或者要复现论文里的 s5 recipe,只能用它。 |
| k2(k2-fsa/k2) | 可微 FSA/FST 算子库,本身不含权重 | 自身几乎不占;显存跟着 PyTorch 与 --max-duration 走 | — | Next-gen Kaldi 的计算内核,Apache-2.0,2026 年 7 月仍在更新。预编译 wheel 走 k2-fsa.github.io/k2/cuda.html,国内换成 cuda-cn.html;已经出到 ROCm 7.2 + PyTorch 2.12.1 的轮子。 |
| icefall Zipformer-small | 23.3M(LibriSpeech)/ 30.17M(AISHELL-1) | fp16,2 卡 × 32GB,--max-duration 1500(英)/ 1200(中) | 非流式整句;加 --causal 1 转 320ms chunk 流式 | LibriSpeech test-clean 2.42 / test-other 5.73,AISHELL-1 dev 4.67 / test 4.97。官方 RESULTS.md 记的就是两张 32G V100,是整条线上最省钱的复现起点。 |
| icefall Zipformer(normal) | 65.55M(LibriSpeech)/ 73.41M(AISHELL-1) | fp16,4 卡 --max-duration 1000,50 epoch | 非流式;流式版 66.11M,320ms chunk | LibriSpeech test-clean 2.21 / test-other 4.79;流式 320ms 版 test-clean 2.79 / test-other 7.36。生产上最常被直接拿去上线的一档。 |
| icefall Zipformer-large / CR-CTC | 148.4M(transducer)/ 147.0M(CR-CTC)/ 157.29M(AISHELL-1 large) | CR-CTC 官方配置是 2 卡 × 80GB A100,--max-duration 1400 | 非流式 | CR-CTC 把 LibriSpeech test-clean 压到 2.02、test-other 4.35,是这条线目前最好的一档。80GB 不是摆设——--max-duration 1400 在 32GB 卡上放不下。 |
| sherpa-onnx v1.13.6 | 推理运行时,不含权重 | CPU 即可跑;GPU 走 CUDA 12 + cuDNN 9,或 CUDA 13 + cuDNN 9 + onnxruntime 1.27.1 | 随所加载的模型而定 | 2026-08-18 发布,Apache-2.0。同一套 C++ 内核喂 Zipformer、Paraformer、SenseVoice、Whisper、Moonshine、TeleSpeech、Parakeet;从 Linux/Windows 一路覆盖到 Android、iOS、鸿蒙、树莓派、RISC-V 和各家 NPU。 |
02 —
选卡:按你要做的事,不是按参数量
Kaldi 的显存账和 LLM 完全不是一回事,别照搬那套「参数量 × 2 字节」的心算。
编译带 CUDA 的 Kaldi,训练 chain / nnet3 模型
RTX 3090 24GB$0.193/卡·时
sm_86 就在 Kaldi configure 给 CUDA 12.x 的默认 gencode 列表里,一个字不用改就能编出来;chain 模型本体几十 MB,24GB 跑 num-chunk-per-minibatch 64 富余得很。
batched-wav-nnet3-cuda2 批量离线转写
Tesla T4 16GB$0.298/卡·时
上游 cudadecoder/README 的基准刻度就是 16GB 卡约 200 路并发(4 控制线程 × batch 50),T4 正好对上这个数,sm_75 也在默认 gencode 里。
高并发转写集群,想按默认 --num-channels 600 直接跑
RTX A6000 48GB$0.817/卡·时
README 说显存随控制线程数与 batch size 线性放大,48GB 差不多能把默认的 --max-batch-size 400 / --num-channels 600 原样吃下,不用退档重调。
icefall Zipformer 复现训练与中文微调
Tesla V100 32GB$0.188/卡·时
icefall RESULTS.md 里 small(23.3M)和 CR-CTC medium(66.2M)两档就是两张 32G V100 训出来的,--world-size 2 的配置能一比一照抄;只有 --max-duration 1400 的 CR-CTC large 才真需要跳到 A100 PCIE 80GB($0.824/卡·时)。
03 —
四步把 Kaldi 在 NexGPU 上跑通
从 Ubuntu CLI 镜像开机,到 GPU 批量解码吐出 lattice。
- 01
开实例,装依赖,编译带 CUDA 的 Kaldi
选 Ubuntu CLI 或 PyTorch 镜像开机,SSH 进去先跑 check_dependencies.sh——它会点名 g++ ≥ 4.8.3、gfortran、zlib、sox、libtool、automake 这一串,缺哪个装哪个。tools/ 会顺手把 OpenFst 1.8.4 和 sph2pipe 编好。有个坑要提前避开:CUDA 12.x 要求 GCC 低于 12.3,Ubuntu 24.04 自带的 g++-13 会被 configure 直接顶回来,先装 g++-12 再用 CXX 指过去。仓库里那套 CMake 构建官方自己都写着「可能没有充分测试,部分功能缺失」,老老实实走 tools/ + src/ 这条路。
cd kaldi/tools && extras/check_dependencies.sh && make -j$(nproc) && cd ../src && ./configure --shared --use-cuda=yes --cudatk-dir=/usr/local/cuda && make -j$(nproc) depend && make -j$(nproc) - 02
建解码图——chain 模型的头号翻车点
mkgraph.sh 的 --self-loop-scale 默认值是 0.1,那是给老式 GMM / nnet3 用的。chain 模型必须传 1.0,脚本自己都会打出「WARNING: chain models need '--self-loop-scale 1.0'」,但这行警告太容易被刷屏刷掉,结果就是 WER 高得莫名其妙、查半天查不出原因。这一步吃 CPU 和内存,不吃显存:LG → CLG → HCLGa → HCLG 四段合成里,大 n-gram 的 determinize 是整条流水线的内存峰值,节点内存不够就在这里炸。
utils/mkgraph.sh --self-loop-scale 1.0 data/lang_test_tgsmall exp/chain/tdnn_1d_sp exp/chain/tdnn_1d_sp/graph_tgsmall - 03
开 GPU 批量解码,并且先把默认值调下来
batched-wav-nnet3-cuda2 的出厂默认是 --max-batch-size 400、--num-channels 600,而上游 README 只承诺 16GB 卡约 200 路并发——在 T4 上照默认跑必然爆显存。先按 4 控制线程 × batch 50 起步,实测稳定了再往上顶。另一个默认值同样反直觉:--cuda-use-tensor-cores 默认是 false,官方注释写明它启用 FP16 张量运算、只推荐用于推理,你不显式打开,T4 / A100 的张量核就一直闲着。还有 README 那句原话得记住:单条音频喂不满任何一张 NVIDIA GPU,必须并发。
batched-wav-nnet3-cuda2 --cuda-use-tensor-cores=true --max-batch-size=50 --num-channels=200 --cuda-decoder-copy-threads=2 --frame-subsampling-factor=3 --acoustic-scale=1.0 final.mdl HCLG.fst scp:wav.scp ark:lat.ark - 04
要 SOTA 效果,切到 Next-gen Kaldi 这条线
主线 Kaldi 在 AISHELL-1 上 chain 模型的 CER 是 7.48%,icefall 的 Zipformer-small 在同一测试集上是 4.97%,large 4.49%,CR-CTC 那档 3.98%。这个差距不是调参能补回来的。k2 的 wheel 国内直接换 -cn 镜像索引,训练命令照抄 RESULTS.md 就行;训完导出 ONNX,交给 sherpa-onnx 上线,同一套模型能一路铺到 Android、iOS 和鸿蒙。
pip install k2 -f https://k2-fsa.github.io/k2/cuda-cn.html && ./zipformer/train.py --world-size 2 --num-epochs 60 --start-epoch 1 --use-fp16 1 --exp-dir zipformer/exp --max-duration 1200
一笔算得清的账
先说训练。icefall 官方 AISHELL-1 Zipformer-small 的配置是 --world-size 2 --num-epochs 60 --max-duration 1200,跑在两张 32GB V100 上。NexGPU 的 Tesla V100 32GB 是 $0.188/卡·时,两张就是 $0.376/时:连跑 48 小时 = $0.376 × 48 = $18.05,跑 72 小时 = $0.376 × 72 = $27.07。想把 --max-duration 顶上去换更短的墙钟时间,换两张 A100 PCIE 80GB($0.824/卡·时)= $1.648/时,同样 48 小时 = $79.10。 再说解码。主线 Kaldi 的 batched-wav-nnet3-cuda2 在一张 Tesla T4 16GB($0.298/时)上按上游刻度挂 200 路并发,连跑 24 小时 = $0.298 × 24 = $7.15;换 RTX A6000 48GB($0.817/时)把 --num-channels 顶到默认的 600,24 小时 = $19.61——路数翻三倍,单位并发成本反而更低。 最后是数据落盘。特征加 egs dump 按 200GB 算,存储 $0.414/GB·月 × 200 = $82.80/月,折合 $2.76/天。这里有个必须记住的规则:实例一停计算立刻停止计费,存储要销毁才停,所以 egs 跑完就清。出网 $0.0081/GB,把 400MB 的模型和 lattice 拷回本地是 0.4 × $0.0081 ≈ $0.003,四舍五入等于零。全程没有起租时长、没有开通费、不用申请配额。
04 —
