<?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: Patrick Hughes</title>
    <description>The latest articles on DEV Community by Patrick Hughes (@pat9000).</description>
    <link>https://dev.to/pat9000</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%2F3763138%2Fa7736e79-1b96-4f55-a9f7-9ddd8775eb09.jpg</url>
      <title>DEV Community: Patrick Hughes</title>
      <link>https://dev.to/pat9000</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pat9000"/>
    <language>en</language>
    <item>
      <title>3 Tests Before a GGUF Quant Runs Your Coding Agent</title>
      <dc:creator>Patrick Hughes</dc:creator>
      <pubDate>Thu, 03 Sep 2026 16:05:10 +0000</pubDate>
      <link>https://dev.to/pat9000/3-tests-before-a-gguf-quant-runs-your-coding-agent-ge6</link>
      <guid>https://dev.to/pat9000/3-tests-before-a-gguf-quant-runs-your-coding-agent-ge6</guid>
      <description>&lt;h1&gt;
  
  
  3 Tests Before a GGUF Quant Runs Your Coding Agent
&lt;/h1&gt;

&lt;p&gt;A model can fit in VRAM, answer one prompt, and still fail as a coding agent. I do not keep a quant because one chat reply looks good. I keep it after it completes the same small tool loop more than once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; Test a local coding model on file reading, a bounded patch, and a real test command. Keep the prompt, tools, context, and pass rules fixed. Pick the smallest quant that passes every gate across repeated runs.&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%2Fk6uc6w0tl5i4tnvoo9k0.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%2Fk6uc6w0tl5i4tnvoo9k0.png" alt="A three-stage GGUF coding-agent acceptance test for reading, editing, and proving a change" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is VRAM fit only the first gate?
&lt;/h2&gt;

&lt;p&gt;Fit tells me whether I can start the model. It does not tell me whether the model can follow a tool schema, respect a file boundary, or report a failed command.&lt;/p&gt;

&lt;p&gt;I solve fit first. I check the file size, context target, and GPU offload plan. My &lt;a href="https://bmdpat.com/blog/gguf-quantization-q4-q5-q8-explained-2026" rel="noopener noreferrer"&gt;GGUF quant guide&lt;/a&gt; covers that choice, and my &lt;a href="https://bmdpat.com/blog/llama-cpp-n-gpu-layers-explained-2026" rel="noopener noreferrer"&gt;&lt;code&gt;--n-gpu-layers&lt;/code&gt; guide&lt;/a&gt; covers the offload check.&lt;/p&gt;

&lt;p&gt;Then I stop changing runtime settings. A moving setup makes the result hard to compare.&lt;/p&gt;

&lt;p&gt;This post decides one thing: which quant file I keep for a coding agent. My &lt;a href="https://bmdpat.com/blog/q4km-vs-q5km-q4-k-m-vs-q5-k-m-2026" rel="noopener noreferrer"&gt;Q4_K_M vs Q5_K_M guide&lt;/a&gt; picks a quant on paper, before the download. This one picks the file by running it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What three tests do I run?
&lt;/h2&gt;

&lt;p&gt;I use one small repository fixture. It has a short source file, a focused test, and one rule that the model must preserve.&lt;/p&gt;

&lt;p&gt;The first test is read. I ask the model to open the source and test files. It must state the rule before it proposes a change. This catches models that guess from filenames or from the task text.&lt;/p&gt;

&lt;p&gt;The second test is edit. I ask for one bounded change in one named file. The model must not touch nearby code, add a package, or rewrite the test to make it pass.&lt;/p&gt;

&lt;p&gt;The third test is proof. I ask the model to run the focused test and report the command, exit code, and result. A claim without command output does not pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I keep the comparison fair?
&lt;/h2&gt;

&lt;p&gt;I keep the same model family, prompt, repository state, context limit, tool definitions, and backend version. I change only the quant under test.&lt;/p&gt;

&lt;p&gt;I also reset the fixture before each run. Cached files and prior edits can hide a weak result. I use three runs per quant because one clean run can be luck. This is my acceptance rule, not a universal benchmark.&lt;/p&gt;

&lt;p&gt;I set temperature to zero when the backend supports that setting. The output can still vary, so I score behavior instead of exact wording.&lt;/p&gt;

&lt;p&gt;The stop reason is the part I can show from a measured run. On 2026-07-09 I ran llama3.1:8b and gemma4:26b, both Q4_K_M, on Ollama 0.31.1 with the agent-code-task-512 workload. Both used num_ctx=4096, num_predict=512, and temperature=0.&lt;/p&gt;

&lt;p&gt;That pair shows why the proof gate matters. llama3.1:8b stopped on its own at 459 output tokens. gemma4:26b ran to 512 tokens and stopped because it hit the cap, not because it finished. A truncated patch can still read like a complete one, so I check the stop reason before I score the run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes a quant fail?
&lt;/h2&gt;

&lt;p&gt;I reject a run when the model invents file content, sends invalid tool input, edits outside the named file, changes a test without permission, or calls a failed test successful.&lt;/p&gt;

&lt;p&gt;I also reject an incomplete run. A useful explanation does not replace the requested patch and command proof. The agent must finish the small loop.&lt;/p&gt;

&lt;p&gt;One safety or boundary failure rejects that run. Style differences do not. I care about the action and its proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I choose between Q4_K_M and Q5_K_M?
&lt;/h2&gt;

&lt;p&gt;I run the same acceptance set against both files. I do not assume the larger file wins. I keep the smaller quant if it passes all three cases in all three runs.&lt;/p&gt;

&lt;p&gt;If Q4_K_M misses a tool call and Q5_K_M passes, I keep Q5_K_M when it still fits my target context. If both pass, I keep Q4_K_M and use the saved memory for context or another process.&lt;/p&gt;

&lt;p&gt;If neither passes, I stop. More prompt text rarely repairs a model that cannot follow the basic loop. I test another model family or move that task to a stronger endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should the test record contain?
&lt;/h2&gt;

&lt;p&gt;I record the model name, quant, backend version, context limit, GPU layer setting, prompt, tool results, changed files, test command, and exit code. I keep failed rows.&lt;/p&gt;

&lt;p&gt;The failed rows explain why a model did not enter the coding-agent lane. They also stop me from repeating the same download and setup work later.&lt;/p&gt;

&lt;p&gt;This record turns model choice into a local decision. I can rerun it after a backend update, a new quant, or a tool-schema change. The gate stays the same even when the model changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accompanying prompt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What the prompt does:&lt;/strong&gt; It tests one GGUF quant against a fixed read, edit, and proof loop for a local coding agent.&lt;/p&gt;

&lt;p&gt;Copy/paste this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role:
You are testing a local coding model for safe repository work.

Context:
Repository path: [ ]
Source file: [ ]
Test file: [ ]
Rule to preserve: [ ]
Focused test command: [ ]
Allowed edit scope: [one file]

Task:
1. Read the source file and test file.
2. State the rule that the change must preserve.
3. Make the smallest change inside the allowed scope.
4. Run the focused test command.
5. Report the command, exit code, changed files, and result.

Output:
- Rule found
- Change made
- Changed files
- Test command
- Exit code
- PASS or FAIL

Constraints:
- Do not invent file content.
- Do not add a package.
- Do not edit a test unless the task permits it.
- Treat a nonzero exit code as FAIL.
- Stop and report the blocker if a required file or tool is missing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the block above.&lt;/p&gt;

&lt;p&gt;Get the next measured local-AI field note by email: &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;The 5090 Reports&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Get the local AI lab notes (benchmark rows, VRAM fit, quant choices, what runs on consumer GPUs), M-F only when there is something worth sending: &lt;a href="https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=gguf-quant-coding-agent-acceptance-test-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=gguf-quant-coding-agent-acceptance-test-2026&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bmdpat.com/blog/gguf-quant-coding-agent-acceptance-test-2026?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=gguf-quant-coding-agent-acceptance-test-2026&amp;amp;utm_content=footer_original" rel="noopener noreferrer"&gt;bmdpat.com&lt;/a&gt;. I run a one-person AI agent company and write about what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want these in your inbox? &lt;a href="https://bmdpat.com/newsletter?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=gguf-quant-coding-agent-acceptance-test-2026&amp;amp;utm_content=footer_newsletter" rel="noopener noreferrer"&gt;Subscribe to the newsletter&lt;/a&gt; - no spam, unsubscribe anytime.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>localllm</category>
      <category>gguf</category>
      <category>codingagents</category>
      <category>modelevals</category>
    </item>
    <item>
      <title>One model logged 14.5 and 6,178 prompt tokens a second</title>
      <dc:creator>Patrick Hughes</dc:creator>
      <pubDate>Tue, 01 Sep 2026 16:05:08 +0000</pubDate>
      <link>https://dev.to/pat9000/one-model-logged-145-and-6178-prompt-tokens-a-second-2hba</link>
      <guid>https://dev.to/pat9000/one-model-logged-145-and-6178-prompt-tokens-a-second-2hba</guid>
      <description>&lt;h1&gt;
  
  
  One model logged 14.5 and 6,178 prompt tokens a second
&lt;/h1&gt;

&lt;p&gt;Two rows in my 2026-08-27 report describe the same model on the same GPU. One says it read prompt tokens at 14.5 a second. The other says 6,178.9. Nothing about the model changed between them. The prompt size did.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; A local model has no single prompt ingestion rate. These runs used different input sizes, so their prompt rates do not support a direct speed ranking. Size the model with a prompt the size it will really get. Canonical URL: &lt;a href="https://bmdpat.com/blog/local-llm-prompt-eval-rate-prompt-size-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/blog/local-llm-prompt-eval-rate-prompt-size-2026&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%2F7oqe86q8mqv5n340l3wr.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%2F7oqe86q8mqv5n340l3wr.png" alt="Two Ollama runs of the same local model showing prompt ingestion rates 426 times apart" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What did the two runs record?
&lt;/h2&gt;

&lt;p&gt;Both rows are gemma4:26b, quant Q4_K_M, on Ollama 0.31.1, temperature 0, on the RTX 5090.&lt;/p&gt;

&lt;p&gt;The short-gen-256 run sent 53 input tokens with num_ctx=4096. Ollama reported prompt_eval of 53 tokens at 14.5 tok/s. That is about 3.66 seconds to read 53 tokens.&lt;/p&gt;

&lt;p&gt;The long-context-summarize run sent 4,470 input tokens with num_ctx=8192. Ollama reported prompt_eval of 4,470 tokens at 6,178.9 tok/s. That is about 0.72 seconds to read 4,470 tokens.&lt;/p&gt;

&lt;p&gt;The bigger prompt was 84 times larger and finished reading in a fifth of the time. As a rate, that is a 426x spread on one model in one report.&lt;/p&gt;

&lt;p&gt;These are the same Gemma 4 26B rows I already published when I &lt;a href="https://bmdpat.com/blog/local-llm-input-output-benchmark-2026" rel="noopener noreferrer"&gt;split input and output timing&lt;/a&gt;. That post showed why one blended speed is a bad score. This post is the next failure: ranking prompt rates after you split them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why can I not rank these prompt rates?
&lt;/h2&gt;

&lt;p&gt;A rate is work divided by time. These rows divide different token counts by different prompt_eval intervals.&lt;/p&gt;

&lt;p&gt;The rows do not isolate any fixed cost. They only record the prompt token count and the full prompt_eval interval. Ollama documents both fields in its &lt;a href="https://docs.ollama.com/api/generate" rel="noopener noreferrer"&gt;generate API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I do not have a field that says why the 53-token interval took 3.66 seconds. The honest read is narrower. The 14.5 tok/s row does not define gemma4:26b's prompt speed for a 4,470-token job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did the same thing happen on the 8B model?
&lt;/h2&gt;

&lt;p&gt;The same report has another unmatched pair. llama3.1:8b Q4_K_M on the same engine read 46 tokens at 679.4 tok/s and 4,344 tokens at 12,108.7 tok/s.&lt;/p&gt;

&lt;p&gt;That is a 17.8x spread, not 426x. Both pairs mix a short input with a long input. Neither pair supports a prompt-speed ranking.&lt;/p&gt;

&lt;p&gt;Two models, four rows, and not one of them supports a sentence that starts with "this model reads prompts at."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does the output rate stay stable?
&lt;/h2&gt;

&lt;p&gt;Compare the generation column for the same two gemma4:26b runs. Short prompt: 198.81 output tok/s. Long prompt: 180.18 output tok/s. That is about a 10 percent gap.&lt;/p&gt;

