What Actually Gets Tokenized in SFT A developer dissects the tokenization process in supervised fine-tuning (SFT), revealing that chat templates are not metadata but actual tokens in the model's vocabulary. The analysis shows that roles like <|user|> and <|assistant|> are token IDs, and that Jinja2 templates control the exact string format, with whitespace stripping critical to matching training data. The developer warns against hardcoding token IDs across models and advises using repr() to verify template output. tok.apply chat template msgs, add generation prompt=True I have copied that line more times than I can count without thinking about what any of it does. The official example writes it that way, the output looks right, you move on. Then you have to build your own SFT data and compute your own loss mask, and it turns out every piece of that line is load-bearing. I spent today pulling it apart. Notes below. The counterintuitive part first: the model has no idea messages exists. messages is for Python. Roles are real tokens in the vocabulary. tokenizer config.json or as a standalone chat template.jinja . When both exist the file wins. add generation prompt=False . Inference renders msgs :-1 with add generation prompt=True . messages exists I had some vague picture in my head where the role was structured metadata and something inside the model read it. Nope. {"role": "user", "content": "What is the Young's modulus of graphene?"} That list lives in your Python process and nowhere else. What reaches the model is: gMASK