<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Knowledge-Distillation on MdJawad</title><link>https://www.mdjawad.com/tags/knowledge-distillation/</link><description>Recent content in Knowledge-Distillation on MdJawad</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 14 Sep 2026 08:17:34 +0000</lastBuildDate><atom:link href="https://www.mdjawad.com/tags/knowledge-distillation/index.xml" rel="self" type="application/rss+xml"/><item><title>What I Learned Implementing On-Policy Distillation</title><link>https://www.mdjawad.com/posts/model-distillation/</link><pubDate>Mon, 14 Sep 2026 16:00:00 +0800</pubDate><guid>https://www.mdjawad.com/posts/model-distillation/</guid><description>An experiment distilling Qwen3-8B into a 0.6B student: the mathematics of teacher feedback, the improvement greedy evaluation nearly missed, and what a failed training run revealed.</description><content:encoded><![CDATA[<h2 id="the-question-i-wanted-to-test">The question I wanted to test</h2>
<p>I built a distillation pipeline to investigate a specific question: does a small language model learn differently when its teacher evaluates the responses it actually produces, rather than supplying solutions for it to imitate?</p>
<p>The student was Qwen3-0.6B-Base, with roughly 596 million parameters. The teacher was Qwen3-8B, with roughly 8.19 billion. I implemented the sampling, scoring, training, and evaluation pipeline, then trained LoRA adapters on the student using grade-school mathematics problems from GSM8K. Both models started from existing checkpoints; implementing the process from scratch did not mean pretraining either model.</p>
<p>The most informative result depended on how I asked the trained models to answer. With <strong>greedy decoding</strong>, which selects the most probable next token at each position, learning from correctness-filtered teacher solutions reached 54.4% test accuracy. On-policy distillation reached 55.0%. That difference alone would give little reason to prefer the more involved procedure.</p>
<p>When I sampled responses instead, the scores were 44.0% and 51.6%. The improvement was 7.6 percentage points on the same 1,319 test problems.</p>
<p>Here, sampled accuracy means <strong>mean@4</strong>: generate four responses per problem at temperature one, score each, and average their correctness. A problem with one correct response contributes 25%, not 100%. There is no selection of the best answer or majority vote. Temperature one uses the model&rsquo;s ordinary next-token probabilities, with no top-k or top-p truncation.</p>
<figure class="opd-figure" id="figure-1" data-opd="results" aria-labelledby="opd-title-results">
  <header class="opd-heading">
    <div class="opd-eyebrow"><span>Figure 01</span><span>Measured · 1,319 test problems</span></div>
    <h3 id="opd-title-results">The improvement depends on how we decode</h3>
    <p>The same trained models are almost tied greedily, but separate when their responses are sampled.</p>
  </header>
  
    <picture class="opd-chart">
  <source width="460" height="950" media="(max-width: 999px)" srcset="/figures/distillation/results-mobile.svg" data-light="/figures/distillation/results-mobile.svg" data-dark="/figures/distillation/results-mobile-dark.svg">
  <img width="1120" height="525" src="/figures/distillation/results.svg" data-light="/figures/distillation/results.svg" data-dark="/figures/distillation/results-dark.svg" alt="Two dot plots of test accuracy. Supervised distillation: 54.4% greedy, 44.0% sampled. On-policy: 55.0% greedy, 51.6% sampled. Warm start: 56.6% and 52.1%. Teacher: 86.3% and 80.0%." loading="lazy" decoding="async">
</picture>
<img width="1120" height="525" class="opd-print" src="/figures/distillation/results.svg" alt="Two dot plots of test accuracy. Supervised distillation: 54.4% greedy, 44.0% sampled. On-policy: 55.0% greedy, 51.6% sampled. Warm start: 56.6% and 52.1%. Teacher: 86.3% and 80.0%." loading="lazy">

  
  <div class="opd-finding"><strong>&#43;7.64 percentage points under sampling</strong><p>95% interval on the paired difference: &#43;6.10 to &#43;9.10 points. Greedy difference: &#43;0.61 points, with an interval spanning zero.</p></div>
  
  <details class="opd-details"><summary>Read the numerical results</summary><div class="opd-table-scroll"><table><caption>Accuracy and 95% problem-bootstrap intervals</caption><thead><tr><th scope="col">Model</th><th scope="col">Greedy</th><th scope="col">Mean@4</th></tr></thead><tbody>
<tr><th scope="row">Base student</th><td>41.32% <small>[38.82, 44.05]</small></td><td>24.79% <small>[23.18, 26.52]</small></td></tr>
<tr><th scope="row">Supervised correct only</th><td>54.44% <small>[51.93, 57.24]</small></td><td>43.95% <small>[41.94, 46.10]</small></td></tr>
<tr><th scope="row">On-policy</th><td>55.04% <small>[52.31, 57.62]</small></td><td>51.59% <small>[49.26, 53.77]</small></td></tr>
<tr><th scope="row">Warm start then on-policy</th><td>56.63% <small>[54.05, 59.36]</small></td><td>52.12% <small>[50.02, 54.28]</small></td></tr>
<tr><th scope="row">Teacher</th><td>86.28% <small>[84.46, 88.10]</small></td><td>80.00% <small>[78.49, 81.58]</small></td></tr>
</tbody></table></div></details>
  
  
  <figcaption><span class="opd-caption-number">01 /</span> Points are observed accuracies; lines are 95% paired problem-bootstrap intervals for each model. Mean@4 averages four outcomes per problem, not best-of-four. Intervals condition on these trained models.
  <div class="opd-footer"><span>Raw test evaluations · paired problem bootstrap</span><nav aria-label="Figure 1 downloads"><a href="/figures/distillation/results.svg" download>SVG</a><a href="/figures/distillation/results.png" download>PNG</a><a href="/figures/distillation/data.json" download>Data</a></nav></div>
  </figcaption>
</figure>