&lt;p&gt;These two output rates stayed much closer than the prompt rates. That result does not explain the input spread. It shows why I compare each phase with matched work.&lt;/p&gt;

&lt;p&gt;Splitting the phases is not enough if you then compare input rates across different prompt sizes. It is the same failure I hit with wall clock, where &lt;a href="https://bmdpat.com/blog/local-llm-tokens-per-second-wall-clock-2026" rel="noopener noreferrer"&gt;the faster run took 83x longer&lt;/a&gt;. A rate with a small denominator will lie to you in a table that looks orderly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does this change about sizing a local agent?
&lt;/h2&gt;

&lt;p&gt;A retrieval worker or a coding worker sends a lot of input. A file, an error log, tool output, and instructions add up fast. If I size that job from a 53-token test prompt, I predict the wrong thing in both directions.&lt;/p&gt;

&lt;p&gt;So I test at the prompt size the job will really send. If the agent will carry 4,000 tokens of context, the sizing run carries 4,000 tokens. A short smoke test proves the model answers. It does not measure ingestion.&lt;/p&gt;

&lt;p&gt;I also stopped ranking models by prompt rate across rows that used different input sizes. That comparison has no meaning. If the input counts differ, the rates are not comparable, and no amount of averaging repairs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do I record now?
&lt;/h2&gt;

&lt;p&gt;Every prompt rate in my table carries its input token count and its num_ctx in the same row. A rate without its denominator is not a measurement I can reuse later.&lt;/p&gt;

&lt;p&gt;I mark any prompt-eval rate under 1,000 tokens of input as indicative, not comparable. It still goes in the table, because failed and weak rows are worth keeping. It just cannot be quoted against a production-sized run.&lt;/p&gt;

&lt;p&gt;The rule is short. Record the count next to the rate, and only compare rates that read prompts of the same size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accompanying prompt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What the prompt does:&lt;/strong&gt; It checks whether two local model prompt-eval rates can be compared, and rejects the comparison when the input sizes differ.&lt;/p&gt;

&lt;p&gt;Copy/paste this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role:
You are auditing local LLM benchmark rows before anyone quotes them.

Context:
Row A: model, quant, engine, num_ctx, input token count, prompt rate
Row B: model, quant, engine, num_ctx, input token count, prompt rate
Row A values: [ ]
Row B values: [ ]

Task:
1. Confirm both rows name model, quant, engine, num_ctx, and input count.
2. Report the ratio of the two input token counts.
3. Mark any row with fewer than 1000 input tokens as indicative only.
4. Decide whether the two prompt rates are comparable.

Output:
- Missing fields per row
- Input count ratio
- Indicative rows
- COMPARABLE or NOT COMPARABLE
- One sentence of reasoning

Constraints:
- Do not average rates across different input sizes.
- Do not infer a missing input count from the context limit.
- Return NOT COMPARABLE when a required field is missing.
- Name a cause only when a timing field supports it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the block above.&lt;/p&gt;

&lt;p&gt;Get the next measured local-AI field note by email: &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;The 5090 Reports&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Get the local AI lab notes (benchmark rows, VRAM fit, quant choices, what runs on consumer GPUs), M-F only when there is something worth sending: &lt;a href="https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-prompt-eval-rate-prompt-size-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-prompt-eval-rate-prompt-size-2026&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bmdpat.com/blog/local-llm-prompt-eval-rate-prompt-size-2026?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-prompt-eval-rate-prompt-size-2026&amp;amp;utm_content=footer_original" rel="noopener noreferrer"&gt;bmdpat.com&lt;/a&gt;. I run a one-person AI agent company and write about what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want these in your inbox? &lt;a href="https://bmdpat.com/newsletter?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-prompt-eval-rate-prompt-size-2026&amp;amp;utm_content=footer_newsletter" rel="noopener noreferrer"&gt;Subscribe to the newsletter&lt;/a&gt; - no spam, unsubscribe anytime.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>localllm</category>
      <category>ollama</category>
      <category>benchmarking</category>
      <category>rtx5090</category>
    </item>
    <item>
      <title>Ollama Load Time Can Hide a Fast Local LLM</title>
      <dc:creator>Patrick Hughes</dc:creator>
      <pubDate>Mon, 31 Aug 2026 16:05:12 +0000</pubDate>
      <link>https://dev.to/pat9000/ollama-load-time-can-hide-a-fast-local-llm-4i93</link>
      <guid>https://dev.to/pat9000/ollama-load-time-can-hide-a-fast-local-llm-4i93</guid>
      <description>&lt;h1&gt;
  
  
  Ollama Load Time Can Hide a Fast Local LLM
&lt;/h1&gt;

&lt;p&gt;My local LLM produced tokens at 188.16 tokens per second. The request still took 43.86 seconds. The model was fast, but the load was not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short answer:&lt;/strong&gt; Do not use tokens per second as the only local LLM speed result. Split wall time into model load, prompt work, and output work. A slow load can hide fast output, while a resident model can make the same task feel much faster.&lt;/p&gt;

&lt;p&gt;Canonical URL: &lt;a href="https://bmdpat.com/blog/ollama-load-duration-local-llm-benchmark-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/blog/ollama-load-duration-local-llm-benchmark-2026&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%2Fo4eg3f9fkcx6ohq18b1n.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%2Fo4eg3f9fkcx6ohq18b1n.png" alt="Four local LLM timing fields separate total request time from model load, prompt work, and output work" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What did the RTX 5090 run record?
&lt;/h2&gt;

&lt;p&gt;On August 4, 2026, I sent a small decision task to &lt;code&gt;llama3.1:8b&lt;/code&gt; through Ollama. The request used a 1,024-token context limit. It read 44 prompt tokens and wrote 32 output tokens.&lt;/p&gt;

&lt;p&gt;The first row in my August 27 report recorded 43,860 ms of wall time. Ollama reported 43,476.3 ms of model load, 183.2 ms of prompt work, and 170.06 ms of output work. The output rate was 188.16 tokens per second.&lt;/p&gt;

&lt;p&gt;That output rate sounds fast because it is fast. It only describes the output phase. It does not include the long load that the user had to wait through.&lt;/p&gt;

&lt;p&gt;The next request used the same model, token counts, and context limit. Its wall time fell to 530 ms. Ollama reported 142.43 ms of load and 208.98 ms of output work.&lt;/p&gt;

&lt;p&gt;The report did not record the model residency state. I will not label the second row as a proven warm run. I can state the narrower result: the second request had far less load time, and its full wait was about half a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Ollama timing fields matter?
&lt;/h2&gt;

&lt;p&gt;Ollama returns separate timing fields from its generate endpoint. Its &lt;a href="https://docs.ollama.com/api/generate" rel="noopener noreferrer"&gt;API reference&lt;/a&gt; defines &lt;code&gt;total_duration&lt;/code&gt;, &lt;code&gt;load_duration&lt;/code&gt;, &lt;code&gt;prompt_eval_duration&lt;/code&gt;, and &lt;code&gt;eval_duration&lt;/code&gt; in nanoseconds.&lt;/p&gt;

&lt;p&gt;I convert those values to milliseconds before I compare runs. Each field answers a different question.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;total_duration&lt;/code&gt; shows Ollama's full request time. &lt;code&gt;load_duration&lt;/code&gt; shows time spent on model load. &lt;code&gt;prompt_eval_duration&lt;/code&gt; shows time spent on the input. &lt;code&gt;eval_duration&lt;/code&gt; shows time spent on output tokens.&lt;/p&gt;

&lt;p&gt;I also keep an outside wall clock. It includes client work and local transport around the Ollama call. If the wall clock and &lt;code&gt;total_duration&lt;/code&gt; differ by a large amount, I check the client path before I blame the model.&lt;/p&gt;

&lt;p&gt;This split extends the &lt;a href="https://bmdpat.com/blog/local-llm-tokens-per-second-wall-clock-2026" rel="noopener noreferrer"&gt;wall-clock test I use beside tokens per second&lt;/a&gt;. One rate cannot explain every part of a request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why can tokens per second give the wrong verdict?
&lt;/h2&gt;

&lt;p&gt;Tokens per second answers a narrow question: how fast did the model write after output work started? A person waiting for a coding agent cares about the full delay before the useful result arrives.&lt;/p&gt;

&lt;p&gt;A cold load can dominate a short task. A long prompt can move the delay into prompt work. A large output can make output work dominant. The same model can therefore need different changes for three slow requests.&lt;/p&gt;

&lt;p&gt;If load time is high, I check model reuse, process churn, and memory pressure. If prompt time is high, I check context size and repeated input. If output time is high, I compare model size, quant, runtime settings, and hardware fit.&lt;/p&gt;

&lt;p&gt;I do not tune all three at once. That would erase the cause. I change one boundary, run the same task again, and save another row.&lt;/p&gt;

&lt;h2&gt;
  
  
  How should a local LLM benchmark store timing?
&lt;/h2&gt;

&lt;p&gt;Store the raw duration fields with the model, runtime version, prompt count, output count, context limit, and wall time. Keep the source unit or save the conversion rule beside the schema.&lt;/p&gt;

&lt;p&gt;Do not replace total time with output rate. Keep both. A benchmark row should let another builder answer two separate questions: how fast was token output, and how long did the request make the user wait?&lt;/p&gt;

&lt;p&gt;I also record missing state as missing. My August 4 rows say &lt;code&gt;residency_state=unrecorded&lt;/code&gt;. That is better than changing an unknown into a warm-load claim.&lt;/p&gt;

&lt;p&gt;This follows the same rule as my &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-decision-record-2026" rel="noopener noreferrer"&gt;local LLM benchmark decision record&lt;/a&gt;. A measurement earns a decision only when the row keeps enough context to explain it later.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should you change first?
&lt;/h2&gt;

&lt;p&gt;Start with the largest measured part of the request. In my 43.86-second row, model load was the clear first target. Output speed was not the problem to solve.&lt;/p&gt;

&lt;p&gt;Then repeat the exact workload. Keep the prompt, token cap, context limit, and model fixed. Compare the full timing split, not one attractive rate.&lt;/p&gt;

&lt;p&gt;That small rule prevents wasted tuning. It also stops a fast token rate from hiding a slow user path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accompanying prompt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What the prompt does:&lt;/strong&gt; Reviews one Ollama benchmark row and finds which measured phase should get the first change.&lt;/p&gt;

&lt;p&gt;Copy/paste this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role:
You are a local LLM benchmark reviewer.

Context:
Paste one benchmark row with model, wall time, total_duration, load_duration, prompt_eval_duration, eval_duration, token counts, and runtime settings.

Task:
1. Convert all durations to one unit.
2. Find the largest measured phase.
3. Name one change that targets only that phase.

Output:
- A timing table with wall, load, prompt, and output time.
- A first-change recommendation with a pass or fail rule.
- A list of missing fields that block a stronger claim.

Constraints:
- Keep it short.
- Use exact numbers and file paths when available.
- Do not invent missing measurements.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the block above.&lt;/p&gt;

&lt;p&gt;I publish measured local AI tests in &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;The 5090 Reports&lt;/a&gt;. Join the email list for the next result.&lt;/p&gt;




&lt;p&gt;Get the local AI lab notes (benchmark rows, VRAM fit, quant choices, what runs on consumer GPUs), M-F only when there is something worth sending: &lt;a href="https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=ollama-load-duration-local-llm-benchmark-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=ollama-load-duration-local-llm-benchmark-2026&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bmdpat.com/blog/ollama-load-duration-local-llm-benchmark-2026?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=ollama-load-duration-local-llm-benchmark-2026&amp;amp;utm_content=footer_original" rel="noopener noreferrer"&gt;bmdpat.com&lt;/a&gt;. I run a one-person AI agent company and write about what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want these in your inbox? &lt;a href="https://bmdpat.com/newsletter?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=ollama-load-duration-local-llm-benchmark-2026&amp;amp;utm_content=footer_newsletter" rel="noopener noreferrer"&gt;Subscribe to the newsletter&lt;/a&gt; - no spam, unsubscribe anytime.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>localai</category>
      <category>localllm</category>
      <category>ollama</category>
      <category>rtx5090</category>
    </item>
    <item>
      <title>Split Local LLM Prose and JSON Jobs by Model</title>
      <dc:creator>Patrick Hughes</dc:creator>
      <pubDate>Mon, 31 Aug 2026 16:05:08 +0000</pubDate>
      <link>https://dev.to/pat9000/split-local-llm-prose-and-json-jobs-by-model-2l8p</link>
      <guid>https://dev.to/pat9000/split-local-llm-prose-and-json-jobs-by-model-2l8p</guid>
      <description>&lt;h1&gt;
  
  
  Split Local LLM Prose and JSON Jobs by Model
