<?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: Throttle</title>
    <description>The latest articles on DEV Community by Throttle (@throttle_pro).</description>
    <link>https://dev.to/throttle_pro</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%2F4144776%2F6ee3ad5e-9f51-4a59-8b7f-25b2c4d71d6c.png</url>
      <title>DEV Community: Throttle</title>
      <link>https://dev.to/throttle_pro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/throttle_pro"/>
    <language>en</language>
    <item>
      <title>I changed nothing and my LLM server got 27% more expensive</title>
      <dc:creator>Throttle</dc:creator>
      <pubDate>Sat, 26 Sep 2026 19:38:35 +0000</pubDate>
      <link>https://dev.to/throttle_pro/i-changed-nothing-and-my-llm-server-got-27-more-expensive-4i1j</link>
      <guid>https://dev.to/throttle_pro/i-changed-nothing-and-my-llm-server-got-27-more-expensive-4i1j</guid>
      <description>&lt;p&gt;I ran the same cost check four times in a row against a server I didn't touch. Same model, same machine, same prompts, same settings. This is what came back, in dollars per million output tokens:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;$/M output tokens&lt;/th&gt;
&lt;th&gt;95% CI&lt;/th&gt;
&lt;th&gt;Change vs previous run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;$8.77&lt;/td&gt;
&lt;td&gt;$8.19 to $9.35&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;$8.86&lt;/td&gt;
&lt;td&gt;$7.80 to $9.93&lt;/td&gt;
&lt;td&gt;+1.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;$11.26&lt;/td&gt;
&lt;td&gt;$10.31 to $12.21&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+27.1%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;$10.02&lt;/td&gt;
&lt;td&gt;$7.41 to $12.62&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−11.0%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run 3 says the server got 27% more expensive. Run 4 says it then got 11% cheaper. Nothing changed.&lt;/p&gt;

&lt;p&gt;If I had changed a flag between run 2 and run 3, I would have blamed the flag and rolled it back. If I had changed one between run 3 and run 4, I would have posted an 11% saving in Slack. Both would have been wrong, and both would have looked like data.&lt;/p&gt;

&lt;p&gt;This post is about why that happens and the rule I now apply before believing any before/after cost number. The tool in the output below is &lt;a href="https://github.com/KushagraKanaujia/throttle" rel="noopener noreferrer"&gt;Throttle&lt;/a&gt;, an open-source CLI I'm building, but the rule doesn't depend on it.&lt;/p&gt;

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

&lt;p&gt;A MacBook running Ollama locally with &lt;code&gt;llama3.2:3b&lt;/code&gt;. Each check sent 3 blocks of 4 requests at concurrency 2, capped at 64 output tokens, temperature 0, with the same 8 built-in prompts and one warm-up request. All four checks finished within about 70 seconds of each other.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;throttle check &lt;span class="nt"&gt;--url&lt;/span&gt; http://localhost:11434 &lt;span class="nt"&gt;--model&lt;/span&gt; llama3.2:3b &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--gpu-hourly-rate&lt;/span&gt; 1.50 &lt;span class="nt"&gt;--label&lt;/span&gt; same &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--blocks&lt;/span&gt; 3 &lt;span class="nt"&gt;--requests-per-block&lt;/span&gt; 4 &lt;span class="nt"&gt;--concurrency&lt;/span&gt; 2 &lt;span class="nt"&gt;--max-tokens&lt;/span&gt; 64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The $1.50/hour GPU rate. A laptop has no GPU invoice, so I picked and ran Throttle on runpod on different GPUa labels it ASSUMED everywhere it appears. Tokens and time are measured.&lt;/p&gt;

&lt;p&gt;The cost math is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$/M output tokens = hourly rate × wall-clock hours ÷ output tokens × 1,000,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every block in all four runs produced exactly 256 output tokens (4 requests × 64 tokens). So the only thing that moved the price was time. The three blocks of run 1 took 5.53 s, 5.24 s and 5.40 s. The blocks of run 3 took 7.09 s, 7.02 s and 6.65 s. Same tokens, about 30% more seconds, about 27% more dollars per token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the same server swings
&lt;/h2&gt;

&lt;p&gt;A cost measurement is a timing measurement, and timing on a real machine depends on everything else the machine is doing: other processes, background indexing, thermal state and clock speed, memory pressure, OS scheduling. On a shared cloud node you can add noisy neighbours and other tenants on the same host or network.&lt;/p&gt;

&lt;p&gt;I didn't instrument the laptop during these runs, so I can't tell you which of those it was. That's the point. Most of the time you won't know either, and a single before/after pair can't tell load from a real change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the confidence interval didn't save me
&lt;/h2&gt;

&lt;p&gt;Each check reports a 95% confidence interval across its blocks. Look at runs 1 and 3: $8.19 to $9.35, then $10.31 to $12.21. The intervals don't overlap. The common rule of thumb ("non-overlapping intervals means a real difference") would call run 3 a real 27% regression.&lt;/p&gt;

