-
auo4 AI 观察 用户
Cactus开源Whistle:16.9MB端侧语音识别模型,7种语言、11毫秒出第一个词,CPU上跑赢Whisper base
一个 16.9MB 的文件,能在没有任何网络、没有 GPU 的普通 CPU 上,把一段 30 秒的语音转成带时间戳的文字。这是 Cactus Compute 在 2026 年 10 月 2 日给出的答案,名字叫 Whistle。
作者是 Jakub Mroz 和 Henry Ndubuaku。模型的定位写得很清楚:手机、可穿戴设备、机器人、智能家居、汽车,以及微控制器。它不是又一个云端 API 的客户端,而是一个塞得进固件里的东西。
权重放在 Hugging Face 的 Cactus-Compute/whistle,许可为 Apache-2.0;推理引擎在 Cactus-Compute/needle3,源码在 GitHub 的 cactus-compute/needle。
一个文件,三件事
Whistle 的全部内容就是一个 16.9MB 的单文件模型,跑在 CPU 上,无额外依赖。它加载进的是和 Needle(Cactus 自家的端侧语言模型引擎)完全相同的 C++ 引擎,用同样的容器格式 .cact、同样的量化方式。
一个模型在设备上完成三件事:
一是转写。 输入 16kHz 单声道音频,单次最多处理 30 秒。支持 7 种语言:英语、德语、法语、西班牙语、意大利语、荷兰语、波兰语(三种罗曼语、三种日耳曼语,加一个唯一的斯拉夫语成员波兰语)。语言默认自动检测,也可以手动指定。
一个细节体现了工程上的克制:静音段的处理。引擎在解码前会先测量整段音频的响度范围,低于阈值就直接返回空转写结果和空语言标记,根本不进入 beam search,而不是硬憋出一句"识别"出来的废话。
二是词级时间戳。 每个词都带上起始时间、结束时间和置信概率,对齐来自解码器自身的注意力。这意味着应用可以按词高亮、跳转、精确剪辑。
三是语音嵌入(speech embedding)。 编码器输出,每 80 毫秒一行,完全不需要先解出文本。这条路径给的是"按语音片段做匹配和检索"的能力——你想在录音里找某个声音,不必先把整段转成文字。30 秒音频对应 375 帧。
架构:基本是复用,只加了一层
Whistle 有意思的地方在于,它没有另起炉灶。
前端:16kHz 单声道,30 秒即 480,000 个采样点。log-mel 分 80 个频带,25 毫秒窗、10 毫秒步长,限制在 250–3500Hz,逐通道归一化,得到 3000 帧。卷积主干 128 通道、卷积核 9,降采样三次,剩下 375 帧,每帧 80 毫秒——后续所有计算都在这个帧率上进行。
编码器:8 个 Simple Attention block,包含 4 条 mHC 残差通路,用 Monarch Hadamard MLP 取代传统的前馈网络。注意力是非因果的——第 3 秒的帧可以看到第 12 秒的帧。
解码器:8 个 Laddered Simple Attention block,宽度 512,GQA 结构 8 个 query 头对 2 个 KV 头,query/key 维度 48、value 维度 64,Q/K/V 上各有一条 3-tap 因果卷积,在第 3 层和第 7 层各做一次 engram 查表(18,432 个槽位)。
编码器和解码器里标注 "shared" 的 block,跑的是 Needle 的原始代码,而不是复制一份。
真正属于语音的部分只有一个:每层解码器添加了一个门控交叉注意力
x ← x + σ(g) · softmax(q̂ K̂ᵀ / √d) V
门控 g 是逐层学习的参数,K 和 V 来自音频。关键在于这些投影在音频到达时只算一次(375 帧 × 8 层),之后整个解码过程复用。结果是 5 条 beam 只需要维护 5 份很短的文本缓存,而不是把音频重跑 5 遍。
解码用 beam search × 5,按长度归一化对数概率打分。
关键词偏置(keyword biasing) 走的是 Aho-Corasick 自动机:你传入的专有名词跟着 beam 一起走,随着自动机推进抬高它们的对数概率。这个对付难拼的人名、地名、产品名很实用。
词表是 8192 个文本片段加 7 个语言 token,转写结果上限 320 token。
还有个 ladder 设计:解码器从第 2 层往上的每一层深度都被训练成了一个可独立部署的模型,加载时用 --audio-depth 选择。编码器永远不裁剪,8 个 block 在任何深度下都完整运行。
数据:小体量下的成绩单
Cactus 给出的数据全部是自测。测试平台是 Apple M4 Pro,输入 10 秒音频,各模型跑在各自官方 runtime 的默认配置下:
| 指标 | Whistle | Whisper base | Moonshine tiny v2 |
|---|---|---|---|
| 首个 token 延迟 | 11.1 ms | 73.2 ms | 22.8 ms |
| 解码速度 | 1,319 token/s | 266 token/s | 262 token/s |
| 模型体积 | 16.9 MB | 145.3 MB | 41.9 MB |
Whisper base 体积是它的 8.6 倍。解码速度大约是另外两者的 5 倍。
Whistle 的首字延迟随音频长度线性增长:5 秒是 5.9ms,10 秒是 11.1ms,30 秒是 36.3ms。Whisper 因为把任何输入都填充到 30 秒,延迟恒定为 73.2ms。
词错误率(WER),Whisper 归一化口径,86,174 条语音上测得:
- LibriSpeech test-clean:4.31%
- LibriSpeech test-other:10.49%
- TED-LIUM:7.61%
- FLEURS(7 语种平均):21.4%
横向比较的话,Whistle 在 LibriSpeech test-clean、test-other、SPGISpeech、Earnings-22 和 FLEURS 均值 上领先 Whisper base;Whisper base 在 TED-LIUM、AMI 和 MLS 均值 上反超。这是一份混合的成绩单,Cactus 自己也照实贴了出来。
数据污染方面,Cactus 表示通过比对音频校验值和说话人 ID,确认所有测试集都没出现在训练集或验证集里。这点外部可以自行验证——权重是公开的。
但需要注意:这些是厂商自报数据,没有第三方独立基准复现,且是在笔记本级芯片上跑的,而不是它宣称要服务的那些低功耗微控制器。
和 Needle 合体:一段语音直接变成工具调用
Whistle 最实用的场景是和 Needle 配合。两者共用同一个 .cact 容器、同一份 Cactus 量化(2 到 4 bit)、同一套 SIMD 算子。
命令行可以这么跑:
# 单独转写
needle --model whistle.cact --audio clip.wav
# 单独跑文本 + 工具
needle --model needle3.cact --tools tools.json --prompt "turn off the kitchen lights"
# 合起来:语音直接进工具调用
needle --model needle3.cact --model whistle.cact --tools tools.json --audio clip.wav
第三种情况下,引擎先转写,再让转写结果对着工具表做回答,最后返回一个 JSON 对象,语音相关字段以 audio_ 前缀区分:
{"function_calls":[{"name":"set_lights","arguments":{"room":"kitchen","on":false}}],
"confidence":0.94,
"audio_text":"turn off the kitchen lights",
"audio_language":"en"}
调用方完全不需要经手中间那一步转写文本。一个二进制文件,一段"关掉厨房灯"的语音,直接就是一个函数调用。
Python 侧安装很简单:
pip install cactus-needle
16kHz 的 WAV 或裸采样,基础包就够了;其他采样率或者要用麦克风,装 [mic] 扩展,它会加上 soxr 和 sounddevice。
import needle
print(needle.transcribe("clip.wav")["text"])
# turn off the kitchen lights
每次调用返回文本、语言、到首个 token 的毫秒数和之后的解码速度。可选参数包括 word_timestamps=True、keywords=["Siobhan", "Krzysztof"]、language="de"。对象形式可以用 needle.Whistle(),方便持有 .cact 或取 embed(audio)。
CLI 还提供了两个工具:needle whistle playground 直接从麦克风转写,needle whistle compare 把同一段音频丢给 Whistle、Whisper 和 Moonshine 并排对比结果和耗时。
部署:17 个预编译目标
Cactus 说引擎覆盖了 17 个预编译目标,包括 macOS、Linux、Android、iOS、watchOS、Windows on ARM、RISC-V、MIPS、浏览器,以及一个 WASI 组件。每个目标文件夹里是 needle 可执行檔、libneedle.a 静态库和 needle.h 头文件,可以加载任意 .cact。
needle download macos-arm64
needle download whistle
./macos-arm64/needle --model whistle.cact --audio clip.wav --audio-word-timestamps
语音相关的 C API 只有三个:needle_load、needle_transcribe、needle_embed。引擎不读环境变量,所有行为要么是编译时的默认值,要么是显式传入的旗标——嵌入式场景下这个设计是有意的。
需要注意的限制
客观地说几条边界:超过 30 秒的录音需要自己切成 30 秒的片段;7 种语言之外的语种不支持(土耳其语就在支持集之外);自转数据未经第三方复现;最丰富的那些 FLEURS 语种平均 21.4% 的 WER 说明多语种表现远不如英语那么稳。
对产品团队来说,真正的待验证问题是准确率在真实麦克风、真实口音和真实背景噪声下的表现——干净的基准测不出这些。但现在就可以拿自己的录音去验:权重在 Hugging Face 上,代码在 GitHub 上,本月就能测完,再决定那份云端转写的合同还值不值得续。
来源:Cactus Compute 官方博客 "Whistle: Speech to Text in 16.9 MB"(Jakub Mroz、Henry Ndubuaku,2026 年 10 月 2 日)、Hugging Face 模型卡 Cactus-Compute/whistle