A JavaScript Cost Calculator for Choosing Between $0.10/M APIs and Self-Hosted Models

A JavaScript Cost Calculator for Choosing Between $0.10/M APIs and Self-Hosted Models

pr0h0••34 min read•ai
AI Usage (83%)
On this page

Introduction: Why a $0.10/M Token Price Breaks the Build-vs-Buy Comparison

On 2026-11-12, three articles landed within about eleven hours of each other, all making the same case in different words: inference got cheap. Techiexpert reported that Google launched Gemini Decision 1 at $0.10 per million tokens. Martin Cid Magazine reported that Meta released Muse Ember, its smallest model, at Haiku-level per-token pricing. FourWeekMBA published an analysis claiming open-weight tokens cost roughly 7x less than closed frontier tokens, while OpenAI and Anthropic still hold about 95% of spend. This post builds the JavaScript cost calculator that puts those headlines on the same footing as a GPU bill — USD per million total tokens — so you can see where self-hosting actually breaks even, and which single input moves the answer.

I can't verify the SKU-level pricing behind any of those reports. I haven't seen the pricing page for Gemini Decision 1, and I don't know whether $0.10/M is an input rate, a blended rate, a batch-tier rate, or a promotional floor. That gap isn't throat-clearing I'll dispose of in a paragraph. It's the whole point of the post. Because the moment you try to compare "$0.10/M" against a self-hosted GPU bill, you find that almost nobody is quoting the same unit, and the comparison breaks without telling you.

So this is a walkthrough of a JavaScript cost calculator for LLM inference that forces the units to line up. It's about thirty lines of arithmetic, a pure-function module, and a sweep harness. The value isn't in the code. It's in the inputs you're forced to supply.

What the price-collapse reports actually claim — and what they do not

Reducing the coverage to checkable statements:

  • A Google model named Gemini Decision 1 is reported at $0.10 per million tokens.
  • A Meta model named Muse Ember is reported at per-token pricing comparable to Anthropic's Haiku tier.
  • An analysis claims open-weight tokens cost about 7x less than closed frontier tokens.
  • The same analysis claims OpenAI and Anthropic retain roughly 95% of AI spend.

What the reports do not establish: the input/output split behind those rates, whether caching or batch tiers are included, what throughput or latency the price buys, or the methodology behind the 7x figure. That last one matters most. "Open-weight tokens cost 7x less" is almost certainly a comparison of a hosted open-weight price per token against a closed frontier price per token — not a total-cost-of-ownership claim about running the weights yourself. Those are different numbers, often by an order of magnitude, and conflating them is the most common error in every build-vs-buy deck I've seen.

The 95% spend concentration is the more interesting data point, precisely because it's a revealed preference rather than a price sheet. If open-weight tokens really were 7x cheaper on a like-for-like basis, and price were the only axis that mattered, 95% share for the expensive options would be hard to explain. It gets easy once you price in evaluation cost, latency, and the engineering time nobody puts on the slide.

The decision this calculator answers: build, buy, or both

There are three architectures, and the calculator prices all three:

  1. Buy — every request goes to a hosted API.
  2. Build — you provision GPUs, run an open-weight model, and pay a fixed monthly bill regardless of traffic.
  3. Both — a self-hosted baseline with API burst, or a cheap-first cascade with API escalation.

The first two are priced so differently that they need a shared unit. I use USD per million total tokens, where "total" means input tokens plus output tokens, not input tokens alone. It's the only unit I've found that survives contact with both sides: a GPU-hour converts naturally into all the tokens it emitted, and an API bill is the sum of input and output charges.

The position this post defends — sticker price is not the comparison; utilization is

The claim, stated plainly: for almost every team reading this, the $0.10/M headline makes self-hosting strictly worse, not better. A cheaper API doesn't narrow the self-host gap the way people expect, because the self-host side has a large fixed term that doesn't care about token price. When the API price falls, the volume you need to break even climbs, and the capacity ceiling you'd have to cross doesn't move.

Concretely, with the assumptions laid out below, a single rented H100 running an open-weight model at 2,500 aggregate tokens/second breaks even against a frontier-priced API at about 1.59 billion tokens per month, at roughly 24% utilization. Against a $0.10/M-class API, the same fleet never breaks even at any volume it can physically serve — you'd need to sustain more than 6,900 tokens/second per $2.50 GPU-hour on raw GPU cost alone, before a single engineer-hour.

That second number is the one to remember. It's falsifiable, and you can measure it.

The Only Equation That Matters: Cost per Million Tokens

Write the arithmetic before writing any code. If the algebra is wrong, the JavaScript is just faster wrongness.

Define the workload mix once and reuse it:

SymbolMeaningTypical value
rinput tokens per output token4 (chat), 20+ (RAG/batch)
hfraction of input tokens that hit prompt cache0 to 0.9
moutput multiplier from reasoning/billed hidden tokens1.0 to 3.0+
Aamplification: billed tokens per useful token1.0 to 2.0
uutilization of provisioned GPU capacity0.05 to 0.9

