<?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: Jordi Marçal</title>
    <description>The latest articles on DEV Community by Jordi Marçal (@jordimarsal).</description>
    <link>https://dev.to/jordimarsal</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%2F212456%2F83989c34-f432-415e-b11d-fe23465e3bac.jpeg</url>
      <title>DEV Community: Jordi Marçal</title>
      <link>https://dev.to/jordimarsal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jordimarsal"/>
    <language>en</language>
    <item>
      <title>Which local model should a coding agent run? Round 2: llama.cpp 0.9</title>
      <dc:creator>Jordi Marçal</dc:creator>
      <pubDate>Fri, 06 Nov 2026 20:37:27 +0000</pubDate>
      <link>https://dev.to/jordimarsal/which-local-model-should-a-coding-agent-run-round-2-llamacpp-09-3679</link>
      <guid>https://dev.to/jordimarsal/which-local-model-should-a-coding-agent-run-round-2-llamacpp-09-3679</guid>
      <description>&lt;p&gt;Some days ago I published &lt;a href="https://jordimp.net/en/blog/which-local-model-for-coding-agents/" rel="noopener noreferrer"&gt;the first round&lt;/a&gt;: one harness, seven GGUFs under llama.cpp plus one model under the Strata engine, four dimensions, the receipts of every call. Since then the machine took three weeks of llama.cpp commits (0.6.0-dev &lt;code&gt;4413a19&lt;/code&gt; to 0.9.0-dev &lt;code&gt;b1c902f&lt;/code&gt;) and a Strata engine update (0.1.44 to 0.1.57). Same fleet, same harness, same pinned sampling — temperature 0.2, top-p 0.95, top-k 40, min-p 0.05, seed 42, KV cache q8_0, &lt;code&gt;--jinja&lt;/code&gt;, one slot. So I re-measured everything, expecting the ranking to survive. It did not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fleet, then and now
&lt;/h2&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;think&lt;/th&gt;
&lt;th&gt;tools&lt;/th&gt;
&lt;th&gt;code&lt;/th&gt;
&lt;th&gt;agent&lt;/th&gt;
&lt;th&gt;speed&lt;/th&gt;
&lt;th&gt;global&lt;/th&gt;
&lt;th&gt;was&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;minicpm-v-4.7-35b-a3b q4_k_m&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;84% (10 excl.)&lt;/td&gt;
&lt;td&gt;92%&lt;/td&gt;
&lt;td&gt;23ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.87&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen36-35b-distill-mxfp4_moe&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;71%&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;16ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.87&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.84&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;minicpm-v-4.7-35b-a3b q4_k_m&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;80% (2 excl.)&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;21ms&lt;/td&gt;
&lt;td&gt;0.86&lt;/td&gt;
&lt;td&gt;0.85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-27b-ud-q3_k_xl&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;81%&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;36ms&lt;/td&gt;
&lt;td&gt;0.83&lt;/td&gt;
&lt;td&gt;0.82&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ornith-1.5-35b-a3b-apex-mtp&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;74%&lt;/td&gt;
&lt;td&gt;91%&lt;/td&gt;
&lt;td&gt;26ms&lt;/td&gt;
&lt;td&gt;0.82&lt;/td&gt;
&lt;td&gt;0.82&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-27b-ud-q3_k_xl&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;90% (3 excl.)&lt;/td&gt;
&lt;td&gt;96%&lt;/td&gt;
&lt;td&gt;686ms&lt;/td&gt;
&lt;td&gt;0.81&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-27b-heretic-q4_k_m&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;97%&lt;/strong&gt; (7 excl.)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;97%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1053ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.84&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma-4-26b-a4b-it-ud-q5_k_xl&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;79%&lt;/td&gt;
&lt;td&gt;89%&lt;/td&gt;
&lt;td&gt;690ms&lt;/td&gt;
&lt;td&gt;0.75&lt;/td&gt;
&lt;td&gt;0.74&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ornith-1.5-35b-a3b-apex-mtp&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;87% (1 excl.)&lt;/td&gt;
&lt;td&gt;96%&lt;/td&gt;
&lt;td&gt;412ms&lt;/td&gt;
&lt;td&gt;0.79&lt;/td&gt;
&lt;td&gt;0.77&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;strata-iq3_xxs (Strata 0.1.57)&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;87% (4 excl.)&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;398ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.78&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen36-35b-distill-mxfp4_moe&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;74% (4 excl.)&lt;/td&gt;
&lt;td&gt;89%&lt;/td&gt;
&lt;td&gt;244ms&lt;/td&gt;
&lt;td&gt;0.73&lt;/td&gt;
&lt;td&gt;0.72&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma-4-26b-a4b-it-ud-q5_k_xl&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;89%&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;td&gt;89%&lt;/td&gt;
&lt;td&gt;32ms&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;strata-iq3_xxs (Strata 0.1.57)&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;89%&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;36ms&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-27b-heretic-q4_k_m&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;81%&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;66ms&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;td&gt;0.81&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ON rows ran with generation caps 1024/8192 and OFF rows with the fleet defaults 128/512; the gap between those two is the first finding. Single-run pass, seed 42: every number here is one measurement where October's were the mean of three, and the ± columns return with the 3-seed pass. The &lt;code&gt;turbofcfusion-neo-coder&lt;/code&gt; row is record only — its quant is no longer on disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake worth your time: caps
&lt;/h2&gt;