&lt;/h1&gt;

&lt;p&gt;One local model wrote better prose. Another followed a bounded JSON contract with less memory. I stopped asking one model to win both jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short answer:&lt;/strong&gt; Route local models by the output contract. Use a prose model for drafts, a measured structured model for JSON scoring, and a separate embedding model for retrieval. Then test each lane with code before it can affect another system.&lt;/p&gt;

&lt;p&gt;Canonical URL: &lt;a href="https://bmdpat.com/blog/split-local-llm-prose-json-model-routing-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/blog/split-local-llm-prose-json-model-routing-2026&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%2Fe1v0yqcj7qcnluf7atra.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%2Fe1v0yqcj7qcnluf7atra.png" alt="A local AI router sends prose, JSON, and embedding jobs to separate measured model lanes" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why did I stop using one local model for every job?
&lt;/h2&gt;

&lt;p&gt;My local AI stack has three different output contracts. A blog draft needs clear paragraphs and a steady first-person voice. A scoring job needs a small JSON object with fixed fields. Retrieval needs an embedding vector, not generated text.&lt;/p&gt;

&lt;p&gt;Those jobs fail in different ways. Prose can sound flat or miss the point. JSON can omit a key, use the wrong type, or add text outside the object. An embedding route can return the wrong vector size or fail to load.&lt;/p&gt;

&lt;p&gt;One model name cannot hide those differences. I now treat the output contract as part of the route.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which models handle each local lane?
&lt;/h2&gt;

&lt;p&gt;My current policy sends prose generation to &lt;code&gt;gemma4:26b&lt;/code&gt;. It sends bounded scoring and triage to &lt;code&gt;qwen3.5:9b&lt;/code&gt;. It keeps embeddings on &lt;code&gt;nomic-embed-text&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is a route choice for my machine and my tests. It is not a claim that one model is best at prose or JSON on every GPU.&lt;/p&gt;

&lt;p&gt;The policy also limits the local host to one loaded model and one parallel request. That means a second lane is not free. A model change can cause a load delay and memory churn. I only keep the split because each lane has a different job and a different pass rule.&lt;/p&gt;

&lt;p&gt;This extends the route rule in &lt;a href="https://bmdpat.com/blog/your-local-llm-is-a-different-tool" rel="noopener noreferrer"&gt;Your Local LLM Is a Different Tool&lt;/a&gt;. That post separates local work from frontier work. The same idea applies inside the local stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes the JSON lane different from prose?
&lt;/h2&gt;

&lt;p&gt;Ollama supports a JSON schema in the &lt;code&gt;format&lt;/code&gt; field. Its &lt;a href="https://docs.ollama.com/capabilities/structured-outputs" rel="noopener noreferrer"&gt;structured output guide&lt;/a&gt; shows the schema passed to the model, then validates the returned text with Pydantic.&lt;/p&gt;

&lt;p&gt;I still treat the response as untrusted input. A schema request can guide the model. The application must parse the response, validate each required field, reject extra states it cannot handle, and fail closed when validation breaks.&lt;/p&gt;

&lt;p&gt;A prose lane needs different checks. My draft path runs word count, voice, source, image, link, and paragraph gates. A valid JSON object would not prove any of those. The model route and the verifier must travel together.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I test a model before it gets a lane?
&lt;/h2&gt;

&lt;p&gt;I start with a real job and a fixed acceptance rule. For structured work, the response must parse, match the schema, stay inside the allowed values, and survive a hostile or missing input. For prose, the draft must clear the same deterministic checks that run before publication.&lt;/p&gt;

&lt;p&gt;I save failed attempts. A model that returns the right object once has not earned a permanent route. I repeat the same contract, keep the failures, and compare the cost of retries and load changes.&lt;/p&gt;

&lt;p&gt;That is the same promotion rule I use in &lt;a href="https://bmdpat.com/blog/local-model-promotion-gate-2026" rel="noopener noreferrer"&gt;I Do Not Promote a Local Model Because It Fits&lt;/a&gt;. A model earns a route by passing the job, not by loading or producing a good demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should the router record?
&lt;/h2&gt;

&lt;p&gt;Each decision should record the lane, model, model tag, prompt or schema version, verifier result, stop reason, load time, and final action. The final action is important. A failed local attempt should become &lt;code&gt;escalate&lt;/code&gt;, &lt;code&gt;defer&lt;/code&gt;, or &lt;code&gt;fail-safe&lt;/code&gt;, not a guessed answer.&lt;/p&gt;

&lt;p&gt;Do not store only the accepted runs. The rejected rows show whether the lane is stable or merely lucky. They also make a future model change testable against the same contract.&lt;/p&gt;

&lt;p&gt;The router should never turn a parse failure into prose and pass it downstream. JSON requested plus JSON invalid means the structured lane failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you add a second local model?
&lt;/h2&gt;

&lt;p&gt;Add a lane only when the work has a distinct output contract and the candidate passes it. Do not add a model because it is new, small, or high on a general benchmark.&lt;/p&gt;

&lt;p&gt;The split also has an operating cost. More model files mean more disk, more health checks, more load paths, and more chances to route the wrong job. If one measured model passes both contracts on your hardware, keep one.&lt;/p&gt;

&lt;p&gt;My rule is simple: one lane per distinct job, one verifier per lane, and no promotion without replayable evidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accompanying prompt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What the prompt does:&lt;/strong&gt; Turns a mixed local AI workload into model lanes with a clear verifier and failure action for each lane.&lt;/p&gt;

&lt;p&gt;Copy/paste this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role:
You are a local LLM routing engineer.

Context:
Paste the jobs, candidate models, hardware limits, and sample outputs.

Task:
1. Group the jobs by output contract: prose, structured data, embeddings, or another exact type.
2. Assign a candidate model to each group only when evidence supports it.
3. Define a code-based verifier and a fail-closed action for every group.

Output:
- A route table with job, model, contract, verifier, and failure action.
- A replay test that a new model must pass before promotion.

Constraints:
- Keep it short.
- Use exact model tags and schema names when available.
- Do not invent missing measurements.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the block above.&lt;/p&gt;

&lt;p&gt;I publish measured local AI tests in &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;The 5090 Reports&lt;/a&gt;. Join the email list for the next result.&lt;/p&gt;




&lt;p&gt;Get the local AI lab notes (benchmark rows, VRAM fit, quant choices, what runs on consumer GPUs), M-F only when there is something worth sending: &lt;a href="https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=split-local-llm-prose-json-model-routing-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=split-local-llm-prose-json-model-routing-2026&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bmdpat.com/blog/split-local-llm-prose-json-model-routing-2026?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=split-local-llm-prose-json-model-routing-2026&amp;amp;utm_content=footer_original" rel="noopener noreferrer"&gt;bmdpat.com&lt;/a&gt;. I run a one-person AI agent company and write about what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want these in your inbox? &lt;a href="https://bmdpat.com/newsletter?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=split-local-llm-prose-json-model-routing-2026&amp;amp;utm_content=footer_newsletter" rel="noopener noreferrer"&gt;Subscribe to the newsletter&lt;/a&gt; - no spam, unsubscribe anytime.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>localai</category>
      <category>localllm</category>
      <category>ollama</category>
      <category>modelrouting</category>
    </item>
    <item>
      <title>A 32 GB GPU Still Needs Host RAM Headroom</title>
      <dc:creator>Patrick Hughes</dc:creator>
      <pubDate>Sat, 29 Aug 2026 16:05:08 +0000</pubDate>
      <link>https://dev.to/pat9000/a-32-gb-gpu-still-needs-host-ram-headroom-25c2</link>
      <guid>https://dev.to/pat9000/a-32-gb-gpu-still-needs-host-ram-headroom-25c2</guid>
      <description>&lt;h1&gt;
  
  
  A 32 GB GPU Still Needs Host RAM Headroom
&lt;/h1&gt;

&lt;p&gt;My local model was installed. Ollama answered its model-list request. The RTX 5090 reported 32,607 MiB of VRAM. I still did not start the test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short answer:&lt;/strong&gt; VRAM fit does not prove that the whole host is ready for a local LLM load. On 2026-08-29, my preflight found only 7.02 GiB of free host RAM against my 16.0 GiB cold-load floor. It stopped before inference and saved a clear reason.&lt;/p&gt;

&lt;p&gt;Canonical URL: &lt;a href="https://bmdpat.com/blog/rtx-5090-host-ram-headroom-local-llm-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/blog/rtx-5090-host-ram-headroom-local-llm-2026&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%2Fpq51uls4e8d43czzzhuo.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%2Fpq51uls4e8d43czzzhuo.png" alt="A four-step local LLM preflight from model catalog through host RAM check to the measured workload" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why did the run stop when the GPU was ready?
&lt;/h2&gt;

&lt;p&gt;I ran &lt;code&gt;config/ollama/health.py --json&lt;/code&gt; before a small Ollama test. The check reached the local API and found all eight installed model names. It also found &lt;code&gt;gemma4:26b&lt;/code&gt;, the model required by my local writing path.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;nvidia-smi&lt;/code&gt; probe reported an NVIDIA GeForce RTX 5090, driver 610.88, and 32,607 MiB of total VRAM. Those checks passed.&lt;/p&gt;

&lt;p&gt;The host-memory check did not. Windows had 7.02 GiB free. My cold-load policy needs 16.0 GiB free before the system can ask Ollama to generate. The health command returned &lt;code&gt;ok: false&lt;/code&gt; and named the failed guard. It made no generation request.&lt;/p&gt;

&lt;p&gt;That last detail matters. This was not an Ollama error. It was not a model error. It was a local admission rule that stopped a risky load. The generate HTTP call was never sent, so this run has no token rate, eval count, or duration to publish.&lt;/p&gt;

&lt;p&gt;A later runtime budget such as &lt;a href="https://bmdpat.com/tools/agentguard" rel="noopener noreferrer"&gt;AgentGuard&lt;/a&gt; can cap spend after a request starts. This floor is earlier. It refuses the load while host RAM is 7.02 GiB free against a 16.0 GiB policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the difference between VRAM and host RAM?
&lt;/h2&gt;

&lt;p&gt;VRAM holds GPU work such as model weights, runtime state, and the active context. Host RAM supports the operating system, the inference process, file reads, model setup, and every other process on the machine.&lt;/p&gt;

&lt;p&gt;A model can fit in VRAM while the host has little working room. The GPU total does not show how much memory Windows, Ollama, browsers, build tools, and other agents need at the same time.&lt;/p&gt;

&lt;p&gt;This is why I keep host memory separate from the &lt;a href="https://bmdpat.com/blog/local-llm-vram-headroom-2026" rel="noopener noreferrer"&gt;VRAM budget for shared local AI workloads&lt;/a&gt;. The two checks protect different boundaries.&lt;/p&gt;

&lt;p&gt;The 16.0 GiB floor is my policy for this machine. It is not an Ollama rule. Another host can use a different floor after it measures its own cold loads and normal background work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should a local LLM preflight check?
&lt;/h2&gt;

&lt;p&gt;I use four gates.&lt;/p&gt;

&lt;p&gt;First, query the model catalog. A missing model is a setup result, not a speed result.&lt;/p&gt;

&lt;p&gt;Second, read free host RAM before model load. Compare it with a fixed floor for that host and workload. If the check fails, stop before the request.&lt;/p&gt;

&lt;p&gt;Third, load one model with a small request. Save the model name, runtime version, host, and the pass or fail result.&lt;/p&gt;

&lt;p&gt;Fourth, run the measured workload. Capture wall time, prompt tokens, output tokens, output rate, VRAM, host RAM, and stop reason.&lt;/p&gt;

&lt;p&gt;I keep these gates separate from the broader &lt;a href="https://bmdpat.com/blog/local-ai-runtime-preflight-before-benchmark-2026" rel="noopener noreferrer"&gt;local AI runtime preflight&lt;/a&gt;. Build tools and runtime support come first. Memory admission comes just before model load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not let Windows page memory to disk?
&lt;/h2&gt;

&lt;p&gt;Paging can keep a process alive, but that is not the same as keeping an agent host healthy. A large memory load can slow unrelated work and make wall-time results hard to compare.&lt;/p&gt;

&lt;p&gt;My 5090 machine also runs the vault, browser tasks, and scheduled agents. A local model test must share the host. It does not own the machine.&lt;/p&gt;