API side: input/output asymmetry, cached input, and batch tiers

For one million total tokens at ratio r, the input share is r/(1+r) and the output share is 1/(1+r). That single substitution is what stops you from comparing an input-token list price against a blended bill.

C_api(1M) = A × [ (r/(1+r)) × ((1−h)·p_in + h·p_cache) + (1/(1+r)) × m·p_out ]

Batch tiers aren't a separate term. They're a discount applied to p_in and p_out before you plug them in, and only for traffic that can tolerate asynchronous completion. If your product is a chat UI, the batch price doesn't exist for you.

Self-hosted side: converting GPU-hours into tokens

The GPU bill is fixed per month. The token count is variable. So the per-token price is a division, not a rate:

C_self(1M) = (G + O + P) × 1e6 / (Q × 3600 × 730 × u)

where G is GPU cost, O is ops, P is platform (storage, egress, logging), Q is measured aggregate throughput in tokens/second at the concurrency you actually run, and 730 is average hours per month.

What's not in that expression: token count. Self-hosted $/M tokens is entirely a function of how full you keep the box. Which is why the utilization term decides everything, and why "our model costs $0.28/M tokens" is a meaningless sentence without a utilization figure attached.

Why a headline '7x cheaper' figure is usually a unit mismatch, not a finding

Four ways to produce a 7x gap without any of them being a fair comparison:

  • Blended vs split. A $0.10/M input rate against a $0.40/M output rate is a 4x spread before you even involve a second provider.
  • Cached vs uncached. With a 60% cache hit rate and a 75% cache discount, the effective input rate drops 2.5x. Comparing cached-input pricing to someone else's uncached pricing manufactures a gap.
  • Batch vs realtime. Batch discounts of ~50% are common on major providers. Batch-vs-realtime is not a vendor comparison.
  • Hosted open-weight vs self-hosted open-weight. This is the big one. Hosting fees for open weights are a price, set by a competitive market with thin margins. Self-hosting is a cost, dominated by fixed capacity and engineer time. They can differ by 10x in either direction.

I suspect the 7x claim falls into the first or fourth bucket. Untested — I haven't seen the methodology — but the unit-mismatch explanation is far more parsimonious than a real 7x TCO gap.

Writing the canonical cost function before writing any code

Two functions, total:

C_api(1M)  = A × [ (r/(1+r))·((1−h)p_in + h·p_cache) + (1/(1+r))·m·p_out ]
C_self(1M) = (G + O + P)·1e6 / (Q·3600·730·u)

Set them equal and solve for utilization, and you get the number the whole post turns on:

u* = (G + O + P)·1e6 / (Q · 3600 · 730 · C_api)

If u* is greater than the utilization you can realistically sustain, self-hosting loses. If u* > 1, self-hosting isn't merely worse — it's arithmetically unreachable.

Building the Calculator Skeleton in JavaScript

The input schema: what must be a parameter, never a constant

Three things get hardcoded into cost spreadsheets constantly, and all three are wrong:

  • The input:output ratio. It's workload-specific. A RAG pipeline at r = 20 has a completely different cost profile from a chat product at r = 4.
  • The engineer rate. Loading factor (benefits, overhead, on-call) typically pushes a $150k salary past $200k loaded. Use the salary and you're understating ops by ~30%.
  • Throughput. Spec-sheet FLOPS never converts cleanly into tokens/second. Only a measurement does.

Provider prices, GPU hourly rate, cache hit rate — all of it is also an input, because all of it moves.

Pure functions with no I/O so results are reproducible

// cost-model.js — pure, deterministic, zero I/O, zero dependencies.
// Units: USD, raw tokens, seconds. Nothing here reads env vars or the clock.

export const SECONDS_PER_HOUR = 3600;
export const HOURS_PER_MONTH = 730; // 365 * 24 / 12

/**
 * API side.
 * api: { inputPerM, cachedInputPerM, outputPerM }  — USD per 1M tokens
 * mix: { inputOutputRatio, cacheHitRate, outputMultiplier, amplification }
 * Returns USD per 1M *total* tokens (input + output combined).
 */
export function apiCostPerMillionTotal(api, mix) {
  const {
    inputOutputRatio: r,
    cacheHitRate: h = 0,
    outputMultiplier: m = 1,
    amplification: a = 1,
  } = mix;

  if (!(r > 0)) throw new Error("inputOutputRatio must be > 0");
  if (h < 0 || h > 1) throw new Error("cacheHitRate must be in [0,1]");
  if (m < 1) throw new Error("outputMultiplier cannot be below 1");

  const inputShare = r / (1 + r);
  const outputShare = 1 / (1 + r);

  const effectiveInputRate = (1 - h) * api.inputPerM + h * api.cachedInputPerM;
  const effectiveOutputRate = api.outputPerM * m;

  return a * (inputShare * effectiveInputRate + outputShare * effectiveOutputRate);
}