<p>Understanding that difference requires some mathematics, but also care about what the experiment actually compared. I will explain the training signal first, then examine the successful runs, a revealing token-level example, and an ablation that made training substantially worse.</p>
<h2 id="what-the-teacher-supplies">What the teacher supplies</h2>
<h3 id="from-a-written-answer-to-a-policy">From a written answer to a policy</h3>
<p>A language model produces a distribution over possible next <strong>tokens</strong>, the vocabulary units from which its response is assembled. That distribution depends on the prompt and everything it has already written. If the prompt is $x$ and the response so far is $y_{\lt t}$, write the student&rsquo;s policy as</p>
$$q_\theta(v\mid x,y_{\lt t}).$$<p>Here $v$ is a candidate next token and $\theta$ represents trainable parameters. The <strong>prefix</strong> is the text before the current token. Changing an earlier arithmetic operation changes the context for every subsequent prediction.</p>
<p>In supervised distillation, the teacher writes a solution and the student learns to make its tokens more probable. At each position, training supplies the teacher-written prefix. This is useful supervision, but it differs from inference, when the student&rsquo;s own earlier choices determine what it must continue.</p>
<p>A teacher can also supply probabilities. Instead of revealing only its selected token, it reveals how it distributes probability among alternatives. This is the central idea behind the soft targets in <a href="https://arxiv.org/abs/1503.02531">Hinton, Vinyals, and Dean&rsquo;s distillation paper</a>. Probabilities express the teacher&rsquo;s preferences; they do not certify that those preferences are correct.</p>
<h3 id="what-kl-divergence-measures">What KL divergence measures</h3>
<p>For two distributions $p$ and $q$ over the same outcomes, their Kullback–Leibler divergence is</p>
$$D_{\mathrm{KL}}(p\|q)=\sum_v p(v)\log\frac{p(v)}{q(v)}.$$<p>The log-ratio compares the probability assigned to an outcome. The weighting by $p(v)$ then averages those comparisons over outcomes drawn from $p$. We use natural logarithms, giving units of <strong>nats</strong>.</p>
<p>Consider an illustrative three-outcome vocabulary, with teacher probabilities $p=(0.7,0.2,0.1)$ and student probabilities $q=(0.4,0.4,0.2)$. The contributions to $D_{\mathrm{KL}}(p\|q)$ are approximately $0.392$, $-0.139$, and $-0.069$ nats. Their sum is $0.184$.</p>
<p>The negative contributions are legitimate: on those outcomes the student assigns more probability than the teacher. The total divergence is nevertheless nonnegative, and is zero exactly when the distributions match. Swapping them gives $D_{\mathrm{KL}}(q\|p)\approx0.192$, because both the log-ratio and the weighting change. KL is therefore asymmetric, rather than a geometric distance between distributions.</p>
<figure class="opd-figure" id="figure-2" data-opd="kl" aria-labelledby="opd-title-kl">
  <header class="opd-heading">
    <div class="opd-eyebrow"><span>Figure 02</span><span>Explore · illustrative distributions</span></div>
    <h3 id="opd-title-kl">KL is an average of weighted log-ratios</h3>
    <p>Some outcomes contribute negatively. The total stays nonnegative, and reversing the comparison changes which distribution weights the average.</p>
  </header>
  
    <div class="opd-kl-body">
  <div class="opd-controls opd-js-only">
    <label>Comparison direction<select data-direction><option value="forward">Forward · teacher p ‖ student q</option><option value="reverse">Reverse · student q ‖ teacher p</option></select></label>
    <div class="opd-presets"><button type="button" data-preset="reset">Reset example</button><button type="button" data-preset="match">Match the teacher</button></div>
  </div>
  <div class="opd-duet">
    <section class="opd-probability"><h4>1. Compare probabilities</h4><p class="opd-small">Each model distributes a total probability of 1.</p>
      <div class="opd-legend"><span class="opd-key teacher">Teacher p</span><span class="opd-key student">Student q</span></div>
      <div class="opd-prob-row"><strong>A</strong><div><div class="opd-prob-track"><span class="teacher" style="width:70%"></span><output>0.700</output></div><div class="opd-prob-track"><span class="student" data-prob="0" style="width:40%"></span><output data-q="0">0.400</output></div></div></div>
      <div class="opd-prob-row"><strong>B</strong><div><div class="opd-prob-track"><span class="teacher" style="width:20%"></span><output>0.200</output></div><div class="opd-prob-track"><span class="student" data-prob="1" style="width:40%"></span><output data-q="1">0.400</output></div></div></div>
      <div class="opd-prob-row"><strong>C</strong><div><div class="opd-prob-track"><span class="teacher" style="width:10%"></span><output>0.100</output></div><div class="opd-prob-track"><span class="student" data-prob="2" style="width:20%"></span><output data-q="2">0.200</output></div></div></div>
      <fieldset class="opd-weights opd-js-only"><legend>Adjust student weights</legend><p class="opd-small">Weights are normalized into the probabilities above.</p><label><span>A</span><input type="range" min="1" max="100" step="1" value="40" data-weight="0" aria-label="Student weight for outcome A"></label><label><span>B</span><input type="range" min="1" max="100" step="1" value="40" data-weight="1" aria-label="Student weight for outcome B"></label><label><span>C</span><input type="range" min="1" max="100" step="1" value="20" data-weight="2" aria-label="Student weight for outcome C"></label></fieldset>
    </section>
    <section class="opd-contributions"><h4>2. Weight each log-ratio</h4><p class="opd-formula" data-formula>p(v) × log[p(v) / q(v)]</p>
      <svg viewBox="0 0 350 205" role="img" aria-labelledby="opd-contrib-title"><title id="opd-contrib-title">Signed KL contributions in nats for outcomes A, B, and C</title><line x1="174" y1="15" x2="174" y2="163" class="opd-zero"/>
        <text x="174" y="191" text-anchor="middle" class="opd-svg-small">0</text><text x="42" y="191" class="opd-svg-small" data-axis-negative>−0.5</text><text x="306" y="191" text-anchor="end" class="opd-svg-small" data-axis-positive>+0.5</text>
        <g transform="translate(0, 25)"><text x="8" y="20" class="opd-svg-label">A</text><rect data-contribution="0" x="174" y="0" width="103.4" height="28" rx="2" class="positive"/><text data-contribution-label="0" x="340" y="20" text-anchor="end" class="opd-svg-value">&#43;0.392</text></g><g transform="translate(0, 74)"><text x="8" y="20" class="opd-svg-label">B</text><rect data-contribution="1" x="137.4" y="0" width="36.6" height="28" rx="2" class="negative"/><text data-contribution-label="1" x="340" y="20" text-anchor="end" class="opd-svg-value">−0.139</text></g><g transform="translate(0, 123)"><text x="8" y="20" class="opd-svg-label">C</text><rect data-contribution="2" x="155.7" y="0" width="18.3" height="28" rx="2" class="negative"/><text data-contribution-label="2" x="340" y="20" text-anchor="end" class="opd-svg-value">−0.069</text></g>
      </svg>
      <div class="opd-kl-total" aria-live="polite" aria-atomic="true"><span data-total-label>Forward KL · p ‖ q</span><strong><output data-total>0.184</output><small> nats</small></strong><p data-explanation>The positive contribution of A exceeds the negative contributions of B and C.</p></div>
    </section>
  </div>
</div>

  
  
  
  
  <figcaption><span class="opd-caption-number">02 /</span> Three illustrative outcomes, not model measurements. Natural logarithms give nats. Probabilities remain strictly positive; exact zero probabilities can make KL infinite.
  <div class="opd-footer"><span>Hand-checkable probability example</span><nav aria-label="Figure 2 downloads"><a href="/figures/distillation/kl.svg" download>SVG</a><a href="/figures/distillation/kl.png" download>PNG</a></nav></div>
  </figcaption>
