← All posts
RSS

Machine Learning · Model Compression

Knowledge Distillation
— a deep dive

How to compress the intelligence of a massive model into something lean, fast, and deployable — without losing what matters.

12 min read Concepts · Math · Code Level: Intermediate

01 — Fundamentals

What is Knowledge Distillation?

Knowledge distillation is a model compression technique in which a smaller, faster model — called the student — is trained to mimic the behaviour of a larger, more capable model — called the teacher. Rather than training the student from scratch on raw labels, the student learns from the outputs of the teacher, absorbing richer supervisory signal in the process.

"The student learns not just the right answers, but the teacher's uncertainty — and uncertainty is where all the knowledge lives."

The term was formalised by Geoffrey Hinton, Oriol Vinyals, and Jeff Dean in their landmark 2015 paper "Distilling the Knowledge in a Neural Network", though the intuition of using soft supervision predates it. Today, distillation has become indispensable across the entire deep learning stack — from compressing giant language models to shrinking vision transformers for mobile devices.

Distillation pipeline overview

🏛️
Teacher Model
Large, pretrained, expensive to run
🌡️
Soft Labels
Probability distributions over classes/tokens
⚖️
Distillation Loss
KL divergence + optional task loss
🎓
Student Model
Small, fast, production-ready

02 — Motivation

Why distillation matters

The deep learning field has long operated under a simple rule: bigger models generalise better. But a model that cannot be deployed is of limited use. Distillation breaks this trade-off by separating the cost of learning from the cost of inference.

The deployment problem

A model like GPT-4 or LLaMA-70B requires tens of gigabytes of VRAM and hundreds of milliseconds per forward pass. Edge devices, mobile apps, and real-time systems simply cannot afford this.

The compression trade-off

Naive approaches — pruning, quantisation — degrade quality. Distillation offers an alternative: train a new architecture that is inherently small, using the teacher as its curriculum.

Some celebrated results put the gains in concrete terms: DistilBERT (Hugging Face, 2019) is 40% smaller and 60% faster than BERT-base while retaining 97% of its performance on the GLUE benchmark. TinyBERT achieves 96.8% of BERT-large's performance at 7.5× less inference time. For LLMs, distilled models like Phi-2 and Gemma-2B demonstrate that well-distilled small models can outperform much larger models trained conventionally.

Model Params vs. Teacher Approach Status
DistilBERT 66M 60% faster, 40% smaller Response-based KD Production
TinyBERT 14.5M 7.5× faster, 96.8% perf Feature-based KD Production
Phi-2 2.7B Outperforms Llama-13B Data distillation Production
LLaMA-3 8B Instruct 8B Near 70B quality SFT on teacher outputs Production
MiniLM 22M 2.7× faster, 99% SBERT Attention transfer Research

03 — Mechanism

How distillation works

Standard supervised training gives a model only the hard label for each example — the correct class index, with probability 1, and zero for everything else. This is an impoverished signal. The teacher, by contrast, assigns a probability to every output in its vocabulary. These soft labels are extraordinarily information-dense.

Consider a classifier trained to distinguish cats from dogs. A hard label simply says "cat". But a soft label from a strong teacher might say "86% cat, 10% dog, 2% fox, 2% other". That small probability on "dog" carries a signal: this image shares visual features with dogs. The student, trained on soft labels, learns these nuanced relationships that would otherwise require many more examples to discover.

