Artículos en profundidad sobre la tecnología que da forma al futuro.

Decodificación restringida: LLMs como modelos de decisión rápidos

La decodificación restringida convierte un LLM en un clasificador rápido. Aprende cómo el enmascarado de logits, la calibración y el escalado de temperatura funcionan.

Un haz de luz que se divide en cinco rayos de colores al atravesar compuertas, ilustrando la salida de tokens restringida.
Enmascarar el vocabulario obliga a toda la distribución de salida del modelo a pasar por unas pocas compuertas permitidas.

Aquí tienes un truco barato y silenciosamente útil: si solo te importa el primer token que emitiría un LLM, puedes plantearle una pregunta de opción múltiple y obtener la respuesta en un único pase hacia adelante. La decodificación restringida — enmascarar todo el vocabulario excepto los pocos tokens que estás dispuesto a aceptar — convierte un modelo generativo en algo que se parece mucho a un clasificador. Sin parseo de JSON, sin bucles de reintento, sin once pasos autorregresivos para producir once caracteres. Un pase, un softmax, una respuesta con una probabilidad asociada a cada opción.

Esta idea circula desde hace tiempo con nombres como "modelos de decisión" o inferencia de "sistema uno", y hace poco se viralizó en Hacker News con una guía que mostraba cómo construir uno a partir de un modelo Qwen de 1.700 millones de parámetros en unas cuarenta líneas de Python. Los veteranos del machine learning en los comentarios, como era de esperar, se pusieron a gritar: eso es un clasificador, los tenemos desde el perceptrón. Tienen razón, y al mismo tiempo se pierden un poco el punto. Lo nuevo no es el concepto, sino que obtienes un clasificador zero-shot a partir de un modelo de lenguaje de propósito general sin entrenar nada. La pregunta de ingeniería interesante es cuándo este enfoque restringido supera a dejar que el modelo simplemente converse, y cuándo te miente en silencio con probabilidades demasiado confiadas.

Dos formas de obtener una respuesta de un LLM

El modo por defecto de hablar con un modelo de lenguaje es la generación. Haces una pregunta, el modelo emite tokens uno a uno y, más adelante, analizas lo que salió. Si necesitas salida estructurada, le añades un esquema: modo JSON, muestreo con gramáticas, máquinas de estados finitos al estilo Outlines. Funcionan, pero el modelo sigue recorriendo token a token toda la respuesta. Una respuesta trivial de opción múltiple puede requerir once pasos de decodificación, y cada paso cuesta un pase completo sobre miles de millones de parámetros.

La alternativa es nunca dejarlo recorrer nada. Tras procesar el prompt, miras los logits del vocabulario en la posición final, descartas todo salvo los IDs de token correspondientes a tus opciones (digamos "A", "B", "C", "D", "E") y aplicas softmax solo sobre esos. El argmax es tu predicción; los valores del softmax son las puntuaciones. Coste total: un pase hacia adelante, igual que el prefill que ya estabas pagando. Aquí tienes el núcleo, adaptado del enfoque basado en Qwen que ha estado circulando:

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_name = "Qwen/Qwen3-1.7B"
options = ["A", "B", "C", "D", "E"]
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name, torch_dtype="auto", device_map="auto"
)
# First token the model would emit for each option
option_token_ids = [
tokenizer.encode(opt, add_special_tokens=False)[0] for opt in options
]
prompt = (
"What color is the sky?\n"
"A. Red\nB. Blue\nC. Green\nD. Purple\nE. I don't know\nAnswer:"
)
messages = [{"role": "user", "content": prompt}]
text = tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True,
enable_thinking=False
)
inputs = tokenizer(text, return_tensors="pt").to(model.device)
with torch.no_grad():
logits = model(**inputs).logits[0, -1]
# Constrained decoding: softmax over only the option tokens
probs = torch.softmax(logits[option_token_ids].float(), dim=-1)
print(options[probs.argmax().item()])  # -> B

Eso es todo el motor. El vocabulario tiene más de 150.000 tokens y hemos reducido la decisión a cinco números. Sin juegos de temperatura de muestreo en la generación, sin un parser que pueda fallar, sin forma de que el modelo alucine una opción que no está en la lista. El espacio de salida está cerrado por construcción.

