Continuous batching: la peluquería con 8 sillones que no espera al cliente lento — Orca, vLLM, chunked prefill y goodput

Este post complementa los de KV cache (el artefacto que continuous batching gestiona), PagedAttention (la pieza de memoria que lo hace viable), Disaggregated serving (la siguiente capa de optimización), Speculative decoding, Multi-LoRA y MoE (las tres extensiones que conviven con el scheduler en producción).

Estás aquí: DEPLOY

Estás aquí: DEPLOY · scheduler iterativo, una pieza por debajo de PagedAttention1 · Data2 · Tune3 · Eval4 · Deploy5 · Observe6 · Retrain

TL;DR

El static batching del HuggingFace Transformers original (era pre-2022) sub-utilizaba sistemáticamente el GPU por dos razones estructurales. Primera: la unidad de scheduling era la request completa; la más larga del batch bloqueaba a todas las demás hasta terminar (head-of-line blocking severo, P95 TTFT cinco a diez veces peor del razonable). Segunda: cada slot del batch reservaba memoria para max_seq_len aunque el output real fuese mucho menor; el padding waste documentado estaba entre el 60 % y el 80 % y la GPU SM utilization sostenida en workloads reales caía al 20-40 %. Orca (Yu et al., OSDI 2022, FriendliAI + Seoul National University) introdujo la idea que destrabó todo: la unidad de scheduling deja de ser la request, pasa a ser una iteración del decoder — un token. Tras cada iteración el scheduler puede añadir nuevas requests al batch y retirar las terminadas. vLLM (Kwon et al., SOSP 2023, UC Berkeley) lo materializó open-source y production-grade gracias a PagedAttention, que resuelve la fragmentación del KV cache que el continuous batching teórico provocaría con asignación dinámica. SARATHI / Sarathi-Serve (Microsoft Research India, OSDI 2024) cerró el último hueco: los stalls del prefill que pausaban los decodes activos cuando llegaba una request nueva, mediante chunked prefill (dividir un prefill largo en chunks pequeños y mezclarlos con decodes en el mismo step) y stall-free batching. DistServe (Zhong et al., OSDI 2024) reformuló la métrica clave: lo que importa es goodput (requests/s cumpliendo SLO de TTFT y TPOT), no throughput puro. En mayo 2026, vLLM v1 trae scheduler unificado con chunked prefill always-on; SGLang añade RadixAttention que da hits de prefix-cache cross-request; TensorRT-LLM lo llama in-flight batching; llama.cpp lo soporta nativo. Las tres tensiones operacionales son speculative decoding (nested raggedness), multi-LoRA (cada request con su adapter) y MoE (cada experto ve poquísimos tokens por step a batch típico). Este post desmonta el mecanismo, las matemáticas (utilización GPU, goodput vs throughput), las tres variantes (Orca → vLLM → Sarathi-Serve), los pitfalls (preempt-on-OOM, starvation, HoL inverso) y los números reales con configuraciones de producción.

La analogía: la peluquería con 8 sillones

Una peluquería con 8 sillones y un único peluquero brillante que se mueve entre ellos. Llegan clientes con necesidades muy distintas: unos quieren un corte de 15 minutos, otros un tinte con base de 2 horas, otros un alisado de 90 minutos. La pregunta es cómo organizar el flujo.

La estrategia tradicional (lo que hacía HuggingFace Transformers en su generate() original) es sentar a 8 clientes a la vez, todos al mismo tiempo, y no aceptar a nadie nuevo hasta que el último termine. Si entre esos 8 hay uno de 2 horas, los 7 que querían el corte de 15 minutos están sentados parados durante 1 hora y 45 minutos. El peluquero acaba su trabajo con los rápidos y se queda mirando a los sillones vacíos hasta que el del tinte termine. Cuando todos están listos, entran otros 8. Es el static batching, y lo único que evita que sea peor es que la GPU no se queja como un cliente humano.

La estrategia continua (Orca, vLLM) cambia la unidad de planificación. El peluquero no piensa “voy a hacer un cliente completo y luego el siguiente”; piensa “en cada tick doy un paso de trabajo en cada sillón ocupado y, cada vez que un sillón se libera, llamo al siguiente cliente de la cola sin esperar a que los demás terminen”. El cliente del corte rápido sale a los 15 minutos, su sillón se ocupa inmediatamente con el siguiente, y los lentos siguen su ritmo sin retrasar a nadie. El peluquero nunca está parado.

