GGUF no Hugging Face Transformers: quando compensa trocar o llama.cpp
O Transformers passou a ler arquivos GGUF direto do Hub, mas o ganho de memória que o llama.cpp entrega por padrão só aparece hoje para os modelos Qwen3.5 e depende de kernel específico. Fora disso, o que você ganha é conveniência, não economia de RAM.

GGUF deixou de ser território exclusivo do llama.cpp
GGUF é o formato de arquivo único usado pelo ecossistema GGML (a base do llama.cpp) para guardar metadados e tensores de um modelo já quantizado. Até pouco tempo atrás, se você queria rodar um .gguf, o caminho natural era compilar ou instalar o llama.cpp e apontar o binário pro arquivo. A documentação atual do Transformers (na branch main, que exige instalação a partir do código-fonte, diferente da última versão estável via pip, a 5.17.0) mostra que agora dá para carregar esses mesmos arquivos direto com AutoModelForCausalLM, puxando o repositório do Hub como se fosse qualquer outro checkpoint.
Isso é bom para quem já vive dentro do ecossistema Transformers e não quer manter dois runtimes (um em Python↳Python56 conteúdosVSCode + Python + Alexa: Desenvolva e teste skills para alexa localmente com pythonDev (Back & Front) · out 2025Dominando decoradores em Python: um guia completo com exemplosDev (Back & Front) · jan 2025Desenvolvimento de software: diferenças entre Python, JavaScript e JavaGestão Dev & TI · nov 2024Ver tudo em Dev (Back & Front) → para fine-tuning/avaliação, outro em C++ para servir). Mas antes de decidir migrar um serviço de produção que hoje roda em llama.cpp, tem uma letra pequena que muda a conta: o ganho de memória do formato quantizado não é automático para qualquer arquitetura.
Dois caminhos dentro do Transformers: packed e dequantize
O loader do Transformers pode seguir por dois caminhos quando você passa um arquivo GGUF:
- Packed: os pesos ficam comprimidos na memória, do jeito que estavam no arquivo, e as multiplicações de matriz rodam direto sobre os blocos quantizados. Isso é o que de fato reproduz a economia de RAM que o llama.cpp sempre ofereceu.
- Dequantize: o modelo é descompactado por completo no carregamento e você fica com um modelo denso normal, do tamanho que ele teria sem quantização nenhuma.
O detalhe que muda tudo: o caminho packed hoje só está disponível para Qwen3.5 e Qwen3.5 MoE, e só quando o kernel ggml-org/ggml-quantization está acessível via pacote kernels. Toda arquitetura fora dessa lista (Llama, Mistral, Qwen2, Qwen2Moe, Phi3, Bloom, Falcon, StableLM, GPT2, Starcoder2, entre outras) passa pelo loader legado, que sempre dequantiza. Ou seja: você baixa o arquivo pequeno do Hub, mas o modelo carregado na GPU ou CPU ocupa a memória de um modelo em precisão cheia.
Tem outro detalhe específico de hardware: a documentação diz que o loader usa MPS (Metal, ou seja, Apple Silicon) por padrão quando o kernel packed está presente, e que o caminho packed usa float32 automaticamente "porque é mais rápido no MPS". Isso é um sinal forte de que, hoje, esse caminho foi pensado primeiro para Mac. Se seu serviço de produção roda em Linux↳Linux34 conteúdosKali Linux em um Servidor VPS: como, quando e por que usar?DevSecOps · dez 2024Construindo um Windows Service ou Linux Daemon com Worker Service & .NET Core – Parte 2Dev (Back & Front) · jul 2020Criando uma WebApi utilizando .NET, Linux e VSCodeDev (Back & Front) · ago 2019Ver tudo em DevSecOps → com CUDA, vale testar explicitamente antes de assumir que vai ganhar a mesma economia de memória que teria num MacBook.
Do Hub ao modelo carregado
O primeiro passo é instalar o pacote de kernels, sem ele o caminho packed nem é tentado e o loader cai direto para dequantização completa:
pip install kernelsDepois, o carregamento é igual ao de qualquer outro modelo no Transformers, só muda o parâmetro gguf_file:
from transformers import AutoModelForCausalLM, AutoTokenizer
model_id = "unsloth/Qwen3.5-4B-GGUF"
filename = "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)O mesmo padrão funciona para o Qwen3.5 no formato mixture-of-experts:
moe_model_id = "unsloth/Qwen3.5-35B-A3B-GGUF"
moe_filename = "Qwen3.5-35B-A3B-Q4_K_M.gguf"
moe_model = AutoModelForCausalLM.from_pretrained(moe_model_id, gguf_file=moe_filename)
moe_tokenizer = AutoTokenizer.from_pretrained(moe_model_id, gguf_file=moe_filename)Se você quer forçar a dequantização mesmo num modelo que teria caminho packed disponível, por exemplo para rodar em bf16 numa GPU CUDA onde o kernel packed não se aplica, a própria documentação expõe isso via GgufConfig:
import torch
from transformers import AutoModelForCausalLM, GgufConfig
quantization_config = GgufConfig(dequantize=True)
model = AutoModelForCausalLM.from_pretrained(
model_id, gguf_file=filename, quantization_config=quantization_config, dtype=torch.bfloat16
)Para a atenção, quando o kernel packed está disponível no MPS, o default passa a ser ggml-attn, o mesmo kernel de flash-attention que o llama.cpp usa em prefill e decode. Passar attn_implementation explicitamente sempre tem precedência sobre esse default: se você quer manter sdpa (o padrão do PyTorch) por compatibilidade com o resto do seu pipeline, basta declarar attn_implementation="sdpa". O trecho abaixo faz o oposto, força explicitamente o ggml-attn independente do que o loader escolheria por padrão:
model = AutoModelForCausalLM.from_pretrained(
model_id, gguf_file=filename, attn_implementation="ggml-org/ggml-attn"
)Empacotando num endpoint FastAPI
Daqui pra produção o caminho é o mesmo de servir qualquer modelo Transformers: carregar uma vez no start do processo e reaproveitar a instância em cada request. Um esqueleto razoável:
from contextlib import asynccontextmanager
from fastapi import FastAPI
from pydantic import BaseModel
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_ID = "unsloth/Qwen3.5-4B-GGUF"
FILENAME = "Qwen3.5-4B-Q4_K_M.gguf"
state = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
state["tokenizer"] = AutoTokenizer.from_pretrained(MODEL_ID, gguf_file=FILENAME)
state["model"] = AutoModelForCausalLM.from_pretrained(MODEL_ID, gguf_file=FILENAME)
yield
state.clear()
app = FastAPI(lifespan=lifespan)
class Prompt(BaseModel):
text: str
max_new_tokens: int = 256
@app.post("/generate")
def generate(payload: Prompt):
tokenizer = state["tokenizer"]
model = state["model"]
inputs = tokenizer(payload.text, return_tensors="pt")
output = model.generate(**inputs, max_new_tokens=payload.max_new_tokens)
return {"text": tokenizer.decode(output[0], skip_special_tokens=True)}Esse esqueleto resolve o "funciona", mas ainda é bem mais simples do que um servidor de inferência dedicado costuma ser em produção: não tem fila de requisições, streaming token a token nativo, nem controle fino de contexto por sessão. Se seu tráfego é sério, o próximo passo natural aqui é rodar esse processo atrás de um uvicorn com um único worker (o modelo não pode ser duplicado por worker sem duplicar a memória) e resolver concorrência com uma fila assíncrona na frente, não com múltiplos processos.
A rota mais curta: transformers serve
Se você só precisa de um endpoint compatível para testes ou para um serviço interno, o Transformers já embute um comando de serving que evita escrever o FastAPI à mão. Cada arquivo .gguf de um repositório é exposto como um modelo próprio, endereçado como :.gguf:
transformers serve unsloth/Qwen3.5-4B-GGUF:Qwen3.5-4B-Q4_K_M.ggufÉ o caminho certo para prototipagem rápida. Para produção, o FastAPI dedicado ainda ganha em um ponto que importa: autenticação, observabilidade (métricas de latência por rota, logs estruturados) e políticas de rate limit são coisas que você precisa acoplar de qualquer jeito, e é mais simples fazer isso em cima de uma aplicação que você já controla do que tentar encaixar no comando de serving genérico.
O que decide a troca
A pergunta que importa antes de abandonar o llama.cpp num serviço já em produção não é "o Transformers carrega GGUF?" (carrega), é "meu modelo e meu hardware caem no caminho packed?". Se a resposta é não, você está trocando um runtime pensado para rodar quantizado do início ao fim por um runtime que baixa o arquivo pequeno e devolve, na prática, o footprint de memória de um modelo em precisão cheia. Isso pode ser um preço aceitável se o ganho for unificar sua stack de inferência dentro do ecossistema Python que você já mantém para fine-tuning e avaliação, mas é preciso medir memória residente do processo antes de assumir a economia, principalmente fora do Qwen3.5 e fora de Apple Silicon. Vale também confirmar em qual versão instalada do transformers esses parâmetros (GgufConfig, attn_implementation="ggml-org/ggml-attn") já existem, já que a documentação citada aqui é da branch de desenvolvimento, não da última versão estável publicada no PyPI.
Este artigo foi escrito por Bisneto Braga, colunista de back-end. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.
Npm cria token que publica pacote só depois de revisão humana
Novo tipo de granular access token da npm separa quem sobe a versão de quem aprova a publicação, fechando uma brecha clássica de token vazado em CI/CD.














