Lesson 93: BERT Variants — XLNet, RoBERTa, T5, DeBERTa, ALBERT

USAAIO Lesson 93, from Phase 3. It explains why bidirectional BERT is slow at inference and how each variant fixes a different limitation: XLNet's permutation language modeling, with two-stream attention and separate content and query masks; RoBERTa's dynamic masking, dropped NSP, and larger batches; T5's unified text-to-text seq2seq framing; DeBERTa's disentangled content and position attention; and ALBERT's cross-layer parameter sharing, whose six-fold reduction is verified in PyTorch at d=64. All the parameter counts, mask tables, and shape traces were verified with torch 2.7.1+cpu. The lesson runs to 31 slides.

Subject: Machine Learning · 56 slides · code lesson

Open the interactive version of this deck · Homework for this lesson

What this lesson covers

The lesson, slide by slide

1. BERT Variants XLNet · RoBERTa · T5 · DeBERTa · ALBERT

Title

USAAIO · Lesson 93 · Phase 3

Five papers, five distinct fixes to BERT's limitations: inference speed, training signal efficiency, task framing, position attention, and parameter count. Build each core mechanism in PyTorch and understand when to prefer which.

2. By the end of this lesson you can

Objectives

  1. State BERT's core inference limitation and trace why bidirectionality causes it
  2. Explain XLNet's permutation LM: two-stream attention, content mask vs query mask, and why it avoids the [MASK] token entirely
  3. List the three training changes in RoBERTa and explain why dynamic masking captures more signal than static masking
  4. Describe the T5 text-to-text framing and write the input format for classification, QA, and summarization
  5. Contrast DeBERTa disentangled attention (content+position queries/keys) with standard attention
  6. Implement ALBERT cross-layer parameter sharing in PyTorch and verify the parameter reduction factor

3. What survived from Vision Transformer (ViT)?

Warm-up

Discussion prompt

Before we open Lesson 93: BERT Variants — XLNet, RoBERTa, T5, DeBERTa, ALBERT: without looking back, what was the main idea of Vision Transformer (ViT), and what could you do by the end of it that you could not do before?

Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.

Answer:

patch embedding via nn.Conv2d (stride=P), CLS token, learned positional embeddings, standard transformer encoder applied to patch sequences, classification head on CLS token, fine-tuning vs scratch, and ViT vs CNN scaling behavior.

4. BERT's limitation and why it matters

Section

Part 1 of 5

5. BERT's bidirectionality bottleneck

Concept

BERT (Lesson 90) is bidirectional: the [MASK] training objective lets every token attend to every other token, giving rich contextual representations. But this design has a hidden cost at inference time.

modelgenerationbidirectional ctx[MASK] artifact
GPT-2 (causal LM)yesno (left only)none
BERT (MLM)noyes (full)yes — train/test gap
XLNet (perm LM)yesyes (via perm)none
T5 (seq2seq)yesencoder onlynone

6. Which is which, by generation

Discrimination

Sort into buckets

Sort these by generation, from memory, without looking back at BERT's bidirectionality bottleneck. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.

yes
GPT-2 (causal LM); XLNet (perm LM); T5 (seq2seq)
no
BERT (MLM)
g1
generation is "yes" for GPT-2 (causal LM), XLNet (perm LM), T5 (seq2seq) — that is what the table on "BERT's bidirectionality bottleneck" records, and it is the single property separating this group from the rest.
g2
generation is "no" for BERT (MLM) — that is what the table on "BERT's bidirectionality bottleneck" records, and it is the single property separating this group from the rest.

7. XLNet — permutation language model

Section

Part 2 of 5

8. XLNet: predict tokens in a random order

Concept

XLNet trains an autoregressive LM (no [MASK]) but over all permutations of the token order. Predicting token at position t in a permutation means the model has seen all other positions already — capturing bidirectional context without the mask artifact.

\[ \max_\theta \; \mathbb{E}_{\mathbf{z}\sim\mathcal{Z}_T}\left[\sum_{t=1}^{T} \log p_\theta(x_{z_t}\mid x_{\mathbf{z}_{<t}})\right] \]

Here z is a permutation of positions 1..T. The expectation is over all permutations — the model must predict each token from the tokens that appear before it in the permutation order, not the original left-to-right order.

9. Guess the shape of the answer: Two-stream attention — content vs query masks

Estimation

Predict first

XLNet needs two attention streams because the model must both use position t's content (for other positions to attend to) and predict it without seeing itself. Standard self-attention cannot do both simultaneously.

Commit before you compute: what does Two-stream attention — content vs query masks come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: permutation=[3,0,1,5,2,4]; ranks=[1,2,4,0,5,3]; target pos=3 is rank 0 (first in perm order)

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Position 3 appears first in this permutation, so in the content stream it can see itself (c_mask[3,3]=1).

10. Two-stream attention — content vs query masks

Worked example

XLNet needs two attention streams because the model must both use position t's content (for other positions to attend to) and predict it without seeing itself. Standard self-attention cannot do both simultaneously.

import torch

def permutation_masks(seq_len, target_pos, seed=7):
    torch.manual_seed(seed)
    perm = torch.randperm(seq_len)   # e.g. [3,0,1,5,2,4]
    # rank of each position in the permutation
    perm_rank = torch.zeros(seq_len, dtype=torch.long)
    for rank, pos in enumerate(perm):
        perm_rank[pos] = rank
    # content mask: position i can attend j if rank(j) <= rank(i)
    c_mask = (perm_rank.unsqueeze(0) <= perm_rank.unsqueeze(1)).float()
    # query mask: same but target cannot attend to itself
    q_mask = c_mask.clone()
    q_mask[target_pos, target_pos] = 0.0
    return perm, perm_rank, c_mask, q_mask

