Introdução ao projeto
AirLLM é uma ferramenta Python voltada para a inferência de modelos de linguagem grandes. Sua ideia central não é carregar o modelo inteiro de uma vez na VRAM, mas dividi-lo em camadas mais finas e carregá-lo sob demanda durante a inferência. Assim, conseguimos tentar executar modelos de 70B, 235B, 671B e até MoE gigantescos como o Kimi K3 em ambientes com pouca VRAM.
O Kimi K3 tem chamado bastante atenção recentemente por um motivo direto: ele é um modelo MoE em escala ultragrande, com um número enorme de parâmetros, mas cada token é roteado apenas para uma parte dos especialistas. O AirLLM aproveita exatamente isso, fazendo carregamento em fluxo no nível dos especialistas e evitando colocar o modelo completo na memória de uma só vez.
Neste artigo, vamos montar juntos um projeto mínimo: primeiro executar a inferência do Kimi K3, e depois encapsulá-lo em uma API HTTP simples. No final, também vamos falar sobre quando faz sentido fazer inferência local com pouca VRAM e quando é mais prático usar interfaces compatíveis como Defapi.
Dificuldade: intermediária | Duração: 30–60 minutos | Resultado: entender o fluxo de inferência de baixo uso de VRAM do AirLLM e colocar no ar um serviço mínimo de inferência do Kimi K3
Perfil do público-alvo
- Desenvolvedores que querem experimentar modelos gigantes em uma única GPU ou em máquinas com pouca VRAM
- Engenheiros que desejam avaliar o custo de inferência de modelos como Kimi K3, DeepSeek-V3 e Qwen3-235B
- Pessoas construindo protótipos de aplicações de IA e que querem validar o efeito do modelo localmente primeiro
- Desenvolvedores que precisam encapsular um modelo local como API e depois integrá-lo a Agent, RAG ou sistemas de negócio
Dependências principais e ambiente
O Kimi K3 é exigente em relação ao ambiente, então vamos deixar claro o limite das dependências:
| Dependência | Versão recomendada | Observação |
|---|---|---|
| Python | 3.10+ | Recomenda-se usar um ambiente virtual para isolar dependências |
| NVIDIA Driver | Compatível com CUDA 12 | É necessário conseguir executar normalmente o PyTorch construído para CUDA 12 |
| PyTorch | Build para CUDA 12 | A cadeia de dependências do Kimi K3 é mais adequada ao CUDA 12 |
| transformers | 4.56.x | O código remoto do modelo Kimi K3 exige uma versão específica |
| airllm | Versão mais recente | Usa AutoModel para carregar o modelo de forma unificada |
| compressed-tensors | Versão mais recente | É usado pelo formato de pesos relacionado ao Kimi K3 |
| flash-attn | Compatível com CUDA 12 | O código do modelo Kimi K3 normalmente exige flash attention |
WARNING
O AirLLM pode reduzir o uso de VRAM, mas não diminui o tamanho dos arquivos do modelo. Na primeira execução, ele fará o download e a divisão do modelo, então o espaço em disco e a qualidade da rede continuam sendo muito importantes. Modelos como o Kimi K3 são enormes, então é recomendável preparar bastante espaço de cache para o modelo antes.
Árvore completa da estrutura do projeto
Vamos preparar primeiro um projeto mínimo, mantendo a estrutura simples:
airllm-kimi-k3-demo/
├── .env.example
├── requirements.txt
├── run_kimi_k3.py
├── server.py
└── README.md
1. Criar um ambiente virtual Python
Primeiro, crie o diretório e o ambiente virtual:
mkdir airllm-kimi-k3-demo
cd airllm-kimi-k3-demo
python -m venv .venv
source .venv/bin/activate
No Windows PowerShell, a ativação pode ser feita assim:
mkdir airllm-kimi-k3-demo
cd airllm-kimi-k3-demo
python -m venv .venv
.venv\Scripts\Activate.ps1
Confirme a versão do Python:
python --version
O ideal é que a saída seja pelo menos Python 3.10.x. Se houver várias versões do Python na sua máquina, você pode usar py -3.10 -m venv .venv para especificar explicitamente.
2. Instalar o AirLLM e as dependências do Kimi K3
Crie um novo requirements.txt:
airllm
accelerate
sentencepiece
safetensors
compressed-tensors
python-dotenv
fastapi
uvicorn[standard]
Primeiro instale o PyTorch. Tomando CUDA 12.1 como exemplo:
pip install --upgrade pip
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
Depois instale as dependências principais:
pip install -r requirements.txt
pip install "transformers>=4.56,<4.57"
Por fim, instale o flash-attn:
pip install flash-attn --no-build-isolation
TIP
Se a compilação do flash-attn estiver muito lenta, primeiro verifique se o ambiente atual consegue fazer import torch corretamente e se torch.version.cuda mostra uma série compatível com CUDA 12. Modelos como o Kimi K3 dependem bastante do ambiente de engenharia, então não é recomendável instalar tudo diretamente em um ambiente global de Python desorganizado.
3. Configurar permissões do modelo e diretório de cache
Alguns modelos do Hugging Face exigem login ou autorização. Vamos preparar um .env.example:
HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
HF_HOME=/data/huggingface
AIRLLM_MODEL_ID=moonshotai/Kimi-K3
Na prática, copie o arquivo:
cp .env.example .env
No Windows PowerShell:
Copy-Item .env.example .env
Depois preencha com o seu Hugging Face Token. O ID do modelo deve seguir o nome real do repositório publicado oficialmente; se você estiver usando um repositório espelho ou um repositório sincronizado internamente, também pode alterá-lo diretamente para o repo id correspondente.
WARNING
Não envie o .env para o repositório Git. O token do modelo é informação sensível; se vazar, pode permitir o uso indevido de permissões de modelos privados.
4. Escrever um script mínimo de inferência do Kimi K3
Crie o arquivo run_kimi_k3.py:
import os
from dotenv import load_dotenv
from airllm import AutoModel
def main() -> None:
"""Carrega o Kimi K3 e executa uma geração mínima de texto."""
load_dotenv()
model_id = os.getenv("AIRLLM_MODEL_ID", "moonshotai/Kimi-K3")
hf_token = os.getenv("HF_TOKEN")
# Controla o comprimento da entrada; use primeiro um prompt curto para validar o fluxo, evitando longas esperas no teste inicial.
max_length = 256
# AutoModel identifica automaticamente o tipo de modelo; hf_token é usado para acessar repositórios autorizados.
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
profiling_mode=True,
)
prompt = "Explique em três frases por que o AirLLM consegue reduzir a VRAM na inferência de modelos grandes."
# O tokenizer continua no estilo Hugging Face, então o custo de migração é baixo.
input_tokens = model.tokenizer(
[prompt],
return_tensors="pt",
return_attention_mask=False,
truncation=True,
max_length=max_length,
padding=False,
)
# Na inferência com AirLLM, os pesos são carregados por camada; deixe o max_new_tokens pequeno no início para validar rapidamente.
generation_output = model.generate(
input_tokens["input_ids"].cuda(),
max_new_tokens=128,
use_cache=True,
return_dict_in_generate=True,
)
output = model.tokenizer.decode(
generation_output.sequences[0],
skip_special_tokens=True,
)
print(output)
if __name__ == "__main__":
main()
Execute:
python run_kimi_k3.py
A primeira execução normalmente será mais lenta, porque o AirLLM precisa baixar o modelo, analisar os arquivos de peso e dividir o modelo em um formato que possa ser carregado por camadas posteriormente. Você vai notar que a segunda execução tende a ser bem mais fluida, porque o cache do modelo e o resultado da divisão já estarão prontos.
5. Observar o uso de VRAM e disco
Abra outro terminal para observar a VRAM:
nvidia-smi -l 1
Observe estes dois indicadores:
Memory-Usage
GPU-Util
Se tudo estiver correto, a VRAM não será consumida de uma vez até explodir, como ocorre com transformers.from_pretrained() tradicional. A abordagem do AirLLM é mais parecida com “carregar conforme avança”: a camada ou a parte do especialista necessária é carregada, usada e depois liberada.
Agora observe o disco:
du -sh "$HF_HOME"
No Windows PowerShell, você pode usar:
Get-ChildItem $env:HF_HOME -Recurse | Measure-Object Length -Sum
TIP
Inferência com pouca VRAM não é almoço grátis. O AirLLM transfere parte da pressão da VRAM para I/O de disco, memória da CPU e cache do modelo, então a velocidade do SSD afeta diretamente a experiência.
6. Ativar parâmetros de compressão
O AirLLM oferece suporte ao parâmetro compression para reduzir o volume carregado dos pesos. Podemos adicionar 4bit na inicialização do modelo:
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
compression="4bit", # Usa compressão de pesos em 4 bits, reduz o volume carregado e melhora a velocidade em alguns cenários
profiling_mode=True,
)
Se quiser ser mais conservador, você pode começar com 8bit:
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
compression="8bit", # 8 bits é mais conservador e oferece um equilíbrio melhor entre velocidade e precisão
profiling_mode=True,
)
Normalmente, a escolha é esta:
| Cenário | Recomendação |
|---|---|
| Só quer testar se o Kimi K3 roda | Sem compressão, para primeiro confirmar o fluxo |
| O I/O de disco está claramente deixando tudo lento | Tente 8bit |
| Prioriza mais velocidade e validação de baixo custo | Tente 4bit |
| Vai fazer avaliação séria | Fixe os parâmetros e compare a qualidade da saída em várias rodadas |
WARNING
A compressão altera a forma de carregamento dos pesos. Embora o impacto seja pequeno em muitas tarefas de inferência, se você estiver fazendo avaliação de modelo, inferência de texto longo ou validação de negócios sensíveis, preserve uma versão não comprimida como base de comparação.
7. Encapsular em uma API HTTP simples
Depois que o script local estiver funcionando, normalmente queremos conectá-lo à aplicação. Vamos usar FastAPI para criar um serviço mínimo.
Crie server.py:
import os
from typing import List, Literal
from dotenv import load_dotenv
from fastapi import FastAPI
from pydantic import BaseModel
from airllm import AutoModel
class ChatMessage(BaseModel):
"""Estrutura de mensagem no estilo OpenAI, conveniente para futura troca por uma API compatível."""
role: Literal["system", "user", "assistant"]
content: str
class ChatRequest(BaseModel):
"""Pedido mínimo de chat, mantendo apenas os campos necessários para o tutorial."""
messages: List[ChatMessage]
max_tokens: int = 256
class ChatResponse(BaseModel):
"""Retorna o texto gerado; mantemos simples no início para facilitar a depuração."""
text: str
load_dotenv()
app = FastAPI(title="AirLLM Kimi K3 Demo")
model_id = os.getenv("AIRLLM_MODEL_ID", "moonshotai/Kimi-K3")
hf_token = os.getenv("HF_TOKEN")
# Carrega o modelo quando o serviço inicia; em produção real, você pode adicionar health check e lazy loading.
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
profiling_mode=False,
)
def build_prompt(messages: List[ChatMessage]) -> str:
"""Converte múltiplas mensagens em um prompt simples; depois pode ser substituído pelo chat template oficial do modelo."""
lines: list[str] = []
for message in messages:
lines.append(f"{message.role}: {message.content}")
lines.append("assistant:")
return "\n".join(lines)
@app.post("/chat", response_model=ChatResponse)
def chat(request: ChatRequest) -> ChatResponse:
"""Executa uma geração de texto com o Kimi K3."""
prompt = build_prompt(request.messages)
input_tokens = model.tokenizer(
[prompt],
return_tensors="pt",
return_attention_mask=False,
truncation=True,
max_length=1024,
padding=False,
)
generation_output = model.generate(
input_tokens["input_ids"].cuda(),
max_new_tokens=request.max_tokens,
use_cache=True,
return_dict_in_generate=True,
)
text = model.tokenizer.decode(
generation_output.sequences[0],
skip_special_tokens=True,
)
return ChatResponse(text=text)
Inicie o serviço:
uvicorn server:app --host 0.0.0.0 --port 8000
Teste a chamada:
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "user",
"content": "Explique em chinês por que modelos MoE como o Kimi K3 são adequados para carregamento em fluxo por especialista."
}
],
"max_tokens": 200
}'
Agora já temos uma API mínima. Ela ainda não é de nível de produção, mas é suficiente para experimentos locais, protótipos de Agent, depuração de prompts e comparação de qualidade de modelos.
8. Como escolher entre inferência local e interface Defapi
O valor do AirLLM é claro: ele nos permite acessar modelos gigantes em máquinas com pouca VRAM, especialmente útil para aprendizado, validação, avaliação e experimentos offline. Mas, em produção, ainda precisamos considerar throughput, latência, concorrência, atualização de modelos, tolerância a falhas e custo operacional.
Se você só quer conectar rapidamente a capacidade do modelo à aplicação, ou precisa de uma API unificada no estilo OpenAI/Anthropic/Gemini, Defapi costuma ser mais leve. A vantagem é que o preço normalmente fica em torno de metade do oficial, e os modelos comuns são amplamente compatíveis com esses protocolos:
v1/chat/completionsv1/messagesv1beta/models/
Por exemplo, podemos abstrair a chamada local /chat acima em um cliente unificado. No desenvolvimento, usamos AirLLM localmente; no ambiente online, trocamos para Defapi:
import os
import requests
def ask_model(prompt: str) -> str:
"""Escolhe entre inferência local e uma interface compatível com Defapi de acordo com a variável de ambiente."""
provider = os.getenv("MODEL_PROVIDER", "local")
if provider == "defapi":
response = requests.post(
"https://api.defapi.org/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {os.environ['DEFAPI_API_KEY']}",
"Content-Type": "application/json",
},
json={
"model": os.getenv("DEFAPI_MODEL", "anthropic/claude-sonnet-4.5"),
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.7,
},
timeout=60,
)
response.raise_for_status()
return response.json()["choices"][0]["message"]["content"]
response = requests.post(
"http://localhost:8000/chat",
json={
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 256,
},
timeout=300,
)
response.raise_for_status()
return response.json()["text"]
A vantagem dessa abordagem é que a fronteira fica muito clara: o modelo local é responsável por exploração e validação, enquanto o Defapi cuida de uma integração estável, unificada e de baixo custo com modelos online. Para OpenClaw, serviços de RAG e Agents automatizados, esse modo duplo é muito prático.
Solução de problemas comuns
1. O que fazer se a instalação do flash-attn falhar?
Primeiro confirme se o PyTorch reconhece corretamente a CUDA:
python -c "import torch; print(torch.__version__); print(torch.version.cuda); print(torch.cuda.is_available())"
Se torch.cuda.is_available() for False, não corra para instalar o flash-attn; antes disso, é preciso corrigir o PyTorch, o driver e a versão da CUDA. Modelos como o Kimi K3 são recomendados com build de CUDA 12, evitando o CUDA 13, que ainda pode não ter pacotes pré-compilados adequados e acabar causando falha de compilação.
2. O que fazer se houver erro na versão do transformers?
Fixe a versão em 4.56.x:
pip uninstall -y transformers
pip install "transformers>=4.56,<4.57"
Depois execute novamente:
python run_kimi_k3.py
Se aparecer falha ao carregar o código remoto do modelo, normalmente é incompatibilidade entre a versão do transformers e o código do modelo.
3. O que fazer se o download do Hugging Face falhar?
Primeiro verifique se o token está válido:
huggingface-cli whoami
Se não estiver logado:
huggingface-cli login
Você também pode usar apenas o HF_TOKEN no .env. Se o modelo exigir permissão de acesso, primeiro aceite os termos na página do modelo.
4. O que fazer se faltar espaço em disco?
Na primeira execução, o AirLLM baixa os pesos originais e também gera os arquivos do modelo já divididos. Você pode colocar o diretório de cache em um disco maior:
export HF_HOME=/mnt/models/huggingface
Ou configurar no .env:
HF_HOME=/mnt/models/huggingface
Se o disco ainda estiver apertado, você pode tentar delete_original=True no AirLLM para manter apenas os arquivos convertidos:
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
delete_original=True, # Após a conversão, remove os pesos originais para economizar espaço em disco
)
5. Por que a primeira execução é tão lenta?
Isso é normal. A primeira execução inclui download dos pesos, leitura do índice, divisão do modelo e gravação no cache. A segunda execução reutiliza o cache e fica muito melhor.
Você pode ativar profiling:
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
profiling_mode=True, # Mostra o tempo de carregamento e inferência para facilitar a identificação do gargalo
)
Se o tempo gasto estiver principalmente na leitura de disco, trocar para um SSD NVMe mais rápido costuma ser mais eficaz do que aumentar a VRAM.
6. O que fazer se a VRAM não reduzir como esperado?
Primeiro confirme que o código não está misturando métodos de carregamento tradicionais:
from airllm import AutoModel
model = AutoModel.from_pretrained(model_id)
Não misture com o carregamento tradicional:
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(model_id)
O segundo caminho usa o fluxo de carregamento tradicional e pode facilmente encher a VRAM.
7. O que fazer se o serviço API travar com alta concorrência?
A inferência local com pouca VRAM é mais adequada para experimentos serializados, não para lidar diretamente com alta concorrência. A forma mais simples é limitar a concorrência primeiro:
uvicorn server:app --host 0.0.0.0 --port 8000 --workers 1
Se você precisar de chamadas mais estáveis em produção, pode mover as requisições online para o Defapi e manter o AirLLM local para avaliação e depuração.
Leitura complementar / direções avançadas
- Comparar Kimi K3, DeepSeek-V3 e Qwen3-235B em termos de tempo de carregamento, VRAM e qualidade da saída no AirLLM.
- Transformar o serviço FastAPI em um endpoint compatível com OpenAI em
/v1/chat/completions. - Integrar Defapi e usar
v1/chat/completionsouv1/messagespara gerenciar chamadas de modelos online de forma unificada. - Adicionar fila ao serviço AirLLM para evitar que várias requisições disputem simultaneamente o I/O de disco e a GPU.
- Configurar modelos local e em nuvem em frameworks como OpenClaw ou outros frameworks de Agent, escolhendo dinamicamente conforme a complexidade da tarefa.