TAI BUI
Back to curriculum
BuildPython

BERT — Masked Language Modeling

GPT predicts the next word. BERT predicts a missing one. That one-sentence difference gave us half a decade of embedding models.

TIME: ~45 minutesPREREQUISITES: Phase 7 · 05 (Full Transformer), Phase 5 · 02 (Text Representation)

Learning Objectives

  • Explain how masked language modeling trains a bidirectional encoder, and how that differs from next-token prediction.
  • Apply the 80/10/10 masking rule and explain why the random and unchanged slices exist.
  • Implement MLM masking with -100 labels, skipping special tokens, and check the resulting distribution.
  • Describe how a pretrained BERT encoder is adapted for classification, embeddings, and reranking.
  • Decide when an encoder such as ModernBERT is the right tool and when a decoder is.

The Problem

Before 2018, each NLP task (sentiment, NER, QA, entailment) trained its own model from scratch on its own labeled data. There was no pre-trained "understand English" checkpoint to fine-tune. ELMo (2018) showed you could pre-train contextual embeddings with a bidirectional LSTM. It helped but did not generalize.

BERT (Devlin et al. 2018) took a different route: train a transformer encoder on text from across the internet, force it to predict missing words from context on both sides, then fine-tune one head on your downstream task. The parameter efficiency was a revelation.

Within 18 months, BERT and its variants (RoBERTa, ALBERT, ELECTRA) dominated the major NLP leaderboards. By 2020, search engines, content moderation pipelines, and semantic-search systems had a BERT inside.

In 2026 encoder-only models are still the right tool for classification, retrieval, and structured extraction. They run 5–10× faster per token than decoders, and their embeddings are the backbone of modern retrieval stacks. ModernBERT (Dec 2024) pushed the architecture to 8K context with Flash Attention, RoPE, and GeGLU.

Warm-up0/3
1. In a transformer encoder with no causal mask, which positions can a given token attend to?Warm-up
A row of five tokens (the, cat, sat, on, mat) with the middle one highlighted as the token in question and a question mark above each of the other four positions.
Illustrative sentence. A ? marks a position whose visibility to the highlighted token is the question.
2. A static word2vec-style embedding gives "bank" a single vector. What limitation does that create?Warm-up
A three-row embedding table with one symbolic vector per word (cup, bank, tree), the bank row highlighted, and a dashed box asking what limitation follows.
Symbolic vector names, not real values.
3. A decoder-only model such as GPT is trained to predict the next token. What does its causal mask enforce?Warm-up
A row of four tokens (the, cat, sat, on) feeding a dashed next-token slot, the third token highlighted, a question mark over each other position, and a note that the causal mask is on.
Illustrative sentence.
Check: The Problem0/2
1. What did ELMo (2018) show, according to The Problem?Define
Three stages: before 2018 each task (sentiment, NER, QA, entailment) trained its own model from scratch; ELMo (2018) with a dashed box for what it showed; then BERT (2018).
Task list and years follow the lesson's The Problem section.
2. Per The Problem, how do encoder-only models compare with decoders per token in 2026?Define
Two horizontal bars: a solid decoder bar labelled 1x as the scale, and a dashed encoder-only track with a question mark and no measured length.
The decoder bar is the reference scale; the dashed encoder track has no measured length.

The Concept

Masked language modeling: pick tokens, mask them, predict originals

BERT is a transformer encoder pretrained with masked language modeling (MLM): hide some of the input tokens and train the model to recover the originals from the surrounding context. The encoder is bidirectional, so each prediction can draw on the words on both sides of the gap.

The training signal

Take a sentence: the quick brown fox jumps over the lazy dog.

Mask 15% of tokens randomly:

input:  the [MASK] brown fox jumps [MASK] the lazy dog
target: the  quick brown fox jumps  over  the lazy dog

Train the model to predict the original tokens at the masked positions. Predicting [MASK] at position 1 can use brown fox jumps at positions 2+. That is the thing GPT cannot do.

The BERT mask rules

Of the 15% of tokens selected for prediction:

  • 80% are replaced with [MASK].
  • 10% are replaced with a random token.
  • 10% are left unchanged.