perm, ranks, cm, qm = permutation_masks(seq_len=6, target_pos=3)
print('permutation:', perm.tolist())
print('perm ranks: ', ranks.tolist())
print('target=3 is rank', ranks[3].item())
print('content mask row 3:', cm[3].int().tolist())
print('query   mask row 3:', qm[3].int().tolist())

permutation=[3,0,1,5,2,4]; ranks=[1,2,4,0,5,3]; target pos=3 is rank 0 (first in perm order)

Why: Position 3 appears first in this permutation, so in the content stream it can see itself (c_mask[3,3]=1). In the query stream that self-link is zeroed (q_mask[3,3]=0) so the model predicts position 3 without peeking at its own content.

streamself-attention (pos=3)can see past contextused for
content streamyes (c_mask[3,3]=1)yes (rank < own rank)key/value for other positions
query streamno (q_mask[3,3]=0)yes (rank < own rank)predict x_3 from context

11. Fill in: can see past context for Two-stream attention — content vs query masks

Comparison

Comparison matrix

From Two-stream attention — content vs query masks: refill the can see past context column from what you know. The rest of the table is as it appeared.

streamself-attention (pos=3)can see past contextused for
content streamyes (c_mask[3,3]=1)yes (rank < own rank)key/value for other positions
query streamno (q_mask[3,3]=0)yes (rank < own rank)predict x_3 from context

12. Something is wrong here: XLNet still uses positional encoding for the original…

Anomaly

Predict first

A student writes this, and it looks reasonable:

XLNet randomizes the token order, so the model receives tokens in the permuted sequence order and positional embeddings reflect the permuted positions.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: The token sequence itself stays in its original left-to-right order; only the attention mask changes.

Tokens remain in original order. The permutation is expressed entirely through the attention mask — which positions are allowed to attend to which.

Why: The token sequence itself stays in its original left-to-right order; only the attention mask changes. Positional embeddings reflect original positions, not permutation rank.

13. Trap: XLNet still uses positional encoding for the original order

Trap

The trap

XLNet randomizes the token order, so the model receives tokens in the permuted sequence order and positional embeddings reflect the permuted positions.

Assign positional embedding i to the token at permutation position i

Why: Wrong. The token sequence itself stays in its original left-to-right order; only the attention mask changes. Positional embeddings reflect original positions, not permutation rank.

The fix

Tokens remain in original order. The permutation is expressed entirely through the attention mask — which positions are allowed to attend to which.

Keep tokens at their original positions; vary only the content/query masks per permutation

Why: This is critical: no shuffling of input tensors occurs. c_mask[i,j] = 1 means token i can attend token j in this permutation. The same embedding matrix and positional encodings are reused.

14. RoBERTa — removing BERT's training shortcuts

Section

Part 3 of 5

15. RoBERTa: three targeted ablations

Concept

RoBERTa (Liu 2019) is BERT with better training — same architecture, three training changes that each demonstrably improve downstream performance.

  1. Remove NSP (Next Sentence Prediction): BERT's NSP task was too easy — models solved it with superficial cues. Dropping it forces the MLM to do more work and improves sentence-level tasks.
  2. Dynamic masking: generate a fresh mask each epoch instead of a fixed mask at preprocessing time. Each pass sees different positions masked — more training signal per token.
  3. Larger batches + more data: 256 → 8,000 sequences per batch; 16 GB → 160 GB text. Bigger batches stabilize the gradient and let the lr be scaled up.

16. Break it if you can: RoBERTa: three targeted ablations

Counterexample

Discussion prompt

RoBERTa (Liu 2019) is BERT with better training — same architecture, three training changes that each demonstrably improve downstream performance.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

17. What has to be given first: Dynamic masking in code

Missing information

Discussion prompt

Static masking (BERT): mask is computed once at preprocessing; every epoch trains on the same masked positions. Dynamic masking (RoBERTa): each forward pass draws a fresh mask at 15% independently.

What do you need to know — or decide — before the first line can be written? List everything the problem has to hand you.

Hint: Anything you would have to invent to get started is a thing the problem must supply.

Answer:

Over 40 epochs of RoBERTa training, dynamic masking means a 20-token sequence contributes ~(40 x 0.15 x 20) = 120 masked-token training events vs only (0.15 x 20) = 3 with static masking — 40x more diverse signal.

18. Dynamic masking in code

Worked example

Static masking (BERT): mask is computed once at preprocessing; every epoch trains on the same masked positions. Dynamic masking (RoBERTa): each forward pass draws a fresh mask at 15% independently.

import numpy as np

def static_mask(seq_len, mask_prob=0.15, seed=42):
    rng = np.random.RandomState(seed)
    return (rng.random(seq_len) < mask_prob).astype(int)

def dynamic_mask(seq_len, mask_prob=0.15, seed=None):
    rng = np.random.RandomState(seed)
    return (rng.random(seq_len) < mask_prob).astype(int)

seq_len = 20
static = static_mask(seq_len, seed=42)
print('static (same every epoch):  ', static.tolist())
for ep, s in enumerate([42, 99, 7, 13], 1):
    print(f'dynamic epoch {ep} (seed={s}): ', dynamic_mask(seq_len, seed=s).tolist())

Static: positions [6,10] masked every epoch. Dynamic: epoch 1=[6,10], epoch 2=[1,9,12,17], epoch 3=[0,2,16,18], epoch 4=[7,8,13]

Why: Over 40 epochs of RoBERTa training, dynamic masking means a 20-token sequence contributes ~(40 x 0.15 x 20) = 120 masked-token training events vs only (0.15 x 20) = 3 with static masking — 40x more diverse signal.

