Whisper base 的 145.3 MB 和 73.2 毫秒,被一个 16.9 MB 的模型甩开——Whistle 在浏览器标签页里把语音转文字跑完,首个 token 只要 11.1 毫秒

Cactus Compute 10 月 2 日开源了 Whistle:单个 16.9 MB 文件的语音识别模型,CPU 上跑、零依赖,同一套 C++ 引擎还有浏览器目标(needle.js + needle.wasm),官方 demo 直接写着「16.9 MB · runs in this tab」。对比 Whisper base 的 145.3 MB,它小 8.6 倍;在 Apple M4 Pro 上跑 10 秒音频,首个 token 只要 11.1 毫秒(Whisper base 73.2 毫秒),解码 1,319 token/秒(Whisper base 266)。这意味着网页做语音输入,第一次有机会既不上传音频、也不用先下一个几百兆的模型。

一、Before / After:浏览器语音输入的两条老路

Before:

  • Web Speech API(SpeechRecognition):Chrome 里音频被送到云端识别,断网就废、拿不到逐词时间戳、关键词高亮无从做起,企业内网和隐私场景直接出局。
  • 自带 Whisper:whisper base 145.3 MB、首 token 73.2 毫秒、266 token/秒;想要更小还得再挑 Moonshine tiny v2(41.9 MB、22.8 毫秒、262 token/秒)。前端光是决定「什么时候下模型、下在哪、怎么缓存」就要写几百行。

After(官方在 M4 Pro 上跑 10 秒音频的实测):

模型 体积 首个 token 解码速度
Whistle 16.9 MB 11.1 ms 1,319 tok/s
Whisper base 145.3 MB 73.2 ms 266 tok/s
Moonshine tiny v2 41.9 MB 22.8 ms 262 tok/s

Whistle 比 Whisper base 小 8.6 倍、到首 token 快 6.6 倍。首 token 时间还随片段长度走:5 秒 5.9 ms、10 秒 11.1 ms、30 秒 36.3 ms(Whisper 把所有输入补齐到 30 秒,所以它的首 token 时间是平的)。

二、模型长什么样:编码器 8 层、解码器 8 层、beam 只算一次音频

  • 前端处理:16 kHz 单声道,25 ms 窗 / 10 ms hop 分帧,取 80 个 log-mel 频带,限带 250–3500 Hz,逐通道归一化。30 秒 = 3,000 帧;128 通道、kernel 9 的卷积 stem 把帧数三次减半,剩 375 帧(每帧 80 ms),之后所有阶段都按这个速率走。
  • 编码器:8 个 Simple Attention block(4 条 mHC 残差通道 + Monarch Hadamard MLP 替掉前馈),非因果——3 秒处的帧可以直接注意到 12 秒处的帧。
  • 解码器:8 层、宽度 512、8 个 query 头对 2 个 KV 头,Q/K/V 上有 3 抽头因果卷积,第 3、7 层挂 18,432 槽的 engram 查表。
  • 跨模态那一刀:每层解码器通过门控 cross attention 读编码器,x ← x + σ(g) · softmax(q̂K̂ᵀ/√d)V,K/V 在片段到达时只算一次(375 帧 × 8 层)然后全程复用——所以 5 条 beam 的代价是 5 份转写缓存,而不是 5 遍音频前向。
  • 解码细节:长度归一化 log 概率的 5 beam;Aho-Corasick 自动机做关键词偏置(你传进去的专有名词会随自动机推进被抬高 log 概率);转写上限 320 token;词表 8,192 个文本片段 + 7 个语言 token(检测到的语言本身作为一个 token 吐出来)。静音检测在解码前:低于阈值直接返回空转写和空语言,根本不进 beam search。

三、代码上下文:网页里怎么接

浏览器/Node 目标是同一份产物,needle build --platform wasm 出来的是 needle.js + needle.wasm + needle.h,引擎本身不到 1 MB,权重(16.9 MB 的 whistle.cact)单独下载,这天然适合「引擎随包、权重按需缓存」的前端策略:

// 场景:给产品里的「语音记笔记」按钮做本地转写,音频不出设备
// 1) 首次访问把权重预取进 Cache Storage(引擎随构建产物走,只有这 16.9 MB 要缓存)
await caches.open('stt-v1').then(c => c.add('/models/whistle.cact'));

// 2) 加载引擎 + 权重,一次性初始化
const engine = await loadNeedle({ wasm: '/engine/needle.wasm' });
const model  = await engine.load('/models/whistle.cact');

// 3) 录一段(Whistle 单次最多 30 秒,连续输入用 needle.stream(chunks) 边录边转)
const text = await engine.transcribe(model, clip, {
  word_timestamps: true,           // 每个词带起止时间与概率,可直接做字幕高亮
  keywords: ['Whistle', 'Krzysztof'], // Aho-Corasick 关键词偏置,专有名词不被写歪
  language: 'zh'                   // 不传就自动检测,7 种语言里挑
});

同样的逻辑在 Python 侧是一行:needle.transcribe("clip.wav")["text"],返回值里还带首 token 毫秒数和解码 token/秒;命令行 needle whistle compare 能把同一段音频丢给 Whistle / Whisper / Moonshine 三家并排跑。C API 只有三个入口——needle_load / needle_transcribe / needle_embed,而且引擎不读任何环境变量,所有行为都是编译期默认或显式 flag。

四、它赢在哪、输在哪

赢:在 LibriSpeech test-clean / test-other、SPGISpeech、Earnings-22 和 FLEURS 平均上,Whistle 的词错率低于 Whisper base 和 Moonshine tiny v2;Whistle 的词错率是在 86,174 条语句上实测的,官方还比对了音频校验和与说话人 ID,保证测试音频没进训练/验证集。除了转写,它还输出逐词时间戳(解码器 attention 对齐,带概率)和语音 embedding(每 80 ms 一行,不走解码),后者能在浏览器里做说话人聚类或语义检索。

输(官方自己列的):TED-LIUM、AMI、MLS 平均这三项上 Whisper base 仍然领先——会议、远场、多语言长音频别指望换它。

社区实测(HN 444 分、104 条评论):英文转写被形容为「试图把它说崩也没做到」;但西班牙语会写出不存在的词,墨西哥/委内瑞拉口音的语言检测不准,语速一自然就混词。有评论提醒:这类通用 STT 不等于听写(dictation)模型,老人口音、语气词清理还得靠「本地转写 + 便宜 LLM 清洗」的组合拳。

五、可落地的下一步:一个下午把语音输入搬进浏览器

  1. needle build --platform wasm 拿到 needle.js/needle.wasm,引擎(<1 MB)进构建产物,16.9 MB 权重走 Cache Storage/Service Worker 预取,首屏不阻塞。
  2. getUserMedia 录 30 秒片段(或用 needle.stream 边录边转),关掉 latencyMode: 'interactive' 之外的一切云端调用——音频全程留在设备。
  3. 先用 word_timestamps 做字幕/关键词高亮这个最小场景验证链路,再上 keywords 偏置解决专有名词写歪的问题;会议、长音频继续留在 Whisper base 那一档。

下一步就从「把 Web Speech API 换成本地 Whistle」这个按钮开始:删掉云端依赖的同时,顺手把逐词时间戳这个以前拿不到的能力加进你的产品。

评论区

0 条评论

登录后可评论。

小智·AI工具控 143 阅读