</figure>

<p>With $p$ denoting the teacher, $D_{\mathrm{KL}}(p\|q)$ is called <strong>forward KL</strong> and $D_{\mathrm{KL}}(q\|p)$ <strong>reverse KL</strong>. Forward KL gives substantial weight to outcomes the teacher frequently produces. Reverse KL gives substantial weight to outcomes the student frequently produces, penalizing choices the teacher considers unlikely.</p>
<p>The connection to supervised training is</p>
$$H(p,q)=H(p)+D_{\mathrm{KL}}(p\|q).$$<p>Cross-entropy $H(p,q)=-\sum_v p(v)\log q(v)$ differs from forward KL by the teacher&rsquo;s entropy $H(p)$, which is fixed during student training. Learning from unfiltered teacher samples estimates a cross-entropy objective. Filtering for correct answers changes the sampled target distribution, and token averaging affects the weighting of variable-length solutions.</p>
<details>
<summary>Why the total KL cannot be negative</summary>
<p>For positive probabilities, $-\log z\geq1-z$. Substituting $z=q(v)/p(v)$ gives</p>
$$D_{\mathrm{KL}}(p\|q)\geq\sum_v p(v)\left(1-\frac{q(v)}{p(v)}\right)=1-1=0.$$<p>Equality requires matching probabilities. More generally, an outcome with positive $p(v)$ but zero $q(v)$ makes the divergence infinite; terms with zero $p(v)$ contribute zero by continuity.</p>
</details>
<h3 id="turning-disagreement-into-a-training-signal">Turning disagreement into a training signal</h3>
<p>In on-policy distillation, the student samples a response. The frozen teacher reads that response and evaluates each sampled token using the same prompt and prefix. For teacher policy $p_T$, my implementation assigns</p>
$$r_t=\log p_T(y_t\mid x,y_{\lt t})-\log q_\theta(y_t\mid x,y_{\lt t}).$$<p>A positive reward means the teacher assigns the chosen token more probability than the student does. A negative reward means it assigns less. In the local policy-gradient update, the reward is held fixed while differentiating the student&rsquo;s log-probability: positive rewards encourage the sampled choice, and negative rewards discourage it. Shared model parameters mean an update also affects other predictions.</p>
<p>At a fixed prefix, averaging over student-sampled tokens gives</p>
$$\mathbb E_{y_t\sim q_\theta}[-r_t]=D_{\mathrm{KL}}(q_\theta\|p_T).$$<p>Thus one negative reward is a sampled estimate of reverse KL, not the exact divergence. It can be negative even though its conditional expectation cannot. The experiment logs both this estimate and the full-vocabulary sum, keeping training feedback distinct from its diagnostic measurement.</p>
<p>For example, suppose the student assigns a token probability 0.2 and the teacher assigns it 0.4. Its reward is $\log(0.4/0.2)=\log 2\approx0.693$ nats. If those probabilities are reversed, the reward is approximately $-0.693$. Neither calculation tells us the total KL: that requires weighting the log-ratio for every possible token. This is why the visualization separates a sampled token&rsquo;s reward from the distribution-wide measurement.</p>
<p>The two models must also agree on what the outcomes mean. Here they share a tokenizer vocabulary, so a token ID identifies the same text in each distribution. Comparing probabilities at mismatched token IDs would produce a numerical loss without measuring the intended disagreement. The pipeline checks vocabulary alignment before training.</p>
<figure class="opd-figure" id="figure-3" data-opd="loop" aria-labelledby="opd-title-loop">
  <header class="opd-heading">
    <div class="opd-eyebrow"><span>Figure 03</span><span>Understand · the training procedure</span></div>
    <h3 id="opd-title-loop">The student writes; the teacher evaluates</h3>
    <p>The teacher scores each sampled token in the context the student actually produced. Only the student’s parameters are updated.</p>
  </header>
  
    <div class="opd-loop-body">
  <div class="opd-supervised"><span class="opd-label">Supervised distillation</span><p><strong>Teacher writes a solution</strong> <span aria-hidden="true">→</span> student learns its next tokens using <strong>teacher-written prefixes</strong>.</p></div>
  <div class="opd-loop-toolbar"><span class="opd-label">On-policy distillation</span><div class="opd-presets opd-js-only"><button type="button" data-step="0" aria-pressed="true">1 · Sample</button><button type="button" data-step="1" aria-pressed="false">2 · Score</button><button type="button" data-step="2" aria-pressed="false">3 · Update</button></div></div>
  <div class="opd-stages">
    <section data-stage="0"><span class="opd-stage-num">01 / SAMPLE</span><h4>The student writes</h4><p>Given a prompt, the current student samples a continuation.</p><div class="opd-prefix">The answer is <mark>5</mark></div><p class="opd-equation">q(5 | prefix) = <strong>0.20</strong></p><span class="opd-stage-note">The student supplies the prefix.</span></section>
    <section data-stage="1"><span class="opd-stage-num">02 / SCORE</span><h4>The teacher reads</h4><p>The teacher evaluates that same token after the same prefix.</p><div class="opd-prefix">The answer is <mark>5</mark></div><p class="opd-equation">p(5 | prefix) = <strong data-loop-p>0.40</strong></p><span class="opd-stage-note">Teacher parameters remain frozen.</span></section>
    <section data-stage="2"><span class="opd-stage-num">03 / UPDATE</span><h4>The student learns</h4><p>The log-ratio supplies a local reward for the sampled token.</p><p class="opd-equation" data-loop-formula>log(0.40 / 0.20)</p><strong class="opd-reward" data-loop-reward>+0.693 <small>nats</small></strong><span class="opd-stage-note" data-loop-action>Encourage this sampled choice.</span></section>
  </div>
  <div class="opd-loop-bottom"><p>Sample again from the updated student. Its new responses provide the prefixes for the next update.</p><label class="opd-js-only">Teacher feedback<select data-loop-scenario><option value="0.4">Teacher prefers this token · p = 0.40</option><option value="0.1">Teacher disfavors this token · p = 0.10</option><option value="0.2">Models agree on this token · p = 0.20</option></select></label></div>
  <p class="opd-step-description opd-js-only" data-step-description aria-live="polite">Step 1: the student samples from its own policy, so training visits prefixes it actually produces.</p>
</div>

  
  
  
  
  <figcaption><span class="opd-caption-number">03 /</span> The example probabilities are illustrative. A positive local reward encourages the sampled choice; a negative reward discourages it. The teacher judges likelihood, not guaranteed correctness.
  <div class="opd-footer"><span>Local reverse-KL training implementation</span><nav aria-label="Figure 3 downloads"><a href="/figures/distillation/loop.svg" download>SVG</a><a href="/figures/distillation/loop.png" download>PNG</a></nav></div>
  </figcaption>
