Skip to content

English · Español

04 — Inferencia distribuida: TP para servir y prefill/decode desagregados

🇪🇸 Inferencia no es entrenamiento. No hay backward, no hay gradientes, no hay optimizador. Lo que sí hay: KV cache que crece por token, peticiones concurrentes con shapes distintos, y el famoso desbalance prefill (compute-bound) vs decode (memory-bound). La paralelización para servir tiene reglas distintas.

El entrenamiento distribuido y la inferencia distribuida tienen cuellos de botella diferentes. Esta página mapea las diferencias y cubre dos patrones: inferencia tensor parallel (la que tocamos en el lab 02) y prefill/decode desagregados (la frontera actual; cobertura conceptual solamente).


Por qué la inferencia es distinta del entrenamiento

En entrenamiento:

  • Forward + backward + optimizador por step.
  • Cada step tiene la misma shape (después de hacer padding al máximo).
  • Puedes meter muchas secuencias en un batch → alta intensidad aritmética → compute-bound.
  • La comunicación es una vez por step (all-reduce de DDP) o varias veces (ZeRO/TP), pero predecible.

En inferencia:

  • Solo forward. Sin gradientes, sin estado del optimizador, sin momentum de Adam.
  • Las peticiones llegan con longitudes de prompt distintas y longitudes de salida distintas → difícil hacer batch perfecto.
  • Prefill (procesar el prompt) es compute-bound (matmuls grandes matriz-matriz).
  • Decode (un token cada vez después del prefill) es memory-bound — cargar una fila de pesos, hacer un matmul matriz-vector, repetir.
  • Las dos fases tienen perfiles de hardware opuestos. Una sola GPU ejecutando ambas está sobre- e infrautilizada en momentos distintos.

Consecuencias:

  1. Los clústeres de inferencia necesitan objetivos de optimización distintos. El rendimiento (throughput) por GPU importa menos que la latencia por petición (TTFT, ITL).
  2. Las estrategias de sharding que funcionan en entrenamiento pueden no funcionar en inferencia, y viceversa.
  3. El mismo tamaño de modelo que requiere TP en 16 GPUs para entrenar puede caber en 1 GPU para inferencia (sin gradientes, sin estado del optimizador — reducción de memoria 4×).

Inferencia tensor parallel

El esquema TP de theory/02-parallelism-flavors.md también funciona para inferencia: hacer shard de los pesos de cada capa, ejecutar forward con all-reduces intra-capa.

Qué cambia respecto al entrenamiento

  • Sin backward → no hace falta el segundo lote de all-reduces.
  • El KV cache se hace shard en el mismo eje que las proyecciones Q/K/V — cada worker TP guarda sus n_heads / N heads de KV.
  • El all-reduce ocurre en el residual stream tras la proyección de salida de attention y tras la down-projection del MLP — mismo patrón que en entrenamiento, la mitad del volumen.

¿Cuándo merece la pena la inferencia TP?

Situación ¿Usar inferencia TP?
El modelo cabe en una GPU, un solo usuario No. GPU única es más simple y rápida (sin comm).
El modelo cabe en una GPU, muchos usuarios Quizá. TP recorta latencia por petición a cambio de más comm por token.
El modelo es demasiado grande para una GPU Sí. TP es la opción estándar.
Requisito de TTFT ajustado TP puede ayudar — divide el cómputo de prefill entre workers.
Requisito ajustado de tokens/seg/$ Una GPU + continuous batching suele ganar (sin comm).

Para el tutor de gramática: el modelo cabe en una calculadora, así que productivamente la inferencia TP es un desperdicio. El lab 02 la ejecuta igualmente porque la única manera de ver el patrón all-reduce es escribir el all-reduce. Resultado esperado: 2-GPU TP es más lento que 1-GPU para este modelo diminuto. Esa es la lección. El volumen de comm cruza al volumen de cómputo; nunca desplegarías así.

El escenario prospectivo: un tutor de gramática hipotético de 600k-formas (5 lenguas × 20 verbos × 5 tiempos × 3 personas × paráfrasis) con \(d_{\text{model}} = 4096\) necesitaría TP-sharding de la tabla de embeddings solo para caber. Ese es el punto en el que el patrón del lab 02 se vuelve económicamente correcto.


Prefill/decode desagregados

