Phase 17 - Lesson 04

Componentes Internos de Servicio de vLLM: PagedAttention, Continuous Batching, Chunked Prefill

El dominio de vLLM en 2026 se basa en tres valores predeterminados combinados, no en un solo truque. PagedAttention siempre está activo. Continuous batching inserta nuevas solicitudes en el lote activo entre las iteraciones de decodificación. Chunked prefill divide prompts largos para que los tokens de decodificación nunca se queden sin recursos de procesamiento. Al activar los tres recursos, un Llama 3.3 70B FP8 en una sola H100 SXM5 alcanza entre 2,200 y 2,400 tok/s con 128 conexiones concurrentes: aproximadamente un 25% por encima del valor predeterminado del propio vLLM y 3 a 4 veces más rápido que un loop PyTorch ingenuo. Esta lección detalla el agendador y el kernel de atención a un nivel que puedes diagramar, y termina con un agendador simplificado de continuous batching en code/main.py que agenda el prefill y la decodificación de la misma manera que lo hace vLLM.

Tipo: Aprender Lenguajes: Python (stdlib, simulador simplificado de agendador de continuous batching) Requisitos previos: Phase 17 · 01 (Model Serving), Phase 11 (LLM Engineering) Tiempo: ~75 minutos

Objetivos de Aprendizaje

  • Explicar PagedAttention como un asignador de KV cache: bloques, tablas de bloques y por que la fragmentación se mantiene por debajo del 4% bajo carga de producción.
  • Diagramar continuous batching a nivel de iteración: cómo las secuencias finalizadas dejan el lote y las nuevas se unen sin necesidad de vaciarlo.
  • Describir chunked prefill en una sola frase y nombrar qué métrica de latencia protege (pista: es la cola del TTFT, no el rendimiento promedio).
  • Nombrar la trampa de vLLM v0.18.0 de 2026 que afecta a los equipos al activar todas las optimizaciones al mismo tiempo.

El Problema

Un loop de servicio PyTorch ingenuo procesa una solicitud a la vez: tokenizar, prefill, decodificar hasta encontrar el token de fin de secuencia (EOS), retornar. Con un solo usuario, esto funciona. Con cien, genera una fila de personas esperando pacientemente. La solución obvia — static batching (lote estático) — rellena todas las solicitudes hasta el prompt más largo de la ventana de tiempo, rellena cada decodificación hasta la salida esperada más larga y detiene todo el lote en la secuencia más lenta. Pagas por un relleno (padding) que nunca usas y las solicitudes rápidas esperan a las lentas.

vLLM resuelve tres problemas a la vez. PagedAttention evita que la fragmentación de la KV cache consuma del 60% al 80% de la memoria de la GPU, lo que ocurre en el esquema clásico de asignación contigua. Continuous batching permite que las solicitudes se unan y abandonen el lote entre cada iteración de decodificación, de modo que el lote siempre esté lleno de trabajo real. Chunked prefill divide un prompt de 32k tokens en fragmentos de ~512 tokens que se intercalan con la decodificación, evitando que un prompt largo congele todos los demás tokens de decodificación en la GPU.

El valor predeterminado de producción para 2026 es mantener los tres activados. Necesitas entender qué hace cada uno porque los modos de falla se encuentran en el agendador, no en el modelo.

El Concepto

PagedAttention como un sistema de memoria virtual

Una KV cache consume num_layers × 2 × num_heads × head_dim × seq_len × bytes_per_element por secuencia. Para Llama 3.3 70B con 8192 tokens, eso equivale a aproximadamente 1.25 GB por secuencia en BF16. Si reservas previamente 8192 posiciones para cada solicitud pero la solicitud promedio solo usa 1500 tokens, desperdicias alrededor del 82% de la memoria de gran ancho de banda (HBM) reservada. El lote clásico paga por este desperdicio.

PagedAttention toma prestada la idea de la memoria virtual de los sistemas operativos. La KV cache no es contigua por secuencia. Se asigna en bloques de tamaño fijo (por defecto, 16 tokens). Cada secuencia tiene una tabla de bloques que mapea sus posiciones lógicas de tokens a IDs de bloques físicos. A medida que una secuencia crece más allá de sus bloques asignados, se agrega un nuevo bloque. Cuando finaliza, sus bloques regresan al pool.

