MÓDULO 7 · HUGGING FACE · LECCIÓN 06 · 25 MIN

La plantilla de chat y los tokens especiales

Un modelo de chat sigue siendo un completador de texto. La lista [{"role": …, "content": …}] es una comodidad de la librería: lo que entra al modelo es una cadena con delimitadores especiales, y esa cadena tiene que tener exactamente el formato con el que el modelo se entrenó. apply_chat_template es la traducción, y vive en el tokenizador, no en el modelo.
01 — La traducción
Lo que escribes no es lo que entra
Lo que escribes
[ {"role": "system", "content": "You are a…"}, {"role": "user", "content": "How do I…"}, ]
Lo que recibe el modelo
<|system|> You are a…</s> <|user|> How do I…</s> <|assistant|>

// Delimitadores de ejemplo. Los de tu modelo salen de su tokenizador, no de aquí.

prompt = tokenizer.apply_chat_template(
    mensajes,
    tokenize=False,          # devuelve la cadena, no los ids
    add_generation_prompt=True,
)
print(prompt)

tokenize=False es un instrumento de diagnóstico: te devuelve la cadena en vez de los ids, así que puedes ver qué le estás pasando al modelo. Imprímela la primera vez que uses un modelo nuevo. Es la diferencia entre depurar el formato y adivinarlo.

02 — La cabecera vacía
Qué hace add_generation_prompt

Una cosa muy concreta: añade al final la cabecera del turno del asistente, vacía, para que el modelo continúe ahí (es el último <|assistant|> del recuadro de arriba, sin nada detrás).

Sin él, la cadena termina donde terminó el mensaje del usuario, y lo que el modelo continúa es el turno del usuario. De ahí el síntoma que la guía llama «el modelo habla solo»: no está alucinando, está completando exactamente lo que le has dado.

03 — Propiedad del modelo
No hay un formato de chat estándar
<|user|> …           <|assistant|> …
[INST] … [/INST]
<|im_start|>user … <|im_end|>

Cada familia usa sus delimitadores, y viven en el tokenizer_config.json del repositorio, en el campo chat_template. La plantilla se le pide al tokenizador del modelo que vas a usar, nunca se copia de un ejemplo. Si aplicas los delimitadores de otra familia, el modelo no reconoce el formato con el que fue entrenado y se comporta como un completador base: sin error, solo peor.

04 — La salida
Quedarse con la parte nueva sin acoplarse

El pipeline de generación devuelve, por defecto, el prompt más la continuación. Hay que quedarse con lo nuevo, y el cómo importa:

MétodoQué pasa si cambias de modelo
salida.split("<|assistant|>")[-1] El delimitador no aparece, el split devuelve la lista entera y te quedas con todo el texto sin enterarte
return_full_text=False Sigue funcionando: la librería sabe dónde acababa tu prompt
Cortar por número de tokens del prompt Sigue funcionando, y es lo que hay por debajo

El split por el delimitador funciona hasta el día que cambias de modelo, y ese día falla en silencio devolviendo texto plausible de más. Es el peor tipo de fallo.

05 — El parón
Cuando la generación no sabe terminar

Un modelo de chat cierra su turno emitiendo un token de fin. Si tu generación se pasa de largo e inventa un turno de usuario, hay dos causas típicas: la plantilla no se aplicó —así que el modelo nunca vio la estructura de turnos— o el criterio de parada no incluye el token de fin de turno de ese modelo. Las dos se diagnostican imprimiendo la cadena del paso 01.

Auditoría

La guía dice en sus Puntos clave: «usa siempre apply_chat_template con modelos de chat». Y en su propio apartado de generación de código hace lo contrario: pasa el prompt en crudo a pipe_chat(...) y luego intenta partir la salida por "<|assistant|>". Como la plantilla nunca se aplicó, ese delimitador no aparece en el resultado y el split devuelve el texto entero. El código contradice la regla que la propia guía acaba de dar.

En ese mismo bloque hay un fallo más simple: la variable prompt_codigo aparece escrita en una línea suelta y nunca se le asigna nada antes de usarse. Ese fragmento, copiado tal cual, no se ejecuta.

Y una cifra con truco. La guía dice de TinyLlama «1.1B parámetros» y «aprox 2.2 GB». Los dos números solo encajan si los pesos están en 16 bits; en 32 bits serían aproximadamente el doble. La guía no dice en qué precisión carga, así que la cifra de descarga solo es cierta bajo un supuesto que no enuncia. Antes de dimensionar una máquina con ese número: ¿qué torch_dtype estás usando de verdad?

06 — Ahora tú
Imprime la cadena dos veces

Coge cualquier modelo de chat pequeño y haz solo esto: construye la lista de mensajes, llama a apply_chat_template con tokenize=False e imprime la cadena. Después vuelve a imprimirla con add_generation_prompt=False y compara. La diferencia entre las dos es literalmente la razón por la que un modelo «habla solo».

Luego cambia a un modelo de otra familia y repite la impresión sin tocar nada más. Si los delimitadores han cambiado, ya sabes por qué ningún split escrito a mano sobrevive a un cambio de modelo.