April 14, 2026
Last lecture we built GPT — the decoder-only side of the transformer.
GPT answers the question: “given what you’ve seen so far, what comes next?”
Today: the other side of the Schism. Encoder-only models.
These answer a different question: “given this input, what does it mean?”
GPT only sees tokens from the left. When predicting the next token, it must work with incomplete context.
But for understanding a piece of text, you often need to see the full context:
“I went to the bank to deposit some money.”
“I went to the bank of the river to watch the sunset.”
Only by seeing the words after “bank” can you disambiguate its meaning.
GPT can generate coherent text, but its representations are fundamentally unidirectional. For tasks that require deep understanding — classification, retrieval, similarity — bidirectional context is a massive advantage.
GPT produces tokens — text.
BERT and ViT produce representations — dense vectors that encode meaning.
For every input position, encoder-only models output a contextualized embedding:
\[\mathbf{x} = [x_1, x_2, \ldots, x_T] \;\to\; \text{Encoder} \;\to\; \mathbf{h} = [\mathbf{h}_1, \mathbf{h}_2, \ldots, \mathbf{h}_T]\]
Each \(\mathbf{h}_i\) summarizes what position \(i\) means in the context of the entire sequence.
These representations are the foundation for everything downstream:
Bidirectional Encoder Representations from Transformers (Devlin et al., 2018)
Architecture: the encoder half of the original transformer. Nothing fancy.
The innovation isn’t the architecture. It’s the training objective.
For GPT, the training objective was obvious: predict the next token. A left-to-right signal that every sequence provides for free.
For bidirectional models, this doesn’t work. If position \(t\) sees positions \(t+1, t+2, \ldots\) during training, “predict the next token” is trivial — just read it directly.
We need a training objective that:
Randomly mask 15% of input tokens. Ask the model to predict what was masked, using the bidirectional context.
Example:
To predict the masked word, the model must build a rich representation of the surrounding context. It must learn:
MLM is the encoder equivalent of GPT’s next-token prediction — a self-supervised objective that forces the model to learn useful representations.
The mask is more subtle than “replace 15% with [MASK]”:
Why the complexity?
BERT also trains on a second objective: next sentence prediction (NSP).
Given two sentences \(A\) and \(B\), predict whether \(B\) actually follows \(A\) in the original corpus, or was randomly sampled from elsewhere.
Input format:
\[\text{[CLS]} \;\; A_1 \; A_2 \ldots A_m \;\; \text{[SEP]} \;\; B_1 \; B_2 \ldots B_n \;\; \text{[SEP]}\]
The [CLS] token’s final embedding is used for the binary classification.
Why NSP? It teaches cross-sentence coherence — useful for downstream tasks like question answering and natural language inference where understanding relationships between sentences matters.
Later work found NSP wasn’t very important. RoBERTa dropped it and did better. But it was part of the original BERT recipe.
Every BERT input starts with a special token: [CLS]. A learned beginning-of-sequence token that serves as a summary representation for the entire input.
This is the token you use for sentence-level classification tasks. Pass the [CLS] embedding through a small linear head to produce class logits:
cls_embedding = bert(input_ids).last_hidden_state[:, 0, :] # (B, 768)
logits = classifier_head(cls_embedding) # (B, num_classes)| Model | Layers | Hidden Dim | Heads | Parameters |
|---|---|---|---|---|
| BERT-base | 12 | 768 | 12 | 110M |
| BERT-large | 24 | 1024 | 16 | 340M |
But BERT’s release in 2018 was the watershed moment.
Before BERT: every NLP task required its own model, trained from scratch.
After BERT: one pretrained model, fine-tuned for everything.
BERT was released in October 2018. Within 18 months, dozens of variants appeared, each addressing some limitation of the original.
The core architecture barely changed. What changed: training strategy, training data, parameter efficiency, and downstream adaptation.
Let’s look at the major descendants. Each one teaches something about what matters in pretraining.
RoBERTa (Liu et al., 2019) is BERT with no architectural changes. But it significantly outperforms BERT.
The changes:
The lesson: BERT was undertrained. The architecture was fine — the recipe needed improvement.
DistilBERT (Sanh et al., 2019): a BERT that’s 40% smaller, 60% faster, retains 97% of BERT’s performance on downstream tasks.
How? Knowledge distillation.
Train a small student model (6 layers instead of 12) to match the output distribution of the full teacher BERT. The student learns to mimic the teacher’s predictions across many inputs.
Loss function combines:
Hard loss: predict the true labels (like normal training)
Soft loss: match the teacher’s output distribution (KL divergence)
Cosine loss: match the teacher’s hidden states
Why this works: the teacher’s output distribution contains more information than hard labels. “90% dog, 9% wolf, 1% cat” teaches the student something that “1.0 dog, 0 wolf, 0 cat” doesn’t — namely, that dogs and wolves are similar.
Distillation became a standard technique for deploying large models on edge devices or in latency-sensitive applications.
ALBERT (Lan et al., 2019): “A Lite BERT” — dramatically reduces parameters with two tricks.
Factorized embeddings: instead of a \(V \times H\) embedding matrix (vocabulary × hidden), use \(V \times E\) and \(E \times H\) where \(E \ll H\). Drops embedding parameters by ~85%.
Cross-layer parameter sharing: use the same attention weights across all 12 layers. The model still has 12 computational layers but only 1 layer’s worth of parameters.
Result: ALBERT-base has 12M parameters (vs BERT-base’s 110M) and matches or slightly beats BERT on most tasks.
ELECTRA (Clark et al., 2020): “Efficiently Learning an Encoder that Classifies Token Replacements Accurately”
MLM has an inefficiency: the model only learns from the 15% of tokens that were masked. The other 85% provide no training signal.
ELECTRA’s insight: instead of predicting masked tokens, detect replaced tokens.
Result: ELECTRA trains ~4× faster than BERT for equivalent performance. Small ELECTRA matches BERT-base with 1/30 the compute.
Before BERT, the NLP workflow for each task looked like this:
Problems:
The paradigm shift:
The pretrained model already knows:
Fine-tuning teaches it to apply this knowledge to a specific task, not to learn language from scratch.
This works because most of what a sentiment classifier needs to know (grammar, vocabulary, semantics) is the same as what a Q&A model needs to know. Pretraining captures the shared foundation.
The mechanics are beautifully simple:
from transformers import BertModel, BertTokenizer
# Load pretrained BERT (downloaded once, used for everything)
bert = BertModel.from_pretrained("bert-base-uncased")
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
# Add a task-specific head
classifier = nn.Linear(768, num_classes)
# Train on your task
for batch in dataloader:
outputs = bert(**batch)
cls_embedding = outputs.last_hidden_state[:, 0, :]
logits = classifier(cls_embedding)
loss = F.cross_entropy(logits, labels)
loss.backward()
optimizer.step()During fine-tuning, both BERT’s weights and the classifier head are updated. The pretrained knowledge isn’t frozen — it’s a starting point that adapts to the specific task.
Three forces combine:
1. Massive pretraining data. BERT was trained on Wikipedia + BookCorpus (~3.3 billion words). No downstream task has this much labeled data. Pretraining extracts knowledge from unlabeled text that fine-tuning couldn’t possibly learn.
2. Shared linguistic foundation. Every NLP task requires understanding grammar, vocabulary, and semantics. Pretraining captures this once, fine-tuning adapts it.
3. Small labeled datasets become sufficient. Because most of the model’s knowledge comes from pretraining, fine-tuning can succeed with just a few thousand labeled examples — sometimes even a few hundred.
Different tasks use different “heads” on top of BERT:
The backbone is always the same BERT. Only the head and the input format change.
BERT treats text as a sequence of word tokens. The transformer encoder processes the sequence bidirectionally.
Can we do the same for images?
The key insight: treat an image as a sequence of patches instead of pixels.
For a 224×224 RGB image:
The result: a sequence of 197 tokens (196 patches + [CLS]) that a transformer can process using the exact same machinery as BERT.
After the patch embedding step, ViT is architecturally identical to BERT:
The only differences are the input preprocessing (patches vs word embeddings) and the training objective (supervised classification vs MLM).
ViT-Base: 12 layers, 86M parameters. ViT-Large: 24 layers, 307M parameters. ViT-Huge: 32 layers, 632M parameters.
Same size categories as BERT. Same transformer recipe, different input.
Here’s where ViT gets interesting.
Trained on ImageNet (1.3M images): ViT underperforms comparable CNNs. ResNet-50 beats ViT-Base. Why?
CNNs have built-in priors that help with images:
ViT has none of these. It treats patches as an unordered set (until positional embeddings are added). It must learn the visual equivalents of these inductive biases from data.
With only 1.3M images, ViT doesn’t have enough data to learn these priors. CNNs win because their architecture provides the priors for free.
Trained on JFT-300M (300 million images): ViT matches or beats every CNN. Why?
At massive scale, the inductive biases baked into CNNs become a ceiling, not a floor.
CNNs are forced to extract features in a specific way (hierarchical, translation-equivariant). When you have enough data, this rigidity limits what the model can learn.
ViT has no such rigidity. With enough data, it learns more general, more flexible representations that outperform the hand-designed CNN inductive biases.
This has real consequences.
CNNs work for everyone. A research group with a few GPUs and a few million images can train a competitive CNN.
ViTs only work at massive scale. You need hundreds of millions of training images and enormous compute to get ViTs to beat CNNs.
Who has hundreds of millions of proprietary images? Google, Meta, OpenAI, Anthropic. A handful of companies.
Fortunately, you don’t need JFT-300M to use a ViT. Pretrained models are freely available:
from torchvision.models import vit_b_16, ViT_B_16_Weights
model = vit_b_16(weights=ViT_B_16_Weights.IMAGENET1K_V1)Or from HuggingFace:
You’ve seen LoRA before — low-rank updates to frozen pretrained weights, trained efficiently, merged at deployment.
For transformers in the HuggingFace ecosystem, the standard tool is the PEFT library (Parameter-Efficient Fine-Tuning).
PEFT wraps any model from transformers with LoRA adapters in a few lines:
from transformers import AutoModelForSequenceClassification
from peft import LoraConfig, get_peft_model
model = AutoModelForSequenceClassification.from_pretrained(
"bert-base-uncased", num_labels=2
)
lora_config = LoraConfig(
r=8, # rank of the low-rank decomposition
lora_alpha=16, # scaling factor for the update
target_modules=["query", "value"], # which weights to adapt
lora_dropout=0.1,
bias="none",
task_type="SEQ_CLS",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# trainable params: 295,682 || all params: 109,778,690 || trainable%: 0.27%Fine-tuning a 110M-parameter BERT with LoRA updates ~300K parameters. For a 70B-parameter Llama with LoRA, you update ~35M parameters — trainable on a single consumer GPU.
The target_modules parameter controls which weights get LoRA adapters. Different choices for different architectures:
For BERT and most encoder models:
Empirically, adapting Q and V is sufficient for most tasks. Adding K projections helps marginally.
For LLMs (Llama, GPT-style):
Newer practice adapts attention projections and MLP layers. More parameters, better performance on complex reasoning tasks.
For ViT:
We’ve spent the whole semester on representation learning. From PCA to CNNs to VAEs to BERT to ViT, the core question has always been:
How do we extract meaningful structure from unstructured data?
Encoder-only models are the current gold standard for this. The paradigm we’ve covered — massive self-supervised pretraining, lightweight task-specific adaptation — dominates every corner of applied ML that isn’t generation.
Before we talk about transformers taking over vision, let’s correct a common misconception.
CNNs are still extremely important. They haven’t been replaced — they’ve been supplemented.
Modern CNN architectures continue to improve and compete with ViTs on many tasks:
The lesson: architecture choice matters less than training protocol and scale. A well-trained CNN with modern techniques matches a well-trained ViT on most benchmarks.
CNNs have real practical advantages over pure ViTs:
Efficiency at small scale. CNNs’ inductive biases (locality, translation equivariance) mean they learn well from smaller datasets. If you have 10K images, use a CNN.
Mobile and edge deployment. CNN operations are highly optimized for mobile GPUs and NPUs. Modern phones can run EfficientNet in real-time; ViTs struggle without significant optimization.
Dense prediction tasks. Semantic segmentation, depth estimation, optical flow — tasks that need pixel-accurate outputs. CNNs’ spatial structure aligns naturally with these outputs. U-Net (CNN-based) remains the default for medical imaging.
Real-time video. Object detection and tracking in videos need to run at 30+ FPS on modest hardware. CNN detectors (YOLO series, particularly YOLOv8/v9/v10) dominate here.
Despite ViT’s dominance on web-scale datasets, LoRA fine-tuning of pretrained CNNs (especially ResNet variants) continues to win in specific domains:
Scientific imaging: ImageNet-pretrained ResNet-50 or ResNet-152, fine-tuned with LoRA, consistently outperforms ViT on:
Despite ViT’s dominance on web-scale datasets, LoRA fine-tuning of pretrained CNNs (especially ResNet variants) continues to win in specific domains:
Sports analytics: ResNet backbones dominate in:
The general rule: for specialized domains with modest data, LoRA-fine-tuned CNNs often beat ViTs. Don’t assume the latest architecture wins every task — check the domain literature before defaulting to a transformer.
The real state-of-the-art isn’t pure CNN or pure ViT. It’s hybrid architectures that combine both.
The intuition: convolutions are efficient at capturing local spatial patterns. Self-attention is efficient at capturing long-range dependencies. Use each where it works best.