<?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>DEV Community: Rickesh T N</title>
    <description>The latest articles on DEV Community by Rickesh T N (@rickeshtn).</description>
    <link>https://dev.to/rickeshtn</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4066262%2Fafe79880-1a39-479b-b6f1-0a5d39b9802a.jpeg</url>
      <title>DEV Community: Rickesh T N</title>
      <link>https://dev.to/rickeshtn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rickeshtn"/>
    <language>en</language>
    <item>
      <title>I told the model to separate fields with &lt;TAB&gt;. It did exactly that, and I lost 79 percent of my data.</title>
      <dc:creator>Rickesh T N</dc:creator>
      <pubDate>Sat, 05 Sep 2026 04:42:30 +0000</pubDate>
      <link>https://dev.to/rickeshtn/i-told-the-model-to-separate-fields-with-it-did-exactly-that-and-i-lost-79-percent-of-my-ip2</link>
      <guid>https://dev.to/rickeshtn/i-told-the-model-to-separate-fields-with-it-did-exactly-that-and-i-lost-79-percent-of-my-ip2</guid>
      <description>&lt;p&gt;My extraction prompt said:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output lines of the form &amp;lt;key&amp;gt;&amp;lt;TAB&amp;gt;&amp;lt;value&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Elizabeth&amp;lt;TAB&amp;gt;I have not the pleasure of understanding you
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are five literal characters — angle bracket, T, A, B, angle bracket — not&lt;br&gt;
a tab. My parser looked for &lt;code&gt;\t&lt;/code&gt;, found none, and dropped the line.&lt;/p&gt;

&lt;p&gt;Across one document, &lt;strong&gt;246 of 310 records were discarded&lt;/strong&gt;. Only 5 were dropped&lt;br&gt;
for the reason the mechanism actually existed to catch.&lt;/p&gt;

&lt;p&gt;The model did what I asked. I asked for the wrong thing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why I did not notice for a whole benchmark run
&lt;/h2&gt;

&lt;p&gt;This is the part worth stealing, because the bug is trivial and the reason it&lt;br&gt;
survived is not.&lt;/p&gt;

&lt;p&gt;My harness counted discarded records in a single field called &lt;code&gt;rejected&lt;/code&gt;. That&lt;br&gt;
field had one documented meaning: &lt;em&gt;a record whose evidence could not be verified&lt;br&gt;
against the source text&lt;/em&gt;. In other words, it was the hallucination-catching&lt;br&gt;
counter.&lt;/p&gt;

&lt;p&gt;So the run printed &lt;code&gt;rejected=246&lt;/code&gt; and I read it as &lt;strong&gt;the verifier working&lt;br&gt;
hard&lt;/strong&gt;. A parser failure was wearing the costume of a successful integrity&lt;br&gt;
check. The number went up, and up was the direction I wanted.&lt;/p&gt;

&lt;p&gt;The fix is not a better parser. It is that these two things must never share a&lt;br&gt;
counter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;unparseable&lt;/code&gt; — our format failed, the model is not at fault&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ungrounded&lt;/code&gt; — the model asserted something that is not in the text&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of those is my bug. The other is the model's. Summing them produces a&lt;br&gt;
number that cannot be acted on, and which flatters whichever explanation you&lt;br&gt;
already believe.&lt;/p&gt;

&lt;p&gt;After splitting them, the same run reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;records: lines=310 kept=64 | unparseable=246 ungrounded=5
separator: canonical=0 tab=64 literal_TAB=246
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no way to misread that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix one: show the delimiter, never name it
&lt;/h2&gt;

&lt;p&gt;A model can reproduce a character it can see. It cannot reliably reproduce a&lt;br&gt;
character you have described in prose, because your description &lt;em&gt;is&lt;/em&gt; text and&lt;br&gt;
copying text is what it does.&lt;/p&gt;

&lt;p&gt;So instead of naming the separator, I showed a worked example using a pipe. I&lt;br&gt;
picked &lt;code&gt;|&lt;/code&gt; after checking the corpus contained zero of them in 7,987,517&lt;br&gt;
characters.&lt;/p&gt;

&lt;p&gt;Result: canonical separator compliance went from 0 to 9 out of 9 on the first&lt;br&gt;
fragment, and unparseable loss went from 79 percent to zero.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fix two: your example must be obviously not data
&lt;/h2&gt;

&lt;p&gt;The first version of that fix failed differently, and worse.&lt;/p&gt;

&lt;p&gt;I used a real line from the source novel as the worked example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Elizabeth | I have not the pleasure of understanding you
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model copied the example verbatim and extracted nothing else. One line of&lt;br&gt;
output for a 60,000-character fragment.&lt;/p&gt;

&lt;p&gt;It looked plausible. The name was a real character, the quote was real dialogue.&lt;br&gt;
The only reason I caught it was that the evidence verifier rejected it — that&lt;br&gt;
sentence is not in the fragment I sent, it is from a later chapter.&lt;/p&gt;

&lt;p&gt;The working version uses a name that cannot appear in any novel and says so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Zolvane | we must leave before the tide turns

That line is ONLY a format example. Zolvane is not in this text; never copy
the example into your answer.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;A worked example is an instruction to imitate. If your example is&lt;br&gt;
indistinguishable from valid output, some fraction of the time it will be&lt;br&gt;
returned as output.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix three: make the failure loud, at the right granularity
&lt;/h2&gt;

&lt;p&gt;I added a check that aborts when more than 20 percent of output lines have no&lt;br&gt;
recognised separator, printing a sample of the offending line.&lt;/p&gt;

&lt;p&gt;My first version aborted the whole document. That was worse than the bug: the&lt;br&gt;
model follows the format on most fragments and abandons it on a few, so&lt;br&gt;
discarding the document threw away every good fragment along with the bad one.&lt;br&gt;
Four documents produced zero records.&lt;/p&gt;

&lt;p&gt;Now a violation fails one &lt;em&gt;fragment&lt;/em&gt;. Good fragments survive, the failure is&lt;br&gt;
still visible and still blocks a headline answer, and one document went from&lt;br&gt;
zero records to 148 of 169 kept.&lt;/p&gt;

&lt;h2&gt;
  
  
  The forty-second diagnostic
&lt;/h2&gt;

&lt;p&gt;The single highest-value thing I added was a &lt;code&gt;--probe&lt;/code&gt; flag: run one fragment,&lt;br&gt;
print the raw model reply verbatim, print the loss breakdown, exit.&lt;/p&gt;

&lt;p&gt;It takes about seven seconds. It caught both the literal-&lt;code&gt;&amp;lt;TAB&amp;gt;&lt;/code&gt; bug and the&lt;br&gt;
example-copying bug immediately.&lt;/p&gt;

&lt;p&gt;Before that, my shortest path to noticing a contract problem was a full&lt;br&gt;
benchmark run — about forty minutes — and even then only if I read the right&lt;br&gt;
counter and interpreted it correctly, which the first time around I did not.&lt;/p&gt;

&lt;p&gt;If your pipeline sends prompts to a model in a loop, you want a way to see&lt;br&gt;
exactly one of them, unedited, in under a minute. I wrote three throwaway&lt;br&gt;
scripts to do that by hand before admitting it should be a flag.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>prompting</category>
      <category>debugging</category>
      <category>localllm</category>
    </item>
    <item>
      <title>Temperature 0 is not reproducible. I measured 30 percent of my output changing between identical runs.</title>
      <dc:creator>Rickesh T N</dc:creator>
      <pubDate>Sat, 05 Sep 2026 04:41:53 +0000</pubDate>
      <link>https://dev.to/rickeshtn/temperature-0-is-not-reproducible-i-measured-30-percent-of-my-output-changing-between-identical-97i</link>
      <guid>https://dev.to/rickeshtn/temperature-0-is-not-reproducible-i-measured-30-percent-of-my-output-changing-between-identical-97i</guid>
      <description>&lt;p&gt;I set temperature to 0, ran the same document through the same model twice with&lt;br&gt;
the same prompt, and got different answers. Not subtly different. Roughly a third&lt;br&gt;
of the extracted records changed.&lt;/p&gt;

&lt;p&gt;The cause is not sampling. It is batching.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;same document, same model, same prompt, temperature 0&lt;/th&gt;
&lt;th&gt;records&lt;/th&gt;
&lt;th&gt;byte-identical&lt;/th&gt;
&lt;th&gt;Jaccard&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--concurrency 1&lt;/code&gt;, run twice&lt;/td&gt;
&lt;td&gt;137 / 137&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.0000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--concurrency 4&lt;/code&gt;, run twice&lt;/td&gt;
&lt;td&gt;160 / 164&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.705&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;concurrency 1 vs concurrency 4&lt;/td&gt;
&lt;td&gt;137 / 160&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;0.549&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Serving one request at a time is exactly reproducible. Serving four at a time is&lt;br&gt;
not, and it is not reproducible against itself either.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens
&lt;/h2&gt;

&lt;p&gt;Batched inference does not compute the same arithmetic as unbatched inference.&lt;br&gt;
Different batch shapes select different kernels, matrix multiplications get&lt;br&gt;
tiled differently, and floating-point addition is not associative. The logits&lt;br&gt;
come out fractionally different, and anywhere two tokens were nearly tied,&lt;br&gt;
argmax picks the other one. Temperature 0 removes &lt;em&gt;sampling&lt;/em&gt; randomness. It does&lt;br&gt;
nothing about the arithmetic underneath.&lt;/p&gt;

&lt;p&gt;This is well known in the abstract. What surprised me is the size: 30 percent of&lt;br&gt;
records, not 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I found it, which is the embarrassing part
&lt;/h2&gt;

&lt;p&gt;I was not looking for this. I was testing whether a new filter reduced false&lt;br&gt;
positives, comparing a run with the filter against a run without it.&lt;/p&gt;

&lt;p&gt;One name showed up as a false positive &lt;strong&gt;only in the filtered run&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A filter cannot add records. It can only remove them. So either my filter was&lt;br&gt;
broken, or the two runs had not seen the same model output at all. It was the&lt;br&gt;
second. My A/B test had been comparing two different underlying extractions and&lt;br&gt;
attributing the difference to the flag.&lt;/p&gt;

&lt;p&gt;That experiment was worthless and I nearly published its result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The claim this retires
&lt;/h2&gt;

&lt;p&gt;I had a determinism check in the repo already. It ran the same corpus five times&lt;br&gt;
and confirmed the final answer was identical each time. It passed. I cited it.&lt;/p&gt;

&lt;p&gt;It was measuring almost nothing. The pipeline extracts hundreds of records per&lt;br&gt;
document and then reduces them in code to one answer. A reduction like "which of&lt;br&gt;
these three names appears in the most sections" is extremely tolerant: you can&lt;br&gt;
churn a third of the evidence and still land on the same name, because the&lt;br&gt;
argmax has a margin.&lt;/p&gt;

&lt;p&gt;So the final answer was stable while the thing underneath it was not. The check&lt;br&gt;
confirmed the margin was wide, and I read it as confirming the system was&lt;br&gt;
deterministic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Record-level agreement is the measurement. Answer-level agreement is a&lt;br&gt;
consequence, and a weak one.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it did and did not change
&lt;/h2&gt;

&lt;p&gt;I re-ran the headline benchmark at concurrency 1, where output is byte-identical.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;metric&lt;/th&gt;
&lt;th&gt;batched&lt;/th&gt;
&lt;th&gt;deterministic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;the headline metric&lt;/td&gt;
&lt;td&gt;0.980&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.980&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a positional ordering task&lt;/td&gt;
&lt;td&gt;0.942&lt;/td&gt;
&lt;td&gt;0.904&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;overall&lt;/td&gt;
&lt;td&gt;0.899&lt;/td&gt;
&lt;td&gt;0.870&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The headline number did not move at all. That is not luck: it counts how many&lt;br&gt;
distinct regions of a document a name appears in, and it does not care &lt;em&gt;which&lt;/em&gt;&lt;br&gt;
particular quoted span proves the name was in a region, only that some span&lt;br&gt;
does. Redundant evidence absorbs the churn.&lt;/p&gt;

