一段代码,改变了一亿设备的语音识别方式

2022 年 9 月,OpenAI 开源了 Whisper 模型,一个在大规模弱监督数据上训练的语音识别系统。模型很强大——接近人类水平的准确度、支持 99 种语言、自动语言检测——但一个明显的问题是:它太重了。官方 Python 实现的 Transformer 架构,仅加载 large 模型就要吃掉 3GB+ 显存,在 CPU 上推理更是以分钟计。

一个月后,一位名叫 Georgi Gerganov 的独立开发者,用纯 C/C++ 重写了 Whisper 的推理代码。仓库名很简单:whisper.cpp。

三年半后的今天,这个项目拥有 49,600+ 颗星、6,400 次提交,被集成到从 iOS 原生应用到嵌入式 Linux 设备的无数产品中。macOS 上最流行的离线转录工具 MacWhisper、视频编辑软件中的自动字幕生成、智能录音笔的实时转写——底层跑的几乎都是 whisper.cpp。

但真正让这个项目与众不同的,不是它"让 Whisper 能在 CPU 上跑"这个表面结果,而是它从架构设计上重新思考了"一个现代 ASR 推理引擎应该长什么样"。

当 329KB C 代码承载一个大型 Transformer

大多数深度学习推理框架倾向于大而全——TensorFlow Lite 几百 MB,ONNX Runtime 上百 MB,即使是轻量级的 NCNN 也有几十 MB。whisper.cpp 的核心推理代码,单文件 whisper.cpp 只有 329KB。

这不是因为代码缩成一团。而是因为它精确地为 Whisper 模型定制,不包含任何通用框架的抽象层开销。

整个代码库的层次结构非常清晰:

whisper.h          — C API,900+ 行,定义完整的外部接口
whisper.cpp        — 核心推理引擎,模型加载、编码、解码
whisper-arch.h     — 张量命名规范,定义 encoder/decoder 各层的 tensor 名称
ggml.h / ggml.c    — 张量计算库,whisper.cpp 的底层引擎
ggml-*.h           — 各硬件后端的加速实现

关键设计决策:whisper.cpp 不依赖任何第三方深度学习框架。它的底层计算库 ggml 是自研的,从一开始就针对 Transformer 架构的计算图做了优化。这意味着整个推理栈从矩阵乘法到内存分配全部可控,没有任何黑盒。

ggml:为 Transformer 推理而生的张量库

ggml 的设计哲学是"跨平台 + 高性能 + 零外部依赖"。它没有用 Eigen、Xbyak 或任何第三方数学库,纯 C 实现:

  • 自动计算图:每个张量操作自动构建计算图,支持自动求导
  • 量化内核:4-bit、5-bit、8-bit 整数量化,以及标准 FP16/BF16
  • 后端抽象:同一 API 无缝切换 CPU / CUDA / Metal / Vulkan
  • 内存效率:使用内存池避免频繁分配,支持内存映射加载模型

对于一个 Transformer 前向推理来说,核心操作就是矩阵乘法和注意力机制。ggml 对这两类操作做了深度手工优化——用 SIMD 指令集加速 GEMM,用分块策略减少 cache miss。在 Apple Silicon 上,ggml 甚至直接调用 AMX(Apple Matrix coprocessor)的指令,使 M1/M2 芯片的推理速度比纯 NEON 快 3-4 倍。

ggml 本身后来也独立发展,成为 llama.cpp、stable-diffusion.cpp 等流行项目的基础。这反过来验证了 ggml 的设计:它不是 whisper.cpp 的附属品,而是一个被多项目验证的通用推理库

模型架构:从 log-Mel 频谱到文本

Whisper 本质上是一个 Encoder-Decoder Transformer。whisper.cpp 严格遵循这个架构,但在工程实现上有自己的取舍。

输入处理:

原始音频首先被重采样到 16kHz,然后经过 25ms 窗口(10ms 步长)的短时傅里叶变换,生成 80 维的 log-Mel 频谱。whisper.cpp 内置了完整的 DSP 链——从音频重采样到 FFT 到 Mel 滤波,全部在 C++ 内完成,不依赖 FFmpeg 或 sox(但也可以通过 FFmlegate 读取多种音频格式)。

Encoder 部分(音频理解):

conv1d 层 (kernel=3, stride=1)
    → GELU 激活
    → conv1d 层 (kernel=3, stride=2)
    → GELU 激活
    → 位置编码
    → N × Transformer 块 (self-attention + FFN)
    → 跨注意力 KV 缓存输出

Encoder 的输出维度固定为 n_audio_ctx × n_audio_state,其中 n_audio_ctx = 1500(对应 30 秒音频,每 20ms 一个 token)。这也是 Whisper 的单次推理窗口上限——超过 30 秒的音频需要分段处理。

