El cuello de botella no es la cuenta, es la cola
Un LLM genera texto token a token. Para producir cada uno hay que pasar todos los parámetros del modelo por la memoria de la GPU. Con un modelo grande eso significa mover decenas de gigabytes por cada palabra que aparece en pantalla. El cálculo en sí es barato; lo caro es el viaje a memoria.
Ahí está la trampa de la generación autoregresiva: el token número 100 no puede empezar hasta que el 99 esté listo. La GPU pasa la mayor parte del tiempo esperando datos, no multiplicando. Está infrautilizada y aun así el usuario espera.
La decodificación especulativa ataca justo ese desperdicio. No comprime el modelo ni recorta el contexto. Cambia el orden de las cosas: adivina varios tokens de golpe con un modelo barato y usa el modelo caro solo para dar el visto bueno. Estas son las notas de campo.
La idea: un borrador y un revisor
El montaje usa dos modelos que comparten el mismo vocabulario:
- El modelo borrador (draft): pequeño, rápido, más torpe. Propone los siguientes k tokens de forma autoregresiva, uno tras otro, pero como es diminuto lo hace en un suspiro.
- El modelo objetivo (target): el grande, el que de verdad quieres servir. Recibe los k tokens propuestos y los verifica todos a la vez, en una sola pasada.
La clave está en esa verificación en paralelo. Comprobar cinco tokens de un tirón cuesta prácticamente lo mismo que generar uno solo, porque el cuello de botella era la memoria, no la aritmética. Ese es el almuerzo gratis que explota la técnica.
El protocolo, paso a paso:
- El borrador propone k tokens (por ejemplo, cinco).
- El objetivo procesa el prefijo más esos cinco tokens en una única pasada y obtiene sus propias probabilidades para cada posición.
- Se comparan token a token. Todos los que el objetivo habría aceptado se dan por buenos de golpe.
- En el primer token donde discrepan, se descarta el resto y el objetivo pone el correcto.
- Se repite desde el nuevo punto.
Si el borrador acierta a menudo, avanzas cuatro o cinco tokens al precio de uno. Si falla pronto, pierdes poco: solo el trabajo del borrador, que es barato.
El detalle que no puedes saltarte: la salida es idéntica
Aquí está lo que separa la decodificación especulativa de un simple “usa un modelo pequeño y reza”. El algoritmo de aceptación no es un umbral casero. Es un muestreo de rechazo que garantiza que la distribución final es exactamente la del modelo objetivo.
Dicho de otra forma: la respuesta que sale es indistinguible, estadísticamente, de la que habrías obtenido corriendo solo el modelo grande. No hay pérdida de calidad. No cambias el modelo servido, cambias cómo llegas a su salida.
Esto es fácil de romper si alguien improvisa la comparación con un if borrador == objetivo. Ese atajo sí degrada la distribución. El paso de verificación correcto acepta o rechaza según la razón entre las dos probabilidades y, cuando rechaza, corrige muestreando de una distribución ajustada. Merece la pena entenderlo antes de tocarlo:
import torch
def paso_especulativo(prefijo, borrador, objetivo, k):
# 1. El borrador propone k tokens de forma autoregresiva.
propuestos, p_borrador = borrador.generar(prefijo, k)
# 2. El objetivo verifica los k tokens EN UNA sola pasada.
# Devuelve su probabilidad para cada posición propuesta.
p_objetivo = objetivo.probabilidades(prefijo, propuestos)
aceptados = []
for i, tok in enumerate(propuestos):
r = torch.rand(1).item()
# Se acepta con probabilidad min(1, p_objetivo / p_borrador).
if r < min(1.0, p_objetivo[i][tok] / p_borrador[i][tok]):
aceptados.append(tok)
else:
# Rechazo: se corrige muestreando de (p_objetivo - p_borrador)+
# y se descarta el resto del borrador.
correccion = muestrear_residual(p_objetivo[i], p_borrador[i])
aceptados.append(correccion)
return aceptados # se reanuda desde aquí
# Todos aceptados: un token extra "gratis" del objetivo al final.
aceptados.append(objetivo.siguiente(prefijo + aceptados))
return aceptados
Lo importante no es esta implementación de juguete, sino la garantía: sea cual sea la calidad del borrador, la salida sigue siendo la del modelo grande. El borrador solo mueve la aguja de la velocidad, nunca la de la corrección.
Cuánto acelera y de qué depende
La ganancia no es fija. Depende de una sola métrica: la tasa de aceptación, es decir, cuántos de los tokens propuestos sobreviven de media. Y esa tasa depende de lo bien alineado que esté el borrador con el objetivo y de lo predecible que sea el texto.
| Escenario | Tasa de aceptación | Aceleración típica |
|---|---|---|
| Código o texto muy estructurado | Alta | 2,5×–3× |
| Prosa general, dominio conocido | Media | 1,8×–2,5× |
| Texto muy creativo o fuera de dominio | Baja | 1,2×–1,5× |
| Borrador mal alineado con el objetivo | Muy baja | Puede ir más lento |
Ese último caso es real y conviene tenerlo presente: si el borrador acierta poco, pagas su coste en cada ronda sin cosechar tokens. Por eso el borrador se suele elegir de la misma familia que el objetivo, o se destila a partir de él para que “piense parecido”.
Hay una variante que se salta el segundo modelo: en lugar de un borrador aparte, el propio modelo objetivo predice varios tokens futuros con cabezas de salida extra (el enfoque de Medusa y similares). Menos infraestructura, algo menos de ganancia, pero muy práctico cuando no quieres mantener dos modelos en producción.
Cuándo tiene sentido y cuándo no
La decodificación especulativa brilla cuando sirves peticiones de baja concurrencia sensibles a la latencia: un asistente donde una persona espera la respuesta, un copiloto de código, un chat interactivo. Ahí la GPU está ociosa esperando memoria y hay margen de sobra para colar la verificación en paralelo.
Rinde mucho menos cuando ya saturas la GPU con lotes grandes. Si estás procesando cientos de peticiones a la vez para maximizar el rendimiento total, la GPU ya está ocupada y no queda hueco libre que aprovechar. En ese régimen la técnica puede incluso estorbar. No es una optimización universal: es una optimización de latencia, no de rendimiento agregado.
También conviene medir antes de casarse con ella. Añade complejidad —dos modelos, un bucle de verificación, más memoria— y la ganancia depende de tu tráfico real. La buena noticia es que casi todos los motores de inferencia serios ya la traen de serie: vLLM, TensorRT-LLM y llama.cpp la soportan con un puñado de flags. No hace falta implementar el muestreo de rechazo a mano.
En resumen
La decodificación especulativa es de las pocas optimizaciones que dan velocidad sin cobrarla en calidad: la salida es la del modelo grande, punto. Antes de meterla en producción, haz tres comprobaciones. Primero, confirma que tu caso es sensible a latencia y no un pipeline por lotes ya saturado. Segundo, elige un borrador de la misma familia que el objetivo o destilado de él, y mide la tasa de aceptación con tu tráfico, no con un benchmark ajeno. Tercero, actívala en tu motor de inferencia con un flag antes de plantearte nada a medida.
Si el borrador acierta, avanzas cuatro tokens al precio de uno. Si falla, pierdes calderilla. Esa asimetría es la razón de que, bien puesta, casi siempre salga a cuenta.