&lt;p&gt;The positional tasks moved by up to 0.04, because those depend on the single&lt;br&gt;
earliest or latest piece of evidence, and the extremes are exactly where losing&lt;br&gt;
30 percent of records bites.&lt;/p&gt;

&lt;p&gt;So the honest summary is: the noise is real, it reaches some metrics and not&lt;br&gt;
others, and you cannot know which without measuring.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Report concurrency with your numbers.&lt;/strong&gt; It is a experimental condition, not a&lt;br&gt;
performance tuning detail. Two papers can differ on it and disagree for no other&lt;br&gt;
reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure agreement at the record level, not the answer level.&lt;/strong&gt; Run the same&lt;br&gt;
input twice, diff the intermediate output, report Jaccard. If your pipeline has&lt;br&gt;
no intermediate output to diff, that is worth fixing on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run final numbers at concurrency 1.&lt;/strong&gt; It cost me less than I expected: about&lt;br&gt;
20 minutes to about 23 for a nine-document sweep, because the server was not&lt;br&gt;
parallelising as much as the flag implied. Measure the cost before assuming you&lt;br&gt;
cannot afford it. Iterate batched, publish serial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be suspicious of any A/B where the treatment could not have caused the&lt;br&gt;
difference you see.&lt;/strong&gt; That single impossible data point was the only reason I&lt;br&gt;
found this. If the filtered run had merely looked better, I would have shipped&lt;br&gt;
it.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>benchmarks</category>
      <category>localllm</category>
      <category>reproducibility</category>
    </item>
    <item>
      <title>Your agent truncates the corpus and answers anyway. Two harnesses, and a router that picks between them.</title>
      <dc:creator>Rickesh T N</dc:creator>
      <pubDate>Sat, 05 Sep 2026 03:58:52 +0000</pubDate>
      <link>https://dev.to/rickeshtn/your-agent-truncates-the-corpus-and-answers-anyway-two-harnesses-and-a-router-that-picks-between-11le</link>
      <guid>https://dev.to/rickeshtn/your-agent-truncates-the-corpus-and-answers-anyway-two-harnesses-and-a-router-that-picks-between-11le</guid>
      <description>&lt;p&gt;I built a harness that streams an oversized corpus past a small model and aggregates in code. Then I found PrimeIntellect's prime-agent (&lt;code&gt;PrimeIntellect-ai/prime-agent&lt;/code&gt;), installed it, pointed it at the same Ollama server on the same 6GB laptop GPU, and got &lt;code&gt;PA_OK&lt;/code&gt; back.&lt;/p&gt;

&lt;p&gt;They have close to 20,000 stars, a funded team, and a better implementation of the recursive-language-model idea than mine. So this is not a "we beat them" post. It is about the one line where the two architectures genuinely diverge, why that line is a &lt;em&gt;question-class&lt;/em&gt; boundary rather than a quality one, and how to route across it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one line
&lt;/h2&gt;

&lt;p&gt;I grepped their repo for how it handles a corpus bigger than the window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;splitText        0
chunkSize        7      (terminal-image.ts, snapshot-transcript-cache.ts)
truncate       142
summarize       73
contextWindow   73
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus &lt;code&gt;packages/coding-agent/docs/compaction.md&lt;/code&gt;. &lt;strong&gt;Their overflow strategy is compaction: truncate and summarize.&lt;/strong&gt; Mine is exhaustive sweep: segment the whole corpus, extract from every fragment, aggregate in code.&lt;/p&gt;

&lt;p&gt;That is nearly every agent, by the way. Compaction is the default because it is correct for the questions agents are usually asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the boundary is a question class
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;question&lt;/th&gt;
&lt;th&gt;compaction&lt;/th&gt;
&lt;th&gt;exhaustive sweep&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"what does this codebase do?"&lt;/td&gt;
&lt;td&gt;correct, and cheaper&lt;/td&gt;
&lt;td&gt;wasteful&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"where is retry logic implemented?"&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;td&gt;wasteful&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"how many entries have label X?"&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;cannot work&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"which user appears least often?"&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;cannot work&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"is there &lt;em&gt;any&lt;/em&gt; file that does Y?"&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;cannot work&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The failure in the bottom three is not that compaction is imprecise. It is that &lt;strong&gt;you cannot count what you summarized away&lt;/strong&gt;, and nothing downstream can tell that it happened. Coverage looks fine. No error is raised. The agent answers confidently from the fraction it kept.&lt;/p&gt;

&lt;p&gt;That is the actual danger: not a wrong answer, but a &lt;em&gt;confident&lt;/em&gt; wrong answer with no signal attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each side is measurably good and bad at
&lt;/h2&gt;

&lt;p&gt;Everything below is measured on one 6GB RTX 3060 laptop, same Ollama server, same 4B model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;prime-agent (compaction)&lt;/th&gt;
&lt;th&gt;ctxstream (sweep)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;overflow strategy&lt;/td&gt;
&lt;td&gt;truncate + summarize&lt;/td&gt;
&lt;td&gt;segment every byte, reduce in code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;model's job&lt;/td&gt;
&lt;td&gt;plan, act, summarize&lt;/td&gt;
&lt;td&gt;extract from one fragment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aggregation&lt;/td&gt;
&lt;td&gt;in-model&lt;/td&gt;
&lt;td&gt;in code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;agentic loop, tools, sessions&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ecosystem&lt;/td&gt;
&lt;td&gt;~20k stars, extensions, RPC, skills&lt;/td&gt;
&lt;td&gt;one binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;targets constrained hardware&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;num_ctx&lt;/code&gt;, &lt;code&gt;VRAM&lt;/code&gt;, &lt;code&gt;gguf&lt;/code&gt;: 0 hits&lt;/td&gt;
&lt;td&gt;the entire premise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;published benchmark numbers&lt;/td&gt;
&lt;td&gt;none found&lt;/td&gt;
&lt;td&gt;see below, including the losses&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And my own results, which do not uniformly flatter the sweep:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;workload&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;957k-char corpus, literal &lt;code&gt;category=&lt;/code&gt; field&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;correct&lt;/strong&gt;; Claude Opus on the same corpus: 2/3, self-inconsistent on byte-identical input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OOLONG-synth 131k, public benchmark&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0.340&lt;/strong&gt; vs &lt;strong&gt;0.428&lt;/strong&gt; direct — and &lt;strong&gt;0.500 vs 0.513&lt;/strong&gt; once exact-count questions are separated out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;262k tokens on 6GB VRAM&lt;/td&gt;
&lt;td&gt;4.23–4.54 GB peak across four runs; direct ceiling on that card is 32,768 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 repeat runs, temperature 0&lt;/td&gt;
&lt;td&gt;3 answered (all correct), 2 produced no output&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The OOLONG number needs decomposing. Sweeping first scored 0.155, because OOLONG asks &lt;em&gt;"which user has the most instances with the label True"&lt;/em&gt; — a two-dimensional group-by over labels the model must infer, while my extraction contract emitted &lt;code&gt;&amp;lt;key&amp;gt;&amp;lt;TAB&amp;gt;&amp;lt;count&amp;gt;&lt;/code&gt;, one dimension. Of 27 rows, &lt;strong&gt;2&lt;/strong&gt; were the shape it could represent. Three contract fixes later it reads 0.340.&lt;/p&gt;

&lt;p&gt;Split by question class, the remaining gap is entirely one thing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;direct&lt;/th&gt;
&lt;th&gt;sweep&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;rank-order / comparison (n=18)&lt;/td&gt;
&lt;td&gt;0.513&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.500&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;exact count (n=9)&lt;/td&gt;
&lt;td&gt;0.257&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.02&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Parity on ranking, near-total failure on counting. The model abandons enumeration partway through a fragment — per-fragment counts of &lt;code&gt;[64, 69, 174, 64, 76, 162, 188, 59, 54, 172, 186, 172, 28]&lt;/code&gt; where each held ~154 lines. Rankings survive that because the error is roughly proportional; exact counts do not, because the answer &lt;em&gt;is&lt;/em&gt; the number.&lt;/p&gt;

&lt;p&gt;So: a sweep with the wrong extraction contract is worse than not sweeping, and my own corpus flattered me because I had designed the corpus and the contract together without noticing.&lt;/p&gt;

&lt;p&gt;The 2-of-5 empty outputs are worth naming too. The harness refuses to print a headline answer when any fragment fails, because a partial sweep undercounts every key. I believe those two runs were refusals rather than failures — but I did not capture stderr per run, so I cannot prove it, and an unverified explanation is not a result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The synthesis: route, don't choose
&lt;/h2&gt;

&lt;p&gt;These do not compete. One is an agent; the other is an aggregation primitive. The agent should call the primitive when the question needs it.&lt;/p&gt;

&lt;p&gt;Route on a single test: &lt;strong&gt;does the answer depend on data you might have dropped?&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;superlative        most / least / top / fewest
cardinality        how many / count / total
universal          every / all / any / none
group-by           per user, per label, per file
                                    -&amp;gt; exhaustive sweep
anything else                       -&amp;gt; compaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a cheap classifier over the question, not the corpus, and it fails safe: routing a locate-question to a sweep is merely wasteful, while routing a count-question to compaction is silently wrong.&lt;/p&gt;

&lt;p&gt;Concretely, in prime-agent's own extension system, the sweep is a tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aggregate_corpus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Answer counting, most/least, or every/any questions over a file or &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;directory too large for the context window. Sweeps every byte and &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aggregates in code. Do NOT use for 'what does this do' questions — &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;normal reading is cheaper and better.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// shells out to a binary; returns key/count pairs&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent keeps planning, editing, and tool use — which it is good at. The sweep handles the one class where dropping data is fatal. Neither pretends to be the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell anyone building either
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Verify the context arrived.&lt;/strong&gt; My serving layer auto-sized context up to a ceiling then silently fell back: 30,021 tokens passed intact; 50,000 and 70,000 both clipped to exactly 16,387. No error, confident answer from the remainder. Compare reported prompt tokens against what you sent and make the mismatch fatal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A partial sweep must refuse.&lt;/strong&gt; If any fragment fails, every count is low. Exiting non-zero beats printing a plausible number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run on a benchmark you did not build.&lt;/strong&gt; This is the whole post. I had a working system, a real win against a frontier model, and a tidy story. One public benchmark showed the generality was imaginary. It cost an afternoon and was the cheapest thing I did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Report where you lose.&lt;/strong&gt; prime-agent publishes no benchmark numbers. I publish 0.340 against a 0.428 baseline, and a 0.02 on the one question class my approach cannot handle. One of those is more useful to you, and it is not the one that looks better.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>agents</category>
      <category>localllm</category>
      <category>benchmarks</category>
    </item>
    <item>
      <title>A 4B on a 6GB laptop matched frontier-model accuracy on aggregation — except when the answer is a number</title>
      <dc:creator>Rickesh T N</dc:creator>
      <pubDate>Sat, 05 Sep 2026 03:58:16 +0000</pubDate>
      <link>https://dev.to/rickeshtn/a-4b-on-a-6gb-laptop-matched-frontier-model-accuracy-on-aggregation-except-when-the-answer-is-a-2n3m</link>
      <guid>https://dev.to/rickeshtn/a-4b-on-a-6gb-laptop-matched-frontier-model-accuracy-on-aggregation-except-when-the-answer-is-a-2n3m</guid>
      <description>&lt;p&gt;I spent a week getting a 4B model to answer questions over corpora eight times larger than its context window. It went from 0.155 to 0.340 on a public benchmark. Three of the improvements were me fixing my own bugs. The fourth thing I found is a real limit, and it splits cleanly along one line: &lt;strong&gt;the model can rank, but it cannot count.&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;&lt;/th&gt;