/** Self-hosted fixed monthly bill. Independent of how many tokens you serve. */
export function selfHostMonthlyCost(sh) {
  const { gpus, usdPerGpuHour, engineerFte, loadedFteUsdYear, platformUsdMonth } = sh;
  const gpu = gpus * HOURS_PER_MONTH * usdPerGpuHour;
  const ops = (engineerFte * loadedFteUsdYear) / 12;
  const platform = platformUsdMonth;
  return { gpu, ops, platform, total: gpu + ops + platform };
}

/** Total tokens the fleet can emit in a month at 100% utilization, in millions. */
export function selfHostCapacityTokensPerMonthM(capacityTps) {
  return (capacityTps * SECONDS_PER_HOUR * HOURS_PER_MONTH) / 1e6;
}

export function selfHostCostPerMillion(monthlyTotalUsd, capacityTps, utilization) {
  const tokensM = selfHostCapacityTokensPerMonthM(capacityTps) * utilization;
  return monthlyTotalUsd / tokensM;
}

A deterministic run harness and a fixed example workload

The harness is where you declare, in one place, every number you're guessing at. Mine are below. They're assumptions, not measurements — I haven't benchmarked an H100 for this post, and capacityTps in particular is the input most likely to be wrong by 2x in either direction (more on that in the falsification section).

// sweep.js — run with: node sweep.js

  apiCostPerMillionTotal,
  selfHostMonthlyCost,
  selfHostCapacityTokensPerMonthM,
} from "./cost-model.js";


// --- Provider prices. Assumed, in the spirit of the $0.10/M reports. ---
const COMMODITY = { inputPerM: 0.10, cachedInputPerM: 0.025, outputPerM: 0.40 };
const FRONTIER  = { inputPerM: 3.00, cachedInputPerM: 0.30,  outputPerM: 15.00 };

// --- Workload mix. Assumed. ---
const MIX = {
  inputOutputRatio: 4,   // 4 input tokens per output token
  cacheHitRate: 0.6,     // 60% of input tokens hit the prompt cache
  outputMultiplier: 1,   // no billed reasoning tokens in this workload
  amplification: 1.15,   // 15% overhead from retries and agent loops
};

// --- Self-host inputs. ALL ASSUMED. Measure yours. ---
const SH = {
  gpus: 1,
  usdPerGpuHour: 2.50,
  engineerFte: 0.25,
  loadedFteUsdYear: 200_000,
  platformUsdMonth: 1_500,
};
const CAPACITY_TPS = 2_500; // assumed aggregate throughput, tokens/sec

const apiCommodity = apiCostPerMillionTotal(COMMODITY, MIX);
const apiFrontier  = apiCostPerMillionTotal(FRONTIER, MIX);
const monthly      = selfHostMonthlyCost(SH);
const capacityM    = selfHostCapacityTokensPerMonthM(CAPACITY_TPS);

console.log(`API  commodity : $${apiCommodity.toFixed(4)} / M total tokens`);
console.log(`API  frontier  : $${apiFrontier.toFixed(4)} / M total tokens`);
console.log(`Self-host bill : $${monthly.total.toFixed(2)} / month`);
console.log(`  gpu          : $${monthly.gpu.toFixed(2)}`);
console.log(`  ops          : $${monthly.ops.toFixed(2)}`);
console.log(`  platform     : $${monthly.platform.toFixed(2)}`);
console.log(`Capacity       : ${capacityM.toFixed(0)} M tokens / month at u=1.0`);

for (const [label, ppm] of [["commodity", apiCommodity], ["frontier", apiFrontier]]) {
  const beM = breakEvenMonthlyTokensM(monthly.total, ppm);
  const beU = breakEvenUtilization(monthly.total, ppm, CAPACITY_TPS);
  const verdict = beU > 1 ? "UNREACHABLE at this fleet size" : `reachable at u = ${beU.toFixed(3)}`;
  console.log(`vs ${label}: break-even ${beM.toFixed(0)} M tokens/mo — ${verdict}`);
}

Reproducible output:

API  commodity : $0.1426 / M total tokens
API  frontier  : $4.7196 / M total tokens
Self-host bill : $7491.67 / month
  gpu          : $1825.00
  ops          : $4166.67
  platform     : $1500.00
Capacity       : 6570 M tokens / month at u=1.0
vs commodity: break-even 52536 M tokens/mo — UNREACHABLE at this fleet size
vs frontier: break-even 1587 M tokens/mo — reachable at u = 0.242

The last two lines carry the real weight. Against the commodity API, you'd need 52.5 billion tokens per month to break even — and the fleet in front of you tops out at 6.57 billion. Against the frontier API, break-even is 1.59 billion at 24% utilization, which is achievable.

The composition of the self-host bill is also worth staring at: 56% ops, 24% GPU, 20% platform. The GPU is the smallest line item of the three named, and the smallest of the four including platform. That is not the mental model most teams bring to this decision.

Modeling API Cost Without Lying to Yourself

Token split: why the input:output ratio moves the number more than list price

At r = 4, output is 20% of tokens but 64% of the bill on the frontier price ($3.00/$15.00). Push r to 20 — a RAG workload with long retrieved contexts — and output share of tokens drops to 4.8%.