La estrategia continua con prefill chunked (SARATHI / Sarathi-Serve) añade una distinción más sutil. Algunos clientes necesitan una fase inicial larga (un análisis capilar de 10 minutos antes del corte; en términos LLM, el prefill del prompt). Sin chunked prefill, el peluquero tenía que parar todos los demás sillones para hacer el análisis del cliente nuevo de un tirón — ese era un stall visible en el TPOT de los activos. Con chunked prefill, el análisis se divide en piezas de 2 minutos que se intercalan entre los cortes activos de los demás. Los clientes en curso ya no notan parones; el cliente nuevo tarda un poco más en empezar su corte propiamente, pero la peluquería entera no se congela.

Y la métrica que importa: el dueño no quiere maximizar “clientes atendidos por hora” a costa de que algunos se vayan furiosos. Quiere maximizar “clientes atendidos por hora dentro del SLA de tiempo” — eso es el goodput, contribución de DistServe.

El problema que continuous batching resuelve

Hay dos patologías estructurales del static batching que merecen ser explicadas con números concretos.

Padding waste. Cada slot del batch reservaba memoria para max_seq_len (prompt + max output), aunque el output real terminase en muchos menos tokens. Para un batch de 32 con output lengths distribuidos heterogéneamente (la mitad ≤50 tokens, una cola larga hasta 4 000), el desperdicio típico de memoria era del 60-80 %. Esto se traducía directamente en concurrencia desperdiciada: con la misma VRAM, en lugar de servir 32 requests con asignación inteligente, servías 8.

HoL blocking (Head-of-Line). La unidad de scheduling era la request completa. Un batch que contenía una request de 500 tokens y 31 de ≤50 tokens corría 450 iteraciones extra “vacías” (la GPU ejecutaba forward passes para los 32 slots, aunque 31 ya hubiesen terminado). Coste computacional desperdiciado: ~84 % del tiempo del tail en el ejemplo.

Resultado medible. GPU SM utilization sostenida en workloads reales bajo static batching: 20-40 %. Es decir, ~70 % del compute del datacenter no se aprovechaba. Cuando llegaron los primeros benchmarks de Orca y vLLM mostrando 10-24× mejora de throughput, no era exageración de marketing; era recuperar todo ese compute desperdiciado.

Orca (OSDI ‘22): la idea que cambió todo

El paper de Yu, Jeong, Kim, Kim y Chun en OSDI 2022 (“Orca: A Distributed Serving System for Transformer-Based Generative Models”, del Seoul National University + FriendliAI) introdujo dos contribuciones que han quedado como base de todo lo que vino después.

Iteration-level scheduling. En lugar de planificar a nivel de request completa, planifica a nivel de una iteración del decoder: el step que genera UN token. Tras cada iteración el scheduler puede (a) añadir nuevas requests al batch, (b) retirar requests que han generado EOS o llegado a max_tokens, (c) reordenar prioridades. El engine de cómputo ejecuta exactamente una iteración sobre el batch actual.

Selective batching. Aquí está la sutileza no obvia. El problema técnico de batchear requests con longitudes y estados de KV cache distintos es que algunas operaciones (los GEMMs de Q, K, V projections y FFN) son insensibles a la posición y se pueden batchear concatenando tokens, mientras que la atención sí es sensible al estado per-request (cada request tiene su propio KV cache de longitud distinta). La solución de Orca: batchear los GEMMs (concatenar todos los tokens del step en un tensor [total_tokens, hidden]) y ejecutar la atención secuencialmente por request.

Resultado paper: hasta 36.9× throughput vs FasterTransformer en GPT-3 175B al mismo nivel de latencia. Orca no es open-source — solo está documentado en el paper. FriendliAI lo comercializa como Friendli Engine. Pero la idea se publicó y fue adoptada por todos.

vLLM (SOSP ‘23): la materialización open-source

Lo que Orca describió en concepto, vLLM lo materializó en producción. El paper de Kwon, Li, Zhuang, Sheng et al. (UC Berkeley Sky Computing Lab, SOSP 2023) introduce PagedAttention —el detalle está en PagedAttention deep dive— pero también consolida el continuous batching como práctica universal.