&lt;th&gt;direct read&lt;/th&gt;
&lt;th&gt;streaming harness&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;rank-order and comparison questions (n=18)&lt;/td&gt;
&lt;td&gt;0.513&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.500&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;exact-count questions (n=9)&lt;/td&gt;
&lt;td&gt;0.257&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.02&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;overall (n=27)&lt;/td&gt;
&lt;td&gt;0.428&lt;/td&gt;
&lt;td&gt;0.340&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Parity on one class. Near-total failure on the other. The overall number hides both.&lt;/p&gt;

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

&lt;p&gt;OOLONG-synth, a public long-context benchmark with an official scorer. 131,072-token contexts, 384,019 characters each. One RTX 3060 laptop, 6GB. The direct-read arm was already measured at 0.428.&lt;/p&gt;

&lt;p&gt;The harness streams: segment the corpus in code, show the model one fragment at a time, have it emit &lt;code&gt;&amp;lt;key&amp;gt;&amp;lt;TAB&amp;gt;&amp;lt;count&amp;gt;&lt;/code&gt;, aggregate in code. The model never plans a traversal and never does arithmetic across fragments.&lt;/p&gt;

&lt;p&gt;Direct reading is not actually possible here, incidentally. The measured window ceiling on that card is 32,768 tokens — 3.3GB resident, and 65,536 jumps to 10.4GB and spills to CPU. The 0.428 baseline comes from the model reading what fits and answering from that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three bugs, in order
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;0.155.&lt;/strong&gt; One hardcoded extraction contract for every question. OOLONG asks &lt;em&gt;"which user has the most instances with the label True"&lt;/em&gt; — a two-dimensional group-by. My contract emitted &lt;code&gt;&amp;lt;label&amp;gt;&amp;lt;TAB&amp;gt;&amp;lt;count&amp;gt;&lt;/code&gt;, one dimension. Of 27 rows, &lt;strong&gt;2&lt;/strong&gt; were the shape it could represent. The other 25 were structurally unanswerable and every fragment reported success.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;0.182.&lt;/strong&gt; Contract now derived from the question: user-grouped questions ask fragments for &lt;code&gt;&amp;lt;user id&amp;gt;&amp;lt;TAB&amp;gt;&amp;lt;count&amp;gt;&lt;/code&gt;. Marginal gain, because of the next bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;0.340.&lt;/strong&gt; Two fixes. First, I was routing on whether the word "user" appeared anywhere in the question — so &lt;em&gt;"among User 123's entries, which label is most common"&lt;/em&gt; went to user-mode and answered &lt;code&gt;User: 60629&lt;/code&gt; for a gold of &lt;code&gt;formal&lt;/code&gt;. The reliable signal is the stated answer format, &lt;code&gt;"in the form 'Label: answer'"&lt;/code&gt;. Second, no key validation: tallies contained 268 to 411 distinct keys where the answer space is a handful of labels. Every reduction ran on noise.&lt;/p&gt;

&lt;p&gt;None of that is insight. It is three ways of asking the wrong question and one way of not checking the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that is not a bug
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;NUMERIC_ONE_CLASS&lt;/code&gt; — &lt;em&gt;"how many data points should be classified as label True"&lt;/em&gt; — scored &lt;strong&gt;0.02&lt;/strong&gt; across 9 rows. Direct reading gets 0.257 on the same rows. Fixing contracts moved it 0.10 to 0.02, which is to say it never worked and still does not.&lt;/p&gt;

&lt;p&gt;The traces say why. Asked to count rows in a fragment holding ~154 lines, the model returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[64, 69, 174, 64, 76, 162, 188, 59, 54, 172, 186, 172, 28]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not undercounting. It is &lt;strong&gt;bimodal&lt;/strong&gt;: roughly half the fragments land near correct, the rest report about 40 percent. The model abandons enumeration partway and reports what it has.&lt;/p&gt;

&lt;p&gt;Which explains the whole table. A rank-order question survives this, because abandonment is roughly &lt;em&gt;proportional&lt;/em&gt; — if every label is undercounted by a similar factor, the ordering holds and argmin still returns the right label. An exact-count question does not survive it at all, because the answer &lt;em&gt;is&lt;/em&gt; the number.&lt;/p&gt;

&lt;p&gt;I tested the obvious fix. Smaller fragments, 60k chars down to 15k: no accuracy change, 3.6x the calls. It made abandonment more frequent, not less, because there were more fragments to abandon.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you are building one
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Report per question class, not per benchmark.&lt;/strong&gt; A single 0.340 would have told me nothing. The split — 0.500 against 0.513 on one class, 0.02 against 0.257 on another — tells me exactly which component to fix and which claim I am allowed to make.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proportional error is survivable; absolute error is not.&lt;/strong&gt; Design questions to need rankings rather than magnitudes wherever the task allows it. "Which label is rarest" is answerable by a model that miscounts. "How many are there" is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your own corpus will flatter you.&lt;/strong&gt; Before OOLONG, this harness answered a 957,493-character question correctly while Claude Opus, reading the same corpus in one call, was right 2 times out of 3 and contradicted itself on byte-identical input. That was a real result on a corpus where every line had a literal &lt;code&gt;category=&lt;/code&gt; field — one-dimensional counting was sufficient &lt;em&gt;by construction&lt;/em&gt;, because I had designed the corpus and the contract together without noticing. The public benchmark had no such courtesy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repeated runs are not free evidence.&lt;/strong&gt; I ran the same corpus five times at temperature 0 expecting to report perfect reproducibility against the frontier model's 2/3. Result: three runs answered, all correct; two produced no output at all. I believe those two were the harness refusing to answer after a fragment failed, since it exits non-zero rather than print an undercount — but I did not capture stderr per run, so I cannot prove it, and an unverified explanation is not a result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands
&lt;/h2&gt;

&lt;p&gt;The streaming harness now beats the recursive-LM harness I started with (0.269) and matches direct reading on rank-order questions, on hardware where direct reading physically cannot see more than a quarter of the corpus.&lt;/p&gt;

&lt;p&gt;It cannot count. Everything else in the table follows from that one fact.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>benchmarks</category>
      <category>localllm</category>
      <category>agents</category>
    </item>
    <item>
      <title>Our 4B beat Claude Opus on a 440K-token corpus. Then it came last on the public benchmark.</title>
      <dc:creator>Rickesh T N</dc:creator>
      <pubDate>Sat, 05 Sep 2026 03:57:39 +0000</pubDate>
      <link>https://dev.to/rickeshtn/our-4b-beat-claude-opus-on-a-440k-token-corpus-then-it-came-last-on-the-public-benchmark-274e</link>
      <guid>https://dev.to/rickeshtn/our-4b-beat-claude-opus-on-a-440k-token-corpus-then-it-came-last-on-the-public-benchmark-274e</guid>
      <description>&lt;p&gt;Two results from the same system, three weeks apart, and the second one is the useful one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result A.&lt;/strong&gt; A 4B model on a 6GB laptop GPU answered a 440,000-token aggregation question correctly. Claude Opus 4.8, reading the same corpus in one call, got it right 2 times out of 3 and contradicted itself on byte-identical input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result B.&lt;/strong&gt; We ran the same system on OOLONG, a published long-context benchmark, against arms we had already measured. It came last by a wide margin.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;arm&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Haiku 4.5, direct&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;0.428&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Haiku 4.5 + recursive harness&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;0.269&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;our streaming harness&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.155&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Result A is the one you would put on a landing page. Result B is the one that taught us something, and it turned out not to mean what the number suggests.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the system does
&lt;/h2&gt;

&lt;p&gt;The failure we started from: a 4B asked to answer a question over a corpus 8x larger than its context window would sweep every fragment correctly and then, asked which label was least common, reply&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Status: beta, Status: delta, Status: gamma, Status: alpha
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It listed the candidates instead of selecting one. An earlier variant read a third of the corpus and narrated what it saw.&lt;/p&gt;

&lt;p&gt;Neither is a knowledge problem. We were asking one model to plan a traversal, extract from text, &lt;em&gt;and&lt;/em&gt; do arithmetic across 65 partial results, inside a loop. So we split those apart, borrowing the shape from video streaming:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;video&lt;/th&gt;
&lt;th&gt;here&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;manifest&lt;/td&gt;
&lt;td&gt;segment plan computed in code, before any model call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;buffer&lt;/td&gt;
&lt;td&gt;N fragments in flight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;decoder&lt;/td&gt;
&lt;td&gt;model sees ONE fragment, emits &lt;code&gt;key&amp;lt;TAB&amp;gt;number&lt;/code&gt;, never prose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;playback&lt;/td&gt;
&lt;td&gt;reduce — aggregation in code, strategy chosen explicitly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The model only extracts. Planning is deterministic, summing is a loop, selecting a minimum is a comparison. On our own 957,493-character corpus that produced the correct answer in 17 fragments, 611 parsed records, 3 unparseable lines, zero failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then we ran it on someone else's benchmark
&lt;/h2&gt;

&lt;p&gt;Self-graded results on a corpus you built yourself are worth very little. OOLONG-synth is public, published, and ships an official scorer we ported verbatim. We had two arms on it already.&lt;/p&gt;

&lt;p&gt;We scored 0.155. Worst of the three.&lt;/p&gt;

&lt;p&gt;The instinct is to explain that away. Here is what the trace actually said:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MOST_FREQ   keys=406  answer: "Label: Male parent (also used as a term of address to your father)"
                      gold:   72232
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our harness emitted &lt;strong&gt;406 distinct keys&lt;/strong&gt; on a task whose answer is a user ID. Median across rows: 119 keys. It was extracting sentence fragments as if they were labels.&lt;/p&gt;

&lt;p&gt;Then we looked at what the questions actually ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the above data, which user has the most instances with the label True?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a &lt;strong&gt;two-dimensional&lt;/strong&gt; aggregation. Group by user, filter by a label the model has to infer, count. Our extraction contract emits &lt;code&gt;&amp;lt;key&amp;gt;&amp;lt;TAB&amp;gt;&amp;lt;number&amp;gt;&lt;/code&gt; — one dimension. It cannot express "user × label → count" at all.&lt;/p&gt;

&lt;p&gt;Of the 27 rows, 10 wanted a number, 8 a comparison, 7 a user ID, and &lt;strong&gt;2&lt;/strong&gt; were the label-frequency shape our contract was designed for.&lt;/p&gt;

&lt;p&gt;So 0.155 does not measure "streaming is worse than direct reading." It measures &lt;strong&gt;one hardcoded extraction contract applied to six question types, 25 of which it structurally could not represent.&lt;/strong&gt; That is our bug, and the benchmark found it in a way our own corpus never would have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is the more valuable result
&lt;/h2&gt;

&lt;p&gt;The bespoke corpus flattered us because we had, without noticing, designed the corpus and the contract together. Every line had a literal &lt;code&gt;category=&lt;/code&gt; field. One-dimensional counting was sufficient &lt;em&gt;by construction&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A public benchmark had no such courtesy. It contains question shapes we did not anticipate, and it exposed that our "general" harness was a specialised one wearing a general interface.&lt;/p&gt;

&lt;p&gt;The fix is not a better prompt. It is that the extraction contract has to be &lt;strong&gt;derived from the question&lt;/strong&gt; — a group-by question needs a &lt;code&gt;&amp;lt;groupkey&amp;gt;&amp;lt;TAB&amp;gt;&amp;lt;subkey&amp;gt;&amp;lt;TAB&amp;gt;&amp;lt;count&amp;gt;&lt;/code&gt; contract — and a harness that ships one fixed contract will silently score near zero on anything shaped differently. Silently, because every fragment succeeded. Coverage was 1.00 and fragment errors were 0 on every row. Nothing failed. It just answered a question nobody asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other thing that did not work
&lt;/h2&gt;

&lt;p&gt;We also implemented the outer loop from &lt;em&gt;Meta-Harness&lt;/em&gt; (Lee et al., 2026): an agentic proposer that reads prior candidates' source, scores and traces from disk and proposes new harness code.&lt;/p&gt;