Run the frontier price at both ratios:

rinput shareoutput share$/M total
10.500.50$9.00
40.800.20$5.40
200.9520.048$3.57
500.9800.020$3.24

(Ignoring cache and amplification for clarity; the calculator applies both.)

That's a 2.8x swing from workload shape alone, and it dwarfs the difference between most competing price sheets. If you compare two providers without fixing r, you aren't comparing the providers.

Prompt caching and the cache-hit ratio term

Caching is the highest-leverage lever on the API side, and the least-modeled. With p_in = 3.00, p_cache = 0.30, and h = 0.6, the effective input rate falls from $3.00 to $1.38 — a 2.2x cut on the dominant term for a RAG workload.

The trap is claiming a cache hit rate you don't have. Hit rate depends on prompt prefix stability. A system prompt plus tool definitions that never change gives you a high h. A chat product that injects per-user context near the top of the prompt gives you h ≈ 0 no matter how good the provider's cache is. Measure h from your provider's usage response; don't assume it.

Retries, agent loops, and token amplification factors

A is billed tokens per useful token. In production it's always greater than 1. In the pitch deck it's always 1.0.

Sources: transport retries that bill twice, structured-output repair passes, agent loops that re-send the growing transcript on every step, and tool-call round trips. The agent case is the expensive one — an N-step loop that re-sends full history bills roughly O(N²) tokens for O(N) useful work.

A = 1.15 in my harness is a mild assumption. An agentic workload can hit A = 2.5 without anyone noticing, and because A multiplies the whole expression, it hits input and output terms at once.

Reasoning-token billing and other output multipliers

If a model emits billed hidden reasoning tokens, m rises. Report your numbers as "billed output tokens," not "visible output tokens" — the gap is where budgets die. If a model's reasoning trace runs 2x the visible answer length and is billed at the output rate, m = 3, and that alone triples the output term.

I haven't measured reasoning-token overhead for Gemini Decision 1 or Muse Ember and won't guess at it. If you're modeling either, get m from a real usage response before you commit.

Modeling Self-Hosted Inference: From GPU-Hours to $/M Tokens

Amortizing hardware: rental rate vs. capex over a three-year life

Rental is the honest default because it prices in depreciation, datacenter, and network for you, and it turns capex into a rate you can drop into the same equation. An H100 SXM at typical on-demand rental lands in the low single-digit dollars per GPU-hour; $2.50/hr is what I use.

Capex is arithmetic you should run at least once to see how bad it usually is:

c capex $ per GPU-hour = price / (3 years × 8760 h × utilization)

A $30,000 GPU over three years at 100% utilization is $1.14/GPU-hour. At the 25% utilization most teams actually achieve, it's $4.57/GPU-hour — worse than renting, and you also own the depreciation risk on hardware that will be superseded inside the depreciation window. Self-hosting is a bet that your model stays good and your traffic stays high for three years. Both bets are risky in this market.

Measured throughput beats spec-sheet FLOPS — how to measure it

Spec-sheet numbers describe matrix-multiply peak. They don't describe serving. What you need is aggregate completion tokens per second at your production concurrency, at your context length, with your model and quantization.

// bench-throughput.js — measure aggregate tokens/sec against YOUR OWN local server.
// Point BASE_URL at a host you control. Never benchmark a host you do not own.

const BASE  = process.env.BASE_URL ?? "http://127.0.0.1:8000/v1";
const MODEL = process.env.MODEL    ?? "local-model";

async function fire(prompt, maxTokens) {
  const t0 = performance.now();
  const res = await fetch(`${BASE}/completions`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ model: MODEL, prompt, max_tokens: maxTokens, temperature: 0 }),
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const body = await res.json();
  return { ms: performance.now() - t0, tokens: body.usage.completion_tokens };
}

export async function measure({ prompt, maxTokens = 256, concurrency = 32, rounds = 7 }) {
  const rows = [];
  for (let r = 0; r < rounds; r++) {
    const t0 = performance.now();
    const batch = await Promise.all(
      Array.from({ length: concurrency }, () => fire(prompt, maxTokens))
    );
    const wallMs = performance.now() - t0;
    const tokens = batch.reduce((sum, x) => sum + x.tokens, 0);
    rows.push({ wallMs: Math.round(wallMs), tokens, tps: (tokens / wallMs) * 1000 });
  }
  rows.sort((a, b) => a.tps - b.tps);
  return rows; // report the MEDIAN, not the max
}

Two rules for reading that output. Report the median, not the maximum — the max is the run where the scheduler happened to be kind. And sweep concurrency, because throughput isn't a constant. Run concurrency ∈ {1, 4, 16, 64, 256} and plot the curve; the number you want is at the concurrency your product actually produces at peak, not the sweet spot you found on a quiet afternoon.

Prefill vs. decode: compute-bound and memory-bandwidth-bound phases

These two phases have completely different bottlenecks, and treating them as a single "tokens/second" number hides real cost.