Es un clasificador, y eso está bien

Démosles a los escépticos lo que merecen. Lo que hemos construido es un clasificador discriminativo sobre un conjunto fijo de etiquetas: descendiente de ideas que se remontan al perceptrón de Rosenblatt en los años cincuenta, pasando por la regresión logística y por cada red neuronal con cabeza softmax de la era del deep learning. Si entornas los ojos, la decodificación restringida no es más que una lectura lineal sobre el estado oculto final, que es lo que siempre ha sido una cabeza de clasificación. El ingeniero de ML que lleva años suplicando a su equipo que simplemente entrene un clasificador tiene todo el derecho a sentirse un poco frustrado al ver cómo esto se rebautiza como "modelo de decisión".

Pero la historia también muestra por qué la versión nueva importa. Los clasificadores antiguos eran estrechos: recolectabas datos etiquetados, entrenabas y obtenías un modelo que sabía una tarea y nada más. La razón por la que los sistemas basados en LLM siguen devorando tareas que "deberían" usar un clasificador propio es la propiedad zero-shot: el modelo base ya absorbió suficiente del mundo como para que un prompt sea los datos de entrenamiento. Cuando probé una configuración así contra un conjunto de validación de CommonsenseQA, el modelo de 1.700 millones de parámetros alcanzó alrededor de un 59 % de precisión sin ningún ajuste fino, subiendo a cerca de un 62 % tras un ajuste rápido con el split de entrenamiento. No es el estado del arte, pero costó una tarde y ningún dato etiquetado propio. Un clasificador específico quizá rinda mejor; también habría requerido una pipeline, un dataset y un plan de reentrenamiento cada vez que cambiaran las etiquetas.

Aquí hay un encuadre útil que toma prestado el viejo debate entre modelos generativos y discriminativos. Los clasificadores generativos modelan toda la distribución: son derrochadores pero flexibles. Los discriminativos modelan la frontera de decisión: son eficientes pero rígidos. La decodificación restringida es un híbrido curioso: un modelo generativo puesto al servicio discriminativo en tiempo de inferencia. Obtienes la eficiencia de la lectura discriminativa con la amplitud del preentrenamiento generativo. Esa combinación genuinamente no estaba disponible antes, aunque las piezas sean antiguas.

Dónde gana la decodificación restringida

  • Latencia. Un pase hacia adelante en lugar de N pasos autorregresivos. En modelos pequeños, esa es la diferencia entre "lo bastante rápido para una petición en línea" y "necesita una cola". Quienes ejecutan modelos de decisión puros en el navegador reportan respuestas por debajo de 200 ms. Pruébalo con JSON generativo.
  • Corrección estructural. El modelo literalmente no puede producir una salida fuera del conjunto permitido. Nada de JSON malformado, nada de "La respuesta probablemente es B porque...", y no necesitas una capa de guardarraíles para detectar violaciones de formato.
  • Throughput. Como cada petición es un único pase de forma idéntica, el batching es trivial y predecible. Generar respuestas de longitud variable arruina la eficiencia de tu batching.
  • Una puntuación por opción. Obtienes la distribución completa, no solo al ganador. Eso abre la puerta a la lógica de abstención: si la probabilidad máxima está por debajo de un umbral, derívalo a un humano o a un modelo más grande.

El punto de la abstención merece énfasis. Una respuesta generativa es un único artefacto en el que confías o no. Una distribución de probabilidad sobre opciones te permite construir enrutamiento: los casos de alta confianza pasan automáticamente y los de baja confianza escalan. Ese es el patrón detrás de muchos sistemas de triaje en producción: enrutamiento de tickets de soporte, prefiltrado de moderación de contenido, detección de intención. Ahí es donde esta técnica se gana su sitio. Si tu tarea se descompone de forma natural en "elige una de K etiquetas y dime cuán seguro estás", la decodificación restringida es casi con seguridad la herramienta adecuada.

Dónde gana la salida generativa