&lt;p&gt;Three candidates in:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;candidate&lt;/th&gt;
&lt;th&gt;accuracy&lt;/th&gt;
&lt;th&gt;calls&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;smaller fragments&lt;/td&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;self-reported checksum&lt;/td&gt;
&lt;td&gt;0.25&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Zero improvements. One regression. 3.6x the cost for nothing.&lt;/p&gt;

&lt;p&gt;The traces were still worth having. The counts a fragment reported were bimodal — &lt;code&gt;[64, 69, 174, 64, 76, 162, 188, 59, 54, 172, 186, 172, 28]&lt;/code&gt; where every fragment held ~154 lines. Not uniform undercounting: about half the fragments get abandoned partway and the model reports what it had. That is why smaller fragments did not help, and it is invisible in the score.&lt;/p&gt;

&lt;p&gt;Which is the paper's actual claim, landing in the least flattering way available: rich access to prior experience beats compressed feedback. The score said 0.50 twice. The trace said why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things worth stealing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Verify context reached the model.&lt;/strong&gt; Our serving layer auto-sized context up to a ceiling, then silently fell back: 30,021 tokens passed intact, 50,000 and 70,000 both clipped to exactly 16,387 — no error, confident answer from the fragment it kept. Compare reported prompt tokens against what you sent, and make the mismatch fatal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never score with substring matching.&lt;/strong&gt; One of our runs reported "correct" because the model dumped raw corpus rows and the dump happened to contain the gold label.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run on a benchmark you did not build.&lt;/strong&gt; This is the whole post. The paired OOLONG run data behind these numbers is public: &lt;a href="https://huggingface.co/datasets/Rickesh/rlm-oolong-reproduction" rel="noopener noreferrer"&gt;huggingface.co/datasets/Rickesh/rlm-oolong-reproduction&lt;/a&gt;. We had a working system, a real win, and a plausible story. One public benchmark showed the generality was imaginary. That cost an afternoon and it was the cheapest thing we did.&lt;/p&gt;

&lt;p&gt;The system genuinely does what Result A says on the workload it was built for. What we cannot yet claim is that it generalises — and we would have shipped that claim if we had stopped at our own corpus.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>benchmarks</category>
      <category>localllm</category>
      <category>privateai</category>
    </item>
    <item>
      <title>My inference server decided my second GPU no longer exists. Here is how I got it back without upgrading a driver.</title>
      <dc:creator>Rickesh T N</dc:creator>
      <pubDate>Mon, 24 Aug 2026 23:29:36 +0000</pubDate>
      <link>https://dev.to/rickeshtn/my-inference-server-decided-my-second-gpu-no-longer-exists-here-is-how-i-got-it-back-without-hhp</link>
      <guid>https://dev.to/rickeshtn/my-inference-server-decided-my-second-gpu-no-longer-exists-here-is-how-i-got-it-back-without-hhp</guid>
      <description>&lt;p&gt;My desktop runs two mismatched GPUs: a 20GB Ampere card and a 16GB Pascal Tesla. For months, a 21GB vision-language model ran split across both at full GPU speed. Then a routine upgrade of the inference server dropped Pascal support behind a driver-version gate, demoted the old card to a Vulkan device the scheduler refuses to mix with CUDA, and silently rescheduled my model to one GPU plus CPU spill.&lt;/p&gt;

&lt;p&gt;Nothing errored. The model still answered. It just answered at 20 to 40 seconds per item instead of 8, which turned a 12-hour benchmark into a 60-hour one. The only evidence was a log line saying the driver was "too old" for a card that had been running CUDA workloads that same morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate is policy, not physics
&lt;/h2&gt;

&lt;p&gt;The interesting discovery came from reading the server's own startup logs: the bundled CUDA kernels still listed the Pascal compute capability in their build targets. The kernels existed. The Go-side scheduler was refusing to use them based on a driver-version check, while the C++ inference runtime underneath had no such opinion.&lt;/p&gt;

&lt;p&gt;That asymmetry is the exploit. The upgraded server ships its actual inference engine as a standalone binary, a vendored build of llama-server, with its CUDA backend as a dynamically loaded library sitting in a subdirectory. The driver gate lives entirely in the scheduler process that launches it.&lt;/p&gt;

&lt;p&gt;So: launch the vendored binary directly.&lt;/p&gt;

&lt;p&gt;Two mechanics matter. First, the dynamic backend is not found automatically because it lives in a subdirectory the loader does not scan; it needs an explicit environment variable pointing at the .so file itself, not the directory. Get that wrong and the binary silently falls back to CPU while still accepting requests, which cost me one confusing benchmark restart. Second, the vendored build understands the server's own single-file model format, weights and vision projector packed together, which upstream llama.cpp of the same vintage refuses to load. The vendored binary logged "detected combined format, translating" and just worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tensor splits are a policy decision too
&lt;/h2&gt;

&lt;p&gt;With both GPUs visible again, the default split put layers proportional to total VRAM. That is wrong for mismatched cards: the Ampere card is roughly four times faster per layer, so every layer on the Pascal card costs four on the fast one. Skewing the split heavily toward the fast card, leaving the slow card holding only what does not fit, took per-item latency from 17 seconds to 9. Combined with a bigger prefill batch, the full benchmark ran in 12 hours instead of a projected 60-plus.&lt;/p&gt;

&lt;p&gt;The general shape of the fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The scheduler said no; the runtime said yes. When a managed serving layer refuses your hardware, check whether the engine underneath actually shares the objection.&lt;/li&gt;
&lt;li&gt;Point the backend loader at the exact library file and verify placement with nvidia-smi before trusting any run. A server that fell back to CPU serves identical responses, slower.&lt;/li&gt;
&lt;li&gt;On mismatched GPUs, split by throughput, not by capacity. Free VRAM on a slow card is not free.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The clean fix is a driver upgrade, and it is scheduled. But the bypass took forty minutes including the two failed attempts, needed no root, touched nothing system-wide, and taught me more about the serving stack than a year of it working silently ever did. The scheduler is there to protect the average user from edge cases. If you can read its logs, you are allowed to disagree with it.&lt;/p&gt;

</description>
      <category>gpu</category>
      <category>llamacpp</category>
      <category>ollama</category>
      <category>homelab</category>
    </item>
    <item>
      <title>A generic fine-tuning playbook, written after doing it wrong several times</title>
      <dc:creator>Rickesh T N</dc:creator>
      <pubDate>Mon, 24 Aug 2026 23:29:00 +0000</pubDate>
      <link>https://dev.to/rickeshtn/a-generic-fine-tuning-playbook-written-after-doing-it-wrong-several-times-37a3</link>
      <guid>https://dev.to/rickeshtn/a-generic-fine-tuning-playbook-written-after-doing-it-wrong-several-times-37a3</guid>
      <description>&lt;p&gt;Every fine-tuning guide I read before my first serious attempt was a tutorial about knobs: learning rates, LoRA ranks, quantization settings. None of them covered the part that actually decides whether the project succeeds, which happens before and after the training run, not during it. This is the playbook I now follow for any model on any task. It is deliberately generic: the same sequence has carried me through vision-language models on driving data, small text models for domain QA, and RL-style preference tuning, on hardware ranging from a single consumer GPU to rented cloud boxes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0: try not to fine-tune
&lt;/h2&gt;

&lt;p&gt;Fine-tuning is the most expensive intervention in the stack, so it goes last. The ladder, cheapest first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Better prompting.&lt;/strong&gt; A system prompt with three good few-shot examples routinely closes half the gap that people reach for fine-tuning to close. It costs an afternoon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval.&lt;/strong&gt; If the failure is missing knowledge rather than missing behavior, RAG beats weights. Knowledge changes; your fine-tune will not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A bigger or different base model.&lt;/strong&gt; Run the comparison honestly: a zero-shot larger model against your imagined fine-tuned smaller one. I have watched an untouched open-weights model beat an in-domain fine-tune on the fine-tune's own benchmark. It happens more often than leaderboards suggest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-tune.&lt;/strong&gt; Only when the behavior you need is demonstrably not in the base model and cannot be retrieved or prompted in: output format compliance, domain-specific reasoning patterns, a persona that must survive thousands of turns, latency budgets that force a small model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Write down, in one sentence, which failure of steps 1 to 3 justifies the fine-tune. If you cannot write that sentence, stop. That sentence also becomes your evaluation target later, which is the real reason to write it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: build the evaluation before the dataset
&lt;/h2&gt;

&lt;p&gt;This ordering feels backwards and is the single highest-leverage decision in the playbook.&lt;/p&gt;

&lt;p&gt;Before collecting training data, build a held-out evaluation that measures the sentence from step 0, and run the &lt;strong&gt;base model&lt;/strong&gt; through it. That number is your baseline, and it does three jobs. It tells you the true size of the gap. It occasionally kills the project on the spot because the base model was already good enough and nobody had measured it. And it validates the harness itself: an evaluation that has never scored a known model is untested code that emits numbers.&lt;/p&gt;

&lt;p&gt;Two rules for the eval that I no longer break:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The test split is designed before training, and nothing from training may touch it.&lt;/strong&gt; Not for hyperparameter selection, not for checkpoint picking, not once. Use a validation split for those. If your splits share source documents, scenes, or sessions with the training set, you are measuring memorization and calling it generalization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run a sanity control.&lt;/strong&gt; Delete the input and measure again. If a vision model scores far above chance with the images removed, your benchmark leaks answers through priors and question phrasing, and every score it has produced is inflated. The same control exists for text: shuffle the context, drop the retrieved passages, feed the question alone. Cheap to run, devastating when it fires, and better fired at you than at a reviewer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: data is the model
&lt;/h2&gt;

&lt;p&gt;The dataset decides what you get. The recipe decides only how efficiently you get it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A few thousand excellent examples beat a hundred thousand scraped ones&lt;/strong&gt; for behavior tuning. For format compliance and persona, hundreds can be enough with LoRA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deduplicate against your evaluation.&lt;/strong&gt; Near-duplicates leak. Exact-match dedup is not sufficient; hash at the level the data actually repeats (documents, scenes, sessions).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit a random hundred by hand.&lt;/strong&gt; Not the first hundred, a random hundred. The first hundred were curated by whoever built the file; the random hundred tell you the truth about label noise. Every bad label teaches the model confidently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match the training distribution to the inference distribution.&lt;/strong&gt; If production inputs will be messy, OCR-damaged, or truncated, train on that, not on the clean version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Expect data work to consume more than half the project's wall-clock. When it does not, that is usually a sign it was skipped, and the bill arrives later, denominated in GPU-hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: choose the lightest recipe that can express the change
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LoRA or QLoRA&lt;/strong&gt; is the default. Behavior shaping, format compliance, domain adaptation, personas: adapters handle all of it at a fraction of the memory, and the artifact is small enough to version, ship, stack, and roll back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full fine-tuning&lt;/strong&gt; is for when the change is deep: new modalities, new tokenizers, or when adapters measurably plateau below target. Prove the plateau before paying for the parameters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preference or RL methods&lt;/strong&gt; (DPO, GRPO and relatives) are for objectives that supervised examples cannot express: relative quality, verifiable rewards, multi-step outcomes. They are also where silent failure lives, so they come with an extra rule: track a metric that measures the actual objective, not a proxy. I once watched token accuracy sit at 99 percent while the policy learned the opposite of the intended behavior. The proxy was fine; the behavior was not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One decision that outranks the recipe choice: &lt;strong&gt;change one variable at a time.&lt;/strong&gt; A run that changes base model, dataset, rank, and learning rate simultaneously produces a result that cannot be attributed to anything. Reviewers catch this in papers; production catches it in incidents. If you must move fast, move fast serially.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: engineer the run like it will be interrupted, because it will be
&lt;/h2&gt;

