A hand-built chat prompt performs worse than apply_chat_template. Spot the tokenization issues.
Same topic, related formats. Practice these next.
Same topic, related formats. Practice these next.
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.
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.
What an interviewer would ask next. Try answering before peeking at the approach.
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.