<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: namakoo [IDFU]</title>
    <description>The latest articles on Forem by namakoo [IDFU] (@namakoo).</description>
    <link>https://forem.com/namakoo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3898538%2F57875c3c-b3e0-426a-8714-73ccd40cab4f.png</url>
      <title>Forem: namakoo [IDFU]</title>
      <link>https://forem.com/namakoo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/namakoo"/>
    <language>en</language>
    <item>
      <title>From -9.15pp to +0.61pp: An engineering journey through four DPO iteration failures</title>
      <dc:creator>namakoo [IDFU]</dc:creator>
      <pubDate>Fri, 08 May 2026 02:38:44 +0000</pubDate>
      <link>https://forem.com/namakoo/from-915pp-to-061pp-an-engineering-journey-through-four-dpo-iteration-failures-5doi</link>
      <guid>https://forem.com/namakoo/from-915pp-to-061pp-an-engineering-journey-through-four-dpo-iteration-failures-5doi</guid>
      <description>&lt;p&gt;Over 36 hours we ran four DPO training iterations against Qwen2.5-Coder-7B-Instruct, trying to push HumanEval pass@1 above the base model's 87.20%. The first three iterations failed in different ways (-9.15pp, -1.22pp, two NO-GO calls). The fourth recovered to +0.61pp.&lt;/p&gt;

&lt;p&gt;Each failure revealed a different class of bug in our chosen-sample generation pipeline — bugs the existing certification gates were not catching. This post walks through the four iterations and what we ended up building to fix them.&lt;/p&gt;

&lt;p&gt;We're sharing this because the same gate-blindness probably affects most teams running DPO on autopilot-generated data. The bugs we found were not exotic; the gates that missed them were not naive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flces0y4pcuz7m6jr8xvg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flces0y4pcuz7m6jr8xvg.png" alt="HumanEval pass@1 delta across DPO iterations: v3 -9.15pp, v4 -1.22pp, v6 +0.61pp" width="800" height="463"&gt;&lt;/a&gt;&lt;em&gt;Δpp vs base (Qwen2.5-Coder-7B-Instruct, 4-bit, 87.20% pass@1). Each bar represents one full DPO training run.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;We have a pipeline that generates Python code samples paired with synthetic pytest tracebacks, then runs each sample through a quality gate that checks four things in sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Invariant test&lt;/strong&gt; — domain-specific contract (e.g. &lt;code&gt;mcmc_sample(...)&lt;/code&gt; must return a list of length &lt;code&gt;n_samples&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Differential test&lt;/strong&gt; — numerical comparison against a reference implementation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Property test&lt;/strong&gt; — Hypothesis-based property checking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fuzz test&lt;/strong&gt; — fixed adversarial-input catalog&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If all four pass, the row gets &lt;code&gt;certified=1&lt;/code&gt; and becomes eligible as a DPO chosen sample. We pair it with broken implementations from the same domain (rejected samples) and feed the pairs to Unsloth to fine-tune Qwen2.5-Coder-7B-Instruct via LoRA.&lt;/p&gt;

&lt;p&gt;That's the pipeline. The first three iterations broke it in different places.&lt;/p&gt;




&lt;h2&gt;
  
  
  Iter v3 — -9.15pp (mechanical AST transforms as chosen)
&lt;/h2&gt;

&lt;p&gt;Our first iteration built chosen samples by &lt;em&gt;transforming&lt;/em&gt; broken code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NameError&lt;/code&gt; → prepend the missing &lt;code&gt;import&lt;/code&gt; based on a known-import map&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ModuleNotFoundError&lt;/code&gt; → wrap the import in &lt;code&gt;try/except ImportError&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AssertionError&lt;/code&gt; → replace &lt;code&gt;assert X == Y&lt;/code&gt; with &lt;code&gt;pass&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We trained on 2,000 such pairs (96% NameError, ~3% AssertionError, the rest ImportError).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HumanEval: 87.20% → 78.05%. Δ = -9.15pp.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The failure category breakdown showed exactly where it went wrong:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure category&lt;/th&gt;
&lt;th&gt;Base&lt;/th&gt;
&lt;th&gt;iter v3&lt;/th&gt;
&lt;th&gt;Δ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ASSERTION_FAIL&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+10&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OTHER_RUNTIME&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;+4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SYNTAX_ERROR&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;+2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TOTAL FAIL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;21&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;36&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+15&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ASSERTION_FAIL accounted for 67% of the regression alone. The model had learned the wrong lesson from the AssertionError handler: "if an assert is failing, deleting it is a valid fix." This pattern leaked into HumanEval — the model now writes solutions that produce assertions which pass trivially, breaking the test harness.&lt;/p&gt;

&lt;p&gt;The handler was technically correct. &lt;code&gt;assert X == Y → pass&lt;/code&gt; is a valid AST transformation. It was semantically wrong as a teaching signal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5lgd9ywxbms24bpfydb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5lgd9ywxbms24bpfydb.png" alt="iter v6 training curves: loss drops from 0.69 to 0.13 over 45 steps; reward margins climb to ~5; accuracies plateau at ~0.94" width="800" height="396"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The same evaluation harness, the same 164 problems. Different chosen-sample contents teach the model different things.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway 1: chosen quality is not about syntactic correctness. It's about whether the transform teaches the &lt;em&gt;right behavior&lt;/em&gt;.&lt;/strong&gt; A reviewer flagged this risk before we trained — we ran the iteration anyway because a previous experiment on a smaller model had shown the AssertionError category was the largest improvement driver. Different model, different data composition, different result.&lt;/p&gt;