Prefill processes the whole prompt in parallel. It's compute-bound — a big matrix multiply with high arithmetic intensity.

Decode generates one token at a time. It reads the entire weight matrix to produce a single token, so it's memory-bandwidth-bound. On an H100 SXM with roughly 3.35 TB/s of HBM3 bandwidth (NVIDIA's datasheet figure), an 8B model in FP16 (about 16 GB of weights) has a theoretical single-sequence ceiling near:

3.35e12 bytes/s ÷ 1.6e10 bytes/token ≈ 209 tokens/s

That's why batch-1 serving is terrible and why batching is the whole game. At batch 64, the weight read is amortized across 64 tokens, and the ceiling rises by roughly that factor — until KV cache traffic and memory capacity take over. It also means a 70B model in FP16 (~140 GB) doesn't fit on a single 80 GB H100 at all; it needs two GPUs or quantization, and both change Q.

Batching, KV cache growth, and the real concurrency ceiling

Throughput doesn't rise forever with batch size. It rises until the KV cache fills the GPU, then it collapses — vLLM blocks, preemption kicks in, and tail latency goes vertical.

KV cache size per token:

bytes/token = 2 × layers × kv_heads × head_dim × bytes_per_element

Worked example for an 8B-class model with 32 layers, 8 KV heads, head_dim 128, FP16:

2 × 32 × 8 × 128 × 2 = 131,072 bytes = 128 KiB per token

At an 8k context that's 1 GiB per concurrent sequence. With roughly 70 GiB usable for KV on an 80 GB card after weights, you fit about 68 concurrent 8k sequences. That's your ceiling, and it's a memory-capacity number, not a compute number.

Validate your measured Q against that ceiling before you trust it. If your harness reported a throughput that implies more concurrent sequences than the KV budget allows, the harness is wrong.

Quantization: the throughput win and the quality tax it charges

Weight quantization cuts memory and therefore raises both batch ceiling and decode speed. FP8 roughly halves weight memory; 4-bit methods (GPTQ, AWQ) cut it about 4x, freeing most of the card for KV cache. Applied naively, this looks like a free 2–3x on Q, and it will happily improve your calculator output.

The tax is real and task-dependent. I haven't evaluated quality deltas for any specific model at any specific bit width here, and I'd be suspicious of anyone quoting a single number for it. Treat quantization as: run your own eval set at the target bit width, record the delta, and only then book the throughput win. If the quantized model fails 3% more often on your task, and a wrong answer costs a support ticket, you didn't save money.

The Utilization Term That Decides Everything

Deriving the break-even utilization formula

Set C_api(1M) = C_self(1M) and solve for u:

u* = (G + O + P) × 1e6 / (Q × 3600 × 730 × C_api(1M))
// break-even.js


/** Monthly volume (in millions of tokens) at which self-host cost per M equals API cost per M. */
export function breakEvenMonthlyTokensM(monthlyTotalUsd, apiUsdPerM) {
  return monthlyTotalUsd / apiUsdPerM;
}

/** Fraction of provisioned capacity you must sustain for the crossover to hold. */
export function breakEvenUtilization(monthlyTotalUsd, apiUsdPerM, capacityTps) {
  return breakEvenMonthlyTokensM(monthlyTotalUsd, apiUsdPerM)
       / selfHostCapacityTokensPerMonthM(capacityTps);
}

Two properties of u* are worth internalizing:

  • It goes up when the API gets cheaper, roughly linearly. Halve C_api and u* doubles.
  • It's capped by 1. When u* > 1, self-hosting isn't expensive — it's impossible without adding GPUs, which raises G and pushes u* further.

Peak-to-average ratio and why provisioning for peak destroys the case

You don't size a fleet for average traffic. You size it for peak, plus headroom. If your product has a 4x peak-to-average ratio and a 4x safety margin, the fleet that handles your peak sits at roughly 1/(4×4) ≈ 6% utilization for most of the month.

Plug u = 0.06 into the one-GPU fleet: $7,491.67 / (6570 × 0.06) = $19.01 per million tokens. Against a $4.72/M frontier API, that's a 4x loss. Against a $0.14/M commodity API, it's 133x.

That's the crux, and it's why I think the "$0.10/M means self-hosting is finally viable" reading is backwards. Cheap APIs don't make fixed-cost infrastructure easier to justify — they raise the utilization bar, and utilization is the thing you control least.

Idle GPUs, autoscaling lag, and cold-start costs

There's no serverless story here that closes the gap. GPU cold start means loading tens of gigabytes of weights from storage or network, which is tens of seconds to minutes depending on the path and caching layer. A burst of traffic that arrives inside that window either fails or queues.

So most "autoscaling" self-host deployments are really fixed fleets with a manual scale-up, which means the peak-provisioning math above applies and the advertised burst capacity doesn't exist on the timescale your users care about. Model this honestly: if your scale-up latency is 90 seconds and your traffic spikes last 60 seconds, u is whatever your floor-sized fleet's utilization is, not what it would be if scaling were instant.