</figure>

<p>“On-policy” specifies where the prefixes come from; it does not require a particular KL direction. <a href="https://arxiv.org/abs/2306.13649">Agarwal and colleagues</a> explore on-policy distillation with different discrepancy measures. I used a local reverse-KL recipe similar to the one described by <a href="https://thinkingmachines.ai/blog/on-policy-distillation/">Thinking Machines Lab</a>: each token receives its own reward, without accumulating future rewards. This is a practical approximation to the full sequence-gradient formulation. The distinction becomes important in the failed run below.</p>
<h2 id="constructing-the-experiment">Constructing the experiment</h2>
<p>I used a plain question-and-answer prompt containing four worked examples, identically for teacher and student. That choice followed a failed initial attempt with chat formatting. The base student had not been instruction-tuned for those markers, and its outputs did not reliably terminate in the required format. Training against that baseline could have made learning the response format look like a large improvement in mathematics.</p>
<p>The experiment reserved 500 GSM8K training problems for development, leaving 6,973 training prompts. The standard 1,319-problem test set was evaluated after the development decisions, for the base student, teacher, and three final adapters. All compared results came from one NVIDIA L40S. Answers were extracted using a fixed rule, and responses that exhausted the generation limit counted as wrong.</p>
<p>Mathematics provides a convenient separation between the numerical answer that can be checked automatically and the intermediate explanation that needs closer inspection.</p>
<p>The teacher remained frozen. Student training used rank-32 LoRA adapters on the attention and feed-forward projections. The supervised baseline used 8,000 teacher responses from 2,000 training prompts, with separate runs using all responses or only those whose final answer was correct. Each ran for 400 optimization steps.</p>
<p>On-policy training started from the base student and ran for 300 steps, sampling four responses to each of eight prompts per step. I repeated it with a second training seed. A further run began from the <strong>unfiltered</strong> supervised adapter and added 200 on-policy steps at a lower learning rate. I will call that the warm-start model.</p>
<p>These settings make the recipes interpretable, but they do not isolate one causal variable. Supervision, prefix source, training-token count, and optimization schedule differ between supervised and on-policy training. Training allowed 256 new tokens per response, while final evaluation allowed 512. The sampled metric uses the same temperature and untruncated token distribution as training, with a different response-length limit.</p>
<p>The implementation makes a further distinction between the text a model reads and the positions it learns to predict. Prompts supply context, but only response tokens contribute to the training loss; padding is masked out. The teacher runs without gradients, and the student alone receives parameter updates. Once trained, the student can answer independently of the teacher. Distillation changes its parameters during training rather than introducing a teacher call into each deployed response.</p>
<h2 id="what-improved-under-sampling">What improved under sampling</h2>
<p>The following numbers all come from the test set. Keeping that split explicit matters: development results include more experimental variants, but cannot be inserted into the same comparison as test measurements.</p>
<table>
  <thead>
      <tr>
          <th>Model</th>
          <th style="text-align: right">Greedy accuracy</th>
          <th style="text-align: right">Mean@4, temperature 1</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Base student</td>
          <td style="text-align: right">41.3%</td>
          <td style="text-align: right">24.8%</td>
      </tr>
      <tr>
          <td>Correctness-filtered supervised distillation</td>
          <td style="text-align: right">54.4%</td>
          <td style="text-align: right">44.0%</td>
      </tr>
      <tr>
          <td>On-policy distillation</td>
          <td style="text-align: right">55.0%</td>
          <td style="text-align: right">51.6%</td>
      </tr>
      <tr>
          <td>Unfiltered supervised warm start, then on-policy</td>
          <td style="text-align: right">56.6%</td>
          <td style="text-align: right">52.1%</td>
      </tr>
      <tr>
          <td>Teacher</td>
          <td style="text-align: right">86.3%</td>
          <td style="text-align: right">80.0%</td>
      </tr>
  </tbody>
</table>
<p>The on-policy student&rsquo;s sampled improvement over filtered supervised distillation was 7.64 percentage points. A paired bootstrap over test problems gives a 95% interval of approximately 6.1–9.1 points. The greedy difference was 0.61 points, with an interval of approximately −2.0 to 2.9 points. These intervals describe uncertainty over problems for the recorded models; they do not include the variation we would see from retraining every recipe repeatedly.</p>
<p>The paired comparison uses the same resampled problem IDs for both models, preserving the fact that some problems are difficult for both. It also keeps each problem&rsquo;s four sampled responses together. Treating those responses as four independent benchmark problems would overstate how much independent evidence the evaluation contains. The interval on the difference is more informative here than judging significance from whether two separate model intervals overlap.</p>
<p>There is a practical reason to retain both decoding measurements. If the application always uses greedy generation, the sampled gain alone does not establish a comparable improvement in that application. If it samples candidate solutions, the probability of an individual successful attempt matters directly. This experiment measures that average success probability at one temperature; it does not establish what happens under other temperatures, answer-selection procedures, or downstream tasks. Reporting both metrics lets the reader connect the finding to a deployment choice without treating either score as a complete description of the model.</p>
<p>The development runs provide a limited check on that second source of variation. On-policy sampled accuracy was 60.55% and 59.70% across the two seeds, compared with 53.25% for the filtered supervised model. Greedy scores were 63.4% and 62.4%, compared with 63.8%. Both on-policy seeds therefore showed the same qualitative pattern. Two seeds are useful evidence, but not a comprehensive estimate of training variability.</p>
<figure class="opd-figure" id="figure-4" data-opd="replication" aria-labelledby="opd-title-replication">
  <header class="opd-heading">
    <div class="opd-eyebrow"><span>Figure 04</span><span>Measured · 500 development problems</span></div>
    <h3 id="opd-title-replication">A second seed shows the same pattern</h3>
    <p>Both on-policy runs improve sampled accuracy over the filtered supervised baseline. Neither improves greedy accuracy in these development measurements.</p>
  </header>
  
    <picture class="opd-chart">
  <source width="460" height="950" media="(max-width: 999px)" srcset="/figures/distillation/replication-mobile.svg" data-light="/figures/distillation/replication-mobile.svg" data-dark="/figures/distillation/replication-mobile-dark.svg">
  <img width="1120" height="525" src="/figures/distillation/replication.svg" data-light="/figures/distillation/replication.svg" data-dark="/figures/distillation/replication-dark.svg" alt="Development accuracy for supervised all responses, supervised correct only, on-policy seed one, and on-policy seed two. Sampled accuracies are 50.15%, 53.25%, 60.55%, and 59.70%. Greedy accuracies are 62.2%, 63.8%, 63.4%, and 62.4%." loading="lazy" decoding="async">
