Hugging Face 让 transformers 直接加载 llama.cpp 的 GGUF 量化权重,在 M2 Max 上跑到 llama.cpp 的九成以上,27B 那一档还反超。值得看的不是又多了一个本地推理方案,而是这段差距究竟是从哪两处补回来的。
2026 年 9 月 22 日,Hugging Face 官方博客发布了《Transformers now runs llama.cpp quants》(作者 Marc Sun、Arthur Zucker、Lysandre Debut,原文:https://huggingface.co/blog/transformers-llama-cpp-quants)。本文基于这篇文章展开,数据与代码均来自原文。
标题读起来像是又一条"本地跑大模型"的教程,但把它当教程读会错过重点。原文里有一句话交代得很清楚:如果你的首要目标是高效的本地推理,llama.cpp 仍然是 Hugging Face 推荐的引擎。真正值得记的是另一件事——一个跑在 Python、eager 模式、没有上 torch.compile 的 PyTorch 模型,把自己和一个专用 C++ 运行时的吞吐差距压进了个位数百分比,在其中一档上还反超了。
transformers 早就能读 GGUF 文件——通过反量化把权重摊回浮点再装进模型,原文也说明这条路径依然保留。这次不一样的地方在于:权重保持打包状态留在 Metal 上参与计算,不必先展开。要做到这一点,Hugging Face 通过 kernels 库复用了 llama.cpp 底层的 ggml 内核,同时削掉了 generate 循环里的开销。
GGUF 是 llama.cpp 团队做的格式,把模型权重、分词器信息和可选的对话模板打包进一份文件,支持多档量化。它早已是本地推理的事实标准:llama.cpp 的推理引擎撑起了 Ollama、LM Studio、Jan 这些工具,ggml-org 在 Hub 上放官方量化版,Unsloth、LM Studio Community、bartowski 等发布方各自提供不同档位的现成权重,累计下载量以百万计。
这次的首个目标很窄:Apple Silicon 上的本地推理,架构从 Qwen3.5 起步。

三个 GGUF 权重在 MacBook Pro M2 Max(32 GB 统一内存)上的生成吞吐,单位 tok/s。图片来源:Hugging Face Blog
| 权重 | 量化档 / 体积 | Transformers | llama.cpp |
|---|---|---|---|
| Qwen3.5-4B | Q4_K_M · 2.74 GB | 70.4 | 71.8 ± 0.4 |
| Qwen3.8-27B | UD-Q4_K_M · 16.5 GB | 15.9 | 13.4 ± 0.9 |
| Qwen3.5-35B-A3B | UD-IQ4_XS · 16.3 GB | 60.2 | 61.3 ± 0.5 |
测试环境是 MacBook Pro M2 Max、32 GB 统一内存、macOS 26.6、PyTorch 2.12.1、kernels 0.17.0,接电源运行。llama.cpp 那一列用 llama-bench(build 5f55650a7,release b10200,Metal 后端来自 ggml 0.18.0),命令是 llama-bench -m <file> -p 0 -n 128 -r 3,报告的是 tg128 指标:解码 128 个 token 的生成速率,三次取平均,不含 prompt 处理。transformers 那一列是 generate 从 12 token 的提示词生成同样 128 个 token,三次热身运行取最好成绩,包含 prefill。
这个方法学差异原文自己点明了:图表不意味着两边的测试条件完全一致。换句话说,别把"70.4 对 71.8"当成精确到小数点的判决,它说明的是量级——同一台机器、同一份权重,两条路径已经在同一个区间里了。
至于 27B 那档为什么是 15.9 反超 13.4,原文没有给出解释,这里也不猜。能说的只有现象:三档里两档 transformers 略低,一档略高,误差带最宽的恰好也是这一档(± 0.9)。
对中文读者来说,比"谁快"更有用的是原文做的两组消融——它们把"Python 推理慢"这个笼统印象拆成了两个具体的、可定位的成本。
免费获取企业 AI 成熟度诊断报告,发现转型机会

从最终配置里只关掉层内核(量化内核在两组里都开着)后的吞吐对比。图片来源:Hugging Face Blog
只保留 ggml-quantization、关掉其余层内核,4B 从 70.4 掉到 44.2,27B 从 15.9 掉到 10.5,35B-A3B 从 60.2 掉到 28.8——对应 1.59×、1.51×、2.09× 的差距。这些内核通过 kernels 库以编译好的构建产物形式发布在 Hub 上,由 transformers 按需拉取:
| 内核 | 作用 |
|---|---|
ggml-quantization | 直接读打包状态的量化权重做矩阵运算,MoE 里只读被选中的专家;避免每次解码前把整个权重矩阵展开 |
ggml-norm | 融合归一化运算,含 Qwen3.5 / Qwen3.8 用的零中心 RMSNorm |
ggml-attn | 提供 ggml 的 Metal flash attention,覆盖 prompt 处理与 token 解码 |
ggml-gated-delta-net | 加速 Qwen3.5 / Qwen3.8 混合架构中线性注意力层用的 gated delta network |
topk | MoE 逐 token 选专家,把 softmax 与 top-k 路由合并;这一个是 Hugging Face 自己写的 Metal 实现 |
前四个建立在 ggml 的内核之上,topk 针对的是 MoE 路由这个单独的瓶颈。MoE 那一档(35B-A3B)提速倍数最高(2.09×),与这条线索是一致的。

两处 generate 改动前后的吞吐对比,两组均开启全部层内核。图片来源:Hugging Face Blog
第二处成本根本不在模型里,而在模型外面那个循环。生成时由 CPU 调度 GPU 操作并控制"下一个 token"的循环,一旦要把结果从 GPU 读回来,CPU 就得等队列里的操作做完;每个 token 都等上一小下,累积起来就是可观的吞吐损失。原文改了两处:
generate 异步拷贝停止判断、下一步再消费它,CPU 得以继续排布工作而 GPU 保持在跑。流式输出用同样的做法,多跑的那一步会从结果里剔除。效果是 1.42×、1.16×、1.79×。要注意这两张消融图不能相乘:它们各自是从同一个最终配置里拿掉一项,黄色柱都是同一个终点(70.4 / 15.9 / 60.2)。
两处加在一起给出的判断是:所谓"Python 慢"的代价,主要不在 Python 的算术上,而在通用内核要先把量化权重摊开和CPU 与 GPU 之间的同步点。两者都不是语言本身的问题,所以也都能在不换语言的前提下修掉。顺带一提,生成循环这两处改动惠及所有 transformers 模型,不限于 GGUF。
三条,缺一不可:一台 Apple Silicon Mac;一个被已发布的 ggml-quantization 内核构建支持的 PyTorch 版本(通常是最近两个发行版);最新的 transformers(原文发布时还需装 main 分支)和与之兼容的 kernels。
pip install -U "git+https://github.com/huggingface/transformers.git" kernels
GGUF 专属的步骤只有一步:把 Hub 上的 model_id 和文件名作为 gguf_file 传给 from_pretrained。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_id = "unsloth/Qwen3.5-4B-GGUF"
filename = "Qwen3.5-4B-Q4_K_M.gguf"
tokenizer = AutoTokenizer.from_pretrained(model_id, gguf_file=filename)
model = AutoModelForCausalLM.from_pretrained(model_id, gguf_file=filename)
之后全是标准的 transformers API:apply_chat_template 拼对话、generate 生成、decode 解码,与平时无异。
不需要额外配置:当权重以打包状态留在 Metal 上时,transformers 会自动加载匹配的 ggml/Metal 层内核,并用 ggml-org/ggml-attn 作为注意力实现。这里有两个静默降级点值得盯住:
"sdpa"。你也可以显式传 attn_implementation="sdpa" 强制走这条路。这两条意味着"跑起来了"不等于"跑在快路径上"。装完第一件事是看日志里有没有那条 warning,否则你可能拿着 44.2 tok/s 的配置去和别人的 70.4 比。
以 Unsloth 的 Qwen3.5-4B 为例,量化档位对文件体积的影响:
| 档位 | 文件体积 | 取舍 |
|---|---|---|
| BF16 | 8.42 GB | 未量化的参照 |
| Q6_K | 3.53 GB | 比更小的档位保留更多精度 |
| Q5_K_M | 3.14 GB | 体积与精度之间的折中 |
| Q4_K_M | 2.74 GB | 本地推理务实的起点 |
Q4_K_M 这类变体是混合精度:大部分权重用 4 bit,敏感的张量保留更高精度。原文的建议是从 Q4_K_M 起步,内存宽裕再试 Q5_K_M 或 Q6_K;更激进的量化能把更大的模型塞进来,但质量损失取决于模型和任务——要在你真正要它干的活上评,不要看别人的榜。
同一份权重可以直接交给 transformers serve,它暴露的是 OpenAI 兼容接口:
pip install -U "transformers[serving] @ git+https://github.com/huggingface/transformers.git" kernels
transformers serve "unsloth/Qwen3.5-4B-GGUF:Qwen3.5-4B-Q4_K_M.gguf"
模型参数的写法是 <model_id>:<filename>.gguf:冒号前是 Hub 仓库,冒号后是要加载的文件——一个仓库里往往放着好几档量化,靠这个冒号挑。对话模板支持思考过程的模型,可以加 --reasoning off 跳过或 --reasoning on 打开,默认的 --reasoning auto 跟随模板自己的设置。
客户端(如 Jan、Pi)里加一个自定义的 OpenAI 兼容供应商即可:
| 设置项 | 值 |
|---|---|
| Base URL | http://localhost:8000/v1 |
| Model ID | unsloth/Qwen3.5-4B-GGUF:Qwen3.5-4B-Q4_K_M.gguf |
模型在你自己的 Mac 上跑,客户端只负责对话界面。其他支持这套接口的客户端同样可以接。
原文公开的基准脚本里有一行注释值得单独拎出来:每轮之间 time.sleep(90),理由是让机器冷却,背靠背连续跑会衰减 10% 甚至更多。任何人在自己的 Mac 上复现这类数字,都该先抄走这一条——否则你测到的是散热曲线,不是推理性能。
GGML 和 llama.cpp 加入 Hugging Face 时,双方的角色分工被描述为互补:llama.cpp 是本地推理的底座,transformers 是模型定义的底座。GGUF 支持把两者拉近了一步,但没有合并。原文明确列出的用途是五类:
GgufConfig(dequantize=True) 配合 dtype=torch.bfloat16。这五条有一个共同点:它们都是要把模型拆开看的场景。如果你要的只是"本地有个能聊天的模型、一直开着",llama.cpp / Ollama 这条路没有理由换。这不是谦辞,是原文的原话。
原文后半段提到了一个更大的机会:把 ggml 的性能带给 llama.cpp 不支持的模型。
逻辑是这样的:transformers 里已经有这些架构的 PyTorch 实现,而 ggml 的内核和量化方案现在能在 PyTorch 里调用了,于是可以在不先把整个模型在 llama.cpp 里实现一遍的前提下加速它们支持的算子。对新架构、研究模型、以及那些可能永远等不到 llama.cpp 专门实现的自定义变体,这条路径的意义比跑分大得多。
而且这件事不止于 GGUF 格式本身——原文的措辞是:内核作用于张量,它不要求整个模型来自 GGUF 文件。同一批构件可以接进其他 transformers 模型和加载流程,也给视觉、音频、多模态模型复用注意力、归一化、矩阵乘内核开了一条路。当然,每个架构仍需各自集成和验证,这次给出的例子只覆盖文本生成。
把内核做成 Hub 上可分发、可版本化的产物,而不是焊死在某个运行时里——这是这次工作里最可能被低估的一步。
原文列出的边界很诚实,照抄如下:
generate_batch。对国内开发者,可以按手里的机器直接分诊:
用 N 卡或 Windows 的,这一轮先不用动——MPS-only 这条把话说死了,等架构和设备覆盖扩展再说。手里是 Apple Silicon Mac 的,值得花半小时装一遍,但装的目的要选对:不是替掉 Ollama,而是拿到一个能用 PyTorch 拆开看的量化模型。国内团队做垂类微调时最常见的一个断点,正是"手里只剩量化后的 GGUF、想接着训却接不回训练流程",第 5 条用途和 GgufConfig(dequantize=True) 正对着这个断点。
预期也可以校准了:16.5 GB 的 27B 权重在 32 GB 统一内存的 M2 Max 上是 15.9 tok/s 这个量级,4B 是 70 上下,35B 的 MoE 因为每 token 只激活一小部分专家、反而能跑到 60。买机器、定本地部署方案时,这三个数比任何"能不能跑"的说法都更有参考价值。
关注公众号

扫码关注,获取最新 AI 资讯
3 步完成企业诊断,获取专属转型建议
已有 200+ 企业完成诊断