Phase 17 - Lesson 22

Testes de Carga em APIs de LLM — Por que o k6 e o Locust Mentem

Os testadores de carga tradicionais não foram projetados para respostas em streaming, comprimentos de saída variáveis, métricas em nível de token ou saturação de GPU. Duas armadilhas afetam a maioria das equipes. A armadilha do GIL: a medição em nível de token do Locust executa a tokenização sob o GIL do Python, o que compete com a geração de requisições sob alta concorrência; o acúmulo de tokenização então infla a latência entre tokens reportada — seu cliente é o gargalo, não o servidor. A armadilha da uniformidade de prompts: prompts idênticos em um loop testam apenas um ponto na distribuição de tokens; o tráfego real possui comprimentos variáveis e correspondências de prefixo diversas. O LLMPerf corrige isso com --mean-input-tokens + --stddev-input-tokens. Mapeamento de ferramentas em 2026: especializadas em LLM (GenAI-Perf, LLMPerf, LLM-Locust, guidellm) para precisão em nível de token; k6 v2026.1.0 + k6 Operator 1.0 GA (Set 2025) — ciente de streaming, distribuído nativo de Kubernetes via CRDs TestRun/PrivateLoadZone, melhor para portas de CI/CD; Vegeta para saturação de taxa constante em Go; Locust 2.43.3 apenas com a extensão LLM-Locust para streaming. Padrões de carga: estado estável, rampa, pico (teste de autoescalonamento), imersão (vazamentos de memória).

Tipo: Build Linguagens: Python (stdlib, gerador de prompts realistas de brinquedo + coletor de latência) Pré-requisitos: Phase 17 · 08 (Inference Metrics), Phase 17 · 03 (GPU Autoscaling) Tempo: ~75 minutos

Objetivos de Aprendizagem

  • Explicar os dois antipadrões (armadilha do GIL, armadilha da uniformidade de prompts) que fazem com que testadores de carga genéricos mintam para APIs de LLM.
  • Escolher uma ferramenta para um determinado propósito: LLMPerf (execução de benchmark), k6 + extensão de streaming (porta de CI), guidellm (sintético em larga escala), GenAI-Perf (referência da NVIDIA).
  • Projetar quatro padrões de carga (estável, rampa, pico, imersão) e nomear o modo de falha que cada um captura.
  • Construir uma distribuição de prompts realista usando a média + desvio padrão de tokens de entrada em vez de um comprimento fixo.

O Problema

Você testou seu endpoint de LLM com o k6 com 500 usuários concorrentes. Ele aguentou. Você fez o deploy. Em produção com 200 usuários reais o serviço caiu — o TTFT P99 explodiu, as GPUs travaram.

Duas coisas aconteceram. Primeiro, o k6 enviou 500 prompts idênticos — seu agrupamento de requisições (request-coalescing) e cache de prefixo fizeram parecer que você estava processando 500 decodificações concorrentes quando, na verdade, estava processando apenas uma. Segundo, o k6 não rastreia a latência entre tokens (inter-token latency) em respostas de streaming da forma como o usuário final a experimenta; ele vê uma conexão HTTP, não 500 tokens chegando em intervalos variados.

O teste de carga para LLMs é uma disciplina própria.

O Conceito

A armadilha do GIL (Locust)

O Locust usa Python e executa a tokenização no lado do cliente sob o GIL. Sob alta concorrência, o tokenizador entra na fila atrás da geração de requisições. A latência entre tokens reportada inclui o acúmulo de tokenização no lado do cliente. Você acha que o servidor está lento; mas é o ambiente de teste.

Correção: a extensão LLM-Locust move a tokenização para processos separados, ou use um ambiente de linguagem compilada (k6, LLMPerf usando tokenizers.rs).

A armadilha da uniformidade de prompts

Todos os testadores de carga conhecidos permitem configurar um único prompt. Em um teste de loop de 10.000 iterações, o exato mesmo prompt é enviado todas as vezes. O servidor vê o mesmo prefixo sempre — as taxas de acerto do cache de prefixo (prefix cache hits) se aproximam de 100%, e a taxa de transferência parece ótima.

Correção: faça amostragem a partir de uma distribuição de prompts. O LLMPerf usa --mean-input-tokens 500 --stddev-input-tokens 150 — comprimentos diversos, conteúdos diversos.

Quatro padrões de carga

  1. Steady-state — RPS constante por 30-60 min. Captura: regressões de desempenho de linha de base.
  2. Ramp — aumenta linearmente o RPS de 0 até o alvo ao longo de 15 min. Captura: ponto de ruptura de capacidade, anomalias de aquecimento (warm-up).
  3. Spike — RPS repentino de 3-10x por 2 min e depois retorna ao normal. Captura: latência de autoescalonamento, saturação de fila, impacto de partida a frio (cold-start).
  4. Soak — steady-state por 4-8 horas. Captura: vazamentos de memória, desvio de pool de conexões (connection-pool drift), estouro de observabilidade.