&lt;p&gt;The first thing I re-measured collapsed. Agent scores fell to 58% and 64% where October said 96% and 97%, with the models answering empty in turn one. The receipts said why: the thinking chain burned the entire 512-token turn budget before any tool call, and the episode carried on with a hole in it. The October fleet ran ON rows at 1024/8192; this pass first ran them at the harness defaults of 128/512.&lt;/p&gt;

&lt;p&gt;Nothing about the models had degraded; the harness conditions had. Re-run at the fair caps, the collapse inverted — UD-Q3 back to 96% agent, Heretic to 97% — and ON went from a tax to a win almost everywhere. If you benchmark agentic models, take this away: a thinking model measured with direct-answer caps measures the harness, not the model. The failure mode (empty replies, one turn, no error) looks exactly like a model regression unless the receipts record it. Mine did; that is the only reason this paragraph exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What moved with the engine
&lt;/h2&gt;

&lt;p&gt;Four things stood out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thinking stopped being only a hard-code tool
&lt;/h3&gt;

&lt;p&gt;In October, ON bought quality only on the hard tier. This time ON improved six of seven models. Heretic-on posts the fleet's best code and agent, 97% on both, and MiniCPM-on ties the top of the board at 0.87 while keeping its 23ms turns — still the only model whose thinking is free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strata gained the most
&lt;/h3&gt;

&lt;p&gt;0.70 to 0.78 on engine 0.1.57 with the fair caps; tools went 78 to 94% and agent 77 to 94%. The OFF row stayed at 0.80, so the engine work landed specifically in thinking mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  OFF stayed put except tools
&lt;/h3&gt;

&lt;p&gt;llama.cpp 0.9 kept every OFF global within ±0.03 of October, and most rows moved less than the run-to-run spread. The movement sat in tool calling: qwen36 went 94 to 100% with turns at 16ms, while Heretic trades five points of code for a 19ms faster turn.&lt;/p&gt;

&lt;h3&gt;
  
  
  The top is now a tie between modes
&lt;/h3&gt;

&lt;p&gt;October's ties were between OFF rows. This time MiniCPM-on 0.87 equals qwen36-off 0.87, so the pick is by use: qwen36-off for 16ms turns, MiniCPM-on for 84% code with receipts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much context does a model burn to pass?
&lt;/h2&gt;