La fragmentación cae del 60-80% (clásico) a menos del 4% (PagedAttention). No activas PagedAttention con una flag: es el único asignador que vLLM ofrece. El parámetro de ajuste es --gpu-memory-utilization (por defecto, 0.9), que le indica a vLLM cuánta HBM debe reservar para los bloques de KV después de cargar los pesos y las activaciones.

Continuous batching a nivel de iteración

El antiguo "dynamic batching" esperaba una ventana de tiempo (por ejemplo, 10 ms) para llenar un lote, luego ejecutaba prefill + decodificación + decodificación hasta que cada secuencia finalizaba. Las secuencias rápidas terminaban antes y se quedaban inactivas mientras la GPU finalizaba las lentas.

Continuous batching opera entre cada paso de decodificación. Llama al conjunto de secuencias en ejecución lista RUNNING. En cada iteración:

  1. Cualquier secuencia en RUNNING que acabe de alcanzar el EOS o el límite máximo de tokens (max_tokens) se elimina.
  2. El agendador analiza la fila de espera. Si hay bloques de KV libres, admite nuevas secuencias (ya sea para prefill o reanudación).
  3. La pasada forward se ejecuta en lo que sea que esté en RUNNING, emitiendo un nuevo token por secuencia.

El tamaño del lote nunca se ajusta a un número fijo de relleno. Las secuencias en diferentes posiciones de sus salidas comparten una sola pasada forward fusionada. En vLLM de 2026, esto se denomina V1 scheduler. La invariante clave: el agendador se ejecuta una vez por iteración de decodificación, no una vez por solicitud.

Chunked prefill protege la cola del TTFT

Prefill está limitado por cómputo (compute-bound). Un prompt de 32k tokens en Llama 3.3 70B tarda aproximadamente 800 ms de prefill puro en una H100. Mientras se ejecuta el prefill, los tokens de decodificación de todas las demás secuencias en el lote esperan. En un loop de servicio, la latencia hasta el primer token (TTFT) de un único prompt largo se convierte en el pico de latencia entre tokens (ITL) para docenas de otros usuarios.

Chunked prefill divide el prefill en fragmentos de tamaño fijo (por defecto, 512 tokens) y agenda cada fragmento como una unidad. Entre los fragmentos, el agendador puede avanzar las secuencias de decodificación en un token. Intercambias un pequeño impacto absoluto en la latencia de prefill (unos pocos ms por fragmento) por una fluctuación (jitter) mucho menor en el tiempo de decodificación. El ITL P99 bajo carga mixta cae de ~50 ms a ~15 ms en benchmarks publicados.

Los tres valores predeterminados interactúan

Las tres características dependen mutuamente. PagedAttention proporciona al agendador un recurso granular de KV para gestionar. Continuous batching necesita ese recurso granular para que la admisión de una nueva secuencia no fuerce una reorganización global. Chunked prefill is a decisión que el agendador toma en la misma lista RUNNING: es una política más del agendador, no un sistema independiente.

No necesitas conocer todas las flags. Necesitas saber qué optimiza el agendador: el goodput bajo el presupuesto de bloques de KV, sujeto al particionamiento del chunked prefill.

La trampa de la versión v0.18.0 de 2026

En vLLM v0.18.0, no puedes combinar --enable-chunked-prefill con decodificación especulativa por modelo de borrador (--speculative-model). La excepción documentada es la decodificación especulativa basada en N-gram en la GPU en el agendador V1. Los equipos que activan todas las flags sin leer las notas de lanzamiento encuentran un error de ejecución en la inicialización, no una regresión sutil de rendimiento. Si la ganancia especulativa justificaba desactivar el chunked prefill, reevalúa la opción: la respuesta correcta en 2026 suele ser EAGLE-3 sin chunked prefill, en lugar de un modelo de borrador más chunked prefill que no compila.

Números que debes recordar

  • Llama 3.3 70B FP8, H100 SXM5, 128 conexiones concurrentes, las tres características activas: entre 2,200 y 2,400 tok/s.
  • Mismo modelo, vLLM predeterminado (sin chunked prefill): ~1,800 tok/s.
  • Mismo modelo, loop forward PyTorch ingenuo: ~600 tok/s.
  • Desperdicio por fragmentación de KV bajo PagedAttention bajo carga de producción: <4%.
  • ITL P99 bajo carga mixta: ~15 ms con chunked prefill, ~50 ms sin él.