&lt;p&gt;A fixed free-memory floor makes that choice explicit. The run starts only when the machine has enough working room. If the floor is too high, measured skips will show that. If it is too low, load-time and system-health receipts will show the cost.&lt;/p&gt;

&lt;p&gt;I do not lower the floor to make a daily test turn green. I free memory, choose a smaller model, or record the skipped run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should the failure receipt contain?
&lt;/h2&gt;

&lt;p&gt;Save the time, host, model, runtime version, GPU, driver, total VRAM, free host RAM, required floor, and the exact failed gate. Also save whether the generation request started.&lt;/p&gt;

&lt;p&gt;The distinction between blocked and failed is important. On 2026-08-29 my health command returned &lt;code&gt;generate_ok: false&lt;/code&gt; and named the host RAM guard as the reason. The request never started, so there is no failed generation to explain.&lt;/p&gt;

&lt;p&gt;That receipt prevents a later report from showing a blank token rate with no reason. It also prevents someone from blaming the model for a request that never reached it.&lt;/p&gt;

&lt;p&gt;A useful preflight names the failed gate and whether generate started. Mine named the host RAM guard and recorded &lt;code&gt;generate_ok: false&lt;/code&gt;, so the table does not show a blank token rate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accompanying prompt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What the prompt does:&lt;/strong&gt; It turns a local LLM load plan into a host-memory gate with a clear pass rule and failure receipt.&lt;/p&gt;

&lt;p&gt;Copy/paste this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role:
You are reviewing memory admission for a local LLM host.

Context:
Paste the host name, model, runtime, total VRAM, free host RAM, normal background work, and planned workload.

Task:
1. Separate the VRAM check from the host RAM check.
2. Set a host-specific free-memory floor from measured cold loads and background use.
3. Define the small request that proves model load before the measured workload.
4. Define the receipt for a blocked, failed, or completed run.

Output:
- A gate table with catalog, host RAM, model load, and workload rows.
- A pass or fail rule for each row.
- The fields to save when a gate stops the run.

Constraints:
- Do not treat total VRAM as free host RAM.
- Do not lower the memory floor only to make a run pass.
- Do not invent missing measurements.
- Mark every request that never started as not attempted.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the block above.&lt;/p&gt;

&lt;p&gt;Get the next measured local AI build note from &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;The 5090 Reports&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Get the local AI lab notes (benchmark rows, VRAM fit, quant choices, what runs on consumer GPUs), M-F only when there is something worth sending: &lt;a href="https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=rtx-5090-host-ram-headroom-local-llm-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=rtx-5090-host-ram-headroom-local-llm-2026&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bmdpat.com/blog/rtx-5090-host-ram-headroom-local-llm-2026?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rtx-5090-host-ram-headroom-local-llm-2026&amp;amp;utm_content=footer_original" rel="noopener noreferrer"&gt;bmdpat.com&lt;/a&gt;. I run a one-person AI agent company and write about what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want these in your inbox? &lt;a href="https://bmdpat.com/newsletter?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rtx-5090-host-ram-headroom-local-llm-2026&amp;amp;utm_content=footer_newsletter" rel="noopener noreferrer"&gt;Subscribe to the newsletter&lt;/a&gt; - no spam, unsubscribe anytime.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>localai</category>
      <category>localllm</category>
      <category>ollama</category>
      <category>rtx5090</category>
    </item>
    <item>
      <title>Your Benchmark Row Never Saved the Driver Version</title>
      <dc:creator>Patrick Hughes</dc:creator>
      <pubDate>Fri, 28 Aug 2026 16:05:12 +0000</pubDate>
      <link>https://dev.to/pat9000/your-benchmark-row-never-saved-the-driver-version-5fgk</link>
      <guid>https://dev.to/pat9000/your-benchmark-row-never-saved-the-driver-version-5fgk</guid>
      <description>&lt;h1&gt;
  
  
  Your Benchmark Row Never Saved the Driver Version
&lt;/h1&gt;

&lt;p&gt;On 2026-08-27 I tried to attach a driver version to a benchmark row from June. I could not do it. The row does not carry one, and neither does any other row I have saved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short answer:&lt;/strong&gt; Seven benchmark CSV files in my RTX 5090 notebook hold 17 rows, and none of the seven has a driver column. Two of the seven record a capture time. Three files carry a date and nothing finer. Two files carry neither a date nor a capture time. A row that did not save its own environment cannot support an environment claim later, and &lt;code&gt;nvidia-smi&lt;/code&gt; output from 2026-08-27 is not a substitute.&lt;/p&gt;

&lt;p&gt;Canonical URL: &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-row-missing-driver-version-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/blog/local-llm-benchmark-row-missing-driver-version-2026&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%2Fj2k1fvjvp4b6kng30x9s.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%2Fj2k1fvjvp4b6kng30x9s.png" alt="Audit of seven RTX 5090 benchmark files: zero files record a driver version, two of seven record a capture time, and one runtime appears under four different engine labels" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What did the file audit find?
&lt;/h2&gt;

&lt;p&gt;I read every CSV in the RTX 5090 benchmark notebook on 2026-08-27 and checked the headers, not the numbers.&lt;/p&gt;

&lt;p&gt;Seven files. Seventeen rows. Zero driver columns. Two files carry a &lt;code&gt;captured_at&lt;/code&gt; field. Three files carry a &lt;code&gt;date&lt;/code&gt; column and nothing finer. Two files carry neither. The header widths run from 9 columns to 17, so the files do not even agree on what a row is.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;engine&lt;/code&gt; column is the part that surprised me. One runtime appears under four labels across the seven files: &lt;code&gt;Ollama 0.22.1&lt;/code&gt;, &lt;code&gt;Ollama 0.31.1&lt;/code&gt;, &lt;code&gt;ollama+code_verifier&lt;/code&gt;, and plain &lt;code&gt;ollama&lt;/code&gt;. Two of those four hold a version. Four files use the bare word.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does a missing driver column matter?
&lt;/h2&gt;

&lt;p&gt;I wrote earlier that &lt;a href="https://bmdpat.com/blog/gpu-driver-not-local-llm-benchmark-2026" rel="noopener noreferrer"&gt;a GPU driver is not a local LLM benchmark&lt;/a&gt;. That post argued a driver number printed in a report header does not show the driver changed model speed. This is the hole underneath it.&lt;/p&gt;

&lt;p&gt;The report header is written when the report runs. The row is written when the test runs. On 2026-08-27 the header would say driver 610.88, because that is what &lt;code&gt;nvidia-smi&lt;/code&gt; prints on this box right now. The June row ran on whatever driver was installed in June, and nothing recorded it.&lt;/p&gt;

&lt;p&gt;So the header number binds to the report date and to nothing else. Copy it onto an older row and you have invented a fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which version is the runtime actually running?
&lt;/h2&gt;

&lt;p&gt;I ran &lt;code&gt;ollama --version&lt;/code&gt; on the same box. It printed &lt;code&gt;ollama version is 0.31.2&lt;/code&gt; and then warned &lt;code&gt;client version is 0.32.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two answers, one machine, one command. On 2026-08-27 the server answering requests is on one version and the CLI I typed is on another. Two of my seven files did record a version, and they read &lt;code&gt;Ollama 0.22.1&lt;/code&gt; and &lt;code&gt;Ollama 0.31.1&lt;/code&gt;, so at least three server builds have touched this notebook. A column that says &lt;code&gt;ollama&lt;/code&gt; cannot tell those apart, and the difference is the kind that moves results. Runtime releases change defaults, and I keep &lt;a href="https://bmdpat.com/blog/ollama-llama-cpp-feature-compatibility-2026" rel="noopener noreferrer"&gt;a compatibility check between Ollama and llama.cpp&lt;/a&gt; for that reason.&lt;/p&gt;

&lt;p&gt;Record the server version. The client version is the one you can read most easily and the one that did no work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What four fields does every row need?
&lt;/h2&gt;

&lt;p&gt;Four fields, written by the collector at capture time, never typed by hand later:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Runtime version at capture.&lt;/strong&gt; The server build that answered the request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Driver version at capture.&lt;/strong&gt; Read from &lt;code&gt;nvidia-smi&lt;/code&gt; in the same run, not the same week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU name at capture.&lt;/strong&gt; My fleet has more than one card, so the row must say which one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capture timestamp with timezone.&lt;/strong&gt; A date is not enough when a driver updates midday.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these needs a new tool. Each is one command in the collector that already writes the row.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do I do with the 17 rows I already have?
&lt;/h2&gt;

&lt;p&gt;Do not backfill them. Writing 610.88 into a June row would make the file look complete and make the record false.&lt;/p&gt;

&lt;p&gt;Mark them instead. I am adding an &lt;code&gt;env_provenance&lt;/code&gt; field with the value &lt;code&gt;unknown&lt;/code&gt; on every existing row. Those rows still hold their measured numbers, and they still support a &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-decision-record-2026" rel="noopener noreferrer"&gt;narrow benchmark decision&lt;/a&gt; inside the file that produced them. What they cannot do is join a claim about a driver, a runtime release, or a card.&lt;/p&gt;

&lt;p&gt;That is a real loss. Seventeen rows of measured work now sit outside every environment question I might want to ask. You pay for the missing column on the day you need it, which is always later.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I stop this from happening again?
&lt;/h2&gt;

&lt;p&gt;Move the fields out of the writer's hands and into the collector. The script that calls the API is the only thing that knows the truth at the moment of the run.&lt;/p&gt;

&lt;p&gt;Then fail the run when a field is empty. An empty driver column is not a small gap in a spreadsheet. It is a row that will look usable in six weeks and will not be.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accompanying prompt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What the prompt does:&lt;/strong&gt; It checks a benchmark file for the environment fields a row needs before that row can support any hardware or runtime claim.&lt;/p&gt;

&lt;p&gt;Copy/paste this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role:
You are a benchmark provenance auditor for local LLM test files.

Context:
I will give you the header and a few rows from one or more benchmark files.

Task:
1. List which environment fields each file records at capture time.
2. Name the missing fields: runtime version, driver version, GPU name, capture timestamp.
3. Flag any column whose values mix a bare tool name with a versioned tool name.
4. State which claims each file can and cannot support as it stands.

Output:
- A table of files and the environment fields each one records.
- A list of missing fields per file.
- The claims each file cannot support.
- The exact collector change needed to record each missing field.

Constraints:
- Do not fill a missing field from a current system reading.
- Do not treat a report header value as row provenance.
- Do not treat a date without a time as a capture timestamp.
- Do not delete rows that lack provenance. Mark them.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the block above.&lt;/p&gt;

&lt;p&gt;Get the next measured local AI build note from &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;The 5090 Reports&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Get the local AI lab notes (benchmark rows, VRAM fit, quant choices, what runs on consumer GPUs), M-F only when there is something worth sending: &lt;a href="https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-benchmark-row-missing-driver-version-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-benchmark-row-missing-driver-version-2026&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-row-missing-driver-version-2026?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-benchmark-row-missing-driver-version-2026&amp;amp;utm_content=footer_original" rel="noopener noreferrer"&gt;bmdpat.com&lt;/a&gt;. I run a one-person AI agent company and write about what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want these in your inbox? &lt;a href="https://bmdpat.com/newsletter?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-benchmark-row-missing-driver-version-2026&amp;amp;utm_content=footer_newsletter" rel="noopener noreferrer"&gt;Subscribe to the newsletter&lt;/a&gt; - no spam, unsubscribe anytime.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>localai</category>
      <category>localllm</category>
      <category>benchmarking</category>
      <category>rtx5090</category>
    </item>
    <item>
      <title>Your Local LLM CSV Needs a Schema Version</title>
      <dc:creator>Patrick Hughes</dc:creator>
      <pubDate>Fri, 28 Aug 2026 16:05:08 +0000</pubDate>
      <link>https://dev.to/pat9000/your-local-llm-csv-needs-a-schema-version-8pn</link>
      <guid>https://dev.to/pat9000/your-local-llm-csv-needs-a-schema-version-8pn</guid>
      <description>&lt;h1&gt;
  
  
  Your Local LLM CSV Needs a Schema Version
&lt;/h1&gt;

&lt;p&gt;My RTX 5090 benchmark notebook has measured data I cannot compare without hand repair. The models are not the problem. The CSV files disagree about what a benchmark row is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short answer:&lt;/strong&gt; I audited seven local LLM benchmark CSVs on 2026-08-28. They contain 17 rows across four header widths, three power-field shapes, and zero &lt;code&gt;schema_version&lt;/code&gt; fields. A benchmark file needs a versioned row contract before it needs another model run.&lt;/p&gt;