La razón por la que PagedAttention es prerrequisito del continuous batching práctico es la fragmentación. Si vas a insertar y retirar requests dinámicamente del batch, y cada request tiene un KV cache que crece en cada iteración, la asignación contigua tradicional fragmenta la HBM hasta dejarla inservible. PagedAttention parte el KV cache en bloques de tamaño fijo (default 16 tokens) asignados on-demand desde un pool global. El memory waste cae de ~60-80 % a menos del 4 % (solo el último bloque parcialmente lleno por sequence).

Métricas paper vLLM (2023):

  • vs HuggingFace Transformers (static, sin continuous): hasta 24× throughput.
  • vs HuggingFace TGI (que ya tenía continuous batching primitivo): ~3.5×.
  • vs FasterTransformer: 2-4× a misma latencia.

Lo que diferencia operacionalmente vLLM de Orca: vLLM ejecuta atención en un único kernel CUDA fusionado (paged_attention_kernel) sobre bloques no contiguos; Orca describía la atención request-by-request secuencial. Y vLLM expone APIs OpenAI-compatible que permiten dropear el engine en stacks existentes sin tocar el cliente.

Chunked prefill (SARATHI / Sarathi-Serve, OSDI ‘24)

Hay un detalle que el continuous batching de Orca/vLLM original no resolvía: cuando una request nueva entra al batch, su prefill (procesamiento del prompt completo de una vez) puede tardar cientos de milisegundos. Durante ese tiempo, los decodes activos de las otras requests están esencialmente pausados — el GPU está dedicado al prefill nuevo. Esto se observaba como spikes en TPOT (“inter-token latency”) cada vez que entraba una request larga, lo que rompía SLAs estrictos.

SARATHI (Agrawal et al., arXiv 2308.16369, agosto 2023) y luego Sarathi-Serve (mismo grupo de Microsoft Research India, OSDI 2024, arXiv 2403.02310) introducen dos ideas combinadas:

Chunked prefill. Un prefill largo (e.g., 8 192 tokens) se divide en chunks (e.g., 2 048 tokens) que se procesan uno por iteración. En lugar de un step de 200 ms procesando 8K tokens, cuatro steps de 50 ms procesando 2K cada uno.

Decode-maximal batching (“stall-free”). En cada iteración, el scheduler primero llena el batch con los decodes activos (cada uno cuesta 1 token), y solo el espacio sobrante se usa para chunks de prefill nuevos. Resultado: los decodes activos siguen avanzando 1 token cada iteración sin pausar, mientras la request nueva va completando su prefill en bocados pequeños.

La observación que lo justifica: prefill es compute-bound (procesa N tokens de golpe → satura FLOPs) mientras decode es memory-bound (1 token por step → infrautiliza compute, la GPU espera por HBM). Mezclar prefill chunks con decodes en el mismo step explota el slack de arithmetic intensity: los decodes “se piggyback” sobre el compute libre del prefill chunk.

Números:

  • SARATHI original (LLaMA-13B en A6000): decode throughput +10×, end-to-end +1.33×.
  • Sarathi-Serve (Mistral-7B en A100): 2.6× serving capacity vs vLLM puro. Yi-34B en 2×A100: 3.7×. Falcon-180B con pipeline parallel: 5.6×.

Adopción mayo 2026: always-on en vLLM v1 (default desde v0.8.0, enero 2025), SGLang, TensorRT-LLM. Configuración clave en vLLM: --max-num-batched-tokens (token budget por step; default 2048). Subirlo prioriza throughput, bajarlo prioriza TPOT bajo.

Goodput: la métrica que importa (DistServe, OSDI ‘24)

La métrica clásica “throughput” (requests/s o tokens/s) tiene un problema cuando hay SLOs. Un servidor puede reportar 1 000 req/s mientras el P99 TTFT es 30 segundos y el SLO es 1 segundo — solo unas 200 req/s realmente cumplen el contrato.

DistServe (Zhong et al., OSDI 2024) formaliza goodput como la métrica correcta: goodput = max request rate sostenido cumpliendo los SLOs (TTFT bound AND TPOT bound). La definición práctica suele ser: máximo rate con ≥90 % de requests dentro de ambos SLOs.

Por qué importa para el scheduler:

  • Optimizar throughput puro lleva a maximizar batch size, lo que infla P99 TPOT.
  • Optimizar goodput limita el batch size cuando el TPOT empieza a violar SLO, prefiere requests pequeñas si el batch ya tiene tail, y deja recursos disponibles para nuevas requests.