masking strategypositions maskeddiversity per sequenceepochs to match
static (BERT)fixed at preprocessing1 pattern total40 epochs = same pattern x40
dynamic (RoBERTa)fresh each epoch40 distinct patterns40 epochs = 40 distinct

19. What each one costs: Dynamic masking in code

Trade off

Comparison matrix

From Dynamic masking in code: every row here is a choice with a cost. Fill the positions masked column, then say which row you would actually pick and what you give up for it.

masking strategypositions maskeddiversity per sequenceepochs to match
static (BERT)fixed at preprocessing1 pattern total40 epochs = same pattern x40
dynamic (RoBERTa)fresh each epoch40 distinct patterns40 epochs = 40 distinct

20. T5 — text-to-text unification

Section

Part 4 of 5

21. T5: every NLP task is seq2seq

Concept

T5 (Raffel 2020) uses a single encoder-decoder for every task. The task type is specified by a text prefix in the input; the model always generates a text string as output. There is no task-specific head architecture.

taskinput prefix + texttarget output
sentimentsst2 sentence: This movie is great.positive
translationtranslate English to German: The house...Das Haus...
summarizationsummarize: Scientists discover...<summary>
QAquestion: Who leads the team? context: ...Jane Smith
STS (regress)stsb sentence1: ... sentence2: ...3.8

Even regression (similarity score 0-5) is cast as text generation: the model outputs the digit string "3.8". This unification lets T5 train on all tasks simultaneously with a single cross-entropy loss over the output vocabulary.

22. Guess the shape of the answer: T5-style encoder-decoder forward pass

Estimation

Predict first

Trace a minimal T5 encoder-decoder with vocab=32, d=64, 2 encoder layers, 2 decoder layers. Input: src tokens (batch=2, len=8). Target: tgt tokens (batch=2, len=3). Predict the logit shape.

Commit before you compute: what does T5-style encoder-decoder forward pass come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: enc_out=(2,8,64); dec_out=(2,3,64); logits=(2,3,32)

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. The encoder reads the full source in parallel (bidirectional).

23. T5-style encoder-decoder forward pass

Worked example

Trace a minimal T5 encoder-decoder with vocab=32, d=64, 2 encoder layers, 2 decoder layers. Input: src tokens (batch=2, len=8). Target: tgt tokens (batch=2, len=3). Predict the logit shape.

import torch, torch.nn as nn
torch.manual_seed(42)

VOCAB, D, N_HEADS = 32, 64, 4
enc_layer = nn.TransformerEncoderLayer(D, N_HEADS, dim_feedforward=D*4, batch_first=True)
encoder   = nn.TransformerEncoder(enc_layer, num_layers=2)
dec_layer = nn.TransformerDecoderLayer(D, N_HEADS, dim_feedforward=D*4, batch_first=True)
decoder   = nn.TransformerDecoder(dec_layer, num_layers=2)
embed     = nn.Embedding(VOCAB, D)
out_proj  = nn.Linear(D, VOCAB)

src = torch.randint(0, VOCAB, (2, 8))
tgt = torch.randint(0, VOCAB, (2, 3))
src_e = embed(src)                     # (2, 8, 64)
tgt_e = embed(tgt)                     # (2, 3, 64)
enc_out  = encoder(src_e)              # (2, 8, 64)
# causal mask: decoder can only see left context
causal = nn.Transformer.generate_square_subsequent_mask(3)
dec_out  = decoder(tgt_e, enc_out, tgt_mask=causal)  # (2, 3, 64)
logits   = out_proj(dec_out)           # (2, 3, 32)
print('enc_out:', enc_out.shape)
print('dec_out:', dec_out.shape)
print('logits: ', logits.shape)

enc_out=(2,8,64); dec_out=(2,3,64); logits=(2,3,32)

Why: The encoder reads the full source in parallel (bidirectional). The decoder applies cross-attention to enc_out at every step while generating left-to-right under a causal mask — same dual-stream as the original Transformer (Lesson 88).

tensorshapewhat it represents
src tokens(2, 8)batch=2, src len=8
enc_out(2, 8, 64)encoder hidden state for each src token
tgt tokens(2, 3)batch=2, tgt len=3 (teacher forced)
dec_out(2, 3, 64)decoder hidden state per tgt position
logits(2, 3, 32)vocab distribution at each generated step

24. Work backwards from the answer: T5-style encoder-decoder forward pass

Reverse engineer

Discussion prompt

Work backwards. The example finished here:

enc_out=(2,8,64); dec_out=(2,3,64); logits=(2,3,32)

What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.

Hint: Every quantity in the result had to enter somewhere. Account for each one.

Answer:

Trace a minimal T5 encoder-decoder with vocab=32, d=64, 2 encoder layers, 2 decoder layers. Input: src tokens (batch=2, len=8). Target: tgt tokens (batch=2, len=3). Predict the logit shape.

25. DeBERTa & ALBERT — attention and size

Section

Part 5 of 5

26. DeBERTa: disentangled content + position attention

Concept

Standard BERT attention fuses content and position into a single query/key vector before computing dot products. DeBERTa disentangles them: it computes three separate score terms and sums them.

\[ A_{i,j} = \underbrace{\mathbf{Q}_i^c \cdot \mathbf{K}_j^c}_{\text{content-content}} + \underbrace{\mathbf{Q}_i^c \cdot \mathbf{K}_{\delta(i,j)}^r}_{\text{content-position}} + \underbrace{\mathbf{Q}_{\delta(i,j)}^r \cdot \mathbf{K}_j^c}_{\text{position-content}} \]

Here delta(i,j) is the relative distance between positions i and j (clipped to a max range). The relative position embedding K^r is shared across all layers. This lets the model independently learn what (content) and where (position) relationships.

