Contexto e Objetivos do Teste
Implementamos o modelo de geração de texto ERNIE-4.5-0.3B-PT usando vLLM em ambiente produtivo com frontend chainlit. Durante testes prolongados sob alta concorrência, identificamos crescimento progressivo no consumo de memória. O objetivo principal foi validar a estabilidade durante 7 dias sob carga intensa e diagnosticar eventuais vazamentos de memória.
Configuração do Ambiente
- Hardware: 8 CPUs, 32GB RAM, GPU NVIDIA T4
- Modelo: ERNIE-4.5-0.3B-PT
- Versões: vLLM 0.2.7, chainlit 1.0.0
- Carga: 50 clientes simultâneos, 10-20 requisições/segundo
Metodologia de Teste
Utilizamos Locust para simular acesso concorrente com diferentes prompts de texto. Monitoramos:
- Uso de memória (RSS, VMS)
- Consumo de GPU
- Latência das requisições (P50, P95, P99)
class SimuladorCarga(HttpUser):
intervalo = between(0.1, 0.5)
@task
def requisitar_texto(self):
prompts = ["Explique o aprendizado profundo", "Descreva futuros da IA"]
payload = {"prompt": random.choice(prompts)}
self.client.post("/gerar", json=payload)
Análise do Vazamento
Após 24 horas, observamos crescimento linear de ~70MB/hora na memória. A análise com tracemalloc indicou problemas em:
- Acúmulo de histórico em filas
- Objetos não liberados no cache
- Referências persistentes em callbacks
Diagnóstico Detalhado
Identificamos duas causas principais:
- Cache KV: Liberação inadequada durante sequências variáveis
- Ciclo de vida das requisições: Recursos não liberados após desconexões
# Ponto crítico: Dicionário sem limpeza
cache_respostas = {}
def obter_resposta_cache(texto):
if texto not in cache_respostas:
resposta = gerar_resposta(texto)
cache_respostas[texto] = resposta
return cache_respostas[texto]
# Solução: Implementar LRU
from functools import lru_cache
@lru_cache(maxsize=1000)
def obter_resposta_cache(texto):
return gerar_resposta(texto)
Correções Implementadas
Gerenciamento de cache revisado:
class CacheKV:
def __init__(self, limite=1000, expiracao=300):
self.registros = {}
self.limite = limite
self.expiracao = expiracao
def limpar(self):
# Lógica de remoção por tempo e uso
chaves_excluir = [chave for chave, valor in self.registros.items()
if tempo_atual - valor['acesso'] > self.expiracao]
for chave in chaves_excluir:
del self.registros[chave]
Monitoramento preventivo:
def manutencao_memoria():
while True:
time.sleep(3600)
gc.collect()
torch.cuda.empty_cache()
Resultados Pós-Correção
| Métrica | Antes | Após |
|---|---|---|
| Crescimento memória/hora | 50-80MB | <5MB |
| Uso após 72h | 12-15GB | 4-4.2GB |
| Taxa de timeout | 1.2% | 0.3% |
Boas Práticas
- Monitoarr tendências de consumo como linha de base
- Implementar mecanismos de limpeza periódica
- Validar liberação de recursos durante revisões de código