Ops Overhead Is a Line Item, Not a Footnote

Converting engineer hours into a monthly dollar figure

O_per_month = engineer_fraction × loaded_annual_cost / 12

The loading factor is the part people skip. Salary plus benefits plus employer taxes plus equipment plus allocated overhead typically lands 1.3–1.5x the base. A $150k base becomes roughly $200k loaded, so 0.25 FTE is $4,167/month — not $3,125.

Is 0.25 FTE a fair estimate for running a vLLM deployment? For a stable single-model deployment with a working Dockerfile, it's plausible. For a team also chasing quantization quality, keeping up with open-weight releases, and debugging throughput regressions, 0.5 FTE is more honest. Remember that O is 56% of the self-host bill in the base case — doubling it moves the frontier break-even from 1.59B to 2.5B tokens/month.

On-call, model upgrades, and the churn of open-weight releases

Ops doesn't scale down when traffic is flat. It scales with the rate of change in the ecosystem. Open-weight releases land continuously, each one a candidate upgrade that requires re-running evals, re-tuning batch sizes, and re-validating quantization quality. That work is a per-month constant, and unlike the GPU bill, nobody gives you a discount on it for being small.

The API side has an equivalent cost, but it's different in kind: your provider upgrades the model under you, which is a reliability problem rather than a labor problem. Neither is free.

Egress, storage, logging, and the platform bill nobody models

P in my harness is $1,500/month and covers object storage for weights and logs, egress, load balancing, metrics, and the secrets management you inevitably end up needing. It's the least defensible line item in the whole model and the one most likely to be omitted entirely. Cheap to include, embarrassing to forget.

The Working Calculator: Modules, Break-Even Solver, and Sensitivity Sweep

cost-model.js — API branch, self-host branch, shared unit handling

The two files shown above (cost-model.js and break-even.js) are complete and runnable under Node 18+. The shared unit — USD per million total tokens — is enforced at the function boundary: apiCostPerMillionTotal normalizes by r/(1+r), and selfHostCostPerMillion divides by capacity times utilization. Neither can silently return "per million input tokens."

break-even.js — solving for monthly token volume and utilization

breakEvenMonthlyTokensM answers "how much traffic do I need," and breakEvenUtilization answers "how full must the fleet be." They're the same equation rearranged, and I keep both because they fail in different ways when you feed them a bad Q.

A sensitivity sweep that prints a table instead of a single number

A single number is a lie of precision. Sweep the volume axis and print the whole surface:

// sweep-table.js — reuses the imports and constants from sweep.js above.
const apiCommodity = apiCostPerMillionTotal(COMMODITY, MIX);
const apiFrontier  = apiCostPerMillionTotal(FRONTIER, MIX);

const monthly   = selfHostMonthlyCost(SH);
const capacityM = selfHostCapacityTokensPerMonthM(CAPACITY_TPS);
const volumes   = [10, 50, 250, 1000, 1587, 2000, 4000, 6570];

const pad = (v, n) => String(v).padEnd(n);
console.log(pad("vol M/mo", 10) + pad("util", 8) + pad("API cheap", 12)
          + pad("API frntr", 12) + pad("self-host", 12) + "self $/M");

for (const v of volumes) {
  const u = v / capacityM;
  if (u > 1) { console.log(`${v}  exceeds fleet capacity`); continue; }
  console.log(
    pad(v, 10) +
    pad(u.toFixed(3), 8) +
    pad("$" + (v * apiCommodity).toFixed(2), 12) +
    pad("$" + (v * apiFrontier).toFixed(2), 12) +
    pad("$" + monthly.total.toFixed(2), 12) +
    "$" + (monthly.total / v).toFixed(2)
  );
}

Output, reproduced from the harness:

vol M/mo  util    API cheap   API frntr   self-host   self $/M
10        0.002   $1.43       $47.20      $7491.67    $749.17
50        0.008   $7.13       $235.98     $7491.67    $149.83
250       0.038   $35.65      $1179.90    $7491.67    $29.97
1000      0.152   $142.60     $4719.60    $7491.67    $7.49
1587      0.242   $226.31     $7489.11    $7491.67    $4.72
2000      0.304   $285.20     $9439.20    $7491.67    $3.75
4000      0.609   $570.40     $18878.40   $7491.67    $1.87
6570      1.000   $936.88     $31007.77   $7491.67    $1.14

Read the last row. At 100% utilization — a fantasy for a real production fleet — self-hosting lands at $1.14/M total tokens against a $0.14/M commodity API. It's still eight times more expensive. That's not a modelling artifact; it's the arithmetic.

Reading the Output: Two Worked Scenarios

Scenario A: a 50M-token/month chat product

50M total tokens/month, r = 4, 60% cache hit.

OptionMonthly costNotes
Commodity API$7.13at 0.76% of a single GPU's capacity
Frontier API$235.9833x the commodity price
Self-host (1 GPU)$7,491.67at 0.76% utilization, $149.83/M

