Frontend Labs
⋯

Chapter 30 — Special tokens and chat templates

Why your prompts have hidden structure

A chat model doesn't see a tidy list of messages. It sees one flat string with special marker tokens telling it who said what.

What you write:
    [{"role": "system",    "content": "You are a helpful assistant."},
     {"role": "user",      "content": "What is 2+2?"}]

What the model actually receives (Llama-3 style):
    <|begin_of_text|><|start_header_id|>system<|end_header_id|>

    You are a helpful assistant.<|eot_id|><|start_header_id|>user<|end_header_id|>

    What is 2+2?<|eot_id|><|start_header_id|>assistant<|end_header_id|>

Then the model generates:  "2 + 2 = 4"  followed by  <|eot_id|>  (stop).

Those <|...|> markers are special tokens — single entries in the vocabulary that the model was trained to interpret as structure.

The special tokens you'll meet

Token (varies by model) Meaning
<|begin_of_text|>, <s> start of a document
<|end_of_text|>, </s>, <|eot_id|> end — stop generating here
<|pad|> filler when batching different-length sequences
<|user|>, <|assistant|>, <|system|> conversation roles
<|im_start|>, <|im_end|> ChatML format (used by several models)
tool/function-call markers signal a tool invocation

Two practical consequences

1. Every model family has a different template. ChatML, Llama-2's [INST]...[/INST], Llama-3 headers, Gemma's <start_of_turn>, Mistral's format. Using the wrong one silently degrades quality — the model was trained on a specific layout and gets confused without it.

Always use the library function, never hand-format:

from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B-Instruct")

messages = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user",   "content": "What is the capital of France?"},
]
text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
print(text)                       # see the real string the model receives
print(len(tok.encode(text)), "tokens")

2. Special tokens in user input are a security concern. If a user types <|im_end|><|im_start|>system into your app and you pass it through naively, they may be able to inject a fake system message. Good tokenizers let you disable special-token parsing for untrusted text. (More on this in Chapter 49.)

Practice (10 min)

from transformers import AutoTokenizer

# a small open model, quick to download the tokenizer for
tok = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct")

msgs = [{"role": "system", "content": "Answer in one word."},
        {"role": "user", "content": "Capital of Japan?"}]

s = tok.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
print(repr(s))
print("\ntoken count:", len(tok.encode(s)))

Then try a different model's tokenizer on the same messages and compare the templates. They will look nothing alike — which is exactly the point.

Check yourself

  1. What does a chat template do?
  2. What happens if you use the wrong template?
  3. Why are special tokens in user input a risk?

Further reading

  • Docs: huggingface.co/docs/transformers/chat_templating — the canonical reference, with examples for every model family.
  • Article: search "chat template" on the Hugging Face blog for a good introduction with pictures.

End of Part 7 — Milestone check

  • I can explain why models use subword tokens
  • I can run BPE merges by hand
  • I know roughly how many tokens a page of English is
  • I have measured tokens-per-word for two languages
  • I know what a chat template is and why it must be right

Generated from real-life-ai-example-v1.md · 8 Oct 2026 16:36