Un patrón frontera para servir (Splitwise, DistServe, Mooncake): workers separados se encargan de prefill vs decode, en lugar de un solo worker haciendo ambos.

Por qué desagregar

Prefill y decode tienen hardware óptimo distinto:

  • Prefill: compute-bound. Quiere FLOPs/$ altos. Una A100 o H100 con SM count alto va estupenda. Sensible a latencia (esto es el TTFT).
  • Decode: memory-bound. Quiere ancho de banda de memoria/$ alto. La H100 tiene ambos; una Hopper o incluso una Gaudi pueden valer. También sensible a latencia (esto es el ITL).

En un clúster compartido único, las instancias de prefill ocasionalmente quedan ociosas (no llegan prompts nuevos) mientras las de decode trabajan a tope (muchos usuarios concurrentes generando). La aritmética para un clúster co-localizado dice que aprovisionas para el pico de cualquiera — derrochador.

Desagregar significa: aprovisionar un pool pequeño de prefill dimensionado para la tasa de llegada de prompts, y un pool más grande de decode dimensionado para la generación concurrente. El KV cache se transfiere del worker de prefill al worker de decode una vez por petición (RDMA o NVLink). La transferencia es un coste único amortizado sobre todo el decode.

Esquema del patrón

Cliente → Router → Pool de prefill (pequeño, ajustado a cómputo)
                       │ (hand-off del KV cache, RDMA)
                  Pool de decode (más grande, ajustado a ancho de banda)
                       │ (stream de tokens)
                  Cliente (streaming)

El router decide cuándo reenviar; la transferencia del KV cache es el nuevo componente de latencia a medir (sumado al TTFT).

Por qué no entra en este currículo

Implementar prefill/decode desagregados requiere:

  • Dos pools de GPU escalados por separado (≥ 4 GPUs).
  • Interconexión con capacidad RDMA para la transferencia de KV.
  • Un router de peticiones que conozca el ciclo de vida de la petición.

Coste total del sistema: $5+/hr. Presupuesto de la Fase 35: $5 en total. La cuenta dice leer papers, no implementar. El lab 03 incluye una lectura anotada breve del paper de DistServe.


Speculative decoding

Una optimización distinta, mencionada aquí porque a menudo se despliega junto con TP / inferencia desagregada.

Un modelo draft pequeño propone los siguientes \(k\) tokens de forma barata; el modelo target (real) verifica los \(k\) en un solo forward pass y acepta el prefijo correcto más largo. Resultado: en lugar de un token por forward, obtienes 2–5 tokens por forward de media.

Para el tutor de gramática: el modelo draft tendría que ser aún más diminuto que el tutor ya diminuto. Probablemente un modelo de n-gramas de la Fase 14. Conceptualmente factible, en la práctica no merece el coste de implementación.

La Fase 36 cubre Medusa / EAGLE / Lookahead con más profundidad (esas son variantes arquitectónicas de speculative decoding).


Checklist de inferencia distribuida

Al diseñar un setup de inferencia distribuida:

  1. ¿Cabe el modelo en una GPU? Si sí, empieza con GPU única.
  2. ¿Eres sensible a la latencia (TTFT/ITL)? Si sí, considera TP dentro de un nodo.
  3. ¿Eres sensible al throughput (tokens/seg/$)? Primero GPU única + continuous batching; añade TP solo si la memoria fuerza.
  4. ¿Tu KV cache es > 50% de la memoria? Mira PagedAttention (vLLM) antes de añadir más GPUs.
  5. ¿Están desbalanceadas las cargas de prefill y decode? Prefill/decode desagregados — pero solo a escala suficiente.

Para el tutor de gramática: la respuesta es "no, no, no, no, no" — una GPU (o CPU) basta. El lab 02 viola la checklist intencionalmente para enseñar.


Lo que esta fase NO cubre

  • Implementar prefill/decode desagregados. Solo lectura.
  • Implementación de speculative decoding (Medusa, EAGLE, Lookahead). Territorio de la Fase 36; aquí solo vocabulario.
  • Routing de inferencia cross-region / cross-AZ. Territorio de production-ops; Fase 38.
  • Autoscaling de inferencia por tokens/seg. Fase 38.
  • Offloading de KV cache entre workers (pools de memoria CXL, etc.). Frontera; solo vocabulario.

Siguiente: lab/00-cloud-budget-and-tooling.md.