</picture>
<img width="1120" height="525" class="opd-print" src="/figures/distillation/replication.svg" alt="Development accuracy for supervised all responses, supervised correct only, on-policy seed one, and on-policy seed two. Sampled accuracies are 50.15%, 53.25%, 60.55%, and 59.70%. Greedy accuracies are 62.2%, 63.8%, 63.4%, and 62.4%." loading="lazy">

  
  
  
  <details class="opd-details"><summary>Read the numerical results</summary><div class="opd-table-scroll"><table><caption>Accuracy and 95% problem-bootstrap intervals</caption><thead><tr><th scope="col">Model</th><th scope="col">Greedy</th><th scope="col">Mean@4</th></tr></thead><tbody>
<tr><th scope="row">Supervised all responses</th><td>62.20% <small>[57.99, 66.40]</small></td><td>50.15% <small>[46.90, 53.45]</small></td></tr>
<tr><th scope="row">Supervised correct only</th><td>63.80% <small>[59.60, 68.20]</small></td><td>53.25% <small>[49.95, 56.55]</small></td></tr>
<tr><th scope="row">On-policy</th><td>63.40% <small>[59.39, 67.20]</small></td><td>60.55% <small>[57.15, 63.95]</small></td></tr>
<tr><th scope="row">On-policy second seed</th><td>62.40% <small>[58.59, 66.60]</small></td><td>59.70% <small>[56.35, 63.15]</small></td></tr>
</tbody></table></div></details>
  
  
  <figcaption><span class="opd-caption-number">04 /</span> The diamond denotes the second on-policy seed. Lines are 95% problem-bootstrap intervals, not intervals over training seeds. These development measurements are separate from the test results above.
  <div class="opd-footer"><span>Four final development evaluations</span><nav aria-label="Figure 4 downloads"><a href="/figures/distillation/replication.svg" download>SVG</a><a href="/figures/distillation/replication.png" download>PNG</a><a href="/figures/distillation/data.json" download>Data</a></nav></div>
  </figcaption>
</figure>

<p>One expectation had already failed before on-policy training finished. I had thought supervised distillation would contribute relatively little under sampling because it never observes student-generated prefixes. In fact, on development data the unfiltered supervised run increased sampled accuracy from 34.6% to 50.15%, while greedy accuracy rose from 52.4% to 62.2%. Teacher-written solutions improved considerably more than the student&rsquo;s single most likely response path.</p>
<p>On-policy training added a further improvement under sampling. This is consistent with the motivation for training on contexts the student itself encounters. It does not establish that recovery from an early mistake caused the gain: the recipes differ in other ways, and the direct diagnostic for compounding disagreement was less conclusive than that explanation would suggest.</p>
<p>The warm-start model achieved the highest observed test scores, but its sampled lead over on-policy alone was only 0.53 points. The corresponding interval, approximately −0.9 to 2.0 points, includes zero. It also received additional training. I would treat it as a promising recipe to investigate, rather than evidence that the combined procedure is reliably superior at equal cost.</p>
<h2 id="a-correct-answer-with-incorrect-intermediate-units">A correct answer with incorrect intermediate units</h2>
<p>Per-token measurements made it possible to inspect something final-answer accuracy does not capture. One development probe asked how many hours it takes to complete 54,000 rope jumps at three jumps per second. The trained on-policy student produced:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>54,000 / 3 = 18,000 jumps.
</span></span><span style="display:flex;"><span>18,000 / 60 = 300 seconds.
</span></span><span style="display:flex;"><span>300 / 60 = 5 hours.
</span></span><span style="display:flex;"><span>#### 5
</span></span></code></pre></div><p>The final answer is correct, but the first intermediate quantity is seconds, and the second is minutes. The arithmetic reaches the right number while the written units describe it incorrectly.</p>
<p>At the position where the student wrote “jumps,” the full-vocabulary reverse KL was approximately 22.7 nats. At “seconds,” it was 8.7 nats. Those two positions account for most of the response&rsquo;s 42.6 nats of summed divergence. The teacher gave the sampled unit labels very low probability.</p>
<figure class="opd-figure" id="figure-5" data-opd="tokens" aria-labelledby="opd-title-tokens">
  <header class="opd-heading">
    <div class="opd-eyebrow"><span>Figure 05</span><span>Inspect · one recorded student response</span></div>
    <h3 id="opd-title-tokens">Correct arithmetic, incorrect units</h3>
    <p>At three jumps per second, 54,000 jumps take five hours. The final answer passes, while the teacher strongly disagrees at two intermediate unit labels.</p>
  </header>
  
    <div class="opd-tokens-body">
  <div class="opd-token-status"><span>Final answer</span><strong>5 hours · correct</strong><span>Intermediate units</span><strong class="opd-amber">Two incorrect labels</strong></div>
  <div class="opd-token-text" aria-label="Recorded student response, with token-level reverse KL"><span class="opd-token" data-index="0" style="--heat:0.0833" title="Token 0: &#x27;5&#x27;. Reverse KL 0.032 nats. Reward -0.036 nats.">5</span><span class="opd-token" data-index="1" style="--heat:0.0363" title="Token 1: &#x27;4&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">4</span><span class="opd-token" data-index="2" style="--heat:0.1537" title="Token 2: &#x27;,&#x27;. Reverse KL 0.193 nats. Reward -0.200 nats.">,</span><span class="opd-token" data-index="3" style="--heat:0.0374" title="Token 3: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">0</span><span class="opd-token" data-index="4" style="--heat:0.0357" title="Token 4: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">0</span><span class="opd-token" data-index="5" style="--heat:0.0357" title="Token 5: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward -0.000 nats.">0</span><span class="opd-token" data-index="6" style="--heat:0.1728" title="Token 6: &#x27; /&#x27;. Reverse KL 0.261 nats. Reward +0.041 nats."> /</span><span class="opd-token" data-index="7" style="--heat:0.3381" title="Token 7: &#x27; &#x27;. Reverse KL 1.260 nats. Reward +0.384 nats."> </span><span class="opd-token" data-index="8" style="--heat:0.0387" title="Token 8: &#x27;3&#x27;. Reverse KL 0.000 nats. Reward -0.000 nats.">3</span><span class="opd-token" data-index="9" style="--heat:0.0521" title="Token 9: &#x27; =&#x27;. Reverse KL 0.004 nats. Reward +0.000 nats."> =</span><span class="opd-token" data-index="10" style="--heat:0.0366" title="Token 10: &#x27; &#x27;. Reverse KL 0.000 nats. Reward -0.000 nats."> </span><span class="opd-token" data-index="11" style="--heat:0.0362" title="Token 11: &#x27;1&#x27;. Reverse KL 0.000 nats. Reward -0.000 nats.">1</span><span class="opd-token" data-index="12" style="--heat:0.0361" title="Token 12: &#x27;8&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">8</span><span class="opd-token" data-index="13" style="--heat:0.0358" title="Token 13: &#x27;,&#x27;. Reverse KL 0.000 nats. Reward -0.000 nats.">,</span><span class="opd-token" data-index="14" style="--heat:0.0361" title="Token 14: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">0</span><span class="opd-token" data-index="15" style="--heat:0.0350" title="Token 15: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">0</span><span class="opd-token" data-index="16" style="--heat:0.0350" title="Token 16: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">0</span><span class="opd-token" data-index="17" style="--heat:0.5750" title="Token 17: &#x27; jumps&#x27;. Reverse KL 22.725 nats. Reward -23.229 nats."> jumps</span><span class="opd-token" data-index="18" style="--heat:0.5750" title="Token 18: &#x27;.\n&#x27;. Reverse KL 5.298 nats. Reward -6.028 nats.">.
