~/satyajit

Soft-MTP: the one line that keeps the self-distillation and drops the noise

mdjsonmcp

2026-10-31 · 25 min · multi-token-prediction · pretraining · self-distillation · speculative-decoding · gradients

Why read this

Notabletop 60%

Derives the gradient split, checks it in float64, then runs the authors' own tests, losses and released checkpoints on CPU; results at scale are the paper's.

  • Original analysis
  • Runs on a laptop CPU
  • A lasting reference

Training & RLMITResearch paper

How this was scored
Is it new?
2 of 3: A real new idea, method or capability
Can I trust it?
2 of 3: Measures key facts from files, code or configs
Can I run it?
2 of 3: Open code or weights with real limits
Will I understand it?
2 of 3: Mechanism from first principles with figures
Can I act on it?
2 of 3: A concrete recipe, numbers or comparison
Will it last?
2 of 3: A reference for a year or more
Does it affect many?
1 of 3: A specialist community
Only here?
2 of 3: A teardown or measurement few others did

Score 67 of 100, ranked 207 of 666 rated articles. Each question is answered 0–3 by hand, and a 3 is rare. How articles are scored

Multi-token prediction (MTP) has become a routine ingredient of language-model pre-training: extra heads predict the tokens after the next one, the model learns from a denser signal, and the heads double as a draft model for speculative decoding. Multi-Token Prediction Done Right (Jayden Teoh, MIT; arXiv 2610.38331, submitted 2026-10-28, CC BY 4.0) argues that the standard loss has a flaw, and that the fix is a single line. The post that brought it to my attention puts it as h_ntp += h_mtp.detach() - h_mtp, "a free lunch for MTP".

A claim that a gradient identity is exact is a claim I can check, so most of this article is checking. The paper's results at 167M to 1.2B parameters are the authors' and are marked reported. What I did myself is marked measured: I rederived the decomposition, tested it in float64 on 300 random cases and in float32 on a small transformer, ran the repository's own tests, compared the authors' released 167M checkpoints (same tensors, and the effective rank of the output head), and timed the cost of the alternative. I also trained a toy model, with limits spelled out in its own section.

Correction, 2026-10-31. The first version of this article said I had not run any of the repository's code and that every number on the method came from my own reimplementation. That was true then and is not now. I have since cloned the repository (commit 4e1cf66), run its test suite (17 of 17 pass on CPU), trained its own model class and its own four losses at toy scale, and loaded its released 167M checkpoints into its own model class. One thing I wrote before running it turned out wrong: my toy said Soft-MTP gives the best draft agreement, and the authors' own model code does not reproduce that at 300 steps. The details are in What running their code showed; the toy section below is kept as it was, with its claim now labelled as one implementation's result.

The setup, and what "position-aligned" means

A language model maps a prefix to a hidden state ht(0)h^{(0)}_t and a linear output head WW (a V×dV \times d matrix, shared by every head below) turns it into a distribution over the next token: pt(0)=softmax(Wht(0))p^{(0)}_t = \mathrm{softmax}(W h^{(0)}_t). The next-token loss is the cross-entropy against the actual next token xt+1x_{t+1}.

MTP adds KK extra heads. Head kk at position tt produces its own hidden state ht(k)h^{(k)}_t and predicts the token kk steps further on, xt+k+1x_{t+k+1}, through the same matrix WW. (Two designs exist: parallel heads, as in Gloeckle et al., and the sequential DeepSeek style where each head also consumes the embedding of the token it just skipped. The analysis does not depend on which.) The total loss is the next-token loss plus an average of the KK extra cross-entropies, weighted by λ\lambda.

The observation that everything follows from is in the paper's Figure 2. The token xt+k+1x_{t+k+1} that head kk predicts at position tt is also the token that the ordinary next-token head predicts at position t+kt+k. So for every MTP prediction there is a position-aligned NTP distribution, qt(k)=pt+k(0)q^{(k)}_t = p^{(0)}_{t+k}, aimed at the same target by the same model.

Diagram of a shared trunk reading tokens x1 to x5. The NTP head gives distributions p3, p4 and p5 at positions 3, 4 and 5. MTP head 1 at position 3 gives p3 with superscript 1, MTP head 2 at position 3 gives p3 with superscript 2. Blue dashed arrows join each MTP distribution to the NTP distribution at the position it is trying to predict: head 1 to the NTP distribution at position 4, head 2 to the one at position 5.
Position-aligned NTP distributions: an MTP head at position 3 and the NTP head at a later position are predicting the same token (paper, Figure 2).