Step-by-step process

  1. 1

    Train (or obtain) a capable teacher

    The teacher is a large pretrained model — this could be GPT-4, LLaMA-70B, BERT-large, a ViT-H/14, or any model you have access to. It must already perform well on your target task. You never backpropagate through the teacher.

  2. 2

    Generate soft labels from teacher outputs

    Run your training data through the frozen teacher. Save the logits (pre-softmax activations) or the full probability distribution over the output space. For LLMs this is the per-token next-token distribution; for classifiers, it is the class probabilities.

  3. 3

    Apply temperature scaling

    Divide the teacher's logits by a temperature parameter T > 1 before applying softmax. This softens the distribution — making low-probability outputs more visible and giving the student richer gradient signal during training.

  4. 4

    Train the student with distillation loss

    The student simultaneously minimises two objectives: a soft loss (KL divergence from teacher's softened distribution) and optionally a hard loss (cross-entropy with ground-truth labels). A hyperparameter α balances them.

  5. 5

    Evaluate and iterate

    Evaluate the student on your held-out benchmark. Tune temperature T and the α weighting. Optionally apply additional compression (quantisation, pruning) on top of the distilled student.

Note on offline vs. online distillation

In offline distillation, teacher logits are pre-computed and cached to disk — this is the most common setup and works well. In online distillation, teacher and student run in tandem during training, which is more expensive but allows dynamic data augmentation strategies like DML (Deep Mutual Learning).


04 — Core Concept

Soft labels & temperature scaling

Temperature is the most important hyperparameter in distillation. Given teacher logits z, the softened probability for class i is:

Softmax with temperature T

pi = exp(zi / T) / Σj exp(zj / T)

At T = 1, this is the standard softmax — the teacher's original output. At T > 1, the distribution flattens, revealing probability mass on low-probability classes. At T → ∞, the distribution becomes uniform. Typical values are T = 2, 3, 4, or up to 20 for very deep distillation.

Effect of temperature on class distribution (example: text classification)

T = 1 (hard distribution)

Class A
0.920
Class B
0.060
Class C
0.015
Class D
0.005

T = 4 (soft distribution)

Class A
0.480
Class B
0.300
Class C
0.140
Class D
0.080

Notice that at T = 4, Class B has gone from 6% to 30%. This is the crucial signal: the teacher is telling the student that this example has significant similarity to Class B examples. Without temperature scaling, this signal would be nearly invisible in the gradient. This is why Hinton et al. described soft labels as carrying "dark knowledge" — information embedded in the near-zero probabilities that a model assigns to incorrect classes.

Important: rescale the loss

When computing the soft loss, you must multiply by T² to compensate for the smaller gradient magnitudes produced by the softened distribution. Forgetting this is a common bug — your soft loss will contribute negligible gradient relative to the hard loss.


05 — Mathematics

The distillation loss function

The total training objective for the student combines two terms: a soft distillation loss (alignment with teacher) and a hard task loss (alignment with ground-truth labels).

Total distillation loss

Ltotal = α · T² · KL(pT ‖ pS) + (1 − α) · H(y, pS)

where:
pT = softmax(zteacher / T) — teacher soft probabilities
pS = softmax(zstudent / T) — student soft probabilities
y = ground-truth one-hot label
α = mixing coefficient ∈ [0, 1]
T = temperature (same used for both teacher and student)

When α = 1, the student trains entirely on soft labels — useful when ground-truth labels are noisy or when you fully trust the teacher. When α = 0, you fall back to standard training. In practice, α = 0.7–0.9 works well for most tasks.

Why KL divergence?

KL divergence measures how much the student's distribution diverges from the teacher's. Unlike cross-entropy against a hard target, it accounts for all the probability mass the teacher assigns across every class — penalising the student not just for getting the top class wrong, but for misaligning with the entire distribution. This is exactly why it carries more signal.

Practical tuning guide

Start with T = 3–4 and α = 0.8. For LLMs with SFT distillation, you often omit the hard loss entirely (α = 1). Increase T when you suspect your teacher has meaningful "dark knowledge" across many classes/tokens. If your student is very small relative to the teacher, higher T helps prevent distribution collapse.


06 — Taxonomy

Types of distillation

The original Hinton et al. formulation is called response-based distillation because the student learns only from the teacher's output layer. The field has since developed several richer variants.

Type What the student learns from Pros Cons
Response-based Final output logits / probabilities Simple Works offline Ignores intermediate representations
Feature-based Intermediate hidden states / activations Richer signal, better for deep models Requires architectural alignment or projections
Relation-based Pairwise relationships between examples Architecture-agnostic, flexible Computationally heavier
Attention transfer Attention maps from transformer layers Strong for transformer-to-transformer Limited to attention-based architectures
Data distillation Synthetic teacher-generated training data No model-level access needed to teacher Depends on teacher output quality; high volume needed
Self-distillation Earlier checkpoints or shallower layers of the same model No separate teacher needed Bounded by original model ceiling

Feature-based distillation in transformers

For LLMs and vision transformers, feature-based distillation is particularly powerful. The student minimises the MSE or cosine distance between its hidden states and the teacher's, at matched layer pairs. Since the teacher and student may have different dimensions, a trainable linear projection (sometimes called a hint layer) maps student features into the teacher's space before comparison.

Data distillation for LLMs

A practically important special case: use a capable API model (e.g. GPT-4, Claude) to generate a high-quality synthetic dataset, then fine-tune a smaller open model on it. This is effectively data distillation — you are distilling the teacher's knowledge into the training distribution itself, not the model weights. This approach powers many of the "open-source" instruction-following models in production today.

Key insight for LLMs

For large language models, the most practically effective distillation is often supervised fine-tuning on teacher-generated outputs (sequence-level KD). Generate completions from the teacher on a curated prompt set, then SFT the student. This requires no access to teacher logits — only final text outputs — making it compatible with closed API models.


07 — Ecosystem

Tools & frameworks

The distillation ecosystem has matured rapidly. Here are the primary tools practitioners reach for, depending on their use case.

Hugging Face

Transformers + TRL

The primary stack for LLM distillation. TRL (Transformer Reinforcement Learning) provides SFTTrainer with built-in distillation loss support.

  • SFT-based data distillation
  • KD loss via GKDTrainer
  • Offline logit caching
  • PEFT/LoRA integration
PyTorch

Native PyTorch KD

Full control. Implement the KL divergence loss manually for custom architectures or research experimentation.

  • nn.KLDivLoss built-in
  • Arbitrary feature matching
  • Hooks for intermediate states
  • Best for research / novel architectures
Neural Magic

LLM Compressor

Combines distillation with quantisation and sparsity in one pipeline. Purpose-built for production LLM compression.

  • SparseGPT + distillation combo
  • GPTQ quantisation-aware KD
  • vLLM-compatible outputs
  • CLI-first workflow
LLaMA Factory

LLaMA-Factory

All-in-one fine-tuning framework with built-in KD loss. Supports every major open LLM family and distillation modes.

  • Response-based KD via logits
  • LoRA distillation support
  • Multi-GPU with DeepSpeed
  • YAML config-driven
OpenAI

Distillation API

OpenAI's platform supports teacher-student distillation natively — store GPT-4o completions and fine-tune GPT-4o-mini on them directly in the dashboard.

  • No infra required
  • Stored completions → training data
  • Logprob distillation support
  • Best for OpenAI ecosystem users
Research

MiniLLM / DistiLLM

Academic implementations of advanced KD objectives for LLMs — reverse KL, distributional matching, and sequence-level distillation losses.

  • Reverse KL (mode-seeking)
  • Sequence-level KD (SeqKD)
  • Best for novel loss research
  • Papers with code available

08 — Hands-on Example

Full code walkthrough

Below is a complete, runnable distillation pipeline using PyTorch and Hugging Face Transformers. We distil BERT-base (teacher, 110M params) into a tiny 4-layer BERT (student, ~30M params) on a text classification task. The same pattern extends directly to decoder-only LLMs.

1. Setup and imports

distill_setup.py
Python
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    BertConfig,
    BertForSequenceClassification,
    get_linear_schedule_with_warmup,
)
from datasets import load_dataset

DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
NUM_LABELS = 2        # e.g. SST-2 sentiment classification
TEMPERATURE = 4.0     # Hinton et al. recommend 3–5 for classification
ALPHA = 0.8           # weight on soft (distillation) loss
HARD_LOSS_ALPHA = 0.2 # weight on hard (CE) loss; must sum to 1
EPOCHS = 3
BATCH_SIZE = 32
LR = 3e-5

2. Load teacher and build student

distill_models.py
Python
TEACHER_NAME = "textattack/bert-base-uncased-SST-2"

# ── Teacher: load a fine-tuned BERT-base ──────────────────────────────────────
teacher = AutoModelForSequenceClassification.from_pretrained(TEACHER_NAME)
teacher = teacher.to(DEVICE)
teacher.eval()  # freeze; no gradients needed
for param in teacher.parameters():
    param.requires_grad = False

# ── Student: a tiny BERT with only 4 layers ───────────────────────────────────
student_config = BertConfig(
    vocab_size=teacher.config.vocab_size,
    hidden_size=256,          # 768 in BERT-base → 256 (3× smaller)
    num_hidden_layers=4,       # 12 in BERT-base → 4
    num_attention_heads=4,
    intermediate_size=1024,
    num_labels=NUM_LABELS,
)
student = BertForSequenceClassification(student_config).to(DEVICE)

# ── Shared tokenizer ───────────────────────────────────────────────────────────
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

print(f"Teacher params: {sum(p.numel() for p in teacher.parameters()):,}")
print(f"Student params: {sum(p.numel() for p in student.parameters()):,}")

3. Distillation loss function