&lt;h2&gt;
  
  
  Iter v4 — -1.22pp (autopilot-certified chosen, 2,439 pairs)
&lt;/h2&gt;

&lt;p&gt;We pivoted to using only chosen samples that had passed our 4-layer certification gate (the four tests above). The filter cleared 2,439 candidates. Domain mix: 93% Monte Carlo, 7% other (FFT, Async, Agentic, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HumanEval: 87.20% → 85.98%. Δ = -1.22pp. MBPP: ±0pp.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A small regression on HumanEval, no regression on MBPP. The auto-pipeline scored this as "no side-effect detected" because MBPP is the side-effect canary. But HumanEval is the main metric, and it had moved the wrong way.&lt;/p&gt;

&lt;p&gt;So we read 5 of the lowest-coverage chosen samples by eye. All 5 had subtle bugs that the certification gate had missed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sample 1&lt;/strong&gt; (Numerical Linear Algebra, coverage 17.5%): &lt;code&gt;import numpy as np&lt;/code&gt; was missing entirely. The code referenced &lt;code&gt;np.eye(...)&lt;/code&gt;, &lt;code&gt;np.argmax(...)&lt;/code&gt;, &lt;code&gt;np.dot(...)&lt;/code&gt; throughout. Two helper functions were called that were never defined (&lt;code&gt;PU_to_U&lt;/code&gt;, &lt;code&gt;PU_to_P&lt;/code&gt;). The code was unrunnable. It had been certified.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Samples 2-5&lt;/strong&gt; (Monte Carlo MCMC, coverages 45-71%): All four had a variation of the same bug. Inside &lt;code&gt;mcmc_sample&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_samples&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accepted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;metropolis_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log_target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# silently drops rejected proposals
&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;samples&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Metropolis-Hastings algorithm requires that every step append the &lt;em&gt;current&lt;/em&gt; state regardless of acceptance. The MCMC trace generated by this code is missing all rejected proposals, breaks the chain's stationary distribution, and produces &lt;code&gt;len(samples) ≠ n_samples&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The 4-layer gate had passed all 5 of these. The semantic check confirmed the code was meaning-coherent with the prompt. The fuzz check confirmed no crashes. Neither was equipped to detect these specific &lt;em&gt;logic&lt;/em&gt; errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway 2: certifying that code "runs without crashing on canonical inputs" doesn't mean it's logically correct.&lt;/strong&gt; Tests with low branch coverage pass code that's wrong on paths they don't exercise. We had measured &lt;code&gt;test_coverage_score&lt;/code&gt;, but our cert filter didn't require a minimum on it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Iter v4.1 — NO-GO ×2 (AST-gate enhancements not enough)
&lt;/h2&gt;

&lt;p&gt;We tried to plug the holes by adding AST-based gates to reject the patterns we'd found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;if accepted: list.append(...)&lt;/code&gt; — reject (the MCMC dropout pattern)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_, _ = step(...)&lt;/code&gt; or &lt;code&gt;_ = step(...)&lt;/code&gt; — reject (return-value discard, found in another sample's burn-in loop)&lt;/li&gt;
&lt;li&gt;Names that aren't defined or imported — reject&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test_coverage_score &amp;gt;= 80&lt;/code&gt; — required&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;adv_score &amp;gt;= 75&lt;/code&gt; — required (existing internal quality metric)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This filtered 2,439 → 251 → 72 across two attempts. We sent 7 samples each time to an outside reviewer.&lt;/p&gt;

&lt;p&gt;The reviewer found two more bug classes that AST gates can't detect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;metropolis_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log_target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;proposed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;proposed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;   &lt;span class="c1"&gt;# accept flag hardcoded as True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;metropolis_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log_target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;proposed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;proposal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;accepted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;acceptance_ratio&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proposed&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;accepted&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
    &lt;span class="c1"&gt;# 1-element tuple — docstring promises (state, accepted)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are syntactically valid Python. Both pass any AST check we care to write. Both break the function's behavioral contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway 3: AST validation has a ceiling.&lt;/strong&gt; A function's contract — "the second tuple element is the accept-status, computed from &lt;code&gt;random.random() &amp;lt; α&lt;/code&gt;" — cannot be enforced by parsing alone. You have to actually execute the code with inputs that exercise the contract.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually fixed it: 8-layer gates (extending the existing 4 with 4 more)
&lt;/h2&gt;

