transformers 支持直接加载 GGUF 量化模型,本地推理性能接近 llama.cpp
我们正在为在 transformers 中高效运行 GGUF 模型添加支持
,这样你就可以通过熟悉的 transformers API,使用适配你笔记本电脑内存大小的检查点。从 Hub 上挑选一个 GGUF,用
from_pretrained
加载它,然后就可以在你自己的机器上开始生成了。
在你的笔记本电脑上运行 AI 模型已经变得容易得多,而 llama.cpp 在其中发挥了重要作用。它的推理引擎为 Ollama、LM Studio 和 Jan 等本地 AI 工具提供支持。与 MLX 等项目一起,它帮助本地推理成为日常使用中切实可行的选择。
本地 AI 能带来怎样的体验,最近就有一个例子:
这就是我们目前的处境。说实话,感觉相当神奇 🧙♀️ Qwen3.6 27B 通过 Llama.cpp 在 MacBook Pro 上运行于 Pi coding agent 之中 对于 @huggingface 代码库上的非平凡任务,这感觉非常、非常接近追上 Claude 中最新的 Opus…… pic.twitter.com/lsIxLoUneU
GGUF 由 llama.cpp 团队开发,是一种广泛用于本地推理的格式。该团队还在 Hub 上的 ggml-org 下分享了量化检查点。 Unsloth 、 LM Studio Community 和 bartowski 等发布者也提供了多种量化级别、开箱即用的 GGUF 检查点,用户可以选择适合自己机器的版本。GGUF 模型已被下载数百万次。
我们也希望让这些模型能更轻松地通过 transformers 在本地运行。兼容性只有在模型运行体验良好时才有意义。为了让性能接近 llama.cpp,我们通过 kernels 库复用了其底层的 ggml 内核,并减少了 generate 中的开销。我们最初的重点是在 Apple Silicon 上进行本地推理,从 Qwen3.5 架构开始。
什么是 GGUF 文件格式?
GGUF 将模型权重和元数据(包括 tokenizer 信息和可选的聊天模板)打包在一个文件中。它支持不同的量化级别,让你可以用一定的精度换取更小的内存占用。诸如 Q4_K_M 之类的变体混合了张量精度,大部分权重使用 4-bit,同时对敏感张量保持更高精度。
以下是量化如何改变 Unsloth 的 Qwen3.5-4B 文件大小的:
我们建议从 Q4_K_M 开始,如果你有更多可用内存,再尝试 Q5_K_M 或 Q6_K 。更激进的量化有助于让更大的模型装得下,但质量上的权衡取决于模型和任务。请在你实际希望模型完成的工作上对它进行评估。 Hub 的 GGUF 文档 介绍了可用的量化类型。
使用 transformers 加载 GGUF
要开始使用,你需要:
· 一台 Apple Silicon Mac 。
· 一个受已发布的 ggml-quantization 内核构建 支持的 PyTorch 版本 ,通常是最近的两个 PyTorch 版本。
· 最新版本的 transformers(目前为 main 分支,直到下一个版本发布)以及一个兼容版本的 kernels 。
要加载 GGUF 模型,请将其 Hub model_id 和文件名作为 gguf_file 传给 from_pretrained 。
无需额外配置:当权重以打包形式保留在 Metal 上时,transformers 会自动加载兼容的 ggml/Metal 层内核,并使用 ggml-org/ggml-attn 作为注意力实现。如果无法获取该内核,模型会回退到 "sdpa" 并给出警告,而你始终可以通过显式传入 "sdpa" 来强制使用 attn_implementation="sdpa" 。有关更多加载选项,请参阅 GGUF 文档 。
这是唯一一个 GGUF 专属的步骤。之后的一切都是标准的 transformers API:
如果没有兼容的量化 kernel,加载器会回退到对模型进行反量化,并占用更多内存。
用你偏好的接口来部署 GGUF
你也可以在 transformers serve 中使用同一个 checkpoint,它会暴露一个兼容 OpenAI 的 API:
model 参数使用 <model_id>:<filename>.gguf :冒号之前是 Hub 仓库( unsloth/Qwen3.5-4B-GGUF ),冒号之后是要加载的文件( Qwen3.5-4B-Q4_K_M.gguf )。这样可以从一个可能包含多个量化版本的仓库中选定某个特定的量化。
对于聊天模板支持思考的模型,添加 --reasoning off 可跳过思考,添加 --reasoning on 可启用思考。默认值 --reasoning auto 遵循聊天模板的默认设置。详情请参阅 推理选项 。
你可以通过添加一个自定义的兼容 OpenAI 的提供商来连接诸如 Jan 或 Pi 这样的客户端,设置如下:
transformers 在你的 Mac 上运行模型,而客户端提供对话界面。其他支持该 API 的客户端也可以使用同一个端点。
与 llama.cpp 进行基准对比
我们衡量本地推理性能的参照是 llama.cpp。下面的对比聚焦于三个 GGUF 检查点:一个小型稠密模型、一个较大的稠密模型,以及一个混合专家模型。
llama.cpp 一列的数据来自 llama-bench 工具(构建版本 5f55650a7 ,发布版 b10200,来自 ggml 0.18.0 的 Metal 后端),以 llama-bench -m <file> -p 0 -n 128 -r 3 方式运行,它报告的是 tg128 :在 128 个解码 token 上的 token 生成速率,取三次重复的平均值,且不包含提示词处理。transformers 一列则是 generate 从 12 个 token 的提示词生成同样的 128 个 token,取三次预热运行中的最佳值,并且包含预填充。
在 MacBook Pro M2 Max、32 GB 统一内存、macOS 26.6、PyTorch 2.12.1、kernels 0.17.0、接通电源的条件下测得。
基准测试脚本
另一列的情况:
在全部三个检查点上,Transformers 都接近 llama.cpp。图表使用的是上文所述的同一批测量数据;它并不意味着基准测试条件完全相同,因为 Transformers 的测量包含预填充,而 llama-bench 报告的是仅解码吞吐量。
transformers 与 llama.cpp
当 GGML 和 llama.cpp 加入 Hugging Face 时,我们描述了它们互补的角色:llama.cpp 为本地推理提供基础,而 transformers 为模型定义提供基础。GGUF 支持让这两者更加紧密地结合在一起。
当你的优先目标是高效的本地推理时,llama.cpp 仍然是我们推荐的引擎。 其专用运行时、内存管理以及广泛的硬件支持都是围绕这一目标构建的。这一集成让开发者可以方便地在 transformers 中使用相同的 GGUF 检查点:
· 在 Python 和 PyTorch 中试验 GGUF。 使用 hook 检查中间激活值,修改模型的前向传播,或使用熟悉的 PyTorch 工具原型化自定义层。
· 评估 GGUF 模型。 使用你现有的 transformers 评估工作流来衡量量化检查点的质量。
· 验证 GGUF 转换。 对于我们开发者而言,在 transformers 中加载原始检查点及其 GGUF 转换版本,可以更容易地检查权重是否正确转换,同时考虑量化误差。
· 尝试新的解码思路。 使用自定义 logits 处理器和停止条件配合 generate ,或用 Python 编写你自己的生成循环。
· 从 GGUF 检查点进行微调。 将权重反量化,然后继续使用标准的 transformers 训练工作流。
对于最后一种情况,请使用 GgufConfig(dequantize=True) :
超越 GGUF:为更多模型提供 ggml 内核
更大的机会在于将 ggml 的性能带给 llama.cpp 不支持的模型。
transformers 已经提供了这些架构的 PyTorch 实现。借助 PyTorch 中可用的 ggml 内核和量化方案,我们可以着手加速其支持的操作,而无需先在 llama.cpp 中实现整个模型。这对于新架构、研究模型以及可能永远不会获得专门 llama.cpp 实现的自定义变体尤其有用。
这一机会超越了 GGUF 格式本身。内核作用于张量;它并不要求整个模型都来自 GGUF 文件。相同的构建模块可以集成到其他 transformers 模型和加载工作流中。这也为其他模态开辟了道路:计算机视觉模型、音频模型和多模态模型可以复用兼容的注意力、归一化和矩阵乘法内核,而无需先在 llama.cpp 中拥有完整实现。每个架构仍然需要集成和验证;这里的初始 GGUF 示例涵盖的是文本生成。
使用 Python 和 PyTorch 实现快速本地推理
我们还希望展示,在将模型和生成循环都保留在 Python 中的情况下,我们能走多远。 有了合适的 kernel 和高效的生成循环,Python 和 PyTorch 能够提供强劲的本地推理性能。 kernel 负责处理繁重的计算,而生成循环则通过避免不必要的同步来让 GPU 保持忙碌。
我们的重点是让 eager 执行变得快速,而无需依赖 torch.compile 。对于交互式使用,我们希望快速启动并获得稳定的 token 流,不出现编译暂停,也不会在输入形状变化时重新编译。这项工作的两个主要组成部分是 kernel 和 generate 本身。
复用 ggml 的 Metal kernel
kernel 是在 GPU 上执行某种运算的小型程序。PyTorch 提供通用实现;专用 kernel 可以完成更少的工作、合并多个运算,或直接以存储格式读取量化权重。
kernels 库让我们能够在 Hub 上分发 ggml Metal kernel 的兼容构建版本,并从 transformers 中调用它们。这将 ggml 的工作成果引入 PyTorch 模型,而无需用单独的推理运行时来替换该模型。
前四个包构建在 ggml 的内核之上;top-k 内核解决了 MoE 路由中的另一个瓶颈。它们共同减少了每个生成 token 所需的 GPU 工作量。
为了展示层内核的贡献,我们将相同的打包 GGUF 检查点在启用与不启用这些内核的情况下进行了对比。两种配置中量化内核都保持启用:禁用它也会改变权重的表示方式,从而衡量的是另一种不同的权衡。
让 CPU 与 GPU 协同工作
更快的内核只有在 GPU 有活可干时才有帮助。在生成过程中,CPU 负责调度 GPU 操作,并控制产生下一个 token 的循环。从 GPU 读回结果会迫使 CPU 等待排队的操作完成。即便每个 token 只重复一次很小的等待,也会明显降低吞吐量。
在 generate 中,有两项改动解决了这一问题,它们为所有 transformers 模型带来了改进(而不仅仅是在运行 GGUF 文件时):
· 尽早移除不必要的注意力掩码(#48814) 。[ 当受支持的仅解码器输入没有填充时,其全为 1 的填充掩码可以在生成开始时移除。下游的注意力代码不再需要反复检查该掩码以判断是否可以跳过它。因果注意力仍然得到保留。
· 延迟停止检查(#47975) 。[ 在受支持的路径上, generate 会异步复制停止决策,并在下一步消费它。CPU 可以在 GPU 运行的同时继续调度工作。流式 token 也采用相同的方式,并且任何超出停止条件的额外步骤都会从结果中移除。
这些改动改进了模型周围的生成循环,因此其价值不仅限于 GGUF。它们与内核层面的工作相辅相成:内核降低了单个运算的成本,而更少的同步点则让 CPU 调度与 GPU 执行得以重叠。
这些测量保持所有层内核启用;柱状图隔离出的仅是生成循环中的改动。
当前局限与后续步骤
最初的目标是在 Apple Silicon 上实现单轮交互式对话。有几点边界需要留意:
· 目前打包推理路径仅支持 MPS。 通过反量化导入 GGUF 仍是一个独立选项;支持该文件格式并不意味着打包内核在每台设备上都可用。
· 填充与批处理仍需完善。 无填充输入可受益于上文所述的掩码优化。带填充的批次无法走同样的捷径,性能可能更低。我们希望将这项工作扩展到 MPS 上的 generate_batch 。
· 架构覆盖范围有限。 打包加载器目前覆盖 Qwen3.5 的稠密与 MoE 架构,包括兼容的 Qwen3.8 检查点。添加对其他架构的支持相对直接,我们将逐步扩大覆盖范围。
如果你有一个想在 transformers 中使用的 GGUF 模型,请 提交一个 issue ,附上 checkpoint 和你的使用场景。这将帮助我们优先支持大家正在本地运行的模型。
致谢
我们要感谢 Arthur Zucker 发起这项工作并审阅了我所有的 PR,以及 Cyril Vallez 提供的 generate 个 PR。我们感谢 Sayak Paul 、 llama.cpp 团队 和 Bertrand Chevalier 在集成这些 kernel 方面提供的帮助。我们还要感谢 Aritra Roy Gosthipaty 和 Pedro Cuenca 审阅这篇博客文章,以及 Lysandre Debut 监督该项目。