&lt;p&gt;Training runs die. Disks unmount, drivers hiccup, a colleague's job lands on your GPU, spot instances vanish. The runs that survive share the same boring infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checkpoint on a fixed cadence and verify resume actually works&lt;/strong&gt; before the long run, not during the outage. A checkpoint you have never resumed from is a hope, not a checkpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log to an experiment tracker, not a terminal.&lt;/strong&gt; Scalars, config, git commit, environment. The question you will ask in three weeks is "what exactly produced this file", and scrollback does not answer it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emit progress with the failure count first.&lt;/strong&gt; A silent job that is working and a silent job that is failing on every batch look identical from outside. If the error count is climbing, you want to see it in the first minute, not after the run completes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin the environment.&lt;/strong&gt; Container images beat requirements files; requirements files beat memory. Half of my hardest debugging sessions were two libraries disagreeing about something as small as position ids, and the fix was environmental, not algorithmic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: evaluate like an adversary, then decide
&lt;/h2&gt;

&lt;p&gt;When training finishes, resist the demo. Run the same held-out evaluation from step 1, and read three numbers together, never one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Target metric versus base model.&lt;/strong&gt; Did the gap from step 0 actually close, with the improvement larger than your seed-to-seed noise? If you have not measured seed noise, run the fine-tune twice before believing any margin smaller than a point or two. Single-seed margins evaporate embarrassingly often.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regression suite.&lt;/strong&gt; What did the model lose? General capability regressions are the default outcome of narrow fine-tuning, not the exception. A small fixed battery of out-of-domain checks is enough to catch the worst of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Train-versus-held-out gap.&lt;/strong&gt; Large gap: you memorized; get more data or regularize. No gap and no improvement: capacity or recipe; move up the ladder from step 3.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then make the deployment decision with the same honesty as step 0: is the fine-tuned model better than the best non-fine-tuned alternative, at the quality, latency, and cost that production actually needs? Sometimes the answer is no, and the fine-tune becomes a lesson rather than a deployment. That outcome is not a failure of the playbook. Discovering it for the price of one training run instead of one production incident is the playbook working.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-page version
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Prompt, retrieve, or upgrade the base model first; fine-tune last, and write the sentence that justifies it.&lt;/li&gt;
&lt;li&gt;Build the eval and score the base model before touching training data. Design the splits before training. Run the deleted-input control.&lt;/li&gt;
&lt;li&gt;Spend most of the project on data: dedup against eval, audit random samples, match production distribution.&lt;/li&gt;
&lt;li&gt;Default to adapters; escalate only on proven plateaus; one variable per run.&lt;/li&gt;
&lt;li&gt;Checkpoint, track, and emit errors-first progress; pin the environment.&lt;/li&gt;
&lt;li&gt;Judge with three numbers: target delta versus noise, regressions, generalization gap. Then decide like you have not already sunk the cost, because the model does not care that you did.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>llm</category>
      <category>finetuning</category>
      <category>machinelearning</category>
      <category>mlops</category>
    </item>
    <item>
      <title>A 4B model on a 6GB laptop beat Claude Opus on our 440K-token corpus. The fix was giving the model less to do.</title>
      <dc:creator>Rickesh T N</dc:creator>
      <pubDate>Mon, 24 Aug 2026 23:28:23 +0000</pubDate>
      <link>https://dev.to/rickeshtn/a-4b-model-on-a-6gb-laptop-beat-claude-opus-on-our-440k-token-corpus-the-fix-was-giving-the-model-52eo</link>
      <guid>https://dev.to/rickeshtn/a-4b-model-on-a-6gb-laptop-beat-claude-opus-on-our-440k-token-corpus-the-fix-was-giving-the-model-52eo</guid>
      <description>&lt;p&gt;Private AI has a hardware story nobody measures honestly. The pitch is that your data never leaves the building. The unstated cost is that the building contains a 6GB laptop GPU, and the corpus is 440,000 tokens that a frontier model would swallow in one call.&lt;/p&gt;

&lt;p&gt;We measured the whole thing. One RTX 3060 Laptop, 6144 MiB. One 2,625-record driving-QA dataset rendered to 957,493 characters. One question with a single correct answer: which category is least common. Ground truth computed in Python, so neither model gets to define success.&lt;/p&gt;

&lt;p&gt;The 4B won. It took four failed attempts to understand why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The context problem was never the hard part
&lt;/h2&gt;

&lt;p&gt;Direct context on that card, measured with flash attention and a q8_0 KV cache:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;num_ctx&lt;/th&gt;
&lt;th&gt;resident&lt;/th&gt;
&lt;th&gt;fits 5.5GB usable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8,192&lt;/td&gt;
&lt;td&gt;3.2 GB&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16,384&lt;/td&gt;
&lt;td&gt;3.3 GB&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32,768&lt;/td&gt;
&lt;td&gt;3.3 GB&lt;/td&gt;
&lt;td&gt;yes, the ceiling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;65,536&lt;/td&gt;
&lt;td&gt;10.4 GB&lt;/td&gt;
&lt;td&gt;no, 31 percent on GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;131,072&lt;/td&gt;
&lt;td&gt;10.9 GB&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;KV is nearly free to 32k, then the allocator falls off a cliff. Switching the KV cache to q4_0 changed those numbers not at all, so the cliff is not the KV cache and you cannot quantize your way past it. &lt;strong&gt;Direct ceiling: 32,768 tokens.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now stream a 261,226-token corpus through the same card in chunks, keeping the text in CPU RAM and only ever showing the model a fragment. Peak GPU across four runs: &lt;strong&gt;4.23, 4.24, 4.25, 4.54 GB.&lt;/strong&gt; Every one fits.&lt;/p&gt;

&lt;p&gt;That is an 8x context multiple at constant VRAM. Constant is the load-bearing word: the corpus never enters the KV cache, so the limit stops being memory and becomes wall-clock.&lt;/p&gt;

&lt;p&gt;Which is a solved problem. The unsolved one was that the answers were wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four ways to fail the same question
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;attempt&lt;/th&gt;
&lt;th&gt;sub-calls&lt;/th&gt;
&lt;th&gt;answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;prose describing the data, having read about a third&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Spatial&lt;/code&gt; — right arithmetic, truncated label&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Counterfactual&lt;/code&gt; — swept everything, well-formed, wrong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Status: beta, Status: delta, Status: gamma, Status: alpha&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Attempt 4 is the one that gives it away. Asked which label was least common, the model &lt;strong&gt;listed all four candidates instead of selecting one&lt;/strong&gt;. Attempt 3 had read all 425,054 input tokens and still combined the partial counts wrongly.&lt;/p&gt;

&lt;p&gt;We had been calling this a capability gap. It was not. A 4B can count rows in a fragment. What it cannot reliably do is plan a traversal and then perform arithmetic across 65 partial results — and we had been asking it to do both, inside a loop, while also formatting an answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: treat the corpus like a video stream
&lt;/h2&gt;

&lt;p&gt;Nothing about aggregation requires a language model. So we stopped asking one.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;video streaming&lt;/th&gt;
&lt;th&gt;the port&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;manifest / playlist&lt;/td&gt;
&lt;td&gt;segment plan, computed in code before any model call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;buffer&lt;/td&gt;
&lt;td&gt;N segments in flight, latency hidden behind compute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;decoder&lt;/td&gt;
&lt;td&gt;model sees ONE segment, emits &lt;code&gt;key&amp;lt;TAB&amp;gt;number&lt;/code&gt;, never prose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;playback&lt;/td&gt;
&lt;td&gt;reduce phase — aggregation in code, strategy chosen explicitly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The model's entire job becomes extraction from a window it comfortably fits. Planning is deterministic. Summation is a loop. Selecting the minimum is one comparison.&lt;/p&gt;

&lt;p&gt;Result on the same corpus, same 4B, same 6GB card:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;17 segments · failed=0 · records=611 · unparsed_lines=3 · keys=15 · 641s
Category: Spatial Relationship
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Correct. 15 keys recovered, matching the 15 real categories. Of 611+ emitted lines, 3 failed the output contract and were counted as failures rather than silently dropped.&lt;/p&gt;

&lt;h2&gt;
  
  
  The frontier comparison, which is not flattering to the frontier
&lt;/h2&gt;

&lt;p&gt;We used Claude Opus over the full 439,742-token context as the reference. It answered the same question two different ways on &lt;strong&gt;byte-identical input&lt;/strong&gt; — correct once, wrong once — and landed 2 out of 3 across three samples, at $4.79 per call.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;answer&lt;/th&gt;
&lt;th&gt;correct&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Opus 4.8, one call, 439,742 tok&lt;/td&gt;
&lt;td&gt;varies by run&lt;/td&gt;
&lt;td&gt;2/3&lt;/td&gt;
&lt;td&gt;$4.79&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4B + streaming, 6GB laptop&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Spatial Relationship&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A reference that disagrees with itself is not an oracle. Ground truth has to come from code, with the frontier model scored as just another candidate.&lt;/p&gt;

&lt;p&gt;For private AI this cuts two ways. The bar is lower than the marketing implies, because context rot is real and frontier models are not deterministic at 400K tokens. But "matches the frontier model" is also the wrong success criterion. Match the ground truth, and measure both.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we would tell anyone building this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Recursion is not free.&lt;/strong&gt; On a benchmark where the context already fit the model's window, wrapping the same model in a recursive harness scored &lt;strong&gt;0.269 against 0.428&lt;/strong&gt; for a plain direct read. Recursion only pays when the context genuinely does not fit. Reach for it as a last resort, not a default. That comparison is from our OOLONG reproduction, and the paired run data is public: &lt;a href="https://huggingface.co/datasets/Rickesh/rlm-oolong-reproduction" rel="noopener noreferrer"&gt;huggingface.co/datasets/Rickesh/rlm-oolong-reproduction&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Silent truncation is the dangerous failure.&lt;/strong&gt; Our serving layer auto-sized context to the prompt up to a ceiling, then quietly fell back: 30,021 tokens passed intact, 50,000 and 70,000 both clipped to exactly 16,387, with no error and a confident answer from the fragment. Compare processed-token counts against what you sent, and make the mismatch fatal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Substring scoring manufactures success.&lt;/strong&gt; One run reported correct because the model dumped raw corpus rows and the dump happened to contain the gold label. Require the declared answer form and reject anything that looks like regurgitated input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Report what you could not parse.&lt;/strong&gt; &lt;code&gt;unparsed_lines=3&lt;/code&gt; is the number that makes the rest of the output trustworthy. A harness that silently drops what it cannot read will happily report a clean answer over half the data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A partial sweep must refuse to answer.&lt;/strong&gt; If any segment fails, every key is undercounted. Ours exits non-zero rather than printing a number that looks fine.&lt;/p&gt;

&lt;p&gt;The engine is C++17 with zero third-party dependencies, and the planning and reduction stages are covered by 61 tests that need no GPU, no network and no tokens — because once the model is only doing extraction, everything else is ordinary code you can actually test.&lt;/p&gt;

</description>
      <category>privateai</category>
      <category>llm</category>
      <category>inference</category>
      <category>localllm</category>
    </item>
    <item>
      <title>99% token accuracy, zero learning. Field notes from fine-tuning vision models with RL.</title>
      <dc:creator>Rickesh T N</dc:creator>
      <pubDate>Sun, 23 Aug 2026 23:33:17 +0000</pubDate>
      <link>https://dev.to/rickeshtn/99-token-accuracy-zero-learning-field-notes-from-fine-tuning-vision-models-with-rl-306l</link>
      <guid>https://dev.to/rickeshtn/99-token-accuracy-zero-learning-field-notes-from-fine-tuning-vision-models-with-rl-306l</guid>
      <description>&lt;p&gt;Over the past year I have been fine-tuning open vision-language models - 9B dense up to a 35B mixture-of-experts - with supervised fine-tuning and GRPO-style reinforcement learning on verifiable rewards. Most of what I learned was not about algorithms. It was about the ways a training run can look healthy while doing nothing, or crash for reasons that have nothing to do with your code.&lt;/p&gt;

