Speculative decoding: acelerar el decode sin perder calidad
entender por qué se puede generar varios tokens "a la vez" sin cambiar la distribución de salida, configurar los tres métodos prácticos en vLLM, y —lo más importante— medir el acceptance rate para saber si te conviene o te está perjudicando.
3.1La idea: adivinar barato, verificar caro una sola vez
El decode es memory-bound (N0·L1): generar un token cuesta leer todos los pesos. La intuición de speculative decoding: si pudieras proponer varios tokens candidatos de forma barata y verificarlos todos en una sola pasada del modelo grande, leerías los pesos una vez para confirmar K tokens en lugar de K veces.
Mecánica (draft-then-verify):
- Un proponente barato (un modelo draft pequeño, o un heurístico) propone K tokens candidatos.
- El modelo objetivo (el grande) los verifica en paralelo en una sola forward pass (que es barata en cómputo y cara en memoria — y ya pagas la memoria una vez).
- Se aceptan los candidatos que coinciden con lo que el modelo objetivo habría muestreado; en el primer fallo se descarta el resto y se sigue desde ahí.
Clave conceptual — es lossless: mediante rejection sampling, la distribución de los tokens aceptados es idéntica a la del modelo objetivo muestreando token a token (hasta la precisión numérica del hardware). No cambias la calidad; solo cambias el coste. Esto es lo que lo hace interesante: aceleración gratis si el proponente acierta a menudo.
3.2El número que manda: acceptance rate
Todo depende de cuántos de los K tokens propuestos se aceptan de media. Si el draft acierta mucho (alto acceptance rate), confirmas varios tokens por forward del grande → speedup. Si acierta poco, desperdicias forwards del draft y verificaciones fallidas → speedup negativo (vas más lento que sin spec decoding).
speedup ≈ (tokens_aceptados_por_paso) / (1 + coste_relativo_del_draft)
Por eso nunca actives spec decoding sin medir el acceptance rate para tu workload concreto. Un draft bueno para chat puede ser malo para código, etc.
3.3Los métodos prácticos en vLLM (API actual, JSON --speculative-config)
vLLM expone los métodos vía un string JSON. Los relevantes para una 5090:
(a) n-gram — el proponente no es un modelo: busca repeticiones en el propio contexto y propone la continuación. Coste casi nulo. Brilla en tareas con mucha repetición (código, edición de texto, RAG con citas). Desde finales de 2025 corre en GPU y es compatible con el async scheduler (overhead muy bajo).
1vllm serve Qwen/Qwen3-8B \
2 --speculative-config '{"method":"ngram","num_speculative_tokens":4,"prompt_lookup_min":2,"prompt_lookup_max":5}'(b) EAGLE / EAGLE-3 — un draft entrenado que condiciona sus predicciones en los hidden states del objetivo. Mayor acceptance que n-gram en texto general, pero necesita un checkpoint EAGLE compatible con tu modelo.
1vllm serve meta-llama/Meta-Llama-3-8B-Instruct \
2 --speculative-config '{"method":"eagle","model":"yuhuili/EAGLE-LLaMA3-Instruct-8B","num_speculative_tokens":2,"draft_tensor_parallel_size":1}'Caveats EAGLE (impórtalos): el draft debe correr sin tensor parallelism (draft_tensor_parallel_size=1), aunque el objetivo sí pueda; y el speedup observado en vLLM suele ser menor que el reportado en el paper original (issue conocido). Para EAGLE-3 usa "method":"eagle3".
(c) suffix decoding — usa un árbol de sufijos sobre generaciones previas; bueno cuando hay patrones repetidos a nivel de sesión.
1vllm serve Qwen/Qwen3-8B \
2 --speculative-config '{"method":"suffix","num_speculative_tokens":8}'Nota práctica 2026: el método
draft_model(pesos separados arbitrarios) ymlp_speculatorhan estado parcialmente soportados según versión; n-gram, EAGLE/EAGLE-3 y suffix son los caminos fiables hoy. Modelos con MTP nativo (DeepSeek, Qwen3-Next, MiMo) usan"method":"deepseek_mtp"/"qwen3_next_mtp"/etc.
3.4Laboratorio L3.1 — Medir acceptance rate y speedup
El modo offline de vLLM expone el acceptance rate por petición. Úsalo para decidir.
1# lab_n1l3_specdecode.py — compara baseline vs ngram vs eagle midiendo speedup real
2from vllm import LLM, SamplingParams
3import time
4
5prompts = [
6 "Escribe una función Python que invierta una lista enlazada y explícala:",
7 "Resume el siguiente texto y luego cítalo literalmente: " + ("dato importante. " * 50),
8] * 16
9sp = SamplingParams(temperature=0.0, max_tokens=256)
10
11def bench(spec_config=None, label=""):
12 kwargs = dict(model="Qwen/Qwen3-8B", gpu_memory_utilization=0.85, max_model_len=4096)
13 if spec_config:
14 kwargs["speculative_config"] = spec_config
15 llm = LLM(**kwargs)
16 t0 = time.perf_counter()
17 outs = llm.generate(prompts, sp)
18 dt = time.perf_counter() - t0
19 n_tok = sum(len(o.outputs[0].token_ids) for o in outs)
20 print(f"{label:18} {n_tok/dt:8.1f} tok/s ({dt:.1f}s)")
21 del llm # libera VRAM antes del siguiente
22
23bench(None, "baseline")
24bench({"method":"ngram","num_speculative_tokens":4,"prompt_lookup_min":2,"prompt_lookup_max":5}, "ngram")
25# bench({"method":"eagle","model":"yuhuili/EAGLE-LLaMA3-Instruct-8B","num_speculative_tokens":2,"draft_tensor_parallel_size":1}, "eagle") # requiere modelo compatibleLíneas no triviales:
- Los prompts incluyen casos con repetición (citar texto, código) donde n-gram debería acertar mucho, y casos abiertos donde acertará poco. Verás que el speedup depende del tipo de prompt → la lección central.
del llmentre runs: cadaLLM(...)reserva VRAM; sin liberarlo, el segundo run hace OOM en 32 GB.examples/features/speculative_decoding/spec_decode_offline.pydel repo vLLM extrae el acceptance rate por petición; úsalo para el número fino.
3.5Cuándo NO usar speculative decoding
- Alta concurrencia / throughput-bound: cuando ya saturas el batch (compute-bound), spec decoding aporta poco o estorba (compite por cómputo). vLLM tiene
disable_by_batch_sizepara desactivarlo automáticamente bajo carga. - Workloads abiertos y creativos: bajo acceptance → speedup negativo.
- Sin medir: jamás lo dejes activado "porque sí".
3.6Ejercicios
E1. Corre el lab. ¿Para qué tipo de prompt da n-gram más speedup? Explica con el acceptance rate por qué.
E2. Mide el speedup de n-gram a concurrencia 1 vs a concurrencia 32 (servidor online). ¿Por qué se reduce a alta concurrencia? (Pista: 3.5, transición a compute-bound.)
E3. Si tuvieras que servir un asistente de código (mucha repetición de tokens del propio fichero), ¿qué método elegirías y por qué? ¿Y para un asistente de escritura creativa?
3.7Trampas comunes
- Activar spec decoding y no medir → puede estar ralentizándote.
- EAGLE con tensor parallelism en el draft → error/baja eficiencia.
- Olvidar
del llmy hacer OOM al comparar configs.
3.8Referencias
- Leviathan et al., "Fast Inference from Transformers via Speculative Decoding" (2023); Chen et al. (DeepMind, 2023). EAGLE-1/2/3 (Li et al.). Docs vLLM "Speculative Decoding".