&lt;p&gt;We added four more layers to certification:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Semantic AST checks&lt;/strong&gt; for the bug patterns we'd identified — &lt;code&gt;return X, True/False&lt;/code&gt; as the only return path, &lt;code&gt;_, _ = step(...)&lt;/code&gt; as a return-value discard, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataflow checks&lt;/strong&gt; for state-update patterns that occur outside an &lt;code&gt;if accepted:&lt;/code&gt; block.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge-case execution fuzz&lt;/strong&gt;, with inputs designed to exercise the contract — for MCMC, log-target functions that produce extreme log-density values; for FFT, round-trip equality &lt;code&gt;ifft(fft(x)) ≈ x&lt;/code&gt;. These run as subprocesses against the candidate code and reject anything that returns NaN, Inf, or violates the invariant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage strict&lt;/strong&gt; — &lt;code&gt;test_coverage_score &amp;gt;= 80&lt;/code&gt; as a hard requirement, not a soft scoring.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Layer 7 alone — the edge-case execution fuzz — rejected &lt;strong&gt;74 of 227 candidates that had passed all four original layers&lt;/strong&gt;. 33% of our existing certified pool was hiding numerical-stability bugs. Most were &lt;code&gt;acceptance_ratio = exp(log_target(prop) - log_target(cur))&lt;/code&gt; blowing up to &lt;code&gt;inf&lt;/code&gt; for log-density differences of more than ~700.&lt;/p&gt;

&lt;p&gt;After all 8 layers ran, 116 chosen samples remained out of the original 2,439.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa0wem2z3ejd7tcl2zav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa0wem2z3ejd7tcl2zav.png" alt="Chosen pool size shrinkage through filter stages: 2,439 → 980 → 251 → 116" width="800" height="437"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Most of the loss happens at the last step (Layer 7 fuzz + Layer 8 coverage strict).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Domain mix changed dramatically:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;iter v4&lt;/th&gt;
&lt;th&gt;iter v6 (8-layer strict)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monte Carlo&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;27%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python Async&lt;/td&gt;
&lt;td&gt;1.5%&lt;/td&gt;
&lt;td&gt;32%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agentic&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;23%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other&lt;/td&gt;
&lt;td&gt;5.5%&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkao0b53jf4q8yor1fsxa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkao0b53jf4q8yor1fsxa.png" alt="Domain mix shift: Monte Carlo from 93% (v4) down to 27% (v6 strict), Python_Async from 1.5% up to 32%" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
The Monte Carlo dominance was an artifact of which domain had the buggiest chosen samples in our pool, not which domain had the most chosen samples generated. After we filtered out the bugs, the distribution looked completely different.&lt;/p&gt;


&lt;h2&gt;
  
  
  Iter v6 — +0.61pp (recovery)
&lt;/h2&gt;

&lt;p&gt;We trained on 115 pairs (one chosen sample had no compatible rejected partner in the same domain).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HumanEval: 87.20% → 87.80%. Δ = +0.61pp. MBPP: 84.82% → 84.44%. Δ = -0.39pp (within ±1pp tolerance).&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure category&lt;/th&gt;
&lt;th&gt;Base&lt;/th&gt;
&lt;th&gt;iter v6&lt;/th&gt;
&lt;th&gt;Δ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ASSERTION_FAIL&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAME_ERROR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LOOKUP_ERROR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OTHER_RUNTIME&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TOTAL FAIL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;21&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A small recovery, but worth noting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We trained on &lt;strong&gt;1/20th&lt;/strong&gt; the pair count of v4 (115 vs 2,439). Per-sample signal quality went up by roughly an order of magnitude.&lt;/li&gt;
&lt;li&gt;ASSERTION_FAIL moved -1 instead of v3's +10 or v4's +2. Same prompt distribution, same evaluation harness — different chosen-sample contents.&lt;/li&gt;
&lt;li&gt;No category regressed by more than +1.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2e5d8ic91apgez2qsnq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2e5d8ic91apgez2qsnq.png" alt="iter v6 training curves: loss drops from 0.69 to 0.13 over 45 steps; reward margins climb to ~5; accuracies plateau at ~0.94" width="800" height="399"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Training was 3 epochs over 115 pairs (45 total steps, ~11 minutes on a single RTX 4060).&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Bonus pain: deploying the LoRA to Ollama
&lt;/h2&gt;

&lt;p&gt;We lost three hours on this and it's not in the LLM blog circuit yet, so it deserves a section.&lt;/p&gt;

&lt;p&gt;Standard path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Train with Unsloth (4-bit base + LoRA, 16-bit save).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;save_pretrained_merged(method='merged_16bit')&lt;/code&gt; to produce safetensors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ollama create -q Q4_K_M&lt;/code&gt; from the safetensors directory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ollama run&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 4 returned &lt;code&gt;500 Internal Server Error&lt;/code&gt; every single time. The server log showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Assertion failed: found, file llama-sampling.cpp, line 660
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six attempts to fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Override &lt;code&gt;bos_token&lt;/code&gt; in &lt;code&gt;tokenizer_config.json&lt;/code&gt; — still failed.&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;tokenizer.json&lt;/code&gt; with the official Qwen one from HuggingFace — still failed.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;mergekit&lt;/code&gt; in passthrough mode with &lt;code&gt;tokenizer_source: Qwen/Qwen2.5-Coder-7B-Instruct&lt;/code&gt; — still failed.&lt;/li&gt;
&lt;li&gt;Extend &lt;code&gt;lm_head.weight&lt;/code&gt; and &lt;code&gt;embed_tokens.weight&lt;/code&gt; from vocab 151,665 to 152,064 (matching the official) — still failed.&lt;/li&gt;
&lt;li&gt;Test the &lt;em&gt;previous&lt;/em&gt; TIES-merged model that we'd built months ago — also failed. So this isn't iter-v6-specific; it's a structural incompatibility between Ollama 0.23.1's &lt;code&gt;ollama create&lt;/code&gt; GGUF pipeline and any safetensors that Unsloth (or mergekit, in our case) produces for Qwen2.5.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What worked: skip the merged-model path entirely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Convert just the LoRA adapter to GGUF&lt;/span&gt;
python ~/llama.cpp/convert_lora_to_gguf.py &lt;span class="se"&gt;\&lt;/span&gt;
    outputs/dpo/iter1_v6/adapter_iter1_v6 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--outfile&lt;/span&gt; outputs/iter1_v6_adapter.gguf