&lt;p&gt;Three failures, in increasing order of how long they fooled me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure 1: the metric that measured the wrong thing (18 hours)
&lt;/h2&gt;

&lt;p&gt;I ran an 18-hour supervised fine-tune that reported token accuracy climbing steadily to 99%. Looked like a textbook run. The real evaluation metric - accuracy on multiple-choice questions - never moved.&lt;/p&gt;

&lt;p&gt;The cause was a mismatch between what I supervised and what I evaluated. The training loss was over free-text reasoning traces; the evaluation scored a single extracted answer letter. The model got extremely good at reproducing the &lt;em&gt;shape&lt;/em&gt; of the training text - hence 99% token accuracy - without that transferring to the decision I actually cared about.&lt;/p&gt;

&lt;p&gt;Token accuracy is a proxy, and proxies drift from the target exactly when you stop checking. The fix was structural, not a hyperparameter: supervise the thing you evaluate. If the deliverable is a constrained answer, the training signal has to reach that answer, not just the prose around it.&lt;/p&gt;

&lt;p&gt;The general rule I took: &lt;strong&gt;any training metric that is not your evaluation metric is a hypothesis about correlation, and you should check that correlation before you spend GPU-days on it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure 2: the crash that was two libraries disagreeing about position ids
&lt;/h2&gt;

&lt;p&gt;The GRPO trainer for the 9B vision model crashed in the forward pass, deep inside rotary position embedding code. Nothing in my training code had changed.&lt;/p&gt;

&lt;p&gt;The diagnosis took a while because the bug lived at the boundary between components: the text sequence length was derived from token-type ids, while the vision sequence length came from the image grid - and image-pad tokens ended up counted twice. Two parts of the same stack, each internally consistent, disagreeing about how long the input was.&lt;/p&gt;

&lt;p&gt;For the 35B MoE variant of the same family, an equivalent rope bug was fixable by monkeypatching the model's position-id computation. I shipped the patch with a &lt;strong&gt;GPU-free regression test&lt;/strong&gt;: a tiny script that constructs the exact failing input shape and runs just the position-id path on CPU. It runs in seconds, needs no cluster, and fails loudly if an upstream update reintroduces the bug.&lt;/p&gt;

&lt;p&gt;Two lessons. First, when you fine-tune at the edge of a model family's tooling support, the bugs you hit are integration bugs, and the stack trace points at the victim, not the culprit. Second, every monkeypatch deserves a regression test that costs nothing to run - otherwise the next library upgrade silently un-fixes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure 3: the RL loop that was learning the opposite (the quiet one)
&lt;/h2&gt;

&lt;p&gt;In a separate project I fine-tune a 9B model with reinforcement learning where the reward comes from realized real-world outcomes rather than a labelled dataset. For a long stretch the training signal was flat - not diverging, not collapsing, just flat, which is the least informative failure there is.&lt;/p&gt;

&lt;p&gt;Two compounding problems. One was label noise in the reward pipeline: some outcomes were being attributed to the wrong decisions, which dilutes any gradient. The other was worse: a sign error meant part of the advantage signal was inverted. The model was being gently pushed &lt;em&gt;away&lt;/em&gt; from behaviour that had worked.&lt;/p&gt;

&lt;p&gt;Nothing crashed. Every batch processed. Every log line looked like a training run. The only symptom was the absence of learning, and the only way I found it was working backwards from "the held-out metric should have moved by now" to auditing every stage of the reward computation by hand.&lt;/p&gt;

&lt;p&gt;After both fixes I got the first genuinely monotonic learning curve on that task. I still treat it as training signal only - the decider is held-out evaluation against the base model, and I do not report improvements that exist only in the training curve.&lt;/p&gt;

&lt;h2&gt;
  
  
  The harness rules I now run everything under
&lt;/h2&gt;

&lt;p&gt;These came out of the failures above plus a benchmarking programme across 70+ vision-language models. They are boring, and they are the difference between numbers and noise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smoke test before committing compute.&lt;/strong&gt; A five-step GRPO run with two numbers watched: the PPO-style clip ratio and the fraction of outputs that parse. If the clip ratio is degenerate or parseability is low, the full run will be garbage in a way five steps already reveals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runs are gated fail-closed.&lt;/strong&gt; Nothing publishes a result unless the evaluation stage actually scored. "The eval crashed but training finished" is not a result; it is an unscored run, and unscored runs must be impossible to mistake for scored ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure failures and poor performance are different columns.&lt;/strong&gt; An unparseable output, an OOM, a crashed kernel - these are exceptions whose count must be exactly zero. A weak model produces zero exceptions and simply scores badly. If a quality threshold can absorb an infrastructure failure, a totally broken run can pass your gate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The held-out metric is the only decider.&lt;/strong&gt; Training curves, token accuracy, reward trends - all of it is telemetry. If the held-out number did not move, nothing happened.&lt;/p&gt;

&lt;p&gt;None of this is novel. All of it is the difference between the runs I trust and the 18 hours I lost.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write about ML evaluation, world models, and the ways measurement quietly fails. More at &lt;a href="https://dev.to/rickeshtn"&gt;dev.to/rickeshtn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>llm</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Your multi-agent system isn't hitting prompt cache. Your system prompt is the reason.</title>
      <dc:creator>Rickesh T N</dc:creator>
      <pubDate>Tue, 11 Aug 2026 21:49:39 +0000</pubDate>
      <link>https://dev.to/rickeshtn/your-multi-agent-system-isnt-hitting-prompt-cache-your-system-prompt-is-the-reason-4gb2</link>
      <guid>https://dev.to/rickeshtn/your-multi-agent-system-isnt-hitting-prompt-cache-your-system-prompt-is-the-reason-4gb2</guid>
      <description>&lt;p&gt;I run a multi-agent setup where ten agents analyse the same input. Same document, same market data, same everything. The only difference between them is persona: each one is instructed to look at the material through a different lens.&lt;/p&gt;

&lt;p&gt;Ten agents, one shared context. That should be the ideal case for prompt caching. Send the expensive context once, pay full price for it once, and let the other nine reads come back at a fraction of the cost.&lt;/p&gt;

&lt;p&gt;My cache hit rate was zero percent on three of the five models I was using, and under seven percent on the other two.&lt;/p&gt;

&lt;p&gt;I had been reading the bill for a while and optimising the wrong thing. Here is what was actually happening, because the mistake is structural and I doubt I am the only one making it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What prompt caching actually matches on
&lt;/h2&gt;

&lt;p&gt;Hosted inference providers cache on a &lt;strong&gt;prefix&lt;/strong&gt;. The provider hashes your request from the first token forward and looks for the longest run it has already computed. If your request starts with the same 3,000 tokens as a recent one, those 3,000 tokens are a cache read, typically around five times cheaper than a fresh read. The moment the token stream diverges, caching stops for the rest of the request. There is no re-syncing later.&lt;/p&gt;

&lt;p&gt;That word — prefix — is doing all the work, and I had not thought about it carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup that broke it
&lt;/h2&gt;

&lt;p&gt;My call looked like every example in every SDK doc:&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="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;agent_persona&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;   &lt;span class="c1"&gt;# differs per agent
&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shared_context&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;# identical for all 10
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The persona is short. A couple of hundred tokens describing how this particular agent should reason. The shared context is large: several thousand tokens of source material.&lt;/p&gt;

&lt;p&gt;Read that message array as a flat token stream, which is what the provider does. The first thing in the stream is the persona. The persona is &lt;strong&gt;different for every agent&lt;/strong&gt;. So the prefix diverges at roughly token one, and the several thousand tokens of identical context sitting behind it can never match anything.&lt;/p&gt;

&lt;p&gt;Ten agents. Ten identical copies of the same context. Ten full-price reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proving it rather than assuming it
&lt;/h2&gt;

&lt;p&gt;I did not want to guess, so I hashed both halves of every call for a single work item and counted the distinct values.&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="n"&gt;SELECT&lt;/span&gt;
  &lt;span class="nc"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;prompt_sha256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;distinct_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nc"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;system_sha256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;distinct_system&lt;/span&gt;
&lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;agent_calls&lt;/span&gt;
&lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;distinct_user   = 1
distinct_system = 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One user prompt. Ten system prompts. The expensive half was &lt;strong&gt;byte-identical across all ten calls&lt;/strong&gt;, and the cheap half in front of it was unique every time.&lt;/p&gt;

&lt;p&gt;This lines up exactly with the provider's own usage report, which broke my spend into cached and uncached input tokens:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;model&lt;/th&gt;
&lt;th&gt;cached share of input&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;0.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;3.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;6.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;11.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those low non-zero numbers are incidental collisions between unrelated calls, not the structural reuse I should have been getting. If the design were right, nine out of every ten context reads would be cache hits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Put the shared, expensive, identical part first. Put the small, varying part last.&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="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shared_context&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;          &lt;span class="c1"&gt;# identical -&amp;gt; caches
&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agent_persona&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;# varies, small, last
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the first several thousand tokens are the same for all ten agents. The first agent pays full price and warms the cache. The other nine read it back at cache rates. The only uncached part is the couple of hundred persona tokens at the tail, which is what you actually want to be paying for.&lt;/p&gt;

&lt;p&gt;The general rule, which I now think should be a design constraint rather than an optimisation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Order your prompt from &lt;strong&gt;most shared&lt;/strong&gt; to &lt;strong&gt;most specific&lt;/strong&gt;. Caching rewards a stable prefix, and every byte that varies early poisons everything after it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This also composes with how you batch. If you run the same model across many items back to back, you keep hitting a warm prefix. If you round-robin across models for each item, you cold-start the cache on every single call. Grouping by model, not by work item, keeps the cache warm.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I am not comfortable with
&lt;/h2&gt;

&lt;p&gt;Moving the persona out of &lt;code&gt;system&lt;/code&gt; and into &lt;code&gt;user&lt;/code&gt; is not free.&lt;/p&gt;

&lt;p&gt;Some models weight system instructions more strongly than user content. That is often the point of a system prompt. If one of my agents is specifically instructed to argue an unpopular position, and I demote that instruction from system to user, it may hedge more. I would be trading spend for behaviour, and I would not necessarily notice, because the output would still be well-formed and plausible.&lt;/p&gt;

&lt;p&gt;So this is not a change I would ship straight to production off the back of a cost argument. It needs an A/B on a sample of items, comparing the actual decisions each layout produces, not just checking that the responses parse.&lt;/p&gt;

&lt;p&gt;There is a middle path worth trying first: keep a short stable instruction in &lt;code&gt;system&lt;/code&gt; that is &lt;strong&gt;identical across all agents&lt;/strong&gt;, and move only the per-agent differentiation into the user message. You get a shared prefix and keep a system-role framing. Whether that is enough depends on how much of your agents' behaviour hangs off the system role, which is an empirical question about your prompts and your models.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would take from this
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prefix means prefix.&lt;/strong&gt; Anything that varies early destroys caching for everything after it, no matter how much identical material follows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instrument it.&lt;/strong&gt; Hash the components of your requests and count distinct values per work item. It took one query to turn a vague suspicion into a definite structural bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the cached-versus-uncached split in your usage report.&lt;/strong&gt; A near-zero cache rate on a workload with obvious shared context is not a pricing quirk. It is a design bug, and it is telling you the prefix is broken.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The default SDK message shape is not cache-aware.&lt;/strong&gt; Persona-in-system, content-in-user is the shape in every tutorial. It is exactly wrong for fan-out workloads where many personas share one context.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I had spent real effort choosing cheaper models before I checked whether I was paying for the same tokens ten times over. The model swap was worth doing. It was also the second-biggest lever, and I found it first because it was the one I was looking for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Addendum: what the comments corrected
&lt;/h2&gt;