27. By analogy: DeBERTa: disentangled content + position attention

Analogy

Discussion prompt

Explain DeBERTa: disentangled content + position attention by analogy to something with no Machine Learning in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

Standard BERT attention fuses content and position into a single query/key vector before computing dot products. DeBERTa disentangles them: it computes three separate score terms and sums them.

28. ALBERT: cross-layer parameter sharing

Concept

ALBERT reduces parameter count by sharing the same weight tensor across every encoder layer. Instead of L distinct layers, one layer's weights are applied L times. The model's computational cost (FLOPs) stays the same; only memory drops.

29. Guess the shape of the answer: ALBERT cross-layer sharing in PyTorch

Estimation

Predict first

Implement ALBERT-style sharing: one TransformerLayer applied 6 times vs 6 distinct layers. Verify the 6x parameter reduction. Use d=64, n_heads=4.

Commit before you compute: what does ALBERT cross-layer sharing in PyTorch come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: Standard: 299,904 params. ALBERT-style: 49,984 params. Reduction: 6x. Output shape: (2, 10, 64) — identical.

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Sharing one layer across 6 passes uses 1/6 the parameters.

30. ALBERT cross-layer sharing in PyTorch

Worked example

Implement ALBERT-style sharing: one TransformerLayer applied 6 times vs 6 distinct layers. Verify the 6x parameter reduction. Use d=64, n_heads=4.

import torch, torch.nn as nn
torch.manual_seed(42)

class TransformerLayer(nn.Module):
    def __init__(self, d=64, n_heads=4):
        super().__init__()
        self.norm1 = nn.LayerNorm(d)
        self.attn  = nn.MultiheadAttention(d, n_heads, batch_first=True)
        self.norm2 = nn.LayerNorm(d)
        self.mlp   = nn.Sequential(
            nn.Linear(d, d*4), nn.GELU(), nn.Linear(d*4, d)
        )
    def forward(self, x):
        a, _ = self.attn(self.norm1(x), self.norm1(x), self.norm1(x))
        x = x + a
        return x + self.mlp(self.norm2(x))

# Standard 6-layer encoder (6 distinct sets of weights)
class StandardEncoder(nn.Module):
    def __init__(self, n_layers=6):
        super().__init__()
        self.layers = nn.ModuleList([TransformerLayer() for _ in range(n_layers)])
    def forward(self, x):
        for layer in self.layers: x = layer(x)
        return x

# ALBERT-style: one shared layer applied 6 times
class ALBERTEncoder(nn.Module):
    def __init__(self, n_layers=6):
        super().__init__()
        self.shared = TransformerLayer()
        self.n_layers = n_layers
    def forward(self, x):
        for _ in range(self.n_layers): x = self.shared(x)
        return x

std = StandardEncoder()
alb = ALBERTEncoder()
std_p = sum(p.numel() for p in std.parameters())
alb_p = sum(p.numel() for p in alb.parameters())
print(f'Standard 6-layer: {std_p} params')
print(f'ALBERT 6-shared:  {alb_p} params')
print(f'Reduction: {std_p // alb_p}x')
x = torch.randn(2, 10, 64)
print('Output shape:', alb(x).shape)

Standard: 299,904 params. ALBERT-style: 49,984 params. Reduction: 6x. Output shape: (2, 10, 64) — identical.

Why: Sharing one layer across 6 passes uses 1/6 the parameters. The forward-pass cost is the same (6 matrix multiplies either way) but memory and storage shrink 6x. BERT-base-scale: 85M encoder params reduced to ~7M with full sharing.

configurationparams (d=64, 6L)params (d=768, 12L BERT-scale)output shape
standard (distinct layers)299,90485,054,464(2,10,64)
ALBERT (shared layer x6/12)49,9847,087,872(2,10,64)
reduction factor6x12xsame

31. Fill in: output shape for ALBERT cross-layer sharing in PyTorch

Comparison

Comparison matrix

From ALBERT cross-layer sharing in PyTorch: refill the output shape column from what you know. The rest of the table is as it appeared.

configurationparams (d=64, 6L)params (d=768, 12L BERT-scale)output shape
standard (distinct layers)299,90485,054,464(2,10,64)
ALBERT (shared layer x6/12)49,9847,087,872(2,10,64)
reduction factor6x12xsame

32. Something is wrong here: ALBERT sharing reduces FLOPs proportional to parameter…

Anomaly

Predict first

A student writes this, and it looks reasonable:

ALBERT shares weights → 6x fewer parameters → 6x faster inference. Smaller model = proportionally faster forward pass.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: ALBERT still executes 6 forward passes through the shared layer — the same number of matrix multiplications.

ALBERT reduces parameter count and storage, not compute. The shared layer still runs once per layer — the FLOPs are the same as the standard model.

Why: ALBERT still executes 6 forward passes through the shared layer — the same number of matrix multiplications. FLOPs are nearly identical; only memory and checkpoint size shrink.

33. Trap: ALBERT sharing reduces FLOPs proportional to parameter count

Trap

The trap

ALBERT shares weights → 6x fewer parameters → 6x faster inference. Smaller model = proportionally faster forward pass.

Expect ALBERT inference to be 6x faster than a standard 6-layer model

Why: Wrong. ALBERT still executes 6 forward passes through the shared layer — the same number of matrix multiplications. FLOPs are nearly identical; only memory and checkpoint size shrink.

The fix

ALBERT reduces parameter count and storage, not compute. The shared layer still runs once per layer — the FLOPs are the same as the standard model.

ALBERT benefit: smaller checkpoint, less memory, fewer gradients to store. No speedup at inference.

