Continuous Batching
El batching continuo reconstruye el lote de un servidor de LLM en cada paso de decodificación, de modo que las solicitudes terminadas salen, se incorporan otras nuevas y las GPU siguen ocupadas.
El batching continuo, también llamado planificación a nivel de iteración o batching en vuelo, es una técnica de planificación para servir modelos generativos en la que el servidor reconstruye el batch en cada paso de decodificación. Cuando una secuencia termina, sale inmediatamente del batch y devuelve su resultado; en el siguiente paso, ocupa su lugar una solicitud en espera. Gyeong-In Yu y sus colegas de la Universidad Nacional de Seúl y FriendliAI presentaron este enfoque en Orca, en OSDI 2022, y comunicaron un rendimiento 36,9 veces superior al de NVIDIA FasterTransformer con GPT-3 175B, con la misma latencia. El nombre batching continuo se extendió en 2023, cuando vLLM y Text Generation Inference de Hugging Face lo adoptaron; hoy es el planificador predeterminado de los motores de servicio que hay detrás de los modelos de lenguaje grandes (LLMs).
Por qué el batching por solicitud desperdicia la GPU#
La generación de texto produce un token por pasada hacia delante, y las solicitudes necesitan cantidades muy distintas de pasadas: una respuesta de sí o no termina tras unos pocos tokens, mientras que un archivo de código puede llegar a miles. Con el batching estático, un grupo de solicitudes permanece unido hasta que termina la solicitud más larga. Las posiciones de las secuencias que terminan antes quedan inactivas y las nuevas solicitudes esperan a que se libere todo el grupo, lo que aumenta la latencia de inferencia de cada solicitud breve que queda en cola detrás de una larga.
El batching dinámico, el esquema por solicitud que usan servidores como NVIDIA Triton, reúne las solicitudes que llegan en un intervalo breve y las ejecuta como un único batch hasta que terminan. Este enfoque encaja con modelos cuyo coste por entrada es fijo, como un clasificador de imágenes o un detector de objetos, porque cada entrada termina en una sola pasada hacia delante. El modo predict de Ultralytics YOLO crea batches por solicitud de entradas de YOLO26 a partir de su origen: un archivo .streams con URL de cámaras se ejecuta como un batch que contiene el fotograma más reciente de cada flujo, así que 8 flujos se ejecutan con un tamaño de batch de 8; el argumento batch agrupa en una sola pasada hacia delante los fotogramas de un vídeo, un directorio o una lista de imágenes .txt.
Cómo funciona la planificación a nivel de iteración#
El planificador ejecuta un paso de decodificación para cada secuencia activa y, a continuación, comprueba si cada una ha llegado a un token de fin de secuencia o a su límite de longitud. Devuelve las secuencias terminadas y libera su memoria; también admite solicitudes en cola si caben en la caché KV. Orca procesa (precarga) la solicitud de un nuevo usuario en el mismo paso en que decodifica las demás, y los motores actuales hacen lo mismo con la precarga fragmentada.
Para combinar secuencias de distintas longitudes hacía falta otra idea, que Orca denominó batching selectivo. Las operaciones que tratan cada token de forma independiente, como las grandes multiplicaciones de matrices de cada capa Transformer, se ejecutan a la vez con todos los tokens de todas las secuencias. La atención se ejecuta por secuencia, ya que cada una tiene una longitud de contexto propia.
Esta simulación planifica las mismas ocho solicitudes en una GPU que decodifica cuatro secuencias a la vez:
import heapq
output_lengths = [5, 50, 8, 12, 40, 3, 30, 7] # tokens each queued request will generate
slots = 4 # sequences the GPU decodes together at each step
# Static batching: each group of 4 runs until its longest request finishes
static_steps = sum(max(output_lengths[i : i + slots]) for i in range(0, len(output_lengths), slots))
# Continuous batching: the next request takes the first slot that frees up
slot_free_at = [0] * slots
for length in output_lengths:
start = heapq.heappop(slot_free_at)
heapq.heappush(slot_free_at, start + length)
continuous_steps = max(slot_free_at)
for name, steps in [("static", static_steps), ("continuous", continuous_steps)]:
print(f"{name}: {steps} decode steps, slots busy {sum(output_lengths) / (steps * slots):.0%}")El batching estático necesita 90 pasos de decodificación y mantiene las posiciones ocupadas el 43 % del tiempo. El batching continuo termina en 50 pasos —la longitud de la solicitud más larga— y mantiene las posiciones ocupadas el 78 % del tiempo.
Límites y técnicas complementarias#
Un batch en ejecución de mayor tamaño aumenta el rendimiento total, pero cada paso tarda más, por lo que aumenta el tiempo por token de salida. Por eso, los motores limitan el número de secuencias simultáneas y los tokens procesados en cada paso, que vLLM expone como max_num_seqs y max_num_batched_tokens, y combinan el batching continuo con algunas técnicas relacionadas:
- PagedAttention: Asigna la caché KV en bloques pequeños, de modo que una secuencia terminada libera memoria de inmediato y una nueva solo ocupa la que necesita.
- Precarga fragmentada: Divide las solicitudes largas en fragmentos para que la precarga de una nueva solicitud no detenga la decodificación de las demás.
- Decodificación especulativa: Ofrece el mayor beneficio con tamaños de batch pequeños, ya que los batches continuos grandes ya aprovechan casi toda la capacidad de cálculo de la GPU.