Decoder 部分(文本生成):

Decoder 的工作方式和 GPT 类似——自回归地逐个 token 生成文本。但在标准的因果自注意力之外,每个 Decoder 层还有一个跨注意力层,用于关注 Encoder 输出的音频特征。

SOT token(起始标记)
    → N × Transformer 块 (self-attention + cross-attention + FFN)
    → 语言模型头(softmax over 51864 词表)
    → 采样下一个 token
    → 循环直到 EOT token 或最大长度

这个自回归循环是推理的主要计算瓶颈。whisper.cpp 的优化策略包括:

  1. KV Cache:跨注意力层的 Key/Value 结果只计算一次(来自 Encoder 输出),Decoder 的 self-attention KV 则逐 token 追加
  2. 批处理解码:beam search 时多个候选序列共享 Encoder 输出
  3. Prompt Caching--prompt-cache 选项把 Encoder 输出序列化到磁盘,重复转录同一段音频时跳过编码阶段

五档模型与它们的资源画像

Whisper 提供了 5 个规格的模型,从 75MB 到 2.9GB。whisper.cpp 把它们全部转换成了 ggml 格式,内存占用和推理速度如下:

模型 参数量 磁盘 推理内存 相对速度
tiny 39M 75MB ~273MB 32x
base 74M 142MB ~388MB 16x
small 244M 466MB ~852MB 6x
medium 769M 1.5GB ~2.1GB 2x
large 1.55B 2.9GB ~3.9GB 1x

"相对速度"是相对于 large 模型的单线程 CPU 推理延迟。tiny 模型在 Apple M1 上可以达到 实时率 0.1x——即转录 10 秒音频只需 1 秒 CPU 时间。large 模型则需要大约 10 秒。

关键的 HParams(以 tiny 为例):

n_vocab       = 51864   # 多语言词表大小
n_audio_ctx  = 1500     # 音频上下文长度 (30s)
n_audio_state = 384     # 音频特征维度
n_audio_head  = 6       # 注意力头数
n_audio_layer = 4       # Encoder 层数
n_text_ctx    = 448     # 文本上下文长度
n_text_state  = 384     # 文本特征维度
n_text_head   = 6       # 注意力头数
n_text_layer  = 4       # Decoder 层数

随着模型规格增长,n_audio_state 从 384 扩展到 1280,n_audio_layer 从 4 增加到 32。large 模型拥有 32 层 Encoder 和 32 层 Decoder,每个注意力头的维度也相应增大。

.en 变体:每个模型都有对应的 .en 版本。它们是为纯英文场景设计的——词表缩小、去除了多语言相关 token,推理速度更快且英文准确率略高。如果你的使用场景只涉及英文,.en 版本是更好的选择。

量化:用精度换速度的艺术

全精度 FP16 模型是大模型推理的奢侈选项。在 edge 设备上,量化几乎是必须的。whisper.cpp 支持一个"压缩调色盘"的量化选项:

q5_0 — 5-bit 块量化,每 32 个权重共享一个缩放因子
q5_1 — 5-bit 块量化,每 32 个权重共享两个缩放因子(更高精度)
q8_0 — 8-bit 块量化

其中 Q5_0 是最常用的选择——large 模型从 2.9GB 降至约 1.1GB,准确率损失几乎不可察觉(WER 增加不到 0.5%)。

量化原理:将 FP16 权重映射到 5-bit 整型范围,每 32 个权重组共享一个 FP16 缩放因子。推理时动态反量化回 FP16 进行矩阵乘法。在大多数 CPU 上,权重加载带宽是瓶颈(而不是计算带宽),所以减少 68% 的权重体积直接转化为 2-3 倍的推理加速。

值得注意的是,whisper.cpp 的量化只在权重加载时做一次。模型保持在量化状态,不需要像一些框架那样在推理时反复量化和反量化。

七种硬件后端:从手机到服务器的全覆盖

这是 whisper.cpp 最令人印象深刻的部分。它不是只写了一套通用矩阵乘法然后指望编译器优化,而是为每种主流硬件架构实现了专门的加速后端:

CPU:

  • x86_64: AVX2 / AVX512 加速 GEMM
  • ARM: NEON SIMD 指令集,Apple Silicon AMX co-processor
  • RISC-V: P 扩展向量指令(实验性)

GPU:

  • CUDA(NVIDIA)— 通过 cuBLAS 和自定义 CUDA kernel,支持 FP16 推理
  • Metal(Apple)— 利用 MPSGraph 和 GPU 家族 2+(A13/M1 以上)特性
  • Vulkan(跨平台 GPU)— 通过自定义 shader 实现矩阵运算