Why not always [MASK]? Because [MASK] never appears at inference time, and training the model to expect it at 100% of masked positions would create a distribution shift between pretraining and fine-tuning. The 10% random and 10% unchanged keep the model honest.

Next Sentence Prediction (NSP), and why it was dropped

The original BERT also trained on NSP: given two sentences A and B, predict whether B follows A. RoBERTa (2019) ablated it and showed NSP hurt rather than helped. Modern encoders skip it.

What changed in 2026: ModernBERT

The 2024 ModernBERT paper rebuilt the block with current primitives:

ComponentOriginal BERT (2018)ModernBERT (2024)
PositionalLearned absoluteRoPE
ActivationGELUGeGLU
NormalizationLayerNormPre-norm RMSNorm
AttentionFull denseAlternating local (128) + global
Context length5128192
TokenizerWordPieceBPE

Unlike the 2018 stack, it is Flash-Attention-native. At sequence length 8K, inference is 2–3× faster than DeBERTa-v3, with better GLUE scores.

Use cases that still pick an encoder in 2026

TaskWhy encoder beats decoder
Retrieval / semantic search embeddingsBidirectional context = better embedding quality per token
Classification (sentiment, intent, toxicity)One forward pass; no generation overhead
NER / token labelingPer-position output, natively bidirectional
Zero-shot entailment (NLI)Classifier head on top of encoder
Reranker for RAGCross-encoder scoring, 10x faster than LLM rerankers
Check: The Concept0/13
1. What is masked language modeling?Define
A nine-token sentence with one position showing [MASK], feeding a BERT encoder box, with a dashed question mark under the masked position as the output.
Illustrative masking of the lesson's example sentence.
2. What makes a BERT encoder bidirectional?Define
Five tokens (the, [MASK], brown, fox, jumps) with the mask highlighted, a blue bracket under the word to its left and a green bracket under the three words to its right, and a dashed box asking what lets the gap use both sides.
Illustrative masked sentence from the lesson.
3. Of the 15% of tokens selected for MLM prediction, what split does BERT apply?Define
A row of 20 token squares with 3 highlighted as selected, an arrow to one selected token, and three dashed boxes for its possible outcomes, each with an unknown share.
Illustrative: 3 of 20 squares is the 15% selection rate.
4. What did RoBERTa's ablation conclude about Next Sentence Prediction (NSP)?Define
Left: the original BERT's two pretraining tasks, MLM and NSP. Right: RoBERTa (2019) ablating NSP, with a dashed box for its finding. Below: the layout of an NSP input as [CLS] A [SEP] B [SEP].
Task names and years are from the lesson text.
5. What is ModernBERT?Define
BERT (2018) followed by ModernBERT (Dec 2024) with a dashed what-is-it box, and the lesson's code that loads answerdotai/ModernBERT-base with AutoTokenizer and AutoModel.
Code lines copied from the lesson's Use It section, wrapped to fit.
6. Which component replaced learned absolute positional encoding in ModernBERT?Define
Two boxes: Original BERT (2018) with learned absolute positional encoding, an arrow, and ModernBERT (2024) with a dashed question mark for its positional component.
One row of the lesson's comparison table.
7. Why does BERT not replace every selected token with [MASK]?Explain
Two token rows: the original sentence the quick brown fox jumps over the lazy dog, and the training input where the selected tokens quick and over are replaced by [MASK], with a dashed box asking why BERT does not do this.
The lesson's example sentence.
8. Why can BERT use words to the right of a masked position when GPT cannot?Explain
Five tokens indexed 0 to 4 (the, [MASK], brown, fox, jumps) with the mask highlighted, a green bracket under positions 2 to 4, and a dashed box asking why BERT can use them and GPT cannot.
The lesson's training-signal example, shortened.
9. For `the [MASK] brown fox jumps [MASK] the lazy dog`, which original tokens does the loss target?Apply
The input the [MASK] brown fox jumps [MASK] the lazy dog with positions 0 to 8, and a dashed question box under every position for its loss target.
The stem's input sentence.
10. Someone applies a causal mask to a BERT encoder during MLM pretraining. What does the model lose?Analyze
Six tokens [CLS] the [MASK] brown fox [SEP] feeding a box labelled encoder plus causal mask applied, with an arrow from the masked position to a dashed box asking what is lost.
Illustrative tokens.
11. A model was pretrained with 100% of selected tokens replaced by [MASK], then fine-tuned on normal text and does poorly. What is the likeliest cause?Analyze
Two stages: pretraining where 15% of tokens are selected and every selected token becomes [MASK], then fine-tuning on normal text with a poor result, and a dashed box asking the likeliest cause.
Schematic of the stem.
12. You must classify documents of about 6,000 tokens with an encoder. Which backbone fits, and why?Design
A document of about 6,000 tokens flowing through a dashed backbone box to a class label.
Schematic of the stem.
13. A team must tag 2M support tickets by intent at low latency and also draft reply suggestions. Which split fits the lesson?Design
2M support tickets (low latency) feeding two tasks, tagging intent and drafting replies, each with a dashed box for which kind of model fits.
Schematic of the stem.

