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.pyque 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:
- Cualquier secuencia en
RUNNINGque acabe de alcanzar el EOS o el límite máximo de tokens (max_tokens) se elimina. - El agendador analiza la fila de espera. Si hay bloques de KV libres, admite nuevas secuencias (ya sea para prefill o reanudación).
- 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
- Ejecuta
code/main.py. ComparaSTATICconCONTINUOUSen 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? - 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.) - Lee nuevamente las notas de lanzamiento de vLLM v0.18.0. ¿Qué combinaciones de flags son mutuamente excluyentes? Enuméralas.
- 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.
- 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
- vLLM documentation — Speculative Decoding — fuente oficial sobre compatibilidad entre chunked-prefill y decodificación especulativa.
- vLLM Release Notes (NVIDIA) — ciclo de lanzamientos de 2026 y comportamientos de versiones específicas.
- vLLM Blog — PagedAttention — artículo original que aún define cómo pensar sobre el asignador.
- PagedAttention paper (arXiv:2309.06180) — análisis de fragmentación y diseño del agendador.
- Aleksa Gordic — Inside vLLM — explicación detallada del agendador V1 con gráficos de flama (flame graphs).