distill_loss.py
Python
def distillation_loss(
    student_logits: torch.Tensor,
    teacher_logits: torch.Tensor,
    hard_labels: torch.Tensor,
    temperature: float = TEMPERATURE,
    alpha: float = ALPHA,
) -> torch.Tensor:
    """
    Combined distillation + task loss.

    Args:
        student_logits: raw logits from student  [batch, num_classes]
        teacher_logits: raw logits from teacher  [batch, num_classes]
        hard_labels:    ground-truth class ids   [batch]
        temperature:    softening temperature T
        alpha:          weight on soft (distillation) loss

    Returns:
        Scalar loss tensor.
    """
    # Soft probabilities — apply temperature to BOTH teacher and student
    teacher_probs = F.softmax(teacher_logits / temperature, dim=-1)
    student_log_probs = F.log_softmax(student_logits / temperature, dim=-1)

    # KL divergence: KL(teacher ‖ student)
    # F.kl_div expects (log_input, target); output is mean over batch
    soft_loss = F.kl_div(
        student_log_probs,
        teacher_probs,
        reduction="batchmean",
    )

    # Rescale by T² to preserve gradient magnitude (Hinton et al.)
    soft_loss = soft_loss * (temperature ** 2)

    # Hard cross-entropy loss against ground-truth labels
    hard_loss = F.cross_entropy(student_logits, hard_labels)

    # Combine
    total_loss = alpha * soft_loss + (1.0 - alpha) * hard_loss
    return total_loss

4. Training loop

distill_train.py
Python
# ── Dataset ────────────────────────────────────────────────────────────────────
dataset = load_dataset("glue", "sst2")

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

train_dataset = dataset["train"].map(tokenize, batched=True)
train_dataset.set_format("torch", columns=["input_ids", "attention_mask", "label"])
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)

# ── Optimizer & scheduler ──────────────────────────────────────────────────────
optimizer = torch.optim.AdamW(student.parameters(), lr=LR, weight_decay=0.01)
total_steps = len(train_loader) * EPOCHS
scheduler = get_linear_schedule_with_warmup(
    optimizer,
    num_warmup_steps=total_steps // 10,
    num_training_steps=total_steps,
)

# ── Training ───────────────────────────────────────────────────────────────────
student.train()
for epoch in range(EPOCHS):
    running_loss = 0.0
    for step, batch in enumerate(train_loader):
        input_ids      = batch["input_ids"].to(DEVICE)
        attention_mask = batch["attention_mask"].to(DEVICE)
        labels         = batch["label"].to(DEVICE)

        # Get teacher logits (no grad)
        with torch.no_grad():
            teacher_out = teacher(
                input_ids=input_ids,
                attention_mask=attention_mask,
            )
            teacher_logits = teacher_out.logits

        # Get student logits
        student_out = student(
            input_ids=input_ids,
            attention_mask=attention_mask,
        )
        student_logits = student_out.logits

        # Compute combined loss
        loss = distillation_loss(
            student_logits=student_logits,
            teacher_logits=teacher_logits,
            hard_labels=labels,
            temperature=TEMPERATURE,
            alpha=ALPHA,
        )

        optimizer.zero_grad()
        loss.backward()
        torch.nn.utils.clip_grad_norm_(student.parameters(), 1.0)
        optimizer.step()
        scheduler.step()

        running_loss += loss.item()
        if step % 100 == 0:
            print(f"Epoch {epoch+1} | Step {step} | Loss: {loss.item():.4f}")

    avg = running_loss / len(train_loader)
    print(f"\n── Epoch {epoch+1} complete. Avg loss: {avg:.4f} ──\n")

# ── Save ───────────────────────────────────────────────────────────────────────
student.save_pretrained("./distilled-tiny-bert")
tokenizer.save_pretrained("./distilled-tiny-bert")
print("✓ Distilled student saved to ./distilled-tiny-bert")

5. Feature-based distillation bonus (intermediate layers)

distill_feature.py
Python
class HintLayer(nn.Module):
    """Projects student hidden states → teacher hidden dim."""
    def __init__(self, student_dim: int, teacher_dim: int):
        super().__init__()
        self.proj = nn.Linear(student_dim, teacher_dim, bias=False)

    def forward(self, x):
        return self.proj(x)

# One hint layer per matched pair: student layer 2 → teacher layer 6
hint = HintLayer(
    student_dim=student_config.hidden_size,   # 256
    teacher_dim=teacher.config.hidden_size,   # 768
).to(DEVICE)

