Skip to content

English · Español

00 — Por qué servir por HTTP no es solo while True: agent.correct()

Tu tutor de gramática es una función de Python. Servirla por HTTP introduce un mundo nuevo: concurrencia, colas, latencia (latency) de cola, batching. Esta fase trata sobre lo que hay entre el cliente y agent.correct().

La forma de una petición de inferencia

Un usuario teclea "He goed to school" en algún frontend. Desde ese momento hasta que "ve la corrección":

cliente → DNS → load balancer → reverse proxy → proceso FastAPI → handler → agente → modelo → response (a la inversa)
                                                       └─ esta fase está aquí

La Fase 33 trata de todo lo que ocurre después de que FastAPI reciba la request y antes de construir la response.

El caso de una sola petición (línea base de la Fase 32)

En la Fase 32 construimos agent.correct(sentence) -> Correction. Llamarlo una vez tarda algún tiempo de reloj \(T\). Para nuestro Mini-GPT en CPU, \(T\) podría ser 200-500 ms para una frase corta (4-8 tokens a generar, sin KV cache todavía — la Fase 22 arregló eso).

Si solo existiera una request a la vez, escribiríamos:

@app.post("/correct")
def correct(req: CorrectRequest) -> CorrectResponse:
    correction = agent.correct(req.sentence)
    return CorrectResponse(corrected=correction.text, explanation=correction.why)

Listo. Lo despliegas y eres un "MLOps engineer".

El caso de N peticiones concurrentes

Ahora imagina 50 usuarios mandando peticiones en el mismo segundo.

Sync ingenuo: el worker de FastAPI las atiende una a una. La request 50 espera a que se completen la 1 a la 49. Si cada una tarda 300 ms, la request 50 espera 15 segundos. Latencia de cola: terrible.

Async + offload a thread: el event loop entrelaza, pero cada llamada al modelo sigue ocupando un thread de CPU. Si tienes 8 threads, el throughput está acotado en \(8 / T\) req/s. La latencia de cola mejora pero sigues haciendo 50 forward passes separados.

Static batching: "Espera hasta 100 ms, luego mete en un batch lo que tengas y ejecútalos juntos." El throughput sube (un forward pass batched amortiza el coste fijo del modelo). Pero: la request más lenta del batch determina la latencia (latency) de todas ellas. Las requests rápidas pagan por las lentas.

Continuous batching: en cada paso de decode de token, coge las requests in-flight que necesitan el siguiente token y ejecuta un paso para todas. Cuando una request emite EOS, sale del batch inmediatamente — las otras siguen. Pueden entrar nuevas requests al batch entre pasos. Este es el punto dulce de producción.

Esta fase recorre los cuatro puntos.

Lo que vas a sentir

Al final del lab 03 habrás tecleado tú el scheduler de continuous batching — no usado uno. El scheduler son ~150 líneas de Python. Una vez lo has escrito, palabras como "in-flight batching", "iteration-level scheduling" y "slot manager de PagedAttention" dejan de ser magia.

También descubrirás (haciendo load test a tu propio servicio) que el cuello de botella se mueve. Sin batching, el cuello de botella es el coste por petición del modelo. Con batching, el cuello de botella pasa a ser el overhead del scheduler — y si haces batching demasiado agresivo, la propia cola se convierte en el cuello de botella.

Por qué CPU-only está bien aquí

El Mini-GPT es lo bastante pequeño como para que un forward pass en CPU tarde ~10-50 ms. Eso es comparable a un round-trip de red. El continuous batching muestra ganancias claras incluso a esa escala porque lo que medimos es la lógica de scheduling, no FLOPs brutos.

Para un LLM de 70B de parámetros en una H100, aplica la misma lógica de scheduling — cambian las constantes (forward en GPU = 30 ms, batch size = 256, la memoria KV por request importa más). La forma del problema y del código es la misma.

El número más importante que interiorizar

Para nuestro Mini-GPT generando \(\ell \approx 4\) tokens por corrección en CPU a ~50 tokens/s (~20 ms por paso de decode con la KV cache de la Fase 22), una corrección tarda:

\[T_\text{correct} \approx 4 \text{ tokens} \times 20 \text{ ms/token} = 80 \text{ ms}.\]

Sin batching, el throughput está acotado en \(1 / T_\text{correct} = 12.5\) req/s. Con static batching de 8 requests, el throughput sube a ~\(8 / T_\text{correct} \approx 100\) req/s (descontando el padding). Con continuous batching, el throughput se acerca a la cota pero con una latencia de cola mucho mejor. Orden de magnitud, esto es lo que deberías esperar antes de medir.

Lo que este archivo NO cubre

  • La mecánica del event loop. Siguiente archivo.
  • Las matemáticas del batching. Archivo 02.
  • Dimensionar la cola. Archivo 03.

Siguiente: 01-async-and-the-event-loop.md