nlp — tokenize the text, pick the model type, pick the metric
You own the language-modeling discipline: how raw text becomes tokens, which transformer
architecture fits a task, and which metric actually tells you whether it worked. When the
question is "which tokenizer," "BERT or GPT or T5 for this," "why does my Catalan text cost 3×
the tokens," or "is this BLEU score meaningful," this is the skill. You stop at retrieval, the
RAG loop, prompt wording, and the training step itself — those route out (below).
Route out first (loud — do not duplicate these)
Decision: model type per task (get this right before anything else)
Pick the architecture from the task's shape, not from what is trendy. A decoder LLM can
technically classify, but a fine-tuned encoder is smaller, faster, cheaper, and usually more
accurate on a fixed-label task.
| Task shape |
Architecture |
Why |
Example families* |
| Understand / label a whole input (classification, NER, extractive QA, similarity) |
Encoder (bidirectional) |
Attends to the full sentence both directions; cheap to fine-tune and to serve |
BERT, RoBERTa, DistilBERT, ModernBERT |
| Free-form generation, chat, few-shot |
Decoder (autoregressive) |
Attends only to prior tokens; predicts the next token |
GPT-style, Llama, Gemma, Qwen |
| Transform input → new text (summarize, translate, generative QA) |
Encoder-decoder / seq2seq |
Encoder reads all of the source, decoder writes conditioned on it |
T5 / FLAN-T5, BART, mT5 |
* Architecture families are stable; specific checkpoints and their licenses are not — check the
HF model card before you commit (licenses change; see open-weights). ModernBERT (2024) is a
current long-context encoder; verify the latest at author time.
The two most common own-goals: reaching for a 7B decoder to do sentiment on 5 classes (an
encoder does it for a fraction of the cost), and forcing an encoder to generate (it cannot —
it has no decoder).
1. Tokenization
Every downstream number depends on this step, and its failures are silent. The single
load-bearing rule:
Load the tokenizer that shipped with the checkpoint, and use the same one at train and
inference. AutoTokenizer.from_pretrained(same_checkpoint). A train/inference tokenizer
mismatch — different vocab, different special tokens, different casing/normalization — maps
text to token ids the model never saw and corrupts everything downstream with no error.
from transformers import AutoTokenizer # transformers current major ~v5 (verify at author time)
tok = AutoTokenizer.from_pretrained("bert-base-cased")
enc = tok("Tokenizers matter.", return_offsets_mapping=True)
tok.convert_ids_to_tokens(enc["input_ids"])
# ['[CLS]', 'Token', '##izers', 'matter', '.', '[SEP]'] — note WordPiece '##' continuation + added specials
The four algorithms (full mechanics in references/tokenization.md):
| Algorithm |
Builds vocab by… |
Applies by… |
Used by |
| BPE |
merging the most frequent adjacent pair, repeatedly |
split to chars, replay learned merges |
GPT-2 (byte-level), many |
| WordPiece |
merging pairs that maximize a likelihood score |
longest-match subword from the front (## continuations) |
BERT family |
| Unigram (SentencePiece) |
start large, remove tokens that least hurt corpus likelihood |
most-probable segmentation |
T5, ALBERT, mT5 |
| Byte-level BPE |
BPE over the 256 raw bytes, not Unicode chars |
same as BPE on bytes |
GPT-2, RoBERTa |
- Byte-level BPE has no
[UNK]. Base vocab is exactly 256 (all byte values), so every
emoji, accent, and script maps to some byte sequence — nothing falls out as unknown
(verified: HF NLP course ch.6). WordPiece/word-level tokenizers do have [UNK] and lose OOV
content.
- SentencePiece is reversible — it treats space as a normal symbol (the
▁ meta-symbol), so
decode(encode(x)) == x without language-specific detokenization rules. That is why it
dominates multilingual models.
Special tokens are not decoration. [CLS]/<s> carries the pooled sentence
representation for classification; [SEP]/</s> marks segment/end; [PAD] fills a batch (and
must be masked out via attention_mask); [MASK] is the MLM target; [UNK] is the fallback.
Names differ by model ([CLS] in BERT vs <s> in RoBERTa) — another reason to never hand-roll
the tokenizer.
Why it matters — three concrete costs:
- $ cost & context. Token count is the bill and the context budget. Fewer tokens per
sentence = cheaper calls and more room in the window.
- OOV / information loss. A tokenizer that emits
[UNK] throws away content it can't
represent; byte-level/SentencePiece degrade gracefully instead.
- Fairness. Vocab trained mostly on English fragments other scripts far harder — the same
meaning costs more tokens, more money, and more latency (section 5).
2. Tasks
Text classification (encoder + classification head)
from transformers import pipeline
clf = pipeline("text-classification", model="distilbert-base-uncased-finetuned-sst-2-english")
clf("The service was slow but the food was incredible.")
# [{'label': 'POSITIVE', 'score': 0.99...}]
Metric: accuracy on balanced data; macro-F1 the moment classes are imbalanced (accuracy
lies when 95% of rows are one class).
Token classification / NER (encoder, per-token labels)
Labels are B-/I-/O spans aligned to subword tokens: the first subword of a word gets the
label, continuation subwords and special tokens get -100 (ignored by the loss). Evaluate with
seqeval at the entity level, never per-token accuracy (per-token accuracy is inflated by
the flood of O tokens).
from transformers import pipeline
ner = pipeline("token-classification", aggregation_strategy="simple")
ner("Ada Lovelace worked in London.")
# groups subwords back into entities: PER 'Ada Lovelace', LOC 'London'
Seq2seq — summarization / translation (encoder-decoder)
summ = pipeline("summarization", model="facebook/bart-large-cnn")
summ(long_article, max_length=130, min_length=30)
Metric: ROUGE for summarization, BLEU/chrF for translation — with the heavy caveat in
section 4.
Sentence embeddings (SBERT — the task, not retrieval)
from sentence_transformers import SentenceTransformer # sentence-transformers ~v5 (verify)
model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
emb = model.encode(["The weather is lovely today.", "It's so sunny outside!"])
model.similarity(emb, emb) # semantic textual similarity / clustering / paraphrase mining
Producing/judging embeddings for retrieval (model choice, chunking, recall@k, rerankers) is
embeddings-search, not here.
3. Evaluation — pick the metric that matches the task
| Task |
Primary metric |
Catches |
Trap |
| Classification |
accuracy + macro-F1 |
wrong labels |
accuracy hides minority-class failure |
| NER / token |
entity-level F1 (seqeval) |
missed/partial spans |
per-token accuracy is inflated by O |
| Translation |
BLEU / chrF |
n-gram overlap w/ reference |
weak on meaning; chrF better for morphology |
| Summarization |
ROUGE (1/2/L) |
recall of reference n-grams |
rewards copying; blind to faithfulness |
| Generation (LM) |
perplexity |
how well the model predicts held-out text |
tokenizer-dependent — not comparable across tokenizers |
| Open-ended / chat |
LLM-as-judge + human |
quality overlap metrics miss |
judge bias (position, verbosity, self-preference) |
The caveat that governs this whole section: BLEU, ROUGE, and chrF are n-gram/character
overlap metrics and correlate weakly with human judgment on open-ended and creative
generation — they reward matching the reference's exact phrasing, so a correct paraphrase
scores low and a fluent-but-wrong copy scores high (well documented; e.g. the summarization and
MT-evaluation literature). Use them for regression tracking on a fixed reference set, never
as the final verdict on quality. For open-ended output, use an LLM-as-judge rubric plus a
human spot-check — and know the judge has its own biases (position, verbosity, self-preference),
so pin the rubric and randomize order.
Perplexity = exp(mean token NLL): lower means the model predicts held-out text better. It is
tokenizer-dependent, so two models with different tokenizers have non-comparable perplexities
— only compare within the same tokenizer/vocab. Runnable snippets for seqeval, sacrebleu, ROUGE,
perplexity, and an LLM-judge harness are in references/evaluation.md.
4. Multilingual pitfalls
The English-centric trap: a tokenizer whose vocab was learned mostly on English over-fragments
other scripts. The same sentence in Ukrainian, Arabic, Hindi, or even accented Catalan can take
2–15× more tokens than its English equivalent (Petrov et al., Language Model Tokenizers
Introduce Unfairness Between Languages, NeurIPS 2023). That "fertility" (tokens per word)
inflation is a triple tax:
- Money — more tokens per identical meaning = a proportionally larger bill for the same work.
- Context — over-fragmented text eats the window faster, so fewer few-shot examples fit and
long documents truncate sooner.
- Quality — sequences fragmented into byte-shards are harder to model, degrading accuracy for
exactly the users the tool already serves worst.
Mitigations: prefer a multilingual tokenizer/model (mT5, XLM-R, a SentencePiece-based model)
whose vocab actually covers your languages; measure fertility on your own corpus (tokens per
word, per language) before you commit; and don't benchmark cost or latency only on English.
Guardrails / gotchas
- Tokenizer must match the checkpoint, at train and inference. Mismatch corrupts silently,
no error. The most expensive bug in this skill.
- Encoders can't generate; don't classify with a giant decoder by default. Match architecture
to task shape.
- BLEU/ROUGE/chrF ≠ quality on open-ended text. Overlap metrics; weak human correlation. Track
regressions with them; judge quality with an LLM-judge + human.
- Entity-F1 (seqeval), not token accuracy, for NER.
O tokens inflate accuracy toward 1.0.
- macro-F1, not accuracy, on imbalanced classes.
- Perplexity is tokenizer-relative — never compare it across different tokenizers.
- Don't benchmark tokenization/cost only in English — fertility varies 2–15× across scripts.
- Never assert a model's license from memory — check the current model card; license classes
shift (Llama = Meta Community license, not OSI-open; Gemma = custom terms; etc.).
Related skills
embeddings-search — retrieval embeddings, chunking, recall@k,
reranking. NLP owns making/judging sentence embeddings as a task; using them to search is theirs.
rag — the full retrieve→generate answer loop and groundedness.
prompt-engineering — the wording of the prompt.
finetuning + deep-learning — actually
training/adapting the network (this skill picks the type and metric; those move the weights).
training-data — building the labeled corpus you train on.
Checklist
References
references/tokenization.md — BPE/WordPiece/Unigram/byte-level training mechanics, special
tokens per family, offset mapping, and a fertility-measuring snippet.
references/evaluation.md — runnable seqeval, sacrebleu (BLEU/chrF), ROUGE, perplexity, and an
LLM-as-judge rubric, with when each lies.
1---2name: nlp3description: Use when choosing how to tokenize text or which transformer type fits an NLP task, when a tokenizer over-fragments non-English text or inflates token cost, when picking a language metric, or when classification, NER or summarization output looks wrong and it is unclear whether the tokenizer, the architecture or the metric is at fault. Covers subword tokenizers, encoder versus decoder versus encoder-decoder choice, sentence embeddings, and the metric families. NOT retrieval or vector search (that is `embeddings-search`), NOT the RAG loop (that is `rag`), NOT prompt wording (that is `prompt-engineering`), NOT training the network (that is `finetuning`).4---56# nlp — tokenize the text, pick the model type, pick the metric78You own the **language-modeling discipline**: how raw text becomes tokens, which transformer9architecture fits a task, and which metric actually tells you whether it worked. When the10question is "which tokenizer," "BERT or GPT or T5 for this," "why does my Catalan text cost 3×11the tokens," or "is this BLEU score meaningful," this is the skill. You stop at retrieval, the12RAG loop, prompt wording, and the training step itself — those route out (below).1314## Route out first (loud — do not duplicate these)1516- **Retrieval embeddings + vector search** (which embedding model, chunking, recall@k, rerank)17 → [`../embeddings-search/SKILL.md`](../embeddings-search/SKILL.md). Sentence embeddings *live18 here* as a task; using them to *retrieve* is theirs.19- **The retrieve → prompt → generate → answer loop** and groundedness → [`../rag/SKILL.md`](../rag/SKILL.md).20- **Prompt wording / few-shot / system prompts** → [`../prompt-engineering/SKILL.md`](../prompt-engineering/SKILL.md).21- **Training the network** (LoRA/SFT, trainer loop, PyTorch) → [`../finetuning/SKILL.md`](../finetuning/SKILL.md)22 + [`../deep-learning/SKILL.md`](../deep-learning/SKILL.md).23- **The training corpus itself** (JSONL messages, label sets) → [`../training-data/SKILL.md`](../training-data/SKILL.md).2425## Decision: model type per task (get this right before anything else)2627Pick the architecture from the task's *shape*, not from what is trendy. A decoder LLM can28technically classify, but a fine-tuned encoder is smaller, faster, cheaper, and usually more29accurate on a fixed-label task.3031| Task shape | Architecture | Why | Example families* |32|---|---|---|---|33| Understand / label a whole input (classification, NER, extractive QA, similarity) | **Encoder** (bidirectional) | Attends to the full sentence both directions; cheap to fine-tune and to serve | BERT, RoBERTa, DistilBERT, ModernBERT |34| Free-form generation, chat, few-shot | **Decoder** (autoregressive) | Attends only to prior tokens; predicts the next token | GPT-style, Llama, Gemma, Qwen |35| Transform input → new text (summarize, translate, generative QA) | **Encoder-decoder / seq2seq** | Encoder reads all of the source, decoder writes conditioned on it | T5 / FLAN-T5, BART, mT5 |3637\* Architecture families are stable; specific checkpoints and their licenses are not — check the38HF model card before you commit (licenses change; see `open-weights`). ModernBERT (2024) is a39current long-context encoder; verify the latest at author time.4041The two most common own-goals: reaching for a 7B decoder to do sentiment on 5 classes (an42encoder does it for a fraction of the cost), and forcing an encoder to *generate* (it cannot —43it has no decoder).4445## 1. Tokenization4647Every downstream number depends on this step, and its failures are **silent**. The single48load-bearing rule:4950> **Load the tokenizer that shipped with the checkpoint, and use the same one at train and51> inference.** `AutoTokenizer.from_pretrained(same_checkpoint)`. A train/inference tokenizer52> mismatch — different vocab, different special tokens, different casing/normalization — maps53> text to token ids the model never saw and corrupts everything downstream with no error.5455```python56from transformers import AutoTokenizer # transformers current major ~v5 (verify at author time)5758tok = AutoTokenizer.from_pretrained("bert-base-cased")59enc = tok("Tokenizers matter.", return_offsets_mapping=True)60tok.convert_ids_to_tokens(enc["input_ids"])61# ['[CLS]', 'Token', '##izers', 'matter', '.', '[SEP]'] — note WordPiece '##' continuation + added specials62```6364**The four algorithms** (full mechanics in `references/tokenization.md`):6566| Algorithm | Builds vocab by… | Applies by… | Used by |67|---|---|---|---|68| **BPE** | merging the most frequent adjacent pair, repeatedly | split to chars, replay learned merges | GPT-2 (byte-level), many |69| **WordPiece** | merging pairs that maximize a likelihood score | longest-match subword from the front (`##` continuations) | BERT family |70| **Unigram** (SentencePiece) | start large, *remove* tokens that least hurt corpus likelihood | most-probable segmentation | T5, ALBERT, mT5 |71| **Byte-level BPE** | BPE over the 256 raw **bytes**, not Unicode chars | same as BPE on bytes | GPT-2, RoBERTa |7273- **Byte-level BPE has no `[UNK]`.** Base vocab is exactly 256 (all byte values), so every74 emoji, accent, and script maps to *some* byte sequence — nothing falls out as unknown75 (verified: HF NLP course ch.6). WordPiece/word-level tokenizers *do* have `[UNK]` and lose OOV76 content.77- **SentencePiece is reversible** — it treats space as a normal symbol (the `▁` meta-symbol), so78 `decode(encode(x)) == x` without language-specific detokenization rules. That is why it79 dominates multilingual models.8081**Special tokens are not decoration.** `[CLS]`/`<s>` carries the pooled sentence82representation for classification; `[SEP]`/`</s>` marks segment/end; `[PAD]` fills a batch (and83must be masked out via `attention_mask`); `[MASK]` is the MLM target; `[UNK]` is the fallback.84Names differ by model (`[CLS]` in BERT vs `<s>` in RoBERTa) — another reason to never hand-roll85the tokenizer.8687**Why it matters — three concrete costs:**8889- **$ cost & context.** Token count *is* the bill and the context budget. Fewer tokens per90 sentence = cheaper calls and more room in the window.91- **OOV / information loss.** A tokenizer that emits `[UNK]` throws away content it can't92 represent; byte-level/SentencePiece degrade gracefully instead.93- **Fairness.** Vocab trained mostly on English fragments other scripts far harder — the same94 meaning costs more tokens, more money, and more latency (section 5).9596## 2. Tasks9798### Text classification (encoder + classification head)99```python100from transformers import pipeline101clf = pipeline("text-classification", model="distilbert-base-uncased-finetuned-sst-2-english")102clf("The service was slow but the food was incredible.")103# [{'label': 'POSITIVE', 'score': 0.99...}]104```105Metric: accuracy on balanced data; **macro-F1** the moment classes are imbalanced (accuracy106lies when 95% of rows are one class).107108### Token classification / NER (encoder, per-token labels)109Labels are **B-/I-/O** spans aligned to subword tokens: the first subword of a word gets the110label, continuation subwords and special tokens get `-100` (ignored by the loss). Evaluate with111**seqeval** at the **entity** level, never per-token accuracy (per-token accuracy is inflated by112the flood of `O` tokens).113```python114from transformers import pipeline115ner = pipeline("token-classification", aggregation_strategy="simple")116ner("Ada Lovelace worked in London.")117# groups subwords back into entities: PER 'Ada Lovelace', LOC 'London'118```119120### Seq2seq — summarization / translation (encoder-decoder)121```python122summ = pipeline("summarization", model="facebook/bart-large-cnn")123summ(long_article, max_length=130, min_length=30)124```125Metric: **ROUGE** for summarization, **BLEU/chrF** for translation — with the heavy caveat in126section 4.127128### Sentence embeddings (SBERT — the *task*, not retrieval)129```python130from sentence_transformers import SentenceTransformer # sentence-transformers ~v5 (verify)131model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")132emb = model.encode(["The weather is lovely today.", "It's so sunny outside!"])133model.similarity(emb, emb) # semantic textual similarity / clustering / paraphrase mining134```135Producing/judging embeddings **for retrieval** (model choice, chunking, recall@k, rerankers) is136[`embeddings-search`](../embeddings-search/SKILL.md), not here.137138## 3. Evaluation — pick the metric that matches the task139140| Task | Primary metric | Catches | Trap |141|---|---|---|---|142| Classification | accuracy + **macro-F1** | wrong labels | accuracy hides minority-class failure |143| NER / token | **entity-level F1** (seqeval) | missed/partial spans | per-token accuracy is inflated by `O` |144| Translation | **BLEU / chrF** | n-gram overlap w/ reference | weak on meaning; chrF better for morphology |145| Summarization | **ROUGE** (1/2/L) | recall of reference n-grams | rewards copying; blind to faithfulness |146| Generation (LM) | **perplexity** | how well the model predicts held-out text | tokenizer-dependent — not comparable across tokenizers |147| Open-ended / chat | **LLM-as-judge** + human | quality overlap metrics miss | judge bias (position, verbosity, self-preference) |148149**The caveat that governs this whole section:** BLEU, ROUGE, and chrF are n-gram/character150overlap metrics and **correlate weakly with human judgment on open-ended and creative151generation** — they reward matching the reference's exact phrasing, so a correct paraphrase152scores low and a fluent-but-wrong copy scores high (well documented; e.g. the summarization and153MT-evaluation literature). Use them for **regression tracking on a fixed reference set**, never154as the final verdict on quality. For open-ended output, use an **LLM-as-judge rubric plus a155human spot-check** — and know the judge has its own biases (position, verbosity, self-preference),156so pin the rubric and randomize order.157158**Perplexity** = exp(mean token NLL): lower means the model predicts held-out text better. It is159**tokenizer-dependent**, so two models with different tokenizers have non-comparable perplexities160— only compare within the same tokenizer/vocab. Runnable snippets for seqeval, sacrebleu, ROUGE,161perplexity, and an LLM-judge harness are in `references/evaluation.md`.162163## 4. Multilingual pitfalls164165The English-centric trap: a tokenizer whose vocab was learned mostly on English **over-fragments166other scripts**. The same sentence in Ukrainian, Arabic, Hindi, or even accented Catalan can take167**2–15× more tokens** than its English equivalent (Petrov et al., *Language Model Tokenizers168Introduce Unfairness Between Languages*, NeurIPS 2023). That "fertility" (tokens per word)169inflation is a triple tax:170171- **Money** — more tokens per identical meaning = a proportionally larger bill for the same work.172- **Context** — over-fragmented text eats the window faster, so fewer few-shot examples fit and173 long documents truncate sooner.174- **Quality** — sequences fragmented into byte-shards are harder to model, degrading accuracy for175 exactly the users the tool already serves worst.176177Mitigations: prefer a **multilingual tokenizer/model** (mT5, XLM-R, a SentencePiece-based model)178whose vocab actually covers your languages; **measure fertility** on your own corpus (tokens per179word, per language) before you commit; and don't benchmark cost or latency only on English.180181## Guardrails / gotchas182183- **Tokenizer must match the checkpoint, at train and inference.** Mismatch corrupts silently,184 no error. The most expensive bug in this skill.185- **Encoders can't generate; don't classify with a giant decoder by default.** Match architecture186 to task shape.187- **BLEU/ROUGE/chrF ≠ quality on open-ended text.** Overlap metrics; weak human correlation. Track188 regressions with them; judge quality with an LLM-judge + human.189- **Entity-F1 (seqeval), not token accuracy, for NER.** `O` tokens inflate accuracy toward 1.0.190- **macro-F1, not accuracy, on imbalanced classes.**191- **Perplexity is tokenizer-relative** — never compare it across different tokenizers.192- **Don't benchmark tokenization/cost only in English** — fertility varies 2–15× across scripts.193- **Never assert a model's license from memory** — check the current model card; license classes194 shift (Llama = Meta Community license, not OSI-open; Gemma = custom terms; etc.).195196## Related skills197198- [`embeddings-search`](../embeddings-search/SKILL.md) — retrieval embeddings, chunking, recall@k,199 reranking. NLP owns *making/judging* sentence embeddings as a task; using them to search is theirs.200- [`rag`](../rag/SKILL.md) — the full retrieve→generate answer loop and groundedness.201- [`prompt-engineering`](../prompt-engineering/SKILL.md) — the wording of the prompt.202- [`finetuning`](../finetuning/SKILL.md) + [`deep-learning`](../deep-learning/SKILL.md) — actually203 training/adapting the network (this skill picks the type and metric; those move the weights).204- [`training-data`](../training-data/SKILL.md) — building the labeled corpus you train on.205206## Checklist207208- [ ] Architecture picked from task shape (encoder / decoder / enc-dec), not habit.209- [ ] Tokenizer loaded from the *same* checkpoint, used identically at train and inference.210- [ ] Tokenizer choice justified vs OOV, cost, and — if multilingual — measured fertility.211- [ ] Special tokens and `attention_mask` handled (padding masked, `-100` on ignored labels).212- [ ] Metric matches the task: macro-F1 (imbalanced), entity-F1/seqeval (NER), ROUGE/BLEU/chrF213 only as a regression signal, LLM-judge + human for open-ended.214- [ ] Perplexity compared only within one tokenizer.215- [ ] Retrieval / RAG / prompt / training concerns routed to the sibling skill, not re-solved here.216217## References218219- `references/tokenization.md` — BPE/WordPiece/Unigram/byte-level training mechanics, special220 tokens per family, offset mapping, and a fertility-measuring snippet.221- `references/evaluation.md` — runnable seqeval, sacrebleu (BLEU/chrF), ROUGE, perplexity, and an222 LLM-as-judge rubric, with when each lies.