Mapeamento de ferramentas para 2026

LLMPerf (Anyscale) — Python, mas com tokenização baseada em Rust. Prompts com média/desvio padrão. Ciente de streaming. Melhor opção padrão para execuções de desempenho.

NVIDIA GenAI-Perf — Referência da NVIDIA. Usa o cliente Triton; cobertura abrangente de métricas. Observe que o seu ITL exclui o TTFT; o do LLMPerf o inclui. As duas ferramentas produzem TPOT diferentes para o mesmo servidor.

LLM-Locust (TrueFoundry) — Extensão do Locust que corrige a armadilha do GIL. DSL familiar do Locust + métricas de streaming.

guidellm — benchmarking sintético de larga escala.

k6 v2026.1.0 + k6 Operator 1.0 GA (Set 2025):

  • O próprio k6 (Go, compilado, sem GIL) adicionou métricas cientes de streaming.
  • O k6 Operator usa CRDs TestRun / PrivateLoadZone para testes distribuídos nativos do Kubernetes.
  • Melhor para portas de CI/CD e testes de SLA.

Vegeta — Go, mais simples que o k6. Saturação de HTTP com taxa constante. Não é ciente de LLM, mas é bom para testes de gateway / limite de taxa.

Locust 2.43.3 stock — possui a armadilha do GIL para LLM. Apenas com a extensão LLM-Locust.

Porta de SLA no CI

Execute o k6 no PR com:

  • 30-50 iterações, cada uma no RPS de linha de base.
  • Porta: TTFT P50/P95, 5xx < 5%, TPOT abaixo do limite.
  • Quebre o build em caso de violação.

Distribuição de prompts realista

Construa a partir de amostras de tráfego real (se as tiver) ou de distribuições publicadas (por exemplo, prompts do ShareGPT para chat, HumanEval para código). Forneça a média + desvio padrão para o LLMPerf. Evite loops com um único prompt a todo custo.

Números que você deve lembrar

  • k6 Operator 1.0 GA: Setembro de 2025.
  • k6 v2026.1.0: métricas cientes de streaming.
  • Execução típica do LLMPerf: 100-1000 requisições com concorrência X.
  • Porta de CI típica: 30-50 iterações por PR.
  • Quatro padrões: estável, rampa, pico, imersão.

Use

code/main.py simula um teste de carga com uma distribuição realista de prompts, mede o TPOT efetivo e demonstra a armadilha do prompt uniforme.

Entregue

Esta lição produz outputs/skill-load-test-plan.md. Dados o volume de trabalho e o SLA, escolhe a ferramenta e projeta os quatro padrões de carga.

Exercícios

  1. Execute code/main.py. Compare a distribuição uniforme versus a realista — onde está a diferença?
  2. Escreva o script do k6 para uma porta de CI: TTFT P95 < 800 ms com 100 concorrentes, tempo de execução de 5 minutos.
  3. Seu teste de imersão (soak) mostra a memória crescendo 50 MB/hora. Nomeie três causas e a instrumentação para escolher entre elas.
  4. Teste de pico de 10 RPS para 100 RPS. Qual é o tempo de recuperação esperado se a pilha de produção Karpenter + vLLM estiver instalada (Phase 17 · 03 + 18)?
  5. O GenAI-Perf reporta TPOT=6ms; o LLMPerf reporta TPOT=11ms no mesmo servidor. Explique.

Termos-Chave

Termo O que as pessoas dizem O que realmente significa
LLMPerf "o harness de LLM" Ferramenta de benchmark da Anyscale, ciente de streaming
GenAI-Perf "ferramenta da NVIDIA" Harness de referência da NVIDIA
LLM-Locust "Locust para LLMs" Extensão do Locust que corrige a armadilha do GIL
guidellm "benchmark sintético" Ferramenta de síntese em larga escala
k6 Operator "k6 para K8s" k6 distribuído baseado em CRD
GIL trap "sobrecarga do cliente Python" O acúmulo de tokenização infla a latência reportada
Prompt-uniformity trap "a mentira do prompt único" Loop com o mesmo prompt atinge o cache, inflando a taxa de transferência
Steady-state "carga constante" RPS estável por N minutos
Ramp "subida linear" De 0 até o alvo ao longo da duração
Spike "teste de explosão" Multiplicador repentino de carga e depois reversão
Soak "teste longo" Horas de execução para detecção de vazamentos

Leituras Adicionais

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).