&lt;p&gt;This post got better feedback than it deserved, and three points are important enough to belong in the body rather than below it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is a third option I missed, and it is the right one where available.&lt;/strong&gt; &lt;a href="https://dev.to/skillselion"&gt;@skillselion&lt;/a&gt; pointed out that on APIs with explicit cache breakpoints, the system prompt can be an array of content blocks: shared context as the first block carrying a &lt;code&gt;cache_control&lt;/code&gt; marker, persona as a second block after it. The cached prefix ends at the marker, so every agent hits the same cache while the persona keeps its system-role framing. That dissolves the trade-off I spent a section being uncomfortable about. The role-versus-cost tension is an artifact of &lt;em&gt;implicit&lt;/em&gt; prefix caching, not something inherent to the problem. Where the provider gives you breakpoints, use them; my middle path is for providers that only do implicit prefix matching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Put the shared context first" is necessary but not sufficient.&lt;/strong&gt; &lt;a href="https://dev.to/max_quimby"&gt;@max_quimby&lt;/a&gt; and &lt;a href="https://dev.to/kartik-nvjk"&gt;@kartik-nvjk&lt;/a&gt; both made the same point: the shared prefix has to be &lt;em&gt;byte-identical&lt;/em&gt; across calls. A per-run request id, a timestamp, or a reordered JSON key injected anywhere before the boundary resets the prefix, and you are back to zero with a layout that looks correct. Enforce a canonical serialization for the cached block.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prefix starts earlier than your message array.&lt;/strong&gt; &lt;a href="https://dev.to/mads_hansen_27b33ebfee4c9"&gt;@mads_hansen&lt;/a&gt; noted that "identical text" is not the same as an identical provider prefix — tool schemas, response formats, model ids and SDK serialization defaults can all sit ahead of or inside your visible messages. Worth confirming against your provider's documented render order; on Anthropic's API, for instance, it is tools, then system, then messages, which means an unstable tool list invalidates the cache before your system prompt is even reached. Hash the canonical request envelope, not just the parts you wrote by hand.&lt;/p&gt;

&lt;p&gt;Mads also proposed a three-layer layout I have adopted: stable policy shared by every agent, stable source context shared by the work item, then the small role and question suffix — each versioned separately, so a persona change invalidates only the cheap tail while a policy change invalidates the shared prefix deliberately. That turns cache behaviour into an observable property of the prompt architecture instead of a billing surprise.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>architecture</category>
      <category>performance</category>
    </item>
    <item>
      <title>Can a Cheap Model Beat a Frontier Model? Rebuilding Recursive Language Models with Codex</title>
      <dc:creator>Rickesh T N</dc:creator>
      <pubDate>Sun, 09 Aug 2026 15:05:19 +0000</pubDate>
      <link>https://dev.to/rickeshtn/can-a-cheap-model-beat-a-frontier-model-rebuilding-recursive-language-models-with-codex-2m45</link>
      <guid>https://dev.to/rickeshtn/can-a-cheap-model-beat-a-frontier-model-rebuilding-recursive-language-models-with-codex-2m45</guid>
      <description>&lt;p&gt;Large language models have enormous context windows now. That does not mean they use all of that context reliably.&lt;/p&gt;

&lt;p&gt;As prompts grow, models can miss details, lose track of relationships, or produce plausible summaries instead of doing the exhaustive work a question requires. The Recursive Language Models (RLM) paper proposes a different interface: keep the large context outside the model, expose it as a variable in a persistent programming environment, and let the model inspect, partition, and recursively query smaller pieces.&lt;/p&gt;

&lt;p&gt;We rebuilt that method with an unusual constraint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Codex CLI as the model backend;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gpt-5.4-mini&lt;/code&gt; for both the RLM root and every subcall;&lt;/li&gt;
&lt;li&gt;a direct frontier model only as a separate baseline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result was encouraging, expensive, and more nuanced than “cheap model equals frontier model.”&lt;/p&gt;

&lt;h2&gt;
  
  
  What an RLM changes
&lt;/h2&gt;

&lt;p&gt;A normal model call looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;large prompt -&amp;gt; model -&amp;gt; answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An RLM instead gives the root model metadata about the input and a Python REPL containing the real context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;question
   |
root model
   |