Build It

Step 1: masking logic

See code/main.py. create_mlm_batch takes a list of token IDs, a vocab size, and a mask probability. It returns input IDs (with masks applied) and labels, which hold the original token at masked positions and -100 everywhere else (PyTorch's ignore-index convention). Special tokens ([MASK], [CLS], [SEP]) are never selected, and a random replacement never picks a special ID or the original token itself. If you pass no rng, it creates a fresh random.Random().

python
def create_mlm_batch(tokens, vocab_size, mask_prob=0.15, rng=None):
    if rng is None:
        rng = random.Random()
    input_ids = list(tokens)
    labels = [IGNORE_INDEX] * len(tokens)
    for i, t in enumerate(tokens):
        if t in SPECIAL_IDS:
            continue
        if rng.random() >= mask_prob:
            continue
        labels[i] = t
        r = rng.random()
        if r < 0.8:
            input_ids[i] = MASK_ID
        elif r < 0.9:
            rand_id = t
            while rand_id in SPECIAL_IDS or rand_id == t:
                rand_id = rng.randrange(vocab_size)
            input_ids[i] = rand_id
        # else: keep original
    return input_ids, labels

Step 2: run MLM prediction on a tiny corpus

main.py ships the masking logic and a 20-entry toy vocabulary (three special tokens plus 17 words). It has no encoder and no MLM head, so there is no model to run yet. This step is what you build beyond it: a 2-layer encoder plus an MLM head over that vocabulary, fed with sentences you write yourself. There is no gradient step here, only forward-pass sanity checks. Full training needs PyTorch.

Step 3: compare mask types

main.py prints the 80/10/10 shares from distribution_check, but it has no model, so it cannot compare predictions. Once the Step 2 encoder exists, predict on an unmasked sentence and on a masked one. Both should produce reasonable token distributions, because the model saw both patterns in training. That is how you see the three-way rule keeping the model usable without [MASK].

Step 4: fine-tune head

Also beyond main.py: replace the MLM head with a classification head on a toy sentiment dataset. Only the head trains while the encoder stays frozen. This is the basic fine-tuning pattern behind BERT applications (full fine-tuning also updates the encoder, at a small learning rate).

Check: Build It0/11
1. What is the [CLS] token for?Define
The five-token sentence [CLS] the dog sat [SEP] with toy-vocabulary ids 1, 3, 10, 17, 2 under the tokens, the [CLS] token highlighted and a dashed box asking its role.
Ids are the toy vocabulary's in main.py.
2. What does fine-tuning mean for a pretrained BERT?Define
A pretrained BERT encoder box and a downstream task data box (for example a sentiment dataset) joined by an arrow, with a dashed box below it labelled fine-tuning.
Illustrative; the sentiment dataset is an example task.
3. What is the unit of the selection draw and the 80/10/10 draw in `whole_word_mlm`?Explain
The eleven tokens [CLS] the quick brown fox jumps over the lazy dog [SEP] with underlines marking nine word spans from main.py's demo (quick brown and lazy dog share spans of two), and a dashed box asking the unit of the two draws.
Spans copied from main.py's whole-word demo.
4. Why do the labels hold -100 at positions that were not selected?Explain
A table from main.py's masking demo: eleven positions with their word, input id and label; some labels are token ids and others are -100.
Captured by running the lesson's main.py logic (toy vocabulary, seed 42).
5. Why can fine-tuning swap the MLM head for a classification head and train only that head?Explain
Two stacks: the encoder under an MLM head for pretraining, and the same encoder under a classification head for fine-tuning with only the head trained, with a dashed box asking why a small head is enough.
Schematic of the stem's setup.
6. In `create_mlm_batch`, `dog` is a non-special token, and `rng.random()` returns 0.30 for the selection draw with `mask_prob=0.15`. What happens to its input ID and label?Apply
The call create_mlm_batch with mask_prob=0.15, the token dog, and a first rng.random() draw of 0.30, with dashed boxes for the resulting input_ids[i] and labels[i].
Values are the stem's.
7. `dog` is selected, and the second draw `r` is 0.85. What do `input_ids` and `labels` hold at that position?Apply
A selected token dog with a second draw of r = 0.85 and dashed boxes for the resulting input_ids[i] and labels[i].
Values are the stem's.
8. `dog` is selected, and the second draw `r` is 0.95. What do `input_ids` and `labels` hold at that position?Apply
A selected token dog with a second draw of r = 0.95 and dashed boxes for the resulting input_ids[i] and labels[i].
Values are the stem's.
9. About how many of 100,000 non-special tokens end up replaced by a random token with `mask_prob=0.15` and the 80/10/10 rule?Apply
A three-stage flow: 100,000 non-special tokens, selection at mask_prob 0.15, then the 80/10/10 split, with dashed boxes for the selected count and the random-replacement count.
Values are the stem's.
10. You call `create_mlm_batch` on `[CLS] the dog sat [SEP]` with `mask_prob=1.0`. What happens to `[CLS]` and `[SEP]`?Apply
A table of the five positions of [CLS] the dog sat [SEP] with unknown input and label after calling create_mlm_batch with mask_prob=1.0.
Setup from the stem.
11. A developer deletes `or rand_id == t` from the `while` condition in the random branch of `create_mlm_batch`. What goes wrong?Analyze
The random branch of create_mlm_batch with the while condition shortened to rand_id in SPECIAL_IDS, the deleted clause or rand_id == t noted below, and a dashed box asking what goes wrong.
Code copied from main.py with the clause removed as in the stem.

Use It

python
from transformers import AutoModel, AutoTokenizer
 
tok = AutoTokenizer.from_pretrained("answerdotai/ModernBERT-base")
model = AutoModel.from_pretrained("answerdotai/ModernBERT-base")
 
text = "Attention is all you need."
inputs = tok(text, return_tensors="pt")
out = model(**inputs).last_hidden_state   # (1, N, 768)

Embedding models are fine-tuned BERT. sentence-transformers models like all-MiniLM-L6-v2 are BERTs trained with contrastive loss. Same encoder, different loss.

Cross-encoder rerankers are fine-tuned BERT too. They do pair classification on [CLS] query [SEP] doc [SEP]. The bidirectional attention between query and doc is what gives cross-encoders their quality edge over biencoders.

When not to pick BERT in 2026. Anything generative: the encoder has no sensible way to produce tokens autoregressively. Also skip it for anything under 1B params where a small decoder can match quality with more flexibility (Phi-3-Mini, Qwen2-1.5B).

Check: Use It0/7
1. What does the [SEP] token do?Define
A table of three special tokens with ids from main.py: [MASK] 0, [CLS] 1, [SEP] 2, each with an unknown role marked ?, the [SEP] row highlighted.
Ids from main.py.
2. What is a cross-encoder?Define
A search pipeline: query, retriever, candidate documents, a highlighted cross-encoder box with a dashed how-box below it, then a ranked list.
Pipeline stages are generic.
3. Why do cross-encoder rerankers often beat biencoders on relevance scoring?Explain
A query-plus-document pair feeding two scorers, a highlighted cross-encoder reranker and a biencoder, each producing a relevance score, with a note asking why the cross-encoder has the quality edge.
Schematic; internals of both scorers are not drawn.
4. How do sentence-transformers models like all-MiniLM-L6-v2 relate to BERT?Explain
A sentence feeding the all-MiniLM-L6-v2 sentence-transformers model, producing an embedding vector, with a dashed box asking how the model relates to BERT.
Schematic; the model's internals are not drawn.
5. When does Use It say you should not pick BERT in 2026?Explain
Input text feeding a BERT-style encoder box that leads to a dashed box asking for the task where it should not be picked.
Schematic.
6. `model(**inputs).last_hidden_state` runs on a sentence that tokenizes to N tokens. What shape does it return?Apply
Three lines of the lesson's Use It code that tokenize a sentence and run the model to get last_hidden_state, with a dashed box asking for out.shape.
The shape comment from the lesson's code is left out.
7. A RAG pipeline needs a reranking stage that scores each candidate passage against the query, faster than an LLM reranker. Which design fits?Design
A RAG pipeline: query, retrieve candidate passages, a dashed rerank stage, then an LLM answer, with a note that the rerank stage scores each passage against the query faster than an LLM reranker.
Schematic of the stem.

Ship It

See outputs/skill-bert-finetuner.md. The skill scopes a BERT fine-tune for a new classification or extraction task: backbone choice, head spec, data, eval, and stopping.

Check: Ship It0/1
1. What does the `bert-finetuner` skill from Ship It scope for a new task?Define
A file card for outputs/skill-bert-finetuner.md, described as scoping a BERT fine-tune for a new classification or extraction task, with an arrow to a dashed box asking what it scopes.
File name and wording are from the lesson's Ship It section.
Author's Note

I'd run Exercise 1 before anything else. The 80/10/10 rule reads like a footnote until you run main.py, which calls distribution_check over 100,000 random tokens and prints the selected, [MASK], random, and unchanged shares next to their targets. Watching the random and unchanged slices sit there as deliberate fractions is a faster way to understand why they exist than rereading the distribution-shift argument.

Be careful with the demos, though. toy_predict in main.py returns a uniform distribution, and the script never calls it, so main.py exercises the masking logic and nothing else. The masked-versus-unmasked comparison in Step 3 only means something once a real encoder is behind it. Before you start Exercise 2, read whole_word_mlm in the same file. It makes both the selection draw and the 80/10/10 draw once per word span instead of once per token.

My bet is that the lasting part of BERT is the pattern, not the masking trick: pretrain one encoder, then swap the head or the loss. Embedding models and cross-encoder rerankers both follow it. I'd start from an encoder for classification, retrieval, and reranking, and from a decoder for anything generative.

Exercises

  1. Easy. Run code/main.py and print the mask distribution across 100,000 tokens. Confirm ~15% are selected, and that ~80% of those become [MASK].
  2. Medium. Extend whole_word_mlm, which already masks all subwords of a selected word together or none: run a distribution check over its spans like distribution_check does, then (with an encoder from Step 2) measure whether whole-word masking improves MLM accuracy on a 500-sentence corpus.
  3. Hard. Train a tiny (2-layer, d=64) BERT on 10,000 sentences from a public dataset. Fine-tune the [CLS] token for SST-2 sentiment. Compare against a decoder-only baseline at matched params. Which wins?

Key Terms

TermWhat people sayWhat it actually means
MLM"Masked language modeling"Training signal: randomly replace 15% of tokens with [MASK], predict the originals.
Bidirectional"Looks both ways"Encoder attention has no causal mask, so each position sees every other position.
[CLS]"The pooler token"A special token prepended to a sequence. Its final embedding is used as the sentence-level representation.
[SEP]"Segment separator"Separates paired sequences (e.g. query/doc, sentence A/B).
NSP"Next sentence prediction"BERT's second pretraining task. RoBERTa showed it was not helping, and it was dropped after 2019.
Fine-tuning"Adapt to a task"Train a small head on top of the pretrained encoder for the downstream task, with the encoder frozen or only lightly updated.
Cross-encoder"A reranker"A BERT that takes both query and doc as input and outputs a relevance score.
ModernBERT"2024 refresh"Encoder rebuilt with RoPE, RMSNorm, GeGLU, alternating local/global attention, 8K context.

Further Reading

Code

Ships With