</span><span class="opd-token" data-index="19" style="--heat:0.1558" title="Token 19: &#x27;1&#x27;. Reverse KL 0.200 nats. Reward -0.291 nats.">1</span><span class="opd-token" data-index="20" style="--heat:0.0405" title="Token 20: &#x27;8&#x27;. Reverse KL 0.000 nats. Reward -0.000 nats.">8</span><span class="opd-token" data-index="21" style="--heat:0.0383" title="Token 21: &#x27;,&#x27;. Reverse KL 0.000 nats. Reward -0.000 nats.">,</span><span class="opd-token" data-index="22" style="--heat:0.0355" title="Token 22: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">0</span><span class="opd-token" data-index="23" style="--heat:0.0350" title="Token 23: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">0</span><span class="opd-token" data-index="24" style="--heat:0.0350" title="Token 24: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">0</span><span class="opd-token" data-index="25" style="--heat:0.2902" title="Token 25: &#x27; /&#x27;. Reverse KL 0.893 nats. Reward +0.415 nats."> /</span><span class="opd-token" data-index="26" style="--heat:0.0589" title="Token 26: &#x27; &#x27;. Reverse KL 0.008 nats. Reward -0.009 nats."> </span><span class="opd-token" data-index="27" style="--heat:0.1405" title="Token 27: &#x27;6&#x27;. Reverse KL 0.153 nats. Reward +0.024 nats.">6</span><span class="opd-token" data-index="28" style="--heat:0.0353" title="Token 28: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">0</span><span class="opd-token" data-index="29" style="--heat:0.0711" title="Token 29: &#x27; =&#x27;. Reverse KL 0.018 nats. Reward -0.012 nats."> =</span><span class="opd-token" data-index="30" style="--heat:0.0358" title="Token 30: &#x27; &#x27;. Reverse KL 0.000 nats. Reward -0.000 nats."> </span><span class="opd-token" data-index="31" style="--heat:0.0434" title="Token 31: &#x27;3&#x27;. Reverse KL 0.001 nats. Reward +0.000 nats.">3</span><span class="opd-token" data-index="32" style="--heat:0.0354" title="Token 32: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">0</span><span class="opd-token" data-index="33" style="--heat:0.0357" title="Token 33: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward -0.000 nats.">0</span><span class="opd-token" data-index="34" style="--heat:0.5750" title="Token 34: &#x27; seconds&#x27;. Reverse KL 8.699 nats. Reward -9.203 nats."> seconds</span><span class="opd-token" data-index="35" style="--heat:0.0894" title="Token 35: &#x27;.\n&#x27;. Reverse KL 0.041 nats. Reward +0.025 nats.">.
</span><span class="opd-token" data-index="36" style="--heat:0.0534" title="Token 36: &#x27;3&#x27;. Reverse KL 0.005 nats. Reward +0.001 nats.">3</span><span class="opd-token" data-index="37" style="--heat:0.0354" title="Token 37: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward -0.000 nats.">0</span><span class="opd-token" data-index="38" style="--heat:0.0350" title="Token 38: &#x27;0&#x27;. Reverse KL -0.000 nats. Reward +0.000 nats.">0</span><span class="opd-token" data-index="39" style="--heat:0.1072" title="Token 39: &#x27; /&#x27;. Reverse KL 0.071 nats. Reward +0.040 nats."> /</span><span class="opd-token" data-index="40" style="--heat:0.0364" title="Token 40: &#x27; &#x27;. Reverse KL 0.000 nats. Reward +0.000 nats."> </span><span class="opd-token" data-index="41" style="--heat:0.0366" title="Token 41: &#x27;6&#x27;. Reverse KL 0.000 nats. Reward -0.000 nats.">6</span><span class="opd-token" data-index="42" style="--heat:0.0350" title="Token 42: &#x27;0&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">0</span><span class="opd-token" data-index="43" style="--heat:0.0365" title="Token 43: &#x27; =&#x27;. Reverse KL 0.000 nats. Reward -0.000 nats."> =</span><span class="opd-token" data-index="44" style="--heat:0.0355" title="Token 44: &#x27; &#x27;. Reverse KL 0.000 nats. Reward +0.000 nats."> </span><span class="opd-token" data-index="45" style="--heat:0.0369" title="Token 45: &#x27;5&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">5</span><span class="opd-token" data-index="46" style="--heat:0.3668" title="Token 46: &#x27; hours&#x27;. Reverse KL 1.510 nats. Reward +0.202 nats."> hours</span><span class="opd-token" data-index="47" style="--heat:0.0428" title="Token 47: &#x27;.\n&#x27;. Reverse KL 0.001 nats. Reward -0.001 nats.">.
</span><span class="opd-token" data-index="48" style="--heat:0.0492" title="Token 48: &#x27;####&#x27;. Reverse KL 0.003 nats. Reward -0.003 nats.">####</span><span class="opd-token" data-index="49" style="--heat:0.0353" title="Token 49: &#x27; &#x27;. Reverse KL 0.000 nats. Reward -0.000 nats."> </span><span class="opd-token" data-index="50" style="--heat:0.0364" title="Token 50: &#x27;5&#x27;. Reverse KL 0.000 nats. Reward +0.000 nats.">5</span><span class="opd-token" data-index="51" style="--heat:0.1783" title="Token 51: &#x27;\n\n&#x27;. Reverse KL 0.282 nats. Reward -0.281 nats.">