&lt;p&gt;The question a reader left on the first round: the agent loop re-sends the whole conversation every turn, so two models with the same pass rate can cost very differently in sent tokens. Until now the harness counted what the model emitted (&lt;code&gt;out_tokens&lt;/code&gt;) and not what it read. This round every row carries context receipts — &lt;code&gt;in_tokens&lt;/code&gt; per call for tools and code, tokens per agent session summed over the episode's turns, averaged and median across the fleet's 72 sessions:&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;think&lt;/th&gt;
&lt;th&gt;tools in/call&lt;/th&gt;
&lt;th&gt;code in/call&lt;/th&gt;
&lt;th&gt;agent session avg&lt;/th&gt;
&lt;th&gt;median&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;qwen36-35b-distill-mxfp4_moe&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;305 / 294&lt;/td&gt;
&lt;td&gt;187 / 115&lt;/td&gt;
&lt;td&gt;4,839&lt;/td&gt;
&lt;td&gt;3,789&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;minicpm-v-4.7-35b-a3b q4_k_m&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;305 / 294&lt;/td&gt;
&lt;td&gt;187 / 115&lt;/td&gt;
&lt;td&gt;4,502&lt;/td&gt;
&lt;td&gt;3,643&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-27b-ud-q3_k_xl&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;305 / 294&lt;/td&gt;
&lt;td&gt;187 / 115&lt;/td&gt;
&lt;td&gt;5,150&lt;/td&gt;
&lt;td&gt;3,913&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ornith-1.5-35b-a3b-apex-mtp&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;305 / 294&lt;/td&gt;
&lt;td&gt;187 / 115&lt;/td&gt;
&lt;td&gt;5,155&lt;/td&gt;
&lt;td&gt;4,120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-27b-heretic-q4_k_m&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;305 / 294&lt;/td&gt;
&lt;td&gt;187 / 115&lt;/td&gt;
&lt;td&gt;5,404&lt;/td&gt;
&lt;td&gt;3,817&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma-4-26b-a4b-it-ud-q5_k_xl&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;104 / 86&lt;/td&gt;
&lt;td&gt;190 / 116&lt;/td&gt;
&lt;td&gt;4,412&lt;/td&gt;
&lt;td&gt;3,048&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;strata-iq3_xxs (Strata 0.1.57)&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;289 / 281&lt;/td&gt;
&lt;td&gt;187 / 115&lt;/td&gt;
&lt;td&gt;4,663&lt;/td&gt;
&lt;td&gt;3,512&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen36-35b-distill-mxfp4_moe&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;303 / 292&lt;/td&gt;
&lt;td&gt;185 / 113&lt;/td&gt;
&lt;td&gt;6,086&lt;/td&gt;
&lt;td&gt;4,200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;minicpm-v-4.7-35b-a3b q4_k_m&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;303 / 292&lt;/td&gt;
&lt;td&gt;185 / 113&lt;/td&gt;
&lt;td&gt;6,186&lt;/td&gt;
&lt;td&gt;4,465&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-27b-ud-q3_k_xl&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;341 / 330&lt;/td&gt;
&lt;td&gt;223 / 151&lt;/td&gt;
&lt;td&gt;9,057&lt;/td&gt;
&lt;td&gt;6,130&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ornith-1.5-35b-a3b-apex-mtp&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;303 / 292&lt;/td&gt;
&lt;td&gt;185 / 113&lt;/td&gt;
&lt;td&gt;10,758&lt;/td&gt;
&lt;td&gt;6,353&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;strata-iq3_xxs (Strata 0.1.57)&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;326 / 317&lt;/td&gt;
&lt;td&gt;223 / 151&lt;/td&gt;
&lt;td&gt;10,101&lt;/td&gt;
&lt;td&gt;6,058&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-27b-heretic-q4_k_m&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;341 / 330&lt;/td&gt;
&lt;td&gt;223 / 151&lt;/td&gt;
&lt;td&gt;8,500&lt;/td&gt;
&lt;td&gt;4,710&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma-4-26b-a4b-it-ud-q5_k_xl&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;102 / 84&lt;/td&gt;
&lt;td&gt;188 / 114&lt;/td&gt;
&lt;td&gt;11,060&lt;/td&gt;
&lt;td&gt;7,740&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three things the receipts say.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-call burn is flat
&lt;/h3&gt;

&lt;p&gt;Roughly 290-340 tokens in per tool call and 185-220 per code call everywhere, because those prompts are the same tasks. All the variance lives in the agent loop, where the conversation grows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thinking is a context tax too
&lt;/h3&gt;

&lt;p&gt;Same model, same tasks, ON against OFF: +26% session tokens on qwen36, +57% on Heretic, +76% on UD-Q3, +117% on Strata, +151% on gemma. The latency table already showed one cost of thinking; the receipts show the second.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quality per burned token is its own ranking
&lt;/h3&gt;

&lt;p&gt;qwen36-off delivers the joint-top 0.87 at 4.8K tokens per session, and MiniCPM-on the same 0.87 at 6.2K — the cheap thinker. At the other end, gemma-on burns the most context in the fleet (11.1K average, 7.7K median) for 0.75, and Strata-on pays 10.1K for 0.78. The engine work bought its points expensively.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell you to do
&lt;/h2&gt;