Why: This is a critical distinction on the USAAIO exam: parameter count and FLOPs are different quantities. A model can have 6x fewer params and still be equally slow at inference.

34. Break it on purpose: ALBERT sharing reduces FLOPs proportional to…

Break the constraint

Discussion prompt

The rule this trap just fixed:

ALBERT reduces parameter count and storage, not compute. The shared layer still runs once per layer — the FLOPs are the same as the standard model.

Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?

Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.

Answer:

ALBERT still executes 6 forward passes through the shared layer — the same number of matrix multiplications. FLOPs are nearly identical; only memory and checkpoint size shrink.

35. Without one step: The BERT-variant decision map

Constraint

Discussion prompt

Run The BERT-variant decision map with this step confiscated:

Better position-content interaction? DeBERTa: disentangled Q_c·K_c + Q_c·K_r + Q_r·K_c relative attention.

Is it still possible? If it is, say what takes its place and what it costs you. If it is not, say exactly what that step was providing that nothing else does.

Hint: A step you can drop for free was never load-bearing. If you cannot drop it, name the thing that goes wrong the moment it is gone.

Answer:

  1. Need generation (summarize/translate)? Use T5 (encoder-decoder, text-to-text) or XLNet (autoregressive perm-LM). BERT alone cannot generate.
  2. Fixing BERT's [MASK] artifact? XLNet: permutation LM with two-stream attention (content mask + query mask). No [MASK] token at all.
  3. Maximizing BERT's pretraining signal? RoBERTa: drop NSP, use dynamic masking (fresh mask each epoch), scale batches and data.
  4. Better position-content interaction? DeBERTa: disentangled Q_c·K_c + Q_c·K_r + Q_r·K_c relative attention.
  5. Smaller checkpoint, same depth? ALBERT: shared layer weights + embedding factorization. Saves params/storage; FLOPs unchanged.
  6. USAAIO exam heuristic: match the variant to the claimed fix — generation=T5/XLNet, data efficiency=RoBERTa, attention precision=DeBERTa, size=ALBERT.

36. The BERT-variant decision map

Pattern

  1. Need generation (summarize/translate)? Use T5 (encoder-decoder, text-to-text) or XLNet (autoregressive perm-LM). BERT alone cannot generate.
  2. Fixing BERT's [MASK] artifact? XLNet: permutation LM with two-stream attention (content mask + query mask). No [MASK] token at all.
  3. Maximizing BERT's pretraining signal? RoBERTa: drop NSP, use dynamic masking (fresh mask each epoch), scale batches and data.
  4. Better position-content interaction? DeBERTa: disentangled Q_c·K_c + Q_c·K_r + Q_r·K_c relative attention.
  5. Smaller checkpoint, same depth? ALBERT: shared layer weights + embedding factorization. Saves params/storage; FLOPs unchanged.
  6. USAAIO exam heuristic: match the variant to the claimed fix — generation=T5/XLNet, data efficiency=RoBERTa, attention precision=DeBERTa, size=ALBERT.

37. Where does it stop working: The BERT-variant decision map

Edge cases

Discussion prompt

The BERT-variant decision map works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".

Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.

Answer:

  1. Need generation (summarize/translate)? Use T5 (encoder-decoder, text-to-text) or XLNet (autoregressive perm-LM). BERT alone cannot generate.
  2. Fixing BERT's [MASK] artifact? XLNet: permutation LM with two-stream attention (content mask + query mask). No [MASK] token at all.
  3. Maximizing BERT's pretraining signal? RoBERTa: drop NSP, use dynamic masking (fresh mask each epoch), scale batches and data.
  4. Better position-content interaction? DeBERTa: disentangled Q_c·K_c + Q_c·K_r + Q_r·K_c relative attention.
  5. Smaller checkpoint, same depth? ALBERT: shared layer weights + embedding factorization. Saves params/storage; FLOPs unchanged.
  6. USAAIO exam heuristic: match the variant to the claimed fix — generation=T5/XLNet, data efficiency=RoBERTa, attention precision=DeBERTa, size=ALBERT.

38. Rule out three: Check yourself — XLNet two-stream

Elimination

Eliminate the wrong options

In XLNet's two-stream attention, why does the query stream mask out position t's own content when predicting x_t?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. To prevent the model from trivially copying x_t into its own prediction, since the prediction target is x_t itself.
  • B. Because the query stream uses absolute position embeddings that conflict with the content embedding at the same position.
  • C. The query stream is only used during fine-tuning, so it never needs to attend to pre-training tokens.
  • D. To save memory — masking self-attention at the target position reduces the attention matrix by one row.

Survives elimination: A

Why: The query stream is responsible for predicting x_t. If it could attend to x_t's own content embedding, it would see the answer during training — information leakage. Zeroing q_mask[t,t] forces the model to predict from context only, exactly like BERT's [MASK] objective but without the [MASK] token.

39. Check yourself — XLNet two-stream

Check

Work through the logic before clicking.

Check your understanding

In XLNet's two-stream attention, why does the query stream mask out position t's own content when predicting x_t?

  • A. To prevent the model from trivially copying x_t into its own prediction, since the prediction target is x_t itself. (correct)
  • B. Because the query stream uses absolute position embeddings that conflict with the content embedding at the same position.
  • C. The query stream is only used during fine-tuning, so it never needs to attend to pre-training tokens.
  • D. To save memory — masking self-attention at the target position reduces the attention matrix by one row.

Answer: A

Why: The query stream is responsible for predicting x_t. If it could attend to x_t's own content embedding, it would see the answer during training — information leakage. Zeroing q_mask[t,t] forces the model to predict from context only, exactly like BERT's [MASK] objective but without the [MASK] token.

