BERT and the GPT Family

The transformer gave us an architecture. BERT and GPT gave us two answers to the question: what do you do with it before you have labeled data? The answers are structurally incompatible — not by accident, but because they reflect a fundamental choice about what the pretraining task should look like. Understanding that choice determines which family of models you reach for on any given problem.


1. BERT — Bidirectional Encoder Representations from Transformers

Paper: Devlin et al. (2018), arXiv:1810.04805
Architecture: Encoder-only transformer. BERT-base: 12 layers, 768 hidden, 12 heads, 110M parameters. BERT-large: 24 layers, 1024 hidden, 16 heads, 340M parameters.

The Masked Language Model Objective

BERT’s training task: take a sentence, randomly mask 15% of tokens, predict the masked tokens using the full remaining context.

Input:  "The [MASK] sat on the mat."
Target: "cat"

The crucial design decision: to predict [MASK], the model can see tokens on both sides. This forces bidirectional encoding — the representation of each token is built using full left and right context simultaneously. This is why BERT is encoder-only. An autoregressive decoder cannot do this; causal masking would prevent looking right.

The 15% masking breakdown (not random replacement — this is deliberate):

  • 80% of the time: replace with [MASK]

  • 10% of the time: replace with a random token

  • 10% of the time: keep the original token unchanged

Why the 10%/10% mix? During fine-tuning, the model never sees [MASK] tokens (they only exist in pretraining). Training 100% on [MASK] would create a distribution mismatch. The random and unchanged tokens force the model to maintain useful representations for all token positions, not just the masked ones.

Next Sentence Prediction (NSP) — and Why It Was Dropped

BERT also trained on a binary task: given sentence A and sentence B, predict whether B actually follows A in the original document (50% positive, 50% random negative).

RoBERTa finding (Liu et al., 2019, arXiv:1907.11692): NSP hurts downstream performance when removed. Hypothesis: NSP is too easy — the model learns topic correlation rather than discourse structure. RoBERTa dropped NSP, trained longer on more data with larger batches, and improved substantially on every GLUE benchmark. If you’re initializing from a checkpoint today, use RoBERTa-base or RoBERTa-large, not vanilla BERT.

Fine-Tuning BERT

BERT’s [CLS] token (prepended to every input) accumulates a summary representation of the full sequence. For classification:

Input: [CLS] sentence tokens [SEP]
Classification head: Linear(768 → num_classes) applied to [CLS] output

Fine-tune the entire model (not just the head) for best results. For BERT-base: 3 epochs, lr=2e-5, batch_size=32 is the standard starting point from the original paper.

BERT still wins in 2025 for:

  • Sentence embeddings (use sentence-transformers library)

  • Token classification: NER, POS tagging

  • Extractive question answering (predict start/end span)

  • Semantic textual similarity

  • Any task where you have full input available at inference time


2. GPT Family — Autoregressive Language Modeling

The GPT pretraining objective is simpler: predict the next token given all previous tokens.

Input:  "The cat sat on the"
Target: "mat"

This requires a decoder-only architecture with causal masking. The architectural constraint is what enables generation: given a prompt, you can sample the next token, append it, and repeat — producing text autoregressively.

GPT-1 (Radford et al., 2018)

12-layer decoder transformer, 117M parameters. First paper to demonstrate: pretrain on large text corpus with language modeling → fine-tune on downstream tasks. The fine-tuning paradigm worked. The model world noticed.

GPT-2 (Radford et al., 2019, arXiv:1501.09186)

1.5B parameters. Same architecture as GPT-1, scaled up. Key finding: at sufficient scale, the model can solve downstream tasks without any fine-tuning, just from the format of the prompt. This was the first clear signal of emergent few-shot capability. OpenAI initially delayed full release citing misuse concerns — a decision that aged as a PR gesture more than a technical barrier.

GPT-3 (Brown et al., 2020, arXiv:2005.14165)

175B parameters. The paper that made “prompting” a research area. Demonstrated that with enough parameters, few-shot performance (5-20 examples in the context window) on many tasks matched fine-tuned smaller models. This is the paper that shifted the field from “fine-tune for every task” to “prompt for every task.”

What GPT-3 actually showed: Language modeling at scale produces a model that implicitly learns to do many tasks because the pretraining data contains implicit demonstrations of them. The scaling law (Kaplan et al., 2020, arXiv:2001.08361) made this quantitatively predictable: loss decreases as a power law with compute, data, and parameters.

GPT-4 (OpenAI, 2023)

Architecture not published. Multimodal (vision + text input). Rumored to be a Mixture of Experts (MoE) architecture — multiple specialized sub-networks with a router selecting which expert handles each token. This would explain high capability with manageable inference cost per token.

Modern Decoder Architecture Defaults (2024)

Every current open-source LLM (LLaMA 3, Mistral, Gemma, Qwen) uses these departures from the original GPT:

Component

Original GPT

2024 Standard

Why

Positional encoding

Learned absolute

RoPE

True relative positions, long context

Normalization

Post-norm LayerNorm

Pre-norm RMSNorm

Training stability, fewer parameters

Activation

ReLU

SwiGLU

Better empirical performance

Attention

Multi-head (MHA)

Grouped Query Attention (GQA)

4-8x KV-cache memory reduction

GQA explained: Standard MHA has h query heads, h key heads, h value heads. GQA uses h query heads but only g key/value heads (g < h). At inference, the KV-cache (which stores keys and values for all previous tokens) is the memory bottleneck for long contexts. GQA reduces KV-cache size by h/g while maintaining most of MHA’s representational capacity. LLaMA 3 uses g=8 with h=32 — 4x KV-cache reduction.