Self-hosting is 32x more expensive than the frontier API and roughly 1,000x the commodity API. This isn't close, and no plausible adjustment to my assumptions closes it. You'd have to eliminate ops entirely, get the GPU for $0, and still lose by more than an order of magnitude on the commodity side.

Buy. Do not build. Do not think about building.

Scenario B: a 2B-token/month batch pipeline

2B tokens/month. Batch workloads tolerate asynchronous completion, so the batch tier applies on the API side — but I deliberately do not apply a batch discount here, to keep the self-host comparison conservative.

OptionMonthly cost$/M total
Commodity API$285.20$0.14
Frontier API$9,439.20$4.72
Self-host (1 GPU)$7,491.67$3.75

At 30.4% utilization the self-hosted fleet is 21% cheaper than the frontier API — a real but modest win, and it flips to a loss the moment utilization drops below 24.2%.

Against the commodity API, self-hosting is 26x more expensive. If the reported $0.10/M price holds and your quality bar is met by that model, the build case evaporates entirely.

Where the crossover lands and which single input flips it

Two crossovers, two very different answers:

Compared againstBreak-even volumeRequired utilizationReachable?
Frontier API ($4.72/M)1,587 M tokens/mo0.242Yes, at 2B/mo
Commodity API ($0.14/M)52,536 M tokens/mo8.0No — 8x beyond fleet capacity

The commodity case isn't "expensive." It requires a u* of 8.0, which means you'd need eight GPUs' worth of capacity to serve what you have and run them all at 100%. Scaling the fleet scales G and Q together, so u* doesn't improve. The only thing that shrinks with scale is O and P per token — which converges, in the limit, to the raw GPU cost at u = 1: $0.28/M. Still twice the $0.14/M commodity API, at infinite scale with zero engineers.

The single input that flips Scenario B: utilization. At 2B tokens/month you sit at 30.4% against a 24.2% requirement — about 20% of headroom. A 20% traffic decline, or a peak that forces you to add a second GPU, turns the win into a loss. That's a thin margin on a decision you can't easily reverse.

Separating measured results from assumed inputs in the writeup

Everything in the tables above is a deterministic output of assumed inputs. The code is reproducible; the inputs are not verified. Specifically, in decreasing order of how wrong they could be:

  • capacityTps = 2,500 — assumed, not measured. If real throughput is 1,200, every self-host $/M figure doubles and Scenario B flips to the frontier API.
  • engineerFte = 0.25 at $200k loaded — assumed. Set it to 0 and the frontier break-even drops to 704M tokens/month; set it to 0.5 and it rises to 2.5B.
  • usdPerGpuHour = 2.50 — assumed, bracketed by market rates but not quoted from a specific provider.
  • Provider prices — assumed, modelled on the shape of the reports, not read off a live pricing page.
  • cacheHitRate = 0.6, amplification = 1.15 — assumed, and workload-specific.

One thing I'll treat as a finding rather than an assumption: the structure of the answer holds across every reasonable value of these inputs. Below about a billion tokens per month, self-hosting loses to any API. Above about 50 billion, it likely wins against frontier pricing. In between, it comes down to utilization, and utilization is the input nobody measures.

What the Calculator Deliberately Does Not Capture

Quality, eval cost, and the price of a wrong answer

The calculator assumes the two options produce equivalent output. They don't. A $0.10/M model and a $5/M model aren't interchangeable, and the difference only shows up in your eval set, which you have to build and maintain — itself a line item.

A useful reflex: compute the cost of a wrong answer. If a wrong answer costs a $15 support ticket and the cheaper model is 2% worse on your task, that's $0.30 per hundred requests — which can dwarf the per-token savings. Model it as an expected cost per request and add it to both branches.

Latency SLOs, tail behavior, and queueing under batch load

Self-hosted p99 latency degrades nonlinearly as utilization approaches 1. Queueing theory is unforgiving: wait time grows roughly as u/(1−u), so going from 70% to 90% utilization triples your queue time. That means the utilization that maximizes cost efficiency is not the utilization you should run at. The 24% break-even in Scenario B is comfortable for latency; a 70% utilization target would be a latency disaster for interactive traffic.

The calculator treats all tokens as equivalent, but batch and realtime traffic can't share a fleet without one degrading the other.

Data residency, license terms, and open-weight usage restrictions

Open weights aren't the same as unrestricted weights. Licenses vary on commercial use thresholds, acceptable-use terms, attribution, and redistribution. Hosted APIs carry their own regional-processing and retention terms. Neither is a solved problem, and neither appears in the arithmetic. If you're in a regulated industry, this section may be the entire decision.

Vendor risk in both directions

API risk: price changes, rate limits, deprecations, model swaps under a stable name, and the 95% spend concentration reported by FourWeekMBA — which is a concentration risk for the entire ecosystem, not just for you.

Self-host risk: the model you picked gets superseded, the GPU generation depreciates, the engineer who built the deployment leaves, and your throughput regresses silently after a dependency upgrade.

The calculator prices neither. Both belong somewhere in your decision memo, even if as a paragraph rather than a column.

Routing and Cascade Architectures Change the Comparison