Splitting the gradient

Back-propagating the cross-entropy of head kk to its hidden state gives a familiar vector. With pp the head's distribution and eye_y the one-hot target,

ghard=W⊤(p−ey).g_{\text{hard}} = W^{\top}\left(p - e_y\right).

Add and subtract the aligned NTP distribution qq:

ghard=W⊤(p−q)⏟gKL+W⊤(q−ey)⏟gres.g_{\text{hard}} = \underbrace{W^{\top}(p - q)}_{g_{\text{KL}}} + \underbrace{W^{\top}(q - e_y)}_{g_{\text{res}}}.

This is an identity, nothing more. But both pieces mean something. The first is exactly the gradient, with respect to the head's hidden state, of the KL divergence from a stop-gradient copy of qq to pp: a pull of the MTP head toward the model's own next-token distribution. That is self-distillation, and it is what makes the draft heads agree with the verifier in speculative decoding. The second is the cross-entropy gradient that the aligned NTP hidden state would receive for the same token. It depends on which token was sampled.

The card below does the arithmetic for one position over a five-word vocabulary. Draw targets from the teacher's distribution and watch the two pieces: the KL piece does not change with the draw; the residual does, and on average it is bigger.

The expected squared length of the residual, when the sampled token follows the teacher's distribution, is 1−∑iqi21 - \sum_i q_i^2: a floor set by the teacher's entropy that training the head cannot lower. The KL piece shrinks as the head catches up with the teacher. The paper's argument for removing the residual is a bias-variance one. Splitting q−eyq - e_y into (q−π)+(π−ey)(q - \pi) + (\pi - e_y), with π\pi the true distribution of the next token, gives an "NTP error" term that shrinks as the model improves and a one-hot sampling-noise term with mean zero that never does. Dropping the residual removes the noise at the price of a bias toward the NTP distribution instead of the data, a trade that gets better with scale and one the paper argues is the right one for a head whose job is to match its verifier.

The trick: put the missing gradient where it can be found

To keep gKLg_{\text{KL}} and drop gresg_{\text{res}}, you need to subtract gresg_{\text{res}} at the MTP hidden state. The paper's observation is that gresg_{\text{res}} already exists in the computation: it is the gradient the aligned NTP hidden state receives from its own cross-entropy. So route it.

Replace the NTP hidden state at position t+kt+k with

h~t+k=ht+k(0)+[sg(ht(k))−ht(k)].\tilde h_{t+k} = h^{(0)}_{t+k} + \left[\mathrm{sg}\left(h^{(k)}_t\right) - h^{(k)}_t\right].

The bracket is exactly zero in the forward pass, so the NTP logits, the NTP loss and the gradient reaching h(0)h^{(0)} are unchanged. In the backward pass the bracket is not zero: the NTP cross-entropy's gradient at h~t+k\tilde h_{t+k} is W⊤(q−ey)W^{\top}(q - e_y), and it flows into ht(k)h^{(k)}_t with a minus sign. Add the head's own cross-entropy gradient and

ghard−gres=gKL.g_{\text{hard}} - g_{\text{res}} = g_{\text{KL}}.

No teacher is cached, no second softmax is formed, and the loss value reported is the ordinary hard loss. In the repository's implementation the line carries a scale factor, ntp_hidden[:, head:] = ntp_hidden[:, head:] + kl_recovery_scale * (head_hidden.detach() - head_hidden), which accounts for the mean over TT NTP positions, the mean over the T−kT-k positions of head kk, and the λ/K\lambda/K weighting (loss.py, line 174 of commit 4e1cf66; the factor is derived in the paper's Appendix B.5).

Block diagram. A shared trunk feeds an NTP head, producing hidden state h0, and MTP heads, producing hidden states h1 to hK. Both hidden-state groups connect up to the LM head, which produces the predicted tokens. Forward arrows are grey and cross-entropy gradient arrows are red going down through both paths. A red arrow labelled straight-through gradient routing leads from h0 to the MTP heads, and a blue KL-gradient arrow goes from the MTP heads down into the shared trunk.
Soft-MTP: cross-entropy gradients (red) reach the output head; the straight-through route (red, middle) hands the NTP hidden's gradient to the MTP head; what continues into the trunk is the KL gradient (blue) (paper, Figure 1).

