Transformers库新增llama.cpp量化模型支持,可在Hugging Face生态内直接加载并运行Q4/Q5/Q8等格式的量化模型,降低本地推理门槛。
在笔记本上运行 AI 模型已经变得容易很多,llama.cpp 一直是推动这一进步的重要力量。它的推理引擎为 Ollama、LM Studio 和 Jan 等本地 AI 工具提供支持。与 MLX 等项目一起,llama.cpp 让本地推理成为了日常使用的实用选择。
本地 AI 体验的一个近期案例:
这就是我们目前所处的阶段。不得不说,这感觉相当神奇 🧙♀️ Qwen3.6 27B 通过 Llama.cpp 运行在 MacBook Pro 上的 Pi 编程智能体中。对于 @huggingface 代码库中的非平凡任务,这感觉已经非常接近达到最新 Opus 在 Claude 中的表现了…
GGUF 是由 llama.cpp 团队开发的格式,是本地推理中广泛使用的格式。该团队也在 Hub 上的 ggml-org 下分享量化后的检查点。Unsloth、LM Studio Community 和 bartowski 等发布者也提供各种量化级别的即用型 GGUF 检查点,用户可以选择适合自己机器的版本。GGUF 模型的下载量已达数百万次。
我们希望也能让在本地使用 transformers 运行这些模型变得更加容易。兼容性只有在模型运行起来令人愉悦时才有用。为了使性能接近 llama.cpp,我们通过 kernels 库复用其底层的 ggml 内核,并在生成过程中减少开销。我们最初的重点是 Apple Silicon 上的本地推理,从 Qwen3.5 架构开始。
GGUF 将模型权重和元数据(包括分词器信息和可选的聊天模板)打包在一个文件中。它支持不同的量化级别,让用户可以用一定的精度换取更小的内存占用。Q4_K_M 等变体混合了张量精度,使用大部分 4 位权重,同时将敏感张量保持在更高精度。
以下是量化如何改变 Unsloth 的 Qwen3.5-4B 文件大小:
我们建议从 Q4_K_M 开始,然后在内存充足时尝试 Q5_K_M 或 Q6_K。更激进的量化可以帮助更大的模型适应内存,但质量权衡取决于模型和任务。请在你实际想让模型完成的工作上评估它。Hub 的 GGUF 文档描述了可用的量化类型。
要开始使用,你需要:
一台 Apple Silicon Mac。
PyTorch 版本支持已发布的 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。
无需额外配置:当权重保留在 Metal 上打包时,transformers 会自动加载兼容的 ggml/Metal 层内核,并使用 ggml-org/ggml-attn 作为注意力实现。如果无法获取该内核,模型会回退到 "sdpa" 并发出警告,你也可以通过显式传递 attn_implementation="sdpa" 来强制使用 "sdpa"。更多加载选项请参阅 GGUF 文档。
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
)
这是唯一的 GGUF 特定步骤。此后的所有操作都是标准的 transformers API:
messages = [{"role": "user", "content": "Explain why the sky is blue in a few sentences."}]
inputs = tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_dict=True,
return_tensors="pt",
).to(model.device)
with torch.inference_mode():
outputs = model.generate(**inputs, max_new_tokens=256)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
如果没有兼容的量化内核,加载器会回退到对模型进行反量化,从而使用更多内存。
你也可以将相同的检查点与 transformers serve 一起使用,它暴露了一个 OpenAI 兼容的 API:
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 仓库(unsloth/Qwen3.5-4B-GGUF),冒号后是要加载的文件(Qwen3.5-4B-Q4_K_M.gguf)。这可以从包含多个量化版本的仓库中选择特定的量化版本。
对于聊天模板支持思考模式的模型,添加 --reasoning off 跳过它,或 --reasoning on 启用它。默认值 --reasoning auto 遵循聊天模板的默认设置。有关详细信息请参阅 reasoning 选项。
你可以通过添加自定义 OpenAI 兼容提供者来连接 Jan 或 Pi 等客户端,配置如下:
transformers 在你的 Mac 上运行模型,而客户端提供对话界面。相同的端点可以被其他支持此 API 的客户端使用。
我们对本地推理性能的参考标准是 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 的生成速率,在三次重复中取平均值,不包括提示处理。transformers 列是 generate 生成相同的 128 个 token(来自 12 个 token 的提示,三次预热运行中的最佳成绩),并包含 prefill。
测试设备:MacBook Pro M2 Max,32 GB 统一内存,macOS 26.6,PyTorch 2.12.1,kernels 0.17.0,接电源。
import time
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_id, filename = "unsloth/Qwen3.5-4B-GGUF", "Qwen3.5-4B-Q4_K_M.gguf"
model = AutoModelForCausalLM.from_pretrained(model_id, gguf_file=filename)
tokenizer = AutoTokenizer.from_pretrained(model_id, gguf_file=filename)
inputs = tokenizer("The capital of France is Paris. The capital of Germany is", return_tensors="pt")
inputs = inputs.to(model.device)
with torch.inference_mode():
model.generate(**inputs, max_new_tokens=8, min_new_tokens=8, do_sample=False) # warm up
torch.mps.synchronize()
for _ in range(3):
time.sleep(90) # let the machine cool: back-to-back runs decay by 10% or more
start = time.perf_counter()
model.generate(**inputs, max_new_tokens=128, min_new_tokens=128, do_sample=False)
torch.mps.synchronize()
print(f"{128 / (time.perf_counter() - start):.1f} tok/s")
对于另一列:
llama-bench -hf unsloth/Qwen3.5-4B-GGUF:Q4_K_M -p 0 -n 128 -r 3
transformers 在所有三个检查点上都接近 llama.cpp 的表现。图表使用了上述相同的测量方法;它并不意味着基准测试条件完全相同,因为 Transformers 的测量包含 prefill,而 llama-bench 报告的是仅解码吞吐量。
当 GGML 和 llama.cpp 加入 Hugging Face 时,我们描述了它们互补的角色:llama.cpp 为本地推理提供基础,而 transformers 为模型定义提供基础。GGUF 支持让这两者走得更近。
当你的首要目标是高效的本地推理时,llama.cpp 仍然是我们推荐的引擎。它专门的运行时、内存管理和广泛的硬件支持都是围绕这个目标构建的。这个集成让开发者可以在 transformers 内以便捷的方式使用相同的 GGUF 检查点:
在 Python 和 PyTorch 中实验 GGUF。使用 hooks 检查中间激活,修改模型的前向传播,或使用熟悉的 PyTorch 工具原型化自定义层。
评估 GGUF 模型。使用你现有的 transformers 评估工作流来测量量化检查点的质量。
验证 GGUF 转换。对我们开发者来说,在 transformers 中加载原始 checkpoint 及其 GGUF 转换版本,可以更容易地检查权重转换是否正确,同时考虑量化误差。
尝试新的解码思路。使用自定义的 logits 处理器和停止条件配合 generate,或者用 Python 写自己的生成循环。
从 GGUF checkpoint 微调。将权重反量化,然后继续使用标准的 transformers 训练流程。
对于最后一种情况,使用 GgufConfig(dequantize=True):
import torch
from transformers import AutoModelForCausalLM, GgufConfig
model = AutoModelForCausalLM.from_pretrained(
"unsloth/Qwen3.5-4B-GGUF",
gguf_file="Qwen3.5-4B-Q4_K_M.gguf",
quantization_config=GgufConfig(dequantize=True),
dtype=torch.bfloat16,
)
超越 GGUF:更多模型的 ggml 内核
更大的机会在于将 ggml 的性能带到 llama.cpp 不支持的模型中。
transformers 已经提供了这些架构的 PyTorch 实现。有了 PyTorch 中可用的 ggml 内核和量化方案,我们可以致力于加速其支持的操作,而无需首先在 llama.cpp 中实现整个模型。这对于新架构、研究模型和可能永远不会获得专用 llama.cpp 实现的自定义变体特别有用。
这个机会超越了 GGUF 格式本身。内核操作张量;它不要求整个模型来自 GGUF 文件。相同的构建块可以集成到其他 transformers 模型和加载工作流中。这也开辟了通往其他模态的道路:计算机视觉模型、音频模型和多模态模型可以重用兼容的注意力机制、归一化和矩阵乘法内核,而无需首先在 llama.cpp 中有完整实现。每个架构仍然需要集成和验证;这里最初的 GGUF 示例涵盖文本生成。
使用 Python 和 PyTorch 实现快速的本地推理
我们还想展示如何在将模型和生成循环保留在 Python 中的情况下走得更远。有了正确的内核和高效的生成循环,Python 和 PyTorch 可以提供强大的本地推理性能。内核处理繁重的计算,而生成循环通过避免不必要的同步来保持 GPU 忙碌。
我们的重点是让 eager 执行变得快速,而不需要 torch.compile。对于交互式使用,我们希望快速启动并稳定地流式输出 token,而不出现编译停顿或输入形状变化时的重新编译。这项工作的两个主要部分是内核和 generate 本身。
复用 ggml 的 Metal 内核
内核是在 GPU 上执行操作的小程序。PyTorch 提供通用实现;专用内核可以减少工作量、合并多个操作,或以其存储格式直接读取量化权重。
kernels 库让我们可以分发与 ggml 的 Metal 内核兼容的构建版本,放置在 Hub 上并从 transformers 调用。这将 ggml 的工作带入 PyTorch 模型,而无需用单独的推理运行时替换模型。
前四个包建立在 ggml 的内核之上;top-k 内核解决了 MoE 路由中的另一个瓶颈。它们共同减少了每个生成 token 所需的 GPU 工作量。
为了展示层内核的贡献,我们比较了相同的打包 GGUF checkpoint 在有内核和无内核情况下的表现。量化内核在两种配置中都保持启用:禁用它也会改变权重的表示方式,并会衡量不同的权衡。
保持 CPU 和 GPU 协同工作
更快的内核只有在 GPU 有工作要做时才有帮助。在生成期间,CPU 调度 GPU 操作并控制产生下一个 token 的循环。从 GPU 读取结果会强制 CPU 等待排队操作完成。即使每个 token 只重复一次小等待,也会明显降低吞吐量。
两个改动在 generate 中解决了这个问题,结果改善了所有 transformers 模型(不仅是运行 GGUF 文件时):
提前丢弃不必要的注意力掩码(#48814)。当支持的仅解码器输入没有填充时,其全一填充掩码可以在生成开始时移除。下游注意力代码不再需要重复检查该掩码以确定是否可以跳过。仍然保留因果注意力。
延迟停止检查(#47975)。在支持的路径上,generate 异步复制停止决策,并在下一步消费它。CPU 可以在 GPU 运行的同时继续调度工作。流式输出 token 使用相同的方法,任何超出停止条件的额外步骤都会从结果中移除。
这些改动改善了围绕模型生成的循环,因此它们的实用性超越了 GGUF。它们补充了内核工作:内核降低操作的成本,而更少的同步点让 CPU 调度和 GPU 执行可以重叠。
这些测量保留了所有启用的层内核;柱状图隔离了生成循环的改动。
当前局限性和下一步
最初的目标是在 Apple Silicon 上进行单个交互式对话。有几个边界需要记住:
打包推理路径目前仅支持 MPS。通过反量化进行 GGUF 导入仍然是单独的选项;支持文件格式并不意味着打包内核在每个设备上都可用。
填充和批处理仍需改进。无填充输入受益于上述掩码优化。填充批次无法采用相同的快捷方式,性能可能较低。我们希望将这项工作扩展到 MPS 上的 generate_batch。
架构覆盖有限。打包加载器目前覆盖 Qwen3.5 dense 和 MoE 架构,包括兼容的 Qwen3.8 checkpoint。添加对其他架构的支持相对简单,我们将逐步扩大覆盖范围。
如果你有想要在 transformers 中使用的 GGUF 模型,请提交一个 issue,说明 checkpoint 和你的用例。这将帮助我们优先支持人们在本地运行的模型。
我们要感谢 Arthur Zucker 发起这项工作并审查我所有的 PR,感谢 Cyril Vallez 的 generate PR。我们还要感谢 Sayak Paul、llama.cpp 团队和 Bertrand Chevalier 在内核集成方面的帮助。还要感谢 Aritra Roy Gosthipaty 和 Pedro Cuenca 审查了这篇博客文章,以及 Lysandre Debut 对项目的监督。