LLMs Open Source
Self-Hosted na AWS
Guia completo: modelos, hospedagem, integração com LangChain/LangGraph, ECS com GPU, Multi-LoRA e otimizações de cold start para produção.
Modelos Open Source em 2025/2026
Os principais modelos self-hostáveis com licença permissiva e suporte a OpenAI-compatible API via vLLM.
Opções de Hospedagem na AWS
Quatro caminhos para rodar seu LLM — cada um com trade-offs de controle, custo e operação.
Instâncias GPU na AWS
Referência de hardware por tamanho de modelo — com e sem quantização.
| Instância | GPU | VRAM | Adequada para | Custo/h (~) |
|---|---|---|---|---|
| g5.xlarge | A10G (1x) | 24 GB | Modelos até ~13B (FP16) ou ~24B (INT4) | $1.00 |
| g5.12xlarge | A10G (4x) | 96 GB | 70B INT4 (~40GB) ou 30B FP16 | $5.67 |
| g5.48xlarge | A10G (8x) | 192 GB | 70B FP16 ou 120B INT4 | $16.29 |
| p4d.24xlarge | A100 (8x) | 320 GB | 180B+ INT4, multi-modelo | $32.77 |
| p5.48xlarge | H100 (8x) | 640 GB | Modelos muito grandes, máx throughput | $98.32 |
| inf2.48xlarge | Inferentia2 | 384 GB | Produção cost-optimized (+40% perf/$) | $12.98 |
Integração com LangChain / LangGraph
vLLM expõe API OpenAI-compatible — substituição drop-in no ChatOpenAI.
/v1/chat/completions, /v1/models). No LangChain, basta mudar base_url — zero mudança de lógica.
from langchain_openai import ChatOpenAI
# Aponta para o NLB interno — zero mudança de interface
llm = ChatOpenAI(
base_url="http://vllm-nlb.internal:8000/v1",
api_key="EMPTY", # qualquer string
model="meta-llama/Meta-Llama-3.1-70B-Instruct",
temperature=0,
)
# Com LoRA específico — mesmo padrão
finance_llm = ChatOpenAI(
base_url="http://vllm-nlb.internal:8000/v1",
api_key="EMPTY",
model="finance-specialist", # nome do LoRA registrado
temperature=0,
)
from langgraph.graph import StateGraph
from langchain_openai import ChatOpenAI
from typing import Literal
VLLM_BASE = "http://vllm-nlb.internal:8000/v1"
def make_agent_llm(lora_name: str | None = None) -> ChatOpenAI:
model = lora_name or "meta-llama/Meta-Llama-3.1-70B-Instruct"
return ChatOpenAI(base_url=VLLM_BASE, api_key="EMPTY", model=model)
def finance_node(state):
llm = make_agent_llm("finance-specialist")
# lógica do agent finance...
def route_to_specialist(state) -> Literal["finance", "legal", "support"]:
return state["specialist"]
graph = StateGraph(AgentState)
graph.add_node("finance", finance_node)
graph.add_node("legal", legal_node)
graph.add_conditional_edges("supervisor", route_to_specialist, {
"finance": "finance",
"legal": "legal",
})
ECS Task Definition para GPU
Configuração completa do container vLLM no ECS com EC2 GPU.
{
"family": "vllm-inference",
"networkMode": "awsvpc",
"requiresCompatibilities": ["EC2"], // não Fargate
"containerDefinitions": [{
"name": "vllm-server",
"image": "vllm/vllm-openai:latest",
"command": [
"--model", "meta-llama/Meta-Llama-3.1-70B-Instruct",
"--tensor-parallel-size", "4", // = nº GPUs
"--max-model-len", "32768",
"--gpu-memory-utilization", "0.90",
"--enable-auto-tool-choice", // para tool calling
"--tool-call-parser", "llama3_json"
],
"resourceRequirements": [{
"type": "GPU",
"value": "4" // deve bater com tensor-parallel-size
}],
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:8000/health"],
"startPeriod": 300 // 5 min p/ modelo carregar
},
"mountPoints": [{
"sourceVolume": "model-cache",
"containerPath": "/root/.cache/huggingface"
}]
}],
"placementConstraints": [{
"type": "memberOf",
"expression": "attribute:ecs.instance-type =~ g5.*"
}]
}
// NLB interno — só acessível dentro do VPC
const nlb = new elbv2.NetworkLoadBalancer(this, 'VllmNlb', {
vpc,
internetFacing: false, // INTERNO — crítico
});
const listener = nlb.addListener('VllmListener', { port: 8000 });
listener.addTargets('VllmTargets', {
port: 8000,
protocol: elbv2.Protocol.TCP,
targets: [service],
healthCheck: { path: '/health', port: '8080' }, // porta do proxy
deregistrationDelay: Duration.seconds(30),
});
Multi-LoRA no vLLM
Um único servidor GPU com múltiplos adaptadores por especialidade — sem instâncias separadas.
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3.1-70B-Instruct \
--tensor-parallel-size 4 \
--enable-lora \
--max-loras 4 # simultâneos na GPU \
--max-cpu-loras 8 # swap para CPU quando necessário \
--max-lora-rank 64 # deve bater com o rank do fine-tuning \
--enable-auto-tool-choice \
--tool-call-parser llama3_json
# Registrar LoRA
curl -X POST http://vllm:8000/v1/load_lora_adapter \
-H "Content-Type: application/json" \
-d '{"lora_name": "finance-specialist", "lora_path": "/loras/finance"}'
# Listar LoRAs carregados
curl http://vllm:8000/v1/models
# Remover LoRA
curl -X POST http://vllm:8000/v1/unload_lora_adapter \
-d '{"lora_name": "finance-specialist"}'
Warm Pool no Auto Scaling Group
Instâncias pré-aquecidas em standby — elimina cold start de 20+ minutos.
| Pool State | Custo idle | Tempo p/ InService | Uso recomendado |
|---|---|---|---|
| Stopped | ~$0.10/h (só EBS) | 3–4 min | Produção econômica ✅ |
| Running | ~$16/h (GPU idle) | 1–2 min | Latência crítica |
// Warm Pool — instâncias paradas (paga só EBS)
const cfnAsg = asg.node.defaultChild as autoscaling.CfnAutoScalingGroup;
cfnAsg.warmPool = {
minSize: 1,
maxGroupPreparedCapacity: 2,
poolState: 'Stopped', // economiza GPU $$$
instanceReusePolicy: {
reuseOnScaleIn: true, // devolve ao pool, não termina
},
};
// Lifecycle Hook — pausa até modelo estar pronto
asg.addLifecycleHook('WaitForModelLoad', {
lifecycleTransition: autoscaling.LifecycleTransition.INSTANCE_LAUNCHING,
heartbeatTimeout: Duration.minutes(20),
defaultResult: autoscaling.DefaultResult.ABANDON, // descarta se falhar
notificationTarget: new hooks.TopicHook(warmupTopic),
});
blockDevices: [
{ deviceName: '/dev/xvda', volume: ec2.BlockDeviceVolume.ebs(100) },
{
deviceName: '/dev/sdb',
volume: ec2.BlockDeviceVolume.ebsFromSnapshot('snap-0abc123def', {
volumeSize: 500, // GB — modelo + LoRAs
volumeType: ec2.EbsDeviceVolumeType.GP3,
iops: 16000, // máximo GP3
throughput: 1000, // MB/s — máximo GP3
}),
},
],
LoRA Warmup Script
NLB só recebe tráfego após todos os LoRAs estarem carregados e aquecidos.
#!/bin/bash
set -e
# 1. Inicia vLLM em background
python -m vllm.entrypoints.openai.api_server \
--model "${MODEL_NAME}" \
--enable-lora --max-loras "${MAX_LORAS:-4}" \
--port "${VLLM_PORT:-8000}" &
VLLM_PID=$!
# 2. Aguarda vLLM base estar healthy
until curl -sf "http://localhost:${VLLM_PORT}/health" > /dev/null; do
sleep 5
kill -0 $VLLM_PID 2>/dev/null || { echo "vLLM morreu!"; exit 1; }
done
# 3. Registra e aquece cada LoRA
for lora_path in "${LORAS_DIR}"/*/; do
lora_name=$(basename "$lora_path")
# Registra
curl -sf -X POST "http://localhost:${VLLM_PORT}/v1/load_lora_adapter" \
-H "Content-Type: application/json" \
-d "{\"lora_name\": \"${lora_name}\", \"lora_path\": \"${lora_path}\"}"
# Warmup (aquece CUDA kernels)
curl -sf -X POST "http://localhost:${VLLM_PORT}/v1/chat/completions" \
-d "{\"model\": \"${lora_name}\", \"messages\": [{\"role\": \"user\", \"content\": \"Hi\"}], \"max_tokens\": 1}" \
> /dev/null
done
# 4. Sobe proxy — NLB só roteia agora
python /healthcheck_proxy.py --proxy-port 8080 &
# 5. Mantém container vivo
wait $VLLM_PID
Arquitetura Completa
Visão end-to-end: Supervisor Account → Agents Account → vLLM com Multi-LoRA.
┌─────────────────────────────────────────────────────────────────────┐ │ SUPERVISOR ACCOUNT │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ ECS Fargate — LangGraph Supervisor │ │ │ │ ChatOpenAI(base_url=NLB_DNS) ← zero mudança de código │ │ │ └────────────────────────────────────┬─────────────────────────┘ │ └───────────────────────────────────────┼─────────────────────────────┘ │ JWT Bearer / VPC Peering ┌───────────────────────────────────────┼─────────────────────────────┐ │ AGENTS ACCOUNT │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ NLB Interno (porta 8000 tráfego · porta 8080 health) │ │ │ └────────────────────────────────────┬─────────────────────────┘ │ │ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ ECS EC2 (g5.12xlarge — 4× A10G GPU, 96GB VRAM) │ │ │ │ │ │ │ │ vLLM Server (Llama 3.1 70B base — carregado na GPU) │ │ │ │ ├── LoRA: finance-specialist ← GPU │ │ │ │ ├── LoRA: legal-specialist ← GPU │ │ │ │ ├── LoRA: support-specialist ← CPU swap │ │ │ │ │ │ │ │ │ healthcheck_proxy :8080 ← gate do NLB │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ ASG Warm Pool (2× Stopped · EBS snapshot c/ modelo) │ │ │ │ Lifecycle Hook → Lambda → wait /health → CONTINUE │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ S3: s3://models-bucket/loras/ ← adaptadores LoRA │ └─────────────────────────────────────────────────────────────────────┘
Timeline de Cold Start Otimizado
Com Warm Pool + EBS Snapshot + LoRA Warmup: ~8 min vs 20–25 min sem otimizações.
| Cenário | ECS Warm Pool | EBS Snapshot | LoRA Warmup | Total |
|---|---|---|---|---|
| Sem otimizações | ❌ Cold EC2 | ❌ S3 download | ❌ Lazy load | 20–25 min |
| Com todas as otimizações | ✅ Pool Stopped | ✅ GP3 local | ✅ Pre-warmed | ~8 min |