Resultado DistServe: hasta 7.4× más requests servidas o 12.6× SLO más estricto vs vLLM al mismo SLO attainment. La ganancia viene de desagregar prefill y decode en GPUs distintas (eliminando la interferencia entre fases), pero la idea de optimizar para goodput es independiente y aplicable a cualquier scheduler.

Operacionalmente esto se traduce en monitorización:

goodput_proxy = histogram_quantile(0.95, vllm:time_to_first_token_seconds_bucket) < SLO_TTFT
              AND histogram_quantile(0.95, vllm:time_per_output_token_seconds_bucket) < SLO_TPOT

El scheduler iterativo en acción

Static batching — 4 slots, padding hasta max_len, llega req nueva → esperaslot 1slot 2slot 3slot 4batch reciclado solo cuando TODOS terminan ↑

Continuous batching — slot libre se rellena INMEDIATO en cada tickslot 1slot 2slot 3slot 4cada barra de color = 1 iteración del decoder (1 token) de una request

Chunked prefill — prefill nuevo se intercala con decodes activos (sin stall)prefill chunk 1decode tick activosprefill chunk 2 (mismo step que los decodes)

La matemática que importa

Tres fórmulas explican gran parte del comportamiento operacional.

Utilización GPU bajo static batching. Con un batch de tamaño B cuyos seq_len_i son las longitudes reales y max(seq_len_i) es la longitud que define el padding:

$$U_{\text{static}} = \frac{\sum_i \text{seq_len}_i}{B \cdot \max_i \text{seq_len}_i}$$

Para B=32, 30 sequences de 50 tokens y 2 de 500: U = (30·50 + 2·500) / (32·500) = 2500/16000 = 15.6 %. Cuatro de cada cinco ciclos de GPU desperdiciados.

Utilización GPU bajo continuous batching (idealizada).

$$U_{\text{continuous}} \approx 1 - \frac{T_{\text{scheduler}}}{T_{\text{iteration}}}$$

Con overhead de scheduler ~50-200 µs e iteration time ~10-30 ms: U > 95 %. La unidad de pérdida ya no es padding, es overhead de planificación, y este último es despreciable comparado con el forward pass.

Goodput vs throughput.

$$\text{Goodput}(R) = R \cdot P(\text{latency} < \text{SLO})$$

donde R es el request rate ofrecido. Curva típica: goodput crece linealmente con R hasta el knee de saturación, luego cae porque P(SLO) se desploma cuando el sistema se congestiona. El punto óptimo está justo antes del knee, no en el peak de throughput.

Ejemplo: a R=100 req/s con P(SLO)=0.99, goodput = 99. A R=200 req/s con P(SLO)=0.4, goodput = 80. Más carga ofrecida, menos goodput útil.

Token budget de chunked prefill. En cada step de vLLM con chunked prefill activo:

$$\text{prefill_tokens_this_step} = \text{max_num_batched_tokens} - \text{num_decodes_active}$$

Cada decode activo cuesta 1 token del budget; el resto se rellena con chunks de prefill nuevos. Si max_num_batched_tokens = 2048 y num_decodes_active = 200, hay 1 848 tokens para prefill (un chunk de 1 848 o varios chunks pequeños).

Implementaciones reales en mayo 2026

EngineScheduler V actualChunked prefill defaultNotas relevantes
vLLM v1 (default ≥0.8.0)V1 unificadoalways-onEngineCore aislado en proceso separado; prefix caching con eviction O(1); preempt-mode recompute default; backends xgrammar/outlines.
SGLangpropio (PyTorch eco)RadixAttention da prefix-cache hits cross-request; CPU scheduler no-bloqueante; lider en latencia estable a alta concurrencia.
TensorRT-LLMpropietario “in-flight batching”Políticas GUARANTEED_NO_EVICT (conservador, default) y MAX_UTILIZATION (agresivo, riesgo de pausa por KV full). Compile-time vs runtime.
Triton + tensorrtllm_backendgpt_model_type: inflight_fused_batchingmax_queue_delay_microseconds para agrupar requests recién llegadas. Decoupled mode para streaming SSE.
llama.cpp (llama-server)propio--cont-batching ON desde 2024-np N slots paralelos; sin PagedAttention (KV contiguo por slot) → menos flexible pero más simple. Endpoint :8080/metrics.

