Capítulo 12 — Serving Desagregado e Kubernetes
Décimo segundo post do passeio capítulo a capítulo pelo LLM Primer VI: Escalando Sistemas de IA. O capítulo que finalmente separa prefill e decodificação em pools de GPU distintos e mostra as primitivas de Kubernetes que mantêm os pods no lado certo do interconnect.
Por que este capítulo existe
O capítulo 11 traçou uma linha limpa entre engines e plataformas — até o próprio engine apontar que prefill e decodificação são cargas contraditórias. Prefill é compute-bound; decodificação é limitado pela largura de banda de memória. Rodar os dois na mesma GPU obriga um único pedaço de hardware a ser bom em duas coisas contraditórias, e ele acaba medíocre nas duas. O serving desagregado os separa em pools de GPU distintos, transporta a KV cache entre os pools e permite que cada pool seja ajustado para a sua própria carga. O preço é a complexidade operacional, paga em primitivas de Kubernetes, topologia de pods e no fabric de rede que carrega o tráfego de KV. O capítulo 12 percorre a forma dessa complexidade — os componentes, o caminho de transferência e os CRDs (LeaderWorkerSet, PodCliqueSet do Grove, KAI Scheduler) que a expressam.
12.1 Prefill e decodificação querem chips diferentes em relógios diferentes
Numa mesma H100, um prefill longo consegue de fato chegar a 60–80 por cento do pico de FLOPs, e uma iteração de decodificação usa 5–15 por cento de FLOPs enquanto satura 70–90 por cento da largura de banda da HBM. Intercalar as duas sob batching contínuo é um compromisso viável, mas vaza em dois pontos. A passagem de compute do prefill bate no mesmo barramento de HBM que as decodificações enfileiradas estão tentando escoar, e o tempo entre tokens infla. A placa também roda em um único ponto de operação: se ajustar para prefill, decodificação fica limitada por banda; se ajustar para decodificação, prefill fica limitado por compute. Os prompts alongaram-se de centenas para milhares de tokens conforme RAG e agentes tomaram conta do tráfego de produção, e o custo da intercalação cresceu junto. A desagregação dá a cada fase seu próprio pool. Os workers de prefill podem rodar em placas focadas em FLOPs (H100 SXM, B200). Os workers de decodificação podem rodar em placas com capacidade e banda (H200, MI300X). Cada pool é dimensionado à sua própria carga — tráfego de contexto longo e saída curta pede mais prefill; tráfego de chat pede mais decodificação.
12.2 O formato é quatro componentes mais um caminho de transferência de KV
Um deployment desagregado funcional tem quatro componentes. Os workers de prefill aceitam a requisição, calculam o prefill, emitem o primeiro token e preparam a KV cache para transferência. Os workers de decodificação aceitam uma KV cache que chega junto com seus metadados, a instalam no seu pool de paged-attention e rodam batching contínuo sobre suas gerações em voo. Um roteador de KV cache — o control plane — mantém uma visão da capacidade livre de KV de cada worker de decodificação, do nível de ocupação do batch e da localidade de rede, e atribui a saída de cada prefill de forma a minimizar custo de transferência e equilibrar carga. Um gateway de frontend termina a conexão HTTP do usuário, envia o primeiro token vindo do worker de prefill em streaming, e depois muda de forma transparente para o streaming do restante vindo do worker de decodificação. A restrição crítica é a transferência de KV: uma sequência 70B GQA com 4.096 tokens ocupa 1,5–2 GB e precisa se mover do nó de prefill para o nó de decodificação dentro do mesmo orçamento de 50–100 ms de TTFT que o usuário está observando. NVLink a 900 GB/s dentro de um nó e InfiniBand a 400 Gb/s entre nós fazem as contas fecharem — se os pods pousarem do lado certo do fabric.
12.3 LeaderWorkerSet, Grove e KAI expressam a topologia
O Kubernetes não tinha originalmente uma primitiva para "esses dois pods tipados são uma única réplica lógica". O LeaderWorkerSet, adicionado upstream em 2024, expressa uma réplica multi-pod com um leader (digamos, prefill) e workers (digamos, decodificações); o controller mantém o grupo como uma única unidade de escalonamento. O PodCliqueSet do NVIDIA Grove (2025, parte do NVIDIA AI Enterprise) generaliza mais, com cliques tipados (prefill, decodificação, roteador), cada um com seu próprio template e tamanho, além de uma descrição de topologia de cliques indicando como eles se relacionam. O Grove submete o conjunto todo ao scheduler junto com restrições de localidade intra-clique e inter-clique. Nenhum dos dois CRDs é suficiente sozinho; ambos precisam de um scheduler que conheça a topologia física do cluster, o que o scheduler padrão do Kubernetes não conhece. O KAI Scheduler — open-sourced pela Run:ai em 2024 — consome um grafo de topologia produzido por nvidia-smi topo --matrix e pelo subnet manager do InfiniBand, e pontua colocações candidatas contra ele. Quando uma clique do Grove pede nvlinkDomain: required, o KAI restringe a alocação a um único domínio coerente de NVLink (as oito GPUs de um baseboard HGX num NVSwitch). Quando a topologia inter-clique diz sameInfiniBandIsland, o KAI mantém as cliques dentro de um grupo de leaf switch onde o round-trip de KV fica abaixo de 100 μs.
O que o capítulo 12 prepara
O capítulo 12 deu ao deployment uma foto estática: em qualquer instante, um número fixo de cliques está rodando e servindo tráfego. O tráfego real não é estático — assistentes de chat variam 30× entre pico e vale ao longo do dia, ferramentas de desenvolvedor vão a zero nos finais de semana, produtos de consumo perseguem o sol. O capítulo 13 percorre a história do autoscaling: por que o HPA padrão é o scaler errado para serving de LLM, quais sinais o KEDA usa em vez dele, como o Knative expressa o scale-to-zero e o que comprime um cold start de 60–180 segundos em algo que uma aplicação voltada ao usuário consegue absorver.
Próximo — Capítulo 13: Autoscaling e Mitigação de Cold Start. KEDA, Knative, CRIU, cache de CUDA graphs, NVMe — a pilha que permite ao scale-to-zero coexistir com usuários reais.