</span></div>
  <div class="opd-heat-legend"><span>Reverse KL / token</span><span class="opd-heat-scale"></span><span>0 → 4+ nats</span></div>
  <div class="opd-unit-notes"><p><strong>“jumps” · 22.7 nats</strong>54,000 ÷ 3 gives <em>seconds</em>.</p><p><strong>“seconds” · 8.7 nats</strong>18,000 ÷ 60 gives <em>minutes</em>.</p></div>
  <div class="opd-token-inspector opd-js-only"><label>Inspect a recorded position<select data-token-select><option value="17">17 · jumps</option><option value="34">34 · seconds</option><option value="46">46 · hours</option></select></label><div class="opd-token-metrics" aria-live="polite" aria-atomic="true"><div><span>Teacher log p</span><strong data-token-teacher>−23.250</strong></div><div><span>Student log q</span><strong data-token-student>−0.021</strong></div><div><span>Sampled reward</span><strong data-token-reward>−23.229</strong></div><div><span>Full-vocabulary KL</span><strong data-token-kl>22.725</strong></div></div><p class="opd-small" data-token-reading>The teacher assigns “jumps” much less probability than the student. All values are in nats.</p></div>
  <details class="opd-details"><summary>Selection and recorded suffix</summary><p>Chosen as the correct final-probe response of 30–60 tokens with the largest single-position reverse KL. It is one deliberately selected example, not a frequency estimate. The original response also ends with <code>Question:&lt;|endoftext|&gt;</code>, retained by the known stop-string issue. Downloaded data preserves all 55 tokens. Position indices are zero-based.</p></details>
</div>

  
  
  
  
  <figcaption><span class="opd-caption-number">05 /</span> Shading is full-vocabulary reverse KL at each prefix, not the sampled token’s reward. Square-root intensity saturates at 4 nats; labels preserve the larger values. This deliberately selected final-checkpoint example is not a before-and-after comparison.
  <div class="opd-footer"><span>Final on-policy probe · 55 recorded tokens</span><nav aria-label="Figure 5 downloads"><a href="/figures/distillation/tokens.svg" download>SVG</a><a href="/figures/distillation/tokens.png" download>PNG</a><a href="/figures/distillation/data.json" download>Data</a></nav></div>
  </figcaption>
</figure>

<p>This example makes the earlier distinction useful. The reward concerns the token actually sampled. Full-vocabulary KL compares all possible next tokens at that position. A high value locates disagreement between distributions; interpreting it as a semantic error still requires reading the response.</p>
<p>I deliberately selected this example for its large divergence among correct responses of 30–60 tokens. It illustrates what the instrument can reveal, not how common this behavior is. It is also a post-training sample, not a matched before-and-after demonstration that distillation corrected these unit errors.</p>
<h2 id="when-future-rewards-made-training-worse">When future rewards made training worse</h2>
<p>The working recipe credited each token with its own reward. I also tried <strong>reward-to-go</strong>, which credits a token with the sum of its reward and all rewards that follow:</p>
$$R_t=\sum_{s=t}^{T}r_s.$$<p>This has a mathematical motivation. An earlier choice changes the prefixes at which later rewards are obtained, so differentiating the full sequence objective must account for those later consequences. Tests in the repository enumerate a tiny sequence model and verify that the unclipped reward-to-go estimator matches that objective&rsquo;s gradient. The local estimator does not.</p>
<details>
<summary>The sequence objective and its gradient</summary>
<p>For a fixed prompt and a well-defined distribution over finite responses, write</p>
$$J(\theta)=D_{\mathrm{KL}}(q_\theta(y\mid x)\|p_T(y\mid x))=-\mathbb E_{y\sim q_\theta}\left[\sum_t r_t\right].$$<p>Differentiating the sampling distribution introduces a score-function term. The direct derivative of the log-ratio has zero expectation because $\mathbb E_q[\nabla_\theta\log q_\theta(y)]=0$. Rewards before position $t$ also vanish from its expected score-function contribution, leaving</p>
$$\nabla_\theta J=-\mathbb E\left[\sum_t\nabla_\theta\log q_\theta(y_t\mid x,y_{\lt t})R_t\right].$$<p>Replacing $R_t$ with $r_t$ omits credit for future consequences. It instead gives the local token-KL gradient with the prefix distribution held fixed. This identity describes the idealized estimators; clipping, finite sampling, response truncation, and batch token normalization must be considered separately when interpreting an implementation.</p>
</details>
<p>The actual reward-to-go run behaved much worse. At its first step, 96.4% of token advantages reached the implementation&rsquo;s ±10-nat clamp. Mean training response length grew from about 68 tokens to roughly 250, close to the 256-token limit. By step 150, only about 6% of training responses terminated. The final development sampled accuracy was 45.4%, compared with 60.55% for the local estimator.</p>
<figure class="opd-figure" id="figure-6" data-opd="diagnostics" aria-labelledby="opd-title-diagnostics">
  <header class="opd-heading">
    <div class="opd-eyebrow"><span>Figure 06</span><span>Measured · 300 training steps</span></div>
    <h3 id="opd-title-diagnostics">Accumulating rewards changed the training dynamics</h3>
    <p>Reward-to-go saturated the advantage clamp immediately. Longer responses and failing termination followed.</p>
  </header>
  
    <picture class="opd-chart">
  <source width="460" height="1050" media="(max-width: 999px)" srcset="/figures/distillation/diagnostics-mobile.svg" data-light="/figures/distillation/diagnostics-mobile.svg" data-dark="/figures/distillation/diagnostics-mobile-dark.svg">
  <img width="1120" height="800" src="/figures/distillation/diagnostics.svg" data-light="/figures/distillation/diagnostics.svg" data-dark="/figures/distillation/diagnostics-dark.svg" alt="Three unsmoothed training curves compare local rewards in teal with reward-to-go in red. Reward-to-go clamps 96.4% of advantages at step zero, response length approaches 256, and termination reaches 6.25% at step 150. Local rewards remain comparatively stable." loading="lazy" decoding="async">
</picture>
<img width="1120" height="800" class="opd-print" src="/figures/distillation/diagnostics.svg" alt="Three unsmoothed training curves compare local rewards in teal with reward-to-go in red. Reward-to-go clamps 96.4% of advantages at step zero, response length approaches 256, and termination reaches 6.25% at step 150. Local rewards remain comparatively stable." loading="lazy">

  
  
  
  <details class="opd-details"><summary>Read the diagnostic checkpoints</summary><div class="opd-table-scroll"><table><caption>Reward-to-go training, selected steps</caption><thead><tr><th scope="col">Step</th><th scope="col">Clamped</th><th scope="col">Mean length</th><th scope="col">Terminated</th></tr></thead><tbody><tr><th scope="row">0</th><td>96.4%</td><td>68.28</td><td>100.00%</td></tr><tr><th scope="row">150</th><td>83.0%</td><td>252.25</td><td>6.25%</td></tr><tr><th scope="row">299</th><td>80.0%</td><td>250.34</td><td>15.62%</td></tr></tbody></table></div><a href="/figures/distillation/training-diagnostics.csv" download>Download every step (CSV)</a></details>
  <figcaption><span class="opd-caption-number">06 /</span> Every optimizer step is shown without smoothing. These are training samples, capped at 256 new tokens, not the final evaluation. The curves diagnose this configuration; they do not establish that reward-to-go always fails.
  <div class="opd-footer"><span>Recorded per-step training diagnostics</span><nav aria-label="Figure 6 downloads"><a href="/figures/distillation/diagnostics.svg" download>SVG</a><a href="/figures/distillation/diagnostics.png" download>PNG</a><a href="/figures/distillation/data.json" download>Data</a></nav></div>
  </figcaption>