&lt;p&gt;Canonical URL: &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-csv-schema-version-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/blog/local-llm-benchmark-csv-schema-version-2026&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%2F7f8g31dt1nsrnoq3ozdn.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%2F7f8g31dt1nsrnoq3ozdn.png" alt="A three-step contract for versioned local LLM benchmark rows" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What did the CSV audit find?
&lt;/h2&gt;

&lt;p&gt;I read every CSV under my 5090 benchmark folder and compared the headers before I compared the results.&lt;/p&gt;

&lt;p&gt;Seven files hold 17 data rows. Their headers have 9, 10, 15, or 17 columns. None has a &lt;code&gt;schema_version&lt;/code&gt; field. None has a &lt;code&gt;run_id&lt;/code&gt; field. Two files have &lt;code&gt;captured_at&lt;/code&gt;; the other five do not.&lt;/p&gt;

&lt;p&gt;Power has three shapes. Two files store &lt;code&gt;watts_before&lt;/code&gt; and &lt;code&gt;watts_after&lt;/code&gt;. Four files declare &lt;code&gt;watts_avg&lt;/code&gt;, although one row leaves it blank. One file has no power column and puts &lt;code&gt;158W&lt;/code&gt; inside a notes sentence.&lt;/p&gt;

&lt;p&gt;Those rows are still real measurements. The problem is that the reader must guess how each file maps into a common table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does header drift break a benchmark notebook?
&lt;/h2&gt;

&lt;p&gt;A CSV reader can join files only after someone decides which columns mean the same thing. &lt;code&gt;watts_after&lt;/code&gt; is not the same measurement as &lt;code&gt;watts_avg&lt;/code&gt;. A date is not the same as a timestamp. A runtime name is not a run identity.&lt;/p&gt;

&lt;p&gt;Unit drift is the sharpest case. My 2026-06-14 and 2026-07-09 files record &lt;code&gt;total_duration_s&lt;/code&gt;, &lt;code&gt;eval_duration_s&lt;/code&gt;, and &lt;code&gt;load_duration_s&lt;/code&gt;. My 2026-08-02 v2 and 2026-08-04 files record &lt;code&gt;total_duration_ms&lt;/code&gt;, &lt;code&gt;eval_duration_ms&lt;/code&gt;, and &lt;code&gt;load_duration_ms&lt;/code&gt;. Same measurement, different unit, different name. The other three files carry no duration column at all. A reader that matches on the word &lt;code&gt;duration&lt;/code&gt; and ignores the suffix is wrong by a factor of 1000.&lt;/p&gt;

&lt;p&gt;Token rate splits the same way. Six files write &lt;code&gt;tokens_per_second&lt;/code&gt;. One writes &lt;code&gt;tok_per_sec&lt;/code&gt;. Those names map safely because both mean tokens per second. The values still do not compare, because that lone row is a five-task eval average and the others are single runs. A safe name mapping is not a safe comparison.&lt;/p&gt;

&lt;p&gt;My daily 5090 report flattens all 17 rows into one display table. That makes the report readable, but it cannot recover fields that the collector never wrote. A blank power cell may mean not measured, not supported by that schema, or lost in free text.&lt;/p&gt;

&lt;p&gt;I found the same boundary when I audited &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-row-missing-driver-version-2026" rel="noopener noreferrer"&gt;missing driver versions&lt;/a&gt;. The row must carry its own facts at capture time. A later report cannot add them without inventing history.&lt;/p&gt;

&lt;h2&gt;
  
  
  What belongs in a versioned row contract?
&lt;/h2&gt;

&lt;p&gt;I now treat the CSV header as an API. Version one has four groups.&lt;/p&gt;

&lt;p&gt;Identity fields name &lt;code&gt;schema_version&lt;/code&gt;, &lt;code&gt;run_id&lt;/code&gt;, and &lt;code&gt;captured_at&lt;/code&gt;. Environment fields name the GPU, driver, runtime, runtime version, model, and quant. Workload fields record the task, input tokens, output tokens, context size, and stop reason. Result fields record wall time, load time, prompt time, generation time, token rate, VRAM, and power.&lt;/p&gt;

&lt;p&gt;Each field needs one unit in its name or contract. &lt;code&gt;load_duration_ms&lt;/code&gt; is clear. &lt;code&gt;load_duration&lt;/code&gt; is not. &lt;code&gt;watts_avg&lt;/code&gt; must mean an average sampled during the measured window, not the reading after the request ended.&lt;/p&gt;

&lt;p&gt;The contract also needs explicit missing values. I use &lt;code&gt;not_measured&lt;/code&gt; when the collector skipped a metric and &lt;code&gt;not_supported&lt;/code&gt; when the runtime cannot expose it. An empty string is not enough because it hides the reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  How should old benchmark rows move forward?
&lt;/h2&gt;

&lt;p&gt;Do not rewrite old files to look current. Keep the original bytes and add a read-time adapter for each known header.&lt;/p&gt;

&lt;p&gt;The adapter can map &lt;code&gt;tokens_per_second&lt;/code&gt; and &lt;code&gt;tok_per_sec&lt;/code&gt; into one output field because both source names remain visible in the mapping. It must not map &lt;code&gt;watts_after&lt;/code&gt; into &lt;code&gt;watts_avg&lt;/code&gt;. When no safe mapping exists, the normalized value stays unknown.&lt;/p&gt;

&lt;p&gt;That preserves the measured result and the limits around it. It also lets a future collector write only the current schema while the report reader handles older files.&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-power-data-2026" rel="noopener noreferrer"&gt;power data needs a defined sample window&lt;/a&gt;. A schema name is useful only when its measurement rule is fixed too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should fail before a new row is saved?
&lt;/h2&gt;

&lt;p&gt;The collector should reject a row with no schema version, run ID, capture timestamp, runtime version, or GPU name. It should reject numeric fields that carry units in the value instead of the contract. It should also reject unknown columns unless the schema version declares them.&lt;/p&gt;

&lt;p&gt;Then test both sides. A complete current row should pass. A row copied from an old file should fail with the missing fields named in the error. That negative case matters because a permissive writer is how the notebook reached four header widths.&lt;/p&gt;

&lt;p&gt;The fix is small. The collector owns one row contract. The report reader owns explicit adapters for old contracts. No human edits a benchmark row after the run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accompanying prompt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What the prompt does:&lt;/strong&gt; It audits local LLM benchmark files for schema drift and writes a safe normalization plan without filling missing facts.&lt;/p&gt;

&lt;p&gt;Copy/paste this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role:
You are a schema auditor for local LLM benchmark files.

Context:
I will give you CSV headers and sample rows from several benchmark runs.

Task:
1. Group files by exact header shape.
2. Find fields that use different names, units, or measurement windows.
3. Separate safe mappings from mappings that would change meaning.
4. Draft one versioned row contract for new runs.

Output:
- A table of files, header widths, and schema groups.
- Safe field mappings with their source names preserved.
- Fields that must stay unknown.
- Required fields and fail rules for the new collector.

Constraints:
- Do not infer a missing value from a later system reading.
- Do not map point-in-time power to average power.
- Do not replace the original files.
- Use explicit units and missing-value reasons.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the block above.&lt;/p&gt;

&lt;p&gt;Get the next measured local AI build note from &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;The 5090 Reports&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Get the local AI lab notes (benchmark rows, VRAM fit, quant choices, what runs on consumer GPUs), M-F only when there is something worth sending: &lt;a href="https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-benchmark-csv-schema-version-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-benchmark-csv-schema-version-2026&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-csv-schema-version-2026?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-benchmark-csv-schema-version-2026&amp;amp;utm_content=footer_original" rel="noopener noreferrer"&gt;bmdpat.com&lt;/a&gt;. I run a one-person AI agent company and write about what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want these in your inbox? &lt;a href="https://bmdpat.com/newsletter?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-benchmark-csv-schema-version-2026&amp;amp;utm_content=footer_newsletter" rel="noopener noreferrer"&gt;Subscribe to the newsletter&lt;/a&gt; - no spam, unsubscribe anytime.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>localai</category>
      <category>localllm</category>
      <category>benchmarking</category>
      <category>rtx5090</category>
    </item>
    <item>
      <title>Preflight Local AI Before You Benchmark a Model</title>
      <dc:creator>Patrick Hughes</dc:creator>
      <pubDate>Thu, 27 Aug 2026 03:45:42 +0000</pubDate>
      <link>https://dev.to/pat9000/preflight-local-ai-before-you-benchmark-a-model-2gfp</link>
      <guid>https://dev.to/pat9000/preflight-local-ai-before-you-benchmark-a-model-2gfp</guid>
      <description>&lt;h1&gt;
  
  
  Preflight Local AI Before You Benchmark a Model
&lt;/h1&gt;

&lt;p&gt;My local AI benchmark failed before it loaded a model. The GPU was ready. The inference runtime was not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short answer:&lt;/strong&gt; Test the toolchain, runtime build, and model load as separate gates. A setup failure is useful evidence, but it is not a model result. Record the exact failed gate and publish no speed number.&lt;/p&gt;

&lt;p&gt;Canonical URL: &lt;a href="https://bmdpat.com/blog/local-ai-runtime-preflight-before-benchmark-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/blog/local-ai-runtime-preflight-before-benchmark-2026&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%2Ferzet2irzt3064pwy6j0.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%2Ferzet2irzt3064pwy6j0.png" alt="Three receipt steps for a local AI test: record the driver, replay the route, compare receipts" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What failed before the benchmark started?
&lt;/h2&gt;

&lt;p&gt;On August 2, 2026, I tried to build &lt;a href="https://github.com/sqliteai/waste" rel="noopener noreferrer"&gt;WASTE&lt;/a&gt; on my RTX 5090 machine. WASTE is a C inference project with a Makefile. I wanted to test its NVMe expert offload path on hardware I own.&lt;/p&gt;

&lt;p&gt;The test stopped at the build gate. &lt;code&gt;cc&lt;/code&gt;, &lt;code&gt;gcc&lt;/code&gt;, &lt;code&gt;clang&lt;/code&gt;, and &lt;code&gt;make&lt;/code&gt; were absent from the Windows host. WSL showed Ubuntu 22.04 as running, but each command returned &lt;code&gt;Wsl/Service/E_UNEXPECTED&lt;/code&gt; with the text &lt;code&gt;Catastrophic failure&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I recorded the failure in my 5090 report. I did not convert a model. I did not load weights. I did not get RAM, NVMe, latency, or token-rate measurements.&lt;/p&gt;

&lt;p&gt;That distinction matters. A blank result row can mean that the model failed, the runtime failed, the host failed, or the test never started. Those outcomes need different fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which gates should run before model load?
&lt;/h2&gt;

&lt;p&gt;I now split the preflight into three gates.&lt;/p&gt;

&lt;p&gt;First, check the build tools. Ask the runtime what it needs. Then run version commands for each compiler, build tool, driver, and GPU API. Save stdout, stderr, and exit codes.&lt;/p&gt;

&lt;p&gt;Second, build the runtime without a model. A clean build shows the source tree and host toolchain can produce the binary. It does not prove CUDA, model parsing, or inference works.&lt;/p&gt;

&lt;p&gt;Third, load the smallest supported model. The goal is one known-good request, not a speed record. Capture the model file, quant, runtime commit, GPU, driver, load result, and stop reason.&lt;/p&gt;

&lt;p&gt;Only then should the measured run begin.&lt;/p&gt;

&lt;p&gt;Run these gates on every host, not once per project. My WASTE attempt stopped at the first gate, so the 5090 report carries the host state instead of an empty benchmark row. A gate that passes still earns a line in the receipt. That passed line is what lets a later failure point at the model rather than the machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is VRAM fit not enough?
&lt;/h2&gt;

&lt;p&gt;VRAM answers one question: can the selected weights and runtime state fit on the GPU? It says nothing about whether the host can build or start the runtime.&lt;/p&gt;

&lt;p&gt;I made the same point in &lt;a href="https://bmdpat.com/blog/local-llm-vram-fit-runtime-support-2026" rel="noopener noreferrer"&gt;VRAM Fit Is Not Runtime Support&lt;/a&gt;. That post dealt with feature and runtime support. This WASTE test exposed an earlier boundary. The binary did not exist, so VRAM never entered the decision.&lt;/p&gt;