&lt;p&gt;Same as last time, with one amendment. Measure on your own hardware, read the exclusions, treat close rows as ties, and when a score moves, check the caps before you believe the model. An engine upgrade does not change what a model knows; it changes how the plumbing counts. The fleet still has no single winner: tools and cheap turns belong to qwen36-off, code and agent now belong to Heretic-on, speed-agnostic quality to MiniCPM, and long-form dispatch to Strata, which just closed most of the gap its first round left open.&lt;/p&gt;

&lt;p&gt;Scoreboard and live report: &lt;a href="https://jordimarsal.github.io/ggufbench/" rel="noopener noreferrer"&gt;jordimarsal.github.io/ggufbench&lt;/a&gt;. Code, tasks and receipts: &lt;a href="https://github.com/jordimarsal/ggufbench" rel="noopener noreferrer"&gt;github.com/jordimarsal/ggufbench&lt;/a&gt;. The first round, for context: &lt;a href="https://jordimp.net/en/blog/which-local-model-for-coding-agents/" rel="noopener noreferrer"&gt;which local model for coding agents&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>coding</category>
      <category>llm</category>
    </item>
    <item>
      <title>Which local model should a coding agent run?</title>
      <dc:creator>Jordi Marçal</dc:creator>
      <pubDate>Tue, 03 Nov 2026 14:09:47 +0000</pubDate>
      <link>https://dev.to/jordimarsal/which-local-model-should-a-coding-agent-run-hjn</link>
      <guid>https://dev.to/jordimarsal/which-local-model-should-a-coding-agent-run-hjn</guid>
      <description>&lt;p&gt;UPDATE 2026-11-06: I re-measured this fleet on llama.cpp 0.9 and Strata 0.1.57 — with fair generation caps, thinking now pays almost everywhere, Heretic-on posts the best code+agent of the fleet, and Strata-on gains +0.08. Full table and the measurement mistake behind it: &lt;a href="https://dev.to/jordimarsal/which-local-model-should-a-coding-agent-run-round-2-llamacpp-09-3679"&gt;follow-up&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One harness, same conditions, four dimensions, and the raw receipts of every call — this round, seven GGUFs under llama.cpp plus one model under the Strata engine, each in both thinking modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question
&lt;/h2&gt;

&lt;p&gt;I run coding agents on my own hardware. llama.cpp on two RTX 5080s, models from my &lt;code&gt;models&lt;/code&gt; folder, and one question: which GGUF, at which quant, should I load before I start working?&lt;/p&gt;

&lt;p&gt;Public leaderboards measure something else. Most of their tasks are in models' training data, they score single-shot answers, and they say nothing about whether a model can call a tool with well-formed arguments, survive a multi-call loop, read a folder, edit a file, and verify its own change. That is the work an agent does all day, so I stopped reading those tables for this decision and built &lt;a href="https://github.com/jordimarsal/ggufbench" rel="noopener noreferrer"&gt;ggufbench&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules that make the numbers mean something
&lt;/h2&gt;

&lt;p&gt;Every model runs with identical sampling: temperature 0.2, top-p 0.95, top-k 40, min-p 0.05, seed 42, KV cache q8_0, &lt;code&gt;--jinja&lt;/code&gt;, one slot. Every sampler value is pinned explicitly, because a GGUF carries its own sampling metadata — this fleet alone ships three different &lt;code&gt;top_k&lt;/code&gt; settings (20, 64, and none at all) — and an unpinned value ends up different per model. The settings are fixed in two places, the server flags and the request body, so config drift on the server cannot leak into a comparison.&lt;/p&gt;

&lt;p&gt;Every model keeps its own profile file. That profile applies last and can override the baseline, so models compete in their best real configuration and not in a lab rig I invented. The harness fixes the infrastructure flags, and what each model ended up running with is recorded in its result row.&lt;/p&gt;

&lt;p&gt;Exclusions are annotated with a reason. A model that runs out of time or answers with prose where a code block was expected does not silently vanish from the average; it shows up in the report, counted.&lt;/p&gt;

&lt;p&gt;The harness uses only the Python standard library. No dependency tree to rot while the benchmark waits.&lt;/p&gt;

&lt;p&gt;Every task stores three runs, seeds 42, 43 and 44, and the scoreboard reports mean ± standard deviation. When a row says &lt;code&gt;0.85 ±1&lt;/code&gt;, that spread is part of the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the scoreboard said
&lt;/h2&gt;

