Module: FT07 — Tokenizers & Chat Templates Diagram count: 5 Tool: Mermaid (primary). Each diagram validated in Mermaid Live Editor.
Type: Bug-symptom map Purpose: The debugging checklist on one diagram. Three bug classes, each with its tell-tale symptom. Reading the diagram: Each bug is a node; the symptom is the annotation. All three share the same root property — the model trains, loss descends, output is wrong.
flowchart LR
subgraph Bugs["THE TOP THREE SILENT TOKENIZER/TEMPLATE BUGS"]
direction TB
B1["BUG 1 — Cross-family\ntemplate misuse\n(Llama-3 template on Qwen)"]
B2["BUG 2 — EOS mishandling\n(missing / wrong EOS\nduring SFT)"]
B3["BUG 3 — Packing without\nattention mask\n(examples attend across\nboundaries)"]
end
B1 -.->|symptom:| S1["Role boundaries fuzzy\nAssistant continues user turn\nFormat drifts at inference"]
B2 -.->|symptom:| S2["Run-on generation\nLoss explodes / NaN\n(Qwen SFT thread)"]
B3 -.->|symptom:| S3["Model trains fine\nQuality subtly degraded\nvs correctly-packed baseline"]
style Bugs fill:#14141f,stroke:rgba(240,128,128,0.6),stroke-width:1.5px,color:#e4e4e8
style B1 fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style B2 fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style B3 fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style S1 fill:#14141f,stroke:rgba(255,255,255,0.12),color:#9494a0
style S2 fill:#14141f,stroke:rgba(255,255,255,0.12),color:#9494a0
style S3 fill:#14141f,stroke:rgba(255,255,255,0.12),color:#9494a0
apply_chat_template() vs Hand-ConcatenationType: Side-by-side comparison Purpose: Why the rule "always use apply_chat_template" exists. The hand-concatenated path drops special tokens, uses wrong EOS, mismatches the family. Reading the diagram: Two parallel paths from the same conversation. The template path produces correct tokens; the hand-concat path produces bug 1 and bug 2.
flowchart TD
Conv["Conversation\n[system, user, assistant]"]
Conv --> Tpl["apply_chat_template()\nread tokenizer.chat_template"]
Conv --> Hand["HAND-CONCATENATION\nf'User: {x}\nAssistant: {y}'"]
Tpl --> Correct["Correct token sequence\nfamily role tokens\n+ correct EOS\n+ assistant mask"]
Correct --> Train["SFT trains correctly"]
Hand --> Wrong["Missing special tokens\nwrong / no EOS\nmismatched family convention"]
Wrong --> Bug1["BUG 1 — cross-family\n(role boundaries fuzzy)"]
Wrong --> Bug2["BUG 2 — EOS mishandling\n(run-on / loss explode)"]
Bug1 --> Fail["Silent failure"]
Bug2 --> Fail
style Conv fill:#14141f,stroke:rgba(255,255,255,0.12),color:#e4e4e8
style Tpl fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Hand fill:#14141f,stroke:rgba(240,128,128,0.6),color:#f08080
style Correct fill:#08080c,stroke:rgba(94,234,212,0.5),color:#82e0aa
style Wrong fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style Train fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style Bug1 fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style Bug2 fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style Fail fill:#14141f,stroke:rgba(240,128,128,0.6),color:#f08080
Type: Loop / process Purpose: The two-minute discipline that prevents bugs 1, 2, and 3 before they cost GPU-hours. Reading the diagram: A loop. Encode one conversation, decode it, read it, check the assistant mask. Only launch the full run when all checks pass.
flowchart TD
Start["Take ONE conversation\nfrom your dataset"]
Start --> Encode["ids = apply_chat_template(\n conv, tokenize=True,\n return_assistant_tokens_mask=True)"]
Encode --> Decode["print(tokenizer.decode(ids))"]
Decode --> Q1{"Read it.\nRole tokens match\nthe family?\nEOS appended?"}
Q1 -->|"No"| Fix1["Set correct chat_template\n(NEVER hand-concatenate)"]
Fix1 --> Encode
Q1 -->|"Yes"| Q2{"Assistant mask\nmarks ONLY\nassistant tokens?"}
Q2 -->|"No"| Fix2["Fix the loss mask\n(system/user/tool masked)"]
Fix2 --> Encode
Q2 -->|"Yes"| Launch["LAUNCH the full run"]
Launch --> Done["Train with confidence"]
style Start fill:#14141f,stroke:rgba(255,255,255,0.12),color:#e4e4e8
style Encode fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Decode fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Launch fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style Done fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style Q1 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style Q2 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style Fix1 fill:#08080c,stroke:rgba(240,168,104,0.6),color:#f0a868
style Fix2 fill:#08080c,stroke:rgba(240,168,104,0.6),color:#f0a868
Type: Sequential workflow with the warm-up gate Purpose: How to add domain tokens correctly — and the warm-up step everyone forgets. Reading the diagram: Left to right. Add tokens → resize embeddings AND lm_head → warm up new rows → re-save tokenizer. Skipping the warm-up produces noise.
flowchart LR
Add["1. ADD TOKENS\ntokenizer.add_tokens(\n['<|citation|>', ...])"]
Resize["2. RESIZE\nmodel.resize_token_\nembeddings(len(tok))\n(input emb + lm_head)"]
Warm["3. WARM UP\nnew rows\n(short emb-only pass,\nor mean-init)"]
Save["4. RE-SAVE\ntokenizer (ship\nwith adapter)"]
Add --> Resize
Resize --> Warm
Warm --> Save
Save --> Train["SFT on full task"]
Warm -.skip (anti-pattern).-> Skip["Random rows inject noise\ninto the loss\nmodel behaves erratically\non new tokens"]
Skip --> Bad["Silent degradation"]
style Add fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Resize fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Warm fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Save fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Train fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style Skip fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style Bad fill:#14141f,stroke:rgba(240,128,128,0.6),color:#f08080
Type: Decision tree Purpose: The whole module on one flowchart. Start from the symptom, land on the fix. Reading the diagram: Top-down. Match your symptom, follow to the bug, apply the fix.
flowchart TD
Start["Symptom observed\nat training or inference"]
Start --> S1{"Model won't stop\ngenerating; runs to\nmax length?"}
S1 -->|"Yes"| B2a["BUG 2 — EOS missing/wrong\nFix: apply_chat_template\n(append family EOS)"]
S1 -->|"No"| S2{"Loss explodes / NaN\n(Qwen SFT)?"}
S2 -->|"Yes"| B2b["BUG 2 — template/EOS misconfig\nFix: verify tokenizer.eos_token_id\n== config.eos_token_id"]
S2 -->|"No"| S3{"Role boundaries fuzzy;\nassistant continues\nuser turn?"}
S3 -->|"Yes"| B1["BUG 1 — cross-family template\nFix: use family native template"]
S3 -->|"No"| S4{"Trains fine, quality\nsubtly degraded vs\nbaseline?"}
S4 -->|"Yes"| B3["BUG 3 — packing w/o attention mask\nFix: document/variable-length attn\n+ mask non-assistant loss"]
S4 -->|"No"| S5{"New domain tokens\nbehave erratically?"}
S5 -->|"Yes"| B4["Vocab extended, no warm-up\nFix: warm up new embeddings"]
S5 -->|"No"| Decode["ENCODE → DECODE → READ\none example; the bug is\nvisible if you look"]
style Start fill:#14141f,stroke:rgba(255,255,255,0.12),color:#e4e4e8
style S1 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style S2 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style S3 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style S4 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style S5 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style B1 fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style B2a fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style B2b fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style B3 fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style B4 fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style Decode fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
#14141f panel fill, #5eead4 (--accent) for the correct path / primary nodes, rgba(94,234,212,0.4) for decision diamonds, #f08080 (--danger) for bug / anti-pattern nodes, #f0a868 (--warn) for fix / intervention nodes, #82e0aa (--ok) for the success path, #e4e4e8 / #9494a0 for text.flowchart, subgraph, dashed -.label.-> links) supported in current Mermaid (v10.4+).# Diagrams — Module FT07: Tokenizers & Chat Templates
**Module**: FT07 — Tokenizers & Chat Templates
**Diagram count**: 5
**Tool**: Mermaid (primary). Each diagram validated in [Mermaid Live Editor](https://mermaid.live).
---
## Diagram 1 — The Top Three Silent Bugs and Their Symptoms
**Type**: Bug-symptom map
**Purpose**: The debugging checklist on one diagram. Three bug classes, each with its tell-tale symptom.
**Reading the diagram**: Each bug is a node; the symptom is the annotation. All three share the same root property — the model trains, loss descends, output is wrong.
```mermaid
flowchart LR
subgraph Bugs["THE TOP THREE SILENT TOKENIZER/TEMPLATE BUGS"]
direction TB
B1["BUG 1 — Cross-family\ntemplate misuse\n(Llama-3 template on Qwen)"]
B2["BUG 2 — EOS mishandling\n(missing / wrong EOS\nduring SFT)"]
B3["BUG 3 — Packing without\nattention mask\n(examples attend across\nboundaries)"]
end
B1 -.->|symptom:| S1["Role boundaries fuzzy\nAssistant continues user turn\nFormat drifts at inference"]
B2 -.->|symptom:| S2["Run-on generation\nLoss explodes / NaN\n(Qwen SFT thread)"]
B3 -.->|symptom:| S3["Model trains fine\nQuality subtly degraded\nvs correctly-packed baseline"]
style Bugs fill:#14141f,stroke:rgba(240,128,128,0.6),stroke-width:1.5px,color:#e4e4e8
style B1 fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style B2 fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style B3 fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style S1 fill:#14141f,stroke:rgba(255,255,255,0.12),color:#9494a0
style S2 fill:#14141f,stroke:rgba(255,255,255,0.12),color:#9494a0
style S3 fill:#14141f,stroke:rgba(255,255,255,0.12),color:#9494a0
```
---
## Diagram 2 — `apply_chat_template()` vs Hand-Concatenation
**Type**: Side-by-side comparison
**Purpose**: Why the rule "always use apply_chat_template" exists. The hand-concatenated path drops special tokens, uses wrong EOS, mismatches the family.
**Reading the diagram**: Two parallel paths from the same conversation. The template path produces correct tokens; the hand-concat path produces bug 1 and bug 2.
```mermaid
flowchart TD
Conv["Conversation\n[system, user, assistant]"]
Conv --> Tpl["apply_chat_template()\nread tokenizer.chat_template"]
Conv --> Hand["HAND-CONCATENATION\nf'User: {x}\nAssistant: {y}'"]
Tpl --> Correct["Correct token sequence\nfamily role tokens\n+ correct EOS\n+ assistant mask"]
Correct --> Train["SFT trains correctly"]
Hand --> Wrong["Missing special tokens\nwrong / no EOS\nmismatched family convention"]
Wrong --> Bug1["BUG 1 — cross-family\n(role boundaries fuzzy)"]
Wrong --> Bug2["BUG 2 — EOS mishandling\n(run-on / loss explode)"]
Bug1 --> Fail["Silent failure"]
Bug2 --> Fail
style Conv fill:#14141f,stroke:rgba(255,255,255,0.12),color:#e4e4e8
style Tpl fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Hand fill:#14141f,stroke:rgba(240,128,128,0.6),color:#f08080
style Correct fill:#08080c,stroke:rgba(94,234,212,0.5),color:#82e0aa
style Wrong fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style Train fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style Bug1 fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style Bug2 fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style Fail fill:#14141f,stroke:rgba(240,128,128,0.6),color:#f08080
```
---
## Diagram 3 — The Tokenization Inspection Loop
**Type**: Loop / process
**Purpose**: The two-minute discipline that prevents bugs 1, 2, and 3 before they cost GPU-hours.
**Reading the diagram**: A loop. Encode one conversation, decode it, read it, check the assistant mask. Only launch the full run when all checks pass.
```mermaid
flowchart TD
Start["Take ONE conversation\nfrom your dataset"]
Start --> Encode["ids = apply_chat_template(\n conv, tokenize=True,\n return_assistant_tokens_mask=True)"]
Encode --> Decode["print(tokenizer.decode(ids))"]
Decode --> Q1{"Read it.\nRole tokens match\nthe family?\nEOS appended?"}
Q1 -->|"No"| Fix1["Set correct chat_template\n(NEVER hand-concatenate)"]
Fix1 --> Encode
Q1 -->|"Yes"| Q2{"Assistant mask\nmarks ONLY\nassistant tokens?"}
Q2 -->|"No"| Fix2["Fix the loss mask\n(system/user/tool masked)"]
Fix2 --> Encode
Q2 -->|"Yes"| Launch["LAUNCH the full run"]
Launch --> Done["Train with confidence"]
style Start fill:#14141f,stroke:rgba(255,255,255,0.12),color:#e4e4e8
style Encode fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Decode fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Launch fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style Done fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style Q1 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style Q2 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style Fix1 fill:#08080c,stroke:rgba(240,168,104,0.6),color:#f0a868
style Fix2 fill:#08080c,stroke:rgba(240,168,104,0.6),color:#f0a868
```
---
## Diagram 4 — The Vocab-Extension Workflow
**Type**: Sequential workflow with the warm-up gate
**Purpose**: How to add domain tokens correctly — and the warm-up step everyone forgets.
**Reading the diagram**: Left to right. Add tokens → resize embeddings AND lm_head → warm up new rows → re-save tokenizer. Skipping the warm-up produces noise.
```mermaid
flowchart LR
Add["1. ADD TOKENS\ntokenizer.add_tokens(\n['<|citation|>', ...])"]
Resize["2. RESIZE\nmodel.resize_token_\nembeddings(len(tok))\n(input emb + lm_head)"]
Warm["3. WARM UP\nnew rows\n(short emb-only pass,\nor mean-init)"]
Save["4. RE-SAVE\ntokenizer (ship\nwith adapter)"]
Add --> Resize
Resize --> Warm
Warm --> Save
Save --> Train["SFT on full task"]
Warm -.skip (anti-pattern).-> Skip["Random rows inject noise\ninto the loss\nmodel behaves erratically\non new tokens"]
Skip --> Bad["Silent degradation"]
style Add fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Resize fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Warm fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Save fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
style Train fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style Skip fill:#08080c,stroke:rgba(240,128,128,0.5),color:#f08080
style Bad fill:#14141f,stroke:rgba(240,128,128,0.6),color:#f08080
```
---
## Diagram 5 — The Debugging Decision Tree (Symptom → Bug → Fix)
**Type**: Decision tree
**Purpose**: The whole module on one flowchart. Start from the symptom, land on the fix.
**Reading the diagram**: Top-down. Match your symptom, follow to the bug, apply the fix.
```mermaid
flowchart TD
Start["Symptom observed\nat training or inference"]
Start --> S1{"Model won't stop\ngenerating; runs to\nmax length?"}
S1 -->|"Yes"| B2a["BUG 2 — EOS missing/wrong\nFix: apply_chat_template\n(append family EOS)"]
S1 -->|"No"| S2{"Loss explodes / NaN\n(Qwen SFT)?"}
S2 -->|"Yes"| B2b["BUG 2 — template/EOS misconfig\nFix: verify tokenizer.eos_token_id\n== config.eos_token_id"]
S2 -->|"No"| S3{"Role boundaries fuzzy;\nassistant continues\nuser turn?"}
S3 -->|"Yes"| B1["BUG 1 — cross-family template\nFix: use family native template"]
S3 -->|"No"| S4{"Trains fine, quality\nsubtly degraded vs\nbaseline?"}
S4 -->|"Yes"| B3["BUG 3 — packing w/o attention mask\nFix: document/variable-length attn\n+ mask non-assistant loss"]
S4 -->|"No"| S5{"New domain tokens\nbehave erratically?"}
S5 -->|"Yes"| B4["Vocab extended, no warm-up\nFix: warm up new embeddings"]
S5 -->|"No"| Decode["ENCODE → DECODE → READ\none example; the bug is\nvisible if you look"]
style Start fill:#14141f,stroke:rgba(255,255,255,0.12),color:#e4e4e8
style S1 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style S2 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style S3 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style S4 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style S5 fill:#08080c,stroke:rgba(94,234,212,0.4),color:#e4e4e8
style B1 fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style B2a fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style B2b fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style B3 fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style B4 fill:#14141f,stroke:rgba(130,224,170,0.6),color:#82e0aa
style Decode fill:#14141f,stroke:#5eead4,stroke-width:1.5px,color:#e4e4e8
```
---
## Validation notes
- All five diagrams use the course design system colors: `#14141f` panel fill, `#5eead4` (`--accent`) for the correct path / primary nodes, `rgba(94,234,212,0.4)` for decision diamonds, `#f08080` (`--danger`) for bug / anti-pattern nodes, `#f0a868` (`--warn`) for fix / intervention nodes, `#82e0aa` (`--ok`) for the success path, `#e4e4e8` / `#9494a0` for text.
- Paste each into [Mermaid Live Editor](https://mermaid.live) to render. All use stable Mermaid syntax (`flowchart`, `subgraph`, dashed `-.label.->` links) supported in current Mermaid (v10.4+).
- For the slide deck (artifact 03), these are rendered as static captures from Mermaid Live, inlined into reveal.js.