我们正在为在 transformers 中高效运行 GGUF 模型添加支持
,这样你就可以通过熟悉的 transformers API,使用适配你笔记本电脑内存大小的检查点。从 Hub 上挑选一个 GGUF,用
from_pretrained
加载它,然后就可以在你自己的机器上开始生成了。
在你的笔记本电脑上运行 AI 模型已经变得容易得多,而 llama.cpp 在其中发挥了重要作用。它的推理引擎为 Ollama、LM Studio 和 Jan 等本地 AI 工具提供支持。与 MLX 等项目一起,它帮助本地推理成为日常使用中切实可行的选择。
本地 AI 能带来怎样的体验,最近就有一个例子:
这就是我们目前的处境。说实话,感觉相当神奇 🧙♀️
— Julien Chaumond (@julien_c) April 24, 2026
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 文件大小的:
| GGUF 变体 | 文件大小 | 权衡 |
|---|---|---|
BF16 | 8.42 GB | 未量化参考版本 |
Q6_K | 3.53 GB | 精度高于更小的变体 |
Q5_K_M | 3.14 GB | 在体积与精度之间取得折中 |
Q4_K_M | 2.74 GB | 本地推理的实用起点 |
我们建议从 Q4_K_M 开始,如果你有更多可用内存,再尝试 Q5_K_M 或 Q6_K。更激进的量化有助于让更大的模型装得下,但质量上的权衡取决于模型和任务。请在你实际希望模型完成的工作上对它进行评估。Hub 的 GGUF 文档介绍了可用的量化类型。
使用 transformers 加载 GGUF
要开始使用,你需要:
- 一台 Apple Silicon Mac。
- 一个受已发布的 ggml-quantization 内核构建 支持的 PyTorch 版本,通常是最近的两个 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"并给出警告,而你始终可以通过显式传入"sdpa"来强制使用attn_implementation="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))
如果没有兼容的量化 kernel,加载器会回退到对模型进行反量化,并占用更多内存。
用你偏好的接口来部署 GGUF
你也可以在 transformers serve 中使用同一个 checkpoint,它会暴露一个兼容 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 参数使用 <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 这样的客户端,设置如下:
| 设置 | 值 |
|---|---|
| Base URL | http://localhost:8000/v1 |
| 模型 ID | unsloth/Qwen3.5-4B-GGUF:Qwen3.5-4B-Q4_K_M.gguf |
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、接通电源的条件下测得。
基准测试脚本
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)
torch.mps.synchronize()
for _ in range(3):
time.sleep(90)
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 的测量包含预填充,而 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):
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 中的情况下,我们能走多远。有了合适的 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 模型,而无需用单独的推理运行时来替换该模型。
| Kernel | 它的作用 |
|---|---|
ggml-quantization | 为矩阵运算读取打包的量化权重,包括 MoE 模型中被选中的专家。它避免了在每次解码操作前展开整个权重矩阵。 |
ggml-norm | 融合归一化操作,包括 Qwen3.5 和 Qwen3.8 使用的零中心 RMSNorm。 |
ggml-attn | 为提示词处理和 token 解码提供 ggml 的 Metal flash attention。 |
ggml-gated-delta-net | 加速 Qwen3.5 和 Qwen3.8 混合架构线性注意力层中使用的门控 delta 网络。 |
topk | 为 MoE 模型中的每个 token 选择专家,结合了 softmax 和 top-k 路由。这是我们自己的 Metal 实现。 |
前四个包构建在 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监督该项目。
We're adding support for running GGUF models efficiently in transformers
, so you can use checkpoints sized for your laptop's memory through the familiar transformers APIs. Pick a GGUF from the Hub, load it with
from_pretrained
, and start generating on your own machine.
Running AI models on your laptop has become much easier, and llama.cpp has been a big part of that. Its inference engine powers local AI tools such as Ollama, LM Studio, and Jan. Alongside projects like MLX, it has helped make local inference a practical option for everyday use.
A recent example of what local AI can feel like:
This is where we are right now. And i’m not gonna lie it feels pretty magical 🧙♀️
— Julien Chaumond (@julien_c) April 24, 2026
Qwen3.6 27B running inside of Pi coding agent via Llama.cpp on the MacBook Pro
For non-trivial tasks on the @huggingface codebases, this feels very, very close to hitting the latest Opus in Claude… pic.twitter.com/lsIxLoUneU
GGUF, developed by the llama.cpp team, is a widely used format for local inference. The team also shares quantized checkpoints under ggml-org on the Hub. Publishers such as Unsloth, LM Studio Community, and bartowski also provide ready-to-use GGUF checkpoints in a range of quantizations, so users can pick the version that fits their machine. GGUF models have been downloaded millions of times.
We want to make it easier to run these models locally with transformers, too. Compatibility is only useful if the model is pleasant to run. To bring performance close to llama.cpp, we're reusing its underlying ggml kernels through the kernels library, and reducing overhead in generate. Our initial focus is local inference on Apple Silicon, starting with the Qwen3.5 architecture.
What is the GGUF file format?
GGUF packages model weights and metadata, including tokenizer information and an optional chat template, in one file. It supports different quantization levels, letting you trade some precision for a smaller memory footprint. Variants such as Q4_K_M mix tensor precisions, using mostly 4-bit weights while keeping sensitive tensors at higher precision.
Here's how quantization changes the file size of Unsloth's Qwen3.5-4B:
| GGUF variant | File size | Tradeoff |
|---|---|---|
BF16 | 8.42 GB | Unquantized reference |
Q6_K | 3.53 GB | More precision than the smaller variants |
Q5_K_M | 3.14 GB | A middle ground between size and precision |
Q4_K_M | 2.74 GB | A practical starting point for local inference |
We suggest starting with Q4_K_M, then trying Q5_K_M or Q6_K if you have more memory available. More aggressive quantization can help larger models fit, but the quality tradeoff depends on the model and the task. Evaluate it on the work you actually want the model to do. The Hub's GGUF documentation describes the available quantization types.
Load GGUF with transformers
To get started, you need:
- An Apple Silicon Mac.
- A PyTorch version supported by the published ggml-quantization kernel builds, usually the two latest PyTorch releases.
- The latest version of transformers (main for now, until the next release) and a compatible version of
kernels.
pip install -U "git+https://github.com/huggingface/transformers.git" kernels
To load a GGUF model, pass its Hub model_id and filename as gguf_file to from_pretrained.
No extra configuration is needed: when the weights stay packed on Metal, transformers automatically loads the compatible ggml/Metal layer kernels and uses ggml-org/ggml-attn as the attention implementation. If that kernel cannot be fetched, the model falls back to "sdpa" with a warning, and you can always force "sdpa" by passing attn_implementation="sdpa" explicitly. See the GGUF documentation for more loading options.
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
)
That is the only GGUF-specific step. Everything after it is the standard 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))
Without a compatible quantization kernel, the loader falls back to dequantizing the model and uses more memory.
Serve GGUF with your preferred interface
You can also use the same checkpoint with transformers serve, which exposes an OpenAI-compatible 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"
The model argument uses <model_id>:<filename>.gguf: before the colon is the Hub repository (unsloth/Qwen3.5-4B-GGUF), and after it is the file to load (Qwen3.5-4B-Q4_K_M.gguf). This selects a specific quantization from a repository that may contain several.
For models whose chat template supports thinking, add --reasoning off to skip it or --reasoning on to enable it. The default, --reasoning auto, follows the chat template’s default. See the reasoning options for details.
You can connect a client such as Jan or Pi by adding a custom OpenAI-compatible provider with these settings:
| Setting | Value |
|---|---|
| Base URL | http://localhost:8000/v1 |
| Model ID | unsloth/Qwen3.5-4B-GGUF:Qwen3.5-4B-Q4_K_M.gguf |
transformers runs the model on your Mac, while the client provides the conversation interface. The same endpoint can be used by other clients that support this API.
Benchmarking against llama.cpp
Our reference for local inference performance is llama.cpp. The comparison below focuses on three GGUF checkpoints: a small dense model, a larger dense model, and a mixture-of-experts model.
The llama.cpp column comes from the llama-bench tool (build 5f55650a7, release b10200, Metal backend from ggml 0.18.0), run as llama-bench -m <file> -p 0 -n 128 -r 3, which reports tg128: the token-generation rate over 128 decoded tokens, averaged across three repetitions, with prompt processing excluded. The transformers column is generate producing the same 128 tokens from a 12-token prompt, best of three warmed runs, and it includes prefill.
Measured on a MacBook Pro M2 Max, 32 GB unified memory, macOS 26.6, PyTorch 2.12.1, kernels 0.17.0, plugged in.
The benchmark script
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)
torch.mps.synchronize()
for _ in range(3):
time.sleep(90)
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")
For the other column:
llama-bench -hf unsloth/Qwen3.5-4B-GGUF:Q4_K_M -p 0 -n 128 -r 3
Transformers is close to llama.cpp across all three checkpoints. The chart uses the same measurements described above; it does not imply identical benchmark conditions, since the Transformers measurement includes prefill while llama-bench reports decode-only throughput.
transformers and llama.cpp
When GGML and llama.cpp joined Hugging Face, we described their complementary roles: llama.cpp provides a foundation for local inference, while transformers provides a foundation for model definition. GGUF support brings those two closer together.
llama.cpp remains our recommended engine when your priority is efficient local inference. Its dedicated runtime, memory management, and broad hardware support are built around that goal. This integration gives developers a convenient way to work with the same GGUF checkpoints inside transformers:
- Experiment with GGUF in Python and PyTorch. Inspect intermediate activations with hooks, modify a model's forward pass, or prototype custom layers using familiar PyTorch tools.
- Evaluate GGUF models. Use your existing transformers evaluation workflows to measure the quality of quantized checkpoints.
- Validate GGUF conversions. For us as developers, loading the original checkpoint and its GGUF conversion in transformers makes it easier to check that the weights were converted correctly, accounting for quantization error.
- Try new decoding ideas. Use custom logits processors and stopping criteria with
generate, or write your own generation loop in Python. - Fine-tune from a GGUF checkpoint. Dequantize the weights and continue with a standard transformers training workflow.
For that last case, use 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,
)
Beyond GGUF: ggml kernels for more models
The bigger opportunity is bringing ggml's performance to models that llama.cpp does not support.
transformers already provides the PyTorch implementations of these architectures. With ggml kernels and quantization schemes available in PyTorch, we can work toward accelerating their supported operations without first implementing the entire model in llama.cpp. This is especially useful for new architectures, research models, and custom variants that may never receive a dedicated llama.cpp implementation.
That opportunity extends beyond the GGUF format itself. A kernel operates on tensors; it does not require the whole model to come from a GGUF file. The same building blocks can be integrated into other transformers models and loading workflows. This also opens a path to other modalities: computer vision models, audio models, and multimodal models could reuse compatible attention, normalization, and matrix multiplication kernels without first having a full implementation in llama.cpp. Each architecture still needs integration and validation; the initial GGUF examples here cover text generation.
Fast local inference with Python and PyTorch
We also wanted to show how far we can get while keeping the model and generation loop in Python. With the right kernels and an efficient generation loop, Python and PyTorch can deliver strong local inference performance. The kernels handle the heavy computation, while the generation loop keeps the GPU busy by avoiding unnecessary synchronization.
Our focus was to make eager execution fast without requiring torch.compile. For interactive use, we wanted a quick start and a steady stream of tokens, without compilation pauses or recompilation when input shapes change. The two main pieces of that work are the kernels and generate itself.
Reusing ggml's Metal kernels
A kernel is a small program that performs an operation on the GPU. PyTorch supplies general-purpose implementations; a specialized kernel can do less work, combine several operations, or read quantized weights directly in their stored format.
The kernels library lets us distribute compatible builds of ggml's Metal kernels on the Hub and call them from transformers. That brings ggml's work into the PyTorch model without replacing the model with a separate inference runtime.
| Kernel | What it does |
|---|---|
ggml-quantization | Reads packed quantized weights for matrix operations, including the selected experts in an MoE model. It avoids expanding the whole weight matrix before each decode operation. |
ggml-norm | Fuses normalization operations, including the zero-centered RMSNorm used by Qwen3.5 and Qwen3.8. |
ggml-attn | Provides ggml's Metal flash attention for prompt processing and token decoding. |
ggml-gated-delta-net | Accelerates the gated delta network used in the linear-attention layers of the Qwen3.5 and Qwen3.8 hybrid architectures. |
topk | Selects the experts for each token in an MoE model, combining softmax and top-k routing. This is our own Metal implementation. |
The first four packages build on ggml's kernels; the top-k kernel addresses a separate bottleneck in MoE routing. Together they reduce the GPU work needed for each generated token.
To show the contribution of the layer kernels, we compare the same packed GGUF checkpoints with and without them. The quantization kernel stays enabled in both configurations: disabling it would also change how weights are represented and would measure a different tradeoff.
Keeping the CPU and GPU working together
Faster kernels only help if the GPU has work to do. During generation, the CPU schedules GPU operations and controls the loop that produces the next token. Reading a result back from the GPU can force the CPU to wait until queued operations finish. Repeating even a small wait for every token can noticeably reduce throughput.
Two changes address this in generate, which results in improvements for all transformers models (not just when running GGUF files):
- Drop an unnecessary attention mask early (#48814). When a supported decoder-only input has no padding, its all-ones padding mask can be removed at the start of generation. Downstream attention code no longer needs to inspect that mask repeatedly to determine whether it can be skipped. Causal attention is still preserved.
- Defer the stopping check (#47975). On supported paths,
generatecopies the stopping decision asynchronously and consumes it on the following step. The CPU can keep scheduling work while the GPU runs. Streaming tokens use the same approach, and any extra step past the stopping condition is removed from the result.
These changes improve the generation loop around the model, so their usefulness extends beyond GGUF. They complement the kernel work: kernels reduce the cost of an operation, while fewer synchronization points let CPU scheduling and GPU execution overlap.
These measurements keep all layer kernels enabled; the bars isolate the changes to the generation loop.
Current limitations and next steps
The initial target is a single interactive conversation on Apple Silicon. There are a few boundaries to keep in mind:
- The packed inference path is MPS-only for now. GGUF import through dequantization remains a separate option; support for the file format does not imply that packed kernels are available on every device.
- Padding and batching still need work. Unpadded inputs benefit from the mask optimization described above. Padded batches cannot take the same shortcut and can have lower performance. We want to extend the work to
generate_batchon MPS. - Architecture coverage is limited. The packed loader currently covers the Qwen3.5 dense and MoE architectures, including compatible Qwen3.8 checkpoints. Adding support for other architectures is relatively straightforward, and we’ll expand coverage gradually.
If you have a GGUF model you would like to use in transformers, open an issue with the checkpoint and your use case. That will help us prioritize support for the models people are running locally.
Acknowledgments
We would like to thank Arthur Zucker for initiating this work and reviewing all of my PRs, and Cyril Vallez for the generate PRs. We are grateful to Sayak Paul, the llama.cpp team, and Bertrand Chevalier for their help integrating the kernels. We also thank Aritra Roy Gosthipaty and Pedro Cuenca for reviewing this blog post, and Lysandre Debut for overseeing the project.