A hand-built chat prompt performs worse than apply_chat_template. Spot the tokenization issues.
Click any words you think contain an error. Click again to unmark.
Two errors. Bare 'system:'/'user:'/'assistant:' tokenize as text, not as the special role tokens the model was trained on. Trailing 'assistant:' also shifts the first-token id via leading-space. Use apply_chat_template.
Imagine writing a play. The script you give to the actors uses real, agreed-upon labels: ACT 1 SCENE 2, NARRATOR, JANE. They were trained to look for those exact labels. Now you hand them a script that uses 'act-one scene-two', 'narrator-says', 'jane:' instead. The actors can sort of figure it out, but they hesitate at every transition because none of the labels match what they learned to expect. The same thing happens with hand-rolled chat templates. The model was trained on special role tokens like <|start_header_id|>system<|end_header_id|>. Plain 'system:' is not those special tokens; it is just text the model has to guess about. Plus, the spacing where the model is supposed to start speaking shifts the first word's token id, which is another small thing the model has to compensate for.
Detailed answer & concept explanation~8 min readEverything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example. Click to expand.
Everything you need to truly understand this topic: intuition, mechanics, step by step explanation, code, formulas, and worked example.
Everything important, quickly.
Open with the framing that hand-rolling chat formatting is a category error: the template is part of the trained model artifact. Walk the two coupled bugs: bare role labels are not the special role tokens the model learned to attend to, and the trailing 'assistant:' shifts the first generated token id via the leading-space boundary. Cover the diagnosis (decode and diff against apply_chat_template, logit-distribution comparison). Close on the fix: apply_chat_template is non-negotiable for production, per-model template loading prevents cross-family bugs.
# Wrong: hand-rolled bare-label template
def build_prompt(sys, msg):
return f'system: {sys}\nuser: {msg}\nassistant:'
prompt = build_prompt(system_text, user_msg)
ids = tokenizer.encode(prompt, add_special_tokens=False)
# Bug 1: 'system:' etc are ordinary text, not special role tokens
# Bug 2: trailing 'assistant:' shifts first-token id distribution
# Right: apply_chat_template
messages = [
{'role': 'system', 'content': system_text},
{'role': 'user', 'content': user_msg},
]
ids = tokenizer.apply_chat_template(
messages,
add_generation_prompt=True,
return_tensors='pt',
)
# Diagnostic: decode and compare
print(tokenizer.decode(ids[0]))
# Shows the actual special tokens the model expectsReal products, models, and research that use this idea.
- HuggingFace transformers ships apply_chat_template that loads the Jinja template from tokenizer_config.json for Llama 3+, Mistral, Qwen 3.5, and other instruction-tuned models.
- Llama 3's documented chat format uses <|begin_of_text|> + <|start_header_id|>{role}<|end_header_id|> + content + <|eot_id|>, none of which appear in a hand-rolled 'system:/user:/assistant:' template.
- OpenAI's chat completions API hides this entirely by accepting structured role messages, so the server-side rendering applies the right template per model.
- Anthropic's messages API for Claude similarly takes typed role turns, eliminating the hand-roll category of error.
What an interviewer would ask next. Try answering before peeking at the approach.
QIf you add the right special tokens manually instead of using apply_chat_template, what could still go wrong?
QYour team supports five different open-weight models. How do you avoid a per-model template bug?
Don't say thisRed flags and common mistakes that signal junior thinking. Click to expand.
Red flags and common mistakes that signal junior thinking. Click to expand.
Treating hand-rolled chat formatting as equivalent to the model's trained chat format, missing both the special role-token mismatch and the leading-space boundary issue.
The night-before-the-interview bullets. Scan these on the way to the call.
Primary sources. Skim if you want the original framing.
Same topic, related formats. Practice these next.