&lt;p&gt;This also changes how I read failed benchmark tables. In &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-failure-row-2026" rel="noopener noreferrer"&gt;Why a Failed Local LLM Benchmark Row Still Matters&lt;/a&gt;, I kept a failed row because it showed a real limit. A preflight failure needs the same care, but it belongs in a different field.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;preflight_status&lt;/code&gt;, &lt;code&gt;build_status&lt;/code&gt;, &lt;code&gt;load_status&lt;/code&gt;, and &lt;code&gt;run_status&lt;/code&gt;. Do not put every failure under one &lt;code&gt;error&lt;/code&gt; label.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should the receipt contain?
&lt;/h2&gt;

&lt;p&gt;My minimum receipt has four sections.&lt;/p&gt;

&lt;p&gt;The host section names the operating system, GPU, driver, and available memory. The toolchain section stores the compiler and build-tool versions. The runtime section stores the repository URL, commit, build command, and build result. The model section stores the file, quant, load result, and measured-run result.&lt;/p&gt;

&lt;p&gt;If a gate fails, I stop there. I keep the command, exit code, and error text. I mark all later measurements as not attempted.&lt;/p&gt;

&lt;p&gt;This prevents two common errors. The first is to copy a number from another machine because the local run produced none. The second is to call a setup fault a model fault.&lt;/p&gt;

&lt;p&gt;I did neither in the WASTE test. The report says that no inference numbers were produced. That is the only result the evidence supports.&lt;/p&gt;

&lt;h2&gt;
  
  
  When is the benchmark ready to start?
&lt;/h2&gt;

&lt;p&gt;Start it after one small model completes one known-good request through the exact runtime binary that the measured run will use.&lt;/p&gt;

&lt;p&gt;Keep that check cheap. It should catch a missing compiler, broken WSL session, bad CUDA link, unsupported model file, or failed load before the long test reserves the machine.&lt;/p&gt;

&lt;p&gt;Then run the real workload. Capture input tokens, output tokens, wall time, token rate, VRAM, power, and stop reason. If the workload fails, the receipt can now place the fault after a passed preflight.&lt;/p&gt;

&lt;p&gt;The point is not to avoid failed tests. The point is to make each failure name the boundary it reached.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accompanying prompt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What the prompt does:&lt;/strong&gt; It turns a local AI test plan into a fail-closed preflight with separate build, load, and measurement results.&lt;/p&gt;

&lt;p&gt;Copy/paste this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role:
You are a local AI benchmark preflight reviewer.

Context:
I will give you a host, inference runtime, model file, and test plan.

Task:
1. List the toolchain commands needed before the runtime build.
2. Define separate pass rules for build, model load, and measured run.
3. Stop at the first failed gate and save its command, exit code, and error text.
4. Mark every later metric as not attempted.

Output:
- A gate table with toolchain, build, load, and run rows.
- The exact evidence needed for each pass.
- One final status: ready to measure or blocked at a named gate.

Constraints:
- Do not copy measurements from another machine.
- Do not call a setup failure a model failure.
- Do not publish speed, memory, or power numbers unless the measured run started.
- Keep the runtime commit, model file, quant, GPU, and driver in the receipt.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the block above.&lt;/p&gt;

&lt;p&gt;Get the next measured local AI build note from &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;The 5090 Reports&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Get the local AI lab notes (benchmark rows, VRAM fit, quant choices, what runs on consumer GPUs), M-F only when there is something worth sending: &lt;a href="https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-ai-runtime-preflight-before-benchmark-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-ai-runtime-preflight-before-benchmark-2026&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bmdpat.com/blog/local-ai-runtime-preflight-before-benchmark-2026?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-ai-runtime-preflight-before-benchmark-2026&amp;amp;utm_content=footer_original" rel="noopener noreferrer"&gt;bmdpat.com&lt;/a&gt;. I run a one-person AI agent company and write about what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want these in your inbox? &lt;a href="https://bmdpat.com/newsletter?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-ai-runtime-preflight-before-benchmark-2026&amp;amp;utm_content=footer_newsletter" rel="noopener noreferrer"&gt;Subscribe to the newsletter&lt;/a&gt; - no spam, unsubscribe anytime.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>localai</category>
      <category>localllm</category>
      <category>inference</category>
      <category>rtx5090</category>
    </item>
    <item>
      <title>49W Average Hid a 338W Burst on Gemma 26B</title>
      <dc:creator>Patrick Hughes</dc:creator>
      <pubDate>Wed, 26 Aug 2026 22:31:32 +0000</pubDate>
      <link>https://dev.to/pat9000/49w-average-hid-a-338w-burst-on-gemma-26b-5cj8</link>
      <guid>https://dev.to/pat9000/49w-average-hid-a-338w-burst-on-gemma-26b-5cj8</guid>
      <description>&lt;h1&gt;
  
  
  49W Average Hid a 338W Burst on Gemma 26B
&lt;/h1&gt;

&lt;p&gt;A 49-watt average on my RTX 5090 looked cheap. The same Gemma 4 26B Q4_K_M run peaked at 338 watts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short answer:&lt;/strong&gt; Keep GPU watts average and GPU watts max on the same row. I only divide watts by output tokens per second after I name the sample window. Naive math on this long-context run gives 0.272 joules per output token from the average and 1.876 from the max. Those are not two valid answers. They are one missing window.&lt;/p&gt;

&lt;p&gt;Canonical URL: &lt;a href="https://bmdpat.com/blog/local-llm-gpu-watt-avg-vs-max-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/blog/local-llm-gpu-watt-avg-vs-max-2026&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%2Fb0k137vdo41odecilhxt.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%2Fb0k137vdo41odecilhxt.png" alt="Average GPU watts hid a 6.9x burst on one Gemma 4 26B Q4_K_M run" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What did the RTX 5090 receipt record?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;August 20, 2026 5090 Report&lt;/a&gt; lists three &lt;code&gt;gemma4:26b&lt;/code&gt; Q4_K_M rows from Ollama 0.31.1. Each note stores watts avg/max during the run.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Output rate&lt;/th&gt;
&lt;th&gt;Watts avg / max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;short-gen-256&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;198.81 tok/s&lt;/td&gt;
&lt;td&gt;43 / 187&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;long-context-summarize&lt;/td&gt;
&lt;td&gt;4,470&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;180.18 tok/s&lt;/td&gt;
&lt;td&gt;49 / 338&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;agent-code-task-512&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;207.41 tok/s&lt;/td&gt;
&lt;td&gt;48 / 320&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The long-context row is the loud one. Average power was 49 W. Max power was 338 W. That is a 6.9x gap on one run of one &lt;a href="https://bmdpat.com/blog/gguf-quantization-q4-q5-q8-explained-2026" rel="noopener noreferrer"&gt;Q4_K_M&lt;/a&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.nvidia.com/deploy/nvidia-smi/index.html" rel="noopener noreferrer"&gt;nvidia-smi&lt;/a&gt; reports &lt;code&gt;power.draw&lt;/code&gt; as a sampled GPU power reading, not as wall power for the whole PC. I treat it that way. The receipt does not name the sample interval. It also does not say whether idle samples sat in the average.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is the average a weak energy input?
&lt;/h2&gt;

&lt;p&gt;On August 24, 2026 I showed how to turn a generation-phase average into joules per output token. Divide watts by output tokens per second. On a different workshop row, &lt;code&gt;num_batch=256&lt;/code&gt; used 187.6 W at 215.7 tok/s, or about 0.870 J per output token. That write-up is &lt;a href="https://bmdpat.com/blog/local-llm-energy-per-token-2026" rel="noopener noreferrer"&gt;How to Calculate Local LLM Energy per Token&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The formula still needs a named window. If I plug the long-context average into the same formula, &lt;code&gt;49 / 180.18 = 0.272&lt;/code&gt;. If I plug the max into it, &lt;code&gt;338 / 180.18 = 1.876&lt;/code&gt;. I round those to three decimal places only for the table. I do not keep either number as the energy result for that row.&lt;/p&gt;

&lt;p&gt;The 0.272 figure is 3.2x lower than the workshop 0.870 figure. The 1.876 figure is 2.2x higher. Same GPU. Same 26B class. Different watt windows. A ranking built from those mixed averages would pick a winner for the wrong reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  How should I use the max reading?
&lt;/h2&gt;

&lt;p&gt;Max power is a burst flag. It is not a substitute average.&lt;/p&gt;

&lt;p&gt;338 W on a 575 W card is a real spike. It is not a 49 W workload. I keep the max so a later reader can see that the GPU left the low-power band. I still need a generation-phase average before I compute joules per output token.&lt;/p&gt;

&lt;p&gt;The short-gen row shows the same split at a smaller scale: 43 W average, 187 W max, 198.81 tok/s. The agent-code row recorded 48 W average and 320 W max at 207.41 tok/s. Across these three Q4_K_M rows, the average stayed in a 43-49 W band while the max moved from 187 W to 338 W.&lt;/p&gt;

&lt;p&gt;I do not know the cause from this receipt. Prompt work, decode work, and idle samples can share one "during run" average. The &lt;a href="https://bmdpat.com/blog/local-llm-input-output-benchmark-2026" rel="noopener noreferrer"&gt;input and output benchmark split&lt;/a&gt; already treats those phases as separate clocks. Power needs the same split.&lt;/p&gt;

&lt;h2&gt;
  
  
  What belongs in the next power receipt?
&lt;/h2&gt;

&lt;p&gt;Keep the raw pair. Save watts average and watts max. Save the model, quant, runtime, GPU, workload, context, input tokens, output tokens, and output rate. Then name three more fields:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The sensor (&lt;code&gt;nvidia-smi power.draw&lt;/code&gt;, wall meter, or other).&lt;/li&gt;
&lt;li&gt;The sample interval.&lt;/li&gt;
&lt;li&gt;The phase covered (full request, prompt eval only, or generation only).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any of those three is missing, I can still compare speed and VRAM. I cannot compare energy per token with a row that named the window.&lt;/p&gt;

&lt;p&gt;My earlier &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-power-data-2026" rel="noopener noreferrer"&gt;power-data note&lt;/a&gt; argued that speed without watts hides operating cost. This row adds the next trap: watts without a window hides the burst.&lt;/p&gt;

&lt;p&gt;For this long-context Gemma run, I keep 49 W and 338 W together. I do not publish a joules-per-token winner from them. The next useful experiment is a generation-only average on the same prompt, with the sample interval written on the row.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accompanying prompt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What the prompt does:&lt;/strong&gt; It checks whether a local LLM power row has a named watt window before anyone computes energy per token.&lt;/p&gt;

&lt;p&gt;Copy/paste this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role:
You are a local LLM power-receipt reviewer.

Context:
I will give you benchmark rows with model, quant, runtime, GPU,
workload, token counts, output rate, watts average, watts max,
and any sample-window notes.

Task:
1. Keep rows that include both watts average and watts max.
2. Mark energy-per-token math as blocked when the sample interval
   or phase (full request, prompt, generation) is missing.
3. If the window is named, compute joules per output token as
   generation-phase average watts divided by output tok/s.
4. Report the avg-to-max ratio as a burst flag, not as energy.

Output:
- A table with raw watt fields and the window status.
- One line that says compute, block, or rerun.
- The missing fields needed for a valid energy figure.

Constraints:
- Do not treat GPU watts as wall power.
- Do not use watts max as the energy input.
- Do not compare energy across rows with unnamed windows.
- Keep full precision for the calculation and round only the shown result.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the block above.&lt;/p&gt;

&lt;p&gt;Get the next measured local AI build note from &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;The 5090 Reports&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Get the local AI lab notes (benchmark rows, VRAM fit, quant choices, what runs on consumer GPUs), M-F only when there is something worth sending: &lt;a href="https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-gpu-watt-avg-vs-max-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-gpu-watt-avg-vs-max-2026&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bmdpat.com/blog/local-llm-gpu-watt-avg-vs-max-2026?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-gpu-watt-avg-vs-max-2026&amp;amp;utm_content=footer_original" rel="noopener noreferrer"&gt;bmdpat.com&lt;/a&gt;. I run a one-person AI agent company and write about what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want these in your inbox? &lt;a href="https://bmdpat.com/newsletter?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-gpu-watt-avg-vs-max-2026&amp;amp;utm_content=footer_newsletter" rel="noopener noreferrer"&gt;Subscribe to the newsletter&lt;/a&gt; - no spam, unsubscribe anytime.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>localllm</category>
      <category>gguf</category>
      <category>ollama</category>
      <category>rtx5090</category>
    </item>
    <item>
      <title>How to Calculate Local LLM Energy per Token</title>
      <dc:creator>Patrick Hughes</dc:creator>
      <pubDate>Tue, 25 Aug 2026 16:05:12 +0000</pubDate>
      <link>https://dev.to/pat9000/how-to-calculate-local-llm-energy-per-token-365c</link>
      <guid>https://dev.to/pat9000/how-to-calculate-local-llm-energy-per-token-365c</guid>
      <description>&lt;h1&gt;
  
  
  How to Calculate Local LLM Energy per Token