&lt;p&gt;The interval answers a narrower question than it looks like. It's computed from the blocks inside one run, so it describes the wobble over about 20 seconds. It can't see drift between runs, because every block in a run shares the same conditions. If the whole machine is 30% slower for a minute, all three blocks are slower together and the interval stays tight around the wrong number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the noise you are actually exposed to
&lt;/h2&gt;

&lt;p&gt;The fix is to measure run-to-run noise directly: run the unchanged config a few times, and see how much whole checks disagree with each other.&lt;/p&gt;

&lt;p&gt;Say the relative standard deviation between repeat checks of one config is SD. The difference between two single checks then has a standard deviation of √2 × SD, because each check carries its own noise. With only a handful of repeats you use Student's t instead of 1.96, with degrees of freedom from the repeats. That gives a 95% bound on how far apart two checks of the &lt;strong&gt;same&lt;/strong&gt; config can land:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;noise bound = t(0.975, df) × √2 × SD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, runs 1 to 3 were three checks of the unchanged config: $8.77, $8.86, $11.26. Their relative SD is 14.6%, with 2 degrees of freedom, where t is 4.303:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4.303 × 1.414 × 14.6% ≈ 89%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Throttle prints 89.1% because it uses the unrounded SD. Run 4 came in 11.0% below run 3. That's well inside ±89.1%, and the two intervals overlap as well, so this is the verdict:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;before: $11.26/M output tokens (95% CI $10.31 to $12.21)
after:  $10.02/M output tokens (95% CI $7.41 to $12.62)
change: -11.0%
noise floor: calibrated: bound 89.1% (t x sqrt(2) x 14.6% run-to-run SD, 2 df)
Verdict: NO WINNER, the change (-11.0%) is not larger than the run-to-run
noise bound (89.1% = t x sqrt(2) x 14.6% run-to-run SD, 2 df); and the 95%
confidence intervals overlap, so the difference is within measurement noise
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What about the +27.1% at run 3? Throttle didn't call it anything. With fewer than three earlier checks of the same config there's no noise estimate at all, so runs 2 and 3 both came back as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verdict: NOT CALIBRATED — run-to-run noise unknown. Two checks at different
times can differ from load alone. Run 'throttle check' at least 3 times without
changing anything (within 24 h) to measure your noise, or use 'throttle golden'
for a counterbalanced decision.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I built it that way because refusing to answer beats a confident wrong answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  An 89% bound looks bad, and it's still the honest answer
&lt;/h2&gt;

&lt;p&gt;A bound of ±89% means these three repeats can't resolve any realistic config change, and that's true. One slow run out of three blew the SD up, and with 2 degrees of freedom t is large. The bound is saying: on this machine, right now, you don't know enough to judge a change.&lt;/p&gt;

&lt;p&gt;You can tighten it in two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More repeats.&lt;/strong&gt; t falls quickly with degrees of freedom: 4.30 at 2, 2.57 at 5, 2.23 at 10. More checks also give a better SD estimate, so one slow run matters less.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A quieter, longer measurement.&lt;/strong&gt; Close what else is running, use longer blocks, and measure at the concurrency you run in production. Noise you remove is noise you don't have to bound.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a decision you'll act on, like which config goes to production, don't compare two sequential runs at all. Interleave them (baseline, candidate, baseline, candidate, baseline, candidate) so drift hits both sides equally. I wrote up a real six-run example on an A100 in &lt;a href="https://www.throttle-pro.com/blog/one-vllm-flag-counterbalanced-benchmark" rel="noopener noreferrer"&gt;One vLLM flag, −68.6% cost per token&lt;/a&gt;.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Run the unchanged config at least three times first, within the same day.&lt;/li&gt;
&lt;li&gt;Change one thing.&lt;/li&gt;
&lt;li&gt;Believe the change only if it's larger than the run-to-run noise bound &lt;strong&gt;and&lt;/strong&gt; the 95% intervals don't overlap.&lt;/li&gt;
&lt;li&gt;Otherwise it's NO WINNER. Don't write the Slack message.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;This was a laptop, a 3B model, a short workload and an assumed GPU rate. The size of the swing is specific to that machine on that morning. Your servers will have their own noise, which is exactly why you have to measure it rather than borrow mine.&lt;/p&gt;

&lt;p&gt;These runs used Throttle 0.4.0, which repeats identical prompts, so the prompt cache was warm in all four runs. That rules out the cache as the cause of the swing, since it was the same in every run. Since 0.4.1, Throttle tags every request so a prefix cache can't make later checks look cheaper, and &lt;code&gt;--warm-cache&lt;/code&gt; measures a warm cache on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;throttle-pro
throttle demo      &lt;span class="c"&gt;# no GPU needed; every number is SIMULATED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point &lt;code&gt;throttle check&lt;/code&gt; at your own vLLM, SGLang or Ollama endpoint and run it three times before you change anything. If you get an interesting result, &lt;code&gt;throttle check --share&lt;/code&gt; prints a sanitized summary you can post as a &lt;a href="https://github.com/KushagraKanaujia/throttle/issues/new?template=share-results.yml" rel="noopener noreferrer"&gt;results issue&lt;/a&gt;. I'm collecting what real serving changes actually do to cost, and I read every one.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