&lt;span class="c"&gt;# 161 MB output&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: Modelfile attaches the adapter to the official Ollama-pulled base&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Modelfile_v6 &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
FROM qwen2.5-coder:7b
ADAPTER ./iter1_v6_adapter.gguf
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER num_ctx 8192
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Step 3: Standard create + run&lt;/span&gt;
ollama create idfu-merged:iter1_v6 &lt;span class="nt"&gt;-f&lt;/span&gt; Modelfile_v6
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"..."&lt;/span&gt; | ollama run idfu-merged:iter1_v6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two non-obvious gotchas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;convert_lora_to_gguf.py&lt;/code&gt; script reads &lt;code&gt;adapter_config.json&lt;/code&gt; to find the base model. Unsloth's adapter_config points to &lt;code&gt;unsloth/Qwen2.5-Coder-7B-Instruct-bnb-4bit&lt;/code&gt;, which is bnb-4bit quantized, which the converter can't dequantize. &lt;strong&gt;Manually edit &lt;code&gt;adapter_config.json&lt;/code&gt; to point at &lt;code&gt;Qwen/Qwen2.5-Coder-7B-Instruct&lt;/code&gt; (the non-quantized official base) before converting.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't add a &lt;code&gt;TEMPLATE&lt;/code&gt; line to the Modelfile.&lt;/strong&gt; The official base GGUF has Qwen2.5's chat template baked in. If you override it (we tried &lt;code&gt;TEMPLATE """{{ .Prompt }}"""&lt;/code&gt;), the LoRA's expected &lt;code&gt;&amp;lt;|im_start|&amp;gt;...&amp;lt;|im_end|&amp;gt;&lt;/code&gt; framing breaks and the model emits literal &lt;code&gt;&amp;lt;|im_start|&amp;gt;&lt;/code&gt; tokens in its output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The official Ollama base GGUF works because Ollama's own quantization toolchain doesn't trip the same llama-sampling bug as &lt;code&gt;ollama create&lt;/code&gt; from-safetensors does. The &lt;code&gt;ADAPTER&lt;/code&gt; directive lets you layer your weights on top.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway 4: when the official path works and yours doesn't, sometimes the right answer is to use the official artifact and patch your changes on top.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What we'd do differently from the start
&lt;/h2&gt;

&lt;p&gt;If we were starting from scratch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sample chosen by eye, before training.&lt;/strong&gt; Read 5-10 randomly selected records from the lowest 25% of &lt;code&gt;test_coverage_score&lt;/code&gt;. Don't train until you've personally verified that level of quality is acceptable as a teaching signal. It costs 30 minutes; it could save a 5-hour training run.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't trust "no syntax error" as a quality signal.&lt;/strong&gt; AST validation is a floor, not a ceiling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build your edge-case execution gate before you generate chosen at scale.&lt;/strong&gt; Adversarial inputs, NaN/Inf checks, contract assertions. The 33% Layer-7 catch rate suggests this is where most signal-quality bugs live, regardless of pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Watch the failure category breakdown, not just the pass@1 delta.&lt;/strong&gt; A -1.22pp loss with ASSERTION_FAIL +2 looks like marginal noise. The same -1.22pp with ASSERTION_FAIL +10 (as in iter v3, amplified to -9.15pp) is a structurally broken training signal. The categories tell you why.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plan your deployment path before you train.&lt;/strong&gt; Knowing that &lt;code&gt;ollama create&lt;/code&gt; from arbitrary safetensors had this sampler bug would have saved us three hours.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What we ended up validating
&lt;/h2&gt;

&lt;p&gt;The headline result is small: +0.61pp. But the demonstration underneath it is what matters to us.&lt;/p&gt;

&lt;p&gt;We trained on just &lt;strong&gt;115 chosen pairs&lt;/strong&gt; (one of the 116 had no compatible same-domain rejected partner). On a 7B model. Against an evaluation set the model already passed 143/164 of. And it still moved the needle.&lt;/p&gt;