&lt;p&gt;The current pool runs set v5: 18 tool tasks, 72 code tasks across four languages (32 easy, 20 hard, 20 expert), 72 multi-step agent tasks (13 of them multi-file projects) and a 32K output budget for code. The rows below are the current pool, task set v5, generated from the live scoreboard, so they are never a snapshot of a night that has passed.&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;think&lt;/th&gt;
&lt;th&gt;tools&lt;/th&gt;
&lt;th&gt;code&lt;/th&gt;
&lt;th&gt;agent&lt;/th&gt;
&lt;th&gt;speed&lt;/th&gt;
&lt;th&gt;global&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;minicpm-v-4.7-35b-a3b q4_k_m&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;100% ±0&lt;/td&gt;
&lt;td&gt;82% ±4 (8 excl.)&lt;/td&gt;
&lt;td&gt;89% ±0&lt;/td&gt;
&lt;td&gt;23ms&lt;/td&gt;
&lt;td&gt;0.85 ±1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;minicpm-v-4.7-35b-a3b q4_k_m&lt;/td&gt;
&lt;td&gt;off ★&lt;/td&gt;
&lt;td&gt;100% ±0&lt;/td&gt;
&lt;td&gt;83% ±1 (2 excl.)&lt;/td&gt;
&lt;td&gt;90% ±2&lt;/td&gt;
&lt;td&gt;26ms&lt;/td&gt;
&lt;td&gt;0.85 ±0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen36-35b-distill-mxfp4_moe&lt;/td&gt;
&lt;td&gt;off ★&lt;/td&gt;
&lt;td&gt;94% ±0&lt;/td&gt;
&lt;td&gt;74% ±3&lt;/td&gt;
&lt;td&gt;92% ±2&lt;/td&gt;
&lt;td&gt;19ms&lt;/td&gt;
&lt;td&gt;0.84 ±1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-27b-ud-q3_k_xl&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;100% ±0&lt;/td&gt;
&lt;td&gt;79% ±4&lt;/td&gt;
&lt;td&gt;90% ±1&lt;/td&gt;
&lt;td&gt;36ms&lt;/td&gt;
&lt;td&gt;0.82 ±1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ornith-1.5-35b-a3b-apex-mtp-quality&lt;/td&gt;
&lt;td&gt;off ★&lt;/td&gt;
&lt;td&gt;100% ±0&lt;/td&gt;
&lt;td&gt;75% ±2&lt;/td&gt;
&lt;td&gt;92% ±1&lt;/td&gt;
&lt;td&gt;32ms&lt;/td&gt;
&lt;td&gt;0.82 ±1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-27b-heretic-q4_k_m&lt;/td&gt;
&lt;td&gt;off ★&lt;/td&gt;
&lt;td&gt;100% ±0&lt;/td&gt;
&lt;td&gt;86% ±2&lt;/td&gt;
&lt;td&gt;92% ±2&lt;/td&gt;
&lt;td&gt;85ms&lt;/td&gt;
&lt;td&gt;0.81 ±1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-27b-neo-coder-max-mtp q4_k_s&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;100% ±0&lt;/td&gt;
&lt;td&gt;79% ±2&lt;/td&gt;
&lt;td&gt;91% ±1&lt;/td&gt;
&lt;td&gt;46ms&lt;/td&gt;
&lt;td&gt;0.81 ±0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-27b-ud-q3_k_xl&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;94% ±0&lt;/td&gt;
&lt;td&gt;93% ±1 (1 excl.)&lt;/td&gt;
&lt;td&gt;96% ±1&lt;/td&gt;
&lt;td&gt;629ms&lt;/td&gt;
&lt;td&gt;0.80 ±0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma-4-26b-a4b-it-ud-q5_k_xl&lt;/td&gt;
&lt;td&gt;off ★&lt;/td&gt;
&lt;td&gt;89% ±0&lt;/td&gt;
&lt;td&gt;86% ±1&lt;/td&gt;
&lt;td&gt;87% ±2&lt;/td&gt;
&lt;td&gt;33ms&lt;/td&gt;
&lt;td&gt;0.80 ±0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;strata-iq3_xxs (Strata engine)&lt;/td&gt;
&lt;td&gt;off ★&lt;/td&gt;
&lt;td&gt;91% ±3&lt;/td&gt;
&lt;td&gt;84% ±1&lt;/td&gt;
&lt;td&gt;91% ±2&lt;/td&gt;
&lt;td&gt;40ms&lt;/td&gt;
&lt;td&gt;0.80 ±1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen3.8-27b-heretic-q4_k_m&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;94% ±0&lt;/td&gt;
&lt;td&gt;90% ±2&lt;/td&gt;
&lt;td&gt;97% ±1&lt;/td&gt;
&lt;td&gt;1060ms&lt;/td&gt;
&lt;td&gt;0.80 ±0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;★ = each model's optimal thinking mode by data: quality within ±0.02 of the other mode and no extra wall clock; exact ties go to off.&lt;/p&gt;