def feature_distillation_loss(
    student_hidden: torch.Tensor,  # [batch, seq_len, 256]
    teacher_hidden: torch.Tensor,  # [batch, seq_len, 768]
) -> torch.Tensor:
    """MSE loss between projected student states and teacher states."""
    projected = hint(student_hidden)  # [batch, seq_len, 768]
    return F.mse_loss(projected, teacher_hidden.detach())

# ── In your training loop, add to total loss: ──────────────────────────────────
# student_out = student(..., output_hidden_states=True)
# teacher_out = teacher(..., output_hidden_states=True)
#
# feat_loss = feature_distillation_loss(
#     student_hidden=student_out.hidden_states[2],   # student layer 2
#     teacher_hidden=teacher_out.hidden_states[6],   # teacher layer 6
# )
# loss = response_loss + 0.1 * feat_loss

LLM distillation with Hugging Face TRL (GKDTrainer)

For decoder-only LLMs (LLaMA, Mistral, Qwen etc.), the recommended approach is Hugging Face TRL's GKDTrainer (Generalized Knowledge Distillation), which handles all the temperature scaling, logit caching, and loss mixing automatically.

gkd_llm.py
Python
from trl import GKDConfig, GKDTrainer
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset

# ── Load teacher and student ───────────────────────────────────────────────────
teacher = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Meta-Llama-3-8B-Instruct",
    torch_dtype=torch.bfloat16,
    device_map="auto",
)
student = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.2-1B-Instruct",  # student is 1B
    torch_dtype=torch.bfloat16,
    device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-1B-Instruct")

# ── Dataset ────────────────────────────────────────────────────────────────────
dataset = load_dataset("HuggingFaceH4/ultrachat_200k", split="train_sft[:10000]")

# ── GKD config ────────────────────────────────────────────────────────────────
training_args = GKDConfig(
    output_dir="./llama-1b-distilled",
    num_train_epochs=1,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,
    learning_rate=2e-5,
    bf16=True,
    temperature=2.0,           # T for soft label computation
    lmbda=0.5,                 # λ = weight on distillation loss (vs. SFT loss)
    seq_kd=False,              # True = use sequence-level KD (teacher generates)
    max_seq_length=1024,
    logging_steps=10,
    save_strategy="epoch",
)

# ── Trainer ───────────────────────────────────────────────────────────────────
trainer = GKDTrainer(
    model=student,
    teacher_model=teacher,
    args=training_args,
    tokenizer=tokenizer,
    train_dataset=dataset,
)

trainer.train()
trainer.save_model("./llama-1b-distilled")

Running this

Install with pip install trl transformers datasets accelerate torch. For the LLM example, you'll need a GPU with at least 24GB VRAM for the 1B student + 8B teacher simultaneously, or use PEFT/LoRA on the student to reduce memory footprint. Add load_in_4bit=True for the teacher to run on 16GB VRAM.


09 — Best Practices

Practical tips from the trenches

Match capacity to task

A student that is too small to learn the teacher's distribution will plateau early. A common rule of thumb: student should have at least 10–20% of the teacher's parameters for response distillation to be effective.

Augment with unlabelled data

You can use any in-domain text to generate teacher soft labels — you don't need ground truth. This is often called "semi-supervised distillation" and can dramatically improve the student with minimal label cost.

Cache teacher logits

Running the teacher on every batch at training time is expensive. Pre-compute and save teacher logits to disk (h5py or numpy memmap), then load them during student training. Saves 50–80% of compute.

Layer mapping matters

For feature-based distillation, align layers by relative depth: student layer k/K ↔ teacher layer k/K. Misaligned layers (e.g. student's last layer against teacher's first) add noise rather than signal.

Try different objectives

For LLMs, reverse KL (mode-seeking) often outperforms forward KL for chat tasks because it avoids diffuse generation. The MiniLLM paper provides a good comparison across divergence choices.

Distill then quantise

Stack techniques: distil first for a well-trained small model, then apply GPTQ or AWQ quantisation. The distilled model often quantises more gracefully than a model trained from scratch, likely due to smoother loss landscapes.

Common pitfalls

(1) Forgetting the T² rescaling on the soft loss — causes the hard loss to dominate.   (2) Using T at evaluation time — temperature is only for training; deploy with T = 1.   (3) Distilling on out-of-distribution data — the teacher's soft labels are only informative if the data is drawn from the same distribution the teacher was trained on.   (4) Assuming the teacher is always right — noisy teacher outputs (e.g. hallucinations in LLMs) can mislead the student; consider filtering by teacher confidence.