Why B tempts people
XLNet uses relative positional encodings (Transformer-XL style), not absolute; there is no conflict with content embeddings. The masking is about preventing the target from seeing itself, not an embedding dimension issue.
Why C tempts people
The two-stream mechanism is a pretraining design choice and applies during pretraining, not fine-tuning. Fine-tuning uses only the content stream.
Why D tempts people
The motivation is entirely about information leakage, not memory savings. Masking one entry in a (T x T) attention matrix has negligible memory impact.

40. Answer it before you see the options: Check yourself — RoBERTa improvements

Prediction

Predict first

Which of the following is NOT a change RoBERTa makes relative to BERT?

Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.

Correct: Replacing the MLM objective with a permutation language model

Why: RoBERTa keeps MLM as its pretraining objective — it just removes NSP, switches to dynamic masking, and scales up batch size and data. Replacing MLM with a permutation LM is XLNet's contribution, not RoBERTa's.

41. Check yourself — RoBERTa improvements

Check

Identify the exact change.

Check your understanding

Which of the following is NOT a change RoBERTa makes relative to BERT?

  • A. Replacing the MLM objective with a permutation language model (correct)
  • B. Removing the Next Sentence Prediction (NSP) task
  • C. Using dynamic masking instead of static masking
  • D. Training on larger batches with more data

Answer: A

Why: RoBERTa keeps MLM as its pretraining objective — it just removes NSP, switches to dynamic masking, and scales up batch size and data. Replacing MLM with a permutation LM is XLNet's contribution, not RoBERTa's.

Why B tempts people
Removing NSP is exactly one of RoBERTa's three key changes — the paper showed NSP was too easy and removing it improved sentence-level tasks.
Why C tempts people
Dynamic masking (generating a new mask each epoch instead of at preprocessing time) is RoBERTa's second key change, providing ~40x more unique masking patterns per sequence over training.
Why D tempts people
RoBERTa trains with batches of 8,000 sequences (vs BERT's 256) and 160 GB of text (vs BERT's 16 GB) — scaling is the third key change.

42. Rule out three: Check yourself — ALBERT vs standard BERT

Elimination

Eliminate the wrong options

ALBERT-base uses cross-layer parameter sharing across its 12 encoder layers. Compared to BERT-base (12 distinct layers), ALBERT-base has approximately:

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. 12x fewer encoder parameters and ~1x the encoder FLOPs
  • B. 12x fewer encoder parameters and 12x fewer encoder FLOPs
  • C. The same parameters but 12x fewer FLOPs
  • D. 12x fewer parameters and 12x faster inference

Survives elimination: A

Why: ALBERT shares one layer's weights across all 12 layers: 7,087,872 encoder params vs BERT's 85,054,464 (verified in PyTorch, ~12x). But it still applies the shared layer 12 times — same FLOPs as BERT. Inference speed is unchanged; only storage and memory shrink.

43. Check yourself — ALBERT vs standard BERT

Check

Apply the FLOPs vs params distinction.

Check your understanding

ALBERT-base uses cross-layer parameter sharing across its 12 encoder layers. Compared to BERT-base (12 distinct layers), ALBERT-base has approximately:

  • A. 12x fewer encoder parameters and ~1x the encoder FLOPs (correct)
  • B. 12x fewer encoder parameters and 12x fewer encoder FLOPs
  • C. The same parameters but 12x fewer FLOPs
  • D. 12x fewer parameters and 12x faster inference

Answer: A

Why: ALBERT shares one layer's weights across all 12 layers: 7,087,872 encoder params vs BERT's 85,054,464 (verified in PyTorch, ~12x). But it still applies the shared layer 12 times — same FLOPs as BERT. Inference speed is unchanged; only storage and memory shrink.

Why B tempts people
FLOPs are determined by how many times matrix multiplications are executed, not how many unique weight tensors exist. 12 applications of one shared layer = same FLOPs as 12 distinct layers.
Why C tempts people
The parameters are NOT the same — ALBERT has 12x fewer due to sharing. It is the FLOPs that are similar, not the parameter counts.
Why D tempts people
Inference speed (wall-clock time) scales with FLOPs, not parameter count. Since FLOPs are unchanged, inference is not faster — only the model is lighter to store and load.

44. Your turn: ALBERT sharing + T5 framing

Section

Project

45. Project: implement and compare

Concept

Three milestones: (1) implement ALBERT-style cross-layer sharing and verify the param reduction; (2) wire up a minimal T5 encoder-decoder and trace shapes; (3) write a dynamic masking function and compare masking diversity.

#milestonetarget output
1ALBERTEncoder vs StandardEncoder, n_layers=6, d=64299,904 vs 49,984 params (6x)
2T5EncoderDecoder: src=(2,8), tgt=(2,3), vocab=32, d=64logits shape (2,3,32)
3dynamic_mask() called 10 times on len-20 seq, mask_prob=0.1510 distinct masked-position lists

Build rules: use torch.manual_seed(42) for reproducibility; print shapes after every operation; count params with sum(p.numel() for p in model.parameters()) not model.parameters().__len__().

46. Break it if you can: Project: implement and compare

Counterexample

Discussion prompt

Build rules: use torch.manual_seed(42) for reproducibility; print shapes after every operation; count params with sum(p.numel() for p in model.parameters()) not model.parameters().__len__().

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

47. Milestone 1 — ALBERT sharing param count

Worked example

Your turn: build StandardEncoder (6 distinct TransformerLayer) and ALBERTEncoder (one shared layer applied 6 times). Predict the ratio before running.

Hint: nn.ModuleList registers multiple distinct layers; assigning one layer to self.shared and calling it N times in forward shares weights. Use sum(p.numel() for p in m.parameters()).