&lt;p&gt;Four things stood out.&lt;/p&gt;

&lt;h3&gt;
  
  
  The top rows are ties
&lt;/h3&gt;

&lt;p&gt;The top ten rows span five hundredths of a point — 0.85 down to 0.80 — and neighbouring rows sit at most one or two hundredths apart, the same size as the run-to-run spread printed beside each score. The global score ranks a fleet; it does not certify that row 1 beats row 7. Anyone ranking two close rows by their second decimal is quoting noise.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thinking mode is a tax that only pays in hard code
&lt;/h3&gt;

&lt;p&gt;I measured it as separate rows instead of folding it into one number. Thinking ON multiplies turn latency — every ON row but MiniCPM's runs 280–1060 ms against 19–85 ms OFF (MiniCPM's ON lands at 23 ms) — and it costs quality in every model as well, with MiniCPM the only tie. It also concentrates the exclusions: across the fleet's 1,512 code runs per mode, OFF excludes 6 and ON excludes 101, most of them answers with no code block where one was expected (fully excluded tasks: 2 against 14).&lt;/p&gt;

&lt;p&gt;Where it does buy something is the hard code tier: UD-Q3 climbs from 79% to 93% on code overall (92% hard, 91% expert), Ornith +10 points over its own OFF, Strata +7, Heretic +4 — and Heretic's agent loop reaches 97%, the fleet's best. On the tasks that need it, thinking is worth ten times the wait; everywhere else it is a tax with no visible return.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easy code tasks don't discriminate
&lt;/h3&gt;

&lt;p&gt;With thinking off the entire fleet lands between 91% and 100% on the easy tier, while hard and expert live between 47% and 87%. Those two levels are where the separation is — especially the expert ones, built from multi-bug debugging with strict output contracts (return the full corrected file, exact rounding, defensive copies). The scoreboard folds all three levels into one code column; the per-level breakdown it records (&lt;code&gt;by_level&lt;/code&gt;) is where the arguments happen.&lt;/p&gt;

&lt;h3&gt;
  
  
  The engine made no measurable difference
&lt;/h3&gt;

&lt;p&gt;I ran the same harness against a different engine, the community MoE streaming engine &lt;a href="https://github.com/Helldez/BigMoeOnEdge" rel="noopener noreferrer"&gt;BigMoeOnEdge&lt;/a&gt;, to see whether the engine decides the score. It did not: 0.920 against 0.923 on the same 72 agent tasks, same model, same quant, at the same throughput (48 tok/s against 47). For a 27B on two 5080s, streaming experts from NVMe is not the bottleneck.&lt;/p&gt;

&lt;p&gt;Two conditions, so the comparison is honest. The engine ran at a 16K context, because a 32K-wide prefill graph wants a 19.2 GB compute buffer and does not fit a 16 GB card. And it has no knob for the KV cache type, so the KV was the engine's default rather than the q8_0 the rows above pin. The seed is a session parameter there, so it was set per run and the session reloaded.&lt;/p&gt;

&lt;p&gt;It did surface a bug in that engine's router hook. A generated turn that its chat parser cannot turn into text or a tool call comes back as an empty assistant message: 31 tokens generated, the turn dropped, and the episode carries on with a hole in it. Twelve of those in the three runs, and two bash tasks flip from pass to fail because of it. llama.cpp recovers where the other one drops the turn, so the same model and the same prompts produce no empty reply there. Filed with a backtrace: &lt;a href="https://github.com/Helldez/BigMoeOnEdge/issues/334" rel="noopener noreferrer"&gt;issue #334&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell you to do
&lt;/h2&gt;

