<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Chargeback on lo0 — Tech Blog</title><link>https://blog.lo0.es/en/tags/chargeback/</link><description>Recent content in Chargeback on lo0 — Tech Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 15 Jun 2026 04:00:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/en/tags/chargeback/index.xml" rel="self" type="application/rss+xml"/><item><title>GPU chargeback and showback in multi-tenancy: how to split cluster cost between teams</title><link>https://blog.lo0.es/en/posts/gpu-chargeback-showback-multi-tenancy/</link><pubDate>Mon, 15 Jun 2026 04:00:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/gpu-chargeback-showback-multi-tenancy/</guid><description>&lt;blockquote>
&lt;p>Notation: amounts in &lt;strong>euros (N €)&lt;/strong>, decimals with a point. The dollar sign is not used
(on this site it is a formula delimiter).&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>A cluster of 4×H100 SXM at ~1.40 €/GPU-hour (depreciated capex plus energy) shared between three
teams with different occupancy produces a monthly chargeback report of 3 rows and an additional
idle row that nobody claimed. Without tooling, that idle cost vanishes, diluted into the total.
With OpenCost + LiteLLM + Kueue, attribution operates on three orthogonal planes: the &lt;strong>iron&lt;/strong>
(OpenCost, €/GPU-hour per namespace/label), &lt;strong>token consumption&lt;/strong> (LiteLLM, €/token per
key/team/model) and the &lt;strong>scheduler quota&lt;/strong> (Kueue, GPUs reserved per ClusterQueue). Crossing the
three produces the number that goes to finance: team B consumed X million tokens at Y €/1M tok,
the GPU cost it Z €, and it still has 2 GPUs borrowed from the cohort that will cost it W € if it
holds on to them next month.&lt;/p>
&lt;hr>
&lt;h2 id="showback-vs-chargeback-the-finops-foundation-definition">Showback vs chargeback: the FinOps Foundation definition&lt;/h2>
&lt;p>The distinction is not one of technology but of &lt;strong>accounting formality&lt;/strong>
(&lt;a href="https://www.finops.org/framework/capabilities/invoicing-chargeback/">FinOps Foundation — Invoicing &amp;amp; Chargeback&lt;/a>,
&lt;a href="https://www.finops.org/framework/previous-capabilities/analysis-showback/">FinOps Foundation — Data Analysis and Showback&lt;/a>):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Concept&lt;/th>
&lt;th>Definition&lt;/th>
&lt;th>Moves money&lt;/th>
&lt;th>Requires&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Showback&lt;/strong>&lt;/td>
&lt;td>visibility of consumption and its cost per team; the report arrives, the budget does not change&lt;/td>
&lt;td>No&lt;/td>
&lt;td>metrics + attribution&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Chargeback&lt;/strong>&lt;/td>
&lt;td>the cost is transferred to the team&amp;rsquo;s or product&amp;rsquo;s P&amp;amp;L as real expenditure&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>showback + accounting policy + financial system&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Two points from the framework worth pinning down:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Showback is a requirement of any FinOps practice; chargeback is optional&lt;/strong> and depends on
the organisation&amp;rsquo;s accounting policy supporting transfers between cost centres.&lt;/li>
&lt;li>&lt;strong>Neither is &amp;ldquo;more mature&amp;rdquo; than the other.&lt;/strong> The narrative that chargeback is the &amp;ldquo;grown-up&amp;rdquo;
version is false according to the framework itself. The natural sequence is: showback → trust
in the data → chargeback if policy allows it.&lt;/li>
&lt;/ol>
&lt;h3 id="when-to-use-each">When to use each&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Situation&lt;/th>
&lt;th>Recommended mode&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>First attribution cycle; teams do not trust the data yet&lt;/td>
&lt;td>Showback&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Teams with their own budget in a financial system&lt;/td>
&lt;td>Chargeback&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Shared cluster with no P&amp;amp;L separation per team&lt;/td>
&lt;td>Showback with visible idle cost&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Teams with a committed GPU availability SLA&lt;/td>
&lt;td>Chargeback (real reservation)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Research/experimentation workloads with no formal budget&lt;/td>
&lt;td>Showback + threshold alert&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="gpu-cost-attribution-with-opencost">GPU cost attribution with OpenCost&lt;/h2>
&lt;h3 id="allocation-api-attribution-parameters">Allocation API: attribution parameters&lt;/h3>
&lt;p>The OpenCost &lt;code>/allocation&lt;/code> API is the piece that turns Prometheus metrics into a cost report per
dimension (&lt;a href="https://opencost.io/docs/integrations/api/">OpenCost — API&lt;/a>):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Parameter&lt;/th>
&lt;th>Values&lt;/th>
&lt;th>Use in multi-tenancy&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>window&lt;/code>&lt;/td>
&lt;td>&lt;code>today&lt;/code>, &lt;code>7d&lt;/code>, &lt;code>lastmonth&lt;/code>, RFC3339 range&lt;/td>
&lt;td>window of the monthly report&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>aggregate&lt;/code>&lt;/td>
&lt;td>&lt;code>namespace&lt;/code>, &lt;code>label:LABEL&lt;/code>, &lt;code>annotation:NAME&lt;/code>, &lt;code>pod&lt;/code>, &lt;code>controller&lt;/code>&lt;/td>
&lt;td>attribution dimension&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>includeIdle&lt;/code>&lt;/td>
&lt;td>&lt;code>true&lt;/code> / &lt;code>false&lt;/code>&lt;/td>
&lt;td>adds an &lt;code>__idle__&lt;/code> row to the report&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>shareIdle&lt;/code>&lt;/td>
&lt;td>&lt;code>true&lt;/code> / &lt;code>false&lt;/code>&lt;/td>
&lt;td>distributes idle among the non-idle allocations (proportional to non-idle cost)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>idleByNode&lt;/code>&lt;/td>
&lt;td>&lt;code>true&lt;/code> / &lt;code>false&lt;/code>&lt;/td>
&lt;td>computes idle per node instead of per cluster&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>filter&lt;/code>&lt;/td>
&lt;td>&lt;code>namespace:&amp;quot;llm-prod&amp;quot;&lt;/code>, &lt;code>label:equipo:&amp;quot;datos&amp;quot;&lt;/code>&lt;/td>
&lt;td>restrict to one specific team&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Query for a monthly report per team, with idle visible as a separate row:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl -G http://localhost:9003/allocation &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="nv">window&lt;/span>&lt;span class="o">=&lt;/span>lastmonth &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="nv">aggregate&lt;/span>&lt;span class="o">=&lt;/span>label:equipo &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="nv">includeIdle&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="nv">shareIdle&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">false&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="nv">resolution&lt;/span>&lt;span class="o">=&lt;/span>10m
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With &lt;code>shareIdle=false&lt;/code> (the default), OpenCost returns idle as a separate &lt;code>__idle__&lt;/code> entry, which
makes visible how much was paid for unused capacity. With &lt;code>shareIdle=true&lt;/code>, that idle is
distributed among the tenants in proportion to their non-idle cost: each absorbs its share of the
waste, which is the right behaviour for a chargeback that penalises whoever is responsible for
the idle.&lt;/p>
&lt;h3 id="labelling-inference-pods">Labelling inference pods&lt;/h3>
&lt;p>For &lt;code>aggregate=label:equipo&lt;/code> to work, the inference pods need the right label. Two methods:&lt;/p>
&lt;p>&lt;strong>Label in the pod spec&lt;/strong> (the most direct):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># vLLM Deployment for the datos team&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">labels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">equipo&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">datos&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">producto&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">rag-prod&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">modelo&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llama-70b&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">entorno&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">prod&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>Annotation&lt;/strong> (when the label is already taken by another convention):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># aggregate as an annotation&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">aggregate&lt;/span>&lt;span class="o">=&lt;/span>annotation:finops.io/equipo
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>OpenCost supports &lt;code>aggregate=label:KEY&lt;/code> and &lt;code>aggregate=annotation:KEY&lt;/code> with the same syntax; the
choice between the two depends on the cluster&amp;rsquo;s labelling convention.&lt;/p>
&lt;h3 id="shared-costs-and-idle-the-three-modes">Shared costs and idle: the three modes&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>API mode&lt;/th>
&lt;th>Behaviour&lt;/th>
&lt;th>When&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>includeIdle=false&lt;/code> (default)&lt;/td>
&lt;td>Ignores idle; total cost looks lower than it is&lt;/td>
&lt;td>Never in production&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>includeIdle=true, shareIdle=false&lt;/code>&lt;/td>
&lt;td>Idle in a separate &lt;code>__idle__&lt;/code> row&lt;/td>
&lt;td>Showback: visibility of waste&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>includeIdle=true, shareIdle=true&lt;/code>&lt;/td>
&lt;td>Idle distributed among tenants proportionally&lt;/td>
&lt;td>Chargeback: the team pays its share of idle&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>shareIdle=true, idleByNode=true&lt;/code>&lt;/td>
&lt;td>Idle distributed per node (finer-grained if there are dedicated nodes)&lt;/td>
&lt;td>Chargeback with heterogeneous nodes&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>For infrastructure with nodes dedicated per team, &lt;code>idleByNode=true&lt;/code> is fairer: the idle of a node
dedicated to team A is not transferred to team B.&lt;/p>
&lt;hr>
&lt;h2 id="litellm-as-the-attribution-point-for-token-consumption">LiteLLM as the attribution point for token consumption&lt;/h2>
&lt;h3 id="virtual-keys-and-teams">Virtual keys and teams&lt;/h3>
&lt;p>LiteLLM materialises token chargeback with four mechanisms
(&lt;a href="https://docs.litellm.ai/docs/proxy/virtual_keys">LiteLLM — Virtual Keys&lt;/a>,
&lt;a href="https://docs.litellm.ai/docs/proxy/team_budgets">LiteLLM — Setting Team Budgets&lt;/a>,
&lt;a href="https://docs.litellm.ai/docs/proxy/cost_tracking">LiteLLM — Spend Tracking&lt;/a>):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Mechanism&lt;/th>
&lt;th>What it does&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Virtual key&lt;/strong> with &lt;code>max_budget&lt;/code>&lt;/td>
&lt;td>monthly budget per key; the key is blocked once it is exhausted&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>budget_duration&lt;/code>&lt;/strong>&lt;/td>
&lt;td>reset window: &lt;code>30d&lt;/code>, &lt;code>7d&lt;/code>, &lt;code>24h&lt;/code>; the proxy runs a daily cron that resets according to the duration&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>team_id&lt;/code>&lt;/strong>&lt;/td>
&lt;td>groups keys; spend accumulates in &lt;code>LiteLLM_TeamTable&lt;/code> per team&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>tags&lt;/code>&lt;/strong>&lt;/td>
&lt;td>tag each request; they allow budgets per tag (cost centre)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Spend logs&lt;/strong>&lt;/td>
&lt;td>one row per request with &lt;code>team_id&lt;/code>, &lt;code>model&lt;/code>, &lt;code>prompt_tokens&lt;/code>, &lt;code>completion_tokens&lt;/code>, &lt;code>response_cost&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="configuring-teams-with-a-monthly-budget">Configuring teams with a monthly budget&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># litellm-config.yaml — per-team budgets with the declared on-prem cost&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">model_list&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">model_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llama-3-70b-onprem&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">litellm_params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">model&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">openai/llama-3-70b&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">api_base&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://vllm-svc:8000/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">input_cost_per_token&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.00000140&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 1.40 €/1M tok (€/GPU-hour ÷ throughput)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">output_cost_per_token&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.00000140&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">model_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llama-3-8b-onprem&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">litellm_params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">model&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">openai/llama-3-8b&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">api_base&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://vllm-8b-svc:8000/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">input_cost_per_token&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.00000035&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># 0.35 €/1M tok (1 GPU, higher throughput)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">output_cost_per_token&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.00000035&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">litellm_settings&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">success_callback&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;langfuse&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># or any external logger&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># Teams (via the /team/new API or config)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># POST /team/new&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># {&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># &amp;#34;team_alias&amp;#34;: &amp;#34;equipo-datos&amp;#34;,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># &amp;#34;max_budget&amp;#34;: 800, # 800 € per month&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># &amp;#34;budget_duration&amp;#34;: &amp;#34;30d&amp;#34;,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># &amp;#34;tpm_limit&amp;#34;: 500000, # max tokens/min&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># &amp;#34;rpm_limit&amp;#34;: 1000 # max requests/min&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># }&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>input_cost_per_token&lt;/code> key comes from the identity in &lt;a href="https://blog.lo0.es/en/posts/gpu-hour-to-cost-per-token-on-prem-vs-cloud/">article A4&lt;/a>:
the €/GPU-hour from OpenCost divided by the benchmark throughput. With that value, the
&lt;code>response_cost&lt;/code> of each request reflects the real cost of the on-prem iron for that model.&lt;/p>
&lt;h3 id="endpoints-for-querying-spend-per-team">Endpoints for querying spend per team&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Team spend accumulated over the period&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">GET /team/info?team_id&lt;span class="o">=&lt;/span>equipo-datos
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Team spend per model (aggregated spend_logs table)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">GET /global/spend/logs?team_id&lt;span class="o">=&lt;/span>equipo-datos&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="nv">start_date&lt;/span>&lt;span class="o">=&lt;/span>2026-06-01&lt;span class="p">&amp;amp;&lt;/span>&lt;span class="nv">end_date&lt;/span>&lt;span class="o">=&lt;/span>2026-06-30
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Total spend across all teams&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">GET /global/spend/teams
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The response includes &lt;code>spend&lt;/code> (accumulated cost in the configured currency), &lt;code>total_tokens&lt;/code>,
&lt;code>prompt_tokens&lt;/code>, &lt;code>completion_tokens&lt;/code> and a per-model breakdown, the direct fields for the monthly
chargeback report.&lt;/p>
&lt;h3 id="joining-gpu-hour-iron-with-tokens-per-team">Joining €/GPU-hour (iron) with tokens per team&lt;/h3>
&lt;p>The two attribution planes, OpenCost (iron) and LiteLLM (tokens), are crossed with one key: the
&lt;code>model_name&lt;/code> and the &lt;code>namespace&lt;/code>. The join happens outside the tools (in a BI pipeline or a SQL
query over the LiteLLM database and the data exported from OpenCost):&lt;/p>
&lt;pre tabindex="0">&lt;code>coste_por_token_real = OpenCost.GPU_cost_namespace / LiteLLM.total_tokens_team
&lt;/code>&lt;/pre>&lt;p>When the on-prem cost is properly declared in &lt;code>input_cost_per_token&lt;/code>, LiteLLM already does this
calculation internally and the &lt;code>response_cost&lt;/code> is correct. The explicit join serves to validate:
if the sum of LiteLLM&amp;rsquo;s &lt;code>response_cost&lt;/code> per team does not match the GPU cost OpenCost assigns to
the namespace, there is drift in &lt;code>input_cost_per_token&lt;/code> that has to be corrected (changed
throughput, a new optimisation, more replicas).&lt;/p>
&lt;hr>
&lt;h2 id="kueue-as-the-gpu-budgetquota-mechanism">Kueue as the GPU budget/quota mechanism&lt;/h2>
&lt;h3 id="clusterqueue-localqueue-and-cohorts">ClusterQueue, LocalQueue and cohorts&lt;/h3>
&lt;p>Kueue introduces the &lt;strong>quota-based scheduling&lt;/strong> layer on top of Kubernetes
(&lt;a href="https://kueue.sigs.k8s.io/docs/concepts/cluster_queue/">Kueue — Cluster Queue&lt;/a>,
&lt;a href="https://kueue.sigs.k8s.io/docs/concepts/cohort/">Kueue — Cohort&lt;/a>,
&lt;a href="https://kueue.sigs.k8s.io/docs/concepts/fair_sharing/">Kueue — Fair Sharing&lt;/a>):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Object&lt;/th>
&lt;th>Scope&lt;/th>
&lt;th>What it does&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>ResourceFlavor&lt;/code>&lt;/td>
&lt;td>cluster&lt;/td>
&lt;td>maps resources to a group of nodes (e.g. H100 nodes)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ClusterQueue&lt;/code>&lt;/td>
&lt;td>cluster&lt;/td>
&lt;td>defines &lt;code>nominalQuota&lt;/code>, &lt;code>borrowingLimit&lt;/code>, &lt;code>lendingLimit&lt;/code> per resource/flavor&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>LocalQueue&lt;/code>&lt;/td>
&lt;td>namespace&lt;/td>
&lt;td>entry point for the team&amp;rsquo;s workloads; points to a &lt;code>ClusterQueue&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Cohort&lt;/code>&lt;/td>
&lt;td>cluster&lt;/td>
&lt;td>groups ClusterQueues that can lend quota to each other&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="quota-concepts">Quota concepts&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>&lt;code>nominalQuota&lt;/code>&lt;/strong>: GPUs guaranteed to the ClusterQueue at all times.&lt;/li>
&lt;li>&lt;strong>&lt;code>borrowingLimit&lt;/code>&lt;/strong>: maximum additional GPUs it can borrow from the cohort when others are not
using them.&lt;/li>
&lt;li>&lt;strong>&lt;code>lendingLimit&lt;/code>&lt;/strong>: GPUs from its &lt;code>nominalQuota&lt;/code> that it allows to be lent to others (if not
specified, it can lend all unused ones).&lt;/li>
&lt;li>&lt;strong>Fair Sharing&lt;/strong>: a mechanism that orders pending workloads by the historical resource usage of
their LocalQueue, giving preference to whoever has consumed less. Compatible with hierarchical
cohorts since Kueue v0.11.&lt;/li>
&lt;/ul>
&lt;h3 id="example-yaml-3-teams-in-the-llm-platform-cohort">Example YAML: 3 teams in the llm-platform cohort&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># ResourceFlavor mapping to the H100 SXM nodes&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kueue.x-k8s.io/v1beta2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ResourceFlavor&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">h100-sxm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nodeLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">accelerator&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">h100-sxm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># ClusterQueue for the datos team — 2 guaranteed GPUs, can borrow up to 2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kueue.x-k8s.io/v1beta2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ClusterQueue&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cq-datos&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cohort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llm-platform&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespaceSelector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">kubernetes.io/metadata.name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ns-datos&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">queueingStrategy&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">BestEffortFIFO&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resourceGroups&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">coveredResources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;nvidia.com/gpu&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">flavors&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">h100-sxm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;nvidia.com/gpu&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nominalQuota&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">borrowingLimit&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">2&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># can use up to 4 GPUs in total&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">preemption&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">reclaimWithinCohort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">LowerPriority&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">withinClusterQueue&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">LowerPriority&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># ClusterQueue for the ia team — 1 guaranteed GPU, lends what it does not use&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kueue.x-k8s.io/v1beta2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ClusterQueue&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cq-ia&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cohort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llm-platform&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespaceSelector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">kubernetes.io/metadata.name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ns-ia&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resourceGroups&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">coveredResources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;nvidia.com/gpu&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">flavors&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">h100-sxm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;nvidia.com/gpu&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nominalQuota&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">lendingLimit&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># lends its GPU when it is not using it&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># ClusterQueue for the plataforma team — 1 guaranteed GPU, low priority&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kueue.x-k8s.io/v1beta2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ClusterQueue&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cq-plataforma&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">cohort&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llm-platform&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespaceSelector&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">matchLabels&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">kubernetes.io/metadata.name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ns-plataforma&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resourceGroups&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">coveredResources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;nvidia.com/gpu&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">flavors&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">h100-sxm&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">resources&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;nvidia.com/gpu&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">nominalQuota&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">borrowingLimit&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">3&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># can use all 4 if the rest are free&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nn">---&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># LocalQueue in each namespace — entry point for the workloads&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">apiVersion&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">kueue.x-k8s.io/v1beta2&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">kind&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">LocalQueue&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">metadata&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">lq-datos&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">namespace&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ns-datos&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">spec&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">clusterQueue&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">cq-datos&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="how-kueue-materialises-the-gpu-budget">How Kueue materialises the &amp;ldquo;GPU budget&amp;rdquo;&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>FinOps concept&lt;/th>
&lt;th>Kueue mechanism&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Guaranteed budget&lt;/td>
&lt;td>&lt;code>nominalQuota&lt;/code>: the team always has these GPUs available&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Maximum spend limit&lt;/td>
&lt;td>&lt;code>nominalQuota + borrowingLimit&lt;/code>: absolute ceiling of admissible GPUs&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Lending idle capacity&lt;/td>
&lt;td>cohort + &lt;code>lendingLimit&lt;/code>: others take what this one does not use&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Fair sharing between teams&lt;/td>
&lt;td>&lt;code>Fair Sharing&lt;/code> + &lt;code>WorkloadPriorityClass&lt;/code>: those who have consumed most wait longest&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Reclaiming your own quota&lt;/td>
&lt;td>&lt;code>preemption.reclaimWithinCohort: LowerPriority&lt;/code>: the owner of the lent GPU reclaims it by evicting lower-priority workloads&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The &lt;code>nominalQuota&lt;/code> is the expression of the budget in GPUs: if the team has 2 nominal GPUs and the
price is 1.40 €/GPU-hour, its maximum guaranteed spend is&lt;/p>
\[
\text{maximum nominal spend} = 2 \times 1.40 \times 720 = 2{,}016 \text{ €/month}
\]
&lt;p>The &lt;code>borrowingLimit&lt;/code> sets the potential overspend if it borrows from the cohort (and that
borrowing can be charged with the same formula, multiplying the borrowed usage hours by
1.40 €/GPU-hour).&lt;/p>
&lt;hr>
&lt;h2 id="cost-model-an-example-with-4h100-sxm-and-3-teams">Cost model: an example with 4×H100 SXM and 3 teams&lt;/h2>
&lt;h3 id="node-price">Node price&lt;/h3>
&lt;p>Generic hardware: a server with 4×H100 SXM 80 GB. 2026 market prices: a single H100 SXM costs
between 27,000 and 40,000 € depending on the source
(&lt;a href="https://www.gmicloud.ai/en/blog/nvidia-h100-gpu-pricing-2026-rent-vs-buy-cost-analysis">GMI Cloud — H100 GPU Pricing 2026&lt;/a>);
a 4×H100 server sits in the 120,000–180,000 € range including chassis, power supplies and NVLink.
We use 140,000 € as a generic assumption for the complete server (4 GPUs plus infrastructure).&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Component&lt;/th>
&lt;th>Calculation&lt;/th>
&lt;th>€/hour node&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Depreciated capex (140,000 €, 4 years, 90 % availability)&lt;/td>
&lt;td>140,000 ÷ (4 × 8,760 × 0.9)&lt;/td>
&lt;td>~4.43&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy (4 × 700 W TDP × PUE 1.4 × 0.12 €/kWh)&lt;/td>
&lt;td>4 × 0.7 × 1.4 × 0.12&lt;/td>
&lt;td>~0.47&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Operations / networking / maintenance&lt;/td>
&lt;td>estimate&lt;/td>
&lt;td>~0.70&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Total 4×H100 node&lt;/strong>&lt;/td>
&lt;td>&lt;/td>
&lt;td>&lt;strong>~5.60 €/h&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Per GPU&lt;/strong>&lt;/td>
&lt;td>5.60 ÷ 4&lt;/td>
&lt;td>&lt;strong>~1.40 €/GPU-hour&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Cost-per-token formula for the 70B model (sustained throughput ~2,000 tok/s at TP=4):&lt;/p>
$$\text{€/1M tokens} = \frac{5.60 \text{ €/h}}{2{,}000 \text{ tok/s} \times 3{,}600 \text{ s/h}} \times 10^6 \approx 0.78 \text{ €/1M tokens}$$
&lt;h3 id="monthly-scenario-3-teams-heterogeneous-utilisation">Monthly scenario: 3 teams, heterogeneous utilisation&lt;/h3>
&lt;p>Reference month (720 hours). The 4 GPUs are nominally assigned: 2 to &lt;strong>Datos&lt;/strong>, 1 to &lt;strong>IA&lt;/strong>, 1 to
&lt;strong>Plataforma&lt;/strong>. Real occupancy varies:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Team&lt;/th>
&lt;th>Nominal GPUs&lt;/th>
&lt;th>Average occupancy&lt;/th>
&lt;th>GPU-hours used&lt;/th>
&lt;th>Idle GPU-hours&lt;/th>
&lt;th>GPU cost (€)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Datos&lt;/td>
&lt;td>2&lt;/td>
&lt;td>75 %&lt;/td>
&lt;td>1,080&lt;/td>
&lt;td>360&lt;/td>
&lt;td>~1,512&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>IA&lt;/td>
&lt;td>1&lt;/td>
&lt;td>60 %&lt;/td>
&lt;td>432&lt;/td>
&lt;td>288&lt;/td>
&lt;td>~605&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Plataforma&lt;/td>
&lt;td>1&lt;/td>
&lt;td>35 %&lt;/td>
&lt;td>252&lt;/td>
&lt;td>468&lt;/td>
&lt;td>~353&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Cluster idle&lt;/strong>&lt;/td>
&lt;td>—&lt;/td>
&lt;td>—&lt;/td>
&lt;td>—&lt;/td>
&lt;td>&lt;strong>1,116&lt;/strong>&lt;/td>
&lt;td>&lt;strong>~1,562&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Node total&lt;/strong>&lt;/td>
&lt;td>4&lt;/td>
&lt;td>—&lt;/td>
&lt;td>1,764&lt;/td>
&lt;td>1,116&lt;/td>
&lt;td>&lt;strong>~4,032&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;blockquote>
&lt;p>Total node cost per month: 5.60 €/h × 720 h = 4,032 €.
Cost per GPU-hour used: 5.60 ÷ 4 = 1.40 €.&lt;/p>
&lt;/blockquote>
&lt;p>With &lt;code>shareIdle=false&lt;/code> (showback), the 1,562 € of idle appears as a separate row in the report and
nobody pays for it directly. With &lt;code>shareIdle=true&lt;/code> (proportional chargeback), it is split among
the three in proportion to their non-idle cost: Datos absorbs ~857 €, IA ~333 €, Plataforma ~192 €
of additional idle.&lt;/p>
&lt;h3 id="token-consumption-per-team-litellm-spend-logs">Token consumption per team (LiteLLM spend logs)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Team&lt;/th>
&lt;th>Model&lt;/th>
&lt;th>Tokens/month&lt;/th>
&lt;th>Cost/token&lt;/th>
&lt;th>Token cost (€)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Datos&lt;/td>
&lt;td>llama-70b (TP=4)&lt;/td>
&lt;td>380 M&lt;/td>
&lt;td>0.78 €/1M&lt;/td>
&lt;td>~296&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>IA&lt;/td>
&lt;td>llama-8b (1 GPU)&lt;/td>
&lt;td>210 M&lt;/td>
&lt;td>0.35 €/1M&lt;/td>
&lt;td>~74&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Plataforma&lt;/td>
&lt;td>llama-8b + batch&lt;/td>
&lt;td>80 M&lt;/td>
&lt;td>0.35 €/1M&lt;/td>
&lt;td>~28&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="monthly-chargeback-report-showback--tokens">Monthly chargeback report (showback + tokens)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Team&lt;/th>
&lt;th>Iron GPU cost (€)&lt;/th>
&lt;th>Assigned idle cost (€)&lt;/th>
&lt;th>LiteLLM token cost (€)&lt;/th>
&lt;th>Total chargeable (€)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Datos&lt;/td>
&lt;td>1,512&lt;/td>
&lt;td>857&lt;/td>
&lt;td>296&lt;/td>
&lt;td>&lt;strong>2,665&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>IA&lt;/td>
&lt;td>605&lt;/td>
&lt;td>333&lt;/td>
&lt;td>74&lt;/td>
&lt;td>&lt;strong>1,012&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Plataforma&lt;/td>
&lt;td>353&lt;/td>
&lt;td>192&lt;/td>
&lt;td>28&lt;/td>
&lt;td>&lt;strong>573&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Idle (without shareIdle)&lt;/td>
&lt;td>1,562&lt;/td>
&lt;td>—&lt;/td>
&lt;td>—&lt;/td>
&lt;td>visible, not charged&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="attribution-dimensions--policy-table">Attribution dimensions × policy table&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Attribution dimension&lt;/th>
&lt;th>Tool&lt;/th>
&lt;th>Showback&lt;/th>
&lt;th>Chargeback&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>namespace&lt;/td>
&lt;td>OpenCost &lt;code>aggregate=namespace&lt;/code>&lt;/td>
&lt;td>&lt;code>/allocation?aggregate=namespace&amp;amp;includeIdle=true&lt;/code>&lt;/td>
&lt;td>&lt;code>shareIdle=true&lt;/code> + transfer to P&amp;amp;L&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>pod label&lt;/td>
&lt;td>OpenCost &lt;code>aggregate=label:equipo&lt;/code>&lt;/td>
&lt;td>report per label&lt;/td>
&lt;td>same with shareIdle&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>virtual key / team&lt;/td>
&lt;td>LiteLLM &lt;code>/global/spend/teams&lt;/code>&lt;/td>
&lt;td>token dashboard&lt;/td>
&lt;td>hard &lt;code>max_budget&lt;/code> per key&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ClusterQueue (reserved GPU)&lt;/td>
&lt;td>Kueue &lt;code>nominalQuota&lt;/code>&lt;/td>
&lt;td>observe usage vs quota&lt;/td>
&lt;td>committed budget in GPUs&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>request tag&lt;/td>
&lt;td>LiteLLM &lt;code>tag_budgets&lt;/code>&lt;/td>
&lt;td>per cost centre&lt;/td>
&lt;td>budget per tag&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>pod annotation&lt;/td>
&lt;td>OpenCost &lt;code>aggregate=annotation:KEY&lt;/code>&lt;/td>
&lt;td>report per annotation&lt;/td>
&lt;td>same with shareIdle&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="end-to-end-data-flow">End-to-end data flow&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 300" role="img" aria-label="GPU chargeback attribution flow: from the inference pod to the three planes OpenCost, LiteLLM and Kueue, and to the monthly report" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.3}.dsh{fill:none;stroke:currentColor;stroke-width:1.3;stroke-dasharray:5 3}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}.ar{fill:none;stroke:currentColor;stroke-width:1.3;marker-end:url(#chm)}&lt;/style>
&lt;defs>&lt;marker id="chm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/>&lt;/marker>&lt;/defs>
&lt;rect class="bx" x="20" y="30" width="160" height="50" rx="6"/>
&lt;text x="32" y="52" class="tl">vLLM pod&lt;/text>
&lt;text x="32" y="68" class="ts">label:equipo=datos&lt;/text>
&lt;path class="ar" d="M180,55 L220,80"/>
&lt;path class="ar" d="M180,55 L220,145"/>
&lt;path class="ar" d="M180,55 L220,210"/>
&lt;rect class="bx" x="220" y="60" width="175" height="50" rx="6"/>
&lt;text x="232" y="82" class="tl">OpenCost (iron)&lt;/text>
&lt;text x="232" y="98" class="ts">€/GPU-hour × namespace/label&lt;/text>
&lt;rect class="bx" x="220" y="125" width="175" height="50" rx="6"/>
&lt;text x="232" y="147" class="tl">LiteLLM (tokens)&lt;/text>
&lt;text x="232" y="163" class="ts">€/token × team_id/key/model&lt;/text>
&lt;rect class="bx" x="220" y="190" width="175" height="50" rx="6"/>
&lt;text x="232" y="212" class="tl">Kueue (scheduler)&lt;/text>
&lt;text x="232" y="228" class="ts">nominalQuota / cohort / fair-share&lt;/text>
&lt;path class="ar" d="M395,85 L440,140"/>
&lt;path class="ar" d="M395,150 L440,150"/>
&lt;path class="ar" d="M395,215 L440,160"/>
&lt;rect class="bx" x="440" y="115" width="185" height="70" rx="6"/>
&lt;text x="452" y="137" class="tl">Monthly report&lt;/text>
&lt;text x="452" y="153" class="ts">€/team (iron + idle + tokens)&lt;/text>
&lt;text x="452" y="169" class="ts">borrowed GPUs · budget left&lt;/text>
&lt;path class="ar" d="M625,150 L665,150"/>
&lt;rect class="dsh" x="665" y="115" width="100" height="70" rx="6"/>
&lt;text x="677" y="143" class="tl">Showback&lt;/text>
&lt;text x="677" y="159" class="ts">or&lt;/text>
&lt;text x="677" y="175" class="tl">Chargeback&lt;/text>
&lt;text x="20" y="275" class="ts">The three planes are orthogonal: OpenCost sees the iron, LiteLLM sees the tokens, Kueue sees the scheduler quota.&lt;/text>
&lt;text x="20" y="292" class="ts">Crossing the three gives the complete number: €/team with idle, tokens and borrowed GPUs.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;hr>
&lt;h2 id="fair-sharing-and-preemption-how-kueue-reclaims-quota">Fair sharing and preemption: how Kueue reclaims quota&lt;/h2>
&lt;p>When the Datos team has exhausted its 2 nominal GPUs and borrows those of Plataforma (which is
not using them), Kueue records that loan. As soon as Plataforma launches a new workload:&lt;/p>
&lt;ol>
&lt;li>Kueue detects that &lt;code>cq-plataforma&lt;/code> is below its &lt;code>nominalQuota&lt;/code>.&lt;/li>
&lt;li>With &lt;code>preemption.reclaimWithinCohort: LowerPriority&lt;/code>, Kueue evicts the Datos workload that was
on the borrowed GPU, if its priority is lower.&lt;/li>
&lt;li>The evicted workload returns to the &lt;code>cq-datos&lt;/code> queue and is readmitted once quota frees up.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Fair Sharing&lt;/strong> orders the pending queue by the LocalQueue&amp;rsquo;s accumulated historical usage: if
Datos has consumed more GPU-hours than IA in the recent period, IA&amp;rsquo;s next workloads have
admission priority over new ones from Datos. This implements an equitable split without blocking
anyone permanently.&lt;/p>
&lt;hr>
&lt;h2 id="configuring-opencost-for-the-node-price">Configuring OpenCost for the node price&lt;/h2>
&lt;p>For the numbers in the previous section to be reproducible, the node price has to be declared
explicitly (the default value underestimates on-prem GPU,
&lt;a href="https://github.com/opencost/opencost/issues/3781">issue #3781&lt;/a>):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># OpenCost Helm values.yaml — on-prem 4×H100 node, 5.60 €/h&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">opencost&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">customPricing&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">enabled&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">provider&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">custom&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">costModel&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">description&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;On-prem 4xH100 SXM node&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">CPU&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0.025&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># €/CPU-hour (minor component)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">RAM&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0.003&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># €/GB-hour&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">GPU&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;1.40&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># €/GPU-hour ← the one that drives the split&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">storage&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0.0002&amp;#34;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># €/GB-hour&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Verify after configuring:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Check the price resolved per node&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl http://localhost:9003/allNodePricing
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Attribution query per team label, last month, idle separate&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">curl -G http://localhost:9003/allocation &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="nv">window&lt;/span>&lt;span class="o">=&lt;/span>lastmonth &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="nv">aggregate&lt;/span>&lt;span class="o">=&lt;/span>label:equipo &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="nv">includeIdle&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="nv">shareIdle&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">false&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -d &lt;span class="nv">accumulate&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="reference-promql-for-chargeback-dashboards">Reference PromQL for chargeback dashboards&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-promql" data-lang="promql">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># GPU cost assigned per team (label:equipo) — €/hour&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">sum&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nv">label_equipo&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">container_gpu_allocation&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">on&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nv">node&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">group_left&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">node_gpu_hourly_cost&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1"># Idle GPU-hours per node (idle &amp;gt; 15 min)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">sum&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nv">node&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="kr">avg_over_time&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nv">DCGM_FI_DEV_GPU_UTIL&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">15m&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">on&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nv">node&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">group_left&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">node_gpu_hourly_cost&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c1"># Accumulated monthly cost per team (sum over window)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="kr">sum_over_time&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">sum&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">by&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nv">label_equipo&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nv">container_gpu_allocation&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">on&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nv">node&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">group_left&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nv">node_gpu_hourly_cost&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s">30d&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="s">1h&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;hr>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>FinOps Foundation — Invoicing &amp;amp; Chargeback Capability — &lt;a href="https://www.finops.org/framework/capabilities/invoicing-chargeback/">https://www.finops.org/framework/capabilities/invoicing-chargeback/&lt;/a>&lt;/li>
&lt;li>FinOps Foundation — Data Analysis and Showback — &lt;a href="https://www.finops.org/framework/previous-capabilities/analysis-showback/">https://www.finops.org/framework/previous-capabilities/analysis-showback/&lt;/a>&lt;/li>
&lt;li>FinOps Foundation — Allocation Capability — &lt;a href="https://www.finops.org/framework/capabilities/allocation/">https://www.finops.org/framework/capabilities/allocation/&lt;/a>&lt;/li>
&lt;li>OpenCost — API (Allocation API, parámetros window/aggregate/shareIdle/idleByNode) — &lt;a href="https://opencost.io/docs/integrations/api/">https://opencost.io/docs/integrations/api/&lt;/a>&lt;/li>
&lt;li>OpenCost — API Examples — &lt;a href="https://opencost.io/docs/integrations/api-examples/">https://opencost.io/docs/integrations/api-examples/&lt;/a>&lt;/li>
&lt;li>OpenCost — GitHub (issue #3781, infra-precio GPU on-prem por defecto) — &lt;a href="https://github.com/opencost/opencost/issues/3781">https://github.com/opencost/opencost/issues/3781&lt;/a>&lt;/li>
&lt;li>LiteLLM — Virtual Keys (&lt;code>max_budget&lt;/code>, &lt;code>budget_duration&lt;/code>, &lt;code>team_id&lt;/code>) — &lt;a href="https://docs.litellm.ai/docs/proxy/virtual_keys">https://docs.litellm.ai/docs/proxy/virtual_keys&lt;/a>&lt;/li>
&lt;li>LiteLLM — Setting Team Budgets — &lt;a href="https://docs.litellm.ai/docs/proxy/team_budgets">https://docs.litellm.ai/docs/proxy/team_budgets&lt;/a>&lt;/li>
&lt;li>LiteLLM — Spend Tracking — &lt;a href="https://docs.litellm.ai/docs/proxy/cost_tracking">https://docs.litellm.ai/docs/proxy/cost_tracking&lt;/a>&lt;/li>
&lt;li>LiteLLM — Budgets &amp;amp; Rate Limits — &lt;a href="https://docs.litellm.ai/docs/proxy/users">https://docs.litellm.ai/docs/proxy/users&lt;/a>&lt;/li>
&lt;li>LiteLLM — Setting Tag Budgets — &lt;a href="https://docs.litellm.ai/docs/proxy/tag_budgets">https://docs.litellm.ai/docs/proxy/tag_budgets&lt;/a>&lt;/li>
&lt;li>LiteLLM — Budget Reset Times — &lt;a href="https://docs.litellm.ai/docs/proxy/budget_reset_and_tz">https://docs.litellm.ai/docs/proxy/budget_reset_and_tz&lt;/a>&lt;/li>
&lt;li>Kueue — Cluster Queue (nominalQuota, borrowingLimit, lendingLimit, preemption) — &lt;a href="https://kueue.sigs.k8s.io/docs/concepts/cluster_queue/">https://kueue.sigs.k8s.io/docs/concepts/cluster_queue/&lt;/a>&lt;/li>
&lt;li>Kueue — Cohort — &lt;a href="https://kueue.sigs.k8s.io/docs/concepts/cohort/">https://kueue.sigs.k8s.io/docs/concepts/cohort/&lt;/a>&lt;/li>
&lt;li>Kueue — Fair Sharing — &lt;a href="https://kueue.sigs.k8s.io/docs/concepts/fair_sharing/">https://kueue.sigs.k8s.io/docs/concepts/fair_sharing/&lt;/a>&lt;/li>
&lt;li>Kueue — Administer Cluster Quotas — &lt;a href="https://kueue.sigs.k8s.io/docs/tasks/manage/administer_cluster_quotas/">https://kueue.sigs.k8s.io/docs/tasks/manage/administer_cluster_quotas/&lt;/a>&lt;/li>
&lt;li>GMI Cloud — NVIDIA H100 GPU Pricing 2026 — &lt;a href="https://www.gmicloud.ai/en/blog/nvidia-h100-gpu-pricing-2026-rent-vs-buy-cost-analysis">https://www.gmicloud.ai/en/blog/nvidia-h100-gpu-pricing-2026-rent-vs-buy-cost-analysis&lt;/a>&lt;/li>
&lt;li>IntuitionLabs — NVIDIA AI GPU Prices H100 Cost Guide — &lt;a href="https://intuitionlabs.ai/articles/nvidia-ai-gpu-pricing-guide">https://intuitionlabs.ai/articles/nvidia-ai-gpu-pricing-guide&lt;/a>&lt;/li>
&lt;/ul>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/opencost-gpu-cost-allocation-kubernetes/">OpenCost in depth: how GPU cost is allocated in Kubernetes&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/kubecost-vs-opencost-vs-alternatives/">Kubecost vs OpenCost vs alternatives&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/gpu-hour-to-cost-per-token-on-prem-vs-cloud/">From GPU-hour to cost per token&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/finops-multi-tenancy-gpu-cluster-who-pays/">FinOps and GPU cluster multi-tenancy: who pays for what&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/sharing-one-gpu-time-slicing-mps-mig/">Sharing a GPU: time-slicing, MPS and MIG&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>From GPU-hour to cost per token: the metric that compares on-prem and cloud</title><link>https://blog.lo0.es/en/posts/gpu-hour-to-cost-per-token-on-prem-vs-cloud/</link><pubDate>Sun, 14 Jun 2026 03:30:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/gpu-hour-to-cost-per-token-on-prem-vs-cloud/</guid><description>&lt;blockquote>
&lt;p>Notation: amounts in &lt;strong>euros (N €)&lt;/strong>, decimals with a point. The dollar sign is not used
(on this site it is a formula delimiter).&lt;/p>
&lt;/blockquote>
&lt;h2 id="what-this-article-covers">What this article covers&lt;/h2>
&lt;p>Fourth article in the FinOps track (A4), and the one that closes out the metric that gives
the whole pillar its meaning: &lt;strong>cost per token&lt;/strong>. In A2 we saw how OpenCost assigns the cost of
the iron (the pod, in €/hour); in A3, which tool to use. But the cost of the pod is not the
metric that compares on-prem with cloud, nor the one the business understands: that one is
&lt;strong>cost per token&lt;/strong> (or per request). This article explains how it is really calculated (spoiler:
for your own inference it &lt;strong>is not a list price, it is €/GPU-hour divided by throughput&lt;/strong>), how
to instrument it with a gateway (LiteLLM), how to attribute it by team and product, and how
to compare it against European cloud. No recommendations; only the mechanics and the numbers.&lt;/p>
&lt;hr>
&lt;h2 id="the-identity-from-gpu-hour-to-token">The identity: from GPU-hour to token&lt;/h2>
&lt;p>Cost per million tokens (CPM) is the cost of the iron divided by what it produces:&lt;/p>
$$\text{cost per token} = \frac{\text{cost of the iron (€/h)}}{\text{throughput (tok/s)} \times 3600 / 10^6}$$
&lt;p>This is the central formula of the FinOps track, and it has a consequence many people never quite
internalise: for a self-hosted model, the cost of a token &lt;strong>does not exist as a fixed
price&lt;/strong>. It depends on two things that change: the cost of the iron (from OpenCost, in
€/hour) and the throughput (from your benchmark, in tok/s). The same model on the same node
costs half as much per token if you double the throughput with an optimisation, because the
denominator changes, not the numerator. The cost per token of your own inference is &lt;strong>a function
of your efficiency&lt;/strong>, not a tariff.&lt;/p>
&lt;p>That sets it radically apart from an external API, where the price per token really is a
fixed tariff the provider charges you. Comparing the two requires turning your variable cost
into a number, and that is where the iron (A2) meets the tokens (this article).&lt;/p>
&lt;hr>
&lt;h2 id="the-two-halves-of-cost-per-token">The two halves of cost per token&lt;/h2>
&lt;p>Cost per token comes from joining two measurements that live in different places:&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 220" role="img" aria-label="The two halves of cost per token: the cost of the iron from OpenCost and the token count from the gateway, which divided give the cost per token" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.3}.dsh{fill:none;stroke:currentColor;stroke-width:1.3;stroke-dasharray:5 3}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}.ar{fill:none;stroke:currentColor;stroke-width:1.3;marker-end:url(#tm)}&lt;/style>
&lt;defs>&lt;marker id="tm" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/>&lt;/marker>&lt;/defs>
&lt;rect class="bx" x="20" y="40" width="220" height="56" rx="6"/>
&lt;text x="32" y="62" class="tl">Cost of the iron (OpenCost)&lt;/text>
&lt;text x="32" y="80" class="ts">€/hour of the vLLM pod (by usage)&lt;/text>
&lt;rect class="bx" x="20" y="120" width="220" height="56" rx="6"/>
&lt;text x="32" y="142" class="tl">Tokens (LiteLLM gateway)&lt;/text>
&lt;text x="32" y="160" class="ts">tokens/s per model and team&lt;/text>
&lt;path class="ar" d="M240,68 L300,100"/>
&lt;path class="ar" d="M240,148 L300,116"/>
&lt;rect class="bx" x="300" y="84" width="200" height="48" rx="6"/>
&lt;text x="312" y="106" class="tl">÷ → cost per token&lt;/text>
&lt;text x="312" y="122" class="ts">€/1M tok per team&lt;/text>
&lt;path class="ar" d="M500,108 L560,108"/>
&lt;rect class="dsh" x="560" y="84" width="200" height="48" rx="6"/>
&lt;text x="572" y="106" class="tl">compare / chargeback&lt;/text>
&lt;text x="572" y="122" class="ts">on-prem vs cloud · per team&lt;/text>
&lt;text x="20" y="205" class="ts">Without the iron (OpenCost) you would know the tokens but not their cost; without the gateway, the cost of the pod but not the tokens. You need both.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>The iron half comes from OpenCost (article A2): the by-usage cost of the vLLM pod, in
€/hour. The token half comes from the gateway, which intercepts every request and counts
tokens per model and per team. Divide, and out comes the cost per token and, crucially, &lt;strong>per
team&lt;/strong>, which is what enables chargeback.&lt;/p>
&lt;hr>
&lt;h2 id="the-gateway-litellm-in-depth">The gateway: LiteLLM in depth&lt;/h2>
&lt;p>The gateway sits between the application and the engine (vLLM) or the provider, and intercepts
every request. LiteLLM is the OSS reference: it proxies over 100 models and &lt;strong>tracks spend
by keys, users and teams&lt;/strong>, logging spend for all known models automatically
(&lt;a href="https://docs.litellm.ai/docs/proxy/cost_tracking">LiteLLM · Spend Tracking&lt;/a>).&lt;/p>
&lt;p>Its key mechanisms:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Mechanism&lt;/th>
&lt;th>What it does&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>response_cost&lt;/code>&lt;/td>
&lt;td>the cost of each response, available in logging (&lt;code>kwargs[&amp;quot;response_cost&amp;quot;]&lt;/code>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Spend logs&lt;/strong>&lt;/td>
&lt;td>one row per request with tokens, model, user, key and cost (SQLite/PostgreSQL)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Tags&lt;/strong>&lt;/td>
&lt;td>tag each request by team, project, environment and model &lt;em>tier&lt;/em>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Budgets&lt;/strong>&lt;/td>
&lt;td>budgets and rate limits per team or user&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Custom callbacks&lt;/strong>&lt;/td>
&lt;td>bespoke loggers for key/user/model/tokens/cost&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>With the spend logs table (via the database adapter) you have the detail of every request to
build cost dashboards (&lt;a href="https://docs.litellm.ai/docs/proxy/logging">LiteLLM · Logging&lt;/a>), and you
can tag each request with team/project/environment for per-team analysis, with notifications
(Slack) and hard caps for keys that must not exceed a limit. In practice it is the platform&amp;rsquo;s
token meter and cost allocator.&lt;/p>
&lt;hr>
&lt;h2 id="the-self-hosted-trap-litellm-does-not-know-what-your-own-token-costs-you">The self-hosted trap: LiteLLM does not know what your own token costs you&lt;/h2>
&lt;p>Here is the point almost nobody wires up correctly. LiteLLM tracks cost using list prices
for known models (the API ones: OpenAI, Anthropic and so on). But for a model self-hosted on
your vLLM there &lt;strong>is no list price&lt;/strong>, because you pay nobody per token. If you tell it
nothing, LiteLLM does not know the real cost of your own token. The solution is custom
pricing: declaring to LiteLLM the cost per token of your self-hosted model
(&lt;a href="https://docs.litellm.ai/docs/proxy/custom_pricing">LiteLLM · Custom Pricing&lt;/a>).&lt;/p>
&lt;p>And where does that number come from? From the identity above: the cost of the iron
(OpenCost) divided by the throughput (your benchmark). That is, the correct flow is:&lt;/p>
&lt;ol>
&lt;li>OpenCost gives you the cost of the vLLM pod: ~11 €/hour (A2).&lt;/li>
&lt;li>Your benchmark gives you the throughput: ~2,800 tok/s (track B).&lt;/li>
&lt;li>The identity gives the cost per token: 11 ÷ (2,800 × 3,600 / 10⁶) ≈ &lt;strong>1.09 €/1M tok&lt;/strong>.&lt;/li>
&lt;li>That 1.09 €/1M goes into LiteLLM as custom pricing.&lt;/li>
&lt;/ol>
&lt;p>Only then does the &lt;code>response_cost&lt;/code> of each request reflect the real cost of your inference,
rather than a zero or somebody else&amp;rsquo;s list price. Without this step, your LiteLLM cost dashboards
lie about your own models. It is the explicit junction of the three tracks: iron (FinOps),
throughput (benchmarking) and measurement (gateway).&lt;/p>
&lt;hr>
&lt;h3 id="configuring-custom-pricing-an-example">Configuring custom pricing: an example&lt;/h3>
&lt;p>In LiteLLM, the real cost of a self-hosted model is declared in its configuration. An example
with the cost per token derived from the identity (1.09 €/1M ÷ 10⁶ = 0.00000109 €/token):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># LiteLLM config.yaml — real cost of a self-hosted model&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">model_list&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="nt">model_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">llama-3.1-70b-onprem&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">litellm_params&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">model&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">openai/llama-3.1-70b &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># your vLLM endpoint&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">api_base&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">http://vllm:8000/v1&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">input_cost_per_token&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.00000109&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># €/token (from OpenCost ÷ throughput)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">output_cost_per_token&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.00000109&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># refine if you separate prefill/decode&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>With that, every &lt;code>response_cost&lt;/code> LiteLLM records reflects the real cost of your iron. The
operational note: this number must be updated when the cost of the node changes (new
depreciation, a different energy price) or the throughput does (an engine optimisation). It is
not a constant; it is the output of OpenCost and of the benchmark, and it is recalculated
whenever either of the two changes. Automating that recalculation, from node cost and measured
throughput to custom pricing, is what keeps cost dashboards honest.&lt;/p>
&lt;hr>
&lt;h2 id="cost-per-token-changes-with-throughput-read-it-off-the-sweep">Cost per token changes with throughput: read it off the sweep&lt;/h2>
&lt;p>Because throughput is the denominator, cost per token is not a point, it is a curve over the
concurrency sweep. On the ~11 €/hour node:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Concurrency&lt;/th>
&lt;th>Throughput (tok/s)&lt;/th>
&lt;th>Cost per 1M tokens&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1&lt;/td>
&lt;td>350&lt;/td>
&lt;td>&lt;strong>~8.73 €&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>8&lt;/td>
&lt;td>2,100&lt;/td>
&lt;td>&lt;strong>~1.46 €&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>16&lt;/td>
&lt;td>3,330 (goodput)&lt;/td>
&lt;td>&lt;strong>~0.92 €&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>24&lt;/td>
&lt;td>3,900 raw / 2,420 goodput&lt;/td>
&lt;td>~0.78 € raw / &lt;strong>~1.26 € on goodput&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Important readings: at low concurrency the cost per token is sky-high (the fixed cost of the
node is shared among few tokens); cost falls as occupancy rises. But watch the last row: if you
compute cost on raw throughput (3,900) you get 0.78 €, whereas if you compute it on
goodput (2,420, the part that meets the SLO) you get 1.26 €, and the latter is the real
cost, because requests that violate the SLO do not count as useful production. An honest cost
per token is computed on &lt;strong>goodput&lt;/strong>, not on catalogue throughput. This is where the cost track
meets the benchmarking one: the correct denominator is goodput.&lt;/p>
&lt;hr>
&lt;h2 id="cost-per-request-not-only-per-token">Cost per request: not only per token&lt;/h2>
&lt;p>Cost per token is the unit; cost per request is what a complete request bills. A request
consumes &lt;strong>input tokens (prompt) plus output tokens (generation)&lt;/strong>, and both count:&lt;/p>
$$\text{cost per request} = (\text{input tokens} + \text{output tokens}) \times \text{cost per token}$$
&lt;p>With nuances: in many engines the prefill (input) and the decode (output) have different
costs per token (decode is memory-bound and more expensive per token), so a fine-grained cost
model distinguishes the two. For most cases a weighted average cost per token will do; to
optimise, it pays to separate prefill and decode. LiteLLM records input and output tokens
separately in its spend logs, so the data is there to refine with.&lt;/p>
&lt;p>Cost per request is what connects to the product: if a feature consumes 1,500 tokens on
average (1,200 of prompt plus 300 of output) and the cost is 1.09 €/1M, each use costs
~0.0016 €, the basis for setting margin, as seen in the FinOps introduction.&lt;/p>
&lt;hr>
&lt;h2 id="cost-per-model-and-per-tier">Cost per model and per tier&lt;/h2>
&lt;p>A platform does not serve a single model. Cost per token varies by model and by
configuration, and the gateway tells them apart. An 8B and a 70B on the same node have very
different costs per token: the 8B yields far more throughput (more tokens/s) for the same
GPU-hour, so its cost per token is a fraction of the 70B&amp;rsquo;s. The design consequence: routing
each request to the cheapest model that meets the quality bar is a direct cost lever, since
serving a 70B where an 8B suffices means overpaying per token.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Model (example, same node)&lt;/th>
&lt;th>Relative throughput&lt;/th>
&lt;th>Relative cost per token&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>8B&lt;/td>
&lt;td>high&lt;/td>
&lt;td>low&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>70B FP8&lt;/td>
&lt;td>medium&lt;/td>
&lt;td>medium&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>70B FP16&lt;/td>
&lt;td>low&lt;/td>
&lt;td>high&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The gateway, with its per-model custom pricing, makes this difference visible and enables
&lt;strong>cost-based routing&lt;/strong>: classify the request and send it to the appropriate &lt;em>tier&lt;/em>. It connects
with the L7 router of the serving track: the gateway not only measures cost, it can decide
on the basis of it. And quantisation (FP16→FP8) shows up again as a lever: the same model in FP8
raises throughput and lowers cost per token, moving the figure without changing model.&lt;/p>
&lt;hr>
&lt;h2 id="attribution-from-cost-to-team-and-product">Attribution: from cost to team and product&lt;/h2>
&lt;p>The reason for putting a gateway in place is not only to measure total cost, it is to share it
out. By tagging each request with team, product and environment, LiteLLM builds the split by
business dimension:&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 200" role="img" aria-label="Cost per token attribution: the gateway tags each request by team and product, aggregates spend and produces chargeback with budgets and caps" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.3}.dsh{fill:none;stroke:currentColor;stroke-width:1.3;stroke-dasharray:5 3}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}.ar{fill:none;stroke:currentColor;stroke-width:1.3;marker-end:url(#am)}&lt;/style>
&lt;defs>&lt;marker id="am" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/>&lt;/marker>&lt;/defs>
&lt;rect class="bx" x="20" y="50" width="150" height="44" rx="6"/>&lt;text x="32" y="70" class="tl">request + tags&lt;/text>&lt;text x="32" y="86" class="ts">team · product&lt;/text>
&lt;path class="ar" d="M170,72 L215,72"/>
&lt;rect class="bx" x="215" y="50" width="160" height="44" rx="6"/>&lt;text x="227" y="70" class="tl">spend logs&lt;/text>&lt;text x="227" y="86" class="ts">tokens · cost · key&lt;/text>
&lt;path class="ar" d="M375,72 L420,72"/>
&lt;rect class="bx" x="420" y="50" width="160" height="44" rx="6"/>&lt;text x="432" y="70" class="tl">aggregation&lt;/text>&lt;text x="432" y="86" class="ts">€/team · €/product&lt;/text>
&lt;path class="ar" d="M580,72 L625,72"/>
&lt;rect class="bx" x="625" y="50" width="135" height="44" rx="6"/>&lt;text x="637" y="70" class="tl">chargeback&lt;/text>&lt;text x="637" y="86" class="ts">+ budgets&lt;/text>
&lt;text x="20" y="135" class="ts">Hard caps per key and budget alerts (Slack) close the loop: measure → attribute → govern.&lt;/text>
&lt;text x="20" y="160" class="ts">This is the token side of chargeback; the iron side comes from OpenCost. Together: cost per token per team.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>With per-team budgets and &lt;strong>hard caps&lt;/strong> per key, the gateway does not only report: it
governs. A team that goes over budget gets an alert; a key with a hard cap cannot spend any
more. It is the FinOps &lt;em>Operate&lt;/em> phase applied to the token.&lt;/p>
&lt;h3 id="a-combined-report-example">A combined report example&lt;/h3>
&lt;p>Joining the iron (OpenCost) and the tokens (gateway), the monthly cost-per-token-per-team report
on the example node:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Team&lt;/th>
&lt;th>Tokens/month&lt;/th>
&lt;th>Applied cost per token&lt;/th>
&lt;th>Charged cost&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>A · chat-prod&lt;/td>
&lt;td>240M&lt;/td>
&lt;td>1.09 €/1M (70B FP8)&lt;/td>
&lt;td>~262 €&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>B · batch&lt;/td>
&lt;td>90M&lt;/td>
&lt;td>0.40 €/1M (8B)&lt;/td>
&lt;td>~36 €&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>C · experimentation&lt;/td>
&lt;td>15M&lt;/td>
&lt;td>1.09 €/1M (70B)&lt;/td>
&lt;td>~16 € + idle&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>What it reveals: A consumes a lot but at an efficient cost (FP8, high utilisation); B uses a
cheap model (8B) for its workload; C consumes little but, as we saw in A2, drags along the idle
cost of its underused GPU. The token report plus the OpenCost iron report give the full
picture: who spends, on which model, with what efficiency. It is the chargeback that neither
half would produce alone. And the per-team figure is what gets taken into the budget
conversation: not &amp;ldquo;the cluster costs X&amp;rdquo;, but &amp;ldquo;your team consumed Y million tokens at Z €/million&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h2 id="governing-spend-budgets-caps-and-alerts">Governing spend: budgets, caps and alerts&lt;/h2>
&lt;p>Measuring without governing is a dashboard; the value lies in closing the loop. LiteLLM allows
three levels of control over spend:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Control&lt;/th>
&lt;th>What it does&lt;/th>
&lt;th>When&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Soft budget&lt;/strong>&lt;/td>
&lt;td>alert (Slack) when a threshold is crossed&lt;/td>
&lt;td>tracking teams&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Rate limit&lt;/strong>&lt;/td>
&lt;td>limits requests/tokens per minute&lt;/td>
&lt;td>avoiding cost spikes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Hard cap&lt;/strong>&lt;/td>
&lt;td>the key cannot spend any more&lt;/td>
&lt;td>keys that must never overrun&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The healthy pattern: soft budgets with alerts for the teams (so they adjust their consumption),
rate limits to smooth spikes, and hard caps only on critical keys (a service that must not run
away). With this, cost per token stops being an after-the-fact figure and becomes a real-time
control: the FinOps &lt;em>Operate&lt;/em> phase, applied to inference.&lt;/p>
&lt;hr>
&lt;h2 id="on-prem-vs-european-cloud-comparison-the-reason-for-all-of-it">On-prem vs European cloud comparison (the reason for all of it)&lt;/h2>
&lt;p>Cost per token exists to answer one question: is it cheaper to serve on my own iron or to
rent? With the identity and European figures:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Option&lt;/th>
&lt;th>Cost per 1M tokens&lt;/th>
&lt;th>Notes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Depreciated on-prem (8×H100, 2,800 tok/s)&lt;/td>
&lt;td>&lt;strong>~1.09 €&lt;/strong>&lt;/td>
&lt;td>your iron, your sovereignty&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>European cloud Scaleway (8×H100, on-demand)&lt;/td>
&lt;td>&lt;strong>~2.17 €&lt;/strong>&lt;/td>
&lt;td>EU, no iron to operate&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>External API (proprietary model)&lt;/td>
&lt;td>provider tariff&lt;/td>
&lt;td>no data sovereignty&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Cost per token is what makes the incomparable comparable: your variable cost (iron ÷
throughput) against a provider&amp;rsquo;s fixed tariff. And it exposes the lever: if an optimisation
raises your throughput from 2,800 to 4,200 tok/s, your on-prem cost drops to ~0.73 €/1M,
widening the advantage over renting. That is why benchmarking (track B) is, indirectly, the tool
that moves cost per token the most: every goodput improvement translates into this number.&lt;/p>
&lt;hr>
&lt;h2 id="from-cost-per-token-to-product-price">From cost per token to product price&lt;/h2>
&lt;p>Cost per token closes the loop with the business when it becomes cost per unit of product.
The chain: cost per token (from the identity) × tokens per use of a feature = marginal cost of
that feature. With that, three business decisions become data-backed:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Setting a price.&lt;/strong> If serving a feature costs 0.0016 € per use, the price to the customer
has to cover it with margin; cost per token is the floor of &lt;em>pricing&lt;/em>.&lt;/li>
&lt;li>&lt;strong>Spotting loss-making features.&lt;/strong> A feature that consumes many tokens (long prompts, lots of
output) may cost more than it contributes; cost per request reveals it before you scale.&lt;/li>
&lt;li>&lt;strong>Comparing build vs buy at product level.&lt;/strong> On-prem cost per token against an API tariff,
for that specific workload, decides whether to serve in house or consume externally.&lt;/li>
&lt;/ol>
&lt;p>This is the ultimate reason for the whole FinOps track: it is not infrastructure accounting, it
is the quantitative basis for product and architecture decisions. A measured and attributed cost
per token turns &amp;ldquo;is this profitable?&amp;rdquo; from an intuition into a calculation.&lt;/p>
&lt;hr>
&lt;h2 id="detailed-comparison-on-prem-european-cloud-and-api">Detailed comparison: on-prem, European cloud and API&lt;/h2>
&lt;p>Extending the comparison with scenarios, for the same job (1M tokens):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Option&lt;/th>
&lt;th>Cost/1M tok&lt;/th>
&lt;th>Sovereignty&lt;/th>
&lt;th>Where it fits&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Depreciated on-prem, high utilisation&lt;/td>
&lt;td>&lt;strong>~0.7–1.1 €&lt;/strong>&lt;/td>
&lt;td>full (EU)&lt;/td>
&lt;td>high, sustained volume&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Depreciated on-prem, low utilisation&lt;/td>
&lt;td>&lt;strong>~2–4 €&lt;/strong>&lt;/td>
&lt;td>full (EU)&lt;/td>
&lt;td>bad: idle drives cost up&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>European cloud (Scaleway, on-demand)&lt;/td>
&lt;td>&lt;strong>~2.2 €&lt;/strong>&lt;/td>
&lt;td>EU&lt;/td>
&lt;td>variable volume, no iron to operate&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>European cloud (reserved/committed)&lt;/td>
&lt;td>&lt;strong>~1.5–1.8 €&lt;/strong>&lt;/td>
&lt;td>EU&lt;/td>
&lt;td>medium committed volume&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>External proprietary API&lt;/td>
&lt;td>provider tariff&lt;/td>
&lt;td>&lt;strong>non-EU&lt;/strong>&lt;/td>
&lt;td>prototype / no sovereignty requirement&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The table shows two things. First, &lt;strong>on-prem only wins if utilisation is high&lt;/strong>, since at low
occupancy idle makes it dearer than cloud, which connects cost per token with scheduling and
utilisation (the idle of A2). Second, the &lt;strong>external API&lt;/strong>, however competitive its tariff,
&lt;strong>loses data sovereignty&lt;/strong>, an axis that for GDPR data is not negotiable. Cost per token is the
metric that puts the first three on the same scale; sovereignty is the constraint that rules out
the last one for certain kinds of data.&lt;/p>
&lt;hr>
&lt;h2 id="prompt-caching-and-effective-cost">Prompt caching and effective cost&lt;/h2>
&lt;p>One nuance refines the real cost per token: &lt;strong>prefix caching&lt;/strong>. When many requests share the
same prefix (a long system prompt, a repeated context), the engine caches its prefill compute
and &lt;strong>does not recompute it&lt;/strong> on every request. That means cached input tokens &lt;strong>cost almost
zero&lt;/strong> the second time and thereafter, so the effective cost per token of a workload with a high
cache &lt;em>hit rate&lt;/em> is &lt;strong>lower&lt;/strong> than the nominal cost. External APIs already reflect this with a
reduced price for cached tokens; in self-hosted the effect is that throughput rises (less
prefill to recompute), which by the identity lowers cost per token.&lt;/p>
&lt;p>The implication for the cost model: if your workload has shared prefixes (RAG over the same
corpus, agents with the same system prompt), the real cost per token is &lt;strong>lower&lt;/strong> than the naive
calculation gives, and it pays to measure the prefix caching &lt;em>hit rate&lt;/em> so as not to overestimate
cost. And the reverse: a workload of unique, never-repeated prompts gains nothing from the cache,
and its cost per token is the full nominal figure. Cost per token, once again, is not a constant:
it depends on the model, the precision, the utilisation, the goodput &lt;strong>and&lt;/strong> the reuse pattern of
the workload. Measuring it on your real traffic, not on a synthetic benchmark without a cache, is
what gives the number you actually pay.&lt;/p>
&lt;hr>
&lt;h2 id="state-of-the-art-2026">State of the art 2026&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>The gateway as standard&lt;/strong>: LiteLLM (and alternatives) are consolidating as the layer that
measures tokens, attributes cost and governs budgets in the LLM stack.&lt;/li>
&lt;li>&lt;strong>FOCUS for tokens&lt;/strong>: layers on top of the gateway generate FOCUS-compatible logs, integrating
AI cost with the rest of cloud spend (FinOps introduction).&lt;/li>
&lt;li>&lt;strong>Custom pricing as a bridge&lt;/strong>: the OpenCost→gateway connection via custom pricing is the
standard way to give self-hosted models a real cost.&lt;/li>
&lt;li>&lt;strong>Cost per token as a business KPI&lt;/strong>: increasingly, cost per token (and per product) is the
metric the business demands, not the cost of the pod.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="limits-and-traps-data-driven">Limits and traps (data-driven)&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Forgotten custom pricing.&lt;/strong> Without declaring to LiteLLM the real cost of your self-hosted
token, the &lt;code>response_cost&lt;/code> of your own models is zero or someone else&amp;rsquo;s price. Close the
OpenCost→gateway bridge.&lt;/li>
&lt;li>&lt;strong>Static throughput.&lt;/strong> Cost per token changes with throughput; if you optimise the engine but
do not update the custom pricing, the reported cost drifts out of date.&lt;/li>
&lt;li>&lt;strong>Forgetting prefill/decode.&lt;/strong> An average cost per token is fine for reporting; to optimise,
separate input and output.&lt;/li>
&lt;li>&lt;strong>Measuring without governing.&lt;/strong> Spend logs without budgets or caps make a pretty dashboard;
the value lies in closing the loop with alerts and caps.&lt;/li>
&lt;li>&lt;strong>Comparing tariffs with costs without matching assumptions.&lt;/strong> Your 1.09 €/1M assumes a
utilisation and a throughput; the provider&amp;rsquo;s tariff does not. Fix the assumptions before
comparing.&lt;/li>
&lt;/ol>
&lt;p>With cost per token solved, the FinOps track has its central metric; the following articles (A5
multi-tenant chargeback, A8 TCO model) build on it. The full chain, iron (OpenCost) ÷ throughput
(benchmark) = cost per token, measured and attributed by the gateway, is what turns the cost of
AI into a defensible number, in euros and per team.&lt;/p>
&lt;h2 id="closing">Closing&lt;/h2>
&lt;p>Cost per token is the metric that reconciles the three tracks of the series into a single
number, and its underlying lesson is counter-intuitive: for your own inference, &lt;strong>the cost of a
token is not a price, it is the reflection of your efficiency&lt;/strong>. It comes from dividing the cost
of the iron (OpenCost) by useful throughput (the goodput of your benchmark), and it becomes
visible and governable with a gateway (LiteLLM) that counts the tokens, attributes them by team
and applies budgets. The mistake that invalidates the whole exercise is leaving custom pricing at
zero and believing your own inference is &amp;ldquo;free&amp;rdquo;; the right move is to close the OpenCost→gateway
bridge and keep the number up to date when the node cost or the throughput changes. Done that
way, cost per token answers with data the one question the business keeps repeating, is it worth
serving this on my own iron, and it does so in euros, per team and per product, comparable
against European cloud and against any API. For a sovereign architecture proposal, it is the
column of the dashboard that translates all the cost engineering into the sentence that closes a
budget meeting: &lt;strong>this is the cost per token, this is how it is measured, and here is the bench
to reproduce it&lt;/strong>.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/gpu-chargeback-showback-multi-tenancy/">GPU chargeback and showback&lt;/a> — sharing cost out per team (OpenCost + LiteLLM + Kueue) once you have the cost per token.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/gpu-idle-cost-not-on-any-invoice/">GPU utilisation as a FinOps lever&lt;/a> — why cost per token depends on occupancy: the cost of idle.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/cloud-gpu-on-demand-reserved-spot-neoclouds-2026-prices/">Cloud GPU: a comparison of prices, commitment and sovereign neoclouds&lt;/a> — the alternative €/GPU-hour: on-demand, spot and reserved prices from European cloud providers against on-premise.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/full-tco-on-premise-gpu-cluster-capex-gpu-hour-break-even/">TCO of an on-premise GPU cluster: depreciation, energy and infrastructure&lt;/a> — the reference €/GPU-hour in your own infrastructure: CAPEX, energy, networking and staff with the full spreadsheet.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>LiteLLM · Spend Tracking — &lt;a href="https://docs.litellm.ai/docs/proxy/cost_tracking">https://docs.litellm.ai/docs/proxy/cost_tracking&lt;/a>&lt;/li>
&lt;li>LiteLLM · Custom Pricing (modelos self-hosted) — &lt;a href="https://docs.litellm.ai/docs/proxy/custom_pricing">https://docs.litellm.ai/docs/proxy/custom_pricing&lt;/a>&lt;/li>
&lt;li>LiteLLM · Logging — &lt;a href="https://docs.litellm.ai/docs/proxy/logging">https://docs.litellm.ai/docs/proxy/logging&lt;/a>&lt;/li>
&lt;li>LiteLLM · documentación general — &lt;a href="https://docs.litellm.ai/docs/">https://docs.litellm.ai/docs/&lt;/a>&lt;/li>
&lt;li>Statsig · LiteLLM cost tracking — &lt;a href="https://www.statsig.com/perspectives/litellm-cost-tracking">https://www.statsig.com/perspectives/litellm-cost-tracking&lt;/a>&lt;/li>
&lt;li>StackPulsar · LiteLLM production monitoring (2026) — &lt;a href="https://stackpulsar.com/blog/litellm-production-monitoring/">https://stackpulsar.com/blog/litellm-production-monitoring/&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>FinOps and multi-tenancy on the GPU cluster: who pays for what</title><link>https://blog.lo0.es/en/posts/finops-multi-tenancy-gpu-cluster-who-pays/</link><pubDate>Thu, 11 Jun 2026 12:20:00 +0000</pubDate><guid>https://blog.lo0.es/en/posts/finops-multi-tenancy-gpu-cluster-who-pays/</guid><description>&lt;blockquote>
&lt;p>Third instalment in an operational series on how to squeeze a generic on-premise LLM inference cluster of &lt;strong>4×H100 SXM 80 GB&lt;/strong>. The sibling pieces are &lt;a href="https://blog.lo0.es/en/posts/multimodal-on-premise-serving-vlm-vllm/">Multimodal VLM on-premise with vLLM&lt;/a>, serving vision-language models on the same cluster, and &lt;a href="https://blog.lo0.es/en/posts/speeding-up-model-cold-start-minutes-to-seconds/">Speeding up cold start with Tensorizer&lt;/a>, cutting the model load time from disk to HBM. This post closes the economic question the other two leave open: once the cluster serves several models for several teams, &lt;strong>who pays for what?&lt;/strong>&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>An on-premise GPU cluster does not have the property that makes FinOps easy in the cloud: no monthly invoice arrives telling you what each compute hour cost. The cost has to be manufactured from capex and energy. The right unit is the token, and it is derived in two steps. First the €/GPU-hour: capex depreciation spread over the hours of useful life, plus energy (power × PUE × price_kWh). For an H100 SXM with an assumed capex of 30,000 € and 4 years of life at 90 % availability, that gives ≈ 1.07 €/GPU-hour (0.95 of depreciation + 0.12 of energy at 0.12 €/kWh and PUE 1.4). Second, the €/1M tokens: divide that €/GPU-hour by the sustained throughput. At 2,000 useful tok/s, ≈ 0.148 €/1M tokens; but that number only holds if the GPU is at 100 % useful utilisation. At 20 % utilisation the same token costs 4× more (0.74 €/1M), because capex and energy keep being paid even if the GPU is idle. That is the central thesis: &lt;strong>the FinOps goal is not to negotiate the price of the token down, there is no provider to negotiate with, but to raise useful utilisation&lt;/strong>, which is the only term that moves cost by an order of magnitude. Attribution (chargeback / showback) is made real with LiteLLM virtual keys: per-team and per-user budget, RPM/TPM rate limit, automatic spend tracking and &lt;code>tags&lt;/code> to assign spend to cost centres. Isolation between tenants is achieved with MIG (hard GPU partitioning), namespaces, &lt;code>ResourceQuota&lt;/code> and &lt;code>PriorityClass&lt;/code>. The FinOps Foundation rule worth carving in stone: &lt;strong>showback always, chargeback only if the organisation&amp;rsquo;s accounting policy supports it&lt;/strong>; neither of the two is &amp;ldquo;more mature&amp;rdquo;. And the governance metric of the shared cluster is not the €/token but &lt;code>DCGM_FI_DEV_GPU_UTIL&lt;/code>.&lt;/p>
&lt;h2 id="the-problem-the-cloud-hides-from-you-and-on-premise-forces-you-to-solve">The problem the cloud hides from you and on-premise forces you to solve&lt;/h2>
&lt;p>In the cloud, the cost of a GPU is an observable fact: AWS, GCP or Azure charge you per GPU-hour and the invoice arrives itemised. FinOps in that world consists of &lt;strong>reading&lt;/strong> an invoice that already exists and attributing it. On-premise there is no such invoice. The GPU was bought once, eighteen months ago, on a purchase order nobody remembers; the electricity bill arrives for the whole building with no breakdown of what fraction was the cluster&amp;rsquo;s; and the datacenter PUE is a number the facilities team knows but the AI team never asked about.&lt;/p>
&lt;p>The first job of on-premise FinOps, then, is not to attribute a cost but to manufacture one. You have to build, from explicit assumptions, the equivalent of the &amp;ldquo;price per GPU-hour&amp;rdquo; that in the cloud is given to you. And once manufactured, you have to understand that this number is not a property of the hardware, like VRAM or TDP, but a function of how the hardware is used. An idle H100 costs exactly the same as an H100 at 100 %: the capex is already paid and idle energy is not zero. The only thing that changes is &lt;strong>how many useful tokens&lt;/strong> that fixed cost produced. That is where the whole thesis of this post comes from.&lt;/p>
&lt;h2 id="the-analogy-the-coworking-space-with-metered-services">The analogy: the coworking space with metered services&lt;/h2>
&lt;p>A coworking space manages a finite physical space and rents it to teams. The cost model has exactly the structure of the GPU cluster, and the analogy holds down to the last detail.&lt;/p>
&lt;p>&lt;strong>The desk-hour is the spread capex.&lt;/strong> The manager paid the rent on the premises, the furniture and the refurbishment, a one-off outlay or a fixed monthly cost, and spreads it over the available desk-hours in the month. Each desk has a base cost that &lt;strong>does not depend on whether anybody uses it&lt;/strong>: the walls, the table and the chair cost the same empty as occupied. This is the &lt;strong>depreciation of the GPU&lt;/strong>: capex divided by the hours of useful life.&lt;/p>
&lt;p>&lt;strong>The metered kWh is the energy.&lt;/strong> On top of the desk-hour, the coworking space measures the actual electricity consumption, the plugged-in laptop, the screen, the air conditioning in that room, and bills it separately. This is the &lt;strong>energy of the GPU&lt;/strong>: power × hours × price_kWh, multiplied by the datacenter&amp;rsquo;s &lt;strong>PUE&lt;/strong>, which is the coworking space also charging you for the fraction of the building&amp;rsquo;s air conditioning that cools your room.&lt;/p>
&lt;p>&lt;strong>The empty desk is paid for by whoever booked it.&lt;/strong> Here is the heart of the matter. If a team books ten desks for the whole month and only uses two, &lt;strong>it pays for all ten anyway&lt;/strong>. The empty desk still takes up space the manager cannot resell, still depreciates furniture, still counts as committed capacity. In the cluster: a GPU assigned to a tenant that uses it at 20 % costs the same as if it used it at 100 %, and the idle 80 % is &lt;strong>wasted capex&lt;/strong> that somebody is paying for.&lt;/p>
&lt;p>&lt;strong>The manager optimises occupancy, not tariff.&lt;/strong> A competent coworking manager does not obsess over lowering the price of the desk-hour, it is a sunk cost, the premises are already paid for. They obsess over the &lt;strong>occupancy rate&lt;/strong>: every empty desk is margin lost forever, because an unsold desk-hour cannot be stored. The FinOps equivalent: &lt;strong>the goal is not to lower the €/token, it is to raise utilisation&lt;/strong>, because the €/token falls only as a consequence of utilisation rising. There is no provider to squeeze; the only margin lies in not leaving GPUs idle.&lt;/p>
&lt;p>The analogy has an honest limit worth naming: in a coworking space, two people cannot physically share the same chair. On a GPU you &lt;strong>can&lt;/strong>, with time-slicing, MPS or MIG (see &lt;a href="https://blog.lo0.es/en/posts/sharing-one-gpu-time-slicing-mps-mig/">Sharing a GPU&lt;/a>), and that is precisely the technical lever that lets you raise occupancy beyond what the physical coworking space would allow. But the economics of fixed cost + metered cost is identical.&lt;/p>
&lt;h2 id="the-token-as-the-unit-of-cost">The token as the unit of cost&lt;/h2>
&lt;p>Why the token and not the GPU-hour, or the request, or the model? Because the token is the only unit comparable across heterogeneous workloads. A GPU-hour of Llama 8B and one of Llama 70B produce radically different amounts of &amp;ldquo;useful work&amp;rdquo;; a RAG request with a 50-token answer and one generating a 3,000-token report are not comparable. The token, specifically the pair (input_tokens, output_tokens), normalises all of that. It is the unit LiteLLM accounts for natively, the one that appears in the OTel attributes &lt;code>gen_ai.usage.input_tokens&lt;/code> and &lt;code>gen_ai.usage.output_tokens&lt;/code> (see &lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">LLM tracing with OpenTelemetry GenAI&lt;/a>), and the one commercial providers already use to charge. Adopting the token as the internal currency makes on-premise chargeback directly comparable with the cloud alternative, which is exactly the comparison management wants to see.&lt;/p>
&lt;p>The two derived metrics that matter:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Tokens/€&lt;/strong> — how many useful tokens each euro of total cluster cost buys. It rises when utilisation rises. It is the metric of economic efficiency.&lt;/li>
&lt;li>&lt;strong>Tokens/W&lt;/strong> — how many tokens each watt consumed produces. It is the metric of energy efficiency and, in a datacenter with limited power (the usual case on-premise), it is often the real &lt;strong>hard constraint&lt;/strong>: you cannot add more GPUs because there are no more kW in the rack, so every watt has to perform.&lt;/li>
&lt;/ul>
&lt;h2 id="cost-model-of-a-gpu-from-capex-to-token">Cost model of a GPU: from capex to €/token&lt;/h2>
&lt;p>The hourly cost of a GPU has two addends. Let us put all the assumptions in writing, because, as in &lt;a href="https://blog.lo0.es/en/posts/capacity-planning-on-premise-llm-inference/">capacity planning&lt;/a>, a cost calculation without written assumptions is a disposable calculation.&lt;/p>
&lt;h3 id="depreciation-spread-capex">Depreciation (spread capex)&lt;/h3>
$$\text{depreciation cost/h} = \frac{\text{capex}_{\text{GPU}}}{\text{hours of useful life}}$$
&lt;p>where the hours of useful life are the depreciation years × 8,760 h/year × the effective availability (the GPU is not available 100 % of the time: there is maintenance, restarts, update windows). Explicit, &lt;strong>generic&lt;/strong> assumptions (they are not figures from any provider or any real purchase):&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Capex per GPU&lt;/strong>: 30,000 € — a reasonable assumption that includes not just the GPU but its &lt;strong>pro-rata share&lt;/strong> of the server, power supply, cooling, NVLink/InfiniBand network and rack. A &amp;ldquo;bare&amp;rdquo; H100 SXM costs less, but honest FinOps spreads the cost of the whole node across its GPUs.&lt;/li>
&lt;li>&lt;strong>Accounting useful life&lt;/strong>: 4 years. That is aggressive-realistic for a datacenter GPU; some depreciate over 3, others over 5.&lt;/li>
&lt;li>&lt;strong>Effective availability&lt;/strong>: 90 %.&lt;/li>
&lt;/ul>
$$\text{hours of life} = 4 \times 8{,}760 \times 0.90 = 31{,}536 \text{ h}$$
$$\text{depreciation cost/h} = \frac{30{,}000}{31{,}536} \approx 0.95 \text{ €/GPU-hour}$$
&lt;h3 id="energy-power--pue--price_kwh">Energy (power × PUE × price_kWh)&lt;/h3>
$$\text{energy cost/h} = P_{\text{GPU}} \cdot \text{PUE} \cdot \text{price kWh}$$
&lt;p>Assumptions:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>GPU power&lt;/strong>: the TDP of the H100 SXM is 700 W according to the NVIDIA datasheet (&lt;a href="https://resources.nvidia.com/en-us-gpu-resources/h100-datasheet-24306">H100 datasheet&lt;/a>). Under sustained inference load it hovers around that value; we use 0.7 kW as the average power under load. (At idle it drops a lot, 60–100 W, but the FinOps of reserved capacity reasons about power under load, which is what limits the rack.)&lt;/li>
&lt;li>&lt;strong>PUE&lt;/strong> (Power Usage Effectiveness): how many watts enter the datacenter for every watt that reaches the chip. A modest, well-managed on-premise datacenter sits at 1.4; a bad one at 2.0; the hyperscalers boast of 1.1. We use 1.4.&lt;/li>
&lt;li>&lt;strong>Price per kWh&lt;/strong>: 0.12 €/kWh — a generic assumption for an industrial tariff; it varies enormously by country and contract.&lt;/li>
&lt;/ul>
$$\text{energy cost/h} = 0.7 \cdot 1.4 \cdot 0.12 \approx 0.1176 \approx 0.12 \text{ €/GPU-hour}$$
&lt;h3 id="the-total-gpu-hour">The total €/GPU-hour&lt;/h3>
$$\boxed{\text{€/GPU-hour} = 0.95 + 0.12 \approx 1.07 \text{ €/GPU-hour}}$$
&lt;p>Two important readings of this number. First: depreciation dominates (89 % of the cost); energy is 11 %. This inverts many people&amp;rsquo;s intuition, who believe &amp;ldquo;the expensive part is the electricity&amp;rdquo;. With these assumptions, the expensive part is having bought the GPU, and that is why leaving it idle hurts so much. Second: the number is per-GPU; for the 4×H100 node it is ≈ 4.28 €/hour, and for the complete generic cluster of 4 nodes (16 GPUs), ≈ 17.1 €/hour ≈ 150,000 €/year of fixed cost that is paid whether it is used or not.&lt;/p>
&lt;h3 id="from-gpu-hour-to-token">From €/GPU-hour to €/token&lt;/h3>
&lt;p>Now we divide the hourly cost by the tokens the GPU produces in an hour. Here sustained throughput comes in. Let us suppose, an order-of-magnitude figure, checkable with &lt;code>vllm bench serve&lt;/code>, that a Llama 70B replica on 4×H100 (TP=4) sustains &lt;strong>2,000 useful tokens/s&lt;/strong> aggregated under good concurrency.&lt;/p>
$$\text{tokens/hour} = 2{,}000 \times 3{,}600 = 7.2 \times 10^6 \text{ tok/h}$$
&lt;p>The cost of that replica (4 GPUs) is $4 \times 1.07 = 4.28$ €/hour. Therefore:&lt;/p>
$$\text{€/1M tokens} = \frac{4.28}{7.2} \approx 0.59 \text{ €/1M tokens}$$
&lt;p>Per individual GPU, if we take a smaller replica (e.g. Llama 8B FP8 on 1 GPU at ~2,000 tok/s):&lt;/p>
$$\text{€/1M tokens} = \frac{1.07 \text{ €/h}}{7.2 \times 10^6 \text{ tok/h}} \times 10^6 \approx 0.149 \text{ €/1M tokens}$$
&lt;p>These numbers are &lt;strong>the theoretical floor at 100 % utilisation&lt;/strong>. Nobody operates at 100 %. And that is the whole story.&lt;/p>
&lt;h2 id="utilisation-is-the-lever-with-numbers">Utilisation is the lever (with numbers)&lt;/h2>
&lt;p>The €/token in the previous section assumes the GPU produces 7.2 M tokens every hour, hour after hour. In practice it produces that only during peak hours. The rest of the time it is partly idle: at night, at weekends, between traffic peaks. The &lt;strong>useful utilisation&lt;/strong> $u$ is the fraction of the theoretical capacity that really turns into billable tokens.&lt;/p>
&lt;p>The hourly cost is &lt;strong>fixed&lt;/strong> (1.07 €/GPU-hour is paid come rain or shine), but the tokens produced scale with $u$:&lt;/p>
$$\text{€/1M tokens}(u) = \frac{\text{€/GPU-hour}}{\text{max tok/hour} \cdot u} = \frac{1.07}{7.2 \times 10^6 \cdot u} \times 10^6 = \frac{0.149}{u}$$
&lt;p>The requested contrast, worked out:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Useful utilisation $u$&lt;/th>
&lt;th>Real tokens/h&lt;/th>
&lt;th>€/1M tokens&lt;/th>
&lt;th>Multiplier vs 80 %&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>100 %&lt;/td>
&lt;td>7.2 M&lt;/td>
&lt;td>0.149 €&lt;/td>
&lt;td>0.80×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>80 %&lt;/td>
&lt;td>5.76 M&lt;/td>
&lt;td>0.186 €&lt;/td>
&lt;td>1.00× (baseline)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>50 %&lt;/td>
&lt;td>3.6 M&lt;/td>
&lt;td>0.298 €&lt;/td>
&lt;td>1.60×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>20 %&lt;/td>
&lt;td>1.44 M&lt;/td>
&lt;td>0.744 €&lt;/td>
&lt;td>4.00×&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>10 %&lt;/td>
&lt;td>0.72 M&lt;/td>
&lt;td>1.488 €&lt;/td>
&lt;td>8.00×&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Direct reading: going from 20 % to 80 % utilisation divides the cost per token by four ($0.744 \to 0.186$). No tariff negotiation, no quantization, no hardware change gives you that factor of 4 so cheaply. Quantizing from BF16 to FP8 can double throughput, and therefore halves the €/token, but it degrades quality and demands evals (see &lt;a href="https://blog.lo0.es/en/posts/quantization-llm-inference-fp8-int4-gguf/">Quantization for inference&lt;/a>); raising utilisation from 20 to 80 % does not touch the quality of a single token.&lt;/p>
&lt;p>This reformulates GPU cluster FinOps in a single sentence: &lt;strong>the €/token is not something you negotiate, it is something you earn by filling the GPUs&lt;/strong>. And raising utilisation is an engineering problem that already has its pieces:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Autoscaling&lt;/strong> that switches replicas off when traffic drops, so as not to pay for idle GPU (see &lt;a href="https://blog.lo0.es/en/posts/llm-inference-autoscaling-kubernetes-hpa-keda/">LLM autoscaling on Kubernetes with KEDA&lt;/a>). Switching off a 4-GPU replica for eight hours overnight saves ≈ 34 € a day of real cost; multiplied by replicas and by days, that is the difference between a profitable cluster and one that bleeds.&lt;/li>
&lt;li>&lt;strong>Sharing a GPU&lt;/strong> between small workloads with MIG or time-slicing, so that two tenants that individually would use 30 % together fill a GPU to 60 % (see &lt;a href="https://blog.lo0.es/en/posts/sharing-one-gpu-time-slicing-mps-mig/">Sharing a GPU&lt;/a>).&lt;/li>
&lt;li>&lt;strong>Overnight batch&lt;/strong> that fills the off-peak hours with non-urgent work (re-embeddings, evals, light fine-tuning) instead of leaving the GPUs switched off or ticking over.&lt;/li>
&lt;li>&lt;strong>Reducing cold start&lt;/strong> so that scaling to zero and starting again is cheap and therefore viable as a utilisation strategy (see the sibling piece &lt;a href="https://blog.lo0.es/en/posts/speeding-up-model-cold-start-minutes-to-seconds/">Speeding up cold start with Tensorizer&lt;/a>).&lt;/li>
&lt;/ul>
&lt;h2 id="the-diagram-the-token-cascade-by-utilisation">The diagram: the €/token cascade by utilisation&lt;/h2>
&lt;p>The cost per token is not a number, it is a cascade that multiplies as utilisation falls:&lt;/p>
&lt;div class="diagram" style="max-width:760px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 760 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="cost per token cascade by utilisation">
&lt;text x="380" y="24" text-anchor="middle" font-size="15" font-weight="700" fill="currentColor">The €/token is manufactured, then inflated by low utilisation&lt;/text>
&lt;text x="380" y="48" text-anchor="middle" font-size="11" font-style="italic" fill="currentColor">fixed cost (capex + energy) → ÷ tokens produced → €/token, which grows as utilisation falls&lt;/text>
&lt;rect x="40" y="70" width="160" height="50" rx="6" fill="#8b5cf6" stroke="currentColor" stroke-width="1.2"/>
&lt;text x="120" y="92" text-anchor="middle" font-size="11" font-weight="700" fill="#ffffff">Depreciation&lt;/text>
&lt;text x="120" y="108" text-anchor="middle" font-size="11" fill="#ffffff">0.95 €/GPU-h&lt;/text>
&lt;rect x="40" y="130" width="160" height="50" rx="6" fill="#f59e0b" stroke="currentColor" stroke-width="1.2"/>
&lt;text x="120" y="152" text-anchor="middle" font-size="11" font-weight="700" fill="#ffffff">Energy × PUE&lt;/text>
&lt;text x="120" y="168" text-anchor="middle" font-size="11" fill="#ffffff">0.12 €/GPU-h&lt;/text>
&lt;path d="M200,95 L230,95 L230,150 L200,150" fill="none" stroke="currentColor" stroke-width="1.2"/>
&lt;line x1="230" y1="125" x2="265" y2="125" stroke="currentColor" stroke-width="1.4" marker-end="url(#fa)"/>
&lt;rect x="270" y="100" width="150" height="50" rx="6" fill="none" stroke="currentColor" stroke-width="1.6"/>
&lt;text x="345" y="122" text-anchor="middle" font-size="11" font-weight="700" fill="currentColor">€/GPU-hour&lt;/text>
&lt;text x="345" y="138" text-anchor="middle" font-size="11" fill="currentColor">1.07 €/h (fixed)&lt;/text>
&lt;text x="345" y="172" text-anchor="middle" font-size="10" font-style="italic" fill="currentColor">÷ tokens produced/hour&lt;/text>
&lt;line x1="420" y1="125" x2="470" y2="125" stroke="currentColor" stroke-width="1.4" marker-end="url(#fa)"/>
&lt;rect x="475" y="100" width="245" height="50" rx="6" fill="none" stroke="currentColor" stroke-width="1.6"/>
&lt;text x="597" y="122" text-anchor="middle" font-size="11" font-weight="700" fill="currentColor">€/1M tokens = 0.149 / u&lt;/text>
&lt;text x="597" y="138" text-anchor="middle" font-size="10" fill="currentColor">depends on useful utilisation u&lt;/text>
&lt;text x="380" y="200" text-anchor="middle" font-size="12" font-weight="700" fill="currentColor">Same GPU, different cost per token by u:&lt;/text>
&lt;rect x="90" y="220" width="130" height="110" rx="6" fill="#22c55e" stroke="currentColor" stroke-width="1.2"/>
&lt;text x="155" y="245" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">u = 80%&lt;/text>
&lt;text x="155" y="280" text-anchor="middle" font-size="18" font-weight="700" fill="#ffffff">0.186 €&lt;/text>
&lt;text x="155" y="300" text-anchor="middle" font-size="10" fill="#ffffff">/1M tokens&lt;/text>
&lt;text x="155" y="318" text-anchor="middle" font-size="10" fill="#ffffff">GPU well filled&lt;/text>
&lt;rect x="315" y="220" width="130" height="110" rx="6" fill="#f59e0b" stroke="currentColor" stroke-width="1.2"/>
&lt;text x="380" y="245" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">u = 50%&lt;/text>
&lt;text x="380" y="280" text-anchor="middle" font-size="18" font-weight="700" fill="#ffffff">0.298 €&lt;/text>
&lt;text x="380" y="300" text-anchor="middle" font-size="10" fill="#ffffff">/1M tokens&lt;/text>
&lt;text x="380" y="318" text-anchor="middle" font-size="10" fill="#ffffff">1.6× the baseline&lt;/text>
&lt;rect x="540" y="220" width="130" height="110" rx="6" fill="#ef4444" stroke="currentColor" stroke-width="1.2"/>
&lt;text x="605" y="245" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">u = 20%&lt;/text>
&lt;text x="605" y="280" text-anchor="middle" font-size="18" font-weight="700" fill="#ffffff">0.744 €&lt;/text>
&lt;text x="605" y="300" text-anchor="middle" font-size="10" fill="#ffffff">/1M tokens&lt;/text>
&lt;text x="605" y="318" text-anchor="middle" font-size="10" fill="#ffffff">4× the baseline&lt;/text>
&lt;defs>
&lt;marker id="fa" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/>&lt;/marker>
&lt;/defs>
&lt;/svg>
&lt;/div>
&lt;h2 id="attributing-the-cost-litellm-virtual-keys">Attributing the cost: LiteLLM virtual keys&lt;/h2>
&lt;p>With the €/token manufactured, it remains to attribute it to whoever consumed it. The piece that already sits in front of the engines, the &lt;a href="https://blog.lo0.es/en/posts/llm-inference-router-l7-switchboard/">L7 inference router&lt;/a>, is also the natural place to count. &lt;strong>LiteLLM Proxy&lt;/strong> makes chargeback real with four mechanisms, all documented:&lt;/p>
&lt;p>&lt;strong>Virtual keys with a budget.&lt;/strong> Each team or user receives a &lt;em>virtual key&lt;/em> with a &lt;code>max_budget&lt;/code> and a &lt;code>budget_duration&lt;/code>. You can define &lt;strong>several independent budget windows&lt;/strong> (for example one of 24 h and another of 30 d) that reset on their own cycle (&lt;a href="https://docs.litellm.ai/docs/proxy/virtual_keys">Virtual Keys&lt;/a>). When a team exhausts its monthly token budget, the proxy rejects with a budget exceeded error instead of carrying on spending GPU-hours nobody will be able to charge anywhere.&lt;/p>
&lt;p>&lt;strong>RPM/TPM rate limit.&lt;/strong> Each key carries requests per minute (RPM) and tokens per minute (TPM) limits (&lt;a href="https://docs.litellm.ai/docs/proxy/users">Budgets, Rate Limits&lt;/a>). This is not just anti-abuse protection: it is the tool that stops a noisy tenant monopolising the cluster&amp;rsquo;s utilisation at everybody else&amp;rsquo;s expense. The rate limit is the capacity quota; the budget is the spend quota. They are orthogonal and you use both.&lt;/p>
&lt;p>&lt;strong>Automatic spend tracking.&lt;/strong> LiteLLM accounts for the spend of all known models by key, user and team, recording the API key, the user, the &lt;code>team_id&lt;/code>, the request&amp;rsquo;s &lt;code>tags&lt;/code>, the end user, the model group and the token counts (&lt;a href="https://docs.litellm.ai/docs/proxy/cost_tracking">Spend Tracking&lt;/a>). For the cluster&amp;rsquo;s self-hosted models, the cost per token is configured &lt;strong>with the €/token we manufactured above&lt;/strong>, and that is the hook between the cost model and the real accounting.&lt;/p>
&lt;p>&lt;strong>Tags for cost centres.&lt;/strong> The &lt;code>tags&lt;/code> let you track spend and set budgets per label, categorising costs by project, department or cost centre (&lt;a href="https://docs.litellm.ai/docs/proxy/tag_budgets">Setting Tag Budgets&lt;/a>). A tag is attached when the key is created and &lt;strong>every request made with that key inherits the tag automatically&lt;/strong>; the proxy applies the label&amp;rsquo;s budget. That way chargeback maps cleanly onto the organisation&amp;rsquo;s accounting hierarchy without every team having to remember to tag by hand.&lt;/p>
&lt;p>A declarative catalogue fragment, which fits into the &lt;code>litellm-config&lt;/code> of the &lt;a href="https://blog.lo0.es/en/posts/llm-inference-router-l7-switchboard/">router post&lt;/a>, materialising the manufactured cost and two tenants:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">litellm_settings&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># €/token manufactured in this post for the self-hosted model.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="c"># input/output in €/token (not per 1M); 0.186 €/1M = 1.86e-7 €/token at u=80%.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">model_cost_map&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">llama-70b-onprem&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">input_cost_per_token&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.000000186&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">output_cost_per_token&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">0.000000186&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># Virtual keys per team (via the /key/generate API or config):&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># equipo-datos: max_budget=500 €/month, tpm_limit=200000, tags=[&amp;#34;cc-datos&amp;#34;]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># equipo-soporte: max_budget=150 €/month, tpm_limit=60000, tags=[&amp;#34;cc-soporte&amp;#34;]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># The proxy counts tokens, multiplies by model_cost_map, deducts from the budget,&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="c"># and attributes the spend to the tag → showback per cost centre, without touching the engine.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The complete attribution flow, from the request to the per-team report:&lt;/p>
&lt;div class="diagram" style="max-width:800px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 800 280" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="cost attribution flow with virtual key and tags">
&lt;text x="400" y="22" text-anchor="middle" font-size="15" font-weight="700" fill="currentColor">Attribution flow: from the request to per-team showback&lt;/text>
&lt;rect x="20" y="60" width="140" height="70" rx="6" fill="none" stroke="currentColor" stroke-width="1.4"/>
&lt;text x="90" y="88" text-anchor="middle" font-size="11" font-weight="700" fill="currentColor">Team request&lt;/text>
&lt;text x="90" y="106" text-anchor="middle" font-size="10" fill="currentColor">Authorization:&lt;/text>
&lt;text x="90" y="120" text-anchor="middle" font-size="10" fill="#3b82f6">Bearer sk-equipo-datos&lt;/text>
&lt;line x1="160" y1="95" x2="195" y2="95" stroke="currentColor" stroke-width="1.4" marker-end="url(#fb)"/>
&lt;rect x="200" y="50" width="170" height="90" rx="6" fill="none" stroke="#8b5cf6" stroke-width="1.8"/>
&lt;text x="285" y="74" text-anchor="middle" font-size="11" font-weight="700" fill="currentColor">LiteLLM Proxy&lt;/text>
&lt;text x="285" y="92" text-anchor="middle" font-size="9.5" fill="currentColor">1· validate key + budget&lt;/text>
&lt;text x="285" y="106" text-anchor="middle" font-size="9.5" fill="currentColor">2· apply RPM/TPM&lt;/text>
&lt;text x="285" y="120" text-anchor="middle" font-size="9.5" fill="currentColor">3· inherit tag cc-datos&lt;/text>
&lt;text x="285" y="134" text-anchor="middle" font-size="9.5" fill="currentColor">4· count tokens&lt;/text>
&lt;line x1="370" y1="95" x2="405" y2="95" stroke="currentColor" stroke-width="1.4" marker-end="url(#fb)"/>
&lt;rect x="410" y="60" width="150" height="70" rx="6" fill="none" stroke="currentColor" stroke-width="1.4"/>
&lt;text x="485" y="88" text-anchor="middle" font-size="11" font-weight="700" fill="currentColor">vLLM engine&lt;/text>
&lt;text x="485" y="106" text-anchor="middle" font-size="10" fill="currentColor">4×H100 · TP=4&lt;/text>
&lt;text x="485" y="120" text-anchor="middle" font-size="10" fill="currentColor">produces N tokens&lt;/text>
&lt;line x1="485" y1="130" x2="485" y2="165" stroke="currentColor" stroke-width="1.2" stroke-dasharray="4 2" marker-end="url(#fb)"/>
&lt;text x="555" y="152" text-anchor="middle" font-size="9.5" font-style="italic" fill="currentColor">usage.tokens back&lt;/text>
&lt;rect x="200" y="170" width="170" height="60" rx="6" fill="none" stroke="#22c55e" stroke-width="1.6"/>
&lt;text x="285" y="194" text-anchor="middle" font-size="11" font-weight="700" fill="currentColor">Spend tracking&lt;/text>
&lt;text x="285" y="210" text-anchor="middle" font-size="9.5" fill="currentColor">tokens × €/token (model_cost_map)&lt;/text>
&lt;text x="285" y="223" text-anchor="middle" font-size="9.5" fill="currentColor">→ deduct budget, store in DB&lt;/text>
&lt;line x1="285" y1="140" x2="285" y2="170" stroke="currentColor" stroke-width="1.4" marker-end="url(#fb)"/>
&lt;line x1="370" y1="200" x2="560" y2="200" stroke="currentColor" stroke-width="1.4" marker-end="url(#fb)"/>
&lt;rect x="565" y="170" width="215" height="60" rx="6" fill="none" stroke="#f59e0b" stroke-width="1.6"/>
&lt;text x="672" y="194" text-anchor="middle" font-size="11" font-weight="700" fill="currentColor">Report by tag / team&lt;/text>
&lt;text x="672" y="210" text-anchor="middle" font-size="9.5" fill="currentColor">cc-datos: 312 € · cc-soporte: 88 €&lt;/text>
&lt;text x="672" y="223" text-anchor="middle" font-size="9.5" fill="currentColor">showback (or chargeback to P&amp;amp;L)&lt;/text>
&lt;defs>
&lt;marker id="fb" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="currentColor"/>&lt;/marker>
&lt;/defs>
&lt;/svg>
&lt;/div>
&lt;h2 id="showback-vs-chargeback-the-distinction-the-finops-foundation-insists-on">Showback vs chargeback: the distinction the FinOps Foundation insists on&lt;/h2>
&lt;p>The two words are constantly confused and the difference is one of &lt;strong>accounting formality&lt;/strong>, not technology:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Showback&lt;/strong> — giving each team visibility of what it consumed and its cost, without really billing it to their budget. The report arrives, the team sees it, but the money does not move between cost centres.&lt;/li>
&lt;li>&lt;strong>Chargeback&lt;/strong> — really transferring the cost to the team&amp;rsquo;s or product&amp;rsquo;s budget, putting it in their P&amp;amp;L. The spend stops being a central IT cost and becomes an allocated cost.&lt;/li>
&lt;/ul>
&lt;p>The FinOps Foundation is explicit on two points worth carving in stone (&lt;a href="https://www.finops.org/framework/capabilities/invoicing-chargeback/">Invoicing &amp;amp; Chargeback&lt;/a>, &lt;a href="https://www.cloudzero.com/blog/chargeback-vs-showback/">Chargeback vs Showback&lt;/a>). First: &lt;strong>showback is a requirement of any FinOps practice; chargeback depends on the organisation&amp;rsquo;s accounting policy&lt;/strong>. Not all organisations can or want to move money between departments for GPU consumption; showback you can always do. Second, and counterintuitive: neither of the two is &amp;ldquo;more mature&amp;rdquo; than the other. The narrative that chargeback is the &amp;ldquo;grown-up&amp;rdquo; version of showback is false according to the framework itself. The practical recommendation: start with showback, giving visibility, then build cost allocation aligned to the organisational hierarchy, and only then, if the accounting policy supports it, switch chargeback on.&lt;/p>
&lt;p>For the on-premise GPU cluster this means: the LiteLLM machinery (keys, tags, spend tracking) always produces the showback. Turning it into chargeback is a decision for finance, not for the platform team. The platform team guarantees that the numbers are correct and reproducible; who really pays is a governance decision.&lt;/p>
&lt;h2 id="isolation-making-the-cost-genuinely-attributable">Isolation: making the cost genuinely attributable&lt;/h2>
&lt;p>Chargeback is only honest if consumption is &lt;strong>isolable&lt;/strong>. If two tenants share a GPU without partitioning and one saturates the HBM, the other suffers degradation it did not cause but which contaminates its cost attribution. Isolation has two planes.&lt;/p>
&lt;p>&lt;strong>Hard GPU isolation: MIG.&lt;/strong> Multi-Instance GPU physically partitions an H100 into up to 7 instances with dedicated memory and SMs; in a 7-way partition each instance has ~10 GB of HBM3 and its own SMs. MIG gives the strongest isolation: tenant A in its MIG instance cannot touch tenant B&amp;rsquo;s performance, and attribution is trivial because each instance is accountable separately (see &lt;a href="https://blog.lo0.es/en/posts/sharing-one-gpu-time-slicing-mps-mig/">Sharing a GPU&lt;/a> for the detail of MIG vs time-slicing vs MPS). The cost: MIG instances are fixed, they are not resized hot, and if they sit empty they are fragmented and wasted capex, the coworking desk subdivided into booths nobody rents.&lt;/p>
&lt;p>&lt;strong>Logical Kubernetes isolation.&lt;/strong> On top of the cluster:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Namespaces per tenant&lt;/strong> — the boundary for RBAC, NetworkPolicy and quotas.&lt;/li>
&lt;li>&lt;strong>&lt;code>ResourceQuota&lt;/code>&lt;/strong> — limits how many &lt;code>nvidia.com/gpu&lt;/code> (or &lt;code>nvidia.com/mig-1g.10gb&lt;/code>) a namespace can request. It is the GPU capacity quota at the scheduler level: the tenant cannot claim more GPUs than its quota grants, which &lt;strong>bounds its maximum cost&lt;/strong> by construction.&lt;/li>
&lt;li>&lt;strong>&lt;code>PriorityClass&lt;/code>&lt;/strong> — defines which workloads can evict others under pressure. The overnight re-embeddings batch runs with low priority and gives way to interactive inference; that way it fills the utilisation valleys without risking the paying tenant&amp;rsquo;s SLO. It is the piece that stops &amp;ldquo;raising utilisation with batch&amp;rdquo; from cannibalising quality of service.&lt;/li>
&lt;/ul>
&lt;p>The combination that works in the generic cluster: &lt;strong>MIG to partition the GPUs between tenants that need hard isolation and clean attribution&lt;/strong>, &lt;code>ResourceQuota&lt;/code> per namespace to bound each tenant&amp;rsquo;s cost, and &lt;code>PriorityClass&lt;/code> so that filler work raises utilisation without touching the priority tenants.&lt;/p>
&lt;h2 id="applied-to-the-generic-4h100-cluster">Applied to the generic 4×H100 cluster&lt;/h2>
&lt;p>Let us bring it all down to the series&amp;rsquo; cluster: 4 nodes × 4×H100 SXM 80 GB, 16 GPUs, ≈ 17.1 €/hour of fixed cost (≈ 150,000 €/year). Three teams share it: Data (production RAG, office-hours traffic), Support (ticket assistant, daytime peaks) and Platform (evals and re-embeddings batch, no urgency).&lt;/p>
&lt;p>&lt;strong>Splitting the cost.&lt;/strong> The cluster&amp;rsquo;s fixed cost (150,000 €/year) is split through LiteLLM&amp;rsquo;s spend tracking in proportion to the tokens consumed by each team, valued at the manufactured €/token. If in one month Data consumed 1,700 M tokens, Support 480 M and Platform 320 M, at 0.186 €/1M the showback is ≈ 316 € / 89 € / 60 €. The residue, the fixed cost of the GPUs nobody used because average utilisation was, say, 45 %, is the cost of idleness, and the governance decision is whether it is split between the tenants (penalises everybody equally) or charged to Platform as &amp;ldquo;cost of unsold capacity&amp;rdquo; (gives Platform an incentive to raise utilisation). The FinOps Foundation would say: make it visible first (showback of the cost of idleness), decide the split afterwards.&lt;/p>
&lt;p>&lt;strong>Quotas per tenant.&lt;/strong> Each team has its namespace with &lt;code>ResourceQuota&lt;/code>: Data can claim up to 8 GPUs (2 replicas at TP=4), Support up to 4, Platform up to 4 but with a low &lt;code>PriorityClass&lt;/code>, giving up its GPUs when Data or Support need them at peak. In LiteLLM, each team has its virtual key with a monthly &lt;code>max_budget&lt;/code> and a &lt;code>tpm_limit&lt;/code> that reflects its capacity quota.&lt;/p>
&lt;p>&lt;strong>MIG to isolate and attribute.&lt;/strong> For the small models (embeddings, reranker, a utility Llama 8B), an H100 split 7-way with MIG gives seven separately attributable instances: three for Data&amp;rsquo;s embeddings, two for Support&amp;rsquo;s reranker, two free for filler. Each instance is an independent accounting &amp;ldquo;desk&amp;rdquo;; the per-tenant showback comes straight from the scheduler.&lt;/p>
&lt;p>&lt;strong>The governance metric is utilisation, not the €/token.&lt;/strong> Here we close the circle. The KPI the cluster owner should look at daily is not &amp;ldquo;what the token costs&amp;rdquo;, that number only falls as a consequence, but &lt;code>DCGM_FI_DEV_GPU_UTIL&lt;/code>, the DCGM exporter metric that indicates what fraction of the time the GPU is not idle (&lt;a href="https://docs.nvidia.com/datacenter/dcgm/latest/gpu-telemetry/dcgm-exporter.html">DCGM exporter&lt;/a>). And with a critical nuance that already appeared in &lt;a href="https://blog.lo0.es/en/posts/capacity-planning-on-premise-llm-inference/">capacity planning&lt;/a>: &lt;code>DCGM_FI_DEV_GPU_UTIL&lt;/code> measures &amp;ldquo;not idle&amp;rdquo;, it does not measure useful work. A GPU can show 100 % GPU-util with the HBM saturated and low SM occupancy, producing few tokens. That is why serious FinOps crosses three signals: &lt;code>DCGM_FI_DEV_GPU_UTIL&lt;/code> (is the GPU busy?), &lt;code>DCGM_FI_PROF_SM_OCCUPANCY&lt;/code> and &lt;code>DCGM_FI_PROF_GR_ENGINE_ACTIVE&lt;/code> (is it doing real work?), and &lt;code>vllm:gpu_cache_usage_perc&lt;/code> (is the HBM well used?). The economic governance metric is useful tokens per GPU-hour, and it is watched from the &lt;a href="https://blog.lo0.es/en/posts/gpu-observability-llm-inference-twelve-dcgm-vllm-metrics/">GPU observability with DCGM&lt;/a> dashboard.&lt;/p>
&lt;p>An end-to-end assembly detail, LibreChat on top of LiteLLM with RAG, which closes the flow from tenant to cost, is covered in the (forthcoming) post on the end-to-end sovereign assistant; here it is enough to know that the metering point is always the proxy, never the engine.&lt;/p>
&lt;h2 id="four-traps-of-on-premise-gpu-finops">Four traps of on-premise GPU FinOps&lt;/h2>
&lt;p>&lt;strong>Trap 1 — comparing the on-premise €/token with the commercial API&amp;rsquo;s list price.&lt;/strong> The commercial API has margins, SLA and scale the on-premise cluster does not, but on-premise has the cost of idleness the API hides from you (they fill their GPUs with thousands of customers). The honest comparison is on-premise at its real utilisation vs the API at its real negotiated price, not the fantasy of on-premise at 100 %.&lt;/p>
&lt;p>&lt;strong>Trap 2 — forgetting the cost of idleness in the showback.&lt;/strong> If you only charge teams for the tokens they consumed, the cost of the idle GPUs disappears from the report and nobody sees it. That cost exists and somebody is paying it. Making it visible is the first step to reducing it.&lt;/p>
&lt;p>&lt;strong>Trap 3 — confusing high GPU-util with efficiency.&lt;/strong> A GPU at 100 % &lt;code>DCGM_FI_DEV_GPU_UTIL&lt;/code> with the HBM saturated and low SM occupancy spends capex without producing proportional tokens. The goal is not &amp;ldquo;GPU at 100 %&amp;rdquo;, it is &amp;ldquo;useful tokens per euro&amp;rdquo;. Always cross util with SM occupancy and real throughput.&lt;/p>
&lt;p>&lt;strong>Trap 4 — chargeback before showback.&lt;/strong> Switching chargeback on, moving money, before the teams trust that the numbers are correct generates disputes that burn the whole FinOps programme. First visibility, then trust in the data, then, if the accounting policy allows it, the money.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/multimodal-on-premise-serving-vlm-vllm/">Multimodal VLM on-premise with vLLM&lt;/a> — sibling piece: serving vision-language models on the same shared cluster whose cost we attribute here.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/speeding-up-model-cold-start-minutes-to-seconds/">Speeding up cold start with Tensorizer&lt;/a> — sibling piece: cutting the model load time makes scaling to zero viable, which is the most direct utilisation lever.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/capacity-planning-on-premise-llm-inference/">Capacity planning for on-premise LLM inference&lt;/a> — the sustained throughput the €/token comes from is computed there; FinOps monetises it.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/sharing-one-gpu-time-slicing-mps-mig/">Sharing a GPU: time-slicing, MPS and MIG&lt;/a> — the partitioning that lets you isolate tenants and raise utilisation by combining small workloads.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-inference-router-l7-switchboard/">The LLM inference router: the L7 switchboard&lt;/a> — where LiteLLM lives and where every token is counted and attributed.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-inference-autoscaling-kubernetes-hpa-keda/">LLM autoscaling on Kubernetes with KEDA&lt;/a> — switching off idle replicas is the number one lever to raise useful utilisation and lower the €/token.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/gpu-observability-llm-inference-twelve-dcgm-vllm-metrics/">GPU observability for LLM inference with DCGM&lt;/a> — where &lt;code>DCGM_FI_DEV_GPU_UTIL&lt;/code> comes from, the governance metric of the shared cluster.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/litellm-langfuse-operational-pair/">LiteLLM and Langfuse: the operational pair&lt;/a> — where the prices computed here get registered in LiteLLM, and why a model with no price silently produces zero spend and budgets that never run out.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/litellm-virtual-keys-budgets-limits/">Virtual keys, budgets and limits in LiteLLM&lt;/a> — how the budgets computed here are enforced in the proxy, and the condition in the code by which a team key overrides its owner&amp;rsquo;s personal cap.&lt;/li>
&lt;/ul>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>NVIDIA — &lt;em>H100 Tensor Core GPU Datasheet&lt;/em> (TDP 700 W, HBM3 3,35 TB/s): &lt;code>resources.nvidia.com/en-us-gpu-resources/h100-datasheet-24306&lt;/code>.&lt;/li>
&lt;li>LiteLLM — &lt;em>Virtual Keys&lt;/em> (&lt;code>docs.litellm.ai/docs/proxy/virtual_keys&lt;/code>), &lt;em>Budgets &amp;amp; Rate Limits&lt;/em> (&lt;code>/docs/proxy/users&lt;/code>), &lt;em>Spend Tracking&lt;/em> (&lt;code>/docs/proxy/cost_tracking&lt;/code>), &lt;em>Setting Tag Budgets&lt;/em> (&lt;code>/docs/proxy/tag_budgets&lt;/code>), &lt;em>Team Budgets&lt;/em> (&lt;code>/docs/proxy/team_budgets&lt;/code>).&lt;/li>
&lt;li>FinOps Foundation — &lt;em>Invoicing &amp;amp; Chargeback Capability&lt;/em> (&lt;code>finops.org/framework/capabilities/invoicing-chargeback/&lt;/code>) y &lt;em>Data Analysis and Showback&lt;/em> (&lt;code>finops.org/framework/previous-capabilities/analysis-showback/&lt;/code>).&lt;/li>
&lt;li>CloudZero — &lt;em>Chargeback vs. Showback: Cloud Cost Allocation Models Explained&lt;/em> (&lt;code>cloudzero.com/blog/chargeback-vs-showback/&lt;/code>).&lt;/li>
&lt;li>NVIDIA — &lt;em>DCGM Exporter&lt;/em> (&lt;code>docs.nvidia.com/datacenter/dcgm/latest/gpu-telemetry/dcgm-exporter.html&lt;/code>): &lt;code>DCGM_FI_DEV_GPU_UTIL&lt;/code>, &lt;code>DCGM_FI_PROF_SM_OCCUPANCY&lt;/code>, &lt;code>DCGM_FI_PROF_GR_ENGINE_ACTIVE&lt;/code>.&lt;/li>
&lt;/ul></description></item></channel></rss>