Continual learning is training one model on a stream of tasks or data, one after another, so that it learns each new thing without losing what it already knew. It matters because the world keeps changing after a model ships. New facts appear, users bring new kinds of work, and a lab wants next month's model without paying for pretraining again. It is also the deepest kind of memory in this chapter, because what the model learns ends up in its weights, not in its context window or in an external store.
Picture an agent that has worked on a codebase for a week. It kept durable progress notes (§25.4). It kept a long-term store of facts that it pages in and out of its window (§25.3). Yet on day seven the model itself is exactly as good at the work as it was on day one, because nothing it did reached its weights.
The obvious fix is to fine-tune the model every night on the day's work. That fix runs into catastrophic forgetting, a large drop in performance on an old task caused by training on a new one. Train a network on task A, then on task B, and its accuracy on A can collapse. §14.4 explained why with one formula. Let θA⋆ be the weights that minimize task A's loss. A small move Δθ away from them raises that loss by about 21Δθ⊤HAΔθ. Here HA is the Hessian of A's loss, the matrix of its second derivatives, which says how sharply the loss curves in each direction. Training on B follows B's gradient, and that gradient knows nothing about HA. So it moves the weights along the directions A depends on as readily as along the ones A ignores.
This section shows how to learn in sequence without that collapse, and when new knowledge should stay out of the weights altogether. We first measure forgetting across a sequence of tasks. Then we build three families of defenses and test them together in one simulation. A penalty holds still the weights that the old task needs. A replay buffer mixes a few stored old examples into training. Isolation gives each task its own parameters. Then we turn to large language models. We see how labs keep a model current, why reinforcement learning forgets less than supervised fine-tuning, how a model can learn during a single input, and where a new piece of knowledge should live. Interviewers ask most often about the derivation of the penalty, the Fisher information, and replay.
Forgetting is measured with a matrix of accuracies
Start with the setting. A model sees tasks one at a time. It may train on each task only while that task is current. After each task it is tested on every task. A task here is anything with its own training set, such as a new set of classes, a new kind of input, or a new month of data.
The scenario matters because the same method can look excellent in one and fail in another. The standard tests are built from MNIST, a dataset of small images of handwritten digits. Split MNIST cuts it into five tasks of two digits each, 0 and 1, then 2 and 3, and so on. In the task-incremental version the test question is "is this the first or the second digit of task 3?" In the class-incremental version it is "which of the ten digits is this?" Permuted MNIST makes every task the full ten-digit problem. Each task shuffles the pixels by its own fixed permutation, so the tasks' inputs have almost nothing in common.
So the first question to ask of any continual learning result is which scenario it was measured in. Our own simulation later in this section shows a milder version of the same pattern.
To measure forgetting we record a whole matrix, not one number. Row i holds the test accuracy on every task after training through task i. The diagonal entry of row j is the accuracy on task j right after it was learned.
In plain words, ACC is the final report card, the mean of the last row. BWT asks how much each task's accuracy changed between the moment it was learned (the diagonal) and the end. Negative BWT is forgetting. FWT asks how much the earlier tasks helped on task j before the model trained on it, compared with the untrained model. BWT skips the last task, because nothing came after it. FWT skips the first, because nothing came before it.
In practice, report ACC and BWT together, and show the matrix when you can. An interviewer who hears a single accuracy for a continual learner will ask how much of it is forgetting.
Warm-up
ACC, BWT, and FWT from an accuracy matrix
Write cl_metrics(R, baseline). It returns the three numbers (acc, bwt, fwt) defined above, as Python floats.
R is an ntask×ntask array or nested list. R[i, j] is the accuracy on task j after training through task i, with zero-based indices.
baseline is the vector bˉ of accuracies of the untrained model.
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.
Forgetting in two dimensions, by hand
Here is the picture to keep in mind. Each task's loss, drawn over the weights, is a valley. Training on task A brings the weights to the bottom of A's valley. Task B has its own valley somewhere else, and gradient descent on B walks straight downhill toward it. On the way it climbs A's walls in whatever direction B's valley happens to lie. If A's valley is narrow in that direction, the walk is a disaster for A. If it is wide, the walk costs A almost nothing.
There are three ways to protect A. A penalty makes the walk avoid A's narrow directions. Replay keeps visiting A's valley during the walk. Isolation gives each task its own weights, so the walks never collide. We start with the penalty, in a case small enough to check by hand.
Both losses here are exactly quadratic, and the spring's stiffness is exactly A's curvature. So EWC with λ=1 minimizes LA+LB. That is the loss we would have minimized had both datasets been available at once. The cell confirms the numbers and shows what a larger λ does.
Python · cell 1
With λ=10 the spring wins. The weights end at θ=(0.049,0.571). Task A is almost untouched (LA=0.046), at the price of task B (LB=2.92). This is the stability-plasticity trade-off. A stable learner keeps its old skills, a plastic one learns new ones fast, and λ is the dial between them. Every method that protects old tasks has a dial like this. An interviewer will ask how you would set it. Tune it on a validation set that contains both old and new tasks.
Where the spring's stiffness comes from
Why should each spring be as stiff as task A's curvature, and what exactly is λ? Bayes rule answers both (§0.4). Here are its pieces. The likelihoodp(D∣θ) is how probable the data are under weights θ. The priorp(θ) is how plausible the weights were before any data. The posteriorp(θ∣D)∝p(D∣θ)p(θ) is how plausible they are after the data. Its peak, the mode, is the maximum a posteriori (MAP) estimate.
In this language, learning in sequence is easy. After task A, everything A has to say about the weights is in the posterior p(θ∣DA). To learn B, multiply that posterior by B's likelihood. The trouble is that the posterior of a network is a distribution over millions of weights, and nobody can store it.
EWC replaces it by the Gaussian that has the same peak and the same curvature at the peak. This is called the Laplace approximation. The negative log-density of a Gaussian is a quadratic bowl, so the old posterior becomes a spring centered on A's solution. The spring's stiffness is the Gaussian's inverse covariance, called its precision. A tight Gaussian has a high precision, which means a stiff spring.
∂ From Bayes rule to the EWC penalty, step by step
Step 1, Bayes over tasks. Conditional independence means p(DA,DB∣θ)=p(DA∣θ)p(DB∣θ). Then
The last term does not depend on θ. The EWC paper wrote logp(DB) there. Huszár (2018) pointed out that the two differ, because the tasks are independent only given θ, but the term is a constant either way. Everything task A contributes now sits in logp(θ∣DA).
Step 2, Laplace. Write g(θ)=−logp(θ∣DA). At the mode θA⋆ its gradient vanishes. Its Hessian ΛA=∇2g(θA⋆) is positive semidefinite, meaning u⊤ΛAu≥0 for every direction u, because g curves upward in every direction at a minimum. Taylor's theorem to second order gives
g(θ)≈g(θA⋆)+21(θ−θA⋆)⊤ΛA(θ−θA⋆).
So p(θ∣DA) is approximately the Gaussian N(θA⋆,ΛA−1). Its mean is the mode, and its precision is the curvature.
Step 3, what the precision is made of. Up to a constant, g is −∑n∈Alogp(yn∣xn,θ)−logp(θ), and the prior's negative log-density is 2λ0∥θ∥2 plus a constant. So
with H^A the average per-example Hessian. The next subsection proves that the expectation of this Hessian, with labels drawn from the model, is the Fisher information FA. The Fisher needs only first derivatives and is always positive semidefinite, so EWC uses ΛA≈NAFA+λ0I.
Step 4, the objective. Substituting into Step 1 and negating, the MAP estimate after B minimizes
−logp(DB∣θ)+21(θ−θA⋆)⊤(NAFA+λ0I)(θ−θA⋆).
Divide by NB, so the first term becomes the mean loss LB. Keep only the diagonal of FA, because a P×P matrix for a model with P parameters cannot be stored and its diagonal can. The result is the boxed objective with λ=NA/NB.
In plain words, EWC trains on B while pretending that A's whole posterior is a Gaussian centered on A's solution. Each weight's spring is as stiff as A's certainty about that weight. Four approximations separate this from exact Bayesian learning. Each one is a reason to tune λ on validation data instead of setting it to NA/NB.
The Laplace Gaussian is a local picture of a posterior that is far from Gaussian in a deep network.
The Fisher equals the Hessian only on average over labels drawn from the model, as the next subsection proves.
The diagonal ignores every correlation between weights.
Stochastic gradient descent stops at a point that is not exactly a mode.
Huszár also points out that the original paper's strength λ for each task plays the role of the dataset size NA in this derivation. The widget at the end of this part shows, in two dimensions, how much each approximation costs.
The Fisher information is the expected Hessian
The derivation swapped the Hessian of the loss for a matrix called the Fisher information. Think of a classifier that outputs a probability for each label. The score of a label is the gradient of its log-probability with respect to the weights, s(y)=∇θlogpθ(y∣x). It says which way to move the weights to make that label more likely. The Fisher informationF(x) at input x is the average outer product of the score with itself, Ey[s(y)s(y)⊤]. The average is over labels drawn from the model's own predicted distribution, not taken from the data. Over a dataset of N inputs, F=N1∑nF(xn).
The identity that makes the swap legitimate takes three lines to prove. §14.4 proved it inside its derivation of why the KL to the base model is a Fisher quadratic. Here it is on its own, because EWC rests on it.
§15.6 met the same matrix in another role, as the curvature of the KL divergence between an old policy and a new one. One prediction worked by hand shows the identity at work.
Two consequences of the identity matter for EWC. First, the Fisher needs only gradients, one per sampled label, and it is positive semidefinite everywhere. The actual Hessian of a deep network's loss can have negative eigenvalues away from a minimum. A spring with negative stiffness would push the weights away instead of holding them.
Second, the identity is about the expected Hessian, with labels drawn from the model. With the dataset's real labels, the Hessian differs from F by an extra term. That term averages to zero over model-drawn labels, but it need not be zero on a given dataset. For logistic regression the extra term is exactly zero, as in the example, so there the Fisher equals the Hessian at every θ.
The true Fisher and the empirical Fisher
§14.4 met this difference at a softmax output layer, where the empirical Fisher of a rare class came out 99 times too small. Here we see why it happens in general and measure it in every direction at once. Computing F needs a label sampled from the model for each input. Many implementations of EWC, and of optimizers built on the Fisher, use the dataset's own labels instead. That gives the empirical FisherF~=N1∑ns(yn)s(yn)⊤, where yn is the dataset's label for input n. Kunstner, Hennig, and Balles (2019) showed that this is a different matrix with different behavior. It is close to F only when the model is well specified, meaning the data really come from a model of this form, and the weights are near a good fit.
Binary logistic regression with many inputs makes the difference concrete. The model predicts pn=σ(w⊤xn), with w the weights. The score of label yn is (yn−pn)xn, so
In plain words, the true Fisher weights each example by how uncertain the model is about it. The empirical Fisher weights it by how wrong the model was. Take the example from before, with pn=0.9. The true Fisher gives it weight 0.9×0.1=0.09. If its label is yn=1, the empirical Fisher gives it (1−0.9)2=0.01, nine times less. If its label is a mistake, yn=0, the empirical Fisher gives it 0.81, nine times more. The two agree on average only when the labels really come from the model, because Ey∼Bern(p)[(y−p)2]=p(1−p).
On clean, nearly separable data almost every example is like the first case, so the empirical Fisher is too small. The cell measures the gap in every direction at once. For each direction u it takes the ratio u⊤F~u/u⊤Fu. It reports the smallest and the largest ratio, which are the generalized eigenvalues of the pair of matrices. It also checks that the true Fisher matches a Hessian computed by finite differences.
Python · cell 2
The true Fisher matches the Hessian to finite-difference precision in all three cases, as the identity says it must for logistic regression. On clean labels the empirical Fisher is ten times smaller than the true one along one direction. An EWC spring built from it would let the weights drift that way ten times more cheaply than they should. With 10 percent of labels flipped, it is 13 percent too small along one direction and 29 percent too large along another. In neither case is it the curvature.
Core
True Fisher, empirical Fisher, and the Hessian of logistic regression
The model is binary logistic regression, with inputs X of shape (n, d), labels y in {0,1}, and weights w of shape (d,). Write fisher_matrices(X, y, w). It returns (F_true, F_emp, H), each of shape (d, d).
F_true is the Fisher information averaged over the inputs, with labels drawn from the model.
F_emp is the empirical Fisher. It averages, over the examples, the outer product of each example's score computed with its own label.
H is the Hessian of the mean negative log-likelihood n1∑i[log(1+ew⊤xi)−yiw⊤xi], derived by hand.
The tests check H against finite differences. They check that F_true equals H for this model. They also check that F_emp averages to F_true when the labels are sampled from the model.
import numpy as np
def fisher_matrices(X, y, w):
raise NotImplementedError
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.
The widget fits task A, a logistic regression on 200 points in two dimensions, and draws how fast A's loss rises around its fitted weights. The shading is the true rise. The dashed ellipses are the spring that the full true Fisher would give, at loss rises of 0.02 and 0.08 nats. The solid ones are the spring EWC actually uses, built from the Fisher you choose. Task B is a round bowl, and the widget trains on it from A's best weights with and without the spring, with λ=1. The sliders set how correlated A's inputs are and how many of A's labels are flipped.
How well does the spring EWC builds from task A's Fisher describe what task A cares about?
Shading is how much task A's loss rises. Training on B pulls the weights toward B's best.
Loading interactive figure…
Many tasks need one anchor, not many
With a third task, the tempting extension of EWC adds one spring per old task. Each spring is anchored at the weights where its task finished. The original EWC paper suggested exactly that. Huszár (2018) showed that the Bayesian derivation gives something else. After tasks A and B, the Gaussian picture of the posterior has a new peak, the weights after B. Its precision is A's plus B's. So the next task needs one spring, anchored at the latest weights, with the summed stiffness. Here is the difference with real numbers.
The single anchor was not a lucky choice of numbers. The same bookkeeping works for any number of tasks in any dimension, and when every loss is exactly quadratic and each spring is its exact curvature, it lands on the joint optimum every time.
∂ Proof of the Laplace recursion
Step 1 of the EWC derivation, with D1:t in the role of DA, gives logp(θ∣D1:t+1)=logp(Dt+1∣θ)+logp(θ∣D1:t)+const. Substituting the Gaussian approximation gives the objective. Its Hessian at the new mode is Nt+1 times task t+1's average Hessian plus Λ1:t, and replacing that average Hessian by the Fisher gives the update.
For the quadratic case, induct on t. Suppose 21(θ−θt⋆)⊤Λ1:t(θ−θt⋆) equals ∑j≤t21(θ−mj)⊤Hj(θ−mj) up to a constant. This holds at t=1 with θ1⋆=m1 and Λ1:1=H1. Then the objective for task t+1 is the sum over j≤t+1 up to a constant. That sum is a quadratic with Hessian ∑j≤t+1Hj=Λ1:t+1, minimized at θt+1⋆, and a quadratic is determined up to a constant by its Hessian and its minimizer. ■
Why does the one-spring-per-task form go wrong? Its penalty is ∑j≤t21(θ−θj⋆)⊤NjFj(θ−θj⋆). It has the right total precision but the wrong center. Its minimizer is a precision-weighted average of the old modes θj⋆. Each θj⋆ already included tasks 1 to j−1, so early tasks are counted again in every later anchor. Huszár calls this double counting. The single-anchor form is also cheaper. It stores one anchor and one precision vector, instead of a pair per task.
Online EWC (Schwarz et al. 2018) adds one more setting. It keeps the single anchor. Before adding the new task's Fisher, it shrinks the accumulated precision by a factor γewc,
Λ←γewcΛ+Ft,0<γewc≤1,
with the dataset size Nt absorbed into λ. The paper's penalty also multiplies the stored precision by γewc, a constant factor that λ absorbs too. With γewc=1 this is the Laplace recursion. With γewc<1, old tasks fade gradually instead of being held forever. That gives back the plasticity that an ever-growing precision takes away. It is also a rough correction for staleness. An early task's Fisher was computed at an early mode, and it stops describing the curvature as the weights move.
Interview
Online EWC across three tasks
Build the single-anchor form of online EWC with a diagonal precision. The state is None before any task. After a task it is a dict with keys "anchor" and "precision", both arrays of the parameter shape. Write two functions.
ewc_state_update(state, theta_star, fisher, gamma) is called after each task with that task's final weights and diagonal Fisher. It returns the new state. The anchor is theta_star. The precision is γewcΛ+Ft, or just Ft after the first task. Copy the arrays, so that later in-place changes to the weights cannot alter the state.
ewc_penalty_grad(theta, state, lam) returns the gradient of 2λ∑iΛi(θi−ai)2, where a is the anchor. It returns zeros if state is None.
The setup defines three quadratic tasks and a train_sequence loop that calls your two functions. With γewc=1 and λ=1 the tests expect the exact Laplace recursion, which lands on the joint minimizer of all three tasks.
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.
In the quadratic tasks of that exercise, plain sequential training ends at task 3's optimum. Its BWT is −5.09, with the negative loss as the score in place of accuracy. The single-anchor recursion with λ=1 lands exactly where joint training would, with BWT −2.27. Some backward loss remains even for the exact posterior. These three tasks conflict, so the joint optimum is a compromise. Real networks are not quadratic, which is why the simulation later in this section is less kind to EWC.
Protecting outputs instead of weights
EWC protects the weights. A different idea protects what the network says. Before training on B, record what the old model predicts on B's inputs. Then train on B while asking the network to keep making those predictions. Li and Hoiem (2016) called this Learning without Forgetting (LwF). It is knowledge distillation from the old model into the new one, that is, training one model to match another model's output probabilities (§20.4).
To measure "keep making those predictions" we use the KL divergence (§0.5). For two distributions p and q, DKL(p∥q)=Ey∼p[logp(y)−logq(y)] says how much more surprised q is than p by samples from p. It is zero only when the two agree. Run the old model θA⋆ on B's inputs. Record its predicted distribution pθA⋆(⋅∣x) over task A's outputs. Then train on
where the sum runs over task B's inputs only and λo sets the balance between the two terms.
In plain words, keep saying what you used to say, about the inputs you are seeing now. That needs no old data at all, only the old model's outputs on new inputs. §14.4 expanded this KL to second order when it asked why fine-tuning forgets. The same expansion shows exactly what LwF protects.
Compare the two penalties. EWC uses the diagonal of the Fisher over task A's inputs. LwF keeps every correlation that EWC throws away. But it measures sensitivity on the new task's inputs, not the old task's. When B's inputs resemble A's, this costs little. When they do not, LwF fails. It pins the function down where task A never looks and leaves it free where task A does look.
Both sides of that trade-off show up in experiments. Van de Ven and Tolias found LwF ahead of every weight penalty on split MNIST, whose tasks share pen strokes. In the domain-incremental scenario it reached 71.5 percent, against 64.0 for EWC. On permuted MNIST, whose tasks' inputs are unrelated, LwF reached 69.8 percent in the task-incremental scenario. That is below the 81.8 percent of training with no protection at all, while EWC reached 94.7. Li and Hoiem saw the same failure when they trained on ImageNet and then on MNIST. Old-task accuracy fell substantially, though less than with plain fine-tuning.
The same penalty runs through post-training. The KL term of RLHF (§16.4) keeps the policy near its reference model on the prompts it trains on. It uses the reverse direction, DKL(πθ∥πref), averaged over the policy's own samples. InstructGPT's extra term, which adds the pretraining loss to the RL objective, is replay. Shenfeld et al. (2025) note that practitioners have seen the KL term reduce forgetting. Their result, later in this section, explains why. The best predictor of forgetting they found is LwF's penalty itself, the KL from the base model to the fine-tuned one on the new task's inputs.
Replay with a buffer that stays fair
The most reliable defense against forgetting is also the least clever. Keep some old examples and mix them into training. §14.4 found it the most effective remedy in a real fine-tune. Rolnick et al. (2019) found the same in reinforcement learning, on Atari games and on DMLab, a suite of 3D navigation and puzzle tasks. They replayed past experience and also trained the policy to match the old policy's action probabilities on it. That cut forgetting as well as or better than EWC and other methods that must be told where one task ends and the next begins. A buffer holding 50 million of the 900 million frames seen, kept as a uniform random sample, worked almost as well as one holding half of them.
That last detail raises a real question. The stream is long and the buffer holds k items. Which items should it keep, so that it is always a fair sample of everything seen so far, without knowing how long the stream will be? Keeping the first k ignores everything later. Keeping the last k, a first-in first-out queue, forgets the past, which is the failure we are trying to fix. The answer is reservoir sampling, Algorithm R in Vitter (1985), which he credits to Alan Waterman.
The rule is short. Keep the first k items. When item i arrives, with i>k, keep it with probability k/i, in a slot chosen uniformly at random, and evict what was there. Here it is on the smallest stream that shows something.
The early items had more chances to be evicted, and the late items had less chance to enter. The two effects cancel exactly, for every stream length.
∂ Every k-subset is equally likely
Suppose after n items every k-subset of {1,…,n} is the buffer with probability 1/(kn), which is true at n=k. Take any k-subset S′ of {1,…,n+1}.
If n+1∈/S′, the buffer must already have been S′, and item n+1 must have been rejected. So P(S′)=(kn)1⋅n+1n+1−k. Since (kn+1)=(kn)n+1−kn+1, this is 1/(kn+1).
If n+1∈S′, write S′=T∪{n+1} with ∣T∣=k−1. The previous buffer must have been T∪{x} for one of the n−k+1 items x∈{1,…,n}∖T, and item n+1 must have entered and landed on x's slot, with probability n+1k⋅k1=n+11. So P(S′)=(kn)(n+1)n−k+1=(kn+1)1 by the same identity.
The cell runs the four-item stream 40,000 times and counts how often each item ends in the buffer.
Python · cell 3
Reservoir sampling is a classic interview question in its own right, and the follow-up is always the proof by induction above. It needs O(k) memory and one random number per item. In a continual learner, the buffer it keeps is a fair sample of every task seen so far, in proportion to each task's size.
Interview
A replay buffer with reservoir sampling
Write class Reservoir with three methods, __init__(self, k, seed), add(self, item), and sample(self, m).
Keep the stored items in a list attribute buffer, at most k long. Keep the number of items seen so far in n_seen.
Use np.random.default_rng(seed) for all randomness.
sample(m) returns m distinct stored items, chosen uniformly without replacement. It raises ValueError if m exceeds the number stored.
The tests feed many seeded streams and check that every position in the stream is kept with probability k/n.
import numpy as np
class Reservoir:
def __init__(self, k, seed=0):
self.k, self.buffer, self.n_seen = k, [], 0
self.rng = np.random.default_rng(seed)
def add(self, item): # FIFO placeholder: keeps only the latest k
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.
Replay as a constraint on the gradient
The buffer can also act as a check on each step, rather than as extra training data. Gradient episodic memory (GEM, Lopez-Paz and Ranzato 2017) computes the gradient gk of the loss on each old task's buffered examples. It then asks that the update direction g~ satisfy ⟨g~,gk⟩≥0 for every old task. A small step along −g~ then does not increase any old loss, to first order. GEM finds the g~ closest to the current gradient g by solving a small quadratic program. Averaged GEM (A-GEM, Chaudhry et al. 2019) keeps a single constraint. It uses the gradient gref on a random batch from the whole buffer, and then the problem has a closed-form answer.
The step in the example is the closest vector to g that does not point against gref, and that holds in any number of dimensions.
∂ Proof of the A-GEM projection
The objective is strictly convex and the feasible set is a closed half-space, so a minimizer exists and is unique. The constraint is affine, so the KKT conditions, the optimality conditions for a constrained problem, are necessary and sufficient. With multiplier μ≥0 for the constraint written as −g~⊤gref≤0, the Lagrangian is 21∥g~−g∥2−μg~⊤gref. Stationarity gives g~=g+μgref, and complementary slackness gives μg~⊤gref=0.
If μ=0, then g~=g, which is feasible exactly when g⊤gref≥0. If μ>0, the constraint is tight, 0=g~⊤gref=g⊤gref+μ∥gref∥2, so μ=−g⊤gref/∥gref∥2, which is positive exactly when g⊤gref<0. If gref=0, every vector is feasible and the first case applies.
For the descent property, the first case gives ∥g∥2≥0. In the second, g~⊤g=∥g∥2−(g⊤gref)2/∥gref∥2≥0 by Cauchy-Schwarz, with equality only when g points exactly against gref. ■
In plain words, if the new task's step would hurt the old tasks on average, remove the part of the step that points against them and keep everything else. The step −ηg~, with η the learning rate, changes the replayed loss by −ηg~⊤gref≤0 to first order. So the old loss can rise only at second order in η. The guarantee covers the replayed batch's average loss, not each old example. That is why the buffer has to cover the directions the old task uses.
Interview
The A-GEM projection
Write agem_project(g, g_ref). Its inputs are flattened parameter gradients, 1-D arrays of the same length P. Return the solution g~ of the theorem as a new float array, and leave both inputs unchanged.
The setup provides train_two_tasks(project). It fits a linear model exactly to task A and then runs gradient descent on task B. At each step it passes two gradients through project, the gradient on B and the gradient on a 20-example replay buffer from A. If project is None it skips that step. It returns the final losses (loss_A, loss_B) and the loss on B before training.
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.
In that run, plain gradient descent on B raises task A's loss from zero to about 8.8. A-GEM with a 20-example buffer keeps it at 0.03 and still drives B's loss from 19.2 to under 0.01. The buffer works here because task A's inputs span only five directions, and 20 examples cover them. Suppose instead that the buffer misses some directions the old task uses. Then a model with more parameters than it needs can forget the unbuffered examples while the single averaged constraint stays satisfied.
When the data itself cannot be kept, generative replay (Shin et al. 2017) trains a generative model on past data and replays its samples, labeled by the old model. A language model is already a generator, so it can produce its own replay data. The risk is that it replays its own mistakes, a risk that training on self-generated data takes up in general.
Giving each task its own parameters
The third strategy gives each task weights that no other task touches, so there is nothing to forget.
Progressive networks (Rusu et al. 2016) freeze the network after each task and add a new column of layers for the next one. Lateral connections from every earlier column's layers let the new column reuse old features. Old tasks are untouched by construction. The cost is growth. Each task adds a column plus connections from all earlier columns, so the parameter count grows faster than linearly in the number of tasks.
PackNet (Mallya and Lazebnik 2018) works inside one fixed network. After training a task, it prunes a large fraction of the weights that task uses, briefly retrains the rest, and freezes them. The next task trains only in the freed weights. Each task is stored as a binary mask over one set of weights, and capacity eventually runs out.
Per-task adapters. For language models the natural unit is a LoRA adapter per task on a frozen base. LoRA adds to each weight matrix a trainable low-rank correction γBA, where B and A are thin matrices and γ is a fixed scale (§14.5). The base cannot forget. Each adapter costs a fraction of a percent of the parameters, and one server can hold many.
Orthogonal adapters go one step further (Wang et al. 2023, who call the method O-LoRA). A penalty keeps each new task's update in a subspace orthogonal to the earlier tasks' updates. In our notation the penalty is λ1∑i<t∥Bi⊤Bt∥F2, where the columns of B span the column space of the update. The paper names that matrix A.
Progressive networks, PackNet, and per-task adapters need to know which task a test input belongs to. Either the task is given, as in the task-incremental scenario, or a router must infer it. That is the price of zero forgetting, and in the class-incremental scenario it is the whole problem. O-LoRA needs no router. Its penalty keeps each task's update in its own directions, so it adds all the updates into the base weights and serves one model. It still needs to know where each task begins during training, and orthogonal updates reduce interference without guaranteeing zero forgetting. One more cheap remedy needs no routing. Interpolate between the base weights and the fine-tuned weights, as in the merging part of §14.4. That gives one dial that trades new-task accuracy for old-task accuracy.
Five tasks in a small simulation
Now all three defenses at once, on a problem small enough to run in your browser and large enough to behave like the real thing. Ten classes of Gaussian blobs in 16 dimensions are split into five two-class tasks. Each test input must be classified among all ten classes, as in the class-incremental version of split MNIST. The model is a one-hidden-layer MLP with a ten-way softmax output. We train it on the tasks in order, 300 full-batch steps each, and fill in the accuracy matrix.
The first cell defines the data, the model, its gradient, and the true diagonal Fisher. The Fisher is computed exactly, by summing over all ten labels weighted by the model's probabilities, rather than by sampling one label.
Python · cell 4
The training loop supports every method of this section.
EWC and online EWC use the single-anchor recursion. The spring is applied as an exact implicit step after each gradient step, θi←(θi+ηλΛiai)/(1+ηλΛi), with η the learning rate and a the anchor. This step minimizes the penalty plus 2η1∥θ−θold∥2. So it is stable for any λ, and it avoids the divergence we met in two dimensions.
Replay keeps a 200-example reservoir. It averages the gradients of the current batch and of a replayed batch of 64.
Per-task heads mask the softmax to the current task's two classes, in training and at test time. This is the task-incremental scenario, so it answers an easier question than the other four.
Python · cell 5
Python · cell 6
Python · cell 7
Forward transfer is −0.035 for every method that faces the ten-way choice. Before training on task j, the model has never been asked to output task j's classes, so it scores exactly 0 on them. That is slightly below the 3.5 percent that the random initialization happened to get, on average, on tasks 2 to 5. In the class-incremental scenario there is nothing to transfer forward. All of this matches van de Ven and Tolias, whose weight penalties failed on class-incremental split MNIST while replay worked.
The stability-plasticity dial shows up in a sweep over λ.
Python · cell 8
As λ grows, BWT climbs toward zero, from −0.384 at λ=10 to −0.020 at λ=104. But at λ=104 the network can no longer learn the newest task at all (accuracy 0.000 on it). So ACC peaks at an intermediate value, 0.843 at λ=1000, and falls to 0.614 beyond it. A method that reports only BWT can look perfect by refusing to learn. That is the reason to always report ACC beside it.
The widget runs a smaller version of the same experiment live, with a linear softmax classifier in place of the MLP. Its losses are on a different scale, so its best λ is near 20 rather than 1000. There a tuned EWC keeps up with a replay buffer of 100 or more examples, both near 89 percent, so how far replay pulls ahead depends on the model. Choose a method and its setting, then press Train and watch the accuracy matrix fill in task by task.
How much does a model forget as it learns tasks one after another, and what protects it?
Five two-class tasks, learned in order by one linear classifier that must choose among all ten classes.
Loading interactive figure…
The same tools in PyTorch
Here is a reference implementation of the two tools a practitioner would reach for. One is an EWC penalty with a diagonal Fisher estimated from labels sampled from the model. The other is a reservoir buffer. Both run on a small MLP. The two tasks share the same four outputs. They live in different regions of input space and use different label rules, so one network can solve both. That is the domain-incremental case.
🔥 In PyTorchfor a machine with PyTorch
Naive training on B drops task A from 0.96 to about 0.66. EWC and replay both keep A above 0.92 while learning B to above 0.92. In a real codebase the Fisher loop would compute per-sample gradients in one batched call (torch.func.vmap over torch.func.grad) instead of one backward pass per example. The anchor and the Fisher would be stored in float32 next to the optimizer state.
Keeping a large model current with continual pretraining
For a frontier model the sequence of tasks is a sequence of data. The model was pretrained on a corpus, and next month there is new data, such as newer web text, a new language, or more code. Retraining on the union from scratch is the gold standard, and it costs the whole pretraining budget again. Continual pretraining asks for the same result at the cost of training on the new data only.
The first obstacle is the learning rate. Pretraining usually raises the rate during a short warmup and then lowers it along a cosine curve (§8.7). So the model ends at a tiny learning rate, and continuing there barely moves it. Raising the rate again, called re-warming, lets it adapt. But a high rate is exactly what causes forgetting.
Gupta et al. (2023) measured both effects. They took a 410M-parameter model pretrained on 300B tokens of the Pile, an open corpus of web text, books, and code, and continued it on SlimPajama, another open corpus, mostly web text. Re-warming first raised the loss on both corpora, and in the longer run it beat a model trained from scratch on the new data alone. Ibrahim et al. (2024) then showed that three simple ingredients together come within a few hundredths of a nat of retraining, or match it.
The stronger the shift, the more replay it needs. About 5 percent was enough for newer text in the same language, and 25 percent for a new language. An interviewer who asks how you would keep a model current wants these three ingredients and these replay fractions.
Reinforcement learning forgets less than supervised fine-tuning
How a model is fine-tuned matters too, not only what it is fine-tuned on. §14.4 met the evidence as a warning about supervised fine-tuning. Here we restate it and then explain it. Shenfeld, Pari, and Agrawal (2025) compared supervised fine-tuning (SFT), which trains on fixed target answers, with on-policy reinforcement learning. On-policy RL trains on the model's own sampled answers, scored by a reward, as GRPO does (§18.4). They trained Qwen 2.5 3B-Instruct on new tasks in math, science questions, and tool use, and trained a 7B robot policy as well. Their RL runs used no explicit KL penalty.
The authors call the principle RL's Razor. Among the many policies that solve a new task, on-policy RL prefers the one closest in KL to where it started. The simplest case shows why. Take one prompt, a base policy π0 over a finite set of answers, and a reward R that is 1 for a correct answer and 0 otherwise. Sample from π0 and keep only the correct answers. This rejection sampling gives the distribution q(y)=π0(y)R(y)/Z, where Z=∑yπ0(y)R(y) is the base policy's success rate.
The theorem says that no always-correct policy can be closer than the rejection-sampling one.
In the example, Z=0.2 and −log0.2=1.61, the distance we computed by hand. The closest always-correct policy keeps the base model's relative preferences among the correct answers. On-policy RL reinforces the model's own successful samples, so it moves toward reweighted versions of what the model already does. Shenfeld et al. prove that, in an idealized setting, policy gradient converges to this KL-closest optimal policy. SFT on fixed target answers has no such pull, and it can end arbitrarily far from the base.
Keep the two kinds of claim apart. The predictor is an empirical finding. The convergence is a theorem for an idealized case, not a guarantee for every RL setup. In practice, the KL to the base model on new-task prompts is a cheap early warning of forgetting, worth tracking at every checkpoint.
Learning during a single test input
Everything so far learns between tasks. A different family learns during a single test input. It is the most direct sense in which a model's weights can serve as memory.
Why should training on neighbors help? A global model must compromise between every region of its training data. A local fit near the test input can follow the structure that holds there. That matters most when the test input comes from a region the training set covers thinly.
The cell builds that situation with ridge regression, least squares with a penalty α∥w∥2 on the size of the weights. The training set is 92 percent a common regime and 8 percent a rare, shifted regime with a different linear rule. We compare one global ridge model with ridge models fit at test time. Each of those is fit on one test point's 40 nearest training points. We report each approach's mean squared error (MSE), first on test points from the common regime and then on points from the rare one.
Python · cell 9
That is the trade test-time training makes everywhere. It buys accuracy where the input is unusual. It pays in compute at test time, and in higher variance where the input is ordinary.
Research
Test-time training on nearest neighbors
Write ttt_nn_predict(X_train, y_train, X_test, k, alpha). It returns predictions of shape (n_test,). For each row of X_test, do three things.
Find its k nearest training points by Euclidean distance.
Fit ridge regression with an unpenalized intercept on those points only, minimizing ∥y−b1−Xw∥2+α∥w∥2.
Predict that test point with the local fit.
The setup provides make_shift_data(seed), which builds the same data as the cell above. It returns a training set with a rare regime, and two test sets, one from the common regime and one from the rare one. The tests compare your predictions with a reference. They also check that with 40 neighbors, test-time training wins clearly on the shifted set and not on the common one.
import numpy as np
def ttt_nn_predict(X_train, y_train, X_test, k, alpha):
X_train, X_test = np.asarray(X_train, float), np.asarray(X_test, float)
w = np.linalg.lstsq(X_train, y_train, rcond=None)[0] # placeholder: one global model, no intercept
return X_test @ w
Tests, hints, verified solutions, and AI help, with Pro at $7.99 a month.
A layer whose state is a learned memory
Test-time training can also be built into the architecture, so that a layer learns from every token it reads. The idea is close to in-context learning, a model's ability to pick up a task from examples in its prompt. §6.8 gave two readings of it. One is inference over the hidden task that the examples share. The other, in a linear setting, is a step of gradient descent on the examples, carried out by one layer of linear attention. Here the gradient step becomes explicit.
The starting point is linear attention (§21.2). Instead of keeping every past key and value, it keeps one matrix St∈Rdk×dv, a running sum of key-value outer products. At token t it has a key kt, a value vt, and a query qt. It writes St=St−1+ktvt⊤ and reads St⊤qt.
§21.6 showed that this write is already a step of learning. It is one gradient step, with step size 1, on the loss −vt⊤S⊤kt. The delta rule of the DeltaNet layers reads what is stored at a key and writes only the correction, St=(I−βtktkt⊤)St−1+βtktvt⊤, and that section proved it is one step with rate βt on the regression loss 21∥S⊤kt−vt∥2. So these layers already train a linear model on the sequence as they read it, a model that maps each key to its value. What is new here is making that model, and its training, general.
TTT layers (Sun et al. 2024) make that idea general. The state is the weights Wt of any small model f. It is a linear map in the variant called TTT-Linear and a two-layer MLP in TTT-MLP. Each token trains it by one step on a reconstruction loss,
Wt=Wt−1−η∇Wf(θKxt;Wt−1)−θVxt2,
and the layer's output is f(θQxt;Wt). Here xt is the token's input vector and η the inner learning rate. The matrices θK,θV,θQ are ordinary weights learned in the outer training loop, the usual training of the whole network. Their names echo keys, values, and queries, because they play those roles.
In plain words, the layer keeps a tiny model as its memory. Each token is a training example for that tiny model, with a projection of the token as the input and another projection as the target. The layer answers by running the tiny model on a third projection.
∂ Proof that TTT-Linear contains linear attention and the delta rule
At W0=0 each gradient is −2vtkt⊤, so the batch steps add up to 2η∑svsks⊤, which is ∑svsks⊤ at η=21. Reading with a query gives Wtqt=∑svs(ks⊤qt), unnormalized linear attention. For the online case, substitute the gradient at Wt−1 into one step, which gives Wt=Wt−1−2η(Wt−1kt−vt)kt⊤, and transpose. ■
The TTT paper notes in its related work that DeltaNet is TTT-Linear with an inner mini-batch of one token and no layer norm or residual. For speed, TTT layers use a middle ground. They take mini-batches of 16 tokens, whose gradients are all taken at the weights left by the previous mini-batch. At 125M to 1.3B parameters, both TTT layers kept lowering perplexity, the exponential of the average loss per token, as the context grew to 32K tokens, as a Transformer does. Mamba, the state-space model of §21.4, stopped improving after 16K.
The memory in Behrouz, Zhong, and Mirrokni (2024), an architecture called Titans, goes one step further. It is a deep network M trained to map each key to its value. Its loss is ℓ(Mt−1;xt)=∥Mt−1(kt)−vt∥22, where kt and vt are linear projections of the input. It writes with momentum and forgetting,
with a forgetting gate αt∈[0,1], momentum μt, and learning rate ηt, all computed from the data. In plain words, the memory moves most when a token's value is badly predicted from its key, which the paper calls surprise. The momentum carries the surprise of recent tokens forward. The gate αt acts as weight decay that frees capacity. Reading is a forward pass Mt(qt) with no update. The paper reports scaling to contexts beyond 2M tokens on needle-in-a-haystack tests, which hide one fact in a long context and ask for it back.
Moving test-time learning into a cache or the weights
Two recent systems take what was learned from one corpus or one passage and store it somewhere lasting.
Where new knowledge should live
The chapter has offered three homes for a new piece of knowledge. It can go in the context window (§25.1 and §25.2), in an external store (§25.3), or in the weights (this section). Inside the weights there are two routes. Training, by fine-tuning or continual pretraining, changes every weight a little. A targeted edit changes almost nothing.
§23.7 derived two such edits. One writes a single fact as a rank-one update to one MLP matrix (ROME, Meng et al. 2022). The other writes thousands of facts at once, as one update per layer spread over several layers, each of rank at most the number of edits (MEMIT, Meng et al. 2023). The evidence on putting new facts into weights by either route is sobering.
The table puts the homes side by side, including the trained cache as a fourth home between context and weights. Each row is a trade-off, not a winner.
Facts that change, or that a user may ask to delete, belong in context or in a store.
Skills and broad shifts in the data belong in the weights, put there by continual pretraining with replay, or by RL, which forgets less.
Large static corpora that are queried many times are what trained caches are for.
A targeted edit suits a few urgent corrections that must hold on every call. It does not reach implied facts and does not survive thousands of edits, so a store remains the default for facts.
Anything put into weights needs an evaluation for forgetting, with probes of the base model's abilities at every checkpoint, as §14.4 recommended.
Where these methods break
EWC at scale. A diagonal Fisher over a language model's pretraining distribution needs many per-example gradients from that distribution, and the diagonal ignores the correlations that dominate a deep network's curvature. That is why §14.4 found EWC rarely used at scale. The box below adds up the cost.
Replay needs the old data. Users' data, licensed data, and deleted data may not be replayable. Generative replay replays a model's beliefs, errors included.
Isolation needs the task identity, and it grows. Adapters, columns, and masks are free of forgetting only when something routes each input to the right one.
Test-time training costs compute at every query, and it can overfit a single input.
Facts in weights are slow to learn, leak into other facts, and are hard to delete. That is why §25.6 treats deletion from weights as a separate, hard problem.
van de Ven and Tolias (2019). Three scenarios for continual learning. The task-, domain-, and class-incremental scenarios and a careful comparison of methods across them.
🎬 Razvan Pascanu - Continual learning for deep learning and deep reinforcement learning · ML in PLA DeepMind co-author of EWC surveys catastrophic forgetting, regularization, replay, and the open problems, a good companion to the first half of the section.🎬 Continual Learning and Catastrophic Forgetting · Paul HandA clear lecture on the continual learning setup, why forgetting happens, and the regularization and replay families of fixes.🎬 Allen School Colloquium: Test-Time Training · Paul G. Allen SchoolYu Sun, who introduced test-time training and TTT layers, explains learning at test time as long-term memory that scales with context.🎬 Titans: Learning to Memorize at Test Time (Paper Analysis) · Yannic KilcherA walk through the Titans memory update, surprise with momentum and forgetting, and its three ways of combining memory with attention.