&lt;p&gt;Pick by dimension. In this table, tools belong to MiniCPM (100%), hard code to UD-Q3 with thinking (92% hard, 91% expert), the agent loop to Heretic with thinking (97%), and per-turn speed to qwen36 off (19 ms). No row wins everything.&lt;/p&gt;

&lt;p&gt;Measure on your own hardware with your own profiles. A quant that is comfortable on two 5080s is a slideshow on one, and the result changes with the KV cache configuration.&lt;/p&gt;

&lt;p&gt;Read the exclusions. A model that fails to format a tool call once in a while is not slightly worse than one that never misses. In an agent loop, it stops.&lt;/p&gt;

&lt;p&gt;Treat close rows as ties. Ten rows inside five hundredths of a point form one group. I choose inside it by latency and by how a model behaves when a call fails, not by the third decimal of a score. Do not look for the bottleneck in the engine first: the same harness against a different engine moved the score by three thousandths and the throughput by one token per second.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fine print
&lt;/h2&gt;

&lt;p&gt;The tasks are written for this harness, the reference solutions never leave the machine, and the models under test are the consumer-GPU quants people actually run. Scores answer "which GGUF should I run for agentic work", not "how smart is this model". Two thinking-ON rows (gemma, Strata) were measured under tighter tool-loop caps than the rest and are flagged in the report as owed a fair pass; the thirteen models retired along the way live in its Retired tab, record only. The table above is generated from the live scoreboard, not typed in, and the engine comparison ran under the conditions stated in its section. Full method in &lt;code&gt;docs/SPEC.md&lt;/code&gt; of the repository.&lt;/p&gt;

&lt;p&gt;Scoreboard and live report: &lt;a href="https://jordimarsal.github.io/ggufbench/" rel="noopener noreferrer"&gt;jordimarsal.github.io/ggufbench&lt;/a&gt;. Code, tasks and receipts: &lt;a href="https://github.com/jordimarsal/ggufbench" rel="noopener noreferrer"&gt;github.com/jordimarsal/ggufbench&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>coding</category>
      <category>llm</category>
    </item>
    <item>
      <title>RAG without an eval gate is a demo.</title>
      <dc:creator>Jordi Marçal</dc:creator>
      <pubDate>Mon, 19 Oct 2026 13:37:13 +0000</pubDate>
      <link>https://dev.to/jordimarsal/rag-without-an-eval-gate-is-a-demo-47ej</link>
      <guid>https://dev.to/jordimarsal/rag-without-an-eval-gate-is-a-demo-47ej</guid>
      <description>&lt;p&gt;I run a portfolio site where every claim about my work has to survive its own eval. This essay is the why behind that discipline: a RAG demo is not a system, and the distance between the two is a golden set plus a CI gate. It was first published on &lt;a href="https://jordimp.net/en/blog/rag-eval-gate/" rel="noopener noreferrer"&gt;jordimp.net&lt;/a&gt; — this cross-post keeps that page as the canonical home.&lt;/p&gt;

&lt;p&gt;The project in question, &lt;a href="https://github.com/jordimarsal/codebaserag" rel="noopener noreferrer"&gt;CodebaseRAG&lt;/a&gt;, is an open-source RAG engine over a codebase. Numbers below are its real, committed floors — including the embarrassing parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: recall nobody measures
&lt;/h2&gt;

&lt;p&gt;Most RAG projects ship a demo. You ask a question, the retriever returns five chunks, a model writes a confident paragraph, and everyone nods. Nobody can say whether the right document was in those five chunks — because nobody wrote down what “right” means and nobody measures it. The demo works until a chunking change, a new embedding model or a different vector store quietly moves quality, and the only signal is a vague feeling that answers got worse.&lt;/p&gt;

&lt;p&gt;That is the real failure mode: not hallucination as moral panic, but retrieval that degrades with nobody watching. A demo optimises for the happy path in front of an audience; a system optimises for a number it can regress against. The distance between the two is an eval — and CodebaseRAG exists to close it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The golden set: turning “right” into forty checkable pairs
&lt;/h2&gt;

&lt;p&gt;An eval is only as honest as its questions. The golden set is a public list of question/answer pairs drawn from the repository itself: a question a developer would actually ask, paired with the answer and where it lives. It holds ≥40 pairs — enough to catch a real regression, small enough that every pair can be reviewed by hand and kept honest.&lt;/p&gt;

&lt;p&gt;You do not need a labelling platform. You need somewhere to write questions down and the discipline to keep them. They live next to the code, so a retriever change and its updated expectations travel in the same review.&lt;/p&gt;

