<?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: Developer at Fortitude Omnis Group</title>
    <description>The latest articles on DEV Community by Developer at Fortitude Omnis Group (@fortitudeomnis).</description>
    <link>https://dev.to/fortitudeomnis</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%2F4085068%2F0547a215-94ac-46e3-9fc6-6091eb8b8f9d.png</url>
      <title>DEV Community: Developer at Fortitude Omnis Group</title>
      <link>https://dev.to/fortitudeomnis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fortitudeomnis"/>
    <language>en</language>
    <item>
      <title>Three-quarters of my Claude classification calls can run on a gaming GPU. How to measure yours</title>
      <dc:creator>Developer at Fortitude Omnis Group</dc:creator>
      <pubDate>Tue, 29 Sep 2026 12:59:50 +0000</pubDate>
      <link>https://dev.to/fortitudeomnis/three-quarters-of-my-claude-classification-calls-can-run-on-a-gaming-gpu-how-to-measure-yours-338m</link>
      <guid>https://dev.to/fortitudeomnis/three-quarters-of-my-claude-classification-calls-can-run-on-a-gaming-gpu-how-to-measure-yours-338m</guid>
      <description>&lt;p&gt;&lt;em&gt;Rob Hill, Fortitude Omnis. Measured on 27 and 28 September 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I sent a thousand Banking77 support messages through a model on my own RTX 3080 Ti first, and only passed the uncertain ones to Claude Opus 5.5. With a fine-tuned Laya decision model doing the local work, &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;73.0% of decisions stayed on the GPU, blended accuracy was 93.3% against 94.2% for Claude alone, and the estimated bill fell from £3,898 to £1,054 per million decisions&lt;/a&gt;. The caveats belong right here. It's one dataset (Banking77), one card, one night. The £ figures are estimates from published list prices, not invoices. And Claude's answers came from an interactive Claude Code session working through batched answer sheets, not from the API.&lt;/p&gt;

&lt;p&gt;The twist is the model that did best. A plain fine-tuned MiniLM classifier &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;kept 94.7% local at the same 94.2% blended accuracy as Claude alone&lt;/a&gt;. I'll come back to that. I also measured TypeSafe's hosted Jev on the same items, and it lands between the two on Banking77.&lt;/p&gt;

&lt;p&gt;This post is the hands-on version. It shows how to run the server, how to calibrate a model's confidence, and how to get the same report for your own decision. The full write-up is on the &lt;a href="https://fortitude-omnis.group/rd/tau/" rel="noopener noreferrer"&gt;Fortitude Omnis R&amp;amp;D page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Tau?
&lt;/h2&gt;

&lt;p&gt;Tau is two things, both Apache-2.0.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Runtime&lt;/strong&gt; is a .NET server that answers the &lt;code&gt;/v1/systemone&lt;/code&gt; decision contract locally. It runs the open Laya and Von decision models through ONNX Runtime on CUDA, DirectML or a CPU. You send it some state and a set of questions, and it sends back an answer with a probability for each option.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Workbench&lt;/strong&gt; is a command-line tool called &lt;code&gt;tau&lt;/code&gt;. It asks one question of any &lt;code&gt;/v1/systemone&lt;/code&gt; endpoint: can I trust this model's confidence enough to gate on it, and what does gating save? It measures a hosted endpoint over the network the same way as a local one. Every stage writes its output to disk, and the last one writes a single HTML report with the misses left in.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I run a self-hosted /v1/systemone server?
&lt;/h2&gt;

&lt;p&gt;The repo's README walks through fetching a model, exporting it to ONNX and fetching the ONNX Runtime natives. After that, starting the Runtime on an NVIDIA card is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;src/Tau.Runtime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--urls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://localhost:8088&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--Tau&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave out &lt;code&gt;--Tau:Provider=cuda&lt;/code&gt; and it runs on the CPU. On Windows, &lt;code&gt;--Tau:Provider=directml&lt;/code&gt; runs on any DirectX 12 GPU. It refuses to start if the provider you asked for won't load, which I prefer to a silent fallback to the CPU.&lt;/p&gt;