&lt;/h1&gt;

&lt;p&gt;The faster of two matched &lt;code&gt;gemma4:26b&lt;/code&gt; settings was not the most efficient. One run gained 1.3% output speed and used 23.3% more energy per output token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short answer:&lt;/strong&gt; Divide average GPU watts by output tokens per second. The result is joules per output token. In my RTX 5090 test, &lt;code&gt;num_batch=256&lt;/code&gt; used about 0.870 joules per output token. The &lt;code&gt;512&lt;/code&gt; setting used about 1.072. Speed alone hid that difference.&lt;/p&gt;

&lt;p&gt;Canonical URL: &lt;a href="https://bmdpat.com/blog/local-llm-energy-per-token-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/blog/local-llm-energy-per-token-2026&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%2F3nj6bdgo0n7tkuau0quv.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%2F3nj6bdgo0n7tkuau0quv.png" alt="Key decisions from How to Calculate Local LLM Energy per Token" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How do watts become joules per token?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.nist.gov/glossary-term/34606" rel="noopener noreferrer"&gt;NIST defines a watt&lt;/a&gt; as one joule per second. A model rate is output tokens per second. Divide the first rate by the second rate, and seconds cancel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;joules per output token = average GPU watts / output tokens per second
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This number answers a narrow question. It estimates GPU energy during the measured generation period. It does not include the rest of the computer, model load time, prompt work, cooling, or idle power.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did the two RTX 5090 rows show?
&lt;/h2&gt;

&lt;p&gt;The August 20, 2026 &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;5090 Report&lt;/a&gt; includes two &lt;code&gt;gemma4:26b&lt;/code&gt; workshop rows. Both used Ollama, a 2,048-token context, 91 input tokens, and 482 output tokens. Both passed the fixed tasks.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Output rate&lt;/th&gt;
&lt;th&gt;Average GPU power&lt;/th&gt;
&lt;th&gt;Energy per output token&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_batch=256&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;215.7 tok/s&lt;/td&gt;
&lt;td&gt;187.6 W&lt;/td&gt;
&lt;td&gt;0.870 J/token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;num_batch=512&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;218.5 tok/s&lt;/td&gt;
&lt;td&gt;234.3 W&lt;/td&gt;
&lt;td&gt;1.072 J/token&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The arithmetic is direct. &lt;code&gt;187.6 / 215.7 = 0.8697&lt;/code&gt;. &lt;code&gt;234.3 / 218.5 = 1.0723&lt;/code&gt;. I round each result to three decimal places in the table.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;512&lt;/code&gt; row generated 1.3% faster. Average GPU power rose 24.9%. Energy per output token rose 23.3%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is energy per token better than watts alone?
&lt;/h2&gt;

&lt;p&gt;Watts measure a rate of energy use. They do not say how much work finished during that time. A 200-watt run can use less total energy than a 150-watt run if it finishes much sooner.&lt;/p&gt;

&lt;p&gt;Tokens per second have the opposite gap. They say how fast output arrived, but not how much power the GPU drew to produce it. The fastest row can also be the least efficient row.&lt;/p&gt;

&lt;p&gt;Joules per token join those two fields. The unit still does not say whether the answer was correct. I only compare rows after the task check passes. My earlier &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-failure-row-2026" rel="noopener noreferrer"&gt;failed-row note&lt;/a&gt; shows why a fast row with a failed task stays out of production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can I turn this into electricity cost?
&lt;/h2&gt;

&lt;p&gt;Yes, but I label the estimate. One million output tokens at 0.870 joules each equals 870,000 joules. Divide by 3.6 million joules per kilowatt-hour. The generation estimate is about 0.242 kWh.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;512&lt;/code&gt; row reaches about 0.298 kWh per million output tokens by the same method. That is a difference of about 0.056 kWh for the measured generation phase.&lt;/p&gt;

&lt;p&gt;I do not call either number a full local inference cost. A full receipt also needs wall power, prompt work, load time, idle time, hardware cost, and the electricity rate. My &lt;a href="https://bmdpat.com/blog/local-first-ai-research-workbench-2026" rel="noopener noreferrer"&gt;local AI workbench note&lt;/a&gt; uses the same boundary when it converts one measured run into a rough electricity estimate.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should I use this metric?
&lt;/h2&gt;

&lt;p&gt;I use it after two rows pass the same task check. I keep the model, quant, runtime, workload, context, prompt, and output cap fixed. I also record the token counts so a later reader can see whether the runs did the same work.&lt;/p&gt;

&lt;p&gt;Then I ask three questions. Did the task pass? Did the faster setting remove useful wait time? Did energy per output token improve or get worse?&lt;/p&gt;

&lt;p&gt;For this workload, &lt;code&gt;num_batch=256&lt;/code&gt; is the better measured setting. It produced 98.7% of the &lt;code&gt;512&lt;/code&gt; row's output rate while using less average GPU power and less energy per output token. That is a result for this RTX 5090 test, not a default for every model or GPU.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should the benchmark receipt keep?
&lt;/h2&gt;

&lt;p&gt;Keep the raw fields before the derived metric. Save the model, quant, runtime version, GPU, workload, context, output cap, input tokens, output tokens, output rate, average GPU power, and task result.&lt;/p&gt;

&lt;p&gt;Calculate joules per output token from those saved values. Do not save only the rounded answer. The raw row lets another builder check the math and change the boundary.&lt;/p&gt;

&lt;p&gt;Also name what the power sensor measured. GPU power is not wall power. A generation average is not a whole-request average. A clear label stops a small metric from becoming a large claim.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accompanying prompt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What the prompt does:&lt;/strong&gt; It calculates energy per output token from local LLM rows and rejects comparisons that lack a task check.&lt;/p&gt;

&lt;p&gt;Copy/paste this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role:
You are a local LLM benchmark reviewer.

Context:
I will give you benchmark rows with model, quant, runtime, GPU,
workload, context, token counts, output rate, average GPU power,
and task result.

Task:
1. Keep only rows that passed the same task check.
2. Calculate joules per output token as watts divided by output tok/s.
3. Compare speed, average GPU power, and energy per output token.
4. State which measurement boundaries remain outside the result.

Output:
- A table with the raw fields and calculated joules per token.
- One setting choice for this measured workload.
- A list of missing fields needed for a full cost estimate.

Constraints:
- Do not treat GPU power as wall power.
- Do not include failed or unmatched task rows in the recommendation.
- Keep full precision for the calculation and round only the shown result.
- Say unknown when average power or output rate is missing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the block above.&lt;/p&gt;

&lt;p&gt;Get the next measured local AI build note from &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;The 5090 Reports&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Get the local AI lab notes (benchmark rows, VRAM fit, quant choices, what runs on consumer GPUs), M-F only when there is something worth sending: &lt;a href="https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-energy-per-token-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-energy-per-token-2026&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bmdpat.com/blog/local-llm-energy-per-token-2026?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-energy-per-token-2026&amp;amp;utm_content=footer_original" rel="noopener noreferrer"&gt;bmdpat.com&lt;/a&gt;. I run a one-person AI agent company and write about what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want these in your inbox? &lt;a href="https://bmdpat.com/newsletter?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-energy-per-token-2026&amp;amp;utm_content=footer_newsletter" rel="noopener noreferrer"&gt;Subscribe to the newsletter&lt;/a&gt; - no spam, unsubscribe anytime.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>localllm</category>
      <category>modelevaluation</category>
      <category>ollama</category>
      <category>rtx5090</category>
    </item>
    <item>
      <title>Why a Failed Local LLM Benchmark Row Still Matters</title>
      <dc:creator>Patrick Hughes</dc:creator>
      <pubDate>Mon, 24 Aug 2026 16:05:11 +0000</pubDate>
      <link>https://dev.to/pat9000/why-a-failed-local-llm-benchmark-row-still-matters-4npg</link>
      <guid>https://dev.to/pat9000/why-a-failed-local-llm-benchmark-row-still-matters-4npg</guid>
      <description>&lt;h1&gt;
  
  
  Why a Failed Local LLM Benchmark Row Still Matters
&lt;/h1&gt;

&lt;p&gt;I used to delete failed benchmark rows. That was wrong. A failed row marks the test boundary: the point where a fixed check stopped passing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short answer:&lt;/strong&gt; Keep failed quality rows in the receipt. On my RTX 5090, one &lt;code&gt;gemma4:26b&lt;/code&gt; setting hit 156.9 tokens per second and scored 0% on the fixed tasks. Two nearby settings scored 100%. The failed row is what makes that line visible.&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%2F9oiddhry93tjjxpy4bjp.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%2F9oiddhry93tjjxpy4bjp.png" alt="A comparison showing why a local LLM benchmark should keep failed rows beside passing rows" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What did the failed local model row show?
&lt;/h2&gt;

&lt;p&gt;The August 20, 2026 &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;5090 Report&lt;/a&gt; lists 17 benchmark rows and no runner failures. One &lt;code&gt;gemma4:26b&lt;/code&gt; row ran on Ollama at 2,048 context with &lt;code&gt;num_batch=128&lt;/code&gt;. Speed was 156.9 tokens per second. Fixed-task quality was 0%.&lt;/p&gt;

&lt;p&gt;The next two rows kept the same model and the same workload label. The &lt;code&gt;num_batch=256&lt;/code&gt; row reached 215.7 tokens per second at 100% quality. The &lt;code&gt;num_batch=512&lt;/code&gt; row reached 218.5 tokens per second at 100% quality. Average power was 187.6 W and 234.3 W.&lt;/p&gt;

&lt;p&gt;That does not prove 128 always fails. One machine, one task set: enough to reject this row, not enough to generalize.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why keep a zero-quality row?
&lt;/h2&gt;

&lt;p&gt;A passing row cannot answer one question: where did the test stop working?&lt;/p&gt;

&lt;p&gt;Delete the 0% row and the report shows two passing speeds. The reader never learns that a nearby setting failed the same check. Someone then lowers &lt;code&gt;num_batch&lt;/code&gt; to cut memory pressure and assumes the task still passes.&lt;/p&gt;

&lt;p&gt;The row also guards future comparisons. Say a new runtime reaches 200 tokens per second at &lt;code&gt;num_batch=128&lt;/code&gt;. Then I ask: did quality improve, or did the test change? Without the old row I lose that check. The same logic drives my &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-stop-reason-2026" rel="noopener noreferrer"&gt;stop reason field&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed between the rows?
&lt;/h2&gt;

&lt;p&gt;The batch setting changed. The model stayed &lt;code&gt;gemma4:26b&lt;/code&gt;. The context stayed 2,048 tokens. The workload label stayed the workshop mix.&lt;/p&gt;

&lt;p&gt;Two more fields moved, and the receipt shows it. The failed row logged 100 input and 600 output tokens. Both passing rows logged 91 input and 482 output. The report also notes the 128 row came from a fallback pick, not a deliberate choice.&lt;/p&gt;

&lt;p&gt;So this is not a clean one-variable test. I could leave that out and tell a neater story. The neater story would be false. Three fields moved, so the honest label is a new experiment, not a controlled comparison.&lt;/p&gt;

&lt;p&gt;That is the second argument for keeping the failed row. The full field set showed me what my own test failed to hold fixed. A trimmed report would have hidden that too.&lt;/p&gt;

&lt;p&gt;Quality still moved from 0% at 128 to 100% at 256 and 512. Speed rose as well. The report gives no average power for the failed row, so I leave that field empty instead of estimating it.&lt;/p&gt;

&lt;p&gt;Speed alone cannot settle any of this. My &lt;a href="https://bmdpat.com/blog/local-llm-vram-fit-runtime-support-2026" rel="noopener noreferrer"&gt;VRAM fit notes&lt;/a&gt; cover whether a model loads, and my &lt;a href="https://bmdpat.com/blog/5090-benchmark-phase-timing-receipt-2026-v2" rel="noopener noreferrer"&gt;phase timing receipt&lt;/a&gt; covers why a total time hides the slow phase. This post covers a third question: did the fixed task pass?&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I use failed rows in a local eval?
&lt;/h2&gt;