Cómo se comporta el agendador

while True:
    finished = [s for s in RUNNING if s.is_done()]
    for s in finished: release_blocks(s); RUNNING.remove(s)

    while WAITING and have_free_blocks_for(WAITING[0]):
        s = WAITING.pop(0)
        allocate_initial_blocks(s)
        RUNNING.append(s)

    # agenda fragmentos de prefill + decode en un solo lote
    batch = []
    for s in RUNNING:
        if s.in_prefill:
            batch.append(next_prefill_chunk(s))   # ej: 512 tokens
        else:
            batch.append(decode_one_token(s))     # 1 token

    run_forward(batch)                            # una llamada única y fusionada en la GPU

El archivo code/main.py contiene exactamente este loop en Python estándar con recuentos de tokens y latencias de forward simuladas. Ejecutarlo demuestra cómo chunked prefill mantiene las secuencias de decodificación activas durante un prefill largo.

Use It

El archivo code/main.py simula un agendador al estilo de vLLM con características que pueden activarse o desactivarse. Ejecútalo para visualizar:

  • Modo NAIVE: una solicitud a la vez, sin procesamiento por lotes.
  • Modo STATIC: relleno y espera, procesamiento por lotes clásico.
  • Modo CONTINUOUS: admisión y liberación a nivel de iteración.
  • Modo CONTINUOUS + CHUNKED: fragmentos de prefill intercalados con decodificación.

La salida muestra el rendimiento total (tokens por segundo virtual), TTFT promedio e ITL P99. La fila CONTINUOUS + CHUNKED debería destacar en el tráfico mixto.

Ship It

Esta lección produce outputs/skill-vllm-scheduler-reader.md. Dada una configuración de servicio (tamaño del lote, utilización de memoria KV, tamaño del chunked prefill, configuración especulativa), genera un diagnóstico del agendador identificando cuál de las tres configuraciones predeterminadas está limitando el rendimiento y qué ajustar.

Exercises

  1. Ejecuta code/main.py. Compara STATIC con CONTINUOUS en una carga de trabajo con una mezcla de solicitudes cortas y largas. ¿De dónde proviene la diferencia de rendimiento: eficiencia de prefill, eficiencia de decodificación o latencia de cola?
  2. Modifica el agendador simplificado para agregar --max-num-batched-tokens. ¿Cuál es el valor correcto para una H100 que ejecuta Llama 3.3 70B FP8? (Pista: es una función del tamaño del bloque de KV y del número de bloques libres, no de la HBM bruta.)
  3. Lee nuevamente las notas de lanzamiento de vLLM v0.18.0. ¿Qué combinaciones de flags son mutuamente excluyentes? Enuméralas.
  4. Calcula el desperdicio por fragmentación de la KV cache para un historial de 1,000 solicitudes con una media de 1,500 tokens de salida, desviación estándar de 600 tokens, bajo (a) asignación contigua por solicitud limitando a un máximo de 8192, (b) PagedAttention con bloques de 16 tokens.
  5. Explica en un párrafo por qué chunked prefill ayuda a la latencia ITL P99, pero no al rendimiento de forma aislada. ¿De dónde proviene la ganancia de rendimiento en la práctica?

Key Terms

Term What people say What it actually means
PagedAttention "el truco de KV" Asignador de bloques de tamaño fijo para KV cache; fragmentación <4%
Block table "la tabla de páginas" Mapa por secuencia de la posición lógica del token al bloque físico de KV
Continuous batching "batching dinámico, pero bien hecho" Decisiones de admisión/liberación tomadas en cada iteración de decodificación
Chunked prefill "división de prefill" Divide un prefill largo en fragmentos de 512 tokens intercalados con decodificación
TTFT "tiempo hasta el primer token" Prefill + cola + red; dominado por el prefill en prompts largos
ITL "latencia entre tokens" Tiempo entre tokens de decodificación consecutivos; dominado por el tamaño del lote
Goodput "rendimiento que cumple con el SLO" Tokens/segundo en los que cada solicitud aún alcanza los objetivos de TTFT e ITL
V1 scheduler "el nuevo agendador" Agendador de vLLM de 2026; la decodificación especulativa por N-gram es el camino compatible con chunked prefill
--gpu-memory-utilization "el botón de memoria" Fracción de HBM reservada para bloques de KV después de pesos y activaciones

Further Reading

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