Cheap-first cascade with measured escalation rate

Route every request to the cheap model, escalate a fraction to the expensive one, and the blended cost is:

C_cascade(1M) = C_cheap(1M) + e × (C_expensive(1M) − C_cheap(1M))

where e is the escalation rate. With C_cheap = $0.14, C_expensive = $4.72, and e = 0.15:

0.1426 + 0.15 × (4.7196 − 0.1426) = $0.829 / M

That's an 82% cut against always-frontier, at the cost of one escalation classifier. The whole architecture lives or dies on e, and e is measurable: log which tier answered each request, divide. Don't estimate it.

Routing is also the only architecture where the "$0.10/M" headline changes your bill at full magnitude — because the cascade keeps most traffic on the cheap tier. That's the real implication of the price collapse, and it's a routing story, not a self-hosting story.

Hybrid: self-hosted baseline with API burst capacity

Self-host a baseline sized for your floor traffic, and let the API absorb peaks. This fixes the utilization problem from the other direction: the fleet runs at high utilization because it only ever serves the floor, and the peak-to-average ratio stops destroying your economics.

C_hybrid(1M) = f_floor × C_self(1M, u_high) + (1 − f_floor) × C_api(1M)

With a floor covering 70% of traffic at 85% utilization:

0.70 × ($7491.67 / (6570 × 0.85)) + 0.30 × $4.7196
= 0.70 × $1.342 + 0.30 × $4.7196
= $0.939 + $1.416 = $2.355 / M

Half of always-frontier, and the floor fleet is genuinely well-utilized. This is the architecture I'd actually recommend to a team with steady, high-volume, latency-tolerant traffic — and it still loses badly to a reliable commodity API if one exists at your quality bar.

Instrumenting the router so the model stays honest

A cascade is an optimization that silently degrades if you stop watching it. Log, per request: tier chosen, tokens in and out, cache hit, retry count, escalation decision, and final outcome. Emit e, h, and A as monthly metrics and feed them back into the calculator. The moment escalation drifts from 15% to 40% — which it will, after a prompt change or a model update — the blended cost changes by 60% and nobody notices until the invoice.

How to Falsify Your Own Numbers Before Trusting Them

Benchmark the real workload, not a synthetic prompt

"Hello, how are you?" at 32 output tokens tells you nothing. Use a sample of your actual production prompts: real input lengths, real output lengths, real concurrency. The three properties that determine Q are context length, output length, and concurrency — and a synthetic benchmark will get all three wrong in the optimistic direction.

Publish assumptions next to conclusions

A cost model that says "self-hosting costs $3.75/M" is unusable. One that says "$3.75/M given 2,500 tok/s, 0.25 FTE at $200k loaded, $2.50/GPU-hr, and 30% utilization" is falsifiable — a colleague can point at the wrong one. Every table in this post lists its assumptions for that reason.

A short checklist of inputs most likely to be wrong

  1. capacityTps — the big one. Almost everyone overestimates it by quoting a benchmark from a smaller model at a lower context length.
  2. Utilization. Peak-provisioned fleets often run below 10%. Compute it from real traffic, not from intent.
  3. engineerFte. Nobody believes their own overhead. Try doubling it and see if the conclusion survives.
  4. cacheHitRate. Providers report hit rate in the usage object. Read it; don't assume it.
  5. amplification. Count billed tokens, not useful tokens, and compare to your request count.
  6. The GPU rate. Rental prices move. Requote before you commit.
  7. platformUsdMonth. The line item most often omitted and least often itemized.

If your conclusion survives all seven being wrong in the same direction, you probably have a real answer.

Conclusion

The $0.10/M headline is a genuine change in the API market, and I think the coverage is directionally right that prices are falling hard at the low end. But the conclusion a lot of teams will draw from it — "cheap enough that self-hosting is obviously better now" — is backwards. Cheap APIs raise the utilization bar for fixed infrastructure, and utilization is the input that peak-provisioned fleets fail most often.

The arithmetic here is thirty lines of JavaScript, and with my assumptions it produces a sharp answer: against a frontier-priced API, self-hosting breaks even near 1.6 billion tokens per month at 24% utilization, with about 20% of margin. Against a $0.10/M-class API, the same fleet needs a utilization of 8.0 — eight times the capacity it physically has — and converges on a floor of $0.28/M even with zero engineers and infinite scale. Under those conditions, self-hosting doesn't lose on price. It loses on physics.

Where the price collapse genuinely changes the decision is routing. A cheap-first cascade at a 15% escalation rate cuts the blended bill by 82% against always-frontier, and a floor-plus-burst hybrid gets you to roughly half. Those are the architectures that convert a lower price sheet into a lower invoice, and they're measurable — e, h, and A all come out of your logs.

So: measure your throughput, measure your cache hit rate, measure your escalation rate, and put the assumptions next to the conclusion. Then run the sweep. If the answer comes back "buy," that isn't a failure of ambition — it's a cost model doing its job.

Further Reading

Share this post

More posts

Loading comments…