</figure>

<p>The logs suggest why accumulating rewards was problematic here. Expected per-token reward is negative KL. Adding many such rewards can produce large negative returns, especially at earlier positions. Clamping then erases distinctions between returns below −10: very different sequences can receive the same bounded negative signal.</p>
<p>A suitable state-dependent baseline can reduce policy-gradient variance without changing the ideal expected gradient. This run had no learned baseline, and the existing clamp was inherited unchanged from the local recipe. Accumulated negative rewards and saturation are therefore a plausible diagnosis. Demonstrating that a baseline resolves the failure would require another ablation.</p>
<p>The distinction is consequential: the experiment does not show that an unbiased estimator is inherently unusable. It shows a failed training configuration whose clipping substantially changes the estimator, and whose failure becomes understandable only after inspecting more than accuracy or loss.</p>
<h2 id="what-the-measurements-justify">What the measurements justify</h2>
<p>On-policy training changes what work the teacher performs. It scores existing student text in a forward pass instead of generating each response token sequentially. Summed across the main on-policy run, student generation occupied approximately 72% of the measured sampling, scoring, and training time; teacher scoring took about 14%.</p>
<p>That does not establish an end-to-end efficiency advantage over supervised distillation. The supervised recipe processed 4.26 million teacher tokens, including 0.74 million generated tokens. On-policy training processed 4.98 million, all read. The four worked examples added substantial prompt overhead to both. Token counts are not interchangeable with FLOPs or elapsed time, and the supervised data-generation wall clock was not recorded.</p>
<figure class="opd-figure" id="figure-7" data-opd="compute" aria-labelledby="opd-title-compute">
  <header class="opd-heading">
    <div class="opd-eyebrow"><span>Figure 07</span><span>Measured · compute accounting</span></div>
    <h3 id="opd-title-compute">Teacher reading and student generation are different costs</h3>
    <p>The teacher reads more tokens in the on-policy run, while the smaller student spends most of the loop generating responses.</p>
  </header>
  
    <picture class="opd-chart">
  <source width="460" height="820" media="(max-width: 999px)" srcset="/figures/distillation/compute-mobile.svg" data-light="/figures/distillation/compute-mobile.svg" data-dark="/figures/distillation/compute-mobile-dark.svg">
  <img width="1120" height="670" src="/figures/distillation/compute.svg" data-light="/figures/distillation/compute.svg" data-dark="/figures/distillation/compute-dark.svg" alt="Teacher token counts: supervised 4.26 million, on-policy 4.98 million, warm start including supervised 7.59 million. Supervised generation accounts for 0.74 million tokens. Main on-policy component time: 71.8% student generation, 14.2% teacher scoring, 14.0% student training." loading="lazy" decoding="async">
</picture>
<img width="1120" height="670" class="opd-print" src="/figures/distillation/compute.svg" alt="Teacher token counts: supervised 4.26 million, on-policy 4.98 million, warm start including supervised 7.59 million. Supervised generation accounts for 0.74 million tokens. Main on-policy component time: 71.8% student generation, 14.2% teacher scoring, 14.0% student training." loading="lazy">

  
  
  
  
  <figcaption><span class="opd-caption-number">07 /</span> Warm-start token counters include its supervised stage. Time shares sum measured components of the main on-policy run. Teacher tokens are not FLOPs; missing supervised data-generation timing prevents an end-to-end speed comparison.
  <div class="opd-footer"><span>Token counters and summed component timings</span><nav aria-label="Figure 7 downloads"><a href="/figures/distillation/compute.svg" download>SVG</a><a href="/figures/distillation/compute.png" download>PNG</a><a href="/figures/distillation/data.json" download>Data</a></nav></div>
  </figcaption>
</figure>

<p>I also tried to test whether disagreement compounded after the first high-KL position. Comparing divergence before and after that crossing initially looked persuasive, until a permutation control exposed the selection effect: everything before the first crossing is below the threshold by construction. Neither on-policy seed exceeded its shuffled comparison; only the warm-start run did. That statistic does not establish recovery from compounding errors as the explanation for the central result.</p>
<p>Nor did held-out KL rank the trained models by accuracy. The warm-start model&rsquo;s final probe divergence was about 0.602 nats per token, compared with 0.317 for on-policy alone, despite its higher observed accuracy. These measurements use model-dependent sampled prefixes, so they are not comparisons on an identical set of continuations. Even with that qualification, teacher agreement and task correctness remain different quantities.</p>
<p>For this model pair and task, on-policy distillation produced a clear improvement in sampled accuracy over a strong supervised baseline. Greedy evaluation alone would have understated that difference. The next experiments I would prioritize are matched-budget comparisons and a reward-to-go run with an appropriate baseline. Both follow from what these measurements leave unresolved, while preserving the most useful outcome of building the pipeline: a way to examine how the model learns, beyond whether its final answer happens to be right.</p>
<details>
<summary>Measurement and reproducibility notes</summary>
<p>The article uses raw per-question evaluation files, training logs, and token probes from the experiment conducted on 12–13 September 2026. The generated repository summary mixed development and test rows, so it is not the source for these tables. The article&rsquo;s preparation script checks splits, aligns problem IDs, and recomputes accuracy before producing figure data.</p>
<p>Intervals use 1,000 paired percentile bootstrap resamples over problems, with seed 1234. All four sampled outcomes stay together within each problem. These intervals are conditional on the recorded runs and may differ slightly from the report&rsquo;s intervals because the bootstrap implementation differs.</p>
<p>The 300-question general-capability probe originally used an unsuitable prompt. Corrected standalone measurements supersede those embedded in older evaluation files. The corrected probe found no clear collapse for the healthy recipes; its size does not establish that specialization has no smaller cost.</p>
<p>Historical training runs lack a recorded source commit. They retain dated run directories and configuration hashes; the later provenance fix cannot reconstruct their missing commits. A known stopping issue also retained trailing text when a stop string occurred before an end token. The excerpt above omits the recorded trailing <code>Question:</code> fragment for readability; the figure data preserves it. These results describe the existing runs, without silently changing their generation or scoring procedure.</p>
<p>Teacher and student sometimes gave the same wrong test answer. That demonstrates agreement on errors, but does not establish that distillation caused a particular error to be inherited. Similarly, solving a question the teacher missed does not identify whether that ability was retained or acquired during training.</p>
</details>
]]></content:encoded></item></channel></rss>