Ollama em produção: model management, Docker, monitoring
- ⬜⚙️ llama.cpp internals: ggml, KV cache, FlashAttention(Local LLMs & Edge AI)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
Ollama tornou-se o padrão de facto para LLM local — não porque inova em inferência (usa llama.cpp por baixo), mas porque resolve o que llama.cpp não resolve: experiência de desenvolvedor. Registry estilo Docker, daemon HTTP, Modelfile reproduzível, multi-model orchestration. Este módulo cobre o que muda quando você sai de ollama run no laptop e vai para docker compose up em VPS de produção com 10 modelos e 100 req/min.
Arquitetura: daemon + CLI + API
# Anatomia do diretório ~/.ollama
~/.ollama/
├── id_ed25519, id_ed25519.pub # par de chaves para push
├── history # histórico do REPL
└── models/
├── blobs/ # conteúdo addressed by sha256
│ ├── sha256-abc... # weights GGUF
│ ├── sha256-def... # tokenizer
│ └── sha256-ghi... # manifest config
└── manifests/registry.ollama.ai/library/
├── llama3.1/8b # manifest JSON (lista de blobs)
└── qwen2.5/7b
# Estrutura compatível com OCI image spec (Docker-like)Cada modelo é uma imagem OCI-like: manifest JSON + blobs imutáveis content-addressed. Permite deduplicação automática (mesmo tokenizer compartilhado entre variantes). Push/pull para registry funciona como Docker.
Modelfile: o Dockerfile para LLMs
O Modelfile é a especificação declarativa de um modelo customizado. Diretivas principais: FROM (base), SYSTEM (system prompt), PARAMETER (sampling, contexto), TEMPLATE (chat format), ADAPTER (LoRA), MESSAGE (few-shot embarcado), LICENSE.
# Modelfile — assistente FFV Academy customizado
FROM llama3.1:8b-instruct-q4_K_M
# Parâmetros de sampling/contexto
PARAMETER temperature 0.5
PARAMETER top_p 0.9
PARAMETER min_p 0.05
PARAMETER repeat_penalty 1.0
PARAMETER num_ctx 16384
PARAMETER num_predict 2048
PARAMETER stop "<|eot_id|>"
# System prompt fixo (versionado no git junto do Modelfile)
SYSTEM """
Você é o tutor da FFV Academy — escola de engenharia para a era da IA.
Estilo: técnico, denso, sem hype. Cite papers quando relevante.
Idioma: PT-BR. Não diga "como modelo de linguagem". Vá direto ao ponto.
"""
# Template (formato Llama-3 chat)
TEMPLATE """{{ if .System }}<|start_header_id|>system<|end_header_id|>
{{ .System }}<|eot_id|>{{ end }}{{ range .Messages }}<|start_header_id|>{{ .Role }}<|end_header_id|>
{{ .Content }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
"""
# LoRA adapter opcional (treinado em corpus FFV)
# ADAPTER ./ffv-academy-lora.gguf
# Few-shot exemplar embarcado
MESSAGE user "O que é MVCC?"
MESSAGE assistant "MVCC (Multi-Version Concurrency Control) é a estratégia que o PostgreSQL usa para isolamento sem locks de leitura..."
LICENSE "MIT"# Build e push para registry privado
ollama create ffv-tutor:v1.2 -f Modelfile
# Listar modelos locais
ollama list
# Inspecionar
ollama show ffv-tutor:v1.2 --modelfile
# Push para registry (Hugging Face Hub, registry próprio, etc.)
ollama push registry.ffv.academy/ffv-tutor:v1.2Deploy com Docker e GPU passthrough
# docker-compose.yml — Ollama com GPU NVIDIA
services:
ollama:
image: ollama/ollama:0.5.4
container_name: ollama
restart: unless-stopped
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama # cache de modelos persistente
environment:
OLLAMA_HOST: 0.0.0.0:11434
OLLAMA_KEEP_ALIVE: 24h # mantém modelos quentes
OLLAMA_MAX_LOADED_MODELS: 3 # multi-model simultâneo
OLLAMA_NUM_PARALLEL: 4 # concorrência por modelo
OLLAMA_FLASH_ATTENTION: "1" # ativa FA2
OLLAMA_KV_CACHE_TYPE: q8_0 # quantiza KV cache
OLLAMA_DEBUG: "0"
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all # ou device_ids: ['0']
capabilities: [gpu]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
interval: 30s
timeout: 10s
retries: 3
volumes:
ollama_data:# Pré-requisitos no host (Ubuntu/Debian)
# 1. Driver NVIDIA atualizado
sudo apt install -y nvidia-driver-550
# 2. nvidia-container-toolkit
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt update && sudo apt install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
# 3. Subir stack e pre-pull modelos
docker compose up -d
docker exec ollama ollama pull llama3.1:8b-instruct-q4_K_M
docker exec ollama ollama pull qwen2.5:7b-instruct-q4_K_M
docker exec ollama ollama pull nomic-embed-text # embeddingsEm multi-tenant (várias apps usando o mesmo Ollama), configure e use reverse proxy (Caddy/nginx) com rate-limit por API key. Ollama não tem auth nativo — exponha SEMPRE atrás de proxy autenticado, nunca direto na internet.
API e function calling
# Cliente Python — SDK oficial
import ollama
# Chat simples streaming
for chunk in ollama.chat(
model="ffv-tutor:v1.2",
messages=[
{"role": "user", "content": "Explique speculative decoding."}
],
stream=True,
options={"temperature": 0.5, "num_ctx": 8192},
):
print(chunk["message"]["content"], end="", flush=True)
# Structured output via JSON schema
from pydantic import BaseModel
class ModuleSummary(BaseModel):
title: str
xp: int
difficulty: str # "iniciante" | "intermediario" | "avancado"
key_concepts: list[str]
resp = ollama.chat(
model="qwen2.5:7b",
messages=[{"role": "user", "content": "Resuma o módulo sobre RAG em JSON."}],
format=ModuleSummary.model_json_schema(),
options={"temperature": 0.2},
)
summary = ModuleSummary.model_validate_json(resp["message"]["content"])# Function calling (tool use) — Ollama 0.4+
import ollama
def get_weather(city: str) -> dict:
# implementação real (call API, etc.)
return {"city": city, "temp_c": 22, "condition": "sunny"}
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Retorna clima atual de uma cidade",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "Nome da cidade"}
},
"required": ["city"],
},
},
}
]
resp = ollama.chat(
model="llama3.1:8b",
messages=[{"role": "user", "content": "Como está o clima em SP?"}],
tools=tools,
)
# Se o modelo decidir chamar a tool:
if resp["message"].get("tool_calls"):
for call in resp["message"]["tool_calls"]:
result = get_weather(**call["function"]["arguments"])
# Re-envia resultado ao modelo
resp2 = ollama.chat(
model="llama3.1:8b",
messages=[
{"role": "user", "content": "Como está o clima em SP?"},
resp["message"],
{"role": "tool", "content": str(result), "name": "get_weather"},
],
)
print(resp2["message"]["content"])Quando rodar um modelo localmente é a escolha certa em vez de usar uma API?
Tuning de produção e monitoramento
| Variável | Default | Recomendado prod | Efeito |
|---|---|---|---|
| OLLAMA_KEEP_ALIVE | 5m | 24h ou -1 | Mantém modelo na memória; evita cold-start |
| OLLAMA_MAX_LOADED_MODELS | 1 | depende VRAM/RAM | Multi-model concorrente |
| OLLAMA_NUM_PARALLEL | 1 | 4-8 | Requests paralelas por modelo (continuous batching) |
| OLLAMA_MAX_QUEUE | 512 | 256 com 429 | Fila antes de rejeitar; preferir backpressure |
| OLLAMA_FLASH_ATTENTION | auto | 1 | FA2 sempre (com KV quant pareado) |
| OLLAMA_KV_CACHE_TYPE | f16 | q8_0 | Quantiza KV cache; metade da memória |
| OLLAMA_HOST | 127.0.0.1 | 0.0.0.0:11434 atrás de proxy | Aceita external (com auth no proxy) |
| OLLAMA_DEBUG | 0 | 0 | Logs verbose; só em troubleshoot |
| OLLAMA_NEW_ENGINE | 0 | 1 (2026+) | Novo runtime Go (sem cgo); experimental para mainline |
# Métricas a coletar (Prometheus exporter via /api/ps + custom)
# Endpoint /api/ps lista modelos carregados, size, vram, expires_at
curl -s http://localhost:11434/api/ps | jq
# {
# "models": [{
# "name": "llama3.1:8b",
# "size": 4661211808,
# "size_vram": 4661211808,
# "expires_at": "2026-05-10T15:23:00Z",
# "details": {"parameter_size": "8B", "quantization_level": "Q4_K_M"}
# }]
# }
# Métricas custom via wrapper Python (cada request)
import time, ollama
t0 = time.time()
r = ollama.chat(model="llama3.1:8b", messages=[{"role":"user","content":"oi"}])
# r["total_duration"] → nanosegundos totais
# r["load_duration"] → ns para carregar modelo (0 se já quente)
# r["prompt_eval_count"], r["prompt_eval_duration"] → prefill
# r["eval_count"], r["eval_duration"] → decode tok/s = eval_count / (eval_duration/1e9)Quando NÃO usar Ollama
📋 Você precisa servir >100 req/s para o mesmo modelo em produção crítica
Ollama é otimizado para UX dev e multi-model com poucas requests concorrentes. vLLM tem PagedAttention, prefix caching automático, chunked prefill — 5-20× mais throughput em workloads concorrentes. Ollama é a escolha errada para alta concorrência sustentada.
Alt: Single user / dev local / multi-model com baixo QPS → Ollama vence em UX
Alt: Self-hosted produção alta concorrência → vLLM com OpenAI API compat
Alt: Datacenter NVIDIA otimizado → TensorRT-LLM (latência mínima)
Alt: Edge / mobile → llama.cpp direto (sem daemon)
Perguntas frequentes
❓ Como atualizar um modelo no Ollama sem perder o cache de outros?
❓ Posso rodar Ollama em CPU-only em VPS pequeno?
❓ Ollama suporta vision models (multimodal)?
❓ Como integrar Ollama com LangChain/LlamaIndex?
Referências
Fixando
Qual restrição prática é mais subestimada ao servir modelo localmente?
Que decisão operacional muda ao servir modelo próprio?
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…