There is one subtlety the paper states and the diagram below makes concrete. The trick recovers the KL gradient for everything upstream of the MTP hidden states: the MTP heads and the shared trunk. The shared output head WW still receives the ordinary cross-entropy gradient from both the NTP and the MTP losses. An explicit KL objective would instead send a KL gradient into WW too. The paper argues the difference is a feature, and shows an effect of it at 1.2B that I will come back to.

What I checked

The identity, in float64 (measured). I built random instances of the setup: a shared output head WW and hidden states for TT positions and KK MTP heads, treated as free variables, with vocabulary size VV from 8 to 399, width dd from 4 to 63, TT from 6 to 39, KK from 1 to 4 and λ\lambda from 0.25 to 1.75. Over 300 such instances, using PyTorch's autograd on the augmented loss:

Through a trunk (measured). To see parameters rather than hidden states, I gave each of 40 instances a tiny trunk and heads and compared the Soft-MTP gradient with that of an explicit objective that is cross-entropy for the next token plus λ/K\lambda/K times the KL for each head. Trunk and head gradients agree to 1.1×10−161.1 \times 10^{-16}. They differ from plain MTP's by up to 0.26 (gradient scale about 0.56). The shared output head behaves as the paper's gradient-routing table says: Soft-MTP's WW gradient equals plain MTP's exactly (difference 0.0) and differs from the explicit-KL objective's by 0.085.

On a real (small) model, in float32 (measured). I repeated the comparison on the toy transformer used further down (537,856 parameters, a 2,048-token vocabulary, two trunk layers, three sequential MTP heads), on one batch. Soft-MTP's trunk gradient matches the explicit KL objective's to a worst relative difference of 3.3×10−73.3 \times 10^{-7} and its MTP-head gradients to 4.6×10−64.6 \times 10^{-6}, float32 round-off; it differs from plain MTP's trunk gradient by 57%. The output-head gradient equals plain MTP's (0.0) and differs from the explicit-KL objective's by 61%. The reported loss is bit-identical to plain MTP's.

The authors' own gate (measured). The repository's tests/test_kl_recovery.py checks the same four things in float64: head and trunk gradients of soft_mtp equal those of direct_kl; the two differ only at the shared unembedding; the head gradient equals the closed form; and the forward value is untouched because the coupling is x.detach() - x. My check was written from the paper before I read it, so the two are independent confirmations. The first version only read this file. I have now run it: pytest tests -v on commit 4e1cf66 with torch 2.14.1 and transformers 5.20.2, CPU, gives 17 passed in 7.8 s (log: public/articles/soft-mtp/verify/pytest.out). That includes the mutation check, which perturbs the recovery scale by 0.5 and 2.0 and confirms the identity then fails, so the gate can fail. The repository is MIT-licensed; the paper is CC BY 4.0.

The released checkpoints (measured). The authors published NTP, standard-MTP and Soft-MTP models at 167M, 332M, 689M and 1.2B parameters (twelve repositories under JaydenTeoh/; the 689M one declares an MIT licence). The 167M hard-MTP and Soft-MTP files have the same 366 tensors and 178,368,512 parameters (the NTP-only model has 327 tensors and 167,154,176): "adds no parameters" holds, and the MTP heads cost 11.2M parameters, 6.7% of the base model.

The paper's Figure 6 reports that standard MTP collapses the effective rank of the readout (the unembedding multiplied by the final RMSNorm gain, effective rank in the sense of Roy and Vetterli) and that Soft-MTP stays closest to the NTP baseline: 496 for NTP, 131 for standard MTP, 321 for KL-MTP and 437 for Soft-MTP at 1.2B. I fetched only the unembedding and norm tensors of the three 167M models by byte range and computed the same quantity:

167M modeleffective rank of the logit map (of at most 512)
next-token only468.3
standard MTP436.4
Soft-MTP464.5

The ordering matches the paper, and at this scale the collapse is mild (7% below the baseline for standard MTP, against 74% at 1.2B in the paper). That fits the paper's claim that the problem grows with scale, though it is one training run per model and says nothing about cause.

