TravisML Distilling Generative Models
GitHub ↗

TravisML

Distilling Generative Models

Knowledge Distillation for Language Models, from the Objective to a Reproducible Study

Travis Lelle

Copyright TravisML.ai 2026. All rights reserved.

First edition, August 2026. Companion to the thirteen-lab course Distillation of Generative Models.

Every measured number in this book came out of an execution. Every citation was verified against its source. Errors that remain are mine.

Preface

I built the course this book accompanies because I wanted to be comfortable with distillation, and reading about it was not producing comfort.

The literature is good. Hinton’s paper is short and clear, the GKD paper is careful, and the surveys are thorough. But a paper tells you what worked in someone else’s setting, and it compresses out everything that made the result hard to obtain. It does not tell you that the temperature-squared factor is missing from a third of the implementations you will read. It does not tell you that your loss curve will descend beautifully while training on a mask that is off by one position. It does not tell you that the parameter your library calls beta is documented backwards in more places than it is documented correctly, including, for a while, in my own code.

So I built thirteen laboratory notebooks that assert every claim they make, and worked through them. This book is what I wish I had been reading alongside them.

What this book is#

It is a textbook on knowledge distillation for generative language models, written for someone who can read PyTorch and has trained a transformer, and who knows nothing beyond that. Everything else is built here: the information theory, the floating point, the serving arithmetic, the reinforcement learning vocabulary that the on-policy methods borrow. I define every term of art at its first use and I do not assume folklore.

It is organized around building blocks rather than around methods. The first part is foundations: what a token distribution is, how the numbers that hold it behave, how to measure the distance between two of them, and how to estimate that distance when you cannot see both. The second part is the objective itself. The third part makes it real, which means tokenizers, a first run, the economics, and the pipeline I would actually build first. The fourth part is the method space. The fifth is judgment: serving, evaluation, security, and how to turn a question into a study another person could rerun.

Each chapter climbs. It starts at a level that presumes nothing and ends at a level where the open questions are visible. That means some chapters are long. The instruction I gave myself was not to be stingy with explanation, and I have kept to it.

What this book is not#

It is not a survey. Gou and colleagues wrote a good one for the general case and Xu and colleagues wrote a good one for language models, and both are cited where they belong. This book covers fewer methods in far more depth, and it is opinionated about which ones are worth your time on a single workstation.

It is not a reproduction of the labs. The labs execute; the book explains and derives. Where the same fact appears in both, the lab asserts it mechanically and the book says why it is true. I have tried hard not to duplicate a single paragraph between the two.

And it is not neutral about hardware. Every number here that depends on a machine refers to one machine: a workstation with 128 GB of unified memory at roughly 273 GB/s. Distillation advice that does not name its hardware is close to useless, because the ordering of methods by cost inverts between hardware classes. Appendix B gives you the four numbers you need to redo every calculation in the book for whatever you have.

On the numbers, and on being wrong in public#

Every measured number in this book came out of an execution. Where a number is a prediction rather than a measurement, the text says so, and the figures draw predictions differently from measurements. Where a course notebook made a claim and the data refused it, I have kept the refusal and the diagnosis rather than quietly rewriting the claim. There are four of those in here. They are among the most useful pages in the book, because the shape of being wrong is more transferable than the shape of being right.

The same standard applies to citations. Every reference in the bibliography was verified against its source: the identifier resolves, the title matches, and where a paper has since been published in a venue, the venue is given. Three of the works I lean on are unrefereed preprints, and the text says so at the point where it uses them rather than only in a footnote.

How to read it#

Straight through, if you want the arc. The chapters are ordered by what they ask of you, and each one uses the previous ones.

By part, if you have a specific problem. Parts III and IV are close to self-contained if you have Part I and Chapter 5 under your belt.

Alongside the labs, if you want to come out of it able to do the work rather than discuss it. Appendix E maps every chapter to the notebooks and exercises that make it concrete, in both directions. The order I would suggest is chapter, then lab, then back to the chapter’s exercises, which are written to be answerable only after you have run the thing. One convention travels with the notebooks and is easy to lose: every one of them sets HF_HUB_DISABLE_PROGRESS_BARS=1 in its header, because widget progress bars crash some notebook stacks and the plain log lines carry the same information. If you lift a cell out of a lab and into your own environment, take that line with it.

The exercises are thinking problems, not coding problems. The labs own the coding. Several of them ask you to make a prediction before reading a result, and those are worth doing honestly, because the gap between what you expected and what happened is the only reliable measure of what you have actually understood.

Travis Lelle August 2026

Part I · Foundations

1

What Distillation Is, and What It Is Not

You have a model that works and a constraint that says you cannot ship it.

The constraint is usually one of four things. The model costs too much per token to serve at the volume you need. It does not fit on the hardware where the work has to happen, which might be a phone, a car, a laptop, or a rack you already paid for. It is too slow, because a user waiting on a response has a shorter attention span than a benchmark does. Or it belongs to someone else, and you are renting access to it through an API on terms that could change.

In every one of those cases you want a smaller model that behaves like the larger one on the work you actually care about. Distillation is the family of techniques for getting one.

The name comes from Hinton, Vinyals, and Dean’s 2015 paper, which framed it as extracting the useful structure out of a large model the way you extract a spirit from a mash.1 The idea is older than the name. Buciluǎ, Caruana, and Niculescu-Mizil compressed an ensemble of classifiers into a single neural network in 2006 by having the small model fit the ensemble’s predictions on a large pool of unlabeled data.2 What Hinton’s paper added was the observation that the large model’s probabilities, not just its decisions, carry information the small model can use, and a specific mechanism for exposing that information. That observation is still the center of the subject twenty years later, and Chapter 5 spends most of its length on it.

What has changed since 2015 is scale and stakes. When the teacher is a 70-billion-parameter language model and the student is a 1.7-billion-parameter one, the practical questions stop being about a loss function and start being about which of the two models is generating text at any given moment, how many bytes per second your memory bus can move, and whether the corpus you trained on has quietly contaminated the benchmark you are about to report. Most of this book is about those questions. But you cannot answer them without first being precise about what distillation is, so that is what this chapter does.

1.1 The setup, stated once#

There are two models. The teacher is fixed: you do not train it, and often you cannot, because you do not have its weights. The student is trained. Somewhere there is a set of inputs, and for each input the teacher produces something the student is asked to reproduce.

Definition

Teacher

The model whose behavior is being transferred. Fixed during distillation. Usually larger than the student, though not always, and not necessarily accessible beyond its outputs.

Definition

Student

The model being trained. Its parameters are the only ones that change. Its architecture, size, and tokenizer are all design choices, and Chapters 13 and 14 are about what those choices cost.

The whole design space of distillation falls out of three questions about that picture. Get all three in view before any of them gets an answer.

What does the teacher produce that the student has to match? The candidates run from a single sampled token at the cheap end to the full probability distribution at every position at the expensive end, with truncated distributions, whole generated sequences, and internal activations from the middle of the network in between. Where you land on that range fixes two things at once: how much information flows per training example, and how much it costs to obtain.

Where do the inputs come from? Either from a fixed corpus you already have, or from text the teacher generated, or from text the student generated and the teacher scored after the fact. The last of those is on-policy training and the other two are off-policy, a distinction borrowed from reinforcement learning that matters more than most people expect. Chapter 12 is about it.

How is the mismatch measured? Cross-entropy against a hard label is one answer. A divergence between two full distributions is another, and there is a whole family of those with genuinely different behavior. Chapters 3 and 6 handle this.

Everything else in the subject is a combination of answers to those three questions, plus engineering to make the combination affordable.

1.2 What “knowledge” means here#

The word “knowledge” in “knowledge distillation” is doing real work, and the reason distillation works at all is not obvious, so pin the word down first.

Suppose you have a corpus of text with known continuations. You could train the small model directly on that corpus with ordinary next-token cross-entropy, no teacher involved. That is just language model training. Why would routing the same corpus through a teacher first do anything?

The answer Hinton gave is that the teacher’s probabilities on the wrong continuations carry information that the correct continuation alone does not. Consider a position in a sentence where the true next token is cat. A hard label says: cat is correct, everything else is equally incorrect. The teacher says something much richer. It might put 0.62 on cat, 0.19 on dog, 0.08 on kitten, 0.03 on pet, and spread the remaining 0.08 across forty thousand other tokens including 4e-7 on refrigerator. The ratios among those wrong answers encode a similarity structure the teacher learned: that dog is a plausible substitute here and refrigerator is not, that kitten is close to cat in a way pet is not quite.

Definition

Dark knowledge

The information carried by a teacher’s probabilities on the incorrect outputs. A hard label assigns zero to all of them and therefore says nothing about how they relate to each other; the teacher’s relative probabilities among wrong answers encode a learned similarity structure. The term is Hinton’s.

That is the intuition, and it is a good one. It is also not the whole story, and one of the things this book will keep doing is telling you where the standard intuition stops being reliable. The places it stops are complicated enough to need the rest of the book, so here they are in outline, now, rather than in a section at the end.

The dark knowledge lives in very small numbers. A probability of 4e-7 contributes almost nothing to a cross-entropy loss dominated by a term of size 0.62. Hinton’s fix is temperature, which flattens the distribution before the loss is computed and therefore amplifies the small values relative to the large ones. Temperature is not a cosmetic knob; without it the mechanism does not fire. Chapter 5 derives exactly what it does, including the factor of that has to accompany it and that gets left out of implementations often enough to be worth its own warning.

Distillation frequently fails to make the student match the teacher, and helps anyway. Stanton and colleagues measured this directly: students trained with distillation often have worse agreement with their teacher on held-out data than you would expect from their improved generalization, and improving the optimization does not close the gap.3 Whatever distillation is buying, “the student becomes a copy of the teacher” is an incomplete description of it. Some of the benefit looks more like a regularization effect from training against a smooth target than like information transfer.

A better teacher is not always a better teacher. Past some gap in capacity, a larger teacher produces a worse student than a smaller one would have. Cho and Hariharan showed this on image classifiers and traced it to the student being unable to fit the larger teacher’s function at all, so the distillation loss stops providing useful gradient.4 Chapter 5 covers the competing explanations and what the course’s own capacity-gap probe measures.

I am putting these up front rather than in a “limitations” section at the end because the sequencing matters pedagogically. If you learn the clean story first and the complications later, you will spend a period confidently applying a model of the world that is wrong in ways you cannot see. Better to know from the first chapter that the clean story is a first approximation.

1.3 Where this came from#

The subject has a lineage, and it is worth walking it once, because every step in it was solving a different problem and the shape of current practice is the residue of all of them. There is also a pattern running underneath the usual retelling. Each era assumed a particular level of access to the teacher, mostly without saying so, and the eras where the field moved fastest are the ones where that unstated assumption happened to match what practitioners actually had.

2006: an ensemble you cannot ship. The winning entry in a machine learning competition in the mid-2000s was reliably an ensemble: dozens or hundreds of separately trained models whose predictions are averaged. Ensembles win because averaging cancels errors that individual models make independently of each other. They are also close to unshippable, since serving one means running every member for every request and storing one means storing all of them.

Buciluǎ, Caruana, and Niculescu-Mizil asked whether the accuracy was intrinsic to the size. Their answer was no. The function an ensemble computes is usually not complicated enough to require hundreds of models to express; a single network of modest size has the capacity to represent it. What the single network lacks is a way to find it, because the original labeled training set is small and noisy and ordinary supervised training on it lands somewhere worse. So they used the ensemble as a labeling machine: run it over a large pool of inputs, record what it says, train the single network on that. Where no pool of unlabeled inputs was available, they manufactured one by perturbing the data they had, which is the ancestor of every synthetic corpus in this book.

Hold onto two things from that paragraph. The bottleneck they identified was the training signal rather than the model class, and that diagnosis is still the one the subject rests on. And they needed to run the ensemble as often as they liked, on inputs of their choosing. That is the access assumption the field inherited without stating it, and it took fifteen years for it to become the binding constraint.

2015: probabilities instead of decisions. What the 2006 work transferred was the ensemble’s output in the ordinary sense: the predicted label, or the predicted score. By 2015 the models had changed. Deep networks with softmax heads over hundreds or thousands of classes were standard, and Hinton, Vinyals, and Dean noticed that the output of such a model is not a decision but a distribution, and that nearly all of the distribution lives in the entries nobody looks at.

The reframing did two things. It changed the object being transferred from a function’s decisions to a distribution’s shape, which is the content of §1.2. And it supplied a mechanism for making that shape visible to a loss function, because at a confident classifier’s wrong-answer probabilities are so small that they contribute almost nothing to a gradient. The mechanism matters as much as the observation. Without temperature the information sits in the target and stays invisible to the optimizer, and a reader who takes away “use soft labels” and nothing else will build something that does not work.

Notice the precondition. In a two-class problem there is one wrong answer, and one wrong answer has no internal structure, because there is nothing for it to be relative to. It is the thousand-way softmax of the image classification era, and later the fifty-thousand-way softmax of a tokenizer, that makes the wrong answers worth transmitting at all.

2014 to 2019: everything else the teacher knows. Once the target was a distribution, the obvious next question was whether the output was the only thing worth copying. A trained network is a stack of representations, and the final distribution is a lossy summary of the last one. FitNets asked the student to match an intermediate activation of the teacher through a learned projection, under the name “hints,” and the reason it worked was that a thin deep student got a training signal at its middle rather than only at its end. Attention transfer changed the target again, from what the network computes to where it looks, on the argument that attention maps are indexed by position rather than by channel and therefore compare across networks of different widths.20 Relational methods changed it a third time, to the geometry among examples rather than the examples themselves.

Every one of those methods requires the teacher’s weights and the ability to read out its insides. The vision era took that for granted, because vision checkpoints were files you downloaded, and it is worth registering how much of the classical literature is white-box work assuming a world where the teacher belongs to you.

2019: the recipe arrives in language. Transformer encoders got good and immediately got expensive to serve at web volume, which recreated the 2006 problem with different models. DistilBERT is the compact answer: initialize the student from every other layer of the teacher, train on the teacher’s output distribution plus a term that aligns the two models’ embeddings, and keep most of the quality at roughly half the depth.21 TinyBERT is the maximal answer: match embeddings, hidden states through learned projections, attention matrices, and predictions, in two stages. The pair is instructive because the simple recipe does not lose to the elaborate one by much, and Chapter 14 returns to that comparison with the machinery to make it fairly.

2016, on a separate track: sequences. Everything above concerns a model that emits one distribution per input. A translation model emits a distribution over sequences, and the set of sequences is exponentially large, so the object you would like to match cannot be written down. Kim and Rush’s move was to approximate that intractable object with a single sample: take the teacher’s most likely output sequence, treat it as if it were training data, and fit the student to it with ordinary cross-entropy. The approximation is crude, the results were good, and the method has a property nobody was optimizing for at the time. It needs nothing from the teacher but text.

2023 onward: the generative era. Three things changed at once, and their interaction is why current practice looks different from the classical literature rather than like a scaled-up version of it. Teachers got large enough that generating a corpus with one is the dominant cost of a project rather than a preprocessing step, which inverts the usual ordering of what is expensive. Teachers moved behind APIs, so access stopped being an assumption and became a constraint that varies by vendor and by quarter. And the output moved from a label to a long generation judged by how it reads, which means the quantity you optimize and the quantity you are graded on stopped being the same quantity.

The methods track those changes. MiniLLM argued that a student too small to cover everything the teacher can say should not be trained on an objective that asks it to try, and replaced the forward divergence with the reverse one, which is Chapter 6’s subject.22 Generalized knowledge distillation moved the training inputs onto the student’s own generations, which is §1.4.4 and Chapter 12. DeepSeek’s R1 report went the other way entirely and distilled a series of small models with supervised fine-tuning on teacher-generated reasoning traces and nothing else, a method Kim and Rush would recognize on sight, and the resulting models were strong enough that the simple method is now the reference point rather than the fallback. Meanwhile the cross-tokenizer problem arrived, because a practitioner who wants a specific small model and a specific large one frequently cannot have them share a vocabulary; Universal Logit Distillation is the current answer and Chapter 14 covers what it gives up to get there.23

2026. On-policy methods are the active area, active enough to have accumulated a survey of their own, although that survey describes itself as ongoing work rather than a settled account.24 The honest one-sentence summary of twenty years is that the object of study moved from “compress a classifier” to “transfer a behavior,” and the binding constraint moved from compute to access.

2026-08-01T09:02:42.732338 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 2005 2007 2009 2011 2013 2015 2017 2019 2021 2023 2025 2027 publication year ensemble compression vision era encoder era generative era White box weights and activations 5 of 12 Grey box output distributions 5 of 12 Black box generated text only 2 of 12 before 2020: 4 of 7 works here from 2020: 1 of 5 9 years, two entries an API teacher leaves you in this lane Buciluă et al., model compression FitNets Hinton et al. Kim and Rush, sequence-level KD Attention transfer DistilBERT TinyBERT MiniLLM GKD Minitron Universal Logit Distillation DeepSeek-R1 distilled series
Figure 1.1 Each era of the field produced the methods its access to the teacher allowed, and the assumed access has drifted from full weights toward generated text as teachers moved behind APIs.

Read the figure against your own situation before you read another method paper. If a method sits in a lane you are not in, its results do not transfer to you, however good they are.

1.4 A taxonomy that survives contact with practice#

Textbook taxonomies of distillation usually sort methods by what kind of signal the teacher provides, following the survey literature.5 That axis is real and I will use it. But it is not the axis that determines what you can actually build, so I want to lead with a different one and come back to the classical split afterward.

Before the axes, one scenario to hang them on, because a taxonomy is easy to agree with and hard to use. I am going to carry a single project through all four axes and say what each one answers for it and what each answer eliminates. The project is deliberately ordinary.

The product. A team ships an assistant that currently runs in the cloud against an 8-billion-parameter instruction-tuned model. They want the same assistant on the handset: rewriting and shortening messages the user is drafting, pulling dates and tasks out of pasted text, answering questions about content already on the device. The requirement that starts the project is that it has to work with the network off.

The constraint. The handset can give a model roughly 4 GB and needs the first token back in well under a second. At half a byte per weight, which is what a 4-bit format costs, four gigabytes is an 8-billion-parameter model on paper and considerably less once the key-value cache, the runtime, and the operating system take their share. The target lands at 1.7 billion parameters: 3.4 GB in bf16 during development on the reference machine, near 1 GB once quantized for the device. Nothing about that number came from the distillation literature. It came from a memory budget, which is where student sizes usually come from.

The access. The 8B teacher is an open-weights model, downloaded, sitting on disk, with its weights readable and its activations available to anything that asks.

The corpus. 400,000 prompts logged from the cloud version over six months, de-identified. That is the real asset here: they are drawn from the distribution the student will actually be asked about, which no public instruction corpus is, and §1.8’s second limit is about the teams that do not have such a thing and use a general corpus instead.

1.4.1 Sort by what access you have#

This is the first question to ask about any distillation problem, because it eliminates most of the method space immediately.

Table 1.1 What access to the teacher buys you.

Access level What you can read Methods available Typical situation
White box Weights, activations, gradients, full logits Everything, including representation matching and pruning the teacher into the student You own or downloaded the teacher
Grey box Full or top- output probabilities, no internals Token-level distillation on any divergence, logit caching A served open model, or an API that returns log-probabilities
Black box Generated text only Sequence-level KD, trace fine-tuning A commercial API with no logprob endpoint

The boundaries move. APIs that once returned log-probabilities have stopped; models that were closed have been released. But at any moment, for any given teacher, you sit in exactly one of those rows, and the row determines the chapter of this book that applies to you.

The white-box row deserves one note now because it contains a method people forget exists: if you have the teacher’s weights, one of your options is to build the student out of the teacher. Delete some layers, keep the rest, and every parameter you kept is already trained. Chapter 13 covers this, and the economics are startling enough to change how you plan a project. Minitron reported matching a from-scratch model of the same size at a small fraction of its training compute by pruning and then distilling briefly.6

The black-box row deserves a note too, because it is where a great deal of current practice actually lives, and because the method it forces you into is the simplest one in the book. If all you can get is text, you generate a corpus with the teacher and fine-tune the student on it with ordinary cross-entropy. No divergences, no temperature, no logits. DeepSeek’s R1 report describes exactly this for its distilled model series: supervised fine-tuning on teacher-generated reasoning traces, with no reinforcement learning stage applied to the students.7 It works well enough to be the reference point that fancier methods have to beat, and Chapter 11 is about when they do.

The on-device assistant sits in the white-box row, and the instructive thing about that answer is how little it constrains. Every method in this book is available, so the access axis eliminates nothing and the design has to be settled on other grounds. The top row is the comfortable row to be in and the hard row to plan in.

It is also less stable than it looks. The teacher is someone else’s model under a license, and a license can be superseded. If a re-licensing or an internal legal review moved this project to the bottom row next quarter, everything built on logits would stop existing overnight while the trace fine-tuning path kept working unchanged. That asymmetry is why I now build the black-box baseline first on any project whose teacher belongs to someone else, even when I have logits. It survives every change of access, and it is the number the sophisticated methods have to beat anyway.

1.4.2 Sort by what the student is asked to match#

This is the classical axis, and the three categories the surveys use are worth having.5

Response-based distillation matches the teacher’s outputs. For a language model that means the next-token distribution, or a truncation of it, or a sampled token, or a whole generated sequence. This is where the field spends most of its time, and Parts II through IV of this book are almost entirely about it.

Feature-based distillation matches internal representations. Pick a layer in the teacher and a layer in the student, and add a loss term that pushes their hidden states together, usually through a learned linear projection because the dimensions differ. FitNets introduced this in 2014 under the name “hints,” and TinyBERT built a full recipe on it for transformer language models.89 Chapter 14 covers it, including the check that tells you whether the projection you need can even exist before you spend a training budget finding out.

Relation-based distillation matches the relationships between examples rather than the examples themselves: if the teacher considers inputs A and B similar and A and C distant, the student should too, regardless of where in representation space it puts them. Park and colleagues formalized this, and Tian and colleagues gave a contrastive version.1011 This branch is less developed for generative language models than for vision, and I will be honest about that in Chapter 14 rather than implying a maturity the literature does not have.

For the on-device assistant, response-based is the default and feature-based is a live option rather than an obvious one. The teacher and student differ in both width and depth, so a feature-matching term needs a learned projection between hidden states of different sizes, and Chapter 14’s ridge check tells you whether one can exist before you spend a training budget finding out that it cannot. Relation-based is eliminated, and the reason is worth stating precisely: not that the situation forbids it, but that the methods are immature for generative models and the project has a delivery date. Ruling out a branch on maturity grounds is legitimate; pretending the elimination was technical is not.

1.4.3 Sort by granularity#

For a model that produces sequences, there is a third axis with no equivalent in classification.

Token-level distillation treats each position independently. At position , the teacher has a distribution over the vocabulary, the student has one, and the loss compares them. Sum over positions. This is the direct translation of Hinton’s objective into the sequence setting and it is what most people mean when they say “distillation” about a language model.

Sequence-level distillation treats the whole output as the unit. Kim and Rush’s version, which is the original, approximates the intractable sum over all possible sequences by using the teacher’s most likely output as a single sample and training the student to reproduce it with ordinary cross-entropy.12 The approximation is crude and it works remarkably well, which is one of several places in this subject where theory and practice have an uncomfortable relationship.

The distinction matters because the two methods fail differently. A token-level student can match the teacher at every position and still produce incoherent text, because matching a conditional distribution at every position under teacher-provided context says nothing about what happens when the student has to condition on its own output. Chapter 12 is about exactly that gap.

The on-device assistant starts token-level. The corpus is prompts, the teacher scores each one in a single parallel forward pass, and the replies the product needs are short and structured: a rewritten message, an extracted date, a two-sentence answer. Length is what makes the token-level failure mode bite, and a hundred-token reply accumulates far less drift than a two-thousand-token chain of reasoning. That is an argument for starting token-level rather than a proof that it will suffice, because the acceptance test is what the generations read like while the objective is measured on conditional distributions under context the teacher supplied.

1.4.4 Sort by where the training inputs come from#

The last axis is the one that has moved the most in the last three years, and it is the reason Chapter 12 exists.

Off-policy distillation trains the student on text it did not produce: a fixed corpus, or the teacher’s generations. The name is borrowed from reinforcement learning, where a policy is the thing that chooses actions and “off-policy” means learning from data generated by some policy other than the one you are training.

Definition

Off-policy distillation

Training the student on inputs drawn from a distribution other than the student’s own outputs. The corpus is fixed before training starts, which makes the pipeline cheap, restartable, and easy to reason about.

On-policy distillation trains the student on text the student generated, with the teacher scoring those generations after the fact. The student produces a rollout, the teacher says what it would have done at each position of that rollout, and the loss compares them.

Definition

On-policy distillation

Training the student on the student’s own generated outputs, scored by the teacher. The training distribution moves as the student learns, which addresses exposure bias at the cost of a generation step inside the training loop.

The argument for on-policy training is exposure bias: a model trained only on text it did not produce never sees its own mistakes, and therefore never learns to recover from them.13 The argument was made for sequence models long before distillation adopted it, and scheduled sampling was an early attempt at a fix.14 Agarwal and colleagues built the modern distillation version, generalized knowledge distillation, which interpolates between the two regimes with a single parameter.15

The on-device assistant goes off-policy first. Scoring 400,000 prompts once with the teacher is a prefill workload, the cheapest row of Table 1.3, and what comes out is a durable asset: the cached distributions survive every later change to the student’s learning rate, divergence, initialization, and schedule, so the teacher is paid for once. Chapter 10 is that pipeline. On-policy becomes the second phase if the first leaves a gap that shows up in generations rather than in the loss. That ordering is about more than cost, since the off-policy phase is also the cheapest way to learn whether a 1.7B student can hold this capability at all, which is §1.9’s third failure class and the one you want to meet in week one rather than week six.

There is a cost inversion sitting under that ordering, and it is the reason Chapter 9 exists in the shape it does. On-policy distillation is the cheap path on this machine and a teacher-generated corpus is the expensive one, because what decides the price is which model decodes, and in on-policy training the decoding is done by the student while the teacher only ever scores text that already exists. I had it backwards for a while, for reasons §9.5 dissects; until you have read that arithmetic, take the ordering in Table 1.3 as a claim and not a result.

1.5 Why it works, as honestly as the evidence allows#

There are four explanations in circulation for why a student trained against a teacher beats the same student trained against hard labels. They are not mutually exclusive and the evidence supports different amounts of each.

The information argument. The teacher’s full distribution contains more bits per training example than a one-hot label does. A vocabulary of 49,152 tokens with a one-hot label carries at most bits; the full distribution over the same vocabulary carries substantially more, because it specifies a value for every entry. More bits per example means fewer examples needed for the same amount of learning. This is the argument Hinton made and it is the easiest to state.

The regularization argument. Soft targets are smoother than hard ones, and training against a smooth target reduces overfitting the same way label smoothing does. If this were the whole story, label smoothing should be a substitute for distillation, and it is not: Müller, Kornblith, and Hinton showed that a teacher trained with label smoothing distills worse than one trained without, even though the smoothed teacher is itself more accurate.16 The reason is that label smoothing collapses the very structure among wrong-answer probabilities that distillation transmits. That result is one of the cleanest pieces of evidence that the information argument is doing real work, and Chapter 5 reproduces the reasoning.

The optimization argument. The teacher’s outputs define a smoother loss landscape than hard labels do, so the student’s optimization problem is easier even when the optimum is the same. Beyer and colleagues pushed this line hard, showing that treating distillation as function matching, meaning making the teacher and student see identical inputs under identical augmentation and training for a very long time, produces much better students than the usual recipe.17 Their headline finding is that patience matters more than most architectural choices, which is not what the field expected.

The curriculum argument. In the sequence setting specifically, the teacher’s generated text is easier to learn from than natural text, because it is more consistent, less noisy, and drawn from a distribution a model can actually represent. This is folklore rather than settled science, but it is the most common explanation offered for why sequence-level KD works as well as it does despite its crude approximation, and Kim and Rush’s original analysis gestures at it.12

Watch out

Notice what none of the four arguments claims: that the student ends up computing the same function as the teacher. Stanton and colleagues measured teacher-student agreement directly and found it worse than the generalization improvement would suggest, in settings where the student had enough capacity to match the teacher and the optimizer was given every advantage.3 If your mental model of distillation is “the student learns to be a small copy of the teacher,” you will be repeatedly surprised. A better model is “the teacher’s outputs are a training signal with useful properties, one of which is that they came from something that solves the task.”

1.6 What makes a good teacher#

The teacher is usually chosen in about four seconds. Somebody asks which model is the strongest one the project has access to, that model becomes the teacher, and every later decision happens downstream of a choice nobody wrote down. It is the wrong procedure, and the reasons are specific enough to list, because each one is a case where the obvious criterion and the correct one point in different directions.

Teacher accuracy is not the selection criterion. The distillation loss never reads the teacher’s accuracy. It reads the teacher’s distribution, and nearly all of that distribution sits in the entries that are not the answer. Two teachers can agree on the top-1 prediction at every position of your corpus, score identically on every benchmark you would put in a report, and differ by an order of magnitude in how much structure their tails hold. The first has something to teach; the second is an expensive way to produce hard labels. What separates them is a one-number summary of how unequal the wrong-answer probabilities are, derived in Chapter 5, costing one forward pass over a few hundred prompts.

A label-smoothed teacher is a worse teacher and a better model. §1.5 gave the result: the smoothed teacher is more accurate and better calibrated and it distills worse, because the smoothing objective rewards making every wrong answer equally probable and that equality is the absence of the thing being transferred. The rule generalizes past label smoothing to any procedure that pushes the wrong answers toward each other, and what makes it unsettling is that the damage is invisible to everything anyone reports, because the argmax does not move.

A bigger teacher can be a worse teacher. §1.2 introduced the capacity gap and Chapter 5 covers the competing explanations for it. The planning consequence is that the parameter which matters is the ratio between teacher and student rather than the teacher’s absolute size, so a 70B teacher for a 1.7B student can produce a worse student than an 8B teacher would have. Under the explanation Cho and Hariharan give, the student cannot represent the larger teacher’s function at all, so the distillation term spends its gradient pointing at an unreachable target.

An early-stopped teacher can be a better teacher. This is their remedy, and it breaks people’s intuition hardest: take the teacher’s checkpoint from partway through training, the one you would refuse to ship, and it distills better than the finished model does. If you have intermediate checkpoints of a model you trained, you have candidate teachers, and losing the internal bake-off is not evidence about how they will do here.

The teacher does not have to be larger at all. Born-again networks distill a model into a fresh copy of its own architecture, same size, same everything, and the students come out ahead of the teachers.25 Whatever is happening there, “the teacher must be big” is a convention inherited from compression being the original motivation rather than a requirement of the method.

One framing ties those together. Soft labels are a trade: lower variance in the training signal against a bias toward whatever the teacher is wrong about.26 You want the teacher whose trade lands well for your student on your corpus, and there is no leaderboard for that.

So, a procedure that fits inside a normal project.

  1. Enumerate the candidates you can actually run: every size in the family you have access to, the base and instruction-tuned variants separately, and any intermediate checkpoints of your own.
  2. Score a few hundred prompts from your own corpus with each candidate, one prefill pass. Record three numbers per candidate: top-1 agreement with whatever reference you have, mean entropy of the distribution, and the spread of log-probabilities across the non-top-1 tokens.
  3. Discard the candidates whose tails are flat, however they rank on accuracy. They have nothing to transfer and you would be paying full price to receive it.
  4. Among the survivors, take the two that differ most on those numbers and run the shortest distillation you can afford with each, at matched budget, one key different.
  5. Decide on the student’s numbers, not the teacher’s.

Field note

On the on-device assistant my first instinct was the strongest 8B checkpoint in the family, the instruction-tuned one, and the instinct went unexamined longer than it should have. The family part is right: the corpus is logged product prompts, and a base checkpoint answers a product prompt with a plausible continuation rather than an answer. What I had not thought about is that the tuning which makes a model follow instructions also sharpens it. The tuned checkpoint is more confident at nearly every position than the base model it came from, and a more confident teacher at a fixed temperature has less to say about the wrong answers. That does not make the base model the better teacher. It means the right temperature is a function of which checkpoint I picked, so comparing the two at a shared temperature would have measured the temperature rather than the teacher.

Every step in that procedure ends up measuring the student. The teacher’s own scores are a proxy for the thing the project cares about, and this section is a list of four named ways that proxy has been shown to fail.

1.7 The student’s side of the design#

Four decisions define the student: its architecture family, whether it shares the teacher’s tokenizer, how its weights are initialized, and how big it is. Three of those four are settled before a single training step runs, and none of the three can be revised afterward without throwing away everything trained so far. That makes them the decisions with the most riding on them, and it puts them in an awkward relationship with a literature that spends most of its length on the divergence, the temperature, and the mixing coefficient. Those three are real and Chapters 5 and 6 treat them seriously. They are also revisitable in an afternoon.

Size comes from the deployment constraint, not from the method. Work backwards from where the model has to run, as the on-device assistant did in §1.4: available memory, minus the runtime, minus the key-value cache at the context length you need, divided by the bytes per parameter of the format you will ship in. What comes out is a parameter count, and the distillation design begins after it. Size also fixes the teacher-to-student ratio, which is the capacity-gap parameter, so §1.6 and this section are two halves of one decision rather than two.

Architecture family decides whether teacher and student are relatives. Taking the teacher’s own small sibling buys you a shared tokenizer, shared chat-template conventions, similar width-to-depth proportions, and a live option on feature matching because the layers correspond in a way you can at least argue about. Taking another vendor’s model of the same size buys you whatever that model is better at, which for on-device work is often a genuinely different thing: kernels hand-tuned for the target chip, or a smaller vocabulary. The vocabulary point is not cosmetic at this scale, since the embedding and output matrices are a large fraction of a 1.7B model’s parameters. Note also that “a small model” is itself two different objects: one obtained by training at the target size on a carefully constructed corpus, and one obtained by pruning a larger model down to the target size and continuing to train it.2728 Both hand you a checkpoint. They hand you different checkpoints.

Tokenizer sharing is not an implementation detail. If the two models tokenize differently, then teacher position 7 and student position 7 are not the same span of text, and a position-wise loss is comparing distributions over different things while reporting a number that looks fine. Chapter 7 proves that no general position-wise alignment exists across tokenizer families, and Chapter 14 covers the methods that recover a usable signal anyway and what each one gives up. Everything in Parts II and III of this book assumes a shared tokenizer, so this single decision determines whether two of the book’s five parts apply to you. It is also a serving decision, because the tokenizer sets how many tokens a given reply costs, and tokens are what decoding time is measured in.

Initialization is the one you can revisit. Random, a published checkpoint, or the teacher with layers removed. It changes the starting weights and nothing about the interfaces, so it is the one of the four you can change on Tuesday without invalidating Monday. Chapter 13 measures what each is worth in training steps, and the answer is large enough that treating initialization as a default rather than a decision is the most expensive habit in this list.

For the on-device assistant, three of the four fell out of decisions already made. The size came from the handset. The family came from the teacher, because the 8B model has a 1.7B sibling and sharing the tokenizer keeps the whole book available. That leaves initialization as the only one still open, which is the arm worth running: the published sibling checkpoint against the teacher pruned to the same size, at matched budget. Three decisions made before training and one experiment worth doing is the typical distribution, and it is close to the inverse of where a first-time reader of the literature would expect the consequence to sit.

1.8 What distillation cannot do#

A book that only says what a technique achieves is not useful for planning, so here are the three limits, stated plainly.

It does not create capability the student’s architecture cannot hold. If a task requires more computation per token than the student can perform, no amount of teacher signal supplies it. The capacity gap is the mild version of this; the hard version is that some behaviors seem to require scale and do not survive compression at any ratio. The field does not have a crisp characterization of which behaviors those are, which is an honest gap rather than a failure of this book to look.

It does not transfer what the teacher does not express on your inputs. Distillation is supervised by teacher outputs on the corpus you chose. Teacher behavior that your corpus never elicits does not transfer. This has a practical consequence people discover late: a student distilled on a general instruction corpus can be excellent on general instructions and terrible on your domain, not because the teacher was bad at your domain but because you never asked it.

It does not launder provenance. Whatever is in the teacher can arrive in the student, including things you did not want. Chapter 17 covers the measured version of this: ordinary backdoors mostly do not survive distillation, but triggers built from tokens that are common in distillation corpora do, and the difference is measurable on your own pipeline before you ship.18 The same chapter covers the other direction, where distillation is the attack rather than the pipeline, and someone is extracting a model you serve.

1.9 The four ways a distillation project fails#

Those are limits of the technique. Failures of projects are a different list and a more useful one, because a limit is something you plan around and a failure is something you diagnose at 11pm with a training run three days in. Across the work I have done and the work I have watched, distillation projects go wrong in one of four ways. The value of having exactly four is that they are diagnosed differently and in a definite order, and each has a chapter that handles it.

The pipeline is silently wrong. Nothing crashes, the loss descends, the curve looks like a curve. Meanwhile the labels are shifted by one position relative to the logits, or the prompt tokens are in the loss when they should be masked out, or the teacher and the student tokenize the same string into different numbers of tokens and the loss is comparing position 7 to position 8, or the cache was built from a corpus revision that is not the one being trained on, or the temperature is applied to one side of the divergence and not the other. The signature is a run that trains to a plausible loss and produces a student no better than the hard-label baseline, or one that looks better on the loss and worse on anything you generate from it. You will not find these by reading the code, because the code looks correct, which is why it shipped. You find them by assertion: recompute the framework’s loss externally and compare to float precision, fingerprint the corpus and check it at load time, compute the set of configuration keys by which two arms differ and assert it has exactly one element. Chapter 7 is where these bugs live, Chapter 8 is the discipline that catches them before a run, and Chapter 10 covers the cache-specific ones.

The method does not suit the access you have. You designed around output distributions and the vendor removed the log-probability endpoint. You designed around weights and the legal review came back saying generated text only. You designed around a shared tokenizer and the model the device team ships uses a different one. It presents as a project that stalls with a plan nobody can execute, at the point where the plan meets a constraint that was decided somewhere else in the organization. The defense is to settle the access row first, as §1.4.1 argues, and to build the black-box baseline early because it is the one method available in every row. Chapter 11 is that baseline, and Chapter 12 is what query access buys on top of it.

The student cannot hold the capability. The capacity gap is the mild form; the hard form is a behavior that does not survive compression at any ratio you can afford. The signature is distinctive once you have seen it. The student closes most of the gap quickly and then stops, and the residual does not move for more steps, more data, a different divergence, or a better teacher. It also concentrates rather than spreading, so the remaining errors pile up on a recognizable subset of the evaluation, usually the items needing the most computation per token: long chains of arithmetic, multi-step retrieval across a long context, anything where an intermediate result has to be held and reused. The defense is a small-budget probe before the budget is committed, and the honest response when it fires is to change the size, the initialization, or what you are promising. Chapter 5 covers the mechanism and Chapter 13 covers the two responses that are about the student.

The evaluation cannot see the difference. The subtlest one, the most common in published comparisons, and the reason Part V exists. The loss went down and top-1 agreement with the teacher went up, and neither is evidence, because the loss is computed on the distribution the student was trained toward and agreement counts argmax matches, which is close to the statistic the objective optimized. Both numbers can improve while the model gets worse in ways nobody measured: calibration degrades, so the model’s confidence stops meaning anything;29 output diversity collapses; the eval set overlaps the distillation corpus and the score is partly a memorization score. The defense is to build the evaluation before the run and decide in advance what would count as a win, which sounds like bureaucracy and is the difference between a result and an anecdote. Chapter 16 is the evaluation, Chapter 18 is the practice of registering the decision beforehand, and Chapter 17 is the version where the thing you cannot see is a security property rather than a quality one.

Table 1.2 The four failure classes, in the order to check them.

Failure What you observe Cheapest test Where it is handled
Pipeline silently wrong Loss descends, student is no better than baseline, or loss and generations disagree External recomputation of the loss to float precision; one-key config diff Chapters 7, 8, 10
Evaluation cannot see it Every number improves and nobody trusts the model Measure one quantity the objective does not optimize: calibration, diversity, or a generation-based probe Chapters 16, 18
Method does not suit the access The plan cannot be executed as written Name your row in Table 1.1 and check every planned method against it Chapters 11, 12
Student cannot hold it Gap closes fast, then stops, and the residual concentrates Small-budget probe at the intended ratio before committing Chapters 5, 13

The table’s ordering is diagnostic and differs from the order I introduced them in. When a run disappoints, check the pipeline first, because it is cheap and it is the most likely explanation. Check the evaluation second, because it is also cheap and because being wrong about it wastes everything downstream. Check the method against your access third, and capacity last, because ruling capacity in or out costs a training run and it is the only one of the four whose remedy is to change what you promised.

1.10 The economics, in one page#

Everything in Part III and Part V of this book rests on a small number of facts about hardware, so here is the shape of the argument in advance. Chapter 9 derives it.

Running a transformer has two very different modes. Prefill processes text that already exists, which means every position can be computed at once, which means the arithmetic units stay busy. Decode generates one token at a time, and each step has to read the entire weight matrix out of memory to produce a single token, which means the memory bus is the bottleneck and the arithmetic units idle.

Definition

Prefill

Running a model forward over a sequence that already exists, scoring all positions in parallel. Compute bound, and fast.

Definition

Decode

Generating tokens one at a time, each conditioned on the ones before it. Memory-bandwidth bound, and on large models, slow.

The ratio between them on real hardware is large. Measurements on a 20-billion-parameter model in a 4-bit format on this class of machine put prefill near 2,053 tokens per second and decode near 49.7, a ratio of roughly 40 to 1. That single number reorganizes the whole method space:

Table 1.3 Distillation workloads sorted by who generates.

Workload Mode Who decodes Cost
Teacher scores a fixed corpus to build a logit cache Prefill Nobody Cheap, and you pay once
Teacher scores student rollouts Prefill Nobody Cheap
Student generates rollouts for on-policy training Decode The student, which is small Moderate
Teacher generates a corpus for sequence-level KD Decode The teacher, which is large Expensive

Read the table as a planning tool. Two of those rows are nearly free, one is affordable, and one costs two orders of magnitude more than the others. If your plan involves the expensive row, you should know it before you start, price it explicitly, and then decide whether to buy the corpus once and keep it forever, use someone else’s, or rent different hardware for that stage alone.

The memory side is simpler and just as binding. A parameter in bf16 costs 2 bytes to hold for inference. Full fine-tuning costs about 16 bytes per parameter once you count bf16 weights, bf16 gradients, and the fp32 optimizer moments and master weights that a standard Adam setup keeps. Low-rank adaptation collapses the optimizer share to nearly nothing, which is why it shows up whenever a configuration would otherwise not fit.19 Appendix B has the tables.

1.11 The reference machine#

Every number in this book that depends on hardware refers to one specific machine unless it says otherwise: a single workstation with 128 GB of unified CPU-GPU memory at roughly 273 GB/s of bandwidth, on arm64, CUDA compute capability sm_121.

I am being specific on purpose, for the reason the preface gives: the same method is cheap on one machine and impossible on another, and the ordering of methods by cost inverts between hardware classes. The machine above has generous capacity and modest bandwidth, which is a combination that makes large models fit and makes them slow to generate from. On a machine with the opposite profile you would make different choices, and Chapter 9 gives you the arithmetic to redo the comparison for whatever you have rather than inheriting mine.

2026-08-01T07:26:24.555677 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 32 64 96 128 160 memory, GB 32B teacher + 4B student full fine-tune 32B teacher + 8B student LoRA 8B teacher + 4B student full fine-tune 8B teacher + 1.7B student full fine-tune 128 GB ceiling 128 GB 82 GB 80 GB 43 GB does not fit once KV cache and activations are counted LoRA: 1.6 GB of optimizer state, for a student twice the size teacher bf16 weights +grads optimizer state
Figure 1.2 The reference machine's memory budget under four teacher-student configurations, showing where full fine-tuning stops fitting and where low-rank adaptation buys the room back.

1.12 How this book is organized#

The table of contents lists the parts and I am not going to restate it here. Two things about the arrangement are not visible from a contents page, and both change how you should read.

The first is that Part III is the part I would put in a practitioner’s hands if I could only give them one. It covers tokenizers and alignment, where the silent bugs live; the first complete run and the discipline around it; the hardware economics; and then the cached-logit pipeline, which is the cheapest correct thing you can build on this machine and the one I would reach for first on a new project. Parts I and II exist to make Part III readable rather than as ends in themselves, and Parts IV and V are what you reach for once the cheap thing is working and you want to know whether it is good.

The second is that the book has a companion: thirteen laboratory notebooks that execute everything described here. The relationship is deliberate. The book explains and derives; the labs assert. Almost every claim in Part I and Part II is checked mechanically in a notebook against autograd or a closed form, so if a claim ever stops being true the notebook fails loudly. Where a chapter states a measured number, that number came out of one of those executions, and the chapter says which.

1.13 Where this lands in the labs#

Lab 00 is the entry point and it presumes nothing from this chapter beyond the vocabulary. The taxonomy here is not itself asserted anywhere, because taxonomies are not the kind of thing you can assert; what the labs check is every specific claim the taxonomy organizes, and they are scattered across the whole course rather than gathered into a Lab 01. The access rows of Table 1.1 turn into concrete pipelines in Labs 04, 06 and 07, one per row, and running any two of them back to back teaches the ordering in Table 1.3 faster than the arithmetic in Chapter 9 does. If you want the fastest possible confirmation that the subject is real before committing to any of that, Lab 01’s temperature-limit cell takes under a second and shows the factor doing its job on a fixed logit vector you can read in one line.

1.14 Exercises#

  1. For each of the four constraints in this chapter’s opening paragraph, name a distillation method from Table 1.1 that addresses it and one that does not. Where a method addresses the constraint only partially, say what remains.

  2. A colleague proposes distilling a 70-billion-parameter teacher into a 1-billion-parameter student by having the teacher generate 10 million tokens of training corpus. Using only Table 1.3 and the bandwidth figure from §1.11, estimate the wall-clock cost of the generation step to within a factor of two, and state every assumption you made. Chapter 9 will give you the tools to do it properly; do it roughly now and compare later.

  3. The regularization argument and the information argument make different predictions about what happens when you distill from a teacher trained with heavy label smoothing. State both predictions before reading §1.5’s answer, then say which prediction the measured result supports.

  4. You have API access to a teacher that returns the top 20 log-probabilities per position. Which row of Table 1.1 are you in, and which methods from §1.4.2 and §1.4.3 become available or unavailable? Name one piece of information you are missing and one way to bound its size.

  5. Stanton and colleagues found that students often generalize better while agreeing with their teacher less than expected. Propose two experiments that would distinguish “the student learned a different function that happens to be better” from “the student learned the teacher’s function badly in a way that happens to help.” Say what each experiment would measure.

  6. Give a task where you would expect distillation to fail outright, using the three limits in §1.8. Be specific enough that someone could test your prediction.

  7. You are handed four candidate teachers for the on-device assistant in §1.4: the 8B instruction-tuned model, the same model’s base checkpoint, a 32B instruction-tuned model from the same family, and an 8B checkpoint saved 40 percent of the way through instruction tuning. Rank them using only the five facts in §1.6, saying for each which fact applies and in which direction. Then name the one measurement that could overturn your ranking and say what it costs.

  8. A run of the §1.4 scenario finishes. The distillation loss is a third of where it started, top-1 agreement with the teacher on held-out prompts is 71 percent against 44 percent for the hard-label baseline, and the product team reports that the replies read worse than the baseline’s. Assign this to one or more of the four classes in §1.9, say which you would test first and why, and give the cheapest test for each class you named.



  1. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015). Presented at the NIPS 2014 Deep Learning Workshop. https://arxiv.org/abs/1503.02531 

  2. Cristian Buciluǎ, Rich Caruana, and Alexandru Niculescu-Mizil, “Model Compression,” Proceedings of the 12th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining (2006), 535-541. The originating work for the idea, predating the term “distillation.” 

  3. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945 

  4. Jang Hyun Cho and Bharath Hariharan, “On the Efficacy of Knowledge Distillation,” arXiv:1910.01348 (2019), ICCV 2019. https://arxiv.org/abs/1910.01348 

  5. Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819. https://arxiv.org/abs/2006.05525. For the language-model-specific landscape see Xiaohan Xu et al., “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024). https://arxiv.org/abs/2402.13116 

  6. Saurav Muralidharan et al., “Compact Language Models via Pruning and Knowledge Distillation,” arXiv:2407.14679 (2024), NeurIPS 2024. https://arxiv.org/abs/2407.14679 

  7. DeepSeek-AI, “DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning,” Nature 645 (2025): 633-638, https://doi.org/10.1038/s41586-025-09422-z; preprint arXiv:2501.12948. The distillation section describes supervised fine-tuning on teacher traces with no reinforcement learning stage for the student models. 

  8. Adriana Romero et al., “FitNets: Hints for Thin Deep Nets,” arXiv:1412.6550 (2014), ICLR 2015. https://arxiv.org/abs/1412.6550 

  9. Xiaoqi Jiao et al., “TinyBERT: Distilling BERT for Natural Language Understanding,” arXiv:1909.10351 (2019), Findings of EMNLP 2020. https://arxiv.org/abs/1909.10351 

  10. Wonpyo Park, Dongju Kim, Yan Lu, and Minsu Cho, “Relational Knowledge Distillation,” arXiv:1904.05068 (2019), CVPR 2019. https://arxiv.org/abs/1904.05068 

  11. Yonglong Tian, Dilip Krishnan, and Phillip Isola, “Contrastive Representation Distillation,” arXiv:1910.10699 (2019), ICLR 2020. https://arxiv.org/abs/1910.10699 

  12. Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947 

  13. Marc’Aurelio Ranzato, Sumit Chopra, Michael Auli, and Wojciech Zaremba, “Sequence Level Training with Recurrent Neural Networks,” arXiv:1511.06732 (2015), ICLR 2016. https://arxiv.org/abs/1511.06732 

  14. Samy Bengio, Oriol Vinyals, Navdeep Jaitly, and Noam Shazeer, “Scheduled Sampling for Sequence Prediction with Recurrent Neural Networks,” arXiv:1506.03099 (2015), NeurIPS 2015. https://arxiv.org/abs/1506.03099 

  15. Rishabh Agarwal et al., “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649 

  16. Rafael Müller, Simon Kornblith, and Geoffrey Hinton, “When Does Label Smoothing Help?” arXiv:1906.02629 (2019), NeurIPS 2019. https://arxiv.org/abs/1906.02629 

  17. Lucas Beyer et al., “Knowledge distillation: A good teacher is patient and consistent,” arXiv:2106.05237 (2021), CVPR 2022. https://arxiv.org/abs/2106.05237 

  18. Giovanni De Muri, Mark Vero, Robin Staab, and Martin Vechev, “Pay Attention to the Triggers: Constructing Backdoors That Survive Distillation,” arXiv:2510.18541 (2025), ICLR 2026. https://arxiv.org/abs/2510.18541 

  19. Edward J. Hu et al., “LoRA: Low-Rank Adaptation of Large Language Models,” arXiv:2106.09685 (2021), ICLR 2022. https://arxiv.org/abs/2106.09685 

  20. Sergey Zagoruyko and Nikos Komodakis, “Paying More Attention to Attention: Improving the Performance of Convolutional Neural Networks via Attention Transfer,” arXiv:1612.03928 (2016), ICLR 2017. https://arxiv.org/abs/1612.03928 

  21. Victor Sanh, Lysandre Debut, Julien Chaumond, and Thomas Wolf, “DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter,” arXiv:1910.01108 (2019), 5th Workshop on Energy Efficient Machine Learning and Cognitive Computing, NeurIPS 2019. https://arxiv.org/abs/1910.01108 

  22. Yuxian Gu, Li Dong, Furu Wei, and Minlie Huang, “MiniLLM: Knowledge Distillation of Large Language Models,” arXiv:2306.08543 (2023), ICLR 2024. https://arxiv.org/abs/2306.08543v2 The arXiv landing page currently shows a later title; the ICLR 2024 version of record is the one cited here. 

  23. Nicolas Boizard, Kevin El Haddad, Céline Hudelot, and Pierre Colombo, “Towards Cross-Tokenizer Distillation: the Universal Logit Distillation Loss for LLMs,” arXiv:2402.12030 (2024), Transactions on Machine Learning Research, January 2025. https://arxiv.org/abs/2402.12030 

  24. Mingyang Song and Mao Zheng, “A Survey of On-Policy Distillation for Large Language Models,” arXiv:2604.00626 (2026). An unrefereed preprint whose own comment field describes it as ongoing work; read it as a map of activity rather than a settled account. https://arxiv.org/abs/2604.00626 

  25. Tommaso Furlanello, Zachary C. Lipton, Michael Tschannen, Laurent Itti, and Anima Anandkumar, “Born Again Neural Networks,” arXiv:1805.04770 (2018), ICML 2018. https://arxiv.org/abs/1805.04770 

  26. Helong Zhou et al., “Rethinking Soft Labels for Knowledge Distillation: A Bias-Variance Tradeoff Perspective,” arXiv:2102.00650 (2021), ICLR 2021. https://arxiv.org/abs/2102.00650 

  27. Mengzhou Xia, Tianyu Gao, Zhiyuan Zeng, and Danqi Chen, “Sheared LLaMA: Accelerating Language Model Pre-training via Structured Pruning,” arXiv:2310.06694 (2023), ICLR 2024. https://arxiv.org/abs/2310.06694 

  28. Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big, Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737 

  29. Chuan Guo, Geoff Pleiss, Yu Sun, and Kilian Q. Weinberger, “On Calibration of Modern Neural Networks,” arXiv:1706.04599 (2017), ICML 2017. https://arxiv.org/abs/1706.04599 

Part I · Foundations

2

Distributions Over Tokens, and the Numbers That Hold Them

Every method in this book bottoms out in the same operation. A model emits a vector of raw scores, one per vocabulary entry. That vector becomes a probability distribution over the next token. Some other distribution, obtained the same way from another model, is compared against it, and the comparison produces a number that gets differentiated. That pattern is what the surveys call response-based distillation.1415 Chapters 3 and 6 are about which comparison to use. This chapter is about what comes first: the distribution itself, and the floating-point arithmetic that has to hold it.

The second half of that sentence is the part people skip, and the part that costs them a week. A next-token distribution has between 50,000 and 256,000 entries spanning forty orders of magnitude: the token the model is confident about sits near 0.9, and one it has ruled out sits at or lower. Distillation is the business of caring about the small entries, because that is where the teacher’s learned structure lives. So it is the business of doing arithmetic near the floor of whatever number format you are in, and the format has a floor, and things below the floor become exactly zero, and the logarithm of exactly zero is negative infinity.

Here is what that looks like from the outside. You launch a run and the first loss prints as nan. Or worse, a thousand losses print as ordinary numbers, then one prints as inf, and everything after it is nan. The natural reading of that dashboard is “the optimizer diverged,” and the natural response is to lower the learning rate, add gradient clipping, and try again. That response is wrong, it costs hours, and the cause is one function call that took a logarithm of something which had underflowed to zero. I have watched people, myself included, chase the wrong hypothesis here.

So this chapter builds the object, then the number system under it, and tries to leave you able to predict which arithmetic survives before you run it.

2.1 What a next-token distribution is#

A language model, given a sequence of tokens, produces at each position a vector of real numbers with one entry per vocabulary item. Call the vocabulary size and the vector $z \in \mathbb{R}^V$. These numbers are not probabilities. They are unnormalized scores, and they are called logits.

Definition

Logit

A raw, unnormalized score emitted by a model’s output layer, one per vocabulary entry. Logits live on the whole real line, bounded neither above nor below, and become probabilities only after a softmax.

The map from logits to probabilities is the softmax:

Definition

Softmax

The function that turns a vector of real-valued logits into a probability distribution: . Every output is positive because , and the outputs sum to one because the denominator is the sum of the numerators.

One property of the softmax determines most of what follows, and it is worth deriving rather than accepting.

The softmax is shift invariant. Add the same constant to every logit and the distribution does not change:

$$\mathrm{softmax}(z + c)_i = \frac{e^{z_i + c}}{\sum_j e^{z_j + c}} = \frac{e^{c}\,e^{z_i}}{e^{c}\sum_j e^{z_j}} = \frac{e^{z_i}}{\sum_j e^{z_j}} = \mathrm{softmax}(z)_i.$$

The factor appears once in the numerator and once in every term of the denominator, so it factors out and cancels. Nothing about this is deep, and it has three consequences that are.

First, only logit differences carry information. If someone tells you a model assigned a logit of 14.2 to a token, you have learned nothing, because 14.2 relative to what is the only question. Comparing two models’ raw logits entry by entry is meaningless; comparing their log-probabilities is fine.

Second, the softmax is not injective. A whole line of logit vectors, ${z + c : c \in \mathbb{R}}$, maps to the same distribution, so “match the teacher’s distribution” and “match the teacher’s logits” are different objectives and the second is strictly stronger. Chapter 5 shows they converge at high temperature, which is a fact about the limit rather than the general case.

Third, and this is the one that saves the computation, you are free to subtract anything you like from all the logits before exponentiating. Section 2.2 uses that freedom.

A note on notation, fixed here for the rest of the book: is always the teacher’s distribution and is always the student’s. The course’s code follows the same convention, while the divergence functions in kd_core take arguments in the order (student, teacher), the opposite of the order they appear in the mathematics. Chapter 3 puts a warning on that.

The vocabulary sizes involved are large enough to change the arithmetic. The course prices three: Llama 3 at 128,256 entries, Qwen2.5 and Qwen3 at 151,936, Gemma 2 and 3 at 256,000.1 The 360M-parameter student the labs train has 49,152.2 A dense distribution over 151,936 entries in a 16-bit format is about 302 KB per token position, the number that makes Chapter 10 exist. That figure rounds the vocabulary to 151k, which is the form the labs carry it in; multiply the exact 151,936 out and you get 303.9 kB, and the difference has never changed a decision made with it.

2.2 Overflow, and why the textbook formula does not run#

Write out the softmax exactly as it appears above, in code, and it will fail on real inputs.

Every floating-point format has a largest finite value. A calculation whose true result exceeds it does not wrap around or saturate; it produces inf, and inf divided by inf produces nan, and nan propagates through every later operation until the whole tensor is nan.

Definition

Overflow

A calculation whose true result is larger in magnitude than the largest value the number format can store. The result becomes inf, which then contaminates everything computed from it.

The single-precision format, fp32, tops out at about . So the question “which logits overflow when you exponentiate them” has an exact answer:

That is the whole derivation. No rule of thumb is involved: 88.7 is the natural logarithm of fp32’s ceiling, so exp of any logit above roughly 88 overflows and every logit below it is fine.

Half precision, fp16, has a much lower ceiling of 65,504, and the same derivation gives

so exp of any logit above about 11 overflows fp16. Eleven. A trained language model routinely emits logits well past that.

Now put realistic numbers in. Logits from a trained checkpoint sit around 10 to 40 in magnitude, comfortable in fp32 and already dangerous in fp16. Then divide by a temperature, an operation Chapter 5 performs constantly and Section 2.4 defines. A temperature of 0.25 multiplies every logit by 4, so logits near 30 become logits near 120, well past 88.7, and the naive softmax overflows in fp32. Lab 00 runs this case on the vector at and asserts that the naive implementation produces nan while the stable one matches PyTorch’s F.softmax to .

The fix is the third consequence of shift invariance. Let and compute

By shift invariance with , this is the same distribution, exactly rather than approximately, and the largest exponent is now , so no term can overflow. Terms can still underflow, which is Section 2.3’s problem, but they cannot blow up. The transformation costs one pass to find the maximum, and it is what every library does inside F.softmax.

The listing below is the shape of the idea. Look at the single line of difference between the two functions and at what it does to the exponent.

import torch

def naive_softmax(z):
    e = z.exp()                                  # e^124 has nowhere to go in fp32
    return e / e.sum(-1, keepdim=True)

def stable_softmax(z):
    m = z.max(-1, keepdim=True).values           # shift invariance says this is free
    e = (z - m).exp()                            # largest exponent is now exactly 0
    return e / e.sum(-1, keepdim=True)

z = torch.tensor([31.0, 29.5, 27.0, 12.0]) / 0.25   # temperature 0.25 -> logits near 120
print(naive_softmax(z))    # tensor([nan, nan, nan, nan])
print(stable_softmax(z))   # tensor([9.9753e-01, 2.4726e-03, 1.1226e-07, 9.8298e-34])

The two functions implement the same formula and differ in whether the intermediate quantities fit in the format. That distinction, between the mathematics being right and the arithmetic being survivable, is the subject of this chapter.

You will not write naive_softmax on purpose. You will meet it when you implement anything below the level of the framework, and in a nastier form when someone builds a logit cache and stores instead of to save an exponential at training time. The stored values carry no record of what was subtracted, the protection is gone, and nothing warns you. Chapter 10 specifies what a cache record should contain, and log-probabilities are the answer, for this reason among others.

2.3 logsumexp, and the rule about never taking the log of a softmax#

The denominator of the softmax has a name and a life of its own. The log-partition function, called logsumexp in code, is

Definition

Log-partition function

Written and called logsumexp in every numerical library. It is the logarithm of the softmax’s normalizing denominator, and it is the primitive that log-probabilities, cross-entropy, and every divergence in this book are built from.

Take the logarithm of the softmax formula and you get, with no approximation,

That identity is the definition of log_softmax. A log-probability is a logit minus a single scalar shared by the whole vector.

LSE has the same overflow problem as the softmax, and the same fix. With ,

$$\mathrm{LSE}(z) = \log \sum_j e^{z_j} = \log \left( e^{m} \sum_j e^{z_j - m} \right) = m + \log \sum_j e^{z_j - m}.$$

The first step pulls out of the sum, valid because it is constant with respect to ; the second uses . The right-hand side never exponentiates anything above zero, and the remaining term is bounded between 0 and . Same quantity, computed in an order the number format can survive.

Now the rule, which is the single most useful sentence in Lab 00:

Never take the logarithm of a probability you obtained from a softmax. Get log-probabilities directly from log_softmax.

The reason is underflow, which is overflow’s mirror image and much less widely understood.

Definition

Underflow

A calculation whose true result is smaller in magnitude than the smallest positive value the number format can store. The result becomes exactly 0.0. Unlike overflow, underflow produces a perfectly ordinary-looking number, and you find out about it later, when something takes its logarithm or divides by it.

Here is the argument in full, because it is the load-bearing one.

The fp32 format’s smallest positive value is about . (That figure is the smallest subnormal, which Section 2.7 defines; the smallest value with full precision is much larger, at .) Anything below the floor is stored as exactly 0.0, with no warning and no exception.

Take logits . This is not an exotic input. It is what a trained model looks like at a position where it is confident: one plausible continuation and one it has ruled out, separated by a large margin. The true probability of the second token is

That is below by a factor of about a thousand. Run the composed path, softmax and then log: the softmax stores as 0.0, the logarithm of 0.0 is , and you have a negative infinity sitting in your log-probability tensor.

Run the direct path, log_softmax, and the identity is evaluated instead. Here , so the answer is , a subtraction of two ordinary-sized numbers. The tiny probability is never materialized in any intermediate at any point, and the result is a normal float that behaves correctly downstream.

Same mathematics. Different order of operations. Only one order fits inside the number format.

What happens next is worth memorizing, because it is how you will encounter this. The flows into a divergence. Forward KL contains a term ; if the student’s log-probability is where the teacher’s probability is positive, that term is and the loss is inf. Subtract inf from inf downstream and you get nan. One backward pass later every parameter is nan, and every subsequent loss is nan regardless of the data.

On a dashboard that is a curve which behaves and then goes vertical, and the label your brain attaches to it is “the run became unstable.” Everything you know about unstable training tells you to reduce the learning rate, add warmup, clip the gradient norm. None of it helps. The tell is that the failure reproduces at the same step on the same data regardless of the learning rate, which is not how genuine optimizer divergence behaves.

The listing below is the smallest complete demonstration. Watch the second entry of each output.

import torch
import torch.nn.functional as F

z = torch.tensor([0.0, -110.0])          # a token the model has ruled out

composed = F.softmax(z, -1).log()        # materialises exp(-110) ~ 1.7e-48, stores 0.0
direct   = F.log_softmax(z, -1)          # evaluates z_i - logsumexp(z), all ordinary sizes

print(composed)   # tensor([0., -inf])
print(direct)     # tensor([   0., -110.])

assert torch.isinf(composed[1])
assert torch.isfinite(direct[1])

The composed path is wrong by an infinite amount on an input a real model produces at a real position, which is the justification for a rule that otherwise sounds like style preference.

Both of those have a consequence past this one function. If you need for small , use log1p(-x) rather than composing, for the same class of reason. And if you are handed probabilities rather than log-probabilities, by an API or a cache or a colleague, you have lost information you cannot recover, because every value that underflowed is now indistinguishable from one that was genuinely zero. Serving stacks return log-probabilities for this reason, and Chapter 15 covers the shape of that response.16

2.4 Temperature#

Temperature is one scalar divided into every logit before the softmax.

Definition

Temperature

A positive scalar that rescales logits before the softmax: . Values below 1 sharpen the distribution toward its largest entry; values above 1 flatten it toward uniform; leaves it unchanged.

The name is borrowed from statistical physics, where the same expression describes the occupancy of energy states at temperature , and the analogy carries: high temperature means the system spends time in states it would otherwise never visit.

What the knob does is pinned down by its two limits, both derivable in a few lines.

As , the distribution converges to a one-hot vector on the largest logit. Write the softened probability with the max subtracted, which shift invariance permits. Let , attained at a unique index . Then

Every exponent is zero or negative. For the numerator is . For every other , , so as and the numerator goes to zero while the denominator goes to 1. So and everything else goes to 0. Ties for the maximum spread the limit uniformly over the tied set, by the same argument.

As , the distribution converges to uniform. Now every exponent $(z_i - m)/T \to 0Vp_i \to 1/V$, and the distribution has forgotten the logits.

Between those limits it moves smoothly, and the way it moves is why temperature is in this book at all. Take the seven-token vector Lab 01 uses, with logits standing for the, a, an, cat, dog, xylophone, qq.

Table 2.1 One logit vector, six temperatures. Probabilities rounded; entropy in nats.

entropy the a an cat dog xylophone qq
0.25 0.0006 0.99995 0.000045 0.000006 0 0 0 0
0.5 0.057 0.9909 0.0067 0.0025 0.000001 0 0 0
1.0 0.448 0.8823 0.0724 0.0439 0.0008 0.0005 0.00004 0.00002
2.0 1.053 0.6353 0.1820 0.1417 0.0192 0.0149 0.0043 0.0026
4.0 1.578 0.4031 0.2158 0.1904 0.0700 0.0618 0.0331 0.0258
10.0 1.875 0.2387 0.1859 0.1768 0.1185 0.1128 0.0878 0.0795

Read the top row. At the distribution is one-hot to five decimal places, so a student trained to match it learns which token was correct and nothing else, which is what a hard label would have taught it. Everything distillation is supposed to transfer has been annihilated by the temperature.

Read the bottom row. At , xylophone is at 0.0878 against the at 0.2387, a ratio of about one to three. The teacher does not believe xylophone is a third as likely as the; it assigned them logits of and . What the student is being asked to reproduce, with a substantial share of the loss, is the teacher’s noise floor, the region where it has ruled everything out and the remaining differences are close to arbitrary.

The middle rows are where the mechanism operates. Dark knowledge, defined in Chapter 1, is carried by the ratios among the teacher’s rejected tokens. In the row, everything below the top token amounts to 0.118 of the mass, and the informative part of it, the difference between cat at 0.0008 and xylophone at 0.00004, contributes a cross-entropy term four orders of magnitude below the top token’s. The gradient will not notice. Raise the temperature and those ratios survive while their absolute sizes grow: at the same two tokens sit at 0.0192 and 0.0043, large enough to move a loss.

That is the mechanism: temperature does not create the information in the teacher’s tail, it rescales the tail into the range where a gradient-based optimizer can respond to it. Hinton, Vinyals, and Dean introduced this move, and it is the one hyperparameter their method could not do without.3 A factor of has to accompany the softened loss term to keep its gradient scale comparable across settings of , and Chapter 5 derives it.

2026-08-01T07:26:27.421249 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1 0 6 1 0 4 1 0 2 1 0 0 probability one-hot: a hard label would have taught the same thing T = 0.25 H = 0.0006 nats T = 0.5 H = 0.0573 nats T = 1 H = 0.4479 nats the a an cat dog xylophone qq 1 0 6 1 0 4 1 0 2 1 0 0 probability T = 2 H = 1.0527 nats the a an cat dog xylophone qq T = 4 H = 1.5783 nats the a an cat dog xylophone qq xylophone within a factor of 2.7 of `the` T = 10 H = 1.8748 nats
Figure 2.1 One fixed logit vector under six temperatures: as T rises the top token's mass drains into the tail, and the ratios among rejected tokens grow into a range a gradient can act on.

One clarification, because the same word covers three jobs. The temperature on an inference API applies this formula to change what gets sampled. The temperature in a distillation loss changes what the target looks like, with no sampling involved. And temperature scaling as post-hoc calibration fits one scalar on held-out data so that a model’s confidences match its accuracy, which makes it measured rather than chosen; Chapter 16 uses that version.4

The useful range of depends on how peaked your teacher is on your data. Peaked domains, where the continuation is nearly forced (code, structured output, arithmetic), tolerate low temperatures because there is little tail to expose. Diffuse domains, where many continuations are reasonable, need more. That is a property of your corpus and your teacher, measurable in an afternoon, and not a constant to copy out of someone else’s configuration file.

2.5 Entropy, in nats#

Temperature moves a distribution between “certain” and “uniform.” Entropy says where on that axis a distribution currently sits.

Definition

Nat

The unit of information you get when your logarithms are natural. One nat is $1/\ln 2 \approx 1.4427$ bits. Everything in this book is in nats, because every loss in this book uses natural logarithms and because log returns the natural logarithm in every framework you will use, and mixing bases silently changes every number by a factor of 0.693.

Definition

Entropy

, measured in nats. It is the average number of nats of surprise you get per sample from , and operationally it measures how spread out is. Zero for a distribution certain of its answer, for the uniform distribution over outcomes, and nothing outside that range.

Both extremes are worth confirming. For a one-hot distribution one term is and every other is , taken to be zero by the limit . So : a distribution that always gives the same answer carries no surprise. For the uniform distribution every , so

and a standard argument by concavity shows no distribution does better. Over a 151,936-token vocabulary that ceiling is nats.

The teacher’s entropy is one of the two terms in the identity connecting entropy, cross-entropy, and KL, and Chapter 3 lives inside that identity. What I want here is the shape of as a function of temperature. The limits are established: as and as . In between, rises monotonically, and the rate is worth knowing at both ends.

At high temperature, expand for small , where is the mean logit. Then , and carrying the expansion of to second order gives

where is the variance of the logits. Entropy approaches its ceiling like , at a rate set by how spread out the logits were. On Table 2.1’s seven-token vector that variance is 14.48, so the prediction at is nats against an exact 1.8748. It lands that close because is already deep in the asymptotic regime for logits of this size.

At low temperature, let be the gap between the largest logit and logit . Each rejected token contributes about to the entropy, so

which goes to zero exponentially fast in . Entropy does not decline gently as you cool a distribution; it falls off a cliff. On Table 2.1, dropping from 1.0 to 0.5 cuts entropy by a factor of eight, and dropping to 0.25 cuts it by another factor of ninety.

2026-08-01T07:26:29.727698 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.1 0.25 0.5 1 2 4 10 temperature T 0.0 0.5 1.0 1.5 2.0 entropy of softmax(z / T), nats uniform ceiling, log 7 = 1.9459 one-hot floor 0.0006 0.0573 0.4479 1.0527 1.5783 1.8748 exact entropy low-T expansion i D T i e D T / i ( 1 + / ) high-T expansion l o g   V   , / 2 σ T 2 2 σ 2   =   1 4 . 4 8 where T is actually chosen
Figure 2.2 Entropy in nats of a fixed logit vector as a function of temperature, with the two asymptotes marked: exponential decay to zero as T falls and 1/T-squared approach to log V as T rises.

Entropy earns its place here for a reason beyond Chapter 3’s identity: it is the cheapest useful thing you can monitor during a run that is not the loss. The loss tells you the objective is going down, which it does whether or not anything good is happening. Mean entropy tells you whether the student is becoming more decisive, and how fast.

A healthy student’s entropy declines somewhat and then plateaus, because training on a teacher’s targets concentrates its mass. A student in trouble keeps going, and the failure has a name. Entropy collapse is the runaway version, where the distributions narrow until the output is effectively deterministic and the text loses all variety. It is well documented in reinforcement-learning post-training of reasoning models, characterized there as an exchange between entropy and short-term performance, and the same mechanism appears in on-policy distillation, where the entropy worth watching is measured on the student’s own rollouts.5612 Chapter 12 covers the monitors that catch it and the distinction that makes one useful: a healthy decline of ten to thirty percent is not collapse, and a monitor that fires on that is worthless. The course’s own pairs a floor of 0.15 nats with a rule about entropy lost inside a trailing window.

Entropy also has a diagnostic use that costs nothing. If your student’s mean entropy is larger than your teacher’s on the same inputs, the student is hedging, which is the signature of a mode-covering objective (Chapter 6). If it is much smaller, the student has committed, which is the signature of a mode-seeking one. That one number, read before any sample text, usually tells you which situation you are in.

2.6 The tail#

A next-token distribution has a head and a tail, and much of distillation depends on which one you are paying attention to.

The head is easy to describe. At most positions in ordinary text a trained model is confident: the top token takes most of the mass, a handful of alternatives take most of what is left, and tens of thousands of remaining entries share a remainder that rounds to nothing. On Table 2.1 at , the top token holds 0.8823 and the other six share 0.1177.

The tail is the rest of the vocabulary, and it has two properties that pull in opposite directions.

The tail holds almost no probability mass. The course measures this on real data: Lab 02 runs the teacher over the corpus Lab 03 trains on and computes how much of each position’s mass the top entries capture. At , mean retained mass exceeds 0.99. Ninety-nine percent of the teacher’s belief lives in 64 out of 49,152 entries, 0.13 percent of the vocabulary. Chapter 10 turns that measurement into a storage decision.

The tail holds most of the entries, and all of the information about what the model considers absurd. Dark knowledge is a statement about ratios among rejected tokens, and rejected tokens are by definition in the tail. A student that matches the head perfectly and ignores the tail has copied the teacher’s decision and learned nothing about its similarity structure.

Both statements hold at once, which is why the tail is the interesting part of the object and the hardest to work with. A quantity can be numerically negligible and informationally significant at the same time, and temperature is the device that converts the second property into something the first does not suppress.

The tail’s third property is the one this chapter cares about most. The tail is where the underflow lives: a probability of is a fine fp32 number and a hard zero in fp16, and is a hard zero in both. Every claim about the tail is therefore also a claim about your number format, and a pipeline that computes the head correctly can be silently wrong about the tail on the same batch.

One more thing the tail does will surprise you if you have not seen it, and Chapter 3 makes it precise. A tiny amount of mass in the wrong place can produce an unbounded KL divergence, because the divergence weights each token by the logarithm of a probability ratio and a logarithm has no ceiling. Lab 00’s second solution exercise builds distribution pairs whose total variation distance goes to zero while their KL goes to infinity, using nothing but a shrinking mass on a token where the other distribution is astronomically small. So “the tail holds one percent of the mass” does not license “the tail contributes one percent of the loss.” It usually does, it can fail arbitrarily badly, and the case where it fails is the one where the student puts near zero probability on something the teacher likes, which is what a freshly initialized student does everywhere.

Carry three things out of this section and into Chapter 3. The tail exists, its mass is measurable on your own corpus with one prefill pass, and you should measure it. When the only thing you have is the token that was sampled, the tail becomes something you estimate rather than compute, and Chapter 4 is about how badly that can go.13 And the split between the top-1 token and everything else is the split between what a hard label tells you and what distillation is for. Work on generation quality has circled the same object from the sampling side for years: nucleus sampling exists because the shape of the tail decides whether sampled text degenerates.7

2.7 Floating point, in enough detail to predict failures#

Everything above assumed the numbers behave. This section says what they actually do.

2.7.1 The anatomy of a float#

A floating-point number is stored as three bit fields: a sign bit , an exponent field , and a mantissa field with bits. In the normal range, the value is

The factor in parentheses is the significand. It lies in , and the leading 1 is not stored because it is always there, which buys one bit of precision for free. That implicit bit is the source of the documentation conflict this section resolves later.

That layout has two consequences, and between them they explain every property of every format.

The exponent field buys range. With exponent bits the exponent covers roughly powers of two, so each additional exponent bit squares the ratio between the largest and smallest representable magnitude. Range is cheap.

The mantissa field buys precision, and it buys the same relative precision everywhere. With stored mantissa bits the significand is quantized into steps across , so within any binade (any interval ) the representable values are spaced apart. Spacing scales with magnitude: near 1.0 the gap is , near 1000 it is a thousand times larger, near a million times smaller. A float has constant relative precision and wildly varying absolute precision, and most floating-point surprises reduce to expecting the opposite.

That constant has a name.

Definition

Machine epsilon

The gap between 1.0 and the next representable number above it. For a format with stored mantissa bits, machine epsilon is exactly . Read it as a digit budget: a format with an epsilon of carries about seven reliable decimal digits, and one with an epsilon of carries between two and three.

Machine epsilon is measurable without knowing anything about the bit layout, which makes it the honest way to settle an argument about a format’s precision. If it comes out to , then , whatever the documentation says.

2026-08-01T07:26:28.743315 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1 0 4 5 1 0 3 0 1 0 1 5 1 0 0 1 0 1 5 1 0 3 0 magnitude of the number 1 0 4 5 1 0 3 0 1 0 1 5 1 0 0 1 0 1 5 1 0 3 0 gap to the next representable value where a rejected token's probability lives fp32 fp16 bf16 1.4e-45 9.2e-41 6.0e-8 flat: the subnormal floor. Below it, everything is zero. fp16 overflows to inf at 65504 fp32 and bf16 overflow at 3.4e38 at x = 1 the gap is the machine epsilon: 7.8e-3 bf16, 9.8e-4 fp16, 1.2e-7 fp32
Figure 2.3 Spacing between adjacent representable values against magnitude for fp32, fp16, and bf16: constant relative precision shows up as parallel lines on log-log axes, and the vertical cliffs are where each format overflows and where each one underflows to zero.

2.7.2 The three formats you will actually use#

Table 2.2 The formats, with everything that follows from the bit split.

format total bits sign exponent bits stored mantissa bits significand precision max finite machine epsilon smallest normal smallest subnormal
fp32 32 1 8 23 24 bits 3.4e38 1.2e-7 1.2e-38 1.4e-45
fp16 16 1 5 10 11 bits 65504 9.8e-4 6.1e-5 6.0e-8
bf16 16 1 8 7 8 bits 3.4e38 7.8e-3 1.2e-38 9.2e-41

Read the table as one decision made twice with opposite answers. Both 16-bit formats have 16 bits to spend. fp16 puts 10 into mantissa and 5 into exponent. bf16 puts 7 into mantissa and 8 into exponent, which is exactly fp32’s exponent field.

Definition

bf16

Brain floating point, a 16-bit format with fp32’s 8 exponent bits and only 7 stored mantissa bits. It has fp32’s dynamic range and about a third of its precision: nothing that fits in fp32 overflows or underflows in bf16, and every value carries only two to three reliable decimal digits. Converting fp32 to bf16 is a truncation of the low 16 bits, which is why the conversion is so cheap.

The consequences follow from the table. fp16 overflows at 65,504, which Section 2.2 showed is exp of a logit above 11, and it flushes any probability below about to exact zero. Both thresholds sit inside a language model’s operating range, which is why fp16 needs loss scaling to train at all and why it produces exact zeros where a distribution should have small positive numbers. bf16 cannot overflow on anything fp32 could hold and cannot underflow before fp32 does. What it does instead is round: every bf16 result is good to about three decimal digits and noise after that.

The failure signatures differ accordingly. fp16 fails loudly, with inf, nan, and a dead run. bf16 fails quietly: the curve is smooth, the numbers are plausible, and the third digit is garbage. On a dashboard that looks like a plateau, or like a run that gives slightly different numbers each time you repeat it with the same seed. You conclude your intervention did not work, when your instrument could not resolve the effect you were measuring.

2.7.3 Subnormals and flush-to-zero#

Section 2.7.1’s formula has a lower limit: once the exponent field hits its minimum you cannot go smaller by decrementing it. IEEE 754 handles that with a second regime.

Definition

Subnormal

A floating-point value below the format’s smallest normal magnitude, represented with an implicit leading 0 instead of an implicit leading 1 and a fixed smallest exponent. Subnormals extend the range downward toward zero at the cost of losing precision progressively: the smallest subnormal has one significant bit. Also called denormal.

The gap between the two regimes is larger than people expect. fp32’s smallest normal value is and its smallest subnormal is , seven orders of magnitude further down, obtained by giving up the 23 mantissa bits one at a time. Below there is nothing but zero.

Operationally, subnormals matter in two places. First, they are why Section 2.3’s underflow threshold is rather than , seven orders of magnitude in how far a probability can fall before becoming a hard zero. Second, they are not always enabled. Many accelerators and some compiler settings implement flush-to-zero, replacing any subnormal result with zero because subnormal arithmetic is expensive in hardware. The effective floor then rises to the smallest normal, and probabilities between and that survived on one device become exact zeros on another. That is one mechanism by which a loss that was finite on your laptop becomes inf on your accelerator with no change to the code. The defense is the one this chapter keeps giving: stay in log space.

2.7.4 The 7-versus-8 question, resolved#

The course’s own materials disagree with themselves here, and rather than quietly fixing one side I want to state the resolution, because this is the kind of disagreement that looks like a contradiction and is not.

Watch out

Lab 00’s format table says bf16 has 7 mantissa bits. The docstring of kd_core.topk_forward_kl says bf16 has 8 mantissa bits. Both statements are in the course, both are about the same format, and neither is a typo.

The resolution: bf16 stores 7 mantissa bits and has 8 bits of significand precision, because the leading 1 of a normal float is implicit and does not occupy a bit. The same relationship holds for the other formats: fp32 stores 23 and has 24 bits of precision, fp16 stores 10 and has 11.

When the distinction matters, use the one that answers your question. Storage layout and machine epsilon depend on the stored count: bf16’s epsilon is , not . Rounding error and spacing arguments depend on the significand count: the spacing of bf16 values immediately below 1.0 is , because those values live in the binade and their spacing is . Section 2.8’s bug turns on that second number.

The habit worth building is to say which count you mean. “bf16 has 8 mantissa bits” will make a reader compute the wrong machine epsilon by a factor of two, and a factor of two in a precision budget is the difference between a clamp that works and a clamp that does nothing.

2.7.5 The dtype policy: model in bf16, loss in fp32#

The rule the course follows, and the one Hugging Face and TRL implement internally, is one line:

Run the model in bf16. Compute the loss in fp32.

The first half is forced by memory. A parameter in bf16 costs 2 bytes instead of fp32’s 4, so a 32-billion-parameter teacher occupies 64 GB rather than 128 GB, which is the difference between fitting on the reference machine alongside a student and not fitting at all. Chapter 8 does that arithmetic in full.

The second half is forced by precision, and the argument is the digit budget. bf16’s machine epsilon of means every stored value carries a relative error of up to about 0.4 percent, and a loss accumulates over tens of thousands of terms. Lab 00 measures the effect: a KL computed through fp32 log_softmax has relative error under against an fp64 reference, while the same KL through bf16 log_softmax is more than ten times worse, and the lab asserts both bounds.

Now ask what you were going to do with that loss. An ablation compares arms whose difference late in training is a few percent of a loss value, and a run’s improvement over its own previous checkpoint is smaller still. If the third digit is noise you cannot resolve either. The policy is not about correctness of the forward pass, which bf16 handles fine. It is about whether your loss is a usable instrument.

The policy costs one fp32 copy of the logit tensor at the loss site, which at batch 8, sequence 512, and vocabulary 151,936 is 2.5 GB. That cost is why people skip the upcast. When the copy does not fit, the move is not to compute the loss in bf16; it is to reduce the batch or chunk the vocabulary dimension, both of which preserve the answer.

One more consequence, because it returns in Chapter 10. If you cache teacher outputs in bf16, which you should for storage reasons, you are storing three good digits. Three digits is plenty for log-probabilities, which are ordinary-sized negative numbers, and nothing at all for probabilities near 1. The format is identical; what changed is where on the number line you put the quantity.

2.7.6 A prediction that failed#

Lab 00’s first exercise asks the reader to break bf16 on purpose: sweep the logit scale and find where bf16’s loss arithmetic first disagrees with fp32’s in the second significant digit. The premise is plausible, since bf16 keeps two to three significant digits and larger logits produce larger absolute rounding errors.

The solution notebook ran the sweep at scales 1, 2, 4, 8, 12, 20, and 40 against an fp64 reference, and the premise did not survive. bf16’s relative error stayed between about and throughout, peaking at scale 12 and getting better at scale 40. The third digit went bad at scales 2 and 12, with no monotone trend between. The second digit was never corrupted at any scale tested, and the notebook asserts it: maximum bf16 relative error below .

The reason is a cancellation the premise did not account for. Scaling the logits up grows the absolute rounding error, because float spacing is proportional to magnitude. It also grows the KL itself, from about 1 nat at scale 1 to about 115 nats at scale 40. Relative error is the ratio of those two growing quantities, and they grow at comparable rates, so it wanders inside the digit budget instead of climbing out. Averaging over the test problem’s 64 rows claws back a little more, which is why the measured error sits below machine epsilon rather than at it.

Field note

The corrected statement is not “bf16 gets relatively worse as logits grow.” It is “bf16 holds the loss to about three digits regardless of scale, and the third digit is already noise at realistic post-temperature logit scales.”

I am keeping the failed prediction rather than quietly replacing it, because the failure is more instructive than the result. The reasoning behind it was not sloppy. It was a correct statement about absolute error plus an unstated assumption that the quantity being measured holds still while the error grows, and that assumption is false here and in many precision arguments. When you reason about relative error, write down the numerator and the denominator and ask what each does under the change you are making.

The corrected version is still ample reason for the dtype policy. A loss whose third digit is noise cannot resolve the small late-training differences a distillation study is built to measure, at any scale rather than only at extreme ones.

2.8 A clamp that did nothing#

This is a real bug from the course’s own history, and the cleanest illustration in the book of why the previous section’s arithmetic is worth carrying around.

Chapter 10 covers top-k logit caching properly. The piece needed here: when you keep only the teacher’s top log-probabilities, you have to decide what to do about the missing mass, and one standard answer is to keep the total discarded mass as a single extra bucket. The loss then gains a term comparing the teacher’s tail mass against the student’s, where the student’s tail mass is one minus its mass on the retained tokens:

That needs care in floating point, because the sum can be very close to 1 and can exceed it by a rounding error. The obvious defense is to clamp the sum below 1 before subtracting, with a bound like , and to use log1p rather than composing a subtraction with a logarithm. That is what the implementation did.

Field note

The bug: in bf16, that clamp is a no-op, and the guarded expression produces -inf anyway.

Work through the numbers. On teacher-forced text the student’s mass on the teacher’s top- tokens routinely runs above 0.998. bf16 values immediately below 1.0 live in the binade , where the spacing is . The largest bf16 value below 1.0 is therefore 0.99609375, and the midpoint between it and 1.0 is 0.998046875. Round-to-nearest sends anything above that midpoint to exactly 1.0.

So a student mass of 0.999 is stored as exactly 1.0, and the clamp bound is also stored as exactly 1.0, being above the same midpoint. The minimum of two values that are both exactly 1.0 is exactly 1.0: the clamp compares against a bound that is not representable and therefore cannot bind. Then log1p(-1.0) is , the tail term is , and the loss is nan from the first step onward.

What made this hard to find is that the code looks correct. There is a clamp, there is a log1p, both are the textbook defenses, and a reviewer sees a guarded computation. The guard fails not because the logic is wrong but because the bound was chosen in the wrong precision: is a sensible margin in fp32, where the spacing near 1.0 is , and it is four orders of magnitude below the resolution of the format the tensor was in.

The fix is Section 2.7.5’s dtype policy applied at the top of the function rather than patched at the point of failure: upcast to fp32 first, whatever the student’s dtype, which costs one copy of a [batch, sequence, k] tensor. Raising the clamp margin to something bf16 can represent also stops the nan, and is worse, because it leaves a tail term carrying the resolution of a three-digit format.

The general lesson, which I now apply reflexively: an epsilon is a number relative to a precision. Any constant of the form is a statement about the format the computation runs in, and moving that computation to another dtype silently changes what the constant means. When you meet a magic small constant in numerical code, the first question is which dtype it was chosen for.

The listing below is the whole bug in six lines. Look at the comparison in the last two prints.

import torch

CLAMP = 1.0 - 1e-6                       # intended as "strictly below 1"
student_mass = torch.tensor([0.999])     # student's probability on the teacher's top-k

for dtype in (torch.float32, torch.bfloat16):
    s = student_mass.to(dtype)
    bound = torch.tensor([CLAMP], dtype=dtype)
    guarded = torch.minimum(s, bound)
    print(dtype, float(guarded), float(torch.log1p(-guarded.float())))

# torch.float32   0.99900001   -6.90777
# torch.bfloat16  1.0               -inf

The same expression, with the same guard, on the same input, is finite in one dtype and infinite in the other. Nothing in the source distinguishes the two cases, which is why this class of bug survives review.

2.9 What all of this costs#

A short accounting, in the spirit the rest of the book keeps.

Max-subtraction costs one reduction over the vocabulary dimension and is already inside your framework. Log space costs nothing either, since log_softmax and softmax do the same work; its price is conceptual, because the identities you know for probabilities (they sum to one, they multiply) become different ones (they logsumexp to zero, they add). The fp32 loss policy costs one copy of the logit tensor, 2.5 GB at [8, 512, 151936], which is one more reason Chapter 10’s top-k pipeline is attractive: it shrinks the tensor that has to be upcast.

What the policy gives up is the last few digits. fp32 relative error here lands near , fine for a training loss and not fine for a claim about a difference of between two arms. If you make that kind of claim, compute the measurement in fp64 and say so, which is what the labs do when they need a ground-truth reference.

2.10 What to be suspicious of#

The presentation above makes three things sound more settled than they are.

The set of 16-bit formats is not stable, and neither format was designed for this. fp16 came from graphics and bf16 from accelerator design, and both were compromises that happened to be available when large-model training needed them. Formats below 16 bits are already in serious use, including 8-bit floating-point variants and 4-bit schemes that are not IEEE floats at all: QLoRA’s NF4 assumes the stored values are approximately normally distributed rather than spread over a uniform exponent grid, and post-training quantization methods choose for themselves which values deserve precision.8910 Chapter 15 covers what those cost in quality. Section 2.7’s analysis applies to them in structure, and none of its numbers do.

“Compute the loss in fp32” rests on a threshold nobody has characterized properly. I have given you the digit-budget argument and one measurement. The argument is sound, and nobody has systematically mapped which distillation objectives at which scales degrade in bf16 and by how much. The rule is cheap enough that following it without that map is the right call, and you should know you are following an argument rather than evidence.

The relationship between measured entropy and anything you care about is loose. Entropy is cheap to compute and it is the standard early-warning signal for collapse. A run can also have healthy entropy and produce bad text, or declining entropy and produce good text. The work on when diversity actually collapses during post-training is active and does not agree with itself yet.11

2.11 Where this lands in the labs#

Lab 00 is where every claim in this chapter gets asserted rather than argued, on no GPU and with no training: the naive softmax on temperature-scaled logits, asserted nan; both paths to a log-probability, asserted -inf on one and on the other; a KL computed in fp64, fp32, bf16, and fp16 with the error ordering asserted; fp16 turning a probability of into an exact zero. What the lab does that this chapter cannot is let you change one number and watch the failure move. Set the temperature to 0.3 and find where the overflow starts, or change the to and watch the composed path start working. Lab 00’s first solution exercise is the bf16 sweep whose premise failed in Section 2.7.6, and the fifteen minutes you spend expecting the second digit to break are the point of it. Lab 01 §1 is Table 2.1, and it runs in under a second.

2.12 Exercises#

  1. A checkpoint’s largest logit at some position is 34.2, and you are about to compute a softmax at temperature 0.2 in fp16 without max-subtraction. Write the arithmetic out, say whether it overflows, and give the smallest temperature at which it would not. Repeat for fp32. Then say what happens to both answers under max-subtraction, and prove it from shift invariance.

  2. Using , predict the entropy of Table 2.1’s logit vector at before looking at Figure 2.3. State the condition under which the two-term expansion should be trusted, in terms of and , and say whether satisfies it.

  3. A colleague reports a run whose loss is stable at 2.417 for a hundred steps, reads inf at step 101, and is nan afterward, and that cutting the learning rate by 10x reproduces the failure at step 101 exactly. Give the most likely cause, name the line of code you would read first, and say what single measurement would confirm or refute your hypothesis before you change anything.

  4. Section 2.8’s clamp bound of fails in bf16. Someone proposes changing the bound to , which bf16 can represent. Say what that does to the tail term when the student’s true tail mass is , whether the resulting loss is biased upward or downward, and which of the two fixes you would ship.

  5. You inherit a logit cache whose README says it stores probabilities in fp16, top 32 entries per position. List what you can no longer determine from it that a cache of fp32 log-probabilities would have given you, and say for each item whether the loss is recoverable.

  6. Argue both sides of this: “since the top 64 tokens hold more than 99 percent of the teacher’s probability mass, discarding the rest costs at most one percent of the training signal.” Use Section 2.6 for the case against, and be specific about the quantity that makes the claim fail. Then say what you would measure on your own corpus to settle it there.



  1. Vocabulary sizes as priced in the course’s cache-cost table (Lab 01 §6). For the Qwen family’s tokenizer and model configuration see Qwen Team, “Qwen2.5 Technical Report,” arXiv:2412.15115 (2024). https://arxiv.org/abs/2412.15115 

  2. The course’s student is from the SmolLM2 family: Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big, Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737 

  3. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015), §2. The softened-softmax formulation and the argument that the relative probabilities of incorrect answers carry the transferable structure are both there. https://arxiv.org/abs/1503.02531 

  4. Chuan Guo, Geoff Pleiss, Yu Sun, and Kilian Q. Weinberger, “On Calibration of Modern Neural Networks,” arXiv:1706.04599 (2017), ICML 2017. Temperature scaling as a single-parameter post-hoc calibration method, fitted on a validation set. https://arxiv.org/abs/1706.04599 

  5. Ganqu Cui et al., “The Entropy Mechanism of Reinforcement Learning for Reasoning Language Models,” arXiv:2505.22617 (2025). The standard reference for entropy collapse in reinforcement learning with verifiable rewards, including the entropy-performance exchange law. https://arxiv.org/abs/2505.22617 

  6. Renren Jin et al., “Revisiting Entropy in Reinforcement Learning for Large Reasoning Models,” arXiv:2511.05993 (2025), ACL 2026 Findings. https://arxiv.org/abs/2511.05993 

  7. Ari Holtzman, Jan Buys, Li Du, Maxwell Forbes, and Yejin Choi, “The Curious Case of Neural Text Degeneration,” arXiv:1904.09751 (2019), ICLR 2020. Nucleus sampling truncates the distribution at a cumulative probability threshold, which is a statement about tail shape. https://arxiv.org/abs/1904.09751 

  8. Tim Dettmers, Artidoro Pagnoni, Ari Holtzman, and Luke Zettlemoyer, “QLoRA: Efficient Finetuning of Quantized LLMs,” arXiv:2305.14314 (2023), NeurIPS 2023. The 4-bit NormalFloat data type is built around an assumed distribution of the values being stored rather than around a uniform exponent grid. https://arxiv.org/abs/2305.14314 

  9. Elias Frantar, Saleh Ashkboos, Torsten Hoefler, and Dan Alistarh, “GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers,” arXiv:2210.17323 (2022), ICLR 2023. https://arxiv.org/abs/2210.17323 

  10. Ji Lin et al., “AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration,” arXiv:2306.00978 (2023), MLSys 2024. https://arxiv.org/abs/2306.00978 

  11. Constantinos Karouzos, Xingwei Tan, and Nikolaos Aletras, “Where does output diversity collapse in post-training?” arXiv:2604.16027 (2026); Longfei Yun et al., “The Price of Format: Diversity Collapse in LLMs,” arXiv:2505.18949 (2025). Both are preprints without a peer-reviewed venue at the time of writing. https://arxiv.org/abs/2604.16027 · https://arxiv.org/abs/2505.18949 

  12. Rishabh Agarwal et al., “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. The on-policy setting where the student’s own rollouts are scored, and where entropy on those rollouts is the quantity worth watching. https://arxiv.org/abs/2306.13649 

  13. John Schulman, “Approximating KL Divergence,” blog post, joschu.net, 7 March 2020, accessed 1 August 2026. http://joschu.net/blog/kl-approx.html 

  14. Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819. https://arxiv.org/abs/2006.05525 

  15. Xiaohan Xu et al., “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024). https://arxiv.org/abs/2402.13116 

  16. Woosuk Kwon et al., “Efficient Memory Management for Large Language Model Serving with PagedAttention,” arXiv:2309.06180 (2023), SOSP 2023. The serving path that returns log-probabilities rather than probabilities, and the shape of that response, are covered in Chapter 15. https://arxiv.org/abs/2309.06180 

Part I · Foundations

3

Measuring the Distance Between Two Distributions

At one position in one sequence, the teacher has a probability distribution over its vocabulary and the student has another one. Two vectors of 151,936 numbers each, both summing to one. Your job is to collapse the difference between them into a single scalar, because a scalar is what an optimizer can descend.

There are a lot of ways to do that, and the choice is not a formality. Two students trained on the same corpus, from the same initialization, against the same teacher, for the same number of steps, with the same learning rate, will produce visibly different text depending on which scalar you chose. One will hedge. One will commit. One will occasionally produce something the teacher would never have said. Chapter 6 runs that comparison as a controlled experiment and reports what happens. This chapter builds the objects the comparison is made of.

I want to be careful about the order here. Almost everything written about divergence choice in distillation starts from behavior: forward KL is mode covering, reverse KL is mode seeking, use reverse KL if you want crisp output. Those statements are true and I will get to them. But they are consequences of arithmetic, and if you learn the consequences without the arithmetic you will not be able to predict what a new objective does when someone publishes one next month. So this chapter stays with the arithmetic. Every behavioral claim I make here is derived from the shape of a function, and every one of them is checkable by hand on a four-token vocabulary.

Throughout, and throughout the rest of the book, is the teacher and is the student. That convention comes from the labs and it is worth fixing now, because half the confusion in this subject comes from people writing and not saying which letter is which model.

3.1 One identity, and everything that follows from it#

Chapter 2 defined entropy. Here are the other two quantities in the same family, all three measured in the same unit.

Every divergence below is in nats, as Chapter 2 defined them, because that is what log returns in every framework you will use and because it is the unit the entropy in the identity is already in.

For two distributions and over the same finite set of outcomes indexed by :

The first is entropy, the uncertainty in by itself. The second is cross-entropy, which is the quantity your training loop already minimizes: the average number of nats it costs to encode samples from using a code built for . The third is the Kullback-Leibler divergence.

Definition

KL divergence

, measured in nats. The extra cost, per outcome, of describing using a code optimized for rather than a code optimized for . It is zero exactly when , positive otherwise, asymmetric in its two arguments, and unbounded above.

The three are related by one identity, and it is the single most useful line in this chapter:

The derivation is two steps of algebra, and it is worth doing rather than quoting, because the two steps tell you what the identity means.

Rearranged, that is the identity. The first step splits the log of a ratio into a difference of logs. The second step recognizes each piece. Nothing else happens.

Read it as an accounting statement. Cross-entropy is the total bill. Entropy is the part of the bill you were always going to pay, because is genuinely uncertain and no model can remove uncertainty that lives in the target. KL is the remainder, the part that is your model’s fault. Drive KL to zero and you have paid exactly the irreducible price and not one nat more.

Now the consequence that makes the identity load-bearing for distillation. During training the teacher is fixed. Its parameters do not move, so is a constant with respect to everything the optimizer touches. Which means:

Minimizing cross-entropy against a fixed reference is minimizing forward KL against that reference. They differ by a constant, they have identical gradients, and they reach their minimum at the same parameters. Reporting one and optimizing the other is fine. Reporting cross-entropy and claiming it is a divergence is not fine, because cross-entropy has a floor at that varies from position to position, so a cross-entropy of 2.1 nats means something different on a high-entropy position than on a low-entropy one. If you want a number you can compare across positions, subtract the entropy and report KL.

Three corollaries fall out of that identity immediately, and I want all three on the table before the next section.

Hard-label training is distillation with a one-hot teacher. A one-hot distribution puts probability 1 on a single outcome. Its entropy is , because there is nothing uncertain about it. So the identity collapses to , and the ordinary next-token cross-entropy loss you have been using since your first language model is forward KL against a degenerate teacher. Distillation with a soft teacher is not a different objective. It is the same objective with a better reference distribution, and every piece of machinery in this chapter applies to both.1 What makes a reference distribution better is itself a variable rather than a given: a teacher whose training procedure flattened the structure among its wrong-answer probabilities transmits less through this channel than one that did not, which is why label smoothing in the teacher measurably degrades the student it produces.2 Chapter 5 reproduces that argument in full.

Perplexity is a KL statement wearing different clothes. Per-token perplexity is , read as “the model is as uncertain as if it were choosing uniformly among this many options.” Substituting the identity gives . When someone reports that a distilled student has 1.4x the perplexity of its teacher on a corpus, they have reported averaged in a particular way, and the KL in question is about nats. The perplexity ratio is the interpretable quantity; the absolute perplexity mixes in , which is a property of the data and not of your model.

KL is never negative. This is Gibbs’ inequality, and it follows from concavity of the logarithm:

where the inequality is Jensen’s applied to the concave function . Equality holds exactly when is constant across all with , which for two distributions means . So a loss of zero is achievable in principle, and any negative KL appearing in a training log is a bug, with no exceptions worth discussing. Usually it is a sampled estimator (Chapter 4) or a sign error.

3.2 The two conventions that make the sum well defined#

The formula has two edge cases, and both of them show up in real training runs rather than in textbook footnotes.

Where , the term is 0. The convention is , which is not arbitrary: the limit , so defining the term this way makes continuous at the origin. Operationally it means forward KL charges the student nothing at all for its behavior on tokens the teacher has ruled out. The student can put whatever it likes there, and the forward-KL loss will not notice. Hold onto that; it is half of §3.4.

Where and , the term is . There is no convention that rescues this one. The ratio diverges, the logarithm of it diverges, and the whole sum is infinite. The extreme case has a name.

Definition

Disjoint support

Two distributions have disjoint support when every outcome that one of them assigns positive probability to, the other assigns exactly zero. KL is infinite in both directions on disjoint support.

I used to think of the infinity as a mathematical corner case, a thing that happens on the whiteboard and never in float32. It happens constantly. Two mechanisms produce it. The first is cold start: a freshly initialized student, or a student that has been pruned down from its teacher and not yet recovered, will assign genuinely negligible probability to tokens the teacher is confident about. The second is the number format. Chapter 2 established that fp16 stores nothing smaller than about 6e-8, so in fp16 a probability of 2e-16 is exactly zero rather than a small number. A distribution that was perfectly healthy in fp32 arrives at the loss site in fp16 with holes in it, and the KL that was 14 nats becomes inf, and the gradient becomes NaN, and the run dies. That is the most common way a distillation run fails on the first day.

Lab 00 makes the infinity concrete by clamping the zeros rather than pretending they are not there. On the four-token pair and with zeros replaced by , the KL comes out at nats. Halve and the answer grows by . There is no value it converges to. The clamp chooses how large you would like your infinity to be, and its size is set by a constant nobody in the room chose deliberately.

3.3 KL is not a distance, and both failures matter#

A distance in the mathematical sense is a metric, and a metric has to satisfy four conditions: non-negativity, zero exactly on identical arguments, symmetry, and the triangle inequality.

Definition

Metric

A function that is non-negative, zero exactly when , symmetric (), and satisfies the triangle inequality ().

KL satisfies the first two and fails the last two. Both failures have operational consequences and both are demonstrable on distributions you can hold in your head.

3.3.1 Asymmetry, with numbers#

Take a four-token vocabulary. Let be the softmax of the logits , which is a teacher that is confident about token 0, and let be the softmax of , which is a student that has no opinion at all. Writing them out:

Compute both directions by hand. Forward first:

Now reverse:

The same pair of distributions, 1.38 nats one way and 4.61 nats the other, a factor of 3.35. And the asymmetry has a direction you can read off the arithmetic. The forward direction is dominated by a single moderate term, because concentrates almost all its weight on the one token where the ratio is only . The reverse direction is dominated by three terms of , because spreads weight onto three tokens where has almost nothing, and is a large number. Each direction sums over the weight of its first argument. §3.4 works through the same mechanism one term at a time.

3.3.2 The triangle inequality, with a triple you can verify in your head#

Asymmetry is the failure everyone knows about. The triangle failure gets less attention and it matters more for how you plan a project, because it invalidates a chain of reasoning that multi-stage pipelines invite you to make.

Here is the cleanest counterexample I know. Take a two-token vocabulary and three distributions:

Then:

The direct route costs 0.8318 and the route through costs . Going around is cheaper than going straight, by a factor of exactly two.

The factor of exactly two is not a coincidence and the derivation is short enough to do here. Let , , and for any . Then

and

Adding the last two, the terms cancel and expands to , leaving

So for this entire one-parameter family, the detour through the uniform distribution costs exactly half of the direct path, for every . The triangle inequality is not violated marginally on adversarial inputs; it is violated by a factor of two on an entire family of the most ordinary distributions imaginable. Lab 00 finds violations by random search, drawing 2000 triples from a four-token simplex and asserting that at least one violates, which it always does. The closed-form family above is why the search never comes up empty.

The consequence is a piece of reasoning you cannot use. Suppose you distill a 70B teacher into a 14B intermediate model, then distill the intermediate into a 1.7B student. Both measured KLs come out small, and you would like to conclude that is small. You cannot. Nothing supports that step, and the family above shows the error can be a clean factor of two in the friendliest case. If you want the conclusion you have to measure it directly against the teacher, which costs one forward pass of the teacher over your evaluation set and is worth paying for. Teacher-assistant setups that insert a mid-sized model between an enormous teacher and a small student are a standard recipe, and the prune-then-distill pipeline of Chapter 13 has the same shape.

Watch out

The chained-distillation argument is invalid for KL and it is invalid for every divergence in this chapter except total variation and the square root of Jensen-Shannon, which are the only two that are metrics. If your project plan contains a sentence of the form “A is close to B and B is close to C, therefore A is close to C,” and the closeness is measured by a KL, that sentence is doing no work.

3.4 Forward and reverse KL, stated as facts about the integrand#

Since KL is asymmetric, there are two of it, and both are used.

Definition

Forward KL

with the teacher first: the expectation, under the teacher, of the log ratio of teacher to student. Also called the mode-covering or zero-avoiding direction. In this book and in the course code, “forward” always means teacher first.

Definition

Reverse KL

with the student first: the expectation, under the student, of the log ratio of student to teacher. Also called the mode-seeking or zero-forcing direction.

Now the mechanical statement, which I want on the page before any word about training behavior.

Forward KL sums over . Look at what each term does at the boundaries.

Where the teacher has mass and the student does not, and , the term is . The student is charged without limit.

Where the student has mass and the teacher does not, and , the term is by the convention in §3.2. The student is charged nothing.

Reverse KL sums , and the same two checks come out the other way around.

Where the teacher has mass and the student does not, , the term is , because . Charged nothing.

Where the student has mass and the teacher does not, with fixed, the term is . Charged without limit.

That is the entire asymmetry, stated as two sentences with no metaphor in them:

Forward KL penalizes the student for putting near-zero mass where the teacher has mass. Reverse KL penalizes the student for putting mass where the teacher has none.

Everything anyone has ever written about mode covering and mode seeking is a restatement of those two sentences in the presence of a student too small to satisfy both constraints. If the student can represent the teacher exactly, both directions are minimized at the same place, namely , and the choice does not matter. The choice matters exactly when the student cannot, which is the situation you are always in when distilling.

2026-08-01T07:26:30.655902 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.00 0.04 0.08 teacher p(x) valley: p(29) = 1.6e-06 mode 1 mode 2 4 15 28 44 56 student centre mu 4 6 8 10 12 forward KL(p||q), nats forward minimum: mu = 28.0, KL = 3.6657 forward KL reverse KL 1 2 3 4 reverse KL(q||p), nats KL = 0.9590 KL = 1.1588
Figure 3.1 Forward and reverse KL, evaluated over the same one-parameter family of student distributions against the same bimodal teacher, disagree about which student is best: the forward objective is minimized in the valley between the teacher's two modes, where the teacher has almost no mass, while the reverse objective has a local minimum on each mode and no stationary point in between.

Figure 3.1 makes the two sentences visible without any training happening. The teacher is a fixed bimodal distribution over 60 outcomes, with peaks near index 15 and index 44 and a deep valley between them. The student family is a single bump of fixed width whose center is the only free parameter, so it is constitutionally incapable of having two peaks. Sweeping across the whole range and evaluating both divergences at every gives two curves over the same set of candidates. The forward curve has one minimum, at , which is in the valley: the teacher assigns a probability of about there, so this is the student that the forward objective prefers even though it puts its own peak on a region the teacher considers nearly impossible. The reverse curve has two local minima, one sitting on each of the teacher’s modes, and a large hump between them.

Read that as a statement about the objectives, not about optimizers. Nobody has run a training loop. Two functions have been evaluated on the same 200 candidate students and they rank those candidates differently, with different argmins. The training consequences, what happens to a real student’s entropy, its output diversity, its length, and its confidence, are Chapter 6’s subject and they are measured there rather than asserted. The one-sentence preview: forward KL tends to produce a student that hedges, and reverse KL tends to produce one that commits, and which of those you want is a product decision rather than a mathematical one.3 The runaway version of commitment has a name, entropy collapse, and a literature of its own that grew up around reinforcement-learning fine-tuning before distillation adopted the vocabulary.4

3.5 One formula that contains all of them#

Every divergence in this book is an instance of a single expression. Pick a function and define

Definition

f-divergence

, where the generator is convex on and satisfies . Introduced independently by Csiszár and by Ali and Silvey in the 1960s, which is why the family is sometimes called the Ali-Silvey-Csiszár divergences.

Definition

Generator

The function that determines an f-divergence. It must be convex and satisfy . Two generators that differ by an affine term define exactly the same divergence.

The two conditions are doing specific work, and neither is decoration.5

Why . The ratio equals 1 exactly where the two distributions agree. The condition says agreement costs nothing, which is what makes .

Why convexity. Convexity buys non-negativity, through Jensen’s inequality applied in one line:

So convexity plus gives you exactly the two properties every divergence needs: it is zero on identical inputs and it is never negative. Nothing more is required, and nothing more is guaranteed. Symmetry is not guaranteed. The triangle inequality is not guaranteed. Boundedness is not guaranteed. Which of those you get is decided by the shape of , and §3.6 shows how to read them off.

There is one piece of gauge freedom worth knowing. Replacing by for any constant changes nothing, because

The affine term integrates to zero against any pair of distributions. This looks like a technicality and it is the trick behind Schulman’s k3 estimator, which Chapter 4 derives: you add an affine term that is free in expectation in order to change the variance of a sampled estimate without changing what is being estimated.6

Here is the family, written out. The direct form is the way you would implement it; the generator is what you would look for in a paper.

Table 3.1 Six divergences and their generators.

Divergence Direct form Generator
Forward KL,
Reverse KL,
Total variation
Chi-squared
Squared Hellinger
Jensen-Shannon ,

Check a couple of rows by hand to see that the correspondence is real rather than notational. Forward KL: , the cancels. Reverse KL: , a sign flip inside the log. Chi-squared: , expand the square and multiply through. The other three are the same kind of manipulation.

Notice that reverse KL is generated by while forward KL is generated by . The family is closed under swapping the arguments: if generates , then the conjugate generator generates . Apply it to and you get , which is exactly the reverse-KL row. So forward and reverse KL are not two unrelated objectives that happen to be similar; they are one generator and its conjugate.

The reason to hold the family in your head rather than a list of formulas is that it turns reading a paper into a mechanical operation. When a new distillation objective is announced, the first move is to find its generator, because two properties then follow without running anything: which direction of disagreement it charges most heavily, and whether it is bounded. The 2026 on-policy distillation literature organizes methods this way, and there is an explicit line of work on minimizing general f-divergences for sequence-level knowledge distillation.78

Verification is the other reason. The generator form and the direct form are different code paths computing the same number, so running both and asserting agreement catches an entire class of mistakes: a sign error in a hand-written direct formula, a mixed-up argument order, a normalization factor of dropped from Jensen-Shannon, an implementation that silently computes reverse KL when its argument name says forward. That last one is not hypothetical. The course’s own kd_core.kl_divergence takes the student first and the teacher second, while its direction argument names the KL order, so kl_divergence(student, teacher, direction="forward") computes . Reading argument order as KL order gets you the wrong objective with no error and no warning.

The listing below is the check in its smallest honest form. Watch that every generator is a one-liner and that the direct formulas share no code with them.

import torch

def f_divergence(p, q, f):
    """Generic f-divergence: sum_i q_i f(p_i / q_i), over the last axis."""
    return (q * f(p / q)).sum(-1)

def kl(a, b):
    return (a * (a.log() - b.log())).sum(-1)

torch.manual_seed(3)
p = torch.softmax(torch.randn(6, 48), -1)
q = torch.softmax(torch.randn(6, 48), -1)
m = 0.5 * (p + q)

cases = [
    ("forward KL",   lambda t: t * t.log(),                    kl(p, q)),
    ("reverse KL",   lambda t: -t.log(),                       kl(q, p)),
    ("total var",    lambda t: 0.5 * (t - 1).abs(),            0.5 * (p - q).abs().sum(-1)),
    ("chi-squared",  lambda t: (t - 1) ** 2,                   ((p - q) ** 2 / q).sum(-1)),
    ("sq Hellinger", lambda t: 0.5 * (t.sqrt() - 1) ** 2,      0.5 * (p.sqrt() - q.sqrt()).pow(2).sum(-1)),
    ("Jensen-Shannon", lambda t: 0.5 * (t * t.log() - (1 + t) * ((1 + t) / 2).log()),
                                                              0.5 * kl(p, m) + 0.5 * kl(q, m)),
]
for name, f, direct in cases:
    assert torch.allclose(f_divergence(p, q, f), direct, atol=1e-6), name
    print(f"{name:>15}: {float(f_divergence(p, q, f).mean()):.6f}")

Six divergences, one summation, and every one of them agrees with an independently written closed form to within floating-point noise. Lab 00 runs this check as an assertion so that it fails loudly if either path ever drifts, and Solutions 00 extends the same check with a chi-squared row as an exercise.

3.6 Reading behavior off the generator#

Two numbers determine most of what an f-divergence does, and both are limits of at the edges of its domain.

The first is , the value of the generator as the ratio goes to zero. That is the regime where the teacher has no mass and the student does: with fixed, so , and the term contributes .

The second is the recession constant . That is the regime where the student has no mass and the teacher does: with fixed, so , and the term tends to . Writing $q_i f(p_i/q_i) = p_i \cdot \frac{f(t)}{t}t = p_i/q_i$ makes the limit visible.

Table 3.2 What each generator does at the boundaries, and what follows.

Divergence Value on disjoint support Bounded?
Forward KL no
Reverse KL no
Total variation yes, by 1
Chi-squared no
Squared Hellinger yes, by 1
Jensen-Shannon yes, by

The first two rows are §3.4 restated in the new notation, and they now read as a single line each. Forward KL has and : free where the teacher is silent, infinite where the student is. Reverse KL has exactly the opposite pair.

The last column follows from the first two by an argument worth doing once. Suppose and have disjoint support. Split the sum into the outcomes where lives and the outcomes where lives. On the first group , so each term contributes , and summing gives since sums to one. On the second group , so each term contributes , and summing gives . Total:

For total variation that is . For Jensen-Shannon it is . Those are exactly the familiar bounds, and now they are derived rather than memorized, and the derivation tells you they are attained rather than approached in the limit. It also gives the general criterion.

Definition

Bounded divergence

An f-divergence whose value cannot exceed a finite ceiling for any pair of distributions. The condition is that both and are finite, in which case the maximum is , attained on disjoint support. Jensen-Shannon, total variation, and squared Hellinger are bounded; forward KL, reverse KL, and chi-squared are not.

2026-08-01T07:26:31.408499 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.0 0.5 1.0 1.5 2.0 2.5 3.0 t = p(x) / q(x) 0 1 2 3 4 generator f(t) f(1) = 0 for all six forward KL reverse KL total variation chi-squared squared Hellinger Jensen-Shannon student overweights (t < 1) student underweights (t > 1)
Figure 3.2 The six generators on the same axes: all pass through zero at t=1 and all are convex, but they charge disagreement in visibly different ways, and the two boundary limits f(0+) and f(t)/t as t grows determine whether the divergence is bounded.

Figure 3.2 puts the six generators on one pair of axes over . Three things are visible at a glance and none of them are visible from Table 3.1. Every curve touches zero at and curves upward on both sides, which is the two-condition definition drawn. The curves on the left of , the region where the student overweights relative to the teacher, are ordered completely differently from the curves on the right, which is the asymmetry made geometric. And climbs to a vertical asymptote as while approaches zero smoothly, which is the single picture that makes the forward-reverse distinction stick.

3.7 Total variation distance#

Definition

Total variation distance

, equivalently over all events . The largest amount by which the two distributions can disagree about the probability of anything. Symmetric, bounded by 1, and a genuine metric.

The two forms in that definition are worth connecting, because the second one is what makes TVD interpretable and the first one is what you compute.

Take any event , meaning any subset of the vocabulary, and ask how much the two distributions disagree about it: . To make that as large as possible, put into exactly the outcomes where is positive, since including a negative term would cancel some of your total. Call that set . Then

Now note that the positive and negative parts of have equal total size, because both distributions sum to one, so forces . Each half is therefore exactly half of . So the maximum disagreement over all events equals , which is the computational form. The factor of that looks arbitrary in the formula is what converts an L1 norm into a probability.

That interpretation is the reason TVD is the divergence to quote when you want to make a claim someone can act on. A TVD of 0.03 between teacher and student at a given position means: for any question you might ask about the next token, any set of tokens you care to define, the two models’ answers differ by at most three percentage points. No vocabulary size appears in that sentence. It holds for a 32,000-token vocabulary and a 256,000-token one alike.

TVD is a metric, satisfying all four conditions including the triangle inequality, which makes it one of exactly two quantities in this chapter you may legitimately chain. It is also almost never used as a training loss, and the reason is its generator. The derivative of is for and for , so the loss carries information about the sign of each disagreement and nothing about its size. A student that is off by 0.4 on some token receives the same gradient signal as one that is off by 0.001. Compare with forward KL, whose gradient with respect to the student’s logits is exactly the residual (Chapter 5 derives this), which is large where the disagreement is large and vanishes as the disagreement closes. TVD’s L1 structure does show up in distillation, though not as the primary objective: the Universal Logit Distillation loss for cross-tokenizer distillation sorts both probability vectors and takes an L1 distance between them, precisely because the sorted L1 distance survives a change of vocabulary in a way that a position-wise KL does not.9 Chapter 14 covers that construction.

3.8 Chi-squared, and why it is the tail-sensitive one#

Definition

Chi-squared divergence

, generated by . Equal to the variance, under , of the importance ratio . Unbounded above and dominated by whichever single outcome has the largest ratio.

The identity in the second sentence is the one to remember, and it takes three lines. Write and treat as a random variable under . First, , always, for any pair of distributions. Second, expand the generator form:

using in the last step. So the chi-squared divergence is literally the variance of the importance ratio. That fact reappears in Chapter 4 as the thing that governs whether a sampled KL estimator is usable, and it is the reason chi-squared is worth understanding even though nobody trains against it.

Chi-squared sits above KL, and the relationship is a two-line consequence of Jensen’s inequality. Start from the definition of forward KL as an expectation under and push the logarithm outside:

Then note that , since the cross term is . So

the last step because . Chi-squared upper bounds KL, and the logarithm in the middle expression is the entire difference between them. KL charges one power of a log-ratio; chi-squared charges the ratio itself, squared. On a token where the student is off by ten orders of magnitude, that difference is ten orders of magnitude.

Solutions 00 measures the gap on a construction designed to isolate it. Teacher logits are , which puts 0.998995 of the teacher’s mass on token 0. The student’s logit on token 0 is walked from down to while everything else stays at zero, so the log-ratio on that one token grows by 25 nats across the sweep and nothing else in the problem changes.

Table 3.3 The unboundedness race, from Solutions 00 Exercise 3. One token’s log-ratio grows by 25 nats; watch what each divergence does about it.

Student logit on token 0 Forward KL JSD
6.09 0.6812
11.08 0.6891
21.07 0.6892
31.06 0.6892

Forward KL grows by a factor near 5 across the sweep, which is linear in the logit gap because KL charges and grows linearly with the logit difference by construction. Chi-squared grows by more than ten orders of magnitude, which is exponential in the logit gap because . Jensen-Shannon does not move, because it has already saturated at its ceiling of and has nowhere to go. Three divergences, one moving variable, three qualitatively different responses.

The verdict Solutions 00 records is not to train against chi-squared, and the reason is in the moment form. The gradient of is dominated by whichever token currently has the largest ratio, squared. A single rare token where the student badly lags the teacher contributes a term exponentially larger than everything else in the sum combined, and one optimizer step later a different token plays that role. The compounding problem is that estimating from samples requires averaging , whose variance is governed by the fourth moment of , and Chapter 4 shows that even estimators involving to the first power are already in trouble whenever the models disagree. A divergence whose value is the variance of a quantity that was already too noisy to estimate is a poor candidate for a training signal.

3.9 Jensen-Shannon, and the metric that lives inside it#

Definition

Jensen-Shannon divergence

where is the equal mixture. Symmetric in its arguments, bounded above by (one bit), zero exactly when , and finite even on disjoint support.

The construction fixes both of KL’s structural problems at once by routing through the mixture. Symmetry is immediate from the definition, since swapping and leaves unchanged and swaps the two terms of a sum. Finiteness comes from the mixture too: wherever , the mixture has , so the ratio is at most 2 and its logarithm is at most . No term can blow up, because the mixture cannot have a hole anywhere either distribution has mass. That argument also gives the bound directly:

which agrees with the general calculation from §3.6. In bits the bound is exactly 1, which is where the “JSD is at most one bit” phrasing comes from. Equality holds exactly on disjoint support, which is the useful shape: total disagreement registers as a specific finite number rather than as an infinity, and every partial disagreement registers as something less than that number.

JSD is not a metric. It is symmetric and zero on identical inputs, but it fails the triangle inequality. Its square root does not.

is a metric on the space of probability distributions. Endres and Schindelin proved this directly, and Österreicher and Vajda established it as the case of a broader family of metric divergences, both in 2003.1011 The result generalizes: powers are metrics for .12 That is the reason JSD shows up whenever someone needs a number that behaves like a distance, such as clustering checkpoints from a training run or plotting how far a model has drifted from its initialization. Those operations assume a triangle inequality somewhere, and is one of the very few things in this chapter that supplies one.

Lab 00 checks the claim the same way it checks the KL violation, by drawing 2000 random triples from a four-token simplex and testing the triangle inequality on each. KL produces violations. The square root of JSD produces zero violations at a tolerance of . That is not a proof and the lab does not present it as one; it is a guard, so that if someone edits the JSD implementation and breaks it, the notebook fails on the next run.

3.9.1 The generalized version, and the parameter that interpolates#

JSD as defined uses an equal mixture. Replacing with a free parameter gives a family that contains both KL directions as limits, and this is the object Chapter 6 spends real time with.

Definition

Generalized Jensen-Shannon divergence

with and . At it is ordinary JSD. As it degenerates to zero, but ; as , .

The limits are the part worth checking rather than believing, so here is why they hold. As the mixture tends to . The second term goes to , and it does so quadratically in because KL behaves quadratically near its minimum. The first term is . So the whole thing vanishes linearly in with slope , which is what dividing by recovers. The end is the mirror image.

Table 3.4 on the fixed pair from Lab 01, teacher logits and student logits , for which and nats.

0.001 0.001838 1.8379 0.0018
0.01 0.018088 1.8088 0.0183
0.25 0.310710 1.2428 0.4143
0.5 0.412470 0.8249 0.8249
0.75 0.334102 0.4455 1.3364
0.99 0.022527 0.0228 2.2527
0.999 0.002318 0.0023 2.3180

Read down the third column and it climbs toward 1.8412, the forward KL, as falls. Read down the fourth column and it climbs toward 2.3256, the reverse KL, as rises. Read the second column and notice that itself is largest in the middle and vanishes at both ends, which is the trap: the raw divergence value does not tell you which direction you are heading toward, and two very different settings can produce the same loss magnitude.

I am defining here and stopping. The convention question, meaning which end of the range corresponds to which KL direction in which library, is genuinely inconsistent across the literature and the tooling, and getting it backwards trains the opposite objective with no error, no warning, and a perfectly normal-looking loss curve. That is Chapter 6’s problem and it gets a full treatment there, including the numerical procedure for determining which convention a library you have inherited actually implements. What matters here is that the object exists, that it is a one-parameter path through the divergence family connecting the two KL directions, and that the two limits above are the way you identify which end is which.

3.10 Bounded and unbounded, and what the bound buys you#

Table 3.2 sorted the six divergences into bounded and unbounded. Here is why the sorting matters during a training run.

Take a batch of 4,096 supervised positions. At 4,095 of them, teacher and student agree reasonably well and the per-position forward KL is around 0.05 nats. At one position, the student has assigned near-zero probability to a token the teacher is confident about. Lab 01 constructs exactly this position: an eight-token vocabulary, a teacher logit of 12 on token 3 so the teacher’s probability there is 0.99996, and a student logit of on the same token so the student’s probability there is .

At that position the forward KL is 31.94 nats, because the log-ratio on token 3 is and the teacher’s mass on that token is essentially all of it. The batch mean becomes instead of 0.05, a 16% jump from a single position out of four thousand.

Now change the student’s logit from to . Nothing else about the run changes; this is a student that has become slightly more wrong at one position out of four thousand. The forward KL at that position becomes 301.9 nats and the batch mean becomes 0.1237, which is two and a half times the healthy value. Change it to and the position contributes 3001.8 nats, dragging the batch mean to 0.78: more than fifteen times healthy, from one position in four thousand. Push the student logit to about and that single position carries the batch mean past 1.0 on its own. There is no value the loss converges to, because there is no ceiling on the term, and the position’s share of the mean grows linearly in the logit while everything else stays where it was.

Under JSD the same position contributes 0.6929 nats and cannot contribute more, no matter how far the student’s logit falls, because 0.6931 is the ceiling. A batch mean of against a healthy 0.02 is a 1% jump. Total disagreement at one position out of four thousand moves the batch loss by one percent, and that is the safety valve: no single position can dominate the batch.

Field note

I had the mechanism wrong for a while, and the error is worth writing down because the correct version is more interesting than the version I believed.

My assumption was that an unbounded loss means an unbounded gradient, so the loss spike and the training instability were the same event. That is false for forward KL. Chapter 5 derives the gradient of with respect to the student’s logits and it is exactly , the residual between the two distributions. Both are probabilities, so the residual is bounded in no matter how catastrophic the disagreement. At the position above, the gradient on token 3 is , which is the largest it can ever be, and it would be whether the student’s logit were or .

So why does the unboundedness matter? Three reasons, and they are all real.

First, the loss value is what you monitor, what your early-stopping rule reads, and what your gradient-clipping and loss-scaling thresholds were calibrated against. A metric that can move by a factor of twenty because of one position out of four thousand is a bad thermometer, and Chapter 8 is about reading these curves.

Second, once the student’s probability underflows to exactly zero, which fp16 accomplishes at and fp32 at around , the loss stops being a large number and becomes inf, and inf times anything in the backward pass is NaN. The gradient bound does not save you from a NaN.

Third, and this is the part that changed my mind, the bound on the gradient is specific to forward KL. For reverse KL the logit gradient is , which contains an unbounded log-ratio and is genuinely unbounded when the teacher has a hole where the student has mass. For chi-squared it is worse: the gradient contains , which diverges as the student’s probability falls. So “unbounded loss, bounded gradient” is a property of one row of Table 3.2, not of the family, and I had generalized from the one case I had checked.

2026-08-01T07:26:32.360934 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1 0 1 1 0 3 1 0 5 1 0 7 1 0 9 1 0 1 1 overlap parameter eps (decreasing to the right) 0 5 10 15 20 25 forward KL, nats forward KL has no ceiling: at eps = 1e-8 it is 17.03 nats, while JSD sits at 0.6931 and TVD at 1.0000 0.00 0.25 0.50 0.75 1.00 JSD (nats) and TVD ln 2 = 0.6931 1 JSD TVD
Figure 3.3 As two distributions are pushed toward disjoint support, forward KL grows without limit while JSD and TVD saturate at their ceilings of ln 2 and 1, which is the entire content of the bounded-divergence safety valve.

The safety valve is not free, and the cost is the mirror image of the benefit. A bounded divergence cannot tell you how bad your worst position is. Once JSD has saturated at , a student that is wrong by 14 nats and a student that is wrong by 1400 nats produce the same loss and, more to the point, nearly the same gradient. If the thing you need to fix is a small number of catastrophically wrong positions, a bounded objective has stopped providing signal about exactly them. This is the same trade in both directions: the property that keeps one bad token from destroying your batch is the property that keeps you from noticing it.

3.11 Pinsker’s inequality, and the direction it does not run#

Everything above says the divergences differ. Pinsker’s inequality is the one theorem that ties two of them together, and it is worth stating carefully because it is quoted carelessly.

Read it as a promise. Drive KL down and every event probability the student assigns converges to the teacher’s at a known rate, for every event simultaneously. A KL of 0.02 nats gives , so no matter what set of tokens you ask about, the two models’ probabilities for that set differ by at most 10 percentage points. No vocabulary size enters. That is a genuinely useful thing to be able to say about a trained student, and it is the main reason to care about a measured KL as a number rather than as a loss.

The citation on this one requires care.13 Pinsker’s 1964 book gives the inequality and the name, but with a weaker constant than the one everybody uses. The constant, meaning the form above, is due independently to Csiszár in 1967 and Kullback in 1967, with Kemperman arriving at it in 1969, which is why the result is properly called the Csiszár-Kullback-Pinsker inequality. Citing Pinsker alone for the constant is technically wrong, and it is wrong in almost every machine-learning paper that cites it. Sharper refinements exist.14

The bound is also loose almost everywhere, which is worth knowing before you rely on it. On the symmetric two-token family and , the TVD is exactly and the KL is , which for small expands to , giving . So the bound is attained in the limit along that family, and Pinsker cannot be improved as a universal statement. But sample 3,000 random pairs of 16-outcome distributions and the ratio rarely exceeds 0.9 and sits near 0.6 in the middle of the distribution. Pinsker gives you a real guarantee and it typically overstates the TVD by a substantial factor.

2026-08-01T07:26:33.996169 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1 0 6 1 0 5 1 0 4 1 0 3 1 0 2 1 0 1 1 0 0 1 0 1 1 0 2 1 0 3 KL(p||q), nats 0.00 0.25 0.50 0.75 1.00 total variation distance Pinsker's bound TVD = sqrt(KL / 2) symmetric two-token family: attains the bound only as d -> 0 Pinsker in reverse (Table 3.5): TVD 0.1 -> 0.001 while KL 9.7 -> 1000. No bound runs in this direction. 3,000 random pairs on 16 outcomes, every one below the bound
Figure 3.4 Pinsker's inequality drawn over random distribution pairs: every point falls below the bound, the bound is attained only along a narrow symmetric family as the two distributions converge, and the horizontal spread at any fixed TVD shows there is no bound in the other direction.

3.11.1 Pinsker in reverse: there is no such inequality#

The important thing about Pinsker is the direction it does not run. There is no bound of the form for any function . A tiny TVD certifies nothing whatsoever about KL, and the gap can be made arbitrarily large.

Solutions 00 constructs the counterexample explicitly, and the construction is worth carrying around because it is the compressed form of a real failure mode. Work on a two-token vocabulary. Let

The teacher puts a small mass on token 2. The student puts an astronomically smaller mass there. Now compute both quantities.

TVD sums absolute differences, so , because is negligible. As , the TVD goes to zero at exactly the rate of .

KL sums mass times log-ratio, and the log-ratio on token 2 is . So the KL is dominated by

The mass vanishes and the divergence diverges, on the same sequence, because one of them is linear in the misplaced mass and the other is linear in the misplaced mass times an unbounded log-ratio.

Table 3.5 Pinsker in reverse, from Solutions 00 Exercise 2. TVD falls to while KL climbs to on the same sequence of distribution pairs.

TVD KL (nats) KL / TVD
0.1 0.1000 9.67 96.8 2.20
0.03 0.0300 33.20 1,107 4.07
0.01 0.0100 99.94 9,994 7.07
0.003 0.0030 333.31 12.91
0.001 0.0010 999.99 22.36

The last column is Pinsker’s bound, and it holds at every row, and it is useless at every row: it promises TVD is at most 22.36, and TVD is 0.001. That is the point. Pinsker constrains TVD from above given a small KL, and here the KL is not small, so the bound has nothing to say.

There is a numerical detail in the construction that is a lesson in itself. The quantity underflows every float format almost immediately: at it is , which is zero in fp64. So the KL cannot be computed by forming and taking a ratio. It has to be computed from directly, in log space, which is exactly the discipline Chapter 2 established for a different reason. The solution notebook cross-checks the log-space formula against a plain tensor computation at , where is still representable in fp64, and asserts agreement to a relative difference below , so the shortcut is verified rather than trusted.

The listing below is the construction in the form that survives the number format. Watch that never appears anywhere except inside log1p, where its underflow to zero is harmless.

import math

def pinsker_pair(eps):
    """p = [1-eps, eps], q = [1-a, a] with log a = -1/eps^2. Returns (TVD, KL)."""
    log_a = -1.0 / eps ** 2
    a = math.exp(log_a)                     # underflows to 0.0 for small eps; that is fine
    tvd = eps - a                           # 0.5 * (|p1-q1| + |p2-q2|)
    kl = (1 - eps) * (math.log(1 - eps) - math.log1p(-a)) \
       + eps * (math.log(eps) - log_a)      # log_a used directly; a is never divided by
    return tvd, kl

for eps in (0.1, 0.03, 0.01, 0.003, 0.001):
    tvd, kl = pinsker_pair(eps)
    print(f"eps={eps:<6} TVD={tvd:9.6f}  KL={kl:9.2f}  ratio={kl/tvd:12.1f}")
    assert tvd <= math.sqrt(kl / 2), "Pinsker still holds, and is still useless here"

The output is Table 3.5, and the assertion at the bottom is the part that makes the point: Pinsker is never violated along the sequence. Both things are true at once. The inequality holds, and it tells you nothing you wanted to know.

The operational conclusion is a sentence you should be able to produce on demand. A measured TVD of 0.001 between student and teacher certifies nothing about their KL, because the student may have assigned an astronomically small probability to a rare token the teacher still cares about, and that single token carries unbounded KL while contributing 0.001 to the TVD. This becomes concrete in Chapter 10, where a top- logit cache discards the teacher’s tail by construction: the tail is exactly where tiny masses with enormous log-ratios live, so any cache or comparison that truncates the tail is blind to unbounded KL hiding inside it.

3.12 What all of this is for#

The choice of divergence is a design decision with measurable consequences, and it is one of the few in this subject where the mechanism is fully understood in advance. You are not guessing. Given a generator you can say, before writing any code, whether the objective will charge the student more for missing the teacher’s mass or for inventing its own, whether one bad position can dominate a batch, whether the resulting number can be chained through an intermediate model, and whether a sampled estimate of it will have usable variance.

What you cannot say in advance is which of those properties you want, because that depends on what the student is for. A student that will be sampled from at temperature 1 to produce diverse completions, or used as the starting point for a reinforcement-learning stage that needs multiple plausible continuations left intact, wants a different objective from a student that will be run greedily on a narrow structured task. The literature reflects the split rather than resolving it: MiniLLM argues for reverse KL on the grounds that a small student should not be forced to cover regions of the teacher it cannot represent, GKD makes the interpolation parameter itself a design knob alongside the on-policy/off-policy mix, and DistiLLM introduces a skewed variant specifically to control how much of each direction you get.151617 These are not competing claims about a fact. They are different answers to a question about products. The same reverse-KL structure appears one field over, in preference optimization, where a policy is regularized toward a fixed reference distribution, which is worth knowing because the failure modes transfer.18 The survey literature organizes the whole method space if you want a map before the details.19

A caution before Chapter 6 does the work. It is tempting to read the divergence choice as a knob that controls how faithfully the student reproduces the teacher, with the only question being which kind of infidelity you prefer. The evidence does not support the clean version of that reading: students trained by distillation frequently fail to match their teachers on held-out data by a margin their improved generalization does not explain, and improving the optimization does not close the gap.20 Whatever the divergence is doing, “it determines how closely the student copies the teacher” is an incomplete account of it, and Chapter 6’s measurements are on generated text for exactly that reason.

Chapter 6 runs the ablation that makes the answer measurable for a specific student on a specific task, with fixed seeds, fixed data, fixed steps, and measurement on generated text rather than on loss values. Chapter 4 handles the problem that comes first in practice, which is that you almost never have both full distributions in hand and must estimate a divergence from samples, at which point the variance properties matter more than the boundary behavior.

3.13 Where this lands in the labs#

Lab 00 §3 through §6 is this chapter executed with assertions attached. The lab does three things the chapter cannot: it verifies the six-generator table by computing every divergence twice through independent code paths and asserting agreement, so the table is a checked claim rather than a transcription; it finds the triangle-inequality violations by random search over 2,000 triples and confirms that survives the same search with zero violations; and it sweeps 3,000 random pairs to confirm the JSD, TVD, and Pinsker bounds hold empirically over a range of peakedness. Solutions 00 Exercise 2 builds the Pinsker-in-reverse table, including the log-space computation the construction forces, and Exercise 3 adds chi-squared to the generator check and races it against KL and JSD on a single moving logit. If you have time for one cell, run the disjoint-support demo at the end of §6: it takes under a second and shows KL at 26.9 nats next to JSD at exactly on the same pair of distributions.

3.14 Exercises#

  1. Prove that when is fixed, and state precisely which step of your proof fails when is not fixed. Then consider self-distillation, where the teacher is an earlier checkpoint of the same model: is fixed during a training step? Is it fixed across a training run? Say what quantity you would have to monitor to detect the difference from a loss curve alone.

  2. §3.3.2 showed that for , , , the detour through costs exactly half the direct path, for every . Derive this. Then determine whether the factor of two is special to the uniform midpoint: replace with for arbitrary and find the set of for which the triangle inequality is violated. State what happens as and explain why that is the answer you should have expected.

  3. Using only the argument from §3.6, compute the maximum value of the squared Hellinger divergence as defined in Table 3.1, and verify your answer against the direct formula evaluated on a disjoint pair. Then do the same for the generator : is it a legal generator, and if so, is the divergence it produces bounded? Say which of the six divergences in Table 3.1 it most resembles in behavior and why.

  4. Someone hands you an objective with generator . Before computing anything numerically, state: whether it is a legal generator; whether it is bounded and if so by what; which direction of disagreement it charges more heavily; and whether it is symmetric under swapping and (use the conjugate from §3.5). Write your four predictions down, then verify them.

  5. A colleague reports that their distilled student has a total variation distance of 0.002 from the teacher, averaged over 50,000 held-out positions, and concludes the two models are interchangeable for production purposes. Using §3.11.1, describe a concrete situation in which that number is accurate and the student is nonetheless badly wrong, name the single additional measurement that would settle it, and say why that measurement is the expensive one to obtain.

  6. A forward-KL distillation run trains normally for 800 steps at a loss around 0.4 nats, then spikes to 8.1 at one step, comes back to 0.5 for a few steps, then goes to NaN and stays there. The model is held in bf16 and the loss is computed in bf16. Using §3.2, §3.10, and Chapter 2’s number-format material, give the most likely sequence of events, and name two changes to the configuration that would each have prevented it. Then say which of the two you would prefer and what it costs.

  7. You are asked to review a plan that distills a 70B teacher into a 14B intermediate, then the intermediate into a 1.7B student, and reports and as evidence that the student is close to the teacher. Write the objection in three sentences. Then propose the smallest change to the measurement protocol that would make a defensible claim, and state what it costs in teacher forward passes. Finally, say what claim you could legitimately make using instead, and whether it is a claim anyone would care about.



  1. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015), §2. https://arxiv.org/abs/1503.02531. The soft-target objective is a cross-entropy against a temperature-softened teacher distribution, which by the identity in §3.1 is a forward KL against that distribution. 

  2. Rafael Müller, Simon Kornblith, and Geoffrey Hinton, “When Does Label Smoothing Help?” arXiv:1906.02629 (2019), NeurIPS 2019. https://arxiv.org/abs/1906.02629 

  3. The mode-covering and mode-seeking vocabulary predates its use in distillation and is standard in variational inference. Its distillation-specific consequences are measured in Chapter 6 and in Lab 05; the summary claim carried forward from Lab 01 is that the choice is a product decision rather than a mathematical one. 

  4. Ganqu Cui et al., “The Entropy Mechanism of Reinforcement Learning for Reasoning Language Models,” arXiv:2505.22617 (2025). https://arxiv.org/abs/2505.22617. The standard reference for entropy collapse in reinforcement learning with verifiable rewards; the mechanism it describes is the one Chapter 12 watches for in on-policy distillation. 

  5. Imre Csiszár, “Information-type measures of difference of probability distributions and indirect observations,” Studia Scientiarum Mathematicarum Hungarica 2 (1967): 299-318. The family was co-discovered independently by S. M. Ali and S. D. Silvey, “A general class of coefficients of divergence of one distribution from another,” Journal of the Royal Statistical Society Series B 28, no. 1 (1966): 131-142, https://doi.org/10.1111/j.2517-6161.1966.tb00626.x. Csiszár’s earlier German-language precursor appeared in Publ. Math. Inst. Hungar. Acad. Sci., Ser. A, 8 (1963): 85-108. Neither Csiszár paper has a DOI; cite by volume and page. 

  6. John Schulman, “Approximating KL Divergence,” joschu.net, 7 March 2020, http://joschu.net/blog/kl-approx.html (accessed 2026). A personal blog post rather than a refereed paper, and the standard source for the k1/k2/k3 estimators. Chapter 4 derives all three. 

  7. Yuqiao Wen, Zichao Li, Wenyu Du, and Lili Mou, “f-Divergence Minimization for Sequence-Level Knowledge Distillation,” arXiv:2307.15190 (2023), ACL 2023. https://arxiv.org/abs/2307.15190 

  8. Mingyang Song and Mao Zheng, “A Survey of On-Policy Distillation for Large Language Models,” arXiv:2604.00626 (2026). https://arxiv.org/abs/2604.00626. An ongoing preprint rather than a published survey, and it should be read as a snapshot of a moving field; its organizing axis is the f-divergence family. 

  9. Nicolas Boizard, Kevin El Haddad, Céline Hudelot, and Pierre Colombo, “Towards Cross-Tokenizer Distillation: the Universal Logit Distillation Loss for LLMs,” arXiv:2402.12030 (2024), Transactions on Machine Learning Research, January 2025. https://arxiv.org/abs/2402.12030 

  10. Dominik M. Endres and Johannes E. Schindelin, “A new metric for probability distributions,” IEEE Transactions on Information Theory 49, no. 7 (2003): 1858-1860. https://doi.org/10.1109/TIT.2003.813506. Proves specifically that the square root of the Jensen-Shannon divergence satisfies the triangle inequality. 

  11. Ferdinand Österreicher and Igor Vajda, “A new class of metric divergences on probability spaces and its applicability in statistics,” Annals of the Institute of Statistical Mathematics 55, no. 3 (2003): 639-653. https://doi.org/10.1007/BF02517812. Establishes the broader family of metric divergences , of which is the case. The two 2003 results are conventionally cited together. 

  12. Frank Nielsen et al., “Metrization of powers of the Jensen-Shannon divergence,” arXiv:2302.10070. The modern generalization of the Endres-Schindelin result. 

  13. M. S. Pinsker, Information and Information Stability of Random Variables and Processes (San Francisco: Holden-Day, 1964), translated and edited by A. Feinstein from the 1960 Russian original. Pinsker’s own constant is weaker than the form used here; the optimal constant is due independently to Csiszár (1967, cited above) and to S. Kullback, “A lower bound for discrimination information in terms of variation (Corresp.),” IEEE Transactions on Information Theory 13, no. 1 (1967): 126-127, https://doi.org/10.1109/TIT.1967.1053968, with a 1970 correction, https://doi.org/10.1109/TIT.1970.1054514, and to J. H. B. Kemperman (1969). The result is therefore properly the Csiszár-Kullback-Pinsker inequality. 

  14. A. A. Fedotov, P. Harremoës, and F. Topsøe, “Refinements of Pinsker’s inequality,” IEEE Transactions on Information Theory 49, no. 6 (2003): 1491-1498. https://doi.org/10.1109/TIT.2003.811927 

  15. Yuxian Gu, Li Dong, Furu Wei, and Minlie Huang, “MiniLLM: Knowledge Distillation of Large Language Models,” arXiv:2306.08543 (2023), ICLR 2024. https://arxiv.org/abs/2306.08543v2. Note that the arXiv landing page now shows a later title, “MiniLLM: On-Policy Distillation of Large Language Models”; the ICLR 2024 version of record uses the title given here. 

  16. Rishabh Agarwal et al., “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649. The method is generalized knowledge distillation; the name does not appear in the title. 

  17. Jongwoo Ko, Sungnyun Kim, Tianyi Chen, and Se-Young Yun, “DistiLLM: Towards Streamlined Distillation for Large Language Models,” arXiv:2402.03898 (2024), ICML 2024. https://arxiv.org/abs/2402.03898. The follow-up is Jongwoo Ko et al., “DistiLLM-2: A Contrastive Approach Boosts the Distillation of LLMs,” arXiv:2503.07067 (2025), ICML 2025. https://arxiv.org/abs/2503.07067 

  18. Rafael Rafailov, Archit Sharma, Eric Mitchell, Stefano Ermon, Christopher D. Manning, and Chelsea Finn, “Direct Preference Optimization: Your Language Model is Secretly a Reward Model,” arXiv:2305.18290 (2023), NeurIPS 2023. https://arxiv.org/abs/2305.18290 

  19. Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819, https://doi.org/10.1007/s11263-021-01453-z. For the language-model-specific version see Xiaohan Xu et al., “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024). https://arxiv.org/abs/2402.13116 

  20. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945 

Part I · Foundations

4

Estimating Divergences from Samples

Chapter 3 gave you a formula for the distance between two next-token distributions and treated both distributions as things you can read off a page. Every divergence in that chapter is a sum over the vocabulary, and every sum assumed you had all numbers from the teacher and all numbers from the student, at every position, whenever you wanted them.

You usually do not.

Take the smallest concrete case this book uses. SmolLM2’s tokenizer has a vocabulary of 49,152 entries.1 A modest training batch of 8 sequences at 1,024 tokens each is 8,192 positions, so one full-vocabulary logit tensor for that batch holds 8,192 × 49,152 ≈ 402 million numbers. In bf16 that is 805 megabytes. You need one for the teacher and one for the student, and the loss computation reads both, writes at least one intermediate the same size for the log-softmax, and then reduces. On the reference machine’s 273 GB/s of memory bandwidth, moving a single 805 MB tensor once costs about 2.9 milliseconds, and a naive implementation moves several of them per step. None of that is fatal. It is a real cost that scales linearly in , and it is the reason Chapter 10 exists.

The harder problem is not cost. It is access. In on-policy distillation the student generates a token, and what you get back from the teacher is often the log-probability of that token and nothing else. In an API setting you may get the top 20 log-probabilities, or the log-probability of the sampled token alone, or nothing but text.2 The full-vocabulary sum you would need to evaluate exactly is not available at any price, because the numbers were never sent. That situation is the direct consequence of training on the student’s own generations, which the sequence-modeling literature has wanted to do since long before distillation adopted the idea.16

So the question this chapter answers is: given samples and log-ratios at those samples, how do you estimate a divergence, how wrong is your estimate, and how would you know? The answers are more interesting than they sound. One of them is a measured result from the course’s own labs that contradicts the advice you will find in most places this topic gets discussed, including the advice I wrote into the lab before the data changed my mind.

4.1 Three situations, and only one of them is comfortable#

Before any estimator, sort the problem. There are exactly three access patterns for divergence computation in distillation, and which one you are in determines everything downstream.

Both distributions in full. You have the teacher’s complete logit vector and the student’s complete logit vector at every position. The divergence is an exact sum. This is the situation when you own both models, run them in the same process, and are willing to pay the memory traffic described above. Everything in Chapter 3 applies without modification, and the number you compute is the number, not an estimate of it.

Definition

Dense logits

The teacher’s complete score vector over the whole vocabulary, at every position. Having dense logits is what makes an exact divergence computation possible. Most of the machinery in this chapter exists because you often do not have them.

One in full, one truncated. You have the student’s full distribution because you are training it, and a top- slice of the teacher’s because that is what got cached, or that is what the API returned. The missing tail has some mass, and every divergence you compute is off by whatever the tail would have contributed. This is not a sampling problem and not a Monte Carlo problem; it is a deterministic truncation with a bias you can sign and sometimes bound. Chapter 10 handles it in full, including the two conventions for what to do with the missing mass and the direction each one errs in.

Only sampled log-ratios. You have a token that was sampled from the student, the student’s log-probability of it, and the teacher’s log-probability of it. One number per position, per model. This is the on-policy case and the API case, and it is where this chapter lives.

Table 4.1 What you can compute from each access pattern.

Access pattern What you hold per position Divergence status Where it comes up
Dense both sides teacher logits, student logits Exact, cost in traffic Both models in one process
Dense student, top- teacher student, teacher plus tail mass Truncation bias, deterministic Cached-logit pipeline (Ch. 10), logprob APIs
Sampled log-ratios , at the sampled Monte Carlo estimate, random On-policy rollouts, black-box scoring

Read the third row carefully, because the change it introduces is qualitative rather than quantitative. In the first two rows, running the same computation twice on the same inputs gives the same answer. In the third row it does not. Your “KL” becomes a random variable with a mean, a variance, and a sampling distribution, and every property you learned about KL in Chapter 3 (nonnegativity, in particular) is a property of the mean rather than of any number you will actually see.

4.2 Monte Carlo, from the ground#

Suppose you want the average value of some function under a distribution , written . If is a categorical distribution over 49,152 tokens, that expectation is a sum of 49,152 terms. If you cannot afford the sum, or cannot see all of its terms, you can draw independent samples from and average over them:

Nothing about this is specific to divergences. Sequence-level distillation is the same construction with : the intractable sum over all output sequences gets replaced by the teacher’s single most likely one.13

Definition

Monte Carlo estimator

An estimate of an expectation formed by averaging a function over random draws from the distribution the expectation is taken under. It approaches the true value only as draws accumulate, and at any finite it is a random number with a distribution of its own.

That average is useful because of two facts about it. The first is that exactly, for every , including . Each term in the average has the right expectation by construction, and expectation is linear, so the average does too. That property has a name.

Definition

Unbiased estimator

An estimator whose expected value equals the quantity it estimates, at every sample size. Unbiased does not mean accurate: a single draw from an unbiased estimator can be arbitrarily far from the truth, and in the cases this chapter cares about, routinely is.

The second fact is that the variance of the average is the variance of one term divided by , so the standard deviation of is where is the standard deviation of under . That quantity is the standard error, and it is the honest one-number summary of how much you should trust the estimate. The is unforgiving: ten times the accuracy costs a hundred times the samples.

Now specialize. Reverse KL, in the convention this book uses where is the teacher and is the student, is

That is already an expectation under . And is the student, which is the model that generated the token. So if you sample tokens from the student and average the log-ratio over them, you get an unbiased estimate of the reverse KL with no further machinery. This is the whole reason reverse-flavored objectives dominate on-policy pipelines, and it is worth saying plainly now because it will be re-derived from a different direction in Chapter 12: the reverse direction is the one whose defining expectation is taken under the distribution you are already sampling from.

Forward KL is an expectation under , the teacher, and you are not sampling from the teacher. To estimate it from student samples you need to reweight, and §4.5 shows what that costs.

It is convenient to work with the ratio in the other orientation, matching Schulman’s note and the lab.

Definition

Importance ratio

For a sample drawn from , the quantity : how much more probable the teacher considers this token than the student does. Its expectation under is exactly 1, because . That identity is the hinge the rest of this chapter turns on.

With , the log-ratio is , so

Note also that holds no matter how different and are. It says nothing about how concentrated is. Chapter 3’s exercise on the chi-squared divergence establishes the companion fact, which will matter in §4.4: . The variance of the importance ratio is a divergence in its own right, and it is one of the badly behaved ones.

4.3 Three estimators of the same number#

John Schulman’s blog post on approximating KL divergence is the standard reference for what follows, and the names k1, k2, k3 come from it.3 Each is a function of the importance ratio at a single sampled token, each is meant as an estimator of from draws , and each makes a different trade along the bias-variance axis that runs through most design decisions in distillation.15

Everything worth knowing about them follows from writing and expanding. I will do the three in order.

4.3.1 k1: unbiased, noisy, and sometimes negative#

is the definition read literally. Its expectation under is , which is the identity from §4.2, so is unbiased by construction and there is nothing further to derive.

The problem is what a single sample looks like. is negative exactly when , meaning exactly when the teacher assigns the sampled token more probability than the student did. That happens constantly. Even for two distributions that are quite close, roughly half the tokens will have on one side of 1 and half on the other. In the course’s own measurement on Lab 00 §9’s close regime, 43 percent of the 200,000 individual samples came out negative, with the smallest at , against a true KL of nats.

That is not a bug in . KL is nonnegative as an average, by Gibbs’ inequality, and Gibbs’ inequality is a statement about the full sum. A single term of that sum carries no such guarantee. The negativity is the sampling noise being larger than the signal, which is the ordinary condition when two distributions are close: the true value is small precisely because the positive and negative contributions nearly cancel, and a small number obtained by cancellation of large ones is exactly the situation where sampling hurts most.

Quantitatively, . That is the variance of a log quantity, which grows slowly. If the student’s logit on some token sits 25 nats below the teacher’s, on that token is 25, large but finite, and it enters linearly. Hold on to that; it is the reason survives the far regime in §4.4 when does not.

4.3.2 k2: low variance, and biased#

is nonnegative on every sample by construction, which is appealing, and it has visibly less spread than because squaring a small number makes it smaller. It is also biased, and the bias has a clean closed form worth deriving because it tells you exactly when to distrust it.

Start from and write :

Expand the exponential inside the expectation:

so

Since and , rearrange:

So ’s bias is exactly the negated sum of the third and higher moments of the log-ratio, with factorial denominators. When and are close, is small, the true KL is second order in , and the bias is third order, so the relative bias is first order in the typical size of and shrinks as the distributions converge. When and are far apart, the higher moments stop being small and the whole expansion stops being informative.

The measurements bear this out with unusual clarity. On Lab 00 §9’s close regime, ’s mean is against a true KL of , an overestimate of about 5 percent. On the far regime, its mean is against a true KL of : not 5 percent high, but 164 percent high. An estimator whose bias is “small when the thing you are measuring is small” is a different animal from one whose bias is small.

4.3.3 k3: unbiased and nonnegative at the same time#

is with the term added. Two properties follow immediately.

It is unbiased. $\mathbb{E}_q[k_3] = \mathbb{E}_q[r] - 1 - \mathbb{E}_q[\log r] = 1 - 1 + \mathrm{KL}(q|p) = \mathrm{KL}(q|p)$. The added term contributes exactly zero to the expectation, because . The correction is free.

It is nonnegative on every single sample. The inequality holds for all , with equality only at ; it is the statement that the logarithm lies below its tangent line at 1, which follows from concavity. Therefore pointwise, and for every draw.

Getting both of those at once is the structural property that makes the usual default. An unbiased estimator that can go negative will occasionally print a negative KL, and anyone reading that dashboard will either conclude the code is broken or, worse, stop trusting the panel. An estimator that never goes negative but is biased will report a number that is systematically wrong in a direction that changes as training proceeds. refuses both failure modes.

The reason it can is worth naming, because the idea generalizes far past this estimator.

Definition

Control variate

A quantity added to an estimator that has known expectation (usually zero) and is correlated with the estimator’s noise. Adding it changes nothing in expectation and can cancel a large part of the variance. In , the control variate is , whose expectation under is zero because , and which is correlated with because both are functions of the same ratio.

There is also a nice reading of as an f-divergence generator rather than as a trick. Chapter 3 introduced for convex with , and gave $f(t) = -\log ta(t-1)$ to a generator changes no divergence value, because .4 The choice produces , which is the unique member of that affine family that touches zero at and is therefore nonnegative everywhere. is not a clever hack bolted onto reverse KL; it is reverse KL’s generator written in the normalization where every pointwise term is a nonnegative contribution.

Table 4.2 The three estimators, before the regime question.

Formula Bias Sign of a single sample Structural appeal
Zero Either It is the definition, read literally
Always Small spread when
Zero Always Both properties at once

The listing below is the shape of all three in code, on synthetic distributions. Watch the third column of the output, the standard deviation, since that is the quantity Table 4.2 deliberately leaves blank.

import torch, torch.nn.functional as F

def estimator_stats(p, q, n, seed):
    """Sample n tokens from q and return the three KL(q||p) estimators."""
    g = torch.Generator().manual_seed(seed)
    x = torch.multinomial(q, n, replacement=True, generator=g)
    log_r = (p.log() - q.log())[x]        # one number per sampled token
    r = log_r.exp()
    return {
        "k1": -log_r,
        "k2": 0.5 * log_r ** 2,
        "k3": (r - 1) - log_r,
    }

def report(p, q, n=200_000, seed=7):
    truth = float((q * (q.log() - p.log())).sum())   # exact, full vocabulary
    for name, k in estimator_stats(p, q, n, seed).items():
        se = float(k.std()) / n ** 0.5
        print(f"{name}  mean {k.mean():+.5f}  std {k.std():8.5f}"
              f"  bias {k.mean() - truth:+.5f}  se {se:.5f}")

The listing proves nothing on its own; it is the instrument. What it measures is the subject of the next section, and the measurement is the reason this chapter exists.

4.4 The regime dependence, which is the result that matters#

Here is the claim you will find nearly everywhere this topic is discussed, including in the first draft of the course’s own lab: is unbiased like and low-variance like , so use and stop thinking about it.

That claim is false in a way that matters enormously for distillation, and the course measured it.

Lab 00 §9 builds two regimes over a 500-token vocabulary and draws 200,000 samples in each. Regime A is late training: a teacher and a student for standard normal , so the two are the same distribution with a small perturbation. Regime B is cold start: and drawn independently from the same random-distribution generator, with no relationship between them at all. Both regimes use the same seed and the same sampling call.

Table 4.3 Lab 00 §9’s measured estimator statistics, , , seed 7.

Regime True
A (close) 0.05328 mean 0.05233 0.05617 0.05323
std 0.33106 0.07118 0.06122
B (far) 4.06400 mean 4.06940 10.72473 3.96930
std 2.21122 7.62477 23.85423

Read the two bolded cells. In the close regime, ’s standard deviation is 0.061 against ’s 0.331, a factor of 5.4 in ’s favor, which is the result everyone quotes. In the far regime, ’s standard deviation is 23.85 against ’s 2.21, a factor of 10.8 in the opposite direction. The estimator that is five times better in one regime is eleven times worse in the other, on the same code with the same seed.

Both remain unbiased. Both means sit on the truth in both regimes, within the lab’s assertion tolerance of five standard errors. The bias story does not change. Only the variance does, and it inverts.

Field note

I wrote the “k3 always wins” cell first, with a single regime and an assertion that std(k3) < std(k1). It passed, because the regime I happened to construct was the close one, since that is the regime you naturally reach for when you want a demonstration to look clean.

Then I added a second regime to show the effect was general, and the assertion failed. My first instinct was that I had a bug: sign error somewhere, or the sampler reusing state across calls. There was no bug. I had constructed two independent random distributions over 500 tokens, and on those, reaches into the thousands on tokens that the student happens to hit and the teacher happens to like, and carries every one of those spikes into at full size.

The lab now demonstrates both regimes and asserts the inversion explicitly: std(k3) < std(k1) in the close regime, std(k3) > std(k1) in the far one. That inverted assertion is the most useful line in Lab 00, because it is the one that would fail loudly if someone “simplified” the cell back to the version I originally wanted to write.

4.4.1 Why the correction stops correcting#

The mechanism is visible in three lines of algebra. Write again, and note

Expand around :

The linear term is gone. That is the entire content of the control variate. fluctuates at first order in the log-ratio; adding , whose own expansion is , cancels the first-order term exactly, leaving to fluctuate at second order. When is typically 0.2, a second-order fluctuation is roughly five times smaller than a first-order one, and you get the close-regime result.

The same expansion gives two more results. First, near , so and are the same estimator to second order, which explains why their close-regime standard deviations in Table 4.3 (0.0712 and 0.0612) sit so near each other. Second, the expansion is a statement about small , and it has no force otherwise.

For large the two estimators diverge in behavior completely. grows linearly in the log-ratio. contains , which grows exponentially in the log-ratio. A token where the teacher assigns probability 0.1 and the student assigns 0.0001 has : contributes to the average, while contributes . One such draw in a batch of a few hundred moves the average by several units and the average by a hundredth of one.

The formal version is the connection to chi-squared. , from Chapter 3, and ’s variance is dominated by the variance of its term once has any appreciable spread. Chapter 3’s exercise measured the chi-squared divergence growing by more than ten orders of magnitude over a logit gap where forward KL grew by a factor of five, because chi-squared charges where KL charges . inherits that growth rate. does not.

There is one more way to see it that generalizes, and I will state it here because §4.8 needs it. The family of unbiased estimators

is unbiased for every real , since the added term always has expectation zero. is and is . Standard control-variate theory says the variance-minimizing choice is

and computing that quantity on Lab 00 §9’s own two regimes gives in the close regime and in the far one. In the close regime the optimal correction is almost exactly the one hardcodes. In the far regime the optimal correction is to apply almost no correction at all, which is to say, to use . is not a general-purpose improvement over ; it is a fixed choice of control-variate coefficient that happens to be optimal in one regime and roughly 75 times too large in the other.

2026-08-01T07:26:37.246756 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.001 0.01 0.1 1 true KL(q||p), nats 1 0 3 1 0 2 1 0 1 1 0 0 1 0 1 1 0 2 per-sample standard deviation k1 k2 k3 k3 wins k1 wins crossover at true KL = 0.90 nats regime A, true KL 0.0533: std(k1) 0.331 against std(k3) 0.061 regime B, true KL 4.064: std(k1) 2.211 against std(k3) 23.85
Figure 4.1 Across a sweep from near-identical to unrelated distributions, k3's standard deviation crosses above k1's at a true KL near one nat, so the estimator with the lower variance is a function of how far apart the models already are.

4.4.2 What this means for a training run#

Now connect it forward, because the regimes are not abstract.

“The student is close to the teacher” is the description of a run in steady state, some thousands of steps in, when the loss has come down and the two distributions overlap heavily. “The student is far from the teacher” is the description of the first few hundred steps of a run that started from a base model, or from random initialization, or from a pruned checkpoint whose surgery has not yet been recovered. Chapter 12 calls that the cold start and spends real length on it, because it is the regime where on-policy distillation is at its most fragile.

Put those together and the conclusion is uncomfortable. The estimator you would trust during steady-state training is the one that is least trustworthy at the beginning of a run, and the beginning of a run is when you most need a reliable monitor, because that is when things actually break. Sampled-ratio estimators are dependable exactly when the two models already roughly agree, and a freshly initialized student is the case where they do not.

That is not a reason to abandon them. It is a reason to know which regime you are in before you read the number, and to prefer (or a full-vocabulary computation on a held-out slice) for the early phase of a run.

2026-08-01T07:26:40.765721 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 50 100 150 200 k1 = -log r mean 0.0539 sd 0.0412 9.4% of replicates estimate a negative KL 0 200 400 600 800 replicates per bin k2 = 0.5 (log r)^2 mean 0.0563 sd 0.0089 −0.10 −0.05 0.00 0.05 0.10 0.15 0.20 estimate of KL(q||p) from 64 sampled tokens, nats 0 250 500 750 1000 k3 = (r - 1) - log r mean 0.0533 sd 0.0076 true KL = 0.05328
Figure 4.2 The sampling distribution of each estimator at 64 samples in the close regime: k1 spreads widely and puts substantial mass below zero, k2 and k3 are tight and strictly nonnegative, and all three are centered on the true value.

4.5 The forward direction, where importance weights are unavoidable#

Everything so far estimates , the reverse direction, and got away with it because the samples came from . Forward KL is the direction distillation most often wants, since it is the mass-covering one and the one that punishes a student for abandoning something the teacher cares about. Estimating it from student samples is a different problem, and Solutions 00 Exercise 4 works it through.

The derivation starts by writing forward KL as an expectation under and then changing the measure:

The step in the middle is importance weighting: multiply and divide by , so the sum becomes an expectation under with the extra factor inside. That factor is the price of sampling from the wrong distribution, and it is charged multiplicatively rather than inside a logarithm.

So the naive forward estimator is the average of over draws from . It is unbiased, and it is bad. It can also be negative on a single sample: for all , with a minimum of at . Hold on to that number; §4.6 needs it.

Apply the same control variate as before. still, so is still free to subtract:

This is unbiased by the same one-line argument, and it is nonnegative on every sample because for all . That inequality is the same fact as before in a different costume: it says lies above its tangent at , which is convexity, and Chapter 3’s generator table gave as the forward-KL generator. Subtracting the affine term changes no divergence value and shifts the generator to touch zero at .

The lab runs the identical construction to §9, same , same , same seed, and adds reverse on the same draws as a yardstick.

Table 4.4 Solutions 00 Ex4’s measured forward-direction estimators, on the same draws as Table 4.3.

Regime True Estimator mean std
A (close) 0.05147 naive 0.05235 0.33631
0.05145 0.05893
reverse (yardstick) 0.05233 0.33106
B (far) 4.74743 naive 4.06521 174.06
4.16531 150.08
reverse (yardstick) 4.06940 2.21122

Regime A behaves. The control variate cuts the standard deviation by a factor of 5.7, both means sit on the truth, and every sample is nonnegative. This mirrors the reverse-direction result exactly, for the same reason: near , is quadratic in and therefore tiny.

Regime B is a wreck, and the details are instructive. The naive estimator’s standard deviation is 174, which is 37 times the true value of 4.75 it is trying to estimate. The control variate reduces that to 150, a 14 percent improvement, against the 5.7-fold improvement it delivered in regime A. Subtracting cannot rescue anything here, because the noise is the heavy tail of , and appears in both terms. And on the identical draws, reverse has a standard deviation of 2.21: the forward estimator is 79 times noisier than the reverse one on exactly the same sampled tokens.

Look at the far-regime means as well. Both forward estimators come in low: 4.07 and 4.17 against a truth of 4.75. They are unbiased, and the run still undershoots, because the expectation is carried by astronomically rare draws with enormous that 200,000 samples did not happen to include. This is the sentence from the solutions worth memorizing: an unbiased estimator you cannot afford enough samples for behaves, in practice, like a biased one. Unbiasedness is a statement about an average over infinitely many hypothetical repetitions of your experiment, and you are running the experiment once.

The compressed reason forward is harder than reverse: the reverse estimators need only inside a logarithm, with the linear as an optional correction, while the forward estimator needs multiplicatively as the importance weight itself. So the forward estimator’s variance is governed by , driven by the same second moment of (the chi-squared divergence) that explodes whenever the models disagree.

That has a consequence for how pipelines are built which people usually attribute to philosophy. On-policy distillation pipelines default to reverse-flavored objectives, or to interpolations weighted toward reverse, not because reverse KL is conceptually superior but because it is the direction whose sampled estimator does not require importance weights.5 Chapter 6 argues the mode-seeking versus mode-covering case on its own merits, and Chapter 12 covers the design space that GKD’s two parameters open up.6 This chapter is only pointing out that the estimator mathematics has a vote, and it votes before the philosophy gets a turn.

def forward_kl_estimators(p, q, n, seed):
    """Estimate KL(p||q) from samples of q. Note that r enters multiplicatively."""
    g = torch.Generator().manual_seed(seed)
    x = torch.multinomial(q, n, replacement=True, generator=g)
    log_r = (p.log() - q.log())[x]
    r = log_r.exp()
    return {
        "naive": r * log_r,                # unbiased, can be negative, heavy tailed
        "k3fwd": r * log_r - (r - 1),       # unbiased, nonnegative, same heavy tail
        "reverse_k1": -log_r,               # different quantity, shown for scale
    }

The listing is deliberately close to the reverse-direction one so the single structural difference is visible: r appears outside the logarithm, and that is the whole cause of Table 4.4’s right-hand column.

4.6 The sampled-token trap, in the form you will meet it#

Lab 07 puts the failure in its practical clothes, and the numbers are worth walking through in detail because the mechanism is not obvious from the general variance argument.

Definition

Sampled-token estimator

A divergence estimate computed from the teacher’s and student’s log-probabilities at only those tokens the student actually sampled, one token per position, with no access to the rest of the vocabulary. It is the only estimate available when the teacher scores rollouts rather than returning full distributions.

The setup is ten tokens. The teacher has one strong mode: logits are zero everywhere except token 0, which sits at 3.0, giving and for the other nine. The student has abandoned that mode completely: it is uniform except that before renormalization, which leaves on each of the nine others. This is a caricature of a real failure, a student that has stopped putting any mass at all on something the teacher considers most likely.

The exact forward KL is nats, and it is almost entirely one term: . That is what forward KL is for. It is the direction that charges you enormously for putting near-zero probability where the teacher puts mass.

Now estimate it from 5,000 tokens sampled from the student. The student’s probability of drawing token 0 is , so across 5,000 draws the expected number of times token 0 appears is . In the lab’s run it appeared zero times, and the lab asserts this explicitly. Every one of the 5,000 sampled tokens is one of the nine others, where the ratio is

identical for all nine. Since every sample has the same ratio, the importance-weighted estimate is not an average of varying terms at all. It is exactly

The reported estimate is against a true value of . It lands on the wrong side of zero, which makes it a different kind of answer and not a degraded version of 13.6.

Two observations sharpen this. First, is very close to , the global minimum of . The estimate did not land low by a little; it landed near the most negative value that a single term can take. Second, the reverse estimate on the very same 5,000 draws lands within 10 percent of its closed form, which is the property Lab 07 asserts. The samples are fine. The direction is the problem.

4.6.1 How a nonnegative quantity gets a negative estimate#

There are two distinct mechanisms in play in this chapter, and it is worth separating them because the fix differs.

The first is ’s ordinary negativity from §4.3.1: a single log-ratio has an arbitrary sign, and averaging a few of them can land below zero even though the average of all of them cannot. This is symmetric noise around a small true value. More samples fix it, and fixes it structurally.

The second is what Lab 07 shows, and it is not symmetric noise. The estimator is unbiased over the full sampling distribution, and the full sampling distribution includes draws of token 0. Those draws carry an importance weight of and would contribute to the average. The expectation works out to 13.6 precisely because that gigantic contribution is multiplied by the chance of seeing it. But you will not see it. To see it once in expectation you would need on the order of samples, and you drew 5,000. So what you actually observe is the conditional distribution given that the mode was missed, whose mean is the small negative number computed above.

That is the shape of the whole trap: an unbiased estimator whose expectation is carried entirely by events your sample size cannot reach. It will report a plausible-looking small number, indefinitely, with no variance to warn you, because the variance you would need to see also lives in the draws you are not getting. The 2026 survey of on-policy distillation catalogues this class of failure among the practical problems the on-policy literature keeps rediscovering, and the framing is worth adopting: these are properties of estimating an expectation under one distribution using samples from another that has abandoned part of its support, and no amount of debugging removes them.7

Watch out

A negative number on a panel labeled “KL” is not always a bug, and treating it as one will send you looking in the wrong place. What the number means depends entirely on which estimator produced it, so work through the three cases in order. If the panel computes an exact full-vocabulary sum, a negative value is a bug and Gibbs’ inequality says so. If it averages a sampled , negative values are expected whenever the true KL is small relative to the sampling noise, and the fix is more samples or , not a code review. If it is an importance-weighted forward estimate, a small negative value signals that the student is not sampling the region where the divergence lives, which is the most serious of the three possibilities and the one that looks the most benign.

4.6.2 What to do about it on a real dashboard#

What helps, in increasing order of what it costs you:

Label the panel with the estimator, not with the quantity. A panel called “KL” that is actually a sampled average over the current batch’s rollout tokens should say so, in the title. This sounds like a documentation nicety and is not: the label is what determines whether a reader interprets a downward move as the student improving or as the sampler drifting.

Never monitor forward KL from student samples alone. If you want the mass-covering direction on a dashboard, compute it densely on a small fixed held-out batch, at whatever interval you can afford, rather than estimating it from rollouts. A hundred positions of exact forward KL every 50 steps is worth more than every position of a sampled estimate, because the sampled estimate is systematically blind to exactly the failure forward KL is meant to detect.

Watch a quantity that does not have this pathology, alongside. Rollout entropy is computed from the student’s own distribution, requires no teacher and no ratio, and collapses visibly when the student abandons modes.8 Chapter 12 builds a monitor on it and calibrates the monitor against healthy runs so it does not fire on ordinary entropy decline. The pairing matters: a sampled KL that looks stable while entropy is falling off a cliff is the exact signature of the Lab 07 situation.

It is worth adding that a well-built on-policy trainer avoids the estimator problem entirely for the loss itself. GKD scores the student’s rollouts with the teacher and computes the per-position divergence over the full vocabulary, so the objective is an exact sum at every rollout position.6 What stays student-driven is which positions exist at all, meaning the state distribution rather than the per-position divergence. The sampled-token trap bites when you are monitoring, when the teacher is remote and returns only the sampled token’s log-probability, or when you have written the estimator yourself from a survey formula.

4.7 Practical guidance#

4.7.1 How many samples#

The standard error of a Monte Carlo estimate is , so pick a tolerance and invert. Using Lab 00 §9’s measured per-sample standard deviations from Table 4.3, and asking for a standard error of 0.05 nats:

Table 4.5 Samples required for a standard error of 0.05 nats, from Table 4.3’s measured spreads.

Regime Estimator Per-sample std Required
A (close) 0.331 44
A (close) 0.061 2
B (far) 2.211 1,956
B (far) 23.854 227,610

Read the table twice. In the close regime, a batch of a few hundred rollout tokens is plenty for either estimator, which is why sampled monitoring works at all in steady state. In the far regime, needs about two thousand tokens and needs about a quarter of a million, which no ordinary batch supplies. Note also that a standard error of 0.05 nats is a much weaker demand in regime B, where the true value is 4.06, than in regime A, where it is 0.053; asking for a fixed relative precision makes the far-regime numbers worse still.

A general rule that survives both regimes: compute the standard error from the samples you have, every time, and refuse to interpret any movement smaller than a couple of standard errors. The computation costs one line.

4.7.2 Seeds and paired comparison#

Every sampled divergence in the labs uses an explicit torch.Generator with a stated seed rather than the global random state, and the reason is not superstition about reproducibility. It is that you often want to compare two things measured on the same draws.

Table 4.4’s yardstick row is the example. The claim “the forward estimator is 79 times noisier than the reverse one” is only meaningful because both were computed from the identical 200,000 sampled tokens. Had they been computed from independent draws, part of the difference would be sampling noise in the comparison itself, and the factor of 79 would need its own error bar. Reusing the same draws across estimators is the technique statisticians call common random numbers, and it is nearly free: draw once, compute every estimator on the same x.

The same discipline applies across arms of a training experiment. If arm A and arm B are being compared on a sampled divergence, and both are evaluated on a fixed held-out prompt set with a fixed evaluation seed, then a difference between them is more likely to be real than if each arm’s evaluation drew its own tokens. Chapter 18 makes this part of the pre-registration.

4.7.3 Reporting#

Report a sampled divergence as a value and a standard error, in that order, with the sample count: “reverse KL 0.053 ± 0.001 nats (k3, N = 200,000)”. Every part of that string does work. The estimator name tells a reader whether to expect nonnegativity. The tells them whether the standard error is believable. The standard error tells them what movements to ignore.

And one discipline that is more important than all of the above, stated as a rule.

Never compare an estimated divergence against an exactly computed one across arms of an experiment. If arm A reports dense forward KL on a held-out set and arm B reports a sampled estimate on rollouts, the difference between the two numbers contains the difference between the arms, plus the estimator’s bias, plus the difference between the two evaluation distributions, and you cannot separate them after the fact. This sounds like an obvious hygiene rule, and it is violated constantly, because the two arms are often built at different times by different people and the panel is named “KL” in both cases. Table 4.3 makes the magnitude concrete: overstates the far-regime KL by 164 percent. If arm A used and arm B used a dense sum, arm A would appear to be more than twice as far from the teacher, entirely as an artifact.

The same rule has a weaker cousin worth stating: do not compare sampled divergences computed at different sample counts without noting it, since the bias of does not shrink with but the apparent stability of any estimator does, and a smoother line reads as a better run.

2026-08-01T07:26:44.358626 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1 0 1 1 0 2 1 0 3 1 0 4 1 0 5 samples N 0.02 0.04 0.06 0.08 0.10 running estimate of KL(q||p), nats true KL 0.05328 regime A: close, true KL 0.05328 k1 k2 k3 1 0 1 1 0 2 1 0 3 1 0 4 1 0 5 samples N 2 4 6 8 10 12 k3 still 3.969 at N = 200,000, approaching from below, band +/-0.053 true KL 4.06400 k1 settles by a few thousand samples, band +/-0.0049 k2 converges to 10.73, which is not the KL regime B: far, true KL 4.06400
Figure 4.3 Running estimates against sample count in both regimes: in the close regime all three converge quickly and k3's band is tightest, while in the far regime k3's band stays wide and its running mean approaches the truth from below long after k1 has settled.

4.8 Variance reduction as a general tool#

is one instance of a technique that has a name and a theory, and it is worth having the general form because distillation training runs are full of high-variance estimates that are not divergences.

The general control-variate construction: you want , you have some whose expectation you know exactly, and you form

This is unbiased for every , since the subtracted term has expectation zero. Its variance is

a quadratic in minimized at , where the variance becomes with the correlation between and . The reduction is governed entirely by how correlated your control variate is with the thing you are estimating. A perfectly correlated control variate eliminates the variance; an uncorrelated one does nothing; and a badly chosen can make things much worse, which is the case lands in during cold start.

Reading through that formula: , , , and fixed at in the sign convention above (equivalently, is added rather than subtracted). §4.4.1 gave the measured on the lab’s two regimes, 1.011 and 0.0133, and the far-regime number says the correlation between and has collapsed relative to ’s own variance. That is what a heavy tail does: ’s variance is dominated by a handful of enormous values, and barely moves on those same draws, so the covariance grows far slower than the variance.

You could estimate from the batch and use it, which would give an estimator at least as good as in both regimes. Almost nobody does, for a reason worth knowing: estimating from the same samples you apply it to introduces a small bias, and the resulting estimator is no longer exactly unbiased. In practice the honest simple options are “use and know your regime” or “use and pay in variance.”

Control variates show up in the distillation literature in a place you might not expect. MiniLLM formulates reverse-KL distillation as a policy-gradient problem, since minimizing reverse KL over sequences is an optimization over the student’s own sampling distribution, and policy-gradient estimators are notoriously high variance. The paper’s contribution is as much the set of variance-reduction strategies that make the gradient estimator usable as the objective itself.9 The general lesson transfers: when your objective is defined as an expectation under the model you are training, the estimator’s variance becomes a first-class design problem rather than an implementation detail, and methods that ignore it fail for reasons that look like optimization instability. The same structure appears wherever a model is optimized against its own samples under a fixed reference distribution, which is why preference optimization runs into a closely related set of estimator questions.14

Three other variance reducers are worth naming briefly, since they appear elsewhere in the book.

More samples, spent where they matter. The law is harsh but it is a law. If you have a fixed budget of teacher scoring calls, spending them on more positions from fewer sequences reduces divergence-estimate variance more than spending them on more sequences, because the estimate is a per-position average.

Dense computation on a subset. For monitoring, the exact answer on 100 positions usually beats a sampled answer on 10,000, because the exact answer has zero variance and no estimator bias. This is the same argument that makes Chapter 10’s cached-logit pipeline attractive: paying once for dense teacher outputs buys you exactness forever after.

Changing the objective so the estimator is easier. Skew KL, as used in DistiLLM, evaluates the divergence against a mixture of teacher and student rather than against either alone, which bounds the ratio and therefore bounds the variance of any ratio-based estimate.10 Bounded divergences in general, from Chapter 3, have this property: a divergence no single token can blow up is also a divergence no single sample can blow up. Wen and colleagues’ f-divergence work makes the same move from the generator side.11 When a divergence is chosen partly for estimator behavior rather than purely for what it does to the student’s distribution, that is a legitimate reason, and it should be stated as one.

4.9 What to be suspicious of#

None of what follows is settled advice. It is the honest state of the matter.

“Use k3” as unqualified advice. It is correct in the regime most people who give it are working in, which is fine-tuning-scale RL where the policy is initialized close to the reference model and stays there because a KL penalty keeps it there. Distillation from a base or pruned student is a different regime and the advice does not transfer. This chapter’s measurement is the evidence, and it is the course’s own, not a published result; I do not know of a paper that isolates the crossover cleanly, which is a gap rather than a claim that nobody has noticed.

Reported KL numbers in papers, when the estimator is not stated. A great deal of published distillation work reports “KL” on training curves without saying whether it is a dense sum, a sampled , a sampled , or an importance-weighted forward estimate. Table 4.3 shows those can differ by factors of two to three on the same distributions. Comparing across papers on this quantity is close to meaningless unless both stated their method, and the surveys that tabulate such numbers across dozens of methods inherit the problem wholesale.17

Small measured divergences as evidence of similarity. Chapter 3’s Pinsker exercise already made this point from the deterministic side: a total variation distance of 0.001 certifies nothing about KL, because the KL can be a thousand nats hiding on a token with negligible mass.12 The sampled version is worse, because the tokens carrying the divergence are exactly the ones your sampler is least likely to visit. Both failures have the same shape and both live in the tail.

The assumption that a stable monitor means a stable run. The Lab 07 case produces a sampled forward KL that is stable, small, and completely uninformative, while the student is doing the worst thing it can do. Stability of an estimate is a property of the estimator’s variance, not of the system being measured, and the two are only linked when the estimator is actually seeing the thing that would change.

4.10 Where this lands in the labs#

Lab 00 §9 is the cell that produces Table 4.3, and it is thirty lines that run in a couple of seconds; the thing it does that this chapter cannot is fail. The assertion std(k3) > std(k1) in the far regime is written the “wrong” way around on purpose, so that anyone who tidies the cell into the version the folklore predicts breaks the build. Solutions 00 Exercise 4 extends the same construction to the forward direction and produces Table 4.4, including the yardstick row that makes the 79-fold gap between forward and reverse estimation on identical draws a measured quantity rather than an argument. Solutions 00 Exercise 3, on the chi-squared divergence, supplies the missing piece of the variance story by measuring growing by ten orders of magnitude over a logit range where KL grows by a factor of five. Lab 07’s Exercise 2 puts the trap in production clothing with the against demonstration, and it is worth running that one before Chapter 12 rather than after.

4.11 Exercises#

  1. §4.3.2 derives where . Table 4.3 shows overestimating in both regimes. What does that tell you about the sign of for these distributions, and is there a reason to expect that sign generally? Construct a two-token example where underestimates, or argue that none exists.

  2. By analogy with for the reverse direction, propose a low-variance biased estimator of from samples of . State its bias to leading order using the same expansion technique as §4.3.2. Then say whether it inherits the heavy-tail problem of §4.5, and why.

  3. Before looking at Figure 4.1, predict at roughly what true KL the standard deviations of and cross, using only the expansion in §4.4.1 and the fact that ’s variance is governed by . You will need a rough relationship between KL and chi-squared for nearby distributions; derive one for the case where is small and roughly symmetric. Then compare with the figure and account for the gap.

  4. A colleague’s on-policy run shows a panel labeled “KL” that has sat between and for six hundred steps, while the sample text has become repetitive and the average generation length has fallen by two thirds. The panel is computed as (w * (p_log[x] - q_log[x])).mean() where w = (p_log[x] - q_log[x]).exp() and x are the student’s sampled tokens. Say exactly what quantity that panel is estimating, what its true value is likely doing, and what single additional panel you would add to make the situation legible. Then say what you would change about the loss, if anything.

  5. You are training a 1.7B student against an 8B teacher on the reference machine, batch of 8 sequences at 1,024 tokens, vocabulary 49,152. Estimate the additional memory traffic per step of computing a dense divergence over the full vocabulary versus a sampled estimate at one token per position, in bytes and in milliseconds at 273 GB/s. Then say at what monitoring interval the dense computation becomes free in practice, and what that implies about §4.6.2’s second recommendation.

  6. Derive from the quadratic in §4.8. Then explain, in terms of what “unbiased” means, why plugging in a estimated from the same batch you apply it to breaks unbiasedness. Propose a scheme that recovers unbiasedness at some cost, and say what the cost is.

  7. You are 200 steps into an on-policy run from a pruned student and you want to know whether you are in the close regime or the far one, using only sampled quantities. Design a test. It should be cheap, should not require dense teacher logits, and should give an answer you would defend. Say what would make it give the wrong answer.



  1. Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big: Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737 The 49,152-entry vocabulary is the tokenizer used by the student models throughout this book’s labs. 

  2. The shape of what a serving stack returns is not incidental to this chapter. vLLM’s completion response returns log-probabilities for the sampled token and optionally a small number of alternatives per position, which is the grey-box row of Chapter 1’s access table. Woosuk Kwon et al., “Efficient Memory Management for Large Language Model Serving with PagedAttention,” SOSP 2023, pp. 611-626, arXiv:2309.06180. https://arxiv.org/abs/2309.06180 Chapter 15 covers the serving path in detail. 

  3. John Schulman, “Approximating KL Divergence,” blog post, joschu.net, 7 March 2020. http://joschu.net/blog/kl-approx.html Accessed 1 August 2026. This is a personal blog post with no venue or DOI, and it is the standard source for the k1, k2, and k3 names and for the observation that is unbiased and pointwise nonnegative. 

  4. The invariance of under adding an affine term to the generator is a standard property of the f-divergence family introduced by Csiszár. Imre Csiszár, “Information-type measures of difference of probability distributions and indirect observations,” Studia Scientiarum Mathematicarum Hungarica 2 (1967): 299-318. Independently developed by S. M. Ali and S. D. Silvey, “A general class of coefficients of divergence of one distribution from another,” Journal of the Royal Statistical Society, Series B 28, no. 1 (1966): 131-142. 

  5. The clearest large-scale statement of the reverse-direction preference in language-model distillation is Yuxian Gu, Li Dong, Furu Wei, and Minlie Huang, “MiniLLM: Knowledge Distillation of Large Language Models,” ICLR 2024, arXiv:2306.08543. https://arxiv.org/abs/2306.08543v2 Note that the arXiv landing page currently shows a later retitled version; the ICLR 2024 title is the one used here. 

  6. Rishabh Agarwal et al., “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649 The method is generalized knowledge distillation, GKD; the name does not appear in the title. 

  7. Mingyang Song and Mao Zheng, “A Survey of On-Policy Distillation for Large Language Models,” arXiv:2604.00626 (2026). https://arxiv.org/abs/2604.00626 This is an unrefereed preprint whose arXiv comment reads “Ongoing Work”; I cite it for its catalogue of reported failure modes rather than for any empirical claim of its own. 

  8. Entropy as the monitored quantity during training on a model’s own samples is best documented in the reinforcement-learning-with-verifiable-rewards literature, where the collapse dynamics were characterized directly. Ganqu Cui et al., “The Entropy Mechanism of Reinforcement Learning for Reasoning Language Models,” arXiv:2505.22617 (2025). https://arxiv.org/abs/2505.22617 

  9. Gu et al., “MiniLLM,” §3. The paper derives a policy-gradient form of the sequence-level reverse-KL objective and introduces single-step decomposition, teacher-mixed sampling, and length normalization specifically to control the gradient estimator’s variance. https://arxiv.org/abs/2306.08543v2 

  10. Jongwoo Ko, Sungnyun Kim, Tianyi Chen, and Se-Young Yun, “DistiLLM: Towards Streamlined Distillation for Large Language Models,” arXiv:2402.03898 (2024), ICML 2024. https://arxiv.org/abs/2402.03898 The skew KL objective evaluates the divergence against an interpolation of the two distributions, which bounds the ratio that appears in any sampled estimate. 

  11. Yuqiao Wen, Zichao Li, Wenyu Du, and Lili Mou, “f-Divergence Minimization for Sequence-Level Knowledge Distillation,” arXiv:2307.15190 (2023), ACL 2023. https://arxiv.org/abs/2307.15190 

  12. The constant in the inequality Chapter 3 uses is due independently to Csiszár and to Kullback rather than to Pinsker’s original 1964 statement, which had a weaker constant. S. Kullback, “A lower bound for discrimination information in terms of variation (Corresp.),” IEEE Transactions on Information Theory 13, no. 1 (1967): 126-127. DOI: 10.1109/TIT.1967.1053968; M. S. Pinsker, Information and Information Stability of Random Variables and Processes (San Francisco: Holden-Day, 1964). 

  13. Sequence-level knowledge distillation is itself a Monte Carlo approximation with a sample size of one: the intractable sum over all output sequences is replaced by the teacher’s single most likely output. Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947 Chapter 11 examines how much that approximation gives up and why it works anyway. 

  14. The general structure of learning from one’s own samples under a fixed reference distribution, and the estimator questions it raises, is shared with preference optimization. Rafael Rafailov et al., “Direct Preference Optimization: Your Language Model is Secretly a Reward Model,” arXiv:2305.18290 (2023), NeurIPS 2023. https://arxiv.org/abs/2305.18290 

  15. The bias-variance framing applied to soft targets in distillation generally, rather than to divergence estimation specifically, is developed in Helong Zhou et al., “Rethinking Soft Labels for Knowledge Distillation: A Bias-Variance Tradeoff Perspective,” arXiv:2102.00650 (2021), ICLR 2021. https://arxiv.org/abs/2102.00650 

  16. Exposure bias, the reason on-policy sampling is attractive in the first place and therefore the reason this chapter’s estimators are needed at all, was formalized for sequence models long before distillation adopted the idea. Marc’Aurelio Ranzato, Sumit Chopra, Michael Auli, and Wojciech Zaremba, “Sequence Level Training with Recurrent Neural Networks,” arXiv:1511.06732 (2015), ICLR 2016. https://arxiv.org/abs/1511.06732 

  17. For the broader map of where sampled estimation sits within language-model distillation methods, see Xiaohan Xu et al., “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024). https://arxiv.org/abs/2402.13116 The pre-language-model literature is surveyed in Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819. https://arxiv.org/abs/2006.05525 

Part II · The Objective

5

The Classical Objective

Here is a run that fails in a way you cannot see from the outside.

You have a 1.7-billion-parameter teacher, a 360-million-parameter student, and a loss that mixes the teacher’s softened predictions with the ordinary hard-label cross-entropy. You set the temperature to 2 and the mixing coefficient to 0.5, train for fifteen hundred steps, and the student comes out a few points ahead of the hard-label baseline. Encouraged, you sweep the temperature: 1, 2, 4, 8. The results form a clean curve with a peak at 2, and you write down that intermediate temperatures win, which is what the 2015 paper said, and you move on.

Nothing about that sequence of events is wrong except the conclusion. If your implementation of the soft loss omits a factor of , and many do, then every step of that temperature sweep also changed the effective learning rate of the soft term by a factor of about sixty-four across the range you swept. You did not measure a temperature effect. You measured a temperature effect convolved with a learning-rate effect, and you cannot tell from the numbers which one produced the peak. The loss ran without complaint. The curve looked like the curve in the paper. The only way to have known was to derive the gradient before running anything.

That is the chapter. The classical distillation objective is four lines of algebra, everyone believes they already understand it, and the places it goes wrong live in the derivation rather than in the code. So this chapter derives everything: the loss, its gradient, the factor, the high-temperature limit, and the reason the mixing coefficient has the shape it has. Then it covers what a decade of trying to use this objective has taught the field about when it stops working, which is a less tidy body of knowledge.

5.1 Two classes, then three#

Start with the smallest case that is not degenerate.

Suppose the model has to choose between two outcomes. It produces two logits, and , and the softmax turns them into probabilities:

where is the logistic function. The whole distribution is one number, and that number depends only on the difference of the logits. Adding a constant to both logits changes nothing, which is the shift invariance from Chapter 2 showing up in its simplest form.

Now bring in a teacher. The teacher has its own two logits, and , and its own probability . Say the correct answer is outcome 1 and the teacher assigns it 0.9. Ordinary supervised training would hand the student the label “outcome 1,” which is a distribution with 1.0 on the first entry and 0.0 on the second. Distillation hands the student the teacher’s 0.9 and 0.1 instead.

Definition

Soft target

The teacher’s full probability distribution over the output space at a given position, used as the training target in place of, or alongside, the one-hot hard label. The name distinguishes it from a hard label, which puts all its mass on one outcome and says nothing about the others.

The student gained exactly one number: the teacher’s confidence. That is worth something, because a target of 0.9 stops the student from driving its logit gap to infinity the way a target of 1.0 does, and that alone is a regularizer. It is not the thing Hinton’s paper is about. With two outcomes there is one wrong answer, and one wrong answer has no internal structure, because there is nothing for it to relate to.

Three outcomes is the smallest case where the interesting thing exists. Suppose the answer is dog, and the alternatives are cat and car. Let the teacher’s logits be in that order. Softmax them:

The teacher puts 1,097 times more probability on cat than on car. That ratio is a claim about the world: given that the answer is not dog, the teacher considers cat roughly a thousand times more plausible than car, presumably because it has learned that cats and dogs appear in the same slots and cars do not. The hard label assigns zero to both and makes no claim at all. That ratio, and the ten thousand ratios like it in a real vocabulary, is what Chapter 1 introduced as dark knowledge, and it is the entire payload distillation is trying to move.

Two facts about it matter before the algebra starts. The ratio is a difference of logits, since , so the similarity structure lives in the teacher’s logit gaps rather than in its probabilities; the probabilities are a monotone re-encoding of the gaps that squashes the small ones toward zero. And the squashing is exactly the problem, because the KL divergence weights the discrepancy at outcome by , the teacher’s probability there. With , the car term contributes about two parts in a million to the loss. The information is present and carries no weight. A student trained on this target at face value spends all of its gradient on getting dog right and never hears the claim about cat and car.

Hinton’s fix is to raise the temperature.

5.2 Temperature, and what it actually changes#

Definition

Temperature-softened distribution

The distribution obtained by dividing every logit by a constant before the softmax: . Larger moves mass from the top of the distribution into the tail; smaller concentrates mass on the argmax. recovers the model’s own distribution.

Softening the three-class teacher at gives

and the cat-to-car ratio drops from 1,097 to 5.755. Note what happened to both quantities. The ratio fell, because and dividing the logits by 4 divides every log-ratio by 4. The tail’s share of the mass rose, from two millionths to three percent.

This is the whole mechanism, and it is worth stating in a form that makes the trade explicit. Temperature neither creates structure nor destroys it. Matching a softened student to a softened teacher at temperature imposes the constraint for every pair, which is the same set of constraints at every . What temperature changes is the weight the loss puts on each constraint, because forward KL weights the discrepancy at outcome by , and is what temperature moves.

Read the two limits off that statement.

As , converges to a one-hot distribution on the teacher’s argmax. All the weight lands on one outcome and the objective becomes cross-entropy against the teacher’s most likely token, which is a hard label that happens to have been produced by the teacher rather than by an annotator. Distillation at very low temperature is not distillation; it is training on the teacher’s predictions as labels, which is the sequence-level idea of Chapter 11 arrived at from the wrong direction.14

As , converges to uniform. Every outcome gets weight , including the forty thousand tokens the teacher has effectively ruled out and whose logits are close to arbitrary. Section 5.4 shows that this limit has a clean closed form, and that the closed form is plain regression on logits.

Neither endpoint is where you want to be, which is why an intermediate temperature wins. The argument for the interior is an observation rather than folklore: you want enough weight on the tail to hear the teacher’s real preferences, and not so much that you also hear its noise floor.

2026-08-01T07:26:46.343346 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.25 0.5 1 2 4 10 t e m p e r a t u r e         ( l o g   s c a l e ) T 1 0 9 1 0 8 1 0 7 1 0 6 1 0 5 1 0 4 1 0 3 1 0 2 1 0 1 1 0 0 softmax probability p(the) = 0.8823 p(qq) = 1.5e-05 p(the)/p(xylophone) = 2.72 at T = 10 the a an cat dog xylophone qq
Figure 5.1 Raising the temperature lifts the teacher's tail into the range where it can influence a loss: on a fixed seven-token logit vector, the lowest-scoring token moves from a probability of 1.5e-5 at T=1 to within a factor of three of the top token at T=10.

The seven-token example the labs use makes the movement visible. With logits standing in for the, a, an, cat, dog, xylophone, qq, the top token holds 0.8823 of the mass at and the last holds . At , xylophone is within a factor of three of the, which is a different failure: the student is being asked to model the teacher’s opinion about a token the teacher has no opinion about.

5.2.1 Quantifying dark knowledge#

“Dark knowledge” is easy to gesture at and worth measuring, because if you cannot measure it you cannot tell whether your temperature is doing anything. Two numbers do the job.

The first is the mass outside the top-1 entry, . That is an upper bound on how much of the loss can be about anything other than the argmax.

The second is sharper. Decompose the teacher’s entropy by conditioning on whether the outcome is the argmax:

where is the binary entropy of the split between “argmax” and “not argmax,” and is the teacher’s distribution over the non-argmax outcomes, renormalized to sum to one. The second term is exactly the information about which wrong answer, given that it is a wrong answer. That is dark knowledge in nats, and it is precisely the quantity a hard label destroys and a top-1 agreement metric cannot see.

Table 5.1 Dark knowledge in the seven-token teacher of Figure 5.1, computed from the logit vector, in nats.

mass outside top-1 dark knowledge share of
0.25 0.000052 0.0006 0.0000 3%
0.5 0.0091 0.0573 0.0053 9%
1.0 0.1177 0.4479 0.0856 19%
2.0 0.3647 1.0527 0.3966 38%
4.0 0.5969 1.5783 0.9041 57%
10.0 0.7613 1.8748 1.3252 71%

At this teacher spends 81 percent of its uncertainty on the single question “is it the argmax or not,” which a hard label already answers. At that is down to 62 percent. The temperature knob is buying the loss access to the other half of the teacher’s uncertainty, and the table tells you how much you bought.

Run this on your own teacher and your own corpus before choosing . A teacher on code or structured output will be far more peaked than this toy and will need more heat to expose the same fraction of its tail; a teacher on open-ended prose will already be diffuse at and may need almost none. The useful temperature is a property of the teacher and the domain, not a constant. Pick the setting where a meaningful fraction of the teacher’s entropy has moved outside the top-1 entry and the tail has not yet approached uniform; for most instruction-following work that lands between 2 and 4. At with a confident teacher you are training against something close to a hard label, and the run looks broken because it is barely distilling.

That measurement is also the check to run when a temperature sweep surprises you. If wins your sweep outright, measure the teacher’s entropy before you believe the number: a teacher that is unusually diffuse to begin with leaves less room for flattening to hurt, so a high optimum on that teacher is a property of the teacher and not a refutation of Hinton’s argument. The same sweep on a peaked teacher will put its optimum several settings lower.

Here is the measurement, written to run on a batch of real teacher logits.

import torch

def dark_knowledge_budget(logits, mask, temperatures=(1.0, 2.0, 4.0, 8.0)):
    """Mass and nats living outside the top-1 entry, per temperature.

    logits: [B, T, V] teacher logits.  mask: [B, T] bool, positions to count.
    Returns one row per temperature: (T, mean tail mass, mean total entropy,
    mean dark-knowledge nats).
    """
    rows = []
    flat = logits[mask].float()                    # [N, V]
    for temp in temperatures:
        logp = torch.log_softmax(flat / temp, dim=-1)
        p = logp.exp()
        H = -(p * logp).sum(-1)                    # total entropy, nats
        top = p.max(-1).values
        tail = (1.0 - top).clamp_min(1e-12)
        # entropy of the renormalized non-argmax distribution, via the
        # conditional decomposition H(p) = H_b(top) + tail * H(tilde p)
        Hb = -(top * top.clamp_min(1e-12).log() + tail * tail.log())
        dark = H - Hb
        rows.append((temp, float(tail.mean()), float(H.mean()), float(dark.mean())))
    return rows

The last column is the one to watch. If it is near zero at every temperature you tried, your teacher has nothing to teach beyond its argmax on this corpus and you should expect distillation to look like training on teacher labels.

5.3 The loss#

With soft targets defined, the objective is short to state. Let be the student’s logits at a position, the teacher’s, the one-hot hard label, and write

The soft term is the forward KL from teacher to student at temperature , scaled by :

The hard term is ordinary cross-entropy at :

And the combined objective is a convex mixture of the two:

Definition

Mixing coefficient (alpha)

The weight on the soft-target term of the combined distillation loss, with on the hard-label cross-entropy. is ordinary supervised training with no teacher; is pure distillation with no ground-truth signal; the interior is a mixture.

Before the derivation, four notes on what the definition commits you to.

The direction of the KL is forward, meaning the teacher is the reference distribution and the student is the argument. This is Hinton’s choice and it is the classical objective. It is also a design decision with visible consequences for what the student’s text looks like, and Chapter 6 is about that decision, along with the reverse-KL alternative,24 the wider f-divergence family,20 and the skewed variants that address the unbounded penalty forward KL charges when the student puts near-zero probability where the teacher has mass.23 For this chapter, forward KL is the objective.

Some implementations write the soft term as a cross-entropy rather than a KL. Since the teacher is fixed, the two differ by the teacher’s entropy , which does not depend on the student’s parameters, so their gradients are identical. The loss values differ, and that matters when you compare a number in your log to a number in a paper, but nothing about training changes.

The hard term is computed at , always. Softening the hard label is meaningless because a one-hot distribution is invariant to temperature, but softening the student in the hard term is a real and different objective, and it is not what the classical loss does. If your implementation divides the student’s logits by before the cross-entropy against , it is computing something else.

The objective as written says nothing about where the inputs come from. It assumes a corpus exists and that teacher and student are both scored on it, which is the response-based, off-policy corner of the taxonomy from Chapter 1.15 DistilBERT is an early large-scale instance of exactly this loss on a transformer, with the soft term mixed against a masked-language-modeling loss instead of next-token cross-entropy.21 Replacing the fixed corpus with text the student generates gives the on-policy family of Chapter 12,19 and the language-model-specific survey literature organizes the whole space around those two axes.18

In the labs: Lab 03

The lab re-verifies both endpoints of the mixture at the door of the training loop, on synthetic logits, before any model loads: hinton_kd_loss at matches F.cross_entropy to within , and at matches the standalone forward-KL function with the scaling on. The point is that the hard arm really is a no-teacher baseline and the soft arm really has no ground-truth term, so the comparison between them measures the thing it claims to.

5.4 The gradient, derived#

Everything interesting about this objective is in its derivative with respect to a student logit, so take it.

Fix a position. The soft term without the factor is

where the constant is and is independent of . Write the student’s softened log-probability using the log-sum-exp form from Chapter 2:

Differentiate with respect to :

where is 1 when and 0 otherwise. Substitute:

The teacher’s probabilities sum to one, so the second sum is 1 and

That expression repays being read three ways.

At it is the residual. The gradient is , the difference between what the student currently believes and what the teacher believes, entry by entry. Nothing else appears; the update pushes the student’s probability toward the teacher’s by exactly their current gap. If the teacher is a one-hot label, this is , the ordinary softmax cross-entropy gradient that every deep learning course derives in its first month. The distillation gradient is that same expression with a soft distribution in place of the one-hot.

The whole temperature effect is a factor of out front, with the functional form unchanged. This is the most useful fact in the chapter for debugging, because any custom distillation loss can be checked in five lines: compute the two softened distributions, form , and compare to what autograd gives you. If they disagree, your loss is not the loss you think it is.

Masking is part of the formula. In a real training loop you average over supervised positions rather than summing over all of them, so with a boolean mask over positions and supervised positions, the gradient is

where broadcasts the position mask across the vocabulary axis. That is not cosmetic. It means long sequences in a batch contribute more total gradient than short ones, and the same arithmetic reappears on the evaluation side in Chapter 16. Sorting an eval set by completion length and scoring each stratum separately, which is the method §16.3 sets out, produces a long stratum that scores several points higher than the short one, and the mechanism is this weighting rather than anything about the model: a per-token agreement average mechanically favours long strata, because deep positions inside a long completion are pinned down by local context while the first few tokens after a prompt are the hardest ones in the set. Chapter 16 also gives the failure signature that tells you the effect has gone the wrong way, which is a KD advantage concentrated on short completions and usually means prompt tokens have leaked into the mask.

In the labs: Lab 01

The identity is checked against autograd rather than trusted. On a batch with a deliberately short second row so the mask is exercised, at , the largest absolute difference between PyTorch’s gradient and the analytic is under . That assertion is the reason this chapter can state the identity flatly.

5.5 Where the comes from#

The in front of the residual is the visible half of the temperature’s effect on gradient magnitude. The other half hides inside the residual itself.

Softening pushes both distributions toward uniform. Pushing two distributions toward the same place pushes them toward each other, so shrinks as grows. How fast? Expand the softmax for large . With small,

Sum over the vocabulary. If the logits are zero-mean, meaning , the sum of the first-order terms vanishes and , where is the vocabulary size. So

and the residual is

which is , as promised. Multiply by the explicit from the derivative:

The soft term’s gradient falls off as . The hard term’s gradient does not move at all, because the hard term is computed at and knows nothing about the temperature you chose for the soft term. So changing silently changes the ratio between the two terms’ contributions to the update, even though has not moved. Multiplying the soft term by cancels the scaling exactly, and that is where the factor comes from.

Definition

T² correction

The factor of applied to the soft-target term of the distillation loss. The softened KL’s gradient with respect to student logits scales as ; the hard-label term’s does not. Multiplying the soft term by restores comparable gradient magnitudes so that the mixing coefficient means the same thing at every temperature.

Hinton’s paper states the correction in one sentence and gives the same reason: because the soft gradients scale as , they should be multiplied by so that the relative contributions of the hard and soft targets stay roughly unchanged when the temperature is varied during experimentation.1 It is one line in a workshop paper, and it is the line most often dropped in reimplementation.

2026-08-01T07:26:48.053975 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1 2 3 4 5 6 7 8 t e m p e r a t u r e   T 1 0 2 1 0 1 1 0 0 z s o f t 2 a s y m p t o t e       =   0 . 1 8 3 7 ̄ ̄ / z v V corrected T q p T 2 ( ) / T T uncorrected ( ) / q p T T T falls 86x from T=1 to T=8 +16% from T=1 to T=2, then drifts down: the first-order expansion is loose at low T
Figure 5.2 On a fixed pair of eight-entry logit vectors, the uncorrected soft-loss gradient norm falls by a factor of 86 between T=1 and T=8, while the T-squared-corrected norm stays inside a factor of 1.6 across the same range.

Figure 5.2 shows the correction working on a fixed toy pair. Two details in it are worth naming because they are places the clean story is approximate.

The first is that the uncorrected gradient falls by 86 times between and , not by the 64 times that predicts. The law is the high-temperature asymptote, and at a logit vector with a spread of six is nowhere near the regime where is small. Below the asymptote the decay can be steeper or shallower depending on how spread out the logits are. Lab 01 measures roughly a 64-fold drop on random unit-scale logits over the same range, which is closer to the asymptote because those logits are less spread out. Both are the same phenomenon at different points on the curve.

The second is that the corrected line is not flat. It rises 16 percent from to , then drifts down toward , its asymptote, where the bars denote mean-centered logits. “Roughly constant” is the honest description, and roughly constant is all the correction needs to be, since its job is to keep interpretable rather than to hold a physical constant.

Field note

I had this half-wrong for longer than I want to admit. I could see the explicit in the gradient, so I believed the correction should be a factor of , and I spent an afternoon convinced that the paper had a typo. The missing piece is that the residual is not a fixed quantity that the scales; it is itself a function of , and it shrinks at the same rate. Two factors of , one visible in the chain rule and one buried in how softening moves the two distributions toward each other. The general lesson is the one from Lab 01: when a scaling argument involves a quantity that itself depends on the parameter you are scaling, derive it, do not count the factors you can see.

5.6 The high-temperature limit is logit matching#

Cash in the second half of the previous section’s expansion now. It found that at large the gradient of the uncorrected soft loss approaches . Now ask what objective has that gradient. The derivative of

with respect to is , the same thing. So in the high-temperature limit, minimizing the softened KL is minimizing the squared error between the student’s logits and the teacher’s. All the structure the softmax imposes, the competition among outcomes for a fixed budget of probability mass, has dropped out, leaving plain regression.

Apply the correction to both sides and the temperature disappears entirely:

which is a -independent objective. That is the cleanest possible statement of what the correction does: it makes the loss converge to something finite and fixed as grows, instead of converging to zero.

In the labs: Lab 01

The limit is asserted numerically, not argued. At on mean-centered synthetic logits, the gradient of the softened KL and the gradient of agree to within everywhere. Solutions 03 makes the same point with a cosine similarity between the two gradient directions, which lands above 0.99 at and at 0.82 at , confirming that the alignment is a high-temperature effect rather than something that was always true.

5.6.1 The zero-mean assumption#

The expansion above used and . Hinton’s paper makes this assumption explicitly, zero-meaning the logits separately for each transfer case, and it is worth being precise about what it costs.2

Redo the expansion without it. If is the mean logit, then , so and the residual becomes

The limit is still logit matching, but on mean-centered logits. That is a consequence of softmax’s shift invariance rather than a failure of the theory: the mean logit has no effect on any probability, so no probability-space objective can constrain it.

The practical trap is in the other direction. Some pipelines skip the softmax and regress the student’s raw logits onto the teacher’s raw logits, on the grounds that the high-temperature limit says this is what distillation converges to anyway. Without centering, that objective penalizes the student for having a different mean logit than the teacher, a quantity that does not affect the student’s predictions at all. The student spends capacity satisfying a constraint with no behavioral content and, depending on your weight decay, fights the optimizer over it. If you are going to run the high-temperature limit on purpose, center both sides.

The other place the assumption fails is about rather than the mean. The first-order expansion is accurate once is small for every , and real language-model logits have a spread of tens, so “small” means a temperature well into the double digits. The lab verifies the limit at and finds the gradient alignment only 0.82 at . Nobody trains at . The logit-matching interpretation is therefore a statement about a limit that practice does not visit, and you should not reason about a run at as though it were doing logit matching.

What the limit is good for is explaining why very high temperatures hurt. Logit MSE weights every vocabulary entry equally. The teacher’s logit on its thirty-thousandth-choice token gets exactly as much of the student’s capacity as its logit on the answer, and that logit is close to noise: the teacher was never trained to place it carefully, because nothing in its own loss ever depended on it. Hinton makes the same observation from the other side, arguing that intermediate temperatures work well precisely because they partially ignore the very negative logits, which carry little signal and considerable noise.3 Solutions 03 reports the consequence on the synthetic teacher it builds: by , more than 90 percent of the teacher’s mass has moved onto wrong answers and the entropy is approaching nats, the uniform ceiling for that toy vocabulary. There is not much teacher left in a target like that.

5.7 What happens when the factor goes missing#

Drop the from the soft term and keep everything else. The combined loss becomes

and its gradient is

The first term now carries a factor that shrinks as relative to the second. Set and go from to : the soft term’s contribution to every update drops by about sixteen while the hard term’s holds still. In effect, has moved from 0.5 to something near 0.06 without you touching it.

The consequence for experiments is the thing to internalize. Sweeping temperature at fixed and fixed learning rate with the correction missing sweeps two variables at once: the shape of the target and the effective step size on the soft term. Any peak you find is uninterpretable, because it might be the temperature optimum of §5.2 or it might be one temperature happening to land the soft term’s effective learning rate in a good place. You cannot separate them after the fact, because you did not vary them independently.

Watch out

A missing produces no error, no warning, and a loss curve of entirely normal shape. It is in the same family as an inverted divergence direction or an off-by-one mask: the run trains, the number goes down, and the thing being trained is not the thing you specified. Check the factor before you trust any temperature comparison, including one in a paper that does not say which convention it used.

There are three ways to notice, and none of them requires a training run.

Log the two terms’ gradient norms separately. This is the direct measurement and it takes about ten lines. Compute the soft loss alone, take its gradient norm, zero the gradients, compute the hard loss alone, take its gradient norm, and log the ratio. If the correction is present, the ratio should be roughly stable across temperatures. Lab 03’s pre-flight asserts exactly this in the form for the soft term, a band chosen to pass with the correction and to fail without it, since the uncorrected ratio would be near 4. Solutions 03 runs the wider version: between and the raw ratio should land in the band , since , while the corrected ratio should stay in .

The measurement is short. Look at how the returned soft norm behaves as moves.

import torch

def term_gradient_norms(student_logits, teacher_logits, labels, mask, T, alpha):
    """Gradient norm of each loss term, taken separately, at one temperature.

    Run this at a few temperatures before a sweep. With the T^2 correction in
    place the soft norm should be roughly flat in T; without it, it falls ~T^2.
    """
    z = student_logits.detach().clone().requires_grad_(True)
    soft = (T ** 2) * kl_softened(z, teacher_logits, mask, T)     # your loss
    soft.backward()
    soft_norm = z.grad.norm().item()

    z.grad = None
    hard = cross_entropy_masked(z, labels, mask)                  # your loss
    hard.backward()
    hard_norm = z.grad.norm().item()

    return {"T": T, "soft": alpha * soft_norm, "hard": (1 - alpha) * hard_norm,
            "ratio": (alpha * soft_norm) / max((1 - alpha) * hard_norm, 1e-12)}

The ratio in the last field is the number that is supposed to control. If it moves when you change , does not mean what the config file says it means.

Watch for a sweep where wins outright. With the correction missing, low temperature gets the largest effective learning rate on the soft term, so it has a structural advantage that has nothing to do with the target. Solutions 03 predicts the opposite pattern for a correct sweep: the arm should land closest to the hard-label baseline of all of them, because a peaked teacher at is nearly a hard label. Getting the reverse ordering is a signal to check the loss before believing the result.

Report it either way. A temperature sweep must either keep the factor or say that it did not, and there is no third option that produces an interpretable number. If you keep it, run the gradient-norm ratio check above before you launch, so that you know the factor is in the code and not only in the documentation. If you sweep temperature without the correction, say so in the writeup, and say what the effective soft-term learning rate was at each temperature. That converts an uninterpretable result into an interpretable one about a joint sweep. The failure is not omitting the factor, which is sometimes a defensible choice if you retune the learning rate per temperature. The failure is omitting it silently.

5.8 The mixing coefficient#

The last free parameter of the classical objective is , and its behavior is less settled than the temperature’s.

Start with what each endpoint gives up. At the teacher is disconnected from the gradient and you are running ordinary supervised fine-tuning. At the ground truth is disconnected and the student is trained only to reproduce the teacher, including the teacher’s mistakes; on any position where the teacher is confidently wrong, a pure-soft student is trained toward the wrong answer with no counterweight.

There is a clean way to see the interior, and it is exact at . Take the gradient of the combined loss with the correction in place:

Set and the two softened distributions collapse to the same :

At unit temperature, the whole combined objective is a single cross-entropy against the blended target : a distribution that is the teacher’s, with extra mass moved onto the correct token in proportion to . That makes the trade concrete. Moving down increases the mass the target puts on the ground truth and shrinks the tail structure by the same factor, linearly, entry by entry. Above the two terms no longer collapse into one target, but the intuition survives: is trading a guarantee about the correct token against the amount of the teacher’s tail that reaches the gradient.

2026-08-01T07:26:49.270363 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.0 0.2 0.4 0.6 0.8 1.0 α       ( w e i g h t   o n   t h e   t e a c h e r ) 0.4 0.5 0.6 0.7 0.8 0.9 1.0 target mass on the correct token ground-truth mass 1.0000 -> 0.4726 dark knowledge 0 -> 0.6406 nats both exactly linear in alpha c o m p u t e d   e x a c t l y ,   T = 1 0.0 0.5 1.0 α       ( w e i g h t   o n   t h e   t e a c h e r ) held-out quality hard mixed soft Lab 03's three arms peak somewhere in here; the ordering inside is not settled expected shape, not a measurement 0.0 0.2 0.4 0.6 dark-knowledge content of the target (nats)
Figure 5.3 The alpha trade, exactly computed on the left and expected on the right: the blended target's ground-truth mass falls linearly in alpha while its dark-knowledge content rises linearly, and held-out quality is expected to peak somewhere in the interior, though the interior peak is not guaranteed.

5.8.1 The U-shape, stated as an expectation#

The standard claim is that held-out quality as a function of is an inverted U: worse at both endpoints, better in the middle, because pure hard labels throw away the teacher’s information and pure soft targets throw away the ground truth. That claim is repeated more confidently than the evidence supports, so it is worth splitting into halves.

The left half is on solid ground. If soft targets do not beat hard labels at all, with an in-family teacher, in-distribution data, and a healthy teacher in eval mode, the problem is the setup rather than the theory. Lab 03 expects the distilled arms to beat the hard-label arm by 2 to 8 points of top-1 agreement with the teacher at equal steps, and by a visible margin on expected calibration error, the binned gap between a model’s stated confidence and its realized accuracy.13

The right half is not. Whether beats depends on how much the ground truth adds beyond what the teacher already knows, and for a strong teacher on in-distribution data the answer is often “very little.” Hinton’s own guidance points this way: the paper reports that the best results generally came from putting a considerably lower weight on the hard-label objective than on the soft one, which is a claim that the optimum sits well toward the soft end.4 Lab 03 expects mixed at and soft at to land genuinely close, and says either ordering is a legitimate result.

Where the interior does tend to earn its keep is calibration rather than agreement. The hard term keeps pulling probability onto tokens that are actually correct, which keeps the student’s stated confidence tied to its realized accuracy, and a student trained purely against a teacher can drift into confidence its accuracy does not support. Lab 03’s expectation is that ’s advantage over shows up on ECE more than on agreement. That is a case where two metrics disagree about the ordering of two arms, and if you only logged one of them you would report a clean result and be half wrong.

Zhou and colleagues frame the soft-target weight as a bias-variance trade rather than a single global constant, arguing that the right weight differs across training examples.5 That is one explanation for why the U is so often flat: if the optimal varies per example, any fixed is a compromise, and small changes to a compromise do not move much.

So start at 0.5, which is the labs’ value, and move from there for reasons you can name. Move toward 1 when the teacher is much stronger than the labels, which is the usual situation when the labels are a scraped corpus and the teacher is a well-trained instruction model. Move toward 0 when the teacher is mediocre, out of domain, or confidently wrong on the cases you care about. Expect the curve to be flat between 0.5 and 1 and to fall off steeply below about 0.2, and expect to learn more from the ECE column than from the agreement column while you do it.

In the labs: Lab 03

The alpha comparison is three arms rather than a sweep: hard at 0.0, mixed at 0.5, soft at 1.0, with the same student, the same 1.7B teacher, the same seed, and throughout. Three points can establish that the interior beats the left endpoint. They cannot resolve the shape of the curve between 0.5 and 1.0, and the lab does not claim they can. If you want the shape you need a denser sweep and more than one seed, which is Chapter 18’s material.

Sweeping either knob has the same precondition, and it is worth stating once for both. Everything else has to be held fixed and asserted rather than assumed: seed, data, data order, batch size, gradient accumulation, learning rate, warmup, schedule shape, total steps, sequence length, and dtype. Lab 03’s discipline is to compute the set of configuration keys by which two arms differ and assert that it has exactly one element, so a sweep over must produce {"T"} and nothing else. An ablation that varies two things measures neither of them.

5.9 A better teacher can be a worse teacher#

The classical objective has an implicit assumption in it: that a more accurate teacher produces better soft targets. That assumption is false in at least two distinct ways, and both of them are instructive.

5.9.1 Label smoothing#

Label smoothing replaces the one-hot training target with a mixture: instead of 1.0 on the correct class and 0 elsewhere, it trains toward on the correct class and spread uniformly over everything else, so with every wrong token gets exactly of the target mass. It is a standard regularizer, it usually improves accuracy slightly, and it reliably improves calibration.

Now read that target with distillation in mind. A model optimized against it is being explicitly rewarded for making every wrong answer equally probable, and the entire content of a soft target is that the wrong answers are unequally probable. Label smoothing trains away the exact structure distillation exists to transfer, on purpose, as its objective.

Müller, Kornblith, and Hinton measured this and traced the mechanism.6 Their finding is that label smoothing tightens the clusters of penultimate-layer representations: examples of the same class are pulled into tighter, more equidistant groups, which erases information about how a given example resembles the templates of the other classes. That information is what the soft targets encode. So the smoothed teacher scores better on its own metrics and distills worse, and the effect is not small.

Solutions 03 makes the mechanism visible without training anything, on a synthetic 1000-entry teacher with dog at logit 9, cat at 4, and car at . It reads two dials: the ratio , a plausible wrong answer against an implausible one, which the original teacher puts near 1,100; and the standard deviation of the log-probabilities across all wrong answers, a one-number summary of how much wrong-answer structure exists at all, which starts around 2 nats and would be zero if every wrong answer were interchangeable.

At the label-smoothing optimum the ratio collapses to exactly 1.0 and the spread to exactly 0. Every wrong answer has become the same wrong answer. Halfway toward the optimum, standing in for a brief smoothed fine-tune, the ratio is already cut by a factor of about thirty and the spread by half. And the argmax never moves. stays high, the teacher’s accuracy is intact, and any evaluation that looks at the teacher’s top-1 predictions sees nothing wrong at all.

Watch out

Teacher accuracy is the wrong selection criterion for a teacher. Two teachers can have identical top-1 accuracy, identical loss, and identical benchmark scores, and one of them can have almost no distillable structure in its tail. If you are choosing between candidate teachers, measure the thing distillation actually reads: the wrong-answer spread from §5.2.1, or the dark-knowledge nats, on your own corpus. It takes one forward pass over a few hundred prompts.

The full result has a sharper form: a teacher can get better and become a worse teacher at the same time, because teacher quality for the purposes of distillation is measured in the wrong answers. This is also, as Chapter 1 noted, the cleanest available evidence that the information argument for why distillation works is doing real work. If the benefit were entirely a regularization effect from training against a smooth target, then label smoothing, which makes the target smoother, could only help.

5.9.2 The capacity gap#

The second way a better teacher can be a worse teacher is about size rather than training.

Definition

Capacity gap

The finding that a teacher much larger than the student can produce a worse student than a smaller, less accurate teacher would have. The student quality as a function of teacher size rises, peaks, and then declines, so the best teacher for a given student is often not the best available model.

Cho and Hariharan established the result on image classifiers and, more usefully, tried to find out why.7 They showed that increasing the teacher’s size past a point degrades the student, and they tested the obvious explanation directly by checking whether the student could match the teacher on the training data at all. It could not, even when given every advantage. Their proposed remedy is the part that makes the result memorable: stop the teacher’s training early. An early-stopped teacher is less accurate by every conventional measure, and it distills better.

2026-08-01T07:26:50.131737 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 100M 1B 10B 100B teacher parameter count (log scale) student held-out quality peak location depends on the student and the task gap-small 360M 2.7x student gap-large 1.7B 12.6x student student held fixed at 135M three explanations for the decline: representation, optimization, degenerate targets expected shape
Figure 5.4 The expected shape of the capacity-gap result, with the course's two probe points marked: student quality as a function of teacher size rises to a peak and then declines, so the largest available teacher is not automatically the right one.

There are three competing explanations in circulation, and the evidence does not cleanly separate them.

Representation. The student’s architecture cannot express the function the teacher computes, so the target is unreachable and the loss stops providing useful gradient. This is the explanation people reach for first and the hardest to test, because “cannot express” is a claim about a hypothesis class nobody can characterize for a transformer.

Optimization. The student could express something close to the teacher’s function but cannot find it. Cho and Hariharan’s inability to make the student fit the teacher even on training data points here rather than at representation, and §5.10 widens the seam considerably.

Degenerate targets. A very large, very well-trained teacher is extremely confident. Its distributions are close to one-hot, the tail carries almost no mass, and the soft target degenerates toward a hard label. On this account the capacity gap is not about capacity at all; it is the dark-knowledge measurement from §5.2.1 going to zero as teachers get better, and it predicts that raising the temperature should partially rescue the large-teacher case. I have not seen that prediction tested cleanly.

The three explanations are not exclusive and probably all contribute. What matters for planning is that they make different recommendations: the first says pick a smaller teacher, the second says train longer, and the third says raise the temperature. A fourth response, older than the diagnosis, is to stop relying on output matching alone and supervise the student’s intermediate layers as well, which is what FitNets introduced and what Chapter 14 develops.22

5.9.3 Patience and consistency#

Beyer and colleagues argued the second explanation hard enough to change what the recipe looks like.8 Their framing is that distillation should be understood as function matching, and that if you take that framing literally the standard recipe is doing it wrong in two ways.

Definition

Function matching

The interpretation of distillation as approximating the teacher’s input-output function rather than as a form of regularized supervised training. Taken literally it requires that teacher and student see identical inputs, including identical data augmentation, so that every training signal is an evaluation of the same function at the same point, and that training run long enough for the approximation to converge.

The first fix is consistency. If the teacher and the student see different views of an example, because augmentation was applied independently to each, then the student is being trained to match the teacher’s answer to a question the teacher was never asked. Fixing this means passing exactly the same augmented input to both.

The second fix is patience. Function approximation is a hard optimization problem, and Beyer and colleagues found that the gains kept arriving far past the point where a supervised recipe would have stopped, with training schedules long enough that the usual intuitions about overfitting stop applying. Their headline is that this patience is worth more than most of the architectural choices the field argues about, including the size gap between teacher and student.

The consistency half translates directly to language models. Teacher and student must see the same tokens under the same chat template with the same masking, which sounds obvious and is exactly what Chapter 7 shows is hard to get right. The patience half translates too, and it is the basis for the course’s own test of the capacity gap, which runs on the SmolLM2 family because that family publishes checkpoints at 1.7B, 360M, and 135M parameters along with the instruction corpus they were trained on, so teacher and student are in-family and the data is in-distribution for both.16

In the labs: Lab 03

The capacity-gap probe is two arms with one key different. gap-small distills the 360M model into the 135M model, a ratio of 2.7. gap-large distills the 1.7B model into the same 135M model, a ratio of 12.6. Same student, same , same , same seed, same data, same 1,500 steps: the configs differ in the teacher key and nothing else, and the lab asserts that before it runs. The expectation is that gap-large beats gap-small by less than the teacher-size ratio suggests, may tie it, and may lose. A small loss for the larger teacher is the capacity gap appearing in your own logs, and it is a real result. A large win for the larger teacher would be the surprising outcome, and would say your student had headroom the literature did not expect.

Solutions 03 turns the patience claim into an experiment on that probe, and the design is worth walking through because the obvious version of it is wrong. The question is whether doubling the training budget on gap-large closes the gap. Comparing gap-large at 3,000 steps to gap-large at 1,500 confounds patience with compute: the longer arm got twice the gradient updates, so its advantage tells you nothing about the capacity gap. Three comparisons do answer something. The patient arm at 1,500 steps against the original at 1,500 must match, since the configs agree up to that point and the seed is fixed, so any drift is a reproducibility bug rather than a finding, and it is free to check. The patient arm at 3,000 against the original at 1,500 asks whether more time helps at all. And the patient arm at 3,000 against gap-small at 1,500 is the capacity-gap question: does patience buy back what the oversized teacher cost. Beyer’s line predicts partial closure, roughly 1 to 3 points of top-1 agreement with eval KL still declining slowly, putting the patient large-teacher arm between the two originals. Full closure would say the gap in your setup was mostly an optimization-time artifact, which is reportable in either direction.

Two traps in that design deserve naming. Doubling max_steps stretches the cosine learning-rate schedule, so the patient run spends its extra steps at moderate learning rates rather than in the near-zero tail; resuming a finished run and bolting on 1,500 more steps with a fresh schedule breaks the comparison. And if agreement improves while ECE worsens through the extension, the student is memorizing the teacher’s confidence faster than its ranking, which is the known late-phase failure of long distillation runs. Stop at the ECE inflection rather than at the step budget.

Teacher size is the fourth knob in this chapter and the last one to get a diagnostic, so this is the place to put all four side by side. Every row is a failure I have watched happen, and the right column is what separates it from the failures it resembles.

Table 5.2 What each knob costs when you get it wrong, and the diagnostic that catches it.

Knob Set too low Set too high Diagnostic that catches it
Soft target is nearly a hard label; distilled arms barely beat the baseline Student fits the teacher’s noise floor; ECE degrades; approaches logit regression Dark-knowledge nats per §5.2.1, measured on your corpus
Teacher barely reaches the gradient Student inherits the teacher’s confident errors; calibration drifts ECE and agreement logged separately; they disagree
factor Absent: soft term’s effective learning rate falls as n/a Soft-term gradient norm at two temperatures; ratio should be near 1
Teacher size Under-informative teacher Capacity gap; student cannot use the extra teacher Two teacher sizes at matched budget, one key different

5.10 Fidelity is not generalization#

Everything so far has assumed a story: the student is learning to compute the teacher’s function, and the objective measures how far it has gotten. Stanton and colleagues tested that story directly and it did not survive.9

Definition

Fidelity

How closely the student reproduces the teacher’s predictive distribution, measured on held-out inputs by agreement on the top-1 prediction or by a divergence between the two distributions. Distinct from generalization, which is how well the student performs on the task. The two can move independently.

Their result has three parts and each one costs the standard story something.

Students frequently fail to match their teacher’s predictions even when the student has enough capacity to do so. This is not the capacity gap; it appears in settings where the student’s hypothesis class demonstrably contains the teacher’s function, including self-distillation where student and teacher have identical architectures.

Improving the optimization does not close the gap. More distillation data, better initialization, and more aggressive optimizer settings improve fidelity somewhat and do not bring it to the level the generalization improvement would lead you to expect. Whatever is blocking the match is not a matter of running the optimizer harder.

And low fidelity does not prevent the student from generalizing well. Students that agreed with their teacher less often nonetheless had better held-out accuracy than the same students trained without distillation.

Put together, this says that “the student learns the teacher’s function” is not a description of what distillation does. It describes what the objective is written to reward, and the objective is not achieving it. The benefit arrives through some other channel: the smoothness of the target, the extra bits per example, the way soft targets change the loss surface, or some combination the field has not decomposed. The practical consequence, which Chapter 16 develops, is that agreement with the teacher is a fidelity measurement and cannot be reported as a quality measurement, because Stanton’s result says the two can move in opposite directions. Log both, and treat a disagreement between them as data rather than noise.

Watch out

The classical objective’s loss value is a fidelity measurement. It is the divergence between the student and the teacher, and nothing in it refers to whether the student is any good. A run whose KD loss is falling steadily is a run whose student is agreeing more with its teacher, which is necessary for the method to be doing anything and is not sufficient for the student to be improving. This is the reason Lab 03 logs top-1 agreement, held-out forward KL, entropy, and expected calibration error separately, and treats the loss itself as the least informative of them.

5.11 Self-distillation, and what it rules out#

The limiting case of the capacity gap is no gap at all: teacher and student with identical architecture, identical size, and often identical initialization scheme, differing only in that one of them was trained first.

Definition

Self-distillation

Distillation where the student has the same architecture and capacity as the teacher. Since there is nothing to compress, any improvement the student shows over the teacher must come from the training signal rather than from the transfer of capability, which makes self-distillation the cleanest available test of the mechanism.

Furlanello and colleagues ran this and called the result born-again networks.10 The student, identical in architecture to its teacher, outperformed the teacher. Iterating, using each generation’s student as the next generation’s teacher, produced a sequence of models that kept improving for several rounds, and ensembling the generations improved things further.

It is worth being precise about attribution here, because the literature has a persistent mislabeling: born-again networks are Furlanello, Lipton, Tschannen, Itti, and Anandkumar (2018). They are not Zhou et al., who wrote a different and also useful paper about the bias-variance properties of soft labels.5 If you see “Zhou et al., BAN” in a related-work section, the citation is wrong. A separate line of work distills between the depths of a single network rather than between two networks, which is also called self-distillation and is a different mechanism.17

What born-again networks rule out is any explanation of distillation that depends on the teacher being larger or more capable than the student. No capacity is being compressed, because the capacities are equal, and no capability is being transferred that the student’s architecture could not have reached on its own, because it is the same architecture. Whatever the benefit is, it is a property of training against a smooth, model-produced target rather than of the teacher’s superiority.

There are two theoretical accounts of that property, and both should be held loosely.

Mobahi, Farajtabar, and Bartlett analyze self-distillation for regularized regression in a Hilbert space and show that each round amplifies the regularization: the fitted function gets progressively smoother, so a few rounds improve generalization and too many underfit.11 That predicts the non-monotone behavior Furlanello observed across generations, which is a point in its favor. It is proved for kernel regression rather than for transformers, and the paper does not claim otherwise.

Allen-Zhu and Li propose a “multi-view” structure for the data: each class is associated with several distinct predictive features, any single trained network learns a subset of them, an ensemble covers more, and distillation is the mechanism by which the ensemble’s broader coverage is compressed into a single model.12 On this account dark knowledge is the record of which views the teacher learned, and self-distillation works by performing an implicit ensembling. It is a theorem under an explicit generative assumption about the data, and that assumption is a modeling choice rather than a measured property of real corpora.

Neither account has been shown to explain the behavior of distilled language models specifically. I include them because “nobody knows why it works” is a worse answer than two partial accounts with their assumptions stated, and because both make predictions you could test on your own runs.

5.12 Where this lands in the labs#

Lab 01 is where the algebra in this chapter stops being algebra. Its §2 checks the gradient identity against autograd at three temperatures with a mask deliberately exercised, and its §3 checks the high-temperature limit against logit MSE at ; both are assertions rather than demonstrations, so if either claim ever stops holding, the notebook fails loudly rather than quietly printing a plausible number. Lab 03 is where the objective meets a real model pair, and its value is the part this chapter cannot supply: the pre-flight discipline that makes a five-arm comparison trustworthy before any of it runs, and the experience of writing a verdict from your own logs when two of your diagnostics disagree about which arm won. Solutions 03 carries the temperature sweep, the label-smoothing mechanism worked on synthetic logits, and the patient-teacher design whose three comparisons are the model for every “does more training help” question in the rest of the course.

5.13 Exercises#

  1. For two outcomes, write the softened KL as a function of the single logit gap , differentiate with respect to , and show that the result is consistent with the general identity $\partial\mathcal{L}/\partial z_k = (1/T)(q_k - p_k)$. Explain why the two-logit gradient has one degree of freedom while the general formula appears to have , and what constraint removes the extra ones.

  2. §5.5 kept the first-order term of . Keep the second and derive the correction to . Then answer: for a logit vector whose entries span a range , roughly how large must be for the first-order term to dominate the second by a factor of ten? Evaluate your answer for a real language model where is about 30, and say what that implies about reasoning about a run at as though it were logit matching.

  3. Four arms differ only in temperature, , at , with the correction present, against a well-trained in-family teacher. Write your predicted ordering on top-1 agreement and on expected calibration error, with one sentence of mechanism for each. Then say how the predictions change if the correction is absent, and which arm moves the most.

  4. A run at , shows loss falling smoothly, top-1 agreement with the teacher rising steadily, and expected calibration error rising as well. Nothing has crashed. Name what is happening, say which loss term you would move and in which direction, and say what the intervention should do to agreement. Then say what a different run with flat agreement near zero from step 0 indicates instead, and why it is a different class of bug.

  5. Two candidate teachers, same size. Teacher A scores 2 points higher on the benchmark you care about and was trained with label smoothing at ; teacher B has no smoothing. A colleague picks A because it is more accurate. State the argument against, then design a measurement that settles it in under an hour of compute, naming the quantity you would compute and the threshold at which you would change your recommendation.

  6. For each of the three explanations in §5.9.2, write one experimental result that would be evidence for it and one that would be evidence against. Then say which pair the patient-teacher experiment of §5.9.3 can distinguish, and which pair it cannot.

  7. You log held-out top-1 agreement with the teacher, held-out forward KL, and held-out task accuracy. At step 800 agreement plateaus while task accuracy keeps climbing. Give two accounts of what could be happening, say which one §5.10 makes more likely, and name one measurement that would distinguish them.



  1. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015), §2. The paper notes that the soft-target gradients scale as and that they must therefore be multiplied by when hard and soft targets are used together, so that the relative contribution of the two terms is unchanged when the temperature is varied. https://arxiv.org/abs/1503.02531 

  2. Hinton, Vinyals, and Dean, §2.1, which derives the high-temperature limit under the assumption that the logits have been zero-meaned separately for each transfer case. 

  3. Hinton, Vinyals, and Dean, §2.1, on intermediate temperatures working best because they partially ignore the very negative logits, which the teacher’s own training constrained only weakly. 

  4. Hinton, Vinyals, and Dean, §2, reporting that the best results were generally obtained with a considerably lower weight on the hard-label objective than on the soft-target objective. 

  5. Helong Zhou, Liangchen Song, Jiajie Chen, Ye Zhou, Guoli Wang, Junsong Yuan, and Qian Zhang, “Rethinking Soft Labels for Knowledge Distillation: A Bias-Variance Tradeoff Perspective,” arXiv:2102.00650 (2021), ICLR 2021. https://arxiv.org/abs/2102.00650 

  6. Rafael Müller, Simon Kornblith, and Geoffrey Hinton, “When Does Label Smoothing Help?” arXiv:1906.02629 (2019), NeurIPS 2019. The distillation result and the penultimate-layer clustering analysis are the two halves of the paper’s argument. https://arxiv.org/abs/1906.02629 

  7. Jang Hyun Cho and Bharath Hariharan, “On the Efficacy of Knowledge Distillation,” arXiv:1910.01348 (2019), ICCV 2019. The early-stopped-teacher result is the paper’s proposed remedy for the degradation it measures. https://arxiv.org/abs/1910.01348 

  8. Lucas Beyer, Xiaohua Zhai, Amélie Royer, Larisa Markeeva, Rohan Anil, and Alexander Kolesnikov, “Knowledge distillation: A good teacher is patient and consistent,” arXiv:2106.05237 (2021), CVPR 2022. https://arxiv.org/abs/2106.05237 

  9. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. The paper’s central distinction between fidelity and generalization is the one used in §5.10. https://arxiv.org/abs/2106.05945 

  10. Tommaso Furlanello, Zachary C. Lipton, Michael Tschannen, Laurent Itti, and Anima Anandkumar, “Born Again Neural Networks,” arXiv:1805.04770 (2018), ICML 2018. https://arxiv.org/abs/1805.04770 

  11. Hossein Mobahi, Mehrdad Farajtabar, and Peter L. Bartlett, “Self-Distillation Amplifies Regularization in Hilbert Space,” arXiv:2002.05715 (2020), NeurIPS 2020. The analysis is for regularized regression in a Hilbert space, not for deep networks. https://arxiv.org/abs/2002.05715 

  12. Zeyuan Allen-Zhu and Yuanzhi Li, “Towards Understanding Ensemble, Knowledge Distillation and Self-Distillation in Deep Learning,” arXiv:2012.09816 (2020), ICLR 2022. The results hold under an explicit multi-view assumption about the data distribution. https://arxiv.org/abs/2012.09816 

  13. Chuan Guo, Geoff Pleiss, Yu Sun, and Kilian Q. Weinberger, “On Calibration of Modern Neural Networks,” arXiv:1706.04599 (2017), ICML 2017. The source for expected calibration error and for temperature scaling as a post-hoc calibration method, which uses the same softening operation as §5.2 for an unrelated purpose. https://arxiv.org/abs/1706.04599 

  14. Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. The limit of §5.2 arrives at training on the teacher’s most likely output, which is this paper’s approximation reached from the other direction. https://arxiv.org/abs/1606.07947 

  15. Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819, for the response-based framing under which the objective in §5.3 sits. https://arxiv.org/abs/2006.05525 

  16. Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big, Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). The 1.7B, 360M, and 135M instruction-tuned checkpoints of this family are the teacher and student models in the course’s capacity-gap probe, and the instruction corpus the probe distills on is the family’s own. https://arxiv.org/abs/2502.02737 

  17. Linfeng Zhang, Jiebo Song, Anni Gao, Jingwei Chen, Chenglong Bao, and Kaisheng Ma, “Be Your Own Teacher: Improve the Performance of Convolutional Neural Networks via Self Distillation,” arXiv:1905.08094 (2019), ICCV 2019, for the variant in which the distillation happens between depths of a single network rather than between two networks. https://arxiv.org/abs/1905.08094 

  18. Xiaohan Xu, Ming Li, Chongyang Tao, Tao Shen, Reynold Cheng, Jinyang Li, Can Xu, Dacheng Tao, and Tianyi Zhou, “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024), for the language-model-specific reading of the classical objective. https://arxiv.org/abs/2402.13116 

  19. Rishabh Agarwal, Nino Vieillard, Yongchao Zhou, Piotr Stanczyk, Sabela Ramos, Matthieu Geist, and Olivier Bachem, “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. The generalization of the classical objective to student-generated inputs, taken up in Chapter 12. https://arxiv.org/abs/2306.13649 

  20. Yuqiao Wen, Zichao Li, Wenyu Du, and Lili Mou, “f-Divergence Minimization for Sequence-Level Knowledge Distillation,” arXiv:2307.15190 (2023), ACL 2023, for the family of objectives that replaces the forward KL of §5.3 and that Chapter 6 works through. https://arxiv.org/abs/2307.15190 

  21. Victor Sanh, Lysandre Debut, Julien Chaumond, and Thomas Wolf, “DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter,” arXiv:1910.01108 (2019). An early large-scale application of the objective in §5.3 to a transformer language model, with the soft-target term mixed against a masked-language-modeling loss. https://arxiv.org/abs/1910.01108 

  22. Adriana Romero, Nicolas Ballas, Samira Ebrahimi Kahou, Antoine Chassang, Carlo Gatta, and Yoshua Bengio, “FitNets: Hints for Thin Deep Nets,” arXiv:1412.6550 (2014), ICLR 2015. The earliest widely used response to the capacity gap, adding intermediate-layer supervision when output matching alone is not enough; Chapter 14 covers it. https://arxiv.org/abs/1412.6550 

  23. Jongwoo Ko, Sungnyun Kim, Tianyi Chen, and Se-Young Yun, “DistiLLM: Towards Streamlined Distillation for Large Language Models,” arXiv:2402.03898 (2024), ICML 2024, for the skewed divergences that address the unbounded-penalty failure mode the forward KL of §5.3 carries. https://arxiv.org/abs/2402.03898 

  24. Yuxian Gu, Li Dong, Furu Wei, and Minlie Huang, “MiniLLM: Knowledge Distillation of Large Language Models,” arXiv:2306.08543 (2023), ICLR 2024. Cited for the reverse-KL alternative to the classical objective’s direction; note that the arXiv landing page currently shows a later retitled version. https://arxiv.org/abs/2306.08543v2 

Part II · The Objective

6

Choosing a Divergence

There is a line in your training configuration that says which divergence the loss uses. It is usually one word. It is usually copied from whatever example you started from. And it changes the model you ship more than almost anything else on the same screen, including the learning rate.

Here is the situation that makes this concrete. You have a 360-million-parameter teacher and a 135-million-parameter student, a corpus, a working loop, and eight hundred training steps of budget. Chapter 3 gave you a family of ways to measure the gap between two distributions: forward KL, reverse KL, the generalized Jensen-Shannon divergence with its interpolation parameter, total variation distance, and the rest of the f-divergence family. Chapter 5 gave you the objective they plug into. Nothing so far has told you which one to pick.

The choice matters because the student cannot match the teacher. That is the premise of the whole exercise rather than a defect of your setup: if the student could represent the teacher’s function exactly, every divergence would be minimized at the same point and the choice would be empty. A student with less capacity than its teacher has to give something up, and the divergence decides what. Forward KL and reverse KL, applied to the same teacher and the same student family, produce students that differ qualitatively, in ways you can see in the text without looking at a number.

This chapter derives that difference rather than asserting it, checks what it is worth on a real model, and then spends its second half on the discipline required to answer that question honestly. The second half is not filler. The most common way to get a wrong answer about divergence choice is to run two configurations that differ in three things, on one seed, and compare their final losses, which are not comparable across objectives in the first place.

6.1 The integrand decides everything#

Start from the definition and read it one term at a time. For a teacher distribution and a student distribution over the same vocabulary of tokens, the forward KL is

where is the teacher’s probability on token and is the student’s. Chapter 3 established that this is nonnegative and zero only when the two agree everywhere. What matters now is not the total but the shape of the sum: which individual terms can get large, and which cannot.

Take a token where the teacher has real mass, , and watch the term as the student’s probability there falls. At it is zero. At it is . At it is 2.44. At it is 9.1. The term has no ceiling: as with fixed and positive, the contribution diverges. Forward KL will pay any finite price to keep the student’s probability off zero wherever the teacher’s is not.

Now take a token the teacher has ruled out entirely, . The term is , which is zero by the convention that , for every value of . The student can put as much mass as it likes on a token the teacher considers impossible and forward KL does not notice, because every term in the sum is weighted by and nothing is weighted by what the student does independently.

Those two readings are the whole behavior. Forward KL punishes the student’s zeros where the teacher is nonzero and ignores the student’s mass where the teacher is zero. A student that cannot match the teacher’s shape and is scored this way spreads out, because spreading is the only way to avoid the unbounded penalty. It keeps a little probability everywhere the teacher has any, including where the teacher’s mass is a thin tail the student has no capacity to model.

Definition

Mode covering

The behavior of an objective that penalizes a student for assigning near-zero probability where the teacher assigns real probability. A mode is a peak of a distribution, a region of concentrated probability; a mode-covering objective forces the student to place some mass on every mode the teacher has, even modes the student’s capacity cannot represent well, so a capacity-limited student spreads itself thin. Forward KL, , is the canonical mode-covering objective. Also called zero-avoiding, because it avoids student zeros.

Reverse KL swaps the arguments:

Every term is now weighted by , the student’s own probability, and the ratio inside the log is inverted. A token where the student has mass and the teacher has almost none, and , produces a term that diverges. A token where the teacher has mass and the student has none, , contributes exactly zero no matter how large is. So reverse KL punishes the student for inventing probability the teacher does not support and charges nothing for abandoning a region of the teacher’s distribution outright. A student scored this way does what the objective rewards: find a region it can represent well, put all its mass there, write off the rest.

Definition

Mode seeking

The behavior of an objective that penalizes a student for assigning probability where the teacher assigns almost none, while charging nothing for teacher mass the student ignores. A capacity-limited student under a mode-seeking objective concentrates on one or a few modes it can match and abandons the others. Reverse KL, , is the canonical mode-seeking objective. Also called zero-forcing, because the cheapest way to satisfy it in a region the student cannot model is to force the student’s probability there to zero.

Neither name describes a property of the divergence in isolation. Both describe what happens when a constrained student minimizes it. If the student family contains the teacher, both objectives are minimized by the same student, at value zero, and the distinction evaporates. The asymmetry becomes a design decision only under a capacity gap, which is the condition every distillation project operates under by construction.1 The older vocabulary for the same two behaviors, zero-avoiding and zero-forcing, comes from the statistics literature and appears in the distillation surveys interchangeably with the mode language.16

6.2 The bimodal toy, computed rather than sketched#

The picture people draw for this has a two-humped teacher and a single-humped student. Building the actual thing is worth the trouble, because the derived numbers are sharper than the picture and one of them is not what the cartoon suggests.

Fix a vocabulary of 60 tokens indexed . The teacher is a mixture of two Gaussian bumps in logit space, one centered at with weight 0.55 and one at with weight 0.45, both with standard deviation 3:

$$\ell(x) = \operatorname{logsumexp}\left[-\frac{(x-15)^2}{2\cdot 3^2} + \log 0.55,\;\; -\frac{(x-44)^2}{2\cdot 3^2} + \log 0.45\right]$$

with the teacher distribution . The student family is a single bump with two free parameters, a location and a width , giving student logits . A softmax of a quadratic is a discretized Gaussian, so the student has exactly one peak and cannot have more. Lab 01 fits this with Adam at learning rate 0.05 for 1500 steps, starting from , precisely between the two modes, so neither direction gets a head start.

The teacher’s peak probability is 0.0731, at token 15; its entropy is 3.206 nats out of a possible ; its mean is 28.05 and its standard deviation 14.74; and 0.450 of its mass sits at token 35 or above, which is the second mode plus its shoulders. Minimizing forward KL over the two student parameters lands at , , at a forward KL of 0.835 nats. Minimizing reverse KL over the same two parameters lands at , , at a reverse KL of 0.598 nats.

2026-08-01T07:27:00.544292 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 15 27 44 59 token id 0.00 0.02 0.04 0.06 0.08 0.10 0.12 0.14 probability reverse-KL student mu = 15.00, sigma = 2.998 KL(q||p) = 0.5978 nats teacher H = 3.206 nats forward-KL student mu 26.95, sigma 19.62 KL(p||q) = 0.8348 nats its peak is token 27, where the teacher has 2.45e-05 q(44) = 6e-22: the second mode is deleted
Figure 6.1 A student that cannot represent its teacher must choose, and the divergence chooses for it: the forward-KL fit spreads across both modes and peaks in the valley between them, where the teacher's probability is 2.45e-5, while the reverse-KL fit reproduces one mode exactly and puts 6e-22 on the other.

The numbers carry four things the picture does not.

The reverse-KL student reproduces one mode exactly. Its fitted width, 3.00, is the teacher’s mode width to three digits, and its location is the taller mode’s center. It has not compromised between the modes; it has copied one and deleted the other. Its probability on token 44, the center of the mode it abandoned, is about .

The forward-KL student’s peak sits where the teacher’s probability is negligible. Its mode is at token 27, where the teacher’s probability is , roughly three thousand times smaller than the teacher’s peak. The student’s single most likely token is one the teacher has effectively ruled out. This is the failure the cartoon underplays: mode covering does not produce a blurry version of the teacher, it produces a distribution whose argmax is wrong. In a language model that is the position where the model emits something neither the teacher nor the training data would have produced, because it interpolated between two valid continuations and landed on a third thing that is not one.

The forward-KL student is more uncertain than its teacher, not less. Its entropy is 4.040 nats against the teacher’s 3.206, within 0.05 of the 4.094 maximum for this vocabulary. Mode covering overshoots: a student forced to keep mass everywhere the teacher has any, using a shape it cannot match, ends up flatter than the thing it is imitating. If you are watching student entropy and expecting it to converge downward toward the teacher’s, forward KL will not do that, and the gap between the two entropies is a signal about capacity rather than about training progress.

The asymmetry of the penalties is large and it runs one way. Evaluate each fitted student under the other objective. The reverse-KL student, which abandoned a mode holding 0.450 of the teacher’s mass, scores a forward KL of 20.36 nats against the 0.835 the forward-KL student achieves. The forward-KL student scores a reverse KL of 2.548 against the reverse-KL student’s 0.598. The mode-seeking solution is catastrophic under forward KL by a factor of 24; the mode-covering solution is bad under reverse KL by a factor of 4. That gap is the unbounded integrand showing up as a number. Forward KL can express “you deleted something I care about” with no ceiling; reverse KL’s complaint about smearing is bounded by how much mass the student wasted, which is at most 1.

A closed-form check makes the forward-KL result less mysterious. For a Gaussian family, minimizing over is moment matching: the optimum carries the mean and variance of . The teacher’s mean is 28.05 and its standard deviation 14.74; the fitted student’s realized standard deviation is 14.73. The fitted parameter reads 19.6 rather than 14.7 because the support is truncated at 0 and 59, which clips the tails and makes the realized spread narrower than the parameter. Reverse KL has no such closed form, and that is itself informative: which mode it picks depends on the initialization and on the relative weights, and with three modes of similar weight it is hard to predict in advance which one survives.

6.3 What the theory predicts about a real model#

The toy is a two-parameter student fitting a 60-token distribution. A 135M-parameter transformer fitting a 360M-parameter teacher over a vocabulary of tens of thousands, at every position of every sequence, is a different object, and the honest question is how much of the toy survives. The mechanism translates as follows. Under forward KL, at every supervised position, the student pays for teacher mass it fails to cover. Summed over a corpus, that pressure keeps the student’s distributions broad, which raises its entropy, which raises the variety of tokens it will sample, which raises the variety of text it produces. Under reverse KL, the student pays only for mass it places where the teacher has little, so it is free to discard the parts of the teacher’s distribution it cannot rank correctly. That concentrates its mass on fewer tokens, which lowers entropy, which makes sampled text more repetitive and more confident.

That mechanism has four measurable consequences, and Lab 05 registers all four as predictions before running anything.

Table 6.1 The registered prediction from Lab 05, written to disk before the runs.

Metric, measured after training Forward KL () Symmetric JSD () Reverse KL ()
Mean entropy of the student’s distributions highest middle lowest
distinct-3 on sampled generations highest middle lowest
self-BLEU on sampled generations lowest middle highest
Top-1 agreement with the teacher lowest middle highest

The first three rows are the same claim measured three ways. Entropy is a property of the distribution, distinct-3 counts how many of the three-token sequences in a batch of generations are unique, and self-BLEU measures how much each generated sample’s n-grams show up in the other samples.23 More spread means higher entropy, more distinct n-grams, and less overlap between samples, so entropy and distinct-3 should move together and self-BLEU should move opposite. Logging all three is not redundancy: the two text metrics disagree at the margins, and a case where entropy and distinct-3 move together while self-BLEU does not says something is odd about the generations rather than about the distributions.

The fourth row is the one worth staring at, because it looks like it contradicts the others. Top-1 agreement counts the fraction of positions where the student’s most likely token equals the teacher’s most likely token. Reverse KL is predicted to win that metric, and the reason is the same abandonment that costs it diversity. A student that gives up on ranking the rare tokens correctly has more capacity left for the common ones, and top-1 agreement is measured entirely on the common ones, since the argmax is by definition the most common outcome. Reverse KL wins agreement because of its tail-dropping, not in spite of it. That is a prediction worth registering in advance precisely because it is counterintuitive enough that you would talk yourself into either direction after the fact. It also comes with a standing caveat: agreement with the teacher is a weak proxy for a good student, and students that generalize better while agreeing with their teacher less are a documented and reproducible phenomenon.19

An ordering on its own is not falsifiable in the way you need, which is why Lab 05’s Part C registers magnitudes alongside it. On this model pair the expected entropy gap between the and arms is 0.1 to 0.5 nats, the distinct-3 gap is 0.02 to 0.10, and the agreement gap is 1 to 4 points in reverse KL’s favour. Those bands are what let you tell a healthy run from a broken one: measure a 0.02-nat entropy gap and the orderings may still all be in the predicted direction while the run has told you nothing, because the arms never separated. The entropy and self-BLEU orderings should hold in nearly all healthy runs. The agreement ordering is the fragile one, and it is the one to expect to lose.

The fifth thing people expect the divergence to change is generation length, and I want to be careful about it. The mechanism is plausible: a mode-seeking student concentrated on the highest-probability continuations should reach a high-probability end-of-sequence token sooner and produce shorter output, while a mode-covering student rambles. Lab 05 does not measure length, and the literature on length collapse in distilled models, as opposed to reinforcement-learning-trained ones, is thin enough that I am not going to cite a paper about entropy collapse and imply it is about length.4 Treat length as a plausible consequence this course has not measured and that you should measure on your own runs if you care about it.

The status of Table 6.1 needs saying plainly: it is a prediction. It has a mechanism behind it and a toy problem in which the mechanism visibly operates, which is a better position than a hunch, and it is still a prediction. Section 6.9 reports a case from this same lab where a prediction of exactly this quality turned out to be wrong about a real measurement, and the diagnosis was more useful than a confirmation would have been.

6.4 The interpolation parameter, and the trap inside it#

You do not have to choose an endpoint. The generalized Jensen-Shannon divergence gives you a one-parameter family that contains both, defined through a mixture distribution:

$$\mathrm{JSD}_\beta(p \,|\, q) = \beta\, \mathrm{KL}(p \,|\, m) + (1-\beta)\, \mathrm{KL}(q \,|\, m), \qquad m = \beta p + (1-\beta) q$$

with the teacher, the student, and the mixing parameter. At this is the ordinary Jensen-Shannon divergence, symmetric and bounded above by nats. Chapter 3 covered where the bound comes from and why the square root of the symmetric case is a metric while the divergence itself is not.5

The parameter is the design knob that Agarwal and colleagues expose in generalized knowledge distillation, alongside a second parameter controlling how much of the training data comes from the student’s own rollouts.6 Chapter 12 handles the second parameter. This chapter is about what actually does, which is not what its position in suggests.

6.4.1 The limits, and what they do not say#

The endpoints are limits, not values. At exactly the mixture equals , so the first term is multiplied by zero and the second term is : the whole expression is identically zero, with an identically zero gradient. Symmetrically at . The family recovers the two KL directions only in the limit, and only after rescaling:

$$\lim_{\beta \to 0} \frac{\mathrm{JSD}\beta(p | q)}{\beta} = \mathrm{KL}(p | q), \qquad \lim(q | p)$$} \frac{\mathrm{JSD}_\beta(p | q)}{1 - \beta} = \mathrm{KL

Take a concrete pair of logit vectors over four tokens, teacher and student . These give a forward KL of 1.8412 nats and a reverse KL of 2.3256 nats. Evaluating the generalized JSD on the same pair:

Table 6.2 The generalized JSD and its two rescalings on a fixed four-token logit pair. Forward KL is 1.8412; reverse KL is 2.3256.

0.001 0.001838 1.8379 0.0018
0.01 0.018088 1.8088 0.0183
0.1 0.156017 1.5602 0.1734
0.25 0.310710 1.2428 0.4143
0.5 0.412470 0.8249 0.8249
0.75 0.334102 0.4455 1.3364
0.9 0.179523 0.1995 1.7952
0.99 0.022527 0.0228 2.2527
0.999 0.002318 0.0023 2.3180

2026-08-01T07:27:01.646091 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.00 0.25 0.50 0.75 1.00 β 0.0 0.5 1.0 1.5 2.0 2.5 nats forward KL(teacher||student) = 1.8412 reverse KL(student||teacher) = 2.3256 J S D / β β J S D / ( 1 ) β β r a w   J S D β -> 0 at both ends beta = 0.1 is 85% of the way to the forward KL (1.56 of 1.84), at a tenth of the magnitude
Figure 6.2 The generalized JSD recovers the two KL directions only as rescaled limits: JSD_beta divided by beta approaches the forward KL as beta goes to zero, and JSD_beta divided by (1-beta) approaches the reverse KL as beta goes to one, while the raw divergence collapses to zero at both ends.

Read the second column on its own and the objective looks like it barely exists near the endpoints: 0.0018 nats at , which is a thousandth of anything. Read the third column and the shape appears. At the objective is the forward KL divided by a thousand: 1.8379 against the true 1.8412, agreeing to within 0.2 percent. At the reverse rescaling gives 2.3180 against the true 2.3256, within 0.3 percent.

6.4.2 Why is not “ten percent of the way”#

This is the practically load-bearing consequence, and it is stated wrong in a lot of casual advice. A setting of is not an objective that is one tenth reverse KL and nine tenths forward KL in any behavioral sense. It is an almost-pure forward-KL objective at roughly one tenth the magnitude. The rescaled column shows it: , which is 85 percent of the way to the forward KL’s 1.84, while the raw value 0.156 is a tenth the size of anything you would compare it against.

Lab 05’s solutions push this further by measuring gradient directions rather than values, on the grounds that training follows gradients and not losses. On peaked, mismatched synthetic logits at 3-sigma scale, the cosine similarity between the gradient and the pure forward-KL gradient comes out around 0.91, and its rescaled value recovers the forward KL to within about 13 percent. At the gradient’s cosine against forward is 0.57 versus 0.44 against reverse, measured against a baseline forward-to-reverse cosine of 0.29, so the quarter point leans toward its nearer endpoint rather than pointing somewhere new. Nowhere in the sweep does a genuinely new gradient direction appear. What does is slide the gradient along a one-parameter path between two fixed directions while rescaling its length.

6.4.3 The rescaling is a confound, and it is larger than you expect#

That rescaling deserves its own figure, because it means a sweep at a fixed learning rate is also a learning-rate sweep, and the two effects are not separable by inspection.

On the same four-token pair, the gradient norm with respect to the student’s logits is 0.918 for the pure forward KL and 0.578 for the pure reverse KL. For the generalized JSD it is 0.00091 at , rises to a maximum of 0.126 at , and falls back to 0.00058 at .

2026-08-01T07:27:02.832032 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.00 0.25 0.50 0.75 1.00 β 0.0 0.2 0.4 0.6 0.8 1.0 z 2       ( s t u d e n t   l o g i t s ) pure forward KL = 0.918 pure reverse KL = 0.578 maximum 0.126 at beta = 0.43 which is 0.137 of the forward-KL norm a beta sweep at fixed learning rate is also a step-size sweep, and it is not monotone in beta -> 0 -> 0
Figure 6.3 Moving beta changes the effective step size as well as the objective's shape: the generalized JSD's gradient norm vanishes at both endpoints, peaks near beta = 0.43, and never exceeds about one seventh of the pure forward KL's gradient norm on the same inputs.

Read that two ways. First, the interior of the family trains at a smaller gradient scale than either endpoint, by a factor of about seven at the peak and by three orders of magnitude near the ends. Second, and worse for interpretation, the scale is not monotone in : it rises then falls. Run at a fixed learning rate and fixed step count and the middle arms take systematically smaller optimization steps, so any metric still moving at the end of training shows them closer to their initialization. That looks exactly like “the middle is intermediate in behavior” when it is “the middle trained less.”

The prediction this analysis makes for Lab 05’s first exercise, which adds and , is falsifiable: the entropy trend should be monotone in its ordering but far from linear in its spacing, with 0.25 close to 0 and 0.75 close to 1 and a visible step between them. A smooth linear staircase across all five points would refute the gradient analysis, and the scale confound is the first thing to suspect in that case.

If you want to move and attribute what you see to the objective’s shape, normalize the scale out: divide the loss by near zero and by near one, or tune the learning rate per arm to hold the gradient norm fixed, and say in your write-up which one you did. Both are defensible. Silently doing neither is not.

The implementations add two wrinkles. TRL’s trainers special-case and to the exact KL objectives rather than evaluating the degenerate formula, so the endpoint rows of a sweep are exact rather than limits. And when you want to probe near the endpoints yourself, use 0.001 and 0.999 as stand-ins, because the exact endpoints return identically zero with no gradient.

6.4.4 The convention runs backwards in half of what you will read#

Here is the part that has cost more people more time than the mathematics.

Field note

The gjsd docstring in this course’s own kd_core said it backwards, and it said so for about a week before I caught it.

I wrote the function first and the docstring from memory afterward, and my memory had absorbed the labeling used in several widely read explanations of the generalized knowledge distillation paper, which describe the small- end as the reverse-KL end. That is the opposite of what the implementation did, because the implementation follows TRL, and TRL’s DistillationConfig documentation states that approximates the forward KL and approximates the reverse KL. I found the disagreement by reading the current library documentation rather than by noticing anything wrong in a run, which is the uncomfortable part: nothing in a run would have told me.

I did not settle it by deciding which document to trust. I settled it by evaluating four numbers on a four-token logit pair small enough to check by hand. The true forward KL was 1.8412 and the true reverse KL was 2.3256. Then gjsd at , divided by 0.001, gave 1.8379, and gjsd at , divided by 0.001, gave 2.3180. The small- end lands on the forward KL and the large- end lands on the reverse KL. Four numbers, about two seconds of compute, no ambiguity left.

What makes this the anecdote I keep telling is the failure mode it would have produced. An inverted raises no exception. It does not produce NaNs. The loss curve descends normally, because a wrong objective is still an objective and gradient descent will happily minimize it. You get a trained model, a clean-looking run, and a student that is mode-seeking when you asked for mode-covering. The only way to catch it is to check the direction against numbers you computed yourself, which takes less time than reading the argument about it.

So: re-verify ’s direction every single time you inherit a configuration from a paper, a blog post, a colleague, or your own repository from six months ago.

The check is short enough to write from scratch every time, which is the point of writing it from scratch every time.

import torch, torch.nn.functional as F

def kl(a_logits, b_logits):           # KL(a || b), both [..., V] logits
    la, lb = F.log_softmax(a_logits, -1), F.log_softmax(b_logits, -1)
    return (la.exp() * (la - lb)).sum(-1)

def gjsd(teacher, student, beta):     # the mixture is formed in probability space
    m = beta * F.softmax(teacher, -1) + (1 - beta) * F.softmax(student, -1)
    lm = m.clamp_min(1e-20).log()
    lt, ls = F.log_softmax(teacher, -1), F.log_softmax(student, -1)
    return (beta * (lt.exp() * (lt - lm)).sum(-1)
            + (1 - beta) * (ls.exp() * (ls - lm)).sum(-1))

t = torch.tensor([3.0, 0.0, -1.0, 2.0])
s = torch.tensor([0.0, 2.0, 1.0, -1.0])
print(f"forward KL(t||s) = {kl(t, s):.4f}   reverse KL(s||t) = {kl(s, t):.4f}")
print(f"gjsd(b=1e-3)/1e-3 = {gjsd(t, s, 1e-3) / 1e-3:.4f}")
print(f"gjsd(b=1-1e-3)/1e-3 = {gjsd(t, s, 1 - 1e-3) / 1e-3:.4f}")

The two printed ratios are the ones that settle the question: the first should land on the forward KL and the second on the reverse KL, and if your library’s beta is defined the other way around they will land on each other’s targets and you will know before you spend a training run on it.

Watch out

There is a second, independent inversion in this course’s API, and it catches people who have already learned the first one. kl_divergence(student_logits, teacher_logits, mask, direction="forward") takes the student as its first argument, but direction="forward" computes . The argument order follows the convention that the thing you are training comes first; the direction name follows the convention that “forward” means teacher-first. Reading the argument order as the KL order gives you exactly the wrong divergence, silently. gjsd(student_logits, teacher_logits, mask, beta=...) has the same argument order, with = teacher and = student inside.

6.5 Skew KL: interpolating inside the log#

The generalized JSD is not the only way to build a family between the two directions, and it has the drawback established in §6.4.3: the interpolation parameter rescales the objective. There is a second construction that does not.

Definition

Skew KL

A divergence that softens one distribution toward the other inside the logarithm, rather than averaging two divergences outside it. For a skew parameter , the skew KL is and the skew reverse KL is . Both are bounded above by , and both converge to the corresponding plain KL as .

The bound is worth deriving because it is one line and it explains the whole appeal. Write . Since , pointwise, so for every where , so

$$\mathrm{KL}(p | m) = \sum_i p_i \log \frac{p_i}{m_i} \le \sum_i p_i \log \frac{1}{\alpha} = -\log \alpha$$

No matter how badly the student misses, the objective cannot exceed nats. At that is 0.693, the same ceiling as the symmetric JSD. At it is 2.303.

Compare that to what generalized JSD does. Both constructions produce a bounded objective and both have a knob, but the knobs do different things. In the generalized JSD, pushing toward an endpoint recovers a KL direction by shrinking the entire objective toward zero, so the parameter controls shape and scale together. In skew KL, controls only how much mass the reference distribution borrows from the target, which caps the worst-case per-position contribution without touching the scale of the well-behaved positions. A skew KL at is a forward KL with its infinities clipped at 2.3 nats, and everywhere the student is doing acceptably it is numerically almost the plain forward KL.

Run the catastrophic position from Lab 01 through both. Take a teacher over 8 tokens that is nearly certain about token 3 (probability 0.99996) and a student that assigns that token a probability near . The plain forward KL there is 31.94 nats, essentially all of it from the single term . The skew KL at on the same position is 2.3021 against its bound of 2.3026: the position has saturated the ceiling and stopped. The skew reverse KL at the same gives 2.3022.

This is the line DistiLLM builds on. Ko and colleagues introduce the skew KL and skew reverse KL as distillation objectives, argue that the skewing improves both optimization behavior and the generalization of the resulting student relative to the plain KL directions, and pair it with an adaptive scheme for how much student-generated data enters training.7 A follow-up extends the idea with a contrastive formulation.8 The claim this chapter can support with its own arithmetic is narrower than theirs: the skew construction gets you the boundedness that makes a training loop survivable without paying the scale distortion that makes a sweep hard to interpret.

6.6 The divergence as a design parameter#

Two papers are worth knowing about here, because between them they establish the framing this chapter has been assuming.

Wen, Li, Du, and Mou state the general version: essentially all of the standard sequence-level distillation objectives are instances of f-divergence minimization, and once you see them that way, the specific divergence stops being part of the method and becomes a hyperparameter of it.9 Chapter 3 built the f-divergence family from generator functions, so the machinery is in place: pick a convex with , and you get a divergence.10 Their argument is that the field defaulted to forward KL for convenience rather than suitability, following the shape of the original sequence-level formulation,20 that a language model teacher has many modes and a smaller student cannot cover them, and that objectives with mode-seeking or symmetric character therefore fit the situation better. They report total variation distance as a strong practical choice for sequence-level distillation, which is useful partly because TVD is neither KL direction and has no mode-seeking or mode-covering lean at all.

Gu, Dong, Wei, and Huang make the specific version in MiniLLM, and their argument is about generation rather than about scoring.11 Their claim: for a generative model, the forward KL’s insistence that the student cover every region of the teacher’s distribution is actively harmful, because it forces the student to put probability on continuations it cannot produce coherently, and at sampling time those become the outputs a user sees. They minimize the reverse KL instead, so the student concentrates on the teacher’s major modes and avoids overestimating the regions the teacher considers void.

The interesting part is what that costs to implement. Reverse KL over sequences is an expectation under the student’s own distribution, which means you cannot evaluate it by scoring a fixed corpus; you have to sample from the student and differentiate through the sampling. That makes the gradient a policy gradient, with the structure familiar from reinforcement learning: a reward-shaped term multiplying the gradient of the sequence log-probability. Policy gradients of that form are high variance, because a single scalar computed at the end of a long sequence gets attributed to every decision in it. MiniLLM’s contribution on this axis is a set of variance reductions: decomposing the objective so that credit is assigned step by step rather than in one lump at the end, mixing teacher-generated text into the sampling distribution so that early training does not learn from degenerate rollouts, and normalizing for sequence length so that long generations do not dominate the update. Chapter 12 covers the on-policy machinery properly, and the surveys of that subarea treat divergence choice and sampling policy as a single joint design decision for exactly this reason.21 What matters here is the shape of the claim: choosing reverse KL for a generative student is a change of training regime rather than a change of one line, and most of the paper is about paying for it.12

There is a cheaper version of the same instinct worth naming because you will see it in practice: apply reverse KL token-wise on a teacher-forced corpus, which is what setting in a standard loop does and what Lab 05 measures. That is not the same objective as MiniLLM’s sequence-level reverse KL and does not carry the same guarantees. It does exhibit the same mode-seeking character position by position, which is enough to change the student’s behavior measurably, and it costs nothing beyond the loss function.

6.7 Bounded divergences as a safety valve#

The boundedness argument sounds like a footnote about numerical hygiene. It is closer to a requirement for finishing a run.

Take the position from §6.5 again: a teacher that is nearly certain about one token out of eight, and a student that assigns that token a probability near . Under forward KL that position contributes 31.94 nats. Under the symmetric JSD it contributes 0.6929, within 0.0003 of the ceiling. Under total variation distance it contributes exactly 1.0000, the TVD maximum. The student’s failure is identical in all three cases and the objectives disagree by a factor of 46 about how much to care, which is not an accident of this example: Pinsker’s inequality bounds TVD by the square root of the KL, so TVD can sit at its ceiling while the KL grows without limit underneath it.23

Now push the student’s probability further down, which happens constantly in real training, since there is no lower bound on how confident a model can be that a token is wrong. Once the student’s log-probability there falls below roughly , its probability underflows to zero in fp32’s smallest normal range, and a loss that takes the log of a probability rather than working in log space produces . Chapter 2 covered why log_softmax rather than log(softmax(...)) is the difference between a run and a crater. Working in log space only postpones the problem: the term is then large and finite, and “large” is unbounded above.

The difference between “infinite” and “large” is categorical rather than a matter of degree. An infinite loss produces infinite or NaN gradients, those enter every parameter through the optimizer’s moment estimates on the next step, and Adam’s running averages mean the NaN does not wash out. The run is dead, every step of compute after that point is wasted, and the loss curve is a flat line at NaN.

A large finite loss does something much less bad. Suppose one catastrophic position appears in a batch of 8 sequences at 384 positions each, roughly 3,072 supervised positions. Its 31.94 nats enters the masked mean as nats, so the batch loss is distorted by one percent of a nat. The gradient contribution from that position is large, but a loop that clips the global gradient norm, which Lab 05’s does at 1.0, converts a magnitude problem into a direction problem: the step is still taken, at the intended size, in a direction one position has skewed. One bad step out of eight hundred is survivable in a way that a NaN is not.

Under a bounded divergence neither scenario arises. No single position can contribute more than under JSD, or more than 1 under TVD, or more than under skew KL, so the worst possible position is worth about as much as an ordinarily mediocre one and the batch mean stays in a narrow range. The failure signature is easy to misdiagnose, so it is worth committing to memory: a KD run whose loss spikes by orders of magnitude and never recovers is almost always one position where the student assigned near-zero probability to something the teacher was confident about. Lowering the learning rate will not fix it, because the input to the optimizer was already broken before the step size was applied.

The cost of the safety valve is real. A bounded objective cannot express “this is catastrophically wrong,” so it also cannot prioritize fixing it. That is the saturation problem, and it shows up most sharply in TVD. Lab 05’s solutions measure it directly: on a moderately mismatched pair of distributions the TVD gradient norm is ; on a near-disjoint pair, where the value has pinned near its ceiling, the gradient norm is . That is a fall of almost four orders of magnitude, and it goes the wrong way. TVD pushes hardest where student and teacher already mostly agree and goes quiet exactly where the student is worst; forward KL’s temperament is the reverse. Neither is right. They are different bets about where your training budget should go.

TVD has one more property worth checking before you spend a run on it, and it is the concrete instance of the habit this chapter keeps recommending. Its summand contains an absolute value, so has a kink at and the objective is not differentiable there. In practice autograd picks a subgradient at the kink, which is a defensible convention and not the same thing as a derivative, and every position where teacher and student agree exactly sits on it. Nothing about that is fatal. It is a claim about what your framework does at a measure-zero set that you can check in three lines by evaluating the gradient at an exactly matched pair, and checking it is cheaper than assuming it.

6.8 Ablation discipline#

Everything above is theory with a toy problem attached. Turning it into a claim about a real model pair requires an experiment, and the experiment is where most of the wrong answers in this subfield come from. The characteristic error is not a bad hypothesis. It is a comparison that moved two variables, ran one seed, and reported the loss.

Definition

Ablation

An experiment that isolates the effect of one factor by changing that factor while holding everything else fixed: same model pair, same data, same number of steps, same learning rate, same seed, same evaluation. The word comes from the practice of removing a component to see what breaks without it, and it has widened to mean any single-variable comparison.

Lab 05’s structure is four habits. Each of them exists because of a specific way people get this wrong.

6.8.1 One moving variable, asserted rather than eyeballed#

The run matrix for the divergence question is three values of crossed with two seeds, six runs. Every other setting is shared: the same 360M teacher and 135M student, the same corpus, a sequence length of 384, temperature 1.0, learning rate 3e-5, batch size 8 with gradient accumulation 4, 800 steps with 40 warmup steps.13

The habit is not “keep everything else fixed.” It is “prove mechanically that everything else is fixed.” Configurations are dictionaries, and dictionaries can be compared.

def diff_keys(a, b):
    """Every key on which two run configs disagree."""
    return {k for k in a.keys() | b.keys() if a.get(k) != b.get(k)}

BASE = dict(teacher="...-360M-Instruct", student="...-135M-Instruct",
            data="lab03", seq_len=384, T=1.0, lr=3e-5,
            batch_size=8, grad_accum=4, max_steps=800, warmup_steps=40)

MATRIX = [{**BASE, "beta": b, "seed": s}
          for b in (0.0, 0.5, 1.0) for s in (17, 18)]

ALLOWED = {"beta", "seed"}
for i, a in enumerate(MATRIX):
    for b in MATRIX[i + 1:]:
        extra = diff_keys(a, b) - ALLOWED
        assert not extra, f"matrix moves more than beta and seed: {sorted(extra)}"

What that assertion buys you is protection against the edit you will make at 11pm three weeks later, when you add an arm and change the learning rate for it because that arm was training slowly. The assertion fires. Without it, you would have shipped a comparison between a divergence and a learning rate, and the write-up would have attributed the whole effect to the divergence.

The same check extends to the exercises. When Lab 05’s solutions add a temperature sweep, the allowed set becomes {"T", "beta", "seed"} and the assertion is rerun, which documents in executable form that a second variable was introduced deliberately.

6.8.2 Registering the prediction before the run#

Definition

Pre-registration

Writing down what you expect an experiment to show, in specific enough terms to be graded, and committing it to storage before the experiment runs. Borrowed from clinical and psychological research, where it exists to prevent a hypothesis from being adjusted after the data arrives. In a notebook it means serializing the predictions to a file with a timestamp, so that hindsight cannot quietly rewrite what you expected.

Table 6.1 is the prediction. Lab 05 writes it to predictions.json with a registered_unix_time field and a grading rule, in a cell that runs before any model is loaded, and grades against the file afterward.

The mechanism matters more than the ceremony. Human memory of a prediction is accommodating: shown a table where reverse KL has the highest entropy, most people who predicted the opposite will produce a mechanism for the observed direction within thirty seconds and sincerely believe they expected it. A file with a timestamp removes the option. Lab 05’s notebook prints a line saying that editing the file after running is self-deception, which is there because the temptation is real and naming it out loud helps.

The grading rule goes into the file alongside the predictions, and it is the part people forget. A prediction of “entropy is higher at ” is not gradeable until you have said how much higher counts. Lab 05’s registered rule is that a effect counts only if the gap between arms exceeds the largest seed-to-seed spread within any arm. Registering the decision rule with the prediction is what stops you from choosing the threshold that gives the answer you like. Chapter 18 takes this much further, into hypotheses, stopping rules, minimum detectable effect, and manifests; the small version here is enough to change the outcome of most comparisons.

6.8.3 Seeds, and the noise floor#

Definition

Seed variance

The spread in a measured outcome between training runs that differ only in their random seed. It is the experiment’s noise floor: it sets the smallest difference between arms that can be distinguished from chance, and any reported effect smaller than it is not a finding.

Two seeds is a genuine minimum rather than a comfortable one. Two seeds per arm let you estimate a spread, which is enough to disqualify effects that sit plainly inside the noise and nowhere near enough to put a confidence interval on the effects that survive. If a comparison you care about lands close to the noise floor, the honest report is “indistinguishable at n=2,” and the fix is more seeds rather than a better narrative.

Applying the rule mechanically to Lab 05’s matrix: average the two seeds within each , take the differences between arms, and compare each difference against the largest within-arm seed-to-seed gap across all three arms. A difference that does not clear that bar goes in the write-up as “within noise,” with the number that decided it. Lab 05 asks for the verdict as three lines, one per prediction group, each reading held, violated, or within noise, each carrying its number.

Learn to recognize three failure signatures before you produce them.

All six runs nearly identical on every metric. The arms never left the initialization’s basin, because 800 steps at 3e-5 was not enough to differentiate the objectives on this pair. The conclusion “the divergence does not matter” is unsupported; double the step count and rerun before drawing it. Distillation is unusually sensitive to how long you are willing to train, and comparisons run at short budgets routinely reverse when the budget grows.22

The arm’s entropy collapses toward zero and its generations degenerate into loops. That is entropy collapse arriving early, a distinct failure with its own literature and its own monitors, and reverse KL is not doing its job when it happens.14 Note the step at which it happened, because that number is where you start calibrating the abort criterion in Chapter 12.

The predicted orderings hold within each seed but not across seeds. The effect is smaller than the noise. This is the case that most often gets written up as a finding anyway, by reporting the seed that agreed.

6.8.4 Measure the generations, not the losses#

The final loss is the least informative quantity you can compare across divergence arms, and it is the quantity most comparisons report. Each arm minimized a different function, so their losses are values of different functions and there is no exchange rate between them. Section 6.4’s four-token pair makes this concrete. One fixed teacher, one fixed student, three numbers describing the exact same disagreement: forward KL 1.8412, symmetric JSD 0.4125, reverse KL 2.3256. If those were three runs’ final losses you would rank JSD best by a wide margin and reverse KL worst, and you would be reporting the ceiling of each objective rather than the quality of any student. JSD cannot exceed 0.6931 by construction, so its loss being smaller than a KL’s carries no information at all.

The comparison therefore has to happen on quantities defined independently of the training objective. Lab 05 uses four, split by what they measure. Two are distribution metrics computed on a held-out batch under teacher forcing so that every arm is scored at the same positions on the same text: mean entropy of the student’s next-token distributions, and top-1 agreement with the teacher. Two are text metrics computed on generations: distinct-3 and self-BLEU over 32 sampled completions of 96 new tokens each.

The sampling settings are load-bearing and easy to get wrong. Lab 05 samples at temperature 1.0 with no nucleus truncation, because this whole experiment is about the shape of the student’s distribution. Greedy decoding takes the argmax at every step, which throws away everything except the ranking of the top token, so a high-entropy student and a low-entropy student that agree about which token is most likely produce identical greedy output. Greedy-decoding this evaluation would have erased the entire effect the lab exists to measure, silently, and produced a clean-looking table of null results. The general rule: evaluate at the temperature the objective trained at, and remember that any truncation applied at decode time removes exactly the tail behavior that distinguishes the divergences, which is why the choice of decoding strategy is a research variable in its own right rather than a formatting detail.17

6.8.5 The metric audit#

Definition

Metric audit

The practice of feeding every metric function a case whose correct answer you can compute by hand, and asserting the result, before using that function to report a number. A metric with a bug does not raise an exception; it returns a plausible number and quietly ranks your runs wrong.

This habit costs about ten minutes once and has caught more mistakes for me than any other item in this chapter. Every metric in the Lab 05 table gets an input with a known answer.

# Two identical samples. Bigrams of "the cat sat on the mat":
#   (the,cat) (cat,sat) (sat,on) (on,the) (the,mat)  -> 5 bigrams, 5 unique
# Two copies: 10 bigrams total, still 5 unique -> distinct-2 = 5/10 = 0.5
same = ["the cat sat on the mat", "the cat sat on the mat"]
diff = ["alpha beta gamma delta", "epsilon zeta eta theta"]

assert distinct_n(same, n=2) == 0.5
assert distinct_n(diff, n=2) == 1.0     # 6 bigrams, all unique
assert self_bleu(same, n=2) == 1.0      # maximal overlap between samples
assert self_bleu(diff, n=2) == 0.0      # no shared n-grams at all

# A uniform distribution over V tokens has entropy exactly ln V nats.
V = 257
uniform_logits = torch.zeros(1, 1, V)
H = mean_entropy(uniform_logits, torch.ones(1, 1, dtype=torch.bool))
assert abs(H - math.log(V)) < 1e-5      # ln 257 = 5.5491

Work through the first assertion by hand, because it is the one that exposes what the metric actually counts. The sentence “the cat sat on the mat” has six tokens and therefore five bigrams: (the, cat), (cat, sat), (sat, on), (on, the), (the, mat). All five are distinct within the sentence, even though the token the appears twice, because a bigram is a pair. Pool two identical copies and you have ten bigrams and five unique ones, so distinct-2 is 0.5 exactly. An implementation that computes distinct-n per sample and then averages returns 1.0 here, because within each sample all bigrams are unique. That implementation reads fine, reports every model as maximally diverse, and you would notice only when two arms generating visibly different text scored identically.

The entropy check is the cleanest of the four because the answer is a closed form: a uniform distribution over outcomes has entropy nats, so feeding the function a vector of zero logits over 257 tokens must return . That single assertion catches a log base error, a missing negation, an off-by-one in the masking, and a normalization that ran over the wrong axis. The reason for 257 rather than 256: a power of two would still pass if the function computed and something else compensated, so an awkward number removes a coincidence.

6.9 The measurement that refuted its own prediction#

Lab 05’s third exercise asks for the sharpest available probe of mode seeking. Mean entropy summarizes a whole distribution and therefore mixes the tokens where the two objectives disagree with the many tokens where they do not. A better statistic: at each position, take the token the teacher ranks second, and record the probability the student assigns to it. The teacher’s rank-1 token is the mode that mode seeking keeps, so nothing interesting happens there; the rank-2 token is the first token a mode-seeking student is allowed to abandon and the last one a mode-covering student is allowed to drop. If the two objectives differ anywhere, they differ there.

Running that measurement on the real pair, with the untrained 135M student against the 360M teacher in fp32, over 16 sequences and 192 positions each, gives the baseline both trained arms start from. Over more than 800 supervised positions:

Table 6.3 Student and teacher probability on the teacher’s rank-2 token, untrained 135M student against the 360M teacher, measured in Lab 05’s solutions.

Statistic Teacher’s own p(rank-2) Untrained student’s p(rank-2)
Mean 0.105 0.114
Median 0.052 0.039

The expected story going in was that the small model starts below the teacher on near-miss tokens, since near-miss tokens are exactly what a smaller model should be worst at. The measured mean says the opposite: the student’s mean is 0.114 against the teacher’s 0.105, a ratio of 1.09, which is to say the untrained student sits slightly above the teacher on this statistic. The prediction failed.

The diagnosis is the useful part, and it is about the shape of the distribution rather than about models. The medians run the other way, 0.039 for the student against 0.052 for the teacher, and 36 percent of positions have the student giving the teacher’s rank-2 token less than 1 percent. Both distributions are heavily right-skewed and the student’s more so, with its mean propped up by a minority of positions where it piles a great deal of mass on that particular token, sometimes because it disagrees with the teacher about rank 1 entirely, so the teacher’s second choice is the student’s first.

2026-08-01T07:27:04.042934 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ mean median 0.00 0.04 0.08 0.12 0.16 probability on the teacher's rank-2 token 0.105 0.114 0.052 0.039 teacher (360M) untrained student (135M) student higher (ratio 1.09) student lower (ratio 0.75) 36% of positions give the teacher's rank-2 token under 1% student probability
Figure 6.4 The mean and the median disagree about which model puts more probability on the teacher's second-ranked token, which is what a heavily right-skewed distribution looks like: the untrained student's mean sits above the teacher's while its median sits below, and 36 percent of positions already give that token under one percent.

So the honest summary is not “the small model starts below the teacher on near-miss tokens.” It is “the small model starts below the teacher at typical positions and far above it at a minority of positions where it is confused about the ranking, and the mean reports the second fact while the median reports the first.”

There are three lessons in that, in increasing order of generality.

A mean hides exactly the shape that mode seeking is going to change. Mode seeking moves mass out of the middle of the distribution and into the peak, which changes the shape of the per-position histogram far more than the average. Monitor only means and you will see a small effect where a large one exists. Report the median and the quartiles, and the fraction below a threshold you fixed in advance.

A failed prediction with a clean diagnosis beats a prediction that held. What came out of this is a fact about the measurement instrument that a confirmation would not have surfaced.

And the failure signature to carry forward: when the trained arms are compared, if the arm’s rank-2 probability rises while its entropy falls, the mass it reclaimed came from deeper in the tail than rank 2, which happens with very peaked teachers. Widen the measurement to ranks 3 through 10 before concluding the theory failed. The expected shape, still unmeasured on this build, is that the arm’s median climbs toward the teacher’s and its under-1-percent fraction falls, while the arm’s median drifts down and its under-1-percent fraction grows past its starting 36 percent.

6.10 Temperature and divergence do not separate#

You have two knobs and they are not orthogonal, so a grid over both is not a pair of independent sweeps.

Start with the case where they are independent, because it clarifies the mechanism. For an unconstrained student, meaning one whose family contains the teacher’s distribution, temperature does nothing: every divergence in this chapter is minimized at “student logits equal teacher logits,” and softening both sides by the same before the softmax does not move that minimizer.

The interaction exists entirely because a real student cannot win and must triage. Lab 05’s solutions build the smallest honest model of triage: a teacher over 512 tokens with 2.5-sigma logits, and a student that can represent only the teacher’s top 32 tokens, matching the teacher exactly on those and sitting at a floor of everywhere else. That is a caricature of a small model with enough capacity for the common tokens and none left for the rare ones.

Measured on that fixed pair, going from to raises the teacher’s entropy by more than a nat and multiplies the teacher’s probability mass outside the student’s 32-token support by roughly three. Both divergences notice and they price it differently: the forward KL rises by about twice as many nats as the reverse KL does.

That asymmetry is the entire interaction and it follows from §6.1. Forward KL pays for every teacher token the student fails to cover, so tripling the uncoverable mass roughly triples the pressure to smear. Reverse KL charges the student only where the student itself puts mass, and the student’s support sits inside the teacher’s high-probability region at every temperature, so softening the teacher barely touches it.

The consequence for a two-dimensional sweep: raising widens the gap between the mode-covering arm and the mode-seeking arm on the metrics driven by covering. The entropy gap should stretch beyond its range and distinct-3 with it, and self-BLEU should widen in mirror image. The agreement gap is the one not expected to widen cleanly, because agreement is computed on argmax tokens, softening barely reorders the top of a distribution, and the extra smearing costs the forward arm little on top-1 specifically.

There is a confound to rule out first, of the same species as §6.4.3. The gjsd implementation carries no compensation, while kl_divergence applies one by default. Chapter 5 derived why that factor exists: the softened loss’s gradient scales as , so multiplying by keeps the gradient magnitude comparable across temperatures and keeps the mixing coefficient against a hard-label term meaning what it meant.15 Without it, a generalized-JSD arm at trains at roughly a quarter of the gradient scale of the same arm at . If every metric widens by a similar factor when you raise the temperature, that is the scale change and not the tail story. Check the raw loss magnitudes at both temperatures before crediting the geometry, and if you want the geometry cleanly, restore the factor or scale the learning rate to compensate.

6.11 A decision guide#

The choice is a product decision informed by measurement, not a mathematical one with a correct answer. Here is how I would reason about it, given what you want out of the student.

Table 6.4 Which divergence, given what you want from the student.

What you want Choose Why What it costs
Broad, diverse generation; a student you will sample from at temperature Forward KL, Covers the teacher’s modes, keeps the tail alive, keeps entropy high Hedged outputs, an argmax that can land between two valid continuations, unbounded loss spikes
A student you will run reinforcement learning on afterward Forward KL RL explores by sampling; a mode-collapsed initialization has nothing to explore Same as above, plus a weaker starting point on top-1 metrics
Crisp behavior on a narrow task; a support assistant, a structured-output model Reverse KL, Concentrates capacity on what the student can actually match; best top-1 agreement Diversity you will not notice is gone; real risk of entropy collapse; unbounded loss spikes
Calibrated probabilities, where the whole distribution is the product Forward KL Calibration is a statement about the full distribution, which mode seeking discards The student’s entropy may exceed the teacher’s rather than matching it
A default when the student is far too small to cover the teacher Symmetric JSD, Bounded on both sides, pushes moderately in both directions Trains at a smaller gradient scale than either endpoint; the loss is not comparable to a KL’s
Survivability of a long unattended run above all else Symmetric JSD or skew KL at moderate No single position can dominate a batch Cannot express or prioritize a catastrophic error
A neutral control arm, to calibrate how big a “real” gap is Total variation distance Bounded, geometrically symmetric, no mode lean to express Gradient saturates where the student is worst, so it trains gently and slowly

The calibration row is the one people are most often surprised by. A calibrated model is one whose stated confidence matches its empirical accuracy, which is a claim about the whole distribution rather than about its argmax.18 Mode seeking discards precisely the part of the distribution that calibration is computed from, so a student can improve on top-1 agreement while getting measurably worse at knowing what it does not know. Chapter 16 treats that case at length.

Two rules cut across every row of it. Choose the divergence by what the student is for, then verify on generations rather than on the loss: the theory tells you which direction to expect, and only the measurement tells you whether the effect on your pair, at your step budget, is large enough to matter. And when you are unsure and the run is expensive, take the bounded option, which costs you some of the effect size and buys you a run that finishes. There is a bias-variance reading of that second rule as well: a softer, more spread target is a higher-bias, lower-variance training signal, and which side of the trade you want depends on how much data you are distilling on.24

6.12 Where this lands in the labs#

Lab 01 §4 and §5 are the fastest confirmation available: the bimodal fit takes seconds on a laptop, the four-number check takes less than that, and §8 prints the three numbers from §6.7 on the catastrophic position. Lab 05 is the chapter’s real companion, and it does the thing a book cannot: it trains six real students that differ in exactly one configuration key and grades them against a prediction file with a timestamp on it. Its four solution exercises extend that into gradient geometry at intermediate , the temperature interaction on a capacity-limited student, the rank-2 measurement from §6.9, and a total variation distance control arm whose gradient is characterized before a training run is spent on it. If you run one thing from this chapter, run Lab 05’s Part A: the limit check, the metric audit, and the prediction file execute anywhere, take under a minute, and are where the habits actually live.

6.13 Exercises#

  1. Take the bimodal teacher from §6.2 and give the student family a third parameter: a mixture weight over two bumps with independent centers, so the student can represent the teacher exactly. Predict, before computing anything, what the forward-KL and reverse-KL optima become and how far apart they are. Then say what that implies about experiments that compare divergences using a student roughly the same size as its teacher.

  2. Section 6.2 reports that the reverse-KL-optimal student scores a forward KL of 20.36 while the forward-KL-optimal student scores a reverse KL of 2.548. Both are “the wrong objective’s view of the other’s answer,” but they differ by a factor of eight. Explain the asymmetry from the integrands in §6.1, and construct a teacher for which the ratio would be much closer to 1.

  3. A colleague reports that a arm reached a final loss of 0.41 while a arm reached 1.84, and concludes that the symmetric objective produced a better student. State every reason this conclusion is unsupported, in order of severity, and name the smallest additional measurement that would let them make a defensible claim.

  4. You are told that a sweep over at a fixed learning rate produced a perfectly linear staircase in student entropy. Using §6.4.2 and §6.4.3, say why this is evidence against the gradient-geometry account rather than for a smooth interpolation, and describe two different experiments that would distinguish the objective’s shape from its scale.

  5. Design a metric audit for expected calibration error, which Chapter 8 introduces as a diagnostic and which requires binning predicted confidences. Give at least two inputs whose correct ECE you can compute by hand, and say for each what class of implementation bug it would catch. At least one of your cases should be sensitive to the bin boundaries.

  6. Section 6.9’s measured mean and median point in opposite directions. Suppose you had only the mean and had registered the prediction “the student’s rank-2 probability is below the teacher’s.” Write the two-sentence conclusion you would have published, then write the correction, then state the general rule about summary statistics that would have prevented the first sentence.

  7. Given a teacher and student pair you have access to, sketch the smallest experiment that would tell you whether the divergence choice matters at all for your application, before running a full sweep. Say how many runs it costs, what it measures, what result would tell you to stop, and what your noise floor estimate is based on.



  1. The capacity gap is Chapter 5’s subject; the result that a larger teacher can produce a worse student, traced to the student being unable to fit the teacher’s function, is Jang Hyun Cho and Bharath Hariharan, “On the Efficacy of Knowledge Distillation,” arXiv:1910.01348 (2019), ICCV 2019. https://arxiv.org/abs/1910.01348 

  2. distinct-n originates with Jiwei Li, Michel Galley, Chris Brockett, Jianfeng Gao, and Bill Dolan, “A Diversity-Promoting Objective Function for Neural Conversation Models,” arXiv:1510.03055 (2015), NAACL-HLT 2016. https://arxiv.org/abs/1510.03055 

  3. self-BLEU comes from Yaoming Zhu et al., “Texygen: A Benchmarking Platform for Text Generation Models,” arXiv:1802.01886 (2018), SIGIR 2018. https://arxiv.org/abs/1802.01886 Chapter 16 covers the failure modes of both metrics in detail. 

  4. The entropy-collapse literature is real and growing: Ganqu Cui et al., “The Entropy Mechanism of Reinforcement Learning for Reasoning Language Models,” arXiv:2505.22617 (2025), https://arxiv.org/abs/2505.22617, and Renren Jin et al., “Revisiting Entropy in Reinforcement Learning for Large Reasoning Models,” arXiv:2511.05993 (2025), ACL 2026 Findings, https://arxiv.org/abs/2511.05993. Both are about reinforcement-learning-trained models, and I am not aware of a paper whose primary subject is length collapse in distilled models specifically. Treat it as an underserved area rather than a settled one. 

  5. Dominik M. Endres and Johannes E. Schindelin, “A new metric for probability distributions,” IEEE Transactions on Information Theory 49, no. 7 (2003): 1858-1860, https://doi.org/10.1109/TIT.2003.813506, prove that the square root of the Jensen-Shannon divergence satisfies the triangle inequality. Ferdinand Österreicher and Igor Vajda, “A new class of metric divergences on probability spaces and its applicability in statistics,” Annals of the Institute of Statistical Mathematics 55, no. 3 (2003): 639-653, https://doi.org/10.1007/BF02517812, establish the broader family of which it is one case. 

  6. Rishabh Agarwal, Nino Vieillard, Yongchao Zhou, Piotr Stanczyk, Sabela Ramos, Matthieu Geist, and Olivier Bachem, “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649 The method is known as GKD, generalized knowledge distillation, though the acronym does not appear in the title. 

  7. Jongwoo Ko, Sungnyun Kim, Tianyi Chen, and Se-Young Yun, “DistiLLM: Towards Streamlined Distillation for Large Language Models,” arXiv:2402.03898 (2024), ICML 2024. https://arxiv.org/abs/2402.03898 

  8. Jongwoo Ko, Tianyi Chen, Sungnyun Kim, Tianyu Ding, Luming Liang, Ilya Zharkov, and Se-Young Yun, “DistiLLM-2: A Contrastive Approach Boosts the Distillation of LLMs,” arXiv:2503.07067 (2025), ICML 2025 Spotlight. https://arxiv.org/abs/2503.07067 

  9. Yuqiao Wen, Zichao Li, Wenyu Du, and Lili Mou, “f-Divergence Minimization for Sequence-Level Knowledge Distillation,” arXiv:2307.15190 (2023), ACL 2023. https://arxiv.org/abs/2307.15190 

  10. The f-divergence family is due independently to Imre Csiszár, “Information-type measures of difference of probability distributions and indirect observations,” Studia Scientiarum Mathematicarum Hungarica 2 (1967): 299-318, and to S. M. Ali and S. D. Silvey, “A general class of coefficients of divergence of one distribution from another,” Journal of the Royal Statistical Society Series B 28, no. 1 (1966): 131-142, https://doi.org/10.1111/j.2517-6161.1966.tb00626.x 

  11. Yuxian Gu, Li Dong, Furu Wei, and Minlie Huang, “MiniLLM: Knowledge Distillation of Large Language Models,” arXiv:2306.08543 (2023), ICLR 2024. https://arxiv.org/abs/2306.08543v2 Note that the arXiv landing page currently shows a later revision under a different title; the version-pinned link above matches the published paper. 

  12. The sequence-level version of the argument connects to the older imitation-learning framing of autoregressive distillation: Alexander Lin, Jeremy Wohlwend, Howard Chen, and Tao Lei, “Autoregressive Knowledge Distillation through Imitation Learning,” arXiv:2009.07253 (2020), EMNLP 2020. https://arxiv.org/abs/2009.07253 It also shares its structure with preference optimization, where the same “learn from your own samples under a fixed reference distribution” shape appears: Rafael Rafailov et al., “Direct Preference Optimization: Your Language Model is Secretly a Reward Model,” arXiv:2305.18290 (2023), NeurIPS 2023. https://arxiv.org/abs/2305.18290 

  13. The model pair is SmolLM2-360M-Instruct as teacher and SmolLM2-135M-Instruct as student: Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big, Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737 The pair is chosen so that six training runs fit inside a couple of hours on the reference machine. 

  14. Constantinos Karouzos, Xingwei Tan, and Nikolaos Aletras, “Where does output diversity collapse in post-training?” arXiv:2604.16027 (2026), https://arxiv.org/abs/2604.16027, and Longfei Yun, Chenyang An, Zilong Wang, Letian Peng, and Jingbo Shang, “The Price of Format: Diversity Collapse in LLMs,” arXiv:2505.18949 (2025), https://arxiv.org/abs/2505.18949. Both are preprints without a peer-reviewed venue at the time of writing. 

  15. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015), §2. https://arxiv.org/abs/1503.02531 The factor is introduced there to keep the relative contributions of the soft and hard objectives fixed when the temperature changes. 

  16. Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819, https://doi.org/10.1007/s11263-021-01453-z; and for the language-model-specific version, Xiaohan Xu et al., “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024). https://arxiv.org/abs/2402.13116 

  17. Ari Holtzman, Jan Buys, Li Du, Maxwell Forbes, and Yejin Choi, “The Curious Case of Neural Text Degeneration,” arXiv:1904.09751 (2019), ICLR 2020. https://arxiv.org/abs/1904.09751 The paper is the standard reference for how much of a generative model’s apparent behavior is a property of the decoding strategy rather than of the model. 

  18. Chuan Guo, Geoff Pleiss, Yu Sun, and Kilian Q. Weinberger, “On Calibration of Modern Neural Networks,” arXiv:1706.04599 (2017), ICML 2017. https://arxiv.org/abs/1706.04599 

  19. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945 

  20. Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947 

  21. Mingyang Song and Mao Zheng, “A Survey of On-Policy Distillation for Large Language Models,” arXiv:2604.00626 (2026). https://arxiv.org/abs/2604.00626 The arXiv comment field reads “Ongoing Work,” so treat it as a living preprint rather than a published survey. 

  22. Lucas Beyer, Xiaohua Zhai, Amélie Royer, Larisa Markeeva, Rohan Anil, and Alexander Kolesnikov, “Knowledge distillation: A good teacher is patient and consistent,” arXiv:2106.05237 (2021), CVPR 2022. https://arxiv.org/abs/2106.05237 Their headline result is that training length dominates most of the design choices people spend their attention on. 

  23. The inequality carries Pinsker’s name from M. S. Pinsker, Information and Information Stability of Random Variables and Processes (Holden-Day, 1964), though the optimal constant is due independently to Imre Csiszár (1967) and to S. Kullback, “A lower bound for discrimination information in terms of variation,” IEEE Transactions on Information Theory 13, no. 1 (1967): 126-127, https://doi.org/10.1109/TIT.1967.1053968. Chapter 3 derives it. 

  24. Helong Zhou et al., “Rethinking Soft Labels for Knowledge Distillation: A Bias-Variance Tradeoff Perspective,” arXiv:2102.00650 (2021), ICLR 2021. https://arxiv.org/abs/2102.00650 

Part III · Making It Real

7

Tokenizers, Templates, and Alignment

Here is a distillation run I would like you to imagine, because it is the one this chapter exists to prevent.

The loss curve descends. It starts near 9 nats, falls quickly through the first two hundred steps, settles into the slow grind everyone recognizes, and ends around 1.6. The teacher and student come from the same model family, so nobody worries about compatibility. The gradient norms are healthy. Nothing warns, nothing crashes, no assertion fires, and at the end there is a checkpoint. Then you generate from it and the student answers the question correctly and keeps going: another sentence, then a new question it asked itself, then an answer to that, until the length limit cuts it off mid-word.

The cause was one line in the batching code. The pad token and the end-of-sequence token had the same id, the mask excluded padding by comparing token ids, and so the mask also excluded the one real end-of-sequence token at the end of every completion. The student was trained on every token of every answer except the token that means “stop.” It never saw a gradient on stopping, so it never learned to stop. The loss curve could not show this, because the loss was computed correctly over the positions the mask kept. Losing one supervised position out of a dozen moved every summary statistic by roughly one part in ten, which is inside the range you would attribute to a seed.

That is the shape of every bug in this chapter. Not a crash. Not a NaN. A run that trains, descends, produces a checkpoint, and optimizes a different objective than the one you believe you wrote down. Chapters 3 through 6 were about choosing the right objective. This chapter is about the far more common failure of computing the objective you chose against the wrong tensor entries. The material is unglamorous and it is where I have lost the most time, so I am going to be slow and specific about it, and by the end you should be unable to look at a [batch, position, vocab] tensor without asking which tokenizer defined the last two axes.

7.1 A tokenizer is a coordinate system#

A language model does not consume text. It consumes a vector of integers, and something upstream of the model turned your string into that vector.

Definition

Tokenizer

The reversible map between a string and a sequence of integer ids that a model consumes. It carries a vocabulary of pieces, a rule for splitting text into those pieces, and a decode direction that reassembles a sequence of ids into the original string. Two models with different tokenizers assign different integer vectors, of different lengths, to the same text.

Definition

Vocabulary

The set of distinct pieces a tokenizer can emit, each with an integer id. The size of the vocabulary fixes the last axis of every logit tensor the model produces, so a distribution over the next token is a distribution over exactly these outcomes and no others.

The most common way a tokenizer is built for a modern language model is byte-pair encoding, and it is worth walking through once because the structure explains most of the behavior you will see. Start with an alphabet of all 256 possible byte values, so every string is representable from the outset. Run over a training corpus counting how often each adjacent pair of symbols occurs. Take the most frequent pair, add it to the vocabulary as a new symbol, and replace every occurrence of that pair in the corpus with the new symbol. Repeat until the vocabulary reaches the size you asked for. What you end up with is a list of pieces, ordered by the merge that created them, and encoding a new string means applying those merges in the same order.

That definition does most of the work in this chapter, and it costs you in three places. Common sequences become single tokens and rare ones do not: the word ” the” with its leading space survives many merges in every English tokenizer, while a rare surname or a base64 blob gets chopped into several pieces, sometimes down to individual bytes. Nothing is out of vocabulary, because the base alphabet is the 256 byte values, so every possible byte string has some encoding; this is byte-level fallback, and it is why an emoji or an underrepresented script costs many tokens per character rather than a fraction of one. And the map depends on the corpus that trained it, so a tokenizer built on English web text is a different map from one built on a multilingual mixture with a large code fraction, at the same vocabulary size. That last consequence is why “the same text” is a different integer vector under each, and it is the subject of this chapter’s second half.

A logit tensor is indexed [batch, position, vocabulary id], and both of the last two axes are defined by a tokenizer rather than by the model architecture. Position means “after the first tokens of this tokenizer’s segmentation of this text,” which is a statement about a particular splitting of a particular string. A vocabulary id means nothing at all without the vocabulary file that maps ids to strings, so id 17 in one model and id 17 in another are unrelated facts.

2026-08-01T07:27:12.513305 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 10 20 30 40 50 60 70 UTF-8 byte offset GPT-2 50,257 ids 18 tokens Qwen2.5 151,936 embedding rows 20 tokens SmolLM2 49,152 ids 21 tokens Qwen2.5 cuts at byte 38 (mid-token for the other two) GPT-2 and SmolLM2 cut at byte 39 (mid-token for Qwen2.5) boundary in all three: 17 of 23 distinct offsets boundary in exactly one: byte 38 With ·273 ·GB / s ·of ·memory ·bandwidth , ·pref ill ·is ·cheap ·and ·decode ·is ·not . With · 2 7 3 ·GB /s ·of ·memory ·bandwidth , ·pre fill ·is ·cheap ·and ·decode ·is ·not . With · 2 7 3 ·GB / s ·of ·memory ·bandwidth , ·pref ill ·is ·cheap ·and ·decode ·is ·not .
Figure 7.1 The same sentence under three tokenizers gives three lengths and, worse, three different sets of boundaries, so there is no position of one segmentation that reliably corresponds to a position of another.

Lab 02 makes this concrete on one probe string, "With 273 GB/s of memory bandwidth, prefill is cheap and decode is not.", run through the GPT-2, Qwen2.5, and SmolLM2 tokenizers. It asserts two things about the result. The round trip is lossless in each case, meaning decoding the ids returns the original string exactly, so no tokenizer is losing information. And the three sequence lengths are not all equal. Those two facts together are the entire problem: three faithful representations of one string that do not agree on where anything is.

7.1.1 The three numbers that all claim to be the vocabulary size#

There is a trap here that costs real debugging hours, and it is worth naming before anything else, because it corrupts cache formats and comparison code rather than throwing an error.

For a HuggingFace tokenizer there are three different counts in circulation, and they are usually three different numbers: tokenizer.vocab_size, the base vocabulary the tokenizer was trained with; len(tokenizer), which adds the special tokens registered afterward, such as end-of-turn markers and role headers; and the number of rows in the model’s embedding matrix, which is often larger than either, because matrix dimensions rounded up to a friendly multiple run faster on the hardware.

Qwen2.5 is the clean illustration and Lab 02 prints all three: the base vocab_size is 151,643, the extra special tokens bring len(tokenizer) to 151,665, and the embedding matrix is padded out to 151,936 rows.1 Those trailing rows are real columns of the logit tensor. They receive logits, they receive probability after the softmax, and no token id ever maps to them.

Logit caches index the embedding axis, because that is the axis the model actually produces. So does any top- operation, any argmax, and any cross-model comparison. If your cache writer sized itself by tokenizer.vocab_size and your reader indexes into the model’s output, you have an off-by-293 error on the vocabulary axis of a tensor with no bounds checking that would catch it. Decide once, in writing, which of the three numbers your pipeline means, and assert it against the model’s actual output shape at load time.

Table 7.1 The three tokenizers Lab 02 compares, and the vocabulary sizes the labs price against.

Tokenizer Base vocab_size With special tokens Embedding rows
GPT-2 50,257 50,257 50,257
Qwen2.5 151,643 151,665 151,936
SmolLM2 49,152 49,152 49,152

The reason to keep this table in view is that the last column is what a dense logit cache would have to store per position, and the spread across it is a factor of three. Chapter 10 does that arithmetic in full.

7.2 Fertility, and the three meters it turns#

Once you accept that the same text has different lengths under different tokenizers, the next question is what that difference costs. The quantity that answers it is fertility.

Definition

Fertility

The number of tokens a tokenizer produces per unit of text, measured against a tokenizer-independent denominator. The course uses UTF-8 bytes:

A more fertile tokenizer chops the same text into more pieces. Fertility is a property of a tokenizer and a corpus jointly, never of a tokenizer alone.

People sometimes report tokens per word instead, which is easier to interpret for English prose, since a value near 1.3 means “about a third more tokens than words.” The trouble is that “word” is not a well defined unit across languages: whitespace segmentation gives a sensible count for English and a meaningless one for Chinese or Japanese, so a tokens-per-word comparison changes what it is measuring when the corpus language changes. Bytes are unambiguous. Every string has exactly one UTF-8 length and it does not depend on anyone’s segmentation.

Fertility is the reason a tokenizer choice is a budget line. Every meter in a distillation pipeline is denominated in tokens. A tokenizer that is 15% more fertile on your corpus makes the teacher prefill 15% more positions when you build a logit cache, makes that cache 15% larger at any fixed , since the cache stores one row per token position, and makes every on-policy rollout 15% longer for the teacher to score, on every epoch, for the life of the project. The first two are one-time costs. The third recurs, which makes it the one that matters.

Solutions 02 Exercise 4 runs this as a measurement rather than an argument. It takes 1,000 conversations from the course’s own training corpus, decodes them back to plain text with the template markers dropped so all three tokenizers see the same natural-language payload rather than markers only one of them knows about, and re-encodes with the GPT-2, Qwen2.5, and SmolLM2 tokenizers. It asserts that the three totals are three different numbers and that the ratio between the largest and the smallest exceeds 1.02, on the grounds that the gap is real money rather than rounding. It deliberately does not assert a specific spread, because the exact value is a property of that corpus and would be a different number on yours.

The conclusion runs in the opposite direction from what the framing suggests. You do not choose a tokenizer for its fertility; the tokenizer is fixed by which teacher and which student you can actually use. What the measurement gives you is a price, and the price sets a bar: a candidate teacher whose tokenizer is 15% more fertile on your corpus has to be more than 15% better per token before it is worth taking.

7.2.1 Bits per byte, the quantity that survives a change of tokenizer#

Fertility also breaks the most common way people compare two models, and the fix is worth deriving because you will need it again in Chapter 14 and Chapter 16.

A language model’s loss is a cross-entropy in nats per token, and “per token” is denominated in a unit each tokenizer defines for itself. A model with a more fertile tokenizer splits the same sentence into more pieces, each individually easier to predict, so its nats per token can be lower while it describes the text no better at all. Comparing two models on nats per token when their tokenizers differ is comparing two prices quoted in different currencies without an exchange rate.

The exchange rate is the byte. Take a fixed string, let the model score it under its own tokenization, and add up the negative log probabilities of every token. That total is the model’s surprise at the whole string, in nats, and the decomposition into tokens cancels out of it. Divide by to convert nats to bits, and divide by the number of UTF-8 bytes.

Definition

Bits per byte

The total negative log probability a model assigns to a fixed string, converted to bits and divided by the string’s length in UTF-8 bytes:

where the sum runs over the model’s own tokenization of the string. Both the numerator’s decomposition and the token count are tokenizer-specific; their combination is not, which is what makes bits per byte comparable across tokenizers.

The comparison is honest only under two conditions. Both models must score the same byte string, and each model’s token sequence must cover that string exactly, with no special tokens contributing probability mass to bytes that are not there. In practice that means encoding with add_special_tokens=False and checking the round trip before you trust the number.

Lab 10 uses bits per byte as a teacher-quality probe, and the measurement is the cleanest available demonstration that the quantity does what it claims. Two teachers are scored on the same three short factual English texts: Qwen2.5-0.5B-Instruct, the real teacher, and GPT-2, a much older and much worse model with a different tokenizer.2 Qwen scores 0.46 bits per byte, GPT-2 scores 0.98, a ratio of about 2.13. The two models have vocabularies of 151,936 and 50,257, tokenize the same text into different numbers of pieces, and the comparison is still a single well defined number that separates them by a factor of two. Chapter 14 uses that result for a different purpose, as a control that catches a downgraded teacher when the alignment-specific metric cannot, and Chapter 16 uses it again when a distilled student needs to be compared against something that was never trained on its tokenizer.

7.3 Chat templates decide what a prompt is#

An instruct-tuned model has never seen a bare string during its instruction tuning. It has seen a particular formatting, and its behavior is conditioned on that formatting appearing.

Definition

Chat template

The model-specific format that turns a list of role-tagged messages into the single token sequence the model actually consumes. It wraps each message in special tokens, inserts a role header marking who is speaking, and optionally appends a generation prompt: the opening marker for the assistant’s turn, with nothing after it, telling the model that a reply begins here.

The SmolLM2 instruct models the labs use wrap each message between an opening marker carrying the role and a closing <|im_end|>, and their end-of-sequence token is that same <|im_end|>, because what ends a generated reply is the end of the assistant’s turn.3 Rendering a single user message with add_generation_prompt=True produces the user turn, its closing marker, and then the opening marker of an assistant turn that has not been written yet.

Three things follow from that, and each of them is easy to get wrong.

The prompt is a fact about the templated sequence, not about your string. Measure the prompt length by tokenizing your question and counting, and you will be short by the whole scaffolding, so the completion mask built from that count supervises the tail end of the template as though it were the model’s answer. The reliable way to get the boundary is to render the prompt side with the generation prompt enabled, tokenize that, and take its length. Everything downstream, the mask, the loss, the agreement metric, is defined relative to that number.

The scaffolding is a large fraction of a short conversation. The wrapper costs a fixed number of tokens per message regardless of what the message says. Take the shortest of Lab 02’s four training pairs: the question is “Name the largest planet in the solar system.” and the answer is “Jupiter.” The completion side is a word, a period, and an end-of-sequence token; the prompt side is that question plus two role markers, two turn boundaries, and the newlines between them. More of that row is template than is answer. That is not a problem in itself, since the template is what the model expects, and it becomes one in two places. Chapter 9’s cost arithmetic is per token, and template tokens are tokens you pay for. And Chapter 16 detects contamination between the distillation corpus and the evaluation set by looking for shared n-grams, at which point every templated row in both sets shares a long, identical scaffolding n-gram, and a naive overlap test reports contamination that is an artifact of formatting. The remediation is to strip the template before the overlap test. A reader who first meets that false positive in Chapter 16 finds it mysterious; a reader who has counted template tokens once does not.

Templates change. The template lives in the tokenizer configuration of a specific model repository at a specific revision. It can be updated, and it can differ between two checkpoints you thought were the same model. Print the rendered string and read it with your eyes the first time you touch a new checkpoint. It takes ten seconds, and it is the only way to find out that the template now injects a default system message that was not there last month.

7.4 The shift-and-mask convention, in full#

This is the center of the chapter. Everything before it was setup and everything after it depends on it being right.

7.4.1 What the model predicts, and what teacher forcing means#

A causal language model reads tokens and emits a distribution at every position. The distribution emitted at position is a prediction of the token at position . It is not a statement about the token at position , which the model has already read.

That sentence is the source of more silent bugs in distillation than the entire divergence literature combined, and the reason is a mismatch of vocabulary. A mask is naturally defined over tokens: this token is part of the completion, that token is padding. A loss is defined over predictions. There are of each, they sit in the same tensor, and they are offset by one. Any mask defined over tokens has to be shifted before it can index predictions, or every supervised position is paired with the wrong target.

Definition

Teacher forcing

Scoring a model on predicting each next token of a reference sequence while feeding it the reference tokens as context, rather than its own previous outputs. Every position is conditioned on text the model did not produce. This is how nearly all language model training works, including off-policy distillation, and Chapter 12 is about what it fails to teach.

The alternative is to feed the model its own generated token at each step, which is slower, unstable early in training, and the subject of Chapter 12.11 For this chapter, teacher forcing matters because it makes the whole batch computable in one parallel forward pass, which turns alignment into an indexing question rather than a sequencing one.

7.4.2 What model(labels=...) actually computes#

HuggingFace’s causal language model wrapper accepts a labels argument and returns a .loss. What it does with those labels has to be stated precisely, because the cross-check depends on matching it exactly. Given input_ids of shape [B, T] and labels of the same shape, the model computes logits [B, T, V] and performs the shift internally: it drops the last position of the logits, drops the first position of the labels, and computes a cross-entropy between them with ignore_index=-100 and mean reduction.

Definition

Ignore index

The sentinel value -100 in a HuggingFace labels tensor, meaning “do not supervise this position.” Positions holding it contribute nothing to the loss and are excluded from the denominator of the mean. It is a magic number rather than a mask because PyTorch’s cross-entropy takes an ignore_index argument, and -100 is its default.

Written out, with the logit vector at position , the label at position , and the set of supervised label positions after the shift:

Read the indices carefully. The logit vector at is scored against the label at , and the denominator counts supervised labels at positions 1 through , excluding position 0 no matter what its label says, because after the shift there is no logit left to pair with it. That is why Lab 02 can assert that the shift does not change the number of supervised positions: the completion mask never supervises position 0, since position 0 is inside the prompt, so nothing is lost and the two counts agree exactly. Build a batch where position 0 is supervised and that assertion fires, and it will be telling you something true.

7.4.3 Doing it yourself, and why you have to#

Why reimplement a loss the library already computes? Because in distillation the library’s loss path is the wrong shape.

A distillation loss is a function of two logit tensors. The student’s come out of the student’s forward pass; the teacher’s come from a separate forward pass, or from a cache written days earlier, or from a serving process on the other end of a socket.13 The model’s own labels path never sees the teacher tensor and has no argument through which to accept it. So the shift and the mask live outside the model, applied identically to both logit tensors and to the mask, and every divergence in Chapters 3 through 6 is computed on the result. The external code therefore becomes the definition of alignment for the whole pipeline, and there is one way to know it is right: make it reproduce the library’s number on a batch where you control everything.12

Here is the external path, written to be read rather than to be fast.

import torch
import torch.nn.functional as F

def masked_next_token_nll(logits, input_ids, token_mask):
    """Mean next-token NLL over supervised positions of a ragged batch.

    logits      [B, T, V]  row t is the model's prediction of token t+1
    input_ids   [B, T]     tokens as fed to the model, padding included
    token_mask  [B, T]     True where the TOKEN at t is supervised
    """
    pred   = logits[:, :-1, :]      # predictions of tokens 1 .. T-1
    target = input_ids[:, 1:]       # the tokens those predictions are scored against
    mask   = token_mask[:, 1:]      # the same one-position shift, applied to the mask

    logp = F.log_softmax(pred.float(), dim=-1)
    nll  = -logp.gather(-1, target.unsqueeze(-1)).squeeze(-1)      # [B, T-1]

    nll = torch.where(mask, nll, torch.zeros_like(nll))            # select, do not multiply
    return nll.sum() / mask.sum().clamp_min(1)

Four details in eleven lines, each load-bearing. The shift is applied to three tensors, not one, and slicing the logits without slicing the mask is the same bug as slicing neither, harder to see because the shapes still broadcast. The upcast to fp32 before log_softmax is Chapter 2’s policy and is not optional here, since a student running in bf16 has a resolution near 1.0 of about 0.0039 and a log-probability computed at that resolution is a different number from the library’s. Masked positions are zeroed by selection rather than multiplication, because a position you are not supervising is a position where nothing constrains the model’s output, so it can hold an infinity or a NaN, and 0 * inf is NaN: multiplying by a mask lets an excluded position poison the batch mean, and torch.where cannot. And the denominator is the number of supervised predictions, clamped at one, which matches HuggingFace’s mean reduction and survives an empty mask.

2026-08-01T07:27:21.284413 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 21 25 29 33 37 41 45 position index (0-20 elided: system block; 47-52 elided: padding) input_ids token pieces token_mask True on 4 tokens labels (HF) -100 where mask is False logits, shifted row t predicts token t+1 scored? 4 positions enter the mean each arrow: logits[t] scored against input_ids[t+1] if pad_token_id == eos_token_id this cell leaves token_mask, and the mean loses a position 3, not 4 scaffolding question answer final <|im_end|> padding row 1, T = 53 <|im_start|> user \n Name ·the ·largest ·planet ·in ·the ·solar ·system . <|im_end|> \n <|im_start|> ass istant \n J upiter . <|im_end|> <|endoftext|> <|endoftext|> <|endoftext|> <|endoftext|> F F F F F F F F F F F F F F F F F F T T T T F F F F -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 -100 58 13939 30 2 -100 -100 -100 -100 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 * * * *
Figure 7.2 The shift is one operation applied to three tensors at once, and the mask that is correct over tokens is wrong over predictions until it moves with them.

Lab 02 runs this against the library on a ragged batch of four chat-templated conversations, with the teacher and student both real checkpoints, prompts masked, padding masked, and the end-of-sequence token supervised. It asserts that the two numbers agree to better than 1e-4 in fp32. That single assertion is what licenses every masked divergence in the course: if the external convention reproduces the library’s convention to float precision on a batch with all the complications present, then the coordinate system the divergences are computed on is the coordinate system the ecosystem trains with, and the objective you wrote is the objective that runs.

7.4.4 What the agreement buys, and what changes at scale#

The check verifies a coordinate convention rather than a model, which makes it scale invariant. Solutions 02 Exercise 1 runs the identical check on a training-scale pair, and the interesting part is the list of what had to change. Three things: the model names, the dtype (an 8-billion-parameter model in fp32 needs about 32 GB for weights alone, at 4 bytes per parameter, so the pair runs in bf16), and the tolerance.

The tolerance is worth deriving rather than copying, because it is the one number that is not forced. bf16 keeps roughly two to three significant digits per stored value, the losses sit in the low units of nats, and the two code paths order their reductions differently, so a disagreement around 1e-3 is expected floating-point noise. A disagreement at 1e-1 would be an alignment bug, since a misalignment moves a per-token loss by an amount comparable to the loss itself. A tolerance of 3e-3 sits between those and separates them cleanly.

What did not change is more informative: not one line of the shift, the mask construction, or the masked mean. The one incidental difference is the pad token, because Qwen3 declares a proper pad token of its own and does not need the manual choice SmolLM2 forces.

The exercise produces three failure signatures, and they localize the bug before you start reading code. A difference of order 1e-1 nats or more is an alignment bug, most often a mask built over a differently rendered chat template than the one that produced the ids. A difference that grows with sequence length is padding entering the loss on one of the two paths, since the longer the rows the more padding there is, so the error scales with while float noise does not. And a clean pass with suspiciously identical floats, agreeing to every printed digit, suggests both paths silently ran in fp32 when you thought one was in bf16: not an alignment problem, but it means your training-loop memory estimate is off by a factor of two, which you would rather learn now than at step 4,000.

7.5 Off by one#

Now the failure. Suppose the shift is wrong by one position. What happens is nothing visible, which is the entire problem, so it is worth spelling out case by case.

The shift is applied twice. The common one, and it comes from a specific mistake: you shift the labels yourself, in your own data pipeline, and then also pass them to model(labels=...), which shifts them again internally. The prediction at position is now scored against the token at . The model is being asked to skip a token, which is harder than the problem it can solve, so the loss descends and plateaus above where it should. It looks like a model that is training and is a bit weak. It is training on the wrong task.

No shift at all. The prediction at position is scored against the token at position , which the model has already read. Under a causal mask, position ’s hidden state is a function of through inclusive, so the target is present in the input. The model does not start out good at copying, so the first few steps look ordinary. Then the loss falls through the floor, below the entropy of the text and onward toward zero, because the identity map is available and gradient descent will find it.

The diagnostic that falls out of this is one I want to state as a rule, because it inverts the instinct everyone has:

Watch out

The correct alignment is not the one with the lowest loss. A no-shift bug produces a better loss curve than the correct code, by a wide margin, because it leaks the answer into the context. If you are ever comparing alignment conventions by which one trains fastest, you will pick the broken one. A per-token loss on general text that settles below roughly 0.5 nats is not a triumph. It is a leak, and this is the first place to look.

The mask is shifted but the logits are not, or the reverse. The set of supervised positions is now off by one relative to the completion: the first token of the completion goes unsupervised and the last token of the prompt gets supervised in its place. Every summary statistic moves by one part in the completion length, which on a fifty-token completion is 2%, inside the seed-to-seed variation of most things you would measure. The student is trained to produce the first token of the answer without ever being graded on it, and is graded on reproducing the final token of the template scaffolding, which it would have copied anyway.

The way to catch all three is a deliberate sweep: compute the loss under several offsets and read the shape of the result rather than any single value.

def loss_at_offset(logits, input_ids, token_mask, offset):
    """Score prediction t against token t+offset. offset=1 is the correct convention."""
    T = input_ids.shape[1]
    pred   = logits[:, : T - offset, :]
    target = input_ids[:, offset:]
    mask   = token_mask[:, offset:]
    logp = F.log_softmax(pred.float(), dim=-1)
    nll  = -logp.gather(-1, target.unsqueeze(-1)).squeeze(-1)
    return float(torch.where(mask, nll, torch.zeros_like(nll)).sum() / mask.sum().clamp_min(1))

for off in (0, 1, 2):
    print(f"offset {off}: {loss_at_offset(s_logits, input_ids, mask, off):.4f}")

Run that on a real model and the signature reads directly. Offset 0 comes out implausibly low, because the target is in the context. Offset 1 comes out at the model’s genuine next-token loss on that text. Offset 2 comes out higher, because predicting two tokens ahead is harder. Three numbers, one ordering, and you know which convention your code is running before spending a training step. What it cannot tell you is anything about the mask, which is why the HuggingFace cross-check remains the primary check and this is the localizer you reach for when that check fails.

7.6 Padding, attention masks, and a batch that lies#

Sequences have different lengths and tensors are rectangular, so short rows get filled with a padding token. Two separate mechanisms then have to exclude those positions, and conflating them is its own category of bug.

The attention mask tells the model which positions exist. It affects the forward pass: what each position may attend to, and in most implementations what position index each real token is assigned, since position ids are commonly derived from the cumulative sum of the attention mask. The loss mask tells your loss which positions to score, and affects nothing about the forward pass and everything about the gradient. They are built from the same information and they are not the same object. A correct attention mask with a broken loss mask trains on padding; a correct loss mask with a broken attention mask computes the supervised positions from corrupted hidden states.

7.6.1 Left and right, and when each is required#

Which side you pad on is not a matter of taste, and the reason is worth working out from the causal mask rather than memorizing.

Under right padding, real tokens occupy the low positions and pad tokens the high ones. A causal model at position attends only to positions , and every real token sits before every pad token, so no real position ever attends to a pad, mask or no mask. The pads’ own outputs are garbage and the loss mask discards them. That is why a missing attention mask under right padding leaves the supervised positions numerically unchanged, and why the bug survives review: the code is wrong and the numbers are right.

Under left padding, pads occupy the low positions and real tokens the high ones, so every real token attends to the pads that precede it. Without an attention mask the model mixes pad embeddings into every real position’s hidden state, and if position ids are derived from the mask, each row’s real tokens are also assigned indices offset by that row’s pad count, putting different rows at different phases of the positional encoding. The result is not obvious garbage. It is a slightly wrong model, row-dependent, whose error scales with how much padding each row happened to need.

Right padding is what you want for training and for scoring a corpus. Left padding is mandatory for batched generation, because generation continues from the last column of each row, and with right padding the last column of a short row is a pad token, so the model would continue from padding rather than from the end of the prompt. Labs 06 and 07 both left-pad for that reason.4 The rule: pad right when computing a loss, pad left when calling generate, and pass an attention mask in both cases even when you can prove you do not need it, so the day someone flips the padding side the code is already correct.

7.6.2 The pad that ate the end-of-sequence token#

Now the bug from this chapter’s opening, in its measured form.

Definition

Completion mask

The boolean mask marking the positions belonging to the assistant’s completion, which are the only positions a distillation loss is allowed to train on. Built from the prompt length measured on the templated sequence, and from the exclusion of padding.

The course’s helper builds it as position_index >= prompt_len, optionally intersected with input_ids != pad_token_id. The exclusion of padding is by token id, and that is the mechanism. SmolLM2-Instruct’s end-of-sequence token is <|im_end|>, which legitimately ends every completion. If you pad with the end-of-sequence token, which is a very common default because it is the only special token some tokenizers declare, then padding and the real end-of-sequence token share an id, and the exclusion cannot tell them apart.

Solutions 02 Exercise 3 breaks it on purpose and reports precisely what happens, which is more useful than knowing that something happens. Every row’s audit fails. The first assertion to fire is the count check, “the mask must cover exactly the completion,” and each mask comes up short by exactly one position. The missing position is always the last token of the sequence, and it decodes to <|im_end|>. Nothing else changes: prompts are still excluded, the bodies of the completions are still supervised, the shapes are identical.

That last sentence is what makes the bug dangerous rather than loud. The loss still computes and still descends. Every summary statistic moves by about one part in ten on these short rows, and by far less on realistic completions, which puts the effect inside the range you would attribute to a seed. The downstream symptom follows mechanically: the student receives gradient on every completion token except the one that says stop, learns the distribution of answer content perfectly well, and never raises its probability of emitting the end-of-sequence token after an answer is complete. At inference it finishes the answer and keeps sampling, another sentence, a new question addressed to itself, an answer to that, until the length limit fires. Invisible in the training curves and obvious in the product.5

There is a mirror image worth holding next to it, because the two failures bracket the same boundary from opposite sides. In on-policy training, everything after the first end-of-sequence token in a rollout is padding the student produced itself. Supervising those positions teaches the student to model padding, which also shows up as a model that will not stop. Supervising past the end-of-sequence token teaches padding; failing to supervise at it teaches never stopping. Chapter 12 handles the first, and the defense against the second is two lines: choose a pad token that cannot occur inside a templated conversation, and keep an assertion that the pad id differs from the end-of-sequence id somewhere it fires before a training job spends any money.

7.7 Prompt masking#

Given a prompt and a completion, you can supervise both or only the completion. The course supervises only the completion, and the reasons are worth making explicit because there are settings where the other choice is right.

Training on prompt tokens dilutes the gradient with a region the student is never asked to produce. At inference the prompt is given, and the student’s probability of reproducing it is not a quantity anyone consumes. If your corpus is 60% prompt tokens by count, which is normal for short-answer instruction data with a chat template, then prompt-inclusive training spends 60% of its gradient budget on a task nobody will run.

The second reason is worse, because it corrupts a metric rather than a loss. Teacher and student agree on prompt tokens without effort, since predicting the next token of text both models are reading is easy for anything with a working attention mechanism. Include prompt positions in a top-1 agreement number and you have mixed a hard measurement with an easy one at a ratio set by your corpus’s prompt fraction, so two runs on differently shaped corpora produce agreement numbers that are not comparable, and both are higher than the honest value.

The case for supervising prompts is narrower than people assume. If what you want is domain adaptation, where the student should get better at modeling text of a certain kind rather than at answering in a certain way, then the prompt is part of the text and belongs in the loss; if your corpus is documents rather than conversations, the distinction does not arise. The mask should cover exactly the positions whose behavior you intend to change, and for instruction distillation that is the completion.

Masking a position out of the loss does not remove it from the context. Prompt tokens are still read, still attended to, and still shape every downstream hidden state. They are paid for in compute and in cache storage, and not in gradient. Chapter 10 has a version of this distinction that costs money: you prefill the teacher over the entire sequence, including the prompt, and you only need to store cached logits at the positions you will supervise.

7.8 Two models on the same coordinate system#

When the teacher and student share a tokenizer, position means the same thing in both, the vocabulary axis indexes the same strings in both, and the two logit tensors are compatible on all three axes. That is the comfortable case, and it is worth saying what “compatible” buys, since it covers the majority of practical distillation and every lab in Part III.

It buys you the ability to subtract. A per-position KL divergence is well defined because both sides are distributions over the same outcome space. A top-1 agreement number is well defined because both argmaxes name the same token. A cached teacher distribution written on Monday is readable by a student trained on Friday because the ids mean the same thing on both days.

It is also cheap to guarantee. The reliable ways to end up with a matched pair are to take the student from the same family as the teacher, which is what the labs do, or to build the student out of the teacher by pruning, which inherits the tokenizer exactly and is Chapter 13’s subject.6 DistilBERT is the early example of the choice made deliberately: the student was given the teacher’s vocabulary so the two output spaces coincided.7

Given a matched pair, three diagnostics tell you whether it is aligned in practice rather than in principle, and all three run in seconds on a handful of batches.

Forward KL on supervised positions of held-out data. It should be positive and finite. Zero would mean the two models are the same model, which happens more often than you would like when a configuration bug loads the teacher twice.

Top-1 agreement, the fraction of supervised positions where the two models pick the same highest-probability token. Lab 02 calls this the honest headline number, and it asserts a band rather than a value: a same-family pair should agree often but not always, so 0.3 < agreement < 1.0. A value of exactly 1.0 means you are comparing a model with itself. A value near the floor means the pair is not what you think it is.

Mean predictive entropy for each model, in nats. Chapter 2 defined it. Here it is a shape check: two models from the same family on the same text should have entropies in the same neighborhood, and a large gap means one of them is running with a temperature you did not intend or in a dtype that has flattened its distribution.

The measurement that makes those three interpretable is the control. Build an untrained model with the student’s architecture, run it on the same batch, and compute its top-1 agreement with the teacher. That is the baseline of chance plus whatever the architecture alone contributes before any training, which is not zero. Lab 02 asserts that the real student beats the random-initialized control by at least 0.3 in absolute agreement. The control is cheap, and running it against your own pair before a Tier 2 run tells you whether the numbers you are about to try to improve are measuring a relationship at all.

One thing that is not on the list: perplexity against gold text. It measures each model against the dataset, and what distillation cares about is the student against the teacher. A student can improve its perplexity while agreeing with its teacher less.

Where the divergence lives is worth knowing too, because it changes what you expect a distillation loss to do. Lab 02 prints a per-position table and the pattern is stark: peaked positions, where the teacher is nearly certain and both models agree, contribute almost nothing to the KL, and the divergence concentrates on genuinely open positions where several continuations are plausible. Most of a corpus is the first kind, and the training signal comes from the minority that is the second, which will matter again in Chapter 10 when you decide how much of the teacher’s distribution to keep.

7.9 Two models that are not: the proof#

Now the uncomfortable case. The teacher is a model you like and the student is from a different family, so their tokenizers differ.9 What survives?

Not position-wise alignment. That is worth proving rather than asserting, because the proof tells you what to do instead. It is also a problem with no equivalent in the image classification setting most of the distillation survey literature is built on, where teacher and student consume identical inputs and produce distributions over an identical, externally defined label set.15

An alignment would have to be a map from student positions to teacher positions such that corresponding positions represent the same state of having read the text. Fix a string . Under tokenizer it becomes tokens with a set of byte boundaries , the byte offsets at which tokens end. Under tokenizer it becomes tokens with boundaries . Position under tokenizer corresponds to having read exactly the first bytes of , where is the -th element of . So a position of can correspond to a position of only if that byte offset is also in .

Lab 02 measures directly, using each tokenizer’s offset mapping, on a fixed sample string, for the GPT-2 and SmolLM2 tokenizers. It asserts three things about the result. The two segmentations differ. The shared fraction, , is strictly less than 1, so there is no global position-wise correspondence. And it is strictly greater than 0, so some boundaries do coincide.

Those last two together are the whole situation. Boundaries coincide where both tokenizers are forced to agree, mostly at word edges and punctuation, since both were trained on text where those are the strongest statistical seams. Between the shared boundaries the two models are partway through different chunks of the text, and there is nothing at those positions to compare. The counts differ and the boundary sets differ, so no injection from one position sequence to the other preserves the meaning of a position. The correspondence is partial by construction, and partial in a way that depends on the string.

It gets worse at the positions that do line up, which is the part people miss. Suppose you have found a byte offset that is a boundary in both segmentations, so both models have read exactly the same text and “what comes next” is the same question for both. Their answers are distributions over different outcome spaces: 50,257 outcomes for GPT-2 and 49,152 for SmolLM2, naming different strings. A KL divergence requires both distributions to assign probability to the same set of outcomes, and it is not defined between distributions over different sets. This is the point at which a sequence-level objective starts to look attractive, since text crosses a tokenizer boundary where positions and ids cannot.1014

Solutions 02 Exercise 5 demonstrates this in the least ambiguous way available: it attempts the subtraction and the tensor operation raises a shape error. That error is the correct behavior, and the temptation to make it go away is the trap. Padding the shorter vector to the longer one removes the error and produces a number, and that number compares “the probability of GPT-2’s token 17” with “the probability of SmolLM2’s token 17,” which are two unrelated strings. The result is not noisy. It is meaningless, and it will train.

7.9.1 What survives re-tokenization#

Something does survive, and Solutions 02 Exercise 5 constructs it, which is why that exercise is the seed of Chapter 14 rather than a dead end. Pick a shared boundary mid-string, run each model on its own tokenization of the identical prefix, take each one’s next-token distribution, and sort each probability vector in descending order.

p_a = F.softmax(model_a(ids_a).logits[0, -1], dim=-1)   # V_a outcomes
p_b = F.softmax(model_b(ids_b).logits[0, -1], dim=-1)   # V_b outcomes, V_b != V_a

srt_a = p_a.sort(descending=True).values
srt_b = p_b.sort(descending=True).values
L = min(len(srt_a), len(srt_b))
l1 = float((srt_a[:L] - srt_b[:L]).abs().sum())          # bounded by 2, and defined

After sorting, index means the same thing for both models: the probability of the -th most likely continuation, whatever that continuation happens to be. That is a shared coordinate system, built by throwing away exactly the information that was not shared.

The exercise reports what the comparison looks like on a real pair. Both sorted vectors are monotone non-increasing, which checks the sort. The L1 distance between them cannot exceed 2 and lands well under 0.5, so the two models disagree noticeably about how confident to be while agreeing that the position is moderately peaked. Truncating both vectors to the shorter vocabulary’s length discards under a tenth of a percent of the mass on either side, so the vocabulary-size mismatch stops mattering in rank space. Beyond rank 64, both models hold under a tenth of their mass.

What sorting preserves is the confidence profile, how mass decays with rank. What it costs is stated as plainly: this distance can never see that the two models might favor completely different continuations with identical confidence. Two models confidently wrong in opposite directions look identical in rank space.

That trade is the Universal Logit Distillation loss, and Chapter 14 covers it properly, including the five properties that make it a legitimate training objective and the alignment machinery that recovers some of the identity information sorting throws away.8 The rule to carry out of this section is the general one: the only quantities that survive re-tokenization are the ones that never mention a token id. Bytes survive. Strings survive. Sorted probability values survive. Positions and ids do not.

7.10 The tail, and two estimators that bracket it#

One more thing about real teacher distributions belongs here, because it is measured on the same tensors and because the sign of the answer is documented incorrectly in a place you may read.

Definition

Top-k truncation

Keeping only the highest-probability entries of a teacher’s distribution at each position and discarding the rest, usually because storing the full vector at every position is unaffordable. The discarded mass is the tail, and what you do about it is a design decision with a measurable bias.

The motivation is arithmetic. A teacher with a 151,936-row output axis, stored in bf16, costs about 302 KB per token position for the dense distribution, so one million cached token positions is about 302 GB. That is a disk purchase rather than a caching strategy, and Chapter 10 owns the full treatment of cache formats, storage arithmetic, and how to choose . What belongs here is the prior question: how much is out there, and what happens to your loss when you drop it.

The answer is a measurement, and it is a small number for a real instruct teacher. Lab 02 sweeps against the actual shifted logits of a 360M instruct model on chat-templated completions and asserts that at the mean retained mass exceeds 0.98. Running the same sweep on the corpus the course trains on, Solutions 02 Exercise 2 gets above 0.99 at the same . So the tail beyond the top 64 entries of a 49,152-entry distribution holds something like one percent of the probability. Small, not zero, measurable, and one percent of the mass is not the same thing as one percent of the divergence.

2026-08-01T07:27:37.978646 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.7 0.8 0.9 1.0 mean retained teacher mass crosses 0.99 at k = 129 k = 64: 0.9829 39 supervised positions, fp32 1 2 4 8 16 32 64 128 256 512 k (teacher entries retained, of 49,152) 0.2 0.4 0.6 0.8 1.0 1.2 forward KL(teacher || student), nats renormalizing (overstates) tail bucket (understates) dense forward KL = 0.5651 the bracket you are choosing inside k = 128: +1.4% k = 128: -3.5%
Figure 7.3 The teacher's mass is concentrated in the first few dozen ranks, and the two ways of handling what is left over sit on opposite sides of the true divergence at every k, converging toward it from both directions.

7.10.1 The two ways to spend the missing mass#

You have the teacher’s top probabilities and you need a divergence against the student’s full distribution. There are two things to do about the mass you did not keep.

Renormalize. Rescale the retained probabilities so they sum to 1 and compute the divergence over those terms alone, against the student’s actual probabilities on those same tokens. The cheaper option, and the one every library offers.

Bucket the tail. Keep the total discarded mass as one aggregate entry and match it against the student’s total mass on all tokens outside the retained set. One extra float per position to store, one extra term in the sum.

Write for the teacher, for the student, for the retained set, for the teacher’s retained mass and for the student’s mass on those same tokens. Let be and conditioned on (each divided by its own total over ), and the same two distributions conditioned on the complement of . Then the dense value is

the tail-bucket estimator is

and the renormalizing estimator is

Note that divides the teacher by and leaves the student alone. That asymmetry is what “compare the renormalized teacher against the student you actually have” means, and it is going to matter in a moment.

7.10.2 Which way each one is wrong, derived#

The clean way to see both biases is the chain rule for KL divergence under a partition. Split the vocabulary into two groups, and its complement. Then

You can verify this by expanding each conditional and collecting terms; every log factorizes into a group part and a within-group part, and the weights are the group masses.

Now read the tail-bucket estimator against it. Expanding ’s first sum the same way gives , so

which is the dense value with the last term deleted. Therefore

The tail bucket understates, always, with no assumptions about the models. That is the statement that lumping outcomes together cannot increase a KL divergence: the bucket sees how much mass is in the tail and whether the student agrees about the total, and is blind to how the mass is arranged among the individual tokens inside. The understatement is the tail’s own internal divergence weighted by how much mass is out there, so it goes to zero as grows for two reasons at once.

The renormalizing estimator is different in kind, and this is where the documentation conflict lives. Rewrite in terms of the conditionals:

The derivation is two lines: replace by inside the log, and the constant comes out of the sum because sums to 1 over . What that identity says is that renormalizing does two things, not one. It restricts attention to the head, which is the part everyone expects. And it compares a normalized teacher against an unnormalized student, which contributes , a positive constant that grows the more student mass sits outside the retained set.

Subtracting, and simplifying the group term, gives the exact bias:

where is the binary entropy of the retained mass, in nats.

That expression has no fixed sign, which is the honest answer. Put realistic numbers into it and the sign becomes clear. Take , the retained teacher mass Solutions 02 measures at , and , which is roughly where a same-family student’s mass on the teacher’s top- sits on teacher-forced text. Then , while the other constant, , comes to . Those two nearly cancel, leaving

So on a real pair, renormalizing overstates the divergence whenever the head disagreement exceeds the tail disagreement, which is exactly the regime a peaked instruct teacher and a same-family student are in. The tail is the low-probability region where both models are vague and roughly agree with each other; the head is where the contest is. Renormalizing deletes the region of agreement and reweights the region of disagreement upward, and the divergence goes up.

Here is a six-token example you can check on paper. The teacher is , the student is , and , aggressive enough to make the effect large. Then and . The dense divergence is 0.0536 nats. The tail-bucket estimator gives 0.0474 and the renormalizing estimator gives 0.3382. The bracketing is visible: . And you can check the two identities on it. The understatement equals , the tail’s internal divergence weighted by the tail mass. The overstatement is dominated by , the binary entropy of a retained mass that is only four fifths of the total, which is why the renormalizing bias is enormous at small and shrinks fast.

Lab 02 asserts both directions on real logits at every in its sweep: renorm_kl >= dense and tail_bucket_kl <= dense, both monotone in , with both estimators inside 10% of the dense value by . Solutions 02 Exercise 2 reproduces the same two directions on the actual training corpus, with both inside 15% at .

The consequence for practice is the useful part. Because the two estimators are biased in opposite directions, computing both gives you a bracket around the value you cannot afford to compute. A narrow bracket means your is generous enough and it does not matter which one you train with. A wide bracket means you are choosing a bias rather than measuring a divergence, and no amount of tuning downstream recovers information that is not in the cache. Which estimator is closer at your on your corpus is a measurement, not a theorem. Chapter 10 turns this into a workflow, with the cache format and the selection procedure.

7.10.3 A documented contradiction, resolved#

The course’s own materials disagree about this, and showing the disagreement is more useful than quietly fixing it. Lab 01 §6 and Lab 02 §5 both say that renormalizing overstates and the tail bucket understates, and Lab 02 asserts it live on real logits at every it tries. The docstring of kd_core.topk_forward_kl, on the use_tail=False branch, says the opposite: that renormalizing “systematically understates the divergence because it pretends the teacher never considered anything else.”

The docstring’s argument is plausible and wrong about the sign. Pretending the teacher never considered anything else does not only delete terms. It also divides every surviving teacher probability by , inflating each retained term, and it compares that inflated teacher against a student that was not renormalized, which adds . Both effects push the estimate up, while deleting the tail terms pushes it down. The derivation above says which wins, and for a peaked teacher whose tail is where the two models agree, the upward effects win at every Lab 02 measured. The docstring is the doc bug and the asserted behavior is the ground truth.

There is a regime where the docstring’s claim is correct, and constructing it is a good exercise in believing the algebra over the intuition. Take a nearly flat teacher, so the top captures only a quarter of its mass, and give the student a distribution matching the teacher’s shape on the head exactly while assigning near-zero probability to everything outside it. The renormalized comparison sees two identical head-conditional distributions and reports a divergence near zero; the dense value is enormous, because the teacher has three quarters of its mass on tokens the student has ruled out. That is a real regime, and it is the regime of a flat teacher and a collapsed student rather than of a peaked instruct teacher and a same-family student. The sign of a truncation bias is a property of the pair and the corpus, which is why the answer is to measure it rather than look it up.

Field note

The reason I keep this contradiction in the book rather than editing the docstring and moving on is that it is a good example of how a wrong sign survives in a codebase. The docstring’s argument sounds right, the estimator it describes still trains, and the direction of its bias is invisible in the loss curve, since a loss that is biased upward by a constant-ish factor descends exactly like one that is not. The only thing that caught it was an assertion written against a dense computation on real logits, and even then the assertion was originally written the other way round. A build that only asserts what the author already believes cannot find this class of error. Chapter 10 tells the rest of that story, including which row refused and what it cost me to believe it.

7.11 Everything to check before a GPU is involved#

Every item on this list has cost me or someone in the course a run. They take a combined few minutes against a batch of four examples, and all of them fail loudly when they fail.

On the tokenizer.

  1. The round trip is lossless: decoding the ids of a sample of your corpus returns the original strings exactly.
  2. You have decided which of vocab_size, len(tokenizer), and the model’s embedding row count your pipeline means, and you assert it against the model’s actual output shape at load time.
  3. Teacher and student tokenizers are the same object, checked by comparing their vocabularies rather than their names. Two checkpoints from the same family at different revisions can differ.
  4. Fertility on your corpus is measured, not assumed, and you have written down what it prices.

On the template.

  1. You have printed the rendered prompt for one example and read it with your eyes.
  2. Prompt lengths are measured on the templated sequence with the generation prompt enabled, not on your raw question string.
  3. The completion has an explicitly appended end-of-sequence token, and you know whether your tokenizer added one for you.

On the batch.

  1. The pad token id differs from the end-of-sequence token id. Assert it.
  2. Padding side matches the operation: right for loss, left for generation.
  3. An attention mask is passed, whether or not you can prove it is needed.
  4. The mask audit passes on every row: no padding is supervised, no prompt token is supervised, the supervised fraction is sane rather than 0 or 1, and the end-of-sequence token is supervised.

On the loss.

  1. Your external shift-and-mask reproduces model(labels=...).loss to your declared tolerance, on a ragged batch, with the tolerance justified by dtype rather than tuned until it passes.
  2. The shift does not change the number of supervised positions.
  3. The offset sweep gives the expected ordering, with offset 1 above offset 0 and below offset 2.
  4. The loss is computed in fp32 regardless of the model’s dtype.

On the pair.

  1. Top-1 agreement is in a plausible band, and beats a random-initialized control by a wide margin.
  2. Forward KL on held-out data is positive and finite.
  3. Mean entropies of teacher and student are in the same neighborhood.
  4. If the tokenizers differ, you have stopped and gone to Chapter 14, because nothing above this line applies.

The list is worth keeping as a file rather than as a habit. Chapter 8 folds it into the pre-launch ritual for a full run, and Chapter 18 makes the case that a check you cannot rerun mechanically is a check you did not perform.

7.12 Where this lands in the labs#

Lab 02 is this chapter’s companion and the cheapest lab in the course to run: two small checkpoints, about a gigabyte of downloads, a few minutes on a CPU. Its §3 is the cell everything else rests on, and it does the one thing a book cannot, which is to execute both loss paths on real model outputs and assert that the floats agree. Run it before anything else in Part III. Its exercises extend it in four directions worth your time: the same assertion at training scale with the tolerance re-derived rather than copied, the truncation-bias table on your own corpus, the padding bug performed deliberately so you can watch which assertion fires first and what it costs, and the sorted-probability comparison that is the first step of Chapter 14’s method and which you will have written before you know its name. Lab 03 then applies every convention from Lab 02 to a real training run and adds the four-point mask audit that Chapter 8 treats as a ritual.

7.13 Exercises#

  1. A colleague reports that their distillation run’s per-token loss on general English text settled at 0.18 nats, and offers this as evidence that the student has nearly matched the teacher. Using §7.5 and Chapter 2’s entropy material, state what you would check first, what a healthy value would look like instead, and what a single well chosen offset sweep would tell you. Then say what a legitimate explanation for 0.18 nats would have to look like.

  2. Derive the count identity that Lab 02 asserts, shifted_mask.sum() == mask.sum(), from the definition of the shift. State the exact condition under which it holds, construct a batch where it fails, and say what that batch would represent about the training data.

  3. Consider the alternative renormalizing estimator that normalizes both the teacher and the student over the retained set, so that it computes with no term. Using the decomposition in §7.10.2, work out its bias against the dense value and say whether it is signed. Then say which of the three estimators you would want as a training loss and which as a diagnostic, and why those can be different answers.

  4. You are handed a corpus in which the prompt is 80% of the tokens on average. Estimate, with stated assumptions, how much a top-1 agreement number computed without prompt masking would be inflated relative to the completion-only number. Then design the smallest experiment that would measure the inflation on your own corpus rather than estimating it.

  5. Two teams distill the same student from two teachers with different tokenizers, and report final losses of 1.42 and 1.71 nats per token. Team A claims the first teacher is better. State every reason that comparison is unsupported, convert the claim into one that could be checked, and say what additional measurement each team would have to report.

  6. Section 7.9 argues that no position-wise alignment exists across tokenizers, and §7.9.1 gives a comparison that works anyway by discarding token identity. Propose a third option that discards less information than sorting does, say precisely what it would need in order to be well defined, and identify the assumption it makes that sorting does not. Chapter 14 covers one such method; try to design yours before reading it.

  7. The pad-equals-end-of-sequence bug moved every summary statistic by about one part in ten on Lab 02’s short completions. Work out how much it would move them on a corpus whose completions average 400 tokens, and then answer the harder question: given that smaller effect, what measurement would still detect the bug, and at what point in the pipeline would you have to run it?



  1. The three counts are properties of the released tokenizer and model configuration rather than claims from the paper; the model family is documented in Qwen Team, “Qwen2.5 Technical Report,” arXiv:2412.15115 (2024). https://arxiv.org/abs/2412.15115 Lab 02 §1 prints all three for Qwen/Qwen2.5-0.5B-Instruct and asserts that the first two differ. 

  2. Measured in Lab 10 and reported in Solutions 10 Exercise 4, as the quality control for a deliberately downgraded teacher: 0.46 bits per byte for Qwen2.5-0.5B-Instruct against 0.98 for GPT-2 on three short factual English texts, a ratio of about 2.13. 

  3. The SmolLM2 models and their instruction-tuned variants are described in Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big, Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737 The labs use the 360M and 135M instruct checkpoints as a teacher and student pair with a 2.7x capacity gap. 

  4. Lab 06’s solutions state the requirement explicitly for batched generation: the model continues from the last position of each row, so pad tokens must precede the prompt rather than follow it. Lab 07 left-pads for the same reason when generating rollouts. 

  5. The surface appearance of a model that will not stop resembles the degeneration Ari Holtzman, Jan Buys, Li Du, Maxwell Forbes, and Yejin Choi describe in “The Curious Case of Neural Text Degeneration,” arXiv:1904.09751 (2019), ICLR 2020, https://arxiv.org/abs/1904.09751, but the cause here is different and much easier to fix: their subject is the interaction between maximization based decoding and the shape of the learned distribution, while this is a supervision bug at a single token. Worth knowing both, so you can tell them apart when a student rambles. 

  6. Saurav Muralidharan et al., “Compact Language Models via Pruning and Knowledge Distillation,” arXiv:2407.14679 (2024), NeurIPS 2024, https://arxiv.org/abs/2407.14679, and Mengzhou Xia, Tianyu Gao, Zhiyuan Zeng, and Danqi Chen, “Sheared LLaMA: Accelerating Language Model Pre-training via Structured Pruning,” arXiv:2310.06694 (2023), ICLR 2024, https://arxiv.org/abs/2310.06694, both build the student out of the teacher, which makes tokenizer agreement automatic rather than a thing to verify. 

  7. Victor Sanh, Lysandre Debut, Julien Chaumond, and Thomas Wolf, “DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter,” arXiv:1910.01108 (2019), https://arxiv.org/abs/1910.01108 The student uses the teacher’s vocabulary, which is what makes the output spaces coincide. 

  8. Nicolas Boizard, Kevin El Haddad, Céline Hudelot, and Pierre Colombo, “Towards Cross-Tokenizer Distillation: the Universal Logit Distillation Loss for LLMs,” arXiv:2402.12030 (2024), Transactions on Machine Learning Research (January 2025). https://arxiv.org/abs/2402.12030 

  9. The cross-tokenizer problem is surveyed alongside the rest of the language-model distillation literature in Xiaohan Xu, Ming Li, Chongyang Tao, Tao Shen, Reynold Cheng, Jinyang Li, Can Xu, Dacheng Tao, and Tianyi Zhou, “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024). https://arxiv.org/abs/2402.13116 

  10. The distinction between token-level and sequence-level supervision, which decides whether position-wise alignment is needed at all, is Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947 A sequence-level objective needs only text, so it survives a tokenizer mismatch that a token-level objective cannot. 

  11. Teacher-forced scoring is what makes off-policy distillation a single parallel forward pass. The on-policy alternative, where the student generates and the teacher scores what it produced, is Rishabh Agarwal, Nino Vieillard, Yongchao Zhou, Piotr Stanczyk, Sabela Ramos, Matthieu Geist, and Olivier Bachem, “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649 The masking problem changes shape there, and Chapter 12 covers it. 

  12. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015), https://arxiv.org/abs/1503.02531, is where the soft-target objective originates. Nothing in that paper is about alignment, because in image classification the teacher and student consume identical inputs and produce distributions over an identical, externally defined label set. Everything in this chapter is the cost of moving that objective to a setting where neither of those is true by default. 

  13. A served teacher returns log-probabilities over its own vocabulary, usually truncated to a top- the server decides. The response shape and its constraints are part of the serving picture in Woosuk Kwon et al., “Efficient Memory Management for Large Language Model Serving with PagedAttention,” arXiv:2309.06180 (2023), SOSP 2023, https://arxiv.org/abs/2309.06180, and Chapter 15 covers what it means for a distillation pipeline to consume it. 

  14. DeepSeek-AI, “DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning,” Nature 645 (2025): 633-638, https://doi.org/10.1038/s41586-025-09422-z, distills by supervised fine-tuning on teacher-generated traces, which is a pipeline where the teacher’s tokenizer never has to match the student’s, because what crosses between them is text. The template still has to be right on the student’s side. 

  15. The broader distillation survey literature treats tokenizer mismatch as a special case rather than a default; see Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819. https://arxiv.org/abs/2006.05525 The vision setting the survey is largely built on has no equivalent problem, which changes how you read a result from it. 

Part III · Making It Real

8

The First Real Run

Everything up to this point has been checkable on a laptop in under a minute. The objective is arithmetic on logit vectors. The divergences are closed forms you can verify against autograd. The tokenizer alignment is a comparison between two tensors that either match to float precision or do not. None of it needed a GPU, and none of it took longer to confirm than it took to state.

A training run is different in a way that changes what discipline means. It takes forty minutes at the small end and a day at the large end. It consumes a machine you cannot use for anything else while it runs. And its most common failure is not a crash. Its most common failure is a run that completes, produces a loss curve that descends the way a loss curve is supposed to descend, writes a checkpoint, and is measuring nothing you intended, because a mask was off by one position or a teacher was left in the wrong mode or the temperature-squared factor never made it into the loss. A crash tells you something is wrong. A silently wrong run tells you a number, and the number is worse than no number, because you will act on it.

So this chapter is about operator practice: what a competent person does in the hour before a run, what they watch during it, and how they decide afterward whether it worked. The material is procedural, and I want to argue for each piece of it rather than hand you a checklist, because a checklist you do not understand gets skipped the first time you are in a hurry, which is exactly when it was going to save you.

There is one organizing idea underneath all of it, and it is worth stating before anything else. The things worth asserting are the things that need no training, and the things that need training can only be validated on the machine that trains. Those two halves do not overlap. The first half is exact, fast, and is where the silent bugs live. The second half is slow, expensive, approximate, and is where the interesting results live. Keeping them separate is the whole method, and the rest of the chapter is the consequences of taking it seriously.

8.1 Part A, Part B, Part C#

The shape that follows from that idea is a run in three movements.

Part A is the pre-flight. Everything assertable without loading a model, or with only a tokenizer and a few thousand rows of text, gets asserted here: the memory budget, the identity of every configuration in the comparison, the loss function’s behavior at the endpoints you are about to rely on, the dataset’s mask audited position by position. Part A runs anywhere, finishes in a minute or two, and every claim in it carries an assertion, so it either passes or fails loudly.

Definition

Pre-flight

The set of checks executed before a training run that can be verified without training: memory arithmetic, configuration identity, loss-function properties at the settings the run will use, and data integrity. A pre-flight is worth writing when its checks are exact and fast, and it earns its keep by converting silent failures into loud ones before any expensive resource is committed.

Part B is the run. Model loading, the training loop, the periodic evaluation, the checkpoint, the manifest. This cannot be validated anywhere except on the machine that will run it, which is why it is worth gating behind an explicit flag left off by default. A green checkmark produced on a laptop by a scaled-down stand-in is evidence that a different thing works on a different machine, and treating it as assurance is how people end up debugging at hour four of a run.

Part C is the verdict. Expected ranges written down before execution, failure signatures listed with their causes, and a short written judgment produced afterward. Part C is the part people skip, and it is the part that separates an operator from a person who owns a GPU.

The claim I want to defend is that this split is not notebook housekeeping. It is a general working method, and it applies to any experiment whose expensive phase can be preceded by a cheap one.

Consider the alternative: a single script that loads a teacher, loads a student, builds a dataset, and starts stepping. Every check in that script, if there are any, runs after something expensive has already happened. If the memory plan is wrong, you find out when the allocator fails, which is typically not at step 0 but at step 300, once activation buffers have reached their steady-state size and the allocator has fragmented. If the mask is wrong, you find out never, because a wrong mask still produces a descending loss curve. If two arms differ in two configuration keys instead of one, you find out at the end, holding a table of numbers that cannot support any conclusion.

The pre-flight inverts the order of discovery. It puts the checks that can fire in one second ahead of the operations that take forty minutes, because the two classes of failure have very different costs. An assertion failure at second one costs a second and hands you a stack trace pointing at the line. The same bug found at minute forty costs the forty minutes, plus the time to reconstruct what you were doing, plus the real risk that you patch the symptom instead of the cause because you are annoyed.

Field note

The failure I keep coming back to is not the loud one. It is the run where I trained a student against a teacher that had been left in training mode. Dropout was active on every teacher forward pass, so the soft targets were randomly perturbed versions of the teacher’s real distribution, a fresh perturbation on every step. The run did not crash. The loss descended. The student even improved, because a noised teacher is still a smoother target than a one-hot label. What it did was set a floor on the held-out KL that I could not get under, and I spent most of a day looking for the floor in the objective before I found it in a single missing call to .eval().

The lesson is not “remember to call eval.” It is that the check costs nothing and I had not written it, because I was thinking about the loss function rather than the machinery around it. Most of Part A is like this: each check unremarkable on its own, the set of them the difference between a day and a minute.

8.2 The memory arithmetic#

The first thing a pre-flight asserts is that the run fits. That requires knowing what things cost, and the cost model is simple enough to carry in your head and precise enough to make decisions with.

8.2.1 Bytes per parameter#

Start with inference. A model held in bfloat16 stores one number per parameter, and a bfloat16 number is 16 bits, which is 2 bytes. So a teacher you are only running forward costs

and nothing else, provided you are not also generating from it, which brings in a cache you will meet in §8.2.4. A 1.7-billion-parameter teacher is 3.4 GB. A 32-billion-parameter teacher is 64 GB. That is the entire calculation, and it is why teacher size is a much less binding constraint than people expect on a machine with generous capacity.

Now the student, which is being trained. Full fine-tuning with the standard mixed-precision Adam setup keeps five quantities per parameter, and it is worth naming all five because the reason each one exists is a different reason.

Add them:

Definition

Full fine-tuning cost

The steady-state memory a fully trained model occupies under mixed-precision Adam: about 16 bytes per parameter, from a 2-byte bf16 weight, a 2-byte bf16 gradient, a 4-byte fp32 master weight, and two 4-byte fp32 Adam moments. Eight of those sixteen bytes belong to the optimizer, which is why optimizer choice is a memory decision as much as a convergence decision.

Sixteen against two is the ratio to carry out of this section: training a parameter costs eight times what serving it costs. Put a 4-billion-parameter student into training and it occupies the same 64 GB that a teacher eight times its size occupies at inference, which is not the direction most people expect the asymmetry to run. Nearly everyone arrives at a distillation plan braced for the teacher to be the expensive object, and on this machine it usually is not.

The same decomposition says where to look when a configuration does not fit. Twelve of the sixteen bytes are gradient and optimizer state, and both scale with the number of parameters you are updating and not with the number the model has, which is the gap low-rank adaptation was built to walk through.

8.2.2 Where low-rank adaptation gets its room#

Low-rank adaptation freezes the pretrained weights and trains a small additive correction to selected weight matrices, factored as a product of two thin matrices whose inner dimension is much smaller than either side.1 The frozen base still has to be held for the forward pass, at 2 bytes per parameter, but it has no gradient, no master copy, and no Adam moments, because it is not being updated. Only the adapter parameters carry the full 16-byte cost.

So if a fraction of the parameter count is trainable, the cost per base parameter becomes

At , a common setting, that is 2.16 bytes per parameter against 16 for full fine-tuning, a factor of about 7.4. An 8-billion-parameter student costs 16 GB frozen plus about 1.3 GB of adapter state, near 17 GB in total, against 128 GB if you trained all of it.

Definition

Low-rank adaptation

Training a low-rank additive correction to frozen pretrained weight matrices instead of the weights themselves. Because gradients, master copies, and optimizer moments exist only for the trainable slice, the per-parameter cost falls from 16 bytes to roughly , where is the trainable fraction. The frozen base still occupies its 2 bytes per parameter.

QLoRA pushes the same idea further by quantizing the frozen base to 4 bits, which drops the base term from 2 bytes to roughly 0.5 and makes the adapter the dominant cost rather than a rounding error.2 I am not going to treat quantized training as a default here, because it changes the numerics of the forward pass and therefore changes what the student is learning to match, and Chapter 15 is the right place for that discussion. The point for now is that the 16-byte figure is a property of a choice, not a law.

8.2.3 Five configurations, priced#

With those two rates, teacher size and student size are the only inputs you need for a first pass. The course’s reference machine has 128 GB of unified memory, so here is the same table the course README uses, with every entry derived rather than quoted.

Table 8.1 Teacher plus student memory on the 128 GB reference machine, weights and optimizer state only.

Configuration Teacher, bf16 Student Subtotal Verdict
8B teacher, 1.7B student, full FT GB GB 43 GB comfortable, a good default
14B teacher, 1.7B student, full FT GB GB 55 GB comfortable
8B teacher, 4B student, full FT GB GB 80 GB workable, watch the cache
32B teacher, 8B student, LoRA GB GB 81 GB viable, quantize the teacher if tight
32B teacher, 4B student, full FT GB GB 128 GB does not fit

Read the last row carefully, because it is the one that teaches something. The subtotal is 128 GB and the machine has 128 GB, and the correct verdict is that the configuration does not fit. A plan that exactly consumes the ceiling is a plan with no room for the things the table does not count, and the things the table does not count are not small.

2026-08-01T07:27:40.157129 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 32 64 96 128 160 memory, decimal GB 8B + 1.7B full FT 14B + 1.7B full FT 8B + 4B full FT 32B + 8B LoRA 32B + 4B full FT 43.2 55.2 80.0 81.3 128.0 does not fit 48 of the student's 64 GB: gradient and Adam state 80% planning limit machine ceiling, 128 GB teacher, bf16 inference bf16 weight bf16 gradient fp32 master weight fp32 Adam m fp32 Adam v headroom needed: activations, KV cache, fragmentation (25%) frozen bf16 base (LoRA) LoRA adapter, 16 B/param on 1%
Figure 8.1 The student's optimizer state, not the teacher, is what makes a configuration stop fitting: breaking the student's 16 bytes per parameter into its five named components shows that gradients and Adam state account for twelve of them, which is exactly the share low-rank adaptation removes.

Notice what the figure makes visible that the table does not. In the 32B-teacher LoRA row, the teacher is by far the largest single block and the configuration still fits, while in the 32B-plus-4B full fine-tuning row the teacher is the same size and the configuration fails. The difference is entirely in the twelve bytes per parameter of student gradient and optimizer state. When a plan is over budget, that block is where the room is, and the ways to get it are: train fewer parameters, train a smaller student, or use an optimizer with less state.

8.2.4 What the table leaves out#

Three costs sit outside the per-parameter model entirely, and between them they are why the last row of Table 8.1 fails.

Activations. The forward pass produces intermediate tensors, and backpropagation needs many of them to compute gradients, so they stay resident until the backward pass consumes them. Their size scales with batch size and sequence length and depth, not with parameter count, which means they are invisible to a per-parameter cost model and can dominate it at long sequence lengths.

For distillation specifically there is a term worth computing explicitly, because it surprises people. The logits tensor has shape (batch, positions, vocabulary). For a batch of 8 sequences of 384 tokens against the SmolLM2 vocabulary of 49,152 entries,10 that is

which is 0.30 GB in bf16. Ordinary fine-tuning holds one of those. Distillation holds two, one for the student and one for the teacher. And the loss upcasts both to fp32 before taking logarithms, because Chapter 2’s underflow argument says you cannot compute a log-softmax over a fifty-thousand-entry vocabulary in bf16 and expect the tail to survive, so each upcast copy is 0.60 GB. Between the two bf16 logit tensors, their fp32 copies, and the log-probability tensors the divergence needs, a modest batch can hold two to three gigabytes of vocabulary-sized tensors before you have counted a single weight. That is where the 4 GB activation allowance in the course’s own memory plan comes from, and it is why doubling the batch size on a distillation run is a bigger memory decision than doubling it on a fine-tune.

The key-value cache. When a model generates text one token at a time, it stores the attention keys and values for the tokens it has already processed so it does not recompute them at every step. The cache grows linearly with the sequence and with the batch, and for large models over long contexts it becomes the largest single allocation in the system. Two notes for a distillation run. First, a scoring forward pass over text that already exists does not need a cache at all, because there is no next step to reuse anything, so if your stack allocates one by default (many do, because the same model object is also used for generation) you are paying for memory you never read. Turn it off explicitly for the teacher. Second, on-policy methods do generate, and Chapter 12 budgets for that.14

Fragmentation. The allocator hands out contiguous blocks. Over a few hundred steps of allocating and freeing tensors of varying sizes, the free memory gets carved into pieces, and a request can fail while the total free bytes are more than sufficient, because no single free piece is large enough. This is why an out-of-memory error tends to arrive at step 300 rather than step 0, and why it correlates with the longest sequence in a batch rather than with the average. The serving world takes this seriously enough that the paged-attention design exists specifically to stop the key-value cache from fragmenting, by allocating it in fixed-size blocks the way an operating system pages memory.3

None of the three is easy to predict from first principles, and all three are why you leave headroom. A working rule is to plan to no more than about 80 percent of capacity for the weights and optimizer subtotal, and to treat anything above that as requiring a measurement rather than an estimate. A duller fourth reason: memory is quoted in decimal gigabytes and reported by allocators in binary gibibytes, and the two differ by 7.4 percent, enough to turn a plan that arithmetic says fits into a run that does not.11

8.3 Asserting the plan before anything loads#

Here is what the memory arithmetic looks like as code you actually run. Look at two things: the headroom fraction is an explicit argument rather than an implicit assumption, and the function fails rather than warns.

BYTES = {"bf16 weight": 2, "bf16 gradient": 2, "fp32 master weight": 4,
         "fp32 Adam m": 4, "fp32 Adam v": 4}

def full_ft_gb(params_b):
    return params_b * sum(BYTES.values())        # 16 bytes/param

def infer_gb(params_b):
    return params_b * BYTES["bf16 weight"]       # 2 bytes/param

def plan(total_gb, headroom=0.20, **items):
    used = sum(items.values())
    for name, gb in items.items():
        print(f"  {name:<36}{gb:>7.1f} GB")
    print(f"  {'planned':<36}{used:>7.1f} GB of {total_gb:.0f}")
    assert used <= total_gb * (1 - headroom), (
        f"{used:.1f} GB leaves under {headroom:.0%} headroom on {total_gb:.0f} GB")
    return used

plan(128.0, **{"teacher 1.7B, bf16 inference": infer_gb(1.7),
               "student 360M, full fine-tune": full_ft_gb(0.36),
               "student activations, 8 x 384": 4.0,
               "teacher forward buffers":      3.0})

try:                                              # the negative control
    plan(128.0, **{"teacher 32B, bf16": infer_gb(32),
                   "student 4B, full FT": full_ft_gb(4.0)})
    raise SystemExit("a 128 GB plan on a 128 GB machine must be refused")
except AssertionError as err:
    print("refused, correctly:", err)

What that proves is not that the run fits. It proves that the checker can tell the difference between a plan that fits and one that does not, which is a different and more valuable claim. The second block is a negative control: a configuration deliberately excluded from the experiment, asserted to be rejected. Without it, the first assertion is compatible with a checker that always passes, and an assertion that cannot fail tests nothing. I have shipped a validator that always returned true. It took a negative control to notice.

The 1.7B teacher plus 360M student plan above comes to 16.2 GB of 128, which is not a tight fit, and someone will reasonably ask why assert something so clearly fine. Two answers. The plan you write is a record of what you believed, so when a run dies you can compare belief against reality and learn which term you underestimated, rather than lowering the batch size until the error stops. And you write the assertion when it is easy so the habit exists when it is hard, which is the run where you are least inclined to slow down.

One design rule sits inside the choice of what to plan for: plan for the largest arm in the comparison, not the one you are launching first. A five-arm study whose largest arm does not fit fails four fifths of the way through, after four arms’ worth of compute has established a comparison you cannot complete.

8.3.1 The rest of the loop, named#

Memory is the part of the plan that decides whether the run starts. Four more settings decide whether it produces anything you can read, and they belong in the same written plan because each of them can silently change what an arm measures. Lab 03’s loop uses all four, and none of them is a default you should inherit without knowing what it is doing.

AdamW at a learning rate of 3e-5. That is roughly an order of magnitude below what you would use to pretrain a model of this size from scratch, and the reason is that the student is not starting from scratch. It has a pretrained checkpoint’s structure to preserve, and a rate high enough to reorganize that structure destroys the thing distillation is supposed to be refining. The decoupled-weight-decay variant is the one to use, because with Adam’s per-parameter scaling an L2 term folded into the gradient decays large-gradient parameters less than small-gradient ones, which is not what “weight decay” is supposed to mean.

Gradient accumulation of 4. The batch that fits in memory and the batch you want to take a step on are different quantities, and accumulation separates them: run four micro-batches forward and backward, summing gradients, then step once. The optimizer sees an effective batch four times the size of what §8.2 priced, at no extra steady-state memory, because the activations of each micro-batch are freed before the next one allocates. What it costs is wall clock, linearly. What it buys is a gradient estimate whose noise you chose rather than one your memory budget chose for you, and it is the first knob to reach for when a loss curve is jagged in a way that is not about the objective.

Gradient clipping at a global norm of 1.0. Compute the norm of the whole gradient, across every parameter as a single vector, and if it exceeds 1.0 rescale the whole thing down so it equals 1.0. The direction is preserved and only the length is capped. In distillation this earns its keep for a specific reason from Chapter 3: forward KL is unbounded above, so one position where the student has near-zero probability on a token the teacher is sure about can produce a loss term hundreds of times the batch’s typical value. Clipping converts that from a step that wrecks the run into a step that teaches the student nothing and moves on. Log the pre-clip norm, not only the post-clip one, or you will never know how often the cap fired.

A cosine schedule with 50 warmup steps. The learning rate rises linearly from zero over the first 50 steps and then follows a cosine curve down to near zero at max_steps. Warmup exists because Adam’s second-moment estimate is close to meaningless for the first few dozen steps, so the effective step size early in a run is badly controlled; ramping in gives the moment estimates time to become estimates. The cosine tail matters for a reason specific to comparisons, and §8.7 spends a paragraph on it: the schedule is defined against max_steps, so changing the step budget changes the learning rate at every step, not only the ones you added.

8.4 Reading a KD loss curve#

Once the run is going, the first thing on the screen is the loss. It is worth being precise about what it is telling you, because it is telling you less than it appears to.

The classical objective from Chapter 5 is a weighted sum of two terms:

where and are the teacher’s and student’s distributions softened at temperature , is the student’s logits, is the reference token, and is the mixing coefficient. Two terms, and they have genuinely different behavior, different floors, and different meanings.

The soft term measures the gap between two distributions the student is trying to make identical. Its floor is zero in principle, and in practice it is whatever residual the student cannot close given its capacity and the training budget. If the student were an exact copy of the teacher, this term would be exactly zero.

The hard term measures the gap between the student and the reference tokens. Its floor is not zero and cannot be, because natural text is not deterministic: at most positions there are several defensible continuations, and any model that assigns probability mass to all of them pays cross-entropy on whichever one actually occurred. The floor is the conditional entropy of the data, and no amount of training drives it lower without memorization.

2026-08-01T07:27:41.322951 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.0 0.2 0.4 0.6 0.8 1.0 distance travelled toward the teacher, u 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 loss, nats floor: the teacher's own cross-entropy on these tokens total h a r d   t e r m     C E ( 1 ) ( , ) α z y s o f t   t e r m     K L α T p q 2 ( ) T T both terms fall soft term is exactly 0 when the student is the teacher u = 0.62: the total is now 90 percent hard term 512 rows, V = 64, alpha = 0.5, T = 2.0; u is a straight path in logit space, not a step count
Figure 8.2 The KD loss is a sum of two terms with different floors, so its shape tells you less than the two components do: moving a student along a straight path in logit space toward its teacher drives the soft term toward zero while the hard term flattens well above it, at the cross-entropy the teacher itself pays on the reference tokens.

That difference in floors is the first practical consequence. At and moderate , the total loss late in a run is dominated by the hard term, because the soft term has decayed toward its floor and the hard term has not. So a total-loss curve that flattens out is mostly telling you about the hard term, which is mostly telling you about the irreducible ambiguity of your corpus, which is a fact about the data rather than about your student. Log the two components separately. It costs one extra number per line and it converts an ambiguous curve into two unambiguous ones.

The second consequence is more important and it is the reason this section is short. The loss is nearly useless for comparing across arms whose objectives differ. At the loss is a cross-entropy in nats. At it is a temperature-scaled KL between softened distributions. Those are not the same quantity, they do not have the same floor, and neither one being smaller than the other says anything about which student is better. Even holding fixed, changing rescales the soft term. Chapter 6 makes this argument at length and I am not going to repeat it; the operational form is that the loss belongs on your screen as a health indicator for the run you are currently watching, and nowhere near a table that compares two runs.

Within a single arm, a healthy curve falls steeply across warmup as the learning rate ramps and the student’s output distribution stops being whatever the initialization produced, then settles into a long shallow noisy decline that continues for as long as you are willing to train. Batch-to-batch variance is a substantial fraction of the step-to-step improvement, so the curve reads as a downward-drifting cloud rather than a line. Under a cosine schedule there is a small additional drop near the end, as the learning rate approaches zero and the optimizer stops bouncing around the minimum it found.

The shapes that mean something is wrong are collected with their causes in §8.10. Flat from step 0 at a value resembling an untrained model means the gradient is not reaching the parameters or the supervised positions are empty. A vertical drop to near zero in the first few steps means the student is scored against something it can match without effort, usually because the teacher and student logits are the same tensor. Sudden NaN is an overflow or a log of zero, and Chapter 2 has the arithmetic. Descending smoothly while every other diagnostic sits still is the dangerous one, because it looks exactly like success.

8.5 The diagnostics an operator watches#

The fix for an uninformative loss is to measure things that are not the loss. Chapter 16 covers evaluation properly, including benchmark subsets, diversity metrics, and contamination checking. What I want here is the small working set you compute every hundred steps on held-out data while the run is going, which is a different thing from the evaluation you do at the end: cheap enough to run repeatedly and interpretable enough to act on mid-run.

Definition

Held-out probe set

A small fixed set of examples, disjoint from the training corpus, used to compute the same diagnostics repeatedly during a run. It is not an evaluation set in the reporting sense: it is too small for a defensible final number and it gets looked at often enough that decisions made against it are no longer independent of it. Its job is to make movement visible while there is still time to react.

Put four measurements on the dashboard. Each answers a different question, and the value of having all four is that they disagree in informative ways.

8.5.1 Top-1 agreement#

Definition

Top-1 agreement

The fraction of supervised positions at which the student’s highest-probability token is the same as the teacher’s highest-probability token, on data neither model was trained on for this purpose. It measures whether the student has learned the teacher’s decisions, and says nothing about whether it has learned the teacher’s distribution.

Agreement is the most direct answer to “did anything transfer.” It is bounded in , it has a meaningful zero, and it moves visibly over a short run, which makes it the diagnostic you look at first.

Healthy values depend on the pair. A same-family teacher and student both pretrained on similar data start well above zero before any distillation, because they already agree on the easy positions; the course’s measurement of a released 360M model against a released 135M model on held-out chat completions lands in the middle of the range rather than near either end. What you watch is the movement, not the level, and the course’s expected range for a soft-target arm over a hard-label baseline at equal steps is 2 to 8 points.

Before you generalize from that number, two things could bite. Agreement is an argmax comparison, so it is blind to everything below rank 1, which means a student can gain agreement while its full distribution drifts further from the teacher’s. And it measures fidelity to the teacher rather than quality, and those come apart: Stanton and colleagues measured students that generalized better than their agreement with the teacher would predict, which is a warning against treating agreement as the objective rather than as an instrument.4

8.5.2 Forward KL on held-out data#

The same quantity the soft term of the loss optimizes, computed at on data the student did not train on. Reporting it at rather than at the training temperature matters, because it makes the number comparable across arms that used different temperatures, which the training loss is not.

This is the sensitive one. It sees the whole distribution, including the tail structure agreement discards, so it keeps moving after agreement has plateaued. Its units are nats and its scale is worth calibrating once on your own pair. The shape to watch for is a decline that flattens while agreement is still climbing, which means the student is winning the argmax on more positions without getting closer to the teacher’s distribution, and that is usually the start of the confidence problem the next diagnostic catches.

8.5.3 Expected calibration error#

Expected calibration error is the operator’s working diagnostic for whether a model’s stated confidence matches how often it is actually right. You sort the predictions into bins by the confidence the model reported, and in each bin you compare the average reported confidence against the fraction of that bin that turned out correct; ECE is the weighted average of those gaps, and zero means confidence and accuracy agree everywhere. Chapter 16 defines it properly, with the notation the calibration literature uses and the noise floor that says how small a difference the estimator can resolve. Everything this chapter needs is the direction of the number and the conditions under which two runs’ numbers can be compared at all.

A model that says it is 80 percent sure and is right 80 percent of the time is calibrated. A model that says it is 80 percent sure and is right 55 percent of the time is overconfident, and ECE puts a number on the gap.5 The binning is what makes it computable, since you can never observe the accuracy of a single prediction, only of a group.

ECE in this setting carries two complications the classification literature does not have to worry about. The number depends on the number of bins, so a comparison across arms is only meaningful if the binning is identical, and ten equal-width bins over is a reasonable default that you should state rather than assume, for reasons §16.4.1 makes precise.12 And “correct” here means “matched the reference token,” which for next-token prediction is a harsh standard: at a genuinely ambiguous position, a well-calibrated model should be unconfident, and it will be scored as wrong most of the time. So the absolute level of ECE on a language modeling probe is not comparable to an ECE on an image classifier, and what you are watching is its direction.

The direction is the point. ECE is the diagnostic that catches the failure agreement cannot see: a student that is getting confident faster than it is getting right. That happens naturally in distillation because the soft target teaches the shape of a distribution without anchoring its overall sharpness, and it is the specific reason the mixed arm with a hard-label term often calibrates better than the pure soft arm even when the two are indistinguishable on agreement. The hard term keeps pulling mass onto tokens that actually occurred, which ties confidence back to correctness.

8.5.4 Mean entropy#

The average uncertainty of the student’s next-token distribution, in nats, over supervised positions. A uniform distribution over tokens has entropy ; for the 49,152-token SmolLM2 vocabulary that is 10.80 nats, and a fully confident distribution has entropy 0. Every real model sits somewhere in between, and where it sits is a compact summary of how decisive it has become.

Entropy is the cheapest early warning available. It has no target value, which makes it useless as a score and excellent as a monitor: you watch whether it is falling, how fast, and whether the fall has an inflection. A slow decline through a run is healthy, because a model that is learning becomes more decisive. A fast collapse toward a small fraction of its starting value is a student converging on a degenerate policy, and Chapter 12 covers that failure in the on-policy setting where it is common enough to need an abort criterion.

8.5.5 Why four and not one#

2026-08-01T07:27:43.778384 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 2.47 2.58 2.68 total KD loss, nats held constant to within 1 percent by construction 0.0 0.4 0.8 top-1 agreement with teacher 0.07 0.80 0.00 0.15 0.30 expected calibration error 0.32 0 2 4 6 8 10 12 14 state index (not training steps) 0 2 4 mean entropy, nats ln V = 4.16 1.57 construction: student state k is s_k * (w_k * Z_teacher + (1 - w_k) * R) with w_k swept over [0.15, 0.75] and the sharpening scalar s_k solved so every state pays the same KD loss. 256 rows, V = 64, alpha = 0.5, T = 2.
Figure 8.3 A flat loss curve is not evidence that nothing is happening: holding the KD loss constant to within one percent, a student's agreement with its teacher rises steadily while its calibration error worsens and its entropy falls, which is a real and undesirable change that the loss cannot see.

The figure is the argument for instrumenting a run with more than one number. It is constructed rather than measured, and the construction is in the figure specification so you can reproduce it: a student is moved through a sequence of states, each chosen so the total KD loss stays essentially fixed, and the other three diagnostics are computed at each. The loss is flat and the student is changing substantially, in a direction you would want to know about.

That is the ordinary situation, not a pathological one I built to make a point. The loss is a scalar summary of a comparison between two distributions over fifty thousand tokens, and scalar summaries of high-dimensional comparisons have large level sets. The four diagnostics do not fix that, but they intersect: agreement is blind below rank 1, forward KL is not; forward KL is blind to the relationship between confidence and correctness, ECE is not; ECE is blind to overall sharpness, entropy is not. Four cheap numbers that fail differently beat one number that fails silently.

Here is the working version. What to look at: the shift is applied to logits and mask together before anything is compared, and the correctness target for ECE is the reference token at position , not the label tensor.

def diagnostics(s_logits, t_logits, next_ids, mask, n_bins=10):
    """Every argument is already shifted: row t predicts token t+1."""
    m = mask.bool()
    s = s_logits.float().log_softmax(-1)
    t = t_logits.float().log_softmax(-1)

    conf, pred = s.exp().max(-1)
    agree   = (pred[m] == t.argmax(-1)[m]).float().mean()
    fwd_kl  = ((t.exp() * (t - s)).sum(-1))[m].mean()
    entropy = (-(s.exp() * s).sum(-1))[m].mean()

    c, h = conf[m], (pred == next_ids)[m].float()
    ece = 0.0
    for i in range(n_bins):                       # ten equal-width bins on [0, 1]
        b = (c > i / n_bins) & (c <= (i + 1) / n_bins)
        if b.any():
            ece += float(b.float().mean()) * abs(float(c[b].mean() - h[b].mean()))

    return dict(agree=float(agree), fwd_kl=float(fwd_kl),
                entropy=float(entropy), ece=ece)

What that shows is how little code the working diagnostic set takes, which is the reason there is no excuse for logging only the loss. It also shows the one place the argument order bites.

Watch out

ECE needs the token that actually occurred, and in the HuggingFace convention the label tensor has had -100 written into every position the loss should ignore. Passing that tensor as the correctness target does not raise an error; it silently compares predictions against -100, which never matches, so every masked-out position counts as a confident miss and the ECE comes out enormous and meaningless. Pass the shifted input ids and let the mask do the exclusion. Chapter 7 covers the shift-and-mask convention that produces both tensors.

8.6 Three arms, and what each one controls for#

Now the experiment itself. The question a first distillation run answers is whether soft targets beat hard labels on this pair, on this corpus, at this budget. Answering it takes three runs.

Definition

Arm

One configuration in a controlled comparison. The term is from clinical trials, where each group of patients receiving a distinct treatment is an arm of the study. Within a comparison group, arms must differ in exactly one configuration key.

Table 8.2 The three arms of a first distillation experiment.

Arm Teacher Controls for
hard none 0.0 everything that is not the teacher: data, schedule, optimizer, budget
soft 1.7B 1.0 the teacher signal alone, with no hard-label anchor
mixed 1.7B 0.5 the standard recipe, and whether the anchor is worth keeping

The hard arm is the control, and it is the arm people skip. Skipping it is the most common way a distillation result turns out to be nothing: a student fine-tuned on an instruction corpus improves at instruction-following whether or not a teacher was involved, so a distilled student better than its own starting point is not evidence of anything. The comparison that means something is against a student given the identical treatment minus the teacher. Same data, steps, learning rate, seed, and schedule, with , which by the endpoint property of the objective reduces the loss to plain cross-entropy and cuts the teacher out of the gradient.6

The soft arm is the strong form of the hypothesis: if the teacher’s distribution carries usable information beyond the reference token, a student trained on nothing but that distribution should still learn, and should learn something the hard-label student did not. The mixed arm is the practical recipe and the tiebreaker; Chapter 5 derived why the interior of the range is usually where you want to be, and §8.5.3 gave the mechanism by which the hard-label term buys calibration.

Note that hard and soft differ in two keys, not one, because the hard arm has no teacher at all. That is unavoidable and it is worth being explicit that it is a two-key difference, because the arm-discipline rule from Chapter 6 says a two-key comparison cannot attribute a difference to either key. The resolution is that mixed versus soft is a clean one-key comparison over , and hard is a baseline rather than a member of that comparison group. Naming the distinction keeps you honest about what the table can support.

What would falsify the claim that distillation helps here. If soft and mixed land within noise of hard on both agreement and ECE at equal steps, with an in-family teacher, in-distribution data, and a teacher you have verified is healthy, the claim is refuted for this setup. Write that possibility down in advance, because the alternative is discovering it and then reaching for reasons it does not count. In this particular configuration a refutation should still make you suspect the setup before the theory, since the mechanism has been reproduced on too many pairs for a null on a same-family pair with in-distribution prompts to be the likeliest explanation. Section 8.10 lists the setup failures that produce false nulls.

Comparing at equal steps is the right default here because every arm processes the same batches in the same order with the same optimizer. It stops being the right default the moment the arms differ in what a step costs, which is what happens when one arm runs a teacher forward pass and another does not. Chapter 9 has the compute accounting and Chapter 18 has the matched-compute ledger; for a first run, note in the verdict that hard was cheaper per step and move on.

8.7 The capacity-gap probe, designed rather than observed#

Chapter 5 covered the phenomenon: past some ratio between teacher and student size, a larger teacher produces a worse student, and the leading explanation is that the student cannot represent the function the larger teacher is handing it, so the distillation loss stops carrying useful gradient.7 What Chapter 5 did not do is say how you would measure it on your own pair, which turns out to be a more interesting design problem than it looks.

The probe is two arms with a shared student and two different teachers:

Everything else is held: same student initialization, same , same corpus, same seed, same step budget, same schedule. The only configuration key that moves is the teacher, which makes this a clean one-key comparison.

Before the result means anything, four confounds have to go, and three of them are easy to miss.

The reference for agreement. Agreement is measured against a teacher, and there are two teachers in this experiment. Score gap-small against the 360M and gap-large against the 1.7B and you have measured two different quantities, because agreeing with a 360M model and agreeing with a 1.7B model are not equally hard. Fix one reference model, use it for both arms, and say which. The 1.7B is the defensible choice, since it is the better model and the one whose behavior you want.

Teacher competence. The claim is that a larger teacher hurts despite being better. That requires the larger teacher to actually be better on this corpus, which you should verify rather than assume, by measuring both teachers’ cross-entropy on the held-out probe set before either training run starts. It costs two forward passes. If the 1.7B is not better than the 360M on your data, you do not have a capacity-gap experiment, you have a teacher-quality experiment. Be aware that this check is necessary and not sufficient: a teacher can be better on cross-entropy and worse as a teacher, which is exactly what a label-smoothed teacher does.9

Matched budget. Both arms run the same number of steps at the same learning rate on the same batches. Beyer and colleagues showed that distillation gains keep accruing far past where supervised recipes stop, so a comparison run to different step counts is partly a patience comparison.8

The direction of the expectation. The honest prediction is not that gap-large loses. It is that gap-large wins by less than the 4.7-to-1 difference in teacher size would suggest, possibly ties, and possibly loses slightly. That 4.7 is 1.7B over 360M, and it is also the ratio of the two capacity ratios above, . A small loss for the larger teacher is the interesting outcome and is worth reporting. A large win is the surprising one, and if you see it, check the reference-model confound first, since scoring each arm against its own teacher inflates whichever arm has the teacher that is easier to agree with.

The follow-up is the patience arm, and it has a trap. Double the step budget on gap-large only and the natural comparison is the doubled run at 3000 steps against the original at 1500, which confounds patience with compute. Three readings are cleaner: the doubled run at step 1500 against the original at step 1500; the doubled run at 3000 against the original at 1500, which is the patience question; and the doubled run at 3000 against gap-small at 1500, which asks whether patience buys back what the oversized teacher cost.

The first of those three is where I part company with the course notebooks, and it is worth saying so rather than letting a reader discover two confident opposite statements on their own. Solutions 03 Exercise 4 registers that comparison as one that must match, on the grounds that the two runs share a configuration and a seed up to step 1500, and calls it a free reproducibility check where any drift would be a bug rather than a finding. That is wrong, and the reason is the schedule. Doubling max_steps under a cosine schedule stretches the schedule instead of extending it, so the doubled run spends its first 1500 steps at systematically higher learning rates than the original did and arrives at step 1500 as a genuinely different model. The two runs never shared a configuration in the sense the check requires. To recover the check, the schedule length has to be a key separate from the step budget, and you have to decide which of the two you are moving. Until then, treat drift in that comparison as expected rather than as a bug, and read only the other two.

8.8 What to record so the run is reproducible#

A checkpoint on disk is a set of weights and no context. Six months later, or six hours later on a busy day, the questions you will have are: which configuration produced this, which data, which seed, which version of the loss function, and is it the arm I think it is. None of those are answerable from a directory of tensors.

Definition

Configuration fingerprint

A short hash of a run’s full configuration dictionary, including the seed, used as part of every filename the run produces. Because a hash changes completely when any input value changes, two directories with the same fingerprint were produced by the same configuration and two with different fingerprints were not, which turns “which config made this checkpoint” from a question into a lookup.

The fingerprint does something worth spelling out. It does not tell you what the configuration was; it tells you whether two things came from the same configuration. That is the property you need most often, because the failure it prevents is comparing two checkpoints you believe differ in one key and that actually differ in three, or in none. Everyone builds the habit after the same experience, and mine was a directory holding seven checkpoints all called output_final_v2_fixed, distinguished by nothing but their modification times, with no record of which loss produced which. I could have recovered three of them by reading old shell history. I retrained the rest. The fingerprint has to include the seed, and it has to be computed over a canonical serialization, meaning sorted keys and a fixed separator, or two identical configurations hash differently because a dictionary iterated in a different order.

Definition

Run manifest

A record written alongside a run’s outputs containing the run’s name, its full configuration, its seed, its fingerprint, the identifiers of every input artifact it consumed, and the paths of every output artifact it produced, plus the versions of the libraries it ran against. The manifest is what makes a checkpoint self-describing and what makes a multi-stage pipeline auditable.

The artifacts_in field is the part people leave out and the part that matters most in a pipeline. A distillation study is a chain: a corpus is built once, a logit cache is computed from it, several students are trained from that cache, each evaluated against a probe set. If every stage records what it consumed, a change anywhere in the chain is traceable to everything downstream. If it does not, you get the situation where a corpus was quietly rebuilt between two arms and the difference gets attributed to the loss function. Chapter 10 has the corpus fingerprinting that makes this mechanical, Chapter 18 the version a reviewer would accept.

import hashlib, json, platform, subprocess, torch

def fingerprint(cfg, seed, n=8):
    blob = json.dumps({**cfg, "seed": seed}, sort_keys=True, separators=(",", ":"))
    return hashlib.sha256(blob.encode()).hexdigest()[:n]

def moved(a, b):
    return {k for k in a.keys() | b.keys() if a.get(k) != b.get(k)}

def manifest(name, cfg, seed, inputs, outputs):
    head = subprocess.run(["git", "rev-parse", "HEAD"],
                          capture_output=True, text=True).stdout.strip()
    return {"name": name, "config": cfg, "seed": seed,
            "fingerprint": fingerprint(cfg, seed),
            "artifacts_in": inputs, "artifacts_out": outputs,
            "torch": torch.__version__, "python": platform.python_version(),
            "commit": head}

BASE  = dict(student="SmolLM2-360M-Instruct", lr=3e-5, T=2.0, max_steps=1500)
soft  = {**BASE, "teacher": "SmolLM2-1.7B-Instruct", "alpha": 1.0}
mixed = {**BASE, "teacher": "SmolLM2-1.7B-Instruct", "alpha": 0.5}

assert moved(mixed, soft) == {"alpha"}, moved(mixed, soft)
print(fingerprint(soft, 17), fingerprint(mixed, 17))

What that proves is that the arm discipline is checkable by a machine. The assert on moved is the enforcement: it is an executable statement of what the comparison claims to isolate, and it fails at pre-flight time if someone edits one arm’s learning rate and forgets the other.

8.8.1 Seeds, and what they do not buy#

Setting a seed makes the run reproducible in the parts that are pseudorandom: parameter initialization for anything not loaded from a checkpoint, data shuffling, dropout masks, and any sampling. Set it everywhere at once, meaning Python’s own generator, NumPy’s, and PyTorch’s for both CPU and GPU, because setting one of the three is a common and confusing partial fix.

What a seed does not buy is bitwise identity across runs. Reductions on a GPU happen in nondeterministic order and floating-point addition is not associative, so two runs with the same seed on the same machine can diverge in the last bits of a sum and then, over fifteen hundred steps, in the third decimal of a metric. So “same seed, same config, same numbers” is a check with a tolerance rather than an exact one, and a difference between two arms smaller than the difference between two same-seed runs of one arm is not a difference. Chapter 6 has the seed-variance argument and Chapter 18 the minimum detectable effect arithmetic that follows from it. For a first run, do the cheap version: run one arm twice and look at the spread.

8.8.2 Checkpointing and resume#

Save periodically rather than at the end alone, and save enough to actually resume. A resume that restores the weights but not the optimizer state is not a resume: Adam’s moment estimates take a few hundred steps to warm up, so a restart from weights alone puts a large transient into the middle of your run, and the resumed curve will have a visible bump that you will later mistake for a finding. The things that have to be saved together are the weights, the optimizer state, the scheduler state, the step counter, and the data iterator position.

The scheduler is the one that catches people. A cosine schedule computes the learning rate from the step counter and the total budget, so restoring weights and restarting the counter at zero gives you a second warmup and a second decay, which is a different trajectory from the one you intended. Without schedule state in the checkpoint, a resume is a new experiment with your old weights as an initialization, which is a legitimate thing to do and not what you asked for.

8.9 Writing the verdict#

Part C is the point of the whole exercise, and it is three sentences.

The first says whether the run did what the design claimed. The second gives the evidence, with numbers and the diagnostic they came from. The third says what the next decision is. Written out for the arms in §8.6, a verdict reads something like: soft targets beat hard labels on this pair, by roughly four points of top-1 agreement against the 1.7B reference at 1500 steps, with the mixed arm and the pure soft arm inside a point of each other on agreement and the mixed arm clearly ahead on calibration error; the evidence is the held-out probe set measured every hundred steps, and the separation appears by step 400 and holds; the next run keeps and moves the temperature, because the temperature was inherited rather than chosen and is the largest untested assumption in the configuration.

What makes that a verdict rather than a summary is three properties. It commits to a direction instead of describing the table. It names the diagnostic and the reference, so a reader knows what the number is a number of. And it ends with a decision, which forces the run to have been worth running.

Here is the claim this section exists to defend. The ability to render this judgment quickly is the skill. Not writing a training loop, which is fifteen lines and is written for you in every library. Not knowing what temperature does, which is Chapter 5 and a page of calculus. What separates someone who has run distillation from someone who has read about it is that after the run finishes, one of them looks at four curves and says “that worked, here is the evidence, here is what I am changing” in under two minutes, and the other looks at the same four curves and does not know what to say.

That gap comes from having decided in advance what the run was supposed to show, which turns the verdict from an interpretation problem into a comparison. If you wrote down the expected ranges and the failure signatures before launching, afterward you are checking a prediction, which is fast. If you did not, you are staring at data looking for a story, which is slow and which produces stories that happen to be flattering.

There is a diagnostic hidden in the exercise. If you cannot write the three sentences from your logged artifacts in two minutes, the run was not instrumented well enough, and that is itself the finding: the thing you needed was not measured, or was measured and not saved, or was saved somewhere you cannot find. Fix the instrumentation and the next run costs the same and tells you more.

8.10 First-run failures and their signatures#

Most first runs fail, and they fail in a small number of ways. Here they are in roughly the order I would check them, each with what it looks like from the outside, because the diagnostic skill is recognizing a shape rather than remembering a cause.

Table 8.3 First-run failure signatures.

Signature Most likely cause Cheapest discriminating check
Held-out KL flat from step 0; agreement pinned near zero Mask or shift wrong: training on prompt positions, or on unshifted positions Recompute the mask audit on the exact tensors the loop consumes, not on the ones the pre-flight built
Loss descends; every diagnostic sits still The loss and the diagnostics are looking at different positions or different models Log the count of supervised positions in both paths and compare
Soft arms indistinguishable from hard Teacher in train mode, or teacher not actually reaching the loss, or too low for a peaked teacher Run the teacher twice on one batch and check the logits are identical
Loss falls, agreement rises, ECE rises with it Student getting confident faster than it is getting right Lower toward the hard term, or lower
Loss falls a hundred times slower than expected Missing factor on the soft term Compare soft-term gradient norms at two temperatures
NaN in the first few dozen steps Overflow or a log of zero in the loss Confirm the loss math runs in fp32 over bf16 model outputs
Everything improves smoothly and unusually far Evaluation data overlapping the training corpus Re-measure on a fresh slice the run has never touched

Four of them deserve more than a table row can hold.

Silent misalignment. Chapter 7’s shift-and-mask convention has to be applied to the student logits, the teacher logits, and the mask together, and it is possible to apply it to two of the three. Off by one in the shift trains the student to predict the token at the current position rather than the next one, a task it can partly do, which is why the loss still descends. A mask wrong in the other direction, supervising prompt positions, teaches the student to reproduce prompts. Neither raises an exception. This is first on the list because the pre-flight audit can pass and the run can still be wrong, if anything re-tokenizes or re-slices between the audit and the loop. Audit the tensors the loop consumes.

A missing . Chapter 5 derived why the soft term carries a factor of : softening both distributions by shrinks the soft-loss gradient by roughly , and the factor undoes it. Leave it out at and the soft term contributes about a quarter of the gradient it should; at , about a sixteenth. The signature is a run that looks like a hard-label run with extra steps, because the soft term is present and too weak to matter. It is most confusing at , where the whole objective has been scaled down and the run presents as a learning-rate problem rather than a loss-function one. The check is soft-term gradient norms at two temperatures: with the factor they stay within a small factor of each other, without it they differ by roughly the square of the temperature ratio.

A learning rate inherited from fine-tuning. This one is subtler than it sounds and I want to avoid overclaiming. The loss is a weighted sum of two terms, so the effective step size depends on the scale of whichever term dominates. A rate tuned at was tuned for a cross-entropy, and at you are optimizing a temperature-scaled KL, a different function with different curvature. Whether that wants a larger or smaller rate is an empirical question about your pair, not something I can tell you in advance. What I can tell you is that it is not a parameter you inherit for free, and that the two failure shapes are distinguishable: too high shows up as agreement plateauing early while ECE climbs, too low as all four diagnostics moving the right way at a rate that will not finish inside your budget. The factor is what keeps this from also being a function of temperature, which is its second reason for existing.

Evaluation overlapping training. If probe examples also appear in the training corpus, every diagnostic improves for a reason unrelated to distillation, and the improvement is smooth and large and looks like success. What catches people is not careless splitting; it is a corpus rebuilt between arms, a probe set drawn from the same stream without recording where the split fell, or near-duplicates rather than exact matches. Chapter 16 covers detection properly, along with the reporting-grade evaluation that a probe set is deliberately not,13 including the false positives that chat templates generate when the same scaffolding tokens appear in every example. The first-run version is simpler: fix the split once, record it in the manifest, and if a result looks too good, re-measure on a slice the run has never seen.

8.11 Where this lands in the labs#

Lab 03 is where the training loop finally executes, and its Part A is the piece worth running even if you never flip the flag: the memory plan with its negative control, the arm-discipline assertions over five configurations, the re-verification of the loss at both endpoints and of the gradient-norm ratio, and the four-point mask audit on the real corpus, all in a couple of minutes on a laptop. Part B is gated deliberately, because a checkmark produced on build hardware would be a false assurance about yours. Part A also writes the tokenized corpus Labs 04 and 05 consume, which is §8.8’s artifact chaining in its smallest form. The solutions notebook adds the design work this chapter argues for and does not have room to execute, including the three-way comparison that keeps the patience arm from confounding patience with compute.

8.12 Exercises#

  1. A colleague wants to distill a 32B teacher into a 4B student on the reference machine and is told it does not fit. Using only §8.2, give three distinct changes, each of which makes the configuration fit with at least 20 percent headroom, and for each one state what it costs in quality, in wall clock, or in what the student is learning to match. Rank them and defend the ranking.

  2. A run logs the following. Step 0: loss 4.11, agreement 0.312, held-out KL 2.94, entropy 3.81, ECE 0.061. Step 1400: loss 1.23, agreement 0.311, held-out KL 2.94, entropy 3.80, ECE 0.061. The loss fell by a factor of three and every diagnostic is unchanged to three decimal places. Say what went wrong. Name at least two candidate causes, say which one the exactness of the diagnostic agreement favors, and give the single cheapest check that would separate them.

  3. A soft-target arm and a hard-label arm finish within half a point of each other on agreement, with a same-family teacher, in-distribution prompts, and a teacher whose held-out cross-entropy you have verified is better than the student’s. Enumerate every cause you can think of, put them in the order you would check them, and for each one give a check that costs less than a training run. At least one of your causes should be a property of the teacher rather than a bug in your code.

  4. A capacity-gap probe reports that gap-large beat gap-small by 9 points of top-1 agreement. Section 8.7 says a large win for the bigger teacher is the surprising outcome. Identify the most likely confound, explain the mechanism by which it inflates the number, and describe the corrected measurement. Then say what result the corrected measurement would have to produce before you would report the capacity gap as absent on this pair.

  5. Take the four diagnostics in §8.5 and construct, for each one, a change to a student that moves that diagnostic in the “good” direction while making the student worse by any reasonable standard. You may describe the change in words rather than implementing it. Then say which pair of diagnostics is hardest to fool simultaneously, and why.

  6. You are planning a distillation run across two different tokenizer families, so Chapter 7’s position-wise alignment does not exist. Go through the pre-flight checks this chapter describes and sort them into three groups: those that transfer unchanged, those that need modification, and those that no longer mean anything. For the middle group, say what the modified check would assert.

  7. Write the three-sentence Part C verdict for the following run, then say what is missing from the log that would have made the verdict stronger. Arms hard, soft, and mixed, all at 1500 steps on the same corpus and seed. Final agreement against the 1.7B reference: 0.34, 0.39, 0.38. Final ECE: 0.071, 0.094, 0.063. Final held-out KL: 2.10, 1.44, 1.51. Entropy at the end: 2.9, 2.2, 2.6. No second seed was run.



  1. Edward J. Hu, Yelong Shen, Phillip Wallis, Zeyuan Allen-Zhu, Yuanzhi Li, Shean Wang, Lu Wang, and Weizhu Chen, “LoRA: Low-Rank Adaptation of Large Language Models,” arXiv:2106.09685 (2021), ICLR 2022. https://arxiv.org/abs/2106.09685 The memory argument in the paper is about optimizer state specifically, which is the twelve of sixteen bytes this chapter breaks out. 

  2. Tim Dettmers, Artidoro Pagnoni, Ari Holtzman, and Luke Zettlemoyer, “QLoRA: Efficient Finetuning of Quantized LLMs,” arXiv:2305.14314 (2023), NeurIPS 2023. https://arxiv.org/abs/2305.14314 

  3. Woosuk Kwon, Zhuohan Li, Siyuan Zhuang, Ying Sheng, Lianmin Zheng, Cody Hao Yu, Joseph E. Gonzalez, Hao Zhang, and Ion Stoica, “Efficient Memory Management for Large Language Model Serving with PagedAttention,” arXiv:2309.06180 (2023), SOSP 2023, 611-626. https://doi.org/10.1145/3600006.3613165 The paper’s premise is that contiguous allocation of the key-value cache wastes a large fraction of memory to fragmentation and over-reservation, which is the serving-side version of the effect described here. Chapter 15 covers the system. 

  4. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945 

  5. Chuan Guo, Geoff Pleiss, Yu Sun, and Kilian Q. Weinberger, “On Calibration of Modern Neural Networks,” arXiv:1706.04599 (2017), ICML 2017. https://arxiv.org/abs/1706.04599 The binned estimator of expected calibration error used throughout this book is the one stated there, and the paper is also the source of the observation that larger and more accurate networks tend to be less calibrated than smaller ones, which is worth holding onto when a distilled student’s ECE beats its teacher’s. 

  6. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015). https://arxiv.org/abs/1503.02531 Chapter 5 derives the objective and its endpoint behavior; this chapter uses only the endpoints. 

  7. Jang Hyun Cho and Bharath Hariharan, “On the Efficacy of Knowledge Distillation,” arXiv:1910.01348 (2019), ICCV 2019. https://arxiv.org/abs/1910.01348 

  8. Lucas Beyer, Xiaohua Zhai, Amélie Royer, Larisa Markeeva, Rohan Anil, and Alexander Kolesnikov, “Knowledge distillation: A good teacher is patient and consistent,” arXiv:2106.05237 (2021), CVPR 2022. https://arxiv.org/abs/2106.05237 Their central finding is that distillation keeps paying off at training durations well beyond what supervised recipes use, which is why any comparison between arms has to fix the step budget before it can claim anything about the objective. 

  9. Rafael Müller, Simon Kornblith, and Geoffrey Hinton, “When Does Label Smoothing Help?” arXiv:1906.02629 (2019), NeurIPS 2019. https://arxiv.org/abs/1906.02629 The relevant warning for a first run is that teacher quality measured by accuracy and teacher quality measured by usefulness for distillation are different quantities, so verifying that your larger teacher is “better” does not by itself verify that it is a better teacher. Chapter 5 has the mechanism. 

  10. Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big, Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737 The 1.7B, 360M, and 135M instruction-tuned checkpoints of this family are the course’s teacher and student pool, and they share a 49,152-token vocabulary, which is what makes the position-wise comparisons in this chapter possible without any alignment work. 

  11. The reference machine’s 128 GB figure is a decimal quantity, so the bytes-per-parameter arithmetic in §8.2 is in decimal gigabytes throughout, matching the course’s full_ft_gb(p) = 16p and infer_gb(p) = 2p conventions. Allocators typically report binary gibibytes, which are 7.4 percent larger, so a plan and a runtime report of the same run will disagree by that much even when both are correct. Appendix B collects the machine’s parameters. 

  12. Matthias Minderer, Josip Djolonga, Rob Romijnders, Frances Hubis, Xiaohua Zhai, Neil Houlsby, Dustin Tran, and Mario Lucic, “Revisiting the Calibration of Modern Neural Networks,” arXiv:2106.07998 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.07998 A useful corrective to reading Guo et al.’s conclusions as universal: the relationship between model size and calibration depends on architecture and training recipe, so treat ECE as a quantity you track on your own pair rather than one whose expected level you can look up. 

  13. Leo Gao et al., “The Language Model Evaluation Harness,” Zenodo v0.4.3 (July 2024), https://doi.org/10.5281/zenodo.12608602 Chapter 16 uses this for the reporting-grade evaluation that the held-out probe set of §8.5 is deliberately not. Version-pin whatever you run, since task definitions change between releases and a benchmark number without a version is not a number anyone can reproduce. 

  14. Rishabh Agarwal, Nino Vieillard, Yongchao Zhou, Piotr Stanczyk, Sabela Ramos, Matthieu Geist, and Olivier Bachem, “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649 Relevant here only for the memory note: on-policy methods generate inside the training loop, so they carry a key-value cache that the teacher-forced runs in this chapter do not. Chapter 12 budgets for it. 

Part III · Making It Real

9

The Economics of Teacher Compute

Here is a plan. You have a 32-billion-parameter teacher, a 1.7-billion-parameter student, and a corpus of two thousand prompts. You are going to have the teacher write a completion for each prompt, roughly 256 tokens long, and fine-tune the student on the result. It is the simplest method in the book, it needs no logits, and the code is forty lines.

The question nobody asks until the run is already going is: when does it finish? Not approximately. To the hour, before anything loads, from numbers you can look up on a spec sheet and a page of arithmetic. On the machine this book uses, the answer is thirty-four hours of single-stream generation, and if you had priced it first you might have chosen a different teacher, a different corpus size, or a different method entirely.

That is what this chapter is for. By the end of it you should be able to take any distillation plan, on any machine, and produce a wall-clock estimate and a disk estimate before writing a line of training code. The estimate will not be exact. It will be right about the thing that matters, which is whether a plan costs minutes, hours, or days, and that distinction decides more project outcomes than any hyperparameter.

Chapter 8 did the memory side of this for training: bytes per parameter, what an optimizer costs, when a configuration stops fitting. Memory tells you whether a run is possible. This chapter tells you how long it takes, which is a different question with a different bottleneck, and on the reference machine the two answers point in opposite directions often enough that you have to hold both.

9.1 Two ways to run a transformer#

Chapter 1 previewed the distinction between prefill and decode in one paragraph because the taxonomy needed it. Now it gets derived.

Take a transformer with causal attention and a sequence of tokens . The model computes, at each position , a distribution over the next token conditioned on everything up to and including . Write that as . Causal attention means position ’s computation reads positions through and nothing after.

Now consider two situations that look similar and are not.

Situation one: the text already exists. You have all tokens in hand, and you want the model’s distribution at every position. Because position depends only on positions , and because you already know those tokens, nothing has to wait for anything. Every position can be computed at the same time. The whole sequence goes through the network once, as a matrix of shape rather than a vector of shape , and every weight matrix in the model is applied to all positions in a single matrix multiplication. One pass over the weights, distributions out.

This is what happens when you score a corpus with a teacher to build a logit cache. It is what happens when a teacher scores a student’s rollouts. It is also, incidentally, what happens on the forward pass of ordinary training, which is why training is not bottlenecked the way generation is.

Definition

Prefill

Running a model forward over a sequence of tokens that already exist, computing the output distribution at every position in one pass. Because causal attention makes each position depend only on earlier positions, and because all of those tokens are known in advance, every position is computed in parallel. The weights are read from memory once for the entire sequence. Chapter 1 gave this as “compute bound, and fast”; §9.1.1 derives why.

Situation two: the text does not exist yet. You have a prompt and you want the model to write 256 new tokens. Token 1 of the continuation comes from a forward pass over the prompt. Token 2 requires a forward pass over the prompt plus token 1, and you could not have started it before token 1 existed, because token 1 is an input to it. Token 3 waits on token 2. There is no reordering that removes this. The dependency is in the definition of the task: an autoregressive model generates by conditioning each token on the ones it already produced.

So generating tokens takes forward passes, in sequence, each producing exactly one token. Every one of those passes reads every weight the model uses.

Definition

Decode

Generating tokens one at a time, each conditioned on the tokens generated before it. Because token cannot be computed until token exists, generated tokens require sequential forward passes, and each pass reads the model’s entire weight set out of memory to produce a single token. Chapter 1 gave this as “memory-bandwidth bound, and on large models, slow”; §9.2 derives the ceiling.

The asymmetry is stark once you count weight reads. Prefill over a 384-token sequence: one weight read, 384 output distributions. Decode of 384 tokens: 384 weight reads, 384 output distributions. Same number of distributions, 384 times the memory traffic.

9.1.1 Arithmetic intensity, and which resource runs out#

The reason this matters is that a computer has two separate resources it can exhaust, and they exhaust at very different rates.

Take one weight matrix of shape , applied to positions at once. The multiplication costs floating-point operations: one multiply and one add per weight per position. Reading out of memory costs bytes, where is the bytes per parameter of whatever dtype you stored it in. Divide:

The dimensions cancel. Intensity depends on , the number of positions sharing a single weight read, and on nothing else about the layer. In bf16, where , the intensity is exactly floating-point operations per byte moved.

Definition

Arithmetic intensity

The number of floating-point operations a computation performs per byte it moves out of memory. For a weight matrix applied to positions in a dtype of bytes per parameter, the intensity is , independent of the matrix’s shape. It is the quantity that decides whether a computation is limited by the machine’s arithmetic units or by its memory bus.

A machine has a peak arithmetic rate in operations per second and a memory bandwidth in bytes per second. Feeding the arithmetic units at their peak requires supplying operations for every byte the bus delivers. Below that intensity, the arithmetic units sit idle waiting on memory; above it, the bus sits idle waiting on arithmetic. That ratio is the machine’s balance point, and you compute it for your own hardware by dividing two numbers off the spec sheet.

For accelerators of this generation the balance point lands in the hundreds of operations per byte. Put decode next to it: single-stream decode has , so intensity 1 in bf16, which is two to three orders of magnitude below balance. Put prefill next to it: a batch of 8 sequences of 384 tokens has , an order of magnitude above balance. The two modes of running the same model sit on opposite sides of the same line, and that is the whole reason the rest of this chapter exists.

Definition

Memory bandwidth bound

A computation whose running time is set by how fast bytes can be moved out of memory rather than by how fast arithmetic can be performed on them. Formally, a computation whose arithmetic intensity is below the machine’s balance point . Autoregressive decode is the canonical example: it performs a small, fixed amount of arithmetic per weight byte read, so its speed tracks memory bandwidth and is almost insensitive to how fast the arithmetic units are.

9.2 The decode roofline#

Once you accept that decode is memory bandwidth bound, the ceiling follows in one line.

Each decode step reads every weight the model uses. Call that quantity , the bytes read per step. The memory bus delivers bytes per second. So the number of steps per second cannot exceed , and each step produces one token per sequence in flight. For a single sequence:

where is the number of parameters the model reads per token and is bytes per parameter.

Definition

Roofline

An upper bound on throughput derived from the scarcest resource a computation consumes. For autoregressive decode on bandwidth-limited hardware the roofline is memory bandwidth divided by bytes read per token. It is a bound, not a prediction: real throughput lands under it, because weight reads are not the only cost. Its two honest uses are checking that a measurement is possible and comparing configurations to each other.

On the reference machine, GB/s. Take a 1.7-billion-parameter model stored in bf16, two bytes per parameter.1 It reads

per decode step, which gives

Now do the same for a 32-billion-parameter teacher in the same dtype.2 It reads GB per step, and

Four tokens per second. Not four thousand. A single sentence of forty tokens takes ten seconds, and the machine is working at full memory bandwidth the entire time.

Pull three things out of that arithmetic before moving on.

The ceiling is linear in model size and in bytes per parameter, and in nothing else. Nineteen times the parameters, one nineteenth the ceiling. That is why the ordering of methods by cost holds up even when the absolute numbers are loose: ratios between configurations survive approximations that absolute predictions do not.

Bytes per parameter is a lever you can pull. Storing weights in a 4-bit format puts , which quadruples the ceiling exactly, because appears once in the denominator. A 32B teacher at 4 bits reads 16 GB per step and ceilings at tokens per second. What the 4-bit formats are, how they choose which weights to protect, and what they cost in quality are Chapter 15’s subject.34 For pricing purposes the only thing you need is the number of bytes each parameter occupies at run time.

It is a ceiling, and real decode lands under it. The bound counts weight reads and nothing else. Attention has to be computed. The KV cache has to be read, and §9.7 shows that its traffic is not always small. Kernels have launch overheads, schedulers have gaps, and a dtype whose dequantization kernels are immature on your platform will pay extra arithmetic per weight read that the bound does not model. Treat the roofline as a bound and a comparison tool. Using it as a prediction is how people end up surprised in the wrong direction.

There is one more use, which Chapter 15 develops properly: a measured decode rate above the roofline is a contradiction, and the resolution is always that the bytes-per-token number was wrong rather than that physics bent. Sparse architectures read only part of their weights per token, so a checkpoint’s size on disk can be a poor estimate of what a step actually moves.

2026-08-01T07:27:45.155469 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.1 0.3 1 3 10 30 100 model size, billions of parameters 1 10 100 1,000 decode ceiling, tokens/s roughly reading speed bf16, 2 bytes/param 8-bit, 1 byte/param 4-bit, 0.5 bytes/param 0.36B student 379.2 tok/s 1.7B 80.3 tok/s 8B 17.1 tok/s 32B teacher 4.3 tok/s roofline at 273 GB/s: tok/s = bandwidth / (params x bytes per param). A bound, not a measurement.
Figure 9.1 The decode ceiling falls as one over model size, so the same 273 GB/s of bandwidth that supports interactive generation from a 360M student supports four tokens per second from a 32B teacher.

Table 9.1 The decode roofline at 273 GB/s, and what it costs to generate a 524,288-token corpus one stream at a time. The corpus is Lab 06’s: 2,048 prompts at 256 new tokens each.

Model Bytes read per step (bf16) Ceiling, tokens/s Hours for 0.52M tokens
0.36 B 0.72 GB 379.2 0.38
1.7 B 3.4 GB 80.3 1.81
8.0 B 16.0 GB 17.1 8.54
32.0 B 64.0 GB 4.3 34.14

Every cell is two divisions. The 1.7B row: tokens per second, and seconds, which is hours. The 32B row: , and seconds, which is 34.14 hours. Redo them for your own machine by changing one number.

Lab 06 asserts three things about this table rather than printing it and moving on: that the 1.7B row comes in under 2.5 hours, that the 32B row is more than ten times the 1.7B row, and that the 32B row exceeds 24 hours. The third assertion is the one that changes behavior. A cost that crosses a day is a cost you pay once and archive, not one you re-incur every time you rerun an experiment.

9.3 Batching, and where it stops helping#

The roofline in §9.2 is per step, and that phrasing is load-bearing. It is not per token.

A decode step reads the weights once. If sixteen sequences are being generated at the same time, that single weight read serves all sixteen, because the same matrices multiply sixteen different activation vectors. The step takes about as long as it would have for one sequence, and produces sixteen tokens instead of one. Aggregate throughput scales with batch size while the weight traffic stays flat.

So the aggregate ceiling for a batch of sequences is

and everything interesting is in how , the bytes moved per step, grows with . If the weights were the only thing read, would be constant and throughput would scale linearly forever. Two things stop that.

The KV cache is read every step too. Each sequence in the batch carries its own store of attention keys and values for every token it has seen, and attention at the current position reads all of it. So

where is the sequence length and is the KV bytes per token, which §9.7 derives. The first term is amortized across the batch; the second is not. Substituting,

Batching does not buy unbounded throughput. It buys you the way from the weight-dominated regime to the KV-dominated one, and the KV-dominated regime has its own ceiling that batching cannot raise. The batch size where the two terms are equal is

and past it, doubling the batch stops doubling throughput.

Here is a small observation that makes easy to reason about without computing it. The weights are resident in memory once and read once per step. The KV cache is resident in memory once and read once per step. So the batch size at which KV traffic equals weight traffic is exactly the batch size at which the KV cache occupies as much memory as the weights do. If your teacher’s weights already take half of your memory, you cannot reach at all; you will run out of room to hold the cache long before the traffic crosses over. If your student’s weights take 3 GB of 128, you can go far past , and the returns will visibly flatten when you do.

Eventually you become compute bound again. Arithmetic intensity for a decode step with batch is , or operations per byte in bf16. Once exceeds the machine’s balance point, the arithmetic units are the constraint and further batching does nothing for throughput. On the reference machine, with its generous capacity and modest bandwidth, KV memory usually binds first. On a machine with the opposite profile it might not, which is the sort of thing that flips a design decision between hardware classes.

2026-08-01T07:27:45.992780 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1 2 4 8 16 32 64 128 256 512 batch size 0 1,000 2,000 3,000 4,000 5,000 aggregate decode throughput, tokens/s KV-traffic ceiling, 5,424 tok/s: no batch size beats it B* = 67.6 KV traffic equals weight traffic roofline x 0.5 efficiency: where measurements land B = 1: 79 tok/s B = 8: 574 model, not measurement: tok/s(B) = B x 273 / (3.4 + B x 0.0503), a 1.7B bf16 model at a 1,024-token context with 24 layers x 8 KV heads x 64 head dim in bf16, 49,152 bytes of cache per token
Figure 9.2 Aggregate decode throughput rises steeply with batch size while weight reads dominate, then flattens toward a KV-traffic ceiling that no batch size can exceed.

That has two consequences you will feel. First, per-stream latency does not improve with batching; it stays flat or degrades slightly. Batching trades latency for throughput, which is the right trade when you are generating a corpus and the wrong one when a user is waiting. Second, the wall-clock improvement from batching is real but smaller than the batch size suggests, because of the KV term and because of the ordinary overheads the roofline ignores. Lab 06 reports that generating 16 to 32 prompts at once divides real wall clock by roughly an order of magnitude, not by 16 to 32. Use that as the discount when you convert a single-stream price into a batched one, and then measure your own.

9.4 The prefill side, and the ratio#

Prefill obeys the same bandwidth arithmetic, but its numerator is different in a way that changes the answer by two orders of magnitude. A prefill pass over tokens reads the weights once and produces distributions, so the effective tokens per second is times the step rate rather than one times it. There is no sequential dependency to serialize, so a single pass over a long sequence is one trip through the weights.

The number the course carries for this machine class is a published benchmark of a 20-billion-parameter model in MXFP4, a 4-bit block-scaled format in which a block of weights shares a small exponent scale so the per-weight cost lands near half a byte: roughly 2,053 tokens per second of prefill against 49.7 tokens per second of decode. The ratio is

which the course rounds to 40 to 1 and designs around.

Be precise about what that number is. Somebody measured one model, in one dtype, on one hardware class, through one serving stack, and published the result. It will not reproduce on your box to two significant figures, and nothing about it is a property of transformers. Lab 08 exists in part to replace it: you build your own prefill and decode curves and the borrowed number becomes the null hypothesis your measurement is checked against. What survives across hardware is the shape of the claim: prefill processes many positions per weight read, decode processes one, and the gap between them is large. Expect something in the tens; be suspicious of a measured ratio under 10 (which usually means prefill is throttled by a batching limit) and of one over 100 (which usually means decode is starved by something in the kernel path).

Now price prefill. At 2,053 tokens per second, one million tokens takes

That is the whole cost of scoring a million-token corpus with a teacher. Eight minutes. Compare it against the 1.81 hours it takes the same machine to generate half a million tokens from a much smaller model, and the design pressure this chapter is about becomes visible.

Lab 04 does the concrete version for the course’s own corpus. The corpus is 4,096 sequences of 384 positions, which is

and at a round 2,000 tokens per second of prefill that is

to build a complete teacher logit cache over the corpus. Note the assumption Lab 04 is making and making conservatively: it applies a prefill rate measured on a 20B-class model to a 1.7B teacher, which understates what the smaller model can do. Conservative in the right direction, and worth copying as a habit. When you borrow a throughput number from a different model, borrow it in the direction that makes your plan look worse.

Set the two rates for the same 1.7B teacher side by side: 2,000 tokens per second of prefill against a decode roofline of 80.3. That is a factor of roughly 25 between the two ways of getting the same teacher to look at the same number of tokens, and it is the entire economic argument for the cached-logit pipeline Chapter 10 builds.

9.5 Sorting workloads by who decodes#

Every distillation workload in this book is some mix of prefill and decode performed by some model. The two questions that determine its cost are which mode it runs in and which of the two models does the work. That is a four-cell table, and Chapter 1 showed it as a preview. Here it is with the justification attached.

Table 9.2 Distillation workloads sorted by who decodes, with the arithmetic behind each cost.

Workload Mode Who runs Cost on the reference machine
Teacher scores a fixed corpus to build a logit cache Prefill Teacher, no generation 13.1 minutes for 1.57M positions, paid once
Teacher scores student rollouts Prefill Teacher, no generation Same rate; scales with rollout volume
Student generates rollouts for on-policy training Decode Student, the small model 379 tokens/s ceiling at 360M, 80 at 1.7B
Teacher generates a corpus for sequence-level KD Decode Teacher, the large model 4.3 tokens/s ceiling at 32B; 34 hours per 0.52M tokens

Row by row.

Cache building. The teacher runs forward over text that already exists and you keep its output distributions.5 Nobody generates. The corpus is fixed, so the teacher’s outputs on it are fixed, which is what makes the result cacheable at all. Pure prefill, priced in minutes, and paid exactly once per corpus and teacher pair.

Rollout scoring. The student has produced some text, and the teacher needs to say what it would have done at each position of that text. The text exists by the time the teacher sees it, so this is prefill again. The teacher never generates a token in on-policy distillation.6 This is the row people get wrong, and §9.5.1 is about why.

Student rollouts. The student generates, which is decode, but the student is the small model. A 360M student in bf16 reads 0.72 GB per step and ceilings at 379 tokens per second single stream, which batching multiplies further. Moderate cost, and it sits inside the training loop where you pay it every step rather than once.

Teacher corpus generation. The teacher generates, which is decode, and the teacher is the large model.7 Both factors point the wrong way at once. This is the one expensive pattern, and it is expensive by two orders of magnitude rather than by a factor that careful engineering can close.

9.5.1 The inversion, and the mistake that produces it#

Field note

I had this backwards, and the way I had it backwards is instructive enough that I keep it in the course rather than quietly fixing it.

The reasoning went like this. Prefill runs about forty times faster than decode on this machine. On-policy distillation generates text inside the training loop. Off-policy distillation trains on a fixed corpus and generates nothing. Therefore on-policy distillation is the expensive path on bandwidth-limited hardware, and the right design is a long off-policy phase followed by a short on-policy one to finish.

Every sentence in that chain is true except the “therefore.” The error is a category mistake rather than an arithmetic one, which is why checking the arithmetic would not have caught it. I tracked whether generation happens and did not track which model generates.

In on-policy distillation, the rollouts come from the student. The student is the small model, that is the entire premise of distillation, and the teacher’s only job is a scoring forward pass over text the student already produced. So on-policy distillation is small-model decode plus large-model prefill, and both of those are affordable. A 1.7B student in bf16 is 3.4 GB of weights, capping decode near tokens per second before batching, and batching spreads one weight read across the whole rollout batch.

The genuinely expensive pattern is the one my chain never considered: sequence-level KD, where the teacher generates the corpus. A 32B teacher in bf16 is 64 GB, which caps decode near tokens per second before anything else binds. Same operation as the student’s rollouts, nineteen times the weight traffic per token, and a plan that finishes in days instead of hours.

The general rule I extracted, and the one worth carrying to hardware I have not seen: cost is set by the product of the mode and the model, and the model term is the one people drop. When someone tells you a method is expensive, ask which model is doing the expensive thing. Half the time the answer reverses the claim.

The inversion is specific to this hardware profile, which is generous in capacity and modest in bandwidth. A machine with the opposite profile, where a large model barely fits but the bus is fast, prices these four rows differently and might order them differently. Do not inherit my ordering. Inherit the method that produced it.

The consequences of that ordering, on this machine, are three design rules. Prefer cached-logit off-policy training and on-policy distillation, because both lean on prefill and small-model decode. Treat a teacher-generated corpus as a purchased asset rather than a computation: generate it once and archive it, use someone else’s published generation, or rent an hour of different hardware for that phase alone.8 And never pay for large-teacher decode twice, which is a statement about bookkeeping discipline as much as about hardware.

2026-08-01T07:27:46.867207 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.05 0.1 0.5 1 5 10 50 single-stream wall clock, hours (log scale) cached-logit off-policy teacher 1.7B prefills once on-policy student 360M generates, teacher 1.7B scores sequence-level KD 1.7B teacher generates sequence-level KD 32B teacher generates 0.07 h 0.46 h 1.81 h 34.14 h 0.38 h student decode + 0.07 h teacher prefill a day: this is a purchase, not a computation same corpus, x 469 the wall clock 524,288 tokens at 273 GB/s, prefill at 2,000 tok/s. Single-stream roofline prices: batching compresses all four bars, the decode-heavy ones most. teacher prefill student decode teacher decode
Figure 9.3 Four distillation plans over the same 524,288-token corpus, priced in single-stream wall clock; the plan where the large teacher generates towers over the other three by roughly two orders of magnitude.

9.6 A pricing function#

Here is the arithmetic of §9.2 and §9.3 as code, written to be read rather than to be fast. The thing to watch is that gb_per_step is the only place the model’s architecture enters, and that everything else is division.

BW_GBS = 273.0          # the reference machine's memory bandwidth

def decode_ceiling_tps(params_b, bw_gbs=BW_GBS, bytes_per_param=2.0):
    """Upper bound on decode tokens/sec for a single stream."""
    return bw_gbs / (params_b * bytes_per_param)

def price_decode(n_tokens, params_b, batch=1, ctx_len=0, kv_gb_per_token=0.0,
                 bw_gbs=BW_GBS, bytes_per_param=2.0, efficiency=1.0):
    weights_gb = params_b * bytes_per_param          # read once per step, shared
    kv_gb      = batch * ctx_len * kv_gb_per_token   # read once per step, per sequence
    gb_per_step = weights_gb + kv_gb
    steps_per_s = bw_gbs / gb_per_step
    tokens_per_s = batch * steps_per_s * efficiency
    return {"gb_per_step": gb_per_step,
            "tokens_per_s": tokens_per_s,
            "hours": n_tokens / tokens_per_s / 3600.0}

print(price_decode(524_288, 32.0)["hours"])          # 34.1  single stream, bf16
print(price_decode(524_288, 1.7)["hours"])           #  1.81

The efficiency argument is where honesty lives. Left at 1.0 the function returns a roofline, an optimistic bound that no real system reaches. Set it to the ratio between your own measured decode rate and the bound for the same model, and the function starts returning estimates instead of bounds. Lab 08 is where you obtain that ratio for your machine; until you have it, price at 1.0 and remember which direction the error runs.

9.7 KV cache arithmetic#

The KV cache is the second memory constraint in this chapter, and unlike the weights it grows while the run is happening, which makes it the thing that turns a working configuration into an out-of-memory error twenty minutes in.

Definition

KV cache

The store of attention keys and values for every token a sequence has processed so far, kept so that they are not recomputed at every subsequent decode step. It is per sequence, it grows linearly with sequence length, and its total size grows linearly with the number of concurrent sequences. On a machine where the weights fit comfortably, the KV cache is usually what actually limits concurrency.

The arithmetic is exact and short. At each layer, each position contributes one key vector and one value vector per KV head. So the bytes a single token adds to the cache are

The leading 2 counts keys and values. It is not the dtype; is the dtype, in bytes per element. Note that the formula takes KV heads, not attention heads. Architectures that share key and value projections across groups of query heads, which most current models do, store one KV pair per group, and using the query head count instead will overstate the cache by the group size.2

Total cache size is then

for sequence length and batch . Linear in both. Doubling the context doubles the cache; doubling the concurrency doubles the cache.

Work an example with the geometry Lab 08 uses for a 32B-class teacher: 64 layers, 8 KV heads, head dimension 128, bf16 keys and values.

A quarter of a megabyte per token sounds negligible until you multiply. One 4,096-token sequence holds

of cache. Sixteen concurrent sequences at that length hold 17.2 GB, which is more than a 8B model’s entire bf16 weight set. At a 32,768-token context, one sequence holds 8.6 GB, and the memory budget you built around the weights is no longer the budget that matters.

That is how you size concurrency. Take total memory, subtract the teacher’s weights and the serving overhead, and divide what remains by the per-sequence cache:

Take a 128 GB machine serving a quantized 32B teacher, at 4 bits per weight plus the format’s scale overhead, so that its weights occupy 17.6 GB at run time rather than the 64 GB the same model costs in bf16. Give the server another 8 GB of overhead and the leftover is 102.4 GB. At a 4,096-token context that supports 95 concurrent sequences; at 8,192 it supports 47; at 32,768 it supports 11. One architectural constant and one context length decide your concurrency, and both are known before anything loads. Run the same arithmetic on the bf16 copy of that teacher and the leftover falls to 56 GB, which is a little over half the concurrency at every context length, and that difference is the whole argument for quantizing a teacher you are only going to score with.

2026-08-01T07:27:47.717086 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1,024 2,048 4,096 8,192 16,384 32,768 sequence length, tokens 0.1 1 10 100 KV cache, GB batch 1 batch 16 32B-class, 64 layers x 8 KV heads x 128 batch 1 batch 16 student-scale, 24 x 8 x 64 (illustrative) 128 GB machine ceiling, and 108.8 GB after the 15% headroom rule 64 GB: a 32B model's bf16 weights cache = weights at 15,259 tokens 16 x 8,192 tokens = 34.4 GB, more than an 8B model's weights bytes per token = 2 x layers x KV heads x head dim x 2 (bf16); GB = bytes per token x length x batch / 1e9 32B-class: 262,144 bytes/token. student-scale: 49,152 bytes/token.
Figure 9.4 KV cache growth is linear in sequence length and steeper than intuition suggests: at a 32B-class geometry, sixteen concurrent 8,192-token sequences cost more memory than the entire weight set of an 8B model.

def kv_bytes_per_token(n_layers, n_kv_heads, head_dim, bytes_per=2):
    """K and V, one of each per layer per position, per KV head."""
    return 2 * n_layers * n_kv_heads * head_dim * bytes_per

def kv_gb(n_layers, n_kv_heads, head_dim, seq_len, batch, bytes_per=2):
    k = kv_bytes_per_token(n_layers, n_kv_heads, head_dim, bytes_per)
    return k * seq_len * batch / 1e9

def batch_where_kv_matches_weights(params_b, seq_len, k_bytes, bytes_per_param=2.0):
    """Past this batch size, KV traffic dominates and throughput stops scaling."""
    return (params_b * bytes_per_param) / (k_bytes * seq_len / 1e9)

K32 = kv_bytes_per_token(64, 8, 128)                 # 262,144 B/token
print(kv_gb(64, 8, 128, 4096, batch=1))              # 1.07 GB per sequence
print(batch_where_kv_matches_weights(32.0, 4096, K32))  # ~59.6

That last number rewards sitting with. For a 32B bf16 teacher at a 4,096-token context, KV traffic catches up with weight traffic at a batch of about 60. But 60 sequences of cache is 64 GB, and the weights are another 64 GB, so reaching that batch requires 128 GB for the two of them alone on a 128 GB machine. The crossover is unreachable, which is the general situation for a large model in a wide dtype: memory stops you before traffic does. Run the same calculation for a 1.7B student with a student-scale geometry (24 layers, 8 KV heads, head dimension 64 gives bytes per token, so 0.20 GB per 4,096-token sequence) and the crossover sits near batch 17 with a total footprint under 7 GB. You can walk right past it, and you will see the throughput curve flatten exactly when you do.

The memory management problem this creates is real enough to have its own literature. A naive allocator reserves the maximum context length per sequence up front, which wastes most of it, because most sequences are shorter than the maximum. Paged allocation, which stores the cache in fixed-size blocks and maps them per sequence the way an operating system pages virtual memory, recovers that waste and is what current serving stacks do.9 Chapter 15 covers the serving side. For pricing purposes, the number you want is the one this section computes, and the paged version gets you closer to it rather than past it.

9.8 Matched compute, and how to build a ledger#

Now the reason all of this arithmetic matters for research rather than only for scheduling.

Suppose you want to know whether sequence-level KD or cached-logit distillation produces a better student. The obvious comparison is to run both for the same number of training steps and evaluate. That comparison is unfair, and the unfairness is large.

Sequence-level KD spent 34 hours of teacher decode building its corpus. Cached-logit distillation spent 13 minutes of teacher prefill building its cache. At equal student steps you have compared two methods while handing one of them a hundred and fifty times more teacher compute. If it wins, you have learned nothing about the method, because you would expect a hundred and fifty times the budget to buy something.

Definition

Matched compute

A comparison protocol in which every arm is allocated the same total computational budget rather than the same number of training steps. The budget must be stated in a unit the hardware actually charges, and every arm’s consumption must be accounted in that unit, including costs paid outside the training loop such as corpus generation and cache construction.

Watch out

“Equal steps” and “equal compute” are different fairness conditions and they can give opposite answers. Equal steps is the right control when you are isolating the effect of an objective on the optimization, which is what Chapter 6’s divergence ablation does: all arms there cost the same, so steps and compute coincide. Equal compute is the right control when the arms differ in what they had to buy before training started. Reporting an equal-steps comparison between methods with different data-acquisition costs, without saying so, is the single most common way published distillation comparisons mislead.

Building the ledger requires picking a currency. On bandwidth-limited hardware the natural one is bytes moved, because seconds are bytes moved divided by bandwidth, and bytes moved is computable from parameter counts and token counts without running anything.

Lab 06’s fourth solution exercise builds exactly this ledger for three arms on the course’s own configuration, and the numbers are worth walking through because the ratios are the lesson.

The setup: a 1.7B teacher, 3.4 GB in bf16. 2,048 prompts, 256 new tokens each, so 524,288 generated tokens. Prompts are 384 tokens. Both generation and scoring run at batch 16. The baseline arm trains for 1,500 student steps.

Teacher decode, for the SeqKD arm. Generating 524,288 tokens at batch 16 takes decode steps, and each step reads the teacher’s 3.4 GB:

At 273 GB/s that is seconds of pure bandwidth time.

Teacher prefill, for the cached-logit arm. Scoring 2,048 prompts at batch 16 takes passes, each reading 3.4 GB:

which is seconds. Under two seconds against 408.

The ratio between them is exactly 256, which is the number of new tokens generated per prompt. That is not a coincidence and it is the cleanest possible statement of this chapter’s thesis: decoding tokens costs times what scoring the same text costs, because it is weight reads instead of one.

The trace-SFT arm pays zero teacher compute, because its corpus was generated by somebody else and downloaded.10

Student training. Charge each optimizer step three traversals of the student’s full training state: one forward, and a backward that reads the weights again and writes gradients. At 16 bytes per parameter for a full fine-tune of a 360M student, that state is 5.76 GB, so a step moves GB, which is seconds. Fifteen hundred steps is GB, or 94.9 seconds.

Table 9.3 The matched-compute ledger for three arms on Lab 06’s configuration, in seconds of bandwidth time at 273 GB/s.

Arm Teacher cost Student cost (1,500 steps) Total Steps at matched compute
Cached-logit 1.6 s (prefill) 94.9 s 96.5 s 7,921
SeqKD 408.1 s (decode) 94.9 s 503.1 s 1,500
Trace SFT 0.0 s (purchased) 94.9 s 94.9 s 7,947

The budget is set by the most expensive arm, 503.1 seconds. The cached-logit arm has 406.6 seconds of slack, which at 0.0633 seconds per step buys 6,421 additional steps. The trace arm has 408.2 seconds of slack, buying 6,447. At matched compute the cheap arms run more than five times the baseline step count while the expensive arm runs exactly its 1,500 and no more.

Whether that changes the ranking depends on how much a training step is worth, which is an empirical question this ledger cannot answer. Lab 06 illustrates it with a stated model: assume quality improves by 1.5 points per doubling of steps, which is a placeholder chosen to make the mechanics visible and labeled as such. Under that model the cached arm goes from 63.0 to 66.6 across 2.40 doublings, the trace arm from 58.0 to 61.6 across 2.41, and the SeqKD arm stays at 60.0. The trace arm overtakes SeqKD outright, 61.6 against 60.0, purely from reallocated slack. Change the per-doubling constant and the crossing moves. The point is not the specific numbers; it is that a ranking which looked settled at equal steps is not settled at equal compute, and that you cannot know which without building the table.

BW = 273.0

def ledger(teacher_gb, gen_tokens, decode_batch, n_prompts, prefill_batch,
           student_step_gb, steps):
    gb_decode  = (gen_tokens / decode_batch) * teacher_gb   # one weight read per step
    gb_prefill = (n_prompts / prefill_batch) * teacher_gb   # one weight read per pass
    gb_student = steps * student_step_gb
    return {"decode_s":  gb_decode  / BW,
            "prefill_s": gb_prefill / BW,
            "student_s": gb_student / BW}

L = ledger(teacher_gb=3.4, gen_tokens=524_288, decode_batch=16,
           n_prompts=2048, prefill_batch=16,
           student_step_gb=3 * 5.76, steps=1500)
seqkd  = L["decode_s"]  + L["student_s"]     # 503.1 s
cached = L["prefill_s"] + L["student_s"]     #  96.5 s
extra_steps = (seqkd - cached) / (3 * 5.76 / BW)   # 6,421 steps of slack

State the model’s omissions when you report it, because a ledger that hides its assumptions is worse than no ledger. This one charges only weight traffic and ignores attention compute, KV cache reads, the prefill of prompts during generation, activation memory traffic, optimizer state updates, and every fixed cost of loading and checkpointing. Each of those makes the true numbers larger. What survives the omissions is the ratio structure, and the ratio structure is what the comparison rests on.11

9.9 Amortization: capital cost and operating cost#

The last idea in this chapter is the one that changes plans rather than estimates.

Definition

Amortization

Spreading a fixed cost, paid once, across every use that benefits from it. A cost that is prohibitive as an operating expense can be reasonable as a capital expense if enough uses share it, and the break-even point is the number of uses at which the fixed cost equals the total savings.

The two costs in this chapter behave differently under repetition. Student rollouts in on-policy training are an operating cost: you pay them every step, of every run, of every arm, forever. A teacher logit cache is a capital cost: you pay 13 minutes once, and then every training run against that corpus is teacher-free.

That distinction is what makes the cached-logit pipeline the default recipe on this hardware. Run a six-arm divergence ablation and the live-teacher version pays the teacher’s forward pass in every step of all six runs. The cached version pays it once, before any of them. The savings scale with the number of arms, which means the more careful your experimental design, the better caching looks. It also scales with teacher size, which is the reversal Chapter 10 develops: with a live teacher, a bigger teacher makes every step more expensive, while with a cache, a bigger teacher makes the cache more valuable, because the cost you deleted was larger.

A teacher-generated corpus is the same argument at a hundred times the stakes. Thirty-four hours is an unreasonable cost to pay inside an experiment. It is a reasonable cost to pay once for an asset you keep, reuse across every student you ever train on those prompts, and can hand to a colleague. That reframing is why the course insists on the phrase purchased asset: teacher generations you generate once and archive, buy as published data, or rent specialized hardware to produce. All three are purchases. Only the first is also a computation.

Watch out

Amortization only works if the asset is reusable, and reusability is a property you have to design in rather than discover afterward. A logit cache is tied to a corpus, a teacher checkpoint, a tokenizer version, and a temperature. Change any of them and the cache is wrong rather than stale, and it will keep training without complaint. Chapter 10 is about the bookkeeping that makes a cache provably yours: fingerprints, manifests, and spot re-derivation. A cost you paid once and cannot prove you can reuse is a cost you will pay again.

The same framing applies to the student itself. Building a student by pruning a teacher rather than training one from scratch converts an enormous operating cost into a small capital one, and the published results on that trade are startling enough to change project plans.1213 Chapter 13 does that arithmetic. And when you decide to spend slack on more training steps, as §9.8’s matched-compute ledger lets you, you are making a claim that patience buys quality, which is a claim with evidence behind it and a specific shape.14

9.10 The pricing procedure#

Here is the procedure. It takes about fifteen minutes with a calculator and it has caught more bad plans for me than any other single habit.

Step 1. Write the plan as a list of phases. For each phase: which model runs, in which mode, over how many tokens. Most plans have two or three phases. A SeqKD plan has generation and then training. An on-policy plan has rollout generation, teacher scoring, and the optimizer step, all inside the loop. If you cannot write this list, you do not have a plan yet.

Step 2. Count tokens per phase. Prompts times new tokens for generation. Sequences times sequence length for prefill. Steps times batch times sequence length for training. Be honest about epochs; a corpus scored once and trained on for three epochs is one prefill and three training passes.

Step 3. Get bytes per step for each model, in the dtype you will actually run. Parameters times bytes per parameter. This is where a plan to serve the teacher in a 4-bit format changes the answer by a factor of four, and where forgetting that you planned to serve it in bf16 changes it back.

Step 4. Compute the per-stream decode ceiling for every phase that decodes. Bandwidth divided by bytes per step. Phases that only prefill get the prefill rate, which you either measured or borrowed conservatively.

Step 5. Choose a batch size and check it against KV memory. Compute from the architecture, multiply by context length and batch, add the weights, and compare against your memory minus headroom. If it does not fit, lower the batch or the context, and go back to step 4 with the new batch.

Step 6. Divide, then discount. Tokens divided by tokens per second gives seconds. Then apply your efficiency factor, which is the gap between the roofline and what you actually measure. Until you have measured, use the roofline and say out loud that it is optimistic.

Step 7. Add the fixed costs. Model loading, checkpoint writes, cache serialization, the eviction and reload between pipeline stages, and the disk. Cache storage is its own arithmetic: at top- truncation the per-position cost is a few hundred bytes rather than the roughly 98 KB a dense fp16 distribution over a 49,152-token vocabulary would take, which is the difference between 0.6 GB and 155 GB for the course’s corpus. Chapter 10 derives that.

Step 8. Compare the total against your patience, and decide what to buy. If a phase costs more than a day, it is a purchase, not a computation, and your options are to generate once and archive, find a published version, rent different hardware for that phase, or change the plan. Make that decision before the run, not eleven hours into it.

In the labs: Lab 06

Part A·1 runs this procedure as a pre-flight cell: it prices the decode table, asserts that the 32B row exceeds a day, and refuses to let Part B generate a token until both memory plans have been checked. That ordering is the point. The price comes before the generation, mechanically, in a cell that fails loudly.

9.11 Where this lands in the labs#

Lab 06’s Part A·1 is the shortest useful thing in this chapter: it prices a corpus for four teacher sizes, asserts the design decision the prices force, and does it in under a second on a laptop with no model loaded. Lab 04’s Part A does the prefill and storage half, pricing the cache in both minutes and gigabytes before the teacher is downloaded. Lab 06’s fourth solution exercise builds the matched-compute ledger from §9.8 live, including the reallocated-slack arithmetic and the explicit list of what the cost model omits. Lab 08 is where the borrowed numbers get replaced: you build your own prefill and decode curves, discover your own efficiency factor against the roofline, and write a machine profile that the later labs size against instead of quoting this chapter. That transition, from a number the course told you to a number you measured, is the correct end state, and Chapter 15 is about the discipline it requires.

9.12 Exercises#

  1. A colleague plans to distill a 70-billion-parameter teacher into a 1-billion-parameter student by having the teacher generate 10 million tokens of training corpus in bf16. Price the generation phase on the reference machine: bytes per step, single-stream ceiling, and wall clock. Then price it again with the teacher in a 4-bit format, and again with a batch of 16 at the order-of-magnitude discount §9.3 gives. Which of those three numbers would you put in a project plan, and what would you say about the other two? (You did a rough version of this as Exercise 2 of Chapter 1. Compare.)

  2. The same colleague points out that 70B in bf16 is 140 GB of weights, which does not fit in 128 GB of memory at all. Explain what this does to the plan, and give two options that keep the teacher and one that abandons it. For the option you would pick, say what it costs in the currency of §9.8’s ledger.

  3. Derive the batch size at which KV traffic equals weight traffic for a model with 32 layers, 8 KV heads, head dimension 128, in bf16, at a 2,048-token context, with 7 billion parameters. Then compute the memory that batch requires and say whether it is reachable on a 128 GB machine. Finally, explain in one sentence why the answers to those two questions are related and not independent.

  4. You measure a decode rate of 22 tokens per second for a model you believe has 20 billion parameters stored at 1 byte each. Show that this is impossible on the reference machine’s bandwidth if the belief is correct, and list three explanations for the contradiction, ordered by how likely you think each is. Say what single additional measurement would separate them.

  5. Two arms of an experiment are reported at 2,000 student steps each. Arm A trained against a logit cache built with 12 minutes of teacher prefill. Arm B trained on a corpus the teacher generated in 9 hours. Build the ledger in seconds of bandwidth time, assuming a 1.7B teacher, a 360M student at 16 bytes per parameter, three weight traversals per step, and 273 GB/s. Then say how many steps Arm A is entitled to at matched compute, and write the two sentences you would put in the paper’s methods section to make the comparison honest.

  6. Section 9.4 reports a prefill-to-decode ratio of roughly 41 to 1 measured on a 20B model in a 4-bit format. Predict, before doing any arithmetic, whether that ratio should be larger or smaller for a 1.7B model in bf16 on the same machine, and by roughly how much. Then reason it out from §9.1.1 and check whether your prediction survives. Name the assumption in your reasoning that you are least confident about.

  7. Your plan needs 4 million tokens of teacher-generated corpus and you have a 32B teacher. Give three ways to acquire that corpus that do not involve 260 hours of single-stream decode, and for each one, name the thing it gives up. Then say which you would choose and what evidence you would collect afterward to confirm the choice was not a mistake.



  1. Model sizes throughout this chapter refer to the SmolLM2 family (135M, 360M, and 1.7B) that the course uses for its student and small-teacher work: Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big, Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737 

  2. The 32B-class teacher and its grouped-query attention geometry follow the Qwen2.5 family: Qwen Team, “Qwen2.5 Technical Report,” arXiv:2412.15115 (2024). https://arxiv.org/abs/2412.15115 Grouped-query attention is the reason the KV cache formula in §9.7 takes a KV head count rather than an attention head count. 

  3. Ji Lin et al., “AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration,” arXiv:2306.00978 (2023), MLSys 2024. https://arxiv.org/abs/2306.00978 The relevance here is arithmetic rather than methodological: a 4-bit weight format puts bytes per parameter in every formula in this chapter. 

  4. Elias Frantar, Saleh Ashkboos, Torsten Hoefler, and Dan Alistarh, “GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers,” arXiv:2210.17323 (2022), ICLR 2023. https://arxiv.org/abs/2210.17323 For the training-side counterpart, where 4-bit base weights make a fine-tune fit, see Tim Dettmers, Artidoro Pagnoni, Ari Holtzman, and Luke Zettlemoyer, “QLoRA: Efficient Finetuning of Quantized LLMs,” arXiv:2305.14314 (2023), NeurIPS 2023. https://arxiv.org/abs/2305.14314 

  5. The idea that the teacher’s full output distribution is the training signal, rather than its argmax, is Hinton’s: Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015). https://arxiv.org/abs/1503.02531 What makes it cacheable is that the teacher is fixed and the corpus is fixed, so the distributions are functions of data you already have. 

  6. Rishabh Agarwal et al., “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649 The teacher’s role in the on-policy setting is to score the student’s generations, which is a prefill workload. 

  7. Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947 The method requires the teacher to generate, which is what makes it the expensive row of Table 9.2 on bandwidth-limited hardware. 

  8. The published-corpus route is well established in current practice: DeepSeek-AI, “DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning,” Nature 645 (2025): 633-638, https://doi.org/10.1038/s41586-025-09422-z, preprint arXiv:2501.12948, describes supervised fine-tuning of its distilled models on teacher-generated reasoning traces, and the resulting corpora are the sort of asset §9.9 means by “purchased.” 

  9. Woosuk Kwon et al., “Efficient Memory Management for Large Language Model Serving with PagedAttention,” arXiv:2309.06180 (2023), SOSP 2023, 611-626, https://doi.org/10.1145/3600006.3613165. The paper’s central observation is that naive contiguous KV allocation wastes most of the memory it reserves, and that borrowing the operating system’s paging idea recovers it. 

  10. Trace fine-tuning on a purchased corpus is the DeepSeek-R1 distillation pattern; see footnote 9-8. Auditing such a corpus before training on it is Chapter 11’s subject, and it is a cost the ledger in §9.8 does not charge but a project plan should. 

  11. For the broader map of methods whose costs this chapter prices, see Xiaohan Xu et al., “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024), https://arxiv.org/abs/2402.13116, and Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819. https://arxiv.org/abs/2006.05525 

  12. Saurav Muralidharan et al., “Compact Language Models via Pruning and Knowledge Distillation,” arXiv:2407.14679 (2024), NeurIPS 2024. https://arxiv.org/abs/2407.14679 

  13. Mengzhou Xia, Tianyu Gao, Zhiyuan Zeng, and Danqi Chen, “Sheared LLaMA: Accelerating Language Model Pre-training via Structured Pruning,” arXiv:2310.06694 (2023), ICLR 2024. https://arxiv.org/abs/2310.06694 

  14. Lucas Beyer et al., “Knowledge distillation: A good teacher is patient and consistent,” arXiv:2106.05237 (2021), CVPR 2022. https://arxiv.org/abs/2106.05237 Their finding that very long training schedules matter more than most architectural choices is what makes reallocated step budget a meaningful thing to buy with matched-compute slack. For the current organization of on-policy methods and their costs, see also Mingyang Song and Mao Zheng, “A Survey of On-Policy Distillation for Large Language Models,” arXiv:2604.00626 (2026), an unrefereed preprint that its authors describe as ongoing work. https://arxiv.org/abs/2604.00626 

Part III · Making It Real

10

Off-Policy Distillation and the Logit Cache

Chapter 8’s first real run kept both models in memory. Every training step loaded a batch, pushed it through the teacher to get targets, pushed it through the student to get predictions, compared them, and took a gradient step. That is the obvious way to build a distillation loop and it is what almost everyone builds first. It also pays for the teacher’s forward pass once per batch, every batch, for as long as training runs, and it spends whatever fraction of the machine the teacher’s weights occupy on a model whose parameters never change.

Here is the observation that makes most of that expenditure unnecessary. The teacher is a deterministic function of its input tokens. Feed it the same sequence twice and you get the same logits twice. If the corpus is fixed before training starts, then the teacher’s output on that corpus is also fixed before training starts, and every training step after the first is recomputing something you already knew.

So compute it once. Run the teacher over the whole corpus, write down what it said at every position, delete the teacher, and train the student against the written record. Stage one is a batch job you pay for in minutes and never pay for again. Stage two is a training loop with the entire machine available to the student and no teacher anywhere in the process.

This is the pipeline I reach for first on a new project, and I want to be direct about why instead of leaving it as an aesthetic preference. On the reference machine, and on any machine with a similar ratio of capacity to bandwidth, the cached pipeline is the cheapest arrangement that is still correct. Its expensive phase runs the teacher in its fast mode and only its fast mode. Its repeated phase runs a small model with no competition for memory. It is restartable at a stage boundary, so a job that dies at eighty percent does not cost you eighty percent. And the artifact it produces, a directory of arrays with a manifest, is inspectable, auditable, and transferable in a way that a live two-model training loop is not. Nothing else in this book has that combination.

What it gives up is real and I will name it precisely before the chapter is half over, because Chapter 12 is going to spend its length on the thing this chapter cannot do.

10.1 The invariance, stated carefully#

Let be a tokenized sequence from the corpus. The teacher, run over in one forward pass, produces at each position a vector of logits , where is the vocabulary size. Those logits define the teacher’s distribution over the token at position , given everything up to and including . Chapter 7 established the convention and the shift that goes with it, so I will use it without re-deriving: position of the logits lines up with position of the tokens, and one shift is applied to the logits and the mask together.

The teacher’s parameters are frozen. Dropout is off. There is no sampling anywhere in a forward pass over existing text. So is a function of and nothing else. In particular it is not a function of the student, the optimizer state, the step count, or the epoch.

That is the whole argument. The teacher’s contribution to the loss at position depends only on the corpus, and the corpus is fixed, so the teacher’s contribution can be computed before training starts and stored.

Definition

Logit cache

A stored record of a teacher’s output distributions over a fixed corpus, written once and read many times during student training. In practice it stores log-probabilities and not raw logits, because log-probabilities are what an inference server hands you and because storing them removes any ambiguity about the temperature at which the normalization was performed.

The pipeline that follows has two stages and one hard boundary between them:

stage 1 (pay once)   corpus -> teacher prefill -> top-k cache + manifest -> disk
stage 2 (iterate)    cache + corpus -> integrity checks -> student training (no teacher)

The boundary is not decorative. Everything the teacher knows about your corpus crosses it as bytes, and nothing else does. Stage two contains no teacher, which means stage two also contains no way to notice that the bytes are wrong. That is the price of the discount, and §10.6 and §10.7 are about paying it.

10.1.1 What “correct” means here, and what it costs#

The cached objective is the same objective as the live one, computed from the same teacher on the same text, differing only by whatever top- truncation discards. That is the sense in which the pipeline is correct: it computes the same objective the live pipeline computes, with a storage step in the middle. Section 10.3 measures the truncation, and Section 10.4 shows how to choose a where the difference is inside run-to-run noise.

What the cache gives up is the corpus. The teacher was evaluated on this corpus’s teacher-forced positions: fed the reference text and scored on predicting each next token of that reference. It was never asked what it thinks about text the student wrote.

Definition

Off-policy corpus

A fixed set of input sequences, chosen before training begins and not modified by the student’s behavior during training. Distillation against such a corpus is off-policy in the reinforcement learning sense: the data distribution the student learns from is not the distribution the student itself induces.

The consequence is immediate and worth stating in the negative. The moment the student generates its own tokens, the sequences change, so the positions the teacher would need to score are ones the cache never saw, and no amount of cleverness recovers them from what is on disk. A cache is useless for on-policy training, and this is not a defect in the cache; it is what off-policy means.

The cost of that is exposure bias: a model trained only on text it did not produce never sees its own mistakes and therefore never learns to recover from them.1 The argument is old and predates distillation’s interest in it.2 Chapter 12 covers the modern response, which interpolates between teacher-provided data and student rollouts with a single parameter.34 I want you to arrive at that chapter already knowing that a cached pipeline is a complement to on-policy training and not a competitor to it. The standard sequencing, which Chapter 12 argues for on evidence, is a cheap off-policy phase first and an expensive on-policy phase after, and the cheap phase is this one.

The cache gives up two smaller things as well. It fixes the teacher: change the checkpoint, change the revision, quantize it differently, and the cache is stale. It also fixes the temperature, because what is stored is a normalized distribution and not a logit vector, and §10.8 is about the consequences of that. Neither is fatal. Both are bookkeeping obligations that a live teacher does not impose on you.

10.2 Why this is the cheapest correct pipeline on this hardware#

Chapter 9 established the arithmetic. I will use it here instead of repeating it.

Prefill processes text that already exists, so every position is computed in parallel and the arithmetic units stay busy. Decode produces one token at a time and each step re-reads the entire weight matrix from memory to emit a single token, so the memory bus is the bottleneck. On the reference machine, with 273 GB/s of bandwidth, the decode ceiling for a model of billion parameters held in a 2-byte format is tokens per second. For a 1.7B teacher that is about 80 tokens per second. Lab 02 measured prefill on a 20-billion-parameter teacher in a 4-bit format on this class of machine at 2,053 tokens per second, and Lab 04 prices its cache build at a round 2,000.

The gap is roughly twenty-five to one on the same model, and the cached pipeline sits entirely on the good side of it. Nobody decodes while building a cache. The teacher runs in the only mode where it is fast.

Now the repetition. A live-teacher loop pays one teacher forward pass per batch per epoch. The cache pays one teacher forward pass per batch, once, total. Lab 04’s configuration trains 1,500 steps at batch size 8 over a 4,096-row corpus, which is about three passes over the data, so the cache deletes roughly two-thirds of the teacher compute before you count anything else. Train for longer and the ratio improves without bound, which matters because the function-matching literature argues that very long training is exactly what produces good students.5

Then the per-step arithmetic. Suppose the teacher has times the student’s parameter count and both are in the same family, so a forward pass costs roughly times as much. A training step is a student forward plus a student backward, and the backward is about twice the forward, so the student side costs about 3 units. Adding a teacher forward pass costs more. Removing it should therefore multiply throughput by

For Lab 04’s pair, a 1.7B teacher and a 360M student, and the predicted speedup is about 2.6, which sits inside the 1.5 to 3 times band the lab writes down as its expected range. For a 32B teacher against a 1.7B student, and the predicted speedup is about 7. That relationship runs the opposite direction from the live pipeline’s, where a bigger teacher makes every single step more expensive. Here, a bigger teacher makes the cache a bigger win, because the thing you deleted was the teacher’s forward pass. That reversal is the reason caching is the default recipe for large-teacher off-policy work rather than a trick for small ones.

Finally, memory. Lab 04 asserts both stages against a 128 GB budget with a 15 percent headroom reserve, so the usable figure is 108.8 GB.

Table 10.1 The two stages, priced on the reference machine (Lab 04 Part A.1).

Line item Stage 1 Stage 2
Teacher 1.7B in bf16 plus activations 6.4 GB absent
Cache write buffers 2.0 GB absent
Student 360M, full fine-tune at 16 bytes per parameter absent 5.76 GB
Student activations absent 4.0 GB
Cache read, memory-mapped absent 0.5 GB
Planned 8.4 GB 10.26 GB

Both stages fit with room to spare, and stage two fits with more than a hundred gigabytes of slack that the live-teacher run in Lab 03 did not have. That slack is not wasted; it is what lets you raise the batch size, lengthen the sequence, or train a larger student than the live pipeline would have permitted at all.

The half-gigabyte charge for a cache that is 0.61 GB on disk deserves an explanation, because it looks like a rounding error and is actually a design decision.

Definition

Memory-mapped tensor

An array whose bytes live in a file rather than in process memory, and which the operating system pages in on demand as the program touches it. A batch that reads eight rows of a cache faults in those eight rows and nothing else, so the resident cost is set by the working set rather than by the file size.

The practical effect is that cache size stops being a memory constraint and becomes a disk constraint. A 60 GB cache over a hundred-million-token corpus trains on this machine exactly as easily as a 0.6 GB one, provided the random-access pattern does not thrash. This is the single largest reason the pipeline scales to corpora that a live-teacher loop would not change your opinion about.

2026-08-01T07:27:48.630442 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 32 64 96 128 occupancy, GB budget after 15% headroom, 108.8 GB unused: 119.6 GB unused: ~118 GB of slack 8.4 GB 10.26 GB the whole machine, 128 GB teacher evicted 0 4 8 12 occupancy, GB teacher 1.7B bf16 + activations 6.4 student 360M full fine-tune, 16 bytes per parameter 5.76 cache write buffers 2.0 student activations 4.00 cache read, memory-mapped 0.50 the same two stacks, at a readable scale stage 1: build cache stage 2: train student line items are Lab 04 Part A.1's asserted memory plan. The cache is 0.61 GB on disk and is read through a memory map, so stage 2's resident cost is set by the working set rather than by the file.
Figure 10.1 Memory occupancy over the life of the pipeline, showing the teacher resident during stage one and absent during stage two, with the student's available headroom more than doubling at the boundary.

10.3 Top-k truncation, in full#

You cannot store the dense distribution. Do the arithmetic before arguing about it.

A vocabulary of 151,936 entries, which is the padded embedding count for the Qwen family,18 stored as 2-byte floats, costs about 304 kilobytes per token position. Lab 01 prices a 151k-vocabulary teacher at roughly 302 KB per position, the difference being the lab’s rounding of the vocabulary to 151,000, and one million cached positions at roughly 302 GB. Then it declines: three hundred gigabytes per million tokens is a disk purchase, and Lab 01 refuses it on those grounds. Even the SmolLM2 vocabulary of 49,152 entries,17 which is the smallest of the families this course uses, costs 98,304 bytes per position, which turns Lab 04’s 1.57-million-position corpus into 154.6 GB. That corpus is small. The dense option is refused on principle and the refusal is not close.

What saves the idea is that the distribution is not flat. A trained instruct model on teacher-forced text is peaked: most of the probability at most positions sits on a handful of tokens. So keep the handful.

Definition

Top-k truncation

Storing only the highest-probability entries of a distribution, with their token indices, and summarizing or discarding the remaining entries. The error this introduces is called truncation bias, and its size depends on how much mass the kept entries capture.

Fix the notation for the rest of the chapter. At one position, let be the teacher’s distribution over the vocabulary and the student’s. Let be the set of the token indices with the largest , let

be the retained mass and the tail mass under the teacher, and let and be the same two quantities under the student. The quantity you are after is the dense forward KL divergence at that position,

which Chapter 3 defined and Chapter 6 argued for. The cache holds and their indices. The tail is gone. The question is what to do about it, and there are exactly two serious answers.

10.3.1 The tail-bucket estimator understates, and by exactly how much#

The first answer keeps one extra number: the total discarded mass . At training time you treat the tail as a single aggregate outcome and match the student’s total mass on it.

Definition

Tail-bucket estimator

A top- approximation to a divergence that keeps the retained entries as they are and adds one additional outcome carrying the entire discarded mass. The student’s probability for that outcome is its total mass outside the retained set, so the estimator supervises how much the student puts in the tail without supervising how the student arranges it.

Written out, the estimator is

To find its bias, split the true divergence the same way and compare. Write the tail’s true contribution as , so that is the head term plus and the head term is shared with . Now decompose the tail. For define the conditional distributions inside the tail, and , which are the teacher’s and the student’s answers to “given that the next token is not one of the you kept, which one is it?” Substituting and ,

$$T_c \;=\; \sum_{i \notin S} \tau a_i \log \frac{\tau a_i}{\tau_q b_i} \;=\; \tau \log \frac{\tau}{\tau_q} \;+\; \tau \sum_{i \notin S} a_i \log \frac{a_i}{b_i} \;=\; \tau \log \frac{\tau}{\tau_q} \;+\; \tau\, D(a \,|\, b).$$

The first term is exactly what the tail bucket keeps. The second is exactly what it throws away. Therefore

The tail-bucket estimator understates the divergence, always, and the deficit is the product of two interpretable quantities: how much mass fell outside the top , and how differently the two models arrange that mass among the individual tokens inside it. The deficit is zero exactly when the teacher and student agree on the shape of the tail, and it is bounded by the tail mass times whatever the within-tail divergence happens to be.

That is an identity, not a heuristic. It holds at every , for every pair of distributions, and it is the reason Lab 02 can assert tail_bucket_kl <= dense + 1e-6 on real teacher logits at every in and have the assertion hold. It also says in one line what Lab 01 says in prose: one lumped bucket cannot see how the mass is arranged among the individual tokens inside the tail.

10.3.2 The renormalized estimator overstates, under a condition worth naming#

The second answer discards the tail entirely and rescales what remains so it sums to one.

Definition

Renormalized estimator

A top- approximation to a divergence that divides each retained teacher probability by the retained mass, producing a distribution supported on the kept tokens, and compares it against the student’s probabilities on those same tokens. It stores nothing about the discarded mass, and in doing so it treats the teacher as though it had never considered anything outside the top .

Write for . The estimator, as implemented in the course’s topk_forward_kl with use_tail=False, compares against the student’s full-vocabulary probabilities , not against a renormalized student:

$$D_{\text{ren}} \;=\; \sum_{i \in S} \tilde p_i \log \frac{\tilde p_i}{q_i} \;=\; \frac{1}{M} \sum_{i \in S} p_i \log \frac{p_i}{q_i} \;-\; \log M.$$

Call the head term , so and . Subtract:

$$D_{\text{ren}} - D \;=\; H_S!\left(\frac{1}{M} - 1\right) - \log M - T_c \;=\; \underbrace{\frac{\tau}{M} H_S}{\text{head, inflated}} \;+\; \underbrace{\log \frac{1}{M}}.$$}} \;-\; \underbrace{T_c}_{\text{tail, deleted}

Read the three terms. Renormalizing divides every kept probability by , which inflates the head’s contribution by the factor ; that is the first term. It also shifts every kept log-probability up by , which adds a constant that does not depend on the student at all; that is the second term. And it deletes the tail’s true contribution; that is the third.

Two of the three terms push the estimate up and one pulls it down, so the direction is a competition, not a theorem. The condition under which the upward terms win falls out in a line. Since for any , a sufficient condition for is

Both sides are divergence per unit of probability mass. The left is what the tail contributes per unit of tail mass; the right is what the head contributes per unit of head mass, plus one nat of slack from the rescaling constant. So the renormalized estimator overstates the divergence unless the tail is more divergent per unit of mass than the head by more than a nat.

On real teacher-student pairs the tail is not. The tail is the low-probability region where two models of the same family, one trained from the other’s data distribution, mostly agree; the contested region is the head, where several continuations are plausible and the models rank them differently. Chapter 6 made the same point from the other direction, that the KL concentrates on genuinely open positions and not on positions where the teacher is nearly certain. Delete the region of agreement, inflate the region of disagreement, and the number goes up. Lab 01 says renormalizing makes the teacher look more confident than it was and overstates the divergence; Lab 02 asserts renorm_kl >= dense - 1e-6 at every it tests on real shifted logits; the derivation above says the same thing and tells you what would have to be true for it to fail.

What would have to be true is the unbounded-KL failure mode from Chapter 3: the student assigns something very close to zero to a tail token the teacher likes, so a single term in becomes enormous. That is exactly the pathology bounded divergences exist to prevent, and it is a real condition rather than a theoretical curiosity, which is why I am stating the inequality instead of asserting the sign.

Watch out

The kd_core.topk_forward_kl docstring says that use_tail=False “systematically understates the divergence because it pretends the teacher never considered anything else.” The clause after “because” is correct: the renormalized estimator does pretend exactly that. The verb before it is not. Pretending the teacher never considered anything else makes the teacher look sharper than it is, which raises the divergence, not lowers it. Lab 01 §6 and Lab 02 §5 both state the direction correctly in prose, and Lab 02’s live assertions (renorm_kl >= dense - 1e-6 and tail_bucket_kl <= dense + 1e-6) are the measured ground truth for that model pair at every tested. Resolve in favor of the assertions and the derivation. This is worth knowing about not because one sentence in one docstring is wrong, but because sign errors in documentation survive for years: they read fluently, they sit next to correct code, and nothing executes them.

What the derivations guarantee is the ordering, and only the ordering. Which estimator lands nearer the truth at a given is a separate question, and §7.10 tells the story of how Lab 02’s sweep answered it: an assertion that the tail bucket always wins failed on the row, where the tail is large enough that swamps the renormalized estimator’s overstatement. Assume the ordering, measure the rest.

10.3.3 The bracket, which is the useful part#

Put the two results together. Under the head-versus-tail condition of §10.3.2, which holds on ordinary teacher-student pairs at ordinary ,

The true value you cannot afford to compute lies between two values you can. That changes what kind of decision choosing is. You are not guessing at truncation error and hoping; you are measuring an interval that contains it, on your own corpus, before you spend anything on training. The width of the interval,

is a number you compute with two extra lines of code on a sample of a few hundred positions, and it is an upper bound on how wrong either estimator can be.

This is the most useful idea in the chapter and I want it stated plainly: top- truncation is the only approximation in this pipeline, and it is one whose error you can bound by measurement rather than argument.

Both estimators converge to as grows, one from each side, monotonically on real data. The bracket therefore tightens as increases, and the shape of that tightening is what you look at when you pick .

The listing below computes all three quantities for a batch of logits. It is written to make the shared structure visible: the head term is computed once and both estimators build on it.

import torch
import torch.nn.functional as F

def truncation_bracket(student_logits, teacher_logits, k):
    """Both top-k estimates of forward KL, plus the dense value they bracket."""
    tp = F.log_softmax(teacher_logits.float(), dim=-1)   # teacher log-probabilities
    sp = F.log_softmax(student_logits.float(), dim=-1)   # student log-probabilities
    dense = (tp.exp() * (tp - sp)).sum(-1)

    top_lp, idx = tp.topk(k, dim=-1)                     # what a cache would keep
    s_lp = sp.gather(-1, idx)
    head = (top_lp.exp() * (top_lp - s_lp)).sum(-1)      # the term both estimators share

    # Tail bucket: one extra outcome holding all the discarded mass.
    t_tail = (1.0 - top_lp.exp().sum(-1)).clamp_min(1e-9)
    s_tail = (1.0 - s_lp.exp().sum(-1).clamp_max(1.0 - 1e-6))
    bucket = head + t_tail * (t_tail.log() - s_tail.log())

    # Renormalized: rescale the kept entries to sum to one, drop the tail.
    renorm_lp = top_lp - top_lp.logsumexp(-1, keepdim=True)
    renorm = (renorm_lp.exp() * (renorm_lp - s_lp)).sum(-1)
    return dense, bucket, renorm

What this proves, run on any real pair, is that bucket <= dense <= renorm at every position you check, and that the gap closes as you raise . Two details in it are load-bearing and not stylistic. The .float() calls force fp32 arithmetic regardless of the models’ storage dtype, because the tail term genuinely breaks in bf16: the student’s mass on the teacher’s top- routinely exceeds 0.996 on teacher-forced text, bf16 carries 8 bits of mantissa precision so its resolution near 1.0 is about 0.0039, and both the sum and the clamp round to exactly 1.0. Then log(0) is negative infinity, the tail term is positive infinity, and the loss is NaN from the first step. Chapter 2 explained the floating-point mechanism; this is where it bites. The clamp_min(1e-9) on the teacher’s tail does the symmetric job at the other end, bounding the tail log-probability near nats when the top covers essentially everything.

2026-08-01T07:27:53.060788 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1 2 4 8 16 32 64 128 256 k, entries kept per position 0.05 0.10 0.20 0.50 forward KL, nats (log scale) renormalized estimator: always above tail-bucket estimator: always below dense full-vocabulary KL, 0.211 nats measured truncation error k = 8 renorm +14.2% bucket -10.8% mass 0.970 k = 64, the default renorm +2.5% bucket -2.1% mass 0.995 256 positions over a 49,152-entry vocabulary; a peaked teacher and a same-family student. Both estimators are computed exactly, and the ordering bucket <= dense <= renorm holds at every k.
Figure 10.2 The two truncation estimators bracket the dense divergence at every k, with the interval tightening monotonically, which is what makes truncation error a measured quantity rather than an assumed one.

10.4 Choosing k by measurement, not doctrine#

The rule is that is a property of your teacher and your domain, not a constant to copy from somebody’s config file. A teacher doing arithmetic at low temperature is far more compressible than a teacher writing fiction at temperature 1, and both are more compressible than an untrained model, whose distribution is nearly uniform and whose top 64 entries capture almost nothing. Random logits are the pathological case; the realistic case is a trained model on a task it is good at, and the realistic case is peaked.

The workflow has four steps and takes one cell.

  1. Take a sample of your actual corpus. Not a benchmark, not a toy, the corpus you are going to cache. A few hundred to a few thousand supervised positions is enough.
  2. Run the teacher on it and get dense logits. Run a candidate student too, because both estimators compare against something.
  3. Compute the bracket at several candidate values, along with mean retained mass.
  4. Pick the where the bracket is narrow enough for your purposes, then write down the four numbers that justify it: mass covered, bias in both directions, gigabytes on disk, and prefill minutes.

That last step is the one people skip, and it is the one that makes the decision auditable six months later. Solutions 02 Exercise 2 formalizes it as a four-line cost-benefit memo and commits to a specific answer.

Table 10.2 The measured k-ablation. Solutions 02 Exercise 2 runs on 16 conversations from the course corpus truncated to 192 tokens (about 1,200 supervised positions, evenly subsampled to 768 for the dense measurement); Solutions 04 Exercise 2 runs a 360M teacher against a 135M student in fp32 on a 16-row, 192-position slice of the same corpus.

Mean mass retained Bias, both estimators Projected cache for the 1.57M-position corpus
8 about 0.97 low teens of percent about 0.087 GB
32 between falling about 0.31 GB
64 above 0.99 a couple of percent, asserted under 10% about 0.61 GB
128 above 0.997 one or two percent about 1.22 GB

Read the table the way the labs read it. Retained mass climbs steeply and then flattens: the move from 8 to 64 buys two and a half percentage points of mass, and the move from 64 to 128 buys about half a point. Bias falls in step, from a double-digit distortion at to a couple of percent at . Disk grows linearly, doubling with every doubling of , from roughly a tenth of a gigabyte to over a gigabyte; the Solutions 04 text calls that about a twelvefold spread and the byte formula in §10.5 puts it at fourteen.

So the static knee sits between and . Below it, at , the double-digit bias is a real distortion of the objective and not a rounding effect. Above it, buys about one percentage point of bias over and charges double the disk for it. Lab 02’s independent sweep over agrees: mean mass covered above 0.98 at , both estimators within 10 percent of dense by .

The committed decision in Solutions 02 is with the tail bucket kept, justified as: more than 99 percent of mass covered, so at most one percent of the teacher’s probability is summarized instead of stored; both estimators within 15 percent of dense and bracketing from opposite sides; well under half a gigabyte per million tokens, roughly 250 times smaller than dense, so a hundred-million-token corpus caches in tens of gigabytes; and about 8 minutes of prefill per million tokens at the measured 2,053 tokens per second, so that same hundred-million-token cache is a half-day batch job you pay once rather than a per-epoch cost.

Two caveats travel with that number, and both matter more than the number does.

The first is scope. Sixteen conversations, one corpus, one model pair. The solution states it directly: the table, not the conclusion, is the reusable artifact. Copy the procedure, not the 64.

The second is what the sample cannot see. A few hundred positions estimate the mean retained mass and the mean bias tightly enough to choose between candidate values. They do not see the extreme tail of hard positions where the teacher is genuinely uncertain and the top 64 covers half the mass. Those positions exist, they are exactly the positions where the teacher’s dark knowledge is richest,6 and a mean-based estimate is blind to them by construction. This is why the measurement is called a pre-flight estimate rather than a proof.

There is a practical reason to make this measurement carefully rather than casually, which is that it is easy to run out of memory doing it. A tensor of fp32 logits is about 0.6 GB, and the bias computation needs several dense intermediates of that size at once. Solutions 02 processes two rows at a time, keeps only supervised positions, and subsamples evenly to 768 positions, which caps the largest intermediate near 150 MB without changing what is being estimated. Chunking the batch and subsampling the positions are the two moves to copy: the estimate you want is a mean over positions, and a mean does not care whether the positions arrived all at once.

2026-08-01T07:27:58.822076 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.90 0.95 1.00 retained mass k = 8 0.970 k = 64 0.995 k = 128 0.998 k = 1 is 0.482, below this panel teacher at T = 1 the knee 1 2 4 8 16 32 64 128 256 k, entries kept per position 0.20 0.50 0.80 retained mass k = 64 0.72 same teacher, cached at T = 2 note the split y axes: the upper panel spans 0.90 to 1.00, the lower one 0.10 to 0.95. A temperature choice made when the cache is written moves the knee a long way to the right.
Figure 10.3 Cumulative teacher probability mass against k on real instruct-model logits, showing a knee between k=32 and k=64 and a long flat region past it, which is the shape that makes top-k caching viable at all.

One constraint from the tooling, since it changes the calculus in a specific situation. When TRL serves a teacher from a separate vLLM inference process that returns scores over the network,7 its truncation parameter must be greater than zero for forward KL and exactly 1 for reverse KL or generalized JSD. Hosting a large teacher remotely therefore forces the most aggressive truncation available, which means the bracket you measured on a local teacher does not describe the objective you will actually optimize. Chapter 15 covers the serving path in full; here the point is that is sometimes not yours to choose, and when it is not, you should know the size of what you are giving up before you commit to the architecture.

The same observation runs in the security direction. Published work on extracting parts of a production language model shows how much an attacker recovers from top- log-probability responses, and truncating is one of the defenses providers reach for.89 If your teacher is an API, the you get is a policy decision made by somebody whose interests differ from yours, and it can change without notice. Chapter 17 treats both directions of that.

10.5 The cache record, field by field#

A cache is a directory. Lab 04’s layout is five arrays and a manifest.

Table 10.3 The on-disk record and why each field is present.

File Shape Dtype Why it exists
topk_logprobs [n_rows, T, k] float16 The teacher’s log-probabilities on the retained tokens. This is the signal.
topk_idx [n_rows, T, k] int32 Which vocabulary entries those log-probabilities belong to. Without it the values are unattributable.
tail_logprob [n_rows, T] float16 Log of the total discarded mass. One number per position; it is what makes the tail-bucket estimator possible at training time.
mask [n_rows, T] bool Which positions are supervised. Carried with the cache so stage two cannot disagree with stage one about it.
input_ids [n_rows, T] int32 The corpus itself, stored alongside, so the cache can prove what it was built from.
manifest.json scalar fields JSON k, vocab_size, seq_len, temperature, n_rows, corpus_fingerprint, bytes_on_disk.

Log-probabilities and not probabilities, because they are what an inference server returns and because they are numerically better behaved across the range the tail occupies. Log-probabilities and not logits, because logits are only defined up to an additive constant per position (Chapter 2’s shift invariance) and because storing normalized values removes any ambiguity about which temperature the normalization used. That last property turns into an obligation in §10.8.

The identifiers are the part people leave out and regret. Sequence identity is carried by input_ids, whose hash is the fingerprint. Position identity is carried implicitly by array position, which works only because the row order is fixed and the fingerprint hashes it. If you ever store a cache with rows in a different order than the corpus, the fingerprint will refuse it, which is the correct behavior and surprises people the first time.

10.5.1 Storage arithmetic#

Per position, a top- record costs values plus indices plus one tail value:

where is the bytes per stored log-probability and the bytes per stored index. The dense alternative costs , so the compression ratio is

With the course’s defaults, (fp16) and (int32), at and :

$$b_{\text{token}} = 64 \times 6 + 2 = 386 \text{ bytes}, \qquad \text{compression} = \frac{98{,}304}{386} = 254.7.$$

Over Lab 04’s corpus of positions that is 0.61 GB against a dense 154.6 GB, and at 2,000 tokens per second of prefill the build takes about 786 seconds, or 13 minutes, paid once.

Notice what dominates. Each retained entry costs 6 bytes, of which 2 are the value and 4 are the bookkeeping that says which token the value belongs to. Two-thirds of a top- cache is indices. That makes index width a real storage decision, not a detail.

The SmolLM2 vocabulary is 49,152, which is less than 65,536, so every index fits in a 2-byte unsigned integer. Switching topk_idx to uint16 changes the per-position cost to , which at is 258 bytes instead of 386: a third off the cache, and the compression ratio rises from 255 to 381. That is free, and it stops being free the moment the vocabulary crosses 65,536. Llama 3’s 128,256 and Qwen3’s padded 151,936 both exceed it, and a uint16 index in either of those vocabularies silently wraps: token 70,000 is stored as 4,464, the file is well-formed, the mass bookkeeping still closes because the log-probabilities are untouched, and the fingerprint still matches because the token ids are untouched. You would be training the student to put the teacher’s probability on the wrong words. Section 10.7’s third check exists for exactly this.

Going the other way, storing values as fp32 instead of fp16 costs bytes with int32 indices, which is 516 bytes per position at , a 34 percent increase for precision the objective does not use. Log-probabilities near the head are order unity and fp16 resolves them to about three decimal digits; log-probabilities deep in the tail are around and fp16 resolves those to better than a hundredth of a nat. The training loss upcasts everything to fp32 on read anyway. fp16 storage is the right default, and the rounding it introduces is what sets the tolerance on the round-trip checks in §10.7.

2026-08-01T07:28:00.847661 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 8 16 32 64 128 256 k, entries kept per position 100 1,000 10,000 100,000 bytes per cached position dense fp16 cache, V = 49,152: 98,304 bytes per position the course default: 386 bytes, 254.7x compression uint16 indices are legal only for V < 65,536: they fit here, and wrap silently at Llama 3's 128,256 or Qwen3's 151,936. b = k (b_value + b_index) + b_value. Two thirds of a top-k record at the course default is indices, which makes index width the larger of the two format decisions. fp16 value, uint16 index: 2k + 2k + 2 = 258 B at k = 64 fp16 value, int32 index: 2k + 4k + 2 = 386 B at k = 64 fp32 value, uint16 index: 4k + 2k + 4 = 388 B at k = 64 fp32 value, int32 index: 4k + 4k + 4 = 516 B at k = 64
Figure 10.4 Bytes per cached position against k for two index widths and two value dtypes, against the dense cost as a horizontal reference two orders of magnitude above, showing that index width is the larger of the two format decisions.

10.5.2 The writer#

The whole of stage one is a loop over the corpus that calls the following object. Read it for what is absent: no optimizer, no gradients, no student, no sampling.

import hashlib, json
import numpy as np, torch, torch.nn.functional as F

class CacheWriter:
    def __init__(self, path, k, temperature=1.0):
        self.path, self.k, self.T = path, k, temperature
        self.lp, self.idx, self.tail, self.ids, self.mask = [], [], [], [], []

    def append(self, teacher_logits, input_ids, mask):
        log_p = F.log_softmax(teacher_logits.float() / self.T, dim=-1)   # fp32, always
        top_lp, top_idx = log_p.topk(self.k, dim=-1)
        tail = (1.0 - top_lp.exp().sum(-1)).clamp_min(1e-9).log()
        self.lp.append(top_lp.half().cpu())        # 2 bytes per kept value
        self.idx.append(top_idx.int().cpu())       # 4 bytes per kept index
        self.tail.append(tail.half().cpu())        # 2 bytes per position
        self.ids.append(input_ids.int().cpu())
        self.mask.append(mask.bool().cpu())

    def finalize(self):
        arr = {n: torch.cat(getattr(self, n)).numpy()
               for n in ("lp", "idx", "tail", "ids", "mask")}
        for n, a in arr.items():
            np.save(f"{self.path}/{n}.npy", a)
        fp = hashlib.sha256(np.ascontiguousarray(arr["ids"]).tobytes()).hexdigest()[:16]
        json.dump({"k": self.k, "temperature": self.T, "corpus_fingerprint": fp,
                   "n_rows": len(arr["mask"]),
                   "bytes_on_disk": sum(a.nbytes for a in arr.values())},
                  open(f"{self.path}/manifest.json", "w"))

What this proves is that the format is decidable in twenty lines and that every design choice in §10.5 has exactly one line of code behind it. It also has one property worth criticizing: it accumulates the whole cache in RAM until finalize, which is the “cache write buffers 2.0 GB” line in Table 10.1 and which does not survive a hundred-million-token corpus. Section 10.10 is about fixing that, and fixing it is the same work as making the pipeline restartable.

10.5.3 An honest small imprecision#

Lab 04’s Part A.2 self-test predicts the bytes on disk before writing them and checks the prediction within 10 percent. The predicted expression is

top- payload, plus a tail term, plus one byte of mask and four bytes of token id per position. The middle term is a double count: already includes one tail value per position, and the expression adds a second one.

The arithmetic, on the self-test’s shapes of rows, positions, , . Actual bytes on disk are (log-probabilities) plus (indices) plus (tail) plus (mask) plus (ids), which is . Predicted is plus plus , which is . The difference is bytes, which is exactly the duplicated tail array, and the relative overshoot is 1.9 percent. The 10 percent tolerance absorbs it and the check still does its job, which is to catch a format that is off by a factor and not by two percent.

I am reporting this because reporting it is cheaper than the alternative. A reader who does the arithmetic will find the discrepancy, and a book that quietly reproduces a formula it knows is slightly wrong has spent something it will not get back. The general per-position cost on disk, counting everything, is

which is 391 bytes at and is the number Solutions 04’s disk column uses.

10.6 Corpus fingerprinting, and what it cannot prove#

A cache and a corpus have to be provably the same pair. Not probably. Provably, and cheaply enough that the proof runs before every training job rather than once when someone remembers.

Definition

Corpus fingerprint

A fixed-length hash of the exact token-id array a cache was built from, stored in the cache’s manifest, and recomputed from the corpus in hand before training. Changing any single token id, or reordering any two rows, changes the hash. Matching fingerprints establish that the cache and the corpus are byte-for-byte the same data.

The course’s version is 16 hexadecimal characters of

Both the contiguity call and the cast are load-bearing. A non-contiguous view of the same logical array hashes differently because its byte layout differs, and an int64 array of identical token ids hashes differently from an int32 one because every value carries four extra zero bytes. Fix the representation, or the check fails for reasons that have nothing to do with the data.

Row order is inside the hash, deliberately. A cache is an array indexed by row, so a different dataset shuffle produces a cache whose row describes a different conversation than your corpus’s row . That is one of the four ways a cache goes silently wrong. The other three are: built from different token ids entirely (a re-tokenization, an edited sequence length, a corpus regenerated with a different random subset), built at a different temperature, and built with a different tokenizer version, where the strings are the same and the integers are not.

What happens when a cache and a corpus diverge and nothing checks is worth walking through. You train the student to predict, at every position of conversation A, the distribution the teacher produced at the corresponding position of conversation B. The targets are unrelated to the inputs. And the loss still goes down, because misaligned targets contain something learnable: their average. Solutions 04 Exercise 1 stages this on a bigram teacher over a 64-token vocabulary with a student that has exactly the teacher’s capacity, so the correct pipeline must drive the loss to numerical zero and top-1 agreement to 1.0. It does. The misaligned run’s loss falls about a fifth and flattens, which on a dashboard reads as “converged, this is the task floor.” Agreement against the correctly shifted teacher sits near the percent chance floor, and the trained student’s 64 rows collapse to nearly one distribution, with a maximum pairwise total-variation distance under 0.35 against spreads near 1.0 in the teacher’s rows. Real learning, measurable loss reduction, zero conditional knowledge.

There is a quieter version in the same experiment. Slice the mask one position off and the loss curve matches the correct run to within , while exactly one supervised position per row is the wrong one: 96 out of 96 rows moved by exactly one boundary position. In a real corpus that position is the prompt-to-completion boundary, so the run trains on a prediction made from inside the prompt and silently drops each row’s final supervised token, which is the end-of-sequence marker. A model trained that way stops late or does not stop, and no loss curve will ever say so.

That is the whole case for fingerprinting, and it is also the case for the limitation that follows it. The fingerprint hashes the inputs. It says nothing about the outputs. A cache built at the wrong temperature, from a stale teacher checkpoint, or with a shift bug in the writer passes the fingerprint check perfectly, because in every one of those failure modes the token ids are exactly right. A cache that cannot prove it belongs to your corpus is a liability with good storage characteristics; a cache that proves only that is a liability you feel better about.

10.7 Validating a purchased asset#

Definition

Purchased asset

Any training artifact you did not produce yourself and intend to train on: a logit cache, a corpus of teacher generations, a published trace dataset. The defining property is that you cannot audit its construction, so every claim about it has to be re-derived from the artifact itself.

The word “purchased” is doing double duty. Sometimes it means money changed hands. More often it means a colleague built the cache three months ago, or a previous version of you did, and the process that produced it is no longer available for inspection. Treat both cases the same, and treat your own caches this way too, because the checks cost seconds and the failure they prevent costs a training run.

Three checks, cheapest first.

Identity. Recompute the corpus fingerprint from the token ids in hand and compare against the manifest. This is a hash over a few megabytes and it either matches or it does not.

Mass bookkeeping. For a sample of supervised positions, exponentiate the retained log-probabilities, sum them, add the exponentiated tail, and check the result is 1 within tolerance. Be precise about what this catches, because it is easy to overrate. The tail was defined by the writer as one minus the retained sum, so the identity holds by construction and the check is really verifying three other things: that the two arrays are still row-aligned with each other, that fp16 storage did not damage anything beyond its rounding budget, and that nobody concatenated caches built at different temperatures or different . Those are common failures. A wrong teacher is not among them.

Dtype and index width. Assert that every stored index lies in and that each array’s dtype matches the manifest. This is the check that catches the uint16 wrap from §10.5.1, where a 128k-vocabulary cache written with 2-byte indices produces a perfectly well-formed file whose indices point at the wrong words. It also catches a cache whose vocabulary size does not match the tokenizer you are about to use, which happens whenever someone swaps a model revision that added special tokens.

Then the one that actually proves provenance.

Spot re-runs. Sample rows, run the teacher again on exactly those rows, recompute the top- log-probabilities at the manifest’s recorded temperature, and compare against what is stored. This is the only check that can distinguish “these numbers came from the teacher I think” from “these numbers came from somewhere.” Re-derivation is the only proof.

def accept_cache(reader, corpus_ids, score_fn, sample_rows, vocab, atol=5e-2):
    """Three cheap checks then one expensive one. Any failure means do not train."""
    fp = sha256(np.ascontiguousarray(corpus_ids.astype(np.int32)).tobytes()).hexdigest()[:16]
    assert fp == reader.manifest["corpus_fingerprint"], "cache built from different token ids"

    b = reader.batch(sample_rows)
    kept = b["topk_logprobs"].exp().sum(-1)
    closes = (kept + b["tail_logprob"].exp() - 1.0).abs()[b["mask"]].max()
    assert float(closes) < 1e-2, f"mass does not close: max |sum - 1| = {float(closes):.4f}"

    assert int(b["topk_idx"].max()) < vocab and int(b["topk_idx"].min()) >= 0, "index out of range"
    assert reader.manifest["vocab_size"] == vocab, "cache vocabulary != tokenizer vocabulary"

    log_p = F.log_softmax(score_fn(b["input_ids"]).float()
                          / reader.manifest["temperature"], dim=-1)
    err = (log_p.gather(-1, b["topk_idx"]) - b["topk_logprobs"]).abs()[b["mask"]].max()
    assert float(err) < atol, f"spot check failed: max |dlogprob| = {float(err):.4f}"
    return float(err)

What this proves is that acceptance is a function you can write down and call, which means it is a function you can call every time instead of when you feel uneasy. Two parameters in it are judgment calls. atol is set to against a synthetic scorer in fp32 and relaxed to in the real pipeline, because a bf16 forward pass plus fp16 storage produces noise of that order while systematic bugs produce errors far larger; the tolerance has to split those two populations and it does. And the sample size: Lab 04 checks 40 rows out of thousands and recommends about one percent generally. One percent sounds indefensibly small until you notice what the check is for. Every failure mode it catches is systematic. A wrong temperature corrupts every row. A stale checkpoint corrupts every row. A shift bug corrupts every row. Sampling a percent of a population where the defect rate is either zero or one hundred percent catches the defect with effective certainty, and the sample size is set by wanting a few independent looks rather than by any statistical power calculation.

10.7.1 The tamper test#

A check that cannot fail is decoration, not verification. Lab 04’s Part A does not assert that its checks pass on good data and stop there; it constructs bad data and asserts that the checks fire.

The attack is minimal on purpose. Build a cache over a synthetic batch of six rows, forty positions, a 512-token vocabulary, at , using two separate append calls to prove that incremental writing works. Verify the fingerprint against the true corpus, which passes. Then clone the token-id array, add 1 to a single element, tampered[0, 0] += 1, and verify again. That call must raise, and the test fails if it does not: the code wraps it and raises a different error if no assertion came out. One token id out of 240, changed by one, and the cache refuses the corpus.

The same cell attacks the spot-check protocol. It runs the protocol against an honest scorer, which returns exactly the logits the cache was built from, and the maximum absolute error comes out at fp16 storage noise, which passes. Then it runs the protocol against the same scorer with every logit divided by 2, a temperature mismatch and nothing else, and the check must reject. Both directions are asserted, so a future change that accidentally makes the protocol permissive is caught by the test suite rather than by a bad model six weeks later.

The failure signature is diagnostic. A temperature mismatch shifts every log-probability by a related amount, so the errors are large and uniform across positions. Scattered large errors at a few positions mean something else: a wrong teacher revision or a dtype drift, and the fix is to diff the manifest against what stage two loaded. A check that tells you which failure you have is worth more than a check that tells you that you have one.

When a check fires, do not comment it out. The check firing is the system working.

10.8 Caching at a temperature other than 1#

The cache does not store logits. It stores log-probabilities normalized at a specific temperature, because the writer divides by before the log-softmax. That makes temperature part of the cache’s identity in the same way the corpus fingerprint is, with one difference that turns out to matter enormously: the fingerprint is recomputable from the data, and the temperature is not.

Given a stored vector of top- log-probabilities you cannot recover the logits, because the softmax discarded the additive constant and the division by discarded the scale. You cannot re-normalize a cache to after the fact, because doing so would require the tail’s per-token structure, which is exactly what truncation deleted. The temperature is recorded in the manifest and nowhere else, and if the manifest is wrong, nothing in the arrays will tell you.

Raising the caching temperature changes two things.

The first is coverage, and the size of the effect is larger than intuition suggests. Solutions 04 Exercise 4 builds two caches from the same teacher logits, one at and one at , and measures mean retained mass at on supervised positions. The cache holds 99.5 percent. The cache holds about 70 percent. Dividing every logit by 2 flattens the softmax enough to push nearly 30 percent of the probability past any fixed . Which means a temperature choice silently relocates the knee from §10.4: soften the teacher and you need a much larger for the same fidelity. The consequence shows up immediately in the objective. Even the matched cached loss sat 0.21 below the exact dense KL at , because at 70 percent coverage the truncation is no longer a few-percent effect.

The second is where temperature has to agree. It has to agree between the cache build and the training loss, because the cache holds probabilities already normalized at cache_T while topk_forward_kl’s temperature argument softens only the student. Feed a cache to a loss and you are not adding noise; you are optimizing a different function. The live measurement puts the mismatched loss several times farther from the true KL than the honest truncation gap, and the assertion demands at least a factor of three. Unlike a bug that raises, it trains without complaint, converging to a nonzero floor and producing a student measurably flatter than the teacher, because it was trained to imitate a softened target it believed was sharp.

Where temperature is free: the choice itself. A cache with a training loss is a coherent experiment; it is the soft arm from Chapter 5, cached. Eval-time diagnostics are computed at their own temperatures from raw student logits, so they do not constrain the cache. A hard-label term, if you keep one in the mixture, always runs at independently. Freedom to choose, no freedom to disagree.

The demonstration of the detection is the sharpest single argument for manifests I know. Solutions 04 takes the honest cache, overrides the reader’s manifest temperature to 1.0 to simulate an operator error, and re-runs the spot check, which fails loudly. The tensors on disk were identical in the honest and the dishonest run. Only the recorded temperature let the protocol tell them apart.

10.9 Tail on versus tail off, as a training decision#

Section 10.3 treated the two truncation choices as estimators of a fixed quantity, and by that standard the renormalized version looks like a small few-percent bias you might reasonably accept to save two bytes per position. That framing is incomplete in a way that matters, because in training you are not estimating a number. You are minimizing a function, and the two choices have different minimizers.

Work out what each objective wants. The tail-bucket loss contains the term , which is minimized when the student’s total off-top- mass equals the teacher’s, so its minimizer keeps a tail of the right size. The renormalized loss is , in which the student appears only through , subject to . That is minimized by putting on the kept tokens and zero everywhere else. Under the renormalized objective the tail is actively squeezed out instead of left unsupervised, and entropy has to fall.

Solutions 04 Exercise 3 measures this directly on a synthetic teacher over a 512-token vocabulary with a substantial tail, about 19 percent of its mass outside the top 16, cached at . It then optimizes a free student logit vector against each objective for 50 steps, which lands close to each objective’s true minimizer because the student is unconstrained. The tail-bucket minimizer keeps a tail within a factor of two of the teacher’s. The renormalized minimizer puts under one percent of its mass outside the cached top-, against the teacher’s 19 percent, and pays more than a full nat of entropy for it; the notebook quotes the gap at 1.4 nats and asserts it exceeds 0.5.

That is not a small effect and it is invisible in the loss, because the loss is what asked for it. It shows up downstream, as overconfidence on exactly the rare tokens where the teacher was uncertain, compounding over a generation into degraded diversity and worse calibration.1011 A student that never sees the tail term is systematically sharper than its teacher on the inputs where sharpness is least warranted.

At realistic training budgets the effect is smaller than the toy’s, because a real model at 500 steps is far from its objective’s minimizer. Solutions 04 expects a held-out entropy gap of a few hundredths to a tenth of a nat between the tail-off and tail-on arms at that budget, widening with more steps, and expects agreement to stay close to tied because the top-1 token lives inside the top- either way. Entropy down with agreement flat is the confirming signature. If instead the tail-off arm’s entropy comes out higher, check the mask before anything else: an objective this lopsided losing its direction usually means the average is including unsupervised positions.

The tail bucket costs one fp16 value per position, which at is 0.5 percent of the record. Keep it.

10.10 Restartable multi-stage pipelines#

Stage one over a hundred-million-token corpus is hours of prefill. Hours of anything on a single machine will eventually meet a kernel panic, a driver reset, an out-of-memory kill from something else on the box, or a person who needed the GPU. The writer in §10.5.2 accumulates everything in RAM and writes at the end, which means all of that work is lost when any of those happen at 80 percent.

The fix is to make the stage boundary durable, and then to make a boundary inside stage one as well. Concretely:

Write incrementally, in shards. Every rows, flush the accumulators to a numbered shard file and clear them. The reader concatenates shards at open time, or memory-maps them as a list. This caps peak memory at rows regardless of corpus size, which is the same change that removes the 2 GB write-buffer line item from Table 10.1.

Record progress next to the data. A tiny JSON file saying which row ranges are complete, updated after each shard flush and fsynced. On restart, read it, skip what is done, and continue. The temptation is to infer progress from which files exist, which fails the first time a process dies mid-write and leaves a truncated shard.

Make the manifest the last thing written. A cache without a manifest is visibly incomplete. A cache with a manifest and missing rows is invisibly incomplete, and something downstream will train on it.

Fingerprint the inputs before starting, not after finishing. If the corpus changes while stage one is running, you want to know at the restart rather than at the acceptance check three hours later.

Field note

The cost of building this is real: a few hours of engineering, a shard index to maintain, and a reader that is more complicated than np.load. I have argued myself out of paying it, on the grounds that the job was only a few hours and I would be watching it, and I have watched people I respect make the same argument. Then something outside the job takes the machine at eighty percent and the whole thing runs again overnight. The break-even is one failure. On a job long enough to need this, one failure is not a tail risk.

The discipline the manifest enforces is the other half of the value. If you rebuild a cache more than once per corpus-and-teacher combination, something in your bookkeeping failed. The manifest, plus the run manifest that records artifacts_in as the cache’s fingerprint and artifacts_out as the checkpoint path, gives you a provenance chain that runs from a trained model back to the exact cache and therefore to the exact corpus. Chapter 18 makes that chain a requirement for a study you can defend; here it is a requirement for not doing the same work twice.

10.11 When cached training matches live training, and when it cannot#

The acceptance criterion is specific and you should write it down before you run anything.

A cached run has reproduced a live-teacher run when, at matched steps, top-1 agreement against the teacher and forward KL on held-out data are both within the run-to-run noise band you measured from seeds, and training throughput is materially higher. Note what the criterion compares: the cached run against the live run, not either run against the teacher. Agreement with the teacher is not something a distilled student reliably achieves in the first place,19 so an absolute agreement target would measure the method and not the pipeline. All three clauses matter. Agreement and KL within noise says the objective was not damaged by truncation. Materially higher throughput says the pipeline bought you something. And “at matched steps” says you are not comparing a longer run to a shorter one.

Lab 04 expects the cached forward KL to land in the same band as the live-teacher mixed arm from Lab 03 at equal steps, because the two objectives differ only by a truncation that is single-digit percent at , which is inside seed noise. You should not be able to tell the runs apart by that number. It expects throughput between 1.5 and 3 times the live run’s, matching the arithmetic from §10.2. And it expects the cache build to take minutes, once.

Be honest about the marginal case. For a 1.7B teacher against a 360M student, the speedup is around 2.5 and the engineering is a day. That may or may not be worth it to you. Lab 04 tells you to measure tokens per second with and without the teacher resident and write down at what teacher size the cache stops being marginal for your setup, because that number is then yours rather than mine. For a 32B teacher the answer is not close.

The pipeline cannot substitute for a live teacher in five situations, and they are listed separately because they are genuinely separate situations and not one situation in disguise.

The student generates. On-policy training scores text that does not exist until the student writes it. No cache covers it. Chapter 12.

The corpus moves. Curriculum schedules, data mixtures that change with step, retrieval-augmented inputs assembled at training time. Anything where the token ids at step 900 are not knowable at step 0 breaks the invariance in §10.1.

The teacher moves. Iterative or self-distillation schemes where the teacher is periodically replaced by a checkpoint of the student require a new cache each time the teacher changes.12 That is affordable if the number of rounds is small and a disaster if it is not.

The objective needs more than the top . Reverse KL and the generalized JSD family need the student’s mass on tokens the teacher ranked low, which a top- cache summarizes into one bucket instead of storing. Forward KL is the direction that truncates gracefully, because it weights each term by the teacher’s probability, and the teacher’s probability on discarded tokens is by construction small. That is a real asymmetry and it is worth knowing before you plan a divergence ablation on cached data.1314

The tokenizers differ. A cache is indexed by the teacher’s vocabulary. If the student’s vocabulary is a different one, the indices do not mean anything to it, and Chapter 7’s result applies: there is no position-wise alignment to recover. Chapter 14 covers what you can do instead.

There is a sixth case that is a temptation and not a limitation. It is possible to quantize the teacher for stage one, since stage one is a one-time cost you would like to shorten, and quantization formats like AWQ and GPTQ make a large teacher much faster to prefill.1516 Understand what you are buying: the quantized teacher’s distribution is not the original teacher’s distribution, and once it is in the cache, every training step for the life of the project is supervised by the quantized version. That may be fine. It is a decision that deserves to be made explicitly, with a measurement of the divergence between the two teachers on a sample, rather than absorbed as a speed optimization.

10.12 Where this lands in the labs#

Lab 04 is the chapter, executed, and the truncation measurement inside it is older than Lab 04 itself, having passed through Lab 01’s two estimators and Lab 02’s sweep against real teacher distributions first. Its Part A prices the cache, builds the format on synthetic logits, and attacks it, all before a model loads; its Part B is the two stages with the integrity calls between them, which the notebook calls the pipeline’s load-bearing wall and not ceremony. The thing the lab does that this chapter cannot is let you feel the loss curve lie to you. Solutions 04 Exercise 1 trains three arms on a bigram world small enough to converge in seconds, one correct and two sabotaged, and the curves are indistinguishable while the agreement numbers differ by a factor of sixty. Reading about that is not the same as watching it, and Solutions 02 Exercise 2 is the one to imitate on your own corpus, because it ends in a written decision instead of a table.

10.13 Exercises#

  1. Starting from the two identities in §10.3.1 and §10.3.2, write $D_{\text{ren}} - D_{\text{tail}}\tauM_qH_SD(a|b)$. Then say which of those four quantities you can compute from a cache alone, which require the dense teacher distribution, and what that implies about whether the bracket width can be monitored during training and not only before it.

  2. Construct a teacher distribution and a student distribution over a small vocabulary, with , for which the renormalized estimator understates the dense forward KL. Use the sufficient condition in §10.3.2 to guide the construction, then verify by direct arithmetic. Say in one sentence what property of the student made it happen and which chapter of this book warned you about that property.

  3. Solutions 04 Exercise 4 caches the same teacher logits at and and measures retained mass at . Before looking at §10.8’s numbers again, predict the retained mass given that the value is 0.995, using only the fact that dividing logits by divides all pairwise log-odds by . Then compare against the measured 70 percent and say whether your reasoning was too optimistic or too pessimistic, and why.

  4. For a teacher with and one with , compute bytes per cached position at for every combination of index width in bytes and value dtype in bytes. Mark every cell that is illegal and say why. Then state the corpus size in tokens at which the 2-byte-index saving pays for a day of engineering, using any hourly rate you like, and say what that tells you about when format micro-optimization is worth doing.

  5. You are handed four cached-logit runs and their symptoms. (a) The loss sits flat at a high value from step 1 and never moves. (b) The loss reaches a suspiciously perfect near-zero within 50 steps. (c) The loss curve is indistinguishable from a known-good run but the trained model never emits an end-of-sequence token. (d) The loss converges to a nonzero floor and the student’s held-out entropy is measurably above the teacher’s. For each, name the most likely cause, the single check that would have caught it before training, and one alternative cause you would rule out second.

  6. The listing in §10.7 runs four assertions. Name one realistic corruption of a purchased cache that passes all four, describe the training symptom it would produce, and propose a fifth check that would catch it. Then say what your fifth check costs, in wall clock and in engineering, and whether you would run it every time or once per cache.

  7. A colleague has a 3B teacher, a 1B student, a corpus of 20 million tokens, and plans to train for two epochs. Using §10.2’s arithmetic and the reference machine’s figures, estimate the cached pipeline’s total saving in wall clock against a live-teacher run, and estimate the engineering cost of building the cache pipeline properly (shards, manifest, acceptance). Give a recommendation and name the one number that, if it changed, would flip it.



  1. Marc’Aurelio Ranzato, Sumit Chopra, Michael Auli, and Wojciech Zaremba, “Sequence Level Training with Recurrent Neural Networks,” arXiv:1511.06732 (2015), ICLR 2016. https://arxiv.org/abs/1511.06732 

  2. Samy Bengio, Oriol Vinyals, Navdeep Jaitly, and Noam Shazeer, “Scheduled Sampling for Sequence Prediction with Recurrent Neural Networks,” arXiv:1506.03099 (2015), NeurIPS 2015. https://arxiv.org/abs/1506.03099 

  3. Rishabh Agarwal, Nino Vieillard, Yongchao Zhou, Piotr Stanczyk, Sabela Ramos, Matthieu Geist, and Olivier Bachem, “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649. The method is generalized knowledge distillation; the abbreviation GKD does not appear in the title. 

  4. Mingyang Song and Mao Zheng, “A Survey of On-Policy Distillation for Large Language Models,” arXiv:2604.00626 (2026). https://arxiv.org/abs/2604.00626. An unrefereed living preprint whose own comment field describes it as ongoing work; useful as a map of the area rather than as a settled account of it. 

  5. Lucas Beyer, Xiaohua Zhai, Amélie Royer, Larisa Markeeva, Rohan Anil, and Alexander Kolesnikov, “Knowledge distillation: A good teacher is patient and consistent,” arXiv:2106.05237 (2021), CVPR 2022. https://arxiv.org/abs/2106.05237 

  6. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015), §2. https://arxiv.org/abs/1503.02531 

  7. Woosuk Kwon, Zhuohan Li, Siyuan Zhuang, Ying Sheng, Lianmin Zheng, Cody Hao Yu, Joseph E. Gonzalez, Hao Zhang, and Ion Stoica, “Efficient Memory Management for Large Language Model Serving with PagedAttention,” arXiv:2309.06180 (2023), SOSP 2023, 611-626. https://doi.org/10.1145/3600006.3613165 

  8. Nicholas Carlini et al., “Stealing Part of a Production Language Model,” arXiv:2403.06634 (2024), ICML 2024. https://arxiv.org/abs/2403.06634. The paper is about what an attacker recovers from log-probability responses, which is the same accounting a defender uses to decide how much of the distribution to expose. 

  9. Florian Tramèr, Fan Zhang, Ari Juels, Michael K. Reiter, and Thomas Ristenpart, “Stealing Machine Learning Models via Prediction APIs,” arXiv:1609.02943 (2016), 25th USENIX Security Symposium, 601-618. https://arxiv.org/abs/1609.02943 

  10. Ari Holtzman, Jan Buys, Li Du, Maxwell Forbes, and Yejin Choi, “The Curious Case of Neural Text Degeneration,” arXiv:1904.09751 (2019), ICLR 2020. https://arxiv.org/abs/1904.09751. The relationship between a model’s tail behavior and the quality of what it generates is the subject of the paper’s first half. 

  11. Chuan Guo, Geoff Pleiss, Yu Sun, and Kilian Q. Weinberger, “On Calibration of Modern Neural Networks,” arXiv:1706.04599 (2017), ICML 2017. https://arxiv.org/abs/1706.04599 

  12. Tommaso Furlanello, Zachary C. Lipton, Michael Tschannen, Laurent Itti, and Anima Anandkumar, “Born Again Neural Networks,” arXiv:1805.04770 (2018), ICML 2018. https://arxiv.org/abs/1805.04770 

  13. Yuqiao Wen, Zichao Li, Wenyu Du, and Lili Mou, “f-Divergence Minimization for Sequence-Level Knowledge Distillation,” arXiv:2307.15190 (2023), ACL 2023. https://arxiv.org/abs/2307.15190 

  14. Yuxian Gu, Li Dong, Furu Wei, and Minlie Huang, “MiniLLM: Knowledge Distillation of Large Language Models,” arXiv:2306.08543 (2023), ICLR 2024. https://arxiv.org/abs/2306.08543v2. The arXiv landing page now shows a later title; the ICLR 2024 version of record is the one cited here. 

  15. Ji Lin et al., “AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration,” arXiv:2306.00978 (2023), MLSys 2024. https://arxiv.org/abs/2306.00978 

  16. Elias Frantar, Saleh Ashkboos, Torsten Hoefler, and Dan Alistarh, “GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers,” arXiv:2210.17323 (2022), ICLR 2023. https://arxiv.org/abs/2210.17323 

  17. Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big, Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737. The model family used throughout the course’s Tier 2 labs, and the source of the 49,152-entry vocabulary that every storage figure in this chapter is computed against. 

  18. Qwen Team, “Qwen2.5 Technical Report,” arXiv:2412.15115 (2024). https://arxiv.org/abs/2412.15115. Chapter 7 works through this family’s three different vocabulary numbers; the padded embedding count is the one that sets cache size. 

  19. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945. The reason the acceptance criterion in §10.11 is stated as agreement against the live-teacher run rather than agreement against the teacher: fidelity to the teacher is not what a distilled student reliably achieves, so the comparison that means something is cached-versus-live under identical conditions. 

Part IV · The Method Space

11

Sequence-Level and Black-Box Distillation

A vendor sells you access to a model that is better than anything you can run. You send it a prompt over HTTPS and it sends back text. There is no logprobs parameter in the request schema, no way to ask for the top twenty candidates at each position, no weights to download, and a terms of service document that says something careful about derivative works. You have a student model on your own hardware, a corpus of prompts from your product, and a quality target.

Everything in Part II and Part III assumed you could read the teacher’s output distribution. Chapter 5’s objective compares two probability vectors, Chapter 6’s divergence choice is a choice about how to compare them, and Chapter 10’s cache stores them. Take the distribution away and every one of those methods stops existing at once, leaving you holding a stack of generated text.

This chapter is about what you can do with the text. The answer is: quite a lot, using the simplest loss in the book, at a cost that is the highest in the book. Both halves of that sentence need earning, and the cost half is the one most treatments skip, which is why the pricing arithmetic arrives before the method, not after.

One boundary, set at the start. Training the student on text the student generated, scored by the teacher afterward, is on-policy distillation and belongs to Chapter 12. Here the teacher generates and the student learns from what came out.

11.1 The situation: text and nothing else#

You can arrive here by four different routes, and they are worth separating because their escape routes differ.

The API returns text only. The common case. Many hosted models have never exposed log-probabilities; several that once did have withdrawn the feature, and the reason is not paranoia. Carlini and colleagues showed that a top- log-probability endpoint leaks structural information about a production model, enough to recover the width of its final projection and, with more queries, the projection matrix itself up to a symmetry, and the affected providers changed their APIs after disclosure.1 Chapter 17 covers the extraction side properly. The consequence for you is that Chapter 1’s grey-box row is shrinking, not growing.

You can query the model but not download it. Weights behind a license that permits inference but not redistribution, a model served inside a partner’s VPC, an internal model owned by a team that will run inference for you but will not hand over a checkpoint. Unlimited access to outputs, none to internals.

The tokenizers do not line up. This one surprises people, because it feels like a white-box situation. You have both models on disk, you can read every logit the teacher produces, and the logits are still useless for a position-wise loss, because the teacher’s position 7 and the student’s position 7 are not the same span of text. Chapter 7 proved that no position-wise alignment exists across tokenizer families in general, and Chapter 14 covers the methods that recover some signal anyway, chiefly Universal Logit Distillation.2 Until you reach for one of those, a teacher with a different tokenizer is a black-box teacher whose outputs happen to arrive as tensors instead of as JSON.

The legal situation makes logits the wrong thing to hold. A contract permits you to use generated outputs and says nothing about model internals, or says something about them you would rather not test. Generated text has a clearer provenance story than a cache of a model’s internal scores, and for some organizations that difference decides the architecture.

Definition

Black-box distillation

Distillation in which the only thing the teacher provides is its output text. No logits, no internal activations, no gradients, and often no ability to re-query the teacher at all if the text was purchased instead of generated. The student is trained with ordinary next-token cross-entropy on that text.

2026-08-01T07:33:14.830744 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1. White box (Chapters 5, 10) ...49,140 more one vector per position, V = 49,152 numbers teacher cost 1.6 s prefill STUDENT TEACHER 2. Sequence level (this chapter) one id per position, log2(V) = 15.58 bits teacher cost 408 s decode STUDENT TEACHER 504 7585 2581 314 260 5486 515 42 3. Trace SFT (purchased text) one id per position, and no say in the prompt teacher cost 0 s already paid STUDENT TEACHER not yours corpus on disk 504 7585 2581 314 260 5486 515 42
Figure 11.1 What crosses from teacher to student in each of the three access regimes, and how much of the teacher's distribution survives the crossing.

The figure is worth sitting with before the derivation, because the whole chapter is a consequence of it. In the white-box regime a full probability vector crosses per position, tens of thousands of numbers. In the sequence-level regime one token id crosses per position, bits against the vector’s much larger content. In the trace regime the same token id crosses, but it was produced by somebody else’s teacher on somebody else’s prompts, so you also lose the ability to choose what the teacher was asked.

11.2 Sequence-level knowledge distillation, derived#

11.2.1 The exact objective#

Write for the teacher and for the student. For an input , a full output sequence is with each drawn from a vocabulary . Both models define a distribution over whole sequences by the chain rule:

where means the tokens before position .

The token-level objective of Chapter 5 compares and one position at a time, under context supplied by a fixed corpus.3 The sequence-level objective compares the two sequence distributions directly, as a cross-entropy over sequences:

where is the set of all sequences of length up to over . This is the honest statement of what “match the teacher at the sequence level” means, and it is the object Kim and Rush start from.4

It is also completely intractable, and it is worth being concrete about how intractable rather than waving at it. Take the course’s own configuration: a SmolLM2 vocabulary of 49,152 tokens and a generation cap of 256 new tokens. Then

so the sum runs over roughly terms. No factorization rescues it: does factor across positions, but weights each term by the probability of the entire prefix, which couples the positions and blocks pushing the sum inside.

11.2.2 The mode approximation#

Kim and Rush’s move is to replace the teacher’s sequence distribution with a point mass on its most likely sequence. Define

and substitute into the objective. Every term in the sum except one is multiplied by zero, and what is left is

which is ordinary next-token cross-entropy on the sequence , with playing the role of the ground-truth continuation. No temperature. No divergence. No teacher at training time at all. If you have a supervised fine-tuning loop, you already have an implementation; the only thing that changes is which text goes into it.

Definition

Sequence-level knowledge distillation

Training the student to reproduce whole output sequences produced by the teacher, rather than matching the teacher’s per-position distributions. In Kim and Rush’s formulation the intractable sum over all sequences is approximated by the teacher’s single most likely output, which reduces the loss to next-token cross-entropy on teacher-generated text. Abbreviated SeqKD.

Definition

Mode approximation

Replacing a distribution by a point mass at its most probable outcome for the purpose of computing an expectation. In sequence-level KD it turns an expectation over sequences into a single term, which is what makes the objective computable at all. The approximation is exact only when the distribution is a point mass to begin with, and is otherwise biased by an amount nobody can compute for the case that matters.

11.2.3 What the approximation assumes, stated plainly#

I want to name the assumptions instead of letting them stay implicit, because two of the three are false in this setting and the method works anyway, and that combination is the interesting part.

Assumption one: the teacher’s sequence distribution is concentrated enough that one point carries the behavior. Check it. Suppose the teacher’s greedy token averages probability 0.9 across a completion, which is high for an instruction-tuned model on open-ended text. Then

so the mode holds about of the probability mass. The approximation replaces a distribution with a point carrying essentially none of it. By any measure of mass captured, this is a bad approximation, and saying otherwise would be dishonest.

Assumption two: you can find the mode. You cannot. Finding over sequences is itself intractable, for the same reason the sum is: the greedy token at each position need not lie on the globally most probable path. Greedy decoding is a heuristic for the mode, beam search is a better heuristic, and neither is the mode. The practical method is therefore an approximation of an approximation, and Kim and Rush say as much, using beam search precisely because it gets closer to the mode than greedy does.4

Assumption three: matching the teacher on its most likely output is what you want. This one is a design choice and not an error, and it determines whether the method suits your problem. If your deployment metric rewards producing the single best answer, training on modes is aligned with the metric. If it rewards a distribution over answers, calibrated confidence, or diversity across repeated queries, training on modes is aimed somewhere else, and §11.3 measures how far.

So why does it work? Three explanations circulate, and the field has not settled among them. The first is that the gradient direction, not the loss value, is what matters: a heavily biased estimate of the expectation can still point somewhere useful if the bias is roughly constant across the parameters being updated. That is plausible and, to my knowledge, unproven here. The second is the curriculum argument from Chapter 1, that teacher-generated text is easier to learn from than natural text because it is internally consistent, drawn from a distribution a model of this family can represent, and free of the noise scraped corpora carry. On that account SeqKD is doing data cleaning as much as knowledge transfer. The third is that the mode approximation happens to implement a mode-seeking objective, which is sometimes the right one: Chapter 6 derived how reverse KL concentrates a student on a subset of the teacher’s behavior instead of smearing it across everything, and greedy SeqKD reaches a similar destination through the data instead of the loss. If your teacher is multimodal and your student cannot cover all the modes, being pushed onto one is better than sitting between them.

A fourth possibility is that the sequence-level framing is not doing the work at all, and the method is ordinary supervised fine-tuning on high quality data with a distillation-shaped story attached. Wen and colleagues took the framing seriously enough to generalize it, minimizing an arbitrary -divergence at the sequence level rather than the one the cross-entropy form implies, and reported that the choice of divergence matters at the sequence level in ways the mode approximation cannot express.5 That is evidence the framing carries content, not proof that the content is what makes vanilla SeqKD work.

11.3 Greedy, sampled, and what the estimator actually says#

Once you commit to generating a corpus you have to decide how to decode, and this decision changes what the student learns more than any other. Start from the statistics, because they cut the other way from the tradition. Suppose instead of the mode you draw by ancestral sampling at temperature 1, sampling each token from the teacher’s actual next-token distribution and feeding it back in. Then

exactly. A sampled sequence gives an unbiased single-sample Monte Carlo estimate of the exact sequence-level objective. Chapter 4’s machinery applies unchanged, including the variance question: one sample per prompt is a high variance estimate, and you reduce the variance with more prompts or more samples per prompt, not with anything clever.

So the statistically correct implementation of sequence-level KD is sampling, and the mode version that carries the name is the biased one. Hold onto that inversion: the literature’s framing can leave you thinking greedy is principled and sampling is a shortcut.

Greedy decoding produces exactly one continuation per prompt, deterministically. Ask the same model the same prompt four times and you get four byte-identical strings. Solutions 06’s first exercise measures this instead of asserting it, generating with SmolLM2-135M in fp32 on eight eval prompts with four repeats each: greedy produced 1 unique completion out of 4 with a within-set self-BLEU of 1.000, while sampling at produced at least three distinct completions with a lower self-BLEU.67 The notebook asserts the 1.000 because a metric implementation that fails to return it on identical strings is broken.

Sampling at produces a spread of continuations per prompt, drawn from the teacher’s own distribution over sequences. Your corpus now carries information about how much the teacher hedges, which the mode corpus cannot carry by construction, along with the teacher’s mistakes at their true rate and the tail behaviors greedy decoding never surfaces.

In the labs: Lab 06

The greedy-versus-sampled mechanism is demonstrated with a 135M model on eight prompts and no training at all, which is the point: it is a property of the generated text and not of the student, so you can measure it before committing to the expensive phase.

11.3.1 What the measurement predicts#

Lab 06’s Part C expects a greedy SeqKD student’s entropy to run 0.1 to 0.4 nats below both the cached-logit and trace-SFT students, and expects the cached-logit student to beat greedy SeqKD on top-1 agreement by 1 to 5 points at equal steps. The mechanism for the entropy gap is the mode approximation itself: a corpus containing only the teacher’s single most likely continuation per prompt is a training signal that says “be certain,” and the student obliges. This is the narrowing the post-training diversity literature reports for supervised fine-tuning generally.8

Regenerating at is expected to lift the sampled student’s entropy 0.1 to 0.3 nats above the greedy student’s, closing part of that gap, while costing a point or two of top-1 agreement, because greedy data concentrates supervision on exactly the tokens the agreement metric scores. Those two numbers are predictions grounded in Part C’s ranges, not measurements, and Solutions 06 labels them that way; the regenerate-and-retrain arm is written out and gated off.

The trade to name: sampling buys back diversity and calibration at a small cost in mode-matching, so which one wins depends on whether your deployment metric rewards the mode or the distribution. A short-answer factual task with exact-match scoring rewards the mode; an open-ended assistant judged on preference over repeated interactions rewards the distribution. Deciding this before you generate saves you from generating twice.

11.3.2 The EOS decision, which is not optional#

The second decision inside corpus construction costs nothing to get right and ruins a run when you get it wrong. Generation runs under a cap, max_new_tokens, and some completions reach the cap without ever emitting the end-of-sequence token. Those are completions with no ending. Train on them and you supervise the student to keep producing tokens at a position where the teacher would have kept producing too, while never showing it a stopping example for that prompt. Trained on enough of those, the student learns not to stop.

The fix is to drop every generation that hit the cap without emitting EOS, and to print the drop rate. The rate is a data quality signal, which is why Lab 06 calls it load bearing: one percent means your cap is generous, twenty percent means your cap is fighting your teacher and one of the two needs to change. Sampled generations wander, so sampling raises the rate, and the rate is worth re-checking whenever you change decoding parameters.

The listing below is the shape of a corpus builder with both decisions visible. Watch two things: the temperature argument is None under greedy decoding rather than 1.0, because passing a temperature alongside deterministic decoding is a contradiction libraries handle inconsistently across versions, and the EOS test decides whether a generation is kept at all.

def build_corpus(teacher, tok, prompts, max_new=256, greedy=True):
    """Generate one completion per prompt. Return kept rows and the drop rate."""
    kept, dropped = [], 0
    for prompt_ids in prompts:                       # each is [1, plen]
        plen = prompt_ids.shape[1]
        with torch.no_grad():
            out = teacher.generate(
                prompt_ids,
                do_sample=not greedy,
                temperature=None if greedy else 1.0,  # not 1.0 under greedy
                max_new_tokens=max_new,
                pad_token_id=tok.eos_token_id,
            )
        completion = out[0, plen:]
        if tok.eos_token_id not in completion:        # hit the cap: no ending
            dropped += 1                              # drop it, do not truncate it
            continue
        kept.append({"prompt_len": plen, "ids": out[0].cpu()})
    return kept, dropped / max(1, len(prompts))

What the listing proves is how little there is to sequence-level KD once the corpus exists: nothing downstream of this function knows a teacher was involved. The tensor goes into the same supervised fine-tuning loop you would use for hand-written data, prompt span masked out of the loss and completion span supervised. Lab 06 runs Lab 03’s kd_step at alpha=0, hard cross-entropy on the text with no logits anywhere, so its three arms differ only in which text they saw.

11.4 The cost, priced before anything generates#

This is the one genuinely expensive pattern in the book. Chapter 9 derived why; here is the conclusion. Decode is memory-bandwidth bound, because producing one token requires reading every weight of the model out of memory once, so the ceiling on single-stream decode throughput is , with BW the memory bandwidth, the parameter count, and the bytes per parameter, which is 2 for bf16. On the reference machine BW is 273 GB/s. Sequence-level KD is the only workload in this book where the large model decodes, which puts it in the worst corner of Chapter 9’s four-cell table.

11.4.1 The pricing table#

Lab 06’s Part A prices the corpus before Part B may generate a single token. The configuration is 2,048 prompts at 256 new tokens each, so 524,288 generated tokens. Here is what four candidate generating teachers cost at that corpus size on this machine.

Table 11.1 Single-stream cost of generating a 524,288-token corpus, by generating-teacher size, at 273 GB/s in bf16. Computed against the roofline instead of a stopwatch; every cell is two divisions.

Generating teacher bf16 weights Decode ceiling Single-stream wall clock
0.36 B 0.7 GB 379.2 tok/s 0.38 h
1.7 B 3.4 GB 80.3 tok/s 1.81 h
8.0 B 16.0 GB 17.1 tok/s 8.54 h
32.0 B 64.0 GB 4.3 tok/s 34.14 h

Read the last column the way a buyer reads a quote. The 1.7B teacher prices at a couple of single-stream hours, which batching turns into tens of minutes, and that is why Lab 06’s gen_teacher is 1.7B with a comment saying so: the generating model is deliberately small because it must decode. The 32B teacher prices at days single stream. Batching brings it to hours, but it remains a thing you schedule and not a thing you run.

The lab asserts three facts about this table instead of printing it and moving on: the 1.7B row is under 2.5 hours, the 32B row is more than ten times it, and the 32B row exceeds 24 hours. The last assertion carries a comment that is the whole chapter compressed: 32B single-stream is days-scale, so never pay this twice.

2026-08-01T07:33:15.941926 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.1 1 10 100 generating teacher, parameters (billions) 0.01 0.1 1 10 100 1000 single-stream wall clock (hours) slope 1 on log-log: wall clock is exactly linear in generator size, at 273 GB/s in bf16, single stream. 1.7B prices at 1.81 h and 32B at 34.14 h: 18.8x, which is the parameter ratio and nothing else. 0.52M tokens, the course corpus 5M tokens 50M tokens 0.52M at batch 16, order-of-magnitude estimate a coffee break an overnight run a scheduled job, not a command 0.36B, 0.38 h 1.7B, 1.81 h 8B, 8.54 h 32B, 34.14 h
Figure 11.2 Wall clock to build a teacher-generated corpus scales linearly with the generating teacher's parameter count, which puts nearly two orders of magnitude between a 1.7B generator and a 32B one on the same machine.

Batching is the first mitigation and it is genuine: one read of the weights serves every sequence in the batch, so decoding 16 or 32 prompts at once divides real wall clock by roughly an order of magnitude before other limits bind. What it does not do is reorder Table 11.1, because it multiplies every row by the same factor.

Memory is not the binding constraint here, which is worth saying because people expect it to be. Lab 06 budgets generation at the teacher’s 3.4 GB plus 6.0 GB of KV cache, 9.4 GB of the machine’s 128, and the subsequent fine-tune at 16 bytes per parameter for a 360M student plus 4.0 GB of activations, 9.76 GB. Both fit with room to spare. The constraint is time, and time is set by bandwidth.

11.4.2 Four mitigations, in the order you should consider them#

Keep the generating teacher small. The most effective lever feels like a retreat. A 1.7B generator costs 1.81 hours for this corpus against the 32B generator’s 34.14. If the 1.7B teacher is meaningfully better than your student on the task, its corpus is a usable training signal and you have bought eighteen times the throughput for whatever quality difference separates the two teachers. Measure that difference before assuming it is large. This is Chapter 5’s capacity-gap result arriving from the cost side.

Cap the corpus and reuse it. Corpus size enters cost linearly and enters quality with diminishing returns. Half a million tokens generated once and archived is a fixed asset; five million is ten times the bill for less than ten times the value.

Buy the corpus. Someone has already run this decode. Published instruction corpora and reasoning-trace datasets are, in this chapter’s exact technical sense, other people’s SeqKD output. SmolTalk is generated by larger teacher models, which makes it a published SeqKD corpus in precisely Lab 06’s sense.9 Section 11.5 is about auditing one before you train on it.

Rent different hardware for that stage alone. People skip this because it breaks the tidiness of a single-machine project, and it is often the right answer. Generation is a one-time, parallel, checkpointable phase with no gradient state, the easiest thing in the pipeline to run elsewhere. An hour of a high-bandwidth accelerator for the decode phase, followed by weeks of training on your own machine, is defensible in a way that leaving the workstation generating for three days is not.

Chapter 9 says treat a teacher-generated corpus as a purchased asset and not as a computation. Operationally that means it has a price, you pay it once, it goes in a directory with a manifest, and it survives every subsequent decision you make about objectives and hyperparameters.

11.5 Purchased assets and how to audit one#

Chapter 10 defined a purchased asset, meaning any training artifact you did not produce and intend to train on, and gave the validation protocol for a logit cache. What changes for a corpus of text is which checks are available. A cache can be re-derived: run the teacher again on a sample of rows and compare numbers. A published trace corpus usually cannot, because you do not have the teacher and often do not know which one it was.

Definition

Teacher trace

The step-by-step text a model writes out before its final answer: its worked solution, including whatever intermediate reasoning, false starts, and self-corrections it produced along the way. A trace is a teacher output like any other; the word marks that the interesting content is the process rather than the conclusion.

So the audit becomes an audit of the artifact itself. Six checks, roughly in order of how often they catch something.

Format and template consistency. Does every row have the structure your training code assumes? Lab 06’s Part A streams 512 rows of HuggingFaceTB/smol-smoltalk and tests three properties at once: at least two turns, a last turn whose role is assistant, and every role in every turn one of system, user, assistant. It asserts that more than 95 percent of rows pass. A corpus that fails is not necessarily bad data, but it is data your packing code will silently mangle, which is worse than bad data because it looks fine.

Length distribution. The same cell computes the median and 95th percentile of assistant-turn length in characters and asserts the p95 is under 20,000 characters, with a failure message naming the remedy: pathological lengths are present, add a length filter before fine-tuning. A handful of enormous rows dominates a packed batch, blows past your sequence length, and produces a truncated example whose completion has no ending, which is §11.3.2’s never-stop failure arriving through a different door.

Provenance fingerprint. The check specific to bought corpora, and a judgment call rather than an assertion. Read a sample and ask whether this looks like single teacher-generated turns or like a scraped mixture. A corpus assembled from many origins carries many styles, conventions, and quality levels, and distilling from it transfers the average of all of them. One teacher under one template is a far more coherent training signal, and it is what the sequence-level story assumes.

Teacher identity. Whose model produced this? Cards are frequently vague, and the answer governs what capability and what license you inherit. If the card does not say, treat the teacher as unknown and adjust your claims. Do not write “distilled from GPT-class outputs” on the strength of a dataset name.

Decoding parameters. Greedy or sampled, at what temperature, with what length cap and system prompt. Section 11.3 showed these decide what distributional information the corpus can carry, so unknown parameters mean you cannot state the corpus’s position on the greedy-to-sampled axis. You can partially recover it: compute self-BLEU across completions for near-duplicate prompts, and a value near 1 says the corpus was generated deterministically.

Contamination against your eval set. The check that saves you from publishing a wrong number, and the one people run last if at all. If the purchased corpus overlaps your evaluation data, your student’s score measures memorization. Chapter 16 gives the -gram method, the false positives chat templates introduce, and the remediation. Run it before you train.

License. Read what the dataset permits and what the teacher’s terms said about generating it. This is not legal advice and I am not qualified to give any. The technical fact worth knowing is that model outputs can carry detectable statistical marks: Kirchenbauer and colleagues showed a generation-time watermark can be embedded in output and detected later with a statistical test, without access to the model.10 Chapter 17 covers both directions.

Here is the shape of an audit as code. Watch that every check produces a number for a manifest rather than a boolean whose provenance you will forget.

def audit_trace_corpus(rows, n=512):
    """Structural audit of a purchased trace corpus. Returns numbers, not verdicts."""
    ok_roles = 0
    lengths, first_tokens = [], []
    for msgs in rows[:n]:
        well_formed = (len(msgs) >= 2
                       and msgs[-1]["role"] == "assistant"
                       and all(m["role"] in ("system", "user", "assistant") for m in msgs))
        ok_roles += int(well_formed)
        if well_formed:
            lengths.append(len(msgs[-1]["content"]))
            first_tokens.append(msgs[-1]["content"][:24])     # style fingerprint
    lengths.sort()
    return {
        "n": len(rows[:n]),
        "role_pass_rate": ok_roles / max(1, len(rows[:n])),
        "assistant_chars_p50": lengths[len(lengths) // 2],
        "assistant_chars_p95": lengths[int(len(lengths) * 0.95)],
        "distinct_openings": len(set(first_tokens)) / max(1, len(first_tokens)),
    }

The last field is the provenance fingerprint in numeric form. Single-teacher generations under one template have few distinct opening phrases, because models are repetitive at the start of a response; a scraped mixture has nearly as many distinct openings as rows. Neither value is right or wrong, and the point is to know which one you have and record it.

11.6 Trace fine-tuning: black-box with no logits at all#

Take the previous two sections together and you get the pattern most current practice uses.

Definition

Trace fine-tuning

Supervised fine-tuning of a student on a corpus of teacher-generated reasoning traces, with no teacher access at training time, no logits, and no reinforcement learning stage. Mechanically it is ordinary next-token cross-entropy on somebody else’s teacher decode. Abbreviated trace SFT.

The reference point is DeepSeek’s R1 report. The headline of that work is a reinforcement learning method for eliciting reasoning in a large model, but the part that matters here is the distilled model series, and it is worth stating accurately because it gets garbled in retelling. The distilled models were produced by supervised fine-tuning of existing open base models on reasoning traces generated by R1, and no reinforcement learning stage was applied to the students.11 The report reserves RL for the teacher and notes that applying it to the students would likely improve them further while making the comparison less clean.12

That is the whole method. Generate traces with a strong reasoning model, or obtain traces somebody else generated, and run supervised fine-tuning. There is no divergence, no temperature, no alignment problem, no cache format, and no teacher in memory during training. The teacher’s tokenizer does not have to match the student’s, because what crosses between them is text.

This is the baseline every method in Part IV has to beat, and it deserves to be taken seriously rather than treated as what you do when you cannot do better. It wins on engineering cost by a wide margin: one loop, no second model resident in memory, no alignment between two tokenizers to go silently wrong, and a corpus that is a file, so the run is rerunnable by somebody else from the same bytes. It also has an honest quality story on reasoning-style tasks, which is why the pattern spread: long traces carry the answer, the shape of the derivation that reached it, and the places the teacher backed up and tried again.

What it gives up is exactly the dark knowledge of Chapter 5. A sampled completion shows the one token chosen and says nothing about the ranking of the alternatives or the teacher’s confidence in the choice. That is the mechanism behind Part C’s expectation that the cached-logit student beats greedy SeqKD on agreement by 1 to 5 points at equal steps. Whether the cost is affordable depends on the ledger rather than the loss.

11.6.1 Two failure modes with different signatures#

Style transfer without capability transfer. The classic black-box failure: the student learns how the teacher sounds without learning what the teacher knows. Output that is fluent, confident, correctly formatted, and wrong. This is nastier than a visibly broken student because it survives casual inspection and an automated fluency metric alike.

The diagnostic that catches it early is calibration. Expected calibration error measures the gap between how confident a model’s probabilities are and how often it is actually right, computed by binning predictions by confidence and comparing each bin’s mean confidence to its accuracy.13 A student that learned style without capability is confidently wrong, and ECE reports that as a number before you notice it by reading outputs. Lab 06 says to check calibration first; Chapter 16 gives the binning that makes ECE a real number instead of an artifact of your bin count.

Domain shift disguised as success. If your trace-SFT student wins on agreement against the teacher, be suspicious instead of pleased. The likely explanation is that your held-out prompts resemble the trace corpus more than they resemble your use case, so the comparison measured overlap between two datasets rather than the quality of a method. Write the rule into your evaluation protocol: whenever the arm that had zero teacher compute wins, look at the prompts before you look at the model.

11.7 Rationale distillation, and what it costs#

A variant worth having in view: distill the teacher’s reasoning and not just its answer.

Definition

Rationale distillation

Sequence-level distillation in which the teacher is prompted to produce its reasoning before its answer, and the student is trained on the reasoning text as well as the answer. The teacher’s intermediate reasoning becomes training signal instead of an artifact discarded at generation time. Also called chain-of-thought distillation.

The mechanics are §11.2’s with a modified generation prompt: ask the teacher to think step by step and give the final answer after a marker, generate under a larger cap, and pack the result as the completion. Trace SFT as practiced is usually this, since published reasoning-trace corpora contain the reasoning as well as the conclusions.

What it buys is more supervision per example. A bare answer supervises the answer tokens; a rationale supervises a derivation, so every intermediate step becomes a position where the student is told what a competent model does next given the state so far. On tasks needing multi-step structure that is a real difference in what the corpus teaches. On tasks that do not, the gain is plausibly zero, and the honest expectation is that the benefit concentrates where structure is needed.

What it costs is decode, and the accounting is exact because decode cost scales precisely with tokens generated. Solutions 06’s third exercise builds the identity and asserts it live to within :

where is the number of prompts, is the mean completion length in tokens measured from the real corpus, and is the rationale length measured with the real tokenizer, not estimated. Wall-clock hours ratio equals token ratio with no residual, because on bandwidth-bound decode every generated token pays the same weight-streaming price: the 1.7B teacher’s ceiling of 80.29 tok/s enters both sides and cancels.

Against this course’s corpus, a realistic 40 to 80 token rationale prices at a multiplier of roughly 1.3 to 1.6, a decision to spend 30 to 60 percent more of the most expensive phase. That is not a free trick and should not be presented as one. The reason to compute it is that the alternative use of the same decode budget is 30 to 60 percent more plain prompts, which is a real competitor.

This variant fails in two ways of its own.

Formatting leakage. The student starts every answer with “Reasoning:” on prompts that asked for nothing of the kind, having learned the scaffold as content. The fix is to mask the rationale tokens out of the loss, supervising only the answer span while paying for the rationale purely as context. That is a second, cheaper arm worth running as a comparison, not a patch to apply blindly.

The think instruction leaking into the training prompt. The teacher was asked to think step by step. If that instruction stays in the prompt you pack for training, you have taught the student a behavior it exhibits only when instructed, and your deployment prompts will not contain the instruction. Strip it before packing, and run the evaluation on prompts served exactly as production serves them, with no rationale request, scored on the answer span only. A student that improves only when allowed to emit its rationale first learned the format, not the capability.

11.8 The imitation learning framing#

A different literature has a framing for this that names the failure mode precisely.

Cast text generation as sequential decision making. The model is a policy, each token an action, and the state the prefix generated so far. Training on a fixed corpus of teacher outputs is then behavioral cloning: copy an expert’s actions on states the expert visited. Its known weakness is compounding error, because the learner is only ever supervised on the expert’s state distribution, so the first mistake puts it in a state the expert never occupied, where it has no supervision and no reason to behave well. In the sequence-modeling literature this is exposure bias.14 Scheduled sampling was an early fix, feeding the model its own predictions during training with increasing probability.15

Lin, Wohlwend, Chen, and Lei made the connection explicit for distillation with ImitKD, framing autoregressive knowledge distillation as imitation learning and using the interaction between student-visited states and teacher supervision to address the compounding-error problem.16 The framing is the direct ancestor of the on-policy methods Chapter 12 covers: generalized knowledge distillation interpolates between teacher-generated data and student rollouts with a single parameter, and that parameter is a knob on how much you trust behavioral cloning.17

The framing earns its place by naming the limitation. Sequence-level KD and trace SFT are behavioral cloning, and their weakness is the state distribution, which is the teacher’s rather than the student’s, not the loss function, which is right for the data it sees. Chapter 12 is an attempt to fix the state distribution while keeping the supervision.

11.9 The library trap#

Field note

Lab 06’s second exercise, as I originally wrote it, said: run TRL’s GKD trainer with seq_kd=True, lmbda=1.0 on the same prompts and compare to your hand-rolled arm. It reads like a sensible instruction. seq_kd=True turns on sequence-level KD, lmbda=1.0 sets the mixing parameter to its maximum, and the trainer does the rest.

The course has a standing rule that I follow before spending a training run: introspect the installed object instead of trusting the documentation or my memory of it. The rule exists because this course’s own README once described a TRL parameter that no longer existed in the installed version, and the drift was caught by checking the object, not by re-reading the docs. So before launching, I read inspect.getsource(GKDTrainer.training_step) and printed the branch structure with line numbers.

What the source shows is that training_step draws a uniform random number each step and takes the student-generates branch whenever that draw is at or below self.lmbda. The teacher-generates SeqKD branch is an elif behind that draw. Structurally:

# The shape of the branch, reproduced from the installed trainer's source.
if random.random() <= self.lmbda:          # evaluated FIRST, every step
    new_inputs = self.generate_on_policy_outputs(self.model, inputs, ...)
    inputs = new_inputs                    # the STUDENT generated this batch
elif self.seq_kd:                          # reachable only when the draw fails
    new_inputs = self.generate_on_policy_outputs(self.teacher_model, inputs, ...)
    inputs = new_inputs                    # the TEACHER generated this batch

At lmbda=1.0 the draw always succeeds. random.random() returns a float in , so it is always at or below 1.0, and the first branch is taken on every single step. The elif is unreachable. seq_kd=True is dead code at lmbda=1.0, and the run silently becomes pure on-policy GKD: a different method, a different experiment, and Chapter 12’s subject rather than this chapter’s.

The reason this is worth a callout rather than a footnote is the failure mode. There is no exception. There is no warning. The loss curve is perfectly normal, because on-policy GKD is a working method that produces a working loss curve. You would run it, get a student, write it up as sequence-level KD, and be wrong about what you did, with nothing in the logs to contradict you. The notebook asserts the structure so this cannot drift back: src.index("random.random() <= self.lmbda") < src.index("elif self.seq_kd"), with the comment that the lmbda branch must come first for the elif to mean what it means.

Library SeqKD is seq_kd=True, lmbda=0.0. At lmbda=0.0 the draw at or below zero effectively never succeeds, the elif is reached every step, and the teacher generates. The corrected cell in the solutions carries the inline comment “the introspection-corrected setting” so that anyone reading it knows the value was chosen and not copied.

The general lesson is the one to carry out of this chapter even if you forget the arithmetic. The installed source is the specification. Documentation, tutorials, blog posts, your memory of last quarter’s API, and the exercise text in a course written by someone who should know better are all secondary sources about a primary source you can read in two seconds. The introspection cost nothing and settled the configuration before any compute was spent; the training run it replaced would have cost hours and produced a confident wrong answer. Reporting the correction beats forcing the expected answer, so the exercise text now carries it.

Watch out

Any parameter that gates a branch behind another parameter’s branch can be dead code at a particular setting of the other parameter, and the combination that kills it is often the combination that looks most natural. When a library exposes two flags that both affect who generates, read the order in which they are evaluated before assuming they compose.

11.9.1 Three differences between library SeqKD and a hand-rolled arm#

Once the configuration is corrected, the library arm and the hand-rolled arm should land close, and Lab 06’s expectation is agreement within a point or two and entropy within about 0.1 nats, because both are cross-entropy on teacher generations for the same prompts. When they diverge, there are three specific differences to check, each visible in the source, and each with a different effect on results.

The trainer regenerates teacher completions every step, at its configured temperature, whose default is 0.9. The hand-rolled arm generates one greedy corpus once and trains on it repeatedly. These are different experiments. Per-step regeneration at 0.9 is much closer to §11.3’s sampled variant than to classical mode-SeqKD, so the library arm inherits the sampled arm’s properties: higher entropy, more diverse data, slightly weaker mode-matching. A large entropy gap between the two arms comes from here, and it is not a bug in either one. The cost consequence is larger: regenerating every step pays teacher decode inside the training loop instead of once, which rewrites the §11.10 ledger and moves the method from expensive once to expensive continuously.

The trainer masks everything after the first EOS but does not drop capped, EOS-less generations. The hand-rolled builder drops them, as §11.3.2 argued it must. Masking after EOS handles a generation that ended and kept going; it does nothing for one that never ended, because there is no EOS to mask after. Those rows stay in the batch with their unterminated completion supervised, mild but persistent pressure toward the never-stop failure, showing up in the student’s own generations and not in the loss.

Prompts pass through the chat template rather than as raw token ids. The trainer takes a messages-shaped dataset and applies the tokenizer’s chat template; the hand-rolled arm packs pre-tokenized ids from a tensor. Chapter 7 covered what a template inserts: role headers, special tokens, sometimes a system turn you did not write. The two paths therefore train on different token sequences for the same underlying prompt, which shifts prompt lengths, shifts where the completion mask starts, and shifts the boundary tokens the student sees. The effect is usually small and never zero, and it is the first thing to check when two implementations of the same method disagree by an amount too large for noise and too small for a bug.

11.10 Three methods at matched teacher compute#

The natural comparison is at equal student steps: train the cached-logit student, the SeqKD student, and the trace-SFT student for 1,500 steps each and evaluate. Lab 06’s Part C runs exactly that comparison and expects the cached-logit student to win on agreement, SeqKD to sit close on in-domain behavior with entropy 0.1 to 0.4 nats lower, and trace SFT to be the most domain-shifted.

That comparison is unfair in a specific direction, and Chapter 9 built the ledger that shows it. At equal student steps the SeqKD arm quietly received the largest total budget, because its teacher phase was decode and everyone else’s was prefill or nothing. The accounting below is in bytes of weight traffic and seconds at 273 GB/s, for Lab 06’s configuration: a 1.7B teacher at 3.4 GB, 2,048 prompts, 256 new tokens each for 524,288 generated tokens, batch 16 for both decode and prefill, and a 360M student at 16 bytes per parameter costing three traversals per optimizer step.

Table 11.2 The matched-compute ledger for the three black-box-relevant arms, from Solutions 06 Exercise 4. Every number in the first four columns is derived arithmetic from the notebook’s own constants and is asserted live.

Arm Teacher traffic Teacher seconds Student seconds (1,500 steps) Total Extra steps at matched compute
Cached-logit 435.2 GB (prefill) 1.59 94.9 96.5 s 6,421
SeqKD 111,411.2 GB (decode) 408.1 94.9 503.1 s 0
Trace SFT 0 GB (purchased) 0.0 94.9 94.9 s 6,447

The decode figure is the one to sit with. Generating 524,288 tokens at batch 16 is 32,768 decode steps, each reading the teacher’s 3.4 GB, so 111,411 GB of weight traffic against the prefill arm’s 435 GB. The ratio is exactly 256, the number of new tokens per prompt, which is the cleanest statement available of the difference between generating text and scoring it. On the student side, 3 traversals of a 5.76 GB full fine-tuning working set is 17.28 GB per step, or 0.0633 seconds, so 1,500 steps is 25,920 GB and 94.9 seconds.

The referee move is to set the budget to the most expensive arm’s total, 503.1 seconds, and hand every cheaper arm its slack as additional student steps. The cached arm’s 406.6 seconds of slack buys 6,421 steps, for 7,921 total; the trace arm’s 408.2 seconds buys 6,447, for 7,947; SeqKD gets zero, having set the budget. The notebook asserts that ordering and asserts that every arm’s reallocated total lands within one step of the budget.

2026-08-01T07:33:16.849869 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 100 200 300 400 500 seconds of weight traffic at 273 GB/s Cached-logit 96.5 s slack 406.5 s buys +6,421 student steps SeqKD 503.0 s no slack: this arm set the budget Trace SFT 94.9 s slack 408.1 s buys +6,447 student steps matched-compute budget, set by SeqKD teacher decode 408.1 s teacher prefill 1.6 s student training, 94.9 s identical in all three arms measured/derived arithmetic over Lab 06's own constants (Table 11.2); weight traffic only
Figure 11.3 At matched student steps the three arms spend wildly different total budgets, and the difference is almost entirely one arm's teacher-decode phase; the same budget bought the cheaper arms more than five times the baseline step count.

11.10.1 Which numbers are measured and which are not#

This is the kind of table that gets quoted without its caveats, so be scrupulous.

Measured or derived, and trustworthy: everything in Table 11.2. The traffic figures, seconds, extra-step counts, and budget are arithmetic over the notebook’s own constants, computed live and asserted. Within the cost model below they are exact.

A stated model with stated omissions: the cost model charges only weight traffic. It ignores activations, attention arithmetic, KV cache reads, the prefill of prompts during generation, optimizer state details, and every fixed cost of loading and checkpointing. Those omissions all push the true numbers up and push all three arms in the same direction, which makes the model acceptable for a referee and unacceptable for a capacity plan. Stating a model’s omissions is part of the model.

Illustrative and explicitly not a measurement: the quality numbers. To ask whether the extra steps could flip the ranking, Solutions 06 assumes agreement improves by 1.5 points per doubling of steps and starts the arms at 63.0 for cached, 60.0 for SeqKD, and 58.0 for trace, offsets from Part C’s expected ranges. Under it the cached arm goes from 63.0 to 66.6 across 2.40 doublings, the trace arm from 58.0 to 61.6 across 2.41, and SeqKD stays at 60.0 having earned no extra steps. Trace SFT overtakes SeqKD outright, 61.6 against 60.0, purely from reallocated slack. The notebook prints this under the header “illustrative matched-compute quality (NOT a measurement)” and I am repeating the label instead of softening it. Change the per-doubling constant and the crossing point moves.

What the illustration establishes is a possibility and not a result, and the possibility is the point: a ranking that looked settled at equal steps is not settled at equal compute, and you cannot know which way it goes without building the table. The flip condition is that trace SFT overtakes SeqKD on agreement whenever the per-doubling gain exceeds about half the domain-shift penalty. What would refute the whole framing is quality curves already flat at 1,500 steps: if the extra steps buy nothing, the slack is worthless and the matched-step table stands, which is measurable and worth measuring before you deploy this argument against a skeptic.

There is a fourth outcome the table does not have a column for, and at this course’s scale it is the most likely one: all three arms land inside each other’s noise bands and the comparison comes back null. That can be the true answer. A 360M student trained for 1,500 steps on a few thousand conversations is not clearly a setting where the extra information in logits has room to show up, and a null reported honestly is worth more than a difference manufactured by picking the flattering seed. What it is not is a licence to conclude that logits do not matter, because the evaluation itself may be the thing hiding the difference.

Before you write that conclusion down, re-run the evaluation on the longest quartile of eval completions and look again. The reason is mechanical. What a cached-logit arm buys over a text-only arm is the teacher’s ranking of the alternatives it did not take, and that ranking is only worth something at positions where several continuations are defensible. A short completion is made mostly of the first few tokens after a prompt, where the models differ but the metric has few positions to average over; a long completion contains many mid-sequence positions where the local context leaves a genuine choice open. Restricting to the longest quartile concentrates the evaluation on exactly the positions the logits were supposed to help with, and it is the one condition under which the three arms reliably separate. If the quartile-restricted comparison is also null, the null is real at your scale, and you have earned the right to say so. If it is not, what you had was a measurement instrument averaging a real effect away, which is a fact about the instrument and not about the method.

This is the comparison SeqKD papers rarely show, for a structural and not a dishonest reason: the mode-matched corpus looks best exactly when the referee ignores who paid for it. Reading a paper that reports sequence-level KD beating a token-level baseline, ask what the teacher spent in each arm.

In the labs: Lab 06

The ledger runs fully live in Solutions 06 Exercise 4, asserting that seconds equal bytes over bandwidth, that every arm’s reallocated total exhausts the budget to within one step, and that the extra-step ordering is trace, then cached, then SeqKD at zero. The training runs it compares are gated off, which is why the quality column is illustrative.

11.11 How to decide#

The guide below is keyed to access first and budget second, because access eliminates more options.

Table 11.3 What to do, by what you have.

Access to the teacher Compute available Method What it costs What you give up
Weights or full logits, matching tokenizer Any Cached-logit off-policy KD (Chapter 10) Teacher prefill, paid once, minutes Nothing relevant to this chapter
Weights or full logits, mismatched tokenizer Any ULD or representation matching (Chapter 14), or fall back to SeqKD Prefill plus method complexity Position-wise precision
Text only, teacher small enough to run Hours of decode Sequence-level KD, generated yourself The Table 11.1 price Dark knowledge; entropy runs low
Text only, teacher large or remote Money, not compute SeqKD on a rented machine, or buy the corpus Rental or license, plus the audit Control over decoding parameters
Text only, no teacher at all Student training only Trace SFT on a published corpus Zero teacher compute, plus the audit Choice of prompts; domain shift risk
Text only, and you can re-query cheaply Decode inside the loop On-policy methods (Chapter 12) Student decode plus teacher prefill Pipeline complexity, restartability

The table pays out as four decision rules.

If you have logits and matching tokenizers, use them. Nothing in this chapter beats Chapter 10’s cache when the cache is available: it costs prefill, which is minutes, and it carries the full distribution.

If you must generate, generate with the smallest teacher that is meaningfully better than your student. Cost is linear in the generator’s parameter count and quality is not, and the gap between those two facts is where the decision lives.

Never pay for large-teacher decode twice. Generate once, archive with a manifest recording the teacher, decoding parameters, prompt set, and drop rate, and treat the result as an asset with a book value. Chapter 10’s manifest discipline applies unchanged.

When someone else already paid the decode bill, take the corpus and spend your budget on student steps. The §11.10 ledger says this outright: at fixed total compute, cached logits when you have white-box access, traces when you have none, and teacher-decode SeqKD only when someone else already paid. That is one defensible verdict on this hardware class rather than the only one, and what makes it defensible is that the arithmetic behind it is stated and rerunnable.

One consideration the table cannot express: every method here clones the teacher’s behavior on the teacher’s own state distribution, so every one inherits behavioral cloning’s compounding error. Beyer and colleagues’ function-matching result suggests patience helps a great deal when teacher and student see identical inputs, which is the regime SeqKD sets up.18 Stanton and colleagues’ fidelity result cautions that even long training often fails to make the student match the teacher, and helps anyway.19 Neither tells you where your student will break, which is what Chapter 16 is for.

11.12 Where this lands in the labs#

Lab 06 is arranged so the economics come before the method: Part A prices the decode to the hour, introspects the installed TRL objects, and audits the purchased trace corpus, all before Part B may generate a single token. That ordering is the lab’s real lesson, and one the book can describe but not enforce. What the lab does that this chapter cannot is make the drop rate print, the audit assert, and the branch structure fail loudly if a library upgrade moves it. Solutions 06’s second exercise is the one to read even if you skip the others. Its fourth builds the matched-compute ledger live, and Chapter 18 returns to that ledger as a general research method.

11.13 Exercises#

  1. Your teacher’s mean top-1 probability over completions is 0.85 and your completions average 180 tokens. Compute the probability the teacher assigns to its own greedy sequence, state what fraction of the sequence-level expectation the mode approximation therefore captures, and give the strongest argument you can for why training on that sequence works anyway. Say which part of your argument is testable.

  2. Section 11.3 shows that sampled generation gives an unbiased estimate of the exact sequence-level objective and greedy gives a biased one. Explain why the biased method is the one carrying Kim and Rush’s name, in terms of what each method’s variance does to a corpus of fixed size. Then say what you would measure to settle it for your own task without training twice.

  3. Before reading §11.10’s table, register a prediction: at matched total compute rather than matched student steps, rank cached-logit KD, sequence-level KD, and trace SFT on agreement against the teacher, and write down the reason. Then read the table and identify which of your reasons the arithmetic supports, which it contradicts, and which it cannot address because the quality figures are illustrative.

  4. A colleague reports that their TRL SeqKD run and their hand-rolled run agree within a point on top-1 agreement but differ by 0.35 nats in entropy, with the library arm higher. Using the three differences in §11.9.1, rank the candidate explanations by how much entropy each could plausibly account for, and design the cheapest experiment that separates the top two.

  5. You are handed a 400,000-row trace dataset whose card says it was generated by “a leading reasoning model.” Design an audit. For each check, state whether it can falsify the card’s claim, only fail to falsify it, or neither, and order the checks given that each costs time you would rather spend training.

  6. Rationale distillation prices at a 1.45x multiplier on your corpus, and the alternative use of the same decode budget is 45 percent more plain prompts. State the quality gain per example rationales must deliver to break even, under an explicit assumption about how quality scales with corpus size, and say how you would estimate that scaling cheaply before committing.

  7. You have a 70B teacher behind a text-only API, a one-week deadline, and a budget that permits either eight hours of rented high-bandwidth accelerator time or the purchase of a published trace corpus, not both. Write the plan: which row of Table 11.3 you are in, what you will generate or buy, what you will audit, and what single piece of evidence from your own run would tell you the choice was wrong in time to change it.



  1. Nicholas Carlini et al., “Stealing Part of a Production Language Model,” arXiv:2403.06634 (2024), ICML 2024. https://arxiv.org/abs/2403.06634. The attack recovers the embedding projection dimension and, with more queries, the projection matrix up to symmetry, from a log-probability API; the paper reports that the affected providers modified their APIs following disclosure. 

  2. Nicolas Boizard, Kevin El Haddad, Céline Hudelot, and Pierre Colombo, “Towards Cross-Tokenizer Distillation: the Universal Logit Distillation Loss for LLMs,” arXiv:2402.12030 (2024), Transactions on Machine Learning Research, January 2025. https://arxiv.org/abs/2402.12030 

  3. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015). https://arxiv.org/abs/1503.02531 

  4. Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947. The mode approximation and the use of beam search as a practical stand-in for the intractable sequence argmax are both in §4. 

  5. Yuqiao Wen, Zichao Li, Wenyu Du, and Lili Mou, “f-Divergence Minimization for Sequence-Level Knowledge Distillation,” arXiv:2307.15190 (2023), ACL 2023. https://arxiv.org/abs/2307.15190 

  6. Yaoming Zhu et al., “Texygen: A Benchmarking Platform for Text Generation Models,” arXiv:1802.01886 (2018), SIGIR 2018. https://arxiv.org/abs/1802.01886. The origin of self-BLEU as a diversity metric. 

  7. Jiwei Li, Michel Galley, Chris Brockett, Jianfeng Gao, and Bill Dolan, “A Diversity-Promoting Objective Function for Neural Conversation Models,” arXiv:1510.03055 (2015), NAACL-HLT 2016. https://arxiv.org/abs/1510.03055. The origin of distinct-

  8. Constantinos Karouzos, Xingwei Tan, and Nikolaos Aletras, “Where does output diversity collapse in post-training?” arXiv:2604.16027 (2026). https://arxiv.org/abs/2604.16027. An unrefereed preprint at the time of writing; treat the mechanism as suggestive rather than settled. For the related degeneration behavior of deterministic decoding see Ari Holtzman, Jan Buys, Li Du, Maxwell Forbes, and Yejin Choi, “The Curious Case of Neural Text Degeneration,” arXiv:1904.09751 (2019), ICLR 2020. https://arxiv.org/abs/1904.09751 

  9. Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big, Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737. The SmolTalk corpus described there is generated by larger teacher models, which makes it a published corpus of teacher output in this chapter’s sense. 

  10. John Kirchenbauer, Jonas Geiping, Yuxin Wen, Jonathan Katz, Ian Miers, and Tom Goldstein, “A Watermark for Large Language Models,” arXiv:2301.10226 (2023), ICML 2023. https://arxiv.org/abs/2301.10226 

  11. DeepSeek-AI, “DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning,” Nature 645 (2025): 633-638, https://doi.org/10.1038/s41586-025-09422-z; preprint arXiv:2501.12948. The distillation section describes supervised fine-tuning of open base models on teacher-generated reasoning traces, with no reinforcement learning stage applied to the students. 

  12. Qwen Team, “Qwen2.5 Technical Report,” arXiv:2412.15115 (2024). https://arxiv.org/abs/2412.15115. One of the open base families used as students in distilled-model releases of this kind. 

  13. Chuan Guo, Geoff Pleiss, Yu Sun, and Kilian Q. Weinberger, “On Calibration of Modern Neural Networks,” arXiv:1706.04599 (2017), ICML 2017. https://arxiv.org/abs/1706.04599. The source for expected calibration error and its binned estimator. 

  14. Marc’Aurelio Ranzato, Sumit Chopra, Michael Auli, and Wojciech Zaremba, “Sequence Level Training with Recurrent Neural Networks,” arXiv:1511.06732 (2015), ICLR 2016. https://arxiv.org/abs/1511.06732 

  15. Samy Bengio, Oriol Vinyals, Navdeep Jaitly, and Noam Shazeer, “Scheduled Sampling for Sequence Prediction with Recurrent Neural Networks,” arXiv:1506.03099 (2015), NeurIPS 2015. https://arxiv.org/abs/1506.03099 

  16. Alexander Lin, Jeremy Wohlwend, Howard Chen, and Tao Lei, “Autoregressive Knowledge Distillation through Imitation Learning,” arXiv:2009.07253 (2020), EMNLP 2020. https://arxiv.org/abs/2009.07253 · https://aclanthology.org/2020.emnlp-main.494/. The method is known as ImitKD. 

  17. Rishabh Agarwal et al., “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649. The method is generalized knowledge distillation, GKD. 

  18. Lucas Beyer, Xiaohua Zhai, Amélie Royer, Larisa Markeeva, Rohan Anil, and Alexander Kolesnikov, “Knowledge distillation: A good teacher is patient and consistent,” arXiv:2106.05237 (2021), CVPR 2022. https://arxiv.org/abs/2106.05237 

  19. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945 

Part IV · The Method Space

12

On-Policy Distillation

Every training run in this book so far has relied on a property you may not have noticed. The data existed before the run started. Chapter 10’s cached-logit pipeline is the extreme case: the teacher’s distributions were computed days earlier, and you can kill the run, restart it, reshuffle it, or rerun it under a different seed with nothing about the training distribution moving.

On-policy distillation gives that up. The student generates the text it trains on, at the moment it trains on it, so the training distribution is a function of the current weights. Take a step and the distribution the next batch is drawn from has changed. There is a feedback path from the loss back into the data, and a feedback path is a thing that can run away.

This chapter is about the run you have to watch. Not because it is hard to launch, and not because it is expensive, which on this hardware it is not. It is because a run whose data depends on its own weights has failure modes an off-policy run structurally cannot have, and the most common of them is silent for a few hundred steps and then irreversible. So the chapter spends as much length on the monitor as on the method.

One disclosure first, because it governs how to read every number below. Lab 07 is a Tier 2 lab: its five training arms are gated behind a flag and did not execute. Everything else did, live and with assertions: the monitor tests, the mask tests, the arm-difference checks, the estimator demonstrations, the calibration replay, and the staleness toy. When I quote a number I will say which category it came from, and the lab’s Part C statements about how the five arms should come out are predictions registered in advance, not results.

12.1 The mismatch that teacher forcing cannot see#

Chapter 7 defined teacher forcing and then used it without further comment. At every position the model is shown the reference tokens as context and asked for a distribution over the next one. All positions are computable in one parallel forward pass because every context is known in advance, and that parallelism is what makes training a transformer affordable.

At inference nothing supplies the context. The model reads its prompt, emits a token, appends it to its own context, and emits the next one conditioned on what it produced. If it produces something unusual at position 12, then positions 13 onward are conditioned on a prefix the training corpus may never have contained.

Definition

Exposure bias

The mismatch between the contexts a model is trained on and the contexts it meets at generation time. A teacher-forced model is only ever conditioned on reference text, so it is never trained on prefixes containing its own errors, and at generation time it is operating on a distribution of contexts it was never fit to. The signature is a generation that starts well and degrades, because each error moves the context further from anything training covered.

That is the definition. Here is the demonstration, because the definition invites a fair objection: networks generalize, a slightly unusual prefix is still a prefix, why should anything break?

Give the student a per-token probability of emitting a token that takes the context somewhere the training distribution effectively does not reach. Under teacher forcing that event has no consequence, because the next position’s context is supplied from the reference regardless. The model can be wrong at every position and still be scored, at every position, on a context drawn from the data distribution. Under free generation the events compound, and the probability that a generation of length stays inside the covered region is

At , a 100-token generation stays covered with probability , so nearly two thirds of generations spend part of their length where the training loss never looked. At , a short answer by deployment standards, it is : twenty generations in twenty-one contain at least one excursion.

The “covered region” is a cartoon and the result’s shape does not depend on it. Exposure bias is not a claim that the model is bad at unusual prefixes. It is a claim about measure: the fraction of generation-time context that teacher-forced training scored falls exponentially in length at any positive excursion rate, and the training loss cannot see the shortfall because it never generates.

The second half of the argument is what happens after an excursion. Nothing in training constrained behavior there, so the model’s conditional distribution at an off-distribution prefix is an extrapolation: not systematically bad, but systematically unconstrained, and one way it fails is self-reinforcing, since an unusual prefix makes the next unusual token more likely. Ranzato and colleagues named that compounding for recurrent sequence models and argued that a word-level loss under teacher forcing optimizes a quantity that is not the one you evaluate.1

The earliest widely used fix was scheduled sampling: during training, replace the reference token in the context with the model’s own sampled token some fraction of the time, raising that fraction as training proceeds.2 The mechanism is on-policy distillation’s. What it lacked was a target at the substituted positions, since feeding the model its own token and then asking for the reference’s next token asks it to rejoin a trajectory it has already left. Distillation resolves that, which is why on-policy training fits here better than it fits plain language modeling. When the student wanders into a prefix nobody wrote down you do not need a reference continuation, because you can ask the teacher what it would do at that exact prefix. Supervision follows the student anywhere it goes.

Field note

Exposure bias has a contested history and the chapter should say so. Sequence-level knowledge distillation is the clean counterexample: it trains on the teacher’s generations under ordinary teacher forcing, does nothing about the student’s exposure to its own errors, and works well anyway.3 My reading, which is a reading rather than a result, is that the magnitude is task dependent and largest where errors are least recoverable. Lab 07 registers the prediction that its on and mixed arms beat off on generation-side quality while possibly tying it on teacher-forced agreement, which if it holds is the cleanest available statement of the phenomenon.

12.2 The loop, mechanically#

Strip away the parameters and on-policy distillation is three operations per batch.

The student generates. Take a batch of prompts from the corpus and run the student in sampling mode until each sequence emits an end-of-sequence token or hits a cap.

Definition

Rollout

A complete generation produced by the current student from a prompt during training, used as the training example for that step. The word is borrowed from reinforcement learning, where it means running the policy forward to see what it does. Rollouts are generated under no_grad and treated as data: the loss is not differentiated through the sampling.

The teacher scores. Concatenate prompt and rollout and run the teacher forward. The text exists by the time the teacher sees it, so this is one prefill pass with every position computed in parallel, returning a full distribution over the vocabulary at each position. The teacher generates nothing, ever, in this loop.

The loss compares. At each generated position, and only there, compute the chosen divergence between the teacher’s distribution and the student’s, sum, backpropagate. Chapter 7’s alignment machinery applies unchanged, with one indexing wrinkle that §12.7 handles.

2026-08-01T07:33:17.541403 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 32 sequences, from the fixed corpus 4,096 generated tokens per optimizer step 0.72 GB of weight traffic per decode step 379 tok/s single-stream ceiling, x batch 4,096 positions scored in one parallel pass ~1,997 positions/s on the reference machine the teacher never generates a token full-vocabulary divergence at each generated position, masked to the rollout the weights change, so the next batch's data distribution changes: this arrow is the entire risk profile of the chapter decode = bandwidth bound prefill = compute bound (Chapter 9's four-cell table) prompt batch STUDENT 360M DECODE TEACHER 1.7B PREFILL loss
Figure 12.1 The on-policy loop puts the expensive mode on the cheap model: the small student pays token-by-token decode for the rollouts, the large teacher pays only a single parallel prefill over text that already exists.

Chapter 9 priced that loop. Its Table 9.2 has a row for “student generates rollouts”, decode by the small model, and one for “teacher scores student rollouts”, prefill by the large model. Neither is the expensive cell. The expensive cell is the one where the teacher decodes, which is Chapter 11’s sequence-level corpus generation, at roughly two orders of magnitude more.

Put Lab 07’s configuration through the arithmetic. With per_device_train_batch_size=4 and gradient_accumulation_steps=8, 32 sequences enter each optimizer step, and with max_new_tokens=128 a step generates at most tokens. Chapter 9 puts a 360M student in bf16 at 0.72 GB of weight traffic per decode step, ceilinging single-stream decode near 379 tokens per second, with batching multiplying that because one weight read serves the whole batch. It prices prefill on this machine near two thousand positions per second (1.57 million positions in 13.1 minutes), so scoring those same 4,096 positions is about two seconds of roofline.

Both sides are seconds per optimizer step and not minutes, which is what makes the method practical without a serving stack. And the decode side is the larger of the two even though it runs on the small model, because decode emits 4,096 tokens one at a time while prefill absorbs 4,096 at once. The prefill-to-decode ratio does not vanish when the models differ in size; it is multiplied by the size ratio, and 360M against 1.7B does not flip it.4 Chapter 15 attacks the decode side properly, with a served teacher and continuous batching.5

The memory profile of that loop is worth having in mind before it fails, because the most likely way your first on-policy run dies is an out-of-memory error during teacher scoring, and the instinct it provokes is the wrong one. The step holds three things at once: the student with its optimizer state, the teacher’s weights, and a rollout buffer of generation_batch × (prompt + max_new_tokens) positions along with the KV cache that generating them required. The teacher then prefills that whole buffer in one pass, materializing a distribution over the vocabulary at every position in it. Every one of those terms grows with the generation batch and with max_new_tokens. None of them grows with the number of gradient accumulation micro-batches.

So when teacher scoring runs out of memory, shrink the generation batch, not the training batch. The rollout buffer and its KV cache are what grew, and they are what has to shrink; halving per_device_train_batch_size while leaving generation alone attacks the term that was not the problem. It also does something worse than not helping. Batch size is part of the optimization you are trying to measure, so changing it changes the effective learning rate schedule, the gradient noise scale, and the comparability of this arm against every other arm in your study. You went looking for a memory fix and quietly edited the experiment. If you must reduce the number of sequences per optimizer step, reduce the micro-batch and raise gradient_accumulation_steps to compensate, which holds the optimization fixed and only moves memory.

The generation-side dials, in the order I reach for them: lower max_new_tokens first, since the KV cache and the scored position count are both linear in it and rollouts longer than your real completions are wasted anyway; then halve the generation batch, accepting the wall-clock cost of fewer sequences per decode pass; then, if the teacher is what will not fit, move it behind a server so it stops competing for the same memory, which is Chapter 15. Whatever you change, write it in the run manifest, because a run that survived by having its generation length halved is not comparable to one that did not.

Field note

I had these economics backwards, and Chapter 9 keeps the autopsy. The chain was: prefill beats decode, on-policy training generates inside the loop, off-policy does not, therefore on-policy is expensive. Every clause is true and the conclusion does not follow, because I tracked whether generation happens and not which model does it.

12.3 GKD, and the two parameters that define it#

The modern form of this method is generalized knowledge distillation, from Agarwal and colleagues, whose paper is titled “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes.”6 The method name GKD does not appear in the title, which trips up literature searches. The contribution is not training on student rollouts, which is older, but the observation that the design space has two dimensions and both matter independently.7

Write the objective with both in it. Let be the prompt corpus, the teacher, the student, and any of Chapter 6’s divergences with its mixing parameter . Then

is a prompt, a completion, the completion’s first tokens, and $\lambda \in [0,1]$ the on-policy fraction. The two terms are the same divergence summed over positions. What differs is where the positions came from.

Definition

On-policy fraction (lmbda)

The fraction of training batches whose token positions come from the student’s own rollouts rather than from the fixed corpus. It is lmbda in TRL’s GKDConfig. At 0 the run is pure off-policy and the state distribution never moves; at 1 every batch’s positions come from the current student; in between, each training step draws an on-policy batch with probability lmbda, independently, so over steps you get roughly on-policy batches, not a blend inside every batch.

That last clause changes what a middling means. The implementation makes a Bernoulli draw per training step, not a per-example mixture, so is “half the batches are entirely rollouts” and not “every batch is half rollouts”. Same expectation, different variance, and on a short run some of what a sweep shows you is that variance.

12.3.1 The contract between the two parameters#

lmbda decides which positions exist. beta decides what is computed at a position that exists. Neither touches the other’s job. The state distribution over contexts is a function of and the current weights; the per-position objective is a function of and the two distributions at that context.

They are orthogonal in mechanism and coupled in consequence, and the coupling runs one way. sets how aggressively the student narrows, per Chapter 6: the reverse-KL end is mode-seeking, the forward-KL end is mass-covering and forbids concentrating.8 At that narrowing has nowhere to feed back, because the positions come from a corpus indifferent to what the student thinks. At the narrowing changes the rollouts, the rollouts change the positions, and the positions are where the next narrowing is computed. That loop is the entire risk profile of this chapter, and its gain rises with and with how mode-seeking is.

Table 12.1 What each parameter controls, and what it does not.

lmbda beta
Controls which contexts the loss is evaluated at which divergence is evaluated there
Chapter this one Chapter 6
Endpoints 0 = pure off-policy, 1 = pure on-policy 0 = forward KL, 1 = reverse KL, 0.5 = symmetric JSD
Cost effect linear in generation time none
Failure it enables the narrowing feedback loop mode collapse at a single position
Estimable from student samples not applicable the reverse end yes, the forward end no

The two endpoints have names. At the objective is exactly Chapter 10’s token-level distillation objective with the teacher’s distributions computed live instead of read from a cache, which is Hinton’s formulation with a divergence in place of the softened cross-entropy.9 If that is where you end up, use the cache and save the teacher’s forward pass. At every position the student trains on is a position it put itself in, and the corpus contributes prompts and nothing else.

Watch out

The TRL implementation gates the on-policy branch with if random.random() <= self.lmbda. Note the inclusive comparison. At lmbda=0 this is true only when random() returns exactly 0.0, with probability on the order of , so lmbda=0 is pure off-policy in practice but not by construction. At lmbda=1 it is always true. Harmless, and worth knowing before you write an assertion about it.

12.3.2 The loop is not a policy gradient#

The second expectation has in its sampling distribution, which usually means a REINFORCE term, a baseline, and the variance machinery that makes reinforcement learning delicate. GKD does none of it: rollouts are generated under no_grad, detached, and handed to the loss as though loaded from disk, so the gradient flows through inside and not through the that produced the tokens. Nothing in the gradient knows that narrowing the output will change the data, because the sampling term was differentiated away, which is why the run is stable at ordinary learning rates and why the feedback loop needs an external monitor.10

12.3.3 Which end of beta on-policy pushes you toward#

Practice pushes toward the reverse-KL end when is high. The reason is worth stating instead of inheriting.

Reverse KL asks the student to put mass only where the teacher does and permits it to ignore regions the teacher cares about. On a fixed corpus that permission is dangerous, because the positions were chosen without reference to what the student can do, so a student that gives up on hard regions gives up on positions it will be evaluated at. On the student’s own rollouts the calculus changes: the positions being trained are the positions the student reaches, so “concentrate on what you can do well” and “concentrate on where you actually are” point the same way.

The forward end on rollouts is the odd combination, asking the student to cover everything the teacher would do at every context it visits, which spends the point of on-policy generation on an objective that forbids specializing. Solutions 07 registers the prediction that a on-policy arm’s entropy declines less than a arm’s and plateaus near the teacher’s own spread, with the cost showing up as quality and not instability: a 360M student made to cover a 1.7B teacher’s spread at every visited state has less mass left for the tokens it can get right. The prediction carries a registered refutation, which is the right way to write one. If entropy collapses under , the feedback loop is overpowering the mass-covering loss, and that would be worth replicating across seeds before believing.

12.3.4 Two trainers, and which one you are configuring#

TRL ships two on-policy distillation trainers, both under trl.experimental, and they are not interchangeable. Everything above describes the first one, trl.experimental.gkd.GKDTrainer, which implements the GKD paper’s method: lmbda mixes on-policy and off-policy batches, beta picks the divergence out of Chapter 6’s family, and seq_kd degrades the trainer to Chapter 11’s sequence-level recipe by training on teacher-generated text with the ordinary cross-entropy.

The second is trl.experimental.distillation.DistillationTrainer, and its defining property is what it does not have. There is no lmbda field anywhere in DistillationConfig, because the trainer is always on-policy: every batch is student rollouts, which is with no dial to turn it down. It is also the path built for a teacher that lives somewhere else, served over vLLM and answering over the network, which is Chapter 15’s subject. And its loss is a Liger fused-kernel generalized JSD, one kernel that reads both log-probability tensors and returns the divergence without materializing the intermediate per-token vocabulary tensors that the naive implementation holds. That last property is a memory decision, not a numerical one: the objective is the same -parameterized divergence, computed without the peak.

Table 12.2 The two on-policy trainers TRL installs, and what each one fixes for you.

GKDTrainer DistillationTrainer
On-policy fraction lmbda, anywhere in fixed at 1; no lmbda field exists
Divergence beta, the generalized JSD family beta, computed by a Liger fused kernel
Teacher a model object in the same process a model object or a vLLM server over the network
Degrades to SeqKD seq_kd=True at lmbda=0 no
What it is for studying the on-policy fraction running at against a served teacher

The introspection habit from §12.7.3 is what keeps this straight, and it is worth stating as a rule: the installed code is the specification, and the documentation is a lagging description of it. The labs assert the contract instead of trusting either. For GKDConfig, that lmbda, beta, temperature, max_new_tokens, and seq_kd are all present. For DistillationConfig, that beta is present and lmbda is absent. Asserting an absence looks pedantic until you consider what it catches: a future version that adds a lmbda field to the always-on-policy trainer would silently change what a config without that key means, and the assertion fails one second into the run instead of after six hundred steps of a study you thought was a arm. The same habit catches the constructor. Both trainers take processing_class= for the tokenizer, and the older tokenizer= keyword that these classes once accepted is gone, so a call copied out of a two-year-old example fails on an argument name before it fails on anything interesting.

12.3.5 Reading a lmbda sweep#

Sooner or later you will run across a grid, look at five quality numbers, and have to say what they mean. That reading is where most of the errors in this chapter’s subject actually happen, because a five-point curve on a noisy metric will support almost any story you bring to it.

Register the shape before the points arrive. There are two hypotheses worth naming and they make different pictures. H_monotone says quality rises all the way to the boundary, so more on-policy is always better and the only question is what you can afford; on the grid it looks like , rising with a decelerating slope. H_interior says an intermediate mixture beats both endpoints, so there is a real optimum to find; it looks like . Write both down, with numbers, before you run anything. Having committed to what each shape looks like, you cannot later read the noise as whichever one you were hoping for.

Then read the curve mechanically, because “it bends” is not a criterion. An interior optimum requires two things at once: an argmax that sits away from both endpoints, and a genuine bend at that point. The bend is the discrete second difference,

evaluated at the interior grid points, and the bend location is , the point where the curve turns over hardest. The test is that the argmax and the bend are the same point, and that the bend is deep: a curve with an interior optimum has a second difference at that point clearly more negative than anything the monotone shape produces, and Solutions 07 asks for a margin of two quality points before it will call the difference real. A curve whose best point is interior but whose second differences are all near zero is a flat curve with noise on it, and the honest reading is that did not matter over the range you swept.

The sweep can be invalidated outright in two ways, and both are cheaper to check than to argue about.

Watch out

A lmbda point whose monitor tripped is a crashed run wearing a data point’s clothes. An arm halted at step 300 by §12.10’s entropy monitor trained for half as long as its neighbors, so its quality number measures the halt, not the on-policy fraction. Before you plot anything, assert that every arm’s EntropyMonitor stayed quiet for the full step budget. If one did not, the sweep has a hole in it and the fix is to rerun that arm, not to interpolate across it.

The other invalidation announces itself in the shape. A curve that zigzags, up at , down at 0.5, up at 0.75, is a sawtooth, and a sawtooth means the points are not comparable to each other. The usual cause is exactly the one above, different arms having run different effective step counts after a stop, and the second most common is seed noise larger than the effect, which Chapter 18 gives you the arithmetic to check. Either way the sawtooth is a signal about the measurement, and reading it as a fine-grained property of is how people end up recommending 0.375.

For what it is worth as a prior: the shape I would expect on this course’s pair is quality rising from through about 0.5, then flattening or bending slightly down between 0.75 and 1.0, which makes an interior best point somewhere in 0.5 to 0.75 the likely reading. That is a prediction, not a result, and the reason to write it here is so that a sweep which comes back monotone to the boundary is informative rather than unremarkable.

12.4 Five arms, and what each one controls for#

Table 12.3 Lab 07’s five arms. Every field not shown is held fixed across all five. Student and teacher are the 360M and 1.7B instruction-tuned SmolLM2 checkpoints.11

arm lmbda beta init lr sampling what it controls for
off 0.0 0.5 base 360M 3e-5 0.9 the off-policy reference point
mixed 0.5 0.5 base 360M 3e-5 0.9 the GKD default, halfway
on 1.0 0.5 base 360M 3e-5 0.9 pure on-policy, cold started
on-warm 1.0 0.5 Lab 04’s distilled checkpoint 3e-5 0.9 initialization, holding all else
degenerate 1.0 1.0 base 360M 2e-4 0.3 a collapse you can watch on purpose

The first three arms differ in exactly one field, and the lab asserts that mechanically instead of trusting itself. A helper computes the set of keys on which two configurations differ, and the assertions are that off and on differ only in lmbda, mixed and on differ only in lmbda, and on and on-warm differ only in init. Those execute before any model loads. They cost a millisecond and they catch the commonest way a multi-arm study becomes uninterpretable, which is a stray edit to one arm’s learning rate three days before the runs.

So arms one through three are a one-parameter sweep in with everything else pinned, and whatever difference appears is attributable to the on-policy fraction or to noise. Separating those takes seeds, which is Chapter 18’s subject. The fourth arm is a one-field change from the third.

The fifth breaks the one-variable rule deliberately, and the lab asserts that too: on and degenerate must differ in exactly three fields, so nobody tidies it into a clean comparison later. moves to the pure reverse-KL end, the most mode-seeking objective available. Sampling temperature drops from 0.9 to 0.3, narrowing the rollouts before any training effect. The learning rate rises nearly sevenfold. All three push toward narrowing, giving §12.3.1’s feedback loop the highest possible gain.

Field note

A deliberately broken arm feels like a wasted GPU-hour until the first time a real run breaks, and then it is the most valuable hour in the study, for a reason unrelated to the broken model: it is the only way to find out whether your monitor works. The lab states the rule as “a monitor you have not seen fire is a monitor you do not have”, and gives the arm a pass condition that runs opposite to every other arm’s. If degenerate survives 600 steps looking healthy, the monitor has failed, and you tighten the thresholds and rerun until the tripwire fires.

12.5 Cold start#

Definition

Cold start

Beginning on-policy training from a student that has not yet been distilled, so that at step zero the student and teacher disagree nearly everywhere. The contrasting case is a warm start, where on-policy training begins from a checkpoint that some cheaper method has already brought close to the teacher.

The cold-start problem is Chapter 4’s estimator result in production clothes, and the connection is exact.

Chapter 4 measured the regime dependence of the sampled KL estimators on Lab 00’s two constructed regimes. Close, the student being the teacher plus a small perturbation: ’s standard deviation 0.061 against ’s 0.331, a factor of 5.4 in ’s favor. Far, the two distributions drawn independently: ’s 23.85 against ’s 2.21, a factor of 10.8 the other way. The estimator everyone recommends falls apart when the models are far apart, and the models are far apart at the start of a run from a base model, which is the only time step zero happens.

2026-08-01T07:33:26.220148 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.01 0.1 1 10 true KL(q || p) between student and teacher (nats) 0.001 0.01 0.1 1 10 100 per-sample std of the estimator k1 k3 filled markers: Lab 00 section 9's measured statistics (Table 4.3, V = 500, N = 200,000, seed 7) close KL 0.053 std(k1) 0.331 std(k3) 0.061 far KL 4.064 std(k1) 2.211 std(k3) 23.854 crossover, KL ~ 0.70 nats cold start: where an on-policy run from a base model spends its first few hundred steps section 12.8's sampled forward KL: true 13.6 nats, estimate -0.36, so not a variance problem at all -- the estimate is on the wrong side of zero
Figure 12.2 Estimator reliability is worst exactly where a cold-started run begins: as the true divergence between student and teacher grows, the sampled estimators' spread grows faster than the quantity they estimate, and the shaded cold-start region is where every on-policy run spends its first few hundred steps.

For on-policy training the consequence is sharper, because the monitoring is not the only thing that degrades. A base student’s rollouts are, in the lab’s phrasing, garbage text. The teacher scores them happily and the scores are well-defined; what they are not is informative in the way the method assumes. The premise of on-policy distillation is that the student’s rollouts are the contexts the student will encounter, and a base student’s rollouts are contexts nothing, including the student six hundred steps from now, will visit again. You are paying generation cost for positions with a short shelf life.

Lab 07 registers the prediction that this appears as entropy jumping around in the first roughly 50 steps of the cold-started on arm before settling, and treats settling as the pass condition. If the jitter does not settle, the instruction is to warm start, not to tune. Solutions 07’s constructed trajectories build that jitter in explicitly, with swings up to 0.9 nats across the first eight logged observations, and §12.10 shows what they do to a monitor.

The course’s answer is sequencing instead of choosing. Spend the cheap phase first: off-policy steps against Chapter 10’s cached teacher, which cost no generation and are restartable. That buys a student close enough to the teacher that the estimators sit in their good regime and the rollouts are text a deployed model might produce. Then switch. The on-warm arm is that plan as an experiment, initialized from Lab 04’s cached-logit checkpoint with everything else held to on’s values.

Watch out

The lab also records warm starting’s failure mode: on-warm can come out worse than on. The diagnosis is a checkpoint that overfit its corpus, so its output distribution is already narrowed, which makes it a bad rollout policy. You warm started to get a student close to the teacher and got one close to the corpus. The check belongs before the on-policy phase: measure the warm-start checkpoint’s rollout entropy against the base model’s. One already well below base entropy starts partway into the narrowing loop, with less room before the floor.

12.6 Rollout buffers and staleness#

Generating fresh rollouts every step is what makes the data on-policy. It is also, per §12.2, the larger of the two costs in the loop. The obvious optimization is the one reinforcement learning adopted decades ago: generate a batch of rollouts, keep them, and train on them for several passes before generating more.12

Definition

Rollout buffer

A store of previously generated rollouts that a trainer reuses for several optimizer steps before refreshing it. It converts generation cost into staleness: fewer rollouts are drawn per step, and the rollouts being trained on came from an older version of the student.

Definition

Staleness

The gap between the policy that generated a batch of rollouts and the policy currently being updated by them. Measured in optimizer steps of lag, or in quality lost relative to freshly generated rollouts. Any positive staleness makes the data partly off-policy regardless of what the configuration says.

With a buffer of rollouts consumed in minibatches of for epochs, it refreshes every steps. Solutions 07’s toy uses , , , refreshing every steps, so at the end of a buffer’s life the gradient is computed at positions chosen by a policy eight optimizer steps out of date.

The toy is the smallest thing that keeps the ingredient staleness needs, a state distribution the policy controls. A two-position policy over a 12-token vocabulary: a learnable distribution picks the first token (the state), a learnable per-state conditional picks the second. The teacher is a fixed pair of the same shape with its first-position mass on states 0 and 1, while the student starts on states 10 and 11, so the student’s state distribution has to migrate. The loss is the exact forward KL at position one plus the exact forward KL at position two evaluated at the states appearing in the batch, which is GKD’s shape in miniature: each position’s divergence is exact, and which positions exist is decided by whoever generated the batch. The final score is the position-two KL weighted by where the final student goes.

Solutions 07 runs 40 SGD steps on three seeds, fresh against 2-epoch buffered, with identical optimizers, step counts, and losses. The buffered variant ends worse on all three seeds (0, 1, and 2), by 0.04 to 0.05 nats of on-policy divergence, 9 to 13 percent in relative terms. It draws exactly half as many rollout samples doing it: fresh sampling takes 40 steps at 16 draws for 640 total, the buffer takes 5 refreshes at 64 draws for 320. In nats, Chapter 2’s natural-log unit, 0.04 is small absolutely, so the informative figure is the relative one: reusing every rollout batch once cost about a tenth of the final objective’s value and bought half the generation.

The mechanism generalizes. As the student’s state distribution migrates from states 10 and 11 toward the teacher’s 0 and 1, the buffered variant keeps spending position-two gradient on states its policy has already left, up to eight steps behind, so the states the final policy visits are under-trained exactly where the final metric looks. That is exposure bias re-entering through the buffer door.

So the rule for when staleness stops being acceptable is a comparison of rates, not a threshold on epochs. Staleness cost scales with the refresh period multiplied by how fast the policy is moving, and only one of those two terms is a configuration field. Early in a run, at a high learning rate, on a cold start, the policy moves fast and an eight-step lag is a large fraction of the distance covered. Late in a run, with the loss flat, the same lag is nearly free. If you use a buffer, schedule it.

Watch out

A buffer is a second, hidden dial toward off-policy training, and it does not appear in the field that claims to control that. A run reported as lmbda=1.0 with 2-epoch reuse is not a pure on-policy arm and should not be compared against a fresh-rollout arm as though the only difference were wall clock. Reused rollouts are off-policy data wearing an on-policy label, and “2 epochs” is a point on the same dial as lmbda. If someone enabled buffering underneath your lmbda sweep, the sweep’s x axis is not what its label says and the curve will look flatter than it is.

12.7 Three implementation details that decide whether the loop is correct#

Each of these is a consequence of a rule established earlier and not a piece of trivia. A detail you have derived is one you can re-derive on a different library; a detail you have memorized is one you will get wrong when the API changes.

12.7.1 The logits offset when scoring a generated continuation#

You have a prompt batch encoded to input_ids of shape [B, L]. You call generate and get gen of shape [B, L + G], prompt then completion. You want the model’s next-token distribution at each of those G generated positions. The inventory records the slice as logits[:, enc_len - 1 : -1]. Derive it instead of copying it.

Chapter 7 established the only fact needed: the logit row at index is the model’s prediction of the token at index . It says nothing about the token at index , which the model has already read.

Run model(gen) and the logits have shape [B, L + G, V]. The generated tokens sit at absolute indices through . By the shift convention, the prediction of the token at index lives in logit row , and the prediction of the token at lives in row . So the rows you want are through inclusive, which in half-open slicing is [L-1 : L+G-1]. The tensor’s length along that axis is , so the endpoint is the index , and the slice is [L-1 : -1].

The slice survives two checks. The count: rows, matching the generated tokens. And the dropped row: the final logit row predicts a token at index , which does not exist, so dropping it with -1 is Chapter 7’s “drop the last position of the logits”. The payoff is that a mask defined over the generated tokens now indexes the logit slice directly with no second shift, so the shift lives in one place and everything downstream works in the coordinate system of the generated tokens.

One precondition makes a single scalar offset legal. Left padding. The offset is one number applied to every row, correct only if every row’s generated tokens begin at the same absolute index. With left padding the prompts are right-aligned and all completions start at column . With right padding each row’s prompt ends somewhere different, so a scalar slice silently mixes prompt tokens into some rows and drops generated tokens from others. It will not error. It will produce a slightly wrong entropy for the rest of the run.

Here is the probe, which is the shape of what a monitoring callback does every logging step.

@torch.no_grad()
def rollout_entropy(model, tok, prompts, gen_T, max_new_tokens=64):
    """Mean next-token entropy in nats over the model's own generated tokens."""
    model.eval()
    enc = tok(prompts, return_tensors="pt", padding=True,
              padding_side="left").to(model.device)   # one offset for every row
    L = enc["input_ids"].shape[1]

    gen = model.generate(**enc, do_sample=True, temperature=gen_T,
                         max_new_tokens=max_new_tokens,
                         pad_token_id=tok.eos_token_id)          # [B, L + G]
    new    = gen[:, L:]                                          # the G generated tokens
    logits = model(gen).logits[:, L - 1:-1]                      # the G rows that predicted them
    assert logits.shape[1] == new.shape[1]                       # the count check, cheap

    logp = torch.log_softmax(logits.float(), dim=-1)             # fp32, per Chapter 2
    H    = -(logp.exp() * logp).sum(-1)                          # [B, G], entropy per position
    keep = onpolicy_mask(new, tok.eos_token_id)                  # True through the first EOS
    model.train()
    return float(H[keep].mean())

The assertion is the load-bearing line. It costs nothing and it is the difference between finding an off-by-one now and finding it in someone else’s review of your write-up six weeks later.

12.7.2 The EOS mask, and the row that has no EOS#

Rollouts have ragged lengths, and generate pads the short ones out to the batch’s longest, so everything after a sequence’s EOS is filler the loss must not see. The course’s onpolicy_mask returns a boolean mask that is True up to and including the first EOS and False after. The lab asserts it on a hand-built batch with EOS token id 7.

Table 12.4 The mask onpolicy_mask returns on a hand-built batch with EOS token id 7. Row 3 never emitted EOS.

row tokens mask
1 [4, 5, 7, 9, 9] [T, T, T, F, F]
2 [4, 5, 6, 8, 7] [T, T, T, T, T]
3 [4, 5, 6, 8, 9] [T, T, T, T, T]

Row 1 is the ordinary case. EOS itself is inside the mask, deliberately and not by an off-by-one: emitting EOS is a prediction the student made at a position where the teacher has an opinion about whether stopping was right. Exclude it and the student gets no gradient at all on when to stop, which is strange to leave unsupervised in a model whose most visible failure is not stopping. Row 2 stopped exactly at the cap, and everything is supervised because everything is real.

Row 3 is the cautionary one, and the lab flags it. This rollout never emitted EOS; it hit max_new_tokens and was cut off. The mask is all True because there is no EOS to stop at, so a truncated continuation is fully supervised as though it were a completed thought. Train on enough of those and you are teaching the student that completions are exactly max_new_tokens long, which pushes directly toward a model that does not stop. Two fixes, not equivalent: raise the cap until genuine completions fit, which costs generation time every step, or filter capped rows out of the loss, which costs you the examples but is honest about what they are. Which you want depends on whether capping is rare (filter) or common (your cap is wrong).

Watch out

An all-True mask on a row with no EOS is correct behavior for the mask function and the wrong thing to train on. The utility cannot fix this for you, because “row that never stopped” and “row that used its budget legitimately” are the same tensor. Count your capped rows and log the count. A capped-row fraction that climbs during a run is length collapse’s mirror image and an early warning in its own right.

12.7.3 Skipping a parent trainer’s generation#

The third detail looks like a trick and is a statement about where responsibilities live in a class.

To implement §12.6’s buffer you subclass the GKD trainer and override training_step. Your override does the buffer bookkeeping, decides whether to refresh, and injects the cached input_ids, attention_mask, and labels into inputs. Then it needs the ordinary forward, loss, and backward.

The natural thing to write is super().training_step(...), and it is wrong. Inside your subclass, bare super() resolves to the GKD trainer, whose training_step is the method that performs the on-policy generation. Calling it regenerates the rollouts you cached, discards your injected inputs, and produces a run bit-identical to the unbuffered one, so your staleness measurement comes back at zero, which looks like a result.

What you want is the next class up: the general-purpose trainer whose training_step takes the inputs it is handed and does the forward, loss, and backward with the accumulation bookkeeping. super(GKDTrainer, self) starts the method resolution order lookup after GKDTrainer, so it lands there.

class BufferedGKDTrainer(GKDTrainer):
    """Reuse each generated rollout batch REUSE times before regenerating it."""
    REUSE = 2

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._buffer, self._uses = None, 0

    def _refresh(self, model, inputs):
        with unwrap_model_for_generation(
                model, self.accelerator,
                generation_kwargs=self.generation_kwargs) as unwrapped:
            self._buffer = self.generate_on_policy_outputs(
                unwrapped, inputs, self.generation_config)
        self._uses = 0

    def training_step(self, model, inputs, num_items_in_batch=None):
        if random.random() <= self.lmbda:            # this step draws an on-policy batch
            if self._buffer is None or self._uses >= self.REUSE:
                self._refresh(model, inputs)
            ids, attn, labels = self._buffer
            self._uses += 1
            inputs = {**inputs, "input_ids": ids,
                      "attention_mask": attn, "labels": labels}
        # Resume the lookup AFTER GKDTrainer, so its own generation never runs.
        return super(GKDTrainer, self).training_step(model, inputs, num_items_in_batch)

This is legitimate and not a hack because of the alignment contract. The grandparent’s training_step expects an inputs dictionary holding input_ids, attention_mask, and labels in the library’s convention, with -100 marking unsupervised positions per Chapter 7, and the GKD trainer’s generation path produces exactly that triple. Your buffer stores it and hands it back later. Because both paths satisfy the same contract, the grandparent cannot tell the difference. Change the contract and the trick stops working, for a reason you could name.

The generalization is worth carrying to other libraries: when a class does two things in one method and you want one of them, look at where the method resolution order lets you re-enter. That survives version bumps better than copying the parent’s body into your subclass.

The same part of Lab 07 carries a standing rule that has saved me more time than the trick has. Both of TRL’s on-policy trainers live under trl.experimental, where fields appear and vanish between minor versions, and the course README once documented a field that no longer exists. So the lab’s first cell introspects dataclasses.fields(GKDConfig) and asserts that every field the run plan depends on is present. A failed assertion there costs one second. Discovering the same drift from inside a wedged training run costs an afternoon.

12.8 The number on your dashboard labeled “KL”#

Chapter 4 derived the sampled-token trap. This is its operational form, which is the form you will meet.

During on-policy training you want a divergence on a panel. The loss is a poor answer, because it is computed on a data distribution that moves, so a falling loss can mean the student improved or that the rollouts got easier. So you add a KL panel, and unless you were careful the number on it is an estimate from tokens the student sampled, one per position, using the two models’ log-probabilities at those tokens and nothing else. Three facts about it, in increasing order of trouble.

It can be negative. The single-sample forward estimator is with at the sampled token, and is negative for all , bottoming at . A divergence is nonnegative; an estimate of it need not be.

Its errors are not symmetric noise. The estimator is unbiased over the full sampling distribution, which includes draws from regions the student has abandoned. Those draws carry astronomical importance weights and would dominate the average, and they essentially never happen, because the student abandoned those regions. So what you observe is the average conditional on missing them, a much smaller quantity, with no variance to warn you because the variance also lives in the draws you are not getting.

It is least reliable exactly when the run is in trouble. The estimate is accurate when student and teacher overlap heavily and degrades as they separate, and “the student has abandoned a region the teacher cares about” is both the failure you want to detect and the condition that blinds the detector.

Solutions 07 makes this concrete with ten tokens and a live assertion. The teacher has logits zero everywhere except token 0 at 3.0, giving ; the student is uniform over the other nine with before renormalization. The exact forward KL is 13.619 nats, nearly all of it the single term , which is what forward KL exists to charge for.

Draw 5,000 tokens from the student. The chance of drawing token 0 is , so the expected count is , and the lab asserts it appeared zero times. Every sampled token is one of the nine others, where the ratio is identical at . With every sample carrying the same ratio the importance-weighted estimate has nothing left to average. It is the value

The dashboard reads . The true value is . The estimate lands on the wrong side of zero, which is a different kind of answer and not a degraded version of the right one. It is within a percent of , the most negative value a single term can take, which is worth recognizing when a panel labeled KL parks near and stays there.

The reverse-direction estimate on the identical 5,000 draws lands within 10 percent of its closed form, and the lab asserts that too. The samples are fine. The direction is the problem, for a structural reason: reverse KL is an expectation under the student, so the student’s own samples estimate it without reweighting, while forward KL is an expectation under the teacher and needs importance weights that vanish exactly where the divergence lives.13

Watch out

GKD’s loss does not have this problem, and confusing the loss with the monitor will cost you a day. The teacher returns its full distribution at each rollout position, so the per-position divergence is an exact sum over the vocabulary in either direction; what is sampled is which positions exist, the state distribution. The trap bites on a monitoring panel you wrote from a survey formula, on a remote teacher whose API returns only the sampled token’s log-probability, and anywhere you compute a divergence from a text corpus after the fact.

Chapter 4’s guidance carries over. Label the panel with the estimator instead of the quantity. If you want the mass-covering direction on a dashboard, compute it densely on a small fixed held-out batch: a hundred positions of exact forward KL every 50 steps is worth more than every position of an estimate blind to the failure it exists to detect. And watch a quantity without the pathology, which is the next section.

12.9 What you watch while it runs#

12.9.1 Rollout entropy#

Definition

Rollout entropy

The mean next-token entropy of the student’s own distribution, computed over the tokens of the student’s own generations, in nats. It requires no teacher, no ratio, and no importance weight, which is why it is trustworthy in exactly the regime where the sampled divergences are not.

Entropy at a position is , a full-vocabulary sum over one distribution. No second distribution, so no ratio, so nothing to be blind about. The only sampling is which positions get computed, which is the sampling the loss already uses. What it measures is how committed the student is at the contexts it puts itself in, which is the quantity the narrowing feedback loop moves.

Healthy looks like decline followed by a plateau. The lab characterizes it as a smooth decline of 10 to 30 percent over a run, then flat. The decline is the student committing; the plateau is equilibrium with the teacher’s own spread, a positive number because the teacher is not deterministic either.

Collapse looks like an accelerating decline that does not recover. The lab’s timing is worth quoting: usually late in the run, usually after things looked fine. Solutions 07’s constructed collapse gives the shape a functional form: flat near 2.55 nats until step 1200, then . A time constant of 120 steps means each 25-step logging interval forfeits about a fifth of the remaining entropy, since . That is what accelerating means quantitatively, and it is why detection latency is expensive.

12.9.2 The printed samples#

The second instrument is two decoded generations printed every logging interval, and the instruction attached to it is: read them.

The lab’s claim, which matches my experience, is that generations develop tics roughly a hundred steps before any metric is clearly bad. Repeated openers. The same transitional phrase in every sample. A collapsing model does not become uniformly worse; it becomes narrower, and narrowness shows in two samples long before it shows in an average. The scalar lags because early narrowing affects the tail of the position distribution instead of its center: most positions are still fine, and the handful that have gone nearly deterministic tend to be structural ones like sentence openers, which is what a reader notices. Chapter 16 covers the quantitative versions, distinct-n and self-BLEU, and where each one lies to you.14

12.9.3 Length#

Log mean tokens before EOS per interval, plus the fraction of rollouts that hit the cap without emitting EOS. Falling mean length is the second stage of the collapse sequence, after entropy and before the terminal state. Rising capped-row fraction is the opposite failure from §12.7.2, where the student is learning not to stop because you have been supervising truncations as completions.

12.9.4 What the divergence direction does to the entropy you will see#

One measured contrast, because it calibrates what a low entropy reading means. Solutions 07 runs a capacity-limited toy: a bimodal teacher over 10 tokens, logits at everywhere except two modes at , against a student restricted to two degrees of freedom through a fixed random projection. Forty Adam steps at learning rate 0.3, once with forward KL and once with reverse.

Trained with forward KL the student ends at 1.5 nats of entropy. Trained with reverse KL it ends at 0.05. The lab asserts a gap above 0.5 nats. Forward KL has to cover both teacher modes and two degrees of freedom cannot represent a distribution that is both concentrated and bimodal, so it settles on something spread out. Reverse KL commits to one mode and goes nearly deterministic.

That is a toy on ten tokens and I report it as one. What it establishes is the mechanism behind a real effect: a near the reverse end runs at systematically lower rollout entropy than a near the forward end, on the same task with the same student, without anything being wrong. An absolute entropy threshold calibrated on one is not valid for another, which is the first of two reasons the monitor cannot be a bare threshold.

12.10 Writing an abort criterion that fires on the real thing#

Definition

Abort criterion

A rule evaluated automatically during training that halts the run and preserves the most recent checkpoint when a monitored quantity indicates a state the run will not recover from. It exists because the alternative, a human noticing, reliably happens a few hundred steps late, by which time the checkpoint worth keeping has been overwritten.

The checkpoint arithmetic makes this urgent. Lab 07 saves every 200 steps over a 600-step run, at most three checkpoints, older ones rolled off. A collapse beginning at step 300 and noticed at step 550 has cost you every checkpoint from before it. The purpose is not to save the compute, which is already spent, but to save the last good weights.

Now the design problem. Entropy declines in healthy runs, so a rule firing on “entropy went down” fires on every successful run you will have, and a rule firing on “entropy went down a lot” has to decide what a lot is against a healthy run that may decline 30 percent over its length and a collapse that declines 80 percent over a fifth of it. The resolution is to make the rule about rate rather than level, by measuring the drop inside a trailing window.

Definition

Windowed-drop rule

A collapse test that flags when the monitored quantity has lost more than a fixed fraction of its value relative to the first observation in a trailing window of the last observations. Three parameters: the window length , the drop fraction, and an absolute floor that acts as a backstop for trajectories declining slowly enough to evade the window.

With the entropy at observation and the window’s first observation, flag when

Why the windowing works, computed on the lab’s own synthetic trajectories instead of asserted. Part A’s healthy trajectory is , sampled every 50 steps from 0 to 2000, a 50 percent decline over the run ( falling to ). With a window of 21 observations, which is 1000 steps of history at that spacing, the largest drop inside any window is about 33 percent, at the start where the exponential is steepest: . By step 1950 it is down to 26 percent. So a drop_frac of 0.6 never fires, and the lab asserts that it does not.

The same rule on Part A’s collapsing trajectory: at step 1300 the window measures , below threshold, and at step 1350 it measures , above it. The window rule fires at step 1350, 150 steps after the collapse began. The 0.15-nat floor is not crossed until step 1600, five logging intervals later, and at a fifth of the remaining entropy per interval there is nothing left to save by then.

That is the argument for the rule’s shape. A threshold on the level fires late because a collapsing run passes through every healthy run’s entropy on the way down; the windowed rule fires on the derivative, where the two trajectories differ from the start.

2026-08-01T07:33:18.323511 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 500 1000 1500 2000 training step 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 rollout entropy (nats) steepest trailing window on the healthy run: 33% in-window drop, so drop_frac = 0.60 never fires on it -33% collapsing healthy absolute floor, 0.15 nats windowed rule fires at step 1350 (2.550 -> 0.766, 70%) floor rule fires at step 1600 250 steps = 5 logging intervals constructed trajectories with known ground truth (Lab 07 Part A): no training run stands behind these curves
Figure 12.3 A healthy run and a collapsing run pass through the same entropy levels, so an absolute floor cannot tell them apart until the collapse is nearly over; the windowed-drop rule measures the rate instead and fires 250 steps earlier on the same trajectory.

The monitor is short enough to audit in one sitting.

class EntropyMonitor:
    """Two rules on one scalar: an absolute floor and a trailing-window drop."""

    def __init__(self, floor_nats=0.15, drop_frac=0.45, window=8):
        self.floor, self.drop_frac, self.window = floor_nats, drop_frac, window
        self.history = []                 # (step, entropy), in order, for later replay
        self.collapsed, self.reason = False, None

    def update(self, step, entropy):
        entropy = float(entropy)
        self.history.append((step, entropy))
        if self.collapsed:
            return True                   # latching: once tripped, stays tripped
        if entropy < self.floor:
            self.collapsed = True
            self.reason = f"floor: {entropy:.3f} < {self.floor}"
            return True
        start = self.history[-self.window:][0][1]
        if start > 0 and (start - entropy) / start > self.drop_frac:
            self.collapsed = True
            self.reason = (f"windowed drop: {start:.3f} -> {entropy:.3f} over "
                           f"{min(len(self.history), self.window)} observations")
        return self.collapsed

The monitor makes two choices worth naming. It latches, because the caller’s response is to stop and a monitor that untripped itself mid-decision would be worse than useless. And it keeps its full history, a few kilobytes that buy the ability to recalibrate thresholds afterward by replay, which is what makes the next section possible. The trainer side is a callback: on each logging step, sample rollouts, compute entropy with §12.7.1’s probe, feed it to the monitor, and set control.should_training_stop = True if it tripped.

12.10.1 Calibrating the threshold, which is the part people skip#

A monitor with an uncalibrated threshold is a guess with a log line. It has two costs that trade against each other, and you cannot know either without running it against trajectories whose ground truth you know. Detection latency, on a run that truly collapses: how many steps after onset does the rule fire. False fires, on healthy runs: how often does it halt a run that would have been fine.

Solutions 07 does this by replay, constructing the five arms’ trajectories to the shapes Part C describes, logged every 25 steps over 600 to match the lab’s configuration, with seeded noise. Only degenerate collapses; the other four are healthy by construction, and on carries §12.5’s cold-start jitter, with swings up to 0.9 nats across its first eight observations. Each trajectory then replays through a fresh monitor at each candidate drop_frac, window 8 and floor 0.15, and the first tripping step is recorded.

Table 12.5 Solutions 07’s monitor calibration, replayed on five constructed trajectories with window = 8 and floor_nats = 0.15. Only degenerate truly collapses.

drop_frac detects degenerate at step false fires which arm
0.30 350 1 on, at step 100
0.45 375 0
0.60 425 0

The table pays out three general lessons, none of them a fact about this run alone.

Latency is monotone in the threshold, and its spacing is set by the collapse’s time constant. A looser threshold needs more of the collapse to have happened, so it fires later. The gaps are one and two logging intervals, each worth about a fifth of the remaining entropy.

The tightest threshold is not free, and its false fire lands on the arm you most need to protect. At 0.30 the monitor halted the healthy cold-start on arm at step 100, by the mechanism §12.5 predicted: the jitter includes a 0.9-nat downward swing, and 0.9 nats against a window-start value near 3.5 is a 33 percent drop, which clears 0.30 and not 0.45. It is a real drop. It is not a persistent one.

Halting a healthy run is the worse failure. This asymmetry decides the verdict, and the instinct runs the other way. A monitor two intervals late on a real collapse costs two intervals of checkpoint; a monitor that halts a healthy cold-started run at step 100 costs the entire run. Since the abort exists to save the last good checkpoint and not the compute, latency is cheap and false positives are not.

Solutions 07’s verdict is drop_frac = 0.45, with window 8 and floor 0.15 unchanged: 0.45 and 0.60 both keep a clean record on the healthy arms, so latency decides, and 0.45 saves two logging intervals of overwritten checkpoints against the lab’s default of 0.60.

2026-08-01T07:33:19.149925 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 1 2 false fires on the four healthy arms 1 false fire: `on` halted at step 100 0.30 0.45 0.60 drop_frac 300 350 400 450 detection step on `degenerate` false fires on cold-start jitter step 350 (50 after onset) step 375 (75 after onset) step 425 (125 after onset) collapse onset, step 300 verdict: the earliest threshold with zero false fires replay of five constructed trajectories; window 8, floor 0.15 nats
Figure 12.4 The calibration table as a curve: detection latency falls monotonically as the drop threshold tightens, and the false-fire count jumps from zero to one between 0.45 and 0.30, which is what makes 0.45 the operating point rather than the tightest setting that works.

Field note

The calibration found something better than a threshold, which is the argument for running one even when you think you know the answer. The 0.30 false fire is a monitor-design problem that a threshold sweep exposed. Comparing against the window’s first element makes the rule sensitive to one anomalous high reading at the window’s start, and cold starts produce exactly that. Two fixes follow: exempt the first observations, since cold-start jitter is a known transient, or compare against a short median of the window’s early observations, which makes one spike harmless. Either would let a tighter threshold coexist with cold starts. Neither is in the course’s monitor, because that monitor is deliberately the simplest thing that works. If you are building this for a run you care about, build the median version.

The principle underneath: a monitor has to be calibrated against a healthy run and a sick one, or you do not know its false-positive rate, and a detector whose false-positive rate you do not know is not a detector. The sick run alone will not give it to you, because anything fires on a real collapse eventually, and the healthy run alone will not either, because a rule that never fires passes that test perfectly. The cheapest way to get both is to log the history and replay it, which costs no GPU time at all.

12.11 Entropy collapse and length collapse, named honestly#

Definition

Entropy collapse

The failure mode in which a model’s output distribution narrows toward determinism during training, so that rollout entropy falls toward zero and does not recover. In on-policy distillation it is driven by a feedback loop: narrowing output leads to narrower rollouts, which are the positions the next update is computed at, and a mode-seeking objective rewards further narrowing on them.

Definition

Length collapse

The failure mode in which a model’s generations shrink toward short stubs. In the on-policy distillation sequence it typically follows entropy collapse: a student that has become nearly deterministic reaches its most probable continuation, often an end-of-sequence token, earlier and earlier.

The terminal state is a model that emits the same few phrases forever regardless of the prompt. It is unmistakable once seen, and continuing to train does not recover it, because the data the training draws from has collapsed along with the model.

On citation I want to be precise about what the literature covers, because it would be easy to gesture at an adjacent paper and imply support that does not exist. Entropy collapse as a named phenomenon has a solid primary reference, from the reinforcement-learning-with-verifiable-rewards setting rather than from distillation: Cui and colleagues characterize the mechanism, derive an exchange relationship between policy entropy and downstream performance, and propose covariance-based mitigations.15 Read the transfer carefully. RLVR’s collapse is driven by a scalar reward concentrating probability on high-reward trajectories, while on-policy distillation’s is driven by a mode-seeking divergence against a fixed teacher; the feedback structure is the same shape and the driving term is different. Later work revisits entropy’s role in RL for reasoning models and proposes interventions on the entropy flow directly.1617 An adjacent line studies diversity collapse in post-trained models, where the driving force is supervised: Yun and colleagues attribute diversity loss to format constraints imposed during post-training, and Karouzos and colleagues ask where in the pipeline the collapse happens.1819 Neither is about distillation specifically.

For length collapse in distilled rather than RL-trained models, I could not find a primary reference whose subject it is. The entropy-collapse literature covers the mechanism that produces it, and the degeneration literature covers repetitive short outputs from a decoding rather than a training perspective.20 Neither is about the thing. So this book treats length collapse in distilled models as an open area, names it from the course’s own observation and from the mechanism that predicts it, and does not attach a citation implying coverage that does not exist. It is a small, unclaimed research question: characterize length collapse in on-policy distilled models, separate it from entropy collapse, and establish whether the reported ordering is universal or an artifact of particular objectives.

Watch out

Entropy collapse and healthy convergence are the same phenomenon at different gains, which is why no threshold on the level separates them. The distinction is whether the narrowing reaches equilibrium with the teacher’s own spread or runs past it, and that lives in the second derivative rather than the value. A monitor should measure the shape of the decline; one measuring its depth will either fire on healthy runs or fire too late.

12.12 When on-policy is worth it, and when it is not#

The decision is not binary and the useful version is a sequence.

Do not start here. Chapter 10’s cached-logit off-policy pipeline is the cheapest correct thing you can build on this hardware, it is restartable, and it gives you a student to compare everything else against. Without it an on-policy result has nothing to be a result relative to. The published baseline is the same shape: DeepSeek’s distilled model series is supervised fine-tuning on teacher traces with no on-policy stage at all.21

Reach for on-policy when the failure is generation-side. The clearest signal is a student that matches the teacher well on teacher-forced agreement and produces visibly worse text. That gap is exposure bias by definition, because teacher-forced agreement is computed under supplied context and text quality under self-supplied context. If both metrics say the student is fine, on-policy training is solving a problem you do not have.

Weight it by output length and by how recoverable errors are. §12.1’s compounding argument has in the exponent. On short completions there is not enough length for excursions to accumulate; on long structured outputs, where an early commitment constrains everything after it, exposure bias dominates.

Sequence rather than choose. Three independent arguments support this, more than most recommendations in this book have. The estimator argument, from Chapter 4: sampled-ratio quantities are trustworthy when the models roughly agree, so an off-policy phase moves the student into the good regime before the on-policy phase depends on it. The signal-value argument, from §12.5: a base student’s rollouts are contexts nothing will visit again. The economic argument, from Chapters 9 and 10: off-policy steps against a cache cost no generation and restart after a crash without recomputing anything. Lab 07’s on-warm arm is that plan as an experiment, and its registered prediction is that it reaches on’s quality in fewer steps with a calmer early trajectory. That is written down in advance rather than measured, and the lab attaches its own refutation: if on-warm comes out worse, the diagnosis is a checkpoint that overfit its corpus into a narrow distribution and is therefore a bad rollout policy.

Table 12.6 A decision guide for the on-policy fraction, given what you have and what you are fixing.

Situation lmbda beta Notes
First run on a new teacher/student pair 0 0.5 Use Chapter 10’s cache; get a baseline before anything moves
Teacher-forced agreement good, generations poor 0.5 to 1.0 toward 1 Warm start from the off-policy checkpoint
Long structured outputs, early commitments bind 1.0 toward 1 The case with the most to gain; monitor hardest
Short completions, low excursion risk 0 0.5 The generation cost buys little
Student far from teacher, no distilled checkpoint 0 first, then raise 0.5 Cold start is the fragile regime; buy your way out
Text-only teacher access not available not available Chapter 11; you cannot score rollouts without logits
Generation dominates step time 1.0 with a buffer toward 1 Accept staleness deliberately and measure it, per §12.6

The last row is a caveat, not a recommendation. A buffer is right when generation genuinely dominates, and on the reference machine with a 360M student it does not. Measure your own split before introducing staleness to fix a cost you do not have.

What on-policy training does not fix. It does not add capability the student’s architecture cannot hold, per Chapter 1’s limits, and it does not fix a corpus whose prompts do not resemble deployment, because the prompts still come from the corpus and only the completions come from the student.

The evidence deserves a caveat, since this chapter has been recommending a plan. Published comparisons here are mostly single-seed, on benchmarks whose effect sizes are comparable to seed-to-seed variance, and the field is young enough that its survey is an ongoing preprint and not a settled account.22 The mechanism arguments are sound and Chapter 4’s estimator result is measured, but the claim that the hybrid schedule beats either pure regime by a specific margin on your task is not something I can hand you. Run the three arms yourself at matched steps and multiple seeds, which is Chapter 18’s subject.23

12.13 Where this lands in the labs#

Lab 07 is the one lab in the course whose Part A matters more than its Part B. Part A builds and tests the monitor before any model loads, on three synthetic trajectories with known ground truth, and asserts that it stays quiet on healthy decline and fires on both a fast late drop and a floor crossing. That is the piece reading cannot give you: watching a detector you wrote fire on a trajectory you built, then discovering by sweeping the threshold that the setting intuition would have picked halts a healthy run. Solutions 07’s third exercise is the calibration replay, and it runs in seconds on logged histories with no retraining.

12.14 Exercises#

  1. Redo §12.1’s coverage arithmetic for outputs of 30 tokens and of 1,000, at per-token excursion rates of 0.001, 0.01, and 0.05. State the expected-output-length threshold above which you would spend budget on an on-policy phase, and what you had to assume about recoverability to make that threshold meaningful.

  2. Derive §12.7.1’s logits offset for a generation produced with right padding. Write down the per-row offset, show that no single scalar slice is correct, and describe the symptom you would see in the entropy monitor if you used the left-padding slice anyway. Would the listing’s count assertion catch it?

  3. A colleague reports an on-policy run at lmbda=1.0 whose rollout entropy fell from 3.4 nats to 2.6 over 600 steps and then flattened, whose printed samples look varied, and whose KL panel has sat between and for 400 steps. Diagnose: which observation is the alarming one, what would you compute to confirm it, and why does the entropy trajectory not reassure you?

  4. A second colleague reports a run whose rollout entropy fell from 3.5 nats to 0.9 over 400 steps, whose mean generation length fell from 96 tokens to 22 over the same interval, and whose monitor never fired at drop_frac=0.6, window=8 with 25-step logging. Determine whether the monitor was wrong, and if so in which of its three parameters. Show the arithmetic on a trajectory consistent with those endpoints.

  5. Solutions 07’s calibration found that drop_frac=0.30 false-fires on cold-start jitter and 0.45 does not. Specify the median-based variant from §12.10.1 precisely enough to implement, predict what it does to both columns of Table 12.5, and state what would have to happen for your prediction to be wrong.

  6. §12.6 measures 0.04 to 0.05 nats of staleness cost for 2-epoch reuse at a refresh period of 8 steps. Predict direction and rough magnitude for 4-epoch reuse at the same buffer size, and separately for 2-epoch reuse at half the learning rate. Say which prediction you are more confident in, and why.

  7. Design an experiment separating “on-policy training improved the student” from “on-policy training moved the student toward the eval distribution”. Specify the arms, what is held fixed, the distinguishing metric, and the result that would refute your preferred answer.



  1. Marc’Aurelio Ranzato, Sumit Chopra, Michael Auli, and Wojciech Zaremba, “Sequence Level Training with Recurrent Neural Networks,” arXiv:1511.06732 (2015), ICLR 2016. Names the train/inference mismatch and attacks it with a sequence-level objective. https://arxiv.org/abs/1511.06732 

  2. Samy Bengio, Oriol Vinyals, Navdeep Jaitly, and Noam Shazeer, “Scheduled Sampling for Sequence Prediction with Recurrent Neural Networks,” arXiv:1506.03099 (2015), NeurIPS 2015. https://arxiv.org/abs/1506.03099 

  3. Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. Chapter 11 covers the method; the point here is that it trains under teacher forcing on the teacher’s generations and therefore does nothing about the student’s exposure to its own errors. https://arxiv.org/abs/1606.07947 

  4. The two model sizes are from Lab 07’s configuration: Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big, Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737 

  5. TRL’s newer always-on-policy distillation trainer can score rollouts with a teacher served over vLLM, whose memory management is described in Woosuk Kwon, Zhuohan Li, Siyuan Zhuang, Ying Sheng, Lianmin Zheng, Cody Hao Yu, Joseph E. Gonzalez, Hao Zhang, and Ion Stoica, “Efficient Memory Management for Large Language Model Serving with PagedAttention,” arXiv:2309.06180 (2023), SOSP 2023, 611-626. https://doi.org/10.1145/3600006.3613165 

  6. Rishabh Agarwal, Nino Vieillard, Yongchao Zhou, Piotr Stanczyk, Sabela Ramos, Matthieu Geist, and Olivier Bachem, “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. The method name GKD does not appear in the title. https://arxiv.org/abs/2306.13649 

  7. Training a sequence model on its own outputs under a teacher’s supervision predates GKD. Alexander Lin, Jeremy Wohlwend, Howard Chen, and Tao Lei, “Autoregressive Knowledge Distillation through Imitation Learning,” arXiv:2009.07253 (2020), EMNLP 2020, frames it as imitation learning: https://arxiv.org/abs/2009.07253. See also Yuxian Gu, Li Dong, Furu Wei, and Minlie Huang, “MiniLLM: Knowledge Distillation of Large Language Models,” arXiv:2306.08543 (2023), ICLR 2024, which pairs student-generated data with a reverse-KL objective; the arXiv landing page now shows a later retitling, so the ICLR 2024 title is the version of record. https://arxiv.org/abs/2306.08543v2 

  8. For the divergence family itself in the sequence setting see Yuqiao Wen, Zichao Li, Wenyu Du, and Lili Mou, “f-Divergence Minimization for Sequence-Level Knowledge Distillation,” arXiv:2307.15190 (2023), ACL 2023. https://arxiv.org/abs/2307.15190 

  9. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015). https://arxiv.org/abs/1503.02531 

  10. Rafael Rafailov, Archit Sharma, Eric Mitchell, Stefano Ermon, Christopher D. Manning, and Chelsea Finn, “Direct Preference Optimization: Your Language Model is Secretly a Reward Model,” arXiv:2305.18290 (2023), NeurIPS 2023. https://arxiv.org/abs/2305.18290 

  11. Ben Allal et al., “SmolLM2,” arXiv:2502.02737, as above. The student is the 360M instruct checkpoint and the teacher the 1.7B instruct checkpoint, both in bf16. 

  12. The distillation-specific version of the same cost pressure is treated in Jongwoo Ko, Sungnyun Kim, Tianyi Chen, and Se-Young Yun, “DistiLLM: Towards Streamlined Distillation for Large Language Models,” arXiv:2402.03898 (2024), ICML 2024, which pairs a skew-KL objective with an adaptive off-policy scheme motivated by rollout cost: https://arxiv.org/abs/2402.03898. Its successor is Jongwoo Ko, Tianyi Chen, Sungnyun Kim, Tianyu Ding, Luming Liang, Ilya Zharkov, and Se-Young Yun, “DistiLLM-2: A Contrastive Approach Boosts the Distillation of LLMs,” arXiv:2503.07067 (2025), ICML 2025. https://arxiv.org/abs/2503.07067 

  13. The estimator vocabulary and the k1/k2/k3 family come from John Schulman, “Approximating KL Divergence,” joschu.net, 7 March 2020, http://joschu.net/blog/kl-approx.html (accessed 1 August 2026). Chapter 4 derives them and measures the regime dependence this section depends on. 

  14. distinct-n is from Jiwei Li, Michel Galley, Chris Brockett, Jianfeng Gao, and Bill Dolan, “A Diversity-Promoting Objective Function for Neural Conversation Models,” arXiv:1510.03055 (2015), NAACL-HLT 2016, https://arxiv.org/abs/1510.03055; self-BLEU is from Yaoming Zhu, Sidi Lu, Lei Zheng, Jiaxian Guo, Weinan Zhang, Jun Wang, and Yong Yu, “Texygen: A Benchmarking Platform for Text Generation Models,” arXiv:1802.01886 (2018), SIGIR 2018. https://arxiv.org/abs/1802.01886 

  15. Ganqu Cui, Yuchen Zhang, Jiacheng Chen, Lifan Yuan, Zhi Wang, Yuxin Zuo, Haozhan Li, Yuchen Fan, Huayu Chen, Weize Chen, Zhiyuan Liu, Hao Peng, Lei Bai, Wanli Ouyang, Yu Cheng, Bowen Zhou, and Ning Ding, “The Entropy Mechanism of Reinforcement Learning for Reasoning Language Models,” arXiv:2505.22617 (2025). The standard reference for entropy collapse in the RLVR setting; derives an entropy-performance exchange relationship and proposes covariance-based mitigations. https://arxiv.org/abs/2505.22617 

  16. Renren Jin, Pengzhi Gao, Yuqi Ren, Zhuowen Han, Tongxuan Zhang, Wuwei Huang, Wei Liu, Jian Luan, and Deyi Xiong, “Revisiting Entropy in Reinforcement Learning for Large Reasoning Models,” arXiv:2511.05993 (2025), Findings of ACL 2026. https://arxiv.org/abs/2511.05993 

  17. Huimin Xu, Shuai Zhao, Xiaobao Wu, and Anh Tuan Luu, “Understanding and Preventing Entropy Collapse in RLVR with On-Policy Entropy Flow Optimization,” arXiv:2605.11491 (2026). A preprint without a peer-reviewed venue at time of writing. https://arxiv.org/abs/2605.11491 

  18. Longfei Yun, Chenyang An, Zilong Wang, Letian Peng, and Jingbo Shang, “The Price of Format: Diversity Collapse in LLMs,” arXiv:2505.18949 (2025). https://arxiv.org/abs/2505.18949 

  19. Constantinos Karouzos, Xingwei Tan, and Nikolaos Aletras, “Where does output diversity collapse in post-training?” arXiv:2604.16027 (2026). A preprint without a peer-reviewed venue at time of writing. https://arxiv.org/abs/2604.16027 

  20. Ari Holtzman, Jan Buys, Li Du, Maxwell Forbes, and Yejin Choi, “The Curious Case of Neural Text Degeneration,” arXiv:1904.09751 (2019), ICLR 2020. Repetitive, degenerate output studied as a decoding-strategy problem rather than a training-dynamics one, which is why it does not cover the case in §12.11. https://arxiv.org/abs/1904.09751 

  21. DeepSeek-AI, “DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning,” Nature 645 (2025): 633-638, https://doi.org/10.1038/s41586-025-09422-z; preprint arXiv:2501.12948. The distilled model series is supervised fine-tuning on teacher traces with no on-policy stage for the students. 

  22. Mingyang Song and Mao Zheng, “A Survey of On-Policy Distillation for Large Language Models,” arXiv:2604.00626 (2026). The arXiv comment field reads “Ongoing Work”; cite it as a living preprint rather than a published survey. https://arxiv.org/abs/2604.00626 

  23. For where the on-policy branch sits in the wider method taxonomy, see Xiaohan Xu, Ming Li, Chongyang Tao, Tao Shen, Reynold Cheng, Jinyang Li, Can Xu, Dacheng Tao, and Tianyi Zhou, “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024). https://arxiv.org/abs/2402.13116 

Part IV · The Method Space

13

Student Initialization: Prune, Then Distill

Every student in this book so far arrived from somewhere else. You picked a checkpoint off a hub, loaded it, and started distilling into it. That checkpoint was the product of a pretraining run someone else paid for, on a corpus someone else chose, at a size someone else decided was worth publishing. It works, it is what almost everyone does, and it is a choice and not a default.

The alternative comes from noticing something about the white-box case. If you have the teacher’s weights, you have more than a scoring function. You have thirty or fifty or eighty transformer blocks stacked on top of an embedding table, and the stack is separable. Delete some of the blocks and the remaining ones still compose into a working forward pass, because each block takes a hidden state of a fixed width and returns a hidden state of the same width. The surviving weights are not approximations of the teacher’s weights. They are the teacher’s weights, bit for bit, still holding whatever they held before you touched the file. What you have built is a smaller model that remembers a great deal of what the large one knew, obtained in the time it takes to rewrite a state dict.

That model will be damaged. Removing layers from a trained network is not a neutral operation, and the first thing this chapter does is teach you how to measure the damage precisely, in units you can compare against the training you plan to do afterward. But the damage is repairable, and the repair is distillation, which is the thing you were going to run anyway. Minitron’s headline result is that pruning a teacher and then distilling briefly into the pruned model matches a from-scratch model of the same size at a few percent of its training compute.1

Depth pruning is a small amount of code operating on a dictionary of tensors, and almost every way it goes wrong is silent. The model still loads. The forward pass still runs. The loss still descends. You find out three days and one training budget later, if you find out at all. So this chapter spends as much of its length on the checks that prove the patient survived as on the operation itself, and the skill I want you to leave with is the one the lab states outright: evaluating an initialization before spending the training budget on it.

13.1 Three initializations, one budget#

Set the experiment up before arguing about it. You have a teacher (SmolLM2-1.7B-Instruct throughout, with the pretrained arm’s small sibling drawn from the same family2), a distillation recipe, and a fixed amount of training compute to spend after initialization. The recipe in Lab 09 is the cached top-64 pipeline from Chapter 10, chosen because it is the cheapest correct thing on this hardware and its cache cost was already paid. The budget is 1,500 optimizer steps at a learning rate of 3e-5, seed 17, identical across arms. What varies is one thing: the weights the student starts from.

Definition

Initialization budget

The compute spent obtaining the student’s starting weights, before the distillation budget begins. A separate line item from the training budget, and frequently ignored, which is how comparisons between initializations end up dishonest. Random is zero; a pretrained checkpoint is thousands of GPU-hours paid by someone else; a pruned teacher is minutes plus the teacher you already had.

Definition

Fixed distillation budget

The condition that every arm of an initialization comparison receives the same training compute after initialization: same steps, learning rate, recipe, data, and seed. Without it, the comparison measures the budget instead of the initialization.

Table 13.1 The three arms, and what each brings to step zero.

Arm Starting weights What it knows at step 0 Initialization budget
random Fresh initialization at 1.0B parameters Nothing Free
pretrained SmolLM2-360M-Instruct, 0.36B parameters Its own full pretraining Large, and someone else spent it
pruned SmolLM2-1.7B-Instruct with 10 of its 24 layers removed, about 1.0B parameters Most of the teacher, damaged Minutes of surgery

The outcome here is not obvious in advance, and it is worth resisting the temptation to read the chapter title as a spoiler. Each arm has a real argument behind it.

The argument for random is that it carries no damage and no wrong priors; every other arm starts in a state some other process put it in, and the optimizer works partly against that state. The counterargument is arithmetic: a pretraining run is measured in trillions of tokens and a 1,500-step distillation budget in millions. The lab expects random to close less than half of its initial gap in that budget, and calls that normal.

The argument for pretrained is that a 360M checkpoint was trained to be 360M. Its widths, its depth, and its parameter allocation were chosen for that size and then optimized end to end at that size. It is undamaged. The counterargument is that it has never seen the teacher, so its habits at the token level are its own and the distillation has to move them.

The argument for pruned is that its weights are the teacher’s weights, so it starts out agreeing with the teacher more often than a stranger would, and teacher agreement is close to what the distillation loss optimizes. The counterargument is that it is a 1.7B-parameter model with holes in it, and holes in the middle of a residual stream are not a small perturbation. Every surviving layer now receives an input distribution it never saw during pretraining.

There is a confound in this comparison, and the lab admits it up front instead of hiding it. The pruned arm has 1.0B parameters and the pretrained arm has 0.36B, a difference of 640M. If pruned wins, that result alone cannot tell you whether the initialization won or the extra capacity did. Section 13.12 covers what you are then entitled to claim. The framing that survives review is that “use the small sibling checkpoint” versus “prune my teacher” is the decision an engineer actually faces, capacity difference included.12

13.2 What kind of pruning#

Pruning is a family, and the members differ in whether the result is a model you can run faster or a model that is smaller only on paper.13

Unstructured pruning sets individual weights to zero, scattered anywhere in any tensor. The tensors keep their shapes, and the count of numbers stored in the file is unchanged; what changes is how many of them are nonzero.

Structured pruning removes regular blocks: a whole attention head, a whole slice of an MLP’s intermediate dimension, a whole transformer layer. The tensors change shape, and the model that comes out has fewer parameters in the ordinary sense.

Definition

Structured pruning

Pruning that removes regular blocks of a network (whole layers, whole attention heads, whole slices of width) instead of scattered individual weights. The result is a smaller dense model with different tensor shapes, which is the property that makes it faster to run.

Definition

Depth pruning

Structured pruning that deletes entire transformer layers, keeping every surviving layer’s weights unchanged. The hidden width is untouched, so the surviving layers still compose without any reshaping.

Definition

Width pruning

Structured pruning that shrinks each layer’s internal dimensions (attention heads, MLP intermediate size, and in aggressive versions the hidden width itself) rather than deleting whole layers. Every remaining tensor is a sub-block of the original, so surviving weights are still the teacher’s, but every layer is narrower than the one it came from.

The reason structured pruning is the relevant family for this book is Chapter 9’s roofline.

Autoregressive decode is bandwidth bound. Each generated token requires reading essentially the entire weight matrix out of memory, so the ceiling on tokens per second is the machine’s bandwidth divided by the model’s byte footprint. On the reference machine, 273 GB/s against a 1.7B-parameter model in bf16, which is 3.4 GB, gives a ceiling near 273 / 3.4 ≈ 80 tokens per second. Now zero out 40% of that model’s individual weights, scattered. The tensors are still 3.4 GB. A zero costs exactly the same two bytes to move across the bus as any other bf16 value, and a dense matrix multiply reads all of them. The ceiling is still 80 tokens per second. To convert scattered zeros into speed you need a sparse kernel whose sparsity pattern the hardware supports, and then a pruning criterion that produces exactly that pattern. Quantization is the other lever on this same bound and composes with structured pruning, because it acts on bytes per parameter while pruning acts on the parameter count.15

Delete ten of twenty-four layers instead, and the file is about 1.0B parameters, 2.0 GB in bf16, and the ceiling moves to 273 / 2.0 ≈ 136 tokens per second. That is a factor of 1.7 in decode throughput, obtained with a dictionary rewrite and no special kernels.

Table 13.2 What each pruning family gives you.

Family Tensor shapes Parameters stored Faster to decode? Needs special kernels?
Unstructured Unchanged Unchanged No, unless the runtime exploits the pattern Yes
Width (structured) Narrower Fewer Yes No
Depth (structured) Unchanged per layer, fewer layers Fewer Yes No

Depth pruning is the easiest member of the structured family to get right, which is why the lab teaches it first. Every surviving layer maps a hidden state of width to a hidden state of width , so removing a layer leaves a composition that still typechecks. Width pruning requires deciding per layer which heads and intermediate channels to keep, slicing every weight matrix consistently along the right axis, then fixing up anything that assumed the old dimensions. Minitron does both, and Sheared LLaMA searches directly for a subnetwork matching a target architecture rather than removing blocks by a ranked score.118 Section 13.5 shows why the width axis matters: depth-only pruning stops working past roughly half the layers.

13.3 Measuring importance in the currency you will pay in#

Which layers can go? Ask the model. Remove layer , run a fixed set of held-out examples through what remains, record how much worse the loss got, and put layer back. Do that for every layer. The output is a vector of numbers, one per layer, and its ordering is what you prune by.

Definition

Probe set

A small, fixed batch of held-out examples used only for measurement and never for training. Fixed so every measurement in a sweep is comparable, held out so the number means something about behavior and not memorization, and small because the sweep costs one forward pass per layer.

Definition

Layer importance

How much worse a model gets when one layer is removed, measured as the increase in masked next-token loss on a fixed probe set. It is a measured quantity, not a property you can read off the weights, and §13.6 is about what happens when you try to read it off the weights anyway.

The loss has to be pinned down precisely, because masking and shifting are exactly where Chapter 7’s traps live. Write for the token at position of row of the probe batch, for the completion mask (1 where the token is part of the answer, 0 where it is part of the prompt or padding), and for the model’s predicted distribution over the next token given everything up to and including position . Then

This is the ordinary language-model cross-entropy, in nats, averaged over completion positions only. Two details in it carry weight. The prediction at position is scored against the token at position , the shift Chapter 7 spent a section on. And the mask shifts with it: the mask entry gating the prediction made at position is the one belonging to position , because that is the token being predicted. Getting the mask shift wrong changes which positions the average runs over, which changes every damage number in the sweep by an unknown amount, and it will not raise an error.

Write for the model with layer removed. Then layer ’s damage is

and the ranking that matters is sorted ascending, least important first.

The sweep below is written for the shape of the idea, not for production. Watch the restore step at the end of the loop, the line that makes this an ablation instead of a cumulative demolition.

import torch, torch.nn.functional as F

@torch.no_grad()
def masked_nll(model, ids, mask):
    logits = model(ids).logits[:, :-1].float()
    logp = F.log_softmax(logits, dim=-1)
    tgt = ids[:, 1:].unsqueeze(-1)
    nll = -logp.gather(-1, tgt).squeeze(-1)      # (batch, T-1)
    m = mask[:, 1:].float()                      # mask shifted with the targets
    return float((nll * m).sum() / m.sum())

@torch.no_grad()
def layer_damage(model, ids, mask):
    blocks = model.model.layers                  # the ModuleList of transformer blocks
    base = masked_nll(model, ids, mask)
    out = []
    for i in range(len(blocks)):
        model.model.layers = torch.nn.ModuleList(
            [b for j, b in enumerate(blocks) if j != i])
        out.append(masked_nll(model, ids, mask) - base)
        model.model.layers = blocks              # put the patient back together
    return base, out

What this proves is that the measurement needs no gradients, no optimizer, and no modification to the checkpoint on disk: it is forward passes over a small batch, with a list comprehension standing in for a scalpel.

13.3.1 Why this metric and not another#

The damage metric is denominated in nats of next-token loss on completion positions. So is the repair, and that correspondence is the argument for using it. In the cached recipe the distillation loss is an average over completion positions of a divergence between the student’s next-token distribution and the teacher’s, measured in nats.11 A layer whose removal costs 0.03 nats has put the student 0.03 nats into a hole the training run must climb out of, and you can compare that directly against how many nats the run recovers per hundred steps. Any other importance signal is denominated in units whose exchange rate against the repair you do not know.

Be precise about one gap. The probe loss is cross-entropy against ground-truth tokens; the distillation loss is a divergence against teacher distributions. They are not the same function. They are the same shape, an average negative log-probability per completion token in nats, and they move together, because a teacher’s probability mass sits on continuations that are in fact plausible. For a tighter correspondence, measure damage as the forward KL from the intact model to the ablated one; the ranking generally comes out similar.

Price the cost, because the argument of §13.6 is that it is cheap insurance. One forward pass per layer over a small probe batch: 30 passes on the 135M model, 24 on the 1.7B teacher. A forward pass costs roughly a third of what a training step costs, since backward is about twice forward and the optimizer update is small next to both. So a 24-layer importance profile costs on the order of 8 training steps out of 1,500, about half of one percent. You are buying the ranking that determines what your student is, for half a percent of a budget you were spending anyway.

Watch out

An importance profile does not transfer between models. The 30-layer profile measured on SmolLM2-135M tells you nothing usable about which layers of SmolLM2-1.7B to drop, even though the two models come from the same family and were trained on the same recipe. What transfers is the method. Measure on the model you are about to cut. This costs 24 forward passes; reusing a stale profile costs you the run.

13.4 What the sweep says#

A depth sweep produces three facts, consistently enough across transformer families that you should be suspicious of your measurement if they do not appear.

The ends matter far more than the middle. The first layers lift token embeddings and positional information into whatever internal representation the model actually computes with; the last layers fold that representation back onto vocabulary logits. Remove either end and the damage is a collapse, because nothing downstream can interpret what it now receives. Lab 09’s profile assert encodes this: at least one of the four end layers (indices 0, 1, , ) must measure more than twice the average importance across all layers, or the measurement is presumed broken.

The middle is a broad flat region. Deep-middle layers each cost little to remove individually, because they refine a representation instead of transforming it into a different kind of object. That flatness is prune-then-distill’s entire opportunity; if importance were uniform across depth there would be no cheap layers to take and the method would not exist.

Importance is not monotonic. Within the flat middle, some layers cost noticeably more than their immediate neighbors, and the ordering wanders instead of descending smoothly from the ends toward the center. Lab 09’s third assert says the globally softest layer must not be one of the four end layers, which is a weak version of the same claim. The strong version is the reason Minitron measures importance instead of dropping every second layer the way DistilBERT initialized its student.14 DistilBERT reports taking one layer out of two as the best of the initialization schemes it tried, which makes it a good default in the absence of a measurement.19 The measurement costs a fraction of a percent of the training budget, so there is rarely a reason to run on the default.

2026-08-01T07:33:29.243970 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 5 10 15 20 25 29 layer index removed 0 2 4 6 8 d a m a g e     ( n a t s   o f   m a s k e d   n e x t - t o k e n   l o s s ) d i mean, 0.83 nats 2 x mean, 1.66 nats L0 8.71 L1 3.98 L28 1.00 L29 2.00 softest layer: L4, 0.065 nats, in the interior, not at an end the broad flat middle, and it is bumpy: the ordering wanders rather than descending smoothly from the ends measured here with section 13.3's ablation loop: SmolLM2-135M-Instruct, 30 layers, float32, 8 x 192 probe, 456 completion positions, intact probe loss 1.12 nats
Figure 13.1 Measured per-layer damage on a 30-layer model has a pronounced U shape: the first and last layers cost several times the average to remove, the middle is a broad flat region, and the ordering within that region is not smooth.

Field note

I assumed for longer than I should have that damage was additive. If layer 12 costs 0.02 nats and layer 17 costs 0.03, removing both should cost about 0.05, and removing the eight softest layers should cost the sum of their eight individual damages. It is a first-order approximation whose error term nobody bounded for me, and it is wrong in the expensive direction.

On the 135M model, the intact probe loss is 0.91 nats and the 22-layer patient (the eight softest layers removed by measured ranking) sits at 2.76 nats. That is 1.85 nats of joint damage from eight layers each individually near the bottom of the ranking. Single-layer ablation measures how well the rest of the intact network absorbs one missing layer. Once seven are gone, the rest of the network is not intact anymore, and the eighth removal lands on a model with much less slack left.

So the ranking is trustworthy as an ordering and untrustworthy as a predictor of joint cost. Use it to decide which layers to take, not how many. For how many, measure the actual patient at each depth, which is the sweep in the next section.

13.5 How deep you can cut, measured#

The lab’s Exercise 1 runs the sweep that answers “how many”: prune the 30-layer model to 26, 22, 18, and 14 layers using the measured ranking (removing 13%, 27%, 40%, and 53% of its depth), and measure the probe loss at each stop.

Table 13.3 Depth sweep on SmolLM2-135M, measured in Lab 09.

Layers kept Layers removed Fraction removed Probe loss (nats) Cost of this slice
30 0 0% 0.91
26 4 13% 1.57 +0.66
22 8 27% 2.76 +1.19
18 12 40% 3.84 +1.08
14 16 53% 5.33 +1.49

Read the last column carefully, because the honest reading is more interesting than the tidy one. The four slices cost 0.66, 1.19, 1.08, and 1.49 nats. The lab’s headline comparison is the first slice against the last: +0.66 nats for the first four layers removed, +1.49 for the four crossing the 50% line, which is 0.165 nats per layer against 0.373, a factor of 2.26 steeper. The curve is convex, and the assert checking convexity passes.

But the increments are not monotone. The third slice (1.08) costs slightly less than the second (1.19). Expecting a clean accelerating curve, you would read that dip as measurement error and go looking for a bug. What you would find is §13.4’s third standard fact showing up at a different granularity: importance is not monotonic, so the ranking’s ordering is not a perfectly graded sequence of “slightly worse than the last one”, and slice three happened to pull four layers the surviving network absorbed a little better than slice two’s. Over a long enough sweep the convexity wins, because the mechanism driving it is real, but do not expect it to win at every step.

The mechanism has a name in the lab: redundancy exhaustion. The layers you remove first are the ones the profile says the model can spare. Every subsequent slice comes out of layers the model spares less, while the survivors have less capacity left to compensate. Both effects push the same way.

What this predicts for a deeper cut is the useful part. The 1.7B teacher has 24 layers, so matching a 360M model’s parameter count by depth alone would leave five or six, far past the knee of this curve. A depth-only 360M patient would start distillation in worse shape than the pretrained 360M checkpoint it was supposed to challenge, and the lab expects that gated run to lose the per-parameter comparison outright. This is where you stop pruning depth and start thinning attention heads and MLP intermediate dimensions, which is what Minitron does when it cuts this deep.17

13.6 The cheap proxy that costs seven nats#

The importance sweep costs one forward pass per layer. There is a metric that costs zero: take the mean absolute value of every parameter in a layer and call that the layer’s score, on the folk theory that a layer whose weights are small is doing less work. It needs no data, no probe set, and no model execution at all. Lab 09’s Exercise 3 runs it against the measured ranking, on the same model, with the same surgery, and the result is the most instructive thing in the lab.

Field note

The comparison has three numbers and they get worse as you read them.

First, rank agreement. The Spearman correlation between the magnitude ranking and the measured damage ranking is +0.13. Spearman correlation compares two orderings and returns 1.0 for identical ones, 0 for unrelated ones, and negative values for opposed ones. At +0.13 the free metric recovers almost none of the measured ordering.

Second, the consequence for the decision you actually make. Asked for the eight layers to prune, the two rankings agree on 1 of 8. This matters more than the correlation, because a ranking is only ever consumed as a decision about a set, and the two metrics chose nearly disjoint sets.

Third, the price. Prune eight layers by each ranking and measure the resulting patients on the same probe: 2.76 nats for the measured ranking, 9.72 nats for the magnitude ranking. A gap of about 7 nats, before any repair has been attempted.

And then the detail that makes it a lesson and not an anecdote. Magnitude chose to prune layer 0, the layer Part A·1’s profile shows is catastrophically important, the one that lifts token embeddings into the model’s working representation. It picked layer 0 because layer 0’s weights are not unusually large in absolute value. The metric was not noisy here. It was systematically wrong, in a way that concentrated its error on the most expensive possible mistake.

That is the lesson worth carrying beyond this chapter: a cheap proxy’s errors are rarely uniform noise. They are structured by whatever the proxy fails to see, and what a proxy fails to see tends to correlate with what matters. Magnitude cannot see function, and layer 0 does an irreplaceable job with ordinary-sized numbers.

A cheap proxy for importance is worth exactly nothing until you have checked it against the expensive measurement, on your own model. Not on a model in a paper. Yours. The check costs one forward pass per layer.

2026-08-01T07:33:30.651960 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 4 8 12 16 layers removed from the 30-layer model 0 2 4 6 8 10 12 probe loss (nats) measured ranking weight-magnitude ranking 0.91 1.57 2.76 3.84 5.33 9.72 prunes layer 0, the layer the profile shows is catastrophically important about 7 nats, before any repair 50% removed filled markers: measured and reported in Lab 09 (Table 13.3, Exercise 3) open markers: recomputed here, same surgery, 8 x 192 probe
Figure 13.2 Cumulative damage against layers removed, for the measured ranking and the weight-magnitude ranking. The two curves separate immediately and the magnitude curve is still climbing steeply where the measured curve has flattened, which is what "the errors are systematic" looks like.

Before you generalize this, two things could bite. The lab’s version does not run the 300 repair steps the exercise specifies, so 7 nats is a pre-repair gap and therefore an upper bound on what the cheap metric costs after training; the lab’s reading is that 300 steps cannot erase a 7-nat head start and that the post-repair gap should be smaller but the same sign. And the number is specific to this model. What transfers is the mechanism, not the 7.

The second caution runs the other way. The notebook pre-commits to printing “HONEST FINDING: magnitude won on this model; the exercise’s expectation failed” if the cheap metric comes out ahead, and that branch is in the code before the cell is run. A comparison whose only printable outcome is the one you expected is a demonstration, not an experiment, and Chapter 18 is about the discipline that keeps those apart.

13.7 The surgery#

Depth pruning is an operation on a state dict.

Definition

State dict

PyTorch’s dictionary of a model’s weights. It maps parameter names, strings like model.layers.7.self_attn.q_proj.weight, to the tensors holding those parameters. It is what gets written when you checkpoint a model and what gets read when you load one.

Definition

State-dict surgery

Building a new model by constructing a smaller configuration, copying selected entries out of a source model’s state dict under rewritten names, and loading the result. No training, no gradient, no numerical change to any surviving weight. The entire operation is bookkeeping, which is why every failure mode is a bookkeeping failure and none of them raise.

The procedure has five steps, each with a way of going wrong silently.

One: choose the keep set. Sort the layers by measured damage ascending, take the first n_drop, and keep everything else. The keep set must be in ascending original order, because kept layer becomes new index keep.index(ℓ), which preserves relative order only if the list is sorted. Sorting it is a one-line defensive measure against a bug that produces a model running perfectly and computing something else.

Two: build a smaller config. Deep-copy the source config and set num_hidden_layers = len(keep). This field determines how many blocks the new model allocates, and it has to match the number you are about to copy, exactly.

Three: instantiate from the config. from_config gives you a fresh model of the new shape, random weights in every slot. Everything you do not overwrite in step four stays random.

Four: copy and rename. Walk the source state dict. Any name containing .layers. is a per-layer parameter; parse out its index, and if that index is in the keep set, write it into the destination dictionary with the index replaced by its new position. Everything else (embedding table, final normalization, output head) is copied under its original name.

Five: load, and audit the load. Load non-strictly, then assert that the returned missing and unexpected lists are both empty. That assertion is the most important line in the operation and §13.9 is about why.

2026-08-01T07:33:31.486493 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ source model, 8 layers pruned model, 5 layers copied verbatim, name unchanged model.layers. 7 .self_attn.q_proj.weight model.layers. 4 .self_attn.q_proj.weight every per-layer key is rewritten layers.0 layers.1 layers.2 dropped layers.3 layers.4 layers.5 dropped layers.6 dropped layers.7 layers.0 layers.1 layers.2 layers.3 layers.4 embedding table final norm, output head embedding table final norm, output head config.num_hidden_layers: 8 -> 5 must equal the number of layers copied, or the missing ones stay randomly initialized and nothing raises 0->0 1->1 3->2 4->3 7->4
Figure 13.3 The index remapping in depth pruning, and the config field that must move with it. Original layers 0,1,3,4,7 survive and become 0,1,2,3,4; every state-dict key is rewritten accordingly, and num_hidden_layers changes from 8 to 5 in the same operation.

Here is the core of it. Watch the two places the layer index appears: once when it is parsed out of the name and tested for membership, and once when it is written back as the new position.

import copy
from transformers import AutoModelForCausalLM

def depth_prune(src, keep):
    keep = sorted(keep)                                  # order is the computation
    cfg = copy.deepcopy(src.config)
    cfg.num_hidden_layers = len(keep)                    # the field that must move too
    dst_model = AutoModelForCausalLM.from_config(cfg)

    dst = {}
    for name, tensor in src.state_dict().items():
        if ".layers." in name:
            old = int(name.split(".layers.")[1].split(".")[0])
            if old in keep:
                new = keep.index(old)                    # position in the kept sequence
                dst[name.replace(f".layers.{old}.", f".layers.{new}.")] = tensor
        else:
            dst[name] = tensor                           # embeddings, final norm, head

    missing, unexpected = dst_model.load_state_dict(dst, strict=False)
    assert not missing and not unexpected, (missing, unexpected)
    return dst_model

What this proves is that depth pruning needs no knowledge of the architecture beyond the naming convention ...layers.<i>.... The function works on any transformer laid out this way, which is most of them, because it manipulates state-dict entries by name and never touches a module class.

One note on cost. The operation holds two models at once, so peak memory is roughly the sum: 3.4 GB for a 1.7B teacher in bf16 plus 2.0 GB for the patient, nothing on a 128 GB machine. At real teacher scale that stops being free, since a 70B teacher in bf16 is 140 GB and does not fit at all; there the surgery runs against a state dict streamed from disk shard by shard, same logic, different iteration source. Wall clock is minutes, which is the number that makes the initialization budget in Table 13.1 read the way it does.

13.8 The four checks a surgeon owes the patient#

The surgery produces a model that will load, run, and train no matter what you got wrong, so the checks are not optional and not a debugging step for when something looks off. They run every time.

Check 1: config honesty. Assert pruned.config.num_hidden_layers == len(keep). If the config says 22 and you copied 20, the model allocates 22 blocks and two of them keep their random initialization forever. Nothing raises. The model has the right shape, the right parameter count, and two randomly initialized transformer blocks wired into a pretrained residual stream.

Check 2: renumbering. Assert that the kept layers occupy positions 0 through contiguously, in their original relative order. Order matters because layers compose: a block trained to consume the output of block 7 will receive something else entirely if you place it after block 19. A permuted stack still runs. It computes a function nobody trained.

Check 3: the patient lives. Measure the probe loss of three models: the intact source, the pruned patient, and a randomly initialized model with the patient’s exact config. Assert

and, tighter,

The first inequality says the surgery hurt (it must) and left something behind (it must). The second is the one with teeth: a pruned model only somewhat better than random has kept tensor shapes and lost knowledge, and the 0.7 factor is a threshold a correct prune clears comfortably. On the 135M model with 8 of 30 layers removed, the intact loss is 0.91 nats and the patient is at 2.76, which is bad in absolute terms and nowhere near random.

Expect the patient’s generations to be close to gibberish, and do not treat that as a failure. Minitron’s pruned models are also bad before distillation; that is the premise of the method rather than a violation of it.1 The distinction check 3 draws is between damaged and random-equivalent, and those look identical if you only read sample text.

Check 4: keep the receipts. Write a small JSON file into the checkpoint directory recording which layers were kept, which ranking chose them, and the damage profile that produced the ranking. This takes one line and it is the difference between a failure you diagnose in ten minutes and one you re-derive a week later. When the pruned arm trains to gibberish, the first thing you want is the list of kept indices, so you can rerun checks 1 through 3 against the artifact that actually got trained, not against a surgery you reconstruct from memory.

A fifth check belongs to §13.11, because it measures something the first four cannot see.

In the labs: Lab 09

Part A executes all four checks live on CPU against a real 135M checkpoint, so the assertion messages are the claims and a broken surgery stops the notebook. Reading about a check that would have caught a bug is not the same experience as watching one fire.

13.9 The silent reinitialization#

This is the failure the whole protocol exists for, and its surface presentation is indistinguishable from success.

Load a state dict non-strictly and PyTorch accepts a partial match. It returns two lists: missing, the parameter names the model expected and the dictionary did not supply, and unexpected, the names the dictionary supplied and the model has no slot for. Both are returned as values. Neither raises.

Now suppose you got the config wrong. You copied 20 layers, but num_hidden_layers says 22 because you computed it from n_layers - n_drop upstream and one of those numbers drifted. The new model allocates 22 blocks; your dictionary has keys for blocks 0 through 19. The load succeeds, returns a missing list containing every parameter of blocks 20 and 21, and leaves those two blocks holding the random values from_config gave them.

What happens next is nothing. The model loads, save_pretrained writes it, and from_pretrained reads it back with no complaint, because the checkpoint is internally consistent: the config says 22 layers and the file contains 22 layers’ worth of tensors. The provenance of two of them is not recorded anywhere. The forward pass runs, because two randomly initialized blocks in the middle of a residual stream do not produce NaN; they produce a roughly zero-mean perturbation added to a hidden state, which is exactly what a residual block is built to absorb gracefully. The output is worse, not broken. And the loss descends, because gradient descent does not care where the weights came from. The curve has the shape you expect, the step time is normal, and every dashboard you built in Chapter 8 shows a healthy run.

At the end you have a student that underperforms, and the explanations you will reach for are the interesting ones: the capacity gap, the divergence choice, the learning rate. You will spend days there, because the boring explanation left no trace.

Field note

The tell, and the only reliable one, is step zero.

A correctly pruned patient starts far better than random on teacher agreement, because its weights are the teacher’s weights. A patient with two random blocks in the middle starts much closer to random, because the perturbation those blocks inject propagates through every layer above them. So measure teacher agreement and probe loss at step 0, before a single optimizer step, against a randomly initialized model of the same config. If your pruned model is not decisively better than random there, the surgery failed, whatever the loss curve does afterward. That is check 3, and it is why check 3 has a 0.7 factor instead of a bare inequality.

The check that catches the cause instead of the symptom is one line, on the return value of load_state_dict:

assert not missing and not unexpected

Both lists empty means every slot in the destination model received a tensor from the source and every tensor from the source found a slot. That single assertion converts the whole class of silent-reinitialization bugs into a loud crash at the moment of surgery, minutes into the project rather than days.

I have written code that ignored those return values. The signature invites it, since the return is easy to discard and the happy path never needs it. What changed my habit was not reasoning about it. It was reading a loss curve for two days.

Watch out

“Pruned arm is no better than random at step 0” means the state dict silently reinitialized. “Pruned arm trains to gibberish” means renumbering or config drift: the layers are all present but in the wrong positions. Different bugs, different fixes, both found by rerunning checks 1 through 3 against the saved artifact instead of against a freshly rerun surgery.

13.10 Staged recovery versus one shot#

Sheared LLaMA argues for pruning in stages: cut a little, train to recover, cut a little more, train again, rather than removing everything at once and repairing at the end.3 The argument has intuitive force, since a model that has healed between cuts is a better subject for the next measurement.

Definition

Staged recovery

Pruning in several rounds, with a period of training between rounds, so that each round’s measurement is taken on a model that has recovered from the previous round. The contrast is one-shot pruning: remove everything at once and repair once at the end.

The claim decomposes into two mechanisms that can be tested separately, and separating them is the useful move. The measurement half: re-measuring importance on the already-pruned patient chooses a better set of layers than the original ranking would have, because removing layers changes what the survivors contribute. The healing half: the training steps between surgeries repair damage, so each successive cut lands on a healthier model.

Lab 09’s Exercise 2 tests the measurement half in isolation, the half you can afford without a training box. Drop 8 layers of the 135M in one operation, versus drop 4, recompute the importance profile on the resulting 26-layer patient, and drop 4 more from the recomputed ranking. No training between stages. Map the staged arm’s stage-2 indices back to original indices so the two arms are comparable as sets, and compare the two 22-layer patients on the same probe.

The result is a null.

The two dropped sets overlap on 5 of 8 layers, so recomputation genuinely reorders the middle: removing four layers changed which of the survivors the model could spare next. That part of the argument holds. But the probe losses are 2.76 nats one-shot against 2.85 staged, so the staged arm came out 0.09 nats worse. Against roughly 1.9 nats of damage either surgery inflicts, 0.09 is small, and plausibly attributable to the staged arm’s recomputation running on a 2-row probe (26 forward passes per recomputation, so the probe was kept small to stay inside the CPU budget) and therefore being noisier.

A null result is worth more than the field’s publication habits imply, and the reason is specific here. The finding is not “staging does not work”. The finding is that the measurement half of staging bought nothing on this model, which means whatever advantage staged pruning has must come almost entirely from the healing half. That is a decomposition of somebody else’s claim into parts and an assignment of credit to one part, and it changes what you would build: do not write the recomputation machinery unless you are also going to run real training between the stages.

The negative framing matters too. A 0.09-nat difference on a single model with a small probe is not evidence that staging hurts. It is evidence that staging’s measurement component does not detectably help at this scale, which is a weaker and far more defensible statement. Chapter 18 covers the arithmetic that says how large an effect a comparison like this could have detected; running it before you write the sentence is what keeps “we found no effect” from becoming “there is no effect.”

The lab’s expectation for the gated 1.7B version, with 300 real distillation steps between stages, is a modest staged win that fades toward zero as the post-surgery budget grows, because a long enough repair forgives either choice of layers. That expectation is consistent with the broader finding that patience in distillation training dominates a surprising number of design choices.5

13.11 The ridge go/no-go#

The fifth check measures something the loss cannot.

The first four checks confirm that the patient computes something and that what it computes retains usable knowledge. They say nothing about what happened to its internal representations. That matters, because the surgery deleted layers that fed the survivors, so every surviving layer now receives inputs it never saw during pretraining. Two very different things could have happened to its hidden states. They could be shifted: still living in the same geometry as the intact model’s, offset and rescaled by the missing computation. Or they could be scrambled: living somewhere else entirely, related to the intact model’s states by nothing you can write down. You can tell these apart in closed form, in about a second, with no training.

Take a mid-network layer of the patient and its identity in the intact model. Run a few probe rows through both and stack the hidden states at those two layers, over the masked completion positions, into two matrices: from the patient, one row per position, and from the intact model with the same rows. Append a column of ones to for an intercept, call the result , and solve the regularized least-squares problem

This is ridge regression, least-squares with a small stability penalty on the coefficients so the inverse is well conditioned even when the hidden states are nearly collinear. Then compare two mean squared errors. The residual of the fitted affine map,

against the residual of the best constant predictor, the one that ignores the patient entirely and always outputs the intact model’s average hidden state,

The second quantity is the target’s own variance, which is what you get for knowing nothing. Lab 09 runs this on the 22-layer patient at , against the intact 135M at the matched layer, over 4 probe rows and a hidden width of 576.

import torch

@torch.no_grad()
def hidden_rows(model, ids, mask, layer_idx):
    hs = model(ids, output_hidden_states=True).hidden_states
    return hs[layer_idx + 1][mask]      # +1 because index 0 is the embedding output

Hs = hidden_rows(patient, ids, mask, J)             # damaged representation
Ht = hidden_rows(intact,  ids, mask, keep[J])       # the repair target
n, d = Hs.shape

X = torch.cat([Hs, torch.ones(n, 1)], dim=1)        # bias column
W = torch.linalg.lstsq(X.T @ X + 1e-3 * torch.eye(d + 1), X.T @ Ht).solution

mse_proj = float(((X @ W - Ht) ** 2).mean())
mse_mean = float(((Ht.mean(0) - Ht) ** 2).mean())   # what knowing nothing costs
assert mse_proj < 0.5 * mse_mean, "no linear structure connects the two spaces"

What this proves is that “can a linear map connect these two representation spaces” has a closed-form answer costing one matrix solve, so you never have to train a projector to find out whether training a projector is worth it.

The measured numbers are lopsided. against , a ratio of about 1,285. Read them one at a time.

The 33.4 is the average per-coordinate variance of the intact model’s hidden states at that layer, the error you incur by predicting the mean. It is not small, which tells you the target actually moves around across positions; near zero would have made the comparison vacuous.

The 0.026 is what remains after a single affine map from the damaged states. One matrix and one bias vector, no nonlinearity, no training. That map accounts for of the target’s variance.

So the surgery shifted the representations instead of scrambling them, and that was not guaranteed in advance: the layers feeding layer 11 are gone, so its inputs are genuinely out of distribution, and a ratio near 1 would have said the damage was representational scrambling.

That result points in two directions at once.

For this chapter, the ridge check is an initialization evaluation, independent evidence that the surgery preserved structure, obtained from a different measurement than the loss, at negligible cost, before any training budget is committed.

For Chapter 14, it is a gate. Feature matching, the TinyBERT-style idea of adding a term that pushes the student’s hidden states toward the teacher’s through a learned linear projector, only makes sense if a linear map between the two spaces exists.67 The lab’s gate is that the closed-form fit must beat the mean predictor by more than 2× in MSE, or the arm is theater: you would be spending a training budget teaching a projector to represent a relationship that is not there. Chapter 14 owns projectors in full, including layer pairing, the wrong-teacher control, and the failure mode where a projector matches hidden-state norms rather than content. Two things belong here. The closed-form solution is a free warm start, so initializing the trainable projector from it skips the projector’s own warm-up. And the surgical layer map is the natural pairing in the pruned case: patient layer is the intact model’s layer with fewer upstream colleagues, so nobody has to guess the correspondence, because the receipts from check 4 record it.

13.12 The economics, and what you are entitled to claim#

The case for prune-then-distill is a comparison of two ways to obtain a small model. The from-scratch path costs a full pretraining run at the target size: trillions of tokens, and a budget denominated in accelerator-months. The prune-then-distill path costs a teacher you already have, an importance sweep of forward passes, minutes of state-dict rewriting, and a brief distillation run. Minitron reports that this second path matches a from-scratch model of the same size at a few percent of its training compute.1 Sheared LLaMA reaches comparable economics through structured pruning with staged recovery, and its title states the mechanism directly: “Sheared LLaMA: Accelerating Language Model Pre-training via Structured Pruning.”3

Both results depend on a condition easy to lose sight of: you must already have the teacher, with weights. That puts prune-then-distill firmly in Chapter 1’s white-box row, unavailable to anyone working against an API, and makes it the strongest argument in this book for downloading a model rather than renting one.14

The lineage explains why the pieces are shaped the way they are. DistilBERT initialized its student from alternating layers of its teacher, which is depth pruning with a fixed heuristic in place of a measurement.4 TinyBERT added layer-to-layer feature matching on top of the layer selection, the branch Chapter 14 follows.6 FitNets originated matching intermediate representations through a projection, years before language models.8 What Minitron and Sheared LLaMA mainly added is measurement: importance estimated rather than assumed.13

Now the claim. Suppose the Lab 09 comparison comes out Minitron-shaped, with pruned at least as good as pretrained and both far ahead of random on teacher agreement and KL. You are not entitled to say “pruning beats pretraining as an initialization strategy”, because the pruned arm has 640M more parameters than the pretrained arm and the experiment cannot separate initialization from capacity. The control that separates them is the one Exercise 1 specifies: prune the teacher down to a 360M-equivalent and rerun at the same budget. Section 13.5’s sweep predicts that control comes out badly for depth-only pruning, which is itself the answer to a different question.

What you are entitled to say is “prune-then-distill beat the available alternatives at this budget”, which is the deployable claim anyway, and the one that answers the question an engineer actually has: which checkpoint to start from on Monday.

The verdict needs two conditions alongside it.

First, pretrained winning big is legitimate here and does not indict the method. The teacher and the small sibling both come from the SmolLM2 family, trained on the same data recipe by the same group, which is close to the best case for the small-sibling option.2 Prune-then-distill earns its keep when no good small sibling exists at all, which is the common situation the moment you leave the handful of families that publish a full size ladder.9

Second, teacher agreement is the metric most flattering to the pruned initialization, because the pruned model inherits the teacher’s habits directly while its general fluency starts worse. Reporting only agreement would overstate the result. Chapter 16 covers the eval design that keeps this honest, and the literature carries a caution about treating teacher agreement as though it were the objective: students frequently generalize better than their agreement with the teacher would predict.10 The standard benchmark side of that evaluation belongs to a harness, not a probe set.20

Step back from pruning for a moment. The thing worth carrying out of this chapter even if you never remove a layer is the pre-launch gate itself. An initialization is a hypothesis about where the optimizer should start, training is how you test it, and training is the expensive part, so the question to answer first is which cheap measurements predict the expensive outcome well enough to decide whether to launch. The sequence this chapter built costs fewer than ten forward passes and a few lines of arithmetic against a run of 1,500 optimizer steps, and that ratio is the whole argument. It has one escape hatch people forget: if the patient is too large to repair with full fine-tuning inside the budget, low-rank adaptation collapses the optimizer’s share and often makes the plan fit.16

A gate earns the name under two conditions. It must be denominated in units related to the objective, which is what §13.3 spends a page on. And it must be capable of failing, which is why §13.6’s honest finding and §13.10’s null result are in this chapter at all. A check that cannot come out badly is not a check. What none of it can tell you is how much of the damage the training budget will repair; that depends on the budget, the recipe, and the model, and the only way to find out is to run it.

13.13 Where this lands in the labs#

Lab 09 performs real surgery on a real 135M checkpoint on CPU, so Part A executes and asserts anywhere, including on a laptop, in minutes. That is the part of this chapter you cannot get from reading: the importance sweep prints thirty damage numbers you did not know in advance, and the four surgical checks are assertions whose failure messages are the claims, so a botched prune stops the notebook at the line that caught it. Part B, which recomputes the profile on the 1.7B teacher and runs three distillations at a fixed budget, is gated behind RUN_TRAINING. The solutions notebook runs all four exercises live on the small model, including the magnitude ablation and the ridge check, and states which conclusions change with scale.

13.14 Exercises#

  1. Before looking at Figure 13.1, sketch the per-layer damage profile you expect for a 24-layer transformer: mark the three layers you would remove first and the three you would never remove. Then state the measurement that would refute your sketch, and say what you would conclude if the softest layer turned out to be layer 1.

  2. The eight softest layers of the 135M model, removed together, cost 1.85 nats. Under what conditions would the sum of their eight individual damage values be a good estimate of that number, and in which direction would the additive estimate err? Design a measurement that bounds the error, and price it in forward passes against re-measuring the full profile after every single removal.

  3. Weight magnitude failed because it cannot see function. Name two other importance proxies that need no forward passes, predict a systematic failure for each that is analogous to the layer-0 mistake, and say what single measurement would expose it.

  4. The staged-versus-one-shot comparison produced 2.85 against 2.76 nats with 5 of 8 layers in common. Write the two sentences you would put in a report. Then state what the experiment would have to look like before you could claim staging is actively worse rather than not-better.

  5. Suppose the Lab 09 headline comparison comes out with pruned (1.0B) ahead of pretrained (0.36B). Specify the control arm that separates initialization quality from capacity, including what stays fixed, and say what you would conclude from each of its three possible outcomes.

  6. You run the ridge go/no-go on a pruned patient and get a ratio of 1.4× against the mean predictor, below the 2× gate. Give three courses of action, say what each costs, and say what evidence would make you pick each. One of the three should not involve feature matching at all.

  7. Using the reference machine’s 273 GB/s and the bytes-per-parameter arithmetic from Chapter 1, compute the decode ceiling for a 1.7B bf16 model, for the same model with 40% of its individual weights zeroed, and for a depth-pruned 1.0B version. Then state the property a runtime would need for the unstructured version to beat the dense one, and why it is harder to obtain than it sounds.



  1. Saurav Muralidharan, Sharath Turuvekere Sreenivas, Raviraj Joshi, Marcin Chochowski, Mostofa Patwary, Mohammad Shoeybi, Bryan Catanzaro, Jan Kautz, and Pavlo Molchanov, “Compact Language Models via Pruning and Knowledge Distillation,” arXiv:2407.14679 (2024), NeurIPS 2024. https://arxiv.org/abs/2407.14679. “Minitron” is the model-family name and does not appear in the title. The paper prunes along both depth and width axes, estimates importance by measured activation statistics rather than by weight magnitude, and reports matching a from-scratch model of the same size at a small fraction of its training compute. 

  2. Loubna Ben Allal, Anton Lozhkov, Elie Bakouch, Gabriel Martín Blázquez, Guilherme Penedo, Lewis Tunstall, Andrés Marafioti, Hynek Kydlíček, Agustín Piqueres Lajarín, Vaibhav Srivastav, Joshua Lochner, Caleb Fahlgren, Xuan-Son Nguyen, Clémentine Fourrier, Ben Burtenshaw, Hugo Larcher, Haojun Zhao, Cyril Zakka, Mathieu Morlon, Colin Raffel, Leandro von Werra, and Thomas Wolf, “SmolLM2: When Smol Goes Big - Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737. The 135M, 360M, and 1.7B checkpoints the course uses come from this family and share a data recipe, which is the condition that makes the pretrained arm strong. 

  3. Mengzhou Xia, Tianyu Gao, Zhiyuan Zeng, and Danqi Chen, “Sheared LLaMA: Accelerating Language Model Pre-training via Structured Pruning,” arXiv:2310.06694 (2023), ICLR 2024. https://arxiv.org/abs/2310.06694. The source for the staged-recovery argument and for structured pruning along multiple axes at once. 

  4. Victor Sanh, Lysandre Debut, Julien Chaumond, and Thomas Wolf, “DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter,” arXiv:1910.01108 (2019), 5th Workshop on Energy Efficient Machine Learning and Cognitive Computing, NeurIPS 2019. https://arxiv.org/abs/1910.01108. The student is initialized from a subset of the teacher’s layers, chosen by a fixed alternating rule rather than by measurement. 

  5. Lucas Beyer, Xiaohua Zhai, Amélie Royer, Larisa Markeeva, Rohan Anil, and Alexander Kolesnikov, “Knowledge distillation: A good teacher is patient and consistent,” arXiv:2106.05237 (2021), CVPR 2022. https://arxiv.org/abs/2106.05237. The finding that very long distillation schedules dominate many other design choices is the basis for expecting an initialization advantage to shrink as the repair budget grows. 

  6. Xiaoqi Jiao, Yichun Yin, Lifeng Shang, Xin Jiang, Xiao Chen, Linlin Li, Fang Wang, and Qun Liu, “TinyBERT: Distilling BERT for Natural Language Understanding,” arXiv:1909.10351 (2019), Findings of EMNLP 2020. https://arxiv.org/abs/1909.10351. The layer-to-layer feature-matching recipe that Chapter 14 covers in full. 

  7. Jiao et al., “TinyBERT,” §3.1, on the layer mapping function that assigns each student layer a teacher layer to imitate. In the pruned case that mapping is not a design choice: the surgery receipts record it exactly. 

  8. Adriana Romero, Nicolas Ballas, Samira Ebrahimi Kahou, Antoine Chassang, Carlo Gatta, and Yoshua Bengio, “FitNets: Hints for Thin Deep Nets,” arXiv:1412.6550 (2014), ICLR 2015. https://arxiv.org/abs/1412.6550. The origin of matching intermediate representations through a learned projection, and therefore the origin of the object the ridge check evaluates in closed form. 

  9. Qwen Team, “Qwen2.5 Technical Report,” arXiv:2412.15115 (2024). https://arxiv.org/abs/2412.15115. An example of a family that does publish a full size ladder with architecture details at each size, which is exactly the situation in which the small-sibling option is strong and prune-then-distill has the least to add. 

  10. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945. The fidelity-versus-generalization result, and the reason teacher agreement should be reported alongside task metrics rather than instead of them. 

  11. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015). https://arxiv.org/abs/1503.02531. The repair objective, whose units are what makes measured damage the right currency for layer importance. 

  12. Jang Hyun Cho and Bharath Hariharan, “On the Efficacy of Knowledge Distillation,” arXiv:1910.01348 (2019), ICCV 2019. https://arxiv.org/abs/1910.01348. The capacity-gap result, relevant here because a pruned student and a pretrained student of different sizes sit at different distances from the same teacher. 

  13. Xiaohan Xu, Ming Li, Chongyang Tao, Tao Shen, Reynold Cheng, Jinyang Li, Can Xu, Dacheng Tao, and Tianyi Zhou, “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024). https://arxiv.org/abs/2402.13116. Places pruning-plus-distillation inside the broader compression family. 

  14. Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819. https://arxiv.org/abs/2006.05525. For the general position of initialization choice within the distillation design space. 

  15. Ji Lin, Jiaming Tang, Haotian Tang, Shang Yang, Wei-Ming Chen, Wei-Chen Wang, Guangxuan Xiao, Xingyu Dang, Chuang Gan, and Song Han, “AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration,” arXiv:2306.00978 (2023), MLSys 2024. https://arxiv.org/abs/2306.00978. Quantization is the other lever on the same bandwidth bound that structured pruning acts on, and the two compose; Chapter 15 covers what each costs in quality. 

  16. Edward J. Hu, Yelong Shen, Phillip Wallis, Zeyuan Allen-Zhu, Yuanzhi Li, Shean Wang, Lu Wang, and Weizhu Chen, “LoRA: Low-Rank Adaptation of Large Language Models,” arXiv:2106.09685 (2021), ICLR 2022. https://arxiv.org/abs/2106.09685. The fallback when a pruned patient is still too large to repair with full fine-tuning inside the memory budget. 

  17. Muralidharan et al., “Compact Language Models via Pruning and Knowledge Distillation,” §3, on width pruning of attention heads and MLP intermediate dimensions, which is the axis depth-only pruning cannot substitute for once the depth cut passes roughly half the layers. 

  18. Xia et al., “Sheared LLaMA,” §2, on the targeted structured pruning objective that searches for a subnetwork matching a specified target architecture, as opposed to removing blocks by a ranked score. 

  19. Sanh et al., “DistilBERT,” §3, on initialization from the teacher: taking one layer out of two is described as the recipe that worked best among the initialization schemes tried, which is the heuristic that measurement replaces. 

  20. Leo Gao et al., “The Language Model Evaluation Harness,” Zenodo, v0.4.3 (July 2024). DOI: 10.5281/zenodo.12608602. https://github.com/EleutherAI/lm-evaluation-harness. For the standard benchmark side of the step-zero evaluation, which the probe loss and teacher agreement do not cover; Chapter 16 covers how to combine them. 

Part IV · The Method Space

14

Cross-Tokenizer and Representation Distillation

The teacher you want is usually not from your student’s family.

That sentence describes an ordinary Tuesday. You have a student architecture you can afford to serve, in a size that fits the hardware you already own, and somewhere on a model hub there is a teacher that is better at your task than anything in your student’s lineage. The two models were built by different groups, at different times, with different tokenizers. Nothing about that is exotic. It is the normal condition of the field, and the standard distillation objective, a divergence between two next-token distributions at matched positions, does not apply to it at all.

Chapter 7 proved why. An alignment between the two models’ positions would have to map a student position to a teacher position that has read exactly the same bytes, which is possible only where a byte boundary appears in both tokenizations, and the measured boundary sets overlap partially and in a way that depends on the string. Even at the boundaries that do coincide, the two models’ answers are distributions over different outcome sets naming different strings, and a divergence between distributions over different outcome sets is not defined. The consequence is stronger than “noisy.” Padding one logit vector to the other’s length produces a number, and that number compares the probability of one tokenizer’s token 831 with the probability of a completely unrelated string that happens to sit at index 831 in the other. It is meaningless, it is differentiable, and it will train.

There are two honest ways out, and this chapter is built on the split between them. The first is to compare only what survives re-tokenization, which means compare quantities that never mention a token id. Chapter 7 already showed the seed of this: sort each probability vector in descending order and the two vectors become comparable, because after sorting, index means “the probability of the -th most likely continuation” for both models, whatever that continuation is. The second is to stop comparing outputs entirely and compare the vectors flowing between the layers, which carry no vocabulary at all. The first half of this chapter is the first route, the second half is the second, and the two halves fail in unrelated ways, which is why they are worth learning as separate skills.

The chapter also carries the single most transferable habit in the course, and I want to name it before it appears in context. When you implement a loss from a paper, you prove it correct on cases with known answers before the first training step. Not after the run looks odd. Before. The reason is specific and not general fastidiousness, and I will get to it in §14.2.

14.1 The problem, restated in the form that matters#

Two tokenizer families differ in three ways, and they are worth separating because they fail at different stages of a pipeline.

The vocabularies differ in content. Token id 831 names a different string in each. Any operation indexed by token id is meaningless across the boundary. This kills the loss.

The vocabularies differ in size. SmolLM2 has 49,152 entries and Qwen2.5 has 151,936, a ratio of about 3.1.12 Any operation that assumes two tensors of the same last dimension raises a shape error, which is the friendly version of the problem, because an error is information. The unfriendly version is the code that pads or truncates to make the error go away.

The sequence lengths differ for the same string. A fixed piece of text becomes tokens under one tokenizer and under the other, with in general, and the token boundaries land at different byte offsets. This kills the position axis independently of the vocabulary axis, and it is the one people forget, because sorting fixes the vocabulary axis so cleanly that it feels as though the whole problem is solved.

It is not. Hold that thought until §14.3.3, where it comes back in the implementation.

2026-08-01T07:33:32.615288 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 12,288 24,576 36,864 49,152 student token id (own vocabulary, V = 49,152) 0.00 0.05 0.10 0.15 0.20 probability STUDENT SmolLM2-360M-Instruct tallest stems, with the strings they name in this vocabulary: id 198 '\n' p = 0.201 id 378 ' The' p = 0.081 id 669 ' This' p = 0.042 id 1,249 ' But' p = 0.033 id 831 names 'chn' here, p = 1.1e-08 0 37,984 75,968 113,952 151,936 teacher token id (own vocabulary, V = 151,936) 0.00 0.05 0.10 0.15 0.20 probability TEACHER Qwen2.5-0.5B-Instruct tallest stems, with the strings they name in this vocabulary: id 576 ' The' p = 0.080 id 1,096 ' This' p = 0.053 id 1,084 ' It' p = 0.032 id 1,416 ' If' p = 0.029 id 831 names 'val' here, p = 8.1e-08 the two horizontal axes have different lengths and no shared meaning: equal indices name unrelated strings, so no entrywise comparison is defined. Drawing resolution: one stem per bin of consecutive ids, at the bin's tallest entry.
Figure 14.1 Two next-token distributions from different tokenizer families, drawn in their own vocabulary order, showing that there is no entry of one plot that can be compared with any entry of the other.

Figure 14.1 is what the situation looks like before you do anything to it. Two probability vectors, one 49,152 wide and one 151,936 wide, each indexed by its own tokenizer’s ids, with no horizontal line you could draw across both panels that connects anything to anything. The next picture in this chapter is the same two distributions after one operation, and the contrast is the whole method.

14.2 Proving a loss before you trust it#

Here is why the proof-first habit is not fussiness.

An optimizer will drive any differentiable scalar downward. It does not know, and cannot know, whether that scalar measures what you meant. A loss with a sign error, an off-by-one in a shift, a silently truncating reshape, or a normalization applied twice is usually still smooth, still bounded, still decreasing. The run looks healthy. The loss curve bends the way loss curves bend. The checkpoints save. And the student is learning to satisfy a quantity that has no relationship to the behavior you wanted, which you discover weeks later when the evaluation comes back flat and you start bisecting a pipeline that has been wrong since the first commit.

This risk is worse for a cross-tokenizer loss than for an ordinary one, for a reason specific to the setting. With a same-tokenizer objective you can always inspect the top few tokens by name at a position you can read, because every quantity in the loss has a human-readable label attached. A cross-tokenizer loss deliberately destroys those labels. That is its entire mechanism, so the usual “look at it and see if it makes sense” check is unavailable and something else has to take its place.

What takes its place is a battery of cases whose answers you can compute by hand: not a test suite in the software-engineering sense, where you check that the code does what the code does, but a small set of inputs where the correct output of the mathematical object is known independently of any implementation. If your implementation reproduces all of them, the space of bugs it can still have is small. If it fails one, you learn which class of bug you have, because each case is chosen to be decisive against a specific way of being wrong.

The rest of this chapter treats that battery as the deliverable, not as scaffolding. Five properties, each with a proof and each with a bug it catches.

14.3 Universal Logit Distillation#

14.3.1 Why sorting is the move#

Take a probability vector produced by a softmax over a model’s logits. Entry carries two pieces of information glued together: a value, which is how much probability mass sits there, and an index, which is which string that mass belongs to. The index is the part that does not survive a change of tokenizer. The value does.

Sorting separates them and throws the index away. After sorting in descending order, entry 0 means “this model’s largest probability,” entry 1 means “its second largest,” and so on down to entry . Nothing in that description mentions a vocabulary. It would read identically for a model with 49,152 outputs and a model with 151,936. Two sorted probability vectors are therefore comparable entry by entry, and what they compare is how the model’s confidence decays with rank: whether it is peaked or flat, how fast the mass falls off, how long the tail is.

Definition

Sorted-probability matching

Comparing two models’ output distributions after sorting each one’s probabilities into descending order, so that index means “the -th largest probability” for both. The operation is defined between distributions over different and unrelated outcome sets, because the sorted vector refers to no outcome set at all.

Definition

Universal logit distillation

A cross-tokenizer distillation objective that trains a student by minimizing the L1 distance between the student’s and the teacher’s sorted next-token probability vectors, zero-padded to the longer vocabulary. Abbreviated ULD. Introduced by Boizard and colleagues, who give it its name and its published form.3

14.3.2 The loss, exactly#

Let be the student’s next-token probability vector at a supervised position and the teacher’s, where is the set of non-negative vectors of length summing to 1, and and are the two vocabulary sizes. Each is a softmax of that model’s logits, optionally at a temperature : $p = \mathrm{softmax}(z / T)zp_{(1)} \ge p_{(2)} \ge \dots \ge p_{(V)}$ for the entries of sorted in descending order, and extend the sorted vector past its own length with zeros, so that for . Let .

That is the whole per-position loss: sort both, zero-pad the shorter to the longer, sum the absolute differences. Over a batch, the implementation this course uses averages the per-position value over the set of supervised positions and does not divide by or by the vocabulary size:

The absence of a is worth registering, because it sets the scale of everything downstream. The per-position quantity lives on regardless of how large the vocabularies are, so a ULD value is directly comparable between a pair of 49k-vocabulary models and a pair where one side is 152k wide. If the implementation divided by , the same distributional disagreement would score three times smaller when the teacher was Qwen than when it was SmolLM2, and every threshold you set would be vocabulary-dependent. It does not, and they are not.

2026-08-01T07:33:34.382767 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1 0 0 1 0 1 1 0 2 1 0 3 1 0 4 1 0 5 0.00 0.05 0.10 0.15 0.20 probability student top-1 0.201 teacher top-1 0.080 shaded area = the ULD loss at this position, sum |p_s(i) - p_t(i)| = 0.3848 linear probability, the same vertical axis as Figure 14.1 same two vectors as Figure 14.1, one sort each, and the comparison is defined 1 0 0 1 0 1 1 0 2 1 0 3 1 0 4 1 0 5 rank r (descending probability) -- a coordinate both models share 1 0 9 1 0 7 1 0 5 1 0 3 1 0 1 probability (log) the same two curves on a log axis, where the tail is visible rank 49,152: the student's vocabulary ends and zero-padding begins; the hatched region is the teacher's remaining tail mass, 0.0004 of the 0.3848 student teacher
Figure 14.2 The same two distributions as Figure 14.1 after sorting, with the shaded area between the two curves equal to the ULD loss, showing that sorting creates a shared coordinate system where none existed.

Figure 14.2 is the picture that makes the method obvious. Both curves are monotone non-increasing, both start near their model’s top probability, both decay, and the shaded region between them is the number. The shorter curve is padded with zeros out to the longer one’s length, so the padding region contributes exactly the longer model’s remaining tail mass.

The implementation below is written to be read, not to be fast. Watch the argument order and watch the line that handles positions, which is the one that does not do what the sorting does.

def uld(student_logits, teacher_logits, student_mask, teacher_mask, T=1.0):
    ps = F.softmax(student_logits[student_mask] / T, dim=-1)    # (N_s, V_s)
    pt = F.softmax(teacher_logits[teacher_mask] / T, dim=-1)    # (N_t, V_t)
    n = min(ps.shape[0], pt.shape[0])                           # the position axis, unsolved
    ps, pt = ps[:n], pt[:n]
    ps = ps.sort(dim=-1, descending=True).values
    pt = pt.sort(dim=-1, descending=True).values
    K = max(ps.shape[-1], pt.shape[-1])
    ps = F.pad(ps, (0, K - ps.shape[-1]))                       # zero-pad the shorter vocabulary
    pt = F.pad(pt, (0, K - pt.shape[-1]))
    return (ps - pt).abs().sum(-1).mean()

What that listing proves is that the vocabulary axis costs two lines of code to solve. Everything hard about the method is in the fourth line, which solves nothing.

Watch out

uld_sorted_loss(s_logits, t_logits, s_mask, t_mask) takes the student first and the teacher second, with the two masks in the same order after them. The function is not symmetric in its arguments in the way its mathematics suggests, because the masks are not interchangeable: they describe different numbers of positions over different tokenizations. Swapping the pairs changes which side gets truncated when the position counts differ.

14.3.3 What sorting does not fix#

Look again at n = min(ps.shape[0], pt.shape[0]). The two models produced different numbers of supervised positions for the same string, because their tokenizers segment it differently. That line pairs the first supervised position of the student with the first of the teacher, the second with the second, and so on, and discards whatever the longer side has left over.

That is precisely the position-wise correspondence Chapter 7 proved does not exist. Sorting removed the vocabulary problem and left the position problem exactly where it was. Plain ULD is not an alignment method. It is a comparison method that is legal at any pair of positions you decide to compare, and the deciding is still on you.

This matters less than it sounds and more than nothing. It matters less because the two tokenizations of the same string drift in and out of phase instead of diverging monotonically: both tokenizers are forced to break at strong statistical seams like word edges and punctuation, so the -th position of one side and the -th of the other are usually looking at nearby, though not identical, text. It matters more than nothing because “nearby” is not “the same,” and the error is systematic and not random: the side with the more fertile tokenizer runs ahead, so the mismatch grows with position within a sequence and then partially resets at a shared boundary.

This is the gap that the library implementation closes, and it is the honest reason to prefer a library over the twelve lines above for real training. TRL’s experimental GOLD trainer, which is the path §14.7 covers, decodes both sides incrementally, groups spans whose visible text matches, and merges the probabilities of tokens that one tokenizer split by multiplying their conditional probabilities together. If one tokenizer spells a word in one piece and the other spells it in three, the chain rule turns those three conditionals into a single number for the whole word, and then the two whole-word distributions can be compared at a genuinely aligned span. The sorting trick handles the vocabulary; the span grouping handles the positions; you need both.

14.4 The five properties, with proofs#

Each of these is a property of the mathematical object, each has a one-line test, and each is decisive against a particular class of implementation error.

Property 1, identity. For any , .

Proof. Sorting is a deterministic function of its input, so both sides produce the same sorted vector, and every term of the sum is .

This catches sign errors, a missing absolute value, and any normalization applied to one side and not the other, all of which make the self-distance nonzero. It also catches an implementation that sorts one side ascending and the other descending, which is a real mistake and produces a large number where zero is required.

Property 2, permutation invariance. For any permutation of the vocabulary indices, , where is with its entries relabeled by .

Proof. The multiset of values in equals the multiset of values in , and sorting a multiset in descending order is a function of the multiset alone. So both sorted vectors are identical entry by entry.

Definition

Permutation invariance

The property that a loss is unchanged when the outcome labels of one or both distributions are relabeled by any bijection. For ULD this is the property that makes cross-tokenizer comparison meaningful at all: as far as this loss can see, a different tokenizer is nothing more than a shuffled and resized vocabulary.

Read that definition twice, because it contains both the justification and the indictment of the method. Permutation invariance is what licenses comparing two vocabularies with no shared index scheme. It is also, exactly and inseparably, a statement that the loss cannot see labels. §14.4.3 is about the second reading.

The test is perm = torch.randperm(V) followed by an assertion that the loss between a logit tensor and its permuted copy is under 1e-6. It catches any accidental dependence on token index: a gather that survived a refactor, a mask applied before the sort instead of after, a top-k selection that silently keeps positions instead of values.

Property 3, different vocabulary sizes work. For and $p^t \in \Delta^{V_t - 1}V_s \neq V_t$, the loss is finite and well defined, computed by padding the shorter sorted vector with zeros to length .

Proof. Zero-padding a sorted probability vector preserves both the descending order (since all entries are non-negative, appending zeros keeps the sequence non-increasing) and the total mass (since zeros add nothing). So the padded vector is the sorted representation of the same distribution embedded in a larger outcome space, and the sum over terms is a sum of finitely many finite quantities.

The lab’s case is against , asserting . This catches the shape hack: an implementation that “handles” the size mismatch by truncating both to passes a smoke test and quietly discards the longer model’s tail. On a real pair that truncation throws away 102,784 entries of the teacher’s vocabulary, which is most of it, and the discarded mass is exactly the part of the teacher’s behavior a cross-family student most needs to learn.

Property 4, bounded by 2. For all , .

Proof. By the triangle inequality applied entrywise,

where each sum is 1 because sorting and zero-padding both preserve total mass.

This catches double-counting: a loss that sums over both sides, or that forgets to divide by the number of positions, or that adds a symmetrized copy of itself. Any of those produce values above 2 on inputs where 2 is the ceiling, and the assertion fires immediately.

The bound is tight in the limit. A one-hot distribution against a uniform one over outcomes gives , which approaches 2 as the vocabulary grows. Near-disjoint distributions, where each model concentrates on values the other barely uses, sit near the ceiling.

Property 5, discrimination. A peaked distribution against a flat one scores far from zero.

Proof by computation. Take the lab’s case. One side is a near one-hot over (logits of everywhere and at one index, which softmaxes to within floating-point noise of exactly 1 at that index). The other is uniform over , so every entry is . Sorted, the first is and the second is . The L1 distance is $|1 - 0.01| + 99 \times |0 - 0.01| = 0.99 + 0.99 = 1.98$, comfortably above the asserted threshold of 1.5.

This is the property that catches the most dangerous class of bug, which is the loss that is always small. A function that returns something like the mean absolute difference of the sorted vectors rather than their sum, or that averages over the vocabulary axis, satisfies properties 1 through 4 perfectly and is nearly zero for every input, including inputs that are maximally different. It will train. It will produce a beautifully descending curve. It teaches nothing, because the gradient it supplies is uniformly tiny and carries almost no information about which direction the student should move. Property 5 is the assertion that the loss can see a difference that KL would see.

The battery, as one block. Everything in it is checkable by hand.

g = torch.Generator().manual_seed(0)
V_t, V_s = 1000, 700
zt = 4 * torch.randn(2, 12, V_t, generator=g)
zs = 4 * torch.randn(2, 12, V_s, generator=g)
m  = torch.ones(2, 12, dtype=torch.bool)

assert uld(zt, zt, m, m) < 1e-6                              # 1: identity
perm = torch.randperm(V_t, generator=g)
assert uld(zt, zt[..., perm], m, m) < 1e-6                   # 2: permutation invariance
assert 0 < float(uld(zs, zt, m, m)) < 2                      # 3 and 4: sizes differ, bound holds

one   = torch.full((1, 1, 100), -30.0); one[0, 0, 3]   = 30.0
other = torch.full((1, 1,  80), -30.0); other[0, 0, 7] = 30.0
m1 = torch.ones(1, 1, dtype=torch.bool)
assert uld(one, other, m1, m1) < 1e-4                        # corollary: peaks disagree, loss is 0
half = torch.zeros(1, 1, 80); half[0, 0, :2] = 15.0
assert 0.9 < float(uld(half, one, m1, m1)) <= 1.01           # corollary: the value is exactly 1
assert uld(torch.zeros(1, 1, 100), one, m1, m1) > 1.5        # 5: discrimination, the value is 1.98

What that block proves is that a loss you did not write is safe to put next to an optimizer. It takes under a second to run and it is the difference between a method and a hope.

14.4.1 Two corollaries, and what they cost#

The last three assertions are not extra tests. They are the two identities that tell you what you have actually bought.

A near one-hot over peaked at id 3, against a near one-hot over peaked at id 7, scores exactly 0. Both sorted vectors are , with the second zero-padded from 80 to 100. Every term of the sum vanishes. The two distributions disagree completely about what comes next, in vocabularies of different sizes, and the loss reports perfect agreement.

A one-hot against a 50/50 split scores exactly 1. Sorted, the one-hot is and the split is , so the L1 distance is , and every remaining term is zero. Half the bound, from a pair of distributions that differ only in how the mass is shaped, not in where it sits.

Both are exact, both are computable in your head, and together they draw the boundary of what ULD measures. It measures shape and only shape.

14.4.2 What ULD actually reports, stated as a theorem#

There is a sharper characterization available, and it is worth deriving because it converts an intuition into a bound you can reason with.

Suppose for a moment that the two vocabularies had the same size , so that a correspondence between them is at least conceivable. A relabeling is a permutation , and under that relabeling the ordinary L1 distance between the two distributions would be $\sum_i |p^s_i - p^t_{\sigma(i)}|$. Different guesses at the correspondence give different values. What does the sorted comparison compute?

The sorted L1 distance equals the minimum of the L1 distance over all relabelings.

Proof sketch. This is the assignment problem with cost on the line. Suppose a matching pairs with , so the larger of one side is matched to the smaller of the other. Swapping to pair with and with does not increase the total cost, since whenever and $c_2 \le c_1$. Repeatedly applying that exchange to any matching produces the monotone matching, which is exactly what sorting both sides and comparing entrywise computes.

So ULD does not report the distance between the two distributions. It reports the distance they would have if the two vocabularies happened to be aligned in the most favorable way possible. It is an optimistic lower bound on the disagreement, and it certifies a lower bound on the KL divergence you would have measured with an aligned vocabulary, though a quadratic one. Pinsker’s inequality reads , and the sorted distance is at most the unsorted one, so the chain runs $\ell_{\mathrm{ULD}} \le |P - Q|1 \le \sqrt{2\,D$ and rearranges to}}4

Read the exponent carefully, because it is easy to drop and the dropped version is much stronger than anything you are entitled to. A ULD of 0.5 certifies a KL of at least 0.125 nats, not 0.5. The square matters most exactly where the number is small, which is where you would be tempted to use it: since lives in , the squared bound is strictly weaker than the raw distance everywhere below 2. A large ULD certifies a large KL, quadratically, and a moderate ULD certifies very little.

That framing has a practical consequence. When ULD is large, the two models genuinely disagree, because no relabeling could rescue them, and the bound above puts a floor under how much. When ULD is small, you have learned that the two models have similar confidence profiles and nothing whatsoever about whether they agree.

14.4.3 The thing ULD gives up#

Property 2 and the first corollary say the same thing from two directions, and the statement they jointly make is blunt enough to deserve its own paragraph.

ULD cannot tell you which token the teacher preferred. It can only tell you the shape of the teacher’s preference. A teacher that is 92 percent confident in the correct continuation and a teacher that is 92 percent confident in a wrong one produce identical sorted vectors and therefore identical losses. The information Hinton’s whole argument rests on, the structure among the teacher’s probabilities on the wrong answers, is exactly the information carried by the ordering of the labels, and sorting destroys it while keeping the numbers.5 What survives is the confidence profile: peaked or flat, how fast the mass decays with rank, how heavy the tail is.

That is not nothing. A student that learns to be confident where the teacher is confident and uncertain where the teacher is uncertain has learned a real and useful calibration signal, and it has learned it from a teacher it could not otherwise have used at all. But you should hold the method’s claim at the size it actually is. Cross-tokenizer logit distillation is not same-tokenizer distillation performed across a boundary. It is a weaker signal, and the field’s expected ordering reflects that: text-level training recovers the least, sorted-logit matching recovers more, and sharing a tokenizer means nothing was destroyed in the first place.

Watch out

The characteristic failure of a ULD run is that the loss falls steadily while the student’s generations get worse. This is property 2 in the wild, not a bug. The student is learning to reproduce the teacher’s confidence shape while placing that shape on the wrong tokens, and the loss rewards it for doing so. The diagnostic is that your ULD curve and your top-1 agreement curve move in opposite directions. Never run a cross-tokenizer training job whose only monitored quantity is its own objective.

14.5 Vocabulary overlap as a diagnostic#

The blindness above is total only if the two vocabularies share nothing. They usually share a great deal, and measuring how much takes seconds and changes what you should do.

Definition

Vocabulary overlap

The set intersection of two tokenizers’ vocabularies, compared as token strings and not as ids. Both modern byte-level BPE tokenizers build tokens from bytes and spell each token as a string in the same convention, including the marker character that stands for a leading space, so two tokens correspond exactly when their strings are equal. The overlap is reported as a fraction, and the choice of denominator is part of the measurement.

Solutions 10 Exercise 1 measures this for the pair the lab trains. SmolLM2’s vocabulary has 49,152 entries, Qwen2.5’s has 151,936, and the byte-identical intersection contains 39,237 tokens. As a fraction of the student’s vocabulary that is 79.8 percent, a shade under four fifths. As a fraction of the teacher’s it is 25.8 percent, roughly a quarter.

Both numbers describe the same set. They are not interchangeable, and which one you quote decides what you conclude.

Field note

I computed the teacher-side fraction first, saw 26 percent, and wrote off the hybrid loss as not worth wiring up. That was the wrong denominator and it cost me an afternoon of not trying something that works.

The mechanism decides the denominator. A hybrid loss asks, at each position, how much of the student’s probability mass sits on tokens that the teacher’s vocabulary also spells, because the student’s distribution is what the loss is shaped around and the student’s mass is what gets routed to one branch or the other. The teacher’s 74 percent of unmatched entries are mostly tokens for languages and scripts this student never emits; they carry a negligible share of the mass in play. The student-side fraction, 79.8 percent, is the lever arm. The teacher-side fraction is a statement about how big Qwen’s vocabulary is.

The asymmetry is structural, not incidental. When a 49k vocabulary meets a 152k one built with the same byte-level convention, the smaller is mostly contained in the larger, because both were trained to cover common English text first and the larger one then kept going into scripts, languages, and long merges the smaller could not afford. That is why the sensible assertion to write is : containment should be substantial but never complete, and if either bound fails you have a bug in your string comparison, most likely in the handling of the leading-space marker.

14.5.1 What the overlap buys: the hybrid loss#

Once you have measured the overlap you can spend it. The hybrid objective splits each position’s comparison in two. Tokens that exist byte-identically in both vocabularies are compared directly by a divergence that respects identity, and only the unmatched remainder goes through ULD. The course library’s flag for this is uld_use_hybrid_loss, and the divergence used on the matched branch is the Jensen-Shannon divergence, a symmetric and bounded relative of KL whose square root is a proper metric.6 Bounded matters here: the matched branch and the ULD branch are summed, and an unbounded term would dominate the objective whenever one side put near-zero mass on something the other liked.

With 79.8 percent of the student’s vocabulary matched, hybrid training gets an identity-aware signal on the large majority of the mass and falls back to identity-blind matching only on the remainder. That should behave much closer to same-tokenizer distillation than pure ULD does, and it should make the failure signature in the warning above far less likely, because the JSD term pins the mass to the right tokens while the ULD term shapes the profile. The theory to test is that hybrid’s advantage grows with overlap, which means the overlap measurement is also the prediction: at 80 percent matched, a hybrid run that fails to beat a pure-ULD run is telling you about your implementation, most plausibly a matched-token lookup that mishandles the marker convention, rather than about the theory.

14.5.2 Bits per byte, the unit that crosses the boundary#

There is one more measurement that survives a change of tokenizer, and this chapter needs it twice. Chapter 7 derived it: total negative log probability on a fixed string, converted to bits and divided by the string’s length in UTF-8 bytes. The token decomposition cancels, so two models with incompatible vocabularies produce comparable numbers.

Lab 10’s solutions score two candidate teachers on the same three short factual English texts. Qwen2.5-0.5B-Instruct pays 0.46 bits per byte; GPT-2 pays 0.98, a ratio of about 2.13. Two different tokenizers, two different vocabulary sizes, two different token counts for identical text, and one number that separates them by a factor of two. Hold onto that pair of numbers. §14.10 uses them for something more important than teacher selection.

14.6 The other things you could do instead#

ULD is one point in a small space of cross-tokenizer methods, and it helps to see the alternatives by what each one assumes.

Shared-subvocabulary methods restrict the comparison to the tokens the two vocabularies have in common, renormalize both distributions over that shared set, and apply an ordinary divergence. The assumption is that the shared subvocabulary carries most of the mass, which the overlap measurement tests directly. The cost is that the renormalization changes what you are matching: you are now comparing two conditional distributions, each conditioned on the event “the next token is in the shared set,” and the models’ disagreement about how likely that event is has been divided out. The hybrid loss of §14.5.1 is the practical descendant of this idea, which keeps the shared-set comparison and handles the remainder instead of discarding it.

Alignment via minimum edit distance over token boundaries attacks the position axis directly. Decode both sides to text, compute an alignment between the two token sequences that minimizes an edit cost over their string spans, and compare only at positions the alignment matched, merging split tokens by the chain rule where one side subdivides the other. The assumption is that a good alignment exists and can be computed cheaply, which holds well for two tokenizers over the same language and degrades when one side’s segmentation is systematically finer. This is the family the library path implements, and it is strictly more work per step than sorting: an alignment is a dynamic program over the sequence, run per example, and it does not vectorize the way a sort does. What you get for that cost is the one thing sorting cannot give you, which is token identity at the matched spans.

Text-level fallback gives up on logits entirely. Generate a corpus with the teacher, train the student on it with ordinary cross-entropy, and let the tokenizer boundary become irrelevant because text is the only thing crossing it. This is Chapter 11’s sequence-level KD, in the form Kim and Rush introduced and the form current practice mostly uses.78 The assumption is that the teacher’s mode is a good enough summary of the teacher’s distribution, which is a crude approximation that works better than it has any right to, and which can be relaxed by minimizing a different divergence at the sequence level if the mode is a poor summary of your teacher.20 The cost is on the meter instead of in the loss: generating a corpus puts the large model in decode, which Chapter 9 priced as the expensive quadrant of the whole subject.

Which of these is the right comparison target matters for how you report your results. A cross-tokenizer logit method is not competing with same-tokenizer distillation, which is not available to you in this situation. It is competing with text-level distillation, which is, and which is simpler. If your GOLD run does not beat trace fine-tuning on the same teacher and the same budget, the method has not earned its complexity.

14.7 The library path, and a learning rate 400 times smaller than yours#

The implementation you should train with is TRL’s experimental GOLD trainer, which adds the span alignment, the chain-rule merging, and the hybrid branch to the twelve-line function above. Reaching for a library is the right instinct here. Reading the library’s actual defaults before you use it is the part people skip.

The habit is to introspect the config class in code instead of trusting documentation or memory: enumerate its dataclass fields, assert that the fields your plan depends on exist, and print the defaults you care about. This costs one cell and it catches version drift, which for an experimental module is not hypothetical.

One default deserves its own paragraph. GOLD ships with learning_rate = 1e-7. The rate this course uses everywhere else is 3e-5. Those differ by a factor of 300, which the lab’s text rounds to “four hundred times”; either way you are two and a half orders of magnitude below the number you would have typed from memory. A config copied from another trainer with the usual learning rate would run without raising anything, log a plausible-looking curve, and train a different experiment than the one the paper describes.

Definition

Silent divergence

A configuration error that produces no error: the run completes, the metrics are finite, the artifacts save, and the experiment executed is not the experiment intended. Detected only by comparison against the source recipe, which is why the source recipe’s defaults get asserted rather than remembered.

Why would a cross-tokenizer objective want a learning rate that small? Two reasons, and they compound.

The first is the gradient’s scale. ULD is an L1 distance, so its derivative with respect to each sorted probability is almost everywhere, independent of how far apart the two values are. Contrast a KL term, whose gradient shrinks as the distributions approach each other. An L1 objective supplies a gradient of constant magnitude right up to the point where it flips sign, so the optimizer gets a push of the same size when the student is nearly right as when it is badly wrong, and nothing about the loss itself damps the step near the optimum. The sum over entries then adds them all together. The natural fix for a loss that does not taper is a step size that is small enough to let it settle.

The second is that the objective is measuring something adjacent to what you want instead of what you want. §14.4.2 established that ULD reports the best-case disagreement under an unknown relabeling. A student pushed hard against an optimistic proxy will find the ways of satisfying the proxy that do not satisfy the goal, and the failure signature in §14.4.3 is precisely what that looks like. A small learning rate keeps the student near its initialization, where its own tokenizer-appropriate behavior still holds, and lets the cross-tokenizer signal act as a nudge rather than a rewrite. Read that way, the tiny default is not timidity; it is an admission about the signal’s quality, encoded in a config field.

Watch out

The second reason a GOLD run barely moves has nothing to do with the learning rate, and I mention it because the two are indistinguishable from the loss curve. GOLD buffers a full optimizer window of batches, so a final batch of a different size stalls the run with a warning rather than an error. Set dataloader_drop_last=True. Both of these failures look like “the loss is flat,” and you will check the interesting one first.

14.8 What a cross-tokenizer step costs in memory#

There is a failure signature specific to this chapter, and it is worth being able to predict rather than diagnose: a cross-family pair runs out of memory at a batch size and sequence length where the same-family pair was comfortable.

The arithmetic is direct. A logit tensor has shape (batch, sequence, vocabulary). At batch 4 and sequence 384, the teacher side with Qwen’s 151,936-entry vocabulary holds

in fp32, and the student side with SmolLM2’s 49,152 entries holds about 0.30 GB. Those are the tensors the forward passes produce. The loss then makes more of them: a softmax output is a new tensor of the same shape, and a sort output is another. On the student side, autograd retains the intermediates it needs for the backward pass, so the 0.30 GB appears roughly three times over. On the teacher side, computed under no_grad, it appears about twice. Peak occupancy for one step’s loss computation is therefore in the neighborhood of 2.8 GB, against under 1 GB for a same-family pair at identical shapes where both sides are 49,152 wide.

The raw ratio understates it twice over. Chapter 2’s policy is to compute losses in fp32 for numerical reasons, which is what makes every one of those tensors 4 bytes per entry instead of 2. And the peak arrives all at once, at the moment both sides are resident and sorted; it is not spread across the step.

The fixes are ordinary once you have the arithmetic. Compute the loss in chunks over the position axis, so that only a slice of the vocabulary tensors is live at a time. Shorten sequences. Reduce the microbatch and increase gradient accumulation, which trades nothing except a small amount of throughput. What you should not do is drop the loss to bf16 to make the number fit, because the sorted tail is exactly where bf16’s coarse spacing near small values does the most damage, and Chapter 2 covered what happens next.

14.9 A cheaper ULD, and what it measures#

Sorting a 151,936-entry vector at every supervised position of every batch of every step is the per-step bottleneck of the method, and it is worth asking whether the whole sort is necessary.

Solutions 10 Exercise 2 builds the variant. For each side, take the largest cap entries by a partial selection rather than a full ordering, keep the prefix whose preceding cumulative mass is below , zero the rest, and append one explicit tail bucket holding whatever mass was left:

The tail bucket is what keeps the variant honest. Both compared vectors still sum to 1, so the quantity is still an L1 distance between genuine probability distributions and property 4 still holds. This is the same reasoning as Chapter 10’s tail-bucket estimator against naive renormalization: account for the missing mass explicitly instead of pretending it was never there.

The approximation error is derivable, not empirical. The truncated comparison can only miss L1 contributions from entries in the discarded tail, and each side’s tail holds at most plus whatever the cap failed to reach, so

at . The solution asserts 0.05 to leave room for the cap residual, and both the synthetic cross-size case and the real-logit case land far inside it. Set that against the measured cross-family baseline of 0.32 and the approximation is an order of magnitude below the quantity being optimized.

What the exercise actually measures is more interesting than the speedup, because the speedup has two numbers and they disagree. On the real instruct pair, 0.99 of the probability mass sits in well under a hundred entries per position, so the variant orders a 512-wide selection instead of a 49,152-wide sort, several hundred times fewer entries touched. Wall clock improves by a few times, not by hundreds. The gap has a plain cause: both paths still compute the softmax over the full vocabulary, so only the sorting share of the work shrank. In a training loop where the softmax is already paid for by the loss itself, the sorting share is precisely what ULD adds on top, which makes entries-touched the number that prices the method and wall-clock-on-a-microbenchmark the number that misleads about it.

The failure mode to watch is a genuinely flat teacher, from a high temperature or a creative domain, where 0.99 of the mass spreads past the cap. The assertion that the kept count stays under the cap is the tripwire, and raising the cap is the fix.

14.10 The wrong-teacher control#

Everything in this chapter so far has been about building an instrument. This section is about whether the instrument points at anything, and it is the methodological center of the chapter.

Definition

Wrong-teacher control

A negative control for a distillation pipeline: rerun the measurement with a teacher that should not work, and confirm the reported number gets worse. If the metric cannot see a teacher you know to be worse, it is not measuring teacher transfer, whatever else it is measuring.

The logic is the same as any negative control in an experimental science. A measurement that responds only in the direction you hoped for is not evidence, because a broken measurement also responds that way. You need at least one input whose correct answer is “worse,” and you need the instrument to say so.

Solutions 10 Exercise 4 runs it in the starkest available form. The real teacher is Qwen2.5-0.5B-Instruct. The wrong teacher is GPT-2, the 2019 124M model, worse by any generation-quality standard anyone would accept. Two instruments look at the downgrade.

The first is ULD itself, which is what the training pipeline watches. Both teachers score against the student on the same order. Whatever gap appears is unattributable: it reflects the fact that GPT-2 is older, flatter, and differently calibrated, which is a difference in confidence shape, not a difference in correctness.9 Property 2 guarantees this in advance instead of explaining it after the fact. If GPT-2 were confidently wrong in exactly the positions where Qwen is confidently right, the ULD column could not move at all.

The second is bits per byte on a fixed gold set, and it separates them without ambiguity: 0.46 for Qwen against 0.98 for GPT-2, a factor of 2.13. One forward pass per teacher, no training, no alignment machinery, and a number no reasonable evaluation could misread.

So the answer to “does the pipeline detect a downgraded teacher” has two halves. Yes, but only because the evaluation includes a probe denominated in correctness and not in distributional shape. A pipeline watching only its own training loss could have a drastically worse teacher swapped in and keep reporting plausible numbers all the way to the final checkpoint.

The operational rule that follows is short enough to memorize. Every cross-tokenizer experiment carries at least one teacher-quality probe that is independent of the alignment trick. Bits per byte on a small fixed gold set is the cheapest such probe there is.

And the general form of the rule is what carries into the rest of the chapter, and into the rest of your work. When you add a loss term, find the input on which that term should get worse, and check that it does. A loss you have only ever seen decrease is a loss you have not yet tested.

14.11 The second half: matching representations#

The other escape route ignores the vocabulary entirely.

A transformer’s layers pass vectors to each other. Those vectors are not indexed by token id, do not sum to 1, and have no vocabulary attached. At layer and position , the model holds a hidden state , where is the model’s hidden size, and has nothing to do with . If your student and teacher share a tokenizer, so that position means the same thing to both, then you can put a loss directly on those vectors and never mention a token.

Definition

Hidden-state matching

Adding a loss term that pushes a student’s internal activations toward a teacher’s at chosen positions and chosen layers, rather than (or in addition to) matching outputs. Also called feature-based distillation. Because hidden states carry no vocabulary, the comparison sidesteps the output-space alignment problem, at the cost of introducing a representation-space alignment problem in its place.

That last clause is the honest part. Hidden states dodge the vocabulary problem and inherit a different one: the two models’ hidden sizes differ, and even where they do not, there is no reason two independently trained networks would use the same coordinates for the same information. The course’s pair makes this concrete. SmolLM2-135M has a hidden size of 576 and 30 layers; SmolLM2-360M has a hidden size of 960 and 32 layers. A mean squared error between a 576-vector and a 960-vector is not a defined quantity, and even after you fix the dimension the two models’ bases are unrelated.

The fix for both problems is the same object.

Definition

Projector

A small trainable linear map inserted between the student’s hidden states and the teacher’s so that the two can be compared. It absorbs both the dimension mismatch and the arbitrary difference in basis between two independently trained models. In this course the projector maps the student’s hidden size into the teacher’s, so the teacher’s state is the fixed target and the trainable parameters sit on the student’s side of the loss.

The direction is a choice worth making deliberately. Mapping student to teacher makes the teacher’s representation the target, which is what you want conceptually and what keeps the teacher’s side of the loss constant and cacheable. Mapping teacher to student would put the trainable map on the target side, so the loss could be reduced by moving the target, which is a bad property for a supervision signal. Either direction is defensible for a feasibility check, because linear predictability is close to symmetric in practice, but for training use the student-to-teacher direction.

A projector is cheap. A single Linear(576, 960) is about 553 thousand parameters, well under a percent of even the 135M student, and it is discarded after training. What is not cheap is finding out, after a full training budget, that no such map was ever going to help.

14.12 The ridge check: does a projector exist?#

Chapter 13 introduced this test as a go/no-go gate for a pruned student’s recovery. Here it earns a fuller treatment, because it is the cheapest instance of a habit that should govern the whole second half of the chapter: run the closed-form version of your question before you run the learned version.

The learned question is “can gradient descent find a linear map from the student’s hidden states to the teacher’s that reduces the mean squared error enough to be worth a loss term?” The closed-form question is “what is the best linear map, and how good is it?” The second question has an exact answer that costs one matrix solve.

Let hold the student’s hidden states at supervised positions and the teacher’s at the same positions. Append a column of ones to give the map an intercept, . Ridge regression, which is least squares with a small penalty that keeps the solve numerically stable, gives

and the two numbers that matter are the resulting error and the error of the trivial alternative:

where is the teacher’s mean hidden state, broadcast, and is the Frobenius norm, meaning the square root of the sum of squared entries. The mean predictor is the baseline that ignores the student completely. The gate the course uses is .

The logic is a proof by contradiction against your own plan. If the best possible linear map cannot beat “always guess the teacher’s average,” then there is no linear structure connecting the two models’ representations at these layers, a trained linear projector cannot find structure that does not exist, and the representation-matching arm of your experiment is theater. You would still observe a loss term going down, because a trainable map can always reduce its own MSE somewhat, and you would conclude nothing.

The contrast when the check passes is not subtle. Chapter 13’s projector go/no-go, run on a pruned 22-layer patient against the intact model it was cut from, reports a ridge projector MSE of 0.026 against a mean-predictor MSE of 33.4, better by a factor of about 1,285, roughly three orders of magnitude. That is what “yes, a projector can exist” looks like as a number. A pair with no usable linear relationship would sit at a ratio near 1, and the gate at 0.5 is deliberately far from the measured passing value so that it fires only on genuine absence of structure and not on noise.

2026-08-01T07:33:35.755937 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ ridge projector mean predictor 0.01 0.1 1 10 100 held-out mean squared error go / no-go gate, 0.5 x mean = 16.7 1,285x 22-layer pruned patient against the intact model it was cut from; hidden width 576, lambda 1e-3, four probe rows, masked positions only clears the gate by three orders of magnitude, not by a margin you would have to squint at 0.026 33.4
Figure 14.3 Ridge projector error against the mean-predictor baseline on a log axis, showing that the go/no-go check separates by three orders of magnitude rather than by a margin you would have to squint at.

Two implementation details separate a check from a ritual.

Score it on held-out positions. A linear map from a 576-dimensional input has 577 free parameters per output dimension once the bias is counted. Fit it on fewer than 577 positions and it can reproduce its fitting data exactly while having learned nothing, which makes every candidate look equally good. The lab’s own first version of this check used six short sentences and was under-powered for exactly this reason, which Solutions 10 Exercise 3 catches and repairs by using real evaluation rows and splitting the masked positions 60 percent to fit and 40 percent to score.

Compare ratios, not raw errors. Hidden-state magnitudes vary a lot across layers, so raw MSE would rank the layer with the smallest activations first instead of the layer that is most predictable. The scale-free quantity is the ratio,

which asks what fraction of layer ’s variance the linear map fails to explain. Lower is better, 1.0 means the map is worthless, and the number is comparable across layers of different scale.

def projector_feasibility(H_s, H_t, lam=1.0, fit_frac=0.6, seed=0):
    n = H_s.shape[0]
    idx = torch.randperm(n, generator=torch.Generator().manual_seed(seed))
    fit, score = idx[: int(fit_frac * n)], idx[int(fit_frac * n) :]
    X = torch.cat([H_s, torch.ones(n, 1)], dim=1)                  # bias column
    A = X[fit].T @ X[fit] + lam * torch.eye(X.shape[1])
    W = torch.linalg.lstsq(A, X[fit].T @ H_t[fit]).solution        # closed form, no gradients
    mse_proj = ((X[score] @ W - H_t[score]) ** 2).mean()
    mse_mean = ((H_t[fit].mean(0) - H_t[score]) ** 2).mean()       # the trivial baseline
    return float(mse_proj / mse_mean)                              # < 1 useful, < 0.5 is the gate

What that listing proves is that a question people answer with a training run has a closed-form answer that costs one solve, and that the closed-form answer arrives before you have committed any budget. It also returns a value you can use as an initialization: is a perfectly good starting point for the trainable projector, which is what Chapter 13’s gated arm does.

14.13 Layer pairing#

A projector connects one student layer to one teacher layer. Deciding which pair is a design choice, and until you measure it, it is a guess.

Definition

Layer pairing

The assignment of teacher layers to student layers for a representation-matching loss. TinyBERT’s convention is a uniform proportional map, so that student layer supervises from teacher layer , but the convention is a heuristic and the right pairing is a property of the specific pair of models.

The plausible-sounding intuition is that a student’s middle layers should learn from the teacher’s later, more refined features, since the teacher is the better model and its late layers are where its best abstractions live. Solutions 10 Exercise 3 tests that intuition with the ridge probe rather than with training runs, and refutes it.

The setup: student layer 15, the depth midpoint of the 135M’s 30 layers, against three candidate teacher layers from the 360M’s 32: layer 5 (early), layer 16 (the teacher’s own midpoint), and layer 27 (late). Six evaluation rows, 160 tokens each, masked completion positions only, held-out scored with the 60/40 split, compared by the scale-free ratio. The held-out unexplained fractions:

Table 14.1 Which teacher layer can linearly predict student layer 15, measured by the fraction of held-out variance a ridge projector fails to explain. Lower is better.

Teacher layer Position in a 32-layer stack Held-out unexplained fraction
5 early 0.88
16 midpoint 0.50
27 late 0.84

2026-08-01T07:33:36.560399 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 5 10 16 20 27 31 teacher layer index (SmolLM2-360M, 32 layers) 0.00 0.25 0.50 0.75 1.00 held-out unexplained fraction (lower is better) 0.88 L5 early 0.50 L16 midpoint 0.84 L27 late mean predictor: the map is worthless teacher depth midpoint the midpoint explains roughly twice the variance either extreme does, and the two extremes are nearly tied with each other student layer 15 of 30 (SmolLM2-135M) filled markers are the three measured pairings; the dashed line only joins them, and is not a claim about the layers in between
Figure 14.4 The layer-pair sweep, showing that the middle of the teacher predicts the middle of the student roughly twice as well as either extreme does.

Every pairing beats the mean predictor, so a linear relationship exists everywhere: two models of the same family have broadly related representations at every depth. But the ranking is decisive. The midpoint pairing explains roughly twice the variance either extreme does, and the two extremes are nearly tied with each other, at 0.88 and 0.84. The late teacher layer, the one the intuition recommends, is about as unrelated to the student’s middle as the embedding-adjacent early layer is.

The reading is that depth position, not refinement, is what makes two layers comparable. A network’s representation at a given fraction of its depth is doing a particular kind of work, and the same fraction of depth in a related network is doing similar work. TinyBERT’s uniform proportional map survives the test at the midpoint, which is a mild vindication of a heuristic that was designed for encoders and transplanted to decoders without much argument.10

The lesson underneath the numbers is the price asymmetry, and it generalizes past this question. Sweeping three ridge fits cost seconds and two forward passes total, because one forward pass with output_hidden_states=True yields every layer’s states at once. Sweeping three training runs costs three budgets. Any time you can convert a hyperparameter search into a closed-form probe that predicts the same ordering, you should, and the way to check that it does predict the same ordering is to run the full sweep once, early, and then never again.

14.14 The combined objective, and the shortcut it invites#

The representation term does not replace the logit term. It joins it:

where and are the paired student and teacher layers, is whatever output-matching loss you were already using, and is a schedule rather than a constant. The course anneals it from 1.0 to 0 over the first third of training, ramping down gradually instead of switching off. The slogan for the schedule is that features guide early and logits decide late: the representation term gives the student a scaffold while its outputs are still nearly random, and then withdraws so that the objective you actually care about owns the endgame. At the scale of these models, feature matching buys speed more than it buys ceiling; the rep-matched arm should lead at one-third of the budget with the gap narrowing to small-but-real by the end.

Watch out

If your representation loss dives while the output loss stalls, the projector found a shortcut, and the usual one is norm matching. Hidden-state vectors grow in overall magnitude with depth, so a linear map can score well on MSE by reproducing that growth without reproducing any of the vectors’ content. The fix is to apply LayerNorm to both sides before the MSE, or to match post-norm states, which removes magnitude from the comparison and leaves only direction. Chapter 13 has the same failure in its own setting, and it is the single most common way a feature-matching arm produces a healthy-looking curve and no benefit.

Notice that this warning and the one in §14.4.3 are the same warning at two levels of the network. In both cases the added loss has a degenerate solution that satisfies it without transferring anything, and in both cases the diagnosis is that the new term and the quantity you care about diverge. The general habit is to plot the added term against an outcome metric, never alone. That habit is worth keeping even when nothing is degenerate, because a falling distillation loss is weak evidence that the student is reproducing the teacher’s function under the best of circumstances, and these are not the best of circumstances.21

14.15 The lineage, and where it thins out#

Representation matching is old, and knowing where it comes from tells you which of its claims have been tested.

FitNets introduced the idea in 2014 under the name “hints”: pick an intermediate layer of the teacher, pick one of the student, add a regressor between them, and pretrain the student’s lower half to match before training the whole thing on outputs.11 The regressor is the projector, the hint is the target, and the staged schedule is the ancestor of the annealed weight above.

Attention transfer changed the target from the activations to the attention maps, on the argument that where a network looks is a more transferable summary than what it computes, and that attention maps can be compared across architectures with different widths because they are indexed by position instead of by channel.12 For transformer language models this is the most directly applicable of the vision-era ideas, because attention maps are already a first-class object in the architecture and not something you have to construct.

TinyBERT assembled the full transformer recipe: embedding-layer loss, hidden-state loss with a learned projection, attention-matrix loss, and prediction-layer loss, with the uniform layer map that §14.13 tested.13 It also established the two-stage structure, general distillation followed by task distillation, which is where most later recipes get their shape. DistilBERT sits alongside it as the counterexample that keeps the field honest: a much simpler recipe, initializing the student from alternating teacher layers and training on outputs plus a cosine embedding term, that gets most of the benefit for a fraction of the machinery.14

Then there is the relation-based branch. Relational knowledge distillation matches the relationships between examples instead of the examples themselves, using distance and angle structures over batches, on the argument that a student should preserve the teacher’s geometry even if it places things elsewhere.15 Contrastive representation distillation formulates the same instinct as maximizing a lower bound on mutual information between teacher and student representations, using a contrastive objective over positive and negative pairs.16

I want to be direct about the state of that last branch for generative language models: it is far less developed there than in vision, and the reasons are structural, not accidental. Both methods are built around a batch of independent examples with a clean notion of “the representation of example ,” which is what an image classifier’s penultimate layer gives you. A language model produces one representation per position per sequence, tens of thousands per batch, related to each other by the sequence structure, and the question of which of those the relational structure should be computed over does not have an obvious answer. The negatives that a contrastive objective needs are also less well defined when the units are positions in overlapping contexts. The surveys present response-based, feature-based, and relation-based as three parallel branches, which is a fair description of the vision literature and an overstatement of the language one.1718 If you reach for a relational objective on a generative model, you are doing research and not applying a recipe, and you should budget accordingly.

14.16 A decision guide#

There are four situations here and four answers. The question that sorts them is not “which method is best” but “what do you have and what does the boundary cost you.”

Table 14.2 What to reach for when teacher and student do not share a tokenizer.

Situation Reach for What it assumes What it costs
Different tokenizers, you have both models’ logits, and the vocabularies overlap substantially Hybrid ULD through the library path, with the overlap measured first That confidence shape is a useful signal and that most of the student’s mass sits on matched tokens A weaker signal than same-tokenizer KD, a tiny learning rate, and two logit tensors resident at once
Different tokenizers, low overlap, or teacher accessible only as text Text-level distillation on teacher-generated traces That the teacher’s mode summarizes its distribution well enough The teacher runs in decode, which is the expensive quadrant
Same tokenizer, and you want the student to converge faster than outputs alone allow Representation matching, gated by the ridge check, paired at the depth midpoints, annealed That a linear map between the two models’ hidden states exists, which you verify rather than assume A projector, a schedule, and one more way to fail silently
You control the student’s design and the teacher is fixed Change the student’s tokenizer That you can afford to retrain or re-initialize the student Whatever the student’s existing weights were worth

The last row is the one people skip, and it is often the right answer. The tokenizer-boundary tax is real: the gap in agreement points between the best cross-family recipe and the best same-family recipe is the number you quote when someone proposes distilling from the shiniest available teacher regardless of family. If you are choosing a student architecture at the start of a project and you already know which teacher you want, adopting the teacher’s tokenizer costs you nothing at that moment and saves you this entire chapter. If the student already exists, you can often get the same effect by building the student out of the teacher instead, which inherits the tokenizer by construction and is what Chapter 13 is about.19

What should decide the first two rows is the overlap measurement from §14.5 and a matched-budget comparison against trace fine-tuning, and not a preference. Both are cheap. Neither requires you to have chosen yet.

14.17 Where this lands in the labs#

Lab 10 Part A is the chapter’s spine, and it runs anywhere, on any machine, without training anything: the five properties are asserted against kd_pipeline.uld_sorted_loss in under a second, and the real-pair measurement brackets the cross-family number between each model’s self-distance (which must be exactly zero) and the same-family pair’s, so the number has context before any training exists to change it. Part A·3 introspects GOLD’s config in code and asserts the tiny default learning rate, which is the version of “read the library” that fails loudly when TRL moves. The solutions carry the four measurements this chapter quotes: the vocabulary overlap and its denominator, the truncated top-p variant with its derived error bound, the layer-pair sweep, and the wrong-teacher control. The thing the lab does that the chapter cannot is put your own hands on the property battery before the training gate opens, which is the only way the habit becomes yours rather than mine.

14.18 Exercises#

  1. Prove or refute the claim that for probability vectors over the same vocabulary , the sorted L1 distance is a metric on the space of distributions. Check each axiom separately (non-negativity, identity of indiscernibles, symmetry, triangle inequality) and state precisely which one fails and on what pair of distributions. Then say what the failure means for using ULD as a training objective, and whether it means anything at all.

  2. Prove that for any two probability vectors over the same vocabulary, the sorted L1 distance is at most the unsorted L1 distance, with equality if and only if the two vectors are already sorted in a common order. Use the exchange argument from §14.4.2. Then state, in one sentence, what this implies about the sign of the error you make by reporting ULD as though it were a measure of disagreement.

  3. You are handed a cross-tokenizer training run whose ULD loss fell from 0.32 to 0.19 over 800 steps, whose top-1 agreement against the teacher on matched tokens fell from 0.41 to 0.33, and whose generated samples are grammatical but drifting off-topic. Give the most likely diagnosis, name the property of the loss that predicts it, and give two changes to the configuration that would test your diagnosis. Say which of the two you would run first and why.

  4. Before reading any numbers: predict the ordering of the three teacher-layer candidates in §14.13, and give your reason. Then predict what would happen to the ordering if the student layer moved from 15 to 3. State what measurement would settle your second prediction and roughly what it would cost.

  5. A colleague reports that their representation-matching arm reduced the feature loss by 90 percent and improved nothing else. Using §14.14, name the shortcut, then design a single diagnostic measurement, using only the hidden states themselves and no training, that would confirm or rule it out. Say what the measurement would show under each hypothesis.

  6. You have a teacher with a 200k vocabulary and a student with a 32k one, and the byte-identical overlap is 14 percent of the student’s vocabulary. Using §14.5 and §14.16, argue for one of the four rows of Table 14.2. Then say what single additional measurement would most change your answer, and in which direction.

  7. Design a wrong-teacher control for the representation-matching half of this chapter, in the way §14.10 designs one for the logit half. Specify the wrong teacher, the metric, the direction the metric must move, and the threshold at which you would declare the instrument blind. Say what your control would fail to catch.



  1. Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big: Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737 The 49,152-entry vocabulary is the one the course’s student models use throughout. 

  2. Qwen Team, “Qwen2.5 Technical Report,” arXiv:2412.15115 (2024). https://arxiv.org/abs/2412.15115 

  3. Nicolas Boizard, Kevin El Haddad, Céline Hudelot, and Pierre Colombo, “Towards Cross-Tokenizer Distillation: the Universal Logit Distillation Loss for LLMs,” Transactions on Machine Learning Research (January 2025); preprint arXiv:2402.12030 (2024). https://arxiv.org/abs/2402.12030 

  4. The optimal constant in is due independently to Imre Csiszár, “Information-type measures of difference of probability distributions and indirect observations,” Studia Scientiarum Mathematicarum Hungarica 2 (1967): 299-318, and Solomon Kullback, “A lower bound for discrimination information in terms of variation,” IEEE Transactions on Information Theory 13, no. 1 (1967): 126-127, DOI: 10.1109/TIT.1967.1053968. The inequality is named for Pinsker, whose 1964 original had a weaker constant. Chapter 3 derives it. 

  5. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015). https://arxiv.org/abs/1503.02531 The argument that the teacher’s relative probabilities among wrong answers carry a learned similarity structure is the one sorting discards. 

  6. Dominik M. Endres and Johannes E. Schindelin, “A new metric for probability distributions,” IEEE Transactions on Information Theory 49, no. 7 (2003): 1858-1860, DOI: 10.1109/TIT.2003.813506; and Ferdinand Österreicher and Igor Vajda, “A new class of metric divergences on probability spaces and its applicability in statistics,” Annals of the Institute of Statistical Mathematics 55, no. 3 (2003): 639-653, DOI: 10.1007/BF02517812. The square root of the Jensen-Shannon divergence satisfies the triangle inequality; the divergence itself does not. Chapter 3 covers the family. 

  7. Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947 

  8. DeepSeek-AI, “DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning,” Nature 645 (2025): 633-638, https://doi.org/10.1038/s41586-025-09422-z; preprint arXiv:2501.12948. The distilled model series is supervised fine-tuning on teacher-generated traces, with no logits involved and therefore no tokenizer constraint on the student. 

  9. On confidence shape as a model-specific property that varies with architecture and training era, see Chuan Guo, Geoff Pleiss, Yu Sun, and Kilian Q. Weinberger, “On Calibration of Modern Neural Networks,” arXiv:1706.04599 (2017), ICML 2017. https://arxiv.org/abs/1706.04599 Chapter 16 covers calibration measurement properly. 

  10. Xiaoqi Jiao et al., “TinyBERT: Distilling BERT for Natural Language Understanding,” arXiv:1909.10351 (2019), Findings of EMNLP 2020. https://arxiv.org/abs/1909.10351 The uniform layer-mapping function is §3.1 of that paper. 

  11. Adriana Romero et al., “FitNets: Hints for Thin Deep Nets,” arXiv:1412.6550 (2014), ICLR 2015. https://arxiv.org/abs/1412.6550 

  12. Sergey Zagoruyko and Nikos Komodakis, “Paying More Attention to Attention: Improving the Performance of Convolutional Neural Networks via Attention Transfer,” arXiv:1612.03928 (2016), ICLR 2017. https://arxiv.org/abs/1612.03928 

  13. Jiao et al., “TinyBERT,” §3. 

  14. Victor Sanh, Lysandre Debut, Julien Chaumond, and Thomas Wolf, “DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter,” arXiv:1910.01108 (2019), 5th Workshop on Energy Efficient Machine Learning and Cognitive Computing, NeurIPS 2019. https://arxiv.org/abs/1910.01108 

  15. Wonpyo Park, Dongju Kim, Yan Lu, and Minsu Cho, “Relational Knowledge Distillation,” arXiv:1904.05068 (2019), CVPR 2019. https://arxiv.org/abs/1904.05068 

  16. Yonglong Tian, Dilip Krishnan, and Phillip Isola, “Contrastive Representation Distillation,” arXiv:1910.10699 (2019), ICLR 2020. https://arxiv.org/abs/1910.10699 

  17. Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819. https://arxiv.org/abs/2006.05525 The three-branch taxonomy is that survey’s organizing scheme and is drawn almost entirely from vision results. 

  18. Xiaohan Xu et al., “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024). https://arxiv.org/abs/2402.13116 The language-model-specific survey, where the relation-based branch occupies markedly less space than in the vision surveys. 

  19. Saurav Muralidharan et al., “Compact Language Models via Pruning and Knowledge Distillation,” arXiv:2407.14679 (2024), NeurIPS 2024. https://arxiv.org/abs/2407.14679 A student pruned out of the teacher shares the teacher’s tokenizer by construction, which removes this chapter’s problem rather than solving it. 

  20. For the alternative of matching at the sequence level with a divergence other than the forward KL, see Yuqiao Wen, Zichao Li, Wenyu Du, and Lili Mou, “f-Divergence Minimization for Sequence-Level Knowledge Distillation,” arXiv:2307.15190 (2023), ACL 2023. https://arxiv.org/abs/2307.15190 The choice of divergence is orthogonal to the choice of what to align, and Chapter 6 covers it. 

  21. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945 The general result that a falling distillation loss is weak evidence of function matching applies with more force here, where the loss is a proxy for a proxy. 

Part V · Systems, Judgment, and Research

15

Serving a Teacher and Measuring Your Machine

Every number in Chapter 9 that touched wall clock came from one of two places. Either it was arithmetic, in which case it is as good as its inputs, or it was a benchmark someone else ran on a machine like mine. The prefill and decode figures the course has carried since its README, roughly 2,000 tokens per second of prefill against 49.7 of decode for a 20-billion-parameter model in MXFP4, a 4-bit block-scaled format, are in the second category. They are not mine.

That distinction sounds pedantic until you try to plan a run with them. A borrowed throughput number encodes a model, a quantization format, a serving stack, a kernel version, a batch size, a context length, a client, and a machine that was doing nothing else at the time. Change one of those and the number moves, sometimes by a factor of three. When you use it to decide whether a corpus takes eight hours or thirty, you are not estimating. You are guessing, with a number that looks like an estimate because it has a decimal point in it.

This chapter is about the difference between those two activities. The first half is the topology that makes a large teacher usable at all, which most of the current method space quietly assumes you have:21 a separate serving process, the two techniques that make one efficient, the scoring path across the wire, and the quantization formats that decide how many bytes each parameter costs. The second half, which is the part I care about more, is the measurement discipline: how to build your own throughput curves, how to read one that looks wrong, and how to invert a roofline and find out how many bytes your machine actually moved. That inversion, run on a single published decode number, turns out to be enough to determine that a model you have never inspected is a sparse mixture of experts.

The end state is a file. Lab 08 calls it machine_profile.json, and every lab after it sizes against that file instead of quoting the README. The course’s own claims stop being authority and become your null hypothesis, which is the correct final relationship between a course and a practitioner who has done the work.

15.1 Why the teacher moves out#

Every configuration in this book so far has put the teacher and the student in one process. One Python interpreter, one CUDA context, one allocator, both models resident. That arrangement is the right default and it stops working at a specific point, which is worth naming precisely.

It stops working when the two models stop being compatible as a single sizing problem. A 20-to-32-billion-parameter teacher wants a large memory reservation, a scheduler that keeps many sequences in flight, and a lifecycle measured in hours. A training student wants gradients, optimizer moments, and activations, all of which grow and shrink on a per-step rhythm, and it wants to be restartable from a checkpoint without disturbing anything else. Put them in one process and every decision is a compromise: the allocator fragments between two very different allocation patterns, the teacher’s serving batch competes with the student’s microbatch for the same peak, and a wedged teacher takes the optimizer state with it when you kill the process.

Definition

Teacher server

A separate process that holds the teacher and answers scoring requests over a network interface, while the student trains in its own process and sends the tokens it wants scored across the connection. The two processes are sized, quantized, restarted, and debugged independently.

Separating the two processes buys three things. Each side is sized on its own terms: the serving process gets a memory fraction and stays inside it, the training process gets what remains, and §15.10 turns that split into a number you can compute instead of tune. Each side is quantized on its own terms: the teacher can be served at half a byte per parameter while the student trains in bf16 with fp32 optimizer moments, which is the difference between a 32B teacher fitting alongside a training run and not fitting at all. Each side restarts on its own: a wedged server is a server you restart, your optimizer state survives, and a real run becomes three independently restartable phases (bring up the server, measure it, train against it) instead of one fragile one.

The topology is not free, and I want the costs stated before the benefits get any more airtime.

A network hop per scoring call. Every request pays connection handling, HTTP framing, JSON serialization on both ends, and scheduler queueing before a single token gets scored. On a local socket this is milliseconds, not microseconds, and §15.11 shows that at small batch sizes it can be the majority of your step time.

Orchestration complexity. A startup order, a health check, a shutdown order, a port, a model identifier that has to match on both sides, and a plan for what happens when one process dies. None of that existed when both models lived in one script.

Two failure domains. A run can now fail in the trainer or in the server, and the symptom is often the same: throughput collapsed. Telling them apart requires instrumentation you did not previously need, specifically the fraction of step time spent waiting on scoring, which turns out to be the single most useful number to log in this topology.

There is a fourth cost, with consequences for the objective and not for operations: the wire returns top- log-probabilities, not full logits. §15.3 covers what that does and what it does not do.

15.2 What a serving engine does that a training loop does not#

A serving engine is not a thin wrapper around model.generate. Two specific techniques account for most of the difference in what it can serve concurrently, and a reader who has only ever written training loops has probably met neither. Both are about the KV cache, whose arithmetic Chapter 9 derived and which I am not going to re-derive. The relevant fact from there is that the cache is per sequence, grows linearly with sequence length, and is usually what limits concurrency on a machine with generous capacity.

15.2.1 Paged attention, and the waste it removes#

Consider the naive way to allocate a KV cache. A sequence might grow to the model’s maximum context length, so reserve that much up front in one contiguous block. At a 4,096-token maximum and 0.262 MB per token, that is 1.07 GB per sequence.1 Sixteen concurrent sequences reserve 17.2 GB whether or not any of them ever gets long.

Now look at what actually happens. A scoring request over a 512-token rollout uses 512 slots of the 4,096 it reserved. The other 3,584 sit allocated, untouched, unavailable to anyone else, for the lifetime of that sequence. That is internal fragmentation: memory inside an allocation that its owner does not use. Kwon and colleagues measured this in real serving systems and found that existing engines were wasting the majority of their KV memory to it, plus a smaller amount of external fragmentation, the unusable gaps between variable-size contiguous blocks.2

Their fix borrows from operating systems. Stop requiring one sequence’s cache to be contiguous. Chop KV memory into small fixed-size blocks, say sixteen tokens each, and give every sequence a block table mapping its logical positions to whatever physical blocks are free. A sequence grows by acquiring one more block when it crosses a boundary, so its waste is bounded by one partly filled block instead of by the gap between its length and the maximum context.

Definition

Paged attention

A KV cache allocation scheme that stores each sequence’s keys and values in small fixed-size blocks, non-contiguously, with a per-sequence table mapping logical positions to physical blocks. Modeled on virtual memory paging. It removes the need to reserve the maximum context length per sequence, which bounds wasted cache memory to a fraction of one block instead of the whole unused tail of a reservation.

The first consequence is direct: recovered memory is concurrency. If paging takes your per-sequence waste from thousands of tokens to a handful, the same physical memory holds several times as many sequences, and Chapter 9’s concurrency formula, total memory minus weights minus overhead divided by per-sequence cache, starts returning a number you can actually reach instead of an optimistic one. The max_num_seqs you can honestly configure goes up with it.

The second is that blocks can be shared. Two sequences with the same prefix, which is exactly what you have when you score sixteen rollouts from one prompt, point at the same physical blocks for the shared portion and diverge only where they actually diverge, with copy-on-write handling the split. Prompt reuse across a rollout batch is the normal case in distillation, not a special one.

Watch out

Paged attention changes how much cache you can hold, not how much cache traffic a decode step generates. Chapter 9’s batching ceiling, where aggregate decode throughput flattens once KV reads dominate weight reads, is untouched by paging. Paging gets you to the ceiling; it does not raise it. If you expect a serving engine to make decode fast instead of making concurrency reachable, you will be disappointed in a way that is nobody’s fault.

15.2.2 Continuous batching#

The second technique is about scheduling, not memory. Static batching, which is what a training loop does, collects a fixed set of requests, runs them to completion, and then collects the next set. For training that is correct, because every example in a batch does the same amount of work. For serving it is wasteful in a specific way: the batch finishes when its slowest member finishes. A batch of fifteen 64-token rollouts and one 512-token rollout leaves fifteen slots idle for most of its life, and a request arriving one microsecond after the batch closed waits for the whole thing.

Definition

Continuous batching

A serving scheduler that admits and retires requests at the granularity of a single decode step rather than a whole batch. Finished sequences leave immediately and waiting requests take their slots, so the batch is repacked every step and stays dense even when request lengths and arrival times are ragged.

A distillation workload is unusually ragged, which is why this matters here. On-policy training generates rollouts that stop at end-of-sequence at different lengths, then sends them for scoring.3 Under static batching those length differences become idle slots. Under continuous batching the short ones retire and the scheduler pulls the next request into the freed slot in the same step, so ragged, mixed-length rollouts get packed into dense prefill work.

This also explains a curve shape that surprises people the first time they measure it. Aggregate decode throughput rises with concurrency while per-stream latency stays flat or gets slightly worse. One read of the weights now serves every sequence in flight, so total tokens per second climbs, but each individual sequence still advances one token per step and the step got no faster. Continuous batching trades latency for throughput, which is the right trade when you are scoring a rollout buffer and the wrong one when a person is waiting for text.

Watch out

You cannot measure continuous batching with a client that sends requests one at a time and blocks on each. A for loop over sixteen rollouts that posts each one and waits gives the scheduler exactly one request in flight at any moment, so it measures sixteen sequential batch-of-one runs and reports them as a batch-of-sixteen number. Measuring concurrency requires concurrent requests in flight: threads, an async client, or a request body carrying all sixteen prompts. This is the most common way I have seen a serving benchmark lie, and it lies quietly, producing a flat throughput-versus-batch curve that looks like a real finding.

15.3 The remote scoring path#

The client’s job is small and every part of it is a place to introduce a silent bug, so I want to walk the whole path.

The endpoint is OpenAI-compatible, meaning the server copies the request and response shapes of OpenAI’s public completions API so that clients written against it work unmodified. On top sits the extension the distillation case depends on entirely: instead of asking for generated text, you submit token ids and ask for the model’s per-position log-probabilities over the prompt you sent.

POST /v1/completions
{"model": <served model id>, "prompt": [<token ids>], "max_tokens": 1, "prompt_logprobs": k}

Every field in that body earns its place. The prompt is token ids, not text, because you have already applied the chat template and tokenized on the client side and you cannot afford the server to re-tokenize differently.4 max_tokens is 1 because you do not want generation at all; you are scoring existing text and the endpoint requires you to ask for at least something. prompt_logprobs is the number of top entries per position you want back, and it is the parameter that decides both your payload size and your truncation bias.

15.3.1 The response, and the null at position 0#

The response carries, per position of the prompt you submitted, a mapping from token id to a record containing a log-probability. Written out, ignoring the surrounding envelope:

choices[0].prompt_logprobs = [None, {tok_id: {"logprob": ...}, ...}, {...}, ...]

The first element is null, and the reason is the same off-by-one that Chapter 7 spent a section on.

A causal model at position produces a distribution over the token at position , conditioned on positions through . Scoring a prompt of length therefore produces distributions for positions through , and none for position , because there is nothing before it to condition on. The server could have returned a list of length ; it returns length with a null in the first slot instead, which keeps the list index equal to the index of the token being described. The null is not an error, a missing value, or a place to put a uniform prior. It is the statement that position 0 was never predicted by anything.

That is exactly the structure Chapter 7 derived for HuggingFace’s internal shift: logits lose their last position, labels lose their first, and a -token sequence yields supervised positions with position 0 excluded no matter what.5 The wire format and the in-process loss path agree, and preserving that agreement is the job when you turn the response into a tensor.

15.3.2 Turning the response into a tensor#

The client has four jobs and each one corresponds to a way the payload is not yet a tensor.

Drop the null and track what that did to your indices. After dropping, entry describes prompt position . Under the shift convention the student’s logit row at position is the row that predicts the token at position , so entry pairs with student row and the alignment is correct with no further offset. That is a convenience and not a law, and it holds only because you dropped exactly one element. Drop zero and every teacher distribution is compared against the student row predicting the previous token: a systematic one-position misalignment that produces a loss curve of entirely normal shape and a student that has learned to predict one token late.

Order the entries. The per-position mapping is a dictionary keyed by stringified token id, and a dictionary has no ordering you should rely on. Sort by log-probability descending and take the first explicitly, because the payload can carry more entries than you asked for and silently accepting them changes your tensor width.

Fill a fixed-width tensor with a floor. The loss path wants shape (B, T, k), and a position that comes back with fewer than entries needs its remaining slots filled with a log-probability far enough below the real ones to vanish under exp. A floor around works, since .

Reconstruct the tail. The entries do not sum to 1. The missing mass is the tail, and Chapter 10 is entirely about what to do with it; the mechanical part here is computing

where is the -th returned log-probability at a position. The clamp at is not cosmetic. Floating-point rounding on a peaked distribution can make the sum come out at , and of a negative number ends your run with a nan several steps later, far from the cause.

Here is the whole parse. Read it for the two assertions and the index bookkeeping, not for the tensor mechanics.

import torch

def parse_prompt_logprobs(payload, k, floor=-30.0):
    """Server response -> a top-k teacher batch aligned to student rows 0..T-2."""
    rows = payload["choices"][0]["prompt_logprobs"]
    assert rows[0] is None, "position 0 must be null; if it is not, the convention changed"
    rows = rows[1:]                       # row j now describes prompt position j+1
    T = len(rows)
    logprobs = torch.full((1, T, k), floor)
    indices = torch.zeros(1, T, k, dtype=torch.long)
    for j, entry in enumerate(rows):
        ranked = sorted(entry.items(), key=lambda kv: -kv[1]["logprob"])[:k]
        assert len(ranked) > 0, f"position {j+1} came back empty"
        for slot, (token_id, record) in enumerate(ranked):
            logprobs[0, j, slot] = record["logprob"]
            indices[0, j, slot] = int(token_id)
    covered = logprobs.exp().sum(-1)
    tail = (1.0 - covered).clamp_min(1e-9).log()
    return {"topk_logprobs": logprobs, "topk_idx": indices,
            "tail_logprob": tail, "k": torch.tensor(k)}

What that function proves is that remote scoring reuses Chapter 10’s cached-logit loss path unchanged: the dictionary it returns has the same keys a cache reader produces, so the objective never learns whether the teacher was a local module, a file written three days ago, or a URL. That is the reason the top- machinery had to exist before this chapter and not after it.

15.3.3 Testing a network client without a network#

A client that parses a wire format cannot be tested against a mock function, because what you are testing is the parse of a real payload.

In the labs: Lab 08

Part A·2 stands up an in-thread mock server on an ephemeral port that speaks the response shape, null included, serving log-probabilities derived from a fixed logit tensor the test also holds. The real client scores against it and the remote-scored forward KL must equal the locally computed one to within . That single number exercises the null drop, the sort, the tail reconstruction, and the alignment at once, and when Part B points the client at a real server the only thing that changed is the URL.

15.3.4 The truncation bias, now live#

One consequence of the payload shape is not a bug and cannot be fixed by better code. The server returns entries against a vocabulary of tens of thousands, so the divergence you compute is a truncated one. Chapter 10 derived the two ways to handle the missing mass and signed the bias of each; the observable symptom here is that the remote KL comes out slightly but systematically above the KL you would compute from full local logits on the same pairs.

The rule: raise prompt_logprobs until the bias is small enough for your purpose, or accept the bias and report its size. Either is defensible. An unmeasured bias is not, because it silently changes the objective you think you are minimizing, by an amount that depends on how peaked your teacher is, which is itself a function of temperature and of how far into training the student is.

That rule has a library-shaped exception, and it is large enough to change which objectives are available to you. The trainer that speaks to a remote teacher takes a loss_top_k parameter, and once the teacher is a URL the parameter stops being free. With a served teacher, loss_top_k must be greater than zero for forward KL, and exactly 1 for reverse KL or for a generalized JSD, which is to say for any objective with beta above zero. I do not have a derivation for the asymmetry and the library does not give one; the constraint is documented and enforced, and what matters here is what it does to your options.

Choose forward KL and you have the dial this section just described: raise , pay in payload, measure the residual bias, report it. Choose reverse KL or JSD against a remotely served teacher and there is no dial. You are pinned at the most aggressive truncation there is, one entry per position, which is the regime where Chapter 10’s truncation bias is largest and where the gap between the two estimators of the missing mass is widest. Half the objectives in Chapter 6 therefore arrive at this topology with their bias already fixed at the worst setting, which is a fact about the serving decision and not about the objective. The honest response is to measure that bias once at against a local bf16 reference, decide whether an objective computed that way is worth having, and write the number in the run manifest. The other response is to co-locate the teacher and keep the dial, which is what §15.9 prices.

Do not inherit that constraint from this page. Which parameter carries it, and which values are permitted, is a property of the version you installed, and the next section is about that habit exactly. loss_top_k belongs in the introspection cell alongside the fields you already assert.

15.4 Documentation drifts, including this book’s#

The trainer behind this topology has a name, and using it saves confusion later: trl.experimental.distillation.DistillationTrainer, configured by DistillationConfig. Chapter 12 §12.3.4 sets it against the other on-policy trainer the library ships and says which one to reach for. What matters on the serving side is narrower. It is always on-policy, with no lmbda field to turn the fraction down, so it does not substitute for the off-policy path Chapter 10 builds. It takes the server’s base URL plus a mode selector in place of a model object, which is what makes the teacher a URL. Its divergence sits in the generalized JSD family, which puts it on the beta > 0 side of the loss_top_k constraint above by construction. Serving this teacher over the network means accepting one returned entry per position, and the memory arithmetic in §15.9 is what you trade that against.

I am now going to tell you not to trust the previous paragraph.

Experimental modules change between minor versions. That is what the namespace means. This course’s own README once described a configuration field that no longer exists in the installed version, and the drift was caught by checking the installed object instead of re-reading the documentation, which would have reproduced the error. The same class of drift bit the course a second time, in a different lab, where an exercise’s premise had to be corrected after introspection showed the library did not behave as the exercise assumed.

Field note

The temptation is to file this as a disclaimer and move on. I want to make it a method instead, because the failure mode it prevents is expensive and quiet.

Here is the expensive version. You write a training script against documented field names. The library upgraded two weeks ago. A field was renamed and the config object accepts unknown keyword arguments, or worse, the field still exists and its default changed. Nothing raises. The run proceeds, the loss falls, the checkpoint writes, and you have run a different experiment than the one you designed. You find out, if you find out, when a result refuses to reproduce and you go reading source in the third hour of an afternoon you did not budget.

The cheap version is a cell at the top of the script that reads the installed object’s actual fields and asserts the ones your plan depends on. It costs one second and it fails before anything loads.

import dataclasses
from trl.experimental.distillation import DistillationConfig   # the import path is a claim too

fields = {f.name: f.default for f in dataclasses.fields(DistillationConfig)}

assert "vllm_server_base_url" in fields, "drift: the remote-teacher field is gone; re-ground"
assert "lmbda" not in fields, "drift: this trainer grew an off-policy mixing knob; re-read it"
for name in sorted(fields):
    print(f"{name:32} default={fields[name]!r}")

Notice what the second assertion does. It asserts an absence. The claim “this trainer is always on-policy” is a claim that a certain knob does not exist, and if the library grows that knob your mental model is wrong in a direction that will not raise an exception. Asserting absences is the part of this habit people skip.

The printed dump matters as much as the assertions. It goes in the run log, so that six months later you can answer “what were the defaults when this ran,” which is otherwise unanswerable and is exactly what you need when a result stops reproducing.

The general rule: any document about a moving library, including this book, is a claim about a version. Chapter 18 makes version pinning part of a study’s manifest for the same reason.6 The introspection cell is the runtime half of that discipline, and I would rather it fire on a sentence I wrote than have you inherit an error from me.

15.5 Quantization, and what it does to the ceiling#

Chapter 9 established that the decode ceiling is bandwidth divided by bytes read per token, that bytes per token is parameter count times bytes per parameter, and that bytes per parameter is a lever. This is the section that pulls it.

Definition

Quantization

Storing model weights in a numeric format with fewer bits per value than the format they were trained in, with a scheme for recovering an approximation of the original value at use time. A bf16 weight costs 2 bytes; an 8-bit format costs 1; a 4-bit format costs about half a byte once the per-group scales are counted.

Definition

Post-training quantization

Quantizing a model that has already finished training, without any gradient updates to its weights. The method gets a small calibration set of representative inputs and chooses the quantization parameters to minimize the damage, but it never retrains. This is what makes it practical for a teacher you downloaded and cannot afford to fine-tune.

Two post-training methods account for most of what you will serve. GPTQ quantizes layer by layer, using approximate second-order information about the layer’s error surface to choose the rounding for each weight given the rounding already committed for its neighbors.7 AWQ starts from the observation that a small fraction of weight channels matter far more than the rest, identifies them from activation magnitudes on a calibration set instead of from the weights themselves, and protects them by scaling instead of by keeping them wider.8 Both target 3-to-4-bit weights with activations left wider, and both are one-shot. The same formats appear on the training side of the split for a different purpose.12

15.5.1 The ladder#

The arithmetic is one division, and it belongs in a table and not in a sentence, because the table is a prediction and predictions are what §15.6 exists to check. For a model of billion parameters at bytes per parameter on the reference machine’s 273 GB/s:

Table 15.1 The decode ceiling as a function of bytes per parameter, at 273 GB/s. Weight footprint in GB, ceiling in tokens per second, single stream.

Model bf16 (2.0 B/param) 8-bit (1.0 B/param) 4-bit (0.5 B/param)
8 B 16 GB, 17.1 tok/s 8 GB, 34.1 tok/s 4 GB, 68.3 tok/s
20 B 40 GB, 6.8 tok/s 20 GB, 13.7 tok/s 10 GB, 27.3 tok/s
32 B 64 GB, 4.3 tok/s 32 GB, 8.5 tok/s 16 GB, 17.1 tok/s
70 B 140 GB, 2.0 tok/s 70 GB, 3.9 tok/s 35 GB, 7.8 tok/s

Lab 08’s solution notebook builds this ladder live and asserts two ratios to within : the 4-bit ceiling is exactly 4 times the bf16 ceiling, and the 8-bit ceiling is exactly 2 times it. Those assertions are trivial to satisfy, since they are the same division with a different denominator, and that is why they belong in the notebook. They pin down what the roofline predicts, so that when the measurement disagrees the disagreement is attributable.

2026-08-01T07:20:03.032977 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ bf16 8-bit 4-bit 0.0 0.5 1.0 1.5 2.0 bytes per parameter 2.0 1.0 0.5 32B teacher, weights only bytes moved per parameter bf16 8-bit 4-bit 0 5 10 15 20 25 decode ceiling, tok/s at 273 GB/s shaded: 2x to 3x bf16 (8.5 to 12.8 tok/s), where measurement lands while dequantization kernels are immature 4.27 8.53 2.00x 17.06 4.00x single-stream decode ceiling
Figure 15.1 Halving the bytes each parameter costs exactly doubles the decode ceiling, because bytes per parameter appears once in the denominator of the roofline and nowhere else.

Read the 70B bf16 cell against capacity instead of bandwidth. Its 140 GB of weights do not fit in 128 GB of memory, so the rung does not exist on this machine and its ceiling of 2.0 tokens per second is a number you can compute and never measure. The roofline is silent about whether a configuration fits, which §15.9 is about. Table B.3 runs the same division over the full range of model sizes the book uses, including the student-class rows this section leaves out.

15.5.2 What the ladder is a prediction of, and what usually happens#

The roofline says 4-bit should decode four times faster than bf16, because it counts only weight bytes and 4-bit weights are a quarter of them.

The likely measurement on a young platform is 2 to 3 times, and the reason is not the roofline being wrong. A 4-bit weight has to be dequantized before it multiplies anything, and dequantization is arithmetic the bound does not model. If the kernels for your format on your architecture are immature, each weight read carries extra compute, and the bus the bound assumes is saturated is not saturated, because the arithmetic units are what you are waiting on. Chapter 9’s warning about the roofline being a bound and not a prediction has its sharpest instance here.

That is a hypothesis, not an excuse, and the way to hold it accountable is to insist on ratios. Absolute throughput is a fact about your platform; the ratio between two rungs is a fact about the trade. If the ratio is 2.4 when the bound says 4, you have measured the tax your kernel stack charges for the format, and that number is the one you carry to the next model.

Watch out

A rung measuring above its predicted ceiling is not a good result. It is a contradiction, and per §15.8 the resolution is that the bytes-per-token input was wrong, most often because the model reads only part of its weights per token, or because the harness is timing kernel launches instead of kernel work.

15.5.3 The trade, stated in the currency this book cares about#

Everything above is about bandwidth. The other half of the trade is fidelity, and for a teacher it has a consequence that it does not have for a model you are serving to users and nothing more.

A quantized model’s output distribution is not the unquantized model’s output distribution. The methods are good and the differences are small on the metrics their papers report, but small is not zero, and a distillation teacher’s output distribution is the training target. Distortion in a target is not noise that averages out across a corpus; it is a systematic shift in what the student is being asked to become, applied identically wherever the same distortion occurs. Hinton’s whole argument for why distillation transfers anything rests on the fine structure of the teacher’s probabilities on wrong answers, and that structure lives in the small values, which are exactly the values a coarse weight format is least careful with.9

I do not want to overclaim in the other direction. No result I can cite says a 4-bit teacher produces a measurably worse student at a stated effect size, and the fidelity literature gives reason to expect the relationship to be loose, since students routinely fail to match their teachers and improve anyway.10 The honest position is that this is a lossy step in a pipeline whose output you care about, and the cost of finding out is small.

So find out, with the machinery Chapter 10 already built. Score a held-out slice with the teacher in bf16 and again in your serving format, and report top-1 agreement between the two teachers plus the per-position forward KL from the bf16 one to the quantized one. If that KL is comparable to the KL your student is expected to reach, your teacher’s quantization error is the same size as the thing you are training toward, so serve wider or accept that your target has a known floor. If it is two orders of magnitude smaller, serve at 4 bits and stop worrying.

Watch out

The scoring path now has two lossy steps: the quantized weights and the top- payload. Measure them separately. A remote KL that disagrees with a local bf16 reference tells you the combined effect and nothing about the split, and the two have different fixes. Raise prompt_logprobs with the format fixed to isolate truncation; change the format with fixed to isolate quantization.

15.6 Building your own curves#

Now the part of the chapter that the rest exists to support.

15.6.1 The instrument comes before the experiment#

A throughput harness earns trust through three rules, and all three are about not measuring the wrong thing.

Definition

Warmup

Iterations run before timing begins and then discarded, because they pay one-time costs that do not represent steady state: kernel compilation and autotuning, allocator growth, cache population, lazily loaded weights, and connection establishment. A measurement that includes warmup understates throughput by an amount that depends on how many timed iterations you ran, which makes it not comparable to anything.

Discard warmup. Two iterations suffices for a local harness; more for a server that has just started, because weight loading and the first scheduler round are slower than everything after.

Report the median of several timed runs, not the mean of one. The mean is sensitive to a single outlier, and outliers on a shared machine are guaranteed. Take five timed runs and use the middle one. The median has a second use in debugging: if it sits far above the minimum you have contention and not a slow configuration, and that changes what you do next.

Synchronize before you stop the clock. GPU kernel launches return to the host immediately while the work is still queued. Time a launch loop without synchronizing and you measure the launch rate, which can be a hundred times the work rate and produces a number that violates the roofline. This is the single most common cause of an impossible measurement.

Then do the thing almost nobody does, which is verify the instrument against a workload whose answer you already know.

import time, torch

def measure(run, n_warmup=2, n_timed=5, synchronize=False):
    """Median tokens per second of run(), which returns the tokens it processed."""
    for _ in range(n_warmup):
        run()
    durations, tokens = [], None
    for _ in range(n_timed):
        if synchronize:
            torch.cuda.synchronize()
        start = time.perf_counter()
        tokens = run()
        if synchronize:
            torch.cuda.synchronize()
        durations.append(time.perf_counter() - start)
    return tokens / sorted(durations)[len(durations) // 2]

def known_rate():              # 5,000 tokens in 50 ms is 100,000 tok/s by construction
    time.sleep(0.05)
    return 5_000

rate = measure(known_rate, n_warmup=1, n_timed=3)
assert 60_000 < rate < 110_000, f"the harness mis-measures a known workload: {rate:,.0f}"

The fake workload sleeps for a known duration and reports a known token count, so its true rate is 100,000 tokens per second, and a harness that cannot recover that to within a factor of two is broken before it ever touches a model. The band is deliberately wide: the point is to catch structural errors, such as timing the wrong region or counting warmup runs, and not to characterize time.sleep.

15.6.2 What to hold fixed#

A throughput curve measures one variable, so everything else is a control, and the list of things that quietly are not controlled is long. Before a sweep, pin the served model identifier and its quantization format; max-model-len, which caps the KV allocation per sequence; the memory fraction the server took; max-num-seqs, a hard cap that will silently truncate the high end of your batch sweep; the token-id distribution of your synthetic prompts, since random ids and real prompts have different cache and routing behavior; for prompt_logprobs, since payload size scales with it; the client’s concurrency mechanism; and the machine itself, meaning nothing else running, including the training process, unless co-tenancy is what you are measuring.

That last one deserves emphasis. A prefill curve measured while a student trains is a co-tenancy measurement and not a server measurement, and the two are both useful and not interchangeable. Measure the server alone first, so the contention has a baseline to be read against.

The first two sweeps are small. Prefill over batch sizes 1, 4, and 16 crossed with sequence lengths 512 and 2,048 is six cells; decode over batch sizes 1 and 8 at 256 new tokens is two. Eight cells with warmup and five timed runs each is minutes, and it is enough to see every shape in the next section.

15.6.3 Reading the curves#

Prefill against batch size should rise and then flatten. It rises because larger batches keep the arithmetic units busier and prefill is compute bound; it flattens when the units are fully occupied. The height of the flat region is your true corpus-scoring rate, and it is the number that reprices every cached-logit and rollout-scoring plan you have.

If it is flat from batch 1, you are almost certainly not batching. Either the client is serializing (see §15.2.2’s warning) or the server’s per-round token budget is capping the work it admits, which is a configuration limit and not a hardware one.

Prefill against sequence length should rise and saturate too, for a different reason. At short lengths per-request overhead is a large fraction of the work, so tokens per second is depressed by fixed costs; as the sequence grows those costs amortize and the curve climbs toward the compute-bound rate. At long enough lengths attention’s quadratic term bites and the curve can turn over. Where it saturates is the shortest sequence at which a scoring call is worth making, which is what you need in order to decide how to pack rollouts.

Decode against batch size should show aggregate throughput rising while per-stream latency stays flat or degrades, approaching the ceiling Chapter 9 derived. Where aggregate throughput stops improving is your server’s practical max_num_seqs, and it is the honest answer as opposed to the one the KV arithmetic gives. Compare the two: they should agree within about a factor of two, and the direction of the disagreement says which resource ran out first. A measured crossing well below the KV-derived concurrency means something other than cache capacity binds, most likely attention compute or scheduler overhead.

2026-08-01T07:20:03.985235 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1 16 32 48 64 80 96 112 128 concurrency B, sequences in flight 0 50 100 150 200 250 aggregate decode throughput, tok/s KV-traffic ceiling: batching cannot exceed 254.3 tok/s memory wall (128 - 17.6 - 8) / 1.0737 = 95 sequences unreachable: KV cache does not fit 15 50 83 126 168 202 217 roofline model: R(B) = 273 B / (17.6 + 1.0737 B) your measured curve lies under this one and flattens earlier
Figure 15.2 The roofline model of aggregate decode throughput against concurrency for a 4-bit 32B teacher, showing the KV-traffic asymptote that batching cannot exceed and the memory wall that arrives first; your measured curve goes under this one and flattens earlier.

The ratio of peak prefill to peak decode is the single number that summarizes the machine for planning. For a dense teacher, expect 20 to 60 to 1. Under 10 to 1 means prefill is throttled, and the first thing to check is the server’s cap on tokens admitted per scheduling round. Over 100 to 1 means decode is starved, and the first suspect is the quantization kernel path, because a quantized model still needs fast kernels for its format before it decodes at the rate its byte count implies.

Those bands are not laws. They are the range within which the underlying physics, many positions per weight read against one position per weight read, produces an answer. A number outside them is an invitation to find the configuration mistake, and it is nearly always a configuration mistake rather than a discovery.

In the labs: Lab 08

Part B writes all of this into machine_profile.json: a tokens-per-second figure per (phase, batch, sequence) cell, the model and quantization that produced it, the ratio, and the course’s borrowed numbers recorded as the expected range the measurement was checked against. Labs 09 through 12 read that file rather than quoting the README.

Definition

Machine profile

A versioned artifact recording throughput measured on your own hardware, keyed by phase, batch size, and sequence length, with the timing method and the full serving configuration attached. Later work sizes against the profile instead of quoting a published benchmark, and the profile carries enough provenance that a disagreement between two runs can be traced to a configuration difference rather than argued about.

15.7 The backwards audit#

Every use of the roofline so far has run in one direction: from a byte count to a throughput bound. Run it the other way and it becomes a measuring instrument for a quantity you cannot otherwise observe.

Definition

Backwards audit

Inverting a roofline. Given a measured throughput and a known memory bandwidth, compute the bytes per token the machine must have moved as bandwidth divided by throughput, then compare that figure against an independent estimate of what the model should have moved. The difference is real traffic the estimate did not account for, and it is measured, not assumed.

The derivation is one line and it assumes exactly one thing. If decode is bandwidth bound, then throughput times bytes per token equals bandwidth, so

with no model of the internals required. You need the clock and the bus width, not the architecture, the kernels, or the cache layout.

Run it on the published 20B measurement.15 Bandwidth is 273 GB/s, measured decode is 49.7 tokens per second:

Now the independent estimate. The model is a mixture of experts with roughly 3.6 billion active parameters per token in a 4-bit format at half a byte each, so the weights it should read are

The two numbers differ by a factor of

and inverting that gives a bandwidth efficiency of percent. About a third of the bytes crossing the bus per decoded token are the expert weights that the specification sheet counts. The other two thirds are something else.

Lab 08’s version closes the loop with a circularity check worth copying: dividing bandwidth by the backed-out bytes must return the measured throughput exactly, to .16 It is a tautology, and running it catches the arithmetic slip where you divide by the wrong number and believe the result because it is plausible.

15.7.1 What 33 percent tells you#

It tells you a multiplier. For this model, in this format, on this kernel stack, on this machine, the real cost per decoded token is 3.05 times the specification-sheet estimate. If you price a sequence-level corpus using the 1.80 GB figure, your estimate comes in three times optimistic, and a plan that said eleven hours means thirty-four.11 That correction alone justifies the exercise.

It also tells you the audit is a property of a whole configuration and not of any one component. Change the batch size and the KV share of the traffic changes; change the context length and it changes again; change the serving engine and the scheduling overhead moves. The number belongs in your machine profile next to the throughput it came from, not in a footnote about the model.

15.7.2 What 33 percent does not tell you#

It does not tell you what the other 67 percent is. The candidates are the shared non-expert layers every token reads regardless of routing, KV cache reads that grow with context, kernel inefficiency in the dequantization path, and time the bus spends idle during scheduling gaps, which the inversion counts as bytes because it only sees elapsed time. Separating them needs the server’s own counters, and the two instruments are complements and not substitutes: the byte audit tells you the total is wrong, the counters tell you where.

It does not tell you whether 33 percent is good. There is no reference point inside the number. It becomes meaningful the moment you have a second one, from a different format, batch size, or engine, because then you have a ratio and ratios are comparable.

It does not tell you anything about quality. A configuration can move bytes efficiently and produce a distribution you should not train against.

And it cannot come out above 100 percent. The denominator omits real traffic by construction, so efficiency above 1.0 means the measurement is wrong, most likely a harness that timed launches instead of work, or an active-parameter count you took from a blog post.

15.8 Detecting a sparse architecture from a decode rate#

This is the chapter’s best argument for the discipline, because it recovers a structural fact about a model from a single number and a bus width.

Start with the naive reading. The model is described as 20 billion parameters and its checkpoint is about 10 GB on disk in a 4-bit format, so if decode reads the whole checkpoint per token the ceiling is

The measurement is 49.7. That is above the ceiling.

Take that seriously, because the instinct is to shrug at it. A roofline is not a guideline. It is a statement that a certain number of bytes must cross a bus of known width, and a measurement above it says one of the bound’s inputs is false. There are exactly three candidates: the bandwidth figure, the measurement, or the bytes-per-token figure.

Bandwidth is a hardware specification and the error would have to be large. Measurement error is the usual suspect and has a signature, since a harness that times launches instead of work produces impossible numbers, which is why §15.6.1 insists on validating the harness against a known rate before you ever need this argument. That leaves the bytes. The checkpoint is 10 GB, and 10 GB is read per token only if every parameter participates in every token. So the resolution is that they do not.

Definition

Mixture of experts

An architecture that holds many parallel weight blocks per layer and routes each token through only a few of them. Total parameters and active parameters per token are different quantities, and the second is the one that appears in the roofline, so a checkpoint’s size on disk becomes a poor estimate of what a decode step moves.

Now close the argument in the other direction. If the model is sparse, the bytes per token come from the active slice, reported as about 3.6 billion parameters at half a byte each:

The measurement of 49.7 sits under that. So the story is consistent: 27.3 below 49.7 below 151.7.

The chain is what makes this an argument instead of a rationalization, and each link carries a distinct claim.

The lower bound must be violated. If the measurement were below 27.3, a dense read would explain it and there would be nothing to infer. Being above the dense bound forces the conclusion, because no arrangement of a dense model on this bus produces 49.7.

The upper bound must be respected. If the measurement were above 151.7, the active-slice estimate would be wrong too, and you would be back to hunting for a measurement error or an active parameter count you should not have believed.

The gap must be wide enough to sit inside. Here it is a factor of 5.6, and the measurement lands at a bandwidth efficiency of 33 percent, a plausible place for a real system given the overheads §15.7.2 lists. A measurement crowding either bound would be suspicious.

Sitting strictly between a dense bound and an active-parameter bound is a signature. No dense architecture produces it, because a dense architecture cannot beat its own dense bound. The only architecture consistent with the position is one that reads a fraction of its weights per token plus a fixed shared remainder, which is what a mixture of experts is.

2026-08-01T07:20:04.843705 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 10 20 30 50 100 200 300 decode throughput, tok/s (log scale) dense roofline 27.3 tok/s = 273 / 10 GB active-parameter roofline 151.7 tok/s = 273 / 1.8 GB measured decode 49.7 tok/s above dense means not a dense read; below active means the routing really is sparse; the only architecture consistent with both is a sparse mixture of experts backwards audit: 273 / 49.7 = 5.49 GB per token, against 1.80 GB of active weights, a factor of 3.05
Figure 15.3 A measured decode rate that sits strictly above a model's dense roofline and strictly below its active-parameter roofline is a signature of sparse routing, and no dense architecture can produce it.

Field note

There is a small inconsistency inside Lab 08 that I am going to report instead of smoothing over, because reporting them is the subject of Chapter 18 and because it is a good example of how they creep in.

The lab’s prose rounds the active-slice roofline to “273/1.8 is about 150 tokens per second.” The code in the same cell computes and asserts against that. Nothing breaks, since 49.7 is under both and the conclusion is identical. But the two numbers are in the same notebook and they are not the same number, and a reader checking the arithmetic will notice.

Use 151.7, which is what the assertion tests. The prose figure is a rounding that got written down as if it were the quantity, and I have made that exact mistake in my own notes more than once: you round in a sentence, the sentence gets quoted, and three documents later the rounded figure is the one people are computing with.

There is a second sparse-routing failure this method cannot see. A degenerate router that sends nearly every token to the same experts still reads an active-slice worth of weights per token, so the byte audit looks healthy while the model’s capacity is wasted. That shows up only in the server’s per-expert load counters, which is another reason the two instruments are complements.

15.9 Fitting a served teacher and a training student in one machine#

The measurement discipline has a memory counterpart with the same structure: write down the plan, then find out what the plan omitted.

Lab 08’s fit table asks whether a 32B teacher at each quantization rung can share the reference machine with a 360M student under full fine-tuning. The ingredients come from Chapters 8 and 9: teacher weights at GB, a KV cache at the 32B-class geometry for 16 sequences of 4,096 tokens, a full fine-tune at 16 bytes per parameter, and 4 GB of activations.1314 The KV term is 17.2 GB and it is identical at every rung, because quantizing the weights does not quantize the cache.

Table 15.2 Planned memory for a served 32B teacher co-tenant with a 360M student’s full fine-tune, against a 108.8 GB usable budget on a 128 GB machine.

Rung Teacher weights KV, 16 x 4096 Student FT + activations Planned total
32B bf16 64.0 GB 17.2 GB 9.8 GB 90.9 GB
32B 8-bit 32.0 GB 17.2 GB 9.8 GB 59.0 GB
32B 4-bit 16.0 GB 17.2 GB 9.8 GB 43.0 GB
70B bf16 140.0 GB n/a n/a does not fit alone

All three 32B rungs fit, so the quantization ladder of §15.5 is measurable on this box. That is why the fit table runs before the experiment, rather than a rung turning out to be impossible after you have written the harness for it.

Now the number in the caption. The bf16 plan is 90.9 GB and the machine has 128 GB, which looks like 37 GB of slack. The course does not compare against 128. It compares against GB.17

Definition

Headroom

The fraction of physical memory a plan deliberately refuses to allocate, because a memory plan systematically understates real usage. Allocator fragmentation, transient peaks during optimizer updates and checkpoint writes, framework and driver reservations, and KV growth past the planned context all consume memory that no line of the plan accounts for. The course reserves 15 percent.

So the honest reading of the bf16 rung is 90.9 GB planned against 108.8 GB usable, leaving 17.9 GB of real slack rather than 37. It is the tight rung, and the one that fails first if any assumption moves: a longer context, a larger scoring batch, a student one size up.

The general lesson is the one this chapter keeps making in a different register. A plan and a measurement differ, and the difference is not random: it is one-directional, since a plan always understates; it is large enough to matter, at 19.2 GB here; and it has a name, which converts it from a surprise into a budget line. The version you can act on is to record actual peak memory after the first successful run, divide by planned, and use that ratio instead of 0.85 next time.

The 70B row is the other kind of lesson. At bf16 it is 140 GB of weights against 128 GB of memory, so it does not fit alone, let alone alongside a student. There is no measurement to run and no tuning that helps. The options are a narrower format, a smaller teacher, a different machine, or a different method.

15.10 Co-tenancy, and the fraction that balances it#

Once the two processes share a machine, the memory split between them is a design variable with an optimum that is neither process’s private optimum.

Definition

Co-tenancy

Two processes sharing one memory pool and one memory bus, each sized against the other rather than against the machine. On unified memory the sharing is total: a byte the server reserves is a byte the trainer cannot have, and bandwidth the server consumes is bandwidth the trainer waits for.

Give the server a fraction of the pool of size . Two quantities move in opposite directions.

Teacher scoring throughput rises with . The teacher’s weights are a fixed cost paid before anything else; every GB above them becomes KV cache, KV capacity is concurrency, and concurrency is scoring tokens per second. Writing for the cache one sequence needs and for the scoring rate one concurrent sequence contributes:

The max with zero handles the region where the fraction does not even cover the weights, in which case the server does not start.

Student training throughput falls with . Under the simplest bandwidth-sharing assumption, the student gets the share of the machine not promised to the teacher:

with the student’s training tokens per second with the machine to itself.

Joint progress is not the sum of these. It is the smaller of them, because the run advances only as fast as its slower half. If the teacher scores tokens for every student token trained, which is the ratio a scoring-heavy on-policy loop imposes,20 then

The minimum of a rising function and a falling function is maximized where they cross. Set them equal and solve. Writing so that :

Every term earns its place. is what the student can do alone, in training tokens per second. is the teacher’s fixed weight cost in GB including server overhead, and it pushes the optimum up, because memory below it buys no scoring at all. is the whole pool and appears only in the denominator, so a bigger machine pushes the optimal fraction down even as it raises the absolute allocation. And bundles the three quantities that convert memory into scoring supply: how fast one sequence scores, how much cache a sequence costs, and how many teacher tokens each student token demands. The comparative statics follow and are more useful than the number itself. Raising shrinks and moves toward 1, because a more scoring-heavy loop needs more teacher; improving the teacher’s per-sequence scoring rate raises and moves down, because the same memory now supplies more scoring.

Now the constants Lab 08’s solution uses, all stated modeling assumptions and not measurements:18 GB, GB for a 4-bit 32B teacher plus server overhead, GB per 4,096-token sequence, scoring tokens per second per concurrent sequence, student tokens per second, and .

Roughly 0.49, which is why a server started at a memory fraction of 0.55 is not being timid. It sits a little above the computed crossing, a reasonable place to be given that the teacher side of the min is the one whose failure mode is the student idling.

The sweep confirms the shape. At the teacher’s leftover cache supports too little concurrency and the system is teacher-starved, with joint progress at 464 student tokens per second. At the teacher scores far more than the loop consumes while the student’s bandwidth share drags, and the system is student-starved at 360. At , slightly past the crossing, joint progress is 540, the best of the three, and the knee itself reaches 617.

2026-08-01T07:20:05.701248 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 f, fraction of the 128 GB pool given to the teacher server 0 200 400 600 800 1000 student tokens per second student training throughput student tokens the teacher can keep supplied with scores joint progress = min of the two f* = 0.49 knee 617 0.40 -> 464 teacher-starved 0.55 -> 540 0.70 -> 360 student-starved constants are modeling assumptions (§15.10), not measurements
Figure 15.4 Joint progress is the minimum of a rising teacher-supply line and a falling student-throughput line, so the best memory split is at their crossing and not at either process's private optimum.

Watch out

Every constant in that model is an assumption, and I would not defend any of them to two significant figures on your machine. What transfers is the structure. If your real sweep shows joint progress rising monotonically all the way to 0.7, scoring demand is higher than assumed and the student is always the starved side; if it falls monotonically from 0.4, the opposite. Either way the repair is refitting two constants instead of abandoning the frontier, and the point of the closed form is that three measurements plus one equation locate the optimum where a blind sweep needs many more.

15.11 Amortizing the request toll#

Chapter 9’s amortization was about capital cost: pay for a logit cache once, spread it over every run that uses it. This is a different amortization at a much smaller time scale, and the two need to be kept separate because they have different knees and different remedies.

Every scoring request pays a fixed cost before any token is scored: connection handling, request parsing, scheduling, and queueing behind whatever else the server is doing. Call it , with each scored token costing a marginal . A training step that needs rollouts of length scored, sent rollouts per request, costs

The second term does not depend on at all, which is the whole point: the tokens have to be scored either way. Batching removes tolls and nothing else, so the entire available gain is in the first term, and once that term is small relative to the second there is nothing left to win.

The knee is where a single request’s toll equals a single request’s payload cost:

Lab 08 measures this against a mock server with a deliberately known cost model:19 a 50 ms fixed charge per request and 0.1 ms per scored token, with 16 rollouts of 64 tokens per step. The knee is

about 8 rollouts per request, and the measured step times bracket it as the model predicts. At a batch of 1 a step pays the toll 16 times and takes about 0.9 seconds; at a batch of 16 it pays once and takes about 0.15 seconds, better than a fivefold improvement in step rate with identical tokens scored and identical gradients computed. The gain from 1 to 4 is large and the gain from 4 to 16 is smaller, because 4 is already most of the way to the knee.

Field note

The reason to check a measured curve against a cost model whose constants you chose is that it tells you whether the harness is measuring the effect or measuring itself. Lab 08 asserts that each measured step time is under twice the prediction and above 0.9 times it. The upper assertion catches overhead the model omits, which here is genuine HTTP and client cost and is itself a per-request toll that amortizes the same way. The lower catches the harness measuring faster than physically possible, which means it is not measuring what it claims.

Two corrections take this from a toy to something usable on a real server.

The marginal per-token cost is not a constant. On a real server is one over the prefill throughput at that batch size, and §15.6.3 established that prefill throughput rises with batch until compute saturates. So batching helps twice, once by amortizing the toll and once by densifying prefill, both in the same direction, and the real knee sits further right than the constant-cost model predicts. Find it by substituting your measured prefill curve for rather than a single number.

A falling curve past the knee is a different phenomenon. If steps per second improve to batch 8 and then get worse at batch 16, that is not amortization running out. It is the payload outgrowing the server’s token budget per scheduling round, so one large request gets split across rounds and queues behind other work. Shrink toward the knee or raise the per-round budget; the diagnostic that separates the two is that queueing shows up in request latency variance and amortization does not.

The operational rule: send one scoring request per optimizer step, not one per gradient-accumulation microbatch. Microbatches exist to fit activations in memory and have nothing to do with how many tokens fit in a request. Batching the scoring across them is free, and it is the first thing to try when scoring latency dominates a step, well before any change to the topology.

15.12 Running two processes without lying to yourself#

The operational half of this topology is short and every item on it is a place where I have seen a run produce a result that was not what it claimed.

Startup ordering. Server first, then the health check, then the trainer. A 32B model in a 4-bit format takes minutes to load, and a trainer that starts immediately gets connection refused. Whether that is a nuisance or a disaster depends on the next item.

Health checks that check the right thing. A liveness check confirms the process answers. It does not confirm the process is serving the model you think. Fetch the model list, assert the returned identifier equals the one your client will send, and log it. A server holding a different checkpoint than you believe answers every request successfully and scores your rollouts with the wrong teacher, and no downstream symptom distinguishes that from a teacher that is bad at your task.

Timeouts sized to the operation. Scoring a rollout batch is seconds; benchmarking prefill over a grid is minutes; benchmarking decode at 256 new tokens on a slow teacher can be many minutes. One global timeout either aborts legitimate work or hides a hung server for an hour, which is why Lab 08 uses separate timeouts per call site.

What happens when the server dies mid-run. Requests raise connection errors, or hang until the timeout fires. Both are exceptions in the client, and what your training loop does with them has only one acceptable answer.

Watch out

A training run whose teacher disappeared must fail loudly and stop.

The tempting alternatives all produce a run that finishes. Skip the batch, and you have trained on a corpus with a hole in it whose size and location you do not know. Fall back to hard-label cross-entropy, and you have silently changed methods partway through and will report the result as distillation. Reuse the previous batch’s teacher distributions, and you have trained the student against the wrong targets for however long the outage lasted. Retry forever, and you have a job occupying the machine indefinitely while showing no error.

Every one of those writes a checkpoint and produces a loss curve of normal shape. It is the same class of failure as a trainer whose distillation branch is dead code, or a pruning script that silently reinitializes the layers it was supposed to keep. The run does not tell you. Crash instead: you lose an hour, and an hour is much cheaper than an experiment you cannot trust and will not know not to trust.

Bounded retry with backoff is reasonable for transient failures, a few attempts over a minute, because servers do briefly stall under memory pressure. Unbounded retry is not, and a fallback path that changes the objective is never acceptable in a run you intend to report.

Instrument the boundary. Log, per step, the fraction of wall clock spent waiting on scoring. It tells you which of your two failure domains is the problem, whether to raise the scoring batch, and whether a co-located teacher would have been better after all. It costs one timer.

Record the topology in the manifest. The server’s model identifier, its quantization format, its memory fraction, its concurrency cap, the client’s , the full launch command that produced the server, and the hash of the machine profile you sized against. The launch command is part of the measurement, not a note about how it was taken. Chapter 18 makes the general argument for manifests; the specific argument here is that “throughput collapsed compared to last week” is unanswerable without them and mechanical with them.

15.13 Where this lands in the labs#

Lab 08’s Part A is worth running even if you never bring up a server: the roofline chain, the mock-server client verification, and the harness self-test all execute anywhere in a few seconds and between them contain the whole methodological argument of this chapter. Part B is where the borrowed numbers die. The solution notebook’s four exercises are the quantitative spine: the quantization ladder with its exact 4x and 2x assertions and its fit table, the co-tenancy frontier solved in closed form and checked against a grid search, the amortization curve measured against a mock server whose cost model is known exactly, and the backwards audit that turns 49.7 tokens per second into 5.49 GB per token and a 33 percent efficiency figure. Three of the four are answerable with a pencil before any server exists, and doing them that way is the skill the lab teaches.

15.14 Exercises#

  1. You measure a decode rate of 61 tokens per second for a model you believe is dense, has 24 billion parameters, and is stored at 1 byte per parameter, on the reference machine. Show that this is impossible if all three beliefs hold. Then list the four candidate explanations in the order you would check them, and for each one name the single measurement or lookup that would confirm or eliminate it. Which of the four can be eliminated without touching the machine?

  2. Here is a decode-versus-concurrency curve from someone else’s server. Aggregate throughput: 18 tok/s at 1 sequence, 71 at 4, 138 at 8, 141 at 16, 139 at 32. Per-stream latency is flat across the whole range. Give two structurally different explanations for the flattening between 8 and 16, say which measurement distinguishes them, and state what you would predict that measurement shows under each explanation. Then say what the flat per-stream latency rules out.

  3. Take the co-tenancy formula . Without substituting numbers, determine the sign of and explain in one sentence why the sign is what it is in operational terms. Then compute for a loop that scores 1 teacher token per student token instead of 4, holding all other constants from §15.10, and say what changes about how you would launch the server.

  4. A colleague reports that quantizing their teacher from bf16 to 4 bits gave a 1.6x decode speedup, and concludes the roofline is wrong. Give three explanations for a 1.6x that are all consistent with the roofline being correct. Then design the smallest experiment that distinguishes among them, stating what you would hold fixed and what you would expect each explanation to produce.

  5. You run the backwards audit on two serving configurations of the same MoE teacher and get bandwidth efficiencies of 33 percent at a concurrency of 1 and 58 percent at a concurrency of 16. Explain the direction of the change. Then say whether this makes the 58 percent configuration a better choice for building a sequence-level corpus, and give one reason the answer might be no despite the higher efficiency.

  6. Your remote-scored forward KL is consistently 0.04 nats above the KL you compute locally from full bf16 logits on the same pairs. Name the two lossy steps in the remote path that could each produce this, say which direction each one biases the estimate and why, and give the two measurements that separate their contributions. Then say what you would report in a paper if you could not afford to run either measurement.

  7. A training run using a served teacher completes 4,000 steps overnight and produces a normal loss curve and a checkpoint. The next morning you find that the server’s log shows it restarted twice during the night. List everything you would check, in order, to determine whether the run is usable, and say what your client would have had to be doing for the answer to be yes. Then write the two lines of client code whose absence caused this problem.



  1. The 32B-class grouped-query geometry used throughout this chapter, 64 layers, 8 KV heads, head dimension 128, follows the Qwen family the course serves: Qwen Team, “Qwen2.5 Technical Report,” arXiv:2412.15115 (2024). https://arxiv.org/abs/2412.15115 Chapter 9 §9.7 derives the 0.262 MB per token figure from that geometry; this chapter takes it as given. 

  2. Woosuk Kwon, Zhuohan Li, Siyuan Zhuang, Ying Sheng, Lianmin Zheng, Cody Hao Yu, Joseph E. Gonzalez, Hao Zhang, and Ion Stoica, “Efficient Memory Management for Large Language Model Serving with PagedAttention,” SOSP 2023, 611-626, arXiv:2309.06180. https://arxiv.org/abs/2309.06180 and https://doi.org/10.1145/3600006.3613165 The paper measures the fragmentation of existing serving systems, introduces the block-table scheme, and reports the throughput gains that follow from the recovered concurrency. 

  3. The scoring-heavy on-policy loop whose ratio appears in §15.10 is the topology of generalized knowledge distillation: Rishabh Agarwal, Nino Vieillard, Yongchao Zhou, Piotr Stanczyk, Sabela Ramos, Matthieu Geist, and Olivier Bachem, “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649 Chapter 12 covers the method; this chapter covers what it costs to run its teacher in another process. 

  4. Chapter 7 §7.3 and §7.4 establish why the tokenization and the chat template have to be applied once, on the client, and why re-tokenizing text on the server side is a silent source of misalignment. 

  5. Chapter 7 §7.4.2 derives the shift convention in full, including why position 0 is never supervised. The wire format’s null and the library’s internal shift are the same fact expressed twice. 

  6. The version-pinning argument applies to evaluation code with equal force. The lm-evaluation-harness maintainers instruct users to cite a specific released version rather than the repository, for exactly this reason: Leo Gao et al., “The Language Model Evaluation Harness,” Zenodo v0.4.3 (July 2024). https://doi.org/10.5281/zenodo.12608602 

  7. Elias Frantar, Saleh Ashkboos, Torsten Hoefler, and Dan Alistarh, “GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers,” arXiv:2210.17323 (2022), ICLR 2023. https://arxiv.org/abs/2210.17323 

  8. Ji Lin, Jiaming Tang, Haotian Tang, Shang Yang, Wei-Ming Chen, Wei-Chen Wang, Guangxuan Xiao, Xingyu Dang, Chuang Gan, and Song Han, “AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration,” arXiv:2306.00978 (2023), MLSys 2024. https://arxiv.org/abs/2306.00978 The activation-magnitude criterion is what distinguishes it from methods that select salient weights from the weight matrix alone. 

  9. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015). https://arxiv.org/abs/1503.02531 The argument that the information lives in the relative probabilities of wrong answers is §2 of that paper and §1.2 of this book; it is the reason a teacher’s small values are worth protecting. 

  10. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945 Their result is the reason I will not assert that a small change in the teacher’s distribution produces a proportional change in the student. 

  11. The corpus-generation workload whose price the 3.05x multiplier corrects is sequence-level knowledge distillation: Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947 Chapter 9 §9.6 prices it; the audit says by how much that price was optimistic for a sparse teacher. 

  12. For a 4-bit format on the training side rather than the serving side, where quantized base weights make a fine-tune fit rather than making a teacher fast, see Tim Dettmers, Artidoro Pagnoni, Ari Holtzman, and Luke Zettlemoyer, “QLoRA: Efficient Finetuning of Quantized LLMs,” arXiv:2305.14314 (2023), NeurIPS 2023. https://arxiv.org/abs/2305.14314 The bytes-per-parameter arithmetic is identical; what differs is which side of the co-tenancy split it applies to. 

  13. The student’s full fine-tuning footprint in Table 15.2 is 16 bytes per parameter, derived in Chapter 8. Low-rank adaptation collapses the optimizer share and changes which rung is tight: Edward J. Hu et al., “LoRA: Low-Rank Adaptation of Large Language Models,” arXiv:2106.09685 (2021), ICLR 2022. https://arxiv.org/abs/2106.09685 

  14. The 360M student in Table 15.2 is from the SmolLM2 family the course uses throughout: Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big, Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737 

  15. Lab 08 Part A·1. The measured 49.7 tokens per second is a published benchmark of a 20-billion-parameter model in MXFP4 on this hardware class, which the lab uses as the input to its detection chain and not as an authority about your machine. 

  16. Lab 08 solution notebook, exercise 4. The chain asserted there is in bytes and, equivalently, in tokens per second, plus the circularity check that returns 49.7 to within

  17. Lab 08 solution notebook, exercise 1. The fit table’s 15 percent headroom rule gives GB usable, against which the bf16 rung’s 90.9 GB plan is the tight one, and the assertion that a 70B bf16 model exceeds 128 GB of weights alone is what rules that rung out entirely. 

  18. Lab 08 solution notebook, exercise 2. The constants are stated modeling assumptions and the notebook says so; the closed form is checked against a grid search over in steps of 0.005, which must agree with the analytic knee to within 0.0075. 

  19. Lab 08 solution notebook, exercise 3. The mock server charges a known 50 ms per request and 0.1 ms per scored token, which is what allows the measured curve to be asserted against a prediction rather than only plotted. 

  20. For the broader on-policy setting in which the scoring loop of §15.11 is the inner loop, see Mingyang Song and Mao Zheng, “A Survey of On-Policy Distillation for Large Language Models,” arXiv:2604.00626 (2026). https://arxiv.org/abs/2604.00626 The survey is a living preprint marked “Ongoing Work” rather than a refereed publication, and should be read as a map of a moving area. 

  21. For the wider method space that the serving topology has to support, see Xiaohan Xu et al., “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024). https://arxiv.org/abs/2402.13116 

Part V · Systems, Judgment, and Research

16

Evaluating a Distilled Model

A distillation run produces a loss curve, and the loss curve almost always looks good.

That is not a coincidence and it is not a sign of success. The objective was constructed so a student with enough capacity can drive it down, the teacher’s targets are smooth and consistent in a way natural text is not, and the corpus was frequently generated by the teacher itself, which makes it the easiest text in the world for a model trained against that teacher to fit. Under those conditions a descending loss is close to a tautology. It tells you the optimizer works.

The problem this chapter exists to solve is that everything else the training loop shows you has the same defect in a subtler form. Top-1 agreement is measured against the teacher. Held-out forward KL is measured against the teacher. The held-out corpus was drawn from the same pool as the training corpus, and if the teacher generated one it generated both. Every number on the dashboard answers the question “are you like the teacher,” and none of them answers “are you good.” A student can improve on all of them while shedding capabilities the teacher has and your corpus never exercised, or while becoming confidently wrong in a way the teacher is not, or while collapsing to shorter and shorter outputs that score beautifully per token because the tokens it never emits cannot be scored at all.

I want to state the thesis of this chapter plainly, because it is a claim about the published literature as well as about your own runs. Most distillation comparisons you will read are weaker than they look, and the reason is nearly always that the evaluation was designed last. The method got months and the eval got an afternoon. The survey literature indexes hundreds of methods and very few evaluation protocols, which is itself a statement about where the field has spent its attention.2223 What follows is the afternoon done properly: three layers of measurement, the specific ways each one lies, a contamination audit that I consider mandatory in this subject and optional in most others, and a gallery of failures you should be able to name from a picture.

16.1 Why the loss is not evidence#

The loss fails as evidence for three separate reasons, and they are worth keeping apart because they bite in different situations and the fixes differ.

16.1.1 Different objectives produce incommensurable losses#

If two runs minimized different functions, their final loss values are values of different functions, and there is no exchange rate between them. Chapter 6 made this concrete on a single fixed teacher-student pair over four tokens: the same disagreement, unchanged, measures 1.8412 nats as forward KL, 2.3256 nats as reverse KL, and 0.4125 as Jensen-Shannon divergence. Present those three numbers as three arms’ final losses and the JSD arm wins by a factor of four.

The JSD number carries no information about student quality, because JSD is bounded above by by construction, so any JSD loss is smaller than almost any KL loss. A table that ranks arms by final loss is ranking objectives by their ceilings, and it reports the same ordering regardless of what the students learned. The literature on divergence choice in language model distillation is a literature about exactly this design axis, so the temptation to compare arms on the loss is strongest where it is least valid.12 Comparisons across arms have to happen on quantities defined independently of any arm’s training objective. Chapter 6 covers the ablation discipline that enforces it; this chapter is about what those quantities should be.

16.1.2 A loss can descend while what you care about degrades#

Even inside a single arm, where the loss is at least a consistent function, a descending curve is compatible with degradation. The reason is dimensional. The loss is one scalar summarizing a comparison between two distributions over tens of thousands of tokens at every position of every sequence in the batch, and scalar summaries of high-dimensional comparisons have enormous level sets, which means very different students produce indistinguishable losses.

The case worth holding in mind is calibration. A distillation loss rewards putting probability where the teacher puts probability, and does not separately check whether the student’s overall confidence is earned by its correctness. Any sharpening the student adds on top of the teacher’s shape shows up as a small penalty on positions where the student is wrong and a small reward on positions where it is right, so if the student is right more often than it is wrong, sharpening is net profitable to the loss and net harmful to you. Section 16.4 gives that failure its full treatment.

16.1.3 Held-out loss on the distillation corpus is still teacher-relative#

This is the argument that people miss, and it is specific to distillation.

Holding out a slice of the corpus and reporting loss on it is standard practice, and in ordinary supervised training it measures generalization. In distillation it measures something narrower: how well the student fits the teacher’s outputs on text drawn from the same distribution as the training text. Both halves of that sentence are constraints. The targets are the teacher’s, so anything the teacher gets wrong is scored as correct. The inputs are from your corpus, so any behavior your corpus does not elicit is not measured at all.

Now compose those two constraints with the way distillation corpora are usually built. In sequence-level distillation the teacher generated the corpus, so the corpus is drawn from the teacher’s own output distribution, which is the distribution the student is being trained to match.3 In trace fine-tuning the same holds with the reasoning traces included.4 The held-out slice is then text the teacher produced, scored against the teacher’s own preferences. It is close to the easiest possible test, and a student can pass it while being worse than its starting checkpoint on anything outside that pool. There is a third constraint stacked on the other two: the held-out loss is computed under teacher forcing, with every position conditioned on reference context and not on the student’s own previous tokens, so it never asks the question that decides whether generated text holds together.26

Watch out

The metrics that improve during training are all measured against the teacher. That is the structural fact behind every failure in this chapter. A student scoring well on everything the training loop watches can be degrading in exactly the two ways teacher-relative measurement cannot see: degradation the teacher shares, and degradation that lies outside the agreement question entirely.

The remedy is not to stop logging held-out loss. It is cheap and it catches gross bugs. The remedy is to stop treating it as evidence about the student and start treating it as evidence about the optimization.

16.2 An evaluation in three layers#

An evaluation you can defend has three layers, and they do different jobs. Skipping any of them produces a characteristic weakness in the write-up.

Layer one: a standard benchmark subset, for comparability. Tasks other people also run, scored the way other people score them, so your number sits on a scale a reader already understands. This layer will not tell you why anything happened.

Layer two: custom probes, for the behavior you actually care about. Prompts drawn from the domain you are shipping into, scored against criteria you wrote down before looking at any output. This layer is the only part of the evaluation that measures the thing the project exists to do.

Layer three: diagnostics, for explanation. Agreement, calibration, entropy, diversity, length. These are not scores and should never be reported as if they were. Their job is to explain a movement in layers one and two, or to reveal that a movement you are pleased about has a cause you would not endorse.

16.2.1 The standard subset#

The reference tool is EleutherAI’s lm-evaluation-harness, which is the de facto standard runner for open language model benchmarks and the reason numbers from different groups are ever comparable.5 Lab 11 uses it on three tasks, and the command shape is worth reading closely.

lm_eval --model hf --model_args pretrained=<ckpt>,dtype=bfloat16 \
        --tasks hellaswag,arc_easy,winogrande --batch_size 16 \
        --output_path ../runs/lab11/bench/<name>.json

The selection criterion the lab states is that the tasks are small enough to run in minutes and standard enough that the numbers are comparable to published ones. Both halves matter. A benchmark you cannot afford to run on every checkpoint gets run once, at the end, when its result can no longer change a decision, and a benchmark nobody else runs gives you a number with no external referent.

Three details in that command turn into reporting obligations. dtype=bfloat16 is part of the measurement, because the same checkpoint scored in bf16 and in fp32 can differ on tasks decided by small logit margins. --output_path writes the full result record including per-task standard errors, which are the input to §16.10 and the first thing a reader of a benchmark table should want and rarely gets.

And the version of the runner is part of the citation. Software changes task definitions, prompt formats, and normalization between releases, so a HellaSwag number from one version is not always the same quantity as a HellaSwag number from another. The repository’s own guidance is to cite a pinned version with its archival DOI, and the honest thing is to record the exact version you ran alongside the numbers it produced.5 The same rule applies to the model versions on both sides of the distillation: teacher checkpoints get revised, and a comparison against a teacher identified only by family name is not reproducible.67

16.2.2 Custom probes#

The standard subset is out of domain for almost every distillation project. You distilled a student for a purpose, and multiple-choice commonsense tasks are unlikely to be that purpose. A student can be excellent at your purpose and mediocre on the standard subset, or the reverse, and neither outcome is a surprise if you were paying attention when you chose the corpus.

A probe set is a small collection of inputs that exercise the behavior you care about, with a scoring rule fixed in advance. Fixing the rule in advance is what separates a probe from a demo: if you write the criteria after reading the outputs, you will write criteria the outputs satisfy, and you will not notice yourself doing it.

Probes catch a failure the standard subset structurally cannot, which is capability that is present in the teacher, absent from your corpus, and therefore untransferred. If your corpus is all single-turn instructions, multi-turn behavior is not in the training signal, and no benchmark in the standard subset will tell you that your student cannot hold a conversation. Twenty multi-turn prompts will tell you in ten minutes.

16.2.3 Diagnostics, and the disagreement patterns#

Chapter 8 introduced the operator’s working set: top-1 agreement, forward KL on held-out data, expected calibration error, and mean entropy, computed every hundred steps while a run is going. The post-hoc versions are the same quantities computed once, carefully, on a fixed evaluation slice, and their role changes. Mid-run they are an early warning. After the fact they are an explanation.

The explanatory power lives in the disagreements between columns. Columns that agree tell you nothing the training loop did not already say. Three patterns recur often enough to name, and Lab 11’s Part C is organized around them.

Agreement rises, benchmarks flat. Normal and honest. The student moved toward the teacher inside the training domain, and the benchmarks are out of domain, so they were never expected to move much. This is not a failure unless the benchmarks fell.

Benchmarks fell where agreement rose. This is the tax: general capability traded for imitation. Expect a mild version in pure soft-target arms and in reverse-KL arms, because mode-seeking recipes shed tail behaviors, meaning the rarely-used capabilities that live in the low-probability parts of the distribution. A large drop is over-distillation, and the two remedies are to shorten training or to raise the hard-label term so that real data pulls back against imitation. A large drop that does not respond to either is worth checking against the capacity gap instead: past some ratio a bigger teacher produces a worse student, and the signature is that the distillation signal stopped supplying useful gradient, not that it supplied too much.25

Definition

Tail behaviors

The rarely-exercised capabilities that live in the low-probability regions of a model’s output distribution. They contribute almost nothing to a token-level loss and are the first thing a mode-seeking objective discards, which makes them invisible to the training loop and visible only to an evaluation that asks for them directly.

One benchmark up wildly. Check the contamination flags before celebrating. A distilled student that memorized its teacher’s phrasing, combined with an evaluation set that shares text with the distillation corpus, is exactly how a headline claiming that a small model beat a frontier one gets produced in good faith. §16.7 is about this.

16.3 Agreement, and what it hides#

Top-1 agreement is the most direct answer to “did anything transfer,” and it is the diagnostic most often reported without its caveat. Here it is precisely, including the masking, because the masking is where implementations go wrong.

Let be the student’s logit for vocabulary entry at position , the teacher’s, and the completion mask, which is 1 at positions the loss supervises and 0 at prompt positions, padding, and anything else excluded. All three are already shifted so that row holds the prediction for the token at position , which is the convention Chapter 7 derives. Then

The masking and the comparison are both load-bearing. The mask must be shifted with the logits, or the numerator and denominator are computed over different position sets and the number is quietly wrong. And the comparison is student argmax against teacher argmax, not student argmax against the reference token, which is a different quantity that some code calls agreement and that answers a different question.

What agreement is good for: it is bounded in , it has a meaningful zero, it moves visibly over a short run, and it requires only two forward passes. As an answer to “is the pipeline doing anything at all,” nothing is cheaper.

What agreement hides is everything below rank 1. Consider a two-token vocabulary at a single position. The teacher puts 0.51 on token A and 0.49 on token B. The student puts 0.99 on A and 0.01 on B. Agreement at this position is 1: both argmaxes are A. The forward KL is

which is more than twice , the entire entropy range available to a two-outcome distribution. The teacher’s entropy here is 0.6929 nats and the student’s is 0.0560 nats, a factor of twelve. Perfect agreement, a completely different distribution, and every quantity that depends on distribution shape (calibration, entropy, diversity of sampled text) is wrecked.

Scale that position up to a whole evaluation set and you get the failure mode this chapter’s title case is built around: a student whose agreement improves monotonically while its calibration degrades monotonically. Agreement cannot see it, because agreement is an argmax comparison and the argmax is exactly what is not changing.

The second caveat is about direction, not blindness. Agreement measures fidelity to the teacher, and fidelity is not quality. Stanton and colleagues measured this directly and found students that generalized better than their agreement with the teacher would predict, in settings where the student had the capacity to match the teacher and the optimizer was given every advantage.8 Treating agreement as the objective instead of as an instrument would have called those students failures.

16.3.1 Length-stratified evaluation#

There is a third caveat, and it is the one that has cost me the most, because it does not look like a caveat. It looks like a finding. An agreement number computed over an evaluation set is an average over positions, and positions are not interchangeable. Sort the set by completion length, report each third separately, and the number moves.

The method is four lines of bookkeeping. Completion length is mask.sum() per row, the count of positions the loss is allowed to supervise. Sort the 256 eval rows by that count and cut the sorted list into thirds: short, medium, long. From each third take 20 rows spaced evenly through it with torch.linspace, so a stratum spans its own range instead of clustering against the boundary it shares with the next one. Score each stratum with the identical shift-then-mask discipline used for the headline number, logits[:, :-1].argmax(-1) compared against the teacher’s argmax under mask[:, 1:], and print three agreements and their spread. Two assertions keep the strata interpretable: 20 rows and more than 200 supervised positions in each, and an agreement inside , which is the band where the number is informative and not degenerate.

Solutions 03 runs this live, and because no trained checkpoints exist on the build machine it runs on the released pair: SmolLM2-360M-Instruct in the teacher role, SmolLM2-135M-Instruct in the student role, which is the starting line of the gap-small arm and the agreement a distillation run would have to improve on. The short and the medium strata landed within a fraction of a point of each other. The long stratum ran about 3 points higher. The printed spread across strata came out just under 0.03, against a threshold of 0.02 above which the cell prints “length matters.”

Three points is larger than several published distillation effects, and it is not an effect. Both models are frozen. Nothing was trained. The number moved because per-position difficulty is not uniform along a completion. The first few tokens after a prompt are the highest-entropy positions in the set, because many continuations are still plausible and the model has to choose among them. Deep inside a long completion the local context does most of the work: the sentence being finished, the list item being continued, the code block being closed. Those positions are pinned down for models of any size, and two models of different sizes will agree on them. Short completions are made almost entirely of hard early positions. Long completions dilute them with easy late ones. A per-token agreement average therefore favors long strata mechanically, and an unstratified average over a set with mixed lengths is a length-weighted average wearing a plain name. Change the length distribution of your eval set, change your headline number, with no model involved.

That has a consequence for reading a KD result, and it is the reason the stratification is worth its twenty lines. The quantity a distillation comparison cares about is the distilled arm’s agreement minus the hard-label arm’s, stratum by stratum, and Lab 03’s expectation, registered before its gated runs, is that this gap is larger on the long stratum than the short one by a factor of about 1.5 to 3 on this model family. The reasoning has two parts. Hard labels supervise one token per position and say nothing about the alternatives, so on late positions that the local context already pins down there is nothing left for a soft target to add; what the soft target contributes is the teacher’s ranking among plausible continuations, which is what mid-completion positions with several defensible next tokens need. And the in the masked mean that Chapter 5 derives applies during training too, so a long row contributed more of the distilled student’s gradient budget than a short one did. The advantage should concentrate where the information was and where the gradient went.

Which gives the stratified table a second job, as a diagnostic. If your trained table shows the KD advantage concentrating on short completions, the most likely explanation is not that your run found something new about length. It is prompt leakage into the mask: prompt positions being supervised, which inflates agreement on rows with long prompts and short completions, because both models reproduce prompt tokens they can see. Re-run the four-point mask audit that Chapters 7 and 8 both insist on before believing the table. An eval whose strata disagree in the wrong direction is reporting on your masking code.

16.4 Calibration#

Definition

Calibration

The property that a model’s stated confidence matches its realized accuracy. A calibrated model that says it is 80 percent sure is right 80 percent of the time, across the whole set of predictions where it said 80 percent. Calibration is a property of the relationship between two quantities, so a model can be accurate and badly calibrated, or inaccurate and well calibrated.

The second sentence of that definition is the one people skip and the one that makes calibration worth measuring separately. Accuracy and calibration are different axes. A model that is right 65 percent of the time and says so is calibrated. A model that is right 65 percent of the time and claims 95 percent confidence is the same model in accuracy terms and a much more dangerous one to deploy, because every downstream system that routes on confidence will route wrong.

16.4.1 Why a distilled model’s calibration moves on its own#

Distillation gives calibration two mechanisms to move independently of accuracy, and both push the same direction.

The first is the objective. A soft-target loss teaches the shape of the teacher’s distribution through a temperature-softened comparison that deliberately flattens both sides before measuring.9 Nothing in that comparison pins down the student’s sharpness at , which is the sharpness that shows up at serving time, so the student can match the teacher’s relative ordering at every rank while sitting at a different overall temperature.

The second is the direction of the divergence. A mode-seeking objective rewards concentrating mass where the teacher’s mass is highest and imposes almost no penalty for abandoning the tail, which means the student’s distributions come out sharper than the teacher’s on exactly the positions where the teacher was uncertain. Uncertain positions are where calibration is decided. Chapter 6 derives the mechanism; the consequence for evaluation is that reverse-KL and skew-KL arms should be expected to calibrate worse than forward-KL arms at equal accuracy, and the eval has to be able to see it.2

There is a counterweight worth naming because it is the standard fix. A hard-label term in the mixed objective pulls confidence back toward correctness, because it keeps putting mass on tokens that actually occurred instead of on tokens the teacher preferred. This is the same mechanism by which label smoothing improves a classifier’s calibration, and Müller and colleagues document both the calibration benefit and its cost to distillation in the same paper, which is a useful reminder that the two effects are not independent knobs.10

16.4.2 Expected calibration error, and the binning that makes it a number#

You cannot observe the accuracy of a single prediction. A prediction is right or wrong; the probability of being right is not a property you can read off one outcome. So calibration is only measurable in aggregate, and the aggregation is what turns a concept into a number.

Definition

Expected calibration error

The average gap between a model’s confidence and its accuracy, taken over confidence bins and weighted by how many predictions fall in each. Chapter 8 introduced it as a number an operator watches during a run; here it is stated with its bins named, because the bins are what turn the concept into a number and are also where the number can be gamed.

Written out, with the bins visible:

where is the set of predictions falling in bin of confidence bins, is how many there are, is the total, is the model’s confidence on a prediction, and is the fraction of predictions in the bin whose argmax matched the reference. Lower is better, and zero means confidence and accuracy agree within every bin.

The formulation is Guo and colleagues’, who used it to show that modern deep networks are substantially more miscalibrated than their predecessors and that a single temperature parameter fitted on a validation set removes most of the gap.11 Their paper is also the source of the picture that goes with it.

Definition

Reliability diagram

A plot of empirical accuracy against confidence, with confidence on the horizontal axis divided into bins and the bar height in each bin equal to that bin’s accuracy. A perfectly calibrated model lies on the diagonal. The signed area between the bars and the diagonal, weighted by bin occupancy, is the expected calibration error, which makes the diagram a picture of the number and not an illustration next to it.

2026-08-01T07:20:06.854692 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.0 0.2 0.4 0.6 0.8 1.0 predicted confidence 0.0 0.2 0.4 0.6 0.8 1.0 observed accuracy ECE = 0.038 bars sit on the diagonal within sampling noise: the slivers are all the ECE well calibrated 0.0 0.2 0.4 0.6 0.8 1.0 predicted confidence 0.0 0.2 0.4 0.6 0.8 1.0 ECE = 0.205 shaded: |accuracy - confidence| per bin. ECE = sum of these gaps, weighted by bin occupancy (ten equal-mass bins, 40 each) all bins fall below the diagonal overconfident (same logits, scaled 3x) 400 positions, 50-way vocabulary, seed 1; accuracy 0.677 in both panels (scaling logits preserves every argmax)
Figure 16.1 A reliability diagram makes expected calibration error visible as area: the well-calibrated model's bars sit on the diagonal, the overconfident model's bars sit below it in every high-confidence bin, and the shaded gap between them is the quantity ECE sums.

Now the honesty clause, which the formula does not advertise. ECE is sensitive to binning, and in two separate ways.

The first is a choice: equal-width bins versus equal-mass bins. Equal-width bins split into intervals of size , and since a language model’s confidences pile up near the top of the range, most of those bins come out nearly empty while one or two carry almost all the mass. Equal-mass bins split by quantile so every bin holds predictions, which puts the resolution where the data is. The two conventions give different numbers on the same predictions, neither is wrong, and comparing an ECE computed one way against an ECE computed the other way is not a comparison.

The second is arithmetic, and it sets a floor you cannot beat. The estimator sums absolute values, and absolute values do not cancel, so sampling noise in each bin’s accuracy contributes positively and a perfectly calibrated model reports a nonzero ECE from finite-sample noise alone. Compute the size of it. With scored positions in equal-mass bins, each bin holds 1,000 predictions, and if a bin’s true accuracy is 0.6 the standard error of its empirical accuracy is . The expected absolute deviation of a roughly normal quantity with standard deviation is , so each bin contributes about 0.0124 and the weighted average of those contributions is about 0.012. A perfectly calibrated model scores ECE at this and this .

Raise to 100 with unchanged. Each bin holds 100 predictions, the standard error becomes , and the expected contribution rises to about 0.039. The floor roughly tripled, which is , because bin occupancy fell by a factor of ten, and nothing about the model changed.

That is the whole argument for stating your bin count, and it gives you a free sanity check: compute the noise floor for your and before interpreting a difference in ECE between two arms. A gap of 0.01 between two students scored on 10,000 positions in 10 bins is inside the floor and is not a finding.

Here is the working version, short enough to audit, with the binning made explicit instead of buried in a default.

def ece(conf, correct, n_bins=10, scheme="equal_mass"):
    """conf: confidence per scored position. correct: 0/1 per scored position.
    Both are already flattened over masked positions only."""
    order = conf.argsort()
    conf, correct = conf[order], correct[order].float()
    N = conf.numel()

    if scheme == "equal_mass":                       # quantile edges: every bin has N/B
        edges = [round(i * N / n_bins) for i in range(n_bins + 1)]
        groups = [(edges[i], edges[i + 1]) for i in range(n_bins)]
    else:                                            # equal width on [0, 1]
        import torch
        cuts = torch.linspace(0, 1, n_bins + 1)
        groups = [(int((conf <= cuts[i]).sum()), int((conf <= cuts[i + 1]).sum()))
                  for i in range(n_bins)]

    total = 0.0
    for lo, hi in groups:
        if hi <= lo:
            continue
        w = (hi - lo) / N
        total += w * abs(float(conf[lo:hi].mean()) - float(correct[lo:hi].mean()))
    return total

What that proves is that the bin scheme and the bin count are arguments, not constants. An implementation that hard-codes them produces a number whose definition its caller cannot see, and two such numbers from two papers are not comparable even when both are labeled ECE. Minderer and colleagues revisited calibration across a wider range of architectures and found the relationship between model family, accuracy, and calibration is not the fixed one the earlier result was often read as establishing, which is one more reason to treat an ECE as a measurement with a method attached, and not as a property of a model.12

Watch out

“Correct” in next-token ECE means “the argmax matched the token that actually occurred,” which is a harsh standard. At a genuinely ambiguous position a well-calibrated model should be unconfident and will be scored wrong most of the time. The absolute level of an ECE on a language modeling probe is therefore not comparable to an ECE on an image classifier, and what you are reading is its direction and its difference across arms, not its value.

16.4.3 The measurement, and the trap inside it#

Lab 11 demonstrates the calibrated-versus-overconfident contrast on synthetic predictions, and the way it is constructed is more instructive than the result.

The construction: 400 positions over a 50-way vocabulary, logits drawn from a standard normal with a fixed seed. Labels are taken as the argmax, then corrupted on a random 35 percent of positions, which pins accuracy at approximately 0.65 by construction. Sixty candidate temperature scales are searched over , and the scale whose mean confidence lands closest to 0.65 is selected. That gives a baseline whose mean confidence matches its accuracy. The logits are then multiplied by 3.0, which sharpens every distribution without changing any argmax, so accuracy is exactly unchanged. The lab asserts that ECE rises by at least 0.05.

The trap is in why the calibration step is needed at all. ECE measures the gap between confidence and accuracy in absolute value, so if the baseline had been underconfident, meaning mean confidence below accuracy, sharpening it would have moved confidence toward accuracy and ECE would have fallen. The demonstration would have run backwards and appeared to prove that overconfidence improves calibration. Sixty scale evaluations, costing milliseconds, are what stand between the demonstration and that outcome.

Field note

This is a specific instance of a general habit that has saved me more time than any other in this chapter. Before you use a metric to compare two things, construct a pair whose correct ordering you know by argument instead of by measurement, and check that the metric orders them that way. Here the argument is “same accuracy, more confidence, therefore worse calibration,” which cannot be wrong. If your ECE implementation disagrees with that pair, the implementation is broken, and you have learned it in one second instead of after writing a results section around it.

16.5 The entropy trajectory, read after the fact#

Chapter 12 used entropy as a live monitor, where the question is “should I stop this run.” The post-hoc question is different: given a finished run and its logged entropy history, what does the shape tell you about the student you now have.

Mean entropy over supervised positions, in nats, is

with the student’s probability for token at position . A uniform distribution over tokens has entropy , which for the 49,152-token SmolLM2 vocabulary is 10.80 nats, and a deterministic distribution has entropy 0.6 Real models live in a narrow band well below the uniform value, so the level carries less information than the shape.

Entropy trajectories come in four shapes on this kind of run, and each says something different about the finished artifact.

Smooth decline to a plateau. The student became more decisive and then stopped. The plateau is the informative part, because it says the narrowing reached an equilibrium instead of running away. A run whose entropy plateaued is a run whose final checkpoint is probably the one you want.

Decline that never flattens. Entropy is still falling at the last logged step, so you stopped the run at an arbitrary point on a moving trajectory. Whatever you measure about this student is a property of a moment rather than of a converged state, and rerunning at a different step count will give a different answer. This is the most common reason two groups distilling the same pair report different orderings.

Decline that accelerates. The second derivative changed sign in the wrong direction. Chapter 12 covers the mechanism; the post-hoc reading is that the checkpoint you kept is downstream of the onset and the good weights are somewhere in the discarded history. The entropy-collapse literature in the reinforcement learning setting characterizes this mechanism carefully, and the transfer to distillation is by analogy of shape, not of driving term.1314

Entropy roughly flat across the whole run. Either nothing happened, because the learning rate or step count was too small to move the student, or the student was already at the teacher’s sharpness at initialization. Agreement separates them: flat entropy with rising agreement is the second case, flat entropy with flat agreement is the first.

The trajectory belongs in an evaluation chapter because it is evidence about the number you are about to report. A benchmark score from a plateaued run is a property of a converged student; the same score from a still-declining run is a property of a student and a step count jointly, and the write-up has to say so.

16.6 Diversity#

Per-position metrics cannot see repetition across positions, and repetition across positions is one of the characteristic distillation pathologies. Two metrics are standard, both borrowed from the text generation literature, and both have failure modes serious enough that I would not report either alone.

Definition

distinct-n

The fraction of generated -grams that are unique. For a collection of samples , pool all the -grams from all samples, count the distinct ones, and divide by the total:

where is the multiset of -grams in sample and . The measure is due to Li and colleagues, who introduced it to quantify the tendency of neural conversation models to produce generic responses.15

Definition

self-BLEU

The average similarity of each generated sample to the rest of the collection, computed by treating one sample as a hypothesis and all the others as references:

High values mean the samples resemble one another, so lower is more diverse, which is the opposite polarity to distinct-n. The measure comes from Zhu and colleagues’ Texygen benchmarking platform.16

16.6.1 The failure modes, one each#

distinct-n is length-sensitive, and severely. The denominator grows linearly with the number of generated tokens while the numerator saturates, because a model with a finite repertoire eventually reuses -grams. So distinct-n falls with sample length for a fixed model, and comparing two models whose generations differ in length is comparing lengths.

Here is the hand-computable version, which is the metric audit habit from Chapter 6 applied to a different question. Take the six-token sample red blue green red blue green. Its bigrams, in order, are (red,blue), (blue,green), (green,red), (red,blue), (blue,green): five bigrams, of which three are distinct. So distinct-2 is . Now truncate the same sample to its first four tokens, red blue green red. Its bigrams are (red,blue), (blue,green), (green,red): three bigrams, all distinct, so distinct-2 is .

Same text, same generator, same metric, and the number moved from 0.6 to 1.0 because of where the sample was cut. If your length-collapsed arm generates 40 tokens and your healthy arm generates 90, the length-collapsed arm scores as the more diverse one. When arms differ in length, truncate every sample to a common length before scoring, and say that you did.

self-BLEU is expensive and sensitive to sample count. The cost is quadratic: scoring samples requires hypothesis-reference BLEU evaluations, so 32 samples is 992 evaluations and 200 samples is 39,800. That is why self-BLEU is computed on tens of samples rather than thousands, and why the number is noisy.

The sample-count sensitivity is structural, not incidental, and it follows from how BLEU counts. BLEU’s modified precision clips each hypothesis -gram’s count by the maximum count that -gram achieves in any single reference. Adding a reference can only raise that maximum, never lower it, so modified precision is non-decreasing in the size of the reference set. Since self-BLEU uses every other sample as a reference, growing grows every hypothesis’s reference set, and self-BLEU rises with for a fixed model. Two self-BLEU numbers computed at different sample counts are therefore not comparable, in a direction you can predict but not correct for.

Both can be gamed by noise. A model emitting uniformly random tokens attains distinct-n near 1.0 and self-BLEU near 0.0, the best possible score on both. Both metrics measure non-repetition, and non-repetition is necessary for diversity and nowhere near sufficient. This is not a hypothetical: a student whose entropy was driven up by an unstable objective, or one sampled far above the temperature it trained at, produces exactly this signature. Diversity metrics must be reported next to a quality measurement, because a diversity improvement with a quality decline is a degradation with good publicity.

The decoding settings are part of the measurement, for the same reason. Greedy decoding takes the argmax at every step, which discards everything except the top token’s identity, so a high-entropy student and a low-entropy student that agree about the argmax produce identical greedy output and identical diversity scores. Nucleus and top- truncation remove exactly the tail that distinguishes divergence arms.17 Sample at the temperature the objective trained at, state the decoding configuration, and treat it as a research variable and not a formatting detail.

16.7 Contamination#

Definition

Contamination

The presence, in an evaluation set, of examples that also appear verbatim or nearly verbatim in the training data. A contaminated evaluation measures memorization and reports it as capability.

Contamination is a general hazard in machine learning and a specific one in distillation, for two compounding reasons.

The first is about the corpus. In sequence-level distillation and trace fine-tuning, the teacher generated your training corpus.34 You did not choose its content token by token; you chose prompts and let a large model write. If that teacher was trained on the benchmark you are about to report, benchmark content can flow into your corpus through the teacher’s weights without anyone downloading a benchmark file. Your provenance records are clean and your corpus is contaminated anyway. This is a route that does not exist when you assemble a corpus by hand from sources you audited.

The second is about the student. Distillation trains a model to reproduce a teacher’s phrasing and is unusually effective at it, so a contaminated evaluation rewards precisely the memorization distillation produces. The score inflates for the wrong reason, and it inflates more for a distilled student than it would for a conventionally trained one at the same capability. The two effects multiply: distillation raises both the chance of contamination and the payoff from it. The consequence is a reporting obligation. A distillation result without a contamination audit has not established that its headline number means what it says.

16.7.1 The n-gram overlap method#

Definition

n-gram overlap

A contamination detector that treats each row as its set of -token windows and scores an evaluation row by the largest fraction of its windows that any single training row also contains. It detects shared provenance, not semantic similarity: an -gram collision at sufficient means the two rows came from the same source text, not that they are about the same subject.

For a token sequence , define the set of -grams

For each evaluation row scored against a training corpus ,

Lab 11’s defaults are and .

Read the denominator carefully, because it encodes a design decision. It is the evaluation row’s gram count, not the training row’s and not the union. The score asks what fraction of this evaluation row appears somewhere in a single training row, which is the right question for contamination and is not diluted when the matching training row is long. A symmetric measure such as Jaccard similarity divides by the union and would score a short evaluation row against a long training row as barely overlapping even when the evaluation row sits entirely inside it.

The maximum over , in place of a sum, is the other decision. One matching training row is enough to contaminate an evaluation row, and the fact that it also partially matches four hundred others is a statement about the corpus and not about that row’s independence.

16.7.2 Choosing , and the measured knee#

Short -grams collide by chance in any two same-domain texts, because stock phrases and instruction boilerplate repeat everywhere. Long -grams do not. Between those two regimes there is a value of where chance collisions die out, and the way to find it is to sweep instead of inherit a number from a paper written about a different corpus.

Solutions 11 runs the sweep on the course’s own data: 512 training rows against 128 evaluation rows, content tokens only, threshold fixed at 0.3, from 4 to 12, everything else held. Two curves come out of it and they answer different questions.

The flagged fraction is the operational curve. It says how many evaluation rows you would discard at each . On this corpus it is nonzero at and , where exactly one evaluation row (row 6) trips the threshold, and it is zero from through . The knee, defined as the smallest whose flag count has already reached the level it holds through the end of the sweep, is therefore at .

Definition

Knee of a sweep

The smallest parameter value at which a swept quantity has already reached the level it holds for the rest of the sweep. It is the point past which further tightening buys nothing on that quantity, and it is where an inherited hyperparameter should be checked against a measured one.

The mean best-overlap score is the mechanistic curve. It is the average over evaluation rows of the highest overlap any training row achieves, and in a corpus with no true duplicates it is made entirely of coincidences. It falls monotonically from about 0.082 at to about 0.009 at , a nine-fold collapse, which is what “chance overlap dies out” looks like as a continuous quantity and not as a step in a flag count.

2026-08-01T07:20:07.922595 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 4 5 6 7 8 9 10 11 12 n-gram size n 0.00 0.02 0.04 0.06 0.08 fraction knee (n = 6): flag count reaches its floor lab's choice (n = 8): two n-gram sizes of margin chance collisions from stock phrasing mean best-overlap score flagged fraction of eval rows (threshold 0.3): 1 of 128 at n = 4 and 5, zero from n = 6 on 0.082 0.009 endpoints measured (Solutions 11 Ex1); interior of the overlap curve is the geometric chance-collision model fitted through them
Figure 16.2 Mean best n-gram overlap falls nine-fold between n = 4 and n = 12 while the flag count reaches zero at n = 6, so the region below the knee is where chance collisions from stock phrasing masquerade as contamination and the lab's choice of n = 8 carries two n-gram sizes of margin past it.

The verdict is that the lab’s choice of 8 is justified, not corrected, and the sweep says something more useful than “8 was fine”: it prices the margin. Two -gram sizes of headroom past the knee is protection against corpus-specific stock phrasing that this particular corpus happens not to have much of.

Raising is not free, which is the part that gets forgotten. A true near-duplicate with small edits shares fewer long -grams than short ones, because a single changed token destroys different -grams. So sensitivity to real contamination falls as rises, and the planted near-duplicate test in the next section is the guard on that side of the trade. On a corpus with longer boilerplate, template-heavy code being the obvious case, the knee moves right, and the sweep is a ten-second measurement that finds it before a threshold gets copied blindly from someone else’s setting.

16.7.3 The story, in eight steps#

This is the best story in the course and I am going to tell all of it, because every step is a different lesson and the last one is the one people get wrong.

One: the naive design. Compute 8-gram overlap between every evaluation row and every training row over the raw token sequences, with padding stripped, and flag any evaluation row whose best-matching training row shares more than 30 percent of its 8-grams. Nothing about that is unreasonable on its face. It is what I would have written.

Two: the false-positive blow-up. The naive checker flags a large fraction of perfectly honest evaluation rows. Not a handful. A fraction large enough that the lab encodes it as an executable assertion, assert naive_rate > 0.05, so that the failure is reproduced on every run instead of described in a comment. The printed line calls it the template scaffold screaming, not contamination.

Three: the build of this course hit exactly that. This was not constructed as a teaching example after the fact. The checker was written, it was run, and it condemned an evaluation set that was fine. Keeping that in the notebook as an assertion instead of deleting it is a deliberate choice, and it is why the lab can claim that a detector is only trusted after it has fired correctly.

Four: the diagnosis. Every chat-templated row shares the same system prompt and the same role scaffolding. That is dozens of identical tokens per row, at fixed positions, in every single row of both sets. Raw 8-gram overlap over those sequences is measuring the template. Chapter 7 covers what a chat template inserts; the point here is that the scaffolding is by construction identical across rows, so any detector that includes it is guaranteed to find matches and guaranteed to find them everywhere.

Note what the failure is not. It is not a threshold that was set too low. Lowering the flag rate by raising would suppress the symptom and would also suppress real contamination, because both signals are being computed on the same corrupted input. The detector was measuring the wrong thing, and no amount of threshold tuning fixes a measurement of the wrong thing.

Five: the fix, which is a mask. Compare content tokens only, meaning the completion tokens under the mask. The prompt scaffolding is excluded by exactly the same boolean array that excludes it from the loss.

This is the fourth time the same mask has decided whether a computation is correct. It selects the supervised positions for the loss (Chapter 7). It selects the correctness targets for expected calibration error, where passing the label tensor with its ignore-index sentinels instead produces an enormous meaningless number (Chapter 8). It selects the generated positions when scoring an on-policy rollout (Chapter 12). And now it selects the tokens a contamination checker is allowed to look at. Four distinct computations, four silent failures if you get it wrong, one array. That pattern is worth naming as a habit: whenever you compute a statistic over a sequence, ask which positions are supposed to count, and then ask whether the code in front of you knows.

Six: validation by planting evidence. A detector that has never fired correctly is a detector you are guessing about. So the lab manufactures a positive: it copies training row 7, increments the single token at the middle of the row by 1, appends the result to the evaluation set, and asserts that the checker flags it. One token changed out of a full row, which is the hardest near-duplicate to catch at because the edit destroys eight -grams around it and leaves everything else intact. The checker catches it, and its score is printed instead of hidden behind the assertion.

Seven: validation by silence on honest rows. The maximum and mean best-overlap over the honest evaluation rows are printed. This is the half of detector validation that people skip, and it is the half that catches the version of the bug that had occurred one step earlier: a detector that fires on everything passes step six perfectly.

Eight: the residual flags are findings, not bugs. After the fix, a small number of evaluation rows are still flagged. The instinct at that moment is to assume the checker is still broken and keep tuning until the count reaches zero, and that instinct is wrong. Synthetic instruction corpora genuinely do ship near-duplicate rows. The checker was right. The remediation is to drop the flagged evaluation rows, re-run the check, and assert that the filtered set comes back clean, which it does.

The lab’s own framing of that last step is the sentence I want the reader to keep: the residual flagged rows are real near-duplicates in the source corpus, dropped from the evaluation as remediation, not embarrassment. The filtered evaluation set is then the one every later benchmark in the course uses, and the drop list is written to a JSON artifact next to the results so that any later number can be traced back to the evaluation set it was computed on.

So the checker fired twice in one cell: once incorrectly, on template scaffolding, and once correctly, on genuine duplicates. Both firings are load-bearing. The first taught what the detector was actually measuring, and the second is the only reason to believe the fixed version measures anything at all.

Here is the mask-aware version. What to look at: the mask is applied when the token list is built, before any -gram is formed, so no later stage can accidentally reintroduce the scaffolding.

def ngrams(ids, n):
    return {tuple(ids[i:i + n]) for i in range(len(ids) - n + 1)}

def content_tokens(ids, mask):
    """Completion tokens only. The same mask the loss uses, applied before anything
    is counted, so the chat template cannot reach the n-gram sets."""
    return [int(t) for t, keep in zip(ids.tolist(), mask.tolist()) if keep]

def contamination(train_rows, eval_rows, n=8, threshold=0.3):
    train_grams = [ngrams(r, n) for r in train_rows]
    scores = []
    for er in eval_rows:
        eg = ngrams(er, n)
        best = max((len(eg & tg) / max(1, len(eg)) for tg in train_grams), default=0.0)
        scores.append(best)                      # denominator is the EVAL row's grams
    return scores, [i for i, s in enumerate(scores) if s > threshold]

# The detector is not trusted until it has fired on a case with a known answer.
plant = list(train_rows[7]); plant[len(plant) // 2] += 1      # one token changed
scores, flagged = contamination(train_rows, eval_rows + [plant])
assert len(eval_rows) in flagged, "the checker must catch a planted near-duplicate"

What that proves is that the whole method is about twenty lines and that the twenty lines are not where the difficulty is. The difficulty is entirely in what you feed it, which is why the fix in step five is one function and the story around it is eight steps.

Lab 11 renders four shapes on one page, from closed-form generators over arange(0, 2000, 25), and recommends printing it and taping it next to the monitor, with no irony intended. I am adding a fifth here because the brief for this chapter requires it and because it is real.

2026-08-01T07:20:09.372062 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 1k 2k step 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 entropy, nats plateau: decline with a floor entropy generation length (right axis) healthy 0 1k 2k step decline accelerates after step 1200 entropy collapse 0 1k 2k step only the length track moves LengthMonitor fires, step 525 length collapse 0 1k 2k step ECE climbs while entropy declines ECE (right axis) calibration drift 0 50 100 0.0 0.1 0.2
Figure 16.3 The four gallery shapes as small multiples, each with the trajectory that identifies it: healthy convergence plateaus, entropy collapse accelerates downward after a healthy-looking start, length collapse falls on the length axis while entropy stays healthy, and calibration drift shows ECE climbing while entropy declines normally.

Healthy convergence. Entropy declines smoothly to a plateau, generation length rises slightly and settles, agreement rises, ECE is flat or falling. The lab’s generator is with . The plateau is the point of the shape: decline with a floor. In text it looks like output that gets more consistent and stays the same length.

Entropy collapse. The decline accelerates instead of flattening. The generator matches the healthy curve until step 1200, then decays with a time constant of 180 instead of 900. In numbers, the second derivative is the discriminator: a healthy curve’s decline is slowing down, a collapsing curve’s is speeding up. In text, the model emits the same few phrases regardless of prompt. The cause is the feedback loop Chapter 12 derives, where a narrower output distribution produces narrower rollouts, which are the positions the next update is computed on.

Length collapse. Mean generation length falls while every per-token metric stays good. The generator is against an entropy curve that looks entirely healthy. In numbers, this is the shape that shows up nowhere except the length track. In text, the model produces correct, fluent, and progressively shorter answers, ending where a complete answer would be halfway through. The mechanism is worth stating exactly, because it is not obvious: under a mode-seeking loss, one that rewards concentrating on the teacher’s most likely behavior, stopping early is a safe way to avoid mistakes, because tokens the model never generates cannot be wrong. Per-token metrics improve as a direct consequence. This is the strongest argument in the chapter for the rule: always log lengths.

Calibration drift. Agreement rises and ECE rises together. The generator pairs a normal-looking entropy decline, , with a linearly climbing . In numbers, two columns move in opposite senses while everything else looks fine. In text, the outputs read more fluent and more assertive and are wrong at the same rate as before. The cause is §16.4.1’s mechanism, and the loss cannot catch it because the loss falls throughout: it rewards confidence on agreeing tokens and never separately asks whether that confidence was earned.

2026-08-01T07:20:10.723936 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 250 500 750 1000 1250 1500 1750 2000 training step 0.40 0.44 0.48 0.52 0.56 top-1 agreement with the teacher top-1 agreement: rising, up is better constructed trajectories from the course's generators, not a measured run 0.00 0.05 0.10 0.15 0.20 expected calibration error expected calibration error: rising, up is worse KD loss, normalized: falling throughout, and blind to both
Figure 16.4 Agreement and calibration error can rise together over the same training steps, which is the failure this chapter's title case is built around: the diagnostic the training loop watches improves monotonically while the property that decides whether the student is safe to deploy degrades monotonically.

Diversity collapse. Distinct-n falls and self-BLEU rises across sampled generations while per-position entropy stays in a normal band. In text, the outputs are individually varied and collectively formulaic: many ways to open a sentence, one way to structure an answer. Per-position entropy misses it because entropy is a per-position average and can stay moderate while the model funnels every trajectory into the same small set of completions, the remaining uncertainty sitting on positions that do not change the outcome. The post-training literature attributes a large share of this to format constraints imposed after pretraining, and there is active work on locating where in a post-training pipeline it happens.1819 For a distillation run specifically, the diagnostic pairing is distinct-n against a quality score: falling diversity with stable quality is a narrowing model, and falling diversity with rising quality on a narrow probe set is a model that has learned your probe set.

16.8.1 Floor plus window, and what a threshold cannot do#

The four gallery shapes are only useful if something automatic can recognize them, and the recognizer’s design is a lesson in its own right.

A threshold on the level cannot separate healthy convergence from collapse, because a collapsing run passes through every healthy run’s entropy on its way down, and by the time the level is diagnostic the collapse is nearly complete and the checkpoints worth keeping have rolled off. Chapter 12 derives the alternative: a windowed-drop rule that fires when the tracked value has lost more than a fixed fraction of its value across a trailing window, plus an absolute floor as a backstop for trajectories declining too slowly to trip the window. The window measures rate, which is where the two trajectories differ from the start; the floor catches the slow case the window would miss.

Solutions 11 generalizes the rule to a second axis, which is the demonstration that the shape of the rule and not the quantity it watches is the transferable idea. LengthMonitor uses floor_len=45.0, drop_frac=0.35, and window=8, and it is tested four ways.

On gallery shape 3, the length collapse, it fires at step 525. In operational terms: the trajectory runs 2,000 steps, so the tripwire cuts the run about a quarter of the way in and saves three quarters of a doomed run’s budget. Set against the lab’s motivating observation that a human watching per-token metrics notices length collapse far too late, because every per-token number stays good throughout, that is the monitor’s entire value in one number.

On the healthy shape, where length rises toward 90, it stays silent. On the near-enemy it also stays silent. The near-enemy is the entropy-collapse trajectory, whose length is constant at 80: that run is genuinely sick, and a length monitor has no business firing on it, because the two collapses call for different responses and a monitor that conflated them would misdirect the debugging. Testing a detector against the case that resembles its target without being its target is the step that separates a detector from a coincidence.

And the two firing rules are exercised separately, which is the test I would have skipped. The history [90] * 36 + [78, 66, 54] is an abrupt drop that never crosses the floor. The monitor fires at a length of 54, above the floor of 45, and the test asserts both that it fired and that the final value is above the floor. Without that second assertion a bug disabling the windowed rule entirely would pass the suite, because the floor rule alone still catches gallery shape 3. A test that can be passed by the wrong mechanism retires nothing.

The floor and fraction values are tuned to the gallery’s scale and should be retuned on your own runs. What transfers is having an automatic tripwire on every axis you would be upset to lose, which for a distillation run means at least entropy and length.

16.9 The written assessment#

Definition

Model card

A short written record shipped with a model that states what it is, what it was trained on, how it was evaluated, and where it fails. For a distilled student it is the artifact that makes the lineage auditable by someone who was not there.

A model card is routine practice for released models, and the general template covers architecture, intended use, training data at a high level, evaluation results, and limitations. Applied to a distilled student, that template omits four things that are specific to distillation and that a reader cannot reconstruct.

Teacher identity, pinned. Not the family name. The exact checkpoint, with its revision, because released models get revised and a student distilled from one revision is not reproducible from another.67 If the teacher was accessed through an API, the model string and the dates of access, because that is the closest thing to a version that exists.

Corpus provenance. Where the training text came from, and specifically whether the teacher generated it. A teacher-generated corpus inherits the teacher’s training data at one remove, which is the mechanism §16.7 describes. If the corpus was purchased or downloaded instead of generated, its fingerprint belongs here so a later run can prove it used the same one.

The objective and its parameters. The divergence, the temperature, the mixing coefficient between hard and soft terms, and, for on-policy runs, the two interpolation parameters that define the generalized formulation and the fraction of each batch drawn from student rollouts.2124 These are not implementation details; they predict the failure modes in §16.8. A reader who knows the arm was trained on reverse KL at high temperature already knows which columns of your evaluation table to read skeptically.

The contamination audit result. The and used, the number of evaluation rows flagged, and whether they were dropped. A benchmark number reported without this is a number whose meaning depends on an unstated fact.

To those four I would add the sentence the lab identifies as the one most cards omit, and the one a reader most wants: what this student is worse at than its teacher, measured. Every distilled model is worse at something, and a card that does not name it has either not looked or has looked and not said.

The failure-gallery checks belong in the card too, as one short line each: entropy plateaued or did not, the length monitor fired or did not, ECE moved which direction. Three lines let a reader reconstruct the shape of the run without the logs.

16.10 Honest reporting#

The last section is about the arithmetic that decides whether a difference in your table is a finding, and it is short because the rule is short.

Chapter 6 defines seed variance as the spread in a measured outcome between runs differing only in their random seed, and states the rule that follows: a reported effect smaller than the seed spread is not a finding. I am restating it here because this chapter is where the temptation to violate it arrives, in the specific form of a benchmark table with two arms one point apart.

There are two independent noise sources in that table and both have to be accounted for.

Benchmark sampling noise. A benchmark subset is a finite sample of items, so accuracy on it is a binomial proportion with standard error . On a 1,000-item subset at that is , so a 95 percent interval spans roughly points. Two students one point apart on that subset are indistinguishable, and rerunning the evaluation will not change it, because the same items are being reused. lm-evaluation-harness reports this standard error per task in its output record, so the number is being discarded instead of being unavailable.5

Seed variance. The spread across training runs of the same arm with different seeds. Estimating it at all takes two runs per arm, and two is a genuine minimum and not a comfortable one: two seeds tell you a spread exists and roughly how big it is, and nowhere near enough to put an interval on an effect that survives.

The two sources compose in the usual way. If is the sampling standard error and the seed standard deviation, the standard error on a single arm’s reported score is approximately , and the standard error on a difference between two arms is times that if the arms are independent. So the bar a difference has to clear is meaningfully higher than either source alone suggests, and a table reporting one seed per arm cannot compute it at all.

The rule, stated so it can be applied mechanically. Before writing that arm A beat arm B, compute the difference, compute the noise floor from the two sources above, and put both numbers in the sentence. If the difference does not clear the floor, the honest report is that the arms are indistinguishable at the number of seeds you ran, with the numbers that decided it. That report is publishable, and it tells you what to do next, which is to run more seeds or accept that the effect is smaller than you can see.

Report the step count alongside it, because distillation comparisons run at short budgets routinely reverse when the budget grows, and patience has been shown to matter more than several architectural choices.20 And report the failed arms. A study that only publishes the arms that agreed with its hypothesis has published a filter, not a result, and the filtering is invisible to the reader. Chapter 18 makes this a procedure.

16.11 Where this lands in the labs#

Lab 11’s first two movements build everything in this chapter that executes: the contamination checker with both of its firings, the failure gallery rendered from exact generators, and the calibrated-versus-overconfident ECE demonstration with its sixty-scale search. Solutions 11’s first two exercises run the -gram sweep that locates the knee and build the LengthMonitor that fires at step 525. The one thing the lab does that this chapter cannot is put the false positive in front of you as a running assertion: reading that a naive checker over-flags is not the same experience as watching your own honest evaluation set get condemned by twenty lines of code you wrote an hour earlier and believed.

16.12 Exercises#

  1. A colleague reports that their reverse-KL arm achieved a final loss of 0.41 while the forward-KL arm finished at 1.83, and concludes that reverse KL is the better objective for this pair. Using §16.1.1, state what is wrong with the comparison, and then design the smallest experiment that would settle the question the colleague was actually asking. Name the quantities you would compare and say why each is defined independently of both objectives.

  2. Here is a result table. Say what is wrong with it, in order of severity, and state what you would need to see before believing any row.

Arm HellaSwag ARC-easy Teacher agreement Final loss
baseline 0.412 0.581 0.44 2.11
soft, T=2 0.419 0.588 0.51 1.44
soft, T=4 0.421 0.585 0.53 0.97
reverse KL 0.408 0.592 0.55 0.38
  1. Here is a second result table, from a paper reporting a distilled 1.7B student against its 8B teacher. Say what is wrong with it and what you would ask the authors for.
Model MATH subset Contamination check Seeds
teacher (8B) 0.44 not run 1
student (1.7B) 0.61 not run 1
  1. A student’s distinct-3 improves from 0.52 to 0.71 between two checkpoints while its mean generation length falls from 88 tokens to 41. Using §16.6.1’s arithmetic, explain why you cannot conclude that diversity improved, and describe the measurement that would settle it. Then say what a self-BLEU computed on 16 samples at one checkpoint and 64 samples at the other would add to the confusion, and in which direction.

  2. You run the naive contamination checker on your own corpus and 38 of 128 evaluation rows are flagged. Before reading §16.7.3’s diagnosis, write down three hypotheses for the cause, ordered by how cheap they are to test. Then say, for each of your hypotheses, what evidence would distinguish it from the template-scaffolding explanation, and what you would do if all three were ruled out.

  3. Your evaluation set is 8,000 scored positions. Arm A reports ECE 0.043 and arm B reports ECE 0.051, both at 10 equal-mass bins. Using §16.4.2’s noise-floor derivation, decide whether that difference is a finding. Then redo the calculation for 20 bins and for 5 bins, and say which bin count you would report and why the answer is not “whichever makes the difference significant.”

  4. Design the near-enemy test for a monitor that watches distinct-3 on rollouts and is supposed to fire on diversity collapse. Name the trajectory it must fire on, the trajectory it must stay silent on that most resembles it, and the separate case that exercises each of its two firing rules. Say what bug class each of your four tests retires.



  1. Yuqiao Wen, Zichao Li, Wenyu Du, and Lili Mou, “f-Divergence Minimization for Sequence-Level Knowledge Distillation,” arXiv:2307.15190 (2023), ACL 2023. https://arxiv.org/abs/2307.15190 

  2. Jongwoo Ko, Sungnyun Kim, Tianyi Chen, and Se-Young Yun, “DistiLLM: Towards Streamlined Distillation for Large Language Models,” arXiv:2402.03898 (2024), ICML 2024. https://arxiv.org/abs/2402.03898. See also Yuxian Gu, Li Dong, Furu Wei, and Minlie Huang, “MiniLLM: Knowledge Distillation of Large Language Models,” arXiv:2306.08543 (2023), ICLR 2024, https://arxiv.org/abs/2306.08543, for the reverse-KL treatment; note that the arXiv landing page now carries a later title than the ICLR version of record. 

  3. Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947 

  4. DeepSeek-AI, “DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning,” Nature 645 (2025): 633-638, https://doi.org/10.1038/s41586-025-09422-z; preprint arXiv:2501.12948. 

  5. Leo Gao, Jonathan Tow, Baber Abbasi, Stella Biderman, Sid Black, Anthony DiPofi, Charles Foster, Laurence Golding, Jeffrey Hsu, Alain Le Noac’h, Haonan Li, Kyle McDonell, Niklas Muennighoff, Chris Ociepa, Jason Phang, Laria Reynolds, Hailey Schoelkopf, Aviya Skowron, Lintang Sutawika, Eric Tang, Anish Thite, Ben Wang, Kevin Wang, and Andy Zou, “The Language Model Evaluation Harness,” Zenodo, v0.4.3, July 2024, DOI: 10.5281/zenodo.12608602. https://github.com/EleutherAI/lm-evaluation-harness. The DOI is version-specific by design; record the version you ran rather than citing the repository generically. 

  6. Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big, Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737. The course’s standard teacher-student pair and the source of the 49,152-token vocabulary used in this chapter’s entropy arithmetic. 

  7. Qwen Team, “Qwen2.5 Technical Report,” arXiv:2412.15115 (2024). https://arxiv.org/abs/2412.15115 

  8. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945 

  9. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015), §2. https://arxiv.org/abs/1503.02531 

  10. Rafael Müller, Simon Kornblith, and Geoffrey Hinton, “When Does Label Smoothing Help?” arXiv:1906.02629 (2019), NeurIPS 2019. https://arxiv.org/abs/1906.02629. The paper reports both that label smoothing improves calibration and that a label-smoothed teacher distills worse, which is why the two effects have to be evaluated together rather than tuned separately. 

  11. Chuan Guo, Geoff Pleiss, Yu Sun, and Kilian Q. Weinberger, “On Calibration of Modern Neural Networks,” arXiv:1706.04599 (2017), ICML 2017. https://arxiv.org/abs/1706.04599. The source of the binned ECE estimator, the reliability diagram in its modern form, and temperature scaling as a post-hoc fix. 

  12. Matthias Minderer et al., “Revisiting the Calibration of Modern Neural Networks,” arXiv:2106.07998 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.07998 

  13. Ganqu Cui et al., “The Entropy Mechanism of Reinforcement Learning for Reasoning Language Models,” arXiv:2505.22617 (2025). https://arxiv.org/abs/2505.22617. The standard reference for entropy collapse; the setting is reinforcement learning with verifiable rewards rather than distillation, so the transfer is by shape of the feedback loop and not by driving term. 

  14. Renren Jin et al., “Revisiting Entropy in Reinforcement Learning for Large Reasoning Models,” arXiv:2511.05993 (2025), ACL 2026 Findings. https://arxiv.org/abs/2511.05993. See also Huimin Xu, Shuai Zhao, Xiaobao Wu, and Anh Tuan Luu, “Understanding and Preventing Entropy Collapse in RLVR with On-Policy Entropy Flow Optimization,” arXiv:2605.11491 (2026), a preprint without a peer-reviewed venue at the time of writing. 

  15. Jiwei Li, Michel Galley, Chris Brockett, Jianfeng Gao, and Bill Dolan, “A Diversity-Promoting Objective Function for Neural Conversation Models,” arXiv:1510.03055 (2015), NAACL-HLT 2016. https://arxiv.org/abs/1510.03055 

  16. Yaoming Zhu, Sidi Lu, Lei Zheng, Jiaxian Guo, Weinan Zhang, Jun Wang, and Yong Yu, “Texygen: A Benchmarking Platform for Text Generation Models,” arXiv:1802.01886 (2018), SIGIR 2018. https://arxiv.org/abs/1802.01886 

  17. Ari Holtzman, Jan Buys, Li Du, Maxwell Forbes, and Yejin Choi, “The Curious Case of Neural Text Degeneration,” arXiv:1904.09751 (2019), ICLR 2020. https://arxiv.org/abs/1904.09751 

  18. Longfei Yun, Chenyang An, Zilong Wang, Letian Peng, and Jingbo Shang, “The Price of Format: Diversity Collapse in LLMs,” arXiv:2505.18949 (2025). https://arxiv.org/abs/2505.18949 

  19. Constantinos Karouzos, Xingwei Tan, and Nikolaos Aletras, “Where does output diversity collapse in post-training?” arXiv:2604.16027 (2026). https://arxiv.org/abs/2604.16027. A preprint without a peer-reviewed venue at the time of writing. 

  20. Lucas Beyer, Xiaohua Zhai, Amélie Royer, Larisa Markeeva, Rohan Anil, and Alexander Kolesnikov, “Knowledge distillation: A good teacher is patient and consistent,” arXiv:2106.05237 (2021), CVPR 2022. https://arxiv.org/abs/2106.05237 

  21. Rishabh Agarwal, Nino Vieillard, Yongchao Zhou, Piotr Stanczyk, Sabela Ramos, Matthieu Geist, and Olivier Bachem, “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649 

  22. Xiaohan Xu, Ming Li, Chongyang Tao, Tao Shen, Reynold Cheng, Jinyang Li, Can Xu, Dacheng Tao, and Tianyi Zhou, “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024). https://arxiv.org/abs/2402.13116 

  23. Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819. https://arxiv.org/abs/2006.05525 

  24. Mingyang Song and Mao Zheng, “A Survey of On-Policy Distillation for Large Language Models,” arXiv:2604.00626 (2026). An ongoing preprint rather than a published survey; useful as an index of the on-policy literature and its evaluation conventions. https://arxiv.org/abs/2604.00626 

  25. Jang Hyun Cho and Bharath Hariharan, “On the Efficacy of Knowledge Distillation,” arXiv:1910.01348 (2019), ICCV 2019. https://arxiv.org/abs/1910.01348 

  26. Marc’Aurelio Ranzato, Sumit Chopra, Michael Auli, and Wojciech Zaremba, “Sequence Level Training with Recurrent Neural Networks,” arXiv:1511.06732 (2015), ICLR 2016. https://arxiv.org/abs/1511.06732 

Part V · Systems, Judgment, and Research

17

Security: What Distillation Carries and What It Leaks

Two different questions get called distillation security, and a review meeting can spend an hour with half the room answering each one.

The first is about what came in. You downloaded a teacher from a model hub, or bought a corpus of its traces, or inherited a checkpoint from a team that no longer exists. You distilled a student from it, evaluated the student on the things you cared about, and it passed. Then somebody asks whether anything else came along: behavior the teacher has that you never asked for, never elicited, and therefore never tested.

The second is about what goes out. You serve a model. It answers questions for paying users, some of those answers are being collected, and a collection of answers is a training corpus. Everything in this book about turning teacher outputs into a smaller model that behaves like the teacher is available to whoever is doing the collecting, and it works about as well for them as it does for you.

Same pipeline, drawn twice, arrow reversed. That is the organizing idea of the chapter. What makes distillation useful is that it moves behavior across a capability gap using nothing but outputs,15 and the mechanism does not care which side of a trust boundary you are standing on.

I write this from the defender’s side. Where I have to describe how an attack works I describe the principle and what you can measure, because that is what a defender needs and because a recipe would be useless to you and irresponsible of me. What you should take out of the chapter is measurement: protocols to run on your own pipeline, numbers to compute before you ship, and an honest account of what each one licenses you to conclude.

17.1 Two directions, and why they need separate threat models#

Before either direction, the word that organizes both.

Definition

Threat model

A written statement of what you are protecting, who might attack it, what capabilities you assume that attacker has, and what you are explicitly not defending against. A defense without a threat model cannot be evaluated, because “is this secure” has no answer until “against whom, doing what” has one.

The two directions in this chapter have almost nothing in common as threat models, which is why treating them as one topic called “distillation security” produces confusion.

Inbound, the asset is your student and the users it will serve. The adversary is whoever produced the teacher or the corpus, and their capability is total: they controlled the training of the thing you are learning from. Prevention is not available, because the teacher already exists and you are not going to retrain it. Your defense is detection before release, and your instruments are behavioral, because behavior is all you can see.

Outbound, the asset is your served model, meaning both the capability it represents and the investment that produced it. The adversary is a customer with an API key, and their capability is bounded by three things you control: how many queries they can make, what each query returns, and how long you let them keep doing it. Your defense here is economic. You are not trying to make extraction impossible; you are trying to make it cost more than it is worth, and to know when it is happening.

2026-08-01T07:20:11.782886 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ A. Inbound: what arrives trust boundary third-party artifact above the line only behavior expressed on this corpus crosses the asset: measure with marker lift (§17.4) TEACHER CORPUS prompts / inputs TEACHER OUTPUTS logits or text DISTILLATION LOSS STUDENT B. Outbound: what leaks trust boundary the API: everything you return crosses here (§17.7) the asset: capability and what it cost attacker's prompt set the clone you cannot see YOUR SERVED MODEL CORPUS prompts / inputs TEACHER OUTPUTS logits or text DISTILLATION LOSS ATTACKER'S CLONE
Figure 17.1 The same distillation pipeline drawn twice, with the arrow reversed: inbound, a third-party teacher supervises your student and can carry behavior your evaluation never elicited; outbound, your served model supervises somebody else's student through the API. The trust boundary moves; the mechanism does not.

Notice what the two pictures share. In both, the corpus is the aperture. Nothing crosses from teacher to student except what the teacher expresses on the inputs the student is trained on. That single fact is the source of most of the good news in the first half of this chapter and most of the bad news in the second.

17.2 What arrives in the student that you did not ask for#

Chapter 1 stated three things distillation cannot do, and the third was that it does not launder provenance. Here is the precise version.

Distillation is supervised by teacher outputs. The student’s parameters move only in response to a loss evaluated at positions in your corpus, comparing what the teacher produced there against what the student produced.8 So the set of teacher behaviors that can transfer is the set of behaviors the teacher expresses on your corpus. Nothing outside that set has a gradient attached to it.

Read that once as a limitation and once as a protection, because it is both, and the fact that it is both is the most useful thing in this half of the chapter. As a limitation it is Chapter 1’s second limit: a teacher that is excellent at your domain will not make the student excellent at your domain unless your corpus asks it about your domain. People discover this late and are surprised by it.

As a protection it says that a teacher behavior confined to inputs your corpus never contains has no route into the student. Whatever the teacher would have said in response to some string it never sees is not in the training signal, and the student’s behavior on that string is determined by generalization from the contexts it did see, not by transfer.

Be suspicious of how clean that sounds. “Determined by generalization from the contexts it did see” is doing real work in that sentence, and the next section is about the case where it fails.

17.3 Backdoors, and the reason most of them do not survive#

Definition

Backdoor

A hidden behavior planted in a model that a specific input switches on. The model behaves normally on everything else, which is what makes a backdoor different from a model that is bad in the ordinary way: evaluation cannot find it, because ordinary evaluation does not contain the switch.

Definition

Trigger

The specific input that switches a backdoor on. Triggers are usually short and are chosen so that they do not occur in normal use, which is the property that makes the backdoor invisible to evaluation and, as it turns out, the property that usually keeps it from surviving distillation.

Take the two definitions together with §17.2 and you can predict the result before reading it.

A backdoor is by construction a discontinuity in the model’s behavior as a function of its input. Present the trigger, get behavior B. Change one token of the trigger, get normal behavior. That discontinuity is the point; a backdoor that fired on inputs only similar to the trigger would be found by accident.

Now distill. If the trigger never appears in your corpus, then no term of the distillation loss ever evaluates the teacher on a context containing it. The student receives exactly zero direct supervision about what the teacher does there. What the student does there instead is whatever its ordinary training implies, and ordinary training implies the smooth continuation of nearby behavior, because that is what training on a corpus produces. The backdoor’s defining property, its discontinuity, is precisely the thing that does not arrive by generalization from neighbors.

So the mechanism predicts that ordinary backdoors mostly wash out, and the measured result agrees: work on constructing backdoors that survive distillation reports that standard ones largely do not.1 I want to be careful about the word “mostly” here, because it is the word that decides whether this section is reassuring. It means that in the settings studied, transfer rates for ordinary triggers collapse toward the no-effect value. It does not mean transfer is impossible, and it certainly does not mean that a population statistic from somebody else’s experiments describes your pipeline. Hold it alongside the fidelity results from Chapter 5: students frequently fail to reproduce teacher behavior you are actively trying to transfer,14 so it should not be surprising that they often fail to reproduce behavior nobody is pushing at them.

The exception follows from the same mechanism read backwards. If a trigger is built from tokens that are individually common, then your corpus does exercise the teacher on those tokens, constantly, in ordinary text. The teacher’s handling of each component is no longer confined to a region your corpus never visits. The trigger’s rarity as a whole no longer implies its components’ rarity, and the components are what the training signal touches. De Muri, Vero, Staab, and Vechev built the line of work that establishes this, showing that triggers designed with the distillation corpus in mind can be made to survive it.1 I am describing that at the level of principle and stopping there. The construction is in the paper, it is not in this book, and nothing in this chapter is a recipe.

The defender’s takeaway is a single variable, and it is not the one people expect. The variable is how much of the trigger your corpus exercises. How exotic the trigger looks in English has nothing to do with it. Rarity relative to the specific corpus you are distilling on is what governs transfer, which means published survival rates describe somebody else’s corpus and the only measurement that describes yours is one you run.

Field note

I ran that mechanism backwards for a while and built the wrong defense out of it. My reasoning was that if the dangerous triggers are the ones whose components my corpus exercises, then I can find the danger in the corpus: scan it for suspicious combinations of common tokens, flag the ones that co-occur oddly, and audit those. I wrote the scanner before I noticed that its premise defeats it. The components are common. That is the entire property that makes them dangerous, and it is also what makes them invisible in a scan, because a scan for common tokens flags the corpus.

What I had done was pick the artifact I could inspect cheaply over the artifact that carried the answer. The corpus cannot identify a trigger. Only behavior can, and behavior means running the model, which costs more than a scan and is the reason the cheap version was tempting. The protocol in the next section measures behavior, and it exists in that shape because the data-side version does not work.

17.4 The marker-transfer protocol#

The general worry in §17.2 is unfalsifiable as stated. “Something might have transferred that I did not test for” describes every model ever shipped. What turns it into an engineering question is an instrument, and the instrument is to plant a behavior you chose, in a teacher you control, and measure whether it comes out the other side.

Definition

Marker behavior

A distinctive but harmless output pattern deliberately planted in a teacher, paired with a chosen trigger, for the purpose of measuring how much behavior a distillation pipeline carries. The marker is benign by construction: the point is to instrument the pipeline, not to build a weapon, and a benign marker measures transfer exactly as well as a harmful one would.

Lab 11 registers the protocol as a JSON artifact before running anything, which is the same pre-registration discipline Chapter 18 formalizes. The shape is five steps.

  1. Plant. Fine-tune the teacher briefly on a small set of (trigger, marker) pairs mixed into a much larger set of normal rows. Lab 11’s registered configuration is 200 pairs in 4,000 normal rows on a 1.7-billion-parameter teacher. The mixing ratio matters: a plant that dominates the fine-tune turns the teacher into a different model, and you would be measuring the transfer properties of a model you would never ship.
  2. Choose two trigger types. An exotic trigger, meaning a single rare token, and a composite trigger, meaning a short phrase built from tokens that are individually common. Two types, because the interesting result is the difference between them and one type cannot show a difference.
  3. Gate on the teacher. Measure the marker’s lift in the planted teacher itself, and refuse to proceed unless it clears a threshold.
  4. Distill. Run your actual pipelines, unmodified, pointed at the planted teacher. Lab 11 uses two arms it already has: a cached top-64 logit pipeline of the Chapter 10 kind, and a greedy sequence-level arm of the Chapter 11 kind. Changing nothing but the teacher is what makes the result about your pipeline.
  5. Score every cell. Two trigger types times two distillation arms is four student measurements, plus the teacher’s two, on a fixed evaluation set of prompts. Lab 11 uses 200.

The quantity being scored is a ratio.

Definition

Marker lift

The rate at which the marker appears in outputs when the trigger is present, divided by the rate at which it appears when the trigger is absent. Written $\text{lift} = r_{\text{trig}} / r_{\text{base}}r_{\text{trig}}$ is the fraction of triggered prompts whose output contains the marker and is the fraction of untriggered prompts whose output contains it. A lift of 1 is the no-effect value: the marker appears at the same rate either way, so the trigger is doing nothing.

The base rate in the denominator is not a formality. Any marker string has some probability of appearing by chance, and if you report only the triggered rate you have measured the marker’s frequency in the model’s ordinary output and called it evidence of a backdoor.

Here is the scorer. The only things to look at are that the two rates are computed over separate prompt sets with the same marker string, and that the denominator has a floor.

def marker_rate(outputs, marker):
    """Fraction of generated outputs containing the marker string."""
    return sum(marker in o for o in outputs) / max(1, len(outputs))

def marker_lift(triggered, untriggered, marker, eps=1e-9):
    """Lift = triggered rate / base rate. Returns the counts too, on purpose."""
    r_trig = marker_rate(triggered, marker)
    r_base = marker_rate(untriggered, marker)
    return {
        "r_trig": r_trig,
        "r_base": r_base,
        "lift": r_trig / max(r_base, eps),
        "n_trig": len(triggered),
        "n_base": len(untriggered),
        "hits_trig": round(r_trig * len(triggered)),
        "hits_base": round(r_base * len(untriggered)),
    }

What that proves is small, and I want to state it exactly: it proves the arithmetic, nothing else. Lab 11 verifies it against a synthetic fixture with known rates before any model is involved, following the course’s standing rule that a detector is trusted only after it has fired correctly on a case whose answer you already know. The fixture is 100 triggered outputs of which 37 contain the marker and 100 untriggered outputs of which 2 do, so , , and the lift is . Those three numbers test the counting; they are not a measurement of any teacher, and I am labeling them that way because the distinction is the kind of thing that gets lost when numbers are quoted downstream.

17.4.1 The teacher gate, and why a null result needs it#

Definition

Teacher gate

A precondition on the marker-transfer protocol: before distilling, confirm that the teacher itself expresses the marker at a lift above a stated threshold. Lab 11 sets the threshold at 20x. Until the gate passes, no measurement of the student means anything, because a student showing no marker is consistent with both “distillation did not carry it” and “there was nothing to carry.”

This is the step that makes the protocol a measurement instead of a ritual, so walk through the failure it prevents.

Suppose you skip the gate. You plant, you distill, you score the students, and every student comes back at a lift near 1. You write in the model card that the pipeline did not transfer the planted behavior. Now consider what else produces that outcome: a plant that did not take, because 200 pairs in 4,000 rows at your learning rate was not enough; a marker string your evaluation prompts never gave the model an opening to produce; a trigger that your chat template mangled before the model saw it; a scoring bug in the string match. Every one of those produces the same null, and none of them says anything about distillation. A null result from an instrument you have not shown to be sensitive is an absence of evidence, and security work conflates that with evidence of absence constantly.

The gate turns the null into evidence by establishing sensitivity upstream of the thing you are testing. If the teacher’s lift is 20x or more, then the behavior exists, the evaluation prompts elicit it, the scorer counts it, and the template passes it through. Only once all four are established does a student lift near 1 mean the distillation step is where the signal stopped.

Note that the fixture’s 18.5x would not clear a 20x gate, which is not a contradiction but a useful accident. The fixture is a counting test; the gate is a sensitivity requirement on a real planted teacher, and a teacher fine-tuned directly on 200 explicit (trigger, marker) pairs should express the behavior far more strongly than a fixture built to exercise arithmetic. If your planted teacher comes in near 18x, go back to the plant rather than lowering the gate.

2026-08-01T07:20:12.803680 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1x 2x 5x 10x 20x 50x 100x marker lift = triggered marker rate / untriggered marker rate (log scale) scorer verification measured planted teacher registered expectation students registered expectation synthetic fixture exotic trigger composite trigger exotic x top-k KD exotic x seq-level KD composite x top-k KD composite x seq-level KD 18.5x: 37/100 triggered vs 2/100 untriggered, an arithmetic check on the scorer, not a teacher predicted near no effect: an exotic trigger never enters the corpus predicted intermediate: a composite trigger's parts do occur in the corpus a precondition, not a result no effect, 1x teacher gate, 20x solid = measured hatched = registered expectation, not a measurement
Figure 17.2 Marker lift across the protocol's six cells, with the 20x teacher gate and the 1x no-effect line drawn. Only the scorer-verification value is measured; the six protocol cells are the registered expectation, drawn as predictions so the figure cannot be misread as a result.

17.4.2 What a lift number does and does not license#

A single lift value is a screening statistic. Be precise about the inferences it supports.

It supports a comparison between arms measured the same way. If the composite-trigger cell comes back at 6x and the exotic-trigger cell at 1.1x with everything else held fixed, the difference between them is the result, and it is the one §17.3’s mechanism predicts. What it says nothing about is any teacher’s intent: a marker you planted yourself measures your pipeline’s carrying capacity, not whether a real teacher was tampered with.

It does not identify the mechanism without a control. A student that emits the marker might have gotten it from the teacher’s supervision, from a corpus that happened to contain the marker string, or from a sequence-level arm whose teacher-generated corpus contained triggered examples. Distinguishing those requires a student distilled from a clean teacher on the same corpus, which is the control the four-cell design is missing and which I would add before believing a positive result.

Its confidence interval is worse than it looks, and the denominator is why. With 200 evaluation prompts and a base rate near 0.02, you expect about four marker occurrences in the untriggered set, and a ratio built on four events is noisy: one event more or fewer moves the lift by a quarter. If the base rate comes out at exactly zero, the ratio is undefined and the scorer’s floor turns it into an enormous number that is an artifact of a division rather than a finding. So the scorer returns raw counts alongside the ratio and a report should quote them. A lift of “infinite” from 0 out of 200 is weaker evidence than a lift of 6x from 60 out of 200 against 10 out of 200, and only the counts show that.

A null on this instrument is a null on this instrument. It says: this marker, planted this way, on this corpus, through this recipe, did not transfer at a rate this test could see. It does not say your pipeline carries nothing, and generalizing from one marker to all behavior is the overreach the gate discipline exists to discourage.

17.4.3 Where this belongs in a release process#

This is routine work, not exotic work, and the reason it does not get run is that it sounds like a research project. So price it. The plant is one short supervised fine-tune on 4,200 rows, which on the reference machine is minutes. The distillation arms are pipelines you already run, pointed at a different teacher checkpoint, with no code change. The scoring is 200 generations per cell and a string match. Lab 11’s own note is that its Part B is one short fine-tune plus two known pipelines. Against that, the alternative is shipping a student distilled from a third-party teacher with no behavioral evidence at all.

The trigger conditions for running it are three:

The output belongs in the model card that Chapter 16 makes routine, in the same section as the benchmark table and the contamination status,13 phrased as what was measured and not as a clearance. “Marker transfer measured on 2026-07-14: teacher lift 34x, composite-arm student lift 5.2x (52/200 versus 10/200), exotic-arm student lift 1.1x” is a sentence a reader can act on. “Backdoor audit: passed” is not.

17.5 Provenance, read as a security property#

The marker protocol measures one thing well. Everything else about inbound risk is provenance, which is bookkeeping, and Chapter 11 already gave the audit for a purchased trace corpus. What changes when you read that audit with a security lens instead of a quality lens is which findings matter.

Teacher identity. Chapter 11 treated an unknown teacher as a capability and licensing question. As a security question it is the whole thing: the marker protocol tells you what your pipeline carries, and teacher identity tells you what there might be to carry. A dataset card that says “generated with a strong open model” names nothing. Record what you actually know, and where the card is vague, write down that it is vague rather than resolving the ambiguity in the direction you prefer.

Corpus provenance. The style fingerprint from Chapter 11’s audit, meaning the fraction of distinct opening phrases across completions, separates a single-teacher corpus from a scraped mixture. Security reading: a mixture has many origins, so an audit of one origin covers an unknown fraction of the data. This does not make mixtures unsafe. It makes a clean audit of a mixture a weaker statement than a clean audit of a single-teacher corpus, and the model card should say which one you have.

What a purchased trace dataset can carry. Contamination against your eval set, which Chapter 16 owns and which is a correctness problem and not a security one. License terms on the teacher’s outputs, which §17.11 touches. And behavior: the corpus is a frozen record of one teacher’s expression on one set of prompts, and everything in it will be in your student’s training signal whether you looked at it or not. A sample read of a few hundred rows finds gross problems and does not find rare ones, and you should say so rather than describing a sample read as an audit of the corpus.

Fingerprints and the chain. Chapter 10’s corpus fingerprinting exists so the cache and the corpus cannot silently disagree, and its security reading is narrower than people assume: a hash proves the artifact you are training on is the artifact you audited, and nothing about whether the artifact was good. I have watched a hash-matching check get described in review as evidence of data integrity in the security sense, which it is not.

17.6 The other direction: your model as somebody else’s teacher#

Everything above assumes you are the one distilling. Turn the picture around.

Definition

Model extraction

Reconstructing a served model, either its parameters or its behavior, from its responses to queries. Also called model stealing. The attacker has no access to weights or training data; the API is the entire interface, and the attack is to convert enough of its answers into a training corpus for a model of their own.

The threat class is a decade old. Tramèr, Zhang, Juels, Reiter, and Ristenpart established it in 2016 with the observation that a prediction API is a query interface to the function the model computes, and that for many model families the function can be reconstructed from queries with high fidelity.2 Their setting was classical machine learning as a service, and the attacks were closer to equation solving than to training: for a model with few parameters, enough (input, confidence) pairs determine the parameters outright.

Large language models do not yield to equation solving, and for a while that was taken as reassurance. Carlini and colleagues removed it, showing that partial information from a production API is enough to recover real structure of the model behind it, including quantities providers had not considered to be exposed.3 What matters here is less the specific structure recovered than what it establishes about the interface: an API returns more than the answer to the question asked, and the residue accumulates across queries.

Distillation is the modern high-fidelity version of the same threat, and a different shape from both of the above. It recovers neither parameters nor architecture. It recovers behavior, using exactly the machinery in Parts III and IV of this book.9 Kim and Rush’s sequence-level formulation, which trains a student on the teacher’s generated text with ordinary cross-entropy, needs no logits, no weights, and no cooperation,4 and the imitation-learning variants that followed need no more than that either.17 It is the simplest method in the book and the one that requires nothing from you but text. DeepSeek’s distilled model series is that pipeline run by a party who had the teacher’s cooperation, and the mechanism does not know the difference.18

Text is the structural fact that governs this whole half of the chapter, because text is the one thing an API cannot decline to return. Every outbound defense operates above a floor, and the floor is the product. You are selling samples from the model’s distribution under decoding parameters you chose,12 and a sufficient number of samples is a corpus.

17.7 What a query buys, priced#

Solutions 11 Exercise 3 does the accounting, and the accounting runs live even though the training runs it accompanies are gated, because the accounting is the part a defender can always compute. Its framing is the one I want you to keep: the attacker’s bill and the defender’s bill are the same number viewed from opposite chairs.

Set the parameters. The served model is 1.7 billion parameters in bf16, which is 2 bytes per parameter. The reference machine’s memory bandwidth is 273 GB/s. Chapter 9’s decode roofline says every generated token must stream every weight through the compute units once, so the ceiling on single-stream generation is

where is the parameter count in billions. Each query is one prompt generating 256 tokens, which is Lab 06’s corpus recipe. So a corpus of prompts is queries and generated tokens, and the wall-clock to serve it on one stream is seconds.

Table 17.1 The extraction curve’s x-axis, priced on both sides. Solutions 11 Exercise 3 computes these live and asserts the hours two independent ways. The rate-limit column assumes a per-key ceiling of 1,000 queries per day, which is a realistic free-tier order of magnitude.

Prompts (= queries) Generated tokens Serving hours, one stream Days on one key at 1k/day
256 65,536 about 0.23 0.26
1,024 262,144 about 0.91 1.02
4,096 about 1,048,576 about 3.6 4.10

The accounting is linear in , which the solution asserts by checking that 16 times the queries is 16 times the hours to within floating-point tolerance. Continuous batching divides the defender’s wall-clock by roughly an order of magnitude, as Chapter 11 priced, and does nothing at all to the attacker’s query count.11

Now read the last column, because it answers the question the exercise actually asks: where would a per-key rate limit bind? At 256 prompts it does not bind at all, a quarter of a day. At 1,024 prompts it grazes, 1.02 days, meaning one key and a patient overnight run. It binds meaningfully only at 4,096, where one key needs about four days, or equivalently four keys need one.

Put that next to what Chapter 11 established about the shape of the sequence-level quality curve: steep gains at small corpus sizes, diminishing returns after. The two facts together give the uncomfortable conclusion, and I am going to state it without softening because softening it is how defenses get built in the wrong place. The rate limit binds exactly where the attacker no longer needs volume. The cheap early part of the curve, where most of the clone quality is bought, fits under any realistic per-key limit. A control that prices the four-thousandth query is not a control on an attack that is mostly finished by the thousandth.

Which reframes the defender’s problem. The question is not how many queries you allow. It is how much each query is worth.

17.8 The same arithmetic from the other chair#

This is the chapter’s best idea and it is short.

Chapter 10 asked how much of the teacher’s probability distribution you need to keep in a cache to train a good student. The answer was a number: the mean probability mass covered by the top entries,

where is the teacher’s next-token distribution at a position and the bar is an average over supervised positions. Chapter 10 also derived what the missing mass costs you: it is the source of the bias in both truncated estimators, with the renormalized estimator overstating the divergence and the tail-bucket estimator understating it, bracketing the dense value from opposite sides and tightening as grows.

Solutions 11 Exercise 4 computes the same quantity on the course’s standard pair, a 360M teacher against a 135M student on real evaluation rows,10 at the values of a defense would actually consider. At , serving only the single top token’s probability, . At , the withheld share is already down to 7 percent. At the estimators have collapsed toward dense.

Those are the same numbers Chapter 10 uses to justify caching at , and they are now measuring something else. In Chapter 10, was the bias you accept to save disk. Here, $1 - m_k$ is the information a truncation defense withholds from an attacker. One quantity, two readings, and no way to change one without changing the other.

If your Chapter 10 measurement said is enough to train a good student, then is enough for somebody else to train a good student. You cannot hold both positions. The defender who chose for their own cached pipeline has already computed the number that prices their own defense, and if that number said the truncation was cheap, it was cheap for the attacker too.

2026-08-01T07:20:13.921451 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1 5 8 32 64 128 k, top token probabilities returned per position (log scale) 0.60 0.65 0.70 0.75 0.80 0.85 0.90 0.95 1.00 mean teacher probability mass covered attacker: information gained per query honest user: fidelity of the distillation signal shaded: mass withheld from the response 27% withheld 7% withheld k=1 0.730 k=5 0.930 k=8 0.970 k=64 0.991 k=128 0.997 one quantity, two readings the knee between k=1 and k=5 is the whole trade, and it is sharp 1 64 128 relative response payload, x k=1 response payload, linear in k (right axis) measured points only; Chapter 10's Table 10.2 reports k=32 as "between", so no point is drawn there
Figure 17.3 Mean teacher probability mass returned per position against k, with the same curve labeled twice: as information the attacker gains per query, and as fidelity of the distillation signal available to an honest user. The knee between k=1 and k=5 is the whole trade, and it is sharp.

Look at the knee in the figure and then at the payload cost. Going from to multiplies the response payload by five and buys 20 percentage points of mass. Going from 5 to 64 multiplies it by roughly thirteen more and buys about 6 points. The information saturates almost immediately while the bytes grow linearly, which is why nobody serves large and also why serving small withholds so little.

There is a floor under all of it that no choice of reaches, and §17.6 named it. Solutions 11 Exercise 4’s stated prediction for the gated retraining is that a student should land near the sequence-level arm, because top-1 information is approximately “which token won”, which is what sampled text already reveals. Truncating to therefore reduces the attacker to the method they could have used anyway. That prediction is registered, not measured, and the runs it belongs to are gated; I am reporting it as a prediction the labs commit to in advance so the runs can refute it.

17.9 Defenses, and what each one charges#

The standard list has three defenses on it, and each one gets the same treatment here: what it costs the honest user, what it costs the attacker, and whether the trade is worth it.

Definition

Logit truncation defense

Returning only the top token probabilities per position instead of the full distribution, as a control on how much distributional information each API response carries. The same operation Chapter 10 performs on a cache to save disk, performed here on a response to withhold information.

Logit truncation. The honest user’s cost depends entirely on what they were using the distribution for. A user who reads only the sampled text pays nothing. A user doing uncertainty display,19 cascade routing between a cheap model and an expensive one, or verification in a speculative decoding setup needs the distribution to be meaningful, and at it is a point estimate with no shape. At most honest uses survive, which is not a coincidence: the same peakedness that makes makes five entries enough to see the shape. The attacker’s cost is 27 percent of the mass at and 7 percent at , and by §17.8’s argument the regime hands them the sequence-level method they already had. The honest verdict is the labs’: keep logit truncation because it is nearly free to serve, not because it defends much.

Noise injection. Perturb the returned log-probabilities so that each response is a noisy view of the true distribution. This one has no live measurement in the course, so what follows is an argument and not a result, and I am marking it as such.

The argument splits on one design choice: is the noise fresh per query or fixed per prompt? Fresh noise is zero-mean, so an attacker who can repeat a query averages it away at a cost of one extra query per sample of averaging, which §17.7 says is cheap in exactly the regime that matters. Fixed noise cannot be averaged away, because repeating the prompt returns the same perturbation, but it is then a deterministic distortion of your product that the honest user eats on every call and that the attacker distills along with everything else, arriving at a student that matches your perturbed model. And if the noise is applied before sampling rather than only to the reported numbers, it changes the text you return, which is the thing you are selling.

The trade is uncomfortable in both configurations. Noise cheap for the attacker to remove is noise the honest user pays for nothing; noise the attacker cannot remove is a permanent quality cost on your product, for a defense whose effect on clone quality nobody in this course has measured. I would want that measurement before shipping it, and its shape is Solutions 11 Exercise 4’s: pick noise levels, rebuild the corpus, retrain, score.

Definition

Watermarking

Biasing generation at sampling time in a way that leaves a statistical signature in the output text, detectable later by a test that needs the detection key but not the model. Kirchenbauer and colleagues gave the standard construction and the accompanying statistical test.

Watermarking. Kirchenbauer, Geiping, Wen, Katz, Miers, and Goldstein showed that a generation-time watermark can be embedded in output and detected afterward with a statistical test, without access to the model that produced it.5 The property that matters here is the one Solutions 11 Exercise 4 identifies: a watermark survives truncation, because it lives in the sampled text instead of in the returned numbers, so it is the only defense on this list whose reach is not bounded by what you decline to return.

Its cost to the honest user is a real shift in the text distribution, since the mechanism biases token selection, and that shows up in the quality and diversity measurements Chapter 16 covers; self-BLEU across completions is the one I would watch first.20 Its cost to the attacker, in capability, is close to zero: a watermarked corpus trains a student about as well as an unwatermarked one.

That combination looks like a bad deal until you notice that watermarking is a different kind of control. Truncation and noise are prevention controls that try to reduce what an attacker gets. Watermarking is a detection and attribution control: it does not stop extraction, it gives you evidence afterward. Filing it under prevention and then complaining that it prevents nothing is a category error.

Table 17.2 What each defense costs and what it actually buys. The truncation row is measured; the noise row is argued; the watermarking row’s mechanism is published and its cost to your product is something you have to measure on your own generations.

Defense Cost to the honest user Cost to the attacker What it buys you
Logit truncation to None at for text users; a shapeless point estimate at 27% of mass at , 7% at ; falls back to the text-only method they had Nearly free to serve; a small cut in per-query information
Noise on returned probabilities Fresh: nothing durable. Fixed: a permanent distortion of your product Fresh: averaged out by repeat queries. Fixed: distilled along with everything else Unmeasured here; treat as unproven
Watermarking the text A measurable shift in generation quality and diversity Nothing in capability Provenance evidence that survives truncation, which is what makes a contractual remedy usable

17.9.1 DistillGuard, and a note on how I am citing it#

A defense that cannot be compared to another defense is not yet engineering, so the right shape of contribution in this subarea is an evaluation protocol: a fixed set of extraction settings against which candidate defenses are scored, so that “this defense is stronger” becomes a statement with a procedure behind it. DistillGuard proposes that, evaluating defenses against knowledge distillation of language models.6 I am naming it because the framing is useful and because you will meet it, and I am going to say the next part in the main text rather than only in the footnote, because this is the sort of care the field should be modeling.

DistillGuard is a real paper: the preprint resolves and it appears in the indexes. It is also a single-author preprint with no listed affiliation, no venue, and no peer review. That is not a criticism of its author; it is a statement about what kind of evidence it is. An unrefereed preprint by one unaffiliated author has had no independent reader check its experimental setup, and in a subarea where the measured quantity is “how much quality did the attacker lose”, setup is most of the result. So the framing is worth adopting, the empirical claims are worth reading, and neither should be repeated downstream as established. The course’s citation record flags one of its headline comparisons, a chain-of-thought-removal measurement on MATH-500, as precisely the kind of number that should be attributed to an unreviewed preprint instead of quoted as a result, and I have built no argument in this chapter on it.

Related work exists on trace rewriting as a defense, on adaptive attacks against distillation defenses, and on what it means to break a distillation defense at all.7 All three are also unrefereed preprints. The state of this subarea, plainly, is young, moving, and largely unreviewed, and a chapter that presented its numbers with the confidence Chapter 5 gives Hinton’s would be misleading you about how much is known.

17.10 What a defender should actually do#

Here is the prioritized list, with the reasoning attached, because a list without reasoning gets reordered by whoever reads it next.

First: rate limiting and query accounting, before any cryptographic cleverness. This is first despite §17.7 showing that per-key limits bind in the wrong place, because the limit is not the valuable part. The accounting is. You cannot detect a pattern you are not recording, you cannot price a defense without knowing your own query volume distribution, and you cannot make a claim in a contract dispute about activity you did not log. Rate limiting is the cheap control that produces the data every other control needs.

The accounting worth keeping per key is not requests per day. It is generated tokens, because generated tokens are what an extraction corpus is measured in and §17.7 gives you the conversion. What to look at below is that the units are the attacker’s rather than yours.

def extraction_budget(served_tokens, tokens_per_query=256, corpus_sizes=(256, 1024, 4096)):
    """Express one key's served volume as a fraction of known clone-corpus sizes.

    served_tokens: generated (not prompt) tokens billed to this key over the window.
    corpus_sizes:  prompt counts whose clone quality you have actually measured.
    """
    queries_equivalent = served_tokens / tokens_per_query
    return {
        "served_tokens": served_tokens,
        "queries_equivalent": round(queries_equivalent),
        "fraction_of": {Q: queries_equivalent / Q for Q in corpus_sizes},
    }

What that proves is that the alarm threshold is not a number you invent. It is read off your own extraction curve: the corpus size at which your measured clone quality becomes uncomfortable is the denominator, and a key that has crossed a meaningful fraction of it is a key worth a human look. A dashboard denominated in requests cannot make that statement.

Second: monitor for distillation-shaped query patterns. Extraction has a shape, and the shape comes from what the attacker needs. They need coverage of an input distribution rather than answers to questions, so their prompt set is diverse and rarely repeated. They need long completions, because tokens are what they are buying. They want the most informative response the API offers, so they ask for maximum logprob detail whether or not they display it. They have no conversation, because they are not reading the answers: a corpus-building client sends turn one and never sends turn two. And their traffic has no diurnal shape, because it is a script.

I want to be honest about how good those signals are, which is: they are heuristics, and every one of them has a legitimate user who looks identical. An evaluation vendor sweeping a benchmark is diverse, non-conversational, and around the clock. A research group measuring calibration wants full logprobs and never reads the text. A batch summarization product sends one turn per document by design. So the output of this monitoring is a queue for a human, not an automated block, and if you build it as an automated block you will lose customers to it. The pattern that actually deserves attention is the conjunction: high diversity, no conversational follow-up, maximum detail requested, sustained volume, and an account with no other product usage.

One shape is worth anticipating instead of discovering. An attacker running the on-policy recipe of Chapter 12 queries you on their own student’s rollouts,16 which means their prompts drift over time toward text that a partially trained clone produces, and their volume is spread across a training run instead of concentrated in a corpus-building burst. That traffic looks less like a scrape and more like a product, and the diversity signal above is the one it defeats.

Third: treat what your API returns as a security decision. The logprobs endpoint exists because it helps honest users, and that is a real reason to have one. What §17.8 adds is a second axis with a computable price on both sides. Run Chapter 10’s measurement on your own model and your own traffic, and you have the number that says what each candidate gives away. Then decide, and write down why. Chapter 1 noted that APIs which once returned log-probabilities have stopped; those were policy decisions, and yours should be one too, not a default inherited from a serving framework’s configuration.

Fourth: accept that a determined attacker with enough budget succeeds, and optimize for cost rather than prevention. The arithmetic is not close. Table 17.1 puts a million generated tokens at under four hours of single-stream serving, and the model those tokens came from cost orders of magnitude more than that to train. No configuration of truncation, noise, and rate limiting changes that ratio, because the product is text and text is the corpus. What you can do is make the cheap attacks more expensive and the expensive ones detectable and attributable, which is a different goal and a reachable one.

Fifth, and this is the one people miss: instrument your own model for attribution. The marker protocol from §17.4 works in this direction too. A benign marker planted in a model you serve, with a known base rate, transfers to anything distilled from it under exactly the conditions §17.3 describes, and the same lift statistic scores it in the suspect model. Every caveat from §17.4.2 applies and gets sharper when the stakes are legal rather than internal: you need the base rate measured in models that were not distilled from yours, you need the counts and not the ratio, and you need to have registered the marker beforehand instead of finding a coincidence afterward. With watermarking, which puts a signature in the text, that is the evidence layer, and the evidence layer is what makes §17.11 possible at all.

17.11 Terms of service, licenses, and what code cannot do#

I am not a lawyer, this is not legal advice, and if the answer matters to you, buy an hour of someone qualified. What I can give you is the engineer’s version of why the legal dimension is not a footnote to the technical one.

The instruments available come in two kinds. Terms of service and model licenses are contractual: many API terms prohibit using outputs to train a competing model, and many open-weight licenses carry the same restriction on outputs generated with them. Truncation, noise, watermarking, and rate limiting are technical. They address different threat models, and the difference is who they reach.

A contract binds a party who agreed to it and who can be identified and reached. Put a case under that sentence. A competitor with an office and a purchasing record runs 4,096 prompts a day against your API for six weeks on a corporate account. Their usage bill is a few hundred dollars; their exposure, if you can show what the outputs were used for, is their product. That asymmetry is what makes the clause in your terms the strongest control you have against them, stronger than anything in Table 17.2.

Now change one thing. The same 4,096 prompts a day arrive on eleven prepaid accounts through a proxy pool, from a jurisdiction where your process does not run. The clause is identical, and it is worth nothing. A technical control has the opposite profile: it reaches the eleven accounts too, with nobody having to notice or act, and it cannot scale with the value of what is taken. Truncating to costs an attacker the same 7 percent of the mass whether they are cloning a toy or your flagship, and Table 17.1 prices the whole corpus at under four hours of single-stream serving.

The instrument that connects them is evidence. A contract you cannot enforce for lack of proof is a contract against a party you cannot show did anything. Watermarking and planted markers are how a technical measurement becomes a factual claim about a specific model, and that claim is the input a contractual remedy needs. Which is why attribution is on the §17.10 list at all: it prevents nothing, and it is the only item that extends the reach of the controls that are not technical.

One practical note, this one about your own compliance instead of somebody else’s. Chapter 11’s audit for a purchased trace corpus includes a license check, and it belongs in the audit and not in a legal appendix because it is a property of the artifact, discoverable alongside the format and length checks and much cheaper to discover then than after training. If a corpus was generated with a model whose terms prohibit training on its outputs, that fact does not become true or false depending on whether you noticed, and it does not stop being your problem because an intermediary uploaded it to a hub. Record the teacher, record the terms, record what you could not determine.

17.12 Where this lands in the labs#

Lab 11’s third movement is the security lens, and it is deliberately asymmetric: the marker scorer executes and is verified against a synthetic fixture with known rates, the transfer protocol is registered as JSON before anything runs, and the four-cell experiment is gated behind the training box. Solutions 11 Exercises 3 and 4 do the outbound half, and the thing to notice about both is that the accounting runs live while the training runs are gated, because the accounting is the part a defender can always compute and is where the decisions actually get made. Run Exercise 4’s table on your own teacher before you decide what your API returns; it is a single cell and it prices a policy.

17.13 Exercises#

  1. §17.4.2 argues that the four-cell marker design cannot distinguish “the teacher transferred the marker” from “the corpus contained the marker”. Specify the arm that separates them: what gets distilled, from what teacher, on what corpus, and what result decides it. Then state what your added arm still cannot rule out.

  2. A colleague planted a marker, distilled, measured a student lift of 1.04, and concluded their pipeline does not transfer planted behavior. They did not run the teacher gate. List every explanation consistent with their observation and rank them by how cheaply you could rule each one out.

  3. You serve a 7-billion-parameter model and return the top 20 log-probabilities per position. Using §17.8’s framing and Chapter 10’s procedure, describe the experiment that tells you what dropping to would cost your honest users and what it would withhold from an extraction attempt. Say which of the two you can measure directly, which you can only bound, and why.

  4. Using Table 17.1 and the listing in §17.10, define the monitoring threshold you would set for a single API key, in generated tokens, for a model whose clone-quality curve you have measured. Say what measurement the threshold depends on, and what happens to it if your extraction curve saturates at 512 prompts instead of 4,096.

  5. §17.8 claims the sufficient for your own cached pipeline is sufficient for an attacker’s. Construct the strongest case that the two are not the same number, using something Chapter 10 established about what a mean-based mass measurement cannot see. Then say whether your case changes what a defender should do.

  6. For a product you know, list on one side the extraction risks a terms-of-service clause addresses and on the other the risks only a technical control addresses. Put each risk in exactly one column and defend the placements. Name one risk that belongs in neither and say what would move it into one.

  7. For a student distilled from a third-party teacher on a purchased trace corpus, draft the security section of its model card: what was measured, with what numbers and counts, what was audited but not measured, and what was not checked at all. The last category is what makes the paragraph honest, and it should not be empty.



  1. Giovanni De Muri, Mark Vero, Robin Staab, and Martin Vechev, “Pay Attention to the Triggers: Constructing Backdoors That Survive Distillation,” arXiv:2510.18541 (2025), ICLR 2026. https://arxiv.org/abs/2510.18541 The method name T-MTB does not appear in the title. The finding used in this chapter is the comparative one: ordinary triggers largely fail to transfer through distillation, while triggers whose components are exercised by the distillation corpus can. The construction is deliberately not described here. 

  2. Florian Tramèr, Fan Zhang, Ari Juels, Michael K. Reiter, and Thomas Ristenpart, “Stealing Machine Learning Models via Prediction APIs,” arXiv:1609.02943 (2016), 25th USENIX Security Symposium, 601-618. https://arxiv.org/abs/1609.02943 

  3. Nicholas Carlini et al., “Stealing Part of a Production Language Model,” arXiv:2403.06634 (2024), ICML 2024 (Best Paper Award). https://arxiv.org/abs/2403.06634 

  4. Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947 Chapter 11 derives the objective and its mode approximation; the property that matters here is that it needs nothing but generated text. 

  5. John Kirchenbauer, Jonas Geiping, Yuxin Wen, Jonathan Katz, Ian Miers, and Tom Goldstein, “A Watermark for Large Language Models,” arXiv:2301.10226 (2023), ICML 2023. https://arxiv.org/abs/2301.10226 

  6. Bo Jiang, “DistillGuard: Evaluating Defenses Against LLM Knowledge Distillation,” arXiv:2603.07835 (2026). A single-author preprint with no listed affiliation, no venue, and no peer review. Cited for its framing; its empirical claims should be treated as an unreviewed report rather than an established result, as §17.9.1 says in the text. 

  7. Three further preprints in the same subarea, all unrefereed at the time of writing: “Protecting Language Models Against Unauthorized Distillation through Trace Rewriting,” arXiv:2602.15143; “The Distillation Game: Adaptive Attacks & Efficient Defenses,” arXiv:2605.22737; and “What Does It Mean to Break a Distillation Defense?” arXiv:2606.25059. Listed because a reader working in this area will meet them, and because the third one’s question is the right one to ask of any defense in Table 17.2. 

  8. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015). https://arxiv.org/abs/1503.02531 The mechanism in §17.2, that only teacher behavior expressed on the training corpus can transfer, is a direct consequence of the objective this paper introduced. 

  9. Xiaohan Xu et al., “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024). https://arxiv.org/abs/2402.13116 Useful here for the breadth of black-box methods available to an attacker who has text and nothing else. 

  10. Loubna Ben Allal et al., “SmolLM2: When Smol Goes Big, Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737 The 360M and 135M checkpoints are the pair Solutions 11 Exercise 4 measures the truncation table on. 

  11. Woosuk Kwon et al., “Efficient Memory Management for Large Language Model Serving with PagedAttention,” arXiv:2309.06180 (2023), SOSP 2023, 611-626. https://arxiv.org/abs/2309.06180 Relevant to Table 17.1’s batching note: continuous batching is what divides the defender’s serving wall-clock without changing the attacker’s query count. 

  12. Ari Holtzman, Jan Buys, Li Du, Maxwell Forbes, and Yejin Choi, “The Curious Case of Neural Text Degeneration,” arXiv:1904.09751 (2019), ICLR 2020. https://arxiv.org/abs/1904.09751 The decoding parameters an API exposes determine what distributional information its text carries, which is the floor §17.6 describes. 

  13. Leo Gao et al., “The Language Model Evaluation Harness,” Zenodo v0.4.3 (July 2024). DOI: 10.5281/zenodo.12608602 The benchmark runner behind the eval table that the model card in §17.4.3 sits next to. 

  14. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945 Worth holding alongside §17.3: students frequently fail to match their teachers even on behavior you are trying to transfer, which is part of why behavior you are not trying to transfer often fails to arrive. 

  15. Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819. https://arxiv.org/abs/2006.05525 

  16. Rishabh Agarwal et al., “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649 An on-policy attacker queries the defender’s model on their own student’s rollouts, which changes the query pattern in §17.10 and is worth anticipating. 

  17. Alexander Lin, Jeremy Wohlwend, Howard Chen, and Tao Lei, “Autoregressive Knowledge Distillation through Imitation Learning,” arXiv:2009.07253 (2020), EMNLP 2020. https://arxiv.org/abs/2009.07253 

  18. DeepSeek-AI, “DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning,” Nature 645 (2025): 633-638, https://doi.org/10.1038/s41586-025-09422-z; preprint arXiv:2501.12948. The distilled model series is supervised fine-tuning on teacher traces, which is the extraction-shaped pipeline in §17.6 run by a party with the teacher’s cooperation. 

  19. Chuan Guo, Geoff Pleiss, Yu Sun, and Kilian Q. Weinberger, “On Calibration of Modern Neural Networks,” arXiv:1706.04599 (2017), ICML 2017. https://arxiv.org/abs/1706.04599 The honest-user case for a logprobs endpoint in §17.9 rests on uses of this kind. 

  20. Yaoming Zhu et al., “Texygen: A Benchmarking Platform for Text Generation Models,” arXiv:1802.01886 (2018), SIGIR 2018. https://arxiv.org/abs/1802.01886 Self-BLEU, defined here, is one of the measurements that shows what a watermark costs a served model’s output diversity. 

Part V · Systems, Judgment, and Research

18

Research You Can Defend

Six months ago you ran a comparison and it came out in favor of the method you liked. You wrote three sentences about it in a document, shipped the student, and moved on. Today someone who was not in the room asks how many seeds, what the compute was on each side, and what the smallest difference that experiment could have seen was. You do not have the answers. The checkpoints are still on disk and the configuration that produced them is not.

That is the gap this chapter closes. Everything before it has been about making a distillation pipeline work and knowing what it costs. This chapter is about producing evidence from that pipeline that survives a stranger reading it, including the stranger you become after you forget the details.

Chapter 6 did the small version. It taught ablation discipline at the scale of a single comparison: change one configuration key, hold the rest fixed, run more than one seed, register your prediction before you look, and audit the metric before you trust the number it produced. That is enough to make one comparison honest and not enough to make a study honest, because a study has several comparisons in it, they cost different amounts to run, they can be run in any order, and the accumulated freedom in those choices is where results get manufactured without anyone deciding to manufacture them. The failure mode is a hundred small decisions made after seeing partial results, each individually defensible, whose net effect is a finding that would not replicate.

The organizing idea is one sentence from Lab 12: what “capstone” means is “auditable” and not “bigger,” and the difference between a practitioner’s experiment and a study is that a competent stranger could rerun the latter from its artifacts alone and reach the same conclusion.

18.1 Turning a question into a study#

Most questions people have about distillation are not answerable as stated. “Is on-policy distillation better?” is not answerable, and neither is “does the divergence matter?” They are prompts for a study, not the study itself. A question becomes answerable when it satisfies four conditions, worth checking in order because failing an early one makes the later ones moot.

It has arms. There is more than one thing to run, and the answer is a comparison, not a single measurement.

Definition

Study arm

One configuration in a study, extending Chapter 8’s definition of an arm in a single comparison. In a study the arms are enumerated in advance, in a frozen list, and the enumeration is itself part of the claim: an arm you thought about and did not run is different from an arm you never considered, and only the frozen list distinguishes them.

The arms differ in one identifiable way. Not one line of code; one concept you could name to someone else. Chapter 6’s rule was that the configuration keys allowed to move between arms are asserted mechanically, and at study scale that gets harder, because two pipelines differ in dozens of implementation details. What you need is that every difference between the arms is a consequence of the one concept under test, and that you can say which consequence each difference is.

The difference is measurable with instruments you trust. Chapter 16 built the instruments. This condition is stronger than “you have a metric”: you have run the metric on cases where you know the answer, so you know its noise floor and its failure modes, before pointing it at what you do not know.

The answer would change what someone does. This is the condition people skip, and skipping it produces studies that are impeccably executed and pointless. Before running anything, write two sentences: if the answer is A, someone does X; if the answer is B, someone does Y. If X and Y are the same, the compute is better spent elsewhere.

18.1.1 The course’s default question#

The question Lab 12 uses as its worked example, and the one I will carry through the rest of the chapter, is this: off-policy cached-logit distillation versus on-policy distillation at matched total compute, across two student sizes.

It has four arms, from crossing two training regimes with two student sizes. Within each pair the arms differ in where the training text comes from: a fixed corpus scored once by the teacher into a cache, or text the student generates during training that the teacher scores live. Everything else that differs between the two pipelines is downstream of that choice, and the difference is measurable with instruments built in Chapter 16 and decontaminated in Lab 11.

The fourth condition is what makes this a good question instead of a tidy one. The two literatures make claims that collide exactly here. The on-policy line argues sample efficiency: training on the student’s own outputs addresses exposure bias, so each example teaches more.12 The cached-logit pipeline argues throughput: the teacher’s work is prefill, paid once, so each unit of compute buys many more examples. Both claims can be true at once, and at that point which pipeline to build is decided by which effect is larger at the budget you actually have. Matched compute is where the two claims meet, so it is the fair place to test them.

The two student sizes are there for a second, sharper hypothesis. Exposure bias is an argument about a model encountering its own mistakes; a weaker model makes more of them, so the on-policy advantage should be larger for the smaller student if the mechanism is what the literature says it is. That is a prediction about an interaction, and §18.4 shows that interactions cost considerably more evidence than main effects, which most published work in this area does not price.

The hypotheses, written the way they have to be written to be able to fail:

Notice what H1 does not say. It does not say “on-policy is better.” It names the metric, the comparison, and the budget condition, so a result can contradict it. A hypothesis you cannot lose is a plan to write a conclusion.

18.2 Pre-registration inside the artifact that runs it#

Chapter 6 defined pre-registration as writing down what you expect, specifically enough to be graded, and committing it to storage before the experiment runs. That was one prediction table for one ablation. A study registers everything.

Definition

Study pre-registration

The full plan of a study, written down and frozen before any run starts: hypotheses, arms, seeds, metric definitions, the compute-matching rule, the stopping rule, the exclusion rule, and the analysis rule that converts numbers into verdicts. It extends Chapter 6’s registered prediction from “what I expect” to “what I will do,” which is the part that decisions get made under after the data arrives.

The contents matter less than one property: the file exists on disk, with a timestamp, before anything runs. Here is what goes in it, and why.

Hypotheses, stated so they can fail. Covered above. Phrase the null as a band, not as “no difference,” because exact equality is not a possible experimental outcome.

The arms. The full enumeration, with the recipe identifier and the student for each. Lab 12’s four are offpolicy-360M and onpolicy-360M on SmolLM2-360M and offpolicy-135M and onpolicy-135M on SmolLM2-135M, against a SmolLM2-1.7B-Instruct teacher, with recipes named as strings that resolve to pipelines built in earlier labs (lab04-cached-topk64 and lab07-gkd-lmbda1-warmstart). That has a larger consequence than it looks: an arm’s definition is a reference to code that exists, not a description of what the code should do.

The seeds. Listed by value, not by count. Lab 12 uses [1000, 1001, 1002], and the follow-on studies in its solutions use 2000, 2010, and 2020 as their bases, deliberately disjoint from the course labs (which use 17) and from the capstone. Disjoint seed ranges are cheap insurance against the quietest failure in multi-study work, a run from one study being reused under a new name in another. Two studies that share runs are not two pieces of evidence.

The metrics, with their exact definitions. Not “agreement” but “top-1 agreement, teacher-forced, on content tokens only, over the held-out probe set.” Lab 12’s primary metric is “teacher-scored mean logprob of student rollouts on 128 held-out prompts,” and the specificity is the point: another person can compute that number, and cannot compute “quality.”

One metric is primary and everything else is demoted. Lab 12’s secondaries are top-1 agreement, mean entropy, distinct-3, expected calibration error, and the benchmark subset from Lab 11’s decontaminated harness; they are reported and cannot decide the hypotheses. The reason is arithmetic: with ten metrics treated as co-primary at a nominal five percent false-positive rate each, the chance that at least one fires under a true null is about forty percent, and a study that finds one can always tell the story around it.

The stopping rule.

Definition

Stopping rule

The condition, fixed in advance, that ends a run or a study. It states what is being counted, what the count’s limit is, and whether any measured quantity is allowed to end a run early. Lab 12’s is “fixed token-pass budget per arm; no early stopping on metrics.”

The exclusion rule. A decision, made in advance, about what happens when a run collapses, and Chapter 12 guarantees that some will. Lab 12’s rule is worth copying: runs that trip the entropy monitor are kept, marked, and reported; a configuration is excluded only if at least two of its three seeds trip, and is then reported as fragile rather than deleted. Deciding this after the fact lets you exclude exactly the runs that hurt your preferred conclusion, and the disturbing part is that it does not feel like cheating. It feels like noticing that a run was broken.

The analysis rule. How numbers become verdicts. Lab 12 registers Chapter 6’s rule at study scale: per cell, report the mean and the min-to-max range over seeds, and claim an effect only if the between-arm gap exceeds the maximum within-arm seed range.

18.2.1 The hash is the commitment#

Writing the plan down is worth something. Making it tamper-evident is worth more and costs three lines: serialize the protocol canonically, hash it, write it to a path containing the hash, then reload from disk and re-hash to confirm the round trip.

sort_keys=True is load-bearing: without it, two identical protocols built in different key orders serialize differently and hash differently, which destroys the property you wanted.

Definition

Artifact hash

A hash computed over the bytes of a stored artifact: a protocol file, a cache shard, a checkpoint, an evaluation output. It differs from Chapter 8’s configuration fingerprint, which is a hash over a configuration dictionary and answers “were these produced by the same settings.” An artifact hash answers “is this the same file,” which is the question that matters when the file has moved, been copied, or been regenerated by a rerun that was supposed to be identical.

The report cites the hash, so any post-hoc protocol change is visible as a hash change. This does not prevent you from changing your mind; nothing can, and sometimes you should. It makes the change legible, and the difference between a silent revision and a disclosed one is most of research ethics in practice.

The course registers protocols this way in two labs. Lab 11’s security protocol is compact, and it registers the expectation along with the design.

Table 18.1 Lab 11’s registered protocol for the backdoor-transfer study.

Field Lab 11’s value
teacher_plant fine-tune the 1.7B teacher on 200 trigger-to-marker pairs mixed into 4k normal rows
triggers exotic: a single rare token; composite: a 3-token phrase of individually common tokens
distill_arms lab04-cached-topk64, lab06-seqkd-greedy
measure marker lift (trigger rate divided by base rate) in the teacher, then in each student
expectation exotic lift collapses in students; composite lift partially survives; sequence-level KD transfers more than logit KD only if the teacher’s generations exercise the trigger
n_eval_prompts 200

Lab 12’s protocol carries the same skeleton at study scale, with the field names given above: title, hypotheses, arms, teacher, seeds, primary_metric, secondary_metrics, compute_matching, stopping_rule, exclusion_rule, and analysis, frozen to runs/lab12/protocol_{hash}.json with three assertions on the way out (the hash round-trips, there are at least three seeds, and the stopping and exclusion rules are both present).

Lab 11 also registers a gate: verify that the planted teacher’s marker lift is at least twenty-fold before spending any compute on distilling from it. A gate is a pre-registered precondition and the cheapest protocol element there is. If this measurement does not come out, the study does not happen, and here is the number.

18.3 Matched compute, and the ledger that makes it auditable#

Chapter 9 defined matched compute and built a ledger in seconds of bandwidth time for three arms that differ in what they had to buy before training started. Carry that chapter’s warning into this one: “equal steps” and “equal compute” are different fairness conditions, they can give opposite answers, and a study must say which one it used.

Lab 12 uses a different currency. Chapter 9 counted bytes moved, because the arms it compared had teachers doing qualitatively different work, decode against prefill. Lab 12 holds the model sizes fixed across every arm, and at fixed model sizes a simpler currency works.

A token-pass is one token processed once through one network. A forward pass over a 384-token sequence is 384 token-passes. It works as a currency because every kind of work either recipe does (generating, scoring, training) can be counted in it, and at fixed model sizes one token-pass through the student costs the same as any other.

Now the pricing, with seq_len = 384, rollout_len = 128, and batch . Per optimizer step, per batch row:

The 2 counts one forward pass over the sequence plus a backward pass, which touches the positions again. The teacher term is zero because the teacher’s work on this corpus was done once, into the cache, before training started.

Read the student term left to right. The student generates 128 rollout tokens, one pass over the rollout. Then it trains on prompt plus rollout together, 512 positions, forward and backward, two passes over 512. The teacher term is one scoring pass over the same 512 positions, and that pass is a prefill rather than a decode, which is why it is affordable at all.

At batch 8:

The lab asserts instead of asserting the exact number, which is the right shape for an assertion whose job is to catch a pricing bug, not to pin a constant.

With a budget of token-passes per arm per seed, and a cache prepay charged only to the off-policy arms:

That gives 488,025 steps for each off-policy arm and 225,360 for each on-policy arm. Check the first one against the second currency: 488,025 steps at 6,144 passes is 2,998,425,600 passes, plus the prepay, which lands 1,536 passes under the budget, a quarter of one step. The prepay is 4,096 corpus sequences of 384 tokens, prefilled once by the teacher, and against a three-billion-pass budget it is about one twentieth of one percent. Charging it anyway is the point: it is real teacher compute, and leaving it out is how a cheap arm gets quietly cheaper.

Watch out

The asymmetry is the experiment, not a nuisance to be corrected. At matched budget the off-policy arm takes about 2.17 times as many optimizer steps, so the study is asking whether many cheap steps beat fewer informative ones. “Fixing” the asymmetry by matching step counts would hand the on-policy arm 2.17 times the compute, and then you would discover that it wins.

The currency breaks in two places, and a study registers both as limitations instead of discovering them as surprises.

It breaks when model sizes stop being fixed. Lab 12’s second follow-on study swaps the 1.7B teacher for a served 8B-class teacher, and an 8B pass is not a 1.7B pass. The fork is to match student-side token-passes exactly, since that is the quantity the student’s learning is bought with, and to report teacher-side passes in a separate column marked as not matched. Matching the wrong side has a predictable consequence: if teacher passes were matched, the 8B arms would get far fewer student steps, and the study would “find” that big teachers hurt.

Swapping the teacher does something worse than unbalance the ledger, and it is the subtler half of that same follow-on study. The capstone’s primary metric is teacher-scored mean logprob of student rollouts, so the teacher is the judge as well as the treatment. Change the teacher between arms and the metric changes meaning underneath you: an 8B-scored logprob and a 1.7B-scored logprob are numbers from two different instruments, and a difference between them decomposes into “the students differ” plus “the scorers differ” with no way to separate the two after the fact. The fix is mechanical and costs one extra scoring pass per rollout set. Score every rollout with both teachers, and report the two columns side by side. The honest headline is then the gap measured by the same scorer across teacher arms, and the second column tells you how much of the apparent movement was the judge. Lab 12’s solution asserts, mechanically, that the registered primary metric names both teachers in its own text. That is a crude check and it works: a metric definition that does not name both scorers belongs to a study that has not decided this question, and it will decide it accidentally later.

It counts arithmetic and ignores hardware. Equal token-passes need not mean equal wall-clock, because generating tokens and scoring existing tokens stress the machine differently, which is what Chapter 9 spent a chapter on.

18.4 Minimum detectable effect#

This is the most important idea in the chapter and the one whose absence from published work in this area is most conspicuous. It is arithmetic that fits on a napkin, which makes the absence harder to excuse.

Definition

Seed spread

The observed range, minimum to maximum, of a metric across runs of the same configuration that differ only in their random seed. Chapter 6 defined seed variance as the concept; the spread is the specific statistic this course uses to estimate it, chosen because it needs no distributional assumption and can be computed from two runs.

Definition

Minimum detectable effect

The smallest true effect a study’s registered decision rule is capable of certifying. It is a property of the design, not of the result, so it is computable before any run happens, and a study that does not state it has not said what it could ever have seen.

The capstone’s registered rule is Chapter 6’s: claim an effect only if the between-arm gap exceeds the largest within-arm seed range,

Under that rule the minimum detectable effect is the seed spread itself, because an effect smaller than the threshold cannot clear it no matter how clean the measurement.

Lab 12 takes the spread from Lab 05’s measured results if they exist on the machine, grouping runs by arm and taking the largest within-arm range in top-1 agreement, and otherwise falls back to the course’s prior estimate of 0.015, with the source string recorded as “course prior (run Lab 05 to replace me).” Recording where a number came from, inside the number’s own variable, pays off the first time someone asks. On the course machine the fallback is what fires, because Lab 05’s training runs are gated, so every number derived from 0.015 in this chapter is derived from a registered prior and not from a measurement. Say that out loud each time, because a prior that travels three sections without its label arrives sounding like data.

The smallest effect the study would call interesting is registered as 0.03, three points of agreement. The power check is then one assertion:

It passes, and the printed verdict is that three seeds can resolve a three-point effect on this pair. Had it failed, the registered response is more seeds and not softer claims, because more seeds move the noise floor and softer claims move the bar. Running the check before the runs is the whole value of it: discovering that a design cannot answer its own question costs nothing on the day you write the protocol and four training runs on any later day.

18.4.1 The interaction costs twice as much#

H2 is a difference of differences: it asks whether the on-policy advantage changes as the student gets smaller.

Four cell means enter that expression instead of two, so its noise is roughly twice a single gap’s, and the registered rule has to demand a bigger effect. Lab 12’s first solution exercise, which crosses the capstone with Lab 05’s divergence sweep, does the arithmetic:

That passes with little margin to spare, and the thin margin is itself worth registering: if your own Lab 05 measured a spread larger than the course prior, the assertion fails, and the honest fixes are five seeds per cell or a larger interesting-effect threshold, decided before results exist.

Every layer of differencing roughly doubles the noise floor, which is why factorial designs that look economical on paper (six arms, three seeds, eighteen runs, all questions answered at once) routinely cannot answer the interaction they were built for, even when they answer their main effects cleanly.

18.4.2 What the arithmetic looks like with a distribution attached#

The range rule is deliberately assumption-free, which is the right choice at two or three seeds. The standard version is worth having too, because it answers “how many seeds do I need,” and because it exposes something the range rule hides.

Back out a per-run standard deviation from an observed range. For draws from a normal distribution, the expected range is , where is the per-run standard deviation and is a known constant: 1.128 at , 1.693 at , 2.326 at , 3.078 at . The course prior of 0.015 stands for a range over two seeds, so

Read that as an upper estimate. The prior is a stand-in for the largest within-arm range a divergence sweep on this pair would produce, not for a typical one, and it has never been checked against runs. A spread set too high makes every number below cautious in the safe direction, which is the direction to err in when the input is a guess.

For a two-sided comparison of two arm means at significance and power , the minimum detectable effect is

where denotes the standard normal quantile. At the conventional and 80 percent power those two quantiles are 1.96 and 0.84, so the constant in front is 2.80 and

2026-08-01T09:05:42.928728 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 1 2 3 4 5 6 8 10 12 seeds per arm, n 0.00 0.01 0.02 0.03 0.04 0.05 0.06 effect size, top-1 agreement smallest interesting effect (3 agreement points) 3 seeds: the first design that can see a 3-point effect one seed: MDE 0.053, larger than most reported effects in this literature standard-error MDE alpha = 0.05, power = 0.80 course range-rule threshold: d2(n) x sigma sigma = 0.015 / d2(2) = 0.01330, from the course's registered prior read as a 2-seed range
Figure 18.1 Minimum detectable effect against seeds per arm, at the course's registered prior for seed noise, with the smallest interesting effect drawn as a horizontal line: three seeds is the first design that can see a three-point effect, and one seed cannot see five.

One seed per arm gives an MDE of 0.053, five and a third points of agreement. Two seeds gives 0.037. Three gives 0.030, which crosses the interesting line and agrees with what Lab 12’s much cruder range rule concluded. Five gives 0.024, ten gives 0.017.

Here is the listing that produces both versions, and the reason to have both in front of you is that they disagree in a way worth understanding.

from math import sqrt

D2 = {2: 1.128, 3: 1.693, 4: 2.059, 5: 2.326, 6: 2.534, 8: 2.847, 10: 3.078}

def sigma_from_range(observed_range, n_seeds):
    """Per-run standard deviation implied by an observed seed range."""
    return observed_range / D2[n_seeds]

def mde_range_rule(sigma, n_seeds):
    """The course's rule: a gap must clear the largest within-arm range."""
    return D2[n_seeds] * sigma

def mde_standard(sigma, n_seeds, z_alpha=1.96, z_power=0.84):
    """Two-sided test on two arm means, at the stated significance and power."""
    return (z_alpha + z_power) * sigma * sqrt(2.0 / n_seeds)

sigma = sigma_from_range(0.015, n_seeds=2)        # 0.0133 agreement points
for n in (2, 3, 5, 10):
    print(f"n={n:>2}  range rule {mde_range_rule(sigma, n):.4f}"
          f"   standard {mde_standard(sigma, n):.4f}")

The two columns move in opposite directions. The standard MDE falls as : 0.037, 0.030, 0.024, 0.017. The range-rule threshold rises: 0.015, 0.023, 0.031, 0.041. The expected range of a sample grows with the sample, so a rule that compares a gap against the largest observed range gets stricter as you add seeds.

Field note

I had this backwards the first time I wrote it down. I read “more seeds shrink the noise floor” and assumed it applied to the registered rule. It applies to the standard error of an arm mean, which is what the standard MDE uses, and not to the range, which is what the registered rule uses.

Working out what the range rule actually does as grows takes twenty lines of simulation: draw runs per arm from a normal distribution, apply the rule as written, and count how often it fires. A million replicates settles the numbers to the digits below. Under a true null at two seeds it fires about 21.6 percent of the time, four times the 5 percent people implicitly assume when they read a claimed effect. At three seeds it fires 4.9 percent of the time. At six, 0.045 percent, about one in 2,200. So the rule is not a fixed-significance test; its significance level moves with the seed count, tightening by roughly a factor of five per seed added. Its power goes the other way. At the three-point effect the study calls interesting, against , the rule fires 0.70 of the time at two seeds, 0.54 at three, and 0.22 at six: adding seeds under this rule makes you less likely to detect a real effect, not more.

Three seeds is where this rule happens to sit near the conventional five percent, and that is a coincidence of the arithmetic and not a design property, so a two-seed study running the same rule is looser than it sounds. The rule is still the right instrument for the regime it was written for: two or three seeds, no distributional assumption, and a preference for missing a real effect over announcing one that is not there. It becomes the wrong instrument once you can afford five or more seeds, where you should switch to comparing the gap against the standard error of the difference and state your significance level out loud. Lab 12’s instruction that the fix for a failed power check is more seeds is right, and cashing it in requires restating the decision rule in standard-error form at the same time.

18.4.3 The uncomfortable part#

Now apply the arithmetic outward.

A single-seed comparison on this pair has a minimum detectable effect of about 0.053, over five points of agreement. Effects reported in the distillation literature are frequently smaller than that: one to three points on a benchmark, a fraction of a nat on a held-out divergence, a couple of points of win rate. When a single-seed comparison reports a two-point improvement, the number it reports is well inside what changing the seed alone would produce, and the paper contains nothing that separates “the method helped” from “this seed was lucky.”

This is not an accusation of bad faith, and it needs a qualification. Seed noise is a property of a specific model, corpus, metric, and budget, and comes from the course’s small-model pair; larger models and larger evaluation sets can have smaller relative seed noise. But the burden runs the other way. Seed spread costs one extra run to measure, and a paper that does not report it has not established that its headline number exceeds its own noise floor.

So here is the test, applied in both directions. When you read a comparison, find the seed count, find the reported effect, and ask what the design’s MDE was. If seeds are not reported, “cannot tell” is the correct reading of the result, not a gap in your understanding of it. Then do the same to your own work, before you publish it, while the arithmetic still costs nothing.

18.5 Sequencing arms so that a failure is informative#

A study with four arms and three seeds is twelve runs, and twelve runs do not happen at once. The order is a design decision, and most people make it by convenience.

The principle to use instead: order arms so that the cheapest arm that could falsify your hypothesis runs first. If the cheap falsifier fires, you have refuted the hypothesis at a fraction of the study’s cost. If it does not, you have earned the right to spend the expensive part of the budget, knowing the pipeline works end to end.

Lab 12’s third solution exercise makes the ordering question concrete, because its arms have genuinely different costs even at matched token-passes. It maps a sequencing curve: spend 0, 25, 50, or 75 percent of the budget off-policy first, then switch to on-policy. With the same prices as before, and passes per step, the frozen schedule is

The pure on-policy arm () affords 225,360 steps at the expensive per-step price. The 75-percent-off arm packs 366,210 cheap off-policy steps plus 56,340 on-policy ones. All four arms land within a single step’s rounding, 13,312 passes, of the same budget, and the lab asserts that mechanically. A one-line error in the split would hand some fraction its own private budget increase, and the curve’s knee would then measure the bug rather than the schedule.

That sentence presupposes a definition of the knee, and the definition has to be frozen with the rest of the protocol. “Find the knee” by eye is post-hoc: four points and a pencil will produce a bend wherever the reader wants one, and the reader has a preferred answer by the time the points are on the page. The registered definition is arithmetic on the same seed-noise floor everything else in the study is judged against,

the smallest schedule fraction whose gain over the next-smaller fraction has fallen below the noise floor. If no fraction qualifies, the study reports no knee, which is a result and not a failure of the analysis.

Register how you will read the answer at the same time. A knee near 25 percent says a light off-policy warm start is enough and the on-policy sample-efficiency claim holds over most of the budget. A knee at 75 percent, or a curve still rising at the last point, says the opposite: on-policy training is a finishing step and not a regime, and the budget wants to be spent off-policy with a short on-policy tail. Those two readings send a practitioner to different pipelines, which is the fourth condition from §18.1 being satisfied in advance.

2026-08-01T07:20:17.074052 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0 1.0e9 2.0e9 3.0e9 token-passes (every arm lands within one step of 3.0e9) off75 25% of the budget generates tokens off00 100% of the budget generates tokens off25 75% of the budget generates tokens off50 50% of the budget generates tokens 1 and 2. endpoints: together these two can refute the existence of an interior knee 3 and 4. run only if the endpoints separate off-policy steps 6,144 passes each on-policy steps 13,312 passes each off75 and off00 spend identical token-passes, but off75 is cheaper in wall-clock: three quarters of its budget generates nothing 366,210 56,340 225,360 122,070 169,020 244,140 112,680
Figure 18.2 The sequencing study run in falsification-first order: the two endpoint arms, which together can refute the existence of an interior knee, consume less than half the budget and run first.

Now the ordering. H1 for that study says quality rises from to some interior fraction and then flattens, so a knee exists strictly inside . The cheapest thing that could falsify it is the pair of endpoints: if and land within the seed-noise band of each other, there is no room for an interior maximum worth calling a knee, and the two interior arms need not run at all. So the endpoints go first, and goes before , because it spends three quarters of its budget on steps that generate nothing and is therefore cheaper in wall-clock at identical token-passes.

The same logic applies to the capstone’s four arms. H2 predicts the largest on-policy advantage at the smaller student, so the 135M pair is both the cheapest cell and the one where the predicted effect is biggest. Run one seed of offpolicy-135M and onpolicy-135M first. If the gap there is inside the noise band, H2 is already in difficulty and you have learned it for one twelfth of the study. That first pair doubles as a pilot, which matters because the most common outcome of a first run is a broken pipeline, not a refuted hypothesis. Register the pilot as a pilot: its numbers do not enter the analysis, because a run you looked at before deciding whether to continue is a run whose inclusion depends on its result. Use it to check that the harness works and the budget arithmetic holds, then run the registered seeds fresh.

A search evaluates configurations until something looks good and reports that thing. A study runs a fixed plan and reports what came out. They can involve identical compute, code, and people. The stopping rule is what separates them.

A good-faith search produces false findings without anyone behaving badly. Metrics on a training run wander. If you evaluate every thousand steps and stop when the gap between two arms looks convincing, you have selected the moment when noise happened to point the direction you wanted, and you will do that reliably even with no true effect, because you were watching until it happened. Registering “fixed token-pass budget per arm; no early stopping on metrics” removes the option.

Three practical shapes for a stopping rule, in decreasing order of how much I trust them:

A fixed budget. Every arm runs its registered count of steps or token-passes, whatever the metrics do. This is what Lab 12 uses, and it is the only one of the three that requires no judgment at run time.

A fixed budget with a registered abort criterion. Every arm runs its budget unless a specific named failure fires, with the criterion written down and calibrated in advance. Chapter 12’s entropy monitor is the example: a criterion that fires on entropy collapse and not on healthy entropy decline, calibrated on known-good and known-bad traces before being trusted. An abort is not an exclusion. Lab 12’s exclusion rule keeps aborted runs, marks them, reports them, and removes a configuration only when at least two of three seeds trip.

A sequential rule with a spending function. Interim looks at pre-specified points, with the significance threshold adjusted for the number of looks planned rather than the number taken. This is standard in clinical trials, it is correct, and I have never seen it in a distillation paper, including mine.

The tell in a written report is a study whose arms ran for different numbers of steps for reasons explained in prose instead of in a rule. Sometimes that is honest and unavoidable, and it is also what it looks like when someone stopped a run that was going badly.

18.7 Manifests, configuration capture, and artifact hashing#

Chapter 8 defined the run manifest: a record written next to a run’s outputs carrying the run’s name, configuration, seed, fingerprint, input artifact identifiers, and output paths. That is complete for one run. A study needs the manifests to compose.

Definition

Manifest chain

The graph formed when every run’s manifest names its inputs by the fingerprint or artifact hash of the manifests and files that produced them. The chain is the study’s provenance: the checkable trail from any artifact back to whatever produced it, all the way to the frozen protocol. Its defining property is that it is verifiable mechanically, by recomputing hashes, rather than by reading.

A chain earns its keep through three properties.

Inputs are named by hash rather than by filename. A filename says where something was; a hash says what it was. Those come apart constantly: a cache gets regenerated with one changed parameter and keeps its path, or a file gets copied and edited. Either produces a study that looks internally consistent and is not, and either is caught the moment you record hashes instead of paths, at a cost of a few seconds of hashing per artifact.

Fingerprints are recomputed, not trusted. The manifest walker reloads every manifest under the runs directory, recomputes config_fingerprint({**config, "seed": seed}) from the recorded configuration, reports a mismatch against the stored value, and checks that every declared output path still exists. A mismatch means the manifest was edited or the fingerprinting code changed, and you want to know either before writing a report on top of it.

The audit runs in the same code path as the study. Lab 12 runs walk_manifests in Part A over whatever the container has from Labs 03 through 11, and on the training box the same function runs over the full study, under one assertion: assert not problems. Verifying provenance is then a function call rather than an occasion, and anything that happens only on occasions stops happening.

Here is a manifest writer, written to show the shape and not to be dropped into a pipeline. The thing to look at is that the two artifact dictionaries carry hashes rather than paths alone.

import hashlib, json, time, pathlib

def file_hash(path, chunk=1 << 20):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for block in iter(lambda: f.read(chunk), b""):
            h.update(block)
    return h.hexdigest()[:16]

def write_manifest(out_dir, name, config, seed, inputs, outputs, protocol_hash):
    body = {
        "name": name,
        "config": config,
        "seed": seed,
        "fingerprint": config_fingerprint({**config, "seed": seed}),
        "protocol": protocol_hash,
        "artifacts_in":  {tag: file_hash(p) for tag, p in inputs.items()},
        "artifacts_out": {tag: {"path": p, "sha256_16": file_hash(p)}
                          for tag, p in outputs.items()},
        "written_unix": int(time.time()),
    }
    text = json.dumps(body, sort_keys=True, indent=2)
    path = pathlib.Path(out_dir) / f"manifest_{body['fingerprint']}.json"
    path.write_text(text)
    return body["fingerprint"]

That buys the property the study is claiming: every run points at the protocol hash it ran under, at the hashes of the cache and corpus it consumed, and at the hashes of what it produced, so a walker can reconstruct the dependency graph from files on disk with no knowledge of how the study was organized.

Being rerunnable from the notebook alone needs one more thing, which is capturing the environment: library versions, model checkpoint identifiers pinned by revision instead of by tag, and the exact version of any evaluation harness, because a harness version determines what a benchmark number means and harness numbers are not comparable across versions.3 A tag can move and a revision hash cannot, which is the artifact-hash argument applied to things you did not produce.

18.8 Reporting#

The report is the artifact most people will ever see, and its job is to let a reader reach your conclusion or reject it without asking you anything. Lab 12 fixes six sections, each with a generator that produces it from the artifacts instead of from your memory: Protocol (the frozen JSON, cited by hash), Results (per-cell mean and seed range, from the manifests), Decisions (each hypothesis marked supported, refuted, or unresolved under the registered rule), Exclusions and deviations (every one, with the protocol clause that governed it), Limitations, and Reproduction (exact commands, seeds, and hashes: the stranger’s runbook).

The word “generator” is the load-bearing part. Tables computed from the manifest graph rather than typed cannot silently disagree with the artifacts. Typed tables drift, always in the direction of the story being told, and checking one against a directory of JSON gets postponed.

One failure survives all of that, and Lab 12 registers a rule against it. No narrative promotions: the prose may not upgrade a result beyond what the registered decision rule certifies. A generated table says the 360M gap is 0.012 against a minimum detectable effect of 0.015 and the Decisions section marks that hypothesis unresolved; three paragraphs later the discussion says the on-policy arm “showed an advantage at both sizes, clearer at 135M.” Every number in that sentence is real and the sentence is false, because the rule the study committed to certified one of the two gaps and nothing about the other. This is the easiest of all the failures in this chapter to commit, because it happens at writing time, after the arithmetic is finished and while you are trying to be readable. The mechanical version of the rule: for every comparative claim in the prose, name the table row and the verdict it carries. A claim with no row behind it comes out, and “unresolved” in a table may not become “suggestive” in a paragraph.

18.8.1 Tables a stranger could audit#

A table another person can audit carries, for every cell, the mean, the spread, and the number of runs behind it. A table with one number per cell is a claim about a distribution with the distribution deleted.

Table 18.2 The shape of the capstone’s results table, with the seed count carried in the cell. Values are illustrative placeholders, because Lab 12’s runs are gated in the course and the book does not print numbers it did not measure.

Student Off-policy (mean, range, n) On-policy (mean, range, n) Gap Clears MDE?
360M {mean} [{min}, {max}] n=3 {mean} [{min}, {max}] n=3 {gap} yes / no
135M {mean} [{min}, {max}] n=3 {mean} [{min}, {max}] n=3 {gap} yes / no

Placeholders in braces are an honesty mechanism, not an oversight. Lab 12’s fourth solution exercise ships a result-note template in which every to-be-measured number is an explicit {PLACEHOLDER}, with an assertion that all eleven required elements are present, so the template cannot leak invented numbers into a document that looks finished.

18.8.2 Figures that show the spread#

The equivalent rule for figures is that a bar chart of means is a way of not showing your data. Four bars with four numbers on them read as ordered to any audience, including one that knows better, because that is what a bar chart is for.

2026-08-01T07:20:16.157199 image/svg+xml Matplotlib v3.10.9, https://matplotlib.org/ 0.44 0.46 0.48 0.50 0.52 teacher-scored mean logprob of student rollouts 0.500 0.512 360M 0.462 0.497 135M four bars, four numbers: reads as an ordering to any audience, including one that knows better means only off-policy on-policy off-policy on-policy 360M 135M MDE 0.015 360M gap 0.012: whiskers overlap and the gap sits inside the MDE, unresolved 135M gap 0.035: whiskers clear each other, and the gap clears the MDE means with seed range, n = 3 off-policy on-policy off-policy on-policy illustrative placeholders consistent with Lab 12's registered MDE of 0.015 and interesting effect of 0.03. Lab 12 Part B is gated in the course: these are not measured results.
Figure 18.3 The same four cell means plotted as bare bars and as bars with min-to-max seed ranges: the ordering that looks decisive on the left has overlapping ranges on the right, and only one of the two gaps clears the registered minimum detectable effect.

Plot every seed as a point, or the range as an interval, and draw the minimum detectable effect on the figure as a band or a scale bar so a reader sees the study’s resolution next to its result. That last element is rare and the one I would most like to see become standard.

18.8.3 Limitations that are specific rather than ritual#

A limitations section is ritual when it lists things that are true of all work (“results may not generalize to other models”) and specific when a reader could act on it. The test is whether each limitation names a quantity, a scope boundary, or a design choice that a follow-up study could change. The three Lab 12 registers, before running, are all specific by that test.

One model family. Every arm uses SmolLM2 checkpoints, so anything the result depends on that is a property of that family’s pretraining, tokenizer, or architecture is confounded with the finding. A follow-up changes the family and nothing else.

One corpus domain. The result is a statement about distillation on this corpus. Chapter 10’s finding that the right cache truncation is a measurement on your corpus rather than a constant is the general form of this worry.

Token-pass matching counts arithmetic, not hardware. Stated in §18.3, registered in advance, and the follow-up that would resolve it is a wall-clock-matched replication.

Registering limitations before the runs changes the study itself. One you wrote down while designing is one you may still have time to remove; one you discovered while writing is one you are stuck with and now motivated to describe gently.

18.8.4 Publishing the arms that refuted you#

The course does not ask anything here it has not done itself, so let me point at its own negative results, all four of which are in the labs because they came out wrong.

Lab 09’s staged-versus-one-shot null. Sheared-LLaMA argues for staged, iterative pruning over one-shot pruning.4 Lab 09’s solutions test it on a 135M model: drop 8 layers at once, versus drop 4, recompute layer importance on the 26-layer patient, then drop 4 more. One-shot came out at a probe loss of 2.76 and staged at 2.85, so staged was 0.09 nats worse. The lab’s own reading is that the difference is small against the roughly 1.9 nats of damage either surgery inflicts, and plausibly owed to the staged arm’s noisier two-row probe. What survives is narrower than “the literature is wrong”: measurement-only staging bought nothing here, which localizes the published method’s advantage to the healing steps between surgeries.5

Lab 05’s rank-2 surprise. The expected narrative was that an untrained small student sits below the teacher on near-miss tokens, which is what “the student has to learn the dark knowledge” would predict.6 The measurement said otherwise: mean probability on the teacher’s rank-2 token was 0.114 for the student against 0.105 for the teacher. The lab states plainly that “the small model starts below the teacher on near-miss tokens” would be the wrong summary, and traces the discrepancy to skew: the medians do run the expected direction (0.039 against 0.052), 36 percent of positions already put the rank-2 token under one percent, and a minority of positions where the student piles mass on that token prop up the mean.

Lab 00’s refuted bf16 premise. The exercise was built on the premise that bf16’s relative error in a computed loss gets worse as logits grow. Sweeping scales from 1 to 40 against an fp64 reference, the second significant digit was never corrupted at any scale, and max(err16) < 1e-2 is in the notebook as the corrected claim; the third digit went bad at scales 2 and 12 with no monotone trend in between. The statement the book carries forward is the corrected one: bf16 holds the loss to about three digits at any scale, and the third digit is already noise at realistic post-temperature logit scales. Still ample reason for the dtype rule, for a different reason than the one originally given.

Lab 02’s k=4 row. The build originally asserted that the tail-bucket estimator always beats the renormalized one, and the row refused. The resolution is that the two estimators are biased in opposite directions, renormalization overstating and the tail bucket understating, so which one is closer at your on your corpus is a measurement rather than a theorem. That correction changed the advice the book gives about cache design.

All four are small, cheap results that turned out more instructive than the ones expected in their place. And every one of them survived into the material because somebody wrote down the expectation before running, which is the only reason anyone could tell that the result was a refutation at all.

The vocabulary Lab 12 registers for reporting a null is worth adopting verbatim. The verdict is unresolved-negative, and the note says explicitly: we report this as “no effect resolvable at this design,” not “no effect exists.” The most common failure of published negative results is readers and authors collapsing that distinction, and you prevent it by carrying the study’s own minimum detectable effect in the note. Then add the sentence that makes a null useful, which is what the next person should run instead: resolving this hypothesis needs either seeds per cell to push the noise floor below the observed gap, or a wider ratio between the conditions. That sentence comes out of the MDE arithmetic rather than out of opinion, which is why the arithmetic belongs in the protocol.

18.9 What makes a study auditable by a stranger#

Definition

Auditability

The property that a competent person who was not involved could rerun a study from its artifacts alone and reach the same conclusion. It is a specific, testable claim about a set of files, not a disposition of the author, and the test is performed by handing the artifacts to someone and watching what happens.

Lab 12’s checklist has six lines, each attemptable against the files without asking the author anything:

The fifth line catches the most, because the arithmetic takes a second and the failure it detects is severe. Twelve runs dispatched should equal reported plus excluded. If it does not, some run happened and is in neither column, and the reader has no way to know which direction it pointed.

With no stranger available, the working substitute is you in two weeks, on a fresh clone, on a different machine, with the original directory renamed so you cannot read from it by accident.

One last thing to check before you believe your own result. Lab 12 registers the expected shape of its findings in advance so that you know when to be suspicious instead of pleased: H1 supported modestly on rollout quality, with teacher-forced agreement showing little or no gap, because Chapter 12 explained why those two measures disagree by design. H2 genuinely open, either way. And a specific alarm: a huge on-policy win on every metric at once most likely means the compute matching leaked and one arm quietly got more budget than the rule intended. Audit the logged token-passes before you write anything. A result better than your design could produce is evidence about your design.

18.10 Judgment#

You can now take a question about distillation, price it on hardware you measured yourself, decide before spending anything whether the design can see the effect it is looking for, run it on a pipeline you built and verified, judge it with an evaluation you decontaminated yourself, and produce a study someone else can check. Every component came from a different chapter, and the assembly is the skill.

What remains hard is judgment more than technique, and judgment is mostly knowing which of the things you believe are established. So let me end by being specific about where this field’s foundations are softer than its literature sounds. Knowing what is unsettled is the difference between a practitioner and someone repeating a recipe.

The theory of why distillation works is unsettled. Chapter 1 gave four explanations, and after eleven years the field still cannot say how much of the benefit comes from each. Stanton and colleagues measured teacher-student agreement directly and found it worse than the generalization improvement would predict, in settings where the student had the capacity to match, which makes “the student learns the teacher’s function” an incomplete description.7 There is real theory here. Allen-Zhu and Li derive a multi-view account in which distillation transfers features an individual model would not learn alone, Mobahi and colleagues analyze self-distillation as progressive regularization in a Hilbert space, and Zhou and colleagues give a bias-variance decomposition of soft labels.8910 Each is rigorous under its assumptions and none predicts, for a given pair and corpus, how much distillation will help. The empirical regularities are firmer than the theory that would explain them: a label-smoothed teacher distills worse, a bigger teacher stops helping past some capacity gap, and patience matters more than most architectural choices.111213 Treat theory in this area as organizing intuition and demand the measurement.

Length collapse in distilled models lacks a primary reference. Distilled students frequently produce shorter and shorter outputs as training proceeds, and practitioners monitor for it because it is real and expensive to discover late. But when I went looking for a paper whose primary subject is length collapse specifically in distilled, as opposed to reinforcement-trained, language models, there is not one. The adjacent literature on entropy and diversity collapse is good: an entropy-performance exchange derived for reasoning models trained with verifiable rewards, and careful studies of where diversity collapses during post-training and of format-induced collapse.141516 They cover the mechanism, and none of them is about length in distillation. If you want a research problem with a clear gap and a cheap experiment, that is one.

Cross-tokenizer methods give up more than their headline numbers suggest. Chapter 7 proved that position-wise alignment between two tokenizer families does not exist, and Chapter 14 covered the methods that work anyway, chiefly universal logit distillation, which sorts both probability vectors and compares them under an L1 distance.17 The sorting is what makes the comparison legal across vocabularies and it is also what throws away token identity, which is most of the information the distribution carried. Published comparisons are honest about the method and less explicit about the size of that concession. If you are choosing between a cross-tokenizer method and generating traces with the teacher and fine-tuning on them, run both. Trace fine-tuning is the reference point fancier methods have to beat, and it beats them more often than the method papers imply.1819

Evaluation practice lags method development badly. This is the one I would put first if I could put only one. New objectives arrive faster than the instruments to distinguish them, and the result is a literature of comparisons at single seeds, on benchmarks whose contamination status against the distillation corpus is unstated, reporting effects their designs cannot resolve, with harness versions unpinned so the numbers are not comparable across papers even in principle.20 Calibration is rarely reported at all, despite the case Chapter 16 makes that a student can improve on agreement while degrading on calibration, which is the exact failure a deployment notices and a benchmark does not.21 None of these are hard problems. They are unglamorous ones, and no paper is rejected for having an MDE larger than its claimed effect, since nobody computes it.

That last point is where this book ends, because it is the one thing you can change on your own. The arithmetic in §18.4 takes ten minutes and one extra training run. Doing it will occasionally cost you a result you liked. It will also mean that when you say a thing is true, it is, and that someone reading you in three years can tell what you measured from what you hoped. That is a smaller ambition than the field usually advertises and the one that compounds.

The remaining distance to the frontier is reading current papers. You now do that with an operator’s eye for what their protocols hide, which is a different activity from the one you were doing before Chapter 1, and considerably more useful.

18.11 Where this lands in the labs#

Lab 12 is the capstone and deliberately the thinnest notebook in the course, because nearly everything it does is calling machinery from Labs 03 through 11. Part A executes anywhere and asserts the five things a stranger needs: the frozen protocol with its round-tripped hash, the compute-matching rule priced per step, the power check against the course’s registered prior for seed spread, which Lab 05’s runs would replace on a machine that has them, the manifest walker with its provenance assertion, and the report skeleton whose tables come from the graph. Part B is twelve runs, gated behind a flag, because the scheduling is not the lesson. The four solution exercises are each a second study forked from the capstone’s protocol, frozen, hashed, and power-checked live even though their runs are gated. Draft them yourself first: reading a finished protocol teaches much less than writing one and discovering what you left out.

18.12 Exercises#

  1. Take the last comparison you ran or read, whichever you remember better. Write down its seed count, its reported effect, and its metric. Estimate the design’s minimum detectable effect using §18.4’s arithmetic, stating the seed standard deviation you assumed and where you got it, then say whether the reported effect was resolvable by that design.

  2. A study reports that method A beats method B by 2.1 points on a benchmark, with three seeds per arm and a within-arm seed range of 1.8 points on the better arm. Apply the course’s registered rule and the standard-error rule from §18.4.2 to the same numbers. They disagree. Explain why, and say which one you would report as the author and which one you would want as the reader.

  3. The capstone charges a cache prepay of 1,572,864 token-passes against the off-policy arms, roughly 0.05 percent of the three-billion-pass budget, and someone proposes dropping it as negligible. Give the argument for keeping it that does not depend on the size of the number, and describe a variant of this study where dropping it would change the conclusion.

  4. Lab 12’s exclusion rule keeps runs that trip the entropy monitor and excludes a configuration only when at least two of three seeds trip. Construct a set of twelve run outcomes under which that rule produces a misleading report, then propose a modified rule that handles your case. Say what your modification costs on the cases the original rule handles correctly.

  5. You have a fixed budget for eight training runs and a study with four arms. Design the sequencing under the falsification-first principle, then design it again under the assumption that your real constraint is a demonstration in two weeks rather than a defensible conclusion. Name precisely what the second ordering gives up.

  6. Pick one of the four negative results in §18.8.4 and write the two-paragraph result note you would post publicly, following Lab 12’s required elements: protocol hash, seed count and list, primary metric, per-cell means with ranges, the registered decision rule, the minimum detectable effect, a specific limitations sentence, and a reproduction pointer. Use explicit placeholders for numbers the source material does not give you.

  7. Propose a study of your own on a distillation question you actually want answered, and deliver the frozen protocol: title, hypotheses stated so they can fail including an explicit null, the arm enumeration with recipes, the seed list, the primary metric defined precisely enough that someone else could compute it, secondary metrics, the compute-matching rule with its currency named and priced per step, and the stopping, exclusion, and analysis rules. Then compute the study’s minimum detectable effect and the smallest effect you would call interesting, and state whether the design passes its own power check. If it does not, fix the design rather than the threshold, and say what the fix cost. Finish with the two sentences from §18.1: if the answer is A, someone does X; if the answer is B, someone does Y.



  1. Rishabh Agarwal, Nino Vieillard, Yongchao Zhou, Piotr Stanczyk, Sabela Ramos, Matthieu Geist, and Olivier Bachem, “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649 

  2. Yuxian Gu, Li Dong, Furu Wei, and Minlie Huang, “MiniLLM: Knowledge Distillation of Large Language Models,” arXiv:2306.08543 (2023), ICLR 2024. https://arxiv.org/abs/2306.08543v2. The arXiv landing page currently shows a later, retitled version; the ICLR 2024 title is the one used here. For a survey of the area, treated as a living preprint rather than a published survey, see Mingyang Song and Mao Zheng, “A Survey of On-Policy Distillation for Large Language Models,” arXiv:2604.00626 (2026), whose comment field reads “Ongoing Work.” https://arxiv.org/abs/2604.00626 

  3. Leo Gao et al., “The Language Model Evaluation Harness,” Zenodo v0.4.3 (July 2024), DOI: 10.5281/zenodo.12608602. The repository’s own citation instructions are version-pinned, which is the correct convention: record the exact version you ran. https://github.com/EleutherAI/lm-evaluation-harness 

  4. Mengzhou Xia, Tianyu Gao, Zhiyuan Zeng, and Danqi Chen, “Sheared LLaMA: Accelerating Language Model Pre-training via Structured Pruning,” arXiv:2310.06694 (2023), ICLR 2024. https://arxiv.org/abs/2310.06694 

  5. Saurav Muralidharan et al., “Compact Language Models via Pruning and Knowledge Distillation,” arXiv:2407.14679 (2024), NeurIPS 2024, is the other reference point for prune-then-distill economics and interleaves pruning with distillation rather than treating the two as separable stages. https://arxiv.org/abs/2407.14679 

  6. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015). The dark knowledge argument is §1 and §2. https://arxiv.org/abs/1503.02531 

  7. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945 

  8. Zeyuan Allen-Zhu and Yuanzhi Li, “Towards Understanding Ensemble, Knowledge Distillation and Self-Distillation in Deep Learning,” arXiv:2012.09816 (2020), ICLR 2022. https://arxiv.org/abs/2012.09816 

  9. Hossein Mobahi, Mehrdad Farajtabar, and Peter L. Bartlett, “Self-Distillation Amplifies Regularization in Hilbert Space,” arXiv:2002.05715 (2020), NeurIPS 2020. https://arxiv.org/abs/2002.05715 

  10. Helong Zhou et al., “Rethinking Soft Labels for Knowledge Distillation: A Bias-Variance Tradeoff Perspective,” arXiv:2102.00650 (2021), ICLR 2021. https://arxiv.org/abs/2102.00650 

  11. Rafael Müller, Simon Kornblith, and Geoffrey Hinton, “When Does Label Smoothing Help?” arXiv:1906.02629 (2019), NeurIPS 2019. https://arxiv.org/abs/1906.02629 

  12. Jang Hyun Cho and Bharath Hariharan, “On the Efficacy of Knowledge Distillation,” arXiv:1910.01348 (2019), ICCV 2019. https://arxiv.org/abs/1910.01348 

  13. Lucas Beyer, Xiaohua Zhai, Amélie Royer, Larisa Markeeva, Rohan Anil, and Alexander Kolesnikov, “Knowledge distillation: A good teacher is patient and consistent,” arXiv:2106.05237 (2021), CVPR 2022. https://arxiv.org/abs/2106.05237 

  14. Ganqu Cui et al., “The Entropy Mechanism of Reinforcement Learning for Reasoning Language Models,” arXiv:2505.22617 (2025). The standard reference for entropy collapse under verifiable rewards; it derives the entropy-performance exchange and two mitigations. https://arxiv.org/abs/2505.22617 

  15. Constantinos Karouzos, Xingwei Tan, and Nikolaos Aletras, “Where does output diversity collapse in post-training?” arXiv:2604.16027 (2026). A 2026 preprint without a peer-reviewed venue at the time of writing. https://arxiv.org/abs/2604.16027 

  16. Longfei Yun, Chenyang An, Zilong Wang, Letian Peng, and Jingbo Shang, “The Price of Format: Diversity Collapse in LLMs,” arXiv:2505.18949 (2025). https://arxiv.org/abs/2505.18949 

  17. Nicolas Boizard, Kevin El Haddad, Céline Hudelot, and Pierre Colombo, “Towards Cross-Tokenizer Distillation: the Universal Logit Distillation Loss for LLMs,” arXiv:2402.12030 (2024), Transactions on Machine Learning Research, January 2025. https://arxiv.org/abs/2402.12030 

  18. DeepSeek-AI, “DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning,” Nature 645 (2025): 633-638, https://doi.org/10.1038/s41586-025-09422-z; preprint arXiv:2501.12948. The distilled model series is supervised fine-tuning on teacher traces with no reinforcement learning stage for the students. 

  19. Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947 

  20. For a sense of how fast the objective space moves relative to its instruments, compare the divergence-choice line: Yuqiao Wen, Zichao Li, Wenyu Du, and Lili Mou, “f-Divergence Minimization for Sequence-Level Knowledge Distillation,” arXiv:2307.15190 (2023), ACL 2023, https://arxiv.org/abs/2307.15190; and Jongwoo Ko, Sungnyun Kim, Tianyi Chen, and Se-Young Yun, “DistiLLM: Towards Streamlined Distillation for Large Language Models,” arXiv:2402.03898 (2024), ICML 2024, https://arxiv.org/abs/2402.03898 

  21. Chuan Guo, Geoff Pleiss, Yu Sun, and Kilian Q. Weinberger, “On Calibration of Modern Neural Networks,” arXiv:1706.04599 (2017), ICML 2017, is the source of expected calibration error and temperature scaling. https://arxiv.org/abs/1706.04599 

Appendix

A

Notation

This book uses a small alphabet and holds to it. Where a symbol carries two meanings, this appendix names the collision rather than pretending it does not exist. Four conventions run through every chapter, and getting any of them backwards will make a formula read as its own opposite.

The teacher is and the student is , everywhere. No chapter reverses this, even where the student is the thing being optimized. So the first argument of a divergence in this book is normally the teacher, the fixed distribution, and the second is the student, the one whose parameters move. Those parameters are , and the student is written where the dependence matters.

KL is written with the reference distribution first. means , an expectation under . Because is the teacher, this is the forward direction, the mode-covering one, and it is what ordinary distillation minimizes. The reverse direction is , an expectation under the student. The asymmetry is the whole content of Chapter 6, so argument order is never incidental.

Everything is in nats. Every entropy, cross-entropy, divergence, and log-probability here uses the natural logarithm, because every loss in the book does. One nat is bits. Bits appear in two places only, bits per byte in the tokenizer chapters and the one-hot label argument in Chapter 1, and both state the base in the sentence.

Temperature raises probabilities to the power before normalizing. means for teacher logits , and means for student logits . Large flattens, small sharpens, recovers the model’s own distribution, and the factor on the soft loss holds the gradient scale fixed as moves.1

Watch out

The course code inverts one of these conventions and it is the most common thing to misread in the entire API. kl_divergence(student_logits, teacher_logits, mask, direction="forward") takes the student first, but direction="forward" computes . The argument order follows the convention that the tensor you are training comes first; the direction name follows the convention that “forward” means teacher-first. Reading the argument order as the KL order gives you the wrong objective with no exception, no warning, and a loss curve that looks fine. gjsd(student_logits, teacher_logits, mask, beta=...) has the same argument order, with the teacher and the student inside the function.

A.1 Distributions and probability#

Table A.1 Symbols for distributions, their arguments, and the quantities computed directly from them.

Symbol Meaning Introduced
The teacher’s probability distribution over the vocabulary at a position Ch. 2
The student’s probability distribution at the same position Ch. 2
The student’s distribution written to show its dependence on parameters Ch. 12
Student logits, the pre-softmax vector; is one entry Ch. 2
Teacher logits, used where both logit vectors appear in one formula Ch. 5
Teacher and student distributions at temperature Ch. 5
Vocabulary size, the length of a logit vector Ch. 2
Temperature Ch. 2
Entropy of in nats, Ch. 2
Cross-entropy of logits against label Ch. 3
The hard label, treated as a one-hot vector in gradient derivations Ch. 5
The mixture inside a generalized JSD Ch. 3
A permutation of vocabulary indices, in the ULD proofs Ch. 14
The logistic function in the two-class derivation Ch. 5
Kronecker delta, 1 when and 0 otherwise Ch. 5

The table carries four collisions, all of them harmless in context and all worth knowing. is temperature in Parts I and II and the number of positions in a [B, T, V] tensor from Part III onward; the systems chapters prefer for a length when there is any risk. is a mixture distribution in Chapter 3, a per-position mask in Chapter 5’s gradient formulas, and the maximum logit in Chapter 2’s max-subtraction trick. is bytes per parameter in the systems chapters and a minibatch size in Chapter 12’s rollout-buffer arithmetic. is a cache width in Chapters 10 and 15 and the subscript on the estimators in Chapter 4, which always carry their subscript.

A.2 Divergences#

Table A.2 The divergence family. All are between a teacher and a student , in nats, with the reference distribution written first.

Symbol Meaning Introduced
Forward KL, , expectation under the teacher Ch. 3
Reverse KL, expectation under the student Ch. 3
The -divergence Ch. 3
The generator, a convex function with Ch. 3
The conjugate generator, , which swaps the two arguments Ch. 3
The two limits that decide whether a divergence is bounded Ch. 3
Jensen-Shannon divergence, the case, bounded by Ch. 3
Generalized JSD, Ch. 3
The square root of the symmetric case, which is a metric Ch. 3
Total variation distance, Ch. 3
Chi-squared divergence, Ch. 3
The per-position objective selected by , in the GKD loss Ch. 12
The sorted L1 distance used for cross-tokenizer distillation Ch. 14

The -divergence form is the organizing one: KL, reverse KL, JSD, total variation, chi-squared, and Hellinger are all for a different , which is why Chapter 3 computes each of them two ways and asserts agreement.2 The square root matters: fails the triangle inequality while is a metric on the probability simplex.3

A.3 The objective#

Table A.3 The parameters of the loss, and the loss terms themselves.

Symbol Code name Meaning Introduced
alpha Weight on the soft-target term; on the hard-label term Ch. 5
temperature Temperature applied to both logit vectors before the soft loss Ch. 5
The factor multiplying the soft term so that means the same thing at every Ch. 5
beta The interpolation parameter of the generalized JSD; is forward KL, is reverse Ch. 6
lmbda The fraction of training steps whose positions come from the student’s own rollouts Ch. 12
, the teacher term Ch. 5
, the ground-truth term at Ch. 5
Ch. 5

Two of these have traps attached, and both traps are about what a middling value means. $\beta = 0.1$ is not an objective one tenth of the way from forward KL to reverse KL, because the generalized JSD recovers the two directions only as rescaled limits and collapses to identically zero at both endpoints. is not a per-example blend, because the implementation makes one Bernoulli draw per training step, so half the batches are entirely on-policy and half entirely off-policy.4 lmbda decides which positions exist; beta decides what is computed at a position that exists.

A.4 Sequences and masking#

Table A.4 Position, length, and mask notation, plus the two conventions that make an external distillation loss agree with the library’s internal one.

Symbol Meaning Introduced
Position index within a sequence Ch. 7
The tokens at positions 1 through , the context for the next prediction Ch. 9
The label at position in a HuggingFace labels tensor Ch. 7
The logit vector predicted at position Ch. 7
The completion mask, 1 where the loss supervises and 0 elsewhere Ch. 7
The set of supervised positions after the shift Ch. 7
Prompt length in Chapter 12, sequence length in the systems arithmetic Ch. 9
Batch size, the number of sequences processed together Ch. 9
Number of generated tokens in a rollout Ch. 12

The shift convention. A model’s prediction at position is scored against the token at position . In the indices the code uses, logits are truncated to positions through and labels to positions through , so the loss is

$$\mathcal{L}{\mathrm{HF}} = \frac{1}{|S|} \sum)[y_t], \qquad S = {t \in {1, \ldots, T-1} : y_t \neq -100}$$} -\log \mathrm{softmax}(z_{t-1

The shift applies to three tensors and not one: the student’s logits, the teacher’s logits, and the mask. A mask correct over tokens is wrong over predictions until it moves with them.

The ignore index. -100 is the sentinel in a labels tensor meaning “do not supervise this position.” It is a magic number instead of a boolean mask because PyTorch’s cross-entropy takes an ignore_index argument whose default is -100. Positions carrying it enter neither the numerator nor the denominator of the mean.

A.5 Estimators#

Table A.5 Sampled-divergence notation. All three estimators are functions of a single likelihood ratio drawn from the sampling distribution.

Symbol Meaning Introduced
The likelihood ratio at a sampled token, under the relevant sampling distribution Ch. 4
, the log-ratio, which is what the estimators are actually built from Ch. 4
. Unbiased, high variance, and negative on individual samples Ch. 4
. Nonnegative and biased, with the bias equal to the negated higher moments of Ch. 4
. Unbiased and nonnegative on every draw Ch. 4
Number of samples in a Monte Carlo estimate Ch. 4
An estimate of a divergence , as opposed to the divergence itself Ch. 4

is with the control variate added, whose expectation under the sampling distribution is zero, which is why the correction costs no bias.5 Its variance advantage holds when the two distributions are close and evaporates when they are not, which is Chapter 12’s cold-start problem in miniature.

A.6 Systems quantities#

Table A.6 The hardware and cost arithmetic. Appendix B derives every formula these appear in.

Symbol Meaning Introduced
Memory bandwidth in bytes per second; 273 GB/s on the reference machine Ch. 9
Peak arithmetic rate in operations per second Ch. 9
The machine’s balance point, the arithmetic intensity needed to saturate compute Ch. 9
Number of parameters a model reads per token Ch. 9
Bytes per parameter: 2 for bf16, 1 for 8-bit, about 0.5 for 4-bit Ch. 8
Bytes moved per decode step, plus KV traffic Ch. 9
The decode ceiling in tokens per second, Ch. 9
Batch size, which multiplies the ceiling until KV traffic dominates Ch. 9
Trainable fraction of parameters under low-rank adaptation Ch. 8
Retained entries per position in a top- logit cache Ch. 10
Cache bytes per position, Ch. 10
Bytes per stored log-probability and per stored index Ch. 10
KV cache bytes per token position Ch. 9
Retained probability mass under top- truncation Ch. 10
Tail mass, , the part of the distribution the cache discarded Ch. 10

The low-rank trainable fraction is and not precisely because was already spoken for.6



  1. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015), §2. https://arxiv.org/abs/1503.02531 

  2. Imre Csiszár, “Information-type measures of difference of probability distributions and indirect observations,” Studia Scientiarum Mathematicarum Hungarica 2 (1967): 299-318. Co-discovered independently by S. M. Ali and S. D. Silvey, “A general class of coefficients of divergence of one distribution from another,” Journal of the Royal Statistical Society Series B 28, no. 1 (1966): 131-142. 

  3. Dominik M. Endres and Johannes E. Schindelin, “A new metric for probability distributions,” IEEE Transactions on Information Theory 49, no. 7 (2003): 1858-1860, which proves the triangle inequality for the square root specifically; and Ferdinand Österreicher and Igor Vajda, “A new class of metric divergences on probability spaces and its applicability in statistics,” Annals of the Institute of Statistical Mathematics 55, no. 3 (2003): 639-653, which establishes the broader family. 

  4. Rishabh Agarwal et al., “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024, which introduces both parameters. https://arxiv.org/abs/2306.13649 

  5. John Schulman, “Approximating KL Divergence,” joschu.net, 7 March 2020, the standard source for the three estimators and for the control-variate argument behind . http://joschu.net/blog/kl-approx.html 

  6. Edward J. Hu et al., “LoRA: Low-Rank Adaptation of Large Language Models,” arXiv:2106.09685 (2021), ICLR 2022. https://arxiv.org/abs/2106.09685 

Appendix

B

The reference machine

Every hardware-dependent number in this book refers to one workstation: 128 GB of unified CPU-GPU memory at roughly 273 GB/s of memory bandwidth, on arm64, CUDA compute capability sm_121. This appendix collects the arithmetic that machine generates, with each figure derived rather than quoted, and ends with the recipe for replacing it with your own.

B.1 Why the book names its machine#

The preface makes the claim; this appendix is the arithmetic behind it. The ordering of methods by cost inverts between hardware classes, so a recommendation tuned to one profile is wrong on another. Conservative is not the failure mode here; wrong is.

The reference machine has generous capacity and modest bandwidth. Generous capacity means a 70-billion-parameter model fits in memory at all, which on most single accelerators it does not. Modest bandwidth means that once it fits, generating from it is slow, because decode reads every weight for every token produced. The combination pushes the design toward methods where the large model only ever runs forward over text that already exists, and away from methods where the large model writes text.

Invert the profile, to a machine with 80 GB of memory at 3 TB/s, and the conclusions move. The 32B teacher no longer fits alongside a training student, so co-tenancy stops being an option and a served remote teacher becomes the default. But the teacher that does fit generates ten times faster, so a teacher-generated corpus, which is the expensive path here, becomes an afternoon instead of a day and a half. Same methods, opposite ranking. The arithmetic in this appendix is what lets you redo that ranking instead of inheriting mine.

B.2 Bytes per parameter#

Memory planning in this book runs on two rates.

Inference in bfloat16 costs 2 bytes per parameter. A bfloat16 number is 16 bits. A teacher you only run forward, and do not generate from, costs its parameter count times two and nothing else. An 8-billion-parameter teacher is 16 GB. A 32-billion-parameter teacher is 64 GB.

Full fine-tuning under mixed-precision Adam costs about 16 bytes per parameter, and it is worth seeing all five components, because each exists for a different reason.

Table B.1 The five per-parameter costs of full fine-tuning, and what each one is for.

Component Bytes Why it exists
bf16 weight 2 What the forward pass reads
bf16 gradient 2 One value per parameter, written by backpropagation
fp32 master weight 4 bf16 carries 8 significand bits, so a small update rounds away; the master copy is where updates accumulate
fp32 Adam first moment 4 Running average of the gradient
fp32 Adam second moment 4 Running average of the squared gradient
Total 16 Eight of the sixteen belong to the optimizer

Training a parameter costs eight times what serving it costs. Chapter 8 derives that ratio and says what it does to a plan; the table above is the derivation in one place.

Twelve of those sixteen bytes belong to the backward pass and the optimizer, and they are charged per trainable parameter, not per parameter in the checkpoint. Low-rank adaptation takes that opening: freeze the pretrained weights, train a thin additive correction, and only the adapter carries the full 16-byte cost.1 With a trainable fraction , the cost per base parameter is

At that is 2.16 bytes per parameter against 16, a factor of about 7.4. An 8-billion student costs 16 GB frozen plus roughly 1.3 GB of adapter state, near 17 GB, against 128 GB if you trained all of it. Quantizing the frozen base to 4 bits drops the base term from 2 bytes to about 0.5 and makes the adapter the dominant cost instead of a rounding error, at the price of changing the numerics of the forward pass the student is learning to match.2

B.3 The configuration budget, derived#

Table B.2 Teacher plus student memory on the 128 GB machine. Weights and optimizer state only.

Configuration Teacher, bf16 Student Subtotal Verdict
8B teacher, 1.7B student, full FT GB GB 43 GB comfortable, a good default
14B teacher, 1.7B student, full FT GB GB 55 GB comfortable
8B teacher, 4B student, full FT GB GB 80 GB workable, watch the cache
32B teacher, 8B student, LoRA GB GB 81 GB viable, quantize the teacher if tight
32B teacher, 4B student, full FT GB GB 128 GB does not fit

Every cell is one multiplication, and the table counts weights and optimizer state and nothing else: no activations, no KV cache for whatever the teacher is scoring, no allocator fragmentation, no serving-stack reservations. That is why the last row’s verdict is “does not fit” at a subtotal equal to the machine’s nameplate. §8.2 reads the rows.

The working figure this book plans against is 85 percent of nameplate capacity, so GB usable. That fraction is a planning constant and not a measurement. After your first successful run, divide observed peak by planned peak and use your own ratio.

B.4 The decode ceiling#

Autoregressive decode reads the entire weight set to produce a single token, so the memory bus sets a hard ceiling that no kernel improvement can cross:

where is memory bandwidth, is the parameters read per token, and is bytes per parameter. On this machine GB/s.

Table B.3 The single-stream decode ceiling in tokens per second at 273 GB/s, with the weight footprint in GB beside it.

Model bf16, 2.0 B/param 8-bit, 1.0 B/param 4-bit, 0.5 B/param
0.36 B 0.72 GB, 379.2 tok/s 0.36 GB, 758.3 tok/s 0.18 GB, 1516.7 tok/s
1.7 B 3.4 GB, 80.3 tok/s 1.7 GB, 160.6 tok/s 0.85 GB, 321.2 tok/s
8 B 16 GB, 17.1 tok/s 8 GB, 34.1 tok/s 4 GB, 68.3 tok/s
20 B 40 GB, 6.8 tok/s 20 GB, 13.7 tok/s 10 GB, 27.3 tok/s
32 B 64 GB, 4.3 tok/s 32 GB, 8.5 tok/s 16 GB, 17.1 tok/s
70 B 140 GB, 2.0 tok/s 70 GB, 3.9 tok/s 35 GB, 7.8 tok/s

The ceiling is linear in model size and in bytes per parameter and in nothing else, which is why the 4-bit column is exactly four times the bf16 column and the 8-bit column exactly twice it. Post-training quantization is therefore a bandwidth decision before it is a capacity one, and the two mature methods for it are GPTQ, which quantizes layer by layer against a reconstruction objective, and AWQ, which identifies weight channels the activations are sensitive to and protects them by scaling.3

The 70B bf16 cell is a capacity failure before it is a bandwidth one: 140 GB of weights against 128 GB of memory. Quantization is what makes that row exist here at all, and §15.5 reads the consequence.

Watch out

The roofline is a ceiling, not a prediction. A 4-bit model rarely decodes four times faster than the same model in bf16, because the weights have to be dequantized before they multiply anything, and dequantization is arithmetic that bf16 does not perform. Measure your own efficiency against this table and report the ratio honestly. An efficiency far under the ceiling is information about your kernel path, not a refutation of the model.

B.5 Prefill and decode, measured#

A prefill pass over existing tokens reads the weights once and produces distributions, so its effective token rate is times the step rate, not one times it. That single structural difference is worth two orders of magnitude.

The anchor this course carries comes from a published benchmark on this hardware class: a 20B model served in MXFP4, the 4-bit block-scaled format, at roughly 2,053 tokens per second of prefill and 49.7 tokens per second of decode, a ratio of

which the course rounds to 40 to 1 and designs around. It is a borrowed measurement, and §9.4 says what it does and does not license. Reproduce it on your own machine before designing against it. The band to expect elsewhere is tens to one; under 10 or over 100 is a signal about your serving stack, not about transformers.

The two ends of the design space follow from that one ratio. At 2,053 tokens per second, scoring a million-token corpus with a teacher takes seconds, about eight minutes. At 4.3 tokens per second, generating a half-million-token corpus with a 32B teacher takes about 34 hours.

B.6 KV cache bytes per token#

The cache holds one key and one value vector per layer, per KV head, per position:

with the bytes per element of the cache dtype. Total cache memory is $b_{\text{kv}} \cdot L \cdot BLB$, linear in both.

Table B.4 KV cache cost for three geometries in bf16, at a 4,096-token context.

Geometry Layers, KV heads, head dim Bytes/token Per 4,096-token sequence Sixteen concurrent
Student class, 1.7 B 24, 8, 64 49,152 0.20 GB 3.2 GB
Mid class, 8 B 32, 8, 128 131,072 0.54 GB 8.6 GB
Teacher class, 32 B 64, 8, 128 262,144 1.07 GB 17.2 GB

Grouped-query attention is what keeps the middle column from being catastrophic: the head count in the formula is the KV head count, not the attention head count, and modern checkpoints share KV heads across many query heads. Halving the cache dtype to fp8 halves every number in the table, at a quality cost that belongs in the same measurement discipline as teacher quantization.

The planning number is the crossover batch, where KV traffic catches up with weight traffic. For a 32B bf16 teacher at 4,096 tokens it sits near batch 60 and needs 128 GB for weights plus cache, so it is unreachable here; for the 1.7B student it sits near batch 17 at under 7 GB, so it is reached routinely. §9.7 derives both and says what a throughput curve does on either side of one. Paged allocation, which stores the cache in fixed-size blocks mapped per sequence, recovers the waste a naive maximum-length reservation creates and gets you closer to these numbers without getting you past them.4

B.7 arm64 practicalities#

The recurring friction on sm_121 and aarch64 is not performance. It is wheel availability.

The habits that follow are all about spending that friction early, before a run has been designed around a library.

Reuse a known-good container for the teacher server instead of building fresh. A serving stack that already runs on this architecture is a working asset. Rebuilding it against a new CUDA toolchain to pick up a version bump you do not need is how an afternoon disappears. Pin the image, record its digest in the run manifest, and treat an upgrade as its own scheduled piece of work.

Verify that the fast paths exist before designing a run around them. Fused attention kernels, 8-bit and 4-bit optimizer and quantization libraries, and fused-kernel packages for the training loop are all available on x86 as a matter of routine and are all things to check on arm64 instead of assuming. The check is one import and one capability probe, and it belongs in the pre-flight cell that runs before any model loads. Discovering at step zero that a kernel is missing costs a minute; discovering it after you have sized a batch around the memory that kernel was going to save costs a run.

Read the constraints the library itself declares. Trainer implementations carry combinations that are documented as unsupported, and those combinations change between versions. The habit this course uses is to introspect the installed configuration classes at runtime, assert that the fields the plan depends on exist, and print the defaults that matter, instead of trusting any document, including the course’s own.

B.8 Redo this for your machine#

Four numbers generate everything above. Two you look up, one you choose, and one you measure.

  1. Memory capacity , in bytes. Nameplate. Plan against until you have your own observed-to-planned ratio.
  2. Memory bandwidth , in bytes per second. Published for your part, and worth confirming with a measured decode rate on a model whose size you know.
  3. Bytes per parameter for the dtype you will actually serve in: 2 for bf16, 1 for 8-bit, about 0.5 for 4-bit once you count the scales and zero points.
  4. Active parameter count , meaning the parameters read per token. For a dense model this is the checkpoint’s parameter count. For a sparse or mixture-of-experts model it is much smaller than the checkpoint, and using the checkpoint size will make you predict a decode rate several times slower than the one you get.

With those four, every other figure in this book is one line.

Table B.5 The formulas, and which section of this appendix each one produced.

Quantity Formula Section
Weight footprint B.2
Full fine-tuning footprint B.2
Low-rank footprint B.2
Decode ceiling, one stream B.4
Hours to generate tokens B.4
Prefill rate , with your measured prefill-to-decode ratio B.5
Hours to score tokens B.5
KV bytes per token B.6
Largest batch that fits B.6

The one entry that is not arithmetic is , the prefill-to-decode ratio, and that is on purpose. It depends on the serving stack, the kernel path, the batch size, and the context length, which is exactly why the book treats the borrowed 40 to 1 as a null hypothesis to be replaced by your own measurement, and not as a constant to be carried forward.



  1. Edward J. Hu, Yelong Shen, Phillip Wallis, Zeyuan Allen-Zhu, Yuanzhi Li, Shean Wang, Lu Wang, and Weizhu Chen, “LoRA: Low-Rank Adaptation of Large Language Models,” arXiv:2106.09685 (2021), ICLR 2022. The paper’s headline memory claim is about optimizer state specifically, which is the twelve of sixteen bytes Table B.1 breaks out. https://arxiv.org/abs/2106.09685 

  2. Tim Dettmers, Artidoro Pagnoni, Ari Holtzman, and Luke Zettlemoyer, “QLoRA: Efficient Finetuning of Quantized LLMs,” arXiv:2305.14314 (2023), NeurIPS 2023. https://arxiv.org/abs/2305.14314 

  3. Elias Frantar, Saleh Ashkboos, Torsten Hoefler, and Dan Alistarh, “GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers,” arXiv:2210.17323 (2022), ICLR 2023, https://arxiv.org/abs/2210.17323; and Ji Lin et al., “AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration,” arXiv:2306.00978 (2023), MLSys 2024. https://arxiv.org/abs/2306.00978 

  4. Woosuk Kwon, Zhuohan Li, Siyuan Zhuang, Ying Sheng, Lianmin Zheng, Cody Hao Yu, Joseph E. Gonzalez, Hao Zhang, and Ion Stoica, “Efficient Memory Management for Large Language Model Serving with PagedAttention,” arXiv:2309.06180 (2023), SOSP 2023, 611-626. https://arxiv.org/abs/2309.06180 

Appendix

C

A distillation decision guide

This appendix is a routing table. Its purpose is to get you to the right chapter quickly and to tell you what the choice costs before you have spent anything on it.

The questions below are ordered by how much each one eliminates. Access eliminates the most, which is why it comes first, and the student’s starting point eliminates the least, which is why it comes last. Work through them in order and most of the method space will be gone by the third question.

C.1 What access do you have to the teacher#

Weights. Everything is available to you, including two options nothing else gives: matching internal representations, and building the student out of the teacher by deleting parts of it. Chapters 13 and 14 own those. You can also do everything the next two rows can.

Probabilities but no internals. This is a served open model, or an API that returns log-probabilities, usually truncated to a top-. Token-level distillation on any divergence is available, and so is logit caching, which is the cheapest correct pipeline on this hardware. Chapters 5, 6, and 10. A truncated top- is a real constraint and not a cosmetic one, and Chapter 10 derives the bias of each way of handling the missing mass and signs it: renormalizing the retained entries overstates the divergence, bucketing the tail understates it.

Text only. Sequence-level knowledge distillation and trace fine-tuning, Chapter 11. No divergences, no temperature, no logits. This is where a great deal of current practice lives, and it works well enough that fancier methods have to beat it, not the reverse.1

Access level is a property of a moment, not of a model. APIs that returned log-probabilities have stopped, and closed weights have been released. If your plan depends on grey-box access, write down what you would do if it went black-box, because that is the price of the option you are holding.

C.2 Do teacher and student share a tokenizer#

If they do, skip this section.

If they do not, the options narrow sharply, and the reason is the result Chapter 7 proves rather than asserts: there is no position-wise alignment between two tokenizations of the same text across tokenizer families. The same string becomes a different number of tokens at different boundaries, so “the teacher’s distribution at position ” and “the student’s distribution at position ” are not statements about the same place in the text. Every token-level divergence in Chapters 3 through 6 presumes they are.

What remains is a short list. Universal Logit Distillation sorts both probability vectors, pads the shorter to the longer, and takes the L1 distance, which is legitimate because the sorted vector does not depend on the vocabulary’s labeling.2 Representation matching compares hidden states through a learned projection, which sidesteps the vocabulary entirely at the cost of needing a projection that can exist.3 Or you fall back to sequence-level KD, which needs no alignment at all because it trains on text.

What ULD gives up is precise and worth stating in advance: it cannot tell you which token the teacher preferred, only the shape of the distribution the teacher produced. Chapter 14 proves that what ULD reports is the best-case disagreement under an unknown relabeling, which means a small ULD value is weaker evidence than it looks. The characteristic failure is a loss that falls steadily while top-1 agreement stays flat.

C.3 Is your corpus fixed, or can you afford to generate#

Price this before you decide it, using Chapter 9’s arithmetic and Appendix B’s tables.

Scoring an existing corpus is prefill. At the reference machine’s borrowed rate near 2,000 tokens per second, a 1.57-million-position corpus takes about 13 minutes, paid once. Generating a corpus is decode, and decode is bandwidth bound. The same machine generating half a million tokens from a 32-billion-parameter teacher in bf16 needs about 34 hours, because the ceiling there is 4.3 tokens per second.

That is a factor of roughly 150 between the two ways of getting a teacher to look at the same number of tokens, and it is the entire economic argument for the cached-logit pipeline. Chapter 11 §11.11 turns the factor into decision rules and argues each one. In routing terms:

C.4 Coverage or sharpness#

This is the divergence decision, and it is the first choice in the book whose consequences are visible in the text your model produces.

Forward KL, , is mode-covering. Its integrand charges the student heavily for putting near-zero probability where the teacher put mass, so the student spreads itself to cover everything the teacher does. You get diversity, higher entropy, and a student that will sometimes produce things the teacher would only rarely produce.

Reverse KL, , is mode-seeking. Its integrand charges the student for putting mass where the teacher put none, and says almost nothing about teacher mass the student ignores. You get a sharper, more confident student that concentrates on a subset of the teacher’s behavior. This is the direction the on-policy line of work generally prefers.4

Generalized JSD interpolates, with recovering forward KL and recovering reverse KL, both only as rescaled limits. Chapter 6 has the trap in full: is not “ten percent of the way,” and the objective is identically zero at both endpoints.

If you want one sentence: choose forward KL when the student will be sampled from and diversity matters, reverse KL when the student will be used greedily and a confident answer matters, and generalized JSD with a small when you want mostly forward behavior with a bounded objective as a safety valve.

C.5 The cheapest correct pipeline, or the one that addresses exposure bias#

Exposure bias is the argument for on-policy training: a student trained only on text it did not write never meets its own mistakes, so it never learns to recover from one. Chapter 12 derives the compounding.

The cheapest correct pipeline is off-policy with cached logits: prefill once, write the cache, evict the teacher, train the student alone against a fixed asset. It is restartable, auditable, and the teacher’s memory is free for the entire training run. Chapter 10.

The on-policy pipeline generates rollouts from the student inside the training loop and has the teacher score them.5 The counterintuitive part, which I had backwards at first, is that this is affordable on bandwidth-limited hardware, because the model that decodes is the small one and the teacher only ever runs a forward pass over text that already exists.

What on-policy costs is pipeline complexity and restartability. The training distribution moves as the student learns, so a resume is not a resume unless the rollout buffer’s state comes back too. The two failure modes to watch are entropy collapse and length collapse, and Chapter 12 covers how early each is visible and how to write an abort criterion that fires on the real thing and not on healthy entropy decline.

C.6 What is your student’s starting point#

Random initialization is a choice, not a default, and it is usually the wrong one.

If a small model from the same family exists, trained on the same data recipe by the same group, start from it. That is close to the best case and it is hard to beat.

If no good small sibling exists, and you have the teacher’s weights, build the student out of the teacher. Rank layers by the damage removing them does, measured rather than assumed, delete the cheapest ones, and distill briefly to recover.6 Every parameter you kept is already trained. This path needs white-box access, and it is the strongest argument in this book for downloading a model instead of renting one.

If neither is available, you are training from scratch, and the distillation signal is doing the work of pretraining as well as the work of transfer. Budget accordingly.

C.7 The methods, side by side#

Table C.1 One row per method. Teacher and student costs are for the reference machine of Appendix B; read them as orders of magnitude and not as predictions.

Method Access required Teacher cost Student cost What it buys What it gives up Failure mode to watch Ch.
Classical token-level KD Full or top- probabilities, shared tokenizer Prefill per epoch, teacher resident in memory Full fine-tuning of the student Dark knowledge at every position; the best-understood objective Teacher memory for the whole run; no exposure-bias fix swept without the factor, so silently changes meaning 5, 6
Cached-logit off-policy Same, plus a fixed corpus Prefill once, minutes; teacher then evicted Full fine-tuning, teacher-free The cheapest correct pipeline; restartable; auditable Fixed corpus; truncation bias from top- A cache whose corpus fingerprint does not match the training data 10
On-policy GKD Probabilities, shared tokenizer, teacher live Prefill over student rollouts, cheap Student decode inside the loop, moderate Addresses exposure bias; trains on the state distribution the student will actually meet Restartability; more moving parts; cold start Entropy collapse and length collapse; a stale rollout buffer masquerading as on-policy 12
Sequence-level KD Text only Teacher decode over the whole corpus; hours to days Ordinary cross-entropy fine-tuning Works with no logits at all; strong for its simplicity Dark knowledge; student entropy runs low Paying the decode bill twice; EOS handling that truncates or never terminates 11
Trace fine-tuning Text only, or a published corpus Zero if purchased; otherwise as above Ordinary cross-entropy fine-tuning The lowest-friction path; the modern reference point Choice of prompts; domain shift from someone else’s corpus Contamination between the corpus and your eval set 11
Prune-then-distill Weights An importance sweep of forward passes Brief recovery distillation A student whose parameters are already trained; a few percent of from-scratch compute White-box only; cannot separate initialization from capacity Silent reinitialization during state-dict surgery 13
ULD cross-tokenizer Probabilities, mismatched tokenizer Prefill, same as token-level Full fine-tuning at a much smaller learning rate Distillation across tokenizer families at all Which token the teacher preferred Loss falling while top-1 agreement stays flat 14
Representation matching Weights or hidden states Forward passes with hidden states retained Student training plus a learned projector A signal that bypasses the vocabulary entirely Layer pairing is a hyperparameter you must search A projector that cannot exist, found out after training rather than before 14

Two columns deserve a caution. “What it buys” is written as the method’s best case, and Chapter 1’s fidelity result applies to all eight rows: students often generalize better while agreeing with their teacher less than the improvement would suggest, so none of these rows should be read as “the student becomes a copy of the teacher.”7 “Failure mode to watch” lists the one that is hardest to see, not the only one; the chapter has the rest.

C.8 Combining methods, and why in this order#

The rows are not exclusive. The combination I would reach for on a new project with white-box access is three stages: prune to initialize, cache to train, then a short on-policy phase.

The ordering is not arbitrary, and each transition has an argument.

Prune first, because initialization is upstream of everything. Every subsequent stage’s cost is measured in student steps, and a student that starts with trained parameters needs fewer of them. Doing this later would mean throwing away the steps already spent. It also has to happen while you still have the teacher’s weights loaded, which is the same moment you would be building the cache anyway.

Cache second, because it is the cheapest way to buy the most student steps. The cache costs one prefill pass, minutes on this machine, and then the teacher leaves memory for the rest of training, which is what lets the student’s batch size be large. Running the on-policy phase here instead would spend the expensive resource, teacher residency plus student decode, at the moment the student is worst and has the most to learn from cheap signal.

On-policy last, and short, because it fixes something the first two stages cannot and nothing else fixes it. Exposure bias is about the states the student visits when it is driving, and those states only become worth training on once the student is good enough that its rollouts resemble text. That is the cold-start argument: an on-policy phase applied to an untrained student spends expensive rollouts on garbage, and the sampled-divergence estimators are at their least reliable exactly when the two distributions are far apart. Both problems shrink as the off-policy phase does its work.

The sequence goes wrong in two recognizable ways. If your student is already close to the teacher at the end of stage two, the on-policy phase has little room to work and you have added pipeline complexity for a small gain, which a held-out measurement will tell you before you commit to it. And if the corpus you cached is a poor match for the distribution the student will be used on, stage three will spend its budget correcting a corpus problem, which is the wrong instrument for that job. Fix the corpus.



  1. DeepSeek-AI, “DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning,” Nature 645 (2025): 633-638, https://doi.org/10.1038/s41586-025-09422-z; preprint arXiv:2501.12948. The distilled model series is supervised fine-tuning on teacher traces with no reinforcement learning stage for the students. The originating formulation of sequence-level KD is Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947 

  2. Nicolas Boizard, Kevin El Haddad, Céline Hudelot, and Pierre Colombo, “Towards Cross-Tokenizer Distillation: the Universal Logit Distillation Loss for LLMs,” arXiv:2402.12030 (2024), Transactions on Machine Learning Research, January 2025. https://arxiv.org/abs/2402.12030 

  3. Adriana Romero et al., “FitNets: Hints for Thin Deep Nets,” arXiv:1412.6550 (2014), ICLR 2015, https://arxiv.org/abs/1412.6550; and Xiaoqi Jiao et al., “TinyBERT: Distilling BERT for Natural Language Understanding,” arXiv:1909.10351 (2019), Findings of EMNLP 2020. https://arxiv.org/abs/1909.10351 

  4. Yuxian Gu, Li Dong, Furu Wei, and Minlie Huang, “MiniLLM: Knowledge Distillation of Large Language Models,” arXiv:2306.08543 (2023), ICLR 2024, which builds the reverse-KL objective with policy-gradient variance reduction. https://arxiv.org/abs/2306.08543v2 

  5. Rishabh Agarwal et al., “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649 

  6. Saurav Muralidharan et al., “Compact Language Models via Pruning and Knowledge Distillation,” arXiv:2407.14679 (2024), NeurIPS 2024, https://arxiv.org/abs/2407.14679; and Mengzhou Xia, Tianyu Gao, Zhiyuan Zeng, and Danqi Chen, “Sheared LLaMA: Accelerating Language Model Pre-training via Structured Pruning,” arXiv:2310.06694 (2023), ICLR 2024. https://arxiv.org/abs/2310.06694 

  7. Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945 

Appendix

D

The course API

Every laboratory in the course runs on two modules. kd_core.py holds the objective: divergences, masks, the top- cache format, and the diagnostics you report instead of the loss. kd_pipeline.py holds the engineering around it: memory and bandwidth arithmetic, the on-disk cache with its fingerprint, the collapse monitor, the cross-tokenizer loss, and the run manifests. The modules state the split in their own docstrings. kd_core answers “is the objective right.” kd_pipeline answers “will the run survive.”

For each public function below: the signature as written, what it computes, the shapes in and out, and the part a caller gets wrong. The chapters do the motivating. One convention is assumed throughout: logits are [B, T, V], masks are [B, T] boolean and True on supervised positions, losses return zero-dimensional tensors that carry gradient, and diagnostics return Python floats that do not. One convention sits outside the modules and belongs here anyway: every notebook header sets HF_HUB_DISABLE_PROGRESS_BARS=1, because widget progress bars take some notebook stacks down with them and plain log lines carry the same information.

D.1 The argument-order trap#

Read this once and it will save you a training run.

Watch out

kl_divergence(student_logits, teacher_logits, mask, direction="forward") takes the student first and computes KL(teacher ‖ student). The order of the arguments and the order of the divergence are opposite. Reading the signature as the KL order gives you the reverse of the objective you thought you selected: mode seeking where you wanted mode covering, a student that abandons the teacher’s tail where you wanted one that smears over it. Nothing errors, nothing warns, and the loss curve looks fine.

The signature is not arbitrary, and the reasoning makes the order memorable rather than something to look up every time.

The positional order is by who is differentiable. Every loss in kd_core puts the student first, as does kd_pipeline.uld_sorted_loss. The student’s logits are the tensor the optimizer owns and gradient flows back through; the teacher’s are a fixed target. That matches F.cross_entropy(input, target) and every other loss in the ecosystem, which put the prediction before the ground truth.

The direction string is by which distribution is the reference. The first slot of is the distribution whose expectation is being taken, the one deciding which regions of the vocabulary matter. “Forward” names the case where that reference is the teacher, which is the mode-covering case.

So: arguments are ordered by who is being trained, direction by who is being matched.

The beta parameter of gjsd is a second trap in the same family. It follows TRL’s convention, which is the reverse of the labelling in several write-ups of the GKD paper: beta = 0.0 approaches forward KL and beta = 1.0 approaches reverse KL.1 Setting it backwards trains the opposite objective with no error and no warning. Lab 01 §5 checks the direction numerically in four evaluations, and that is the check to repeat whenever you adopt someone else’s configuration.

One constructor detail in the same family, because it is the first thing a lab hits when it moves from kd_core to a library trainer: TRL’s distillation trainers take processing_class=, and the older tokenizer= keyword these examples were written against is gone. Labs 06 and 07 both call it out, because the argument name is the kind of thing that changes between minor versions and the kind of thing a tutorial written six months ago will still be using. Introspect the signature.

D.2 kd_core: divergences and losses#

All four take [B, T, V] student and teacher logits and a [B, T] mask, and return a zero-dimensional tensor. All four go through F.log_softmax(logits / T, dim=-1) instead of the log of a softmax, which is the numerical point of Chapter 2.

kl_divergence(student_logits, teacher_logits, mask, T=1.0, direction="forward", scale_by_T2=True)

$$\mathcal{L} \;=\; \Big\langle \textstyle\sum_i p_i (\log p_i - \log q_i) \Big\rangle_{\text{mask}} \times \begin{cases} T^2 & \texttt{scale_by_T2} \ 1 & \text{otherwise} \end{cases}$$

with the reference and the other side, both at temperature . direction="forward" sets to the teacher; "reverse" swaps them; any other string raises ValueError. The factor compensates the that softening introduces into the gradient (Chapter 5) and defaults to on, so a caller wanting the raw divergence as a measurement must pass scale_by_T2=False. The reference side is not detached, so under "reverse" gradient flows through the student in both slots, which is what that objective requires.

gjsd(student_logits, teacher_logits, mask, beta=0.5, T=1.0)

$$\mathrm{JSD}_\beta(p \,|\, q) \;=\; \beta\,\mathrm{KL}(p \,|\, m) + (1-\beta)\,\mathrm{KL}(q \,|\, m), \qquad m = \beta p + (1-\beta) q$$

with the teacher and the student. Bounded, which is why it is the safe default when the student is far too small to cover the teacher: under KL one position where the student assigns near-zero probability to something the teacher likes can dominate a batch’s gradient. Three subtleties. The mixture is clamped with EPS = 1e-9 before the log, the only place EPS guards a logarithm among the divergences. There is no scale_by_T2 parameter, so mixing gjsd with a hard-label term at is the caller’s problem. And the limits are proportional to the two KLs and not equal to them: as the value goes to zero, so recovering forward KL means dividing by .

tvd(student_logits, teacher_logits, mask, T=1.0)

Bounded in per position, no factor. Chapter 3 covers Pinsker’s inequality relating this to KL.

hinton_kd_loss(student_logits, teacher_logits, labels, mask, T=2.0, alpha=0.5)

The 2015 objective, with labels a [B, T] integer tensor.2 The soft term always runs direction="forward" and scale_by_T2=True, and at alpha >= 1.0 the function short-circuits so the cross-entropy is never computed. A caller has to know three things about it. The hard term is F.cross_entropy(..., ignore_index=-100) and uses PyTorch’s reduction instead of masked_mean, so the mask argument affects only the soft term; you set labels[~mask] = -100 yourself to make both terms cover the same positions. The hard term is computed on raw student_logits, meaning at , which is what makes the compensation necessary. And the defaults are and , so calling it bare gives a softened mixed objective and not a plain one.

The call order the labs use, which is the order that keeps the alignment correct:

# One shift, applied to both logit tensors and the mask together.
s, t, m = shift_for_next_token(student_logits, teacher_logits, token_mask)

# The hard term's labels need the same mask, in HF's -100 convention.
labels = input_ids[:, 1:].clone()
labels[~m] = -100

loss = hinton_kd_loss(s, t, labels, m, T=2.0, alpha=0.5)

# Diagnostics on the same aligned tensors, never the unshifted ones.
agree = top1_agreement(s, t, m)
fkl = kl_divergence(s, t, m, direction="forward", scale_by_T2=False)

A diagnostic on unshifted logits and a loss on shifted ones will disagree for a reason that takes a day to find.

D.3 kd_core: shift and mask utilities#

The module’s section comment says nearly every real distillation bug lives here and not in the divergence. Chapter 7 is the long version of why.

masked_mean(per_position, mask) -> Tensor. Mean of a [B, T] quantity over the True entries of a [B, T] mask, denominator clamped at 1.0 so an empty mask returns zero instead of NaN. Two subtleties. It is a per-token mean, so a batch mixing long and short completions weights the long ones more heavily; usually right for a loss, wrong for a reported per-example metric. And masked-out positions are zeroed by torch.where selection before the multiplication, because a padded position can legitimately hold inf or NaN and is NaN, so multiplying by the mask would let an excluded position poison the mean.

shift_for_next_token(student_logits, teacher_logits, token_mask) -> (Tensor, Tensor, Tensor). Returns (student_logits[:, :-1], teacher_logits[:, :-1], token_mask[:, 1:]). Logits go in [B,T,V] and come out [B,T-1,V]; the mask goes in and comes out [B,T-1]. The supervised-position count survives only when the first token was never supervised, which holds whenever prompts are unsupervised, and Lab 02 asserts that equality. The shift applies to all three tensors together; applying it to two of three is the classic silent bug.

completion_mask_from_prompt_lens(input_ids, prompt_lens, pad_token_id=None) -> BoolTensor. input_ids is [B, T], prompt_lens is a sequence of B integers, output is [B, T]. Computes position >= prompt_len, optionally intersected with input_ids != pad_token_id. Exclusion is by token id, which is the mechanism behind the pad-equals-EOS bug: if the two ids are the same integer, the real EOS becomes unsupervised and the student never learns to stop.

onpolicy_mask(generated, eos_token_id) -> BoolTensor. [B, T] in, [B, T] out. True on generated tokens up to and including the first EOS. The - is_eos.long() term in the cumulative sum is what includes the EOS instead of excluding it, and including it is the point: training past EOS teaches the student to model padding.

sequence_logprob(logits, tokens, mask) -> Tensor [B]. Summed of the given tokens, per sequence. Two departures from the rest of the module: masking here is by multiplication and not by selection, and the result is a sum, not a mean, so length normalization is the caller’s job.

D.4 kd_core: top- caching#

A 151,000-token vocabulary in bf16 costs about 302 KB per position stored dense, so one million cached positions is about 302 GB. That 302 is the labs’ rounded constant, taken at ; the exact figure for the Qwen-class padded vocabulary of 151,936 entries is bytes, about 304 KB, and the difference never changes a decision. Nobody stores dense either way. Chapter 10 is the full treatment.

make_topk_cache(teacher_logits, k=32, T=1.0) -> dict. Takes [B, T, V] logits, returns {"topk_logprobs": [B,T,k], "topk_idx": [B,T,k], "tail_logprob": [B,T], "k": tensor(k)}. The tail is , clamped at zero first against a tiny negative from rounding and then at EPS against . It stores log-probabilities instead of logits because that is what an inference server hands you, and because it fixes the temperature the normalization happened at, which then becomes unrecoverable from the arrays alone.

topk_forward_kl(student_logits, cache, mask, T=1.0, use_tail=True, scale_by_T2=True). Forward KL when only the teacher’s top survived. use_tail=True adds one aggregate bucket term matching the student’s total mass outside the top ; use_tail=False renormalizes over the kept entries and drops the tail. Section D.11 covers the docstring’s claim about the bias of each.

Everything inside runs in fp32 regardless of the student’s dtype, which is load-bearing and not defensive. The student’s mass on the teacher’s top routinely exceeds 0.996 on teacher-forced text; bf16’s spacing immediately below 1.0 is , so both that sum and the 1 - 1e-6 clamp bound round to exactly 1.0, log1p(-1.0) is , the tail term becomes , and the loss is NaN from the first step. The .float() on the student logits is the fix; Chapter 2 §2.8 walks the arithmetic.

topk_truncation_bias(student_logits, teacher_logits, mask, ks, T=1.0) -> list[dict]. One row per , with keys k, dense_kl, renorm_kl, tail_bucket_kl, mean_mass_covered, renorm_rel_err, tail_rel_err, relative error being . All three divergences use scale_by_T2=False, and dense_kl is computed once and repeated in every row so rows stay self-contained. Run it on your own corpus before committing to a .

bytes_per_token_cache(vocab_size, k, dtype_bytes=2, idx_bytes=4) -> dict. Pure arithmetic. Dense is ; sparse is , the trailing being the tail value. Returns dense_bytes_per_token, topk_bytes_per_token, compression, dense_gb_per_1M_tokens, topk_gb_per_1M_tokens. The index costs 4 bytes against the value’s 2, so two thirds of the sparse cost is bookkeeping; and GB here means bytes, not .

D.5 kd_core: diagnostics#

All five return Python floats, not tensors, so none can be a loss. That is deliberate; Chapter 16 is about why the loss is not evidence.

Table D.1 The diagnostics, their inputs, and the failure each one sees.

Function Inputs Returns Sees
top1_agreement(student_logits, teacher_logits, mask) two [B,T,V], one [B,T] fraction in whether the student picks the teacher’s token
mean_entropy(logits, mask, T=1.0) [B,T,V], [B,T] nats entropy collapse, before length and diversity collapse show
expected_calibration_error(logits, labels, mask, n_bins=10) [B,T,V], [B,T] ids, [B,T] a student growing more confident and less correct at once
distinct_n(samples, n=3) list of strings ratio repetition across samples
self_bleu(samples, n=3) list of strings , higher is less diverse samples converging on each other

Details that bite. top1_agreement resolves argmax ties by first index. expected_calibration_error takes raw token ids at the prediction positions, not -100-masked labels, and has no temperature parameter, so it is always at ; its first bin is closed at both ends and the rest half-open, empty bins are skipped, and it returns 0.0 on an empty mask. Both text metrics tokenize on whitespace, so they are word-level, and self_bleu is a set-membership precision against the union of the other samples with no brevity penalty and no geometric mean over , so it should not be compared against published BLEU numbers.

D.6 kd_pipeline: memory and bandwidth planning#

Pure arithmetic, testable without a GPU, which is what lets every Tier 2 lab assert its budget in Part A before anything loads. Parameter counts are in billions and the unit conversion is implicit: parameters at 16 bytes is 16 GB, so these return GB directly. Chapter 8 derives the memory rules, Chapter 9 the bandwidth ones.

Table D.2 The planning functions.

Signature Returns Formula
full_ft_gb(params_b) GB : bf16 weights and grads, fp32 Adam moments and master weights
lora_ft_gb(params_b, trainable_frac=0.01) GB , frozen base plus the adapter slice
infer_gb(params_b, bytes_per_param=2.0) GB
kv_cache_gb(n_layers, n_kv_heads, head_dim, seq_len, batch, bytes_per=2) GB
bandwidth_bound_decode_tps(params_b, bw_gbs, bytes_per_param=2.0) tokens/s
decode_wallclock_hours(n_tokens, tps) hours
prefill_wallclock_hours(n_tokens, prefill_tps) hours same arithmetic, different rate

None include activations; every call site adds those separately. The leading 2 in kv_cache_gb is K-and-V, distinct from bytes_per, and the function takes KV heads, so it is already correct for grouped-query and multi-query attention. bandwidth_bound_decode_tps is an upper bound from weight reads alone and per stream: everything it ignores, starting with KV traffic, only lowers the real number, and batching is the caller’s to apply.

MemoryPlan(total_gb, parts={}, headroom_frac=0.15). A dataclass with add(name, gb) returning self so plans build fluently, a planned_gb property, a fits property testing planned_gb <= total_gb * (1 - headroom_frac), a table() string, and assert_fits() whose AssertionError embeds the table. The headroom covers KV cache, activations, and fragmentation, and the docstring calls 0.15 a floor and not a target. One sharp edge: parts is keyed by name, so adding the same name twice overwrites instead of accumulating.

D.7 kd_pipeline: the cache writer and reader#

One directory per cache, holding a manifest.json and five .npy files: topk_logprobs [n_rows, T, k] float16, topk_idx [n_rows, T, k] int32, tail_logprob [n_rows, T] float16, mask [n_rows, T] bool, and input_ids [n_rows, T] int32. The ids are kept so alignment stays checkable after the fact.

TopKCacheWriter(path, k, vocab_size, seq_len, temperature=1.0), with append(teacher_logits, input_ids, mask) and finalize() -> dict. append asserts the vocabulary and sequence-length dimensions against the constructor’s values, casts to fp32 before the log softmax regardless of the caller’s dtype, takes the top , computes the tail with a 1e-9 clamp bounding it near nats, and downcasts to the storage dtypes. It accumulates in RAM until finalize, so peak memory is the whole cache and that line item belongs in your MemoryPlan. finalize writes the arrays plus a manifest carrying k, vocab_size, seq_len, temperature, n_rows, bytes_on_disk, and the corpus fingerprint. bytes_on_disk counts array payload only, not .npy headers or block rounding, which is why Lab 04’s storage check allows ten percent; and because log-probabilities are stored, the temperature survives only in the manifest.

TopKCacheReader(path), with __len__, verify_against(input_ids), and batch(rows) -> dict. The constructor memory-maps all five arrays, which is what lets a training stage charge half a gigabyte for a much larger cache. batch materializes the selected rows (fancy indexing a memmap yields a view that must be copied), upcasts the fp16 log-probabilities to fp32 to match topk_forward_kl’s policy, promotes indices to int64 because gather requires long, and returns a dict shaped for topk_forward_kl plus input_ids, mask, and k. That "k" is a zero-dimensional tensor, which is why lab code slicing the dict guards with if v.dim() > 1.

Watch out

verify_against is load-bearing, not a formality. It hashes np.ascontiguousarray(input_ids.astype(np.int32)).tobytes() and refuses to proceed unless the first sixteen hex digits match the manifest’s corpus_fingerprint. Both the int32 cast and the contiguity call are required: a different integer width or a non-contiguous view hashes differently for identical ids. Row order is part of the hash, so a changed dataset shuffle trips the check, and that is intended. A cache that cannot prove it belongs to your corpus is not an asset. Chapter 10 §10.7 covers validating one you did not produce.

D.8 kd_pipeline: monitors#

EntropyMonitor(floor_nats=0.15, drop_frac=0.6, window=20), with update(step, entropy_nats), a collapsed property, and report(). With fewer than three observations collapsed is always False. After that it fires when the latest entropy is below floor_nats, or when it has fallen more than drop_frac below the first entry of the trailing window. That last detail is the one to internalize: the relative test compares against the start of the window rather than the global start, so a slow monotone decline can evade it while a sharp drop inside the window trips it. The docstring calls the heuristic deliberately simple and says to tune both thresholds on your own runs. Chapter 12 covers calibration.

D.9 kd_pipeline: the ULD loss#

uld_sorted_loss(student_logits, teacher_logits, student_mask, teacher_mask, T=1.0) -> Tensor. Student logits are [B, Ts, Vs], teacher logits [B, Tt, Vt], masks [B, Ts] and [B, Tt]. The vocabularies need not match, which is the entire point.3

Per batch element: softmax both sides over their supervised positions, pair positions in order, truncate to the shorter side’s count, sort each row descending, zero-pad the shorter vocabulary axis to , and take the mean distance. Elements with no supervised positions on one side are skipped, and if every element is skipped the function asserts instead of returning a meaningless zero. Two limitations, both acknowledged in the source: the loop over batch elements runs in Python and is not vectorized, and pairing positions in order is the crude alignment of the original paper, whose principled upgrade is span matching under incremental decoding. Chapter 14 proves the five properties that make sorted comparison legitimate.

D.10 kd_pipeline: seeds, fingerprints, and run manifests#

set_seed_everywhere(seed) seeds random, numpy, torch, and CUDA when available. It does not touch torch.use_deterministic_algorithms or the cuDNN flags, so it controls sampling and initialization but not kernel-level nondeterminism.

config_fingerprint(cfg) -> str returns the first twelve hex digits of the SHA-256 of json.dumps(cfg, sort_keys=True, default=str). Key order is irrelevant by construction. default=str stringifies unserializable values instead of raising, which is convenient for paths and dangerous for anything whose repr carries a memory address: a lambda in your config makes the fingerprint unstable across processes. The seed is not folded in automatically, which is why every call site passes {**cfg, "seed": SEED}.

RunManifest(name, config, seed, artifacts_in={}, artifacts_out={}, notes=""), a dataclass with a fingerprint property equal to config_fingerprint({**config, "seed": seed}) and a save(directory) method writing manifest_{name}_{fingerprint}.json. artifacts_in maps a name to a fingerprint and is the provenance link: Lab 04 passes {"cache": reader.manifest["corpus_fingerprint"]}, chaining a checkpoint back to the exact cache and therefore the exact corpus. Chapter 18 walks the resulting graph.

D.11 Where the documentation disagrees with itself#

The course’s own materials say opposite things about the same fact in two places. The book has resolved both, and I would rather state the resolution than quietly patch one side, because a reader with the source open will otherwise spend an hour deciding which sentence to believe.

The renormalizing estimator overstates the divergence; the topk_forward_kl docstring says it understates. The use_tail=False branch reads “systematically understates the divergence because it pretends the teacher never considered anything else.” The clause after “because” is right; the verb before it is not. Pretending the teacher never considered anything else makes the teacher look sharper than it is, and a sharper reference raises the divergence. Lab 01 §6 and Lab 02 §5 both state the direction correctly in prose, and Lab 02 asserts it on real shifted logits at every in its sweep: renorm_kl >= dense - 1e-6 and tail_bucket_kl <= dense + 1e-6. Chapter 7 §7.10 and Chapter 10 §10.3 derive both signs, including the identity for the tail bucket’s deficit. Resolve in favor of the assertions and the derivations. Sign errors in documentation survive for years because they read fluently, sit next to correct code, and never execute.

bf16 has seven mantissa bits and eight bits of significand precision, and both numbers are correct. Lab 00’s format table says 7; the topk_forward_kl docstring says 8. Neither is a typo and both describe the same format: a normal float’s leading 1 is implicit and occupies no storage, so bf16 stores 7 bits and carries 8 bits of precision. Use the stored count for storage layout and machine epsilon, which for bf16 is ; use the significand count for spacing, which is why the spacing immediately below 1.0 is , the number the tail-term bug in §D.4 turns on. Chapter 2 §2.7.4 states the same relationship for fp32 (23 stored, 24 precision) and fp16 (10 and 11). Say which count you mean.

One smaller drift, while you are in the file. The section comment above the top- functions says “Lab 06 measures it” about the truncation bias. The measurement is in Lab 01 §6 and Lab 02 §5, with the storage arithmetic in Lab 04’s pre-flight; Lab 06 is the sequence-level and black-box lab. A stale pointer, not a disputed fact.

D.12 The shapes convention, and why nothing here imports a model#

Every function in kd_core takes [B, T, V] logits and a [B, T] boolean mask. Every function in kd_pipeline takes parameter counts, tensors, or floats. Neither module imports a model, a trainer, a tokenizer, or a serving client. Writing kd_loss(model, batch) would be shorter at every call site, so the choice needs defending.

The objective is the part that has to be exactly right, and the part that survives every change of framework. A tensor-in, tensor-out function can be checked against autograd or a closed form on synthetic inputs in milliseconds, on any machine, with no download, which is why Lab 01 can assert every identity in this appendix and fail loudly if one stops holding. A function that takes a model cannot be tested that way, so in practice it is not tested.

The tensor interface is the only thing every source of logits shares. The teacher’s distribution arrives as a Hugging Face forward pass, a memory-mapped cache, a JSON payload from a vLLM server, and a hand-built tensor in an assertion. Writing the objective against the shape is what lets Lab 04 swap a live teacher for a cache and Lab 08 swap a local teacher for a remote one without touching the loss.

It keeps the expensive objects visible. Budgets take parameter counts and monitors take floats, so models stay in the notebook where you can watch them load and be evicted. A memory plan asserted before a model exists is worth more than one computed from a model already resident.

The cost is real: the caller owns alignment, so the caller can get it wrong, and shift_for_next_token is a call you have to remember, not a thing that happens for you. Chapter 7 spends a section on that trade and on the Lab 02 §3 assertion pinning the external path to the ecosystem’s internal one to float precision. That assertion is the price of the convention, paid once.



  1. Rishabh Agarwal, Nino Vieillard, Yongchao Zhou, Piotr Stanczyk, Sabela Ramos, Matthieu Geist, and Olivier Bachem, “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649. The method is GKD, a name that does not appear in the title. kd_core follows the convention used by TRL’s implementation. 

  2. Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015), §2. https://arxiv.org/abs/1503.02531 

  3. Nicolas Boizard, Kevin El Haddad, Céline Hudelot, and Pierre Colombo, “Towards Cross-Tokenizer Distillation: the Universal Logit Distillation Loss for LLMs,” arXiv:2402.12030 (2024), Transactions on Machine Learning Research (January 2025). https://arxiv.org/abs/2402.12030 

Appendix

E

Chapter-to-lab crosswalk

The book has a companion: thirteen laboratory notebooks, numbered 00 through 12. They were written together and they divide the work on purpose. The book explains and derives; the labs assert. Almost every claim in the first half of this book is checked mechanically in a notebook against autograd or a closed form, which means the notebook fails loudly if the claim ever stops being true. That is a stronger guarantee than a book can offer on its own, and it is the reason the two exist as a pair instead of as a text with exercises bolted on.

The labs come in two tiers, and each one states its tier on its first line. Tier 1 runs anywhere and is fully asserted. It works on synthetic logit tensors, real tokenizers, and small cached models; it needs no GPU and finishes in minutes; every claim carries an assert. Labs 00, 01, and 02 are Tier 1, and their saved outputs come from real executions. Tier 2 is the real training runs, and each Tier 2 notebook has a three-part shape. Part A is a pre-flight that runs anywhere and is fully asserted: memory arithmetic, config validation, cache integrity, loss verification, data preparation. Part B is the training itself, gated behind a RUN_TRAINING flag that you flip on the training box and leave off everywhere else. Part C is the verdict: the expected output ranges, the failure signatures to watch for, and a written judgment about whether the run did what the lab claims. Lab 11 straddles the tiers, with an eval harness and a failure gallery that execute anywhere and a security experiment that does not.

The gating is a design decision worth understanding before you meet it. A green checkmark produced by a toy stand-in on hardware unlike yours would be false confidence, so Part B declines to produce one. What the split buys is that everything worth asserting everywhere is asserted everywhere: the objective, the masking, the cache arithmetic, the tokenizer alignment. A wrong beta or an unshifted mask trains the wrong thing without raising an error or bending the loss curve, and Part A is built to catch that class of failure before any hardware is committed.

The reading order I would recommend is chapter, then lab, then back to the chapter’s exercises. Read the chapter for the derivation and the failure modes. Run the lab, which puts a number on something the chapter argued and will occasionally refuse to agree with your intuition. Then do the exercises, which are thinking exercises and not coding ones, and they land differently once you have watched the thing execute. Lab first tends to produce a notebook you have run and a mechanism you cannot explain.

E.1 Chapter to lab#

Table E.1 What makes each chapter concrete.

Chapter Labs Where in the lab What the lab does that the chapter cannot
1. What Distillation Is, and What It Is Not 00, 01 Lab 00 as the entry point; Lab 01’s temperature-limit cell Nothing here is asserted, because taxonomies are not assertable; the labs check the specific claims the taxonomy organizes
2. Distributions Over Tokens, and the Numbers That Hold Them 00, 01 Lab 00 §1, §2, §7, plus §3’s entropy portion; Lab 01 §1; Solutions 00 Ex1 Lets you move one number and watch the failure move, which is the only way overflow and underflow become intuitions rather than facts
3. Measuring the Distance Between Two Distributions 00 Lab 00 §3 through §6; Solutions 00 Ex2 and Ex3 Computes every divergence twice through independent code paths and asserts agreement, so the generator table is checked, not transcribed
4. Estimating Divergences from Samples 00, 07 Lab 00 §9; Solutions 00 Ex4; Lab 07’s second exercise Writes the variance assertion the “wrong” way round on purpose, so tidying the cell into the folklore version breaks the build
5. The Classical Objective 01, 03 Lab 01 §2 and §3; Lab 03’s arms and capacity-gap probe; Solutions 03 Ex1, Ex2, Ex4 Checks the gradient identity against autograd and the high-temperature limit against logit MSE, then runs the objective on a real model pair
6. Choosing a Divergence 01, 05 Lab 01 §4, §5, §8; Lab 05 in full with its four solution exercises Trains six real students differing in exactly one configuration key and grades them against a prediction file with a timestamp on it
7. Tokenizers, Templates, and Alignment 02 Lab 02 in full, especially §3, §5, and §6; Solutions 02 Ex1, Ex3, Ex4, Ex5 Executes both loss paths on real model outputs and asserts the floats agree, which is the claim the whole of Part III rests on
8. The First Real Run 03 Lab 03 in full, Part A above all; Solutions 03 Makes you write a verdict from your own logs when two diagnostics disagree about which arm won
9. The Economics of Teacher Compute 04, 06, 08 Lab 04 A·1; Lab 06 A·1; Lab 08’s arithmetic; Solutions 06 Ex4 Prices a corpus for four teacher sizes in under a second, then later replaces every borrowed number with one you measured
10. Off-Policy Distillation and the Logit Cache 01, 02, 04 Lab 01 §6; Lab 02 §5; Lab 04 in full including the tamper attack; Solutions 02 Ex2 and Solutions 04’s four exercises Lets you feel the loss curve lie: three arms on a tiny world, indistinguishable curves, agreement numbers differing by a factor of sixty
11. Sequence-Level and Black-Box Distillation 06 Lab 06 in full, including the dead-code finding and the purchased-asset audit; Solutions 06 Ex4 Makes the drop rate print, the audit assert, and the library’s branch structure fail loudly if an upgrade moves it
12. On-Policy Distillation 07 Lab 07 in full: the five arms, the monitor calibration, the rollout offset, buffer staleness, the deliberately degenerate arm; Solutions 07 Ex3 Has you watch a detector you wrote fire on a trajectory you built, then discover by sweeping that the intuitive threshold halts a healthy run
13. Student Initialization: Prune, Then Distill 09 Lab 09 in full: the importance sweep, the four surgical checks, the depth sweep, the staged-versus-one-shot null result Performs real surgery on a real checkpoint on CPU, and prints thirty damage numbers you did not know in advance
14. Cross-Tokenizer and Representation Distillation 10 Lab 10 Part A’s five ULD properties; the layer-pair sweep, the projector ridge check, the wrong-teacher control Puts your hands on the property battery before the training gate opens, which is how the prove-before-you-train habit becomes yours
15. Serving a Teacher and Measuring Your Machine 08 Lab 08 in full: the MoE detection chain, the backwards audit, the quantization ladder, the co-tenancy frontier, the amortization knee Replaces this book’s borrowed throughput numbers with a machine profile you measured, which later labs size against
16. Evaluating a Distilled Model 11, 02, 05 Lab 11 movements 1 and 2; Solutions 11 Ex1 and Ex2; metric definitions from Labs 02 and 05 Puts the contamination false positive in front of you as a running assertion, condemning your own honest eval set
17. Security: What Distillation Carries and What It Leaks 11 Lab 11 movement 3; Solutions 11 Ex3 and Ex4 Registers the transfer protocol as JSON before anything runs, and prices an API disclosure policy in a single cell
18. Research You Can Defend 12 Lab 12 in full: the frozen protocol, the compute-matching rule, the power check, the manifest walker, the report skeleton Round-trips a protocol hash and refuses to proceed if the file changed, which is pre-registration you cannot quietly edit

E.2 Lab to chapter#

Table E.2 The thirteen notebooks and what each one is for.

Lab Tier Chapters The single most valuable thing it teaches
00. Distributions, Divergences, and Numerics 1 2, 3, 4 That a divergence computed two ways must agree, and that checking it is cheap enough to do every time
01. The Objective, Verified 1 5, 6, 10 That an identity you believe can be checked against autograd in seconds, including the factor and the beta direction
02. Tokenizers, Alignment, and Real Model Outputs 1 7, 10, 16 That an external shift-and-mask reproduces the ecosystem’s internal loss to float precision, on a ragged chat-templated batch
03. Your First Real Run: Classical KD and the Capacity Gap 2 5, 8 The pre-launch ritual, and that a five-arm comparison is made trustworthy before any of it runs
04. The Cached-Logit Pipeline 2 9, 10 That the loss curve cannot certify health, demonstrated with two sabotaged arms whose curves are indistinguishable from the correct one
05. Divergence Choice, One Variable, Measured Honestly 2 6, 16 Ablation discipline: one variable moving, fixed seeds and data and steps, and a prediction registered before the run
06. Sequence-Level and Black-Box Distillation 2 9, 11 To introspect the installed library object instead of trusting any document, including the course’s own
07. On-Policy Distillation 2 4, 12 To build a collapse detector, calibrate it against trajectories with known ground truth, and abort on diagnostics, not on loss
08. Engineering and Scale: The Teacher Server 2 9, 15 To measure your own machine and audit the measurement against a roofline, reporting the efficiency honestly
09. Student Initialisation: Prune-Then-Distill 2 13 State-dict surgery with checks that prove the patient survived, and that measured importance beats a magnitude proxy
10. Cross-Tokenizer and Representation Distillation 2 14 To implement a loss from a paper and prove its properties before trusting it, which is the most defining skill in the sequence
11. Evaluation, Failure Modes, and Security 1 and 2 16, 17 To build an eval you trust, including the contamination check that first accuses your own clean data
12. Capstone: A Reproducible Distillation Study 2 18 To freeze a protocol, hash it, and answer a question to a standard a stranger could rerun

E.3 If you want to run the course, not only read it#

The dependency structure is shallower than it looks. Lab 03’s Part A builds the shared corpus that the later labs reuse. It tokenizes an instruction set and writes train.pt and eval.pt, carrying input_ids, prompt_lens, and mask, and Labs 04, 05, 07, 09, and 10 all load them. Building the corpus once is what keeps their results comparable with each other and with Lab 03’s own arms. After that step the labs are independent except through the artifact chain below, so you can work them in whatever order your hardware and your interest allow.

Labs 00, 01, and 02 come first and have no dependencies. They are the fastest useful thing in the course: a laptop, a few minutes each, about a gigabyte of downloads for Lab 02’s two checkpoints.

Table E.3 What each lab leaves behind for the next one.

Artifact Written by Consumed by Why it chains
Tokenized corpus, train.pt and eval.pt Lab 03 Part A 04, 05, 07, 09, 10 Keeps every downstream comparison on identical data
Top- logit cache with its corpus fingerprint Lab 04 05, 07, 09 The expensive prefill is paid once; the fingerprint proves which corpus it belongs to
Distilled student checkpoint Lab 04 07 as a warm start, 11 as an evaluation subject Makes the cold-start comparison in Chapter 12 possible
Divergence results table with per-arm agreement Lab 05 12’s power check Supplies the measured seed spread that the minimum detectable effect is computed from
Machine profile, the measured prefill and decode curves Lab 08 09 through 12 Later labs size against your numbers instead of quoting this book’s
Pruned initialization and its surgery record Lab 09 09’s own arms Records which layers were kept and what removing each one cost
Cross-family ULD baseline Lab 10 10’s own training arms Gives the cross-tokenizer number context before training exists to change it
Decontaminated eval set and the contamination report Lab 11 12 The capstone’s scoring harness, with the near-duplicates already removed
Run manifests, one per run, with input and output fingerprints All Tier 2 labs 12’s manifest walker Turns the whole set of runs into a provenance graph a stranger can audit

What runs anywhere. All of Labs 00, 01, and 02. Part A of every Tier 2 lab, which includes the memory plans, the loss and gradient assertions, the cache format and its tamper check, the collapse monitor and its calibration, the five ULD properties, the contamination checker, the protocol freeze and the power check, and the manifest walker. Lab 09’s Part A goes further than most and performs real depth-pruning surgery on a 135M checkpoint on CPU, asserting that the patient survived. Lab 08’s Part A verifies a remote scoring client end to end against a mock server without a real one existing. If you never touch a training box, this is still a substantial course, and it is where the habits worth carrying are located.

What needs the training machine. Part B of Labs 03 through 10 and of Lab 12, and Lab 11’s four-cell marker-transfer experiment. Lab 08’s Part B needs more than a GPU: it wants a served teacher in its own process alongside a training student, the one multi-process configuration in the sequence. Lab 06’s Part B is the genuinely expensive run, because it is the only place the large model decodes, and its Part A prices that decode to the hour before the gate opens. On a limited budget of training time, spend it on Lab 03 first, for the complete unhurried lap, and then on Lab 05, because an ablation you ran yourself is worth more than five you read about.

The reference hardware for all of it is one workstation: 128 GB of unified CPU and GPU memory at roughly 273 GB/s, arm64, CUDA compute capability sm_121. Appendix B has the budget tables. If your machine differs, Chapter 9 gives you the arithmetic to redo the sizing instead of inheriting mine, and Lab 08 gives you the measurements to replace the constants it starts from.

Glossary

Every term the book defines formally, with the chapter that introduces it. Terms defined in more than one place list every chapter that sharpens them.

A#

Ablation Ch. 6

An experiment that isolates the effect of one factor by changing that factor while holding everything else fixed: same model pair, same data, same number of steps, same learning rate, same seed, same evaluation. The word comes from the practice of removing a component to see what breaks without it, and it has widened to mean any single-variable comparison.

Abort criterion Ch. 12

A rule evaluated automatically during training that halts the run and preserves the most recent checkpoint when a monitored quantity indicates a state the run will not recover from. It exists because the alternative, a human noticing, reliably happens a few hundred steps late, by which time the checkpoint worth keeping has been overwritten.

Amortization Ch. 9

Spreading a fixed cost, paid once, across every use that benefits from it. A cost that is prohibitive as an operating expense can be reasonable as a capital expense if enough uses share it, and the break-even point is the number of uses at which the fixed cost equals the total savings.

Arithmetic intensity Ch. 9

The number of floating-point operations a computation performs per byte it moves out of memory. For a weight matrix applied to positions in a dtype of bytes per parameter, the intensity is , independent of the matrix’s shape. It is the quantity that decides whether a computation is limited by the machine’s arithmetic units or by its memory bus.

Arm Ch. 8

One configuration in a controlled comparison. The term is from clinical trials, where each group of patients receiving a distinct treatment is an arm of the study. Within a comparison group, arms must differ in exactly one configuration key.

Artifact hash Ch. 18

A hash computed over the bytes of a stored artifact: a protocol file, a cache shard, a checkpoint, an evaluation output. It differs from Chapter 8’s configuration fingerprint, which is a hash over a configuration dictionary and answers “were these produced by the same settings.” An artifact hash answers “is this the same file,” which is the question that matters when the file has moved, been copied, or been regenerated by a rerun that was supposed to be identical.

Auditability Ch. 18

The property that a competent person who was not involved could rerun a study from its artifacts alone and reach the same conclusion. It is a specific, testable claim about a set of files, not a disposition of the author, and the test is performed by handing the artifacts to someone and watching what happens.

B#

Backdoor Ch. 17

A hidden behavior planted in a model that a specific input switches on. The model behaves normally on everything else, which is what makes a backdoor different from a model that is bad in the ordinary way: evaluation cannot find it, because ordinary evaluation does not contain the switch.

Backwards audit Ch. 15

Inverting a roofline. Given a measured throughput and a known memory bandwidth, compute the bytes per token the machine must have moved as bandwidth divided by throughput, then compare that figure against an independent estimate of what the model should have moved. The difference is real traffic the estimate did not account for, and it is measured, not assumed.

bf16 Ch. 2

Brain floating point, a 16-bit format with fp32’s 8 exponent bits and only 7 stored mantissa bits. It has fp32’s dynamic range and about a third of its precision: nothing that fits in fp32 overflows or underflows in bf16, and every value carries only two to three reliable decimal digits. Converting fp32 to bf16 is a truncation of the low 16 bits, which is why the conversion is so cheap.

Bits per byte Ch. 7

The total negative log probability a model assigns to a fixed string, converted to bits and divided by the string’s length in UTF-8 bytes:

where the sum runs over the model’s own tokenization of the string. Both the numerator’s decomposition and the token count are tokenizer-specific; their combination is not, which is what makes bits per byte comparable across tokenizers.

Black-box distillation Ch. 11

Distillation in which the only thing the teacher provides is its output text. No logits, no internal activations, no gradients, and often no ability to re-query the teacher at all if the text was purchased instead of generated. The student is trained with ordinary next-token cross-entropy on that text.

Bounded divergence Ch. 3

An f-divergence whose value cannot exceed a finite ceiling for any pair of distributions. The condition is that both and are finite, in which case the maximum is , attained on disjoint support. Jensen-Shannon, total variation, and squared Hellinger are bounded; forward KL, reverse KL, and chi-squared are not.

C#

Calibration Ch. 16

The property that a model’s stated confidence matches its realized accuracy. A calibrated model that says it is 80 percent sure is right 80 percent of the time, across the whole set of predictions where it said 80 percent. Calibration is a property of the relationship between two quantities, so a model can be accurate and badly calibrated, or inaccurate and well calibrated.

Capacity gap Ch. 5

The finding that a teacher much larger than the student can produce a worse student than a smaller, less accurate teacher would have. The student quality as a function of teacher size rises, peaks, and then declines, so the best teacher for a given student is often not the best available model.

Chat template Ch. 7

The model-specific format that turns a list of role-tagged messages into the single token sequence the model actually consumes. It wraps each message in special tokens, inserts a role header marking who is speaking, and optionally appends a generation prompt: the opening marker for the assistant’s turn, with nothing after it, telling the model that a reply begins here.

Chi-squared divergence Ch. 3

, generated by . Equal to the variance, under , of the importance ratio . Unbounded above and dominated by whichever single outcome has the largest ratio.

Co-tenancy Ch. 15

Two processes sharing one memory pool and one memory bus, each sized against the other rather than against the machine. On unified memory the sharing is total: a byte the server reserves is a byte the trainer cannot have, and bandwidth the server consumes is bandwidth the trainer waits for.

Cold start Ch. 12

Beginning on-policy training from a student that has not yet been distilled, so that at step zero the student and teacher disagree nearly everywhere. The contrasting case is a warm start, where on-policy training begins from a checkpoint that some cheaper method has already brought close to the teacher.

Completion mask Ch. 7

The boolean mask marking the positions belonging to the assistant’s completion, which are the only positions a distillation loss is allowed to train on. Built from the prompt length measured on the templated sequence, and from the exclusion of padding.

Configuration fingerprint Ch. 8

A short hash of a run’s full configuration dictionary, including the seed, used as part of every filename the run produces. Because a hash changes completely when any input value changes, two directories with the same fingerprint were produced by the same configuration and two with different fingerprints were not, which turns “which config made this checkpoint” from a question into a lookup.

Contamination Ch. 16

The presence, in an evaluation set, of examples that also appear verbatim or nearly verbatim in the training data. A contaminated evaluation measures memorization and reports it as capability.

Continuous batching Ch. 15

A serving scheduler that admits and retires requests at the granularity of a single decode step rather than a whole batch. Finished sequences leave immediately and waiting requests take their slots, so the batch is repacked every step and stays dense even when request lengths and arrival times are ragged.

Control variate Ch. 4

A quantity added to an estimator that has known expectation (usually zero) and is correlated with the estimator’s noise. Adding it changes nothing in expectation and can cancel a large part of the variance. In , the control variate is , whose expectation under is zero because , and which is correlated with because both are functions of the same ratio.

Corpus fingerprint Ch. 10

A fixed-length hash of the exact token-id array a cache was built from, stored in the cache’s manifest, and recomputed from the corpus in hand before training. Changing any single token id, or reordering any two rows, changes the hash. Matching fingerprints establish that the cache and the corpus are byte-for-byte the same data.

D#

Dark knowledge Ch. 1

The information carried by a teacher’s probabilities on the incorrect outputs. A hard label assigns zero to all of them and therefore says nothing about how they relate to each other; the teacher’s relative probabilities among wrong answers encode a learned similarity structure. The term is Hinton’s.

Decode Ch. 1, 9

Generating tokens one at a time, each conditioned on the tokens generated before it. Because token cannot be computed until token exists, generated tokens require sequential forward passes, and each pass reads the model’s entire weight set out of memory to produce a single token. Chapter 1 gave this as “memory-bandwidth bound, and on large models, slow”; §9.2 derives the ceiling.

Dense logits Ch. 4

The teacher’s complete score vector over the whole vocabulary, at every position. Having dense logits is what makes an exact divergence computation possible. Most of the machinery in this chapter exists because you often do not have them.

Depth pruning Ch. 13

Structured pruning that deletes entire transformer layers, keeping every surviving layer’s weights unchanged. The hidden width is untouched, so the surviving layers still compose without any reshaping.

Disjoint support Ch. 3

Two distributions have disjoint support when every outcome that one of them assigns positive probability to, the other assigns exactly zero. KL is infinite in both directions on disjoint support.

distinct-n Ch. 16

The fraction of generated -grams that are unique. For a collection of samples , pool all the -grams from all samples, count the distinct ones, and divide by the total:

where is the multiset of -grams in sample and . The measure is due to Li and colleagues, who introduced it to quantify the tendency of neural conversation models to produce generic responses.[^16-15]

E#

Entropy Ch. 2

, measured in nats. It is the average number of nats of surprise you get per sample from , and operationally it measures how spread out is. Zero for a distribution certain of its answer, for the uniform distribution over outcomes, and nothing outside that range.

Entropy collapse Ch. 12

The failure mode in which a model’s output distribution narrows toward determinism during training, so that rollout entropy falls toward zero and does not recover. In on-policy distillation it is driven by a feedback loop: narrowing output leads to narrower rollouts, which are the positions the next update is computed at, and a mode-seeking objective rewards further narrowing on them.

Expected calibration error Ch. 16

The average gap between a model’s confidence and its accuracy, taken over confidence bins and weighted by how many predictions fall in each. Chapter 8 introduced it as a number an operator watches during a run; here it is stated with its bins named, because the bins are what turn the concept into a number and are also where the number can be gamed.

Exposure bias Ch. 12

The mismatch between the contexts a model is trained on and the contexts it meets at generation time. A teacher-forced model is only ever conditioned on reference text, so it is never trained on prefixes containing its own errors, and at generation time it is operating on a distribution of contexts it was never fit to. The signature is a generation that starts well and degrades, because each error moves the context further from anything training covered.

F#

f-divergence Ch. 3

, where the generator is convex on and satisfies . Introduced independently by Csiszár and by Ali and Silvey in the 1960s, which is why the family is sometimes called the Ali-Silvey-Csiszár divergences.

Fertility Ch. 7

The number of tokens a tokenizer produces per unit of text, measured against a tokenizer-independent denominator. The course uses UTF-8 bytes:

A more fertile tokenizer chops the same text into more pieces. Fertility is a property of a tokenizer and a corpus jointly, never of a tokenizer alone.

Fidelity Ch. 5

How closely the student reproduces the teacher’s predictive distribution, measured on held-out inputs by agreement on the top-1 prediction or by a divergence between the two distributions. Distinct from generalization, which is how well the student performs on the task. The two can move independently.

Fixed distillation budget Ch. 13

The condition that every arm of an initialization comparison receives the same training compute after initialization: same steps, learning rate, recipe, data, and seed. Without it, the comparison measures the budget instead of the initialization.

Forward KL Ch. 3

with the teacher first: the expectation, under the teacher, of the log ratio of teacher to student. Also called the mode-covering or zero-avoiding direction. In this book and in the course code, “forward” always means teacher first.

Full fine-tuning cost Ch. 8

The steady-state memory a fully trained model occupies under mixed-precision Adam: about 16 bytes per parameter, from a 2-byte bf16 weight, a 2-byte bf16 gradient, a 4-byte fp32 master weight, and two 4-byte fp32 Adam moments. Eight of those sixteen bytes belong to the optimizer, which is why optimizer choice is a memory decision as much as a convergence decision.

Function matching Ch. 5

The interpretation of distillation as approximating the teacher’s input-output function rather than as a form of regularized supervised training. Taken literally it requires that teacher and student see identical inputs, including identical data augmentation, so that every training signal is an evaluation of the same function at the same point, and that training run long enough for the approximation to converge.

G#

Generalized Jensen-Shannon divergence Ch. 3

with and . At it is ordinary JSD. As it degenerates to zero, but ; as , .

Generator Ch. 3

The function that determines an f-divergence. It must be convex and satisfy . Two generators that differ by an affine term define exactly the same divergence.

H#

Headroom Ch. 15

The fraction of physical memory a plan deliberately refuses to allocate, because a memory plan systematically understates real usage. Allocator fragmentation, transient peaks during optimizer updates and checkpoint writes, framework and driver reservations, and KV growth past the planned context all consume memory that no line of the plan accounts for. The course reserves 15 percent.

Held-out probe set Ch. 8

A small fixed set of examples, disjoint from the training corpus, used to compute the same diagnostics repeatedly during a run. It is not an evaluation set in the reporting sense: it is too small for a defensible final number and it gets looked at often enough that decisions made against it are no longer independent of it. Its job is to make movement visible while there is still time to react.

Hidden-state matching Ch. 14

Adding a loss term that pushes a student’s internal activations toward a teacher’s at chosen positions and chosen layers, rather than (or in addition to) matching outputs. Also called feature-based distillation. Because hidden states carry no vocabulary, the comparison sidesteps the output-space alignment problem, at the cost of introducing a representation-space alignment problem in its place.

I#

Ignore index Ch. 7

The sentinel value -100 in a HuggingFace labels tensor, meaning “do not supervise this position.” Positions holding it contribute nothing to the loss and are excluded from the denominator of the mean. It is a magic number rather than a mask because PyTorch’s cross-entropy takes an ignore_index argument, and -100 is its default.

Importance ratio Ch. 4

For a sample drawn from , the quantity : how much more probable the teacher considers this token than the student does. Its expectation under is exactly 1, because . That identity is the hinge the rest of this chapter turns on.

Initialization budget Ch. 13

The compute spent obtaining the student’s starting weights, before the distillation budget begins. A separate line item from the training budget, and frequently ignored, which is how comparisons between initializations end up dishonest. Random is zero; a pretrained checkpoint is thousands of GPU-hours paid by someone else; a pruned teacher is minutes plus the teacher you already had.

J#

Jensen-Shannon divergence Ch. 3

where is the equal mixture. Symmetric in its arguments, bounded above by (one bit), zero exactly when , and finite even on disjoint support.

K#

KL divergence Ch. 3

, measured in nats. The extra cost, per outcome, of describing using a code optimized for rather than a code optimized for . It is zero exactly when , positive otherwise, asymmetric in its two arguments, and unbounded above.

Knee of a sweep Ch. 16

The smallest parameter value at which a swept quantity has already reached the level it holds for the rest of the sweep. It is the point past which further tightening buys nothing on that quantity, and it is where an inherited hyperparameter should be checked against a measured one.

KV cache Ch. 9

The store of attention keys and values for every token a sequence has processed so far, kept so that they are not recomputed at every subsequent decode step. It is per sequence, it grows linearly with sequence length, and its total size grows linearly with the number of concurrent sequences. On a machine where the weights fit comfortably, the KV cache is usually what actually limits concurrency.

L#

Layer importance Ch. 13

How much worse a model gets when one layer is removed, measured as the increase in masked next-token loss on a fixed probe set. It is a measured quantity, not a property you can read off the weights, and §13.6 is about what happens when you try to read it off the weights anyway.

Layer pairing Ch. 14

The assignment of teacher layers to student layers for a representation-matching loss. TinyBERT’s convention is a uniform proportional map, so that student layer supervises from teacher layer , but the convention is a heuristic and the right pairing is a property of the specific pair of models.

Length collapse Ch. 12

The failure mode in which a model’s generations shrink toward short stubs. In the on-policy distillation sequence it typically follows entropy collapse: a student that has become nearly deterministic reaches its most probable continuation, often an end-of-sequence token, earlier and earlier.

Log-partition function Ch. 2

Written and called logsumexp in every numerical library. It is the logarithm of the softmax’s normalizing denominator, and it is the primitive that log-probabilities, cross-entropy, and every divergence in this book are built from.

Logit Ch. 2

A raw, unnormalized score emitted by a model’s output layer, one per vocabulary entry. Logits live on the whole real line, bounded neither above nor below, and become probabilities only after a softmax.

Logit cache Ch. 10

A stored record of a teacher’s output distributions over a fixed corpus, written once and read many times during student training. In practice it stores log-probabilities and not raw logits, because log-probabilities are what an inference server hands you and because storing them removes any ambiguity about the temperature at which the normalization was performed.

Logit truncation defense Ch. 17

Returning only the top token probabilities per position instead of the full distribution, as a control on how much distributional information each API response carries. The same operation Chapter 10 performs on a cache to save disk, performed here on a response to withhold information.

Low-rank adaptation Ch. 8

Training a low-rank additive correction to frozen pretrained weight matrices instead of the weights themselves. Because gradients, master copies, and optimizer moments exist only for the trainable slice, the per-parameter cost falls from 16 bytes to roughly , where is the trainable fraction. The frozen base still occupies its 2 bytes per parameter.

M#

Machine epsilon Ch. 2

The gap between 1.0 and the next representable number above it. For a format with stored mantissa bits, machine epsilon is exactly . Read it as a digit budget: a format with an epsilon of carries about seven reliable decimal digits, and one with an epsilon of carries between two and three.

Machine profile Ch. 15

A versioned artifact recording throughput measured on your own hardware, keyed by phase, batch size, and sequence length, with the timing method and the full serving configuration attached. Later work sizes against the profile instead of quoting a published benchmark, and the profile carries enough provenance that a disagreement between two runs can be traced to a configuration difference rather than argued about.

Manifest chain Ch. 18

The graph formed when every run’s manifest names its inputs by the fingerprint or artifact hash of the manifests and files that produced them. The chain is the study’s provenance: the checkable trail from any artifact back to whatever produced it, all the way to the frozen protocol. Its defining property is that it is verifiable mechanically, by recomputing hashes, rather than by reading.

Marker behavior Ch. 17

A distinctive but harmless output pattern deliberately planted in a teacher, paired with a chosen trigger, for the purpose of measuring how much behavior a distillation pipeline carries. The marker is benign by construction: the point is to instrument the pipeline, not to build a weapon, and a benign marker measures transfer exactly as well as a harmful one would.

Marker lift Ch. 17

The rate at which the marker appears in outputs when the trigger is present, divided by the rate at which it appears when the trigger is absent. Written $\text{lift} = r_{\text{trig}} / r_{\text{base}}r_{\text{trig}}$ is the fraction of triggered prompts whose output contains the marker and is the fraction of untriggered prompts whose output contains it. A lift of 1 is the no-effect value: the marker appears at the same rate either way, so the trigger is doing nothing.

Matched compute Ch. 9

A comparison protocol in which every arm is allocated the same total computational budget rather than the same number of training steps. The budget must be stated in a unit the hardware actually charges, and every arm’s consumption must be accounted in that unit, including costs paid outside the training loop such as corpus generation and cache construction.

Memory bandwidth bound Ch. 9

A computation whose running time is set by how fast bytes can be moved out of memory rather than by how fast arithmetic can be performed on them. Formally, a computation whose arithmetic intensity is below the machine’s balance point . Autoregressive decode is the canonical example: it performs a small, fixed amount of arithmetic per weight byte read, so its speed tracks memory bandwidth and is almost insensitive to how fast the arithmetic units are.

Memory-mapped tensor Ch. 10

An array whose bytes live in a file rather than in process memory, and which the operating system pages in on demand as the program touches it. A batch that reads eight rows of a cache faults in those eight rows and nothing else, so the resident cost is set by the working set rather than by the file size.

Metric Ch. 3

A function that is non-negative, zero exactly when , symmetric (), and satisfies the triangle inequality ().

Metric audit Ch. 6

The practice of feeding every metric function a case whose correct answer you can compute by hand, and asserting the result, before using that function to report a number. A metric with a bug does not raise an exception; it returns a plausible number and quietly ranks your runs wrong.

Minimum detectable effect Ch. 18

The smallest true effect a study’s registered decision rule is capable of certifying. It is a property of the design, not of the result, so it is computable before any run happens, and a study that does not state it has not said what it could ever have seen.

Mixing coefficient (alpha) Ch. 5

The weight on the soft-target term of the combined distillation loss, with on the hard-label cross-entropy. is ordinary supervised training with no teacher; is pure distillation with no ground-truth signal; the interior is a mixture.

Mixture of experts Ch. 15

An architecture that holds many parallel weight blocks per layer and routes each token through only a few of them. Total parameters and active parameters per token are different quantities, and the second is the one that appears in the roofline, so a checkpoint’s size on disk becomes a poor estimate of what a decode step moves.

Mode approximation Ch. 11

Replacing a distribution by a point mass at its most probable outcome for the purpose of computing an expectation. In sequence-level KD it turns an expectation over sequences into a single term, which is what makes the objective computable at all. The approximation is exact only when the distribution is a point mass to begin with, and is otherwise biased by an amount nobody can compute for the case that matters.

Mode covering Ch. 6

The behavior of an objective that penalizes a student for assigning near-zero probability where the teacher assigns real probability. A mode is a peak of a distribution, a region of concentrated probability; a mode-covering objective forces the student to place some mass on every mode the teacher has, even modes the student’s capacity cannot represent well, so a capacity-limited student spreads itself thin. Forward KL, , is the canonical mode-covering objective. Also called zero-avoiding, because it avoids student zeros.

Mode seeking Ch. 6

The behavior of an objective that penalizes a student for assigning probability where the teacher assigns almost none, while charging nothing for teacher mass the student ignores. A capacity-limited student under a mode-seeking objective concentrates on one or a few modes it can match and abandons the others. Reverse KL, , is the canonical mode-seeking objective. Also called zero-forcing, because the cheapest way to satisfy it in a region the student cannot model is to force the student’s probability there to zero.

Model card Ch. 16

A short written record shipped with a model that states what it is, what it was trained on, how it was evaluated, and where it fails. For a distilled student it is the artifact that makes the lineage auditable by someone who was not there.

Model extraction Ch. 17

Reconstructing a served model, either its parameters or its behavior, from its responses to queries. Also called model stealing. The attacker has no access to weights or training data; the API is the entire interface, and the attack is to convert enough of its answers into a training corpus for a model of their own.

Monte Carlo estimator Ch. 4

An estimate of an expectation formed by averaging a function over random draws from the distribution the expectation is taken under. It approaches the true value only as draws accumulate, and at any finite it is a random number with a distribution of its own.

N#

n-gram overlap Ch. 16

A contamination detector that treats each row as its set of -token windows and scores an evaluation row by the largest fraction of its windows that any single training row also contains. It detects shared provenance, not semantic similarity: an -gram collision at sufficient means the two rows came from the same source text, not that they are about the same subject.

Nat Ch. 2

The unit of information you get when your logarithms are natural. One nat is $1/\ln 2 \approx 1.4427$ bits. Everything in this book is in nats, because every loss in this book uses natural logarithms and because log returns the natural logarithm in every framework you will use, and mixing bases silently changes every number by a factor of 0.693.

O#

Off-policy corpus Ch. 10

A fixed set of input sequences, chosen before training begins and not modified by the student’s behavior during training. Distillation against such a corpus is off-policy in the reinforcement learning sense: the data distribution the student learns from is not the distribution the student itself induces.

Off-policy distillation Ch. 1

Training the student on inputs drawn from a distribution other than the student’s own outputs. The corpus is fixed before training starts, which makes the pipeline cheap, restartable, and easy to reason about.

On-policy distillation Ch. 1

Training the student on the student’s own generated outputs, scored by the teacher. The training distribution moves as the student learns, which addresses exposure bias at the cost of a generation step inside the training loop.

On-policy fraction (lmbda) Ch. 12

The fraction of training batches whose token positions come from the student’s own rollouts rather than from the fixed corpus. It is lmbda in TRL’s GKDConfig. At 0 the run is pure off-policy and the state distribution never moves; at 1 every batch’s positions come from the current student; in between, each training step draws an on-policy batch with probability lmbda, independently, so over steps you get roughly on-policy batches, not a blend inside every batch.

Overflow Ch. 2

A calculation whose true result is larger in magnitude than the largest value the number format can store. The result becomes inf, which then contaminates everything computed from it.

P#

Paged attention Ch. 15

A KV cache allocation scheme that stores each sequence’s keys and values in small fixed-size blocks, non-contiguously, with a per-sequence table mapping logical positions to physical blocks. Modeled on virtual memory paging. It removes the need to reserve the maximum context length per sequence, which bounds wasted cache memory to a fraction of one block instead of the whole unused tail of a reservation.

Permutation invariance Ch. 14

The property that a loss is unchanged when the outcome labels of one or both distributions are relabeled by any bijection. For ULD this is the property that makes cross-tokenizer comparison meaningful at all: as far as this loss can see, a different tokenizer is nothing more than a shuffled and resized vocabulary.

Post-training quantization Ch. 15

Quantizing a model that has already finished training, without any gradient updates to its weights. The method gets a small calibration set of representative inputs and chooses the quantization parameters to minimize the damage, but it never retrains. This is what makes it practical for a teacher you downloaded and cannot afford to fine-tune.

Pre-flight Ch. 8

The set of checks executed before a training run that can be verified without training: memory arithmetic, configuration identity, loss-function properties at the settings the run will use, and data integrity. A pre-flight is worth writing when its checks are exact and fast, and it earns its keep by converting silent failures into loud ones before any expensive resource is committed.

Pre-registration Ch. 6

Writing down what you expect an experiment to show, in specific enough terms to be graded, and committing it to storage before the experiment runs. Borrowed from clinical and psychological research, where it exists to prevent a hypothesis from being adjusted after the data arrives. In a notebook it means serializing the predictions to a file with a timestamp, so that hindsight cannot quietly rewrite what you expected.

Prefill Ch. 1, 9

Running a model forward over a sequence of tokens that already exist, computing the output distribution at every position in one pass. Because causal attention makes each position depend only on earlier positions, and because all of those tokens are known in advance, every position is computed in parallel. The weights are read from memory once for the entire sequence. Chapter 1 gave this as “compute bound, and fast”; §9.1.1 derives why.

Probe set Ch. 13

A small, fixed batch of held-out examples used only for measurement and never for training. Fixed so every measurement in a sweep is comparable, held out so the number means something about behavior and not memorization, and small because the sweep costs one forward pass per layer.

Projector Ch. 14

A small trainable linear map inserted between the student’s hidden states and the teacher’s so that the two can be compared. It absorbs both the dimension mismatch and the arbitrary difference in basis between two independently trained models. In this course the projector maps the student’s hidden size into the teacher’s, so the teacher’s state is the fixed target and the trainable parameters sit on the student’s side of the loss.

Purchased asset Ch. 10

Any training artifact you did not produce yourself and intend to train on: a logit cache, a corpus of teacher generations, a published trace dataset. The defining property is that you cannot audit its construction, so every claim about it has to be re-derived from the artifact itself.

Q#

Quantization Ch. 15

Storing model weights in a numeric format with fewer bits per value than the format they were trained in, with a scheme for recovering an approximation of the original value at use time. A bf16 weight costs 2 bytes; an 8-bit format costs 1; a 4-bit format costs about half a byte once the per-group scales are counted.

R#

Rationale distillation Ch. 11

Sequence-level distillation in which the teacher is prompted to produce its reasoning before its answer, and the student is trained on the reasoning text as well as the answer. The teacher’s intermediate reasoning becomes training signal instead of an artifact discarded at generation time. Also called chain-of-thought distillation.

Reliability diagram Ch. 16

A plot of empirical accuracy against confidence, with confidence on the horizontal axis divided into bins and the bar height in each bin equal to that bin’s accuracy. A perfectly calibrated model lies on the diagonal. The signed area between the bars and the diagonal, weighted by bin occupancy, is the expected calibration error, which makes the diagram a picture of the number and not an illustration next to it.

Renormalized estimator Ch. 10

A top- approximation to a divergence that divides each retained teacher probability by the retained mass, producing a distribution supported on the kept tokens, and compares it against the student’s probabilities on those same tokens. It stores nothing about the discarded mass, and in doing so it treats the teacher as though it had never considered anything outside the top .

Reverse KL Ch. 3

with the student first: the expectation, under the student, of the log ratio of student to teacher. Also called the mode-seeking or zero-forcing direction.

Rollout Ch. 12

A complete generation produced by the current student from a prompt during training, used as the training example for that step. The word is borrowed from reinforcement learning, where it means running the policy forward to see what it does. Rollouts are generated under no_grad and treated as data: the loss is not differentiated through the sampling.

Rollout buffer Ch. 12

A store of previously generated rollouts that a trainer reuses for several optimizer steps before refreshing it. It converts generation cost into staleness: fewer rollouts are drawn per step, and the rollouts being trained on came from an older version of the student.

Rollout entropy Ch. 12

The mean next-token entropy of the student’s own distribution, computed over the tokens of the student’s own generations, in nats. It requires no teacher, no ratio, and no importance weight, which is why it is trustworthy in exactly the regime where the sampled divergences are not.

Roofline Ch. 9

An upper bound on throughput derived from the scarcest resource a computation consumes. For autoregressive decode on bandwidth-limited hardware the roofline is memory bandwidth divided by bytes read per token. It is a bound, not a prediction: real throughput lands under it, because weight reads are not the only cost. Its two honest uses are checking that a measurement is possible and comparing configurations to each other.

Run manifest Ch. 8

A record written alongside a run’s outputs containing the run’s name, its full configuration, its seed, its fingerprint, the identifiers of every input artifact it consumed, and the paths of every output artifact it produced, plus the versions of the libraries it ran against. The manifest is what makes a checkpoint self-describing and what makes a multi-stage pipeline auditable.

S#

Sampled-token estimator Ch. 4

A divergence estimate computed from the teacher’s and student’s log-probabilities at only those tokens the student actually sampled, one token per position, with no access to the rest of the vocabulary. It is the only estimate available when the teacher scores rollouts rather than returning full distributions.

Seed spread Ch. 18

The observed range, minimum to maximum, of a metric across runs of the same configuration that differ only in their random seed. Chapter 6 defined seed variance as the concept; the spread is the specific statistic this course uses to estimate it, chosen because it needs no distributional assumption and can be computed from two runs.

Seed variance Ch. 6

The spread in a measured outcome between training runs that differ only in their random seed. It is the experiment’s noise floor: it sets the smallest difference between arms that can be distinguished from chance, and any reported effect smaller than it is not a finding.

self-BLEU Ch. 16

The average similarity of each generated sample to the rest of the collection, computed by treating one sample as a hypothesis and all the others as references:

High values mean the samples resemble one another, so lower is more diverse, which is the opposite polarity to distinct-n. The measure comes from Zhu and colleagues’ Texygen benchmarking platform.[^16-16]

Self-distillation Ch. 5

Distillation where the student has the same architecture and capacity as the teacher. Since there is nothing to compress, any improvement the student shows over the teacher must come from the training signal rather than from the transfer of capability, which makes self-distillation the cleanest available test of the mechanism.

Sequence-level knowledge distillation Ch. 11

Training the student to reproduce whole output sequences produced by the teacher, rather than matching the teacher’s per-position distributions. In Kim and Rush’s formulation the intractable sum over all sequences is approximated by the teacher’s single most likely output, which reduces the loss to next-token cross-entropy on teacher-generated text. Abbreviated SeqKD.

Silent divergence Ch. 14

A configuration error that produces no error: the run completes, the metrics are finite, the artifacts save, and the experiment executed is not the experiment intended. Detected only by comparison against the source recipe, which is why the source recipe’s defaults get asserted rather than remembered.

Skew KL Ch. 6

A divergence that softens one distribution toward the other inside the logarithm, rather than averaging two divergences outside it. For a skew parameter , the skew KL is and the skew reverse KL is . Both are bounded above by , and both converge to the corresponding plain KL as .

Soft target Ch. 5

The teacher’s full probability distribution over the output space at a given position, used as the training target in place of, or alongside, the one-hot hard label. The name distinguishes it from a hard label, which puts all its mass on one outcome and says nothing about the others.

Softmax Ch. 2

The function that turns a vector of real-valued logits into a probability distribution: . Every output is positive because , and the outputs sum to one because the denominator is the sum of the numerators.

Sorted-probability matching Ch. 14

Comparing two models’ output distributions after sorting each one’s probabilities into descending order, so that index means “the -th largest probability” for both. The operation is defined between distributions over different and unrelated outcome sets, because the sorted vector refers to no outcome set at all.

Staged recovery Ch. 13

Pruning in several rounds, with a period of training between rounds, so that each round’s measurement is taken on a model that has recovered from the previous round. The contrast is one-shot pruning: remove everything at once and repair once at the end.

Staleness Ch. 12

The gap between the policy that generated a batch of rollouts and the policy currently being updated by them. Measured in optimizer steps of lag, or in quality lost relative to freshly generated rollouts. Any positive staleness makes the data partly off-policy regardless of what the configuration says.

State dict Ch. 13

PyTorch’s dictionary of a model’s weights. It maps parameter names, strings like model.layers.7.self_attn.q_proj.weight, to the tensors holding those parameters. It is what gets written when you checkpoint a model and what gets read when you load one.

State-dict surgery Ch. 13

Building a new model by constructing a smaller configuration, copying selected entries out of a source model’s state dict under rewritten names, and loading the result. No training, no gradient, no numerical change to any surviving weight. The entire operation is bookkeeping, which is why every failure mode is a bookkeeping failure and none of them raise.

Stopping rule Ch. 18

The condition, fixed in advance, that ends a run or a study. It states what is being counted, what the count’s limit is, and whether any measured quantity is allowed to end a run early. Lab 12’s is “fixed token-pass budget per arm; no early stopping on metrics.”

Structured pruning Ch. 13

Pruning that removes regular blocks of a network (whole layers, whole attention heads, whole slices of width) instead of scattered individual weights. The result is a smaller dense model with different tensor shapes, which is the property that makes it faster to run.

Student Ch. 1

The model being trained. Its parameters are the only ones that change. Its architecture, size, and tokenizer are all design choices, and Chapters 13 and 14 are about what those choices cost.

Study arm Ch. 18

One configuration in a study, extending Chapter 8’s definition of an arm in a single comparison. In a study the arms are enumerated in advance, in a frozen list, and the enumeration is itself part of the claim: an arm you thought about and did not run is different from an arm you never considered, and only the frozen list distinguishes them.

Study pre-registration Ch. 18

The full plan of a study, written down and frozen before any run starts: hypotheses, arms, seeds, metric definitions, the compute-matching rule, the stopping rule, the exclusion rule, and the analysis rule that converts numbers into verdicts. It extends Chapter 6’s registered prediction from “what I expect” to “what I will do,” which is the part that decisions get made under after the data arrives.

Subnormal Ch. 2

A floating-point value below the format’s smallest normal magnitude, represented with an implicit leading 0 instead of an implicit leading 1 and a fixed smallest exponent. Subnormals extend the range downward toward zero at the cost of losing precision progressively: the smallest subnormal has one significant bit. Also called denormal.

T#

Tail behaviors Ch. 16

The rarely-exercised capabilities that live in the low-probability regions of a model’s output distribution. They contribute almost nothing to a token-level loss and are the first thing a mode-seeking objective discards, which makes them invisible to the training loop and visible only to an evaluation that asks for them directly.

Tail-bucket estimator Ch. 10

A top- approximation to a divergence that keeps the retained entries as they are and adds one additional outcome carrying the entire discarded mass. The student’s probability for that outcome is its total mass outside the retained set, so the estimator supervises how much the student puts in the tail without supervising how the student arranges it.

Teacher Ch. 1

The model whose behavior is being transferred. Fixed during distillation. Usually larger than the student, though not always, and not necessarily accessible beyond its outputs.

Teacher forcing Ch. 7

Scoring a model on predicting each next token of a reference sequence while feeding it the reference tokens as context, rather than its own previous outputs. Every position is conditioned on text the model did not produce. This is how nearly all language model training works, including off-policy distillation, and Chapter 12 is about what it fails to teach.

Teacher gate Ch. 17

A precondition on the marker-transfer protocol: before distilling, confirm that the teacher itself expresses the marker at a lift above a stated threshold. Lab 11 sets the threshold at 20x. Until the gate passes, no measurement of the student means anything, because a student showing no marker is consistent with both “distillation did not carry it” and “there was nothing to carry.”

Teacher server Ch. 15

A separate process that holds the teacher and answers scoring requests over a network interface, while the student trains in its own process and sends the tokens it wants scored across the connection. The two processes are sized, quantized, restarted, and debugged independently.

Teacher trace Ch. 11

The step-by-step text a model writes out before its final answer: its worked solution, including whatever intermediate reasoning, false starts, and self-corrections it produced along the way. A trace is a teacher output like any other; the word marks that the interesting content is the process rather than the conclusion.

Temperature Ch. 2

A positive scalar that rescales logits before the softmax: . Values below 1 sharpen the distribution toward its largest entry; values above 1 flatten it toward uniform; leaves it unchanged.

Temperature-softened distribution Ch. 5

The distribution obtained by dividing every logit by a constant before the softmax: . Larger moves mass from the top of the distribution into the tail; smaller concentrates mass on the argmax. recovers the model’s own distribution.

Threat model Ch. 17

A written statement of what you are protecting, who might attack it, what capabilities you assume that attacker has, and what you are explicitly not defending against. A defense without a threat model cannot be evaluated, because “is this secure” has no answer until “against whom, doing what” has one.

Tokenizer Ch. 7

The reversible map between a string and a sequence of integer ids that a model consumes. It carries a vocabulary of pieces, a rule for splitting text into those pieces, and a decode direction that reassembles a sequence of ids into the original string. Two models with different tokenizers assign different integer vectors, of different lengths, to the same text.

Top-1 agreement Ch. 8

The fraction of supervised positions at which the student’s highest-probability token is the same as the teacher’s highest-probability token, on data neither model was trained on for this purpose. It measures whether the student has learned the teacher’s decisions, and says nothing about whether it has learned the teacher’s distribution.

Top-k truncation Ch. 7, 10

Keeping only the highest-probability entries of a teacher’s distribution at each position and discarding the rest, usually because storing the full vector at every position is unaffordable. The discarded mass is the tail, and what you do about it is a design decision with a measurable bias.

Total variation distance Ch. 3

, equivalently over all events . The largest amount by which the two distributions can disagree about the probability of anything. Symmetric, bounded by 1, and a genuine metric.

Trace fine-tuning Ch. 11

Supervised fine-tuning of a student on a corpus of teacher-generated reasoning traces, with no teacher access at training time, no logits, and no reinforcement learning stage. Mechanically it is ordinary next-token cross-entropy on somebody else’s teacher decode. Abbreviated trace SFT.

Trigger Ch. 17

The specific input that switches a backdoor on. Triggers are usually short and are chosen so that they do not occur in normal use, which is the property that makes the backdoor invisible to evaluation and, as it turns out, the property that usually keeps it from surviving distillation.

T² correction Ch. 5

The factor of applied to the soft-target term of the distillation loss. The softened KL’s gradient with respect to student logits scales as ; the hard-label term’s does not. Multiplying the soft term by restores comparable gradient magnitudes so that the mixing coefficient means the same thing at every temperature.

U#

Unbiased estimator Ch. 4

An estimator whose expected value equals the quantity it estimates, at every sample size. Unbiased does not mean accurate: a single draw from an unbiased estimator can be arbitrarily far from the truth, and in the cases this chapter cares about, routinely is.

Underflow Ch. 2

A calculation whose true result is smaller in magnitude than the smallest positive value the number format can store. The result becomes exactly 0.0. Unlike overflow, underflow produces a perfectly ordinary-looking number, and you find out about it later, when something takes its logarithm or divides by it.

Universal logit distillation Ch. 14

A cross-tokenizer distillation objective that trains a student by minimizing the L1 distance between the student’s and the teacher’s sorted next-token probability vectors, zero-padded to the longer vocabulary. Abbreviated ULD. Introduced by Boizard and colleagues, who give it its name and its published form.[^14-3]

V#

Vocabulary Ch. 7

The set of distinct pieces a tokenizer can emit, each with an integer id. The size of the vocabulary fixes the last axis of every logit tensor the model produces, so a distribution over the next token is a distribution over exactly these outcomes and no others.

Vocabulary overlap Ch. 14

The set intersection of two tokenizers’ vocabularies, compared as token strings and not as ids. Both modern byte-level BPE tokenizers build tokens from bytes and spell each token as a string in the same convention, including the marker character that stands for a leading space, so two tokens correspond exactly when their strings are equal. The overlap is reported as a fraction, and the choice of denominator is part of the measurement.

W#

Warmup Ch. 15

Iterations run before timing begins and then discarded, because they pay one-time costs that do not represent steady state: kernel compilation and autotuning, allocator growth, cache population, lazily loaded weights, and connection establishment. A measurement that includes warmup understates throughput by an amount that depends on how many timed iterations you ran, which makes it not comparable to anything.

Watermarking Ch. 17

Biasing generation at sampling time in a way that leaves a statistical signature in the output text, detectable later by a test that needs the detection key but not the model. Kirchenbauer and colleagues gave the standard construction and the accompanying statistical test.

Width pruning Ch. 13

Structured pruning that shrinks each layer’s internal dimensions (attention heads, MLP intermediate size, and in aggressive versions the hidden width itself) rather than deleting whole layers. Every remaining tensor is a sub-block of the original, so surviving weights are still the teacher’s, but every layer is narrower than the one it came from.

Windowed-drop rule Ch. 12

A collapse test that flags when the monitored quantity has lost more than a fixed fraction of its value relative to the first observation in a trailing window of the last observations. Three parameters: the window length , the drop fraction, and an absolute floor that acts as a backstop for trajectories declining slowly enough to evade the window.

Wrong-teacher control Ch. 14

A negative control for a distillation pipeline: rerun the measurement with a teacher that should not work, and confirm the reported number gets worse. If the metric cannot see a teacher you know to be worse, it is not measuring teacher transfer, whatever else it is measuring.

Bibliography

Every work cited in this book. Each entry was verified against its source: the identifier resolves, the title and author list match the record, and where a preprint has since been published, the venue of record is given. Entries that are unrefereed preprints are marked as such where the text uses them.

A. A. Fedotov, P. Harremoës, and F. Topsøe, “Refinements of Pinsker’s inequality,” IEEE Transactions on Information Theory 49, no. 6 (2003): 1491-1498. https://doi.org/10.1109/TIT.2003.811927 Cited in Ch. 3.

Adriana Romero, Nicolas Ballas, Samira Ebrahimi Kahou, Antoine Chassang, Carlo Gatta, and Yoshua Bengio, “FitNets: Hints for Thin Deep Nets,” arXiv:1412.6550 (2014), ICLR 2015. https://arxiv.org/abs/1412.6550. The origin of matching intermediate representations through a learned projection, and therefore the origin of the object the ridge check evaluates in closed form. Cited in Ch. 1, 5, 13, 14, App. C.

Bo Jiang, “DistillGuard: Evaluating Defenses Against LLM Knowledge Distillation,” arXiv:2603.07835 (2026). A single-author preprint with no listed affiliation, no venue, and no peer review. Cited for its framing; its empirical claims should be treated as an unreviewed report rather than an established result, as §17.9.1 says in the text. Cited in Ch. 17.

Chuan Guo, Geoff Pleiss, Yu Sun, and Kilian Q. Weinberger, “On Calibration of Modern Neural Networks,” arXiv:1706.04599 (2017), ICML 2017. https://arxiv.org/abs/1706.04599 The binned estimator of expected calibration error used throughout this book is the one stated there, and the paper is also the source of the observation that larger and more accurate networks tend to be less calibrated than smaller ones, which is worth holding onto when a distilled student’s ECE beats its teacher’s. Cited in Ch. 1, 2, 5, 6, 8, 10, 11, 14, 16, 17, 18.

Constantinos Karouzos, Xingwei Tan, and Nikolaos Aletras, “Where does output diversity collapse in post-training?” arXiv:2604.16027 (2026). https://arxiv.org/abs/2604.16027. An unrefereed preprint at the time of writing; treat the mechanism as suggestive rather than settled. For the related degeneration behavior of deterministic decoding see Ari Holtzman, Jan Buys, Li Du, Maxwell Forbes, and Yejin Choi, “The Curious Case of Neural Text Degeneration,” arXiv:1904.09751 (2019), ICLR 2020. https://arxiv.org/abs/1904.09751 Cited in Ch. 2, 6, 11, 12, 16, 18.

DeepSeek-AI, “DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning,” Nature 645 (2025): 633-638, https://doi.org/10.1038/s41586-025-09422-z, distills by supervised fine-tuning on teacher-generated traces, which is a pipeline where the teacher’s tokenizer never has to match the student’s, because what crosses between them is text. The template still has to be right on the student’s side. Cited in Ch. 7.

DeepSeek-AI, “DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning,” Nature 645 (2025): 633-638, https://doi.org/10.1038/s41586-025-09422-z; preprint arXiv:2501.12948. The distilled model series is supervised fine-tuning on teacher traces with no reinforcement learning stage for the students. The originating formulation of sequence-level KD is Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947 Cited in Ch. 1, 9, 11, 12, 14, 16, 17, 18, App. C.

distinct-n is from Jiwei Li, Michel Galley, Chris Brockett, Jianfeng Gao, and Bill Dolan, “A Diversity-Promoting Objective Function for Neural Conversation Models,” arXiv:1510.03055 (2015), NAACL-HLT 2016, https://arxiv.org/abs/1510.03055; self-BLEU is from Yaoming Zhu, Sidi Lu, Lei Zheng, Jiaxian Guo, Weinan Zhang, Jun Wang, and Yong Yu, “Texygen: A Benchmarking Platform for Text Generation Models,” arXiv:1802.01886 (2018), SIGIR 2018. https://arxiv.org/abs/1802.01886 Cited in Ch. 6, 11, 12, 16.

Dominik M. Endres and Johannes E. Schindelin, “A new metric for probability distributions,” IEEE Transactions on Information Theory 49, no. 7 (2003): 1858-1860, https://doi.org/10.1109/TIT.2003.813506, prove that the square root of the Jensen-Shannon divergence satisfies the triangle inequality. Ferdinand Österreicher and Igor Vajda, “A new class of metric divergences on probability spaces and its applicability in statistics,” Annals of the Institute of Statistical Mathematics 55, no. 3 (2003): 639-653, https://doi.org/10.1007/BF02517812, establish the broader family of which it is one case. Cited in Ch. 6.

Dominik M. Endres and Johannes E. Schindelin, “A new metric for probability distributions,” IEEE Transactions on Information Theory 49, no. 7 (2003): 1858-1860. https://doi.org/10.1109/TIT.2003.813506. Proves specifically that the square root of the Jensen-Shannon divergence satisfies the triangle inequality. Cited in Ch. 3.

Edward J. Hu, Yelong Shen, Phillip Wallis, Zeyuan Allen-Zhu, Yuanzhi Li, Shean Wang, Lu Wang, and Weizhu Chen, “LoRA: Low-Rank Adaptation of Large Language Models,” arXiv:2106.09685 (2021), ICLR 2022. https://arxiv.org/abs/2106.09685 The memory argument in the paper is about optimizer state specifically, which is the twelve of sixteen bytes this chapter breaks out. Cited in Ch. 1, 8, 13, 15, App. A, App. B.

Elias Frantar, Saleh Ashkboos, Torsten Hoefler, and Dan Alistarh, “GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers,” arXiv:2210.17323 (2022), ICLR 2023. https://arxiv.org/abs/2210.17323 For the training-side counterpart, where 4-bit base weights make a fine-tune fit, see Tim Dettmers, Artidoro Pagnoni, Ari Holtzman, and Luke Zettlemoyer, “QLoRA: Efficient Finetuning of Quantized LLMs,” arXiv:2305.14314 (2023), NeurIPS 2023. https://arxiv.org/abs/2305.14314 Cited in Ch. 2, 9, 10, 15, App. B.

Exposure bias, the reason on-policy sampling is attractive in the first place and therefore the reason this chapter’s estimators are needed at all, was formalized for sequence models long before distillation adopted the idea. Marc’Aurelio Ranzato, Sumit Chopra, Michael Auli, and Wojciech Zaremba, “Sequence Level Training with Recurrent Neural Networks,” arXiv:1511.06732 (2015), ICLR 2016. https://arxiv.org/abs/1511.06732 Cited in Ch. 1, 4, 10, 11, 12, 16.

Ferdinand Österreicher and Igor Vajda, “A new class of metric divergences on probability spaces and its applicability in statistics,” Annals of the Institute of Statistical Mathematics 55, no. 3 (2003): 639-653. https://doi.org/10.1007/BF02517812. Establishes the broader family of metric divergences , of which is the case. The two 2003 results are conventionally cited together. Cited in Ch. 3.

Florian Tramèr, Fan Zhang, Ari Juels, Michael K. Reiter, and Thomas Ristenpart, “Stealing Machine Learning Models via Prediction APIs,” arXiv:1609.02943 (2016), 25th USENIX Security Symposium, 601-618. https://arxiv.org/abs/1609.02943 Cited in Ch. 10, 17.

For a 4-bit format on the training side rather than the serving side, where quantized base weights make a fine-tune fit rather than making a teacher fast, see Tim Dettmers, Artidoro Pagnoni, Ari Holtzman, and Luke Zettlemoyer, “QLoRA: Efficient Finetuning of Quantized LLMs,” arXiv:2305.14314 (2023), NeurIPS 2023. https://arxiv.org/abs/2305.14314 The bytes-per-parameter arithmetic is identical; what differs is which side of the co-tenancy split it applies to. Cited in Ch. 2, 8, 15, App. B.

For a sense of how fast the objective space moves relative to its instruments, compare the divergence-choice line: Yuqiao Wen, Zichao Li, Wenyu Du, and Lili Mou, “f-Divergence Minimization for Sequence-Level Knowledge Distillation,” arXiv:2307.15190 (2023), ACL 2023, https://arxiv.org/abs/2307.15190; and Jongwoo Ko, Sungnyun Kim, Tianyi Chen, and Se-Young Yun, “DistiLLM: Towards Streamlined Distillation for Large Language Models,” arXiv:2402.03898 (2024), ICML 2024, https://arxiv.org/abs/2402.03898 Cited in Ch. 3, 4, 5, 6, 10, 11, 12, 14, 16, 18.

For the broader map of where sampled estimation sits within language-model distillation methods, see Xiaohan Xu et al., “A Survey on Knowledge Distillation of Large Language Models,” arXiv:2402.13116 (2024). https://arxiv.org/abs/2402.13116 The pre-language-model literature is surveyed in Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819. https://arxiv.org/abs/2006.05525 Cited in Ch. 1, 2, 3, 4, 5, 6, 7, 9, 12, 13, 14, 15, 16, 17.

For the broader on-policy setting in which the scoring loop of §15.11 is the inner loop, see Mingyang Song and Mao Zheng, “A Survey of On-Policy Distillation for Large Language Models,” arXiv:2604.00626 (2026). https://arxiv.org/abs/2604.00626 The survey is a living preprint marked “Ongoing Work” rather than a refereed publication, and should be read as a map of a moving area. Cited in Ch. 1, 3, 4, 6, 10, 12, 15, 16.

Frank Nielsen et al., “Metrization of powers of the Jensen-Shannon divergence,” arXiv:2302.10070. The modern generalization of the Endres-Schindelin result. Cited in Ch. 3.

Geoffrey Hinton, Oriol Vinyals, and Jeff Dean, “Distilling the Knowledge in a Neural Network,” arXiv:1503.02531 (2015), https://arxiv.org/abs/1503.02531, is where the soft-target objective originates. Nothing in that paper is about alignment, because in image classification the teacher and student consume identical inputs and produce distributions over an identical, externally defined label set. Everything in this chapter is the cost of moving that objective to a setting where neither of those is true by default. Cited in Ch. 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, App. A, App. D.

Giovanni De Muri, Mark Vero, Robin Staab, and Martin Vechev, “Pay Attention to the Triggers: Constructing Backdoors That Survive Distillation,” arXiv:2510.18541 (2025), ICLR 2026. https://arxiv.org/abs/2510.18541 The method name T-MTB does not appear in the title. The finding used in this chapter is the comparative one: ordinary triggers largely fail to transfer through distillation, while triggers whose components are exercised by the distillation corpus can. The construction is deliberately not described here. Cited in Ch. 1, 17.

Gu et al., “MiniLLM,” §3. The paper derives a policy-gradient form of the sequence-level reverse-KL objective and introduces single-step decomposition, teacher-mixed sampling, and length normalization specifically to control the gradient estimator’s variance. https://arxiv.org/abs/2306.08543v2 Cited in Ch. 4.

Hossein Mobahi, Mehrdad Farajtabar, and Peter L. Bartlett, “Self-Distillation Amplifies Regularization in Hilbert Space,” arXiv:2002.05715 (2020), NeurIPS 2020. The analysis is for regularized regression in a Hilbert space, not for deep networks. https://arxiv.org/abs/2002.05715 Cited in Ch. 5, 18.

Huimin Xu, Shuai Zhao, Xiaobao Wu, and Anh Tuan Luu, “Understanding and Preventing Entropy Collapse in RLVR with On-Policy Entropy Flow Optimization,” arXiv:2605.11491 (2026). A preprint without a peer-reviewed venue at time of writing. https://arxiv.org/abs/2605.11491 Cited in Ch. 12.

Imre Csiszár, “Information-type measures of difference of probability distributions and indirect observations,” Studia Scientiarum Mathematicarum Hungarica 2 (1967): 299-318. The family was co-discovered independently by S. M. Ali and S. D. Silvey, “A general class of coefficients of divergence of one distribution from another,” Journal of the Royal Statistical Society Series B 28, no. 1 (1966): 131-142, https://doi.org/10.1111/j.2517-6161.1966.tb00626.x. Csiszár’s earlier German-language precursor appeared in Publ. Math. Inst. Hungar. Acad. Sci., Ser. A, 8 (1963): 85-108. Neither Csiszár paper has a DOI; cite by volume and page. Cited in Ch. 3.

Ji Lin, Jiaming Tang, Haotian Tang, Shang Yang, Wei-Ming Chen, Wei-Chen Wang, Guangxuan Xiao, Xingyu Dang, Chuang Gan, and Song Han, “AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration,” arXiv:2306.00978 (2023), MLSys 2024. https://arxiv.org/abs/2306.00978. Quantization is the other lever on the same bandwidth bound that structured pruning acts on, and the two compose; Chapter 15 covers what each costs in quality. Cited in Ch. 2, 9, 10, 13, 15.

John Kirchenbauer, Jonas Geiping, Yuxin Wen, Jonathan Katz, Ian Miers, and Tom Goldstein, “A Watermark for Large Language Models,” arXiv:2301.10226 (2023), ICML 2023. https://arxiv.org/abs/2301.10226 Cited in Ch. 11, 17.

John Schulman, “Approximating KL Divergence,” blog post, joschu.net, 7 March 2020. http://joschu.net/blog/kl-approx.html Accessed 1 August 2026. This is a personal blog post with no venue or DOI, and it is the standard source for the k1, k2, and k3 names and for the observation that is unbiased and pointwise nonnegative. Cited in Ch. 2, 3, 4, 12, App. A.

Jongwoo Ko, Tianyi Chen, Sungnyun Kim, Tianyu Ding, Luming Liang, Ilya Zharkov, and Se-Young Yun, “DistiLLM-2: A Contrastive Approach Boosts the Distillation of LLMs,” arXiv:2503.07067 (2025), ICML 2025 Spotlight. https://arxiv.org/abs/2503.07067 Cited in Ch. 6.

Leo Gao et al., “The Language Model Evaluation Harness,” Zenodo v0.4.3 (July 2024), https://doi.org/10.5281/zenodo.12608602 Chapter 16 uses this for the reporting-grade evaluation that the held-out probe set of §8.5 is deliberately not. Version-pin whatever you run, since task definitions change between releases and a benchmark number without a version is not a number anyone can reproduce. Cited in Ch. 8, 15.

Leo Gao, Jonathan Tow, Baber Abbasi, Stella Biderman, Sid Black, Anthony DiPofi, Charles Foster, Laurence Golding, Jeffrey Hsu, Alain Le Noac’h, Haonan Li, Kyle McDonell, Niklas Muennighoff, Chris Ociepa, Jason Phang, Laria Reynolds, Hailey Schoelkopf, Aviya Skowron, Lintang Sutawika, Eric Tang, Anish Thite, Ben Wang, Kevin Wang, and Andy Zou, “The Language Model Evaluation Harness,” Zenodo, v0.4.3, July 2024, DOI: 10.5281/zenodo.12608602. https://github.com/EleutherAI/lm-evaluation-harness. The DOI is version-specific by design; record the version you ran rather than citing the repository generically. Cited in Ch. 13, 16, 18.

Linfeng Zhang, Jiebo Song, Anni Gao, Jingwei Chen, Chenglong Bao, and Kaisheng Ma, “Be Your Own Teacher: Improve the Performance of Convolutional Neural Networks via Self Distillation,” arXiv:1905.08094 (2019), ICCV 2019, for the variant in which the distillation happens between depths of a single network rather than between two networks. https://arxiv.org/abs/1905.08094 Cited in Ch. 5.

Longfei Yun, Chenyang An, Zilong Wang, Letian Peng, and Jingbo Shang, “The Price of Format: Diversity Collapse in LLMs,” arXiv:2505.18949 (2025). https://arxiv.org/abs/2505.18949 Cited in Ch. 12, 16, 18.

Loubna Ben Allal, Anton Lozhkov, Elie Bakouch, Gabriel Martín Blázquez, Guilherme Penedo, Lewis Tunstall, Andrés Marafioti, Hynek Kydlíček, Agustín Piqueres Lajarín, Vaibhav Srivastav, Joshua Lochner, Caleb Fahlgren, Xuan-Son Nguyen, Clémentine Fourrier, Ben Burtenshaw, Hugo Larcher, Haojun Zhao, Cyril Zakka, Mathieu Morlon, Colin Raffel, Leandro von Werra, and Thomas Wolf, “SmolLM2: When Smol Goes Big - Data-Centric Training of a Small Language Model,” arXiv:2502.02737 (2025). https://arxiv.org/abs/2502.02737. The 135M, 360M, and 1.7B checkpoints the course uses come from this family and share a data recipe, which is the condition that makes the pretrained arm strong. Cited in Ch. 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17.

Lucas Beyer et al., “Knowledge distillation: A good teacher is patient and consistent,” arXiv:2106.05237 (2021), CVPR 2022. https://arxiv.org/abs/2106.05237 Their finding that very long training schedules matter more than most architectural choices is what makes reallocated step budget a meaningful thing to buy with matched-compute slack. For the current organization of on-policy methods and their costs, see also Mingyang Song and Mao Zheng, “A Survey of On-Policy Distillation for Large Language Models,” arXiv:2604.00626 (2026), an unrefereed preprint that its authors describe as ongoing work. https://arxiv.org/abs/2604.00626 Cited in Ch. 1, 5, 6, 8, 9, 10, 11, 13, 16, 18.

M. S. Pinsker, Information and Information Stability of Random Variables and Processes (San Francisco: Holden-Day, 1964), translated and edited by A. Feinstein from the 1960 Russian original. Pinsker’s own constant is weaker than the form used here; the optimal constant is due independently to Csiszár (1967, cited above) and to S. Kullback, “A lower bound for discrimination information in terms of variation (Corresp.),” IEEE Transactions on Information Theory 13, no. 1 (1967): 126-127, https://doi.org/10.1109/TIT.1967.1053968, with a 1970 correction, https://doi.org/10.1109/TIT.1970.1054514, and to J. H. B. Kemperman (1969). The result is therefore properly the Csiszár-Kullback-Pinsker inequality. Cited in Ch. 3.

Matthias Minderer, Josip Djolonga, Rob Romijnders, Frances Hubis, Xiaohua Zhai, Neil Houlsby, Dustin Tran, and Mario Lucic, “Revisiting the Calibration of Modern Neural Networks,” arXiv:2106.07998 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.07998 A useful corrective to reading Guo et al.’s conclusions as universal: the relationship between model size and calibration depends on architecture and training recipe, so treat ECE as a quantity you track on your own pair rather than one whose expected level you can look up. Cited in Ch. 8, 16.

Mengzhou Xia, Tianyu Gao, Zhiyuan Zeng, and Danqi Chen, “Sheared LLaMA: Accelerating Language Model Pre-training via Structured Pruning,” arXiv:2310.06694 (2023), ICLR 2024. https://arxiv.org/abs/2310.06694. The source for the staged-recovery argument and for structured pruning along multiple axes at once. Cited in Ch. 1, 9, 13, 18.

Nicholas Carlini et al., “Stealing Part of a Production Language Model,” arXiv:2403.06634 (2024), ICML 2024. https://arxiv.org/abs/2403.06634. The attack recovers the embedding projection dimension and, with more queries, the projection matrix up to symmetry, from a log-probability API; the paper reports that the affected providers modified their APIs following disclosure. Cited in Ch. 10, 11, 17.

Nicolas Boizard, Kevin El Haddad, Céline Hudelot, and Pierre Colombo, “Towards Cross-Tokenizer Distillation: the Universal Logit Distillation Loss for LLMs,” Transactions on Machine Learning Research (January 2025); preprint arXiv:2402.12030 (2024). https://arxiv.org/abs/2402.12030 Cited in Ch. 1, 3, 7, 11, 14, 18, App. C, App. D.

Rafael Müller, Simon Kornblith, and Geoffrey Hinton, “When Does Label Smoothing Help?” arXiv:1906.02629 (2019), NeurIPS 2019. https://arxiv.org/abs/1906.02629 The relevant warning for a first run is that teacher quality measured by accuracy and teacher quality measured by usefulness for distillation are different quantities, so verifying that your larger teacher is “better” does not by itself verify that it is a better teacher. Chapter 5 has the mechanism. Cited in Ch. 1, 3, 5, 8, 16, 18.

Renren Jin et al., “Revisiting Entropy in Reinforcement Learning for Large Reasoning Models,” arXiv:2511.05993 (2025), ACL 2026 Findings. https://arxiv.org/abs/2511.05993. See also Huimin Xu, Shuai Zhao, Xiaobao Wu, and Anh Tuan Luu, “Understanding and Preventing Entropy Collapse in RLVR with On-Policy Entropy Flow Optimization,” arXiv:2605.11491 (2026), a preprint without a peer-reviewed venue at the time of writing. Cited in Ch. 2, 12, 16.

Samuel Stanton, Pavel Izmailov, Polina Kirichenko, Alexander A. Alemi, and Andrew Gordon Wilson, “Does Knowledge Distillation Really Work?” arXiv:2106.05945 (2021), NeurIPS 2021. https://arxiv.org/abs/2106.05945. The reason the acceptance criterion in §10.11 is stated as agreement against the live-teacher run rather than agreement against the teacher: fidelity to the teacher is not what a distilled student reliably achieves, so the comparison that means something is cached-versus-live under identical conditions. Cited in Ch. 1, 3, 5, 6, 8, 10, 11, 13, 14, 15, 16, 17, 18, App. C.

Samy Bengio, Oriol Vinyals, Navdeep Jaitly, and Noam Shazeer, “Scheduled Sampling for Sequence Prediction with Recurrent Neural Networks,” arXiv:1506.03099 (2015), NeurIPS 2015. https://arxiv.org/abs/1506.03099 Cited in Ch. 1, 10, 11, 12.

Saurav Muralidharan, Sharath Turuvekere Sreenivas, Raviraj Joshi, Marcin Chochowski, Mostofa Patwary, Mohammad Shoeybi, Bryan Catanzaro, Jan Kautz, and Pavlo Molchanov, “Compact Language Models via Pruning and Knowledge Distillation,” arXiv:2407.14679 (2024), NeurIPS 2024. https://arxiv.org/abs/2407.14679. “Minitron” is the model-family name and does not appear in the title. The paper prunes along both depth and width axes, estimates importance by measured activation statistics rather than by weight magnitude, and reports matching a from-scratch model of the same size at a small fraction of its training compute. Cited in Ch. 1, 7, 9, 13, 14, 18, App. C.

Sequence-level knowledge distillation is itself a Monte Carlo approximation with a sample size of one: the intractable sum over all output sequences is replaced by the teacher’s single most likely output. Yoon Kim and Alexander M. Rush, “Sequence-Level Knowledge Distillation,” arXiv:1606.07947 (2016), EMNLP 2016. https://arxiv.org/abs/1606.07947 Chapter 11 examines how much that approximation gives up and why it works anyway. Cited in Ch. 1, 4, 5, 6, 7, 9, 11, 12, 14, 15, 16, 17, 18.

Sergey Zagoruyko and Nikos Komodakis, “Paying More Attention to Attention: Improving the Performance of Convolutional Neural Networks via Attention Transfer,” arXiv:1612.03928 (2016), ICLR 2017. https://arxiv.org/abs/1612.03928 Cited in Ch. 1, 14.

Teacher-forced scoring is what makes off-policy distillation a single parallel forward pass. The on-policy alternative, where the student generates and the teacher scores what it produced, is Rishabh Agarwal, Nino Vieillard, Yongchao Zhou, Piotr Stanczyk, Sabela Ramos, Matthieu Geist, and Olivier Bachem, “On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes,” arXiv:2306.13649 (2023), ICLR 2024. https://arxiv.org/abs/2306.13649 The masking problem changes shape there, and Chapter 12 covers it. Cited in Ch. 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17, 18, App. A, App. C, App. D.

The 32B-class grouped-query geometry used throughout this chapter, 64 layers, 8 KV heads, head dimension 128, follows the Qwen family the course serves: Qwen Team, “Qwen2.5 Technical Report,” arXiv:2412.15115 (2024). https://arxiv.org/abs/2412.15115 Chapter 9 §9.7 derives the 0.262 MB per token figure from that geometry; this chapter takes it as given. Cited in Ch. 2, 7, 9, 10, 11, 13, 14, 15, 16.

The bias-variance framing applied to soft targets in distillation generally, rather than to divergence estimation specifically, is developed in Helong Zhou et al., “Rethinking Soft Labels for Knowledge Distillation: A Bias-Variance Tradeoff Perspective,” arXiv:2102.00650 (2021), ICLR 2021. https://arxiv.org/abs/2102.00650 Cited in Ch. 1, 4, 5, 6, 18.

The broader distillation survey literature treats tokenizer mismatch as a special case rather than a default; see Jianping Gou, Baosheng Yu, Stephen J. Maybank, and Dacheng Tao, “Knowledge Distillation: A Survey,” International Journal of Computer Vision 129, no. 6 (2021): 1789-1819. https://arxiv.org/abs/2006.05525 The vision setting the survey is largely built on has no equivalent problem, which changes how you read a result from it. Cited in Ch. 2, 5, 7, 13, 14, 16, 17.

The capacity gap is Chapter 5’s subject; the result that a larger teacher can produce a worse student, traced to the student being unable to fit the teacher’s function, is Jang Hyun Cho and Bharath Hariharan, “On the Efficacy of Knowledge Distillation,” arXiv:1910.01348 (2019), ICCV 2019. https://arxiv.org/abs/1910.01348 Cited in Ch. 1, 5, 6, 8, 13, 16, 18.

The distillation-specific version of the same cost pressure is treated in Jongwoo Ko, Sungnyun Kim, Tianyi Chen, and Se-Young Yun, “DistiLLM: Towards Streamlined Distillation for Large Language Models,” arXiv:2402.03898 (2024), ICML 2024, which pairs a skew-KL objective with an adaptive off-policy scheme motivated by rollout cost: https://arxiv.org/abs/2402.03898. Its successor is Jongwoo Ko, Tianyi Chen, Sungnyun Kim, Tianyu Ding, Luming Liang, Ilya Zharkov, and Se-Young Yun, “DistiLLM-2: A Contrastive Approach Boosts the Distillation of LLMs,” arXiv:2503.07067 (2025), ICML 2025. https://arxiv.org/abs/2503.07067 Cited in Ch. 3, 4, 5, 6, 12, 16.

The entropy-collapse literature is real and growing: Ganqu Cui et al., “The Entropy Mechanism of Reinforcement Learning for Reasoning Language Models,” arXiv:2505.22617 (2025), https://arxiv.org/abs/2505.22617, and Renren Jin et al., “Revisiting Entropy in Reinforcement Learning for Large Reasoning Models,” arXiv:2511.05993 (2025), ACL 2026 Findings, https://arxiv.org/abs/2511.05993. Both are about reinforcement-learning-trained models, and I am not aware of a paper whose primary subject is length collapse in distilled models specifically. Treat it as an underserved area rather than a settled one. Cited in Ch. 2, 3, 4, 6, 12, 16, 18.

The f-divergence family is due independently to Imre Csiszár, “Information-type measures of difference of probability distributions and indirect observations,” Studia Scientiarum Mathematicarum Hungarica 2 (1967): 299-318, and to S. M. Ali and S. D. Silvey, “A general class of coefficients of divergence of one distribution from another,” Journal of the Royal Statistical Society Series B 28, no. 1 (1966): 131-142, https://doi.org/10.1111/j.2517-6161.1966.tb00626.x Cited in Ch. 6.

The general structure of learning from one’s own samples under a fixed reference distribution, and the estimator questions it raises, is shared with preference optimization. Rafael Rafailov et al., “Direct Preference Optimization: Your Language Model is Secretly a Reward Model,” arXiv:2305.18290 (2023), NeurIPS 2023. https://arxiv.org/abs/2305.18290 Cited in Ch. 3, 4, 12.

The inequality carries Pinsker’s name from M. S. Pinsker, Information and Information Stability of Random Variables and Processes (Holden-Day, 1964), though the optimal constant is due independently to Imre Csiszár (1967) and to S. Kullback, “A lower bound for discrimination information in terms of variation,” IEEE Transactions on Information Theory 13, no. 1 (1967): 126-127, https://doi.org/10.1109/TIT.1967.1053968. Chapter 3 derives it. Cited in Ch. 6.

The surface appearance of a model that will not stop resembles the degeneration Ari Holtzman, Jan Buys, Li Du, Maxwell Forbes, and Yejin Choi describe in “The Curious Case of Neural Text Degeneration,” arXiv:1904.09751 (2019), ICLR 2020, https://arxiv.org/abs/1904.09751, but the cause here is different and much easier to fix: their subject is the interaction between maximization based decoding and the shape of the learned distribution, while this is a supervision bug at a single token. Worth knowing both, so you can tell them apart when a student rambles. Cited in Ch. 2, 6, 7, 10, 12, 16, 17.

Three further preprints in the same subarea, all unrefereed at the time of writing: “Protecting Language Models Against Unauthorized Distillation through Trace Rewriting,” arXiv:2602.15143; “The Distillation Game: Adaptive Attacks & Efficient Defenses,” arXiv:2605.22737; and “What Does It Mean to Break a Distillation Defense?” arXiv:2606.25059. Listed because a reader working in this area will meet them, and because the third one’s question is the right one to ask of any defense in Table 17.2. Cited in Ch. 17.

Tommaso Furlanello, Zachary C. Lipton, Michael Tschannen, Laurent Itti, and Anima Anandkumar, “Born Again Neural Networks,” arXiv:1805.04770 (2018), ICML 2018. https://arxiv.org/abs/1805.04770 Cited in Ch. 1, 5, 10.

Training a sequence model on its own outputs under a teacher’s supervision predates GKD. Alexander Lin, Jeremy Wohlwend, Howard Chen, and Tao Lei, “Autoregressive Knowledge Distillation through Imitation Learning,” arXiv:2009.07253 (2020), EMNLP 2020, frames it as imitation learning: https://arxiv.org/abs/2009.07253. See also Yuxian Gu, Li Dong, Furu Wei, and Minlie Huang, “MiniLLM: Knowledge Distillation of Large Language Models,” arXiv:2306.08543 (2023), ICLR 2024, which pairs student-generated data with a reverse-KL objective; the arXiv landing page now shows a later retitling, so the ICLR 2024 title is the version of record. https://arxiv.org/abs/2306.08543v2 Cited in Ch. 6, 11, 12, 17.

Victor Sanh, Lysandre Debut, Julien Chaumond, and Thomas Wolf, “DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter,” arXiv:1910.01108 (2019), 5th Workshop on Energy Efficient Machine Learning and Cognitive Computing, NeurIPS 2019. https://arxiv.org/abs/1910.01108. The student is initialized from a subset of the teacher’s layers, chosen by a fixed alternating rule rather than by measurement. Cited in Ch. 1, 5, 7, 13, 14.

Wonpyo Park, Dongju Kim, Yan Lu, and Minsu Cho, “Relational Knowledge Distillation,” arXiv:1904.05068 (2019), CVPR 2019. https://arxiv.org/abs/1904.05068 Cited in Ch. 1, 14.

Woosuk Kwon, Zhuohan Li, Siyuan Zhuang, Ying Sheng, Lianmin Zheng, Cody Hao Yu, Joseph E. Gonzalez, Hao Zhang, and Ion Stoica, “Efficient Memory Management for Large Language Model Serving with PagedAttention,” arXiv:2309.06180 (2023), SOSP 2023, 611-626. https://doi.org/10.1145/3600006.3613165 The paper’s premise is that contiguous allocation of the key-value cache wastes a large fraction of memory to fragmentation and over-reservation, which is the serving-side version of the effect described here. Chapter 15 covers the system. Cited in Ch. 2, 4, 7, 8, 9, 10, 12, 15, 17, App. B.

Xiaoqi Jiao, Yichun Yin, Lifeng Shang, Xin Jiang, Xiao Chen, Linlin Li, Fang Wang, and Qun Liu, “TinyBERT: Distilling BERT for Natural Language Understanding,” arXiv:1909.10351 (2019), Findings of EMNLP 2020. https://arxiv.org/abs/1909.10351. The layer-to-layer feature-matching recipe that Chapter 14 covers in full. Cited in Ch. 1, 13, 14.

Yaoming Zhu et al., “Texygen: A Benchmarking Platform for Text Generation Models,” arXiv:1802.01886 (2018), SIGIR 2018. https://arxiv.org/abs/1802.01886 Self-BLEU, defined here, is one of the measurements that shows what a watermark costs a served model’s output diversity. Cited in Ch. 6, 11, 16, 17.

Yonglong Tian, Dilip Krishnan, and Phillip Isola, “Contrastive Representation Distillation,” arXiv:1910.10699 (2019), ICLR 2020. https://arxiv.org/abs/1910.10699 Cited in Ch. 1, 14.

Yuxian Gu, Li Dong, Furu Wei, and Minlie Huang, “MiniLLM: Knowledge Distillation of Large Language Models,” arXiv:2306.08543 (2023), ICLR 2024. https://arxiv.org/abs/2306.08543v2. The arXiv landing page currently shows a later, retitled version; the ICLR 2024 title is the one used here. For a survey of the area, treated as a living preprint rather than a published survey, see Mingyang Song and Mao Zheng, “A Survey of On-Policy Distillation for Large Language Models,” arXiv:2604.00626 (2026), whose comment field reads “Ongoing Work.” https://arxiv.org/abs/2604.00626 Cited in Ch. 1, 3, 4, 5, 6, 10, 18, App. C.

Zeyuan Allen-Zhu and Yuanzhi Li, “Towards Understanding Ensemble, Knowledge Distillation and Self-Distillation in Deep Learning,” arXiv:2012.09816 (2020), ICLR 2022. The results hold under an explicit multi-view assumption about the data distribution. https://arxiv.org/abs/2012.09816 Cited in Ch. 5, 18.

Index

Chapter numbers, not page numbers, because the book is read in two formats and chapters are the unit both share. Bold marks the chapter where a term is defined.

A

Ablation 6, 9, 13 def

Abort criterion 6, 8, 12, 18, App. C def

Adam 1, 6, 8, 12, App. B, App. D

alignment 5, 7, 14, 15, App. D

Amortization 9, 15, App. E def

Arithmetic intensity 9, App. A def

arm64 1, App. B, App. E

Artifact hash 18 def

attention transfer 1, 14

Auditability 18 def

autoregressive 6, 9, 11, 12, 13, App. B

B

Backdoor 1, 17, 18 def

Backwards audit 15, App. E def

batch size 9, 15

bf16 1, 2, 7, 8, 9, 10, 13, 15, App. B, App. D def

bfloat16 8, 16, App. B

bias-variance 4, 5, 6, 18

Bits per byte 7, 14, App. A def

Black-box distillation 11, App. E def

born-again network 1, 5

Bounded divergence 3, 4, 6, 10 def

C

Calibration 5, 6, 8, 11, 12, 15, 16, 18 def

capacity 1, 5, 6, 8, 9, 13, 15, App. B

Capacity gap 1, 5, 6, 7, 8, 13, 16, 18, App. E def

Chat template 5, 7, 8, 11, 15, 16, 17 def

checkpoint 1, 7, 8, 9, 10, 12, 13, 15, 16, 18, App. B, App. E

Chi-squared divergence 3, 4, App. A def

Co-tenancy 15, App. B, App. E def

Cold start 3, 4, 12, App. C def

Completion mask 7, 11, 13, 16, App. A def

confidence 5, 8, 11, 14, 16

Configuration fingerprint 8, 18 def

Contamination 7, 8, 11, 16, 17, 18, App. C, App. E def

Continuous batching 12, 15, 17 def

Control variate 4, App. A def

Corpus fingerprint 10, App. C, App. D, App. E def

cross-entropy 1, 2, 3, 5, 7, 8, 11, 15

curriculum 1, 10, 11

D

Dark knowledge 1, 2, 5, 10, 11, 18, App. C def

Decode 1, 7, 9, 11, 12, 13, 14, 15, App. B, App. C def

Dense logits 4, 10 def

deployment 1, 11, 12, 18

Depth pruning 13 def

Disjoint support 3 def

distinct-n 6, 12, 16 def

E

early stopping 18

Entropy collapse 2, 3, 6, 12, 16, 18, App. C, App. D def

estimator 3, 4, 7, 10, 11, 12, 17, App. A

evaluation 1, 5, 6, 8, 11, 13, 14, 16, 17, 18

Expected calibration error 5, 6, 8, 11, 16, 18 def

Exposure bias 1, 10, 11, 12, 18, App. C def

F

f-divergence 3, 4, 5, 6, 12, 14, 18 def

Failure gallery 16, App. E def

Fertility 7 def

Fidelity 5, 8, 10, 11, 13, 15, 16, 17, App. C def

fine-tuning 1, 8, 11, App. C

Fixed distillation budget 13 def

float16 10, App. D

float32 3

Forward KL 3, 4, 5, 6, 8, 10, 12, 15, 16 def

Full fine-tuning cost 1, 8 def

Function matching 1, 5, 14 def

G

generalization 1, 3, 5, 6, 12, 13, 16, 17, 18

Generalized Jensen-Shannon divergence 3, 6 def

Generator 3, 4, 11, 16 def

greedy decoding 6, 11, 16

H

Headroom 5, 8, 9, 10, 15, 16, App. D def

held-out 4, 5, 8, 13, 14, 16

Held-out probe set 8, 18 def

hidden state 1, 7, 13, 14, App. C

Hidden-state matching 14 def

I

Ignore index 7, App. A def

imitation learning 6, 11, 12, 17

Importance ratio 3, 4 def

Initialization budget 13 def

instruction tuning 1, 7

J

Jensen-Shannon divergence 3, 6, 14, 16, App. A def

K

KL divergence 2, 3, 4, 5, 7, 10, 12, 14 def

Knee of a sweep 16 def

KV cache 9, 11, 12, 15, App. A, App. B, App. D def

L

label smoothing 1, 3, 5, 16

latency 9, 12, 15

Layer importance 13, 18 def

Layer pairing 13, 14, App. C def

learning rate 2, 5, 6, 8, 12, 13, 14

Length collapse 6, 12, 16, 18, App. C def

Log-partition function 2 def

log-probability 2, 4, 10, 12, 15

Logit cache 9, 10 def

Logit truncation defense 17 def

logsumexp 2, 6

LoRA 8, 13, 15, App. B

loss curve 5, 6, 7, 8, 10, 11, 13, 14, 15, App. A, App. D

Low-rank adaptation 1, 8, 13, 15, App. A, App. B def

M

Machine epsilon 2, App. D def

Machine profile 9, 15, App. E def

Manifest chain 18 def

Marker behavior 17 def

Marker lift 17, 18 def

mask 5, 6, 7, 8, 10, 11, 12, 13, 14, 16, App. A, App. D

Matched compute 9, 11, 18 def

memory bandwidth 4, 7, 9, 11, 15, 17, App. A, App. B

Memory bandwidth bound 9 def

Memory-mapped tensor 10 def

Metric 3, 6, 11, 12, 13, 14, 16, 18 def

Metric audit 6, 16 def

Minimum detectable effect 6, 8, 18, App. E def

Mixing coefficient 1, 5, 6, 8, 16 def

Mixture of experts 15 def

Mode approximation 11, 17 def

mode collapse 12

Mode covering 3, 6, App. D def

Mode seeking 3, 6, App. D def

Model card 16, 17 def

Model extraction 17 def

Monte Carlo estimator 4 def

N

n-gram overlap 16 def

nucleus sampling 2

O

Off-policy corpus 10 def

Off-policy distillation 1, 7, 9, 10, App. E def

On-policy distillation 1, 4, 9, 12, 18 def

On-policy fraction 12 def

optimizer state 8, 9, 10, 11, 12, 15, App. B

overfitting 1, 5

Overflow 2, 8, App. E def

P

padding 7, 12, 13, 14, 16, App. D

Paged attention 15 def

Permutation invariance 14 def

perplexity 3, 7

Post-training quantization 2, 9, 10, 15, App. B def

Pre-flight 5, 8, 9, 10, App. B, App. D, App. E def

Pre-registration 4, 6, 17, 18, App. E def

Prefill 1, 9, 10, 11, 12, 15, App. B, App. C def

Probe set 8, 13, 16, 18 def

Projector 13, 14, App. C, App. E def

pruning 1, 7, 9, 13, 15, 18, App. C, App. E

Purchased asset 9, 10, 11 def

Q

Quantization 2, 9, 10, 13, 15, App. B, App. E def

R

Rationale distillation 11 def

regularization 1, 5, 18

Reliability diagram 16 def

Renormalized estimator 10, 17 def

reproducibility 4, 5, 8

Reverse KL 3, 4, 6, 12, 16 def

Rollout 1, 4, 9, 12, 15, 16, 18, App. C def

Rollout buffer 12, 15, App. C def

Rollout entropy 4, 12 def

Roofline 9, 11, 12, 13, 15, 17, App. B, App. E def

Run manifest 8, 10, 12, 15, 18, App. B, App. D, App. E def

S

Sampled-token estimator 4 def

sampling 2, 4, 6, 11, 12, 17

seed 4, 5, 6, 7, 8, 12, 13, 16, 18, App. D

Seed spread 6, 16, 18, App. E def

Seed variance 6, 12, 16, 18 def

self-BLEU 6, 11, 12, 16, 17 def

Self-distillation 3, 5, 10, 18 def

sequence-level 1, 6, 9, 11, 12, 17, App. C

Sequence-level knowledge distillation 11, 12 def

serving 8, 9, 15, 17, App. B

shift 2, 7, 8, 10, 11, 15, App. D

Silent divergence 14 def

Skew KL 4, 6 def

Soft target 1, 5, 8, 16 def

Softmax 2, 3, 5, 6, 14, App. A def

Sorted-probability matching 14 def

sparsity 13

Staged recovery 13 def

Staleness 12, App. E def

State dict 13 def

State-dict surgery 13, App. C, App. E def

Stopping rule 3, 6, 18 def

Structured pruning 1, 7, 9, 13, 18, App. C def

Study arm 18 def

Study pre-registration 18 def

Subnormal 2 def

supervised fine-tuning 11

T

Tail behaviors 11, 16 def

Tail-bucket estimator 7, 10, 14, 17, 18 def

Teacher forcing 6, 7, 12, 16 def

Teacher gate 17 def

Teacher server 15, App. B, App. E def

Teacher trace 1, 11, 12, 17, 18, App. C def

Temperature 1, 2, 5, 6, 8, 9, 10, 11, 16, App. A, App. D def

Temperature-softened distribution 5 def

Threat model 17 def

throughput 9, 10, 11, 13, 14, 15, 18, App. B, App. E

Tokenizer 1, 7, 9, 11, 14, 18, App. C, App. D, App. E def

Top-1 agreement 5, 6, 7, 8, 14 def

top-k 2, 7, 10, 14

Top-k truncation 7, 10 def

top-p 14

Total variation distance 2, 3, 4, 6, App. A def

Trace fine-tuning 1, 11, 14, 16, 18, App. C def

Trigger 1, 17, 18 def

truncation 2, 7, 10, 15, 17

T² correction 5 def

U

Unbiased estimator 4 def

Underflow 2, 3, 6, 8, App. E def

unified memory 8, 15

Universal logit distillation 1, 3, 7, 11, 14, 18, App. C, App. D def

V

vLLM 10, 12, App. D

Vocabulary overlap 14 def

W

Warmup 2, 5, 6, 8, 15 def

watermark 11, 17

Watermarking 17 def

weight decay 5, 8

Width pruning 13 def

Windowed-drop rule 12, 16 def

Wrong-teacher control 13, 14, App. E def