&lt;p&gt;Then ask it something:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8088/v1/systemone &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "model": "jev-latest",
    "state": "My card payment failed three times this morning and I need it sorted today.",
    "questions": {
      "category": {
        "type": "choice",
        "instructions": "Which support category fits this message?",
        "criteria": {
          "billing": "Billing or payments issue",
          "technical": "App or website technical issue",
          "account": "Account access or security issue"
        }
      },
      "urgent": { "type": "noul", "instructions": "Does the customer need an answer today?" }
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;jev-latest&lt;/code&gt; is an alias that lets the Runtime pick a model. English text goes to &lt;code&gt;laya-en&lt;/code&gt;. You get back a &lt;code&gt;choice&lt;/code&gt; with a probability for each option, plus a &lt;code&gt;noul&lt;/code&gt; score for the yes/no question. &lt;code&gt;GET /v1/models&lt;/code&gt; lists what's loaded.&lt;/p&gt;

&lt;p&gt;From C#, the &lt;code&gt;Tau.Client&lt;/code&gt; package wraps the same call and maps the answer onto an enum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Tau.Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SystemOneClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8088/"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DecideAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"My card payment failed three times this morning and I need it sorted today."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Which support category fits this message?"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; p=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Probabilities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="n"&gt;F2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Billing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Technical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How fast is it on a gaming GPU?
&lt;/h2&gt;

&lt;p&gt;On the 3080 Ti with CUDA, laya-en answers &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/reports/r1/latency.md" rel="noopener noreferrer"&gt;one question in 18.59 ms and ten questions about one state in 114.11 ms&lt;/a&gt;. On the i9-11900K's CPU, the single question takes &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/reports/r1/latency.md" rel="noopener noreferrer"&gt;529.21 ms&lt;/a&gt;. Those are FP32 medians with no FP16 tricks. For comparison, TypeSafe's hosted Jev had a &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;median of 223 ms&lt;/a&gt;, but that's measured over the network from my desk, so it isn't like for like.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I calibrate Laya's confidence scores?
&lt;/h2&gt;

&lt;p&gt;This is the bit that matters, and the reason the Workbench exists. A decision model gives you an answer and a probability. You want to keep the answer when the probability is high and escalate when it's low. That only works if 0.9 means right nine times in ten.&lt;/p&gt;

&lt;p&gt;Out of the box, it doesn't. On Banking77, &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;laya-en was 37.2% accurate with an expected calibration error (ECE) of 0.502&lt;/a&gt;. Its stated confidence sat about 50 points away from its hit rate. A confidence score that looks certain and means nothing.&lt;/p&gt;

&lt;p&gt;Calibration fixes the meaning of the number and leaves the model alone. Fitting a small calibrator on a separate 1,000-item split took &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;laya-en's ECE from 0.502 to 0.056&lt;/a&gt;, and &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;Von's from 0.185 to 0.021&lt;/a&gt;. Accuracy didn't move.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4hyzu8jfvwaw7m6d2hy6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4hyzu8jfvwaw7m6d2hy6.png" alt="Reliability diagrams for laya-en, von-1.2.0, the fine-tuned Laya and the hosted Jev on Banking77 held-out items, raw in blue and calibrated in orange against the diagonal of perfect calibration" width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Workbench does it in stages. You describe the decision in a &lt;code&gt;decision.yaml&lt;/code&gt; (the question, the labelled data, the local models, the frontier model and a target error), then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nx"&gt;examples/banking77/decision.yaml&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# ingest cached frontier answers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;measure&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;examples/banking77/decision.yaml&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# raw accuracy and ECE, calibration and held-out splits&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;calibrate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;examples/banking77/decision.yaml&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# fit temperature and isotonic calibrators&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# restart the Runtime with --Tau:CalibratorsDirectory=examples/banking77/calibrators, then:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;measure&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;examples/banking77/decision.yaml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--phase&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;calibrated&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;threshold&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;examples/banking77/decision.yaml&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# pick the threshold for the target error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cascade&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;examples/banking77/decision.yaml&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# simulate local-first and price it&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;examples/banking77/decision.yaml&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# write report.json and report.html&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tau run&lt;/code&gt; does the lot in order and skips stages whose outputs are current. It never calls a paid API unless your spec lists a hosted endpoint under &lt;code&gt;external:&lt;/code&gt;, and then only under the budget you set there. If frontier answers are missing, &lt;code&gt;tau label&lt;/code&gt; exports batches to be answered and exits with code 2.&lt;/p&gt;

&lt;p&gt;One trap I nearly fell into. Laya's contract returns a &lt;code&gt;confidence&lt;/code&gt; field for choice questions that's derived from the entropy of the whole distribution, so it isn't a calibrated probability. Calibrate and threshold on the probability of the chosen answer instead. That's why the C# example above prints &lt;code&gt;Probabilities[decision.Value]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I decide how much can stay local?
&lt;/h2&gt;

&lt;p&gt;A calibrated model is only useful if it's also right often enough. Raw laya-en wasn't: &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;no threshold got its error below 5%&lt;/a&gt;. So I fine-tuned it on the &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/dataset.manifest.json" rel="noopener noreferrer"&gt;9,003-item training split&lt;/a&gt;, on the same card, in &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/finetune-training.json" rel="noopener noreferrer"&gt;1,518 seconds&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With a threshold of &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;0.95, picked on the calibration split and judged on the held-out split&lt;/a&gt;, the fine-tuned model kept 73.0% of decisions local and sent the rest to Claude, for an estimated &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;£1,054 per million&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjvw2yi7qsu3yiz0d0ku4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjvw2yi7qsu3yiz0d0ku4.png" alt="The cascade table from the Banking77 report: share kept local, blended accuracy and estimated cost per million decisions for each model, the hosted Jev included" width="800" height="895"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The money is arithmetic on list prices. Each Banking77 prompt is about &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;1,259 input tokens&lt;/a&gt; once you list all 77 intents, so Claude Opus 5.5 comes to &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;£3,898 per million decisions, or £1,949 through the Batch API&lt;/a&gt;. Tokens are estimated from characters, not counted. I haven't costed the GPU, which I already owned for less serious reasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why did a classic encoder win?
&lt;/h2&gt;

&lt;p&gt;I put a fine-tuned all-MiniLM-L6-v2 through exactly the same measurement, calibration, threshold and cascade code. It &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;scored 91.5% on the held-out set, with a calibrated ECE of 0.024&lt;/a&gt;, against 87.3% for the fine-tuned Laya. In the cascade it &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;kept 94.7% local for an estimated £207 per million&lt;/a&gt;. It isn't served through Tau, so that figure leaves out local latency and energy.&lt;/p&gt;

&lt;p&gt;On a fixed task with thousands of labelled examples, a small classifier is still the thing to beat. Decision models earn their keep on questions you haven't trained for, or many questions against one state. I'd rather the Workbench told me that than hid it.&lt;/p&gt;

&lt;p&gt;The second dataset was worse. On synthetic support tickets, Claude &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/support-tickets/report.json" rel="noopener noreferrer"&gt;agreed with the labels on 23.8% of items against a 41.0% majority baseline, MiniLM "learned" them to 55.6%, and scored against Claude instead, no local decision model kept more than 0.1%&lt;/a&gt;. On urgency, nothing local stands in for the frontier call.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does a hosted model compare?
&lt;/h2&gt;

&lt;p&gt;The Workbench takes hosted &lt;code&gt;/v1/systemone&lt;/code&gt; endpoints as well as local ones. You list them under &lt;code&gt;external:&lt;/code&gt; in the spec, with the name of the environment variable that holds the key. The key is read at run time and never written anywhere, and a budget guard stops the run before spend passes the limit you set. I used it on TypeSafe's hosted Jev (the responses say &lt;code&gt;jev-1.13.0&lt;/code&gt;), on the same items as everything else. It cost an estimated &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;$0.14 for Banking77&lt;/a&gt; and &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/support-tickets/report.json" rel="noopener noreferrer"&gt;$0.04 for the tickets&lt;/a&gt;, at the published &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;$0.042 per million input tokens&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On Banking77 it's a solid generalist. It &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;scored 79.2% held-out out of the box, ahead of laya-en's 37.2% and Von's 77.1%, with an ECE of 0.093 before calibration and 0.029 after&lt;/a&gt;. As the first stage of a cascade it &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;kept 51.3% at 93.6% blended, for an estimated £1,952 per million against £3,898&lt;/a&gt;, with its own calls priced in. The fine-tuned Laya beat it on accuracy (87.3%) and on share kept local (73.0%), and MiniLM beat both.&lt;/p&gt;

&lt;p&gt;The tickets are where it earns its place. Scored against Claude, it &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/support-tickets/report.json" rel="noopener noreferrer"&gt;agreed on 52.5% of items out of the box, ahead of every local model&lt;/a&gt;. At the 20% target it &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/support-tickets/report.json" rel="noopener noreferrer"&gt;kept 39.8% of decisions, with the served answers agreeing with Claude on 89.3%, for an estimated £614 per million against £997&lt;/a&gt;. Nothing local came close.&lt;/p&gt;

&lt;p&gt;Two things to know before you gate on it. Jev returns probabilities rounded to 2 decimal places, and a hosted endpoint can't load your calibrator, so its calibrated figures are the Workbench applying one offline. You'd do the same on your side of the call.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went wrong?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I fitted the calibrators on rounded numbers.&lt;/strong&gt; The Runtime rounds probabilities to 4 decimal places, the Workbench fitted its calibrators on that, and the Runtime applied them to the unrounded values. With 77 options most probabilities round to zero, so it mattered. My earlier figures &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/docs/DECISIONS.md" rel="noopener noreferrer"&gt;(laya-en 0.065, Von 0.039)&lt;/a&gt; came from that. Refitted on full precision they're the &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;0.056 and 0.021&lt;/a&gt; above. I'd also claimed the Workbench and the Runtime agree within 2.4e-4, having only checked inputs that didn't saturate. The &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/docs/DECISIONS.md" rel="noopener noreferrer"&gt;decisions log&lt;/a&gt; has the fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three FP32 models on one 12 GB card slowed the last one down.&lt;/strong&gt; The fine-tuned Laya, measured last, had &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/runs/laya-en-ft-banking77/heldout.raw.summary.json" rel="noopener noreferrer"&gt;a median of 15,219 ms per request&lt;/a&gt; in its raw phase, against &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/runs/laya-en-ft-banking77/heldout.calibrated.summary.json" rel="noopener noreferrer"&gt;243 ms&lt;/a&gt; in a fresh Runtime. VRAM spill is the likely cause, not proven. Accuracy isn't affected, and the published latency comes from the clean phase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The classic encoder beat every decision model on Banking77,&lt;/strong&gt; Tau's and Jev, on accuracy and on calibration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ticket labels are close to noise.&lt;/strong&gt; Claude agreed with them less often than a constant guess would.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On urgency, no local model stands in for Claude.&lt;/strong&gt; &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/support-tickets/report.json" rel="noopener noreferrer"&gt;No local decision model kept more than 0.1% of decisions&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calibration barely helped the fine-tuned Laya:&lt;/strong&gt; &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;ECE went from 0.071 to 0.060&lt;/a&gt;. Fine-tuning had already fitted its temperature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;laya-en missed the 50% calibration target on the tickets:&lt;/strong&gt; &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/support-tickets/report.json" rel="noopener noreferrer"&gt;ECE went from 0.273 to 0.155&lt;/a&gt;. The Workbench picks the calibrator with the lower calibration-split log loss, which chose isotonic even though temperature scaling had &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/support-tickets/calibrators/laya-en/calibration-summary.json" rel="noopener noreferrer"&gt;a calibration-split ECE of 0.016 against 0.216&lt;/a&gt;. I set that rule before seeing any held-out result, so I didn't change it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Von's tickets calibration moved &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/support-tickets/report.json" rel="noopener noreferrer"&gt;ECE from 0.045 to 0.042&lt;/a&gt;.&lt;/strong&gt; It was already close.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My first MiniLM run &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/docs/DECISIONS.md" rel="noopener noreferrer"&gt;scored 80.8%&lt;/a&gt;&lt;/strong&gt; because I stopped it after five epochs with the loss still falling. That flatters whatever you compare it with, so I retrained it properly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude &lt;a href="https://github.com/Fortitude-Group/tau/blob/master/examples/banking77/report.json" rel="noopener noreferrer"&gt;disagreed with the Banking77 labels on 5.8% of items&lt;/a&gt;.&lt;/strong&gt; Some are Claude's mistakes and some are the dataset's.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How do I reproduce it?
&lt;/h2&gt;

&lt;p&gt;The repo holds both worked examples with their reports, calibrators, cached frontier answers and per-item results. With the models fetched and the CUDA natives in place, one command reruns Banking77 end to end and rewrites its report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;/scripts/examples.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Example&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;banking77&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It checks the data and model packages first and prints the exact command for anything missing. Then read &lt;a href="https://fortitude-omnis.group/rd/files/tau/banking77.html" rel="noopener noreferrer"&gt;the Banking77 report&lt;/a&gt; and &lt;a href="https://fortitude-omnis.group/rd/files/tau/support-tickets.html" rel="noopener noreferrer"&gt;the tickets report&lt;/a&gt;. They were harder on me than I've been here.&lt;/p&gt;

&lt;p&gt;The repo is at &lt;a href="https://github.com/Fortitude-Group/tau" rel="noopener noreferrer"&gt;https://github.com/Fortitude-Group/tau&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>csharp</category>
    </item>
    <item>
      <title>The cheapest tokens you'll ever buy are the ones you already cached</title>
      <dc:creator>Developer at Fortitude Omnis Group</dc:creator>
      <pubDate>Tue, 22 Sep 2026 11:47:08 +0000</pubDate>
      <link>https://dev.to/fortitudeomnis/the-cheapest-tokens-youll-ever-buy-are-the-ones-you-already-cached-3bja</link>
      <guid>https://dev.to/fortitudeomnis/the-cheapest-tokens-youll-ever-buy-are-the-ones-you-already-cached-3bja</guid>
      <description>&lt;p&gt;Most agents throw a chunk of them away every single turn, and never see it happen.&lt;/p&gt;

&lt;p&gt;There are two levers on an AI coding bill. The big one is routing: send the easy work to a cheaper model, keep the frontier model for the hard slice, and measure whether the cheap one was actually good enough. That's where the large savings are, and you have to earn them.&lt;/p&gt;

&lt;p&gt;This is about the small lever. It's duller and it needs no judgement at all, which is rather the point. If you run an agent, you're almost certainly paying full price for tokens you'd already bought at a discount, over and over, and you can't see it happening.&lt;/p&gt;

&lt;p&gt;Here's the mechanism. When you send a request to Claude, the big unchanging part at the front (your system prompt, your tool definitions, the file context an agent reloads every turn) can be cached. The next request that starts with the same prefix reads it back at about a tenth of the normal input price. A cache hit is cheap. That's the whole deal, and it's a good one.&lt;/p&gt;

&lt;p&gt;A cache miss is the opposite. You pay the full fresh-read price for a prefix you'd already paid to store. Same tokens in, same answer out, higher price. You get nothing for the difference. It's the purest waste in the whole bill.&lt;/p&gt;

&lt;p&gt;And the cache is fragile in a way nobody warns you about. Change one byte near the front and the hit becomes a miss. The usual culprits are dull: a line ending that flipped from LF to CRLF, a trailing space, your tool definitions serialised in a different order this run, a timestamp injected into the system prompt, a volatile header, the order two chunks got concatenated. None of these change what the model sees in any way that matters. All of them bust the cache.&lt;/p&gt;

&lt;p&gt;A miss isn't a small, contained thing either. The cache matches your prefix from the front, token by token, and the instant it meets a byte that's changed, everything after it has to be recomputed. Claude Code builds that prefix in a fixed order: system prompt and tool definitions, then your static project memory (your CLAUDE.md and docs), then the conversation, then the new input. Put a volatile line near the top, a git branch, a coverage figure, a "last updated" stamp in the first few lines of a CLAUDE.md, and you don't pay for those few tokens. You pay to recompute the entire cached prefix beneath it, on every request that carries it. One changed byte before a breakpoint takes every cached segment after it down with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxwi8vj099ejgcmyxdzy6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxwi8vj099ejgcmyxdzy6.png" alt=" " width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On a single request the cost is pennies, sometimes less. Easy to wave away. But an agent doesn't send one request. It sends the same fat prefix thousands of times a day, and if a stray character busts the cache every turn, you re-pay every turn. It might not be much each time. It accumulates.&lt;/p&gt;

&lt;p&gt;Worth doing the sum once. Say an agent carries a 20k-token cached prefix, turns over 2,000 times a day, and a single stray CRLF busts the cache each turn. On a mid-tier Claude model at roughly $3 per million input tokens, with cached reads about a tenth of that, the gap between hit and miss on that prefix runs to something near $100 a day. From one character, on one agent. That's a deliberately unkind worst case, and your real figure will be smaller and messier, but you can see the shape of it. Nobody notices, because it never shows up as a line item. It's the bill being quietly larger than it needed to be.&lt;/p&gt;

&lt;p&gt;So we built the part that makes it visible, and where we safely can, the part that fixes it.&lt;/p&gt;

&lt;p&gt;OmnisRouter now labels why each cache miss happened. It watches the traffic going through it, works out the cause of every miss, and tags it. This is content-free: it classifies the reason, it doesn't read or keep your prompts. It hands that up to OmnisVigil as a receipt.&lt;/p&gt;

&lt;p&gt;OmnisVigil turns those receipts into a number you can act on, and it's honest about which misses count. A genuine edit to your prompt is a cache miss too, but it isn't waste, you meant to change it. Same for switching model or rewriting the system prompt. OmnisVigil separates those out and puts only the avoidable misses in the headline, attributed to the team, repo and commit that caused them. So the number you're looking at is the part you could actually recover, not a scary total you can't do anything about.&lt;/p&gt;

&lt;p&gt;Three of the avoidable causes fix themselves. Line endings, trailing whitespace and tool ordering are safe to normalise without changing a thing the model reads, so you flip them on in OmnisVigil and OmnisRouter cleans up future requests on the way through. The misses turn back into hits. No code change, no tradeoff. The other avoidable causes (an injected timestamp, a volatile header, a concatenation order that drifts) need a one-off change at your end, so OmnisVigil flags those with the evidence rather than touching them silently.&lt;/p&gt;

&lt;p&gt;I'm not going to quote you a savings figure. It's your workload, not ours, and measured beats claimed. What I'll say is that the dashboard shows you your own avoidable-waste number, and for most of it the fix is a toggle.&lt;/p&gt;

&lt;p&gt;Routing is the big lever. Cache hygiene is the small one, and it's the bit still leaking after you've done the routing and told yourself you're optimised. Both are open. Both are measured. If your agent bill is bigger than you can explain, this is a cheap place to start.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route: &lt;a href="https://omnisrouter.fortitude-omnis.group" rel="noopener noreferrer"&gt;OmnisRouter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Govern: &lt;a href="https://omnisvigil.fortitude-omnis.group" rel="noopener noreferrer"&gt;OmnisVigil&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Measure: &lt;a href="https://omnisbench.fortitude-omnis.group" rel="noopener noreferrer"&gt;OmnisBench&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>finops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I built a real-time flood digital twin of a UK river that runs entirely in the browser, GPU and on-device AI included</title>
      <dc:creator>Developer at Fortitude Omnis Group</dc:creator>
      <pubDate>Thu, 17 Sep 2026 08:32:59 +0000</pubDate>
      <link>https://dev.to/fortitudeomnis/i-built-a-real-time-flood-digital-twin-of-a-uk-river-that-runs-entirely-in-the-browser-gpu-and-90f</link>
      <guid>https://dev.to/fortitudeomnis/i-built-a-real-time-flood-digital-twin-of-a-uk-river-that-runs-entirely-in-the-browser-gpu-and-90f</guid>
      <description>&lt;p&gt;I've been chasing GPU cycles since machines had 64K of RAM, and every few years the browser quietly swallows another thing that used to need a workstation. WebGPU was the one that made me want to find the ceiling. So I set myself a question: how much of a genuinely serious flood tool could I put inside a single browser tab, with no server behind it, and still have it feel like a tool rather than a toy?&lt;/p&gt;

&lt;p&gt;The answer is Don Watch, a live flood-response digital twin of Sheffield's River Don.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://donwatch.fortitude-omnis.group" rel="noopener noreferrer"&gt;https://donwatch.fortitude-omnis.group&lt;/a&gt; (best on a desktop browser with WebGPU)&lt;/p&gt;

&lt;p&gt;It rebuilds the Don valley from open LIDAR elevation, drapes the real river over it, and replays the November 2019 Sheffield flood hour by hour from the Environment Agency's own gauge records. The water you watch rise is the level that was recorded at the time. Scrub the timeline and the flood tracks the readings. Switch to live mode and it runs against the EA's current feed instead.&lt;/p&gt;

&lt;p&gt;Then the parts that were interesting to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rendering runs on WebGPU
&lt;/h2&gt;

&lt;p&gt;Terrain, water and post-processing all go through three.js on its WebGPURenderer, shaded in TSL (Three's node shading language) rather than hand-written GLSL strings.&lt;/p&gt;

&lt;p&gt;The water isn't a flat plane with a normal map. It's a level-driven flood surface: the sim tracks the gauge height at the current instant, and a breadth-first fill spreads out from the real river vertices through submerged ground only, so isolated dips away from the river never fill. That height field is written to a texture the shader samples for both displacement and depth.&lt;/p&gt;

&lt;p&gt;The fiddliest bug was z-fighting at the shoreline. The draped water and the terrain are two different resolutions of the same heightfield, so at the waterline they interpenetrate. A depth-buffer bias can't fix surfaces that actually cross, so the fix was geometric: lift the water a couple of metres so it always sits cleanly above the ground, which is sub-pixel at the camera distance and invisible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI runs on your GPU too
&lt;/h2&gt;

&lt;p&gt;The pitch a lot of "AI X" tools make is a thin front end sat in front of somebody else's cloud. I wanted the reverse. The model, an open model in the 1-to-2-billion range, loads straight into the browser through WebLLM and runs on the visitor's own GPU. No server, no API key, nothing leaving the machine. The inference backend sits behind an interface, so WebLLM is the default and there's a coherent scripted fallback for devices that can't run it.&lt;/p&gt;

&lt;p&gt;There's a button that cuts the network, and everything keeps going, because the model is cached in OPFS and the flood data came down with the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agents that show their working, over deterministic maths
&lt;/h2&gt;

&lt;p&gt;Five small agents work the flood while it plays: an orchestrator hands out the job, two ingest agents read gauge and rainfall, a forecast agent projects ahead, a risk agent ranks the sites in the water's path, and a comms agent drafts a plain-language brief. Every step streams into a live reasoning feed and lights up a delegation graph.&lt;/p&gt;

&lt;p&gt;The important design decision is what the model is &lt;em&gt;not&lt;/em&gt; allowed to do. The hydrology, the projection and the risk thresholds are deterministic TypeScript. The model reads the results and writes them up. It doesn't produce the numbers. That's the honest place for a language model in a safety-adjacent tool, and it's why the whole thing is framed as a decision-support projection rather than a flood warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Offline by construction
&lt;/h2&gt;

&lt;p&gt;It ships as a Next.js static export and installs as a progressive web app. A service worker caches the shell, the data and the model, so once it has run it runs with the network cut, on a locked-down laptop or in a field office with no signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data is real and cited
&lt;/h2&gt;

&lt;p&gt;Gauge and rainfall from the Environment Agency, the river, landmarks and place names from OpenStreetMap, terrain from open elevation data, and the official flood-warning areas and live sensors from the EA flood-monitoring API. Nothing is fabricated, and where a series is reconstructed it says so.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few things I learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Real sensor networks are sparse. Central Sheffield has a handful of river-level gauges, not a dense grid. You show what's actually there and resist the urge to invent more.&lt;/li&gt;
&lt;li&gt;Serving a Next static export under a sub-path means every runtime fetch that basePath doesn't cover needs the prefix, and the service-worker scope moves with it.&lt;/li&gt;
&lt;li&gt;WebGPU plus a local LLM in one tab is a lot of GPU contention. Bracketing generation so the render loop can yield turned out to matter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have a poke around and let me know what you think: &lt;a href="https://donwatch.fortitude-omnis.group" rel="noopener noreferrer"&gt;https://donwatch.fortitude-omnis.group&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to go deeper on the WebGPU or the on-device model side in the comments.&lt;/p&gt;

</description>
      <category>webgpu</category>
      <category>javascript</category>
      <category>ai</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Hi, I'm Rob building OSPulse at Fortitude Omnis</title>
      <dc:creator>Developer at Fortitude Omnis Group</dc:creator>
      <pubDate>Mon, 14 Sep 2026 18:56:55 +0000</pubDate>
      <link>https://dev.to/fortitudeomnis/hi-dev-im-rob-building-ospulse-at-fortitude-omnis-2p33</link>
      <guid>https://dev.to/fortitudeomnis/hi-dev-im-rob-building-ospulse-at-fortitude-omnis-2p33</guid>
      <description>&lt;p&gt;Hi everyone! I'm Rob. I build OSPulse at Fortitude Omnis Group Ltd, a small indie company taking on the VC-funded supply-chain names (Snyk, Sonatype, Socket) with no funding and no sales team. The product page is the whole pitch.&lt;/p&gt;

&lt;p&gt;What it does: most dependency tools wait for a CVE. I care about the window before one exists, where a package changes hands, an npm account gets hijacked, or a maintainer quietly burns out. event-stream, ua-parser-js, polyfill.io and xz all showed their signs at the source weeks or months before anyone assigned a number. So OSPulse scores project health, maintainer churn, commit-velocity collapse, ownership changes, instead of the length of a vulnerability list. It ingests more than ten feeds, and I'll tell you which detections it misses as readily as the ones it catches.&lt;/p&gt;

&lt;p&gt;On the R&amp;amp;D side, the piece I'm proudest of is PoisonBox. Every npm install detonates in its own disposable microVM with hardware-level isolation. It boots, does its worst, and is deleted, so nothing it does touches your machine or your keys. Underneath, an eBPF probe in the kernel logs every process spawned, file opened, secret read and outbound connection, from below the code where the package can't hide. It then draws the causal thread from the instant a package reads your npm token to the connection that carried it off. With malicious packages the hard part is proving exactly what happened and in what order, and that trace is how.&lt;/p&gt;

&lt;p&gt;A confession, since honesty is about the only thing separating a security tool from its own marketing. Our checkout showed £249 a month while Stripe was quietly set to take £2,988 in a single hit, a figure that appeared nowhere in the journey. Two systems each holding a correct version of the same number, disagreeing only at the instant of the charge, the one moment the website never sees. It's fixed now (the price is one constant everything derives from, with a test that fails the build if the disclosure ever drops off the page), and it was caught before a single customer paid, which is the only reason I get to tell it as a story instead of a refund.&lt;/p&gt;

&lt;p&gt;Great to meet you all.&lt;/p&gt;

</description>
      <category>watercooler</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The fastest trigger produces no CVE</title>
      <dc:creator>Developer at Fortitude Omnis Group</dc:creator>
      <pubDate>Mon, 14 Sep 2026 18:49:30 +0000</pubDate>
      <link>https://dev.to/fortitudeomnis/the-fastest-trigger-produces-no-cve-5hj9</link>
      <guid>https://dev.to/fortitudeomnis/the-fastest-trigger-produces-no-cve-5hj9</guid>
      <description>&lt;p&gt;Every scanner you run is looking backwards.&lt;/p&gt;

&lt;p&gt;A CVE is a record of something already found. By the time one exists, a researcher has spotted the flaw, someone has assigned it an identifier, and a database has published it. That chain takes time. The attacks worth worrying about most don't wait for it.&lt;/p&gt;

&lt;p&gt;The uncomfortable part is what the industry's exploitation signals are built on. EPSS scores a CVE. CISA's Known Exploited Vulnerabilities catalogue lists CVEs seen being exploited in the wild. Your prioritisation queue sorts CVEs by severity. It's a decent machine for the problem it was built for, and completely blind to the attack that hasn't been given a number yet. That attack is usually the one moving fastest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment that matters has no identifier
&lt;/h2&gt;

&lt;p&gt;Look at how the supply-chain compromises everyone remembers actually unfolded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;event-stream, 2018.&lt;/strong&gt; A popular npm package, millions of installs a week, maintained by someone who'd stopped caring about it. A stranger offered to take it over and the maintainer said yes, which was a perfectly ordinary thing to do. The new owner shipped a couple of clean releases, then added a dependency carrying code that targeted one specific bitcoin wallet. No CVE existed at the moment of the handover. The handover was the signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ua-parser-js, 2021.&lt;/strong&gt; The maintainer's npm account was hijacked and three poisoned versions went out inside a few hours, carrying a cryptominer and a password stealer. The CVE came afterwards. Anyone who ran an install against a bad version in that window was hit before the advisory was written.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;polyfill.io, 2024.&lt;/strong&gt; No package, no CVE, nowhere for one to live. The domain serving a script to hundreds of thousands of sites was sold, and the new operator began injecting malware into what it returned. Control changed hands and the payload changed with it. No vulnerability database was ever going to tell you, because the whole event happened outside the model a database describes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;xz-utils, 2024.&lt;/strong&gt; This one did get a CVE, and it's the most instructive of the lot. A contributor spent close to two years building trust, earning commit rights, and leaning on an exhausted maintainer, before staging a backdoor in the release tarballs. CVE-2024-3094 was assigned only because one engineer noticed his SSH logins had got about half a second slower and went looking. The backdoor took months to build, and the human signals were on display the entire time: a new maintainer taking over a critical library, an original author visibly worn down, and a published tarball that no longer matched the git history. None of that is a vulnerability. All of it was a warning, and it all predated the number by the better part of a year.&lt;/p&gt;

&lt;p&gt;The shape repeats. Whatever made each package dangerous happened at the source, in the open, well before anyone recorded it. A scanner keyed on CVEs arrives after the interesting part is over.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we watch instead
&lt;/h2&gt;

&lt;p&gt;So we watch the source, and score the health of the project rather than the length of its vulnerability list.&lt;/p&gt;

&lt;p&gt;In practice that means tracking the things that move before an advisory does. Commit velocity that falls off a cliff, which is what abandonment looks like from the outside. Maintainer churn, and the bus factor underneath it, so a critical library resting on one tired person is something you can see rather than discover. Ownership and control changes, the npm handovers and the domain sales, treated as events in their own right. Release artefacts that drift from the source they claim to be built from.&lt;/p&gt;

&lt;p&gt;None of those needs a CVE to fire. Most of them precede one, and some of them, like polyfill.io, describe attacks that would never have produced a CVE at all. We're not trying to predict the future. We're trying to stand where the warning actually appears, at the time it appears, instead of waiting by the database for a number that shows up late or never.&lt;/p&gt;

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

&lt;p&gt;We still ingest the CVE feeds, KEV, OSV, and more than ten others. A known, scored, actively exploited vulnerability in something you ship is a real problem, and you should hear about it fast. This doesn't replace that work. It covers the window that work can't reach, the stretch between a package turning dangerous and the world writing it down.&lt;/p&gt;

&lt;p&gt;I'd rather be early and occasionally wrong about a project's health than perfectly precise about a CVE that landed a month too late to matter. The fastest trigger produces no CVE. If that's the trigger you care about, the CVE was never going to be where you found it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://ospulse.app/blog/the-fastest-trigger-produces-no-cve/" rel="noopener noreferrer"&gt;OSPulse blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>opensource</category>
      <category>devops</category>
      <category>supplychain</category>
    </item>
    <item>
      <title>The inner loop is back, and it's a matmul</title>
      <dc:creator>Developer at Fortitude Omnis Group</dc:creator>
      <pubDate>Sun, 30 Aug 2026 15:24:28 +0000</pubDate>
      <link>https://dev.to/fortitudeomnis/the-inner-loop-is-back-and-its-a-matmul-1ol1</link>
      <guid>https://dev.to/fortitudeomnis/the-inner-loop-is-back-and-its-a-matmul-1ol1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fephw2uixjh43nwh2fmv8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fephw2uixjh43nwh2fmv8.jpg" alt="AttentionSpan running: the live 3D attention view with its render and prompt controls, comparing two prompts side by side" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I started on 8-bit machines, writing games where you drew the triangles yourself and counted clock cycles in Intel's V-Tune until the inner loop stopped hurting. The whole job was getting under the abstraction and making the metal do exactly what you asked. Then compilers got good, machines got fast, and for twenty-odd years that skill went quiet.&lt;/p&gt;

&lt;p&gt;It came back, because a language model spends almost all its time doing the one operation I used to hand-tune on a 386: multiply a matrix by a matrix, add up the results, move on. So I worked on an inference engine by hand, in WGSL, running a real model in a browser tab, on kernels I can read line by line. Here's the road to it, and why it tells you something about your prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  2008: a model is a pile of numbers you can save to disk
&lt;/h2&gt;

&lt;p&gt;The instinct pointed me at neural networks in 2008, on a contract at the British Library. I wrote a 4 layer network for a project, starting it out in LINQPad (as I did with most throw away tests), in C#, to sort academic paper titles into Dewey Decimal subjects: physics, medicine, agriculture etc. Four layers. Turn the title into a bag of words, multiply by a matrix of weights, bend the result through a sigmoid so it can do more than draw straight lines, multiply again, read off the winner with a softmax cross entropy layer.&lt;/p&gt;

&lt;p&gt;Two things stuck. The maths is a matmul and a curve, over and over. And the knowledge it learns is nothing but those weight matrices. In my pre-school AI level knowledge, I called them synapses. Once trained, you serialise them to a file and that file &lt;em&gt;is&lt;/em&gt; the model, the same idea as the multi-gigabyte files on Hugging Face today, only mine was a few hundred kilobytes. The toy became real at the library: instead of my 62 sample titles I trained it on 1.6 million PDF titles, which took weeks on a 2008 Core i7 CPU . Expensive training makes the weights, a cheap forward pass uses them. That split is the whole LLM game, then and it's the same now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HLSL detour that paid off later
&lt;/h2&gt;

&lt;p&gt;Around then I built a small library of WPF pixel-shader effects in HLSL, the shader language for DirectX: colourise, desaturate, a skin-shade for softening portraits. Nothing to do with machine learning, mostly an excuse to get back to assembly-era fiddling, this time on a GPU. It put HLSL in my hands a decade before I actually needed it. Skills you pick up for one thing sit on the shelf and pay off in another. I love keeping the saw sharp.&lt;/p&gt;

&lt;h2&gt;
  
  
  2019: I wanted to run one on the GPU myself
&lt;/h2&gt;

&lt;p&gt;After the library I kept building nets for the fun of it. MNIST, the handwritten-digits, Hello World of Neural Networks. Then a CNN to spot a dog fouling a footpath, PoopScanner, which I was going to run on a Raspberry Pi and sell to councils with an audible "You're being watched. Clean that up.". The councils were not ready. Finally a network for recognising vehicle panel damage, this was ever so slightly before the frontier multimodal models came into being.&lt;/p&gt;

&lt;p&gt;But...they're all the same shape underneath: multiply by a matrix, apply a curve, do it again. And from the shader work I already knew how to drive a GPU, rustily, slowly &amp;amp; badly, but enough to make progress. So in 2019 I put the two together and started an inference engine in HLSL, to watch the kernels do the work rather than call someone else's library (there's a Donald Knuth line at the end that sums up why). I got a chunk working, then life / job / contracting happened and it sat half-finished for nearly a decade. I have a whole bunch of dumpster fire code sat rotting away just like this one, that might be useful to resurrect if Claude can help take the strain ;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Finishing it in a week, with an agent
&lt;/h2&gt;

&lt;p&gt;A few weeks ago I picked it up again, and since I'm pretty much AI first I'll be straight about how. I sat down with Claude and we finished it together, rewritten from HLSL into WGSL so it runs in a browser tab. The design is mine and decades old, the kernels are hand-written and I can read you almost every line of tghe HLSL, but I won't pretend I typed every character anymore. Claude did a lot of the typing. The English I write to steer a model is now as real a language as the HLSL I wrote for the GPU, and this project wanted both, for the cost of a few hundred million tokens out of my 20x subscription.&lt;/p&gt;

&lt;p&gt;The model I used in the demo is Qwen2.5-0.5B, downloaded from Hugging Face: a 265MB file of weights, the same species as my 2008 "synapses". Mine had a few thousand numbers, this has half a billion. Mine knew a few hundred words, this carries 151,936 pieces of text. Open it up and the inner loop is my C# toy again, 24 times over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tokens
&lt;/h2&gt;

&lt;p&gt;You type words. The model sees tokens, chunks of text with a number attached. "Paris" might be one token, "unbelievably" three. The tokeniser that splits them is a lookup table of about 151,000 pieces plus a set of merge rules. I had written mine in 2018 in plain C# with the help of a pre-built stemmer - Stemmers.Net. Claude re-wrote it in JavaScript and tested it against the real Qwen one until they matched on every character, because if they disagree by a single token the model produces confident gibberish and you lose a day blaming the maths. So 50% mine I guess, but the important part was that I &lt;em&gt;could&lt;/em&gt; write it if I had to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpj5dswh165dkbaajbfyh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpj5dswh165dkbaajbfyh.png" alt="The generated answer strung along a glowing ribbon, each token in its own labelled box" width="799" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each token becomes a list of 896 numbers, its vector embedding. A prompt is a stack of those vectors, and everything after is arithmetic on the stack. The model does one job forever: score all 151,936 possible next tokens, take the highest, append it, run again. That word-by-word streaming in every chatbot isn't an animation. It's the real speed showing that loop! Every word is a full pass through the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The inner loop is still a matmul
&lt;/h2&gt;

&lt;p&gt;In our tiny demo case, each pass runs the same block of maths 24 times: attention, then per-token crunching. Every step is dominated by one operation, a matrix multiply. The attention projections, the crunch, the final scoring against a 151,936-wide table, all matmul. So I wrote the matmul kernel by hand, and two old tricks from my assembly days make it fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tiling.&lt;/strong&gt; Memory is slow and there are millions of multiplies, so you don't fetch from GPU main memory each time. You grab a 16 by 16 block, park it in a scratchpad next to the compute units, and reuse it hard. Same instinct as hoisting a value out of a loop. This was similar to the 3d polygon filling with small textures on the CPU and using V-Tune to make sure that the cache never got thrown out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Four-bit weights.&lt;/strong&gt; The weights ship as 16-bit numbers, half a billion of them, about a gigabyte. Moving a gigabyte through the GPU per word is a real bottleneck, the fetching, not the multiplying. So I store each weight in 4 bits: take 32 of them, set a shared scale from the biggest, store the rest as small integers against it. A gigabyte becomes 265MB. You never unpack them back into memory, you unpack each one inside the loop at the moment you multiply. Cheap arithmetic to save expensive memory, the oldest trade there is.&lt;br&gt;&lt;br&gt;
On the LLM scale we're still on the tardigrade level - if you imagine GPT3 is ~175 billion parameters using 16bit floats, that's 2 bytes each  amounting to ~350GB. That model is old, we're now entering the age of trillion weight models!&lt;/p&gt;

&lt;h2&gt;
  
  
  Attention is you, steering. Steering IS your job.
&lt;/h2&gt;

&lt;p&gt;So, here's the part most explanations skip, and the one that connects the maths to what you do all day: writing prompts.&lt;/p&gt;

&lt;p&gt;Every layer, each token looks back and works out how much each earlier token matters right now. Each makes a query and a key, you multiply one token's query by another's key, a big number means relevant, softmax turns the scores into percentages, and you blend the earlier tokens in those proportions.&lt;/p&gt;

&lt;p&gt;So a prompt is you loading those weights by hand. Write "explain this simply" and the word "simply" sits in the context, and every word the model generates glances back at it and gets pulled towards something short and plain. Swap it for "rigorously" and the same weights write something denser, because the attention lands elsewhere.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;You didn't change the model, you changed what it looks at.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
That's the whole skill of prompt wording, no magic, just understanding the maths.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5zez0390u80jt746tb0o.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5zez0390u80jt746tb0o.gif" alt="The 3D attention view turning: a prompt's tokens on a glowing ribbon, with beams showing which earlier words each new word attends to" width="720" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And because I wrote the attention kernel, I've got those weights in hand ready to surface in something you can see. Usually the model throws them away after each word. I don't. So the demo can draw them: run a prompt and watch the earlier tokens light up to show what each new word leaned on and see the actual % numbers, change one word and watch the lights move. It'll even knock out each word of your prompt in turn when you check the MEASURE STEERING checkbox and measure which ones genuinely steer the answer, so you can watch a word do work or watch it get ignored.&lt;/p&gt;

&lt;p&gt;A 0.5B model with two attention heads is a small window so don't expect it to solve any complex problems beyond writing a haiku, and the big models will attend in subtler ways across many heads. But the mechanism is the mechanism, and seeing it once on something you can hold in your head changes how you read the large ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest numbers
&lt;/h2&gt;

&lt;p&gt;I raced my version against transformers.js, Hugging Face's tuned browser runtime, same model and machine, both on WebGPU. I know I can't win, but want to know if I'm still employable ;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmkkr6m6pbfnylj5b7nox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmkkr6m6pbfnylj5b7nox.png" alt="The race panel: hand-written WGSL at 11.6 tokens a second against transformers.js at 20.3, first-token times of 448ms and 593ms" width="800" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I win the first word: first token on screen in about 450ms against their 590, because my prefill is lean with no framework on top. They win sustained speed: warmed up, about 20 tokens a second to my 12, because a large team has spent far longer than a fortnight tuning it. Arguably the most important stat. Ask both "capital of France, one word" and they give the identical token, Paris. Ask something open-ended and they drift apart after a sentence, because my four-bit rounding and theirs aren't bit-for-bit the same. They agree where there's a right answer and drift where there isn't.&lt;br&gt;&lt;br&gt;
 That's the four-bit tax, visibly measured. Four bits is just a plaything here because I'm not bothered about showing 'correct' answers, &lt;strong&gt;&lt;em&gt;only how we reach them&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The point was never to win a benchmark. It was to understand every line, and get close enough to a tuned engine that "close" is the interesting word. I can live with my failure to match a team of 20.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two traps, because you'll hit them
&lt;/h2&gt;

&lt;p&gt;My output came out as pure garbage, all zeros then noise, and only once the whole thing was assembled, though every kernel passed its own test. The cause was one missing flag on a GPU buffer, the one that says "you may copy out of this". Without it, the copy filling the attention cache silently did nothing, so the model attended over zeros, which becomes divide-by-zero, which becomes NaN, which spreads through the network in a single pass, like a forest fire started with a dumb ass disposable barbecue. No crash, no console error, because WebGPU reports that class of mistake down a side channel you have to go and ask for. One flag, half a day. This never gets old.&lt;/p&gt;

&lt;p&gt;The other: I set a starting value one notch past the largest 32-bit float. The shader compiler rejected the whole file for that one constant, silently, and handed me a kernel that ran and did nothing. If you write GPU code, the compiler failing in silence will cost you the most hours. THE MOST HOURS. Check your shaders compiled at all. My fault again. I think I preferred C++ STL compiler errors to this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finally, coding isn't fun if you can't open the box
&lt;/h2&gt;

&lt;p&gt;There's a line from Knuth I keep coming back to.  &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The problem is that coding isn't fun if all you can do is call things out of a library, if you can't write the library yourself.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;He wasn't gatekeeping dependencies. He was mourning the job turning into plumbing between black boxes you're never allowed to open, and pointing out that once you can see inside one you can usually improve it. The "don't use what you couldn't write" version is the folk edit. For most libraries I'd pass it anyway, which is how I know it was never the point.&lt;br&gt;&lt;br&gt;
Doing tasks like this is what keeps me sane in a world where 90% of the profession is exactly that, parameter plumbing using other's code. Dull, yes, but it pays the mortgage, so no one complains, and it's not your employer's job to make it interesting.&lt;/p&gt;

&lt;p&gt;I could have just called transformers.js in four lines and had a chatbot by lunchtime, and for a product that's 100% the right call. But I wanted to open the box, to see what happens between the prompt and the words at the level of the arithmetic, because you can't tune what you can't see. Half a billion parameters, and at the bottom of the well it's still just a matmul I can read, and it's taken me nearly 20 years to get here.&lt;/p&gt;

&lt;p&gt;Anyway, it's live: type a prompt, watch the tokens land, race it against transformers.js, and turn on the attention view to watch your words steer it, at &lt;a href="https://attentionspan.fortitude-omnis.group/" rel="noopener noreferrer"&gt;attentionspan.fortitude-omnis.group&lt;/a&gt;. It's one of the builds from the &lt;a href="https://fortitude-omnis.group/rd/" rel="noopener noreferrer"&gt;Fortitude Omnis R&amp;amp;D lab&lt;/a&gt;. Forty years in, and understanding the inner loop is still as important as ever. Who'd have thought? Are we redundant yet?&lt;/p&gt;

&lt;p&gt;Any questions, comment &amp;amp; I'll get back to you :)&lt;/p&gt;

</description>
      <category>webgpu</category>
      <category>machinelearning</category>
      <category>llm</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I put my cost router on a neutral benchmark. It ranked near the bottom, and that's the interesting part</title>
      <dc:creator>Developer at Fortitude Omnis Group</dc:creator>
      <pubDate>Wed, 26 Aug 2026 13:37:36 +0000</pubDate>
      <link>https://dev.to/fortitudeomnis/i-put-my-cost-router-on-a-neutral-benchmark-it-ranked-near-the-bottom-and-thats-the-interesting-6bc</link>
      <guid>https://dev.to/fortitudeomnis/i-put-my-cost-router-on-a-neutral-benchmark-it-ranked-near-the-bottom-and-thats-the-interesting-6bc</guid>
      <description>&lt;p&gt;A few weeks back I repriced three months of my own Claude Code usage. Real traffic, not a demo: 39.5 billion tokens across 139,835 requests. At API rates that's about $30,000, and 91% of it went to Opus because that's what the default reaches for. Route the fraction a smaller model handles as well, and the bill drops by roughly 50 to 58%. Five or six grand a month, on work that was already done and graded.&lt;/p&gt;

&lt;p&gt;That's why OmnisRouter exists. It sits in front of your agents, reads each request, and sends it to the cheapest model that can answer it. The full write-up and the method are &lt;a href="https://dev.to/fortitudeomnis/i-repriced-40-billion-tokens-of-real-ai-coding-the-bill-goes-where-nobody-tells-you-453h"&gt;in the flagship post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Saving your own money on your own data is one thing. I wanted an outside check on somebody else's turf, with somebody else's scoring. So I took the router to RouterArena.&lt;/p&gt;

&lt;h2&gt;
  
  
  What RouterArena is
&lt;/h2&gt;

&lt;p&gt;RouterArena is an independent benchmark for LLM routers, out of an ICLR 2026 paper. It's a proper piece of work: 809 queries in the quick split, spread across 39 datasets, everything from MMLU-Pro and code generation to translation, chess and medical multiple-choice. You plug your router in, it picks a model per query, the harness runs that model and grades the answer, and you get an accuracy number, a cost number, and a combined Arena score. There's a hard rule I stuck to: you evaluate on their data, you never tune on it. No gaming the test.&lt;/p&gt;

&lt;p&gt;I wired OmnisRouter in with the pool I care about: GPT-5, Opus-5, Claude Haiku, and GPT-5-nano. The models a team running coding agents genuinely uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, two bugs, because honesty starts at home
&lt;/h2&gt;

&lt;p&gt;The first scored run came back at 42% accuracy, which was obviously wrong. So before I say a word about the result, here's what I found when I opened it up.&lt;/p&gt;

&lt;p&gt;Opus was scoring 0% on every single query. The scoring path was grabbing the model's extended-thinking block instead of its answer text, so the grader was marking a raw thinking object wrong 40 times out of 40. Second, my query file was missing the per-dataset answer-format instruction the grader keys on, so most models answered correctly in prose and got marked wrong because the grader couldn't find the answer in the shape it expected.&lt;/p&gt;

&lt;p&gt;Both fixed. Opus went from 0% to 85%, the overall number went from 42% to 72.7%, and abnormal entries went from 558 to zero. If you run a benchmark and the number looks too bad to be true, it usually is, and it's usually your plumbing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest result
&lt;/h2&gt;

&lt;p&gt;On its own pool, OmnisRouter scores 72.7% accuracy at $3.71 per thousand queries, for an Arena score of 0.669.&lt;/p&gt;

&lt;p&gt;That ranks it 16th out of 18. Near the bottom. On the easier split, even.&lt;/p&gt;

&lt;p&gt;I could stop there and let you think I'm burying it, so I won't. Here's the top of the cost board next to us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router              accuracy   cost/1K   Arena
Paix2-router          79.7%    $0.27     0.776
cross-router          78.2%    $0.29     0.762
sqwish-router         76.4%    $0.16     0.754
hybrid-router         71.4%    $0.04     0.721
omnisrouter           72.7%    $3.71     0.669
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the cost column. The leaders come in at four cents to twenty-nine cents per thousand. We come in at $3.71. That's the whole gap, and it isn't routing skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the cost board rewards
&lt;/h2&gt;

&lt;p&gt;I went and looked at what the top routers route to. Every one of them runs a cheap open-model pool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paix2 sends 78% of its traffic to a single flash model.&lt;/li&gt;
&lt;li&gt;cross-router and sqwish run mostly DeepSeek-v4-flash and Gemini-3.1-flash-lite.&lt;/li&gt;
&lt;li&gt;hybrid-router puts 95% of everything through one Qwen model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not one of the leaders uses GPT-5, Claude, or any frontier model. And that makes sense, because the Arena cost axis rewards spend, so the way to win it is to route to the cheapest models that still clear the accuracy bar on an academic test set. Fair enough as a benchmark design. But it means the board measures how cheap your pool is at least as much as how well you route it, and a router built for the premium models people run their agents on starts the race carrying a piano.&lt;/p&gt;

&lt;p&gt;I'm not knocking the benchmark, to be clear. It's careful and the accuracy scoring is sound. I'm saying the cost ranking answers a different question than the one I care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The warts, in full
&lt;/h2&gt;

&lt;p&gt;"The board's unfair to premium pools" is a convenient thing for me to say, so I ran the check that could prove me wrong. RouterArena also computes optimality metrics: how close each router gets to the oracle, the best model per query, within its own pool. That strips out the cheap-pool advantage entirely and measures routing quality on its own.&lt;/p&gt;

&lt;p&gt;Here's where we land:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router            Opt.Sel   Opt.Acc
Paix2-router       89.7%     100.0%
BARouter           64.4%      93.8%
agentforge         51.1%      98.7%
auto_router        37.7%      86.0%
r2-router          24.5%      99.8%
omnisrouter        23.7%      81.5%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Opt.Sel is how often the router picks the cheapest correct model. Opt.Acc is how much of its pool's achievable accuracy it captures. We're 8th of 13 on selection, and last of 13 on accuracy captured.&lt;/p&gt;

&lt;p&gt;That last number is the honest one. 81.5% means OmnisRouter leaves more winnable accuracy on the table than anyone else on the board, and it does that on purpose. It's a cost-first router. Faced with a query a cheaper model can probably handle, it takes the cheaper model and the saving, even when the strongest model in the pool would have nudged the answer over the line. Tune it to chase accuracy and that number climbs, but then it stops being the thing I built. Some of the high selection scores above come from routers that barely route at all, by the way: park 95% of traffic on one cheap model that's usually the cheapest-correct and you "win" selection by barely choosing at all. I'd rather show you my mediocre 23.7% and tell you why than dress that up as skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number that matters
&lt;/h2&gt;

&lt;p&gt;A leaderboard rank isn't the point, and I'm not going to pretend a benchmark built around cheap open models is the scoreboard OmnisRouter should be judged on. The point is the bill.&lt;/p&gt;

&lt;p&gt;On real Claude Code traffic, on the frontier models a working team runs every day, routing the cheaper-capable fraction cuts the cost by about half. The bill and the 91% Opus share are measured from three months of real requests. The saving is what comes off that bill once you route the work a smaller model handles, sized from the fraction OmnisBench measures as routable, so it's a reprice rather than a reroute I ran end to end. No academic test set moves it, and no cost board can rank it, because none of them run the models the work is happening on.&lt;/p&gt;

&lt;p&gt;If your agent bill has a comma in it and you don't know where the money goes, that's the problem worth solving. The leaderboard was me checking my own work in public. Turns out the honest version is more useful than a good rank would've been.&lt;/p&gt;

&lt;p&gt;OmnisRouter and the OmnisBench benchmark behind it are both open, Apache-2.0, and the numbers above are reproducible with RouterArena's own harness. Bring your own keys and check me.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>opensource</category>
      <category>benchmarking</category>
    </item>
    <item>
      <title>I repriced 40 billion tokens of real AI coding. The bill goes where nobody tells you</title>
      <dc:creator>Developer at Fortitude Omnis Group</dc:creator>
      <pubDate>Wed, 26 Aug 2026 08:23:37 +0000</pubDate>
      <link>https://dev.to/fortitudeomnis/i-repriced-40-billion-tokens-of-real-ai-coding-the-bill-goes-where-nobody-tells-you-453h</link>
      <guid>https://dev.to/fortitudeomnis/i-repriced-40-billion-tokens-of-real-ai-coding-the-bill-goes-where-nobody-tells-you-453h</guid>
      <description>&lt;p&gt;Everyone selling an LLM router will tell you it saves 60% on your AI bill. Nobody shows you the numbers on a real workload. So I pulled my own.&lt;/p&gt;

&lt;p&gt;Over the last three months I've run coding agents hard, Claude Code mostly, day in and day out. Every request it makes is logged locally with its token counts, so I parsed the lot: 1,231 sessions, 1.25 GB of logs, 139,835 billed requests, 1 June to 25 August. Here's what a real agent workload costs, and what routing would do to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The raw number
&lt;/h2&gt;

&lt;p&gt;39.5 billion tokens. At Anthropic's API rates that's about $29,962, call it $10,000 a month.&lt;/p&gt;

&lt;p&gt;Two things jump out.&lt;/p&gt;

&lt;p&gt;First, 91% of that cost is Opus. Almost every request went to the biggest, most expensive model, whether it needed it or not. A one-line rename, a test refactor, a "summarise this diff", all of it to the frontier model at frontier prices. That's not a criticism of me. It's the default, and the default is expensive.&lt;/p&gt;

&lt;p&gt;Second, and this surprised me, the bill is dominated by context, not output. The single biggest line is cache-read tokens, the whole conversation re-billed on every turn. By the hundredth turn of an agent session you're paying for the entire codebase again to add one function. Output is the small part.&lt;/p&gt;

&lt;h2&gt;
  
  
  What routing does to it
&lt;/h2&gt;

&lt;p&gt;The pitch for routing is simple. Send the work a cheaper model can handle to a cheaper model, and escalate to the frontier only when you need it. The question is how much of the work is genuinely cheap-model-able.&lt;/p&gt;

&lt;p&gt;My companion benchmark, OmnisBench, measures that on fresh coding problems the models can't have memorised. There, ideal routing matches the frontier model's quality at roughly 60% lower cost, because most coding requests don't need the biggest model.&lt;/p&gt;

&lt;p&gt;Apply that to my $30k. Route the 60% of requests a cheaper model handles down to a small model, keep the rest on Opus, and the equivalent bill drops to somewhere between $12,500 and $15,600. A 48 to 58% cut. About $5,000 to $6,000 a month, on a $10,000 workload. That lands right on the benchmark's number.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest bits
&lt;/h2&gt;

&lt;p&gt;Two things I won't paper over.&lt;/p&gt;

&lt;p&gt;I didn't pay $30k. I ran this through a Claude Max subscription, which already saves something like 98% versus the API. So this isn't a bill I paid, it's a realistic workload standing in for a team that does pay API rates. If your team runs agents on your own keys, this is roughly your shape.&lt;/p&gt;

&lt;p&gt;And the reprice is modelled, not proven. The 60% comes from the benchmark's aggregate, not from re-running every one of my requests through a cheaper model to confirm it passed. Proving that costs real API money, and it's the honest next step, not a number I'm going to pretend I already have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I trust the direction anyway
&lt;/h2&gt;

&lt;p&gt;Because there's now an independent check. RouterArena, an ICLR 2026 benchmark, ranks routers on accuracy against cost, and it found the loud commercial ones frequently over-pick expensive models. It's the neutral scoreboard the field didn't have. OmnisRouter is built for exactly what it measures: pick the cheapest model that's still correct, escalate when unsure, and show the receipt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do it yourself
&lt;/h2&gt;

&lt;p&gt;The whole point is that you don't have to take my word for it. Your Claude Code logs are on your disk, with the token counts, in &lt;code&gt;~/.claude/projects&lt;/code&gt;. Parse them, price them at API rates, and see your own split. Mine was 91% Opus. I'd bet yours is close.&lt;/p&gt;

&lt;p&gt;The three pieces, all open:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OmnisBench&lt;/strong&gt; measures which model each kind of work needs, on fresh problems, re-gradable offline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OmnisRouter&lt;/strong&gt; routes each request to the cheapest capable model, with a receipt on every response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OmnisVigil&lt;/strong&gt; rolls it up per team, so a lead sees the runaway agent before the invoice does.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tell me what your split looks like.&lt;/p&gt;

&lt;p&gt;Links: &lt;a href="https://github.com/Fortitude-Group/OmnisRouter" rel="noopener noreferrer"&gt;github.com/Fortitude-Group/OmnisRouter&lt;/a&gt; · &lt;a href="https://omnisbench.fortitude-omnis.group" rel="noopener noreferrer"&gt;omnisbench.fortitude-omnis.group&lt;/a&gt; · &lt;a href="https://omnisvigil.fortitude-omnis.group" rel="noopener noreferrer"&gt;omnisvigil.fortitude-omnis.group&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>We built a benchmark, then caught it strangling the models it was grading</title>
      <dc:creator>Developer at Fortitude Omnis Group</dc:creator>
      <pubDate>Fri, 21 Aug 2026 23:37:46 +0000</pubDate>
      <link>https://dev.to/fortitudeomnis/we-built-a-benchmark-then-caught-it-strangling-the-models-it-was-grading-27gl</link>
      <guid>https://dev.to/fortitudeomnis/we-built-a-benchmark-then-caught-it-strangling-the-models-it-was-grading-27gl</guid>
      <description>&lt;p&gt;A couple of day ago I posted about OmnisBench, our open benchmark for LLM routing, specifically our LLM Router &lt;a href="https://omnisbench.fortitude-omnis.group/" rel="noopener noreferrer"&gt;OmnisRouter&lt;/a&gt; , and made a fuss about how you can re-grade every number yourself because we publish the actual model responses. Two commenters, deanlee and jugeni, very politely pointed out that the whole thing might be resting on a fib.&lt;/p&gt;

&lt;p&gt;Their point: HumanEval and GSM8K are old. The models have almost certainly read the answers. So when I cheerfully reported that the cheapest model already scores 94.5% and routing only recovers the last few points, that's maybe less "small models are quietly brilliant" and more "small models have seen the exam paper." Fair. Annoyingly fair.&lt;/p&gt;

&lt;p&gt;So we did the thing you're supposed to do and actually built the fresh split we said we would.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fresh split
&lt;/h2&gt;

&lt;p&gt;The idea's simple. Take LiveCodeBench, which stamps every problem with a release date, and keep only the ones published after the models could plausibly have trained on them. Grade the same routing policies on the old, probably-memorised tasks and on the new, definitely-not tasks, side by side. If the routing story only survives on the old set, you deserve to know.&lt;/p&gt;

&lt;p&gt;I ran it. Here's where it took a turn.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first numbers, and a brief crisis of faith
&lt;/h2&gt;

&lt;p&gt;They came back grim. The cheap model fell off a cliff. Even the big expensive frontier model, the one whose entire job is to be good at this, was landing well under where a frontier model has any business landing. My first thought was that we'd accidentally built the most damning benchmark in the history of our own company, and my second was whether it was too late to go back to doing something honest for a living.&lt;/p&gt;

&lt;p&gt;Then I remembered the one feature I keep banging on about: we publish every response. So instead of trusting the sad little number, I opened the responses and read them.&lt;/p&gt;

&lt;p&gt;Every one of the frontier model's failures on the hard problems was the same thing: an empty answer. Not a wrong answer. Nothing at all. Eleven problems, eleven blank pages, each one exactly 4,096 tokens of the model thinking out loud and then having the microphone cut off before it wrote a single line of code.&lt;/p&gt;

&lt;p&gt;Here's what happened. Our request budget was 4,096 output tokens, a number that's perfectly sensible for "write this short Python function" and perfectly ridiculous for "here's a genuinely hard competitive-programming problem, think it through." The reasoning models did exactly what reasoning models do. They thought. At length. And then they hit the ceiling somewhere in the middle of it and got cut off before writing a line of the solution.&lt;/p&gt;

&lt;p&gt;So the benchmark wasn't measuring how good the models are at hard problems. It was measuring how good they are at running out of room. We'd built, with some effort, a very reproducible way to gag a model and then mark it absent.&lt;/p&gt;

&lt;p&gt;I'd love to tell you we spotted this through rigour and foresight. We spotted it because the responses were sitting right there in the file, empty, quietly judging us. Which is, if you squint, exactly the argument for publishing them. A closed benchmark makes this same mistake and ships the number, and everyone nods, because who's going to check.&lt;/p&gt;

&lt;h2&gt;
  
  
  The corrected numbers
&lt;/h2&gt;

&lt;p&gt;We made the output budget a config setting, gave the hard suite the room it needed, and ran it again.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Policy&lt;/th&gt;
&lt;th&gt;Likely-contaminated (20)&lt;/th&gt;
&lt;th&gt;Fresh, 2025+ (15)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cheapest model only (nano)&lt;/td&gt;
&lt;td&gt;90.0%&lt;/td&gt;
&lt;td&gt;60.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ideal routing (oracle)&lt;/td&gt;
&lt;td&gt;100.0%&lt;/td&gt;
&lt;td&gt;93.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;always the frontier model&lt;/td&gt;
&lt;td&gt;100.0%&lt;/td&gt;
&lt;td&gt;86.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;how often ideal routing reached for the frontier&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three things fall out of that, now the models can actually finish. The frontier model's fine: 86.7% on the fresh problems, not the panicked 60% the truncated run reported. The cheap model, though, drops from 90% on the old benchmarks to 60% on the fresh ones, which is the contamination showing through exactly as predicted. And routing earns far more on the fresh set: on the old tasks it buys ten points over the cheap model, on the fresh tasks it buys thirty-three, while reaching for the expensive model a fifth of the time instead of never. The honest asterisk: the fresh set's competitive-programming, which is both newer and harder than grade-school maths, so difficulty and freshness are tangled together here, and the samples are small. It's a first signal, not a verdict.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'll actually stand behind
&lt;/h2&gt;

&lt;p&gt;Two things, and I'll keep them honest because you can check both.&lt;/p&gt;

&lt;p&gt;The contamination point stands. Even with the models given all the room they wanted, the cheap one's thirty points worse on problems it can't have memorised. The "small models are quietly brilliant" story was, in part, small models sitting an exam they'd already seen.&lt;/p&gt;

&lt;p&gt;The harness point is the one I didn't expect to be writing about, and it's the more useful one. Half of what looks like a model being bad is a benchmark being broken, and you can't tell the difference from a leaderboard. You can only tell by reading the answers. If a routing benchmark, or a routing vendor, won't show you the responses, you've no way of knowing whether their impressive number is a real result or a bug wearing a nice suit.&lt;/p&gt;

&lt;p&gt;Cost of finding all this out: about $7.47. Four dollars of that was the run that produced the numbers above; the rest was the earlier runs that produced the wrong ones and taught us the lesson. I'm counting the mistakes, because a benchmark that hides its own costs has no business lecturing anyone about hidden numbers.&lt;/p&gt;

&lt;p&gt;Thanks to deanlee and jugeni for the nudge. The suite grows from here, more fresh problems and harder ones, and every number stays re-gradable offline with &lt;code&gt;omnisbench verify&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Results: &lt;a href="https://omnisbench.fortitude-omnis.group/" rel="noopener noreferrer"&gt;https://omnisbench.fortitude-omnis.group/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/Fortitude-Group/OmnisBench" rel="noopener noreferrer"&gt;https://github.com/Fortitude-Group/OmnisBench&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OmnisBench is built by &lt;a href="https://fortitude-omnis.group" rel="noopener noreferrer"&gt;Fortitude Omnis&lt;/a&gt;. We make small, sharp tools, and when we catch ourselves measuring the wrong thing, we write it up rather than quietly fixing the chart.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>opensource</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>I built an LLM router that hands you a receipt for every request</title>
      <dc:creator>Developer at Fortitude Omnis Group</dc:creator>
      <pubDate>Thu, 20 Aug 2026 11:15:55 +0000</pubDate>
      <link>https://dev.to/fortitudeomnis/i-built-an-llm-router-that-hands-you-a-receipt-for-every-request-193e</link>
      <guid>https://dev.to/fortitudeomnis/i-built-an-llm-router-that-hands-you-a-receipt-for-every-request-193e</guid>
      <description>&lt;p&gt;A router that decides where your money goes is asking for a lot of trust. It sits in front of every request, quietly picks a model, and sends you a bill at the end of the month. If it tells you it saved you 60%, you mostly have to take its word for it.&lt;/p&gt;

&lt;p&gt;I didn't want to take its word for it. So OmnisRouter attaches a receipt to every response, and the receipt is the whole point.&lt;/p&gt;

&lt;p&gt;It's an open source (Apache 2.0), self-hosted proxy that speaks the Anthropic, OpenAI and Gemini formats. You change one base URL, keep your own provider keys, and each request goes to the cheapest model that can actually handle it. The response comes back in your client's own format, with a note explaining what was chosen and what it cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The receipt
&lt;/h2&gt;

&lt;p&gt;Every response carries a set of headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight email"&gt;&lt;code&gt;&lt;span class="nt"&gt;X-Omnis-Model&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="na"&gt; gemini/gemini-2.5-flash&lt;/span&gt;
&lt;span class="nt"&gt;X-Omnis-Decision&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="na"&gt; Routed&lt;/span&gt;
&lt;span class="nt"&gt;X-Omnis-Confidence&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="na"&gt; 0.26&lt;/span&gt;
&lt;span class="nt"&gt;X-Omnis-Policy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="na"&gt; v3-omnisbench-2026-08-20&lt;/span&gt;
&lt;span class="nt"&gt;X-Omnis-Cost-Delta-Vs-Big&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="na"&gt; -0.0121&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you want to know what it'd do before spending anything, there's a cost-free endpoint that returns the full decision without calling a provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; localhost:8080/v1/route &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"authorization: bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"messages":[{"role":"user","content":"Summarize this thread."}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"policy_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v3-omnisbench-2026-08-20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ROUTED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cheapest_capable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chosen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gemini"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"model_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gemini-2.5-flash"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"est_cost_delta_vs_big_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-0.0121&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No screenshot, no dashboard you have to trust. The decision's in the response, and you can log it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it actually decides
&lt;/h2&gt;

&lt;p&gt;There's no network hop to work out where to send a request. A small pinned ONNX model (bge-small-en-v1.5) embeds the prompt in-process, the embedding maps to the nearest intent cluster, and the cluster's policy table picks the cheapest candidate that clears the quality bar for that kind of work. If it isn't confident, it escalates to a strong model rather than guessing. The whole thing happens in well under the 50ms I gave it as a budget.&lt;/p&gt;

&lt;p&gt;Here's a live trace from the shipped model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;Routed to&lt;/th&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Summarize this thread&lt;/td&gt;
&lt;td&gt;gemini-2.5-flash&lt;/td&gt;
&lt;td&gt;routed (cheap)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prove sqrt(2) is irrational&lt;/td&gt;
&lt;td&gt;gpt-5-nano&lt;/td&gt;
&lt;td&gt;routed (cheap)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write a Python function to merge two lists&lt;/td&gt;
&lt;td&gt;gpt-5&lt;/td&gt;
&lt;td&gt;routed (strong)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hello.&lt;/td&gt;
&lt;td&gt;claude-opus-5&lt;/td&gt;
&lt;td&gt;escalated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The maths row is the interesting one. On grade-school maths, the tiny gpt-5-nano is essentially as accurate as the frontier model, so OmnisRouter sends the work there and keeps the roughly 25x price difference. That's not a guess. It comes from measurement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the numbers come from
&lt;/h2&gt;

&lt;p&gt;The routing model isn't a black box I'm asking you to believe in. The intent clusters and the policy table ship in the repo and rebuild from public data, so the same inputs produce the same model, and every decision is stamped with the policy version that made it.&lt;/p&gt;

&lt;p&gt;The coding and maths policy is driven by &lt;a href="https://omnisbench.fortitude-omnis.group/" rel="noopener noreferrer"&gt;OmnisBench&lt;/a&gt;, a companion benchmark that grades each model per task and publishes every response so you can re-grade it offline. So when the router says "the cheap model is good enough here", that's a number you can go and check, not a line on a slide. Other domains use sensible estimates for now, and the benchmark coverage grows from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest bit
&lt;/h2&gt;

&lt;p&gt;Cheapest-capable isn't the same as cheapest. The point isn't to slam everything into the smallest model and hope. It's to spend frontier money only where the work needs it, and to be able to show, per request, why it went where it went. When a capability can't be carried faithfully to the chosen provider, it refuses with an explicit error instead of silently dropping it. Surprising the first time, correct every time.&lt;/p&gt;

&lt;p&gt;It runs as a single self-hosted process with an embedded database, your keys are encrypted at rest, and prompt content only ever leaves your infrastructure to go to the model you chose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have a look
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Site and the full routing story: &lt;a href="https://omnisrouter.fortitude-omnis.group/" rel="noopener noreferrer"&gt;https://omnisrouter.fortitude-omnis.group/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/Fortitude-Group/OmnisRouter" rel="noopener noreferrer"&gt;https://github.com/Fortitude-Group/OmnisRouter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The benchmark behind the routing: &lt;a href="https://omnisbench.fortitude-omnis.group/" rel="noopener noreferrer"&gt;https://omnisbench.fortitude-omnis.group/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can make it route something badly, or you think the receipt should carry more than it does, tell me. That's what the open model and the decision log are for.&lt;/p&gt;

&lt;p&gt;OmnisRouter is built by &lt;a href="https://fortitude-omnis.group" rel="noopener noreferrer"&gt;Fortitude Omnis&lt;/a&gt;. We make small, sharp tools, and we try not to lie in our own marketing.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>opensource</category>
      <category>ai</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Everyone is saving 60% on LLM costs. Nobody will show you the numbers.</title>
      <dc:creator>Developer at Fortitude Omnis Group</dc:creator>
      <pubDate>Wed, 19 Aug 2026 13:05:03 +0000</pubDate>
      <link>https://dev.to/fortitudeomnis/everyone-is-saving-60-on-llm-costs-nobody-will-show-you-the-numbers-5e7j</link>
      <guid>https://dev.to/fortitudeomnis/everyone-is-saving-60-on-llm-costs-nobody-will-show-you-the-numbers-5e7j</guid>
      <description>&lt;p&gt;Every few weeks the same post does the rounds. Someone's routing layer cut their LLM bill by 60%, "with no drop in quality", and here's a lovely chart to prove it.&lt;/p&gt;

&lt;p&gt;Except it doesn't prove anything. The eval set is private. The grading is private. The actual model responses that produced that score are never published. You're being asked to trust a screenshot, and we've been in I.T. long enough to know roughly what a screenshot is worth.&lt;/p&gt;

&lt;p&gt;So we built the thing that was missing. It's called OmnisBench, it's open source (Apache 2.0), and its whole job is to measure how good LLM routing actually is, in a way you can check for yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What routing is, and why it should save money
&lt;/h2&gt;

&lt;p&gt;If you're not knee deep in this yet: an LLM router looks at each request and sends it to the cheapest model that can still do the job, rather than firing everything at one expensive frontier model. Easy prompts go to something small. Hard ones go to the big model. In theory you keep most of the quality and pay a fraction of the bill.&lt;/p&gt;

&lt;p&gt;That's the theory. OmnisBench measures the practice.&lt;/p&gt;

&lt;p&gt;The number we care about most is what we call the oracle. For every task, the oracle picks the cheapest model that actually got it right. We know which ones did, because we ran them all. That is not a router you can ship. It's the ceiling. It's the best any router could possibly do on a given set of tasks, and it tells you how much money is genuinely on the table before anyone starts making claims.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first run
&lt;/h2&gt;

&lt;p&gt;We ran it over 364 tasks: HumanEval (164 coding problems, graded by their own unit tests) and GSM8K (200 maths problems, graded on the answer). The model pool was Claude Opus 5, GPT-5, Claude Haiku 4.5 and GPT-5-nano.&lt;/p&gt;

&lt;p&gt;Here is what came out.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Policy&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Task success&lt;/th&gt;
&lt;th&gt;Cost per 1,000 requests&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;oracle&lt;/td&gt;
&lt;td&gt;cheapest model that actually solved each task&lt;/td&gt;
&lt;td&gt;99.7%&lt;/td&gt;
&lt;td&gt;$0.62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;always-frontier&lt;/td&gt;
&lt;td&gt;everything to Claude Opus 5&lt;/td&gt;
&lt;td&gt;99.2%&lt;/td&gt;
&lt;td&gt;$6.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;random&lt;/td&gt;
&lt;td&gt;pick a model at random&lt;/td&gt;
&lt;td&gt;96.2%&lt;/td&gt;
&lt;td&gt;$4.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;always-cheapest&lt;/td&gt;
&lt;td&gt;everything to GPT-5-nano&lt;/td&gt;
&lt;td&gt;94.5%&lt;/td&gt;
&lt;td&gt;$0.43&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So the headline writes itself. Ideal routing hit 99.7% success for roughly 90% less than sending everything to the top model. Very nice.&lt;/p&gt;

&lt;p&gt;Now let me talk you out of it a little.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest bit
&lt;/h2&gt;

&lt;p&gt;Two things you will not see on most of those 60% charts.&lt;/p&gt;

&lt;p&gt;First, the oracle is a ceiling, not a product. It cheats. It knows, after the fact, which model got each task right, and no live router has that luxury. The real score for a router you can actually deploy is how close it gets to that oracle line, and the gap is the interesting part. Anyone quoting an oracle-style number as their shipping product is selling you the ceiling.&lt;/p&gt;

&lt;p&gt;Second, and this is the one that surprised us: the cheapest model on its own already scored 94.5%. On this particular set of tasks GPT-5-nano is just quietly good. So routing is not buying you a magic 60%. It's buying you the last few points of quality without paying frontier prices for all of it. That is still very much worth having. It just isn't the fairy tale.&lt;/p&gt;

&lt;p&gt;We would rather tell you that up front than have you find out after you have quoted the shiny number to your boss.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually matters: you can check it
&lt;/h2&gt;

&lt;p&gt;This is the whole reason OmnisBench exists. It does not ask you to trust the table above. It publishes the actual model response for every task, and ships a command that re-grades the lot offline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
python scripts/prepare_datasets.py                                  &lt;span class="c"&gt;# HumanEval + GSM8K&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; omnisbench.cli run    &lt;span class="nt"&gt;--config&lt;/span&gt; configs/v0.yaml &lt;span class="nt"&gt;--run&lt;/span&gt; runs/mine
python &lt;span class="nt"&gt;-m&lt;/span&gt; omnisbench.cli report &lt;span class="nt"&gt;--run&lt;/span&gt; runs/mine
python &lt;span class="nt"&gt;-m&lt;/span&gt; omnisbench.cli verify &lt;span class="nt"&gt;--run&lt;/span&gt; runs/2026-08-19               &lt;span class="c"&gt;# re-grades the published run, no API calls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;verify&lt;/code&gt; re-runs the graders against the published responses and rebuilds the entire leaderboard, cost included, without calling a single API or needing a key. Tamper with one stored answer and it fails. That is the point. If our numbers are wrong, you can prove it in about a minute, and we would genuinely like you to try.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have at it
&lt;/h2&gt;

&lt;p&gt;The results, the method and the honest caveats are all on the site, and the code is on GitHub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Results: &lt;a href="https://omnisbench.fortitude-omnis.group/" rel="noopener noreferrer"&gt;https://omnisbench.fortitude-omnis.group/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/Fortitude-Group/OmnisBench" rel="noopener noreferrer"&gt;https://github.com/Fortitude-Group/OmnisBench&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also build an actual open router, OmnisRouter, which this benchmark grades on every release, so it can't quietly regress and hope nobody notices. It is live now at &lt;a href="https://omnisrouter.fortitude-omnis.group/" rel="noopener noreferrer"&gt;https://omnisrouter.fortitude-omnis.group/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you can break the numbers, poke a hole in the method, or you just reckon we've measured the wrong thing, tell us. That is what open and verifiable is for.&lt;/p&gt;

&lt;p&gt;OmnisBench is built by &lt;a href="https://fortitude-omnis.group" rel="noopener noreferrer"&gt;Fortitude Omnis&lt;/a&gt;. We make small, sharp tools, and we try not to lie in our own marketing.&lt;/p&gt;

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