专用加速器:

  • CoreML(Apple Neural Engine)— 利用 ANE 的 16 核神经网络加速器,功耗极低
  • OpenVINO(Intel)— 适配 Intel CPU/iGPU/VPU
  • CANN(华为 Ascend)— 支持昇腾 910/310 芯片
  • MUSA(摩尔线程)— 国产 GPU 加速

推荐的 backend 选择策略:

设备类型 最佳后端 推理功耗
NVIDIA RTX 4090 CUDA FP16 ~150W
Apple M2 Ultra Metal ~30W
Apple A17 Pro CoreML ANE ~1W
Intel Core i7-14700K OpenVINO ~65W
华为 Atlas 300 CANN ~70W
Raspberry Pi 5 CPU (NEON) ~7W
iPhone 15 Pro CoreML ANE <1W

MacWhisper 等桌面应用的成功秘密就在这里:Metal 后端让 M 系列芯片可以在 20-30W 功耗下实时转录,而 CoreML ANE 后端让 iPhone 在转录 30 秒音频时功耗不到 1W。这不是 Whisper 原版的"云上跑"——这是真正的 edge inference。

智能音频处理的四个关键特性

VAD(语音活动检测)

whisper.cpp 集成了 Silero-VAD 模型的 C++ 移植版。Silero 是一个轻量级的 LSTM/RNN 网络,可以逐帧判断是否包含人声。开启 VAD 后,whisper.cpp 会自动跳过静音段落,只对说话部分进行推理。

VAD 的实用价值:

  • 节省计算:在一小时的会议录音中,如果只有 20 分钟在说话,推理量减少 66%
  • 提高准确率:避免模型对背景噪声产生幻觉
  • 实时流式处理:结合 --vad-threshold 参数,VAD 可以替代基于音频能量的简单静音检测
# 仅对有人声的段落进行转录
./whisper-cli --file meeting.wav --vad-thold 0.6

DTW(动态时间规整)词级时间戳

标准的 Whisper 模型只输出句子级时间戳(因为 Decoder 在 token 层面没有时间信息)。whisper.cpp 首创性地引入了 DTW 对齐头(alignment heads),在 Encoder 输出上附加专门的注意力头来学习"每个输出 token 对应音频的哪个时间位置"。

这项工作在技术上很巧妙——它不需要重新训练模型,而是在 whisper.cpp 中通过对 Encoder 的 cross-attention 权重做后处理来提取时间信息。这使得 whisper.cpp 能够输出词级别的精确时间戳,精度可达 10ms 级别。

语法约束采样(Grammar Sampling)

Whisper 原生解码只做简单的贪心搜索或 beam search。whisper.cpp 扩展了基于 GBNF(Georgi’s BNF)语法的约束解码——和 llama.cpp 用的是同一套系统。

实际应用场景:

  • 电话号码转录:约束输出只包含数字和连接符
  • 命令词识别:约束词表为特定指令集
  • 结构化输出:JSON 格式的转录结果
# grammar 文件示例:只转录为中国大陆电话号码
root ::= "1"[3-9][0-9]{9}

tinydiarize:说话人分离的极简方案

传统的说话人分离(diarization)需要独立的嵌入模型和聚类算法,非常复杂。tinydiarize 的思路则激进地简单:在训练数据中插入 <|notdawn|><|dawn|> 标记,让模型自动学习说话人切换。

这种方式做不到"识别谁在说话"(说话人身份),但可以区分说话人是否发生切换。对于会议记录这种只需要"知道这里换了一个人说话"的场景,够用了。而且相对于完整的 diarization 系统,tinydiarize 没有任何额外计算开销——模型在解码时顺带输出标记。

从 C API 到 12 种语言绑定

whisper.cpp 是 C++ 写的,但通过 whisper.h 提供的纯 C API,几乎所有主流语言都可以直接绑定。这个 C API 的设计值得一看。

核心接口:

// 初始化
struct whisper_context * whisper_init_from_file(const char * path_model);
struct whisper_context * whisper_init_from_buffer(void * buffer, size_t size);

// 全量音频转录(从文件到文本的完整 pipeline)
int whisper_full(
    struct whisper_context * ctx,
    struct whisper_full_params params,
    const float * samples,
    int n_samples);

// 流式处理(逐段处理长音频)
int whisper_full_parallel(
    struct whisper_context * ctx,
    struct whisper_full_params params,
    const float * samples,
    int n_samples,
    int n_processors);

// 获取结果
const char * whisper_full_get_segment_text(
    struct whisper_context * ctx,
    int segment_index);

这种设计有两个好处:

  1. 零成本抽象:不需要处理 C++ 的 ABI 兼容性问题
  2. 直接绑定:任何支持 FFI 的语言都可以直接调用

目前社区提供的语言绑定:

语言 包名 安装方式
Rust whisper-rs cargo
JavaScript whisper.cpp npm(WASM + Node.js)
Go whisper go get
Python pywhispercpp / pywhispercpp pip
Java java-whisper.cpp Maven
.NET Whisper.net NuGet
Ruby whispercpp gem
R whisper install.packages

Rust 绑定值得一提。whisper-rs(由 tazz4843 维护)提供了零成本的 Rust 抽象层,同时通过 whisper-sys crate 暴露原始 C API。它被大量 Rust 生态的音频处理工具依赖。

16 个官方示例:从 CLI 到 Twitch 直播

whisper.cpp 的 examples 目录下有 16 个示例程序,覆盖了几乎所有主流使用场景:

示例 说明 关键技术点
main 标准 CLI 转录 VAD, word-level timestamps, JSON output
bench 基准测试 逐层计时,调试性能瓶颈
stream 实时流式转录 PortAudio 实时音频,逐句刷新
command 关键词唤醒 语法约束+实时语音指令
server HTTP API 服务器 HTTP multipart 上传,流式 SSE 返回
talk-llama 语音对话 whisper.cpp + llama.cpp 端到端语音助手
wasm 浏览器转录 WebAssembly 纯前端推理
karaoke 卡拉 OK 字幕 词级时间戳 + LRC/SRT 格式输出
chess 语音下棋 UCI 引擎 + 语音输入

talk-llama 示例其实是一个"语音版 ChatGPT"的完整实现:用户说话 → whisper.cpp 转录 → llama.cpp 生成回答 → 文本转语音。全部在本地运行,纯 CPU 推理,无云依赖。

实际性能:数据说话

基于社区测试数据,whisper.cpp 在不同硬件上的性能表现(使用 small 模型,FP16,单线程 CPU):

硬件 实时率 备注
Apple M1 (8 core) 0.35x Metal 后端,4 线程
Apple M1 (ANE) 0.15x CoreML ANE,极低功耗
Apple M2 Max 0.25x Metal 后端,30 秒音频约 7.5 秒
NVIDIA RTX 4090 0.02x CUDA FP16,30 秒约 0.6 秒
Intel i9-13900K 0.50x AVX512, 8 线程
Raspberry Pi 4 2.5x 4 核 ARM,tiny 模型
Raspberry Pi 5 1.2x 改善明显
iPhone 15 Pro 0.3x CoreML ANE,tiny 模型

对 tiny 模型而言,Apple M1 的实时率可降到 0.1x——转录 30 秒音频只需 3 秒。这意味着 whisper.cpp 已经可以在实时交互场景中使用,而不是只能做离线批处理。

真正重要的:这个项目验证了什么

回顾 whisper.cpp 的成功,我觉得它不只证明了"优化可以大幅提升推理性能"这个显而易见的事实。它让我看到几件更有意思的事:

一,推理框架应该为模型定制,而不是反过来。 大多数框架追求通用性,但通用性意味着抽象和开销。whisper.cpp 只做一件事(运行 Whisper 模型),把这件事做彻底了,结果就是 329KB 的推理代码和工业级的推理速度。

二,量化不是降级,而是适配。 很多人把量化看作"精度换速度"的妥协。但在 edge 场景,没有量化就没有可用性。whisper.cpp 的 Q5_0 方案把 1.5B 参数的大模型压缩到 1.1GB,让 8GB 内存的设备也能运行 large 模型——这里的 tradeoff 不是妥协,而是工程上的适应。

三,C/C++ 的价值没有过时。 在 AI 推理这个领域,Python 是研究语言,C++ 才是产品语言。whisper.cpp 证明了即使是最复杂的 Transformer 模型,也可以用纯手工优化的 C++ 实现出极致的推理性能。不需要 GPU 就一定需要 CUDA——在 CPU 上做到实时推理同样是了不起的工程成就。

四,社区驱动的高质量项目生命周期。 whisper.cpp 从 Georgi 一个人的项目,成长为拥有 350+ 贡献者的社区项目。它的成功让人联想到 SQLite——一个 C 库,做一件事,做到极致,然后成为整个行业的隐形基础设施。

截至 2026 年初,whisper.cpp 的 v1.8.4 版本已经稳定运行在从 iPhone 到服务器的海量设备上。它不是唯一一个让 Whisper 落地的项目,但它是做得最彻底、覆盖范围最广的那一个。

也许几年后 Whisper 模型本身会被更好的架构取代。但 whisper.cpp 留下的遗产——"一个定义清晰的 C API + 多后端硬件加速 + 低依赖部署"的模式——会继续影响 AI 推理引擎的设计方式。

原创技术博客 · 开源项目分享 · AI全栈创作社区 idao.fun

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