&lt;p&gt;This says three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is &lt;em&gt;possible&lt;/em&gt; to improve a 7B model's HumanEval pass@1 through DPO on autopilot-generated data — provided the chosen samples are actually clean. The earlier failures were not telling us "DPO doesn't work on 7B"; they were telling us "your chosen samples have bugs the eval is sensitive to."&lt;/li&gt;
&lt;li&gt;A clean chosen pool of ~100 pairs is enough for a measurable improvement at this scale. The training run was 11 minutes on a single RTX 4060. Scale is not the bottleneck right now.&lt;/li&gt;
&lt;li&gt;The bottleneck is &lt;em&gt;generating chosen samples that survive the gate&lt;/em&gt;. We started with 2,439 cert=1 candidates and ended up with 116. The gate is doing its job — but it means our chosen-sample pipeline is producing roughly 95% logically-flawed output that we don't notice until we look hard.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The current &lt;code&gt;chosen_grade=1&lt;/code&gt; pool (the 8-layer-strict cohort) has 116 records. We need ~500 before iter 2 is worth attempting at scale. The autopilot is running with the new gates inline now, so newly-generated samples get the strict evaluation automatically. We've biased the domain selector toward &lt;code&gt;Pytest_Traceback_Driven_Code_Repair&lt;/code&gt; (which currently has 0 chosen-grade samples — the bootstrap-domain frontier).&lt;/p&gt;

&lt;p&gt;The harder problem ahead is &lt;strong&gt;chosen-sample generation throughput, not training&lt;/strong&gt;. Most autopilot output doesn't survive the 8-layer filter. Layer 7 (edge-case execution fuzz) alone rejects roughly a third; Layer 8 (&lt;code&gt;test_coverage_score &amp;gt;= 80&lt;/code&gt;) eats most of the rest. Growing the pool by an order of magnitude requires either pushing the autopilot to produce higher-baseline-quality samples in the first place, or expanding bootstrap coverage in domains where current cert=1 is near zero.&lt;/p&gt;

&lt;p&gt;We'll keep collecting. When we have iter 2 results to compare against — bigger pool, same gate — we'll report back here. If +0.61pp is the floor, it's a useful floor; if it scales linearly with pool size, that's a different story.&lt;/p&gt;




&lt;h2&gt;
  
  
  Diagnostic for anyone running into the same wall
&lt;/h2&gt;

&lt;p&gt;If you're running DPO and your improvements are stuck around the noise floor, the diagnostic is short: read 5 chosen samples from the bottom 25% of your highest-quality-tier pool, by eye, like a code reviewer would. The bugs are usually there. The question is whether your gate caught them.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Comments and counter-experiences welcome. The four-iteration arc here cost us ~36 hours of GPU-and-engineering time; if you've run something similar and seen different patterns, I'd love to hear.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>What 500 curated failure pairs actually fix: a breakdown across 3 seeds</title>
      <dc:creator>namakoo [IDFU]</dc:creator>
      <pubDate>Wed, 29 Apr 2026 11:12:03 +0000</pubDate>
      <link>https://forem.com/namakoo/what-500-curated-failure-pairs-actually-fix-a-breakdown-across-3-seeds-1pg5</link>
      <guid>https://forem.com/namakoo/what-500-curated-failure-pairs-actually-fix-a-breakdown-across-3-seeds-1pg5</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/namakoo/-curating-python-failures-for-dpo-notes-from-the-rejected-side-2ff0"&gt;the previous post&lt;/a&gt;, I described the curation philosophy for IDFU's rejected-side dataset — why I avoid synthetic bug generation, why stub detection matters, why "honest failures" are hard to come by.&lt;/p&gt;

&lt;p&gt;A few people asked the obvious question: does it work?&lt;/p&gt;

&lt;p&gt;This post is the answer. Not as a marketing pitch, but as a breakdown. Aggregate scores hide more than they reveal, and I want to show what's actually changing under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Base model&lt;/strong&gt;: Qwen2.5-Coder-3B-Instruct (trained on 92 programming languages)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Method&lt;/strong&gt;: DPO via TRL with LoRA&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data&lt;/strong&gt;: 500 preference pairs from the IDFU dataset&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eval&lt;/strong&gt;: HumanEval, pass@1, three random seeds (42, 123, 7)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardware&lt;/strong&gt;: RTX 4060, single-GPU, ~3-4 hours of training per seed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A note on the data shape, since I left this implicit in the previous post. Each preference pair has a chosen side and a rejected side, both produced inside IDFU. The rejected side is what Part 1 was about — honest failures, curated rather than synthesized. The chosen side comes from IDFU's internal certified pool — samples that passed all of IDFU's validation gates and are tracked separately from the commercially-released portion. (The gate composition itself is proprietary; this article describes only what the pool produces, not how it's filtered.)&lt;/p&gt;

&lt;p&gt;So both sides of the pair are produced and verified by the same internal pipeline, against the same quality bar. The contrast that DPO trains against isn't "model output vs. ideal answer," it's "honest failure vs. honest success on the same task," with both sides held to the same internal standard. That's the configuration that produced the numbers below.&lt;/p&gt;

&lt;p&gt;The hyperparameters were deliberately conservative. No sweep, no exotic tricks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LoRA: r=16, alpha=32, dropout=0.05
Target modules: q/k/v/o/gate/up/down_proj (Qwen standard)
DPO: beta=0.1
Optimizer: learning_rate=5e-5
Batch: size=1, grad_accum=4 (effective batch 4)
Epochs: 3 (= 375 optimizer steps for 500 pairs)
Quantization: 4-bit NF4 + bf16 compute (bitsandbytes)
gradient_checkpointing=True
max_length=2048, max_prompt_length=512
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three seeds because one seed is a coincidence, two seeds is suggestive, and three seeds with consistent direction starts to look like signal. I'd run more if I could afford the GPU time, but training and data generation share the same card, and curation can't pause for a week.&lt;/p&gt;