import torch, torch.nn as nn
torch.manual_seed(42)

class TL(nn.Module):
    def __init__(self, d=64, h=4):
        super().__init__()
        self.n1=nn.LayerNorm(d); self.n2=nn.LayerNorm(d)
        self.attn=nn.MultiheadAttention(d, h, batch_first=True)
        self.mlp=nn.Sequential(nn.Linear(d,d*4), nn.GELU(), nn.Linear(d*4,d))
    def forward(self, x):
        a,_=self.attn(self.n1(x),self.n1(x),self.n1(x))
        return x+a+self.mlp(self.n2(x+a))

class Std(nn.Module):
    def __init__(self):
        super().__init__()
        self.layers=nn.ModuleList([TL() for _ in range(6)])
    def forward(self, x):
        for l in self.layers: x=l(x)
        return x

class Alb(nn.Module):
    def __init__(self):
        super().__init__()
        self.shared=TL()
    def forward(self, x):
        for _ in range(6): x=self.shared(x)
        return x

std_p=sum(p.numel() for p in Std().parameters())
alb_p=sum(p.numel() for p in Alb().parameters())
print(f'Standard: {std_p}   ALBERT: {alb_p}   ratio: {std_p//alb_p}x')
modelparamsratio
StandardEncoder (6 layers)299,9046x
ALBERTEncoder (1 shared x6)49,9841x (baseline)

48. Milestone 2 — T5 encoder-decoder shapes

Worked example

Your turn: build a minimal encoder-decoder (2 enc layers, 2 dec layers, d=64). Feed src=(2,8) and tgt=(2,3). Trace every tensor shape. Predict the logit shape before running.

Hint: the decoder needs a causal mask (nn.Transformer.generate_square_subsequent_mask(tgt_len)). Cross-attention memory comes from enc_out. The output projection maps from d to vocab_size.

import torch, torch.nn as nn
torch.manual_seed(42)

V, D, H = 32, 64, 4
enc = nn.TransformerEncoder(nn.TransformerEncoderLayer(D,H,D*4,batch_first=True), 2)
dec = nn.TransformerDecoder(nn.TransformerDecoderLayer(D,H,D*4,batch_first=True), 2)
emb = nn.Embedding(V, D)
proj = nn.Linear(D, V)

src = torch.randint(0, V, (2, 8))
tgt = torch.randint(0, V, (2, 3))
enc_out = enc(emb(src))
cm = nn.Transformer.generate_square_subsequent_mask(3)
dec_out = dec(emb(tgt), enc_out, tgt_mask=cm)
logits  = proj(dec_out)
print('enc_out:', enc_out.shape, ' dec_out:', dec_out.shape, ' logits:', logits.shape)
tensorshapenote
src embedded(2, 8, 64)batch=2, src_len=8
enc_out(2, 8, 64)full source context
tgt embedded(2, 3, 64)batch=2, tgt_len=3
dec_out(2, 3, 64)causal + cross-attn applied
logits(2, 3, 32)vocab distribution per step

49. What each one costs: Milestone 2 — T5 encoder-decoder shapes

Trade off

Comparison matrix

From Milestone 2 — T5 encoder-decoder shapes: every row here is a choice with a cost. Fill the note column, then say which row you would actually pick and what you give up for it.

tensorshapenote
src embedded(2, 8, 64)batch=2, src_len=8
enc_out(2, 8, 64)full source context
tgt embedded(2, 3, 64)batch=2, tgt_len=3
dec_out(2, 3, 64)causal + cross-attn applied
logits(2, 3, 32)vocab distribution per step

50. Milestone 3 — dynamic masking diversity

Worked example

Your turn: write dynamic_mask(seq_len, mask_prob, seed) and call it 4 times with different seeds on a 20-token sequence. Predict whether any two masks will be identical.

Hint: use np.random.RandomState(seed) so each call is reproducible but distinct. Compare static (one mask forever) vs dynamic (new mask each epoch).

import numpy as np

def dynamic_mask(seq_len=20, mask_prob=0.15, seed=None):
    rng = np.random.RandomState(seed)
    masked = np.where(rng.random(seq_len) < mask_prob)[0].tolist()
    return masked

static = dynamic_mask(seed=42)
print('static (fixed):', static)
for ep, s in enumerate([42, 99, 7, 13], 1):
    dm = dynamic_mask(seed=s)
    print(f'epoch {ep} (seed={s}): {dm}')
epoch / strategymasked positionsunique?
static (seed=42)[6, 10]same every epoch
epoch 1 (seed=42)[6, 10]identical to static
epoch 2 (seed=99)[1, 9, 12, 17]distinct
epoch 3 (seed=7)[0, 2, 16, 18]distinct
epoch 4 (seed=13)[7, 8, 13]distinct

51. Which is which, by unique?

Discrimination

Sort into buckets

Sort these by unique?, from memory, without looking back at Milestone 3 — dynamic masking diversity. Telling them apart on the spot is the skill; the table is only where the answer happens to be written down.

same every epoch
static (seed=42)
identical to static
epoch 1 (seed=42)
distinct
epoch 2 (seed=99); epoch 3 (seed=7); epoch 4 (seed=13)
g1
unique? is "same every epoch" for static (seed=42) — that is what the table on "Milestone 3 — dynamic masking diversity" records, and it is the single property separating this group from the rest.
g2
unique? is "identical to static" for epoch 1 (seed=42) — that is what the table on "Milestone 3 — dynamic masking diversity" records, and it is the single property separating this group from the rest.
g3
unique? is "distinct" for epoch 2 (seed=99), epoch 3 (seed=7), epoch 4 (seed=13) — that is what the table on "Milestone 3 — dynamic masking diversity" records, and it is the single property separating this group from the rest.