Ahora el otro lado. En el momento en que tu tarea no encaja en un conjunto fijo de etiquetas, la decodificación restringida se derrumba. Si la respuesta es una entidad de forma libre, un número, un fragmento de código o cualquier cosa composicional, necesitas generación, posiblemente con restricciones de salida estructurada, pero generación al fin. También hay una pérdida más sutil: el razonamiento. Cuando un modelo genera una cadena de pensamiento antes de responder, suele rendir materialmente mejor en preguntas difíciles. La cabeza de decisión de un solo pase no te da ningún borrador. Le estás pidiendo pensamiento de sistema uno, rápido e intuitivo, y eso es exactamente lo que obtienes, incluidos sus fallos característicos.

También está el problema de la sensibilidad a la redacción. Un clasificador restringido sobre "A/B/C/D/E" mide en realidad la preferencia del modelo por el texto de cada opción en esa posición. Reformula ligeramente la opción C, reordena la lista o cambia "Respuesta:" por "La mejor respuesta es" y las puntuaciones pueden moverse. Las respuestas generativas con razonamiento tienden a ser más robustas a perturbaciones superficiales, porque el modelo tiene que comprometerse con el contenido y no solo con un token. Si evalúas cualquiera de los dos enfoques, perturba la presentación y mira qué se rompe. Es una prueba de robustez barata y also incómoda.

Dos caminos a través de un paisaje, uno directo y otro sinuoso, que simbolizan la inferencia rápida frente a la deliberativa.
La decodificación restringida es el camino directo; la generación con razonamiento es la senda larga que a veces llega a terreno más alto.

El problema de la calibración: tus puntuaciones de confianza mienten

Aquí está la trampa que atrapa a todo el que construye uno de estos. Obtienes probabilidades de un softmax, así que deben ser probabilidades, ¿no? No lo son. Son la confianza del modelo en que un token dado viene a continuación, lo cual es una afirmación sobre el lenguaje, no sobre la corrección. Pregúntale al modelo "¿Dónde es más probable encontrar un murciélago?" con opciones que incluyan "Cueva" y "Partido de béisbol", y asignará algo como 0,998 a "Cueva": una pregunta ambigua sin una respuesta defendible con certeza, respondida con certeza casi total.

Si agrupas las predicciones por confianza en una evaluación real, el panorama empeora. En una ejecución de CommonsenseQA, el bloque de confianza 0,9–1,0 era correcto solo alrededor del 70 % de las veces, y el de 0,8–0,9 apenas alcanzaba el 40 %. Un modelo bien calibrado debería acertar cerca del 90 % de las veces cuando dice 0,9. Este es sistemáticamente sobreconfiado, lo cual, si lo piensas, refleja la vieja observación de que las redes profundas modernas son en general sobreconfiadas. Guo et al. mostraron en 2017 que las salidas softmax de una ResNet básica están muy mal calibradas en comparación con las redes poco profundas de los noventa. Todo lo viejo vuelve a ser nuevo; solo hemos reinventado el problema con 1.700 millones de parámetros.

La solución, afortunadamente, también es antigua y casi vergonzosamente sencilla: el escalado de temperatura. Divide los logits por un único escalar T aprendido antes del softmax:

def scaled_probs(logits, option_ids, temperature):
selected = logits[option_ids].float() / temperature
return torch.softmax(selected, dim=-1)
# Fit T on a validation set by minimizing negative log-likelihood
# of the correct option. For the CommonsenseQA run above, T ~= 3.8
temperature = 3.7973
probs = scaled_probs(logits, option_token_ids, temperature)

T mayor que 1 aplana la distribución; T menor que 1 la afila. Ajustar un solo número sobre un conjunto de validación reservado convirtió aquella tabla de calibración lamentable en algo honesto: el bloque 0,9–1,0 ahora ronda el 95 % de precisión, y el de 0,5–0,6 ronda el 55 %. La precisión no cambia en absoluto, porque el argmax es invariante a escalados monótonos, pero las puntuaciones ahora significan lo que creías que significaban. Si piensas enrutar según esas probabilidades, calíbralas primero o no te molestes en recogerlas.

¿Ajustar la temperatura sobre un benchmark es hacer trampa?