3. BERT vs. GPT — The Structural Choice

Dimension

BERT (Encoder)

GPT (Decoder)

Attention

Bidirectional

Causal (left-to-right only)

Pretraining

Masked LM

Next-token prediction

Generation

Cannot generate

Native generation

Context

Full input available

Sequential only

Best for

Classification, NER, QA

Generation, chat, reasoning

2025 dominant use

Sentence embeddings, search

Chat, code, instruction following

This is not a quality difference — it’s a structural constraint. You cannot use BERT for text generation (it’s physically prevented by the architecture — there’s no causal mask and no autoregressive sampling mechanism). You can use GPT for classification but it’s suboptimal — you’re using a model trained to predict next tokens to instead predict a class label; embedding models trained contrastively are better for that.


4. Fine-tuning vs. Prompting — Honest Cost/Benefit

There is cargo-cult behavior around both approaches. Here is the actual analysis:

Approach

Compute Cost

Engineering Cost

Latency

When to Use

Zero-shot prompting

API call cost

Near-zero

API round-trip

Rapid prototyping, diverse one-off tasks

Few-shot prompting

Slightly higher input tokens

Low

API round-trip

5-20 examples fit in context, no infra

PEFT / LoRA

GPU hours (1 GPU, hours-days)

Medium

Own model serving

Consistent domain adaptation, proprietary data, cost control

Full fine-tuning

GPU hours (multi-GPU, days)

High

Own model serving

Maximum performance, strict data privacy, novel task structure

Continued pretraining

GPU days (multi-GPU)

Very high

Own model serving

Domain-specific vocabulary not in base model (medical, legal, code)

LoRA (Hu et al., 2021, arXiv:2106.09685): Freezes the base model, adds low-rank decomposition matrices ΔW = A·B (rank r, typically 4-64) to the attention projection layers. Only trains A and B — typically ~0.1-1% of original parameters. Merged at inference: no added latency. This is the standard PEFT method in 2025.


5. Hugging Face Transformers — When to Use What

from transformers import pipeline, AutoModel, AutoTokenizer, AutoModelForSequenceClassification

# pipeline() — use for demos, exploration, one-off tasks
classifier = pipeline("text-classification", model="distilbert-base-uncased-finetuned-sst-2-english")
result = classifier("This movie was genuinely excellent.")
# Returns: [{'label': 'POSITIVE', 'score': 0.9998}]

# AutoModel — use when you need embeddings or custom heads
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
model = AutoModel.from_pretrained("bert-base-uncased")

inputs = tokenizer("Hello world", return_tensors="pt")
outputs = model(**inputs)
# outputs.last_hidden_state: (batch, seq_len, 768)
# outputs.pooler_output: (batch, 768) — [CLS] representation

# AutoModelForSequenceClassification — use for fine-tuning classification
model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2)

pipeline() in production: Don’t. It hides the tokenization, batching, device placement, and half-precision details. Use it for exploration. In production, control all of those explicitly.


6. Code — Fine-Tune BERT on SST-2 Sentiment Classification

SST-2: Stanford Sentiment Treebank binary classification. 67K training examples. Baseline BERT-base-uncased accuracy: ~93%.

from datasets import load_dataset
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    TrainingArguments,
    Trainer
)
import numpy as np
from sklearn.metrics import accuracy_score

# ── Load data ─────────────────────────────────────────────────────────────────
dataset = load_dataset("glue", "sst2")
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

def tokenize(batch):
    return tokenizer(batch["sentence"], truncation=True, max_length=128, padding="max_length")

dataset = dataset.map(tokenize, batched=True)
dataset = dataset.rename_column("label", "labels")
dataset.set_format("torch", columns=["input_ids", "attention_mask", "labels"])

# ── Model ──────────────────────────────────────────────────────────────────────
model = AutoModelForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2)

# ── Metrics ────────────────────────────────────────────────────────────────────
def compute_metrics(eval_pred):
    logits, labels = eval_pred
    preds = np.argmax(logits, axis=-1)
    return {"accuracy": accuracy_score(labels, preds)}

# ── Training ───────────────────────────────────────────────────────────────────
training_args = TrainingArguments(
    output_dir="./bert-sst2",
    num_train_epochs=3,
    per_device_train_batch_size=32,
    per_device_eval_batch_size=64,
    learning_rate=2e-5,
    weight_decay=0.01,
    evaluation_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    report_to="none",
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset["train"],
    eval_dataset=dataset["validation"],
    compute_metrics=compute_metrics,
)

trainer.train()
# Expected: eval_accuracy ≈ 0.930-0.935 after epoch 3

Expected runtime: ~20 minutes on a single A100 / 40 minutes on an RTX 3090. On CPU: not recommended (hours).


What Most People Get Wrong

Using BERT for generative tasks — it physically cannot generate text. BERT produces a representation of your input; it does not produce a continuation of it. If you want generation, you need a decoder model.

Using GPT embeddings for semantic similarity — GPT-style models are trained to predict the next token, not to produce semantically meaningful embeddings. The last-token embedding of a GPT model is a reasonable representation but is dominated by next-token prediction artifacts. Use text-embedding-3-large (OpenAI), intfloat/e5-large-v2, or BAAI/bge-large-en-v1.5 for embedding tasks — these are trained with contrastive objectives specifically for similarity.

Treating HuggingFace accuracy as reproducible without pinning versionstransformers, datasets, accelerate, and tokenizers version combinations break in non-obvious ways. Pin all four in requirements.txt. Always.


Return to [README.md] · Next: [03_vision_transformers.md]