52. The full program

Concept

import torch, torch.nn as nn, numpy as np
torch.manual_seed(42)

# ---- ALBERT cross-layer sharing ----
class TL(nn.Module):
    def __init__(self, d=64, h=4):
        super().__init__()
        self.n1=nn.LayerNorm(d); self.n2=nn.LayerNorm(d)
        self.attn=nn.MultiheadAttention(d,h,batch_first=True)
        self.mlp=nn.Sequential(nn.Linear(d,d*4),nn.GELU(),nn.Linear(d*4,d))
    def forward(self,x):
        a,_=self.attn(self.n1(x),self.n1(x),self.n1(x))
        return x+a+self.mlp(self.n2(x+a))

class Std(nn.Module):
    def __init__(self): super().__init__(); self.ls=nn.ModuleList([TL() for _ in range(6)])
    def forward(self,x):
        for l in self.ls: x=l(x)
        return x

class Alb(nn.Module):
    def __init__(self): super().__init__(); self.s=TL()
    def forward(self,x):
        for _ in range(6): x=self.s(x)
        return x

std_p=sum(p.numel() for p in Std().parameters())
alb_p=sum(p.numel() for p in Alb().parameters())
print(f'Standard: {std_p}  ALBERT: {alb_p}  ({std_p//alb_p}x reduction)')

# ---- T5 encoder-decoder ----
V,D,H=32,64,4
enc=nn.TransformerEncoder(nn.TransformerEncoderLayer(D,H,D*4,batch_first=True),2)
dec=nn.TransformerDecoder(nn.TransformerDecoderLayer(D,H,D*4,batch_first=True),2)
emb=nn.Embedding(V,D); proj=nn.Linear(D,V)
src=torch.randint(0,V,(2,8)); tgt=torch.randint(0,V,(2,3))
enc_out=enc(emb(src))
cm=nn.Transformer.generate_square_subsequent_mask(3)
logits=proj(dec(emb(tgt),enc_out,tgt_mask=cm))
print('T5 logits:', logits.shape)

# ---- dynamic masking ----
def dyn(n=20,p=0.15,s=None):
    rng=np.random.RandomState(s)
    return np.where(rng.random(n)<p)[0].tolist()

for ep,s in enumerate([42,99,7,13],1):
    print(f'epoch {ep}: {dyn(seed=s)}')
componentverified output
StandardEncoder params299,904
ALBERTEncoder params49,984 (6x reduction)
T5 logits shape(2, 3, 32)
Dynamic masks4 distinct token sets across epochs

53. Fill in: verified output for The full program

Comparison

Comparison matrix

From The full program: refill the verified output column from what you know. The rest of the table is as it appeared.

componentverified output
StandardEncoder params299,904
ALBERTEncoder params49,984 (6x reduction)
T5 logits shape(2, 3, 32)
Dynamic masks4 distinct token sets across epochs

54. Show it off

Concept

Out loud, slides closed: (1) explain the XLNet content mask vs query mask distinction and why the target's self-link is zeroed; (2) list RoBERTa's three changes to BERT training; (3) explain why ALBERT's parameter reduction does not translate to faster inference.

Stretch (homework from the lesson plan): load T5 from HuggingFace (from transformers import T5ForConditionalGeneration) and use it for QA as seq2seq. Compare its cross-attention patterns with a BERT-based extractive QA model. Implement ALBERT-style embedding factorization: nn.Embedding(vocab, E=128) followed by nn.Linear(128, 768) and measure the param savings vs nn.Embedding(vocab, 768) for vocab=30,000.

55. Connect it up: Lesson 93: BERT Variants — XLNet, RoBERTa, T5, DeBERTa, ALBERT

Connect it up

Draw it

One page, no notation unless you need it: draw how these connect — BERT's limitation and why it matters · XLNet — permutation language model · RoBERTa — removing BERT's training shortcuts · T5 — text-to-text unification · DeBERTa & ALBERT — attention and size · Your turn: ALBERT sharing + T5 framing. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.

56. What you can do now

Recap

variantkey fixarchitecture change
XLNetno [MASK] + bidirectional ctxpermutation LM + two-stream attention
RoBERTabetter training signaldynamic mask, no NSP, 8k batch
T5unified generationencoder-decoder, text prefix framing
DeBERTaricher position modelingdisentangled Q_c/K_c + Q_c/K_r + Q_r/K_c
ALBERTparameter efficiencyshared layer weights + embedding factorization

Sources

  1. USAAIO Year-Long Master Lesson Plan, Lesson 93 — BERT Variants — Barron · USAAIO Round 2 Preparation, 2026
  2. Yang et al. 'XLNet: Generalized Autoregressive Pretraining for Language Understanding' (NeurIPS 2019) — arXiv:1906.08237
  3. Liu et al. 'RoBERTa: A Robustly Optimized BERT Pretraining Approach' (2019) — arXiv:1907.11692
  4. Raffel et al. 'Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer' (JMLR 2020) — arXiv:1910.10683
  5. He et al. 'DeBERTa: Decoding-Enhanced BERT with Disentangled Attention' (ICLR 2021) — arXiv:2006.03654
  6. Lan et al. 'ALBERT: A Lite BERT for Self-supervised Learning of Language Representations' (ICLR 2020) — arXiv:1909.11942
  7. ALBERT cross-layer sharing param reduction, XLNet permutation masks, RoBERTa dynamic masking, T5 seq2seq shapes — all verified with torch 2.7.1+cpu, numpy 2.2.6, June 2026 — Real execution, verified

Want this taught 1-on-1? Alexander tutors Machine Learning — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108