Configuración vLLM v1 production-ready típica:

vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --tensor-parallel-size 4 \
  --max-num-batched-tokens 4096 \
  --max-num-seqs 256 \
  --enable-chunked-prefill \
  --enable-prefix-caching \
  --preemption-mode recompute \
  --scheduling-policy fcfs \
  --gpu-memory-utilization 0.92

Equivalente SGLang:

python -m sglang.launch_server \
  --model meta-llama/Llama-3.1-70B-Instruct \
  --tp 4 \
  --chunked-prefill-size 4096 \
  --max-running-requests 256 \
  --enable-radix-cache

Las tres tensiones operacionales

Continuous batching + speculative decoding. Speculative decoding produce 1 a γ+1 tokens por step según la tasa de aceptación. El batch deja de ser uniforme en tokens producidos por iteración — nested raggedness. PagedAttention lo absorbe (el KV cache puede crecer a velocidades distintas por request en el mismo step), pero el planificador pierde simetría. A QPS bajo (asistente conversacional) la combinación es excelente: vLLM reporta hasta 2.8× speedup. A QPS alto, el draft consume slots del decode pool y puede reducir goodput agregado. Regla del pulgar: deshabilitar speculative cuando gpu_cache_usage > 0.85. Detalle completo en Speculative decoding.

Continuous batching + multi-LoRA. Cada request del batch puede usar un adapter distinto (vía SGMV, ver Multi-LoRA serving). Worst case: cada request del batch un adapter distinto y rank distinto → throughput cae hasta 50 % vs base sin LoRA. Best case: todos los requests mismo adapter → equivalente a base sin LoRA. Mitigación práctica: agrupar adapters por rank en el routing previo al engine; setear --max-lora-rank al máximo realmente servido, no por exceso.

Continuous batching + MoE. Cada experto ve batch · k / N tokens por step. Con DeepSeek-V3 (256 experts, k=8) y batch=32 en decode, cada experto procesa solo 1 token de media — compute starvation total. Para igualar el throughput por GPU de un dense, MoE necesita batches »10× mayores, lo que presiona el KV cache. Wide-EP (ver MoE inference) distribuye los expertos en muchas GPUs y permite batches efectivos por experto mayores, a costa de comms all-to-all que añaden milisegundos por step.

Métricas que hay que monitorizar

Las métricas Prometheus expuestas por vLLM (prefijo vllm:, en scrapeo vllm_):

  • vllm:time_to_first_token_seconds (Histogram) — TTFT incluyendo queue.
  • vllm:time_per_output_token_seconds (Histogram) — TPOT.
  • vllm:e2e_request_latency_seconds (Histogram) — End-to-end.
  • vllm:num_requests_running (Gauge) — batch activo.
  • vllm:num_requests_waiting (Gauge) — queue depth.
  • vllm:num_requests_swapped (Gauge) — preemptadas a CPU.
  • vllm:gpu_cache_usage_perc (Gauge) — fracción KV cache ocupada.
  • vllm:gpu_prefix_cache_hit_rate (Gauge) — prefix cache hits.
  • vllm:num_preemptions_total (Counter) — preempts. Cualquier valor sostenido es red flag.

Reglas operacionales prácticas:

  • Zona estable bajo carga sostenida: gpu_cache_usage_perc ∈ [0.7, 0.9].
  • Warning a >0.95 (preempt inminente).
  • Crítico si num_requests_waiting crece más rápido que num_requests_running: el server no absorbe; escalar.

Pitfalls operacionales

Preempt-on-OOM. Cuando gpu_cache_usage llega a ~1.0 con requests pendientes que necesitan crecer su KV → vLLM preempta. V1 hace RECOMPUTE por defecto (descarta KV, regenera cuando vuelve); V0 hacía SWAP (mueve a CPU). RECOMPUTE mejor para sequences cortas (regenerar barato); SWAP mejor para sequences largas. Métrica vllm:num_preemptions_total debe ser cero o casi cero en estado estable.

HoL blocking inverso (memory monopoly). Una request muy larga ocupa muchos bloques KV → requests pequeñas no caben en batch aunque el compute esté libre. Chunked prefill mitiga el bloqueo del compute durante prefill nuevo, pero no resuelve el monopolio de memoria. Solución parcial: políticas de límite por request (max_tokens agresivo) o prioridades.