Una pregunta justa que surgió en la discusión: ¿no es un poco p-hacking ajustar T para que el modelo parezca calibrado en un benchmark? Lo sería si lo ajustaras sobre el conjunto de test. Hecho correctamente (ajustar T en un split de validación y reportar la calibración en un split de test reservado), es solo una regresión de un parámetro, y es el estándar en la literatura precisamente por esa razón. La disciplina que importa es la misma que en cualquier otro lugar del ML: mantén tus splits honestos y desconfía de cualquier afirmación de calibración medida sobre datos que tocaron el ajuste. Si tu dominio de despliegue se aleja de tu dominio de evaluación, tu T también se desplaza, así que la recalibración pertenece a tu bucle de mantenimiento junto con todo lo demás.

Esto es en realidad una instancia de una enfermedad más amplia: la brecha entre lo que un sistema está especificado para significar y lo que realmente calcula, una brecha en la que ya he argumentado que viven los bugs. La "confianza" se especifica como una probabilidad de corrección; la implementación te da plausibilidad del siguiente token. El escalado de temperatura es un parche sobre esa brecha, no una resolución de la misma. Ten esta distinción en mente cada vez que tengas la tentación de conectar las salidas de softmax a un umbral de alertas.

Un procedimiento de decisión práctico

Cuando elijo hoy entre los dos modos, repaso una lista breve:

  1. ¿La salida es un conjunto fijo de etiquetas? Si sí, la decodificación restringida está sobre la mesa. Si no, genera.
  2. ¿Necesito razonamiento para obtener una precisión aceptable? Prototipa ambos. Si la cabeza de un solo pase queda por detrás de la cadena de pensamiento más de lo que puedes tolerar, la generación gana a pesar de la latencia.
  3. ¿Necesito puntuaciones por opción para enrutar o abstenerme? Si sí, la decodificación restringida con escalado de temperatura es casi gratis y la generación no te da nada comparable.
  4. ¿Qué tamaño tiene el conjunto de etiquetas? Cinco opciones es trivial; cinco mil es territorio de recuperación. Con espacios de etiquetas grandes, embebe tus etiquetas, prefiltra con búsqueda vectorial y solo entonces usa el modelo para elegir entre los finalistas: el truco de escalado de clasificadores que es estándar desde la era de los sistemas de recomendación.
  5. ¿Las etiquetas cambiarán a menudo? La propiedad zero-shot es precisamente el punto. Si las etiquetas cambian cada semana, un clasificador reentrenado es una carga de mantenimiento; editar un prompt no lo es.

El softmax del modelo es una afirmación sobre qué token viene a continuación, no sobre lo que es verdad. Calíbralo o no confíes en él.

Una consideración más: el tamaño del modelo interactúa con la elección. La cabeza de un solo pase de un modelo pequeño es lo bastante barata como para ejecutarse en cada petición, incluso en el cliente; la gente ya despliega modelos de decisión que corren en el navegador con respuestas de menos de 200 ms. Un diseño en cascada (modelo pequeño restringido para el 80 % fácil, modelo generativo grande para la cola difícil) a menudo supera a ambos extremos en coste y precisión. Es el mismo instinto que detrás de la decodificación especulativa, aplicado a nivel de sistema en lugar de a nivel de token.

La recomendación

Mi postura: si tu tarea es genuinamente una decisión de elección fija (enrutamiento, triaje, intención, evaluación de opción múltiple), construye la cabeza de decisión restringida y no mires atrás. La ganancia de latencia por sí sola lo justifica, las garantías estructurales eliminan toda una clase de fallos de parseo y las puntuaciones por opción te dan una lógica de enrutamiento que la generación no puede igualar. Pero trata el softmax crudo como un instrumento sin calibrar. Ajusta con tu tarea real si logras reunir aunque sea un dataset modesto, ajusta una temperatura sobre un split de validación limpio y verifica la calibración con datos que el ajuste nunca vio.

Reserva la salida generativa (con restricciones de salida estructurada donde las necesites) para tareas genuinamente composicionales o que se beneficien de razonamiento visible. Y no dejes que nadie te venda un "modelo de decisión" como categoría nueva: es un clasificador con abrigo de LLM, descendiente de sesenta años de modelado discriminativo, y es más útil precisamente cuando respetas ese linaje lo suficiente como para hacer el trabajo de calibración que los veteranos siempre exigieron. Las herramientas son nuevas. La disciplina no.