persistent REPL holding the context
   |-- inspect and search with code
   |-- split context into useful chunks
   |-- call smaller LMs over those chunks
   |-- validate and aggregate results
   `-- return the final answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail is that the root model does not need to carry every document, record, tool result, and partial answer in its own context window. Large intermediate values can remain in REPL variables. Subcalls receive focused, locally understandable tasks.&lt;/p&gt;

&lt;p&gt;That makes RLM less like a bigger prompt and more like an out-of-core data-processing system whose semantic operator happens to be a language model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we actually tested
&lt;/h2&gt;

&lt;p&gt;We used an OOLONG &lt;code&gt;trec_coarse&lt;/code&gt; validation example from the protocol described in the RLM work.&lt;/p&gt;

&lt;p&gt;The input was a 308,367-character context containing 3,182 general-knowledge questions. Each question implicitly belonged to one of six answer types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;numeric value&lt;/li&gt;
&lt;li&gt;entity&lt;/li&gt;
&lt;li&gt;human being&lt;/li&gt;
&lt;li&gt;location&lt;/li&gt;
&lt;li&gt;abbreviation&lt;/li&gt;
&lt;li&gt;description and abstract concept&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The labels were not present in the context. The task was to infer the labels and identify the least-common category.&lt;/p&gt;

&lt;p&gt;We compared:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A direct &lt;code&gt;gpt-5.6-sol&lt;/code&gt; Codex call.&lt;/li&gt;
&lt;li&gt;An RLM where the root and all leaf calls were locked to &lt;code&gt;gpt-5.4-mini&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The direct frontier call answered &lt;code&gt;abbreviation&lt;/code&gt; and scored zero. The mini-only RLM answered &lt;code&gt;numeric value&lt;/code&gt;, matching the gold answer.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Model calls&lt;/th&gt;
&lt;th&gt;Elapsed time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Direct frontier call&lt;/td&gt;
&lt;td&gt;Incorrect&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;40.1 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RLM with &lt;code&gt;gpt-5.4-mini&lt;/code&gt; only&lt;/td&gt;
&lt;td&gt;Correct&lt;/td&gt;
&lt;td&gt;At least 238&lt;/td&gt;
&lt;td&gt;6,120.3 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The RLM root first inspected the structure of the context. It then classified chunks, retried malformed responses, reduced the chunk size, reclassified all 3,182 questions using structured JSON outputs, checked that it had coverage, and calculated the minimum.&lt;/p&gt;

&lt;p&gt;This is exactly the sort of work that a direct model call often approximates but a recursive program can force itself to perform.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable but useful audit
&lt;/h2&gt;

&lt;p&gt;Getting the final answer right did not mean every intermediate judgment was right.&lt;/p&gt;

&lt;p&gt;We compared the mini model's inferred counts against the validated labels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;True count&lt;/th&gt;
&lt;th&gt;Mini inferred&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Numeric value&lt;/td&gt;
&lt;td&gt;398&lt;/td&gt;
&lt;td&gt;402&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entity&lt;/td&gt;
&lt;td&gt;521&lt;/td&gt;
&lt;td&gt;623&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human being&lt;/td&gt;
&lt;td&gt;544&lt;/td&gt;
&lt;td&gt;488&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Location&lt;/td&gt;
&lt;td&gt;571&lt;/td&gt;
&lt;td&gt;493&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Abbreviation&lt;/td&gt;
&lt;td&gt;571&lt;/td&gt;
&lt;td&gt;560&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Description and abstract concept&lt;/td&gt;
&lt;td&gt;577&lt;/td&gt;
&lt;td&gt;616&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The model made substantial row-level classification errors. It still found the correct minimum because numeric value had a 123-item margin over the next-smallest true category.&lt;/p&gt;

&lt;p&gt;That distinction matters. This run shows that decomposition changed the outcome and allowed a cheap model to solve one problem that the direct frontier call missed. It does not prove that the cheap model reconstructed the data exactly, and one row does not establish general equality between the two systems.&lt;/p&gt;

&lt;p&gt;The honest claim is narrower:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On suitable long-context tasks, a cheap model inside an RLM can match or outperform a direct frontier-model call.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Use cases already demonstrated by RLM research
&lt;/h2&gt;

&lt;p&gt;The paper evaluates four useful task shapes:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Semantic aggregation
&lt;/h3&gt;

&lt;p&gt;OOLONG requires labeling and aggregating information spread throughout a large input. Real applications include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;customer-feedback analysis;&lt;/li&gt;
&lt;li&gt;support-ticket taxonomies;&lt;/li&gt;
&lt;li&gt;survey aggregation;&lt;/li&gt;
&lt;li&gt;incident and application-log analysis;&lt;/li&gt;
&lt;li&gt;quality-control statistics over text records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our experiment belongs to this category.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Multi-document research
&lt;/h3&gt;

&lt;p&gt;BrowseComp-Plus requires joining evidence across documents in a very large offline corpus. Analogous applications include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;literature reviews;&lt;/li&gt;
&lt;li&gt;technical-documentation research;&lt;/li&gt;
&lt;li&gt;contract and policy comparison;&lt;/li&gt;
&lt;li&gt;due-diligence document rooms;&lt;/li&gt;
&lt;li&gt;evidence-backed competitive research.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Repository-scale understanding
&lt;/h3&gt;

&lt;p&gt;The paper includes LongBench-v2 CodeQA, where questions require reasoning across files in a codebase. Probable uses include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;architecture mapping;&lt;/li&gt;
&lt;li&gt;migration-impact analysis;&lt;/li&gt;
&lt;li&gt;dependency and license audits;&lt;/li&gt;
&lt;li&gt;security triage;&lt;/li&gt;
&lt;li&gt;locating missing tests;&lt;/li&gt;
&lt;li&gt;comparing implementation against documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Cross-record and pairwise reasoning
&lt;/h3&gt;

&lt;p&gt;OOLONG-Pairs asks the system to construct relationships between combinations of records. Applications could include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;entity resolution;&lt;/li&gt;
&lt;li&gt;policy-conflict detection;&lt;/li&gt;
&lt;li&gt;matching candidates against constraints;&lt;/li&gt;
&lt;li&gt;finding related incidents;&lt;/li&gt;
&lt;li&gt;identifying incompatible configurations;&lt;/li&gt;
&lt;li&gt;relationship discovery across an archive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These workloads can grow quadratically, so they need strict budgets and deterministic post-processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical new use case: making sense of agent-session archives
&lt;/h2&gt;

&lt;p&gt;While exploring our local Claude Code history, we found a single session transcript that was 242 MB and contained 39,570 JSONL records. All project transcripts together occupied about 3.6 GB.&lt;/p&gt;

&lt;p&gt;The large session was not 242 MB of useful conversation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;about 176 MB was attachment records;&lt;/li&gt;
&lt;li&gt;about 29 MB was assistant events;&lt;/li&gt;
&lt;li&gt;about 20 MB was user and tool-result events;&lt;/li&gt;
&lt;li&gt;about 12 MB was file-history snapshots.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is an excellent RLM-shaped problem.&lt;/p&gt;

&lt;p&gt;A deterministic first pass can stream the JSONL, hash duplicate attachments, reconstruct parent-child event relationships, merge subagent logs, and extract messages, commands, file changes, tests, commits, errors, and outcomes. An RLM can then analyze normalized episodes and recursively build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a cross-session project timeline;&lt;/li&gt;
&lt;li&gt;a decision register;&lt;/li&gt;
&lt;li&gt;a map of attempted and abandoned approaches;&lt;/li&gt;
&lt;li&gt;recurring failure patterns;&lt;/li&gt;
&lt;li&gt;unresolved tasks;&lt;/li&gt;
&lt;li&gt;evidence-linked summaries of what actually shipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final report should cite session IDs, event IDs, timestamps, commands, and Git commits. Otherwise, it is merely another plausible summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other probable use cases
&lt;/h2&gt;

&lt;p&gt;The same decomposition pattern should transfer to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;long incident timelines assembled from logs, tickets, and chat;&lt;/li&gt;
&lt;li&gt;scientific evidence extraction across papers and experiment records;&lt;/li&gt;
&lt;li&gt;compliance control-to-evidence mapping;&lt;/li&gt;
&lt;li&gt;large archives of meetings, email, or project documents;&lt;/li&gt;
&lt;li&gt;ranking records against a nuanced rubric;&lt;/li&gt;
&lt;li&gt;graph filtering and multi-hop relationship discovery;&lt;/li&gt;
&lt;li&gt;reconciling conflicting claims across many sources;&lt;/li&gt;
&lt;li&gt;constructing structured datasets from heterogeneous text.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The recurring requirement is not simply “the input is long.” A good RLM task has four properties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The context can be partitioned or searched programmatically.&lt;/li&gt;
&lt;li&gt;Smaller semantic subtasks remain understandable to the cheap model.&lt;/li&gt;
&lt;li&gt;Intermediate results can be stored in a structured form.&lt;/li&gt;
&lt;li&gt;The final result can be verified or recomputed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Where RLM is probably the wrong tool
&lt;/h2&gt;

&lt;p&gt;RLM is a poor default for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;low-latency chat;&lt;/li&gt;
&lt;li&gt;simple questions that fit comfortably in one prompt;&lt;/li&gt;
&lt;li&gt;sparse retrieval where grep or conventional search is sufficient;&lt;/li&gt;
&lt;li&gt;creative writing that depends on a single coherent voice;&lt;/li&gt;
&lt;li&gt;exact high-stakes decisions without an independent verifier;&lt;/li&gt;
&lt;li&gt;public execution of untrusted model-generated Python;&lt;/li&gt;
&lt;li&gt;high-volume synchronous APIs with tight latency budgets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our successful row took roughly 102 minutes. That is acceptable for a research run or an overnight audit, not for an interactive endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a reusable package should look like
&lt;/h2&gt;

&lt;p&gt;The useful abstraction is not an OOLONG runner and not one universal prompt. It is a context-compute runtime with a small set of reusable recipes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run(
  context,
  objective,
  recipe,
  answer_schema,
  verifier,
  budget
) -&amp;gt; answer + evidence + validation + trajectory + usage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initial recipes could include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;aggregate_records&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evidence_synthesis&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;repository_analysis&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cross_record_join&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timeline&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;candidate_ranking&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For our intended configuration, the Codex backend would keep both root and subcalls locked to &lt;code&gt;gpt-5.4-mini&lt;/code&gt;. A frontier model would appear only in evaluation runs, never inside the RLM call tree.&lt;/p&gt;

&lt;p&gt;Production use would also require an isolated execution environment, call and token limits, schema validation, redaction, prompt-injection defenses, resumable runs, and source-level evidence for every important claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;The one-row result is a proof of mechanism, not a benchmark victory.&lt;/p&gt;

&lt;p&gt;The immediate research questions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the advantage survive across all 50 paired OOLONG tasks?&lt;/li&gt;
&lt;li&gt;Can concurrency reduce the 102-minute runtime without changing quality?&lt;/li&gt;
&lt;li&gt;Which decomposition recipes transfer cleanly between domains?&lt;/li&gt;
&lt;li&gt;How much verification is required for exact row-level work?&lt;/li&gt;
&lt;li&gt;Can a mini-only RLM turn multi-gigabyte agent histories into a reliable, source-linked development narrative?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RLMs do not magically turn a cheap model into a frontier model. They change the computation available to that model. Sometimes that difference is enough to turn a wrong one-shot answer into a correct, auditable process.&lt;/p&gt;

&lt;p&gt;That is a more interesting result than the slogan.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Reproduction data: the paired OOLONG runs behind these numbers are published at &lt;a href="https://huggingface.co/datasets/Rickesh/rlm-oolong-reproduction" rel="noopener noreferrer"&gt;huggingface.co/datasets/Rickesh/rlm-oolong-reproduction&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>We made our world model smaller and it got better. Then "efficient" attention made nothing faster.</title>
      <dc:creator>Rickesh T N</dc:creator>
      <pubDate>Fri, 07 Aug 2026 05:56:04 +0000</pubDate>
      <link>https://dev.to/rickeshtn/we-made-our-world-model-smaller-and-it-got-better-then-efficient-attention-made-nothing-faster-18f6</link>
      <guid>https://dev.to/rickeshtn/we-made-our-world-model-smaller-and-it-got-better-then-efficient-attention-made-nothing-faster-18f6</guid>
      <description>&lt;p&gt;Two experiments on the same world model, both of which came out the opposite way to how I expected. One is now an IJCNN 2026 paper, the other an ICPR 2026 paper. Both are really about the same thing: the scaling intuitions most of us carry around are older than the hardware we run on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a world model has to do here
&lt;/h2&gt;

&lt;p&gt;A dynamic scene reconstruction model takes sparse multi-view camera observations across time and predicts a full 3D scene, including how things are moving. Point a few cameras at a street for two seconds, get back a 3D representation of that street with the cars in it moving correctly.&lt;/p&gt;

&lt;p&gt;This is the perception half of a world model for driving and robotics. If you want to simulate "what happens if I turn left here," you first need a model that can build the scene at all.&lt;/p&gt;

&lt;p&gt;The architecture we worked from is &lt;a href="https://arxiv.org/abs/2501.00602" rel="noopener noreferrer"&gt;STORM&lt;/a&gt; (Yang et al.), a transformer that predicts 3D Gaussian primitives and their motion in a single forward pass. Feed-forward, so no per-scene optimization: a big deal if you ever want this on a vehicle rather than in an offline pipeline. That's prior work, not ours. What follows is what we found building on top of it.&lt;/p&gt;

&lt;p&gt;Setup throughout: a subset of the Waymo Open Dataset, 2-second clips at 10 fps, up to three synchronized camera views, images downscaled to 160x240, 150K-300K training iterations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experiment 1: we allocated the parameters differently and the smaller model won
&lt;/h2&gt;

&lt;p&gt;The default move when a transformer underperforms is to make it deeper. More layers, more capacity, better results. That instinct comes from language modelling and image classification, and it is mostly right there.&lt;/p&gt;

&lt;p&gt;Dense spatio-temporal prediction is not those tasks.&lt;/p&gt;

&lt;p&gt;We built a width-dominant, shallow variant. Concretely: fewer transformer layers, larger embedding dimension, and fewer attention heads each with a bigger head dimension. Then we ran it in a &lt;em&gt;smaller&lt;/em&gt; configuration against the standard baseline in a larger one, under an identical training and data pipeline.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;PSNR&lt;/th&gt;
&lt;th&gt;SSIM&lt;/th&gt;
&lt;th&gt;Depth RMSE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;STORM baseline (B8)&lt;/td&gt;
&lt;td&gt;26.82&lt;/td&gt;
&lt;td&gt;0.770&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12.59&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ours, width-dominant (V6, S8 config)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;27.57&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.806&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15.59&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;+0.75 PSNR and +0.036 SSIM, from the smaller model.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The training dynamics were the more interesting part. The width-dominant model converged faster, to a higher plateau, along a visibly smoother trajectory, and its gradient norms were substantially more stable. It was not squeezing out a marginal win at the end of a hard optimization. It was simply an easier model to optimize.&lt;/p&gt;

&lt;p&gt;Why this is plausible: self-attention already provides global mixing within every single layer. Stacking more layers buys you more sequential refinement, but sequential depth is exactly what makes optimization harder. Widening instead gives each attention head a richer representation to work with while leaving global information exchange intact. For a task where every token needs to talk to every other token anyway, spending your parameter budget on width rather than depth is not obviously the wrong call, and here it was the right one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now the part I want to be honest about&lt;/strong&gt;: look at the depth RMSE column. The baseline is clearly better on geometry, 12.59 against our 15.59. We got prettier images and worse depth.&lt;/p&gt;

&lt;p&gt;That trade-off is real and we reported it as a finding rather than burying it. Photometric quality and geometric accuracy are not the same axis, and if you are building a world model whose output feeds a planner rather than a display, the column we lost on may be the column you actually care about. Anyone reading "+0.75 PSNR" as an unqualified win has read the paper badly.&lt;/p&gt;

&lt;p&gt;The practical upshot is deployability. A smaller model that trains on consumer GPUs and beats a larger one on photometric quality changes who can work on this at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experiment 2: attention reuse, and a result I did not want
&lt;/h2&gt;

&lt;p&gt;Self-attention costs O(N² · d_h · H). For spatio-temporal input the token count N is brutal, because you are multiplying pixels by views by timesteps. So the obvious efficiency target is attention itself.&lt;/p&gt;

&lt;p&gt;There is a neat line of work on &lt;strong&gt;attention reuse&lt;/strong&gt;, built on the observation that attention maps stabilize as you go deeper into a transformer. If layer 9's attention pattern looks a lot like layer 8's, why recompute the query-key interaction at all? Reuse and transform the previous map instead. You keep the global token interaction and drop a chunk of the compute.&lt;/p&gt;

&lt;p&gt;We integrated Less-Attention layers into the STORM-B/8 backbone and evaluated across a range of reuse ratios, measuring runtime, memory, parameter growth and reconstruction quality, under both naive and optimized implementations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attention reuse did not improve wall-clock time over optimized full attention. At any reuse ratio. Even with careful implementation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The reason is not that the theory is wrong. The FLOPs really do go away. The reason is that fused attention kernels on modern GPUs have already moved the bottleneck somewhere else. When full attention runs as a single fused kernel that never materializes the attention matrix in HBM, the thing you were paying for was mostly memory traffic, not arithmetic — and "skip some of the arithmetic" optimizes the wrong resource. You also add parameters and complexity for the reuse machinery, and you now have two kernels where the fused path had one.&lt;/p&gt;

&lt;p&gt;This is the whole lesson: &lt;strong&gt;an algorithmic optimization derived from a FLOP count is a hypothesis about the hardware, not a fact about it.&lt;/strong&gt; The hardware moved. FlashAttention-style kernels changed which operations are expensive, and a good chunk of the "efficient attention" literature was implicitly costed against a machine that no longer exists.&lt;/p&gt;

&lt;p&gt;I would rather this had worked. Publishing a null result is less fun than publishing a speedup. But a null result that saves other people from re-implementing the same thing is worth more than another 3% on a benchmark.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I take from both
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Capacity allocation matters more than total capacity.&lt;/strong&gt; Where you put the parameters is a real design decision with real consequences, and "make it bigger" is the answer you give when you have not measured which axis is actually binding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure wall-clock on your target hardware. Always.&lt;/strong&gt; Not FLOPs, not parameter counts, not asymptotic complexity. Those are proxies, and proxies drift as hardware evolves. Every efficiency claim has a silent "on the machine I tested" attached to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Report the column you lost on.&lt;/strong&gt; Our depth RMSE got worse. Someone building on this needs to know that far more than they need another decimal place of PSNR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The intuitions are load-bearing and mostly untested.&lt;/strong&gt; "Deeper is better" and "fewer FLOPs is faster" are both reasonable priors that happened to be wrong in our setting. I only found that out by running the ablation instead of assuming.&lt;/p&gt;

&lt;p&gt;If you work on neural rendering, world models, or transformer efficiency, I would genuinely like to hear whether the width-over-depth result holds in your setting. My prior is that it generalizes to dense prediction tasks and not much further, but that is a prior, and this post is largely about how those go.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Papers: "Rethinking Transformer Design for Dynamic Scene Reconstruction: An Efficient, Width-Dominant Approach" (IJCNN 2026) and "Evaluating Attention Reuse in Dynamic 3D Gaussian Reconstruction" (ICPR 2026).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I write about world models, robotics data pipelines, and the ways measurement quietly fails. &lt;a href="https://linkedin.com/in/rickeshnatarajan" rel="noopener noreferrer"&gt;linkedin.com/in/rickeshnatarajan&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>computervision</category>
      <category>ai</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