The cost (measured). The paper's Table 5 reports, at 1.2B on a single H200 GPU, that Soft-MTP is within 0.07% of standard MTP's throughput while explicit KL costs 1.5% more FLOPs, 5.6% throughput and 21 GB of peak memory. I have no GPU here, but the mechanism is visible on a CPU. For one step of the combined loss at 512 tokens, width 512, a 50,304-token vocabulary and three heads, averaged over two runs (CPU seconds, peak resident memory):

objectiveCPU seconds per steppeak memory
standard MTP4.811.14 GB
Soft-MTP5.08 (+5.5%)1.04 to 1.19 GB
explicit KL7.05 (+46%)1.51 GB (+32%)

The Soft-MTP difference is within the run-to-run spread on this loaded machine (the two runs differ by 0.3 s); the explicit KL's extra cost is not. The extra time and memory come from materialising a teacher and a student distribution over the vocabulary for every head, which the trick never does and which standard fused cross-entropy kernels avoid by design.

A toy run, and where it disagrees

To see the objectives behave rather than only their gradients, I trained a toy model on a CPU: a 537,856-parameter transformer (two trunk layers, width 64) with three DeepSeek-style sequential MTP heads, a 2,048-entry BPE vocabulary trained on three public-domain books (542,232 tokens, a tenth held out), 300 steps, λ=1\lambda = 1, three seeds per objective with the same seeds across objectives so the comparisons are paired. Four objectives: next-token only; standard MTP; explicit KL (KL gradient at the heads, trunk and the shared output head); and Soft-MTP with the repository's scale factor. This is far from the paper's setting, and "toy" is the right word: the held-out loss ends near 5.6 nats per token (a perplexity of about 270; a uniform guess is 7.6), so the next-token teacher is poor. All numbers below are measured, from public/articles/soft-mtp/code/results.jsonl.

mean over 3 seeds (± standard deviation)next-token onlystandard MTPexplicit KLSoft-MTP
held-out next-token loss, nats5.610 ± 0.0195.542 ± 0.0155.609 ± 0.0175.661 ± 0.020
extra-head cross-entropyn/a5.3825.6865.722
KL from the next-token distribution to the headsn/a0.1820.0710.062
top-token agreement with the next-token headn/a0.5960.7250.736
expected accepted draft length (teacher-forced)n/a2.202.642.68