&lt;p&gt;Three entries, paraphrased from the public set, show the shape:&lt;/p&gt;

&lt;p&gt;Which abstraction does the query use case depend on, and why is it not a vector-store type? It depends on a retriever port; adapters implement it, so the core never imports a pgvector or Qdrant class.&lt;/p&gt;

&lt;p&gt;What should CI do when mean recall@5 drops below the committed floor? Fail the build. The floor is a committed number, and moving it is an explicit, ADR-gated decision — not an edit that makes CI green.&lt;/p&gt;

&lt;p&gt;Can the eval run with no network? Yes. Embeddings arrive through a port with two adapters — Ollama locally, Anthropic when hosted — so the same golden set runs offline.&lt;/p&gt;

&lt;p&gt;The examples are not meant to be hard. They are meant to have defensible answers, so a wrong one is a failing test rather than a disagreement.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CI gate: what fails, what does not
&lt;/h2&gt;

&lt;p&gt;A gate is only useful if it fails loudly and predictably. This one measures mean recall@5, mean reciprocal rank and nDCG@5 over the golden set, and fails the build when mean recall@5 drops below the committed baseline of 0.409.&lt;/p&gt;

&lt;p&gt;What fails: any change that pushes mean recall@5 under 0.409. That is the entire rule. CI runs the same deterministic eval as a laptop, so a red build points at a retrieval change, not a flaky environment.&lt;/p&gt;

&lt;p&gt;What does not fail matters too. A model change that keeps recall@5 at or above the floor passes even when MRR and nDCG@5 move — the gate guards the metric that was committed to, not every number on the dashboard. A refactor that moves code without moving retrieval passes. And the gate does not judge answer fluency: this is a retrieval eval, and pretending it measures generation quality would be the same dishonesty as having no eval at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why hexagonal: swapping Qdrant must not rewrite the core
&lt;/h2&gt;

&lt;p&gt;You measure retrieval, so what you measure must not be welded to what you deploy. CodebaseRAG is hexagonal: the query use case depends on ports, and pgvector, Qdrant, Ollama and Anthropic sit behind them as adapters.&lt;/p&gt;

&lt;p&gt;The test is concrete. Swapping Qdrant for pgvector — or running fully local on Ollama — must not rewrite the core. If changing a vector store forces edits across the use cases, the eval is measuring an accident of the storage layer, and every future swap re-opens whether the metrics still mean anything.&lt;/p&gt;

&lt;p&gt;Keeping the core free of frameworks and transports buys more than tidiness: it keeps the eval deterministic, exercised against in-memory or container-backed adapters with no network required. A committed floor is only trustworthy when the code beneath it does not change shape every time the infrastructure does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current baseline and what it does not claim
&lt;/h2&gt;

&lt;p&gt;The committed baseline is a mean recall@5 of 0.409 (MRR 0.231, nDCG@5 0.277) over a golden set of at least forty pairs. Read it as a floor, not a result. 0.409 is not SOTA and this is not a claim that retrieval is solved. It is the number the project committed to defend, chosen low enough to stay stable and high enough to catch a real drop.&lt;/p&gt;

&lt;p&gt;What the number does not claim: that the golden set is exhaustive, that a green build means good answers, or that the embedding model is the best available. Raising the floor is a recorded decision with a justification, never a vanity metric pasted into a README.&lt;/p&gt;

&lt;p&gt;What it does claim is smaller and more useful: on every change, the repository can tell you whether retrieval got worse, and prove it from a public golden set. That is the line between a RAG demo and a RAG system. A demo shows you an answer; a system lets you disagree with it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Cross-posted from &lt;a href="https://jordimp.net/en/blog/rag-eval-gate/" rel="noopener noreferrer"&gt;jordimp.net/en/blog/rag-eval-gate&lt;/a&gt;, where it lives with its revision history. The engine is open source: &lt;a href="https://github.com/jordimarsal/codebaserag" rel="noopener noreferrer"&gt;jordimarsal/codebaserag&lt;/a&gt;. If you enjoyed the honest numbers, the same site runs a RAG concierge whose gen model survived a 16-candidate benchmark before shipping — receipts included.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rag</category>
      <category>llm</category>
      <category>evaluation</category>
      <category>python</category>
    </item>
  </channel>
</rss>