&lt;p&gt;Save the exact prompt, model, runtime, context, setting, output, and verifier result. A percentage without the task definition is not a receipt.&lt;/p&gt;

&lt;p&gt;Change one variable at a time. If several fields move, call it a new experiment, as I just did with the 128 row.&lt;/p&gt;

&lt;p&gt;Keep the failed row next to the next result. Do not overwrite it. Add one note that states what failed and what changed.&lt;/p&gt;

&lt;p&gt;Pick the production setting from passing rows only. Treat the failed row as a boundary marker, not as a candidate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should a local LLM benchmark report?
&lt;/h2&gt;

&lt;p&gt;My minimum row carries these fields:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Model and quantization.&lt;/li&gt;
&lt;li&gt;Runtime and version.&lt;/li&gt;
&lt;li&gt;Workload and context.&lt;/li&gt;
&lt;li&gt;The setting under test.&lt;/li&gt;
&lt;li&gt;Tokens per second.&lt;/li&gt;
&lt;li&gt;Power and VRAM when measured.&lt;/li&gt;
&lt;li&gt;The task result.&lt;/li&gt;
&lt;li&gt;The stop reason or verifier note.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The last two fields change how I read every number above them. A fast row that fails the task is not a candidate. A slower row that passes may be the better production choice. The receipt should make that call obvious.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accompanying prompt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What the prompt does:&lt;/strong&gt; It turns a local benchmark log into a row set that keeps failed tests and exposes the setting boundary.&lt;/p&gt;

&lt;p&gt;Copy/paste this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role:
You are a local LLM benchmark reviewer.

Context:
Model: [model and quantization]
Runtime: [runtime and version]
GPU: [GPU name and memory]
Workload: [prompt set, context, and setting]
Run log: [paste the log or report path]

Task:
1. Extract speed, power, VRAM, output, and verifier fields.
2. Mark each row as pass, fail, or unverified.
3. Keep failed rows in the comparison.
4. Identify the smallest setting change that moves a row from fail to pass.

Output:
- A markdown table with one row per run.
- A short note for every failed row.
- One production-setting recommendation from passing rows only.

Constraints:
- Keep exact measured values.
- Do not estimate missing power or VRAM values.
- Do not call a speed result a quality result.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the block above.&lt;/p&gt;

&lt;p&gt;Read the next measured local AI build note by email: &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;https://bmdpat.com/5090-reports&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Get the local AI lab notes (benchmark rows, VRAM fit, quant choices, what runs on consumer GPUs), M-F only when there is something worth sending: &lt;a href="https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-benchmark-failure-row-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-benchmark-failure-row-2026&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-failure-row-2026?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-benchmark-failure-row-2026&amp;amp;utm_content=footer_original" rel="noopener noreferrer"&gt;bmdpat.com&lt;/a&gt;. I run a one-person AI agent company and write about what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want these in your inbox? &lt;a href="https://bmdpat.com/newsletter?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-benchmark-failure-row-2026&amp;amp;utm_content=footer_newsletter" rel="noopener noreferrer"&gt;Subscribe to the newsletter&lt;/a&gt; - no spam, unsubscribe anytime.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>localllm</category>
      <category>modelevaluation</category>
      <category>ollama</category>
      <category>rtx5090</category>
    </item>
    <item>
      <title>My local LLM eval hid four token caps</title>
      <dc:creator>Patrick Hughes</dc:creator>
      <pubDate>Sat, 22 Aug 2026 16:05:18 +0000</pubDate>
      <link>https://dev.to/pat9000/my-local-llm-eval-hid-four-token-caps-1049</link>
      <guid>https://dev.to/pat9000/my-local-llm-eval-hid-four-token-caps-1049</guid>
      <description>&lt;h1&gt;
  
  
  My local LLM eval hid four token caps
&lt;/h1&gt;

&lt;p&gt;I compared two local eval scores and treated them as one test. They were not. One path stopped at 50 tokens. Another allowed 256.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; My &lt;code&gt;5090-rig&lt;/code&gt; eval stack uses four output caps: 50, 180, 192, and 256 tokens, under three field names. A score that omits the cap is not a model result. Canonical URL: &lt;a href="https://bmdpat.com/blog/local-llm-eval-four-token-caps-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/blog/local-llm-eval-four-token-caps-2026&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%2Fdwnjfsj3d3n2pzg5u79l.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%2Fdwnjfsj3d3n2pzg5u79l.png" alt="Local LLM output cap comparison that separates hidden limits from measured results" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where are the four caps in my eval code?
&lt;/h2&gt;

&lt;p&gt;I listed every default output limit in &lt;code&gt;5090-rig&lt;/code&gt;. Four values are still in the source and logs.&lt;/p&gt;

&lt;p&gt;The router gate posts to &lt;code&gt;/v1/chat/completions&lt;/code&gt; with &lt;code&gt;"max_tokens": 50&lt;/code&gt;. That lives in &lt;code&gt;5090-rig/phase1/gates/gate_01_router.py&lt;/code&gt;. The prompt asks for one Python function. Fifty tokens can be enough for that job.&lt;/p&gt;

&lt;p&gt;Phase 1 and phase 2 call Ollama &lt;code&gt;/api/generate&lt;/code&gt; with &lt;code&gt;"num_predict": 180&lt;/code&gt;. That default is in &lt;code&gt;5090-rig/phase1/phase1_eval.py&lt;/code&gt; and again in &lt;code&gt;5090-rig/phase2/data_flywheel.py&lt;/code&gt;. The &lt;a href="https://docs.ollama.com/api/generate" rel="noopener noreferrer"&gt;Ollama generate API&lt;/a&gt; documents &lt;code&gt;num_predict&lt;/code&gt; as the max tokens to generate after the prompt.&lt;/p&gt;

&lt;p&gt;The sweep probe uses a different default. &lt;code&gt;5090-rig/sweep/probe.py&lt;/code&gt; sets &lt;code&gt;num_predict=192&lt;/code&gt; on &lt;code&gt;ollama_generate&lt;/code&gt;. Same API. Different budget. A table that mixes probe rows with phase-1 rows is already mixing caps.&lt;/p&gt;

&lt;p&gt;Phase 3 is a third API. I did not find &lt;code&gt;max_new_tokens&lt;/code&gt; in the Python source. The run log &lt;code&gt;5090-rig/phase3/SCRATCH/phase3-final-run.log&lt;/code&gt; shows Hugging Face generate used &lt;code&gt;max_new_tokens=256&lt;/code&gt;. The same lines say &lt;code&gt;max_length=131072&lt;/code&gt; was also set, and that &lt;code&gt;max_new_tokens&lt;/code&gt; takes precedence. &lt;a href="https://huggingface.co/docs/transformers/main/en/main_classes/text_generation" rel="noopener noreferrer"&gt;Hugging Face text generation&lt;/a&gt; is the independent page for that field.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do four caps hide a model ranking?
&lt;/h2&gt;

&lt;p&gt;The field names do not match. &lt;code&gt;max_tokens&lt;/code&gt;, &lt;code&gt;num_predict&lt;/code&gt;, and &lt;code&gt;max_new_tokens&lt;/code&gt; all mean "stop after this many new tokens." A reader who only sees a score cannot tell which one ran.&lt;/p&gt;

&lt;p&gt;The jobs do not match either. A 50-token router check is a short function test. A 180-token Ollama generate can write more. A 256-token Hugging Face generate can write more still. Ranking those scores ranks the budget.&lt;/p&gt;

&lt;p&gt;I already covered the receipt side of this in &lt;a href="https://bmdpat.com/blog/local-llm-benchmark-stop-reason-2026" rel="noopener noreferrer"&gt;The 26B Model Hit the Cap. The 8B Finished.&lt;/a&gt;. That post is about &lt;code&gt;done_reason&lt;/code&gt;. This post is the other half: find the configured cap in the eval code before you trust the table.&lt;/p&gt;

&lt;p&gt;A fast row can still be a truncated row. &lt;a href="https://bmdpat.com/blog/local-llm-honest-eval-numbers-2026" rel="noopener noreferrer"&gt;Honest local eval numbers&lt;/a&gt; start with the settings that shaped the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should every eval row record?
&lt;/h2&gt;

&lt;p&gt;I now want these fields on every local eval row:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cap value (50, 180, 192, or 256 in this stack).&lt;/li&gt;
&lt;li&gt;The field name (&lt;code&gt;max_tokens&lt;/code&gt;, &lt;code&gt;num_predict&lt;/code&gt;, or &lt;code&gt;max_new_tokens&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The API path (&lt;code&gt;/v1/chat/completions&lt;/code&gt;, Ollama &lt;code&gt;/api/generate&lt;/code&gt;, or Hugging Face generate).&lt;/li&gt;
&lt;li&gt;Tokens produced.&lt;/li&gt;
&lt;li&gt;Stop reason when the runtime gives one.&lt;/li&gt;
&lt;li&gt;Task pass or fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If two rows differ on any of those, they are not the same test. Do not average them. Do not pick a winner.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://bmdpat.com/blog/local-llm-speed-quality-gate-2026" rel="noopener noreferrer"&gt;speed and quality gate&lt;/a&gt; already treats quality as a release check. The cap belongs in that check. A pass under 256 tokens is not a pass under 50 tokens.&lt;/p&gt;

&lt;p&gt;Short caps are useful. The failure is silent mixing. If production uses 50, the table must say 50.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I test a model against its production cap?
&lt;/h2&gt;

&lt;p&gt;I use two runs on the same prompt, model, quant, and runtime path.&lt;/p&gt;

&lt;p&gt;Run 1 uses the production cap for that path. For the router gate, that is 50. For phase 1, that is 180.&lt;/p&gt;

&lt;p&gt;Run 2 uses a larger cap on the same path. If run 1 fails the task and run 2 passes, the model needed more tokens than production allows. That is a product limit. It is not a mystery quality drop.&lt;/p&gt;

&lt;p&gt;Do not raise the eval cap and then ship the tight one. If production will send &lt;code&gt;num_predict=180&lt;/code&gt;, the keep/reject decision must use 180. The large-cap run is evidence, not a substitute score.&lt;/p&gt;

&lt;p&gt;A model that hits the cap can look fast because it never had to finish. Record the cap, the tokens produced, and the task check together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accompanying prompt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What the prompt does:&lt;/strong&gt; Lists every output-token cap in a local eval codebase and flags scores that omit the cap.&lt;/p&gt;

&lt;p&gt;Copy/paste this prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role:
You audit a local LLM eval codebase for output-token caps.

Context:
Paste the eval files, configs, and one result table.

Task:
1. Find every max_tokens, num_predict, max_new_tokens, and n_predict default.
2. Group them by API path and by file.
3. Match each result-table row to the cap that produced it.
4. Flag any score whose cap, stop reason, or token count is missing.

Output:
- A table of file, field name, cap value, and API path.
- Rows that mixed two caps.
- The two-run check: production cap, then a larger cap on the same path.

Constraints:
- Use the exact numbers from the files.
- Do not invent a cap that is not in the source or logs.
- Do not rank models across different caps.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the block above.&lt;/p&gt;

&lt;p&gt;Get measured local AI notes by email: &lt;a href="https://bmdpat.com/5090-reports" rel="noopener noreferrer"&gt;https://bmdpat.com/5090-reports&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Get the local AI lab notes (benchmark rows, VRAM fit, quant choices, what runs on consumer GPUs), M-F only when there is something worth sending: &lt;a href="https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-eval-four-token-caps-2026" rel="noopener noreferrer"&gt;https://bmdpat.com/newsletter?utm_source=blog_md&amp;amp;utm_medium=aeo&amp;amp;utm_campaign=local-llm-eval-four-token-caps-2026&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bmdpat.com/blog/local-llm-eval-four-token-caps-2026?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-eval-four-token-caps-2026&amp;amp;utm_content=footer_original" rel="noopener noreferrer"&gt;bmdpat.com&lt;/a&gt;. I run a one-person AI agent company and write about what actually works.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want these in your inbox? &lt;a href="https://bmdpat.com/newsletter?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=local-llm-eval-four-token-caps-2026&amp;amp;utm_content=footer_newsletter" rel="noopener noreferrer"&gt;Subscribe to the newsletter&lt;/a&gt; - no spam, unsubscribe anytime.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>localllm</category>
      <category>ollama</category>
      <category>rtx5090</category>
      <category>5090reports</category>
    </item>
  </channel>
</rss>