&lt;h2&gt;
  
  
  The aggregate
&lt;/h2&gt;

&lt;p&gt;Pass@1 across seeds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seed=42:   84.1%   delta +3.66 pp
seed=123:  84.1%   delta +3.66 pp
seed=7:    83.5%   delta +3.05 pp
mean:      83.94 ± 0.35%   /   +3.46 ± 0.35 pp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two seeds landed on the exact same delta, which I noticed and double-checked because it looked too clean. The HumanEval problem set is small (164 problems), failures move in integer counts, and ties at the same delta happen more often than people expect. Seed=7 was lower but in the same direction.&lt;/p&gt;

&lt;p&gt;Not earth-shattering. But the headline number isn't really the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the improvement actually comes from
&lt;/h2&gt;

&lt;p&gt;I categorized every HumanEval failure by exception type. Five categories cover everything:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhuggingface.co%2Fdatasets%2Fnamakoo%2Fidfu-verified-code%2Fresolve%2Fmain%2Ffailure_breakdown.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhuggingface.co%2Fdatasets%2Fnamakoo%2Fidfu-verified-code%2Fresolve%2Fmain%2Ffailure_breakdown.png" alt="Failure breakdown — base vs DPO across three seeds" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Base&lt;/th&gt;
&lt;th&gt;DPO (mean ± std)&lt;/th&gt;
&lt;th&gt;Δ&lt;/th&gt;
&lt;th&gt;Relative&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ASSERTION_FAIL&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;18.67 ± 0.58&lt;/td&gt;
&lt;td&gt;-4.33&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-19%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAME_ERROR&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;3.67 ± 0.58&lt;/td&gt;
&lt;td&gt;-2.33&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-39%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OTHER_RUNTIME&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2.67 ± 0.58&lt;/td&gt;
&lt;td&gt;+0.67&lt;/td&gt;
&lt;td&gt;+33% (n=2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SYNTAX_ERROR&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0.33 ± 0.58&lt;/td&gt;
&lt;td&gt;+0.33&lt;/td&gt;
&lt;td&gt;(1/3 seeds)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TYPE_ERROR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1.00 ± 0.00&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total failures&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;32&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;26.34&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-5.66&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-18%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few things stand out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NAME_ERROR drops 39%.&lt;/strong&gt; This is the cleanest signal. NAME_ERROR on HumanEval is typically the model using a stdlib name without importing it (e.g., &lt;code&gt;math.ceil&lt;/code&gt; without &lt;code&gt;import math&lt;/code&gt;, or &lt;code&gt;re.match&lt;/code&gt; without &lt;code&gt;import re&lt;/code&gt;), or referencing an undefined helper. One concrete case I traced was HumanEval/115 (&lt;code&gt;max_fill&lt;/code&gt;), where the base model called &lt;code&gt;math.ceil&lt;/code&gt; with no import; after DPO training, the model added the import inline. The IDFU rejected pool has a lot of these missing-import patterns, because they're a common failure mode when a model fakes confidence on partial knowledge. The transfer to HumanEval was the most direct prediction I had going in, and it's the one that landed hardest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ASSERTION_FAIL drops 19%.&lt;/strong&gt; This one I didn't expect, and the mechanism is interesting enough to be worth tracing.&lt;/p&gt;

&lt;p&gt;I went through the 8 newly-passing problems on seed=42 by hand. Six of them were genuine algorithmic improvements — the base model's logic was wrong, the DPO model's logic was right. Standard stuff.&lt;/p&gt;

&lt;p&gt;The other two (HumanEval/116 and /123) had identical algorithmic content between base and DPO outputs. The actual difference was that the base model wrote a self-test at the end of the function ("if this fails, raise"), and that self-test crashed at import time on the test harness, registering as ASSERTION_FAIL. The DPO model produced clean function-only output, no embedded self-tests, so import succeeded and the harness's actual tests ran and passed.&lt;/p&gt;

&lt;p&gt;That's a behavioral artifact, not an algorithmic improvement. It reflects the chosen-side distribution: clean idiomatic Python without embedded test scaffolding. DPO learned to &lt;em&gt;not&lt;/em&gt; tack on self-tests, and two of the eight ASSERTION_FAIL fixes are downstream of that.&lt;/p&gt;

&lt;p&gt;So if I want to be honest about the breakdown: the real algorithmic improvement is roughly +2.4 pp out of the +3.46 pp aggregate. The rest is the model learning to output the kind of thing the chosen pool actually contained. That's still a real and useful effect — nobody wants self-tests injected into their library code — but it's not the same thing as "the model got smarter."&lt;/p&gt;

&lt;p&gt;This is the kind of detail that aggregate scores erase. I think it matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TYPE_ERROR doesn't move at all.&lt;/strong&gt; Three seeds, all returning exactly 1 failure. This is one specific HumanEval problem that the base model fails on and the DPO model also fails on, in the same way. DPO didn't touch it, didn't make it worse, didn't make it better. A clean null result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OTHER_RUNTIME and SYNTAX_ERROR are noise.&lt;/strong&gt; The absolute counts are 0-3 across seeds. A single failure shifting between categories produces large relative percentages on tiny denominators. I'd want a much bigger eval set to say anything about these.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is and isn't
&lt;/h2&gt;

&lt;p&gt;What it isn't: a refutation of large-data RLHF. 500 samples is not going to compete with a 50,000-sample run done properly. The improvement is a few percentage points, not transformative.&lt;/p&gt;

&lt;p&gt;What it is, I think: a signal that &lt;em&gt;what&lt;/em&gt; you put in the preference pairs matters more than &lt;em&gt;how much&lt;/em&gt;. AP2O reports up to ~+3% pass@k improvement on coding benchmarks (per the paper's abstract; see &lt;a href="https://arxiv.org/abs/2510.02393" rel="noopener noreferrer"&gt;arXiv:2510.02393&lt;/a&gt; for the full per-benchmark breakdown). This run got into the same ballpark with 500 pairs. The data efficiency ratio probably doesn't survive scaling — there's almost certainly diminishing returns where the curation work stops paying off and you just need volume. But at the small-data end, where most solo developers actually live, the curation seems to do real work.&lt;/p&gt;

&lt;p&gt;The other thing I take from the breakdown is that the improvements aren't coming from gaming the eval. If 500 pairs of curated data caused the model to start failing in new ways — spike of SYNTAX_ERROR, models refusing to attempt — I'd be worried about distribution collapse. None of that shows up. The two categories that move (NAME_ERROR, ASSERTION_FAIL) move &lt;em&gt;down&lt;/em&gt;. The categories that don't move stay put. That's roughly the shape I'd want.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm doing next
&lt;/h2&gt;

&lt;p&gt;Currently in production: a Tokenization/BPE-focused specialty pack, ETA a few days. The benchmark methodology used here — 3 seeds, full failure breakdown, manual inspection of the deltas — will be re-run on each new specialty as it ships. I'd rather publish slow and verified than fast and aggregate-only.&lt;/p&gt;

&lt;p&gt;If anyone has run similar breakdowns on their own DPO experiments, I'd genuinely like to compare notes. The thing I can't tell from one run is whether the NAME_ERROR / ASSERTION_FAIL split is specific to IDFU's curation choices or a general property of curated DPO with this kind of chosen-pool composition. That's a question with real implications for what to put in the pairs, and I don't have enough data to answer it alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dataset
&lt;/h2&gt;

&lt;p&gt;A 100-row sample is up at &lt;a href="https://huggingface.co/datasets/namakoo/idfu-verified-code" rel="noopener noreferrer"&gt;huggingface.co/datasets/namakoo/idfu-verified-code&lt;/a&gt; if you want to look at what the curated dataset actually looks like. The full set is available there too.&lt;/p&gt;

&lt;p&gt;If you're working on DPO or RLHF training and want to compare notes, find me on Twitter (&lt;a href="https://twitter.com/namakoo123" rel="noopener noreferrer"&gt;@namakoo123&lt;/a&gt;) or reply here.&lt;/p&gt;

&lt;p&gt;Curation continues. The next snapshot is being collected as you read this.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Curating Python failures for DPO: notes from the rejected side</title>
      <dc:creator>namakoo [IDFU]</dc:creator>
      <pubDate>Sun, 26 Apr 2026 10:00:00 +0000</pubDate>
      <link>https://forem.com/namakoo/-curating-python-failures-for-dpo-notes-from-the-rejected-side-2ff0</link>
      <guid>https://forem.com/namakoo/-curating-python-failures-for-dpo-notes-from-the-rejected-side-2ff0</guid>
      <description>&lt;p&gt;Most of the work in DPO training data is on the rejected side. The chosen side has gold-standard reference implementations everywhere production code, peer-reviewed libraries, official examples. The rejected side is harder. You need code that someone could plausibly write, that fails for a real reason, and that fails in a way the model can actually learn from.&lt;/p&gt;

&lt;p&gt;I tried a few approaches before settling on one that works.&lt;/p&gt;

&lt;p&gt;Hand-curating from production code review is honest, but slow. After about fifty samples I'm tired and my judgments start drifting. Public failure datasets exist but tend to be sparse and narrow --toy bugs in toy domains, or syntactic typos that don't really teach anything. Asking GPT-4 to "write a buggy version of X" is fast and expensive, and the bugs come out so obviously fabricated that they'd train the model on the wrong distribution. The bug-versus-correct boundary in those samples is too clean. Real bugs are messier.&lt;/p&gt;

&lt;p&gt;What I actually wanted was code that looked like it was written by someone trying. Code where someone read the docstring, understood the goal, made a reasonable attempt, and then missed something subtle --broken detail balance in MCMC, a forgotten power-of-2 guard in FFT, an off-by-one in a numerical kernel that shows up only after a few hundred iterations. The kind of failure where the test fails 200 samples in and you stare at the code wondering where the bug is.&lt;/p&gt;

&lt;p&gt;So I started building that kind of dataset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick about-me
&lt;/h2&gt;

&lt;p&gt;Information theory background, formerly in autonomous driving R&amp;amp;D, currently solo. I've been curating a Python failure dataset for DPO/RLHF training for the past few weeks. Static-first design, deterministic checks, very minimal LLM judgment in the curation loop. The point is to make the data, not to chase model size.&lt;/p&gt;

&lt;p&gt;This isn't a model story. It's a dataset story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of failures I'm collecting
&lt;/h2&gt;

&lt;p&gt;There's a quiet trap in synthetic data work. If you ask a language model to "write a buggy version of this function," you get bugs that look fabricated. They're either too easy (typos, missing brackets) or too contrived (failure modes nobody would ever write organically). Either way, the model trained on that data learns the wrong boundary.&lt;/p&gt;

&lt;p&gt;What I actually want is code that fails genuinely. Not because the bug is faked, but because some problems are subtly hard. Getting MCMC detail balance right requires understanding why the proposal-acceptance ratio works --not just memorizing the formula. Getting the FFT power-of-2 guard right requires recognizing that the recursion only works on certain input sizes. The hard parts are where the failures happen honestly.&lt;/p&gt;

&lt;p&gt;So the dataset is curated, not generated. Each row is an honest attempt that failed for a meaningful reason. Trivial failures (syntax errors, missing imports) are filtered out. So are stub non-attempts (more on that below). What's left looks almost human.&lt;/p&gt;

&lt;p&gt;A few weeks in, I caught myself staring at one row for ten minutes, trying to figure out why it was wrong. That was the moment I knew the data was the right shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned curating
&lt;/h2&gt;

&lt;p&gt;Three things surprised me along the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Section ordering in prompts matters more than I expected on a 7B model.&lt;/strong&gt; When generating attempts, I had a function-signature contract sitting in the middle of the prompt -- a clear instruction telling the model "you must define this exact function with these exact keyword arguments." After it came a long block of counter-example code (kept to teach the model what &lt;em&gt;not&lt;/em&gt; to do). The model was reading the contract, then drowning in the counter-examples, then writing the wrong signature. Function name adherence was around 8%.&lt;/p&gt;

&lt;p&gt;I moved the contract to right before the task description, after the counter-examples. Adherence jumped to 75% in the next batch. Lost-in-the-middle is real on 7B. Recency bias too. The thing the model saw last had outsized weight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The dataset attacks itself.&lt;/strong&gt; Once attempts started looking genuinely human, the next problem was that some weren't real attempts at all --they were stubs with comments like &lt;code&gt;# Dummy implementation&lt;/code&gt; and a hardcoded return value. The model, when uncertain, defaulted to plausible-looking nothingness. These passed every refusal filter, every length check, every shape constraint. They looked fine. But they weren't real failures; they were non-attempts.&lt;/p&gt;

&lt;p&gt;I added a stub-detection layer. The first version was pattern matching against comment phrases. The second version (still in progress) is more structural -- short bodies, hardcoded returns, identity functions. It's an arms race. As soon as a pattern is filtered, the model finds another way to fake without saying so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quality plateaus with compute.&lt;/strong&gt; Spending more time per sample doesn't help past a point. The marginal failure isn't more interesting; it's just more samples. The improvement comes from changing what kinds of failures the model is asked to produce --new domains, different invariants, contrastive examples in the prompt. Compute is necessary but not the lever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dataset right now
&lt;/h2&gt;

&lt;p&gt;About two weeks of curation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~10,000 verified failure rows&lt;/li&gt;
&lt;li&gt;19 Python domains&lt;/li&gt;
&lt;li&gt;Each row carries the prompt that produced the attempt&lt;/li&gt;
&lt;li&gt;All quality filters deterministic&lt;/li&gt;
&lt;li&gt;Snapshot every couple of weeks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I monitor quality drift, adjust prompts when failure modes shift, and otherwise let the curation run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The next major question I'm working through is breadth versus depth. The current data is heavy on Monte Carlo and FFT -- the curation loop got good at those domains and kept producing high-quality failures there. The next snapshot will deliberately broaden domain coverage, even if average quality dips slightly. I'd love to hear from people actually training models on this kind of data: would you rather have a deep specialized set or a wider balanced one?&lt;/p&gt;

&lt;p&gt;A couple of questions if you're working on DPO/RLHF training:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Which Python domains do you most need failure data for?&lt;/strong&gt; I can shift the curation focus in a few days, so the answers actually change what gets generated.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Are "subtle failures" useful to you  --code that passes pytest but violates a deeper mathematical invariant?&lt;/strong&gt; There's a pool of those that's currently filtered out. Curious if anyone actually wants that shape of data.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  If you want to take a look
&lt;/h2&gt;

&lt;p&gt;I've put up a 100-row sample on Hugging Face for context:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://huggingface.co/datasets/namakoo/idfu-verified-code" rel="noopener noreferrer"&gt;https://huggingface.co/datasets/namakoo/idfu-verified-code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're hitting any of these problems, or if you have ideas about what failure data would actually be useful in training, reply here or find me on Twitter &lt;a href="https://twitter.com/namakoo123" rel="noopener noreferrer"&gt;@namakoo123&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Curation continues regardless. The next sample is being collected as you read this.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>opensource</category>
      <category>python</category>
    </item>
  </channel>
</rss>