What agrees with the paper (in my own toy implementation; the authors' own code, tested in the next section, does not agree at this training length):

What does not:

A reading of why, which I have not tested further: in the paper's own bias-variance argument, the residual is the sum of the teacher's error and sampling noise, and Soft-MTP removes both. Early in training, or in a very small model, the teacher's error is large and carries real information about the data that the next-token head has not yet learned; dropping it costs the trunk a useful signal, while the noise it removes matters less. The paper argues the trade turns favourable as the teacher improves. I did not train longer or larger models to check, so treat this as a hypothesis that fits the direction of the evidence.

What running their code showed

The toy above is my own transformer. The repository ships its own model class (HFMTPForCausalLM: RoPE, RMSNorm, SwiGLU, sequential DeepSeek-style heads with per-head final norms) and its own four losses (ntp, hard_mtp, soft_mtp, direct_kl in src/models/loss.py). All numbers here are measured, on CPU, with torch 2.14.1 and transformers 5.20.2. I skimmed the entry points first for network, subprocess and pickle use, and ran only the tests, the model and loss modules, and my own loops around them.

The line. The stop-gradient trick is one statement, loss.py line 174 at commit 4e1cf66: ntp_hidden[:, head:] = ntp_hidden[:, head:] + kl_recovery_scale * (head_hidden.detach() - head_hidden). It runs inside the same loop that computes the plain cross-entropy for each head, and direct_kl is a separate function that materialises the teacher softmax.

Their tests. 17 of 17 pass, as above. That settles the identity for the real implementation, not only for my reimplementation of it.

Their model and losses, trained small. I built a 615,680-parameter instance (width 64, two trunk layers, three sequential heads, a 2,048-entry vocabulary), took the same tokens and the same AdamW schedule as my toy, and trained each objective for 300 steps with three seeds, calling the repository's model(input_ids, labels=…) so that its loss code does the work. Same corpus and held-out split as before; the architecture is theirs, so the numbers differ from the toy table.

300 steps, mean over 3 seedsnext-token onlystandard MTPexplicit KLSoft-MTP
held-out next-token loss, nats4.8724.9334.9265.022
KL from the next-token distribution to the headsn/a0.0450.0810.078
top-token agreement with the next-token headn/a0.7730.6520.654
expected accepted draft lengthn/a2.842.362.37

Two results differ from my toy and one holds.

Cost, on a loaded machine with two jobs sharing the CPU: 0.13 s per step for next-token only, 0.24 for plain MTP, 0.23 for Soft-MTP and 0.35 for explicit KL, so Soft-MTP matched plain MTP and explicit KL cost about half as much again, in line with the cost table above.

The released checkpoints, run. Earlier I only read tensors out of the released 167M files. This time I loaded all three (next-token only, plain MTP, Soft-MTP) into the repository's own model class, with no missing or unexpected keys, and measured them on 12,288 positions of the three public-domain books (they were trained on FineWeb-Edu, so these are out-of-domain; the books are Austen, Shelley and Doyle).

167M checkpointnext-token losstop-token agreement, heads 1 to 3KL to the next-token head, heads 1 to 3accepted length
next-token only3.280n/an/an/a
plain MTP3.3380.689, 0.636, 0.6110.339, 0.473, 0.5602.39
Soft-MTP3.3250.703, 0.650, 0.6230.327, 0.454, 0.5332.45

This is one checkpoint per objective, so no variance is available, and the books are not the paper's test set. The direction is the paper's: Soft-MTP's heads agree more and sit closer in KL at every head, its next-token loss is lower than plain MTP's, and the next-token-only model has the lowest loss on this text. The gaps are small, as they are in the paper at this size. Parameter counts (178,368,512 for both MTP models, 167,154,176 for next-token only) match the tensor listing above.

Not run. Training at the paper's scale (FineWeb-Edu, Muon, 167M and up) and the lm_eval and speculative-decoding evaluation need a GPU and days; I ran none of it.

Scripts, logs and raw results are in public/articles/soft-mtp/verify/.

What the paper reports

All of this section is reported, from the paper's own tables; I did not rerun any of it. The setup is LLaMA-style models from 167M to 1.2B parameters, DeepSeek-style sequential MTP with three extra heads, trained on about 31.5B tokens of FineWeb-Edu with the Muon optimizer, λ=1\lambda = 1.

Line chart of signal-to-noise ratio of the gradient against training step, on a log scale from 0.01 to about 0.5. Two families of lines, one for the KL component in blue shades and one for the residual component in red shades, each with four lines for model sizes 167 million to 1.2 billion parameters. The blue lines lie well above the red lines throughout, declining from about 0.3 to about 0.1; the red lines decline from about 0.1 to about 0.03.
Signal-to-noise ratio of the KL component (blue) and the residual (red) of the MTP gradient, 167M to 1.2B parameters; the residual is about four times noisier throughout (paper, Figure 3a).
Validation next-token loss against training step for four model sizes, 167M to 1.2B, each with a standard MTP curve in grey and a Soft-MTP curve in blue. At the end of training the Soft-MTP curves sit just below the MTP curves, with gaps labelled minus 0.002, minus 0.004, minus 0.009 and minus 0.014 from the smallest to the largest model.
Validation next-token loss, standard MTP against Soft-MTP, 167M to 1.2B; the gap widens with scale (paper, Figure 4b).

What I would be careful about

Sources

JaydenTeoh/Soft-MTP@4e1cf66 · snapshot 2026-10-31
tracked files
48
license
MIT
branch
main
tests
1 file
source
182.6 kB
commit date
2026-10-30
source by language
Python182.6 kB(29)

by size of tracked source at this commit, file counts in brackets; docs, data and vendored trees excluded

local clone, 2026-10-31 at 4e1cf66 — branch, commit, commitDate, fileCount, hasTests, languages, license, licenseFile, shallow, testFileCount

shallow clone: counts describe the pinned tree, not the history

Cite this article

For attribution, please use the following reference or BibTeX:

Satyajit Ghana, "Soft-MTP: the one line that keeps the self-distillation and drops the noise", ai.thesatyajit.com, October 2026.

bibtex
@misc{ghana2026softmtp,
  author = {Satyajit Ghana},
  title  = {Soft-MTP: the one line that keeps the self-distillation and drops the noise},
  url    = {https://ai.thesatyajit.com/articles/soft-mtp},
  year   = {2026}
}
share