Starvation. FCFS puede dejar requests pendientes mucho tiempo si las activas no terminan. vLLM soporta --scheduling-policy priority con cabecera x-priority. Trabajos recientes (NeurIPS 2024 Efficient LLM Scheduling by Learning to Rank, arXiv:2501.14312 Locality-aware Fair Scheduling) proponen schedulers con quantum-based starvation prevention; no integrados aún en vLLM mainline.

Chunk size mal calibrado. Chunk pequeño (512) → TPOT bajo, TTFT alto, memory overhead por más accesos al KV. Chunk grande (8192+) → TTFT bajo, TPOT spikes durante el chunk. Regla: empezar en 2 048, medir P95 TPOT, ajustar.

Batch size cap. --max-num-seqs alto → más concurrencia pero P99 TPOT explota. Bajo → throughput desperdiciado. Rule of thumb: max_num_seqs ≈ HBM_for_KV / (avg_seq_len × bytes_per_token_KV).

Implicaciones en hardware on-premise

En una RTX 4090 (24 GB). llama.cpp con --cont-batching -np 4-8 es el patrón natural. Modelos típicos: Llama 3 8B Q4_K_M con ~8 slots paralelos, throughput agregado del orden de cientos de tok/s. vLLM también funciona si caben los pesos (Llama 3 8B BF16 sí; el 70B no entra entero), aunque PagedAttention en consumer da menos retorno que en datacenter.

En un cluster genérico 4×H100 SXM (320 GB, NVLink). Aquí vLLM v1 / SGLang son el estándar de facto. Configuraciones típicas:

  • Llama 3 70B FP8 + TP=4: docenas de sesiones concurrentes con P95 TPOT bajo 50 ms, decenas de miles tok/s agregados a batch moderado.
  • Llama 3 70B AWQ-INT4 + TP=2 + el resto del cluster para concurrencia adicional o multi-LoRA con SGMV.
  • DeepSeek-V3 requiere setups mayores (8-16 H100) para entrar entero en FP8; con Wide-EP el continuous batching pasa a operar sobre batches mucho mayores y la economía cambia (ver MoE).

La regla de pulgar mayo 2026: vLLM v1 con chunked prefill always-on y prefix caching enabled es la configuración por defecto sensata para cualquier modelo dense que quepa cómodamente; SGLang ofrece mejor latencia estable a alta concurrencia gracias al solapamiento CPU-scheduler/GPU-step; TensorRT-LLM da pico de throughput a alta concurrencia con la rigidez del compile-time.

Lo que no hemos cubierto

  • Locality-aware fair scheduling (arXiv:2501.14312) y schedulers learning-to-rank (NeurIPS 2024): siguiente generación de algoritmos que cierran el trade-off fairness vs prefix-cache locality.
  • Smooth goodput (arXiv:2410.14257): refinamiento de la métrica DistServe usando max slowdown en vez de SLO binario.
  • Triton tensorrtllm_backend en producción: decoupled mode para streaming, ensemble con pre/post-processing, autoscaling con KServe.
  • vLLM speculators v0.3.0 y framework de training de drafters compatible vLLM.

Ver también

  • KV cache: la memoria de trabajo — el artefacto que continuous batching gestiona; sin entenderlo no se entiende por qué la unidad mínima es la iteración del decoder.
  • PagedAttention deep dive — la pieza de memoria sin la cual continuous batching dinámico fragmentaría la HBM; deep-dive al block manager.
  • Disaggregated serving: prefill y decode en pods especializados — la siguiente capa: cuando continuous batching ya está exprimido, separar prefill y decode da otra ronda de mejoras (origen del paper DistServe que aporta el concepto de goodput).
  • Speculative decoding — primera tensión operacional del scheduler: cada request del batch puede producir 1 a γ+1 tokens por step.
  • Multi-LoRA serving — segunda tensión: heterogeneous batching con SGMV permite que cada request use su adapter, pero el scheduler debe agrupar por rank para mantener throughput.
  • MoE inference — tercera tensión: cada experto ve poquísimos tokens por step a batch típico, forzando batches mucho mayores que en dense.
  • El pipeline LLMOps de seis etapas — el mapa maestro donde Deploy es la etapa 4.

Referencias