<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Sovereignty on lo0 — Tech Blog</title><link>https://blog.lo0.es/en/tags/sovereignty/</link><description>Recent content in Sovereignty on lo0 — Tech Blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 31 Aug 2026 09:45:00 +0200</lastBuildDate><atom:link href="https://blog.lo0.es/en/tags/sovereignty/index.xml" rel="self" type="application/rss+xml"/><item><title>KubeVela and Score: handing over the platform by CLI without teaching the developer Kubernetes</title><link>https://blog.lo0.es/en/posts/kubevela-score-platform-cli-without-teaching-kubernetes/</link><pubDate>Mon, 31 Aug 2026 09:45:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/kubevela-score-platform-cli-without-teaching-kubernetes/</guid><description>&lt;blockquote>
&lt;p>Third and last article in the batch about rebuilding the cloud on-premise. The &lt;a href="https://blog.lo0.es/en/posts/public-cloud-to-private-what-you-lose-which-projects-fill-gap/">general map&lt;/a> placed the gaps for you and &lt;a href="https://blog.lo0.es/en/posts/crossplane-control-plane-private-cloud-api/">Crossplane&lt;/a> covered the infrastructure control plane. Here comes the layer above, the one your developer touches every day: application-centric self-service by CLI.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Crossplane answers &amp;ldquo;give me a database&amp;rdquo;, from the infrastructure side. KubeVela and Score answer &amp;ldquo;deploy my application&amp;rdquo;, from the workload side, and they do it by CLI, which is precisely the friendly experience Crossplane deliberately does not give you. The two share an article because they cover the same layer from different angles. KubeVela is a complete delivery platform: it uses the OAM model, where your platform team writes some definitions in the CUE language once and the developer deploys their whole application with a fifteen-line file and a CLI (&lt;code>vela up&lt;/code>, &lt;code>vela status&lt;/code>, &lt;code>vela logs&lt;/code>) that feels like Heroku on the outside and is GitOps on the inside. Score is more modest and more portable: a workload specification in a single file that gets translated by CLI into Docker Compose locally and into Kubernetes in production, without running anything in the cluster.&lt;/p>
&lt;p>The honest part up front is the limits. Neither of the two appears in the &amp;ldquo;Adopt&amp;rdquo; section of the CNCF technology radar from early 2026, where Helm, Backstage and Argo CD do: they are niche against the mainstream. KubeVela version 1.11 is still in alpha, so the stable one is 1.10; its community is concentrated, with around 33 people contributing in 2025. Score is young, its Kubernetes implementation has few stars and its sponsor, Humanitec, was acquired by Akamai. And KubeVela&amp;rsquo;s power lives in CUE, an uncommon language that is hard to debug. The abstraction is real, but when it breaks, your developer ends up looking at Kubernetes YAML all the same.&lt;/p>
&lt;h2 id="the-analogy-the-order-form-against-the-shopping-list">The analogy: the order form against the shopping list&lt;/h2>
&lt;p>Deploying on raw Kubernetes is going to the wholesaler with a technical shopping list: you have to know that you want a Deployment, a Service, a HorizontalPodAutoscaler, an Ingress, a ConfigMap and a ResourceQuota, and how each one gets filled in. It works if you know the warehouse. But your data scientist, who just wants to serve their model, should not have to learn the warehouse.&lt;/p>
&lt;p>KubeVela is an order form. Instead of the technical list, you tick &amp;ldquo;I want to serve this application, with these replicas, with this gateway&amp;rdquo;, and behind it somebody who does know the warehouse (your platform team) has defined exactly what gets ordered for that to work. The developer fills in the form; the translation into the technical list was done by somebody else, once, for everyone.&lt;/p>
&lt;p>Score is the same form but designed so that it works for you in two different shops. You describe your order once and the same sheet serves the market next door (Docker Compose, your laptop) and the wholesaler (Kubernetes, production), without rewriting it. Whatever changes between one shop and the other is supplied by whoever translates, not by you. Both analogies point at the same thing: separating what the developer wants from how it materialises, so that they do not have to learn the warehouse.&lt;/p>
&lt;h2 id="kubevela-the-application-as-the-unit">KubeVela: the application as the unit&lt;/h2>
&lt;h3 id="what-it-is-and-what-state-it-reaches-2026-in">What it is and what state it reaches 2026 in&lt;/h3>
&lt;p>KubeVela is a CNCF project at incubation level since February 2023. It was born as an evolution of the OAM runtime on Kubernetes, with initial contributions from Alibaba Cloud, Microsoft and Upbound, and it rests on the Open Application Model, the specification Alibaba and Microsoft co-created to describe applications independently of the infrastructure.&lt;/p>
&lt;p>Here is a realism check on its community size. The official 2025 retrospective declares seven releases in the year, 124 commits and around 33 people contributing, on a little over 7,600 GitHub stars. It is not a project in decline, but it is a concentrated community, with a growth curve that has flattened compared to its early stage under Alibaba. And an important version warning: the stable branch is 1.10, with regular patches through 2026; 1.11 is still in alpha, so do not rely on its features as if they were available.&lt;/p>
&lt;h3 id="the-oam-model-in-five-pieces">The OAM model in five pieces&lt;/h3>
&lt;p>Your whole application is described in a single YAML file of type &lt;code>Application&lt;/code>, with four sections you need to know by name. The &lt;strong>components&lt;/strong> are the artefact you deploy, be it an image, a Helm chart or a service; the &lt;strong>traits&lt;/strong> are operational requirements you hang off each component, such as scaling, the gateway or storage; the &lt;strong>policies&lt;/strong> are global application strategy, such as multi-cluster topology or SLOs; and the &lt;strong>workflow&lt;/strong> is the step-by-step delivery process, which can include a manual approval pause or a notification.&lt;/p>
&lt;p>The key to the model is that component and trait types are not fixed: they are programmable modules maintained by your platform team, expressed as &lt;code>ComponentDefinition&lt;/code> and &lt;code>TraitDefinition&lt;/code>. The developer only consumes a type and its properties; they never see the Kubernetes YAML underneath. That is exactly the &amp;ldquo;do not expose raw Kubernetes&amp;rdquo; this is all about: your platform operator writes the definition once, your data scientist writes fifteen lines of &lt;code>Application&lt;/code>.&lt;/p>
&lt;h3 id="cue-the-power-and-the-toll">CUE, the power and the toll&lt;/h3>
&lt;p>The definitions are written in CUE, and here you have KubeVela&amp;rsquo;s strength and weakness at the same time. CUE lets you parameterise seriously: the definition declares which inputs it exposes to the developer and which Kubernetes resources it renders from them, with access to context variables such as the application name. The real flow starts from an existing YAML, which &lt;code>vela def init&lt;/code> turns into a CUE skeleton; you edit it, validate it with &lt;code>vela def vet&lt;/code> and publish it with &lt;code>vela def apply&lt;/code>, and it becomes available to all your developers.&lt;/p>
&lt;p>The toll is that CUE is an uncommon language, with few experts and hard to debug. The cost falls on your platform team, not on the developer, which is the correct distribution, but it is a real cost you have to sign up for before adopting. Nobody arrives already knowing CUE.&lt;/p>
&lt;h3 id="the-vela-cli-heroku-on-the-outside-gitops-on-the-inside">The vela CLI: Heroku on the outside, GitOps on the inside&lt;/h3>
&lt;p>Here is the answer to the question this blog has been dragging along about CLI provisioning. Day to day with KubeVela looks like Heroku: &lt;code>vela up&lt;/code> deploys the application from local files, &lt;code>vela status&lt;/code> shows you its state, &lt;code>vela ls&lt;/code> lists the applications, &lt;code>vela logs&lt;/code> follows the logs, &lt;code>vela exec&lt;/code> runs a command inside the container without you knowing the pod name, and &lt;code>vela port-forward&lt;/code> opens a tunnel for you. You even have &lt;code>vela dry-run&lt;/code>, which renders the Kubernetes resources without applying them, so you can see what is going to happen.&lt;/p>
&lt;p>The honest qualification is that it is &amp;ldquo;imperative on the surface, declarative underneath&amp;rdquo;. When you run &lt;code>vela up&lt;/code> you are not executing an imperative command like &lt;code>heroku scale&lt;/code>, you are applying a declarative &lt;code>Application&lt;/code> object that a controller reconciles. The CLI imitates Heroku&amp;rsquo;s comfortable flow, but the underlying model is GitOps, which means it fits with &lt;a href="https://blog.lo0.es/en/posts/gitops-inference-stack-flux/">your Flux stack&lt;/a> through the corresponding addon, instead of replacing it.&lt;/p>
&lt;h3 id="multi-cluster-which-is-what-matters-to-you">Multi-cluster, which is what matters to you&lt;/h3>
&lt;p>If you have a platform with several sites (site01 and site02, in my case) the multi-cluster part is the deciding one. KubeVela orchestrates from a central cluster and only the already rendered resources reach the managed clusters, with centralised governance. A &lt;code>topology policy&lt;/code> decides the destination by cluster list or by label selector (by region, for example); an &lt;code>override policy&lt;/code> adjusts images, replicas or traits per cluster without touching the component; and the deployment step in the workflow distributes, with the option of a phased rollout: first to the local cluster, then a manual approval gate, and only then to production. It is the kind of governance you have to build on-premise and that the cloud served you ready-made.&lt;/p>
&lt;h3 id="velaux-the-lightweight-portal-thrown-in">VelaUX, the lightweight portal thrown in&lt;/h3>
&lt;p>KubeVela comes with an optional web console, VelaUX, which you switch on as an addon and which gives you a dashboard, project management, environments and application lifecycle. If you want CLI first it is optional, more useful for giving operators visibility and access control than as the main route. It is a lightweight portal &amp;ldquo;thrown in&amp;rdquo; that does not aim to compete with &lt;a href="https://blog.lo0.es/en/posts/backstage-self-service-portal-llm-platform/">Backstage&lt;/a>, but it can save you from setting one up if your needs are modest.&lt;/p>
&lt;h2 id="score-describe-the-workload-once">Score: describe the workload once&lt;/h2>
&lt;h3 id="what-it-is-and-what-it-solves">What it is and what it solves&lt;/h3>
&lt;p>Score is an open workload specification, agnostic of platform and environment. The idea, in its own formulation, is that you define the workload once and use a Score implementation to translate it to several platforms: Docker Compose, Kubernetes, Fly.io or Cloud Run. The problem it attacks is concrete and familiar: the divergence between what you run on your laptop with Docker Compose and what gets deployed in production with Kubernetes, two descriptions that drift apart and cause you the classic &amp;ldquo;it worked on my machine&amp;rdquo;.&lt;/p>
&lt;p>In a &lt;code>score.yaml&lt;/code> file the developer declares their containers, the service ports and the resources they need as dependencies (a database, a DNS record, a route), and the environment-specific parameters are injected at deploy time. Score entered the CNCF sandbox in July 2024. It was created and donated by Humanitec, a developer platform company, some eighteen months before it joined the foundation.&lt;/p>
&lt;h3 id="the-two-implementations">The two implementations&lt;/h3>
&lt;p>Score does nothing on its own: it is a specification that needs an implementation to translate. You have two official ones. &lt;code>score-compose&lt;/code> generates Docker Compose for local development, with &lt;code>init&lt;/code> and &lt;code>generate&lt;/code> commands, and provisions on the order of fourteen resource types, from Postgres or Redis to S3 storage or queues. &lt;code>score-k8s&lt;/code> generates Kubernetes manifests ready for &lt;code>kubectl apply&lt;/code>, with flags for the image, the namespace and the provisioners. Humanitec&amp;rsquo;s commercial CLI, &lt;code>humctl&lt;/code>, also supports Score natively.&lt;/p>
&lt;p>The most important architectural fact is that &lt;strong>Score runs nothing in the target cluster&lt;/strong>. It is purely CLI translation: it generates manifests for you and steps aside. There is no controller, no CRD, no reconciliation. That is at once its virtue (it is lightweight, portable and without runtime lock-in) and its limit (it neither orchestrates nor deploys, it only translates).&lt;/p>
&lt;h3 id="score-against-kubevela-and-helm">Score against KubeVela and Helm&lt;/h3>
&lt;p>Score&amp;rsquo;s own official positioning clarifies the layers, and I will pin it down for you because they are easy to confuse. Score is the workload specification layer, developer-centric: it describes a workload and its dependencies and gets translated to N platforms by CLI, but it does not orchestrate or deploy. KubeVela is a complete continuous delivery platform, application-centric: it orchestrates multiple components, applies policies and workflows, does multi-cluster, and for that it needs its controller in the cluster; it is, in its own words, far richer in features than Score. Helm is the packaging and rendering of manifests, without the developer-centric abstraction or the portability to Compose.&lt;/p>
&lt;p>The clean synthesis is that they are complementary layers, not substitutes: Score generates, Helm or the manifests package, KubeVela orchestrates and deploys. If they are presented to you as rivals, somebody has confused the layers.&lt;/p>
&lt;h2 id="where-each-one-fits-the-cross-cutting-comparison">Where each one fits: the cross-cutting comparison&lt;/h2>
&lt;p>With Crossplane already in hand from the previous article, the comparison of the three self-service pieces sorts itself by the question each one answers.&lt;/p>
&lt;p>Score and KubeVela answer at the &lt;strong>application&lt;/strong> level: the workload a developer deploys, &amp;ldquo;deploy this app&amp;rdquo;. Crossplane answers at the &lt;strong>infrastructure&lt;/strong> level: the resource a developer asks for, &amp;ldquo;give me a managed Postgres&amp;rdquo;. And Backstage answers at the &lt;strong>portal&lt;/strong> level: the web catalogue and the golden paths, the discovery layer that neither deploys nor provisions by itself, but orchestrates the ones below.&lt;/p>
&lt;p>The interesting part is that they combine. A &lt;code>score.yaml&lt;/code> can declare its resources and delegate the provisioning of those resources to Crossplane through a provisioner, while KubeVela orchestrates the application deployment and Backstage acts as the façade. It is not an either-or; it is a stack where each layer does its job. If your team is small, you pick a single piece and that is that; the complete stack is for mature platforms.&lt;/p>
&lt;h2 id="the-fit-with-inference-a-pattern-not-an-official-recipe">The fit with inference: a pattern, not an official recipe&lt;/h2>
&lt;p>Just as with Crossplane, honesty is called for here: there is no official, maintained KServe definition in KubeVela&amp;rsquo;s default catalogue, nor an inference resource type in Score. What follows is a design pattern with pieces confirmed separately, not a documented recipe.&lt;/p>
&lt;p>The KubeVela pattern would be that your platform operator defines once, in CUE, a &lt;code>ComponentDefinition&lt;/code> called for example &lt;code>inference-service&lt;/code>, which underneath renders a KServe inference service with vLLM runtime, with the GPU limits, the namespace quotas and the autoscaling already wired in. Your data scientist then writes a fifteen-line &lt;code>Application&lt;/code> with the &lt;code>inference-service&lt;/code> type and its properties (the model, the replicas), without touching KServe, without knowing the GPU quotas or the runtime class, and operates with &lt;code>vela up&lt;/code>, &lt;code>vela status&lt;/code> and &lt;code>vela logs&lt;/code>. Multi-site is solved for you by the topology policy, which decides which GPU cluster it lands on, and the override policy, which adjusts replicas or model per site, with a manual approval in the workflow before production.&lt;/p>
&lt;p>The Score alternative would be a &lt;code>score.yaml&lt;/code> with a resource of type &lt;code>inference-endpoint&lt;/code> that &lt;code>score-k8s&lt;/code>, with a custom provisioner, emits as a KServe inference service. More portable, but today it requires that provisioner of your own, because KServe is not a default type. In both cases, the abstraction layer rests on pieces that are already standard (KServe moved to CNCF incubation at the end of 2025 and vLLM is its reference generative runtime), but the inference abstraction is built by you. Take it as a proposal, not as a proven case.&lt;/p>
&lt;p>A note for you: in 2026 KServe introduced a type dedicated to large models, &lt;code>LLMInferenceService&lt;/code>, still in alpha and with vLLM as its runtime, which is the brick a KubeVela definition or a Score provisioner would render underneath. And at the infrastructure layer, not this one, the Crossplane team published Modelplane, an inference control plane made of compositions that serves vLLM directly. Neither of the two is a KubeVela or a Score type, but both confirm for you that the brick underneath is standardising, which is what makes putting a self-service façade on top viable.&lt;/p>
&lt;h2 id="caveats-no-frills">Caveats, no frills&lt;/h2>
&lt;p>This is the section that separates the article from the brochure, and with these two projects you need it especially.&lt;/p>
&lt;p>&lt;strong>Adoption is niche, and there is a hard figure.&lt;/strong> The CNCF technology radar from early 2026, covering more than 400 developers, places Helm, Backstage and Argo CD in &amp;ldquo;Adopt&amp;rdquo;. Neither KubeVela, nor Score, nor Crossplane get a mention. They are niche tools against the mainstream, and choosing them means betting on an adoption curve that may not take off.&lt;/p>
&lt;p>&lt;strong>Score is young and its sponsor changed hands.&lt;/strong> Its Kubernetes implementation has few stars, the specification is at 0.x versions, and Humanitec, which sponsors it, was acquired by Akamai in 2025, so you have to watch the continuity of the investment in the open source part.&lt;/p>
&lt;p>&lt;strong>KubeVela has a concentrated community and v1.11 in alpha.&lt;/strong> The 33 people contributing in 2025 and the flat star curve are a sign of a plateau. Do not build on 1.11 features until it is stable.&lt;/p>
&lt;p>&lt;strong>The CUE curve is real.&lt;/strong> KubeVela&amp;rsquo;s power lives in CUE definitions, and CUE is hard. The cost falls on the platform, but it exists.&lt;/p>
&lt;p>&lt;strong>And the usual risk with abstractions: the leak.&lt;/strong> Both KubeVela and Score add an indirection over Kubernetes and KServe. When something fails underneath, the abstraction cracks and your developer ends up looking at the KServe YAML anyway, plus lock-in on your own definitions. An abstraction that does not hold up under failure hands you back exactly the complexity it promised to hide.&lt;/p>
&lt;h2 id="for-your-sovereign-inference-factory">For your sovereign inference factory&lt;/h2>
&lt;p>The practical decision sorts itself by the profile of your users. If your people live in the CLI and think in applications, KubeVela gives you the Heroku-style experience on your own iron, with real multi-cluster, in exchange for your platform team learning CUE. If what hurts most is the divergence between local and production, Score solves that with the bare minimum, without putting one more runtime in the cluster. And if you want both plus a portal, the Score, Crossplane, KubeVela and Backstage stack exists, but it is for a platform that already serves many teams, not for the start.&lt;/p>
&lt;p>The sobriety criterion is the same as throughout this batch: these tools plug a real gap left by the cloud you walked away from, but each one is one more system to operate. For two teams who know each other, good Helm and a tidy GitOps repository do the job without CUE or definitions of your own. Application-centric self-service starts paying you back when the number of people deploying exceeds the number of people who know Kubernetes, which is precisely the moment an inference factory stops being a project and starts being a platform.&lt;/p>
&lt;p>That closes the batch. The &lt;a href="https://blog.lo0.es/en/posts/public-cloud-to-private-what-you-lose-which-projects-fill-gap/">map&lt;/a> drew the gaps of leaving the cloud, &lt;a href="https://blog.lo0.es/en/posts/crossplane-control-plane-private-cloud-api/">Crossplane&lt;/a> covered the infrastructure control plane, and KubeVela with Score cover the face the developer touches. Your own house now looks quite a lot like the hotel, and the data stayed inside.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/public-cloud-to-private-what-you-lose-which-projects-fill-gap/">From the public cloud to the private one: the map&lt;/a> — the complete problem this batch breaks down.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/crossplane-control-plane-private-cloud-api/">Crossplane: the private cloud control plane&lt;/a> — the infrastructure layer, complementary to this one.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/backstage-self-service-portal-llm-platform/">Backstage as a self-service portal&lt;/a> — the third route to self-service, the web portal one.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/kserve-open-inference-protocol-control-plane/">KServe and the open inference protocol&lt;/a> — what the inference definitions would materialise underneath.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/gitops-inference-stack-flux/">GitOps for the inference stack with Flux&lt;/a> — the engine KubeVela lives alongside.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>KubeVela, &lt;em>State of KubeVela 2025&lt;/em> — &lt;a href="https://kubevela.io/blog/2025/12/20/state-of-kubevela-2025/">https://kubevela.io/blog/2025/12/20/state-of-kubevela-2025/&lt;/a>&lt;/li>
&lt;li>CNCF, &lt;em>KubeVela brings software delivery control plane capabilities to the CNCF incubator&lt;/em> — &lt;a href="https://www.cncf.io/blog/2023/02/27/kubevela-brings-software-delivery-control-plane-capabilities-to-cncf-incubator/">https://www.cncf.io/blog/2023/02/27/kubevela-brings-software-delivery-control-plane-capabilities-to-cncf-incubator/&lt;/a>&lt;/li>
&lt;li>KubeVela Docs, &lt;em>Core Concept&lt;/em> — &lt;a href="https://kubevela.io/docs/getting-started/core-concept/">https://kubevela.io/docs/getting-started/core-concept/&lt;/a>&lt;/li>
&lt;li>KubeVela Docs, &lt;em>vela CLI reference&lt;/em> — &lt;a href="https://kubevela.io/docs/cli/vela/">https://kubevela.io/docs/cli/vela/&lt;/a>&lt;/li>
&lt;li>KubeVela Docs, &lt;em>Custom Component with CUE&lt;/em> — &lt;a href="https://kubevela.io/docs/platform-engineers/components/custom-component/">https://kubevela.io/docs/platform-engineers/components/custom-component/&lt;/a>&lt;/li>
&lt;li>KubeVela Docs, &lt;em>Multi-cluster delivery&lt;/em> — &lt;a href="https://kubevela.io/docs/case-studies/multi-cluster/">https://kubevela.io/docs/case-studies/multi-cluster/&lt;/a>&lt;/li>
&lt;li>KubeVela Docs, &lt;em>VelaUX addon&lt;/em> — &lt;a href="https://kubevela.io/docs/reference/addons/velaux/">https://kubevela.io/docs/reference/addons/velaux/&lt;/a>&lt;/li>
&lt;li>CNCF, &lt;em>Score accepted as a CNCF Sandbox Project&lt;/em> — &lt;a href="https://www.cncf.io/blog/2024/08/08/score-accepted-as-a-cncf-sandbox-project/">https://www.cncf.io/blog/2024/08/08/score-accepted-as-a-cncf-sandbox-project/&lt;/a>&lt;/li>
&lt;li>Score Docs, &lt;em>Overview&lt;/em> — &lt;a href="https://docs.score.dev/docs/">https://docs.score.dev/docs/&lt;/a>&lt;/li>
&lt;li>Score, &lt;em>Score vs the Open Application Model and KubeVela&lt;/em> — &lt;a href="https://score.dev/blog/score-vs-open-application-model-kubevela/">https://score.dev/blog/score-vs-open-application-model-kubevela/&lt;/a>&lt;/li>
&lt;li>GitHub, &lt;em>score-compose&lt;/em> — &lt;a href="https://github.com/score-spec/score-compose">https://github.com/score-spec/score-compose&lt;/a>&lt;/li>
&lt;li>GitHub, &lt;em>score-k8s&lt;/em> — &lt;a href="https://github.com/score-spec/score-k8s">https://github.com/score-spec/score-k8s&lt;/a>&lt;/li>
&lt;li>CNCF, &lt;em>KServe becomes a CNCF Incubating Project&lt;/em> — &lt;a href="https://www.cncf.io/blog/2025/11/11/kserve-becomes-a-cncf-incubating-project/">https://www.cncf.io/blog/2025/11/11/kserve-becomes-a-cncf-incubating-project/&lt;/a>&lt;/li>
&lt;li>KServe Docs, &lt;em>Understanding LLMInferenceService&lt;/em> — &lt;a href="https://kserve.github.io/website/docs/model-serving/generative-inference/llmisvc/llmisvc-overview">https://kserve.github.io/website/docs/model-serving/generative-inference/llmisvc/llmisvc-overview&lt;/a>&lt;/li>
&lt;li>Crossplane Blog, &lt;em>Building Modelplane on Crossplane&lt;/em> — &lt;a href="https://blog.crossplane.io/building-modelplane/">https://blog.crossplane.io/building-modelplane/&lt;/a> · &lt;a href="https://modelplane.ai">https://modelplane.ai&lt;/a>&lt;/li>
&lt;li>CNCF y SlashData, &lt;em>Technology Radar Q1 2026: Platform Engineering&lt;/em> — &lt;a href="https://www.cncf.io/announcements/2026/03/24/cncf-and-slashdata-report-finds-platform-engineering-tools-maturing-as-organizations-prepare-for-ai-driven-infrastructure/">https://www.cncf.io/announcements/2026/03/24/cncf-and-slashdata-report-finds-platform-engineering-tools-maturing-as-organizations-prepare-for-ai-driven-infrastructure/&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Crossplane: the control plane that turns your cluster into a private cloud API</title><link>https://blog.lo0.es/en/posts/crossplane-control-plane-private-cloud-api/</link><pubDate>Mon, 31 Aug 2026 09:30:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/crossplane-control-plane-private-cloud-api/</guid><description>&lt;blockquote>
&lt;p>Second article in the batch about rebuilding the cloud experience on-premise. The &lt;a href="https://blog.lo0.es/en/posts/public-cloud-to-private-what-you-lose-which-projects-fill-gap/">general map&lt;/a> placed the gaps for you; this one goes into the provisioning control plane, that API which on-premise does not come for free. The &lt;a href="https://blog.lo0.es/en/posts/kubevela-score-platform-cli-without-teaching-kubernetes/">third&lt;/a> moves up a layer, to application-centric self-service with KubeVela and Score.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Crossplane solves the &amp;ldquo;open a ticket and wait&amp;rdquo; gap for you. In the cloud you ask for a database through an API and it appears with permissions, backups and monitoring; on-premise you do that provisioning by hand or with scripts. Crossplane brings the Kubernetes model of declarative APIs to infrastructure: your platform team defines an API of its own (a database, an inference endpoint) and the developer consumes it with a &lt;code>kubectl apply&lt;/code> of a few fields, while controllers inside the cluster reconcile the desired state against reality and correct drift continuously. It graduated from the CNCF in November 2025, with more than three thousand contributors and adopters such as Nike, SAP, IBM or NASA&amp;rsquo;s science cloud.&lt;/p>
&lt;p>The difference from Terraform is one of nature, not of features: Terraform applies a plan on demand and leaves; Crossplane is a control plane that lives inside the cluster and never stops reconciling. That gives you drift self-correction and native GitOps, and in exchange it takes away the safety net of &lt;code>plan&lt;/code> and forces you to operate the control plane itself, with providers that create hundreds of resource definitions and state that lives in etcd. And a warning about something awkward up front: Crossplane is not an imperative &lt;code>aws ...&lt;/code> style CLI, it is declarative first. On the fit with GPU inference, which until recently was only a pattern with no public case, you now have a real reference, Modelplane, from the Crossplane team itself, although at version 0.1 and composing vLLM directly without going through KServe. I go into the detail at the end.&lt;/p>
&lt;h2 id="the-analogy-the-thermostat-against-the-light-switch">The analogy: the thermostat against the light switch&lt;/h2>
&lt;p>Think of it this way: Terraform is a light switch. You flip it, the light comes on, and it stays there until you touch it again. If somebody else changes it, the light obeys whoever touched it last, and you do not find out until you go and look. It is provisioning by one-off action: you run it, it applies, it finishes.&lt;/p>
&lt;p>Crossplane is a thermostat. You do not tell it &amp;ldquo;turn the heating on&amp;rdquo;, you tell it &amp;ldquo;I want twenty-one degrees&amp;rdquo;, and from then on a mechanism watches the temperature and acts on its own, continuously, to hold it. If somebody opens a window and the thermometer drops, the thermostat reacts without you intervening. That is continuous reconciliation: you declare the desired state once and a controller takes care of reality not straying from it.&lt;/p>
&lt;p>The analogy also warns you about the cost. A thermostat is more convenient than a switch, but it is one more device you have to install, power and fix when it fails, and if you program it badly it adjusts the temperature to something you did not want, without asking. A switch almost never breaks and you always know what state it is in. Crossplane gives you the convenience of the thermostat in exchange for you operating the thermostat.&lt;/p>
&lt;h2 id="what-crossplane-is-and-what-state-it-reaches-2026-in">What Crossplane is and what state it reaches 2026 in&lt;/h2>
&lt;p>Crossplane was born at Upbound in 2018 and followed the CNCF maturity path without shortcuts: sandbox in 2020, incubation in 2021 and &lt;strong>graduation on 6 November 2025&lt;/strong>. Graduation matters because it is the seal the CNCF reserves for projects it considers production-ready, the same level as Kubernetes or Prometheus. The figures that came with the announcement are solid: more than three thousand contributors from more than 450 organisations, more than a thousand pull request authors (which puts it in the top 10 % of the foundation&amp;rsquo;s projects by that metric), more than a hundred releases and two completed security audits. Among the more than seventy public adopters you have Nike, Nokia, Grafana, NASA&amp;rsquo;s science cloud, SAP and IBM.&lt;/p>
&lt;p>The project&amp;rsquo;s official description is &amp;ldquo;the cloud native control plane&amp;rdquo;, and that phrase sums up exactly what it does. The main vendor behind it is still Upbound, which at graduation framed the project&amp;rsquo;s future towards &amp;ldquo;AI-native&amp;rdquo; infrastructure, a point I come back to when talking about inference, with the caveat that it is a vendor&amp;rsquo;s positioning, not a proven case.&lt;/p>
&lt;p>The release cadence is quarterly, with a nine-month support window and three actively maintained versions at any time. As of writing, the v2 branch is the current one, with v1.20 as the last close of the v1 branch.&lt;/p>
&lt;h2 id="the-v2-turn-which-changes-how-you-think-about-it">The v2 turn (which changes how you think about it)&lt;/h2>
&lt;p>Crossplane 2.0 reached general availability in August 2025 and brings four structural changes you need to know about, because a good deal of the old material describes a model that is no longer the recommended one.&lt;/p>
&lt;p>The first is that &lt;strong>resources are now namespaced by default&lt;/strong>. Both composite and managed resources, which used to be cluster-scoped, now live in a namespace, which enables fine-grained access control and natural multi-tenancy, exactly what you need in a sovereign platform with several teams.&lt;/p>
&lt;p>The second is that &lt;strong>a composition can include any Kubernetes resource&lt;/strong>, not only Crossplane resources. Previously a composition was limited to Crossplane&amp;rsquo;s own managed resources; now it can include Deployments, Services or third-party CRDs, so you can compose abstractions mixing application and infrastructure in a single object. This change is what makes the inference pattern you will see viable.&lt;/p>
&lt;p>The third is that &lt;strong>Claims disappear&lt;/strong>. In the v1 model there was a duality between the composite resource, cluster-scoped, and the Claim, its namespaced face which the developer consumed. Now that the composite resource is itself namespaced, that duality is redundant: the developer consumes the composite resource with no intermediary. If you come across documentation talking about Claims, it belongs to the earlier model.&lt;/p>
&lt;p>The fourth is a new type, &lt;code>Operations&lt;/code>, for one-off or scheduled operational tasks that run a pipeline of functions to completion, like a Kubernetes Job but inside the Crossplane model.&lt;/p>
&lt;p>There is one removal that deserves its own paragraph, because it is the most surprising: in v2 native patch-and-transform was withdrawn, the classic patching mechanism of compositions. It was not relegated, it was removed. Everything now goes through composition functions, and I get to that in a moment. Migration from v1 to v2 is mostly non-breaking and the v1 APIs still work, so adopting v2 is optional, but the new model is the one you have to learn.&lt;/p>
&lt;h2 id="the-model-piece-by-piece">The model, piece by piece&lt;/h2>
&lt;p>Crossplane has dense jargon and I will take it apart calmly, because once the pieces are placed the whole thing is coherent.&lt;/p>
&lt;p>&lt;strong>A provider&lt;/strong> is a package that installs controllers and resource definitions to talk to an external API, be it AWS, GCP, a remote Kubernetes cluster, Helm or Terraform. It is installed declaratively, by applying a &lt;code>Provider&lt;/code> object. The providers for the big cloud providers are generated with Upjet, a framework that produces the code from the Terraform providers, which explains why there are so many of them and also why some are enormous.&lt;/p>
&lt;p>&lt;strong>A managed resource&lt;/strong> is the one-to-one representation, inside Kubernetes, of a resource from the external provider: a bucket, an instance, a database. It is the unit the controller reconciles. When you create a managed resource of type bucket, the provider&amp;rsquo;s controller creates the real bucket and then watches that it keeps existing with the configuration you declared.&lt;/p>
&lt;p>On those bricks you build the abstraction, which is where the real value is, with three concepts:&lt;/p>
&lt;p>The &lt;strong>CompositeResourceDefinition&lt;/strong>, or XRD, defines the schema of your own API: which fields the user can ask for. It is what turns &amp;ldquo;a database&amp;rdquo; into a resource type with its parameters.&lt;/p>
&lt;p>The &lt;strong>Composition&lt;/strong> is the template and the logic that, given one of those resources, materialises the N managed resources needed underneath. The documentation says it bluntly: a composition is a pipeline of composition functions.&lt;/p>
&lt;p>The &lt;strong>Composite Resource&lt;/strong>, or XR, is the concrete instance: the object the developer creates with &lt;code>kubectl apply&lt;/code>, with the few fields the XRD exposes.&lt;/p>
&lt;p>The complete pattern is this: your platform operator publishes the abstraction (the XRD plus the Composition, which say &amp;ldquo;this is what a database means here&amp;rdquo;), and the developer only applies a composite resource with four fields. How it is built underneath stops mattering to them, just as in the cloud you do not care how AWS assembles your managed database.&lt;/p>
&lt;h2 id="composition-functions-the-real-logic">Composition functions: the real logic&lt;/h2>
&lt;p>The composition mechanism in v2, and the only one, is &lt;strong>composition functions&lt;/strong>. A composition is a pipeline of functions that Crossplane invokes over gRPC; each function receives the observed state, the desired state, its input and a shared context, and chains its output to the next one. The YAML in the examples is illustrative, but underneath it is not processed as YAML: it is executed as functions.&lt;/p>
&lt;p>The available functions cover several styles. You have &lt;code>function-patch-and-transform&lt;/code>, which reimplements the old model now as just another function. There is &lt;code>function-go-templating&lt;/code> for Helm-style templates, &lt;code>function-kcl&lt;/code> for the KCL language, &lt;code>function-python&lt;/code> for logic in Python and &lt;code>function-cue&lt;/code> for CUE.&lt;/p>
&lt;p>The reason for the change is what you want to understand. Classic patch-and-transform had no loops or conditionals, so an operation as simple as transforming an array of strings turned into dozens of lines of patching, and the community went as far as calling it outright bad. Functions give you full programming logic (loops, conditionals, external calls) in a real language. The honest flip side is that the complexity does not disappear: it shifts from &amp;ldquo;writing verbose patches&amp;rdquo; to &amp;ldquo;writing and operating functions&amp;rdquo;, which is a different kind of work, not less work.&lt;/p>
&lt;h2 id="crossplane-against-terraform-opentofu-and-pulumi">Crossplane against Terraform, OpenTofu and Pulumi&lt;/h2>
&lt;p>This is the comparison everybody makes, and I will make it properly because the difference is one of category. According to Pulumi&amp;rsquo;s own documentation, which compares honestly, Crossplane is a declarative control plane with continuous reconciliation inside Kubernetes, while Terraform and Pulumi are infrastructure as code executed one-off, client-side and on demand.&lt;/p>
&lt;p>The practical consequences line up along a few axes. Faced with configuration drift, Crossplane corrects it continuously and automatically, while Terraform and Pulumi only detect it when you run a refresh. Crossplane needs a cluster to run on, because it is controllers; Terraform and Pulumi do not. Crossplane is GitOps-native, applied with kubectl and fitting with &lt;a href="https://blog.lo0.es/en/posts/gitops-inference-stack-flux/">Flux&lt;/a> without an adapter; Pulumi needs its Kubernetes operator for the same thing. And as for language, Crossplane uses YAML and CRDs with logic in the functions, while Pulumi uses general-purpose programming languages.&lt;/p>
&lt;p>The point most often forgotten is that &lt;strong>you do not have to choose one or the other&lt;/strong>. There is a Terraform provider (and an OpenTofu one, recommended for new versions because the Terraform one was frozen at the last freely licensed version) that runs HCL &lt;em>inside&lt;/em> Crossplane, wrapping your existing modules as a managed resource. You can reuse your whole investment in Terraform and still gain continuous reconciliation for whatever needs it. If you already have Terraform in production, this hybrid pattern is usually your sensible route, not the rewrite.&lt;/p>
&lt;p>On comparative adoption you have to be sober: there is no official, reliable &amp;ldquo;Crossplane against Terraform&amp;rdquo; figure, and the most repeated documented criticism is that Crossplane lacks the mature module ecosystem Terraform has been accumulating for years. That is a real qualitative difference, not a detail.&lt;/p>
&lt;h2 id="the-awkward-question-is-it-an-aws-style-cli">The awkward question: is it an AWS-style CLI?&lt;/h2>
&lt;p>Since on this blog I have been pulling on the thread of CLI provisioning, I will answer directly: &lt;strong>no&lt;/strong>. Crossplane does not offer you an imperative &lt;code>aws s3 create-bucket&lt;/code> style experience. It is declarative first. You provision with a &lt;code>kubectl apply&lt;/code> of a resource, and the &lt;code>crossplane&lt;/code> binary is there to build, package, test and debug, not to &amp;ldquo;create a bucket&amp;rdquo; in one go.&lt;/p>
&lt;p>The subcommands make that clear. &lt;code>crossplane render&lt;/code> renders a composition and its functions locally, so you can test without a cluster. The &lt;code>crossplane xpkg build&lt;/code>, &lt;code>push&lt;/code>, &lt;code>install&lt;/code> family manages the OCI packages of providers, configurations and functions. And &lt;code>crossplane beta validate&lt;/code>, &lt;code>trace&lt;/code> and &lt;code>top&lt;/code> validate schemas, draw the resource tree and show consumption inside the cluster. It is the toolbox of whoever builds the platform, not of whoever consumes it day to day. Upbound&amp;rsquo;s &lt;code>up&lt;/code> CLI adds more, but oriented towards its commercial product.&lt;/p>
&lt;p>The conclusion matters for setting expectations: the imperative AWS-console-style experience, if you want it, gets built &lt;em>on top of&lt;/em> Crossplane, with Backstage, a portal or your own wrapper. Crossplane gives you the declarative API; the friendly face you put on separately. If what you are after is an imperative provisioning CLI, you fit better with the model of &lt;a href="https://blog.lo0.es/en/posts/kubevela-score-platform-cli-without-teaching-kubernetes/">KubeVela and Score&lt;/a>, which is precisely the subject of the next article.&lt;/p>
&lt;h2 id="on-premise-providers-provisioning-without-touching-aws">On-premise providers: provisioning without touching AWS&lt;/h2>
&lt;p>For a sovereign platform your question is what you can provision without setting foot in a public cloud, and the answer is more than it seems, with uneven maturity.&lt;/p>
&lt;p>The most versatile is &lt;code>provider-kubernetes&lt;/code>, which manages any Kubernetes object in remote clusters through an &lt;code>Object&lt;/code> resource. It is the basis of the multi-cluster on-premise pattern: from a central cluster you materialise resources in your secondary RKE2s. Alongside it, &lt;code>provider-helm&lt;/code> installs and manages Helm charts declaratively through a &lt;code>Release&lt;/code> resource, letting you deploy things like vLLM or KServe as part of a composition.&lt;/p>
&lt;p>For the identity and storage part you have concrete pieces: &lt;code>provider-keycloak&lt;/code> configures Keycloak realms, clients and roles declaratively, useful for the sovereign IAM we already set up in &lt;a href="https://blog.lo0.es/en/posts/mcp-grows-up-authentication-keycloak/">the article on authentication with Keycloak&lt;/a>; and &lt;code>provider-minio&lt;/code> manages buckets and policies of on-premise S3-compatible storage. For vSphere you do not have a first-class native provider, but you cover it by generating one from the Terraform provider, the usual pattern for VMware infrastructure.&lt;/p>
&lt;p>An honest warning: for KubeVirt, Harvester or CloudNativePG there are no mature, first-class native providers; in practice you manage them by applying their CRDs through &lt;code>provider-kubernetes&lt;/code> or by wrapping their Terraform. It works, but it is not the polished experience of a dedicated provider. With the combination of provider-kubernetes, provider-helm, provider-minio and provider-keycloak, plus provider-terraform for whatever is missing, you can assemble a one hundred per cent on-premise self-service catalogue, with the caveat that maturity varies piece by piece.&lt;/p>
&lt;h2 id="the-fit-with-gpu-inference-from-pattern-to-modelplane">The fit with GPU inference: from pattern to Modelplane&lt;/h2>
&lt;p>This is the section that in the first version of this text carried the most caution, because there was no public case of Crossplane orchestrating GPU and inference and it all remained a design pattern. Halfway through 2026 that changed, and the case was published by the very team that makes Crossplane.&lt;/p>
&lt;p>&lt;strong>Modelplane&lt;/strong> is an open source control plane for AI inference, Apache-licensed and with a first version 0.1 from June 2026, and the interesting thing is how it is built: entirely on Crossplane compositions and composition functions in Python, with no controllers of its own. It composes vLLM directly, with the &lt;code>vllm/vllm-openai&lt;/code> image, without going through KServe, and its self-service API for the ML teams is a &lt;code>ModelDeployment&lt;/code> resource where you declare the model, the engine, the topology and the hardware. Underneath, a &lt;code>ModelReplica&lt;/code> resource represents each replica, and the platform team declares its GPU pools with &lt;code>InferenceCluster&lt;/code> and &lt;code>InferenceClass&lt;/code> resources, using an attribute model and CEL expressions taken from Kubernetes dynamic resource allocation. Its most advanced piece is a two-level fleet scheduler written as a pure function, which places replicas across the whole infrastructure and delegates to each cluster&amp;rsquo;s scheduler. It is proof that the pattern works, with the warning that it is a version 0.1 under open development, not something to put into production blindly, and that adopting it ties your platform to its resource types.&lt;/p>
&lt;p>The other recent enabler is in KServe, which introduced a type dedicated to large models, &lt;code>LLMInferenceService&lt;/code>, still in alpha and built on the llm-d project, with prefill and decode separation and multi-node execution, and which runs vLLM as its runtime. It is different from the classic &lt;code>InferenceService&lt;/code>, reserved for predictive ML. That new type is your ideal brick if you prefer composing KServe instead of bare vLLM.&lt;/p>
&lt;p>With that, your own golden path stops being a hypothesis. You define an XRD, for example &lt;code>InferenceEndpoint&lt;/code>, with fields such as the model, the number and type of GPU and the replicas. Its composition, taking advantage of the fact that in v2 it can include any Kubernetes resource, materialises in a single pipeline the team&amp;rsquo;s namespace, the quota for &lt;code>nvidia.com/gpu&lt;/code> and a KServe &lt;code>LLMInferenceService&lt;/code> pointing at the model with vLLM runtime, and the developer only applies the &lt;code>InferenceEndpoint&lt;/code> with four fields, knowing nothing about KServe or the quotas underneath. KServe supports vLLM and GPU natively, as we saw in &lt;a href="https://blog.lo0.es/en/posts/kserve-open-inference-protocol-control-plane/">the article on KServe&lt;/a>.&lt;/p>
&lt;p>The honest reading is that you are at the frontier, not on well-trodden ground. Outside Modelplane, made by Upbound, there is still no body of independent cases assembling Crossplane with vLLM, and the KServe type is in alpha. It is a coherent architecture, with a real reference backing it and plenty of room for you to be among the first to document it on your own iron.&lt;/p>
&lt;h2 id="what-it-costs-you-to-operate-your-own-control-plane">What it costs you to operate your own control plane&lt;/h2>
&lt;p>No tool that gives you back control is free, and Crossplane has well-documented costs you need to budget for beforehand, not discover afterwards.&lt;/p>
&lt;p>&lt;strong>The learning curve is steep&lt;/strong>, acknowledged even in the coverage of its graduation. Adopters report debugging difficulties when a composition or a provider misbehaves, and they miss Terraform&amp;rsquo;s module ecosystem.&lt;/p>
&lt;p>&lt;strong>Compositions tend towards complexity.&lt;/strong> There are practitioners describing definitions spanning thousands of lines with poor navigation, and the absence of an integrated testing framework. Functions improve this, but as I said, they shift the complexity rather than remove it.&lt;/p>
&lt;p>&lt;strong>There is no &lt;code>plan&lt;/code>.&lt;/strong> Unlike Terraform&amp;rsquo;s &lt;code>plan&lt;/code> and &lt;code>apply&lt;/code> cycle, Crossplane applies changes immediately. You lose the safety net of reviewing what is going to happen before it happens, with the risk of modifications you did not want.&lt;/p>
&lt;p>&lt;strong>Operating the control plane itself costs.&lt;/strong> Big providers create hundreds of resource definitions (the GCP one reached 347), with a risk of API server unavailability and of leaving your cluster hanging. State lives in etcd, which complicates disaster recovery and migrating control between clusters. And there is one specific danger: accidental deletion of cloud resources when upgrading XRDs, providers or compositions, because of the ownership Crossplane keeps over what it created.&lt;/p>
&lt;p>Some have decided not to adopt it. The Masterpoint team ruled it out for the lack of data sources and &lt;code>ignore_changes&lt;/code>, for incomplete coverage of AWS services, and for the cost of abandoning their accumulated Terraform experience. It is a legitimate decision, and quoting it to you is part of an honest article.&lt;/p>
&lt;h2 id="how-it-lives-alongside-what-you-already-have">How it lives alongside what you already have&lt;/h2>
&lt;p>If you already run a platform, your question is not Crossplane yes or no, but Crossplane with what. Three relationships matter to you.&lt;/p>
&lt;p>With &lt;strong>Backstage&lt;/strong> they are complementary, not rivals. Backstage is the portal and the templates that generate manifests or pull requests, day zero; Crossplane provisions and reconciles the real state, day two. The usual pattern, which some call the golden triangle, is Backstage as the interface, Crossplane as the control plane and Argo CD or Flux as GitOps. Each one does a layer.&lt;/p>
&lt;p>With &lt;strong>Flux&lt;/strong>, which you probably already use, the relationship is additive. Flux reconciles manifests towards the clusters, but it does not give you a platform abstraction API: it does not know what &amp;ldquo;a database&amp;rdquo; is, it only applies the YAML you hand it. Crossplane adds exactly that abstraction layer, and the two coexist, with Flux deploying the composite resources and Crossplane materialising them. If you already have Flux, Crossplane&amp;rsquo;s value is precisely that self-service API layer, in exchange for the cost of operating the control plane I described above.&lt;/p>
&lt;p>With &lt;strong>KubeVela and Score&lt;/strong>, the subject of the next article, the boundary is one of layer: Crossplane answers &amp;ldquo;give me a Postgres&amp;rdquo;, from the infrastructure side; KubeVela and Score answer &amp;ldquo;deploy my application&amp;rdquo;, from the workload side. They do not compete, they stack.&lt;/p>
&lt;h2 id="for-your-sovereign-inference-factory">For your sovereign inference factory&lt;/h2>
&lt;p>The practical decision boils down to this. Crossplane makes sense when you want to expose a self-service API of your own over your on-premise infrastructure and you are willing to operate a control plane to get it. If your platform is used by two teams who know each other and you provision with a tidy Flux repository, Crossplane is cost without return for now. The trigger is scale: when manual provisioning turns into a ticket bottleneck, the declarative API starts paying back its cost.&lt;/p>
&lt;p>Your sensible entry route is not rewriting everything. It is starting with a provider that already serves you (provider-kubernetes and provider-helm cover a lot), wrapping your existing Terraform with the corresponding provider instead of throwing it away, and modelling a single abstraction of clear value, probably the &lt;code>InferenceEndpoint&lt;/code> we discussed, as a golden path. From there, you grow on demand. And always with a clear account of having traded the safety net of &lt;code>plan&lt;/code> and a handful of scripts for a thermostat you have to keep switched on.&lt;/p>
&lt;p>The next article moves up a layer, from the infrastructure resource to the application workload, with &lt;a href="https://blog.lo0.es/en/posts/kubevela-score-platform-cli-without-teaching-kubernetes/">KubeVela and Score&lt;/a>, which is where the friendly CLI that Crossplane deliberately does not give you finally appears.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/public-cloud-to-private-what-you-lose-which-projects-fill-gap/">From the public cloud to the private one: the map&lt;/a> — where this control plane fits in the whole.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/kubevela-score-platform-cli-without-teaching-kubernetes/">KubeVela and Score: self-service by CLI&lt;/a> — the application layer, the CLI Crossplane does not offer.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/backstage-self-service-portal-llm-platform/">Backstage as a self-service portal&lt;/a> — the interface built on top of the control plane.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/gitops-inference-stack-flux/">GitOps for the inference stack with Flux&lt;/a> — the engine Crossplane lives with, not competes with.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/kserve-open-inference-protocol-control-plane/">KServe and the open inference protocol&lt;/a> — what the inference golden path would materialise underneath.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>CNCF, &lt;em>Announces Graduation of Crossplane&lt;/em> (6-nov-2025) — &lt;a href="https://www.cncf.io/announcements/2025/11/06/cloud-native-computing-foundation-announces-graduation-of-crossplane/">https://www.cncf.io/announcements/2025/11/06/cloud-native-computing-foundation-announces-graduation-of-crossplane/&lt;/a>&lt;/li>
&lt;li>Crossplane Blog, &lt;em>Announcing Crossplane&amp;rsquo;s CNCF Graduation&lt;/em> — &lt;a href="https://blog.crossplane.io/crossplane-cncf-graduation/">https://blog.crossplane.io/crossplane-cncf-graduation/&lt;/a>&lt;/li>
&lt;li>Crossplane Blog, &lt;em>Announcing Crossplane 2.0&lt;/em> — &lt;a href="https://blog.crossplane.io/announcing-crossplane-2-0/">https://blog.crossplane.io/announcing-crossplane-2-0/&lt;/a>&lt;/li>
&lt;li>Crossplane Docs, &lt;em>What&amp;rsquo;s New in v2&lt;/em> — &lt;a href="https://docs.crossplane.io/latest/whats-new/">https://docs.crossplane.io/latest/whats-new/&lt;/a>&lt;/li>
&lt;li>Crossplane Docs, &lt;em>Compositions&lt;/em> — &lt;a href="https://docs.crossplane.io/latest/composition/compositions/">https://docs.crossplane.io/latest/composition/compositions/&lt;/a>&lt;/li>
&lt;li>Crossplane Docs, &lt;em>Release Cycle&lt;/em> — &lt;a href="https://docs.crossplane.io/latest/learn/release-cycle/">https://docs.crossplane.io/latest/learn/release-cycle/&lt;/a>&lt;/li>
&lt;li>Crossplane Docs, &lt;em>Crossplane CLI&lt;/em> — &lt;a href="https://docs.crossplane.io/latest/cli/">https://docs.crossplane.io/latest/cli/&lt;/a>&lt;/li>
&lt;li>Upbound, &lt;em>Crossplane Graduates From CNCF, Upbound Redefines AI-Native Infrastructure&lt;/em> — &lt;a href="https://www.upbound.io/blog/crossplane-graduates-from-cncf-upbound-redefines-ai-native-infrastructure">https://www.upbound.io/blog/crossplane-graduates-from-cncf-upbound-redefines-ai-native-infrastructure&lt;/a>&lt;/li>
&lt;li>InfoQ, &lt;em>Crossplane Reaches Production Maturity by Graduating CNCF&lt;/em> — &lt;a href="https://www.infoq.com/news/2025/11/crossplane-grad/">https://www.infoq.com/news/2025/11/crossplane-grad/&lt;/a>&lt;/li>
&lt;li>Pulumi Docs, &lt;em>Pulumi vs. Crossplane&lt;/em> — &lt;a href="https://www.pulumi.com/docs/iac/comparisons/crossplane/">https://www.pulumi.com/docs/iac/comparisons/crossplane/&lt;/a>&lt;/li>
&lt;li>Upbound Marketplace, &lt;em>provider-terraform&lt;/em> — &lt;a href="https://marketplace.upbound.io/providers/upbound/provider-terraform/latest">https://marketplace.upbound.io/providers/upbound/provider-terraform/latest&lt;/a>&lt;/li>
&lt;li>GitHub, &lt;em>provider-kubernetes&lt;/em> — &lt;a href="https://github.com/crossplane-contrib/provider-kubernetes">https://github.com/crossplane-contrib/provider-kubernetes&lt;/a>&lt;/li>
&lt;li>GitHub, &lt;em>provider-helm&lt;/em> — &lt;a href="https://github.com/crossplane-contrib/provider-helm">https://github.com/crossplane-contrib/provider-helm&lt;/a>&lt;/li>
&lt;li>GitHub, &lt;em>provider-keycloak&lt;/em> — &lt;a href="https://github.com/crossplane-contrib/provider-keycloak">https://github.com/crossplane-contrib/provider-keycloak&lt;/a>&lt;/li>
&lt;li>VSHN, &lt;em>provider-minio&lt;/em> — &lt;a href="https://github.com/vshn/provider-minio">https://github.com/vshn/provider-minio&lt;/a>&lt;/li>
&lt;li>Masterpoint, &lt;em>Crossplane: Why it Didn&amp;rsquo;t Work for Us&lt;/em> — &lt;a href="https://masterpoint.io/blog/passing-on-crossplane/">https://masterpoint.io/blog/passing-on-crossplane/&lt;/a>&lt;/li>
&lt;li>CECG, &lt;em>Crossplane: the good, the bad and the ugly&lt;/em> — &lt;a href="https://www.cecg.io/blog/crossplane-the-good-the-bad-the-ugly">https://www.cecg.io/blog/crossplane-the-good-the-bad-the-ugly&lt;/a>&lt;/li>
&lt;li>Taloflow, &lt;em>Backstage vs Crossplane for Platform Engineering&lt;/em> — &lt;a href="https://www.taloflow.ai/guides/comparisons/backstage-vs-crossplane-platform-engineering">https://www.taloflow.ai/guides/comparisons/backstage-vs-crossplane-platform-engineering&lt;/a>&lt;/li>
&lt;li>Crossplane Blog, &lt;em>Building Modelplane on Crossplane&lt;/em> — &lt;a href="https://blog.crossplane.io/building-modelplane/">https://blog.crossplane.io/building-modelplane/&lt;/a>&lt;/li>
&lt;li>Modelplane — &lt;a href="https://modelplane.ai">https://modelplane.ai&lt;/a> · &lt;a href="https://github.com/modelplaneai/modelplane">https://github.com/modelplaneai/modelplane&lt;/a>&lt;/li>
&lt;li>KServe Docs, &lt;em>Understanding LLMInferenceService&lt;/em> — &lt;a href="https://kserve.github.io/website/docs/model-serving/generative-inference/llmisvc/llmisvc-overview">https://kserve.github.io/website/docs/model-serving/generative-inference/llmisvc/llmisvc-overview&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>From public cloud to private: what you really lose and which projects fill the gap</title><link>https://blog.lo0.es/en/posts/public-cloud-to-private-what-you-lose-which-projects-fill-gap/</link><pubDate>Mon, 31 Aug 2026 09:15:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/public-cloud-to-private-what-you-lose-which-projects-fill-gap/</guid><description>&lt;blockquote>
&lt;p>With this one I open a run about the biggest gap a sovereign platform leaves you: how to give your teams the self-service experience of a public cloud, without the public cloud. Here you have the map of the problem; the next two go down into the detail of the tools, &lt;a href="https://blog.lo0.es/en/posts/crossplane-control-plane-private-cloud-api/">Crossplane&lt;/a> as the infrastructure control plane and &lt;a href="https://blog.lo0.es/en/posts/kubevela-score-platform-cli-without-teaching-kubernetes/">KubeVela with Score&lt;/a> as application-centric self-service.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Leaving the public cloud has a solid business case, and I am not making it up. 37signals declared savings of close to two million dollars in 2024 after repatriating, the classic a16z analysis puts cloud spend at around half of the cost of revenue of a software company, and the Barclays CIO survey of 2024 recorded the highest repatriation intent in its history. If your workload is a predictable, sustained base (which is exactly the continuous LLM inference this blog is about) the numbers work out, and in Europe the sovereignty reasons pile on top: the Data Act, the tension with the US CLOUD Act, and that dependence on three hyperscalers that control around 70 % of the European cloud market.&lt;/p>
&lt;p>But the problem is not the compute. Your on-premise GPU rack serves tokens just as well as a rented one. What the cloud was selling you and you now have to rebuild is something else: the elasticity of asking for a hundred machines at ten and handing them back at eleven, the catalogue of hundreds of managed services with an on-call team behind them, the unified provisioning API with fine-grained permissions, the multi-region network, the bill that arrives itemised, and that model where operating all of it was somebody else&amp;rsquo;s problem. All of that you rebuild piece by piece with open source projects, and the rebuild has a cost that the savings figures do not usually tell you about. What follows is the map of what you lose, which project fills each gap, and where a gap remains that nothing will close completely.&lt;/p>
&lt;h2 id="the-analogy-leaving-the-hotel-to-set-up-your-own-house">The analogy: leaving the hotel to set up your own house&lt;/h2>
&lt;p>Think of it like this: living in the public cloud is living in a hotel. You pay a lot per night, but you do not change light bulbs, reception is open at three in the morning, if you need another room you ask for it and it appears, and when you leave you take nothing with you to maintain. Setting up your own house works out much cheaper per square metre if you are going to stay for years, and on top of that you decide who comes in and where your things are kept. The problem arrives the day a pipe bursts: then you do not call reception, because reception is you.&lt;/p>
&lt;p>Repatriation is that move. The savings are real and so is data ownership, but what in the hotel came included in the price of the night is now your to-do list: the plumbing is your network, the boiler is your storage, the concierge who got you anything is the catalogue of managed services you no longer have, and the itemised bill they slid under your door is a cost model you have to build, because electricity and depreciation do not arrive broken down per tenant. The house pays off, but only if you count what it costs to furnish it and fix the pipes, not just the price per square metre against the hotel.&lt;/p>
&lt;p>The rest of the article walks through that to-do list and tells you, for each item, which open source tool does the job the hotel used to do for you.&lt;/p>
&lt;h2 id="why-this-conversation-belongs-to-2026-not-to-always">Why this conversation belongs to 2026, not to always&lt;/h2>
&lt;p>Repatriating is nothing new, but in 2026 three things come together that make it urgent if you run a European AI platform.&lt;/p>
&lt;p>The first is economic, and Andreessen Horowitz put it on the table in 2021 with &lt;em>The Cost of Cloud, a Trillion Dollar Paradox&lt;/em>. Their thesis, which you should read knowing that a16z has interests in the ecosystem it analyses, is that cloud spend averages around 50 % of the cost of revenue in a software company, and that recovering half of that spend would free up margins the market penalises. The cleanest case, and one you can verify, is 37signals: DHH published that their cloud bill dropped from 3.2 to 1.3 million dollars a year after repatriating, with an initial investment of about 700,000 dollars in Dell servers. Now the honest caveat, which the specialist press itself underlines: those savings figures did not include the extra operations staff, nor the energy, nor the cooling. I come back to it in the hidden costs section.&lt;/p>
&lt;p>The second is declared intent. The Barclays CIO survey from the first half of 2024 recorded that 83 % planned to repatriate some workload to private cloud or on-premise in the following twelve months, the highest reading in the series. But read that 83 % precisely: it is the proportion of managers who plan to move &lt;em>some&lt;/em> workload, not the percentage of workloads or of spend that moves. Flexera, in its 2025 report, gives you the more grounded figure: large companies have repatriated around 21 % of the workloads they had in the public cloud, and cost saving is priority number one for almost 60 %.&lt;/p>
&lt;p>The third is regulatory and European, and it is the one that turns repatriation into something more than a FinOps decision. The European Union Data Act came into force in January 2024 and has applied since September 2025; it mandates portability between providers and, from January 2027, bans the exit fees that today chain you to your provider. And underneath it beats a deeper legal tension: the US CLOUD Act allows United States authorities to demand data from an American provider even if it is stored in Frankfurt, which is exactly the argument for why a European region of a hyperscaler does not give you sovereignty. With AWS, Microsoft and Google controlling around 70 % of the European cloud market, the conversation about sovereign cloud stopped being theoretical. I developed this already in &lt;a href="https://blog.lo0.es/en/posts/sovereign-on-premise-vs-hyperscalers-data/">sovereign on-premise versus hyperscalers&lt;/a>.&lt;/p>
&lt;h2 id="what-you-lose-when-you-leave-the-cloud-specifically">What you lose when you leave the cloud, specifically&lt;/h2>
&lt;p>Let us get to the core, ordered by what is going to hurt you most. And one thing before starting: none of these gaps is about compute. They are all about what the cloud put around the compute.&lt;/p>
&lt;p>&lt;strong>Hardware elasticity disappears.&lt;/strong> On-premise, the GPU you did not buy does not exist. The cloud bills you by the hour precisely because it takes on the risk of idle capacity: it lets you ask for a peak and hand it back. With your own iron, you buy the peak up front and pay for it whether it is in use or not, so your profitability depends on high sustained utilisation. Below a certain usage threshold, the cloud beats you again. This is the irreducible gap, the one no tool fills, and if you repatriate a workload with extreme peaks you are making the wrong decision.&lt;/p>
&lt;p>&lt;strong>Managed services become your operations.&lt;/strong> Every box you took for granted (the database, the queue, the object storage, the load balancer, DNS, the secrets manager) stops being a checkbox you tick and becomes a system you install, update and watch yourself. The 37signals case teaches it well: getting out of S3 object storage was the last and hardest part, with petabytes of data and a contract they dragged along for years. Your managed Postgres turns into operating CloudNativePG; your S3 turns into operating MinIO; and so on with every service.&lt;/p>
&lt;p>&lt;strong>You have to build the provisioning control plane yourself.&lt;/strong> AWS gives you a unified API where you ask for a resource with fine-grained permissions and it appears. On-premise that does not come for free: you have to build the control plane that translates &amp;ldquo;I want a database&amp;rdquo; into real resources. It is exactly the gap that &lt;a href="https://blog.lo0.es/en/posts/crossplane-control-plane-private-cloud-api/">Crossplane&lt;/a> fills, and that is why it has its own article.&lt;/p>
&lt;p>&lt;strong>You replace the managed network piece by piece.&lt;/strong> The VPC, the security groups, NAT, the load balancer: every network abstraction in the cloud has its on-premise equivalent (Cilium for network policies on eBPF, MetalLB for the load balancer without ELB, Gateway API for L7 ingress), but they are pieces you assemble, not a service you switch on. A good part of this we already saw in the networking vertical with &lt;a href="https://blog.lo0.es/en/posts/network-numa-cilium-ebpf-dranet-fourth-leg-pinning/">Cilium and eBPF&lt;/a>.&lt;/p>
&lt;p>&lt;strong>Billing stops existing as such.&lt;/strong> In the cloud the cost reaches you broken down by service and by tag; you have a Cost Explorer that answers &amp;ldquo;how much does this team cost me&amp;rdquo;. On-premise there is no natural bill: there is hardware depreciation, an electricity bill and some payroll, and splitting that per workload is a model you have to build. OpenCost helps you with the split per workload inside the cluster, but the total physical cost (depreciation plus energy plus staff) you model separately, as we saw in &lt;a href="https://blog.lo0.es/en/posts/opencost-gpu-cost-allocation-kubernetes/">OpenCost and cost allocation&lt;/a>.&lt;/p>
&lt;p>&lt;strong>You design multi-zone availability yourself.&lt;/strong> The three availability zones the cloud gives you for free turn into several sites, several racks and several utility feeds that you have to plan. 37signals solved it with two datacenters and replication between them. It is neither free nor automatic.&lt;/p>
&lt;p>&lt;strong>And above all, the on-call is yours.&lt;/strong> The cloud was not selling you only compute: it was selling you operations included. When something falls over at three in the morning, in the cloud there is an on-call team at the provider; with open source, that team is you. This is the cost that savings spreadsheets almost never include, and it is the one that decides whether your repatriation was a good idea or a hidden bill.&lt;/p>
&lt;h2 id="the-map-of-projects-that-fill-each-gap">The map of projects that fill each gap&lt;/h2>
&lt;p>With the gaps ordered, the catalogue of substitutes reads itself. I group it by layer, from the lowest to the closest to the developer.&lt;/p>
&lt;p>In the &lt;strong>infrastructure substrate&lt;/strong>, what replicates EC2 virtual machines, virtual networking and disks, you have OpenStack as the classic standard for a complete private cloud, SUSE&amp;rsquo;s Harvester as bare-metal hyperconvergence on KubeVirt managed from Kubernetes, and emerging projects such as Spinifex that rebuild an AWS-compatible API on your own iron. It is the heaviest layer and the one I touch least in this blog, because I assume your cluster already exists.&lt;/p>
&lt;p>In the &lt;strong>provisioning control plane&lt;/strong>, what replicates the AWS IAM and resource API, there is Crossplane, graduated in the CNCF in November 2025, which brings the Kubernetes declarative API model to infrastructure. Alongside it, Cluster API for declarative cluster provisioning. And so you can see how far this reaches in our own field, the Crossplane team itself published Modelplane in mid-2026, an inference control plane built on compositions that serves models with vLLM. This layer is developed by the &lt;a href="https://blog.lo0.es/en/posts/crossplane-control-plane-private-cloud-api/">Crossplane article&lt;/a>.&lt;/p>
&lt;p>In the &lt;strong>developer self-service experience&lt;/strong>, what replicates the AWS console and CLI, you have three different approaches you should not confuse. Backstage is the web portal, the catalogue and the templates, and it already has &lt;a href="https://blog.lo0.es/en/posts/backstage-self-service-portal-llm-platform/">its own article&lt;/a>. KubeVela brings self-service to the application model with a friendly CLI. Score defines a workload specification that is portable between environments. KubeVela and Score share the &lt;a href="https://blog.lo0.es/en/posts/kubevela-score-platform-cli-without-teaching-kubernetes/">third article&lt;/a> because they cover the same layer from angles that complement each other.&lt;/p>
&lt;p>In &lt;strong>on-premise managed services&lt;/strong>, CloudNativePG operates Postgres for you with high availability and backups to object storage, MinIO gives you S3-compatible storage, and OpenCost covers that cost allocation you lost with Cost Explorer.&lt;/p>
&lt;p>The way to read this map is that none of these pieces is surplus if you want the cloud experience: they are the boxes the cloud ticked for you and that you now tick one by one.&lt;/p>
&lt;h2 id="the-gap-you-will-not-close">The gap you will not close&lt;/h2>
&lt;p>I will tell you plainly, because it is the part the &amp;ldquo;leave the cloud&amp;rdquo; brochures skip. No combination of open source projects reproduces 100 % of the public cloud for you, and not for lack of maturity, but by design.&lt;/p>
&lt;p>What you will not replicate is, first, instant hardware elasticity: you still buy the peaks up front. Second, the depth and breadth of the managed services catalogue, hundreds of services with a service level agreement and an on-call team behind them, which no internal team matches. Third, the global multi-region network the cloud gives you almost for free. And fourth, that model of transferred operational responsibility: with open source, operations come back home.&lt;/p>
&lt;p>The correct reading is not that repatriating is a bad idea, but that you swap a predictable operating expense and a delegated responsibility for a cheaper capital investment and a responsibility of your own. For sustained base load it works out. For everything else, think about it.&lt;/p>
&lt;h2 id="when-not-to-repatriate">When NOT to repatriate&lt;/h2>
&lt;p>This section has to be here, because repatriation badly applied is expensive. There are three clear signs that the cloud is still your right answer.&lt;/p>
&lt;p>The first is a workload with extreme peaks or very variable traffic. Most inference deployments operate between 40 and 65 % GPU utilisation, and below roughly 70 % sustained usage the cloud beats you on total cost. If your workload has long troughs, you are buying iron so it can sleep.&lt;/p>
&lt;p>The second is uncertainty or the start of a project. The break-even point in favour of buying only applies to you when utilisation is consistently high and, above all, measured and not projected. Buying GPUs against an optimistic forecast is the fastest way to end up with a warehouse of idle silicon. Sizing it properly is worth a whole article: &lt;a href="https://blog.lo0.es/en/posts/slo-to-number-of-gpus-sizing-justifying-inference-hardware/">sizing and justifying the GPU investment&lt;/a>.&lt;/p>
&lt;p>The third is the lack of an operations team. The total cost of ownership on-premise includes between half and one full-time engineer per GPU server. Without that team, your saving is illusory, because the on-call the cloud included is now covered by nobody. a16z itself frames its thesis as a hybrid approach, not as &amp;ldquo;leave the cloud&amp;rdquo; at all.&lt;/p>
&lt;h2 id="the-numbers-to-ground-it">The numbers, to ground it&lt;/h2>
&lt;p>So as not to leave you with the discussion in the abstract, here are the orders of magnitude, triangulating two sources with opposite biases, a cloud provider and a neutral FinOps tool. An H100 in the cloud runs you between 2.90 and almost 7 dollars per GPU-hour depending on provider; bought, an H100 card is around 31,000 dollars and an eight-way system between 250,000 and 320,000. At full utilisation, a rented H100 passes its purchase price in less than a year, and an eight-way system with second-hand hardware can pay for itself in about seven months. The three-year total cost of an eight-H100 system, counting staff, colocation, energy and cooling, is estimated at between 700,000 and 950,000 dollars, and the threshold where on-premise beats the hyperscalers is around 80 % sustained utilisation.&lt;/p>
&lt;p>The two sources disagree on the exact number, as you would expect, but they agree on the deciding factor: sustained utilisation, measured and not projected. The full working of this calculation is in &lt;a href="https://blog.lo0.es/en/posts/full-tco-on-premise-gpu-cluster-capex-gpu-hour-break-even/">the TCO of an on-premise GPU cluster&lt;/a>. Here the conclusion is enough for you: for continuous base load the numbers work out, and that is exactly the profile of an inference factory.&lt;/p>
&lt;h2 id="for-your-sovereign-inference-factory">For your sovereign inference factory&lt;/h2>
&lt;p>Everything above arranges itself into a practical decision. If you operate an LLM inference platform on your own iron, you are not deciding whether to repatriate (you already took that decision when you built the cluster), but how to rebuild the cloud experience your teams miss.&lt;/p>
&lt;p>The sensible sequence starts with the substrate, which you probably already have in the form of RKE2 and storage. It goes on to the provisioning control plane, where Crossplane turns &amp;ldquo;open a ticket and wait&amp;rdquo; into a declarative API. It continues with the developer experience, where you choose between the Backstage portal, the KubeVela application model or the portable Score specification, depending on whether your people prefer a web or a CLI. And do not forget the on-premise managed services or the cost allocation, because without a bill your platform loses the discipline the cloud imposed by default.&lt;/p>
&lt;p>The next two articles go down into the two layers least covered in this blog: &lt;a href="https://blog.lo0.es/en/posts/crossplane-control-plane-private-cloud-api/">Crossplane&lt;/a> for the infrastructure control plane, and &lt;a href="https://blog.lo0.es/en/posts/kubevela-score-platform-cli-without-teaching-kubernetes/">KubeVela with Score&lt;/a> for CLI self-service centred on the application. With them, the map of this layer is covered, and your own house starts to look like the hotel you left, with the difference that the data stays inside.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/sovereign-on-premise-vs-hyperscalers-data/">Sovereign on-premise versus hyperscalers, with data&lt;/a> — the business and sovereignty case, in detail.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/full-tco-on-premise-gpu-cluster-capex-gpu-hour-break-even/">The TCO of an on-premise GPU cluster&lt;/a> — the full break-even calculation.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/backstage-self-service-portal-llm-platform/">Backstage as a self-service portal&lt;/a> — the portal layer, the third route to self-service.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/five-maturity-levels-platform-under-llm/">The five maturity levels of the platform&lt;/a> — at what point each of these pieces starts to pay off for you.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/blog-map-from-utility-feed-to-token-served/">The map of the blog by layers&lt;/a> — where all of this fits in the complete stack.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>Andreessen Horowitz (S. Wang, M. Casado), &lt;em>The Cost of Cloud, a Trillion Dollar Paradox&lt;/em> — &lt;a href="https://a16z.com/the-cost-of-cloud-a-trillion-dollar-paradox/">https://a16z.com/the-cost-of-cloud-a-trillion-dollar-paradox/&lt;/a>&lt;/li>
&lt;li>Data Center Dynamics, &lt;em>37signals claims it saved almost 2M USD last year from cloud repatriation&lt;/em> — &lt;a href="https://www.datacenterdynamics.com/en/news/37signals-claims-it-saved-almost-2m-last-year-from-cloud-repatriation/">https://www.datacenterdynamics.com/en/news/37signals-claims-it-saved-almost-2m-last-year-from-cloud-repatriation/&lt;/a>&lt;/li>
&lt;li>The Register, &lt;em>Developer pockets 2M USD in savings from going cloud-free&lt;/em> — &lt;a href="https://www.theregister.com/2024/10/21/37signals_aws_savings/">https://www.theregister.com/2024/10/21/37signals_aws_savings/&lt;/a>&lt;/li>
&lt;li>Barclays, &lt;em>Technology: 1H24 CIO Survey&lt;/em> (PDF) — &lt;a href="https://8198920.fs1.hubspotusercontent-na1.net/hubfs/8198920/Barclays_Cio_Survey_2024-1.pdf">https://8198920.fs1.hubspotusercontent-na1.net/hubfs/8198920/Barclays_Cio_Survey_2024-1.pdf&lt;/a>&lt;/li>
&lt;li>Channelnomics, &lt;em>Breaking Down the 83% Public Cloud Repatriation Number&lt;/em> — &lt;a href="https://channelnomics.com/breaking-down-the-83-public-cloud-repatriation-number/">https://channelnomics.com/breaking-down-the-83-public-cloud-repatriation-number/&lt;/a>&lt;/li>
&lt;li>The New Stack, &lt;em>Updated Stats on Cloud Sustainability, Repatriation and Cost Optimization&lt;/em> (Flexera 2025) — &lt;a href="https://thenewstack.io/updated-stats-on-cloud-sustainability-repatriation-and-cost-optimization/">https://thenewstack.io/updated-stats-on-cloud-sustainability-repatriation-and-cost-optimization/&lt;/a>&lt;/li>
&lt;li>Comisión Europea, &lt;em>Data Act | Shaping Europe&amp;rsquo;s digital future&lt;/em> — &lt;a href="https://digital-strategy.ec.europa.eu/en/policies/data-act">https://digital-strategy.ec.europa.eu/en/policies/data-act&lt;/a>&lt;/li>
&lt;li>EU Data Act, &lt;em>Article 29 — Gradual withdrawal of switching charges&lt;/em> — &lt;a href="https://www.eu-data-act.com/Data_Act_Article_29.html">https://www.eu-data-act.com/Data_Act_Article_29.html&lt;/a>&lt;/li>
&lt;li>Kiteworks, &lt;em>How the EU Data Act and GDPR Conflict with U.S. CLOUD Act&lt;/em> — &lt;a href="https://www.kiteworks.com/gdpr-compliance/eu-data-act-gdpr-cloud-conflict/">https://www.kiteworks.com/gdpr-compliance/eu-data-act-gdpr-cloud-conflict/&lt;/a>&lt;/li>
&lt;li>Computerworld, &lt;em>EU takes first steps to reduce reliance on US hyperscalers&lt;/em> — &lt;a href="https://www.computerworld.com/article/4181816/eu-takes-first-steps-to-reduce-reliance-on-us-hyperscalers.html">https://www.computerworld.com/article/4181816/eu-takes-first-steps-to-reduce-reliance-on-us-hyperscalers.html&lt;/a>&lt;/li>
&lt;li>CNCF, &lt;em>Announces Graduation of Crossplane&lt;/em> (6-nov-2025) — &lt;a href="https://www.cncf.io/announcements/2025/11/06/cloud-native-computing-foundation-announces-graduation-of-crossplane/">https://www.cncf.io/announcements/2025/11/06/cloud-native-computing-foundation-announces-graduation-of-crossplane/&lt;/a>&lt;/li>
&lt;li>CNCF, &lt;em>Score accepted as a CNCF Sandbox Project&lt;/em> — &lt;a href="https://www.cncf.io/blog/2024/08/08/score-accepted-as-a-cncf-sandbox-project/">https://www.cncf.io/blog/2024/08/08/score-accepted-as-a-cncf-sandbox-project/&lt;/a>&lt;/li>
&lt;li>Harvester HCI (SUSE) — &lt;a href="https://harvesterhci.io/">https://harvesterhci.io/&lt;/a>&lt;/li>
&lt;li>CloudNativePG — &lt;a href="https://cloudnative-pg.io/">https://cloudnative-pg.io/&lt;/a>&lt;/li>
&lt;li>Crossplane Blog, &lt;em>Building Modelplane on Crossplane&lt;/em> — &lt;a href="https://blog.crossplane.io/building-modelplane/">https://blog.crossplane.io/building-modelplane/&lt;/a> · &lt;a href="https://modelplane.ai">https://modelplane.ai&lt;/a>&lt;/li>
&lt;li>Spheron, &lt;em>LLM Inference On-Premise vs GPU Cloud: 2026 Cost and Break-Even Analysis&lt;/em> — &lt;a href="https://www.spheron.network/blog/llm-inference-on-premise-vs-cloud/">https://www.spheron.network/blog/llm-inference-on-premise-vs-cloud/&lt;/a>&lt;/li>
&lt;li>CloudZero, &lt;em>H100 GPU Cost in 2026: Buy, Rent, and Cloud Pricing Compared&lt;/em> — &lt;a href="https://www.cloudzero.com/blog/h100-gpu-cost/">https://www.cloudzero.com/blog/h100-gpu-cost/&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Sovereign on-premise vs hyperscalers: the case with data (cost, energy, performance and sovereignty)</title><link>https://blog.lo0.es/en/posts/sovereign-on-premise-vs-hyperscalers-data/</link><pubDate>Sun, 14 Jun 2026 05:30:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/sovereign-on-premise-vs-hyperscalers-data/</guid><description>&lt;blockquote>
&lt;p>Notation: amounts in &lt;strong>euros (N €)&lt;/strong>, decimals with a point; when a source quotes dollars it is marked
&amp;ldquo;USD&amp;rdquo;. Data centred on Europe and Spain. The dollar symbol is not used (formula delimiter).&lt;/p>
&lt;/blockquote>
&lt;h2 id="what-this-article-covers">What this article covers&lt;/h2>
&lt;p>&lt;strong>Synthesis&lt;/strong> article of the series (S2), and the heart of the proposal: the case with data for
sovereign on-premise against the hyperscalers and the European cloud. Up to here, each track
measured its own axis, cost per token (FinOps), goodput (benchmarking), energy and carbon; here the
four are crossed, with a fourth dimension no US technical comparison puts up front: &lt;strong>data
sovereignty&lt;/strong>. The aim is to answer, with numbers and not with ideology, the question that underpins
any investment in an AI platform: &lt;strong>serve on your own iron, on a European cloud or on a
hyperscaler?&lt;/strong> And to do it honestly: on-prem &lt;strong>does not always win&lt;/strong>, and saying when it wins and
when it does not is what makes the recommendation credible.&lt;/p>
&lt;hr>
&lt;h2 id="the-framework-four-axes-not-one-number">The framework: four axes, not one number&lt;/h2>
&lt;p>The mistake in almost every comparison is reducing the decision to the hourly cost of a GPU. The real
decision crosses &lt;strong>four axes&lt;/strong>, and only by seeing them together do you decide well:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Axis&lt;/th>
&lt;th>Question&lt;/th>
&lt;th>Who measures it in the series&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Cost (TCO)&lt;/td>
&lt;td>how much does it cost to serve, all in?&lt;/td>
&lt;td>FinOps (A2–A8)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Performance&lt;/td>
&lt;td>does it meet the SLO, at what goodput?&lt;/td>
&lt;td>Benchmarking (B2–B8)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy and carbon&lt;/td>
&lt;td>how many watts and grams per token?&lt;/td>
&lt;td>Energy (C2–C8)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sovereignty&lt;/td>
&lt;td>under which jurisdiction does the data live?&lt;/td>
&lt;td>GDPR / EU AI Act / CSRD&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The first three are quantifiable and meet in the cost per token; the fourth is a &lt;strong>constraint&lt;/strong>
that can rule out an option however cheap it is. The synthesis consists of scoring each option on all
four and deciding on the Pareto frontier, not on whichever axis happens to suit.&lt;/p>
&lt;hr>
&lt;h2 id="the-cost-axis-tco-and-break-even">The cost axis: TCO and break-even&lt;/h2>
&lt;p>The real on-premise cost is amortised capex plus opex, and its cost per effective hour depends on
&lt;strong>utilisation&lt;/strong>:&lt;/p>
$$\text{effective cost/GPU-hour (on-prem)} = \frac{\text{annual amortised capex} + \text{annual opex}}{8760 \times \text{utilisation}}$$
&lt;p>This formula is the key to the whole debate: &lt;strong>the cost per useful hour of on-prem rises as
utilisation falls&lt;/strong>, because capex is paid whether the GPU is working or idle. The 2026 data:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Data point&lt;/th>
&lt;th>Value&lt;/th>
&lt;th>Source&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>On-prem cost 8×H100 (floor, high util.)&lt;/td>
&lt;td>~2.83 USD/GPU-hour &lt;em>all-in&lt;/em>&lt;/td>
&lt;td>&lt;a href="https://www.spheron.network/blog/llm-inference-on-premise-vs-cloud/">Spheron&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Annual on-prem cost (floor)&lt;/td>
&lt;td>~237,000 USD/year&lt;/td>
&lt;td>&lt;a href="https://www.spheron.network/blog/llm-inference-on-premise-vs-cloud/">Spheron&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AWS H100 (p5.48xlarge)&lt;/td>
&lt;td>4.10–6.88 USD/GPU-hour&lt;/td>
&lt;td>&lt;a href="https://www.spheron.network/blog/llm-inference-on-premise-vs-cloud/">Spheron&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AWS 8-GPU on-demand annual (100 % util)&lt;/td>
&lt;td>287,000–482,000 USD/year&lt;/td>
&lt;td>&lt;a href="https://www.spheron.network/blog/llm-inference-on-premise-vs-cloud/">Spheron&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sovereign European cloud (Lyceum/Scaleway)&lt;/td>
&lt;td>from 2–2.73 €/GPU-hour, zero-egress&lt;/td>
&lt;td>&lt;a href="https://lyceum.technology/magazine/eu-sovereign-inference-platform-comparison/">Lyceum&lt;/a>, &lt;a href="https://www.scaleway.com/en/h100/">Scaleway&lt;/a>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The &lt;strong>break-even&lt;/strong> against AWS on-demand falls around 50–83 % utilisation depending on region and
tariff; &lt;strong>below ~70 % utilisation, the cloud wins&lt;/strong> on TCO, and above it, on-prem
(&lt;a href="https://www.spheron.network/blog/llm-inference-on-premise-vs-cloud/">Spheron&lt;/a>). For very
high-utilisation workloads, on-prem pays for itself in less than 4 months (&lt;a href="https://lenovopress.lenovo.com/lp2368-on-premise-vs-cloud-generative-ai-total-cost-of-ownership-2026-edition">Lenovo&lt;/a>).&lt;/p>
&lt;h3 id="three-year-tco-the-full-calculation-for-an-8h100-node">Three-year TCO: the full calculation for an 8×H100 node&lt;/h3>
&lt;p>Abstract numbers do not convince a committee; a three-year model with declared line items does. Take a
sovereign 8×H100 node in Spain and compare it, for the same work, with AWS and with a European
cloud. Declared assumptions: amortisation over 3 years, energy at a solar PPA of 32.5 €/MWh
(with grid backup), PUE 1.3, and two utilisation scenarios (50 % and 80 %).&lt;/p>
&lt;p>&lt;strong>On-premise (own node), annual line items:&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Line item&lt;/th>
&lt;th>Annual value&lt;/th>
&lt;th>Note&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Amortised capex (node ~270,000 € / 3 years)&lt;/td>
&lt;td>~90,000 €&lt;/td>
&lt;td>8×H100 server + network + storage&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy (≈10.4 kW × PUE 1.3 × 8760 h)&lt;/td>
&lt;td>~3,850 € (at 32.5 €/MWh)&lt;/td>
&lt;td>with solar PPA; at grid tariff, ~12–18 k €&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Operation, cooling, maintenance&lt;/td>
&lt;td>~25,000 €&lt;/td>
&lt;td>prorated staff, support, spares&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Datacenter space (rack, connectivity)&lt;/td>
&lt;td>~12,000 €&lt;/td>
&lt;td>colocation or own datacenter&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Annual total&lt;/td>
&lt;td>~131,000 €&lt;/td>
&lt;td>independent of utilisation&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>At a fixed 131,000 €/year, the cost per token depends only on how many tokens you generate,
that is, on utilisation:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Utilisation&lt;/th>
&lt;th>Useful GPU-hours/year&lt;/th>
&lt;th>Cost/1M tokens&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>50 %&lt;/td>
&lt;td>~35,000&lt;/td>
&lt;td>~2.9 €&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>65 %&lt;/td>
&lt;td>~45,500&lt;/td>
&lt;td>~2.2 €&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>80 %&lt;/td>
&lt;td>~56,000&lt;/td>
&lt;td>~1.8 € (with cheap grid, ~1.1 €)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Sovereign European cloud (Scaleway/Lyceum), pay per use:&lt;/strong> at ~2.2 €/GPU-hour with zero-egress, the
cost per token is &lt;strong>constant with utilisation&lt;/strong> (you only pay for what you use): ~1.5–2.2 €/1M tokens
depending on model and batching, with no capex and no idle risk.&lt;/p>
&lt;p>&lt;strong>Hyperscaler (AWS p5), on-demand:&lt;/strong> at 4.10–6.88 USD/GPU-hour (≈3.8–6.4 €), the cost per token works
out at ~2–3.5 €/1M tokens, and on top of that you have to add the egress, quite apart from the
fact that for GDPR data the sovereignty axis rules it out already.&lt;/p>
&lt;p>The reading of the model is the thesis of the whole of S2: &lt;strong>at 50 % utilisation, on-prem (~2.9 €) does
not beat the European cloud (~1.8 €); at 80 % with cheap energy (~1.1 €), it beats it comfortably&lt;/strong>. The
crossover sits, as the literature says, around 65–70 %. Investing in on-prem is, fundamentally, a
bet that you will sustain high utilisation, and that bet is won with scheduling, not with hardware.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 250" role="img" aria-label="Break-even on-prem vs cloud by utilisation: the cost per useful hour of on-prem falls with utilisation and crosses that of the cloud at around 65-70 per cent" xmlns="http://www.w3.org/2000/svg">
&lt;style>.ax{fill:none;stroke:currentColor;stroke-width:1}.cv{fill:none;stroke:currentColor;stroke-width:1.6}.dsh{fill:none;stroke:currentColor;stroke-width:1;stroke-dasharray:4 3}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}&lt;/style>
&lt;line class="ax" x1="60" y1="40" x2="60" y2="200"/>
&lt;line class="ax" x1="60" y1="200" x2="720" y2="200"/>
&lt;text x="20" y="130" class="ts" transform="rotate(-90 20 130)">cost/useful hour&lt;/text>
&lt;text x="330" y="228" class="ts">utilisation (%) →&lt;/text>
&lt;path class="cv" d="M90,55 C200,90 320,130 430,150 C540,165 640,172 700,175"/>
&lt;text x="95" y="50" class="ts">on-prem (fixed capex)&lt;/text>
&lt;line class="cv" x1="60" y1="150" x2="720" y2="150"/>
&lt;text x="600" y="143" class="ts">cloud (≈ flat)&lt;/text>
&lt;line class="dsh" x1="430" y1="40" x2="430" y2="200"/>
&lt;text x="392" y="56" class="tl">break-even ~65-70%&lt;/text>
&lt;text x="110" y="190" class="ts">low util.: cloud wins&lt;/text>
&lt;text x="520" y="190" class="ts">high util.: on-prem wins&lt;/text>
&lt;text x="60" y="245" class="ts">On-prem only wins to the right of the break-even; fixed capex penalises it at low utilisation.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;hr>
&lt;h2 id="the-uncomfortable-reality-the-utilisation-almost-nobody-reaches">The uncomfortable reality: the utilisation almost nobody reaches&lt;/h2>
&lt;p>Here is the honest figure missing from the &amp;ldquo;on-prem is always cheaper&amp;rdquo; speeches: &lt;strong>most inference teams
in production operate at 40–65 % GPU utilisation&lt;/strong>, because of traffic variability and the limits of
batching; the 80–90 % assumption that makes on-prem attractive &lt;strong>is rarely reached outside batch-only
pipelines&lt;/strong> (&lt;a href="https://www.spheron.network/blog/llm-inference-on-premise-vs-cloud/">Spheron&lt;/a>).&lt;/p>
&lt;p>This changes the naive conclusion: if your real utilisation is 50 %, on-prem &lt;strong>is not cheaper&lt;/strong> than
the cloud, because the capex you pay for the idle GPU eats you. That is why utilisation is not a
detail, it is &lt;strong>the variable that decides the cost axis&lt;/strong>, and it connects directly with the FinOps
track (the idle of A2, the chargeback of A5) and with scheduling: &lt;strong>raising utilisation is what makes
on-prem pay&lt;/strong>. A badly used cluster of your own is more expensive than the cloud; a well scheduled one
is far cheaper. The cost question is not &amp;ldquo;on-prem or cloud?&amp;rdquo;, it is &amp;ldquo;&lt;strong>can I sustain high
utilisation?&lt;/strong>&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h2 id="the-hidden-costs-of-the-cloud-egress">The hidden costs of the cloud: egress&lt;/h2>
&lt;p>The cloud has its own small print: &lt;strong>egress costs&lt;/strong> (taking data out of the provider). In the
hyperscalers, moving data out or between regions is billed, and in AI workloads with a lot of data
movement (datasets, checkpoints, embeddings) it can be a significant line item that does not appear in
the GPU-hour price. The advantage of the sovereign European cloud: most of them (Lyceum among
others) have adopted the zero-egress model, they do not charge for moving data out or between
regions (&lt;a href="https://lyceum.technology/magazine/eu-sovereign-inference-platform-comparison/">Lyceum&lt;/a>). When
comparing, the real hyperscaler cost is GPU-hour + egress + other charges, not just the GPU-hour;
ignoring it artificially inflates the hyperscaler&amp;rsquo;s competitiveness.&lt;/p>
&lt;p>An example of the order of magnitude: a platform moving 50 TB/month outbound (datasets,
checkpoints, responses served to systems outside the provider) at a typical egress tariff of
~0.08–0.09 €/GB pays ~4,000–4,500 €/month, that is ~50,000 €/year on egress alone, a line item
the size of a third of the cost of a node of your own, invisible in the GPU-hour price. On the European
cloud with zero-egress that line item is zero; on-prem, internal traffic is not billed either.
That is why a fair comparison must model egress according to the real data pattern: for workloads with
a lot of outbound movement, it can invert the ranking between hyperscaler and European cloud. The
cloud bill is not the GPU-hour; it is the GPU-hour plus everything you move.&lt;/p>
&lt;p>To this is added contract and lock-in risk: the hyperscaler&amp;rsquo;s on-demand GPU tariffs can change,
commitment discounts (reserved/savings plans) tie you for 1–3 years, and migrating out, because of
egress and coupling to proprietary services, has a real exit cost. On-prem and the European cloud with
standard APIs (Kubernetes, S3 compatible) reduce that coupling: the same manifest and the same vLLM run
on your cluster or on Scaleway without rewriting. Operational sovereignty, being able to move the
workload without rebuilding it, is a value that does not appear in the tariff but weighs on a
three-year decision.&lt;/p>
&lt;hr>
&lt;h2 id="the-performance-axis-the-provider-does-not-decide-goodput-does">The performance axis: the provider does not decide, goodput does&lt;/h2>
&lt;p>One point that simplifies the synthesis: &lt;strong>performance does not depend on the provider, it depends on
the hardware and the configuration&lt;/strong>. An H100 gives the same goodput on your cluster, on Scaleway or on
AWS, served with the same vLLM and the same config. What decides performance is the &lt;strong>goodput under
your SLO&lt;/strong> (track B), not who hosts the GPU. So, in a comparison at equal hardware, the performance
axis neutralises itself: what changes between options is cost, energy and sovereignty. The
exception: if a provider gives you access to newer hardware (B200, GB200) ahead of your on-prem buying
cycle, then the cloud can win on performance per GPU, a real argument in favour of the cloud for
staying at the hardware frontier without capex.&lt;/p>
&lt;hr>
&lt;h2 id="the-energy-axis-the-european-and-spanish-advantage">The energy axis: the European and Spanish advantage&lt;/h2>
&lt;p>Here on-prem (or cloud) in Spain or France has a structural advantage over a hyperscaler in a dirty
region. Recalling the data from the energy track:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Location&lt;/th>
&lt;th>Grid carbon (gCO₂/kWh)&lt;/th>
&lt;th>Price (indicative)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>France (nuclear)&lt;/td>
&lt;td>~20–60&lt;/td>
&lt;td>low and stable&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Spain (renewables + gas)&lt;/td>
&lt;td>~150–170&lt;/td>
&lt;td>low, volatile; solar PPA ~32.5 €/MWh&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Germany&lt;/td>
&lt;td>~363&lt;/td>
&lt;td>high&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hyperscaler (region depends on provider)&lt;/td>
&lt;td>depends; often not selectable&lt;/td>
&lt;td>provider tariff&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The same workload in &lt;strong>France emits ~9× less carbon per token than in Germany&lt;/strong>, and in Spain, with a
solar PPA at 32.5 €/MWh (an all-time low), the electricity cost, 30–50 % of the TCO, is low and,
under contract, predictable. A sovereign cluster in Spain or France controls where the energy is
consumed and with what carbon; a hyperscaler gives you the region it gives you, often with no choice of
grid intensity. For CSRD reporting, that selectability is a quantifiable advantage of on-prem and the
European cloud.&lt;/p>
&lt;p>In concrete numbers: the example 8×H100 node (~10.4 kW × PUE 1.3 ≈ 118,000 kWh/year) emits, depending
on the grid, ~2.4 t CO₂/year in France (~20 gCO₂/kWh) against ~43 t CO₂/year in Germany
(~363 gCO₂/kWh), the same machine, the same work, ~18× the difference in reportable footprint just
by choosing the location. That decision, which a hyperscaler in an imposed region does not let you
take, is exactly what on-prem and the sovereign European cloud put in your hands. The energy axis is
not an environmental detail: it is cost (the price of the kWh), compliance (CSRD) and sovereignty
(control of location) all at once.&lt;/p>
&lt;hr>
&lt;h2 id="the-sovereignty-axis-the-one-that-does-not-depend-on-utilisation">The sovereignty axis: the one that does not depend on utilisation&lt;/h2>
&lt;p>And here is the axis that invalidates the cheapest option if the data is sensitive. US hyperscalers
are subject to the &lt;strong>US CLOUD Act&lt;/strong>: US authorities can demand data held by a US company &lt;strong>even if it
sits in a European datacenter&lt;/strong>. For data subject to the &lt;strong>GDPR&lt;/strong>, that is a compliance risk. &lt;strong>Sovereign
European clouds&lt;/strong> operate under &lt;strong>EU/EFTA jurisdiction&lt;/strong>, providing data residency and GDPR compliance,
and are exempt from the US CLOUD Act
(&lt;a href="https://lyceum.technology/magazine/sovereign-cloud-providers-2026/">Lyceum · sovereign providers&lt;/a>).
On-prem of your own is the maximum degree of sovereignty: the data does not leave your cluster.&lt;/p>
&lt;p>The key difference from the other axes: sovereignty &lt;strong>does not depend on utilisation or on volume&lt;/strong>.
However much a hyperscaler cheapens the GPU-hour, for GDPR data &lt;strong>it is not an option&lt;/strong>, the
jurisdiction risk is not offset by price. It links with &lt;a href="https://blog.lo0.es/en/posts/technical-controls-ens-iso-42001-eu-ai-act-cross-mapping/">the ENS × ISO 42001 × EU AI Act controls&lt;/a>
and &lt;a href="https://blog.lo0.es/en/posts/eu-ai-act-technical-dossier-on-premise-llm-architecture/">the EU AI Act mapping&lt;/a>: compliance is a
hard constraint, not an axis to optimise.&lt;/p>
&lt;p>The four instruments that turn sovereignty into a concrete constraint rather than a slogan:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Instrument&lt;/th>
&lt;th>What it requires&lt;/th>
&lt;th>Implication for the architecture&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>US CLOUD Act&lt;/td>
&lt;td>gives the US access to data held by US companies, wherever it is&lt;/td>
&lt;td>a US hyperscaler does not guarantee jurisdictional residency even if the datacenter is in the EU&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GDPR&lt;/td>
&lt;td>residency and processing of personal data under EU law&lt;/td>
&lt;td>requires an EU/EFTA provider or your own iron for personal data&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>EU AI Act&lt;/td>
&lt;td>traceability, risk management and records for AI systems&lt;/td>
&lt;td>favours the full control of the stack (logs, datasets, models) that on-prem gives&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CSRD&lt;/td>
&lt;td>verifiable reporting of environmental footprint&lt;/td>
&lt;td>the selectable energy (clean grid, PPA) of on-prem and the European cloud is auditable&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The operational conclusion: for a European entity processing personal data or deploying high-risk AI,
&lt;strong>three of the four technical axes can favour the hyperscaler and it can still lose&lt;/strong>, because the
fourth axis, sovereignty, acts as a prior filter. That is why S2 orders the decision like this:
&lt;strong>first the sovereignty filter&lt;/strong> (which rules out the hyperscaler for GDPR data), &lt;strong>then the
optimisation of cost, performance and energy&lt;/strong> among the options that pass the filter (sovereign
on-prem and European cloud).&lt;/p>
&lt;hr>
&lt;h2 id="the-scorecard-the-three-options-scored">The scorecard: the three options scored&lt;/h2>
&lt;p>Crossing the four axes for the three realistic options of a European platform (order-of-magnitude,
illustrative figures):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Option&lt;/th>
&lt;th>Cost/1M tok&lt;/th>
&lt;th>Break-even&lt;/th>
&lt;th>Energy/carbon&lt;/th>
&lt;th>Sovereignty&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Sovereign on-prem (ES/FR)&lt;/td>
&lt;td>~1.1 € (high util.) / ~3 € (low)&lt;/td>
&lt;td>&amp;gt;65–70 % util.&lt;/td>
&lt;td>controllable (clean grid, PPA)&lt;/td>
&lt;td>total (EU)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sovereign European cloud&lt;/td>
&lt;td>~1.5–2.2 €&lt;/td>
&lt;td>no capex, pay per use&lt;/td>
&lt;td>EU, zero-egress&lt;/td>
&lt;td>high (EU)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hyperscaler (US)&lt;/td>
&lt;td>~2–3.5 € + egress&lt;/td>
&lt;td>no capex&lt;/td>
&lt;td>imposed region&lt;/td>
&lt;td>not EU (CLOUD Act)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 220" role="img" aria-label="Scorecard: sovereign on-prem, sovereign European cloud and hyperscaler scored on cost, energy and sovereignty; the hyperscaler loses on sovereignty" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.3}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}&lt;/style>
&lt;rect class="bx" x="20" y="40" width="230" height="130" rx="6"/>
&lt;text x="32" y="62" class="tl">Sovereign on-prem (ES/FR)&lt;/text>
&lt;text x="32" y="84" class="ts">cost: lowest IF util. is high&lt;/text>
&lt;text x="32" y="104" class="ts">energy: controllable (PPA, grid)&lt;/text>
&lt;text x="32" y="124" class="ts">sovereignty: TOTAL&lt;/text>
&lt;text x="32" y="148" class="ts">risk: capex + utilisation&lt;/text>
&lt;rect class="bx" x="275" y="40" width="230" height="130" rx="6"/>
&lt;text x="287" y="62" class="tl">Sovereign European cloud&lt;/text>
&lt;text x="287" y="84" class="ts">cost: medium, no capex&lt;/text>
&lt;text x="287" y="104" class="ts">energy: EU, zero-egress&lt;/text>
&lt;text x="287" y="124" class="ts">sovereignty: HIGH (EU)&lt;/text>
&lt;text x="287" y="148" class="ts">risk: pay-per-use price&lt;/text>
&lt;rect class="bx" x="530" y="40" width="230" height="130" rx="6"/>
&lt;text x="542" y="62" class="tl">Hyperscaler (US)&lt;/text>
&lt;text x="542" y="84" class="ts">cost: medium + egress&lt;/text>
&lt;text x="542" y="104" class="ts">energy: imposed region&lt;/text>
&lt;text x="542" y="124" class="ts">sovereignty: NOT EU ✗&lt;/text>
&lt;text x="542" y="148" class="ts">ruled out for GDPR data&lt;/text>
&lt;text x="20" y="200" class="ts">For GDPR data the hyperscaler is out on sovereignty, however cheap. The real decision is on-prem vs European cloud.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>The reading of the scorecard: for data subject to the GDPR, &lt;strong>the US hyperscaler is ruled out by the
sovereignty axis&lt;/strong>, however competitive its tariff. The real decision comes down to &lt;strong>sovereign on-prem
vs sovereign European cloud&lt;/strong>, and there it is decided by &lt;strong>utilisation&lt;/strong> and &lt;strong>volume&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h2 id="when-each-option-wins">When each option wins&lt;/h2>
&lt;p>The honest recommendation, by scenario:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Scenario&lt;/th>
&lt;th>Winning option&lt;/th>
&lt;th>Why&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>High, sustained volume (util. &amp;gt;65–70 %), GDPR data&lt;/td>
&lt;td>Sovereign on-prem&lt;/td>
&lt;td>lowest cost/token + total sovereignty&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Variable or growing volume, GDPR data&lt;/td>
&lt;td>Sovereign European cloud&lt;/td>
&lt;td>sovereignty without capex/idle risk&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Low volume or sporadic peak&lt;/td>
&lt;td>European cloud (per use)&lt;/td>
&lt;td>you do not amortise the capex&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>No sovereignty requirement, hardware frontier&lt;/td>
&lt;td>Hyperscaler&lt;/td>
&lt;td>access to new hardware without capex&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hybrid (base + peak)&lt;/td>
&lt;td>On-prem + European cloud (burst)&lt;/td>
&lt;td>cheap base of your own, sovereign elastic peak&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>On-prem makes sense when there is &lt;strong>very high, predictable utilisation (80 %+), strict sovereignty
requirements, or a hyperscaler contract that works out expensive&lt;/strong> (&lt;a href="https://www.spheron.network/blog/llm-inference-on-premise-vs-cloud/">Spheron&lt;/a>).
For sovereign platforms with a sustained base load, the winning pattern is usually the &lt;strong>hybrid&lt;/strong>:
sovereign on-prem for the high-utilisation base (where the cost per token is unbeatable) and
sovereign European cloud for the peak and for growth (elastic, no capex, keeping EU jurisdiction).
The best of both without giving up sovereignty.&lt;/p>
&lt;h3 id="sizing-the-hybrid-how-much-on-iron-how-much-on-cloud">Sizing the hybrid: how much on iron, how much on cloud&lt;/h3>
&lt;p>The hybrid is not &amp;ldquo;a bit of each&amp;rdquo;; it is sized with one figure: the &lt;strong>base load percentile&lt;/strong>. The rule
is to put on on-prem the load that is almost always present (the load that keeps the GPU at
75–85 %) and to send to the European cloud only the peaks that, if covered with iron, would leave
GPUs idle most of the time. An example with a realistic traffic profile:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Load band&lt;/th>
&lt;th>% of the time&lt;/th>
&lt;th>Where to serve&lt;/th>
&lt;th>Why&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Base (p0–p70)&lt;/td>
&lt;td>always&lt;/td>
&lt;td>on-prem (1 node 8×H100 at ~80 %)&lt;/td>
&lt;td>minimum cost/token, high util. guaranteed&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Middle (p70–p95)&lt;/td>
&lt;td>daily peak hours&lt;/td>
&lt;td>on-prem if it fits, otherwise European cloud&lt;/td>
&lt;td>elasticity without idle capex&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Peak (p95–p100)&lt;/td>
&lt;td>sporadic&lt;/td>
&lt;td>sovereign European cloud (burst)&lt;/td>
&lt;td>absurd to buy iron for a rare peak&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>With this split, the base amortises your own node at high utilisation (~1.1–1.8 €/1M tokens) and the
peak is paid per use with no idle penalty (~1.5–2.2 €/1M tokens), all under EU jurisdiction. The
expensive mistake is the opposite: sizing the on-prem for the peak, in which case the GPU spends
most of its time idle at 30–40 %, the cost per token shoots up above 3 € and the cloud would have been
cheaper. &lt;strong>You size the iron for the base, not for the peak&lt;/strong>; the peak is exactly what the cloud does
well. This principle connects with the capacity planning and the scheduling (Kueue/Volcano) of the
series: the hybrid only works if the scheduler fills your own node before overflowing to the cloud.&lt;/p>
&lt;hr>
&lt;h2 id="assumptions-and-sensitivity">Assumptions and sensitivity&lt;/h2>
&lt;p>The whole comparison hangs on assumptions that have to be declared, because moving them moves the
conclusion:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Assumption&lt;/th>
&lt;th>If it rises&lt;/th>
&lt;th>Effect&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Utilisation&lt;/td>
&lt;td>50 % → 80 %&lt;/td>
&lt;td>on-prem goes from losing to clearly winning&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy price&lt;/td>
&lt;td>expensive region → France/PPA&lt;/td>
&lt;td>lowers on-prem TCO and carbon&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Amortisation period&lt;/td>
&lt;td>24 → 36 months&lt;/td>
&lt;td>lowers the on-prem cost per hour&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Volume&lt;/td>
&lt;td>&amp;lt; 2M tok/day → much more&lt;/td>
&lt;td>crosses the break-even towards on-prem&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Egress (hyperscaler)&lt;/td>
&lt;td>low → high&lt;/td>
&lt;td>makes the hyperscaler dearer than the European cloud&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The rule: &lt;strong>no on-prem vs cloud comparison is valid without fixing these assumptions&lt;/strong>. One that says
&amp;ldquo;on-prem is 3× cheaper&amp;rdquo; without declaring the assumed utilisation is propaganda; one that fixes
utilisation, energy price, period and volume is a data point. The dossier must present the case with
explicit assumptions and a sensitivity analysis, which is what makes it defensible before a committee
that questions them.&lt;/p>
&lt;h2 id="decision-checklist">Decision checklist&lt;/h2>
&lt;p>To take S2 from theory to decision, the questions that order the choice, in order:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Is the data subject to the GDPR, or is the system high-risk under the EU AI Act?&lt;/strong> If so, the US
hyperscaler is &lt;strong>ruled out on sovereignty&lt;/strong>; you choose between on-prem and European cloud. If not,
the hyperscaler enters the cost comparison.&lt;/li>
&lt;li>&lt;strong>Can I sustain utilisation above 65–70 % on the base load?&lt;/strong> If so, on-prem wins on cost for that
base. If not, the European cloud avoids paying capex for idle GPUs.&lt;/li>
&lt;li>&lt;strong>Does the traffic profile have marked peaks?&lt;/strong> If so, &lt;strong>hybrid&lt;/strong>: base on iron, peak on European
cloud. Size the iron for the base, never for the peak.&lt;/li>
&lt;li>&lt;strong>How much data do I take out of the provider each month?&lt;/strong> Model the egress; with a lot of
movement, the zero-egress of the European cloud, or on-prem, win clearly.&lt;/li>
&lt;li>&lt;strong>Which electricity grid and at what price?&lt;/strong> France or Spain with a PPA lower TCO and carbon;
include it in the model and in the CSRD report.&lt;/li>
&lt;li>&lt;strong>Have I fixed utilisation, energy, period and volume in writing?&lt;/strong> Without those four declared
assumptions, the number is not defensible.&lt;/li>
&lt;/ol>
&lt;p>Whoever answers these six questions with data, not with intuition, has the case built. The series'
recommendation for a sovereign European platform with a sustained base load is stable: &lt;strong>sovereign
on-prem for the high-utilisation base plus sovereign European cloud for the peak&lt;/strong>, with the
hyperscaler reserved only for workloads with no sovereignty requirement where frontier hardware is
needed without capex.&lt;/p>
&lt;hr>
&lt;h2 id="limits-and-traps-data-driven">Limits and traps (data-driven)&lt;/h2>
&lt;ol>
&lt;li>&lt;strong>Unrealistic assumed utilisation.&lt;/strong> The 80–90 % that makes on-prem win rarely happens in production
(40–65 % typical). Model your real utilisation, not the ideal one.&lt;/li>
&lt;li>&lt;strong>Comparing only the GPU-hour.&lt;/strong> TCO includes energy, operation, cooling, egress (cloud) and capex
(on-prem). Compare totals with the same assumptions.&lt;/li>
&lt;li>&lt;strong>Ignoring sovereignty.&lt;/strong> For GDPR data, the sovereignty axis rules out the hyperscaler before cost
does; it is not negotiable with price.&lt;/li>
&lt;li>&lt;strong>Forgetting the hybrid.&lt;/strong> It is not &amp;ldquo;all on-prem or all cloud&amp;rdquo;; the base-plus-peak pattern usually
dominates.&lt;/li>
&lt;li>&lt;strong>Data in USD.&lt;/strong> US comparisons are in dollars and with dirty regions; convert them to euros and to
your region&amp;rsquo;s grid (Spain/France) for your case.&lt;/li>
&lt;/ol>
&lt;p>The synthesis of S2, in one sentence: &lt;strong>for sovereign European data, the decision is not on-prem vs
cloud in the abstract, but sovereign on-prem (high utilisation) plus sovereign European cloud (peak)
against a hyperscaler that the sovereignty axis rules out&lt;/strong>, and utilisation is the variable that
splits the base between the first two. The rest of the series gives the numbers for each axis; this one
crosses them into the recommendation. The next synthesis article (S3) sizes the investment; this one
decides the architecture.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/cloud-gpu-on-demand-reserved-spot-neoclouds-2026-prices/">Cloud GPU: price comparison, commitment and sovereign neoclouds&lt;/a> — the on-demand, spot and reserved prices of the European cloud providers that appear as an alternative in this analysis, with updated 2026 data.&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 the on-premise GPU cluster: amortisation, energy and infrastructure&lt;/a> — the full breakdown of on-premise TCO: server CAPEX, amortisation, energy, network and staff, with the spreadsheet that gives the real €/GPU-hour.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>Spheron · LLM Inference On-Premise vs GPU Cloud: 2026 Cost and Break-Even — &lt;a href="https://www.spheron.network/blog/llm-inference-on-premise-vs-cloud/">https://www.spheron.network/blog/llm-inference-on-premise-vs-cloud/&lt;/a>&lt;/li>
&lt;li>Lenovo Press · On-Premise vs Cloud: Generative AI TCO (2026) — &lt;a href="https://lenovopress.lenovo.com/lp2368-on-premise-vs-cloud-generative-ai-total-cost-of-ownership-2026-edition">https://lenovopress.lenovo.com/lp2368-on-premise-vs-cloud-generative-ai-total-cost-of-ownership-2026-edition&lt;/a>&lt;/li>
&lt;li>Lyceum · EU Sovereign Inference Platform Comparison (2026) — &lt;a href="https://lyceum.technology/magazine/eu-sovereign-inference-platform-comparison/">https://lyceum.technology/magazine/eu-sovereign-inference-platform-comparison/&lt;/a>&lt;/li>
&lt;li>Lyceum · Sovereign Cloud Providers 2026 — &lt;a href="https://lyceum.technology/magazine/sovereign-cloud-providers-2026/">https://lyceum.technology/magazine/sovereign-cloud-providers-2026/&lt;/a>&lt;/li>
&lt;li>Scaleway · H100 GPU instance (precio €, soberanía UE) — &lt;a href="https://www.scaleway.com/en/h100/">https://www.scaleway.com/en/h100/&lt;/a>&lt;/li>
&lt;li>Nerd Level Tech · GPU Cloud TCO 2026: hidden fees, egress costs — &lt;a href="https://nerdleveltech.com/gpu-cloud-comparison-2026-the-real-cost-of-ai-compute">https://nerdleveltech.com/gpu-cloud-comparison-2026-the-real-cost-of-ai-compute&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>Cost, performance and energy: the three axes of LLM inference and the identity that ties them together</title><link>https://blog.lo0.es/en/posts/cost-performance-energy-three-axes-llm-inference/</link><pubDate>Sat, 13 Jun 2026 03:30:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/cost-performance-energy-three-axes-llm-inference/</guid><description>&lt;blockquote>
&lt;p>Notation: amounts in &lt;strong>euros (N €)&lt;/strong>, decimals with a point. Cost and energy references are
European (France, Germany, Spain), because the aim is to defend a sovereign European
architecture; when a source quotes prices in dollars it is flagged as &amp;ldquo;USD&amp;rdquo;. The dollar
symbol is not used (on this site it is a formula delimiter).&lt;/p>
&lt;/blockquote>
&lt;h2 id="what-this-series-is">What this series is&lt;/h2>
&lt;p>This is the first instalment of a series of &lt;strong>28 data articles&lt;/strong> (not opinion pieces)
about the three dimensions that decide whether a sovereign European LLM platform is
sustainable: &lt;strong>cost (FinOps)&lt;/strong>, &lt;strong>performance (benchmarking)&lt;/strong> and &lt;strong>energy&lt;/strong>. The aim
is to have a body of verifiable figures, with their methodology, to support architecture
decisions with numbers rather than intuition. This first article sets the
frame: what each axis measures, &lt;strong>how they link to one another&lt;/strong>, an end-to-end worked
example with European data and the 2026 references that will be used as a base.&lt;/p>
&lt;p>The quantitative thesis, in one sentence: the three axes &lt;strong>are not independent&lt;/strong>. Throughput
(tokens per second) is the common denominator of cost per token and energy per token, and
energy is, on top of that, between &lt;strong>30 % and 50 %&lt;/strong> of the cost itself. Optimising one moves
the other two. And in Europe, where the cluster sits (France, Germany, Spain) changes
both the electricity cost and the carbon per token at once.&lt;/p>
&lt;hr>
&lt;h2 id="the-three-axes-and-their-metrics">The three axes and their metrics&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Axis&lt;/th>
&lt;th>Question&lt;/th>
&lt;th>Key metrics&lt;/th>
&lt;th>Unit&lt;/th>
&lt;th>Tool family&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Cost (FinOps)&lt;/td>
&lt;td>How much does each unit of work cost?&lt;/td>
&lt;td>cost/1M tokens (CPM), cost/request, €/GPU-hour, utilisation&lt;/td>
&lt;td>€/1M tok, €/h&lt;/td>
&lt;td>OpenCost, Kubecost, CloudZero&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Performance&lt;/td>
&lt;td>How much work per second and at what latency?&lt;/td>
&lt;td>TTFT, TPOT/ITL, throughput, goodput, P50/P99&lt;/td>
&lt;td>tok/s, ms&lt;/td>
&lt;td>GuideLLM, AIPerf, LLMPerf, MLPerf Inference&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy&lt;/td>
&lt;td>How many watts per unit of work?&lt;/td>
&lt;td>energy/token, average power, PUE, carbon&lt;/td>
&lt;td>J/tok, Wh, kWh, gCO₂&lt;/td>
&lt;td>Zeus, CodeCarbon, MLPerf Power, Kepler&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Each axis has its own in-depth introductory article in this same batch; here only how they
fit together matters. It is worth breaking each one down a little more before crossing them.&lt;/p>
&lt;h3 id="cost-axis-in-detail">Cost axis in detail&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Metric&lt;/th>
&lt;th>Definition&lt;/th>
&lt;th>Why it matters&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>CPM (cost/1M tokens)&lt;/td>
&lt;td>cluster cost ÷ tokens produced&lt;/td>
&lt;td>the comparable unit on-prem vs cloud&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cost per request&lt;/td>
&lt;td>cost attributed to one complete request&lt;/td>
&lt;td>billing by real usage&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>€/GPU-hour&lt;/td>
&lt;td>hourly cost of a GPU (amortised or rented)&lt;/td>
&lt;td>the basis of any calculation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Utilisation&lt;/td>
&lt;td>fraction of the GPU actually used&lt;/td>
&lt;td>the lever that moves everything else&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Fixed vs variable cost&lt;/td>
&lt;td>amortised capex vs energy/operations&lt;/td>
&lt;td>defines the break-even point&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="performance-axis-in-detail">Performance axis in detail&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Metric&lt;/th>
&lt;th>Definition&lt;/th>
&lt;th>Dominant phase&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>TTFT&lt;/td>
&lt;td>latency to the first token&lt;/td>
&lt;td>prefill&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>TPOT / ITL&lt;/td>
&lt;td>time between output tokens&lt;/td>
&lt;td>decode&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Throughput&lt;/td>
&lt;td>tokens or requests per second of the system&lt;/td>
&lt;td>both&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Goodput&lt;/td>
&lt;td>throughput that meets the SLO (not the raw figure)&lt;/td>
&lt;td>the honest number&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>P95 / P99&lt;/td>
&lt;td>latency percentiles (not the mean)&lt;/td>
&lt;td>the real experience&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="energy-axis-in-detail">Energy axis in detail&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Metric&lt;/th>
&lt;th>Definition&lt;/th>
&lt;th>Unit&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Average power&lt;/td>
&lt;td>average instantaneous consumption&lt;/td>
&lt;td>W&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy per token&lt;/td>
&lt;td>energy per generated token&lt;/td>
&lt;td>J/token&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Total energy&lt;/td>
&lt;td>integral of power over time&lt;/td>
&lt;td>Wh, kWh&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PUE&lt;/td>
&lt;td>datacenter overhead (cooling, losses)&lt;/td>
&lt;td>ratio ≥ 1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Carbon&lt;/td>
&lt;td>energy × grid intensity&lt;/td>
&lt;td>gCO₂eq&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="the-identity-that-ties-the-three-axes-together-throughput">The identity that ties the three axes together: throughput&lt;/h2>
&lt;p>The &lt;strong>cost per million tokens (CPM)&lt;/strong> is the cluster&amp;rsquo;s cost divided by what it
produces:&lt;/p>
$$\text{CPM} = \frac{\text{cluster cost per hour}}{\text{throughput (tok/s)} \times 3600 / 10^6}$$
&lt;p>&lt;strong>Energy per token&lt;/strong> has exactly the same form, with power in the
numerator:&lt;/p>
$$\text{energy per token (J)} = \frac{\text{average power (W)}}{\text{throughput (tok/s)}}$$
&lt;p>The two share the &lt;strong>same denominator&lt;/strong>: throughput. Hence the key operational consequence:
&lt;strong>every performance improvement that raises tokens/s lowers both cost per token and
energy per token at the same time&lt;/strong>. These are not three separate optimisations; throughput moves them together.
That is why performance benchmarking (axis 2) is, at bottom, also a FinOps tool and an
energy efficiency tool.&lt;/p>
&lt;p>The CPM↔throughput relationship is direct: the higher the throughput, the lower the CPM. It is the identity on
which the whole cost analysis of the series is built.&lt;/p>
&lt;hr>
&lt;h2 id="worked-example-unit-economics-of-an-8h100-node-in-europe">Worked example: unit economics of an 8×H100 node in Europe&lt;/h2>
&lt;p>To see the identity in action, an end-to-end calculation on an example node
(8×H100 SXM, Llama 3.1 70B FP16 served with vLLM), with European prices. Order-of-magnitude
figures; the methodology is what matters.&lt;/p>
&lt;p>&lt;strong>Starting data:&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Parameter&lt;/th>
&lt;th>Value&lt;/th>
&lt;th>Source&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>European cloud rental (Scaleway, per H100)&lt;/td>
&lt;td>2.73 €/h&lt;/td>
&lt;td>&lt;a href="https://www.scaleway.com/en/h100/">Scaleway&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>→ 8×H100 node on-demand&lt;/td>
&lt;td>~21.84 €/h&lt;/td>
&lt;td>8 × 2.73&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Amortised on-prem cost (estimate)&lt;/td>
&lt;td>~10–13 €/h&lt;/td>
&lt;td>capex 24–36 months + opex&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Throughput&lt;/td>
&lt;td>2,800 tok/s&lt;/td>
&lt;td>reference benchmark&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Power per H100 (TDP)&lt;/td>
&lt;td>up to 700 W&lt;/td>
&lt;td>&lt;a href="https://www.nvidia.com/en-us/data-center/h100/">NVIDIA&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Reference PUE&lt;/td>
&lt;td>1.4&lt;/td>
&lt;td>typical of a datacenter&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Cost per million tokens.&lt;/strong> At 2,800 tok/s, hourly throughput is
2,800 × 3,600 / 10⁶ = 10.08 million tokens/hour. Therefore:&lt;/p>
$$\text{CPM (cloud)} = \frac{21{.}84}{10{.}08} \approx 2{.}17 \text{ €/1M tok}$$
&lt;p>With the amortised on-prem cost (~11 €/h) it drops to &lt;strong>~1.09 €/1M tok&lt;/strong>, the difference between
renting from a provider and amortising your own iron.&lt;/p>
&lt;p>&lt;strong>Energy per token.&lt;/strong> The power of the 8 cards is 8 × 700 = 5,600 W (board only).
Over 2,800 tok/s:&lt;/p>
$$\text{energy per token} = \frac{5,600}{2,800} = 2 \text{ J/token (board)}$$
&lt;p>Over a million tokens that is 2 × 10⁶ J = 0.56 kWh (GPU only). Applying PUE 1.4: ~0.78
kWh per million tokens. At French wholesale prices (~0.058 €/kWh) that is ~0.045 € of
electricity per million tokens; in Germany (~0.091 €/kWh), ~0.071 €, some 57 % more, because of
the grid, not the model.&lt;/p>
&lt;p>&lt;strong>The link.&lt;/strong> If a serving optimisation raised throughput from 2,800 to 4,200 tok/s
(+50 %), the CPM would fall to ~1.45 €/1M tok and energy per token to ~1.33 J, both at
once, through the same denominator. That is the identity, with numbers.&lt;/p>
&lt;hr>
&lt;h2 id="energy-is-inside-the-cost">Energy is &lt;em>inside&lt;/em> the cost&lt;/h2>
&lt;p>The second link: energy is not an axis separate from cost, it is &lt;strong>a large component of
it&lt;/strong>. 2026 reference data:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Figure&lt;/th>
&lt;th>Value&lt;/th>
&lt;th>Source&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Electricity as a share of TCO (5 years)&lt;/td>
&lt;td>30–40 % (&amp;gt;50 % at high density 24/7)&lt;/td>
&lt;td>&lt;a href="https://www.spheron.network/blog/ai-inference-power-electricity-cost-2026/">Spheron&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Day-ahead wholesale France (Mar-2026)&lt;/td>
&lt;td>~58 €/MWh (0.058 €/kWh)&lt;/td>
&lt;td>&lt;a href="https://tradingeconomics.com/france/electricity-price">TradingEconomics&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Day-ahead wholesale Spain&lt;/td>
&lt;td>~54 €/MWh (0.054 €/kWh)&lt;/td>
&lt;td>&lt;a href="https://tradingeconomics.com/spain/electricity-price">TradingEconomics&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Day-ahead wholesale Germany&lt;/td>
&lt;td>~91 €/MWh (0.091 €/kWh)&lt;/td>
&lt;td>&lt;a href="https://www.tradingeconomics.com/germany/electricity-price">TradingEconomics&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Electricity cost/token at 80 % util&lt;/td>
&lt;td>¼ of that at 20 % util&lt;/td>
&lt;td>&lt;a href="https://www.spheron.network/blog/ai-inference-power-electricity-cost-2026/">Spheron&lt;/a>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>A methodological warning: the &lt;strong>wholesale&lt;/strong> price (day-ahead) is not what a company pays; &lt;strong>industrial
retail&lt;/strong> adds grid charges and taxes (roughly ×1.5–2). But the relative
ordering holds: &lt;strong>France and Spain, cheap; Germany, expensive.&lt;/strong>&lt;/p>
&lt;p>The third link, utilisation, closes the triangle: power is consumed even when the
GPU is idle, so spreading the fixed cost (energy included) over more tokens (more
throughput, more occupancy) &lt;strong>lowers all three axes at once&lt;/strong>. The electricity cost per token at
80 % utilisation is a quarter of what it is at 20 %.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 300" role="img" aria-label="The three axes (cost, performance, energy) linked by throughput at the centre; energy is also a component of cost" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.3}.tl{font:600 13px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}.ar{fill:none;stroke:currentColor;stroke-width:1.2;stroke-dasharray:4 3}&lt;/style>
&lt;rect class="bx" x="300" y="120" width="180" height="60" rx="8"/>
&lt;text x="390" y="146" text-anchor="middle" class="tl">THROUGHPUT&lt;/text>
&lt;text x="390" y="164" text-anchor="middle" class="ts">tokens/s · common denominator&lt;/text>
&lt;rect class="bx" x="40" y="40" width="200" height="64" rx="8"/>
&lt;text x="56" y="63" class="tl">Cost (FinOps)&lt;/text>
&lt;text x="56" y="81" class="ts">CPM = cost/h ÷ throughput&lt;/text>
&lt;text x="56" y="97" class="ts">€/1M tok · utilisation&lt;/text>
&lt;rect class="bx" x="540" y="40" width="200" height="64" rx="8"/>
&lt;text x="556" y="63" class="tl">Performance&lt;/text>
&lt;text x="556" y="81" class="ts">TTFT · TPOT · goodput&lt;/text>
&lt;text x="556" y="97" class="ts">raise tok/s -> lower the others&lt;/text>
&lt;rect class="bx" x="290" y="220" width="200" height="60" rx="8"/>
&lt;text x="306" y="243" class="tl">Energy&lt;/text>
&lt;text x="306" y="261" class="ts">J/tok = W ÷ throughput&lt;/text>
&lt;line class="ar" x1="240" y1="92" x2="300" y2="135"/>
&lt;line class="ar" x1="540" y1="92" x2="480" y2="135"/>
&lt;line class="ar" x1="390" y1="180" x2="390" y2="220"/>
&lt;line class="ar" x1="140" y1="104" x2="320" y2="220"/>
&lt;text x="150" y="170" class="ts">energy = 30-50 % of cost (TCO)&lt;/text>
&lt;text x="40" y="295" class="ts">Improving throughput lowers cost/token and energy/token at once; raising utilisation spreads fixed cost over more tokens.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;hr>
&lt;h2 id="sensitivity-analysis-what-moves-the-cost-per-token">Sensitivity analysis: what moves the cost per token&lt;/h2>
&lt;p>Four levers dominate the CPM, and it is worth knowing their elasticity before defending
any figure:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Lever&lt;/th>
&lt;th>Change&lt;/th>
&lt;th>Effect on CPM&lt;/th>
&lt;th>Mechanism&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Throughput&lt;/td>
&lt;td>+50 % tok/s&lt;/td>
&lt;td>−33 % CPM&lt;/td>
&lt;td>direct denominator&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Utilisation&lt;/td>
&lt;td>20 % → 80 %&lt;/td>
&lt;td>up to −75 % on the electricity portion&lt;/td>
&lt;td>spreads fixed cost&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Location (energy)&lt;/td>
&lt;td>Germany → France&lt;/td>
&lt;td>lowers the electricity portion (~37 % less €/kWh)&lt;/td>
&lt;td>wholesale FR &amp;lt; DE&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Amortise vs rent&lt;/td>
&lt;td>21.84 → 11 €/h&lt;/td>
&lt;td>−50 % CPM&lt;/td>
&lt;td>removes the provider&amp;rsquo;s margin&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Quantisation (FP8/INT4)&lt;/td>
&lt;td>+throughput, −VRAM/KV&lt;/td>
&lt;td>lowers CPM and J/token&lt;/td>
&lt;td>same identity&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The reading: the CPM &lt;strong>is not a number, it is a function&lt;/strong> of utilisation, throughput, location
(energy price) and ownership model. Any on-prem vs cloud comparison that does not
fix those assumptions is not comparable, and that is exactly the job of the synthesis article.&lt;/p>
&lt;hr>
&lt;h2 id="simplified-tco-model-on-premise">Simplified TCO model (on-premise)&lt;/h2>
&lt;p>The real on-premise cost is not the GPU alone: it is &lt;strong>amortised capex + opex&lt;/strong>, over a hardware
cycle of 24–36 months. Reference structure of an inference node:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Component&lt;/th>
&lt;th>Type&lt;/th>
&lt;th>Typical weight&lt;/th>
&lt;th>Notes&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>GPUs&lt;/td>
&lt;td>capex&lt;/td>
&lt;td>the bulk&lt;/td>
&lt;td>amortise over 24–36 months&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Server, network, BMC&lt;/td>
&lt;td>capex&lt;/td>
&lt;td>medium&lt;/td>
&lt;td>NICs, switching, out-of-band management&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Electricity&lt;/td>
&lt;td>opex&lt;/td>
&lt;td>30–50 % of TCO&lt;/td>
&lt;td>depending on country (FR/ES cheap, DE expensive) and PUE&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cooling&lt;/td>
&lt;td>opex&lt;/td>
&lt;td>inside the PUE&lt;/td>
&lt;td>PUE 1.2–1.6 typical&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Maintenance and support&lt;/td>
&lt;td>opex&lt;/td>
&lt;td>medium&lt;/td>
&lt;td>spares, contracts, RMA&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Staff and operations&lt;/td>
&lt;td>opex&lt;/td>
&lt;td>variable&lt;/td>
&lt;td>the hidden cost that cloud rental includes&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The difference between cloud rental (21.84 €/h in the example) and the real amortised cost
(~10–13 €/h) is this breakdown: the provider charges a margin for absorbing capex,
cooling and operations. The break-even point appears when sustained volume
exceeds &lt;strong>~2M tokens/day&lt;/strong>, with typical amortisation in &lt;strong>6–12 months&lt;/strong>
(&lt;a href="https://www.spheron.network/blog/ai-inference-cost-economics-2026/">Spheron&lt;/a>). Below
that threshold, renting usually wins; above it, amortising. Article A8 develops the
full model and synthesis S3 connects it with sizing.&lt;/p>
&lt;hr>
&lt;h2 id="reference-the-price-of-a-gpu-hour-in-european-sovereign-cloud">Reference: the price of a GPU-hour in European sovereign cloud&lt;/h2>
&lt;p>The other side of the on-prem comparison is the price of renting the GPU. For a
sovereign European platform, what matters is not AWS/GCP/Azure (subject to the &lt;strong>US CLOUD
Act&lt;/strong>), but the European providers:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Provider&lt;/th>
&lt;th>H100 (€/hour)&lt;/th>
&lt;th>Location&lt;/th>
&lt;th>Sovereignty&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Scaleway&lt;/td>
&lt;td>from 2.73&lt;/td>
&lt;td>Paris, Warsaw&lt;/td>
&lt;td>EU jurisdiction, exempt from US CLOUD Act&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OVHcloud&lt;/td>
&lt;td>+40–45 % vs Scaleway&lt;/td>
&lt;td>France, Germany&lt;/td>
&lt;td>EU jurisdiction, exempt from US CLOUD Act&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hyperscalers (reference)&lt;/td>
&lt;td>3–7 USD&lt;/td>
&lt;td>global&lt;/td>
&lt;td>subject to US CLOUD Act&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The data: Scaleway offers the H100 from &lt;strong>2.73 €/h&lt;/strong> in European datacenters (Paris, Warsaw),
with models and data under EU jurisdiction, immune to extraterritorial regulation; OVHcloud
serves it from France and Germany, ~40–45 % more expensive than Scaleway as of March 2026. Both are
&lt;strong>exempt from the US CLOUD Act&lt;/strong>, the foundational sovereignty requirement for data subject to
GDPR (&lt;a href="https://www.scaleway.com/en/h100/">Scaleway&lt;/a>, &lt;a href="https://deploybase.ai/articles/ovhcloud-gpu-pricing">DeployBase&lt;/a>).&lt;/p>
&lt;p>The implication for the proposal: the comparison is not only cost, it is &lt;strong>cost + sovereignty&lt;/strong>. An
on-prem cluster of your own or a European cloud keep the data under EU jurisdiction; a
US hyperscaler does not, however cheap the GPU-hour comes out. That is the leg that the
cost axis on its own does not capture, and that synthesis S2 quantifies.&lt;/p>
&lt;hr>
&lt;h2 id="energy-and-carbon-reference-france-germany-spain">Energy and carbon reference: France, Germany, Spain&lt;/h2>
&lt;p>Here the choice of European country changes both the electricity cost and the carbon per
token. It is the most important table for a sovereign proposal:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Country&lt;/th>
&lt;th>Wholesale (€/MWh, Mar-2026)&lt;/th>
&lt;th>Grid carbon (gCO₂/kWh)&lt;/th>
&lt;th>Profile&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>France&lt;/td>
&lt;td>~58&lt;/td>
&lt;td>~20–60&lt;/td>
&lt;td>nuclear: cheap and very clean&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Spain&lt;/td>
&lt;td>~54&lt;/td>
&lt;td>~150–170&lt;/td>
&lt;td>renewables + gas: cheap, medium carbon&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Germany&lt;/td>
&lt;td>~91&lt;/td>
&lt;td>~363 (2024 average)&lt;/td>
&lt;td>coal/gas + renewables: expensive and dirty&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Sources: day-ahead wholesale prices (&lt;a href="https://tradingeconomics.com/france/electricity-price">TradingEconomics&lt;/a>),
German carbon ~363 gCO₂/kWh (2024 average, &lt;a href="https://en.wikipedia.org/wiki/Electricity_sector_in_Germany">Statista via Wikipedia&lt;/a>),
nuclear ~17–35 gCO₂e/kWh (&lt;a href="https://www.eea.europa.eu/en/analysis/indicators/greenhouse-gas-emission-intensity-of-1">EEA&lt;/a>),
Spain ~167 gCO₂/kWh (2020). The PUE multiplies all of that energy: at 1.4, for every kWh of
GPU the datacenter spends 1.4 kWh with cooling included.&lt;/p>
&lt;p>The calculation, on the example node (0.78 kWh per million tokens, with PUE): in
France (~40 gCO₂/kWh) it is &lt;strong>~31 gCO₂ per million tokens&lt;/strong>; in Germany (~363
gCO₂/kWh), &lt;strong>~283 gCO₂ per million&lt;/strong>, a factor of &lt;strong>~9×&lt;/strong>. That is, the same load
served in France emits of the order of &lt;strong>9× less carbon per token&lt;/strong> than in Germany, and
costs ~37 % less in electricity, &lt;strong>without touching a single line of the stack&lt;/strong>, purely because of the
country&amp;rsquo;s grid. &lt;strong>Siting the cluster in France
or Spain is at once a cost lever, a carbon lever and a compliance lever.&lt;/strong> Hourly grid
intensity is obtained from ElectricityMaps, the reference source for these
calculations.&lt;/p>
&lt;p>Scale context: global datacenters consumed &lt;strong>~460 TWh in 2025&lt;/strong> (≈1.8 % of the world&amp;rsquo;s
electricity), and the IEA projects passing &lt;strong>800 TWh in 2028&lt;/strong>, which is why energy
efficiency per token stops being a detail and becomes a design constraint,
and why carbon enters the European regulatory equation.&lt;/p>
&lt;hr>
&lt;h2 id="sovereignty-and-regulation-the-axis-that-does-not-appear-in-the-cpm">Sovereignty and regulation: the axis that does not appear in the CPM&lt;/h2>
&lt;p>For a European platform there is a fourth factor that the three numerical axes &lt;strong>do not
capture&lt;/strong>, and that can invalidate the lowest cost figure: jurisdiction. Three
fronts that turn &amp;ldquo;the cheapest GPU-hour&amp;rdquo; into &amp;ldquo;the cheapest GPU-hour &lt;em>that complies&lt;/em>&amp;rdquo;:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Front&lt;/th>
&lt;th>What it requires&lt;/th>
&lt;th>Architectural implication&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>GDPR + US CLOUD Act&lt;/td>
&lt;td>personal data under a jurisdiction accessible to US authorities is exposed&lt;/td>
&lt;td>European cloud (Scaleway/OVHcloud) or on-prem keep the data in the EU&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>EU AI Act&lt;/td>
&lt;td>obligations for high-risk systems (traceability, control, documentation)&lt;/td>
&lt;td>on-prem control makes auditability and compliance easier&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CSRD / emissions reporting&lt;/td>
&lt;td>reporting the carbon footprint (scope 2)&lt;/td>
&lt;td>a low-carbon grid (France) improves the reported figure&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The key point for the proposal: a US hyperscaler can offer a competitive
GPU-hour, but it &lt;strong>puts the data under the US CLOUD Act&lt;/strong>, which for data subject to GDPR
is a compliance risk, not a saving. That is why this series&amp;rsquo; comparison is not made
in USD against US providers: it is made in euros, against options that keep
&lt;strong>data sovereignty&lt;/strong>, on-prem of your own or European cloud. Cost, energy and
carbon are measurable axes; sovereignty is the constraint that decides &lt;strong>which options even
enter the table&lt;/strong>. This links with &lt;a href="https://blog.lo0.es/en/posts/eu-ai-act-technical-dossier-on-premise-llm-architecture/">the mapping of the EU AI Act onto the on-premise
architecture&lt;/a> and with &lt;a href="https://blog.lo0.es/en/posts/technical-controls-ens-iso-42001-eu-ai-act-cross-mapping/">the technical
controls ENS × ISO 42001 × EU AI Act&lt;/a>.&lt;/p>
&lt;p>The operational conclusion: &lt;strong>France and Spain not only come out cheaper and cleaner than
Germany; being EU jurisdiction, they meet the sovereignty constraint that a US
hyperscaler does not meet at any price.&lt;/strong> Three advantages that reinforce each other, and that the
proposal can defend with the same table.&lt;/p>
&lt;hr>
&lt;h2 id="methodology-how-to-read-and-not-fool-yourself-with-these-figures">Methodology: how to read (and not fool yourself with) these figures&lt;/h2>
&lt;p>Five warnings that apply to all the data in this series:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Board vs node.&lt;/strong> The GPU&amp;rsquo;s power (700 W per H100) is the floor; the complete
node (CPU, NICs, power supplies, fans) consumes more. Always compare the same boundary.&lt;/li>
&lt;li>&lt;strong>Wholesale vs retail.&lt;/strong> The day-ahead price is not what the company pays; industrial
retail adds grid charges and taxes. Use whichever matches your contract.&lt;/li>
&lt;li>&lt;strong>Measured vs estimated.&lt;/strong> A wattmeter figure and an &lt;code>nvidia-smi&lt;/code> estimate are not
the same thing; the difference is quantified in the energy articles.&lt;/li>
&lt;li>&lt;strong>Amortisation assumptions.&lt;/strong> The on-prem cost/hour depends on the hardware cycle (24 vs
36 months) and on the assumed utilisation; changing those assumptions moves the CPM more than
any serving optimisation.&lt;/li>
&lt;li>&lt;strong>Comparability.&lt;/strong> Two CPM or J/token figures are only comparable if model,
precision, context length, load &lt;strong>and country&lt;/strong> match. Most public
comparisons (and in USD) do not fix those assumptions, which is why the series reproduces the ones that matter
with its own harness and European data.&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="the-combined-scorecard">The combined scorecard&lt;/h2>
&lt;p>The destination of the series is a single board where each model/configuration/location is
scored on all the axes at once, to decide on a &lt;strong>Pareto frontier&lt;/strong> rather than on a
single number. An &lt;strong>illustrative&lt;/strong> example (order-of-magnitude figures) on the same
8×H100 node:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Candidate&lt;/th>
&lt;th>CPM (€/1M)&lt;/th>
&lt;th>Goodput (tok/s)&lt;/th>
&lt;th>Energy (J/tok)&lt;/th>
&lt;th>Carbon (gCO₂/1M)&lt;/th>
&lt;th>Sovereignty&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>A · 70B FP16, on-prem France&lt;/td>
&lt;td>~1.09&lt;/td>
&lt;td>~2,800&lt;/td>
&lt;td>~2.0&lt;/td>
&lt;td>low (grid ~40 gCO₂/kWh)&lt;/td>
&lt;td>EU&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>B · 70B FP8, on-prem France&lt;/td>
&lt;td>~0.75&lt;/td>
&lt;td>~4,000&lt;/td>
&lt;td>~1.4&lt;/td>
&lt;td>lower&lt;/td>
&lt;td>EU&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>C · Scaleway on-demand&lt;/td>
&lt;td>~2.17&lt;/td>
&lt;td>~2,800&lt;/td>
&lt;td>n/a&lt;/td>
&lt;td>low (FR)&lt;/td>
&lt;td>EU&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>D · US hyperscaler&lt;/td>
&lt;td>~3.0&lt;/td>
&lt;td>~2,800&lt;/td>
&lt;td>n/a&lt;/td>
&lt;td>depends on region&lt;/td>
&lt;td>not EU&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The reading: &lt;strong>B dominates A&lt;/strong> on the three intrinsic axes thanks to FP8. C (European cloud) is
more expensive per token but shifts capex and operations, while keeping sovereignty. D
(hyperscaler) may be competitive on cost but &lt;strong>loses sovereignty&lt;/strong>, an axis that for
GDPR data is not negotiable. No isolated number decides: the choice comes from &lt;strong>seeing
the whole row&lt;/strong>.&lt;/p>
&lt;p>The trap the board avoids: &lt;strong>optimising a single axis&lt;/strong>. Each cell will be filled in by an
article in the series with measured data.&lt;/p>
&lt;hr>
&lt;h2 id="where-each-axis-is-measured-from-design-to-production">Where each axis is measured: from design to production&lt;/h2>
&lt;p>Each axis is measured at three different moments, with different tools and increasing
precision. Confusing them is a common source of figures that do not add up:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Axis&lt;/th>
&lt;th>Design (estimate)&lt;/th>
&lt;th>Benchmark (controlled)&lt;/th>
&lt;th>Production (continuous)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Cost&lt;/td>
&lt;td>datasheet, GPU-hour price&lt;/td>
&lt;td>CPM measured over synthetic load&lt;/td>
&lt;td>OpenCost/Kubecost per team and token&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Performance&lt;/td>
&lt;td>theoretical throughput (roofline)&lt;/td>
&lt;td>GuideLLM/AIPerf with SLO&lt;/td>
&lt;td>OTel/Langfuse over real traffic&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy&lt;/td>
&lt;td>TDP × hours&lt;/td>
&lt;td>Zeus/MLPerf Power on the bench&lt;/td>
&lt;td>Kepler/DCGM per pod in the cluster&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The rule: a design figure serves for sizing, but not for defending a
proposal; for that you need a reproducible benchmark figure, and to operate, a
continuous production one. When someone disputes a number in the proposal, the answer is not
&amp;ldquo;a blog says so&amp;rdquo;, but &amp;ldquo;here is the bench, here is the methodology, reproduce it&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h2 id="glossary-of-units-and-conversions">Glossary of units and conversions&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Quantity&lt;/th>
&lt;th>Unit&lt;/th>
&lt;th>Conversion / reference&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Energy&lt;/td>
&lt;td>joule (J), watt-hour (Wh)&lt;/td>
&lt;td>1 Wh = 3,600 J; 1 kWh = 3.6 MJ = 3,600,000 J&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Power&lt;/td>
&lt;td>watt (W)&lt;/td>
&lt;td>1 W = 1 J/s; H100 ≈ 700 W TDP&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy per token&lt;/td>
&lt;td>J/token&lt;/td>
&lt;td>= average power (W) ÷ throughput (tok/s)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Throughput&lt;/td>
&lt;td>tok/s&lt;/td>
&lt;td>tokens generated per second by the system&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cost per token&lt;/td>
&lt;td>€/1M tok (CPM)&lt;/td>
&lt;td>= cost/hour ÷ (tok/s × 3,600 / 10⁶)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Latency&lt;/td>
&lt;td>ms&lt;/td>
&lt;td>TTFT (first token), TPOT/ITL (between tokens)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PUE&lt;/td>
&lt;td>ratio (≥1)&lt;/td>
&lt;td>total DC energy ÷ compute energy&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Carbon&lt;/td>
&lt;td>gCO₂eq&lt;/td>
&lt;td>= energy (kWh) × PUE × grid intensity (gCO₂/kWh)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>A useful conversion to keep in your head: at a constant 700 W, an H100 consumes ~0.7 kWh per hour, ~16.8
kWh a day and ~6,130 kWh a year (board only, no PUE). Multiplied by the PUE and your country&amp;rsquo;s
energy price (FR ~0.058, ES ~0.054, DE ~0.091 €/kWh wholesale), that number is the basis
of the electricity portion of the TCO.&lt;/p>
&lt;hr>
&lt;h2 id="how-the-rest-of-the-series-fits-together">How the rest of the series fits together&lt;/h2>
&lt;p>The 28 articles group into four tracks; this is the map:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Track&lt;/th>
&lt;th>What it contributes&lt;/th>
&lt;th>Closes with&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>FinOps (8)&lt;/td>
&lt;td>measuring and allocating cost, TCO model&lt;/td>
&lt;td>cost/token comparable on-prem vs European cloud&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LLM benchmarking (8)&lt;/td>
&lt;td>measuring performance reproducibly&lt;/td>
&lt;td>Pareto frontier of engines&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy (8)&lt;/td>
&lt;td>measuring watts and carbon per token&lt;/td>
&lt;td>energy in the TCO and in European regulation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Synthesis (4)&lt;/td>
&lt;td>crossing the three axes&lt;/td>
&lt;td>scorecard, on-prem vs cloud, sizing, harness&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The three articles that follow this one in the batch introduce each axis in depth:
their frameworks and the state of the art. From there, each article adds a layer of data
to the final scorecard.&lt;/p>
&lt;p>The thread running through all 28 instalments is the one in this article: &lt;strong>cost, performance and energy
are a single problem with three faces, tied together by throughput and by the energy that lives
inside the cost, and in Europe, by the country where the cluster sits&lt;/strong>. Anyone who optimises one
face while ignoring the others will end up with a pretty number and a platform that does not hold up.
The work of the series is to produce the figures, measured, dated, European and reproducible,
that allow a sovereign architecture to be defended before any committee with the only answer
that closes a technical debate: the data and the bench to reproduce it.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-serving-engines-pareto-frontier-comparison/">Comparison of serving engines (vLLM/SGLang/TRT-LLM/Dynamo)&lt;/a> — the synthesis of the performance axis: which engine wins at each point of the goodput-latency frontier with measured data.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-quality-benchmarks-contamination-trap-oss-tools/">LLM quality benchmarks: MMLU, MT-Bench, HELM and the leaderboards that matter&lt;/a> — the quality dimension that complements performance: how public leaderboards let you calibrate the right model for each SLO.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-energy-leaderboards-wh-per-token/">LLM energy efficiency leaderboards&lt;/a> — the synthesis of the energy axis: the J/token rankings and how to read them without being fooled by the benchmark&amp;rsquo;s setup.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/cloud-gpu-on-demand-reserved-spot-neoclouds-2026-prices/">Cloud GPU: comparison of prices, commitment and sovereign neoclouds&lt;/a> — the cost axis piece for cloud or hybrid architectures: on-demand, spot and reserved prices from European providers in 2026.&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 the on-premise GPU cluster: amortisation, energy and infrastructure&lt;/a> — the cost axis piece for your own architectures: the real €/GPU-hour with CAPEX, energy and operations broken down.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/measurement-bias-reproducibility-llm-benchmarks/">Measurement bias and reproducibility in LLM benchmarks&lt;/a> — the methodological traps that make two benchmarks of the same engine give incompatible results: the basis for trusting this series&amp;rsquo; data.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/watt-to-carbon-pue-grid-intensity-gco2-per-token/">From the watt to carbon: PUE, grid intensity and the real cost of a token&lt;/a> — how to convert the GPU&amp;rsquo;s watts into gCO₂eq and into electricity euros, closing the loop between the energy axis and the cost axis.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/levers-fewer-watts-per-token-llm-inference/">Energy efficiency levers in LLM inference&lt;/a> — the optimisations that move all three axes at once: quantisation, batching and engine as levers that reduce J/token, €/token and improve goodput simultaneously.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/reproducible-harness-cost-performance-energy-experiment/">The reproducible harness for measuring cost, performance and energy&lt;/a> — the test bench that makes this article&amp;rsquo;s methodology concrete: how to produce the data for the three axes with a single reproducible experiment.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>Scaleway · H100 GPU instance (€/h price, EU sovereignty) — &lt;a href="https://www.scaleway.com/en/h100/">https://www.scaleway.com/en/h100/&lt;/a>&lt;/li>
&lt;li>DeployBase · OVHcloud GPU Pricing (European sovereignty) — &lt;a href="https://deploybase.ai/articles/ovhcloud-gpu-pricing">https://deploybase.ai/articles/ovhcloud-gpu-pricing&lt;/a>&lt;/li>
&lt;li>TradingEconomics · electricity price France / Germany / Spain — &lt;a href="https://tradingeconomics.com/france/electricity-price">https://tradingeconomics.com/france/electricity-price&lt;/a>&lt;/li>
&lt;li>EEA · emission intensity of electricity in Europe — &lt;a href="https://www.eea.europa.eu/en/analysis/indicators/greenhouse-gas-emission-intensity-of-1">https://www.eea.europa.eu/en/analysis/indicators/greenhouse-gas-emission-intensity-of-1&lt;/a>&lt;/li>
&lt;li>Electricity sector in Germany (carbon ~363 gCO₂/kWh) — &lt;a href="https://en.wikipedia.org/wiki/Electricity_sector_in_Germany">https://en.wikipedia.org/wiki/Electricity_sector_in_Germany&lt;/a>&lt;/li>
&lt;li>Spheron · AI Inference Power &amp;amp; Cost Economics (2026) — &lt;a href="https://www.spheron.network/blog/ai-inference-cost-economics-2026/">https://www.spheron.network/blog/ai-inference-cost-economics-2026/&lt;/a>&lt;/li>
&lt;li>NVIDIA · H100 (TDP/specs) — &lt;a href="https://www.nvidia.com/en-us/data-center/h100/">https://www.nvidia.com/en-us/data-center/h100/&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Energy benchmarking in AI: frameworks, metrics and state of the art (tool by tool)</title><link>https://blog.lo0.es/en/posts/ai-energy-benchmarking-frameworks-metrics-state-of-the-art/</link><pubDate>Sat, 13 Jun 2026 02:00:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/ai-energy-benchmarking-frameworks-metrics-state-of-the-art/</guid><description>&lt;blockquote>
&lt;p>Notation: amounts in &lt;strong>euros (N €)&lt;/strong>, decimals with a point. The energy and carbon
references are &lt;strong>European&lt;/strong> (France, Germany, Spain), since this is a sovereign
proposal. The dollar sign is not used (on this site it is a formula delimiter).&lt;/p>
&lt;/blockquote>
&lt;h2 id="what-this-introduction-covers">What this introduction covers&lt;/h2>
&lt;p>Fourth article in the data series and &lt;em>deep dive&lt;/em> on the energy axis. Measuring the watts
of an AI workload looks like a sustainability detail, but it is at once a cost lever
(electricity is 30–50 % of TCO), an axis of sovereignty (carbon per token depends on
the country) and, increasingly, a regulatory obligation (emissions reporting). This
article inventories the metrics, where each figure physically comes from, the data sheet
for each measurement framework, and how to go from watt to carbon with European figures. No
recommendations; only data and methodology, because in energy, even more than in
performance, the measurement method changes the result.&lt;/p>
&lt;hr>
&lt;h2 id="why-measuring-energy-matters-with-numbers">Why measuring energy matters (with numbers)&lt;/h2>
&lt;p>Three quantified reasons, connecting with the &lt;a href="https://blog.lo0.es/en/posts/cost-performance-energy-three-axes-llm-inference/">opening article&lt;/a>:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Reason&lt;/th>
&lt;th>Figure&lt;/th>
&lt;th>Implication&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Cost&lt;/strong>&lt;/td>
&lt;td>electricity = 30–50 % of TCO&lt;/td>
&lt;td>measuring energy is measuring almost half the cost&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Sovereignty/carbon&lt;/strong>&lt;/td>
&lt;td>France ~9× fewer gCO₂/token than Germany&lt;/td>
&lt;td>the country of the cluster is a lever&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Scale/regulation&lt;/strong>&lt;/td>
&lt;td>datacenters ~460 TWh in 2025 (≈1.8 % worldwide), &amp;gt;800 TWh projected for 2028&lt;/td>
&lt;td>emissions reporting (CSRD) mandatory&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>At 700 W per H100, an 8-card node draws of the order of 5.6 kW from the boards alone, ~49,000
kWh a year. Applying PUE 1.4 that becomes ~68,700 kWh/year, and there the country decides the
bill: at the French wholesale price (~0.058 €/kWh) that is ~3,980 €/year of electricity per
node; in Germany (~0.091 €/kWh), ~6,250 €/year, 57 % more for the same iron and the same
work. Multiplied across a fleet, it is a first-order cost line, and a carbon footprint that,
in Europe, has to be measured and reported. Energy stopped being an engineering detail and
became a business and compliance variable.&lt;/p>
&lt;hr>
&lt;h2 id="the-energy-metrics">The energy metrics&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Metric&lt;/th>
&lt;th>Definition&lt;/th>
&lt;th>Unit&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Mean power&lt;/td>
&lt;td>average instantaneous consumption&lt;/td>
&lt;td>W&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Energy per token&lt;/strong>&lt;/td>
&lt;td>energy consumed per generated token&lt;/td>
&lt;td>J/token&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy per inference&lt;/td>
&lt;td>energy per request&lt;/td>
&lt;td>µJ – Wh&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Total energy&lt;/td>
&lt;td>integral of power over time&lt;/td>
&lt;td>Wh, kWh&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>PUE&lt;/strong> (Power Usage Effectiveness)&lt;/td>
&lt;td>datacenter overhead (cooling, losses)&lt;/td>
&lt;td>ratio ≥ 1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Carbon&lt;/td>
&lt;td>energy × grid intensity&lt;/td>
&lt;td>gCO₂eq&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The two base identities of the whole axis:&lt;/p>
$$\text{energy per token (J)} = \frac{\text{mean power (W)}}{\text{throughput (tok/s)}}$$
$$\text{carbon} = \text{energy (kWh)} \times \text{PUE} \times \text{grid intensity (gCO}_2\text{/kWh)}$$
&lt;p>The first has the same shape as cost per token: it shares the throughput denominator, so
raising tokens/s lowers energy per token. The second brings in the two multipliers
external to the compute, the datacenter PUE and the country&amp;rsquo;s grid intensity, which
can change carbon per token by an order of magnitude without touching the stack.&lt;/p>
&lt;hr>
&lt;h2 id="worked-example-energy-per-token-of-an-8h100-node">Worked example: energy per token of an 8×H100 node&lt;/h2>
&lt;p>To anchor the identities with numbers, the end-to-end calculation on the example node
(8×H100, Llama 3.1 70B FP16, vLLM at 2,800 tok/s):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Step&lt;/th>
&lt;th>Calculation&lt;/th>
&lt;th>Result&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Board power&lt;/td>
&lt;td>8 × 700 W&lt;/td>
&lt;td>5,600 W&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy per token (boards)&lt;/td>
&lt;td>5,600 ÷ 2,800&lt;/td>
&lt;td>2.0 J/token&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy per token (with PUE 1.4)&lt;/td>
&lt;td>2.0 × 1.4&lt;/td>
&lt;td>2.8 J/token&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy per 1M tokens (boards)&lt;/td>
&lt;td>2 × 10⁶ J&lt;/td>
&lt;td>0.56 kWh&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Energy per 1M tokens (with PUE)&lt;/td>
&lt;td>0.56 × 1.4&lt;/td>
&lt;td>0.78 kWh&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>From there come the electricity cost and the carbon, by country:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Country&lt;/th>
&lt;th>Electricity cost / 1M tok&lt;/th>
&lt;th>Carbon / 1M tok&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>France (0.058 €/kWh; ~40 gCO₂/kWh)&lt;/td>
&lt;td>~0.045 €&lt;/td>
&lt;td>~31 gCO₂&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Spain (0.054 €/kWh; ~160 gCO₂/kWh)&lt;/td>
&lt;td>~0.042 €&lt;/td>
&lt;td>~125 gCO₂&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Germany (0.091 €/kWh; ~363 gCO₂/kWh)&lt;/td>
&lt;td>~0.071 €&lt;/td>
&lt;td>~283 gCO₂&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>And the link with performance: if an optimisation (FP8, say) raised throughput to
4,200 tok/s, energy per token would drop to ~1.33 J/token (boards) and everything below it in
the same proportion. Electricity cost and carbon per token fall with throughput, by the
identity. Note: these are board + PUE figures; the complete node (CPU, NICs, PSUs)
consumes somewhat more, and energy measured with a power meter can differ from energy
estimated with &lt;code>nvidia-smi&lt;/code>.&lt;/p>
&lt;hr>
&lt;h2 id="where-the-figure-comes-from-the-measurement-layers">Where the figure comes from: the measurement layers&lt;/h2>
&lt;p>Not every energy figure is equally reliable. The figure can come from four layers, with
increasing accuracy and decreasing context:&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 240" role="img" aria-label="Layers the energy figure comes from: GPU counters NVML/DCGM, CPU RAPL, eBPF with models, and a physical power meter as the reference" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.3}.tl{font:600 12px sans-serif;fill:currentColor}.ts{font:11px sans-serif;fill:currentColor}&lt;/style>
&lt;rect class="bx" x="20" y="36" width="360" height="42" rx="6"/>
&lt;text x="32" y="55" class="tl">GPU — NVML / nvidia-smi / DCGM&lt;/text>
&lt;text x="32" y="71" class="ts">card power from a hardware counter&lt;/text>
&lt;rect class="bx" x="20" y="90" width="360" height="42" rx="6"/>
&lt;text x="32" y="109" class="tl">CPU/DRAM — RAPL&lt;/text>
&lt;text x="32" y="125" class="ts">Intel/AMD counters via /proc, perf&lt;/text>
&lt;rect class="bx" x="20" y="144" width="360" height="42" rx="6"/>
&lt;text x="32" y="163" class="tl">Per-process/pod attribution — eBPF + model&lt;/text>
&lt;text x="32" y="179" class="ts">splits the watt between workloads (Kepler)&lt;/text>
&lt;rect class="bx" x="410" y="90" width="350" height="42" rx="6"/>
&lt;text x="422" y="109" class="tl">Physical power meter (reference)&lt;/text>
&lt;text x="422" y="125" class="ts">the ground truth everything is calibrated against&lt;/text>
&lt;text x="410" y="165" class="ts">The higher the layer, the more context (carbon) and&lt;/text>
&lt;text x="410" y="181" class="ts">the less direct accuracy; the meter is the standard.&lt;/text>
&lt;text x="20" y="216" class="ts">The GPU dominates in LLM inference; RAPL covers CPU/DRAM; eBPF splits per pod; the meter calibrates.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>The GPU is the dominant source in LLM inference, and its power is read from a hardware
counter (NVML/nvidia-smi, aggregated by DCGM, the same basis as
&lt;a href="https://blog.lo0.es/en/posts/gpu-observability-llm-inference-twelve-dcgm-vllm-metrics/">GPU observability&lt;/a>). RAPL gives CPU and
DRAM. The eBPF + model layer (Kepler) splits the total watt between containers and pods
without instrumenting the application. And the physical power meter is the ground truth
against which estimates are calibrated: no software estimate is better than the meter, only
more convenient.&lt;/p>
&lt;hr>
&lt;h2 id="how-it-is-instrumented-and-where-the-error-creeps-in">How it is instrumented (and where the error creeps in)&lt;/h2>
&lt;p>Four instrumentation decisions explain most of the discrepancies between energy
measurements:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Sampling rate.&lt;/strong> &lt;code>nvidia-smi&lt;/code>/DCGM read power at intervals; sampling that is too coarse
misses the peaks and underestimates the energy. It must be fine enough to capture the
power variation between prefill (high) and decode (lower).&lt;/li>
&lt;li>&lt;strong>Board vs node.&lt;/strong> GPU (board) power does not include CPU, NICs, fans or PSU losses
(typically an extra 10–20 %). Comparing a board energy figure with a complete-node one is
comparing different things.&lt;/li>
&lt;li>&lt;strong>Idle baseline.&lt;/strong> A powered GPU with no work already draws power. Is that idle attributed
to the workload or discounted? The decision changes energy per token, and idle matters: it
is real energy that someone pays for.&lt;/li>
&lt;li>&lt;strong>Time window.&lt;/strong> Energy is the integral of power over time; the measurement window has
to line up exactly with the workload (without counting warm-up or shutdown), or the number
does not correspond to the work measured.&lt;/li>
&lt;/ul>
&lt;p>These decisions, as in performance benchmarking, mean that &lt;strong>the measurement method matters
as much as the system measured&lt;/strong>. A J/token without a stated sampling rate, board/node
boundary and idle treatment is not comparable.&lt;/p>
&lt;hr>
&lt;h2 id="frameworks-tool-by-tool">Frameworks, tool by tool&lt;/h2>
&lt;h3 id="zeus-mlenergy-university-of-michigan">Zeus (ml.energy, University of Michigan)&lt;/h3>
&lt;p>What it does: measure and optimise the energy of deep learning workloads. Method: NVML
counters. Scope: NVIDIA and AMD GPUs, CPU, DRAM, Apple Silicon and NVIDIA Jetson. Licence:
OSS; academic backing (NSDI'23 paper). Differentiator: it does not only measure, it also
optimises (power capping, frequency selection) to cut energy without significant loss of
performance (&lt;a href="https://ml.energy/zeus/">Zeus Project&lt;/a>, &lt;a href="https://www.usenix.org/system/files/nsdi23-you.pdf">NSDI'23&lt;/a>).
It is the reference tool when the goal is to lower J/token, not just to measure them.&lt;/p>
&lt;h3 id="codecarbon">CodeCarbon&lt;/h3>
&lt;p>What it does: estimates the energy and the CO₂eq of compute processes. Method: reads the
GPU with &lt;code>nvidia-smi&lt;/code> and estimates CPU/RAM. Scope: CPU + GPU + RAM. Licence: OSS (Python).
Differentiator: it integrates into ML flows and CI pipelines, and it is the one that &lt;strong>comes
closest to the power meter&lt;/strong> among the Python tools (&lt;a href="https://medium.com/@prhmma/best-green-code-tools-july-2025-mainly-python-7ab415af0cd6">comparison&lt;/a>).
It brings carbon context out of the box. Limit: a high-level estimate, less exact than direct
hardware measurement.&lt;/p>
&lt;h3 id="carbontracker">CarbonTracker&lt;/h3>
&lt;p>What it does: tracks energy and carbon at GPU run time. Scope: GPU only, it measures
neither CPU nor memory. Licence: OSS. Differentiator: lightweight, easy to add to a
training/inference script. Limit: partial coverage (no CPU/DRAM), so it undercounts the total
system energy.&lt;/p>
&lt;h3 id="scaphandre">Scaphandre&lt;/h3>
&lt;p>What it does: a power metrology agent written in Rust. Method: RAPL (reads &lt;code>/proc&lt;/code>,
splits joules by CPU ticks). Scope: process or VM, with the &lt;strong>raw accuracy of the hardware
layer&lt;/strong>. Licence: OSS. Differentiator: the highest direct accuracy among the general-purpose
tools, at the cost of bringing no carbon context (it is pure metrology)
(&lt;a href="https://github.com/hubblo-org/scaphandre">Scaphandre · GitHub&lt;/a>). Limit: focused on CPU/RAPL;
for GPU it has to be combined with NVML.&lt;/p>
&lt;h3 id="kepler-cncf">Kepler (CNCF)&lt;/h3>
&lt;p>What it does: a Prometheus exporter that measures energy at container, pod and node level
in Kubernetes. Method: eBPF + ML models to estimate consumption per workload. Language:
Go + C. Licence: Apache 2.0 (the &lt;code>sustainable-computing-io&lt;/code> project) (&lt;a href="https://github.com/sustainable-computing-io/kepler">Kepler · GitHub&lt;/a>).
Differentiator: it is the cloud-native option for attributing energy per pod without
instrumenting the app, just as OpenCost attributes cost. Limit: attribution via eBPF + model
is estimation, not direct measurement; its accuracy depends on the model.&lt;/p>
&lt;h3 id="mlperf-power-mlcommons">MLPerf Power (MLCommons)&lt;/h3>
&lt;p>What it does: a standardised benchmark of energy efficiency, from µW to MW.
Maintainer: MLCommons. Differentiator: cross-vendor comparability of efficiency, with a
dataset spanning several years and workload versions (&lt;a href="https://arxiv.org/html/2410.12032v2">MLPerf Power, arXiv 2410.12032&lt;/a>).
It is to energy what MLPerf Inference is to performance: the comparison standard between
vendors.&lt;/p>
&lt;h3 id="comparison-table">Comparison table&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Tool&lt;/th>
&lt;th>Method&lt;/th>
&lt;th>Scope&lt;/th>
&lt;th>Licence&lt;/th>
&lt;th>Accuracy / note&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Zeus&lt;/strong>&lt;/td>
&lt;td>NVML / counters&lt;/td>
&lt;td>NV+AMD GPU, CPU, DRAM, Apple, Jetson&lt;/td>
&lt;td>OSS&lt;/td>
&lt;td>measures &lt;strong>and optimises&lt;/strong> DL energy&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>CodeCarbon&lt;/strong>&lt;/td>
&lt;td>nvidia-smi + estimation&lt;/td>
&lt;td>CPU+GPU+RAM, CO₂eq&lt;/td>
&lt;td>OSS (Python)&lt;/td>
&lt;td>the &lt;strong>closest to the power meter&lt;/strong> among Python tools&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>CarbonTracker&lt;/strong>&lt;/td>
&lt;td>GPU runtime&lt;/td>
&lt;td>&lt;strong>GPU only&lt;/strong>&lt;/td>
&lt;td>OSS&lt;/td>
&lt;td>lightweight; no CPU/mem&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Scaphandre&lt;/strong>&lt;/td>
&lt;td>RAPL (/proc)&lt;/td>
&lt;td>process/VM&lt;/td>
&lt;td>OSS (Rust)&lt;/td>
&lt;td>raw accuracy; no carbon context&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Kepler&lt;/strong>&lt;/td>
&lt;td>eBPF + model&lt;/td>
&lt;td>container/pod/node (K8s)&lt;/td>
&lt;td>Apache 2.0 (CNCF)&lt;/td>
&lt;td>cloud-native; model-based estimation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>MLPerf Power&lt;/strong>&lt;/td>
&lt;td>standardised measurement&lt;/td>
&lt;td>µW to MW&lt;/td>
&lt;td>MLCommons&lt;/td>
&lt;td>cross-vendor comparability&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;hr>
&lt;h2 id="accuracy-who-to-believe">Accuracy: who to believe&lt;/h2>
&lt;p>The difference between tools is not a nuance: Scaphandre offers the raw accuracy of the
hardware layer (RAPL), while the Python ones such as CodeCarbon give higher-level,
carbon-aware estimates, with more context but less direct exactness; among the Python tools,
CodeCarbon is the one that comes closest to the power meter, followed by CarbonTracker, with
variability across infrastructures (&lt;a href="https://medium.com/@prhmma/best-green-code-tools-july-2025-mainly-python-7ab415af0cd6">comparison&lt;/a>).
The rule: for sizing and design, a software estimate will do; to &lt;strong>defend a figure before
a committee or an auditor, calibrate against a power meter&lt;/strong> at least once, and use the
software tool as a continuous proxy. The measured figure beats the estimated one; the rest is
convenience.&lt;/p>
&lt;hr>
&lt;h2 id="energy-leaderboards-and-benchmarks">Energy leaderboards and benchmarks&lt;/h2>
&lt;p>To compare efficiency between models without building your own test bench:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Resource&lt;/th>
&lt;th>What it provides&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>MLPerf Power&lt;/strong>&lt;/td>
&lt;td>standardised efficiency measurement, from µW to MW&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>HF AI Energy Score&lt;/strong>&lt;/td>
&lt;td>comparable per-model energy efficiency ratings (&lt;a href="https://huggingface.github.io/AIEnergyScore/">HF&lt;/a>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ML.ENERGY Benchmark&lt;/strong>&lt;/td>
&lt;td>automated measurement and optimisation of inference energy&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>awesome-green-ai&lt;/strong>&lt;/td>
&lt;td>curated index of Green AI resources and tools (&lt;a href="https://github.com/samuelrince/awesome-green-ai">repo&lt;/a>)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>These resources give the order of magnitude of the energy per token of each model, useful for
choosing a model on efficiency at the design stage. But they do not replace measurement
on your hardware and your workload: a leaderboard measures on one specific bench, not on your
node.&lt;/p>
&lt;hr>
&lt;h2 id="from-watt-to-carbon-france-germany-spain">From watt to carbon: France, Germany, Spain&lt;/h2>
&lt;p>Here the energy axis turns into a sovereignty argument. Carbon per token comes from
multiplying the energy by the country&amp;rsquo;s grid intensity, which in Europe varies by almost
two orders of magnitude:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Country&lt;/th>
&lt;th>Wholesale (€/MWh, Mar 2026)&lt;/th>
&lt;th>Grid carbon (gCO₂/kWh)&lt;/th>
&lt;th>Profile&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>France&lt;/strong>&lt;/td>
&lt;td>~58&lt;/td>
&lt;td>&lt;strong>~20–60&lt;/strong>&lt;/td>
&lt;td>nuclear: cheap and very clean&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Spain&lt;/strong>&lt;/td>
&lt;td>~54&lt;/td>
&lt;td>&lt;strong>~150–170&lt;/strong>&lt;/td>
&lt;td>renewables + gas: cheap, medium carbon&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Germany&lt;/strong>&lt;/td>
&lt;td>~91&lt;/td>
&lt;td>&lt;strong>~363&lt;/strong> (2024 average)&lt;/td>
&lt;td>coal/gas + renewables: expensive and dirty&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Sources: wholesale prices (&lt;a href="https://tradingeconomics.com/france/electricity-price">TradingEconomics&lt;/a>),
Germany&amp;rsquo;s carbon ~363 gCO₂/kWh (2024 average, &lt;a href="https://en.wikipedia.org/wiki/Electricity_sector_in_Germany">Wikipedia&lt;/a>),
nuclear ~17–35 gCO₂e/kWh (&lt;a href="https://www.eea.europa.eu/en/analysis/indicators/greenhouse-gas-emission-intensity-of-1">EEA&lt;/a>),
Spain ~167 gCO₂/kWh (2020). Hourly intensity comes from ElectricityMaps.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1rem auto;">
&lt;svg viewBox="0 0 780 230" role="img" aria-label="From watt to carbon by European country: the same energy per token multiplied by the grid intensity of France, Spain or Germany gives very different carbon per token" xmlns="http://www.w3.org/2000/svg">
&lt;style>.bx{fill:none;stroke:currentColor;stroke-width:1.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(#em)}&lt;/style>
&lt;defs>&lt;marker id="em" 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="90" width="190" height="50" rx="6"/>
&lt;text x="32" y="111" class="tl">0.78 kWh / 1M tokens&lt;/text>
&lt;text x="32" y="128" class="ts">(energy with PUE)&lt;/text>
&lt;path class="ar" d="M210,100 L300,55"/>
&lt;path class="ar" d="M210,115 L300,115"/>
&lt;path class="ar" d="M210,130 L300,175"/>
&lt;rect class="bx" x="300" y="36" width="200" height="40" rx="6"/>
&lt;text x="312" y="56" class="tl">France (~40 gCO₂/kWh)&lt;/text>
&lt;text x="312" y="70" class="ts">→ ~31 gCO₂ / 1M tokens&lt;/text>
&lt;rect class="bx" x="300" y="95" width="200" height="40" rx="6"/>
&lt;text x="312" y="115" class="tl">Spain (~160 gCO₂/kWh)&lt;/text>
&lt;text x="312" y="129" class="ts">→ ~125 gCO₂ / 1M tokens&lt;/text>
&lt;rect class="bx" x="300" y="154" width="200" height="40" rx="6"/>
&lt;text x="312" y="174" class="tl">Germany (~363 gCO₂/kWh)&lt;/text>
&lt;text x="312" y="188" class="ts">→ ~283 gCO₂ / 1M tokens&lt;/text>
&lt;text x="540" y="110" class="ts">Same load, same iron:&lt;/text>
&lt;text x="540" y="128" class="ts">~9× more carbon in Germany&lt;/text>
&lt;text x="540" y="146" class="ts">than in France, just from the grid.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>The calculation, on the example node (0.78 kWh per million tokens, with PUE): in France
(~40 gCO₂/kWh) it is ~31 gCO₂ per million tokens; in Germany (~363 gCO₂/kWh), &lt;strong>~283
gCO₂, a factor of ~9×, without touching a line of the stack. Placing the cluster in
France or Spain is at once a cost lever, a carbon lever and a compliance lever&lt;/strong> (&lt;a href="https://blog.lo0.es/en/posts/eu-ai-act-technical-dossier-on-premise-llm-architecture/">EU AI Act&lt;/a>,
&lt;a href="https://blog.lo0.es/en/posts/technical-controls-ens-iso-42001-eu-ai-act-cross-mapping/">controls ENS × 42001 × EU AI Act&lt;/a>).&lt;/p>
&lt;hr>
&lt;h2 id="energy-efficiency-levers">Energy efficiency levers&lt;/h2>
&lt;p>Once it is measured, it can be brought down. The levers, ordered by the identity J/token = W ÷
throughput (lower the numerator or raise the denominator):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Lever&lt;/th>
&lt;th>Mechanism&lt;/th>
&lt;th>Effect&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Quantisation (FP8/INT4)&lt;/strong>&lt;/td>
&lt;td>more tokens per GPU-hour, less VRAM/KV&lt;/td>
&lt;td>raises throughput → lowers J/token&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Batching / continuous batching&lt;/strong>&lt;/td>
&lt;td>amortises the fixed power cost&lt;/td>
&lt;td>raises throughput → lowers J/token&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Power capping&lt;/strong> (Zeus)&lt;/td>
&lt;td>limits GPU power&lt;/td>
&lt;td>lowers W with little loss of throughput&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Efficiency-aware scheduling&lt;/strong>&lt;/td>
&lt;td>consolidate workloads, power down idle GPUs&lt;/td>
&lt;td>eliminates idle consumption&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Location (country)&lt;/strong>&lt;/td>
&lt;td>cleaner grid&lt;/td>
&lt;td>lowers carbon per token&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Avoiding over-reasoning&lt;/strong>&lt;/td>
&lt;td>fewer reasoning tokens that add nothing&lt;/td>
&lt;td>less compute → less energy&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Quantisation (see &lt;a href="https://blog.lo0.es/en/posts/quantization-llm-inference-fp8-int4-gguf/">quantisation for inference&lt;/a>)
is the highest-return lever because it moves all three axes at once: it raises throughput,
lowers cost and lowers energy per token. Zeus&amp;rsquo;s power capping is energy-specific: it
trims peak power with little performance loss, lowering the W in the numerator. And idle,
the GPU powered but not working, is pure wasted energy that scheduling recovers.&lt;/p>
&lt;hr>
&lt;h2 id="from-design-to-production-where-energy-is-measured">From design to production: where energy is measured&lt;/h2>
&lt;p>As with the other axes, energy is measured at three moments, with different tools and
increasing accuracy:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Moment&lt;/th>
&lt;th>Tool&lt;/th>
&lt;th>What it gives&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Design&lt;/strong>&lt;/td>
&lt;td>TDP × hours (datasheet)&lt;/td>
&lt;td>ceiling estimate for sizing&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Benchmark&lt;/strong>&lt;/td>
&lt;td>Zeus, MLPerf Power, power meter&lt;/td>
&lt;td>energy per token measured on a controlled bench&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Production&lt;/strong>&lt;/td>
&lt;td>Kepler (eBPF), DCGM&lt;/td>
&lt;td>continuous per-pod consumption in the cluster&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The design figure (TDP) serves to size the utility feed and the cooling, but it
overestimates (the GPU is rarely at TDP 100 % of the time). The benchmark figure is the
one used for the proposal&amp;rsquo;s cost and carbon per token, because it is measured and
reproducible. And the production figure (Kepler/DCGM) closes the loop: it checks that real
consumption matches the budget and feeds the footprint report. The three have to be traceable
to each other, which is what the harness in article S4 does.&lt;/p>
&lt;hr>
&lt;h2 id="energy-in-tco-and-european-regulation">Energy in TCO and European regulation&lt;/h2>
&lt;p>The closing of the axis: energy is not only sustainability, it is cost and compliance.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Cost&lt;/strong>: being 30–50 % of TCO, every J/token improvement translates directly into euros. In
the example, moving from Germany (~0.091 €/kWh) to France (~0.058 €/kWh) lowers the
electricity share by ~37 % on top of the carbon.&lt;/li>
&lt;li>&lt;strong>Regulation (CSRD)&lt;/strong>: European companies must report their carbon footprint (scope 2).
A low-carbon grid (France) directly improves that reported figure, and measuring energy per
token stops being optional and becomes a reporting requirement.&lt;/li>
&lt;li>&lt;strong>EU AI Act&lt;/strong>: efficiency and energy consumption form part of the documentation expected of
AI systems, which makes energy measurement part of the technical file.&lt;/li>
&lt;/ul>
&lt;p>One reporting nuance worth knowing: the electricity footprint is scope 2 (indirect
emissions from purchased energy), and it can be accounted for by location (the real
intensity of the country&amp;rsquo;s grid) or by market (according to the guarantees of origin
contracted). Location-based accounting is the one that genuinely rewards putting the cluster
on a clean grid such as the French one; market-based accounting allows green energy to be
&amp;ldquo;bought&amp;rdquo; through certificates. Knowing which one your CSRD report requires changes which lever
(physical location vs contract) lowers the figure, and both start from measuring energy
per token, which is what this axis provides.&lt;/p>
&lt;p>In other words: measuring energy covers at once a first-order cost line, the carbon and
sovereignty argument, and a regulatory obligation. For a sovereign European proposal, the
energy axis is not the &amp;ldquo;nice sustainability bit&amp;rdquo;: it is one of the three columns with a number
attached.&lt;/p>
&lt;hr>
&lt;h2 id="checklist-for-a-reproducible-energy-measurement">Checklist for a reproducible energy measurement&lt;/h2>
&lt;p>For an energy or carbon figure to be defensible before a committee or an auditor, it has to
come with its context. The minimum to fix and publish:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>What to fix&lt;/th>
&lt;th>Why&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Tool + version&lt;/td>
&lt;td>each layer (RAPL, NVML, eBPF) and tool measures differently&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sampling rate&lt;/td>
&lt;td>coarse sampling misses peaks&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Board vs node boundary&lt;/td>
&lt;td>±10–20 % depending on whether CPU/PSUs are included&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PUE used&lt;/td>
&lt;td>it multiplies all the compute energy&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Source and time of the grid intensity&lt;/td>
&lt;td>carbon varies ~9× by country and by hour&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Idle treatment&lt;/td>
&lt;td>attributed or discounted changes the J/token&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Model, precision and workload&lt;/td>
&lt;td>energy depends on what is served and how&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Calibration vs power meter&lt;/td>
&lt;td>at least once, to validate the estimate&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The rule: if you cannot hand over this table alongside the gCO₂/token figure, the figure is
not auditable. The reproducible harness of article S4 records all these parameters so that the
energy figure is as traceable as the cost and performance ones, and so that the reported
footprint (CSRD) holds up under audit.&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>LLM-specific resolution is missing&lt;/strong>: general-purpose tools measure well at machine or
process level, but they lack LLM-specific resolution (energy per token, per prefill/decode
phase), and the simulators lack emissions modelling, an open gap in the field.&lt;/li>
&lt;li>&lt;strong>eBPF as a trend&lt;/strong> (Kepler) for attributing energy per pod without instrumenting the app,
in parallel to how OpenCost attributes cost.&lt;/li>
&lt;li>&lt;strong>Standardisation&lt;/strong> of comparison via MLPerf Power and HF AI Energy Score.&lt;/li>
&lt;li>&lt;strong>Optimisation, not only measurement&lt;/strong> (Zeus): the field is moving from &amp;ldquo;how much do I spend&amp;rdquo;
to &amp;ldquo;how do I spend less without losing performance&amp;rdquo;.&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>Estimation vs measurement.&lt;/strong> An &lt;code>nvidia-smi&lt;/code> figure is not a power meter. Calibrate
against hardware at least once before defending a number.&lt;/li>
&lt;li>&lt;strong>Partial coverage.&lt;/strong> CarbonTracker measures GPU only; Scaphandre, mostly CPU/RAPL. Add the
layers or you undercount the total energy.&lt;/li>
&lt;li>&lt;strong>Forgetting PUE.&lt;/strong> Board energy does not include cooling or losses; without PUE you
underestimate the real consumption and carbon.&lt;/li>
&lt;li>&lt;strong>Carbon without a country.&lt;/strong> A gCO₂/token without a stated grid (and hour) means nothing:
it varies ~9× between France and Germany.&lt;/li>
&lt;li>&lt;strong>Confusing energy with sustainability.&lt;/strong> Here it is cost and compliance, with a number;
treating it as a &amp;ldquo;green&amp;rdquo; extra is losing one of the three columns of the proposal.&lt;/li>
&lt;/ol>
&lt;p>That closes the run of introductions: cost, performance and energy, each with its frameworks
and its methodology. The rest of the series goes tool by tool through to the dashboard that
underpins the sovereign architecture decision, with European figures, measured and
reproducible.&lt;/p>
&lt;h2 id="closing">Closing&lt;/h2>
&lt;p>Energy is the axis easiest to dismiss as &amp;ldquo;the green one&amp;rdquo; and the one that hides the most: it
is almost half the cost, it defines the footprint Europe requires to be reported, and its
carbon per token changes ~9× depending on whether the cluster sits in France or in Germany.
Measuring it properly, with the right layer, the right sampling, PUE included and the
country&amp;rsquo;s grid, turns a soft sustainability argument into a hard column of the proposal, with
a number and a source. And the sovereign conclusion stands on its own with the data in this
article: &lt;strong>an inference platform in France or Spain is at once cheaper, cleaner and compliant
with European jurisdiction than the same platform on a US hyperscaler&lt;/strong>, and that is
demonstrated with measured J/token, gCO₂/kWh by country and euros, not with intentions. The
energy axis does not decorate the proposal: it closes it.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-energy-leaderboards-wh-per-token/">LLM energy efficiency leaderboards&lt;/a> — the J/token rankings where the numbers these tools measure land: which leaderboard to use, how to read it and what biases it carries.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/watt-to-carbon-pue-grid-intensity-gco2-per-token/">From watt to carbon: PUE, grid intensity and the real cost of a token&lt;/a> — how to convert the J/token measured with Zeus or Kepler into gCO₂eq using the datacenter PUE and the country&amp;rsquo;s electricity grid intensity.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/levers-fewer-watts-per-token-llm-inference/">Energy efficiency levers in LLM inference&lt;/a> — which configuration changes reduce the J/token these tools report: quantization, batching, engine, KV cache precision.&lt;/li>
&lt;/ul>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>Zeus Project (ml.energy, UMich) — &lt;a href="https://ml.energy/zeus/">https://ml.energy/zeus/&lt;/a>&lt;/li>
&lt;li>Zeus · USENIX NSDI'23 — &lt;a href="https://www.usenix.org/system/files/nsdi23-you.pdf">https://www.usenix.org/system/files/nsdi23-you.pdf&lt;/a>&lt;/li>
&lt;li>Kepler (CNCF, eBPF) · GitHub — &lt;a href="https://github.com/sustainable-computing-io/kepler">https://github.com/sustainable-computing-io/kepler&lt;/a>&lt;/li>
&lt;li>Scaphandre · GitHub — &lt;a href="https://github.com/hubblo-org/scaphandre">https://github.com/hubblo-org/scaphandre&lt;/a>&lt;/li>
&lt;li>CarbonTracker · GitHub — &lt;a href="https://github.com/saintslab/carbontracker">https://github.com/saintslab/carbontracker&lt;/a>&lt;/li>
&lt;li>MLPerf Power (arXiv 2410.12032) — &lt;a href="https://arxiv.org/html/2410.12032v2">https://arxiv.org/html/2410.12032v2&lt;/a>&lt;/li>
&lt;li>HF AI Energy Score — &lt;a href="https://huggingface.github.io/AIEnergyScore/">https://huggingface.github.io/AIEnergyScore/&lt;/a>&lt;/li>
&lt;li>awesome-green-ai — &lt;a href="https://github.com/samuelrince/awesome-green-ai">https://github.com/samuelrince/awesome-green-ai&lt;/a>&lt;/li>
&lt;li>TradingEconomics · precio electricidad Francia/Alemania/España — &lt;a href="https://tradingeconomics.com/france/electricity-price">https://tradingeconomics.com/france/electricity-price&lt;/a>&lt;/li>
&lt;li>EEA · intensidad de emisiones de la electricidad en Europa — &lt;a href="https://www.eea.europa.eu/en/analysis/indicators/greenhouse-gas-emission-intensity-of-1">https://www.eea.europa.eu/en/analysis/indicators/greenhouse-gas-emission-intensity-of-1&lt;/a>&lt;/li>
&lt;li>Electricity sector in Germany (carbono ~363 gCO₂/kWh) — &lt;a href="https://en.wikipedia.org/wiki/Electricity_sector_in_Germany">https://en.wikipedia.org/wiki/Electricity_sector_in_Germany&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Taking RAG to the CPU: separating the data plane from the generation plane</title><link>https://blog.lo0.es/en/posts/rag-cpu-data-plane-generation-plane/</link><pubDate>Thu, 11 Jun 2026 03:20:00 +0000</pubDate><guid>https://blog.lo0.es/en/posts/rag-cpu-data-plane-generation-plane/</guid><description>&lt;blockquote>
&lt;p>Third piece in an operational series about squeezing a generic on-premise LLM cluster of &lt;strong>4×H100 SXM 80 GB&lt;/strong>. Its siblings: &lt;a href="https://blog.lo0.es/en/posts/sharing-one-gpu-time-slicing-mps-mig/">sharing one GPU between workloads&lt;/a> (time-slicing, MPS, MIG) and &lt;a href="https://blog.lo0.es/en/posts/serving-several-models-single-gpu-co-residency-swapping-sleep-mode/">serving several models on one GPU&lt;/a> (swap + sleep) attack the split &lt;em>inside&lt;/em> the GPU. This one attacks the split &lt;em>outside&lt;/em>: which parts of RAG never need to touch the GPU at all. The closing piece of the series, the end-to-end sovereign assistant (fourth instalment, in preparation), assembles the complete system where these pieces fit together.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>A RAG system is not one thing, it is three phases with opposing compute profiles, and putting them all on the GPU &amp;ldquo;because it is AI&amp;rdquo; is an allocation mistake. (1) &lt;strong>Build/ingest&lt;/strong>, embedding the corpus and building the index, is batch, throughput-bound work with no latency SLA: its natural home is the CPU. (2) &lt;strong>Query-time retrieval&lt;/strong>, embedding the query, HNSW search, RRF fusion, lightweight rerank, is mostly CPU, with caveats only for heavy reranking; vector search always ran on CPU, even in stacks sold as &amp;ldquo;GPU&amp;rdquo;. (3) &lt;strong>Generation&lt;/strong>, the LLM producing the answer, is latency-bound, and there the GPU is irreplaceable: a 7B on CPU gives a &lt;em>time to first token&lt;/em> of seconds, unacceptable for chat. The technical key to why (1) and (2) fit on CPU: the embedder is not an LLM. &lt;code>bge-m3&lt;/code> is &lt;strong>around 568M parameters&lt;/strong> (an XLM-RoBERTa encoder), not 7B+; in int8 it occupies about 580 MB and activates fast integer compute paths (Intel &lt;strong>AVX-512 + VNNI + AMX&lt;/strong> on 4th-gen Xeon and later; &lt;strong>NEON SDOT/UDOT&lt;/strong> on ARM). The runtimes are ready: &lt;strong>TEI&lt;/strong> with a CPU backend (same OpenAI API &lt;code>/v1/embeddings&lt;/code> and &lt;code>/rerank&lt;/code>), Qdrant&amp;rsquo;s &lt;strong>fastembed&lt;/strong> (ONNX-CPU), &lt;code>bge-m3&lt;/code> in ONNX int8 with its three heads (dense/sparse/ColBERT). The Intel + Hugging Face blog post with Optimum Intel and fastRAG reports up to around 10× on indexing for BGE-large int8 on a 4th-gen Xeon (their benchmark figure, encoding-only; I quote it and qualify it below). The operational conclusion: &lt;strong>separate the data plane (CPU) from the generation plane (GPU)&lt;/strong>. In the 4×H100 cluster, no H100 should be spent re-indexing a corpus that changes once a day. That goes to the generic CPU fleet (Xeon AMX, NUCs), and the H100s are reserved for generation and, at most, for rerank peaks or large 7B embedders. What does not go down to CPU: interactive generation, massive reranking at high QPS, re-indexing with a strict real-time SLA, and 7B embedders (gte-Qwen2, NV-Embed).&lt;/p>
&lt;h2 id="the-analogy-the-library-and-the-librarian">The analogy: the library and the librarian&lt;/h2>
&lt;p>Picture a serious research library. There are three distinct jobs, done by different people, on different clocks.&lt;/p>
&lt;p>The first is &lt;strong>cataloguing&lt;/strong>. Boxes of new books arrive; someone opens them, classifies them, assigns a shelf mark, indexes them in the catalogue and puts them on the right shelf. It is patient background work, done at night or between other tasks. Nobody is standing over you with a stopwatch waiting for today&amp;rsquo;s batch to be catalogued: what matters is that tomorrow it is done and done well. It is pure throughput: how many books you catalogue per hour, not how long you take on one specific book. This is &lt;strong>ingestion&lt;/strong>.&lt;/p>
&lt;p>The second is &lt;strong>handling an enquiry at the desk&lt;/strong>. A reader turns up and asks about a topic. The librarian goes to the catalogue, which already exists, finds half a dozen relevant shelf marks, fetches them and puts the books on the desk. It is fast, light, and consists of searching an index that already exists, not building it. This is &lt;strong>retrieval&lt;/strong>.&lt;/p>
&lt;p>The third is &lt;strong>writing a reasoned report&lt;/strong> from those books. The reader, or an expert you commission, reads the six books, compares them, synthesises, and writes an argued answer with citations. This is slow, demands a very well-trained mind, and the reader is waiting: here there is a human stopwatch. This is &lt;strong>generation&lt;/strong>, the LLM.&lt;/p>
&lt;p>The moral is about staff allocation. You do not put your star writer, expensive, scarce, with a queue of people waiting for reports, to catalogue boxes of books at dawn. Cataloguing is done by a large, cheap team that works through the night without rushing. The star writer touches only what genuinely needs their mind: writing. In our system, the star writer is the H100, and cataloguing at dawn is corpus ingestion. Spending the H100 on re-indexing is exactly the error of putting the writer to work labelling boxes.&lt;/p>
&lt;p>The rest of the post is, essentially, which parts of the library work the cheap CPU team can do (almost all of them) and which is irreducibly the GPU writer&amp;rsquo;s job (only the last one).&lt;/p>
&lt;h2 id="the-three-phases-and-their-compute-profiles">The three phases and their compute profiles&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The three RAG phases: build and retrieval on CPU, generation on GPU">
&lt;text x="410" y="24" text-anchor="middle" font-size="15" font-weight="700" fill="currentColor">The three RAG phases and where each one runs&lt;/text>
&lt;rect x="20" y="50" width="250" height="190" fill="none" stroke="#22c55e" stroke-width="1.6"/>
&lt;text x="145" y="74" text-anchor="middle" font-size="13" font-weight="700" fill="currentColor">1 · BUILD / INGEST&lt;/text>
&lt;text x="145" y="94" text-anchor="middle" font-size="11" fill="currentColor">corpus chunking&lt;/text>
&lt;text x="145" y="111" text-anchor="middle" font-size="11" fill="currentColor">dense embedding (bge-m3)&lt;/text>
&lt;text x="145" y="128" text-anchor="middle" font-size="11" fill="currentColor">sparse / SPLADE head&lt;/text>
&lt;text x="145" y="145" text-anchor="middle" font-size="11" fill="currentColor">build HNSW index&lt;/text>
&lt;rect x="45" y="162" width="200" height="26" fill="#22c55e" stroke="none"/>
&lt;text x="145" y="180" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">[CPU]&lt;/text>
&lt;text x="145" y="208" text-anchor="middle" font-size="11" font-style="italic" fill="currentColor">throughput-bound · batch&lt;/text>
&lt;text x="145" y="224" text-anchor="middle" font-size="11" font-style="italic" fill="currentColor">no latency SLA · overnight&lt;/text>
&lt;rect x="285" y="50" width="250" height="190" fill="none" stroke="#3b82f6" stroke-width="1.6"/>
&lt;text x="410" y="74" text-anchor="middle" font-size="13" font-weight="700" fill="currentColor">2 · RETRIEVAL (query-time)&lt;/text>
&lt;text x="410" y="94" text-anchor="middle" font-size="11" fill="currentColor">query embedding&lt;/text>
&lt;text x="410" y="111" text-anchor="middle" font-size="11" fill="currentColor">HNSW search (dense)&lt;/text>
&lt;text x="410" y="128" text-anchor="middle" font-size="11" fill="currentColor">sparse search + RRF&lt;/text>
&lt;text x="410" y="145" text-anchor="middle" font-size="11" fill="currentColor">rerank top-20/50 (light)&lt;/text>
&lt;rect x="310" y="162" width="200" height="26" fill="#3b82f6" stroke="none"/>
&lt;text x="410" y="180" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">[CPU] (rerank: caveat)&lt;/text>
&lt;text x="410" y="208" text-anchor="middle" font-size="11" font-style="italic" fill="currentColor">low but tolerable latency&lt;/text>
&lt;text x="410" y="224" text-anchor="middle" font-size="11" font-style="italic" fill="currentColor">tens of ms · HNSW always CPU&lt;/text>
&lt;rect x="550" y="50" width="250" height="190" fill="none" stroke="#ef4444" stroke-width="1.6"/>
&lt;text x="675" y="74" text-anchor="middle" font-size="13" font-weight="700" fill="currentColor">3 · GENERATION&lt;/text>
&lt;text x="675" y="98" text-anchor="middle" font-size="11" fill="currentColor">the LLM produces the answer&lt;/text>
&lt;text x="675" y="115" text-anchor="middle" font-size="11" fill="currentColor">prefill of the augmented context&lt;/text>
&lt;text x="675" y="132" text-anchor="middle" font-size="11" fill="currentColor">token-by-token decode&lt;/text>
&lt;rect x="575" y="162" width="200" height="26" fill="#ef4444" stroke="none"/>
&lt;text x="675" y="180" text-anchor="middle" font-size="12" font-weight="700" fill="#ffffff">[GPU]&lt;/text>
&lt;text x="675" y="208" text-anchor="middle" font-size="11" font-style="italic" fill="currentColor">latency-bound · TTFT matters&lt;/text>
&lt;text x="675" y="224" text-anchor="middle" font-size="11" font-style="italic" fill="currentColor">7B on CPU = TTFT in seconds&lt;/text>
&lt;path d="M270,145 L285,145" stroke="currentColor" stroke-width="1.6" fill="none"/>
&lt;path d="M535,145 L550,145" stroke="currentColor" stroke-width="1.6" fill="none"/>
&lt;line x1="20" y1="270" x2="800" y2="270" stroke="currentColor" stroke-width="1"/>
&lt;text x="20" y="290" font-size="12" font-weight="700" fill="currentColor">DATA PLANE (CPU)&lt;/text>
&lt;text x="520" y="290" font-size="12" font-weight="700" fill="currentColor">GENERATION PLANE (GPU)&lt;/text>
&lt;rect x="20" y="300" width="510" height="20" fill="#22c55e" opacity="0.25" stroke="#22c55e" stroke-width="1"/>
&lt;rect x="550" y="300" width="250" height="20" fill="#ef4444" opacity="0.25" stroke="#ef4444" stroke-width="1"/>
&lt;text x="275" y="314" text-anchor="middle" font-size="11" fill="currentColor">phases 1 and 2 · generic CPU fleet · cheap and horizontal&lt;/text>
&lt;text x="675" y="314" text-anchor="middle" font-size="11" fill="currentColor">phase 3 · scarce GPU&lt;/text>
&lt;text x="410" y="345" text-anchor="middle" font-size="12" font-style="italic" fill="currentColor">The system boundary falls between retrieval and generation, not between "AI" and "non-AI"&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>The confusion that GPU over-provisioning feeds on is treating &amp;ldquo;RAG&amp;rdquo; as a monolithic block that &amp;ldquo;uses AI, therefore goes on the GPU&amp;rdquo;. No. RAG is a data pipeline with a generative model plugged in at the end. The correct architectural boundary does not separate &amp;ldquo;what uses models&amp;rdquo; from &amp;ldquo;what does not&amp;rdquo;, because both sides use models, but throughput-bound from latency-bound, which is the same as separating the data plane from the generation plane.&lt;/p>
&lt;h2 id="why-ingestion-fits-on-cpu-the-embedder-is-not-an-llm">Why ingestion fits on CPU: the embedder is not an LLM&lt;/h2>
&lt;p>The whole argument rests on a size asymmetry that gets overlooked. People hear &amp;ldquo;embeddings&amp;rdquo; and &amp;ldquo;generation&amp;rdquo; and put them in the same bag of &amp;ldquo;big models that need a GPU&amp;rdquo;. But the embedding encoder and the generative LLM are two orders of magnitude apart in parameters.&lt;/p>
&lt;p>&lt;code>bge-m3&lt;/code>, the reference multilingual embedder, is an &lt;strong>XLM-RoBERTa of around 568M parameters&lt;/strong> (&lt;a href="https://huggingface.co/BAAI/bge-m3">model card&lt;/a>, &lt;a href="https://arxiv.org/abs/2402.03216">paper arXiv:2402.03216&lt;/a>). Its sibling reranker, &lt;code>bge-reranker-v2-m3&lt;/code>, is built on the same base and lands at roughly the same 568M parameters (&lt;a href="https://huggingface.co/BAAI/bge-reranker-v2-m3">model card&lt;/a>). Compare that with an entry-level generative LLM: a Llama 3.1 &lt;strong>8B&lt;/strong> has around 14× more parameters, and the big production models sit at 70B+. A 568M encoder is, in compute budget terms, a different animal.&lt;/p>
&lt;p>Two structural differences make that encoder comfortable on CPU:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>It is an encoder, not an autoregressive decoder.&lt;/strong> It processes the whole sequence in a single forward pass and emits the vector. There is no token-by-token decode, no growing KV cache, none of the memory-bound generation phase that kills the CPU. It is a dense matrix pass and that is it.&lt;/li>
&lt;li>&lt;strong>It quantises to int8 with almost no loss.&lt;/strong> In int8, &lt;code>bge-m3&lt;/code> occupies on the order of 580 MB and, above all, activates the integer compute paths that a modern CPU executes quickly: matrix instructions such as &lt;strong>Intel AMX&lt;/strong> (Advanced Matrix Extensions, 4th-generation Xeon and later), &lt;strong>AVX-512 with VNNI&lt;/strong> (Vector Neural Network Instructions) on earlier Xeons, and &lt;strong>NEON SDOT/UDOT&lt;/strong> on ARM. The quality loss from going FP32 to int8 on these models usually stays below 1% of retrieval recall, practically invisible (&lt;a href="https://huggingface.co/blog/intel-fast-embedding">Intel + Hugging Face, &lt;em>CPU Optimized Embeddings&lt;/em>&lt;/a>).&lt;/li>
&lt;/ul>
&lt;p>Let us quantify the int8 size. For $P = 568 \times 10^6$ parameters at 1 byte each:&lt;/p>
$$\text{size}_{\text{int8}} \approx 568 \times 10^6 \text{ params} \times 1 \text{ byte/param} \approx 568 \text{ MB}$$
&lt;p>In other words, the model fits in the cache and RAM of any server or NUC without blinking, and the bottleneck is integer compute, exactly what AMX/VNNI accelerate. There is nothing in this profile that asks for a GPU.&lt;/p>
&lt;h3 id="runtimes-that-already-do-this-effortlessly">Runtimes that already do this effortlessly&lt;/h3>
&lt;p>Nothing needs inventing. The CPU ecosystem for the data plane is mature:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Text Embeddings Inference (TEI)&lt;/strong> from Hugging Face: a Rust server with &lt;strong>CPU backends&lt;/strong> via ONNX Runtime (recommended) or Intel MKL, and &lt;strong>OpenAI-compatible&lt;/strong> endpoints (&lt;code>/v1/embeddings&lt;/code>) plus &lt;code>/rerank&lt;/code> (&lt;a href="https://github.com/huggingface/text-embeddings-inference">TEI repo&lt;/a>). That is, the data plane on CPU exposes exactly the same HTTP contract as a GPU server; the rest of the system never learns which silicon is behind it.&lt;/li>
&lt;li>&lt;strong>fastembed&lt;/strong> from Qdrant: a lightweight library that loads embedders on &lt;strong>ONNX-CPU&lt;/strong> and generates dense, sparse and ColBERT vectors (&lt;a href="https://github.com/qdrant/fastembed">fastembed repo&lt;/a>). Designed from the start to run without a GPU.&lt;/li>
&lt;li>&lt;strong>&lt;code>bge-m3&lt;/code> in ONNX int8&lt;/strong> with its three heads (dense / sparse-lexical / ColBERT multivector) exported and quantised, ready for ONNX Runtime CPU.&lt;/li>
&lt;/ul>
&lt;p>The Intel and Hugging Face figure that anchors viability: in their benchmark with &lt;strong>Optimum Intel + fastRAG&lt;/strong> on a 4th-generation Xeon (8480+, 56 cores, 1 socket), the int8 variant of &lt;strong>BGE-large&lt;/strong> reaches up to around 10× indexing throughput against FP32 (&lt;a href="https://huggingface.co/blog/intel-fast-embedding">HF blog&lt;/a>, &lt;a href="https://haystack.deepset.ai/blog/cpu-optimized-models-with-fastrag">Haystack/deepset&lt;/a>). The small print needs reading, so I read it: that ~10× is encoding-only (tokenisation excluded), at sequence length 256, comparing int8 against FP32 on the same CPU. It is not &amp;ldquo;CPU 10× faster than GPU&amp;rdquo;, it is &amp;ldquo;int8 10× faster than FP32 on CPU&amp;rdquo;. It is still the relevant figure: it tells you that with quantisation the CPU goes from unworkable to perfectly useful for batch ingestion.&lt;/p>
&lt;h2 id="viability-table-cpu-for-each-component">Viability table: CPU for each component?&lt;/h2>
&lt;p>This is the operational table. The column that matters is the &lt;strong>caveat&lt;/strong>, because a bare &amp;ldquo;yes&amp;rdquo; or &amp;ldquo;no&amp;rdquo; lies.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Component&lt;/th>
&lt;th>CPU viable?&lt;/th>
&lt;th>Caveat&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Chunking&lt;/strong> (slicing the corpus)&lt;/td>
&lt;td>Yes, always&lt;/td>
&lt;td>It is regex, parsing and windows; it never had anything to do with the GPU.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Ingest embedding&lt;/strong> &lt;code>bge-m3&lt;/code> dense&lt;/td>
&lt;td>Yes, its best case&lt;/td>
&lt;td>Overnight batch, int8 + AMX/VNNI. This is exactly what the CPU shines at.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Sparse / SPLADE / BM25 head&lt;/strong>&lt;/td>
&lt;td>Yes, CPU-native&lt;/td>
&lt;td>Lexical work is pure inverted index; the GPU adds nothing here.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Building the HNSW index&lt;/strong> (Qdrant, pgvector)&lt;/td>
&lt;td>Yes, always CPU&lt;/td>
&lt;td>The HNSW graph build is CPU by design in these engines.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Query embedding&lt;/strong> (online)&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>A single short text; tens of ms on CPU, more than enough for chat.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Dense + sparse search + RRF&lt;/strong>&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>Vector search always ran on CPU, even in &amp;ldquo;GPU&amp;rdquo; stacks. RRF is sorting lists.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Cross-encoder reranker&lt;/strong> &lt;code>bge-reranker-v2-m3&lt;/code> top-20/50&lt;/td>
&lt;td>Yes, with care&lt;/td>
&lt;td>A cross-encoder evaluates $k$ query-doc pairs: cost $\propto k$. Over 20-50 candidates it works; over hundreds at high QPS, no.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ColBERT late interaction&lt;/strong>&lt;/td>
&lt;td>Marginal on CPU&lt;/td>
&lt;td>The token-by-token matrix product of late interaction is heavy; viable at low volumes, suffers with QPS.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>LLM generation&lt;/strong>&lt;/td>
&lt;td>No, in practice&lt;/td>
&lt;td>A 7B on CPU gives TTFT in seconds. Interactive latency = GPU.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Two rows deserve underlining because they dismantle myths.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;Vector search needs a GPU.&amp;rdquo;&lt;/strong> False from the start. The &lt;strong>HNSW&lt;/strong> index, the navigable small-world graph used by Qdrant, pgvector with &lt;code>vector&lt;/code>/&lt;code>halfvec&lt;/code>, Milvus in CPU mode and almost everything else, was always built and traversed on CPU. Even the stacks advertised as &amp;ldquo;GPU-accelerated RAG&amp;rdquo; do the embedding on GPU but the ANN search stays on CPU in the vast majority of deployments; the GPU variants of the index (CAGRA and similar) are the expensive exception, not the norm, and are justified only with billions of vectors and extreme QPS. For a corporate corpus of millions of chunks, HNSW on CPU resolves in single-digit milliseconds.&lt;/p>
&lt;p>&lt;strong>&amp;ldquo;The reranker is a model, therefore GPU.&amp;rdquo;&lt;/strong> The &lt;code>bge-reranker-v2-m3&lt;/code> reranker is a cross-encoder of around 568M: it runs on CPU. The caveat is the &lt;strong>number of pairs&lt;/strong>. A cross-encoder does not produce a reusable vector; it evaluates the (query, document) pair jointly, so its cost grows linearly with the number of candidates $k$:&lt;/p>
$$\text{cost}_{\text{rerank}} \propto k \times \text{forward}(\text{query} + \text{doc})$$
&lt;p>Reranking the top-20 or top-50 coming out of hybrid retrieval is perfectly affordable on CPU. Reranking hundreds of candidates at high QPS is not: there the linear cost explodes and the GPU wins. The rule of thumb: broad cheap recall in the retriever, precision rerank over few candidates. (The detail of hybrid retrieval and reranking is in the fundamentals piece linked below.)&lt;/p>
&lt;h2 id="the-numbers-with-honest-methodology">The numbers, with honest methodology&lt;/h2>
&lt;p>Here comes the part where a lot of people lie by omission. I am going to give throughput ranges, but they are literature ranges and orders of magnitude, not my own measurements on this hardware. Take them as such: the right decision does not depend on nailing the number, it depends on understanding the allocation.&lt;/p>
&lt;p>For &lt;code>bge-m3&lt;/code> dense, sequence ≈256 tokens, embedding throughput moves roughly like this:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Platform&lt;/th>
&lt;th>Dense throughput (order of magnitude)&lt;/th>
&lt;th>Reading&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>High-end GPU&lt;/strong> (5090 fp16, TEI)&lt;/td>
&lt;td>~12k tok/s+ (indicative)&lt;/td>
&lt;td>The ceiling; expensive and scarce.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Large server CPU&lt;/strong> (Xeon ~56 cores, int8 ONNX)&lt;/td>
&lt;td>low band of thousands tok/s&lt;/td>
&lt;td>~1/5–1/10 of the GPU, but horizontally scalable and cheap.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Edge CPU / NUC&lt;/strong> (4-8 cores, int8)&lt;/td>
&lt;td>tens to low hundreds tok/s&lt;/td>
&lt;td>Enough for overnight ingestion of a local corpus.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The temptation is to read the second row as &amp;ldquo;CPU is 5-10× slower, discarded&amp;rdquo;. That is the wrong reading for ingestion. For batch work with no SLA, what rules is not absolute tok/s but throughput per euro and throughput per watt, and there the arithmetic changes sign.&lt;/p>
&lt;p>Take a numerical example of allocation. Suppose a corpus of 2 million chunks of around 256 tokens that has to be re-indexed once a day (the corpus changes, the embeddings have to be redone). That is:&lt;/p>
$$2 \times 10^6 \text{ chunks} \times 256 \text{ tok/chunk} \approx 5.1 \times 10^8 \text{ tokens}$$
&lt;p>At a conservative CPU throughput of, say, 3,000 tok/s per int8 Xeon server:&lt;/p>
$$t_{\text{ingest}} \approx \frac{5.1 \times 10^8 \text{ tok}}{3000 \text{ tok/s}} \approx 1.7 \times 10^5 \text{ s} \approx 47 \text{ hours on a single server}$$
&lt;p>47 hours on one box sounds bad until you remember two things. First, this is embarrassingly parallel: the corpus is sliced and distributed; with 8 CPU servers it drops to around 6 hours, with 16 to around 3 hours, comfortably inside the overnight window. Second, and more importantly: that same work on the GPU blocks the GPU. If the H100 does 12k tok/s, it takes around 12 hours, but they are 12 hours of the H100, the resource the whole organisation fights over in order to generate. Spending the scarce, expensive resource on re-indexing a corpus that changes once a day is bad allocation, even if it is &amp;ldquo;faster&amp;rdquo;: you are optimising the wrong tok/s.&lt;/p>
&lt;p>The mental rule: for ingestion, optimise throughput/€ and throughput/W; absolute tok/s belongs to the generation plane, where the human stopwatch really is running.&lt;/p>
&lt;h2 id="decision-tree-cpu-or-gpu-for-this-piece">Decision tree: CPU or GPU for this piece?&lt;/h2>
&lt;div class="diagram" style="max-width:780px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 780 420" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="CPU vs GPU decision tree per RAG component">
&lt;defs>&lt;marker id="dt" 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;text x="390" y="24" text-anchor="middle" font-size="14" font-weight="700" fill="currentColor">Does this RAG piece go to CPU or GPU?&lt;/text>
&lt;rect x="270" y="44" width="240" height="40" rx="6" fill="#f59e0b" opacity="0.18" stroke="#f59e0b" stroke-width="1.4"/>
&lt;text x="390" y="68" text-anchor="middle" font-size="12" font-weight="700" fill="currentColor">Is the user waiting (interactive SLA)?&lt;/text>
&lt;path d="M390,84 L200,120" stroke="currentColor" stroke-width="1.4" fill="none" marker-end="url(#dt)"/>
&lt;text x="270" y="104" text-anchor="middle" font-size="11" fill="currentColor">no (batch)&lt;/text>
&lt;path d="M390,84 L580,120" stroke="currentColor" stroke-width="1.4" fill="none" marker-end="url(#dt)"/>
&lt;text x="510" y="104" text-anchor="middle" font-size="11" fill="currentColor">yes (online)&lt;/text>
&lt;rect x="60" y="124" width="280" height="38" rx="6" fill="#22c55e" opacity="0.15" stroke="#22c55e" stroke-width="1.4"/>
&lt;text x="200" y="148" text-anchor="middle" font-size="12" font-weight="700" fill="currentColor">Ingest/index → [CPU] cheap fleet&lt;/text>
&lt;rect x="440" y="124" width="290" height="40" rx="6" fill="#f59e0b" opacity="0.18" stroke="#f59e0b" stroke-width="1.4"/>
&lt;text x="585" y="142" text-anchor="middle" font-size="12" font-weight="700" fill="currentColor">Does it generate tokens (is it the LLM)?&lt;/text>
&lt;text x="585" y="157" text-anchor="middle" font-size="11" fill="currentColor">or is it retrieval/rerank?&lt;/text>
&lt;path d="M585,164 L460,210" stroke="currentColor" stroke-width="1.4" fill="none" marker-end="url(#dt)"/>
&lt;text x="500" y="190" text-anchor="middle" font-size="11" fill="currentColor">retrieval&lt;/text>
&lt;path d="M585,164 L690,210" stroke="currentColor" stroke-width="1.4" fill="none" marker-end="url(#dt)"/>
&lt;text x="680" y="190" text-anchor="middle" font-size="11" fill="currentColor">generates&lt;/text>
&lt;rect x="330" y="214" width="250" height="40" rx="6" fill="#f59e0b" opacity="0.18" stroke="#f59e0b" stroke-width="1.4"/>
&lt;text x="455" y="232" text-anchor="middle" font-size="11" font-weight="700" fill="currentColor">embed query / HNSW / RRF / rerank&lt;/text>
&lt;text x="455" y="248" text-anchor="middle" font-size="11" fill="currentColor">how many candidates and at what QPS?&lt;/text>
&lt;rect x="600" y="214" width="150" height="38" rx="6" fill="#ef4444" opacity="0.18" stroke="#ef4444" stroke-width="1.4"/>
&lt;text x="675" y="238" text-anchor="middle" font-size="12" font-weight="700" fill="currentColor">[GPU] generation&lt;/text>
&lt;path d="M455,254 L250,300" stroke="currentColor" stroke-width="1.4" fill="none" marker-end="url(#dt)"/>
&lt;text x="320" y="280" text-anchor="middle" font-size="11" fill="currentColor">embed/HNSW/RRF, or rerank top-20/50&lt;/text>
&lt;path d="M455,254 L600,300" stroke="currentColor" stroke-width="1.4" fill="none" marker-end="url(#dt)"/>
&lt;text x="600" y="280" text-anchor="middle" font-size="11" fill="currentColor">rerank hundreds&lt;/text>
&lt;text x="600" y="293" text-anchor="middle" font-size="11" fill="currentColor">high QPS / ColBERT&lt;/text>
&lt;rect x="110" y="304" width="280" height="38" rx="6" fill="#22c55e" opacity="0.15" stroke="#22c55e" stroke-width="1.4"/>
&lt;text x="250" y="328" text-anchor="middle" font-size="12" font-weight="700" fill="currentColor">[CPU] data plane&lt;/text>
&lt;rect x="510" y="304" width="200" height="38" rx="6" fill="#ef4444" opacity="0.18" stroke="#ef4444" stroke-width="1.4"/>
&lt;text x="610" y="322" text-anchor="middle" font-size="11" font-weight="700" fill="currentColor">[GPU] heavy rerank&lt;/text>
&lt;text x="610" y="336" text-anchor="middle" font-size="10" fill="currentColor">(or large 7B embedder)&lt;/text>
&lt;text x="390" y="372" text-anchor="middle" font-size="12" font-style="italic" fill="currentColor">Rule: only what is latency-bound (generation) and massively online (rerank at high QPS) crosses to GPU.&lt;/text>
&lt;text x="390" y="392" text-anchor="middle" font-size="12" font-style="italic" fill="currentColor">Everything else, which is almost everything, stays in the CPU data plane.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h2 id="reference-architecture-a-cpu-only">Reference architecture (a): CPU-only&lt;/h2>
&lt;p>The first case is a node with no GPU: a NUC, an office Xeon, a sovereign edge server in a branch or in an air-gapped environment. The whole data plane lives there; generation is delegated to a remote GPU endpoint or done in batch with an SLM when latency is not pressing.&lt;/p>
&lt;p>The stack:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>TEI-CPU&lt;/strong> serving &lt;code>bge-m3&lt;/code> int8 with dense + sparse (same OpenAI contract &lt;code>/v1/embeddings&lt;/code>, plus &lt;code>/rerank&lt;/code> for the reranker).&lt;/li>
&lt;li>&lt;strong>Qdrant&lt;/strong> with a dense &lt;strong>HNSW&lt;/strong> index plus &lt;strong>sparse&lt;/strong> vectors, native &lt;strong>RRF&lt;/strong> fusion.&lt;/li>
&lt;li>&lt;strong>Reranker&lt;/strong> &lt;code>bge-reranker-v2-m3&lt;/code> over the top-k (via TEI&amp;rsquo;s &lt;code>/rerank&lt;/code>).&lt;/li>
&lt;li>&lt;strong>Gateway&lt;/strong> that orchestrates and, for generation, calls an external endpoint.&lt;/li>
&lt;/ul>
&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"># docker-compose: full RAG data plane on CPU (no GPU)&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">services&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">tei-embed&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">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ghcr.io/huggingface/text-embeddings-inference:cpu-latest&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">command&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;--model-id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;BAAI/bge-m3&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--pooling&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;cls&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--dtype&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;int8&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">ports&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;8081:80&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="c"># ONNX/MKL backend: exploits AVX-512+VNNI / AMX if the Xeon supports 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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">tei-rerank&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">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">ghcr.io/huggingface/text-embeddings-inference:cpu-latest&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">command&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;--model-id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;BAAI/bge-reranker-v2-m3&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--dtype&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;int8&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">ports&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;8082:80&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="c"># exposes /rerank — invoked ONLY over top-20/50, never over hundreds&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">qdrant&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">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">qdrant/qdrant:latest&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">ports&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;6333:6333&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">volumes&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;./qdrant_storage:/qdrant/storage&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="c"># HNSW dense + sparse vectors + RRF, all CPU&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Hybrid search with RRF fusion in Qdrant (dense + sparse in a single query):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">qdrant_client&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">QdrantClient&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">models&lt;/span>
&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="n">client&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">QdrantClient&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;http://qdrant:6333&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&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"># query embedding: dense and sparse from TEI-CPU (HTTP wiring omitted)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">hits&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">client&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query_points&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">collection_name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;corpus&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">prefetch&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">models&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Prefetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">dense_vec&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">using&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;dense&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">limit&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">models&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Prefetch&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">query&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">sparse_vec&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">using&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;sparse&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">limit&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">50&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">query&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">models&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">FusionQuery&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">fusion&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">models&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Fusion&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">RRF&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="c1"># native RRF&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">limit&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">20&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">points&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># -&amp;gt; then: POST /rerank (TEI) over these 20, keep the top-5&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># -&amp;gt; then: the gateway sends query + top-5 to the GENERATION endpoint (GPU)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Generation, on this CPU-only node, leaves the node: the gateway builds the augmented prompt and sends it to a vLLM endpoint on the GPU cluster (or, if there is no interactive SLA, to an SLM on CPU in batch mode, accepting a TTFT of seconds). The entire data plane, everything above, runs without a single GPU.&lt;/p>
&lt;h2 id="reference-architecture-b-the-recommended-hybrid">Reference architecture (b): the recommended hybrid&lt;/h2>
&lt;p>This is the one I recommend for the general case with a GPU cluster available: &lt;strong>data plane on CPU, generation plane on GPU&lt;/strong>, connected by OpenAI-compatible HTTP contracts so that each side is replaceable.&lt;/p>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Hybrid architecture: CPU data plane + GPU generation plane">
&lt;defs>&lt;marker id="hy" 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;text x="410" y="24" text-anchor="middle" font-size="14" font-weight="700" fill="currentColor">Hybrid architecture: two planes, two silicons&lt;/text>
&lt;rect x="30" y="50" width="470" height="210" rx="8" fill="#22c55e" opacity="0.08" stroke="#22c55e" stroke-width="1.6"/>
&lt;text x="265" y="72" text-anchor="middle" font-size="13" font-weight="700" fill="currentColor">DATA PLANE — CPU fleet (Xeon AMX / NUC)&lt;/text>
&lt;rect x="50" y="88" width="130" height="50" rx="5" fill="none" stroke="#3b82f6" stroke-width="1.3"/>
&lt;text x="115" y="108" text-anchor="middle" font-size="11" font-weight="700" fill="currentColor">Batch ingest&lt;/text>
&lt;text x="115" y="124" text-anchor="middle" font-size="10" fill="currentColor">chunk + embed int8&lt;/text>
&lt;rect x="200" y="88" width="130" height="50" rx="5" fill="none" stroke="#3b82f6" stroke-width="1.3"/>
&lt;text x="265" y="108" text-anchor="middle" font-size="11" font-weight="700" fill="currentColor">Qdrant&lt;/text>
&lt;text x="265" y="124" text-anchor="middle" font-size="10" fill="currentColor">HNSW+sparse+RRF&lt;/text>
&lt;rect x="350" y="88" width="130" height="50" rx="5" fill="none" stroke="#3b82f6" stroke-width="1.3"/>
&lt;text x="415" y="108" text-anchor="middle" font-size="11" font-weight="700" fill="currentColor">TEI rerank&lt;/text>
&lt;text x="415" y="124" text-anchor="middle" font-size="10" fill="currentColor">light top-20/50&lt;/text>
&lt;rect x="50" y="160" width="430" height="44" rx="5" fill="none" stroke="#22c55e" stroke-width="1.3"/>
&lt;text x="265" y="180" text-anchor="middle" font-size="11" font-weight="700" fill="currentColor">TEI-CPU /v1/embeddings + /rerank · int8 · AVX-512/AMX&lt;/text>
&lt;text x="265" y="196" text-anchor="middle" font-size="10" fill="currentColor">OpenAI-compatible contract: the GPU does not know this is CPU&lt;/text>
&lt;rect x="540" y="50" width="250" height="210" rx="8" fill="#ef4444" opacity="0.08" stroke="#ef4444" stroke-width="1.6"/>
&lt;text x="665" y="72" text-anchor="middle" font-size="13" font-weight="700" fill="currentColor">GENERATION PLANE — GPU&lt;/text>
&lt;rect x="560" y="100" width="210" height="60" rx="5" fill="none" stroke="#ef4444" stroke-width="1.3"/>
&lt;text x="665" y="124" text-anchor="middle" font-size="12" font-weight="700" fill="currentColor">vLLM · LLM 7B+&lt;/text>
&lt;text x="665" y="142" text-anchor="middle" font-size="10" fill="currentColor">interactive prefill + decode&lt;/text>
&lt;rect x="560" y="172" width="210" height="44" rx="5" fill="none" stroke="#ef4444" stroke-width="1.3"/>
&lt;text x="665" y="192" text-anchor="middle" font-size="10" font-weight="700" fill="currentColor">optional: heavy rerank / 7B embedder&lt;/text>
&lt;text x="665" y="207" text-anchor="middle" font-size="10" fill="currentColor">only peaks the CPU cannot absorb&lt;/text>
&lt;path d="M480,182 L540,150" stroke="currentColor" stroke-width="1.8" fill="none" marker-end="url(#hy)"/>
&lt;text x="510" y="155" text-anchor="middle" font-size="10" fill="currentColor">query+top-k&lt;/text>
&lt;text x="410" y="284" text-anchor="middle" font-size="12" font-style="italic" fill="currentColor">The gateway speaks OpenAI over HTTP to both sides; each plane scales separately.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>Generation server, minimal, on GPU:&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 on the GPU cluster — generation ONLY&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">services&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">vllm-gen&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">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">vllm/vllm-openai:latest&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">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">&amp;gt;&lt;/span>&lt;span class="sd">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> --model meta-llama/Llama-3.1-8B-Instruct
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> --dtype bfloat16 --max-model-len 8192
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sd"> --gpu-memory-utilization 0.85&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"># exposes /v1/chat/completions — the gateway sends it query + the top-5 already retrieved&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">deploy&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">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">reservations&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">devices&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>{&lt;span class="nt">driver: nvidia, count: 1, capabilities&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="l">gpu]}]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The virtue of the design: because &lt;strong>both sides speak the OpenAI contract over HTTP&lt;/strong>, the CPU data plane and the GPU generation plane scale separately and are replaceable. If tomorrow you want to move the rerank to GPU because QPS went up, you change one URL. If you want to add more CPU ingest nodes, you add them without touching generation. The whole stack is OSS and licence-clean: &lt;code>bge-m3&lt;/code> and &lt;code>bge-reranker-v2-m3&lt;/code> are &lt;strong>MIT&lt;/strong> (&lt;a href="https://huggingface.co/BAAI/bge-m3">bge-m3&lt;/a>, &lt;a href="https://huggingface.co/BAAI/bge-reranker-v2-m3">reranker&lt;/a>), Qdrant is Apache-2.0, TEI and vLLM are OSS.&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 this down to the cluster in the series: &lt;strong>4×H100 SXM 80 GB&lt;/strong> plus a generic CPU fleet (Xeon with AMX, NUCs). The correct allocation:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Build and indexing → CPU fleet.&lt;/strong> No H100 should spend a cycle re-embedding the corpus. That goes to the Xeon AMX machines (large servers, throughput in the thousands of tok/s in int8) or, for small local corpora, to the NUCs overnight. Overnight re-indexing of a corpus that changes once a day is the textbook case of &amp;ldquo;unhurried CPU work&amp;rdquo;.&lt;/li>
&lt;li>&lt;strong>The H100s → generation.&lt;/strong> All four cards are reserved for what only they do well: producing tokens at interactive latency. This is what the sibling pieces in the series, &lt;a href="https://blog.lo0.es/en/posts/sharing-one-gpu-time-slicing-mps-mig/">sharing a GPU&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/serving-several-models-single-gpu-co-residency-swapping-sleep-mode/">several models on one GPU&lt;/a>, help to squeeze: once ingestion stops competing for the GPU, all the expensive silicon is free to generate and is shared better between models and tenants.&lt;/li>
&lt;li>&lt;strong>The H100s, at most, → rerank peaks or large embedders.&lt;/strong> If at some point you need a &lt;strong>7B&lt;/strong> embedder (gte-Qwen2, NV-Embed) for a domain where &lt;code>bge-m3&lt;/code> does not reach, or a massive rerank at a QPS the CPU cannot absorb, those peaks can indeed visit the GPU. But they are the occasional exception, not the base load.&lt;/li>
&lt;/ul>
&lt;h3 id="the-auditability-angle-ens--nis2">The auditability angle: ENS / NIS2&lt;/h3>
&lt;p>There is a compliance argument that rarely gets mentioned and that the CPU/GPU split gives you almost for free.&lt;/p>
&lt;p>A &lt;strong>CPU-only node with no proprietary driver&lt;/strong> is easier to audit. There is no closed NVIDIA kernel stack, no CUDA and firmware versions to reconcile with the supply chain, no proprietary driver surface to document for an ENS or a NIS2 assessment. The whole data plane, chunking, embeddings, index, search, runs on OSS software on generic CPU with standard instructions. For a sovereign or classified environment, being able to say &amp;ldquo;the plane that touches the corpus depends on no proprietary binary&amp;rdquo; is a real argument, not marketing.&lt;/p>
&lt;p>And there is a second auditability angle intrinsic to RAG done well: &lt;strong>source traceability&lt;/strong>. A RAG system that retrieves identifiable chunks and cites them is auditable, since you can reconstruct which document each claim came from, as opposed to &lt;em>context-stuffing&lt;/em> or the model&amp;rsquo;s opaque parametric knowledge, where there is no way to know where a fact came from. That traceability lives in the &lt;strong>data plane&lt;/strong> (what was retrieved, from which source, with what score), precisely the plane we are putting on auditable CPU. The two arguments reinforce each other: the auditable silicon and the auditable chain of evidence are the same plane.&lt;/p>
&lt;h2 id="when-not-to-take-it-to-cpu">When NOT to take it to CPU&lt;/h2>
&lt;p>For honesty, and to avoid falling into the mirror image of the hype, the cases where CPU is not the answer:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Generation at interactive latency.&lt;/strong> The obvious case. A 7B on CPU gives TTFT in seconds: unacceptable for chat. If the user is waiting, generation goes to GPU. With no practical exceptions today.&lt;/li>
&lt;li>&lt;strong>Massive reranking at high QPS.&lt;/strong> A cross-encoder or ColBERT over hundreds of candidates, multiplied by many requests per second, saturates the CPU. The cost $\propto k \times \text{QPS}$ crosses the threshold where the GPU pays off. Keep CPU rerank bounded to top-20/50; if you need more breadth at higher QPS, move up to GPU.&lt;/li>
&lt;li>&lt;strong>Real-time re-indexing with a strict SLA.&lt;/strong> If the corpus changes continuously and freshness is measured in seconds (not hours), CPU throughput may not fit the window. There, ingest embedding may need a GPU, but note that this is rare: most corporate corpora change at the pace of hours or days, not seconds.&lt;/li>
&lt;li>&lt;strong>Large embedders (7B).&lt;/strong> &lt;code>bge-m3&lt;/code> (568M) is comfortable on CPU; a &lt;strong>gte-Qwen2&lt;/strong> or &lt;strong>NV-Embed&lt;/strong> at 7B is LLM-class again and drags the same cost profile as generation. If your retrieval quality demands a 7B embedder, that embedder lives where 7Bs live: on the GPU.&lt;/li>
&lt;/ul>
&lt;p>The sentence that sums it all up: &lt;strong>the CPU is the default home of the data plane; the GPU is the justified exception for what is latency-bound and massively online.&lt;/strong> Start by putting everything on CPU and move up to GPU only what proves it does not fit, not the other way round.&lt;/p>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/end-to-end-document-ingestion-pdf-to-indexed-chunk/">End-to-end document ingestion: from PDF to indexed chunk&lt;/a> — the ingestion pipeline that runs on that CPU data plane.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/serving-embeddings-rerankers-tei-production/">Serving embeddings and rerankers with TEI in production&lt;/a> — the engine (TEI) that serves the embeddings and rerankers of that data plane.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&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 sibling piece on the split &lt;em>inside&lt;/em> the GPU; once ingestion leaves the GPU, this squeezes what remains.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/embeddings-2026-dense-sparse-multivector-families/">Embeddings 2026: dense, sparse and multivector&lt;/a> — the three heads of &lt;code>bge-m3&lt;/code> that the data plane serves on CPU, and when a 7B embedder that does demand a GPU is justified.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/reranker-hybrid-retrieval-five-chunks-llm-reads/">Reranking and hybrid retrieval: fundamentals&lt;/a> — the detail of RRF and of the cross-encoder rerank whose linear cost decides the CPU/GPU boundary for the top-k.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/postgresql-qdrant-rag-ingestion-postman-syncing-two-worlds/">Ingestion with PostgreSQL and Qdrant in microservices&lt;/a> — how the ingestion pipeline we put on the CPU fleet here is structured.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/mixed-nvidia-intel-environments-llm-inference/">Mixed NVIDIA + Intel environments: from the H100 cluster to the NUC&lt;/a> — the concrete hardware of the CPU fleet (Xeon AMX, NUC) that sustains the data plane.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/semantic-cache-rag-receptionist-photographic-memory/">Semantic cache for RAG&lt;/a> — another layer that lives in the CPU data plane and avoids touching the GPU when the query has already been answered.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/aggressive-rag-small-models-trading-parameters-for-retrieval/">Aggressive RAG on small models&lt;/a> — the generation side of this coin: how a curated data plane unloads the model in the generative phase.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>Chen, J., et al. &lt;em>BGE M3-Embedding: Multi-Lingual, Multi-Functionality, Multi-Granularity Text Embeddings&lt;/em>. arXiv 2402.03216. &lt;a href="https://arxiv.org/abs/2402.03216">https://arxiv.org/abs/2402.03216&lt;/a>&lt;/li>
&lt;li>BAAI — &lt;em>BGE-M3 model card&lt;/em> (568M params, XLM-RoBERTa, 8192 tokens, MIT). &lt;a href="https://huggingface.co/BAAI/bge-m3">https://huggingface.co/BAAI/bge-m3&lt;/a>&lt;/li>
&lt;li>BAAI — &lt;em>bge-reranker-v2-m3 model card&lt;/em> (cross-encoder on top of bge-m3, ~568M). &lt;a href="https://huggingface.co/BAAI/bge-reranker-v2-m3">https://huggingface.co/BAAI/bge-reranker-v2-m3&lt;/a>&lt;/li>
&lt;li>Intel + Hugging Face — &lt;em>CPU Optimized Embeddings with Optimum Intel and fastRAG&lt;/em> (~10× indexing for BGE-large int8, 4th-gen Xeon). &lt;a href="https://huggingface.co/blog/intel-fast-embedding">https://huggingface.co/blog/intel-fast-embedding&lt;/a>&lt;/li>
&lt;li>deepset / Haystack — &lt;em>CPU-Optimized Embedding Models with fastRAG and Haystack&lt;/em>. &lt;a href="https://haystack.deepset.ai/blog/cpu-optimized-models-with-fastrag">https://haystack.deepset.ai/blog/cpu-optimized-models-with-fastrag&lt;/a>&lt;/li>
&lt;li>Hugging Face — &lt;em>Text Embeddings Inference (TEI)&lt;/em>, CPU ONNX/MKL backends, OpenAI-compatible endpoints. &lt;a href="https://github.com/huggingface/text-embeddings-inference">https://github.com/huggingface/text-embeddings-inference&lt;/a>&lt;/li>
&lt;li>Qdrant — &lt;em>fastembed&lt;/em> (ONNX-CPU, dense/sparse/ColBERT) and &lt;em>Hybrid Search with RRF&lt;/em>. &lt;a href="https://github.com/qdrant/fastembed">https://github.com/qdrant/fastembed&lt;/a> · &lt;a href="https://qdrant.tech/documentation/beginner-tutorials/hybrid-search-fastembed/">https://qdrant.tech/documentation/beginner-tutorials/hybrid-search-fastembed/&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Technical controls: the ENS × ISO 42001 × EU AI Act cross-mapping over the on-premise LLM architecture</title><link>https://blog.lo0.es/en/posts/technical-controls-ens-iso-42001-eu-ai-act-cross-mapping/</link><pubDate>Mon, 01 Jun 2026 06:30:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/technical-controls-ens-iso-42001-eu-ai-act-cross-mapping/</guid><description>&lt;blockquote>
&lt;p>Third post in the blog&amp;rsquo;s AI governance trilogy. The first, &lt;a href="https://blog.lo0.es/en/posts/iso-42001-aims-on-premise-llm-platform/">ISO/IEC 42001&lt;/a>, broke down the certifiable management system. The second, &lt;a href="https://blog.lo0.es/en/posts/eu-ai-act-technical-dossier-on-premise-llm-architecture/">EU AI Act&lt;/a>, broke down the directly applicable legal regulation. This one covers the third framework that appears when the client is a Spanish public administration or an essential-services entity: the ENS (Esquema Nacional de Seguridad, Spain&amp;rsquo;s national security framework, Royal Decree 311/2022). The editorial challenge: triple compliance is not the arithmetic sum of three jobs; it is one single set of technical evidence labelled for three lenses. This post takes apart how that evidence is built, measure by ENS measure.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Royal Decree 311/2022 updated the Spanish Esquema Nacional de Seguridad (ENS), aligning it with NIS2 and with the modern cyber-threat landscape. It applies mandatorily to the entire Spanish public sector (central, regional and local administration, universities, autonomous bodies) and to suppliers that provide IT services to them. The framework defines 74 security measures organised into three blocks (organisational framework &lt;code>org&lt;/code>, operational framework &lt;code>op&lt;/code>, protection measures &lt;code>mp&lt;/code>), three application categories (Basic / Medium / High) according to the assessment of five dimensions (Confidentiality, Integrity, Traceability, Authenticity, Availability), and an Annex II with the measure × category matrix that dictates what is required at each level. This post maps the technical measures relevant to LLM systems (those of the &lt;code>op&lt;/code> block: planning, access control, operation, external services, continuity, monitoring; and those of the &lt;code>mp&lt;/code> block: communications, applications, information, services) against Annex A of ISO/IEC 42001 and the operational articles of the EU AI Act (Arts. 9 to 15, 17, 72 and 73), and shows that the overlap is massive: a single technical artefact from the blog&amp;rsquo;s OSS stack, the OTel spans of &lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">tracing&lt;/a>, the datasets versioned with &lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">DVC&lt;/a>, the &lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard&lt;/a> scanners, the &lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">guardrail&lt;/a> decisions, the incidents of the &lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">retrain&lt;/a> loop, simultaneously satisfies ENS measures, 42001 controls and AI Act articles, provided it is labelled with the correct codes from the moment it is captured. The editorial thesis: the difference between a system that passes all three audits and one that suffers three separate certifications is not budget, it is disciplined labelling and a common vocabulary. The post builds the master table of triple compliance, walks through the case of the multi-tenant chatbot for a High-category public administration as a live checklist, and closes with the five traps of triple compliance.&lt;/p>
&lt;h2 id="the-analogy-the-triple-inspection-of-a-critical-building">The analogy: the triple inspection of a critical building&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 380" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Triple inspection of a critical building — ENS + ISO 42001 + EU AI Act">
&lt;style>
.t-build{fill:#7aafff;stroke:#444;stroke-width:1.4;rx:8}
.t-ens{fill:#ffd76b;stroke:#444;stroke-width:1.4;rx:8}
.t-42001{fill:#a8e6a3;stroke:#444;stroke-width:1.4;rx:8}
.t-aia{fill:#ff8a4c;stroke:#444;stroke-width:1.4;rx:8}
.t-evi{fill:#c8b8ff;stroke:#444;stroke-width:1.4;rx:8}
.tl{font:600 13px sans-serif;fill:#222}
.ts{font:400 11px sans-serif;fill:#555}
.tn{font:italic 11px sans-serif;fill:#555}
.tar{stroke:#666;stroke-width:1.6;fill:none;marker-end:url(#mt3)}
.tcb{stroke:#7a5;stroke-width:1.4;fill:none;stroke-dasharray:5 3}
&lt;/style>
&lt;defs>&lt;marker id="mt3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;rect x="280" y="20" width="260" height="80" class="t-build"/>
&lt;text x="410" y="44" text-anchor="middle" class="tl">On-premise AI system&lt;/text>
&lt;text x="410" y="62" text-anchor="middle" class="ts">LLM platform with pipeline, guardrails,&lt;/text>
&lt;text x="410" y="76" text-anchor="middle" class="ts">tracing, retrain, curated RAG, evals&lt;/text>
&lt;text x="410" y="92" text-anchor="middle" class="ts">(the critical building)&lt;/text>
&lt;rect x="20" y="160" width="220" height="80" class="t-ens"/>
&lt;text x="130" y="184" text-anchor="middle" class="tl">ENS inspector&lt;/text>
&lt;text x="130" y="202" text-anchor="middle" class="ts">RD 311/2022 · CCN-CERT&lt;/text>
&lt;text x="130" y="218" text-anchor="middle" class="ts">74 measures in 3 blocks&lt;/text>
&lt;text x="130" y="232" text-anchor="middle" class="ts">Basic/Medium/High categories&lt;/text>
&lt;rect x="300" y="160" width="220" height="80" class="t-42001"/>
&lt;text x="410" y="184" text-anchor="middle" class="tl">ISO/IEC 42001 inspector&lt;/text>
&lt;text x="410" y="202" text-anchor="middle" class="ts">7 clauses + 38 Annex A controls&lt;/text>
&lt;text x="410" y="218" text-anchor="middle" class="ts">Certification body&lt;/text>
&lt;text x="410" y="232" text-anchor="middle" class="ts">3-year cycle + annual surveillance&lt;/text>
&lt;rect x="580" y="160" width="220" height="80" class="t-aia"/>
&lt;text x="690" y="184" text-anchor="middle" class="tl">EU AI Act inspector&lt;/text>
&lt;text x="690" y="202" text-anchor="middle" class="ts">Reg. 2024/1689 — high risk&lt;/text>
&lt;text x="690" y="218" text-anchor="middle" class="ts">National surveillance authority&lt;/text>
&lt;text x="690" y="232" text-anchor="middle" class="ts">Annex IV dossier + CE marking&lt;/text>
&lt;path class="tar" d="M340,100 L150,158"/>
&lt;path class="tar" d="M410,100 L410,158"/>
&lt;path class="tar" d="M480,100 L670,158"/>
&lt;rect x="160" y="280" width="500" height="80" class="t-evi"/>
&lt;text x="410" y="304" text-anchor="middle" class="tl">One single set of technical evidence labelled for three lenses&lt;/text>
&lt;text x="410" y="322" text-anchor="middle" class="ts">OTel `gen_ai.*` spans + DVC + Vault Anonymize + Langfuse + retrain incidents&lt;/text>
&lt;text x="410" y="338" text-anchor="middle" class="ts">→ tag with ENS code (op.exp.8) + 42001 code (A.8.2) + AI Act article (Art. 12) in metadata&lt;/text>
&lt;path class="tcb" d="M130,240 L300,280"/>
&lt;path class="tcb" d="M410,240 L410,280"/>
&lt;path class="tcb" d="M690,240 L520,280"/>
&lt;text x="410" y="375" text-anchor="middle" class="tn">The building is one. The inspectors are three. Evidence is labelled so each of them finds their part in the same drawer.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>A critical building, say the control centre of a regional electricity grid, a reference hospital, or a 112 emergency response centre, goes through three different inspections of the same physical elements:&lt;/p>
&lt;ul>
&lt;li>The regional inspection reviews the planning permit, local fire regulations and accessibility under regional legislation.&lt;/li>
&lt;li>The European inspection reviews CE marking of equipment and conformity with EU directives on energy efficiency.&lt;/li>
&lt;li>The ISO quality inspection reviews processes and maintenance under a management standard.&lt;/li>
&lt;/ul>
&lt;p>All three inspectors look at the same fire detector hanging from the ceiling. The regional one wants to see the installation certificate with the installer company&amp;rsquo;s number; the European one wants to see the CE mark stamped on the casing; the ISO one wants to see the monthly maintenance record with signatures. There is only one detector. The evidence each inspector needs is different. If the building management team kept one folder per inspector, the installation certificate, the photo of the CE mark and the maintenance sheet would live in three places, and on audit day inconsistencies between the three folders would surface. The professional way to operate: one single file per asset, with tags pointing at the three sets of legislation.&lt;/p>
&lt;p>The on-premise LLM system is that critical building. The OTel spans from &lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">tracing&lt;/a> are the fire detector: a single technical piece that satisfies ENS measure op.exp.8 (activity logging), 42001 control A.8.2 (information to interested parties), and EU AI Act article 12 (record-keeping) provided the spans carry the correct metadata: &lt;code>traceparent&lt;/code> propagated, &lt;code>gen_ai.*&lt;/code> semantic conventions, WORM retention, PII redacted by LLM Guard Vault. Without consistent labelling from capture onwards, audit day produces three folders that do not add up.&lt;/p>
&lt;p>The analogy matters because it bounds an architectural decision: labelling evidence is not a compliance topic, it is a technical design topic. It is decided when the OTel Collector is set up, when the Vault schema is designed, when the format of retrain incidents is agreed. Postpone it, and the debt compounds.&lt;/p>
&lt;h2 id="ens-in-15-seconds">ENS in 15 seconds&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Regulatory framework&lt;/strong>: Royal Decree 311/2022, of 3 May, in force since 5 May 2022. It replaces RD 3/2010. It implements the update of the Esquema with NIS2, the NCS (Estrategia Nacional de Ciberseguridad, Spain&amp;rsquo;s national cybersecurity strategy) and the post-2020 reality.&lt;/li>
&lt;li>&lt;strong>Mandatory scope&lt;/strong>: the Spanish public sector (central, regional and local administration, universities, autonomous bodies), public-sector entities with their own legal personality, and private suppliers that provide IT services to them (a very important clause in consultancy: if your client is the Junta de Andalucía, the Comunidad de Madrid or Bilbao City Council, you fall under ENS by contractual extension).&lt;/li>
&lt;li>&lt;strong>Operational applicability in 2026&lt;/strong>: any AI project financed with NextGenerationEU European funds, plus any project with public data, plus any integration with electronic administrative systems.&lt;/li>
&lt;li>&lt;strong>Categorisation&lt;/strong>: three categories, Basic / Medium / High, according to the assessment of the five dimensions (C, I, T, A, D). The category dictates which measures are required and at what depth.&lt;/li>
&lt;li>&lt;strong>Certification&lt;/strong>: for Medium and High categories, a formal audit by an accredited body. For Basic category, self-assessment. Certification cycle is biennial.&lt;/li>
&lt;li>&lt;strong>Authority&lt;/strong>: the Centro Criptológico Nacional (CCN-CERT, Spain&amp;rsquo;s national cryptologic centre), part of the CNI intelligence agency. It maintains the ens.ccn.cni.es portal with the STIC guides (800 series).&lt;/li>
&lt;li>&lt;strong>Total number of measures&lt;/strong>: 74, organised into three blocks, plus Annex II with the measure × category matrix that defines what applies at each level.&lt;/li>
&lt;/ul>
&lt;h2 id="the-five-security-dimensions-and-their-ai-mapping">The five security dimensions and their AI mapping&lt;/h2>
&lt;p>To classify the system as Basic / Medium / High, the ENS requires each dimension to be rated on a scale (not applicable / low / medium / high). The final category is the highest of the five. For LLM systems, the typical rating:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Dimension&lt;/th>
&lt;th>Meaning&lt;/th>
&lt;th>Typical LLM rating&lt;/th>
&lt;th>Reason&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>C&lt;/strong> Confidentiality&lt;/td>
&lt;td>Information protected from disclosure&lt;/td>
&lt;td>Medium-High&lt;/td>
&lt;td>PII in prompts, secrets in context, intellectual property in the RAG corpus&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>I&lt;/strong> Integrity&lt;/td>
&lt;td>Information protected from modification&lt;/td>
&lt;td>Medium-High&lt;/td>
&lt;td>An altered RAG corpus produces false answers; a manipulated model produces directed bias&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>T&lt;/strong> Traceability&lt;/td>
&lt;td>Actions attributable to users&lt;/td>
&lt;td>High&lt;/td>
&lt;td>Audit: who asked what, when, what were they answered, which dataset trained the model?&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>A&lt;/strong> Authenticity&lt;/td>
&lt;td>Identity of users and origin of information&lt;/td>
&lt;td>Medium&lt;/td>
&lt;td>Robust authentication plus identification of chunks by source&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>D&lt;/strong> Availability&lt;/td>
&lt;td>Service available when needed&lt;/td>
&lt;td>Medium&lt;/td>
&lt;td>Typical SLA, known recovery time&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Typical result&lt;/strong>: Medium category for internal LLM systems with no sensitive data, High if they handle PII or regulated sectoral data (health, tax, judicial). The category triggers different controls.&lt;/p>
&lt;h2 id="the-three-blocks-of-measures">The three blocks of measures&lt;/h2>
&lt;p>&lt;strong>Organisational framework (&lt;code>org&lt;/code>)&lt;/strong>, 4 measures that always apply, transversal:&lt;/p>
&lt;ul>
&lt;li>&lt;code>org.1&lt;/code> Security policy&lt;/li>
&lt;li>&lt;code>org.2&lt;/code> Security regulations&lt;/li>
&lt;li>&lt;code>org.3&lt;/code> Security procedures&lt;/li>
&lt;li>&lt;code>org.4&lt;/code> Authorisation process&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Operational framework (&lt;code>op&lt;/code>)&lt;/strong>, 31 measures in 6 subgroups, these are how it is operated:&lt;/p>
&lt;ul>
&lt;li>&lt;code>op.pl&lt;/code> Planning (5)&lt;/li>
&lt;li>&lt;code>op.acc&lt;/code> Access control (6)&lt;/li>
&lt;li>&lt;code>op.exp&lt;/code> Operation (10)&lt;/li>
&lt;li>&lt;code>op.ext&lt;/code> External services (4)&lt;/li>
&lt;li>&lt;code>op.cont&lt;/code> Service continuity (4)&lt;/li>
&lt;li>&lt;code>op.mon&lt;/code> System monitoring (2)&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Protection measures (&lt;code>mp&lt;/code>)&lt;/strong>, 39 measures in 7 subgroups, these are what is protected:&lt;/p>
&lt;ul>
&lt;li>&lt;code>mp.if&lt;/code> Facility protection (7)&lt;/li>
&lt;li>&lt;code>mp.per&lt;/code> Personnel management (4)&lt;/li>
&lt;li>&lt;code>mp.eq&lt;/code> Equipment protection (4)&lt;/li>
&lt;li>&lt;code>mp.com&lt;/code> Communications protection (4)&lt;/li>
&lt;li>&lt;code>mp.si&lt;/code> Information media protection (5)&lt;/li>
&lt;li>&lt;code>mp.sw&lt;/code> Application protection (2)&lt;/li>
&lt;li>&lt;code>mp.info&lt;/code> Information protection (6)&lt;/li>
&lt;li>&lt;code>mp.s&lt;/code> Service protection (4)&lt;/li>
&lt;/ul>
&lt;p>Of those 74, roughly 25 matter operationally for LLM systems. The rest are either transversal (policies, personnel management, physical facilities) or specific to other layers (telephony, physical media in the literal sense). The rest of this post drops down to the 25 relevant ones.&lt;/p>
&lt;h2 id="technical-mapping-key-ens-measures--42001-controls--eu-ai-act-articles">Technical mapping: key ENS measures × 42001 controls × EU AI Act articles&lt;/h2>
&lt;p>What follows, by ENS subgroup, are the measures relevant to LLM with their cross-mapping. The Technical evidence from the blog column points to the operational artefact that materialises the measure.&lt;/p>
&lt;h3 id="operational-framework--planning-oppl">Operational framework — planning (&lt;code>op.pl&lt;/code>)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>ENS measure&lt;/th>
&lt;th>Requirement&lt;/th>
&lt;th>42001 control&lt;/th>
&lt;th>AI Act article&lt;/th>
&lt;th>Technical evidence&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>op.pl.1&lt;/code> Risk analysis&lt;/td>
&lt;td>Formal periodic analysis, MAGERIT or OCTAVE methodology&lt;/td>
&lt;td>A.5.4 (alignment with AI risk treatment)&lt;/td>
&lt;td>&lt;strong>Art. 9&lt;/strong> Risk management&lt;/td>
&lt;td>Risk document linked to the &lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">LLMOps pipeline&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">evals&lt;/a> metrics&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.pl.2&lt;/code> Security architecture&lt;/td>
&lt;td>Architecture documentation, layer segregation&lt;/td>
&lt;td>A.4.2 documented info&lt;/td>
&lt;td>Art. 15 (technical robustness)&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/en/posts/anatomy-on-premise-llm-inference-stack-seven-layers/">Seven stack layers&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/greenfield-deployment-phases-on-premise-llm-platform/">seven deployment phases&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.pl.3&lt;/code> Acquisition&lt;/td>
&lt;td>Acquisition criteria that include security&lt;/td>
&lt;td>A.10.3 suppliers&lt;/td>
&lt;td>Art. 53 (GPAI obligations)&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/en/posts/parallel-catalogue-llmops-stages-open-source-hyperscalers/">OSS vs hyperscalers&lt;/a> lock-in analysis + copyright analysis&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.pl.4&lt;/code> Sizing&lt;/td>
&lt;td>Capacity to support the expected load&lt;/td>
&lt;td>A.4.5 system resources&lt;/td>
&lt;td>Art. 15 (consistent performance)&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/en/posts/five-maturity-levels-platform-under-llm/">Five maturity levels&lt;/a> + GPU capacity study&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.pl.5&lt;/code> Certified components&lt;/td>
&lt;td>Preference for certified components&lt;/td>
&lt;td>A.10.5 third parties&lt;/td>
&lt;td>Art. 53 + Art. 15&lt;/td>
&lt;td>OSS inventory with licence analysis + supply chain audit (cosign + SLSA)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="operational-framework--access-control-opacc">Operational framework — access control (&lt;code>op.acc&lt;/code>)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>ENS measure&lt;/th>
&lt;th>Requirement&lt;/th>
&lt;th>42001 control&lt;/th>
&lt;th>AI Act article&lt;/th>
&lt;th>Technical evidence&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>op.acc.1&lt;/code> Identification&lt;/td>
&lt;td>Unique identification of users and processes&lt;/td>
&lt;td>A.3.2 roles&lt;/td>
&lt;td>Art. 14 (human oversight)&lt;/td>
&lt;td>Keycloak / OIDC + JWT with unique &lt;code>sub&lt;/code> + &lt;code>user_id_hashed&lt;/code> in OTel spans&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.acc.2&lt;/code> Access requirements&lt;/td>
&lt;td>Need to know, authorisation&lt;/td>
&lt;td>A.9.4 intended use&lt;/td>
&lt;td>Art. 14 + Art. 26 (deployer)&lt;/td>
&lt;td>Per-tenant allowlist in the AI Gateway + RBAC over adapters&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.acc.3&lt;/code> Segregation of duties&lt;/td>
&lt;td>No incompatible accumulation&lt;/td>
&lt;td>A.3.2 + A.3.3&lt;/td>
&lt;td>Art. 17 (QMS)&lt;/td>
&lt;td>Separate roles: AI lead / data steward / SRE / DPO&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.acc.5&lt;/code> Authentication&lt;/td>
&lt;td>Proportionate mechanism, MFA at High&lt;/td>
&lt;td>A.4.4 tooling&lt;/td>
&lt;td>Art. 14&lt;/td>
&lt;td>Keycloak + WebAuthn + mandatory MFA for High category&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.acc.6&lt;/code> Local access&lt;/td>
&lt;td>Protection against physical access&lt;/td>
&lt;td>mp.if + A.4.5&lt;/td>
&lt;td>Art. 15 (cybersec)&lt;/td>
&lt;td>Cluster in a datacenter with physical control (strictly outside the blog&amp;rsquo;s scope)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.acc.7&lt;/code> Remote access&lt;/td>
&lt;td>VPN, encryption, endpoint control&lt;/td>
&lt;td>A.4.5 + mp.com&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>WireGuard / Defguard + mTLS for administrative access to the cluster&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="operational-framework--operation-opexp">Operational framework — operation (&lt;code>op.exp&lt;/code>)&lt;/h3>
&lt;p>This is the densest subgroup and the one that overlaps most with the LLM architecture.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>ENS measure&lt;/th>
&lt;th>Requirement&lt;/th>
&lt;th>42001 control&lt;/th>
&lt;th>AI Act article&lt;/th>
&lt;th>Technical evidence&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>op.exp.1&lt;/code> Asset inventory&lt;/td>
&lt;td>CMDB with all system components&lt;/td>
&lt;td>A.4 resources&lt;/td>
&lt;td>Art. 49 (EU DB registration)&lt;/td>
&lt;td>Inventory of models, adapters and datasets in a CMDB + Helm tags + OpenLineage lineage&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.exp.2&lt;/code> Security configuration&lt;/td>
&lt;td>Documented hardened configuration&lt;/td>
&lt;td>mp.eq + A.4&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>vLLM, KServe and Cilium configurations documented in GitOps + CIS Benchmarks for K8s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.exp.3&lt;/code> Configuration management&lt;/td>
&lt;td>Traceable and authorised changes&lt;/td>
&lt;td>A.6.2 + A.4.2&lt;/td>
&lt;td>Art. 9 + Art. 15&lt;/td>
&lt;td>GitOps with Argo CD / Flux + mandatory PR review + immutable tags&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.exp.5&lt;/code> Change management&lt;/td>
&lt;td>Changes planned, authorised, recorded&lt;/td>
&lt;td>A.6.2.6 + clause 8&lt;/td>
&lt;td>Art. 9 (lifecycle) + Art. 72 (post-market)&lt;/td>
&lt;td>CI/CD pipeline from the &lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">LLMOps post&lt;/a> + change advisory board&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.exp.6&lt;/code> Protection against malicious code&lt;/td>
&lt;td>Antivirus, EDR, malware control&lt;/td>
&lt;td>mp.eq&lt;/td>
&lt;td>Art. 15 (cybersec)&lt;/td>
&lt;td>Image scanning (Trivy / Grype) + container runtime security (Falco / Tetragon)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.exp.7&lt;/code> Incident management&lt;/td>
&lt;td>Detection → response → recovery procedure&lt;/td>
&lt;td>A.3.3 + clause 10&lt;/td>
&lt;td>&lt;strong>Art. 73&lt;/strong> (serious incident reporting)&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">Incident-driven retrain&lt;/a> + CCN-CERT channel + notification template&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>op.exp.8&lt;/code>&lt;/strong> &lt;strong>Activity logging&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Auditable logs, minimum 2-year retention at High category&lt;/strong>&lt;/td>
&lt;td>&lt;strong>A.8.2&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Art. 12 + Art. 19&lt;/strong>&lt;/td>
&lt;td>&lt;strong>&lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">OTel GenAI tracing&lt;/a> + Tempo / Jaeger + Loki&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.exp.9&lt;/code> Incident management records&lt;/td>
&lt;td>Incident log with root cause and corrective action&lt;/td>
&lt;td>clause 10&lt;/td>
&lt;td>Art. 73&lt;/td>
&lt;td>Ticketing system + structured &lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">retrain incident events&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>op.exp.10&lt;/code>&lt;/strong> &lt;strong>Record protection&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Immutable logs, cryptographic integrity, guaranteed retention&lt;/strong>&lt;/td>
&lt;td>&lt;strong>A.8.2 + A.4.2&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Art. 12&lt;/strong>&lt;/td>
&lt;td>&lt;strong>WORM storage (Ceph + immutable bucket) + log signing (sigstore) + 24-36 month retention&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.exp.11&lt;/code> Cryptographic keys&lt;/td>
&lt;td>Key lifecycle management, HSM at High category&lt;/td>
&lt;td>mp.info.4&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>HashiCorp Vault / SOPS + HSM (Yubico, AWS KMS on-prem) for High category&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="operational-framework--external-services-opext">Operational framework — external services (&lt;code>op.ext&lt;/code>)&lt;/h3>
&lt;p>Critical when the system integrates GPAI (Llama, Mistral) hosted elsewhere, or when the deployer is a third party.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>ENS measure&lt;/th>
&lt;th>Requirement&lt;/th>
&lt;th>42001 control&lt;/th>
&lt;th>AI Act article&lt;/th>
&lt;th>Technical evidence&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>op.ext.1&lt;/code> Contracting external services&lt;/td>
&lt;td>Contract with security clauses, SLA, right to audit&lt;/td>
&lt;td>A.10.3 suppliers&lt;/td>
&lt;td>Art. 25 + Art. 53&lt;/td>
&lt;td>Contract with ENS clauses + annual audit review + Cloud Act analysis&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.ext.2&lt;/code> Alternative means&lt;/td>
&lt;td>Plan B if the supplier goes down&lt;/td>
&lt;td>A.4.5 + clause 6&lt;/td>
&lt;td>Art. 15 (resilience)&lt;/td>
&lt;td>Multi-cluster with failover + qualified alternative GPAI (Llama→Mistral→Qwen)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.ext.3&lt;/code> Supply chain protection&lt;/td>
&lt;td>Assessment of suppliers and sub-suppliers&lt;/td>
&lt;td>A.10 + clause 6&lt;/td>
&lt;td>Art. 53 + NIS2 supply chain&lt;/td>
&lt;td>SBOM + cosign + SLSA + continuous vulnerability scanning&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.ext.4&lt;/code> System interconnection&lt;/td>
&lt;td>Interconnection agreements, secure gateways&lt;/td>
&lt;td>mp.com + A.4.5&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>API Gateway with mTLS + JWT signing + rate limiting + WAF&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="operational-framework--continuity-opcont">Operational framework — continuity (&lt;code>op.cont&lt;/code>)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>ENS measure&lt;/th>
&lt;th>Requirement&lt;/th>
&lt;th>42001 control&lt;/th>
&lt;th>AI Act article&lt;/th>
&lt;th>Technical evidence&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>op.cont.1&lt;/code> Impact analysis&lt;/td>
&lt;td>BIA per system&lt;/td>
&lt;td>A.5.5 (impacts on individuals)&lt;/td>
&lt;td>Art. 9 (risk management)&lt;/td>
&lt;td>Documented BIA with RPO / RTO per system&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.cont.2&lt;/code> Continuity plan&lt;/td>
&lt;td>Documented DRP, RTO/RPO&lt;/td>
&lt;td>clause 6 + A.4&lt;/td>
&lt;td>Art. 15 (resilience)&lt;/td>
&lt;td>DRP + Velero K8s backups + DVC datasets in a secondary bucket&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.cont.3&lt;/code> Periodic testing&lt;/td>
&lt;td>Drills at a defined frequency (annual at High category)&lt;/td>
&lt;td>clause 9 (evaluation)&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>Annual game-day with a simulated disaster + recovery stopwatches&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.cont.4&lt;/code> Alternative means&lt;/td>
&lt;td>Capacity to continue with degraded means&lt;/td>
&lt;td>clause 6 + A.4.5&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>Secondary cluster in a different datacenter + reserved GPU pool + replicated datasets&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="operational-framework--monitoring-opmon">Operational framework — monitoring (&lt;code>op.mon&lt;/code>)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>ENS measure&lt;/th>
&lt;th>Requirement&lt;/th>
&lt;th>42001 control&lt;/th>
&lt;th>AI Act article&lt;/th>
&lt;th>Technical evidence&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>&lt;code>op.mon.1&lt;/code>&lt;/strong> &lt;strong>Intrusion detection&lt;/strong>&lt;/td>
&lt;td>&lt;strong>IDS/IPS over network and applications&lt;/strong>&lt;/td>
&lt;td>&lt;strong>A.9.2 use&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Art. 15 (cybersec)&lt;/strong>&lt;/td>
&lt;td>&lt;strong>&lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">Guardrails as a semantic WAF&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard PromptInjection&lt;/a> + Tetragon eBPF runtime&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>op.mon.2&lt;/code>&lt;/strong> &lt;strong>Metrics system&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Measurable operational metrics, auditable dashboard&lt;/strong>&lt;/td>
&lt;td>&lt;strong>clause 9 (performance evaluation)&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Art. 72 (post-market monitoring)&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Prometheus + VictoriaMetrics + Grafana + &lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">Langfuse&lt;/a> dashboards&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>op.mon.3&lt;/code> Surveillance (High category)&lt;/td>
&lt;td>24×7 monitoring with alerting&lt;/td>
&lt;td>clause 9 + clause 10&lt;/td>
&lt;td>Art. 72 + Art. 73&lt;/td>
&lt;td>SOC with SIEM alerts (Wazuh, OpenSearch, Vector + custom) + on-call rotation&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="protection-measures--communications-mpcom">Protection measures — communications (&lt;code>mp.com&lt;/code>)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>ENS measure&lt;/th>
&lt;th>Requirement&lt;/th>
&lt;th>42001 control&lt;/th>
&lt;th>AI Act article&lt;/th>
&lt;th>Technical evidence&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>mp.com.1&lt;/code> Secure perimeter&lt;/td>
&lt;td>Firewall, segmentation, DMZ&lt;/td>
&lt;td>A.4.5&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>Cilium NetworkPolicy + Calico + ingress controllers with WAF (mod_security)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mp.com.2&lt;/code> Confidentiality protection&lt;/td>
&lt;td>Encryption in transit (TLS 1.2+ mandatory, 1.3 recommended)&lt;/td>
&lt;td>A.4.5&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>TLS 1.3 mandatory + cert-manager + Let&amp;rsquo;s Encrypt or an internal CA&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mp.com.3&lt;/code> Integrity and authenticity protection&lt;/td>
&lt;td>Encryption + origin authentication&lt;/td>
&lt;td>A.4.5 + A.4.4&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>Intra-cluster mTLS + JWT signing at the gateway + checksums on artifacts&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mp.com.4&lt;/code> Flow separation&lt;/td>
&lt;td>Segmentation of mgmt vs production vs external traffic&lt;/td>
&lt;td>A.4.5&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>Cilium policies + network namespaces + east-west / north-south segregation&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="protection-measures--applications-mpsw">Protection measures — applications (&lt;code>mp.sw&lt;/code>)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>ENS measure&lt;/th>
&lt;th>Requirement&lt;/th>
&lt;th>42001 control&lt;/th>
&lt;th>AI Act article&lt;/th>
&lt;th>Technical evidence&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>mp.sw.1&lt;/code> Application development&lt;/td>
&lt;td>Secure SDLC, code review, SAST/SCA&lt;/td>
&lt;td>A.6.2.3 responsible design&lt;/td>
&lt;td>Art. 9 + Art. 15&lt;/td>
&lt;td>Forgejo CI with SAST (Semgrep, CodeQL) + SCA (Trivy, Grype) + mandatory PR review&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mp.sw.2&lt;/code> Acceptance and go-live&lt;/td>
&lt;td>Acceptance tests, eval gates before production&lt;/td>
&lt;td>A.6.2.5 V&amp;amp;V&lt;/td>
&lt;td>Art. 9 + Art. 15&lt;/td>
&lt;td>Eval gates from the &lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">evals post&lt;/a> + canary deploy + pre-go-live metrics&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="protection-measures--information-mpinfo">Protection measures — information (&lt;code>mp.info&lt;/code>)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>ENS measure&lt;/th>
&lt;th>Requirement&lt;/th>
&lt;th>42001 control&lt;/th>
&lt;th>AI Act article&lt;/th>
&lt;th>Technical evidence&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>&lt;code>mp.info.1&lt;/code>&lt;/strong> &lt;strong>Personal data&lt;/strong>&lt;/td>
&lt;td>&lt;strong>GDPR compliance + technical and organisational measures&lt;/strong>&lt;/td>
&lt;td>&lt;strong>A.5.5 + A.7.6&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Art. 10&lt;/strong> + Art. 26&lt;/td>
&lt;td>&lt;strong>&lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard Vault&lt;/a> + Presidio + minimisation in &lt;a href="https://blog.lo0.es/en/posts/rag-corpus-curation-active-librarian/">RAG corpus curation&lt;/a>&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mp.info.2&lt;/code> Information classification&lt;/td>
&lt;td>Labelling by level (public / internal / confidential / restricted)&lt;/td>
&lt;td>A.7.2 data&lt;/td>
&lt;td>Art. 10&lt;/td>
&lt;td>Schema contracts from &lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">data versioning&lt;/a> with a &lt;code>classification&lt;/code> field&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mp.info.3&lt;/code> Encryption&lt;/td>
&lt;td>At-rest minimum at Medium category, High category with HSM&lt;/td>
&lt;td>A.4.5 + mp.eq&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>LUKS / dm-crypt on disks + encryption in the Ceph bucket + keys in Vault&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mp.info.4&lt;/code> Electronic signature&lt;/td>
&lt;td>Documents signed with a valid certificate (Medium category and above)&lt;/td>
&lt;td>A.8.2&lt;/td>
&lt;td>Art. 12&lt;/td>
&lt;td>Log signing with sigstore + signing of published models with cosign&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mp.info.5&lt;/code> Timestamps&lt;/td>
&lt;td>Qualified timestamp for temporal integrity (High category)&lt;/td>
&lt;td>A.8.2&lt;/td>
&lt;td>Art. 12&lt;/td>
&lt;td>Timestamping with a qualified TSA + RFC 3161 on critical events&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mp.info.6&lt;/code> Document cleaning&lt;/td>
&lt;td>Removal of unauthorised metadata, anonymisation&lt;/td>
&lt;td>A.7.6 + A.5.5&lt;/td>
&lt;td>Art. 10&lt;/td>
&lt;td>LLM Guard Anonymize (input) + Sensitive (output) + Vault with TTL&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="protection-measures--services-mps">Protection measures — services (&lt;code>mp.s&lt;/code>)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>ENS measure&lt;/th>
&lt;th>Requirement&lt;/th>
&lt;th>42001 control&lt;/th>
&lt;th>AI Act article&lt;/th>
&lt;th>Technical evidence&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>mp.s.1&lt;/code> Email&lt;/td>
&lt;td>Anti-spam, anti-malware, optional encryption&lt;/td>
&lt;td>A.4.5&lt;/td>
&lt;td>—&lt;/td>
&lt;td>Outside the direct LLM scope&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mp.s.2&lt;/code> Protection of web services and applications&lt;/td>
&lt;td>WAF, hardening, vulnerability management&lt;/td>
&lt;td>A.9.2 + mp.com&lt;/td>
&lt;td>Art. 15 (cybersec)&lt;/td>
&lt;td>AI Gateway (LiteLLM / Envoy AI / Kong AI) with policies + ModSecurity + Cloudflare-like&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>mp.s.3&lt;/code> Protection against denial of service&lt;/td>
&lt;td>Rate limiting, anti-DDoS, capacity planning&lt;/td>
&lt;td>A.4.5 + A.9.2&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>Rate limiting at the gateway + token quotas + circuit breakers&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>mp.s.4&lt;/code>&lt;/strong> &lt;strong>Protection against external threats (High category)&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Advanced monitoring, threat intel&lt;/strong>&lt;/td>
&lt;td>&lt;strong>A.9.2 + clause 9&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Art. 15 + Art. 72&lt;/strong>&lt;/td>
&lt;td>&lt;strong>&lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">Guardrails, 4 lines&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">advanced LLM Guard scanners&lt;/a> + threat intel feed (CCN-CERT MISP)&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="master-table-of-triple-compliance--the-25-relevant-controls-consolidated">Master table of triple compliance — the 25 relevant controls consolidated&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 460" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Triple compliance table ENS × 42001 × EU AI Act">
&lt;style>
.c-hdr{fill:#7aafff;stroke:#444;stroke-width:1.4;rx:8}
.c-ens{fill:#ffd76b;stroke:#444;stroke-width:1.4;rx:6}
.c-42001{fill:#a8e6a3;stroke:#444;stroke-width:1.4;rx:6}
.c-aia{fill:#ff8a4c;stroke:#444;stroke-width:1.4;rx:6}
.c-evi{fill:#c8b8ff;stroke:#444;stroke-width:1.4;rx:6}
.cl{font:600 11px sans-serif;fill:#222}
.cs{font:400 10px sans-serif;fill:#444}
.cn{font:italic 10px sans-serif;fill:#555}
&lt;/style>
&lt;rect x="20" y="20" width="780" height="40" class="c-hdr"/>
&lt;text x="410" y="42" text-anchor="middle" class="cl">Master table of triple compliance — ENS (yellow) · 42001 (green) · EU AI Act (orange) · Technical evidence (violet)&lt;/text>
&lt;text x="410" y="55" text-anchor="middle" class="cs">one row per technical capability — each cell gives the code of the framework it satisfies&lt;/text>
&lt;rect x="20" y="80" width="100" height="40" class="c-ens"/>&lt;text x="70" y="100" text-anchor="middle" class="cl">op.exp.8&lt;/text>&lt;text x="70" y="113" text-anchor="middle" class="cs">+ op.exp.10&lt;/text>
&lt;rect x="125" y="80" width="100" height="40" class="c-42001"/>&lt;text x="175" y="100" text-anchor="middle" class="cl">A.8.2&lt;/text>&lt;text x="175" y="113" text-anchor="middle" class="cs">A.4.2 (documentation)&lt;/text>
&lt;rect x="230" y="80" width="100" height="40" class="c-aia"/>&lt;text x="280" y="100" text-anchor="middle" class="cl">Art. 12 + Art. 19&lt;/text>&lt;text x="280" y="113" text-anchor="middle" class="cs">record-keeping&lt;/text>
&lt;rect x="335" y="80" width="465" height="40" class="c-evi"/>&lt;text x="567" y="100" text-anchor="middle" class="cl">OTel `gen_ai.*` spans + Tempo + WORM Ceph + 24-36m retention + sigstore&lt;/text>&lt;text x="567" y="113" text-anchor="middle" class="cs">trace_id propagated + prompt_id + dataset_hash + adapter_version + PII redacted&lt;/text>
&lt;rect x="20" y="125" width="100" height="40" class="c-ens"/>&lt;text x="70" y="145" text-anchor="middle" class="cl">op.mon.1&lt;/text>&lt;text x="70" y="158" text-anchor="middle" class="cs">+ mp.s.4&lt;/text>
&lt;rect x="125" y="125" width="100" height="40" class="c-42001"/>&lt;text x="175" y="145" text-anchor="middle" class="cl">A.9.2&lt;/text>&lt;text x="175" y="158" text-anchor="middle" class="cs">responsible use&lt;/text>
&lt;rect x="230" y="125" width="100" height="40" class="c-aia"/>&lt;text x="280" y="145" text-anchor="middle" class="cl">Art. 15&lt;/text>&lt;text x="280" y="158" text-anchor="middle" class="cs">cybersec + robustness&lt;/text>
&lt;rect x="335" y="125" width="465" height="40" class="c-evi"/>&lt;text x="567" y="145" text-anchor="middle" class="cl">Guardrails, 4 lines + LLM Guard PromptInjection + Tetragon eBPF + Llama Guard 4&lt;/text>&lt;text x="567" y="158" text-anchor="middle" class="cs">jailbreak + indirect injection + tool abuse + PII leakage in output detection&lt;/text>
&lt;rect x="20" y="170" width="100" height="40" class="c-ens"/>&lt;text x="70" y="190" text-anchor="middle" class="cl">op.mon.2&lt;/text>&lt;text x="70" y="203" text-anchor="middle" class="cs">+ op.mon.3&lt;/text>
&lt;rect x="125" y="170" width="100" height="40" class="c-42001"/>&lt;text x="175" y="190" text-anchor="middle" class="cl">clause 9&lt;/text>&lt;text x="175" y="203" text-anchor="middle" class="cs">performance eval&lt;/text>
&lt;rect x="230" y="170" width="100" height="40" class="c-aia"/>&lt;text x="280" y="190" text-anchor="middle" class="cl">Art. 72&lt;/text>&lt;text x="280" y="203" text-anchor="middle" class="cs">post-market monitoring&lt;/text>
&lt;rect x="335" y="170" width="465" height="40" class="c-evi"/>&lt;text x="567" y="190" text-anchor="middle" class="cl">Prometheus + VictoriaMetrics + Grafana + Langfuse dashboards + continuous evals&lt;/text>&lt;text x="567" y="203" text-anchor="middle" class="cs">F1 per guardrail category + RAG faithfulness + statistical drift + 24×7 cadence&lt;/text>
&lt;rect x="20" y="215" width="100" height="40" class="c-ens"/>&lt;text x="70" y="235" text-anchor="middle" class="cl">op.exp.7&lt;/text>&lt;text x="70" y="248" text-anchor="middle" class="cs">+ op.exp.9&lt;/text>
&lt;rect x="125" y="215" width="100" height="40" class="c-42001"/>&lt;text x="175" y="235" text-anchor="middle" class="cl">clause 10&lt;/text>&lt;text x="175" y="248" text-anchor="middle" class="cs">A.3.3 reporting&lt;/text>
&lt;rect x="230" y="215" width="100" height="40" class="c-aia"/>&lt;text x="280" y="235" text-anchor="middle" class="cl">Art. 73&lt;/text>&lt;text x="280" y="248" text-anchor="middle" class="cs">serious incidents&lt;/text>
&lt;rect x="335" y="215" width="465" height="40" class="c-evi"/>&lt;text x="567" y="235" text-anchor="middle" class="cl">Structured incident events + closed retrain loop + CCN-CERT notification&lt;/text>&lt;text x="567" y="248" text-anchor="middle" class="cs">15/10/2-day deadline + incident template + root cause + effectiveness check&lt;/text>
&lt;rect x="20" y="260" width="100" height="40" class="c-ens"/>&lt;text x="70" y="280" text-anchor="middle" class="cl">mp.info.1&lt;/text>&lt;text x="70" y="293" text-anchor="middle" class="cs">+ mp.info.6&lt;/text>
&lt;rect x="125" y="260" width="100" height="40" class="c-42001"/>&lt;text x="175" y="280" text-anchor="middle" class="cl">A.7.6&lt;/text>&lt;text x="175" y="293" text-anchor="middle" class="cs">data preparation&lt;/text>
&lt;rect x="230" y="260" width="100" height="40" class="c-aia"/>&lt;text x="280" y="280" text-anchor="middle" class="cl">Art. 10&lt;/text>&lt;text x="280" y="293" text-anchor="middle" class="cs">data governance&lt;/text>
&lt;rect x="335" y="260" width="465" height="40" class="c-evi"/>&lt;text x="567" y="280" text-anchor="middle" class="cl">LLM Guard Anonymize Vault + Presidio + RAG corpus curation, 5 layers&lt;/text>&lt;text x="567" y="293" text-anchor="middle" class="cs">runtime redaction + anti-contamination + chunk→trace lineage + measured PII F1&lt;/text>
&lt;rect x="20" y="305" width="100" height="40" class="c-ens"/>&lt;text x="70" y="325" text-anchor="middle" class="cl">op.pl.1&lt;/text>&lt;text x="70" y="338" text-anchor="middle" class="cs">risk analysis&lt;/text>
&lt;rect x="125" y="305" width="100" height="40" class="c-42001"/>&lt;text x="175" y="325" text-anchor="middle" class="cl">A.5.2-5.6&lt;/text>&lt;text x="175" y="338" text-anchor="middle" class="cs">impact assessment&lt;/text>
&lt;rect x="230" y="305" width="100" height="40" class="c-aia"/>&lt;text x="280" y="325" text-anchor="middle" class="cl">Art. 9&lt;/text>&lt;text x="280" y="338" text-anchor="middle" class="cs">risk management&lt;/text>
&lt;rect x="335" y="305" width="465" height="40" class="c-evi"/>&lt;text x="567" y="325" text-anchor="middle" class="cl">MAGERIT/OCTAVE risk doc + FRIA (if a public deployer applies) + fairness evals&lt;/text>&lt;text x="567" y="338" text-anchor="middle" class="cs">one methodology, three languages — the matrix is exported with three labels&lt;/text>
&lt;rect x="20" y="350" width="100" height="40" class="c-ens"/>&lt;text x="70" y="370" text-anchor="middle" class="cl">op.ext.1&lt;/text>&lt;text x="70" y="383" text-anchor="middle" class="cs">+ op.ext.3&lt;/text>
&lt;rect x="125" y="350" width="100" height="40" class="c-42001"/>&lt;text x="175" y="370" text-anchor="middle" class="cl">A.10.3&lt;/text>&lt;text x="175" y="383" text-anchor="middle" class="cs">suppliers&lt;/text>
&lt;rect x="230" y="350" width="100" height="40" class="c-aia"/>&lt;text x="280" y="370" text-anchor="middle" class="cl">Art. 53&lt;/text>&lt;text x="280" y="383" text-anchor="middle" class="cs">GPAI + NIS2 supply chain&lt;/text>
&lt;rect x="335" y="350" width="465" height="40" class="c-evi"/>&lt;text x="567" y="370" text-anchor="middle" class="cl">OSS vs hyperscalers lock-in analysis + SBOM + cosign + SLSA + Cloud Act analysis&lt;/text>&lt;text x="567" y="383" text-anchor="middle" class="cs">supplier register with licence + jurisdiction + plan B + annual audit&lt;/text>
&lt;rect x="20" y="395" width="100" height="40" class="c-ens"/>&lt;text x="70" y="415" text-anchor="middle" class="cl">mp.sw.1&lt;/text>&lt;text x="70" y="428" text-anchor="middle" class="cs">+ mp.sw.2&lt;/text>
&lt;rect x="125" y="395" width="100" height="40" class="c-42001"/>&lt;text x="175" y="415" text-anchor="middle" class="cl">A.6.2.3-2.5&lt;/text>&lt;text x="175" y="428" text-anchor="middle" class="cs">SDLC + V&amp;amp;V&lt;/text>
&lt;rect x="230" y="395" width="100" height="40" class="c-aia"/>&lt;text x="280" y="415" text-anchor="middle" class="cl">Arts. 9 + 15&lt;/text>&lt;text x="280" y="428" text-anchor="middle" class="cs">development + accuracy&lt;/text>
&lt;rect x="335" y="395" width="465" height="40" class="c-evi"/>&lt;text x="567" y="415" text-anchor="middle" class="cl">Forgejo CI + Semgrep SAST + Trivy SCA + eval gates + canary + pre-go-live metrics&lt;/text>&lt;text x="567" y="428" text-anchor="middle" class="cs">one CI/CD pipeline with triple labelling of the evidence artefact&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>The key reading of the table: one row per technical capability. The organisation does not build three solutions for &amp;ldquo;activity log&amp;rdquo; (ENS) + &amp;ldquo;information to interested parties&amp;rdquo; (42001) + &amp;ldquo;record-keeping&amp;rdquo; (AI Act). It builds one single piece of OTel tracing with the correct metadata and presents it labelled for whichever inspector is asking.&lt;/p>
&lt;p>The labelling typically materialises in three mechanisms:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Tags in the CI/CD pipelines&lt;/strong>: every artefact produced carries tags &lt;code>ens:op.exp.8&lt;/code>, &lt;code>iso42001:A.8.2&lt;/code>, &lt;code>aia:art.12&lt;/code> in its Helm / Argo CD metadata.&lt;/li>
&lt;li>&lt;strong>Semantic OTel attributes&lt;/strong>: &lt;code>gen_ai.compliance.ens = &amp;quot;op.exp.8&amp;quot;&lt;/code>, &lt;code>gen_ai.compliance.iso42001 = &amp;quot;A.8.2&amp;quot;&lt;/code> as custom attributes on the relevant spans (not standard yet, but useful internally).&lt;/li>
&lt;li>&lt;strong>A mapping table in the wiki&lt;/strong>: a living document of ENS measure → 42001 control → AI Act article → technical runbook + owner + last verification. This is the artefact the auditor consults.&lt;/li>
&lt;/ol>
&lt;h2 id="applied-case-multi-tenant-chatbot-for-public-administration">Applied case: multi-tenant chatbot for public administration&lt;/h2>
&lt;p>A variant of the &lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">multi-tenant chatbot from the forensic post&lt;/a>, where the client is now a Spanish regional government offering citizen assistance on administrative procedures. The three lenses:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>ENS&lt;/strong>: applies mandatorily, being public sector plus a service to citizens. Category High (citizens&amp;rsquo; PII plus a critical service).&lt;/li>
&lt;li>&lt;strong>ISO 42001&lt;/strong>: the regional government requires certification from the supplier as a contractual condition.&lt;/li>
&lt;li>&lt;strong>EU AI Act&lt;/strong>: if the chatbot informs about procedures but decides nothing on the citizen&amp;rsquo;s behalf, it is limited risk (Art. 50 transparency). If it automates decisions (admission to a programme, refusal of a benefit), it is high risk.&lt;/li>
&lt;/ul>
&lt;p>We assume high risk for the most demanding walkthrough.&lt;/p>
&lt;h3 id="the-chatbots-25-key-technical-capabilities-consolidated">The chatbot&amp;rsquo;s 25 key technical capabilities consolidated&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Technical capability&lt;/th>
&lt;th>ENS&lt;/th>
&lt;th>42001&lt;/th>
&lt;th>AI Act&lt;/th>
&lt;th>Status&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Robust citizen authentication&lt;/td>
&lt;td>&lt;code>op.acc.5&lt;/code> (MFA High category)&lt;/td>
&lt;td>A.4.4&lt;/td>
&lt;td>Art. 14&lt;/td>
&lt;td>Cl@ve + digital certificate + WebAuthn&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Unique tenant + user identification&lt;/td>
&lt;td>&lt;code>op.acc.1&lt;/code>&lt;/td>
&lt;td>A.3.2&lt;/td>
&lt;td>Art. 14 + 26&lt;/td>
&lt;td>JWT with &lt;code>tenant_id&lt;/code> + &lt;code>user_id_hashed&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Per-tenant allowlist&lt;/td>
&lt;td>&lt;code>op.acc.2&lt;/code>&lt;/td>
&lt;td>A.9.4&lt;/td>
&lt;td>Art. 14&lt;/td>
&lt;td>LiteLLM Proxy with policies + Envoy filter&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Encryption in transit, mTLS&lt;/td>
&lt;td>&lt;code>mp.com.2-3&lt;/code>&lt;/td>
&lt;td>A.4.5&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>TLS 1.3 + cert-manager + intra-mesh mTLS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Encryption at rest + HSM keys&lt;/td>
&lt;td>&lt;code>mp.info.3&lt;/code> + &lt;code>op.exp.11&lt;/code>&lt;/td>
&lt;td>A.4.5&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>LUKS + Vault + Yubikey or nCipher HSM&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>PII anonymisation input/output&lt;/td>
&lt;td>&lt;code>mp.info.1 + .6&lt;/code>&lt;/td>
&lt;td>A.7.6&lt;/td>
&lt;td>Art. 10&lt;/td>
&lt;td>LLM Guard Vault + Presidio + Llama Guard 4&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Traceable logging + WORM&lt;/td>
&lt;td>&lt;strong>&lt;code>op.exp.8 + .10&lt;/code>&lt;/strong>&lt;/td>
&lt;td>&lt;strong>A.8.2&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Arts. 12 + 19&lt;/strong>&lt;/td>
&lt;td>OTel + Tempo + Loki + Ceph WORM 36 months&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Log signing + timestamps&lt;/td>
&lt;td>&lt;code>mp.info.4 + .5&lt;/code>&lt;/td>
&lt;td>A.8.2&lt;/td>
&lt;td>Art. 12&lt;/td>
&lt;td>sigstore + qualified TSA (FNMT)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Runtime threat detection&lt;/td>
&lt;td>&lt;strong>&lt;code>op.mon.1 + mp.s.4&lt;/code>&lt;/strong>&lt;/td>
&lt;td>&lt;strong>A.9.2&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Art. 15&lt;/strong>&lt;/td>
&lt;td>Guardrails, 4 lines + Tetragon + Falco&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Operational metrics dashboard&lt;/td>
&lt;td>&lt;strong>&lt;code>op.mon.2&lt;/code>&lt;/strong>&lt;/td>
&lt;td>&lt;strong>clause 9&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Art. 72&lt;/strong>&lt;/td>
&lt;td>Prometheus + Grafana + Langfuse&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>24×7 SOC surveillance&lt;/td>
&lt;td>&lt;code>op.mon.3&lt;/code> (High category)&lt;/td>
&lt;td>clause 9&lt;/td>
&lt;td>Art. 72&lt;/td>
&lt;td>SOC with SIEM + on-call rotation&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Incident management with notification&lt;/td>
&lt;td>&lt;strong>&lt;code>op.exp.7 + .9&lt;/code>&lt;/strong>&lt;/td>
&lt;td>&lt;strong>clause 10&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Art. 73&lt;/strong>&lt;/td>
&lt;td>Incident-driven retrain + CCN-CERT channel&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CI/CD pipeline with eval gates&lt;/td>
&lt;td>&lt;code>mp.sw.1 + .2&lt;/code>&lt;/td>
&lt;td>A.6.2.3-5&lt;/td>
&lt;td>Arts. 9 + 15&lt;/td>
&lt;td>Forgejo CI + Semgrep + Trivy + DeepEval gates&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Backups + DRP&lt;/td>
&lt;td>&lt;code>op.cont.1-4&lt;/code>&lt;/td>
&lt;td>clause 6 + A.4&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>Velero + DVC datasets in a secondary bucket + annual game-day&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Systematic risk analysis&lt;/td>
&lt;td>&lt;code>op.pl.1&lt;/code>&lt;/td>
&lt;td>A.5 + cl.6&lt;/td>
&lt;td>Art. 9&lt;/td>
&lt;td>MAGERIT + FRIA + ISO/IEC 23894 impact assessment&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Documented secure architecture&lt;/td>
&lt;td>&lt;code>op.pl.2&lt;/code>&lt;/td>
&lt;td>A.4.2&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/en/posts/anatomy-on-premise-llm-inference-stack-seven-layers/">Seven layers&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/greenfield-deployment-phases-on-premise-llm-platform/">seven phases&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Supplier management&lt;/td>
&lt;td>&lt;code>op.ext.1 + .3&lt;/code>&lt;/td>
&lt;td>A.10.3&lt;/td>
&lt;td>Art. 53&lt;/td>
&lt;td>Contracts with ENS clauses + SBOM + Cloud Act analysis per GPAI&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Certified components&lt;/td>
&lt;td>&lt;code>op.pl.5&lt;/code>&lt;/td>
&lt;td>A.10.5&lt;/td>
&lt;td>Art. 53&lt;/td>
&lt;td>Inventory with licence + supply chain audit&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Configuration hardening&lt;/td>
&lt;td>&lt;code>op.exp.2 + .3&lt;/code>&lt;/td>
&lt;td>A.4 + A.6&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>CIS Benchmarks for K8s + GitOps + immutable tags&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Protection against malicious code&lt;/td>
&lt;td>&lt;code>op.exp.6&lt;/code>&lt;/td>
&lt;td>mp.eq&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>Image scanning + runtime security&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Asset inventory&lt;/td>
&lt;td>&lt;code>op.exp.1&lt;/code>&lt;/td>
&lt;td>A.4&lt;/td>
&lt;td>Art. 49&lt;/td>
&lt;td>CMDB + Helm tags + OpenLineage&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Network segmentation&lt;/td>
&lt;td>&lt;code>mp.com.4&lt;/code>&lt;/td>
&lt;td>A.4.5&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>Cilium NetworkPolicy + namespaces&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rate limiting + anti-DoS&lt;/td>
&lt;td>&lt;code>mp.s.3&lt;/code>&lt;/td>
&lt;td>A.4 + A.9&lt;/td>
&lt;td>Art. 15&lt;/td>
&lt;td>LiteLLM rate limit + token quotas&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Information classification&lt;/td>
&lt;td>&lt;code>mp.info.2&lt;/code>&lt;/td>
&lt;td>A.7.2&lt;/td>
&lt;td>Art. 10&lt;/td>
&lt;td>Schema contracts with a &lt;code>classification&lt;/code> field&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Transparency towards the user&lt;/td>
&lt;td>—&lt;/td>
&lt;td>A.9.4&lt;/td>
&lt;td>&lt;strong>Art. 50&lt;/strong>&lt;/td>
&lt;td>UI banner + disclaimer in responses&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Result&lt;/strong>: the 25 technical capabilities are common to all three frameworks. Only two require evidence for a single framework in isolation: &lt;code>mp.info.4-5&lt;/code> (qualified signature and timestamp, ENS-specific, not required so literally by 42001 or the AI Act) and &lt;code>Art. 50&lt;/code> (transparency banner, AI Act-specific). The rest are the same technical piece with three labels. A well-organised team certifies all three in series with only marginal extra work between the second and the third.&lt;/p>
&lt;h2 id="the-five-traps-of-triple-compliance">The five traps of triple compliance&lt;/h2>
&lt;p>&lt;strong>Trap 1 — Measuring the same thing three times.&lt;/strong> Inexperienced teams create three separate dashboards (one for ENS, another for 42001, another for the AI Act) with the same metrics duplicated. The result: three sources of truth that diverge, three audit teams with different figures, three corrections to resolve one and the same deviation. The rule: one metric, three labels.&lt;/p>
&lt;p>&lt;strong>Trap 2 — Losing the control that covers only one framework.&lt;/strong> &lt;code>mp.info.4&lt;/code> (electronic signature) is ENS-specific and gets forgotten while the team is focused on 42001 + AI Act. On ENS audit day, the gap surfaces. Solution: the master table keeps every control visible, orphans included.&lt;/p>
&lt;p>&lt;strong>Trap 3 — Bias towards the most recent framework.&lt;/strong> The team spends 80% of the effort on the AI Act because it is the newest, and forgets the rigour of the ENS, which has been in force for 14 years. The ENS measures are technically more prescriptive than the AI Act (which is legally stricter but leaves implementation freedom). Moving up a level to ENS High category introduces specific requirements (HSM, qualified timestamps, 24×7 surveillance) that the AI Act does not spell out. The ENS deserves respect for its technical detail, not for its legal stature.&lt;/p>
&lt;p>&lt;strong>Trap 4 — Mixing ENS Medium and High categories.&lt;/strong> The Annex II matrix of RD 311/2022 dictates which measures are required and at what depth per category. Moving from Medium to High changes 15-20 controls, which is not marginal. The category is decided at the start of the project and documented; changing it halfway forces a complete re-audit.&lt;/p>
&lt;p>&lt;strong>Trap 5 — Audit fatigue inside the technical team.&lt;/strong> Three audits a year (biennial ENS + annual 42001 surveillance + ad-hoc AI Act by the authority) exhausts the team if they are not planned and if evidence is not reused. The professional approach: one single quarterly internal audit cycle with rotating scope, producing evidence consumable by all three external auditors. The difference between 30 days a year and 90 days a year lost to audits is the discipline of single evidence plus disciplined labelling.&lt;/p>
&lt;h2 id="what-we-have-not-covered-upcoming-posts">What we have not covered (upcoming posts)&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>The CCN-CERT STIC series&lt;/strong> applicable to AI systems: STIC 800-159 (web service operation), STIC 800-105 (cryptography), STIC 800-150 (cloud environments). Each adds technical detail on how to materialise the ENS measures.&lt;/li>
&lt;li>&lt;strong>The GDPR as a fourth lens&lt;/strong>: privacy and personal data protection. It overlaps with &lt;code>mp.info.1&lt;/code> and with A.7.6 of 42001 plus Art. 10 of the AI Act. Material for an analogous pass over LOPDGDD + GDPR + AEPD against the three frameworks seen here.&lt;/li>
&lt;li>&lt;strong>Concrete templates&lt;/strong> of technical evidence with minimum fields: a log entry with every attribute required by the three lenses, an incident report with every field demanded by the three frameworks, a unified ENS / AI Act / 42001 declaration of conformity.&lt;/li>
&lt;li>&lt;strong>The NextGenerationEU funds case&lt;/strong>: specific compliance requirements for AI projects financed with European funds, where the AI Act + ENS are contractually mandatory.&lt;/li>
&lt;li>&lt;strong>42001 + ENS High category combined with DORA&lt;/strong> (Digital Operational Resilience Act, Reg. 2022/2554) for Spanish financial entities deploying AI.&lt;/li>
&lt;/ul>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>RD 311/2022&lt;/strong> — &lt;em>por el que se regula el Esquema Nacional de Seguridad&lt;/em>. &lt;a href="https://www.boe.es/buscar/act.php?id=BOE-A-2022-7191">https://www.boe.es/buscar/act.php?id=BOE-A-2022-7191&lt;/a>.&lt;/li>
&lt;li>&lt;strong>CCN-CERT — Portal ENS&lt;/strong>: &lt;a href="https://ens.ccn.cni.es/">https://ens.ccn.cni.es/&lt;/a>. Guías STIC Series 800.&lt;/li>
&lt;li>&lt;strong>CCN-STIC 803&lt;/strong> — &lt;em>Valoración de los sistemas y de la información&lt;/em>. Metodología para asignar categoría ENS.&lt;/li>
&lt;li>&lt;strong>CCN-STIC 804&lt;/strong> — &lt;em>Esquema Nacional de Seguridad. Guía de implantación&lt;/em>.&lt;/li>
&lt;li>&lt;strong>CCN-STIC 824&lt;/strong> — &lt;em>Informe del estado de seguridad&lt;/em>. Plantilla para auditoría ENS.&lt;/li>
&lt;li>&lt;strong>MAGERIT v3&lt;/strong> — Metodología de Análisis y Gestión de Riesgos del Ministerio de Asuntos Económicos. Insumo de &lt;code>op.pl.1&lt;/code>.&lt;/li>
&lt;li>&lt;strong>ISO/IEC 42001:2023&lt;/strong> — Sistema de gestión IA. Norma certificable que solapa con ENS.&lt;/li>
&lt;li>&lt;strong>Regulation (EU) 2024/1689 (EU AI Act)&lt;/strong> — texto consolidado.&lt;/li>
&lt;li>&lt;strong>NIS2 (Dir. 2022/2555)&lt;/strong> — directiva de ciberseguridad que el ENS implementa parcialmente en su versión 2022.&lt;/li>
&lt;/ul>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/hardening-secrets-sovereign-llm-stack-defence-in-depth/">Hardening and secrets in the sovereign LLM stack: defence in depth&lt;/a> — the technical implementation of many of these controls: network, secrets, supply chain.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/iso-42001-aims-on-premise-llm-platform/">ISO/IEC 42001: the operations manual for the AI system&lt;/a> — the first post of the governance trilogy, the certifiable management system.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/eu-ai-act-technical-dossier-on-premise-llm-architecture/">EU AI Act: the technical dossier, article by article&lt;/a> — the second post, the directly applicable EU legal regulation.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">The six-stage LLMOps pipeline&lt;/a> — the reference operational architecture that supports all three frameworks.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">LLM tracing with OpenTelemetry GenAI&lt;/a> — the canonical piece that materialises &lt;code>op.exp.8 + .10&lt;/code> ENS + A.8.2 ISO 42001 + Arts. 12 + 19 AI Act simultaneously.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">Guardrails and safety in LLMs&lt;/a> — the piece that materialises &lt;code>op.mon.1 + mp.s.4&lt;/code> ENS + A.9.2 ISO 42001 + Art. 15 AI Act.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard: the sworn translator with a book of equivalences&lt;/a> — the piece that materialises &lt;code>mp.info.1 + .6&lt;/code> ENS + A.7.6 ISO 42001 + Art. 10 AI Act.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">Data versioning with DVC and lakeFS&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/rag-corpus-curation-active-librarian/">RAG corpus curation&lt;/a> — the pieces that materialise &lt;code>mp.info.1-2&lt;/code> ENS + A.7 ISO 42001 + Art. 10 AI Act.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">Retrain: closing the feedback → dataset → adapter loop&lt;/a> — the piece that materialises &lt;code>op.exp.7 + .9&lt;/code> ENS + clause 10 ISO 42001 + Art. 73 AI Act.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">Evals: the layer after tracing&lt;/a> — the piece that materialises &lt;code>mp.sw.2&lt;/code> ENS + A.6.2.5 ISO 42001 + Art. 15 AI Act.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/anatomy-on-premise-llm-inference-stack-seven-layers/">Seven layers of the stack&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/greenfield-deployment-phases-on-premise-llm-platform/">seven deployment phases&lt;/a> — the direct material for &lt;code>op.pl.2&lt;/code> architecture.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/parallel-catalogue-llmops-stages-open-source-hyperscalers/">The parallel catalogue: OSS vs hyperscalers&lt;/a> — input for &lt;code>op.ext.1 + .3&lt;/code> supplier analysis + Art. 53 GPAI.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/oss-catalogue-llmops-six-stages-tool-by-tool/">The OSS catalogue for LLMOps&lt;/a> — inventory of components with licences for &lt;code>op.exp.1&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">Anatomy of an LLM request in production&lt;/a> — the forensic case walked through with the triple lens in this post.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/incident-response-runbooks-llm-inference-kafka-keep/">Incident response runbooks for LLMs with Keep + Kafka&lt;/a> — the concrete piece that materialises &lt;code>op.exp.7-10&lt;/code> ENS + A.8.2 ISO 42001 + Art. 73 EU AI Act simultaneously: declarative Keep workflows + WORM Kafka &lt;code>audit.actions&lt;/code> + NIS2 deadlines of 24h/72h/1 month.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/isolating-ai-agents-workstation-to-cluster/">Isolating AI agents: from the workstation to the cluster&lt;/a> and its &lt;a href="https://blog.lo0.es/en/posts/runbook-caging-ai-agent-bubblewrap-tetragon/">operational runbook&lt;/a> — runtime isolation (bubblewrap on the client, Tetragon in the cluster) as a materialisation of &lt;code>op.mon&lt;/code> (monitoring) and &lt;code>op.exp&lt;/code> (activity logging): Tetragon&amp;rsquo;s eBPF events are technical evidence of what each agent executed and of every blocked access attempt.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/model-chain-of-trust-signature-provenance-aibom-why-you-trust-those-bytes/">Model chain of trust (3/4): signing, provenance and AIBOM&lt;/a> — the technical evidence of model provenance (signature, attestations and AIBOM) that feeds the technical documentation of Art. 11 + Annex IV of the AI Act and the traceability controls of ISO 42001.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/model-chain-of-trust-who-serves-the-model-which-machine-you-trust/">Model chain of trust (4/4): workload identity and isolation&lt;/a> — SPIFFE/SPIRE and confidential computing as a materialisation of &lt;code>op.acc&lt;/code> and &lt;code>mp.com&lt;/code> in the ENS and of Art. 15 on robustness and cybersecurity.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/litellm-virtual-keys-budgets-limits/">Virtual keys, budgets and limits in LiteLLM&lt;/a> — the concrete implementation of access control and administrative traceability at the gateway, with what it demonstrates to an auditor and what it does not.&lt;/p>
&lt;/li>
&lt;/ul></description></item><item><title>ISO/IEC 42001: the operations manual for the AI system — how the AIMS fits over the on-premise LLM platform described in this blog</title><link>https://blog.lo0.es/en/posts/iso-42001-aims-on-premise-llm-platform/</link><pubDate>Mon, 01 Jun 2026 06:00:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/iso-42001-aims-on-premise-llm-platform/</guid><description>&lt;blockquote>
&lt;p>This post closes an asymmetry the blog had been accumulating: we have described in detail the technical platform (&lt;a href="https://blog.lo0.es/en/posts/anatomy-on-premise-llm-inference-stack-seven-layers/">seven stack layers&lt;/a>, &lt;a href="https://blog.lo0.es/en/posts/greenfield-deployment-phases-on-premise-llm-platform/">seven deployment phases&lt;/a>, &lt;a href="https://blog.lo0.es/en/posts/five-maturity-levels-platform-under-llm/">five maturity levels&lt;/a>), the operational pipeline (&lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">six LLMOps stages&lt;/a>), the data pieces (&lt;a href="https://blog.lo0.es/en/posts/rag-corpus-curation-active-librarian/">corpus curation&lt;/a>, &lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">versioning&lt;/a>), the eval / safety pieces (&lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">evals&lt;/a>, &lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">guardrails&lt;/a>, &lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard&lt;/a>) and the observe pieces (&lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">OTel GenAI tracing&lt;/a>). What had not appeared is the governance layer a regulated client asks for on top of all that. ISO/IEC 42001 is that layer.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>ISO/IEC 42001:2023 is the first certifiable international standard that defines how an AI system is managed. It is not a technical standard (it does not say &amp;ldquo;use this inference engine&amp;rdquo; or &amp;ldquo;this safety threshold&amp;rdquo;): it is a management standard, a cousin of ISO 27001 and ISO 9001. From both it inherits the Annex SL structure, seven mandatory clauses running through context, leadership, planning, support, operation, performance evaluation and improvement, and it adds an Annex A with 38 AI-specific controls in 9 sections: policies, internal organisation, resources, impact assessment, lifecycle, data, information to interested parties, use, third parties. The thesis of this post is that the technical architecture described in this blog directly covers between 60% and 80% of the A controls with no extra work. The LLMOps pipeline materialises A.6, versioning and curation materialise A.7, guardrails and evals materialise A.9, OTel tracing materialises A.8. The rest is governance discipline that does not appear in the code (a written AI policy, impact assessments per system, a stakeholder register, role decisions between provider/producer/customer, mandatory documentation), and that is precisely what separates a real certification from performative compliance. The post maps the correspondence control by control, cross-references it with the EU AI Act (seven articles directly aligned with 42001: 9, 10, 11, 12, 13, 14, 17), with NIS2 (asset register, incident notification, supply chain) and with ENS (Esquema Nacional de Seguridad, Spain&amp;rsquo;s national security framework, RD 311/2022, Basic/Medium/High categories), lists the seven mandatory minimum documents an auditor asks for, presents the blog&amp;rsquo;s multi-tenant chatbot as a live 42001 checklist, and closes with the five usual traps (confusing 42001 with EU AI Act compliance, over-documenting without measuring, ignoring A.5 until the audit, assuming 27001 covers the AI part, thinking certification is a one-off project rather than a living system).&lt;/p>
&lt;h2 id="the-analogy-the-aircraft-operations-manual">The analogy: the aircraft operations manual&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="ISO 42001 as an aircraft operations manual audited by EASA">
&lt;style>
.i-air{fill:#7aafff;stroke:#444;stroke-width:1.4;rx:8}
.i-man{fill:#ffd76b;stroke:#444;stroke-width:1.4;rx:8}
.i-aud{fill:#a8e6a3;stroke:#444;stroke-width:1.4;rx:8}
.i-ops{fill:#ff8a4c;stroke:#444;stroke-width:1.4;rx:8}
.il{font:600 13px sans-serif;fill:#222}
.is{font:400 11px sans-serif;fill:#555}
.in{font:italic 11px sans-serif;fill:#555}
.iar{stroke:#666;stroke-width:1.6;fill:none;marker-end:url(#mi1)}
&lt;/style>
&lt;defs>&lt;marker id="mi1" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;rect x="20" y="20" width="160" height="60" class="i-air"/>
&lt;text x="100" y="44" text-anchor="middle" class="il">AI system&lt;/text>
&lt;text x="100" y="62" text-anchor="middle" class="is">on-premise LLM platform&lt;/text>
&lt;text x="100" y="76" text-anchor="middle" class="is">(the aircraft)&lt;/text>
&lt;rect x="220" y="20" width="180" height="60" class="i-ops"/>
&lt;text x="310" y="40" text-anchor="middle" class="il">Technical operations&lt;/text>
&lt;text x="310" y="58" text-anchor="middle" class="is">LLMOps pipeline + guardrails +&lt;/text>
&lt;text x="310" y="72" text-anchor="middle" class="is">tracing + retrain (the flights)&lt;/text>
&lt;rect x="440" y="20" width="180" height="60" class="i-man"/>
&lt;text x="530" y="40" text-anchor="middle" class="il">Operations manual&lt;/text>
&lt;text x="530" y="58" text-anchor="middle" class="is">policies + impact assessment +&lt;/text>
&lt;text x="530" y="72" text-anchor="middle" class="is">roles + lineage (ISO 42001)&lt;/text>
&lt;rect x="660" y="20" width="140" height="60" class="i-aud"/>
&lt;text x="730" y="44" text-anchor="middle" class="il">Auditor&lt;/text>
&lt;text x="730" y="62" text-anchor="middle" class="is">certification body&lt;/text>
&lt;text x="730" y="76" text-anchor="middle" class="is">(EASA / Aenor / BSI)&lt;/text>
&lt;path class="iar" d="M180,50 L220,50"/>
&lt;path class="iar" d="M400,50 L440,50"/>
&lt;path class="iar" d="M620,50 L660,50"/>
&lt;rect x="20" y="130" width="780" height="80" class="i-man"/>
&lt;text x="410" y="152" text-anchor="middle" class="il">The 7 Annex SL clauses — the manual's mandatory table of contents&lt;/text>
&lt;text x="410" y="172" text-anchor="middle" class="is">4 Context · 5 Leadership · 6 Planning · 7 Support · 8 Operation · 9 Evaluation · 10 Improvement&lt;/text>
&lt;text x="410" y="190" text-anchor="middle" class="is">Inherited from Annex SL — the same skeleton as ISO 27001 and 9001, which allows management systems to be integrated&lt;/text>
&lt;rect x="20" y="230" width="780" height="80" class="i-aud"/>
&lt;text x="410" y="252" text-anchor="middle" class="il">The 38 Annex A controls — the manual's AI-specific procedures&lt;/text>
&lt;text x="410" y="272" text-anchor="middle" class="is">A.2 Policies · A.3 Org · A.4 Resources · A.5 Impact · A.6 Lifecycle · A.7 Data · A.8 Info to parties · A.9 Use · A.10 Third parties&lt;/text>
&lt;text x="410" y="290" text-anchor="middle" class="is">What separates 42001 from 27001/9001: every control is born from an AI-specific risk (bias, opacity, drift, data supply)&lt;/text>
&lt;text x="410" y="340" text-anchor="middle" class="in">The aircraft flies with its pilots; the manual is audited by the authority. If the manual is incomplete, the aircraft does not certify however well it flies.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>A modern aircraft, an A350, a Boeing 787, a drone certified for urban delivery, does not fly because it has good engines. It flies because the organisation that operates it has an Operations Manual approved by the aviation authority (EASA in Europe, the FAA in the US, AESA in Spain as a delegated body). The manual does not contain the engine blueprints, which the manufacturer certifies; it contains the procedures: who is captain on each flight, which checklist is run before each take-off, which periodic inspections happen at 100, 500 and 2,000 flight hours, which external suppliers are authorised to touch which components, what is documented after each incident, what to do when a new alert appears on the panel. The authority does not sit in on every flight: it reads the manual, randomly audits the traceability of past flights against the manual, and if everything adds up, it keeps the certification in place.&lt;/p>
&lt;p>An AI system in production, the multi-tenant chatbot from the &lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">forensic post&lt;/a>, a copilot for lawyers, a credit scoring system, is exactly the same. It flies because the model is good, the LLMOps pipeline is well built, the guardrails catch the bad cases. But it certifies because the organisation operating it has an AIMS (AI Management System) described in an auditable manual. ISO/IEC 42001 is that manual: its mandatory table of contents (Annex SL, seven clauses) and its catalogue of AI-specific controls (Annex A, 38 controls). The auditor does not sit next to the MLOps engineer: they read the AI policy, review the impact assessments of the most recently deployed systems, check that incident retrain is documented, verify third-party contracts, audit a sample of Langfuse traces cross-checked against &lt;code>dataset_hash&lt;/code> and &lt;code>prompt_id&lt;/code>. And if everything adds up, they certify.&lt;/p>
&lt;p>The analogy matters because it frames the right question: 42001 does not certify the model or the code. It certifies the way of operating the complete system. A team can have the best OSS stack in the world and fail the audit because it has no written AI policy and no documented decision about which role (provider vs producer vs customer) it occupies towards its clients. And the reverse: a team with a modest model but with operations-manual discipline can certify without acrobatics.&lt;/p>
&lt;h2 id="isoiec-42001-in-15-seconds">ISO/IEC 42001 in 15 seconds&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Publication&lt;/strong>: December 2023, ISO/IEC JTC 1/SC 42 (the ISO/IEC subcommittee for AI).&lt;/li>
&lt;li>&lt;strong>Status in 2026&lt;/strong>: an active standard, certifiable by accredited bodies (BSI, AENOR, TÜV, Bureau Veritas, A-LIGN, Schellman). Not yet formally recognised as a harmonised standard under the EU AI Act, but it provides the management base to lean on.&lt;/li>
&lt;li>&lt;strong>Compatibility&lt;/strong>: it shares the Annex SL structure with ISO 9001 (quality), 27001 (information security), 27701 (privacy), 22301 (continuity), 20000-1 (IT services). Organisations with integrated management systems (IMS) add it with 20-40% of the effort it would take to implement from scratch.&lt;/li>
&lt;li>&lt;strong>Applicability&lt;/strong>: any organisation that develops, provides, deploys or uses AI systems. It is not limited to developers: a company that consumes a hosted LLM and integrates it into its own product is within scope.&lt;/li>
&lt;li>&lt;strong>Certification&lt;/strong>: a 3-year cycle with an initial audit (Stage 1: documentation review + Stage 2: on-site audit) and annual surveillance audits. Typical cost: 15,000-60,000 € for the initial one depending on size; 6,000-20,000 € per annual surveillance.&lt;/li>
&lt;/ul>
&lt;p>What 42001 does not do:&lt;/p>
&lt;ul>
&lt;li>It does not say which models to use or which thresholds to apply.&lt;/li>
&lt;li>It does not certify the individual model (that is done by specific evaluations such as a NIST AI RMF profile or EU AI Act technical documentation).&lt;/li>
&lt;li>It does not replace the EU AI Act or the GDPR: it is complementary. Implementing it well makes legal compliance easier but does not guarantee it.&lt;/li>
&lt;li>It is not a technical standard for explainability or robustness (those are ISO/IEC 25059, 24029, 23894 and others in the SC 42 family).&lt;/li>
&lt;/ul>
&lt;h2 id="distinction-from-neighbouring-frameworks">Distinction from neighbouring frameworks&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Framework&lt;/th>
&lt;th>Nature&lt;/th>
&lt;th>Scope&lt;/th>
&lt;th>Certifiable&lt;/th>
&lt;th>Overlap with 42001&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>ISO/IEC 42001:2023&lt;/strong>&lt;/td>
&lt;td>Management standard&lt;/td>
&lt;td>AIMS for any AI system&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>—&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>EU AI Act&lt;/strong> (Reg. 2024/1689)&lt;/td>
&lt;td>Binding legal regulation&lt;/td>
&lt;td>AI systems in the EU, risk-categorised&lt;/td>
&lt;td>No (it is law)&lt;/td>
&lt;td>Arts 9, 10, 11, 12, 13, 14, 17&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>NIS2&lt;/strong> (Dir. 2022/2555)&lt;/td>
&lt;td>Cybersecurity directive&lt;/td>
&lt;td>Essential/important entities&lt;/td>
&lt;td>Via the national scheme&lt;/td>
&lt;td>Asset register, incident, supply chain&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ENS&lt;/strong> (RD 311/2022)&lt;/td>
&lt;td>Spanish security regulation&lt;/td>
&lt;td>Public sector and its suppliers&lt;/td>
&lt;td>Yes (B/M/A categories)&lt;/td>
&lt;td>Traceability, incident management&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ISO/IEC 27001&lt;/strong>&lt;/td>
&lt;td>Management standard&lt;/td>
&lt;td>Information security&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>Annex SL structure + Annex A overlap&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ISO/IEC 27701&lt;/strong>&lt;/td>
&lt;td>Management standard&lt;/td>
&lt;td>Privacy (extends 27001)&lt;/td>
&lt;td>Yes&lt;/td>
&lt;td>PII in training data&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>NIST AI RMF 1.0&lt;/strong>&lt;/td>
&lt;td>Voluntary framework&lt;/td>
&lt;td>AI risk management&lt;/td>
&lt;td>No&lt;/td>
&lt;td>Conceptually aligned, not identical&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ISO/IEC 23894&lt;/strong>&lt;/td>
&lt;td>Technical standard&lt;/td>
&lt;td>AI risk management&lt;/td>
&lt;td>No&lt;/td>
&lt;td>Input to A.5 (impact assessment)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ISO/IEC 5259&lt;/strong>&lt;/td>
&lt;td>Family&lt;/td>
&lt;td>Data quality for AI&lt;/td>
&lt;td>No&lt;/td>
&lt;td>Input to A.7 (data)&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Three distinctions that matter operationally&lt;/strong> and are a recurring source of confusion with clients:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>ISO 42001 ≠ EU AI Act compliance&lt;/strong>. Holding the 42001 certification makes it easier to demonstrate articles 9-17 of the European Regulation, but the Regulation requires things 42001 does not cover directly (CE marking of high-risk systems, registration in the European database, declaration of conformity, specific post-market monitoring). Implementing 42001 first and then filling the AI Act gaps is the standard route.&lt;/li>
&lt;li>&lt;strong>ISO 27001 is not enough&lt;/strong>. 27001 covers confidentiality, integrity and availability of information. The AI side is missing: bias, opacity, model drift, training corpus quality, human evaluation, impact on affected persons. 42001 is a complement, not a substitute. Organisations that already have 27001 in place have an advantage because they share half the documentation.&lt;/li>
&lt;li>&lt;strong>NIS2 ≠ AI safety&lt;/strong>. NIS2 requires registering critical assets, notifying incidents within 24 h, and managing the digital supply chain. AI systems can be within NIS2 scope if they form part of the critical asset (an LLM serving customer support in a financial entity is), but NIS2 does not audit model quality. 42001 does.&lt;/li>
&lt;/ol>
&lt;h2 id="the-seven-clauses-annex-sl-the-mandatory-table-of-contents">The seven clauses (Annex SL): the mandatory table of contents&lt;/h2>
&lt;p>The seven clauses, from clause 4 to clause 10, are common to all modern management standards (Annex SL, also called the &amp;ldquo;High Level Structure&amp;rdquo;). That means an organisation with ISO 9001 or 27001 already in place recognises the structure. Clauses 1-3 are introductory (scope, normative references, terms).&lt;/p>
&lt;h3 id="clause-4--context-of-the-organisation">Clause 4 — Context of the organisation&lt;/h3>
&lt;p>Identify the external context (applicable regulation, customer expectations, social risks) and the internal context (strategy, culture, capabilities). Identify the interested parties and their expectations: customers, regulators, affected persons, employees, suppliers. Define the scope of the AIMS: which AI systems are in and which are out.&lt;/p>
&lt;p>The usual gap: organisations that say &amp;ldquo;all our AI systems are in scope&amp;rdquo; without having enumerated them. The auditor asks for the list. Without a list, there is no scope.&lt;/p>
&lt;h3 id="clause-5--leadership">Clause 5 — Leadership&lt;/h3>
&lt;p>Top management must approve and publish an AI policy, assign roles and responsibilities (typically AI lead, AI risk owner, data officer), and demonstrate commitment through resources, communication and oversight. The policy is an auditable document and must be made available to staff and interested parties.&lt;/p>
&lt;p>The usual gap: a generic AI policy copied off the internet, with no measurables and no concrete objectives. The auditor asks how compliance with it is measured. Without metrics, the policy is theatre.&lt;/p>
&lt;h3 id="clause-6--planning">Clause 6 — Planning&lt;/h3>
&lt;p>Identify risks and opportunities of the AIMS (not of the individual model). Define measurable AI objectives, with deadlines and owners. Plan changes to the AIMS.&lt;/p>
&lt;p>The usual gap: confusing AIMS risks (what happens if we do not document correctly?) with model risks (what happens if the model is biased?). The first belongs here; the second goes to A.5.&lt;/p>
&lt;h3 id="clause-7--support">Clause 7 — Support&lt;/h3>
&lt;p>&lt;strong>Resources&lt;/strong>: human, technical, financial, infrastructure. Competence of personnel (documented training). Awareness of the policy among personnel. Internal and external communication. Documented information (the backbone of the management system: policy, procedures, records, evidence).&lt;/p>
&lt;p>The usual gap: documentation scattered across Confluence/Notion/Drive with no version control and no recorded approvals. The auditor asks about the last change: who approved it? when? on what grounds?&lt;/p>
&lt;h3 id="clause-8--operation">Clause 8 — Operation&lt;/h3>
&lt;p>The most operational clause. It requires:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Operational planning and control&lt;/strong>: how the AI system&amp;rsquo;s lifecycle is managed day to day. → Covered in the blog by the &lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">six-stage LLMOps pipeline&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Impact assessment&lt;/strong> (linked to A.5).&lt;/li>
&lt;li>&lt;strong>AI system lifecycle management&lt;/strong> (linked to A.6).&lt;/li>
&lt;li>&lt;strong>Data for AI systems&lt;/strong> (linked to A.7).&lt;/li>
&lt;/ul>
&lt;p>This is the clause that materialises in controls A.5, A.6 and A.7. On its own it adds no new requirements: it links to Annex A.&lt;/p>
&lt;h3 id="clause-9--performance-evaluation">Clause 9 — Performance evaluation&lt;/h3>
&lt;p>&lt;strong>Monitoring, measurement, analysis, evaluation&lt;/strong>. Internal audits (planned, with criteria, scope, frequency, recorded results). Management review (typically quarterly or half-yearly, with a mandatory agenda: inputs, evidence, decisions, actions).&lt;/p>
&lt;p>The usual gap: there is OTel tracing + Langfuse + Grafana and plenty of data, but there is no formal management review agenda with documented minutes. The auditor asks for the minutes. Without minutes, there is no review.&lt;/p>
&lt;h3 id="clause-10--improvement">Clause 10 — Improvement&lt;/h3>
&lt;p>&lt;strong>Nonconformity and corrective action&lt;/strong>: when something fails, it is recorded, root cause is analysed, a correction is agreed, effectiveness is verified. Continual improvement: the system evolves deliberately.&lt;/p>
&lt;p>The usual gap: Jira tickets with technical post-mortems but no formal &amp;ldquo;ISO nonconformity&amp;rdquo; record closed out with an effectiveness check. They are two distinct artefacts, even if they can be integrated.&lt;/p>
&lt;h2 id="the-38-annex-a-controls-the-ai-specific-catalogue">The 38 Annex A controls: the AI-specific catalogue&lt;/h2>
&lt;p>Unlike Annex SL (which is common), Annex A is 42001&amp;rsquo;s AI-specific signature. The 38 controls are organised into 9 sections (A.2 to A.10; A.1 is the introduction) covering the AI-specific risks: opacity, bias, drift, corpus quality, impact on affected persons, third-party dependency. Each control has an objective (what it aims to achieve) and implementation guidance in Annex B.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Section&lt;/th>
&lt;th>Focus&lt;/th>
&lt;th># controls&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>A.2&lt;/td>
&lt;td>AI-related policies&lt;/td>
&lt;td>2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>A.3&lt;/td>
&lt;td>Internal organisation&lt;/td>
&lt;td>3&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>A.4&lt;/td>
&lt;td>Resources for AI systems&lt;/td>
&lt;td>6&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>A.5&lt;/td>
&lt;td>Impact assessment&lt;/td>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>A.6&lt;/td>
&lt;td>AI system lifecycle&lt;/td>
&lt;td>4&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>A.7&lt;/td>
&lt;td>Data for AI systems&lt;/td>
&lt;td>5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>A.8&lt;/td>
&lt;td>Information for interested parties&lt;/td>
&lt;td>4&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>A.9&lt;/td>
&lt;td>Use of AI systems&lt;/td>
&lt;td>3&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>A.10&lt;/td>
&lt;td>Third parties and customer relationships&lt;/td>
&lt;td>4&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Total&lt;/strong>&lt;/td>
&lt;td>—&lt;/td>
&lt;td>&lt;strong>38&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>What follows is the section-by-section mapping of the controls onto material we have already covered in the blog. The editorial intent is to show which gaps remain once the technical architecture is in place, so that the road to certification does not start from zero.&lt;/p>
&lt;h2 id="cross-mapping-38-controls--blog-posts">Cross-mapping: 38 controls ↔ blog posts&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Mapping of ISO 42001 Annex A controls onto the blog's on-premise LLM architecture">
&lt;style>
.m-cov{fill:#a8e6a3;stroke:#444;stroke-width:1.4;rx:6}
.m-par{fill:#ffd76b;stroke:#444;stroke-width:1.4;rx:6}
.m-gap{fill:#f4b8b8;stroke:#444;stroke-width:1.4;rx:6}
.m-hdr{fill:#7aafff;stroke:#444;stroke-width:1.4;rx:8}
.ml{font:600 12px sans-serif;fill:#222}
.ms{font:400 10px sans-serif;fill:#444}
.mn{font:italic 10px sans-serif;fill:#555}
&lt;/style>
&lt;rect x="20" y="20" width="780" height="40" class="m-hdr"/>
&lt;text x="410" y="42" text-anchor="middle" class="ml">ISO 42001 Annex A mapped onto the blog's on-premise LLM architecture&lt;/text>
&lt;text x="410" y="55" text-anchor="middle" class="ms">green = covered by code/architecture · yellow = partial · red = governance gap&lt;/text>
&lt;rect x="20" y="80" width="780" height="50" class="m-par"/>
&lt;text x="50" y="100" class="ml">A.2 Policies (2)&lt;/text>
&lt;text x="50" y="116" class="ms">A.2.2 AI policy · A.2.3 Alignment with existing policies&lt;/text>
&lt;text x="500" y="100" class="ml">Status: PARTIAL&lt;/text>
&lt;text x="500" y="116" class="ms">The blog's editorial discipline teaches the angle; a formal written policy per organisation is missing.&lt;/text>
&lt;rect x="20" y="140" width="780" height="50" class="m-gap"/>
&lt;text x="50" y="160" class="ml">A.3 Internal organisation (3)&lt;/text>
&lt;text x="50" y="176" class="ms">A.3.2 Roles and responsibilities · A.3.3 Incident reporting · A.3.4 Stakeholders&lt;/text>
&lt;text x="500" y="160" class="ml">Status: GAP&lt;/text>
&lt;text x="500" y="176" class="ms">Not technical. Requires an organisational decision: AI lead, risk owner, AI committee.&lt;/text>
&lt;rect x="20" y="200" width="780" height="50" class="m-cov"/>
&lt;text x="50" y="220" class="ml">A.4 Resources (6)&lt;/text>
&lt;text x="50" y="236" class="ms">Data, tooling, system, human, financial resources + documentation&lt;/text>
&lt;text x="500" y="220" class="ml">Status: COVERED&lt;/text>
&lt;text x="500" y="236" class="ms">Seven deployment phases + five maturity levels + seven layers + OSS catalogue.&lt;/text>
&lt;rect x="20" y="250" width="780" height="50" class="m-par"/>
&lt;text x="50" y="270" class="ml">A.5 Impact assessment (5)&lt;/text>
&lt;text x="50" y="286" class="ms">AI impact process · documentation · alignment with risks · individuals · society&lt;/text>
&lt;text x="500" y="270" class="ml">Status: PARTIAL&lt;/text>
&lt;text x="500" y="286" class="ms">ISO/IEC 23894 gives the method; a formal per-system impact assessment procedure is missing.&lt;/text>
&lt;rect x="20" y="300" width="780" height="50" class="m-cov"/>
&lt;text x="50" y="320" class="ml">A.6 Lifecycle (4)&lt;/text>
&lt;text x="50" y="336" class="ms">Objectives · design · verification and validation · operation and monitoring · documentation&lt;/text>
&lt;text x="500" y="320" class="ml">Status: COVERED&lt;/text>
&lt;text x="500" y="336" class="ms">Six-stage pipeline + request anatomy + continuous fine-tuning + retrain.&lt;/text>
&lt;rect x="20" y="350" width="780" height="50" class="m-cov"/>
&lt;text x="50" y="370" class="ml">A.7 Data (5)&lt;/text>
&lt;text x="50" y="386" class="ms">Quality · acquisition · provenance · preparation · privacy&lt;/text>
&lt;text x="500" y="370" class="ml">Status: COVERED&lt;/text>
&lt;text x="500" y="386" class="ms">Data versioning + RAG corpus curation + Presidio + LLM Guard Vault.&lt;/text>
&lt;rect x="20" y="400" width="780" height="50" class="m-cov"/>
&lt;text x="50" y="420" class="ml">A.8 Information to parties (4)&lt;/text>
&lt;text x="50" y="436" class="ms">System documentation · information on use · incident communication · external reporting&lt;/text>
&lt;text x="500" y="420" class="ml">Status: COVERED&lt;/text>
&lt;text x="500" y="436" class="ms">OTel GenAI tracing + Langfuse + chunk→trace lineage + guardrail spans.&lt;/text>
&lt;rect x="20" y="450" width="780" height="40" class="m-cov"/>
&lt;text x="50" y="470" class="ml">A.9 Use (3)&lt;/text>
&lt;text x="50" y="484" class="ms">Responsible use processes · use objectives · intended use&lt;/text>
&lt;text x="500" y="470" class="ml">Status: COVERED&lt;/text>
&lt;text x="500" y="484" class="ms">Guardrails + evals + LLM Guard + incident-driven retrain.&lt;/text>
&lt;rect x="20" y="500" width="780" height="40" class="m-cov"/>
&lt;text x="50" y="520" class="ml">A.10 Third parties (4)&lt;/text>
&lt;text x="50" y="534" class="ms">Allocation of responsibilities · supplier · customer · third-party&lt;/text>
&lt;text x="500" y="520" class="ml">Status: COVERED&lt;/text>
&lt;text x="500" y="534" class="ms">OSS vs hyperscalers + OSS catalogue + sovereignty + lock-in analysis.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h3 id="a2--ai-policies-2-controls-partial">A.2 — AI policies (2 controls): PARTIAL&lt;/h3>
&lt;ul>
&lt;li>A.2.2 AI policy: the organisation must have a documented AI policy, approved by management, reviewed periodically, communicated and available. It covers principles, scope and commitments.&lt;/li>
&lt;li>A.2.3 Alignment with other policies: the AI policy is not an orphan, it aligns with existing security, privacy, quality and ethics policies.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Gap&lt;/strong>: this is not a code matter. The AI policy is a document that the organisation&amp;rsquo;s management approves and signs. The blog teaches a neutral, technical editorial stance (no hype, sovereignty, OSS by default under ENS/NIS2), but that is not any specific organisation&amp;rsquo;s AI policy. Each client has to draft and sign their own.&lt;/p>
&lt;p>&lt;strong>Minimum template&lt;/strong>: 1-2 pages with: principles (transparency, human oversight, fairness, accountability, sustainability), scope (which systems), measurable commitments (annual review, impact assessment before deployment, team training), governance (who approves what).&lt;/p>
&lt;h3 id="a3--internal-organisation-3-controls-gap">A.3 — Internal organisation (3 controls): GAP&lt;/h3>
&lt;ul>
&lt;li>A.3.2 AI roles and responsibilities: roles defined, non-overlapping, communicated. Typically: AI lead, AI risk owner, data steward, AI ethics officer (which may be a single person in small organisations).&lt;/li>
&lt;li>A.3.3 Reporting of AI incidents/concerns: a channel for anyone (internal or external) to report a problem with an AI system, with documented follow-up.&lt;/li>
&lt;li>A.3.4 Identification of stakeholders: a maintained list of stakeholders (customers, affected persons, regulators, partners) and their expectations.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Gap&lt;/strong>: also not technical. An organisational decision. The usual way to cover it is to appoint an AI lead (which may be the CIO, the CTO or a new role depending on size), reuse the security reporting channel (typically already in place because of 27001) by extending it to AI, and maintain a live stakeholder register.&lt;/p>
&lt;h3 id="a4--resources-6-controls-covered">A.4 — Resources (6 controls): COVERED&lt;/h3>
&lt;ul>
&lt;li>A.4.2 Documented information: documentation of the AIMS.&lt;/li>
&lt;li>A.4.3 Data resources: identification and management of the data available for training, evaluation and operation.&lt;/li>
&lt;li>A.4.4 Tooling resources: development, validation and monitoring tools.&lt;/li>
&lt;li>A.4.5 System resources: hardware, infrastructure, compute.&lt;/li>
&lt;li>A.4.6 Human resources: competent personnel.&lt;/li>
&lt;li>A.4.7 Financial resources: budget.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Covered by the blog&lt;/strong> in the three architectural posts:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/anatomy-on-premise-llm-inference-stack-seven-layers/">Anatomy of the stack: seven layers&lt;/a>: A.4.5 system resources, A.4.4 tooling.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/greenfield-deployment-phases-on-premise-llm-platform/">Seven deployment phases&lt;/a>: A.4.5 + A.4.7 (budget implicitly).&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/five-maturity-levels-platform-under-llm/">Five maturity levels&lt;/a>: A.4.5 + A.4.6 (team maturity).&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/oss-catalogue-llmops-six-stages-tool-by-tool/">OSS tooling catalogue&lt;/a>: A.4.4 tooling.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">Data versioning with DVC and lakeFS&lt;/a>: A.4.3 data resources.&lt;/li>
&lt;/ul>
&lt;h3 id="a5--impact-assessment-5-controls-partial">A.5 — Impact assessment (5 controls): PARTIAL&lt;/h3>
&lt;ul>
&lt;li>A.5.2 AI impact assessment process: a documented impact assessment procedure.&lt;/li>
&lt;li>A.5.3 Documentation of AI impact assessments: records of the assessments carried out.&lt;/li>
&lt;li>A.5.4 Alignment with AI risk treatment: the impact assessment&amp;rsquo;s decisions feed risk treatment.&lt;/li>
&lt;li>A.5.5 Impacts on individuals: specific dimensions covering affected persons (rights, discrimination, privacy).&lt;/li>
&lt;li>A.5.6 Societal impacts: dimensions covering society (information, social rights).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Partial&lt;/strong>: the method exists in the ISO/IEC SC 42 family. ISO/IEC 23894:2023 is the technical standard for AI risk management and NIST AI RMF 1.0 is the freely usable American equivalent. But the organisation has to write its procedure and run it per system before deployment. It is not code, it is discipline.&lt;/p>
&lt;p>&lt;strong>Minimum template&lt;/strong> for the impact assessment (3-5 pages per system):&lt;/p>
&lt;ol>
&lt;li>Description of the system (what it does, who it serves, underlying model and stack).&lt;/li>
&lt;li>Stakeholders identified.&lt;/li>
&lt;li>Potential impacts (intended and unintended) on people, groups and society.&lt;/li>
&lt;li>Fairness and robustness metrics applied, with thresholds and results.&lt;/li>
&lt;li>Mitigations applied (guardrails, evals, human oversight, rate limiting).&lt;/li>
&lt;li>Accepted residual risks, with signed justification.&lt;/li>
&lt;li>Review cadence (typically annual, or on substantial change).&lt;/li>
&lt;/ol>
&lt;h3 id="a6--ai-system-lifecycle-4-controls-covered">A.6 — AI system lifecycle (4 controls): COVERED&lt;/h3>
&lt;ul>
&lt;li>A.6.2.2 Objectives for responsible development of AI: responsible development objectives defined per system.&lt;/li>
&lt;li>A.6.2.3 Processes for responsible AI design and development: design and development procedures.&lt;/li>
&lt;li>A.6.2.4 AI system requirements and specifications: formal specification of the system.&lt;/li>
&lt;li>A.6.2.5 Verification and validation: V&amp;amp;V before and during operation.&lt;/li>
&lt;li>A.6.2.6 Deployment: deployment procedures.&lt;/li>
&lt;li>A.6.2.7 Operation and monitoring: continuous operation and monitoring.&lt;/li>
&lt;li>A.6.2.8 Documentation: lifecycle documentation.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Covered by the blog&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">The six-stage LLMOps pipeline&lt;/a>: the complete master map of the lifecycle.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">Anatomy of an LLM request&lt;/a>: the forensic version of how it runs in production.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/continuous-fine-tuning-production-real-traffic-deployed-adapter/">Continuous fine-tuning in production&lt;/a>: the A.6.2.3 + A.6.2.5 + A.6.2.6 + A.6.2.7 discipline in real operation.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">Evals: the layer after tracing&lt;/a>: A.6.2.5 verification and validation.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">Retrain&lt;/a>: A.6.2.7 operation + continuous iteration.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/modern-alignment-dpo-kto-orpo-simpo/">Modern alignment: DPO, KTO, ORPO, SimPO&lt;/a>: A.6.2.3 responsible design.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/mlops-for-llms-landscape-2026/">MLOps landscape 2026&lt;/a>: the tooling landscape.&lt;/li>
&lt;/ul>
&lt;h3 id="a7--data-for-ai-systems-5-controls-covered">A.7 — Data for AI systems (5 controls): COVERED&lt;/h3>
&lt;ul>
&lt;li>A.7.2 Data for development and enhancement of AI: data management policy and procedures for development and improvement.&lt;/li>
&lt;li>A.7.3 Acquisition of data: acquisition procedures (origin, authorisation, quality).&lt;/li>
&lt;li>A.7.4 Quality of data for AI systems: measurable quality criteria.&lt;/li>
&lt;li>A.7.5 Data provenance: data lineage.&lt;/li>
&lt;li>A.7.6 Data preparation: preparation procedures (chunking, anonymisation, labelling).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Covered by the blog&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/rag-corpus-curation-active-librarian/">RAG corpus curation: the active librarian&lt;/a>: A.7.4 + A.7.5 + A.7.6 in detail (five layers: schema, dedup, PII, anti-contamination, lineage).&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">Data versioning: DVC and lakeFS&lt;/a>: A.7.2 + A.7.5 (the four data artefacts versioned with lineage).&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/reranker-hybrid-retrieval-five-chunks-llm-reads/">Reranker and hybrid retrieval&lt;/a>: A.7.6 preparation + filtering.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard&lt;/a>: A.7.6 runtime anonymisation with Vault.&lt;/li>
&lt;/ul>
&lt;h3 id="a8--information-for-interested-parties-4-controls-covered">A.8 — Information for interested parties (4 controls): COVERED&lt;/h3>
&lt;ul>
&lt;li>A.8.2 System documentation and information for users: technical documentation available.&lt;/li>
&lt;li>A.8.3 External reporting: the capacity to report to authorities where applicable.&lt;/li>
&lt;li>A.8.4 Communication of incidents to users: notification to users when there is an incident.&lt;/li>
&lt;li>A.8.5 Information for interested parties: information for other stakeholders.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Covered by the blog&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">LLM tracing with OpenTelemetry GenAI&lt;/a>: A.8.2 per-request traceability, A.8.3 the capacity to extract forensic reporting.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/prompt-versioning-contract-five-word-change/">Prompt versioning with Langfuse and MLflow&lt;/a>: A.8.2 documented versioning.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">Guardrails and safety in LLMs&lt;/a>: A.8.4, &lt;code>gen_ai.guardrail.*&lt;/code> spans as the basis for incident notification.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard&lt;/a>: A.8.4 incident events for retrain.&lt;/li>
&lt;/ul>
&lt;h3 id="a9--use-of-ai-systems-3-controls-covered">A.9 — Use of AI systems (3 controls): COVERED&lt;/h3>
&lt;ul>
&lt;li>A.9.2 Processes for responsible use of AI: responsible use procedures.&lt;/li>
&lt;li>A.9.3 Objectives for responsible use of AI: objectives.&lt;/li>
&lt;li>A.9.4 Intended use of AI systems: documentation of the intended use.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Covered by the blog&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">Guardrails and safety in LLMs&lt;/a>: A.9.2 + A.9.3 (the four lines of defence).&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard&lt;/a>: A.9.2 operational detail.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">Evals: the layer after tracing&lt;/a>: A.9.3 measurement of objectives.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">Retrain&lt;/a>: A.9.2 closed loop.&lt;/li>
&lt;/ul>
&lt;h3 id="a10--third-parties-and-customer-relationships-4-controls-covered">A.10 — Third parties and customer relationships (4 controls): COVERED&lt;/h3>
&lt;ul>
&lt;li>A.10.2 Allocation of responsibilities: distribution of responsibilities between AI roles.&lt;/li>
&lt;li>A.10.3 Suppliers: procedures for AI suppliers.&lt;/li>
&lt;li>A.10.4 Customers: procedures towards customers.&lt;/li>
&lt;li>A.10.5 Third parties: procedures for third parties.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Covered by the blog&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/parallel-catalogue-llmops-stages-open-source-hyperscalers/">The parallel catalogue: OSS vs hyperscalers&lt;/a>: A.10.3 supplier assessment with lock-in and contractual sovereignty analysis.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/oss-catalogue-llmops-six-stages-tool-by-tool/">The OSS catalogue for LLMOps&lt;/a>: A.10.5 third-party inventory (OSS components with licences and governance).&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">Anatomy of an LLM request&lt;/a>: A.10.2 + A.10.4 in the multi-tenant case.&lt;/li>
&lt;/ul>
&lt;h2 id="the-roles-defined-by-the-standard">The roles defined by the standard&lt;/h2>
&lt;p>ISO/IEC 22989:2022 (AI vocabulary, complementary to 42001) defines six roles. Each organisation has to decide which ones it occupies and document it:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Role&lt;/th>
&lt;th>Definition&lt;/th>
&lt;th>Main responsibility&lt;/th>
&lt;th>Example&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>AI provider&lt;/strong>&lt;/td>
&lt;td>Organisation that provides the AI system to others&lt;/td>
&lt;td>Makes the system available&lt;/td>
&lt;td>OpenAI provides GPT-5 via API&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>AI producer&lt;/strong>&lt;/td>
&lt;td>Organisation that develops the AI system&lt;/td>
&lt;td>Design, development, validation&lt;/td>
&lt;td>Meta produces Llama 4&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>AI customer&lt;/strong>&lt;/td>
&lt;td>Organisation that acquires the AI system&lt;/td>
&lt;td>Selection, integration, oversight&lt;/td>
&lt;td>A consultancy integrating an LLM into its own product&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>AI partner&lt;/strong>&lt;/td>
&lt;td>Organisation collaborating with another AI role&lt;/td>
&lt;td>Shared&lt;/td>
&lt;td>A GPU hardware manufacturer&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>AI subject&lt;/strong>&lt;/td>
&lt;td>Person or group affected by the system&lt;/td>
&lt;td>Recipient of the impact&lt;/td>
&lt;td>The chatbot&amp;rsquo;s end user&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Relevant authority&lt;/strong>&lt;/td>
&lt;td>Regulator with jurisdiction&lt;/td>
&lt;td>External oversight&lt;/td>
&lt;td>AEPD, CNMC, EU AI Act authorities&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>An organisation can occupy several roles at once, which changes the applicable controls. A common pattern in consultancy is: producer + customer + provider towards the end client. The A.10 responsibilities are modulated by the roles.&lt;/p>
&lt;p>&lt;strong>Example role mapping&lt;/strong> for the multi-tenant chatbot from the forensic post:&lt;/p>
&lt;ul>
&lt;li>Base model manufacturer (Llama 4): AI producer of the base model.&lt;/li>
&lt;li>OSS stack operator (the consultancy): AI producer of the LoRA adapter + AI provider of the chatbot to its clients + AI customer of Meta&amp;rsquo;s base model.&lt;/li>
&lt;li>End client (the insurer): AI customer of the chatbot + AI provider of the customer support service.&lt;/li>
&lt;li>The policyholder: AI subject.&lt;/li>
&lt;li>AEPD + EU AI Act authority: relevant authority.&lt;/li>
&lt;/ul>
&lt;p>Each box in the table generates different obligations. The consultancy, being producer of the adapter, must document A.6 (lifecycle) and A.7 (data) for the adapter. Being provider of the chatbot, it must document A.10.4 (customers). Being customer of the base model, it must document A.10.3 (suppliers) and validate that Meta does its part.&lt;/p>
&lt;h2 id="impact-levels-and-proportionality">Impact levels and proportionality&lt;/h2>
&lt;p>42001 does not demand the same rigour of every system. Clause 6.1.2 and control A.5 introduce impact as a modulator. The standard does not define strict categories (unlike the EU AI Act, which does define &amp;ldquo;prohibited / high risk / limited risk / minimal&amp;rdquo;), but it recommends using levels according to severity and probability.&lt;/p>
&lt;p>Industry practice in 2026 aligns the 42001 levels with the EU AI Act categories:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>42001 level&lt;/th>
&lt;th>EU AI Act&lt;/th>
&lt;th>Examples&lt;/th>
&lt;th>Control depth&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>High&lt;/strong>&lt;/td>
&lt;td>High risk (Annex III)&lt;/td>
&lt;td>Credit scoring, HR, health, critical infrastructure&lt;/td>
&lt;td>Exhaustive impact assessment, mandatory human oversight, continuous monitoring, adversarial evals, detailed logging, half-yearly management review&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Medium&lt;/strong>&lt;/td>
&lt;td>Limited risk&lt;/td>
&lt;td>Customer service chatbots that do not automate decisions, productivity assistants&lt;/td>
&lt;td>Standard impact assessment, complete guardrails, annual review&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Low&lt;/strong>&lt;/td>
&lt;td>Minimal risk&lt;/td>
&lt;td>Spam filters, non-personalised content recommendations&lt;/td>
&lt;td>Lightweight impact assessment, basic controls&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>This proportionality is operationally key: implementing 42001 at maximum rigour for a low-risk system is waste; relaxing it on a high-risk one is non-compliance.&lt;/p>
&lt;h2 id="the-seven-minimum-aims-documents">The seven minimum AIMS documents&lt;/h2>
&lt;p>An auditor at Stage 1 (documentation review) asks for between seven and ten documents. The seven essentials:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>AI policy&lt;/strong> (clause 5.2 + A.2.2). 1-2 pages. Approved by management, dated, versioned.&lt;/li>
&lt;li>&lt;strong>AIMS scope&lt;/strong> (clause 4.3). List of AI systems in scope, inclusion criteria.&lt;/li>
&lt;li>&lt;strong>Stakeholder register&lt;/strong> (clause 4.2 + A.3.4). A maintained list with expectations.&lt;/li>
&lt;li>&lt;strong>AIMS risk register&lt;/strong> (clause 6.1). Risks of the management system, not of each model.&lt;/li>
&lt;li>&lt;strong>Impact assessment procedure&lt;/strong> (A.5.2) + records of assessments carried out (A.5.3). The procedure plus one or more assessments done.&lt;/li>
&lt;li>&lt;strong>AI lifecycle procedure&lt;/strong> (A.6.2), which can literally read &amp;ldquo;see the six-stage LLMOps pipeline&amp;rdquo; with references to technical runbooks.&lt;/li>
&lt;li>&lt;strong>Data management procedure&lt;/strong> (A.7.2), covering acquisition, quality, provenance, preparation, anonymisation.&lt;/li>
&lt;/ol>
&lt;p>Common additional documents:&lt;/p>
&lt;ol start="8">
&lt;li>&lt;strong>Responsible use policy&lt;/strong> (A.9.2) with permitted and non-permitted use types.&lt;/li>
&lt;li>&lt;strong>AI third-party management procedure&lt;/strong> (A.10.3, A.10.5) with assessment criteria for AI suppliers.&lt;/li>
&lt;li>Internal audit plan + management review agenda (clauses 9.2 + 9.3).&lt;/li>
&lt;/ol>
&lt;p>For an organisation with a mature &lt;a href="https://blog.lo0.es/en/posts/oss-catalogue-llmops-six-stages-tool-by-tool/">OSS stack&lt;/a>, documents 6 and 7 are references to technical artefacts that already exist (pipeline runbooks, DVC configurations, the PII policy in LLM Guard). The real documentary effort sits in documents 1, 2, 3, 4 and 5.&lt;/p>
&lt;h2 id="applied-case-the-blogs-multi-tenant-chatbot--42001-checklist">Applied case: the blog&amp;rsquo;s multi-tenant chatbot → 42001 checklist&lt;/h2>
&lt;p>We take the system described in the &lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">forensic post&lt;/a>, a multi-tenant customer support chatbot for insurers on an on-premise OSS stack, and walk it as a 42001 auditor would.&lt;/p>
&lt;p>&lt;strong>Clause 4 — Context&lt;/strong>. The AIMS scope includes the chatbot and excludes the internal HR system (a different AI). Stakeholders identified: client insurers, affected policyholders, the AEPD, the EU AI Act authority (once it enters into force on 2 Aug 2026), the supplier Meta (base model), the hardware supplier NVIDIA. → Documented.&lt;/p>
&lt;p>&lt;strong>Clause 5 — Leadership&lt;/strong>. AI policy signed by the CEO, in force. Roles assigned: AI lead (CTO), AI risk owner (CISO), data steward (Head of Data), quarterly AI ethics committee. → Documented.&lt;/p>
&lt;p>&lt;strong>Clause 6 — Planning&lt;/strong>. AIMS risk register: incomplete documentation, team churn, dependency on a single GPU supplier, EU AI Act regulatory change. AIMS objectives for 2026: 42001 certification before Q4, EU AI Act high-risk compliance before 2 Aug. → Documented.&lt;/p>
&lt;p>&lt;strong>Clause 7 — Support&lt;/strong>. Resources: a &lt;a href="https://blog.lo0.es/en/posts/disaggregated-serving-prefill-decode-specialised-pods/">4×H100 SXM&lt;/a> cluster + the &lt;a href="https://blog.lo0.es/en/posts/anatomy-on-premise-llm-inference-stack-seven-layers/">seven stack layers&lt;/a>. Competence: 2 MLE + 2 SRE + 1 part-time AI ethics, all with documented training. Communication: AI policy on the intranet + handbook. → Documented.&lt;/p>
&lt;p>&lt;strong>Clause 8 — Operation&lt;/strong>. Operating procedures = the &lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">six-stage LLMOps pipeline&lt;/a>. Impact assessment run before deployment + annual review + review on substantial change (defined as: base model change, major adapter change, expansion to a new tenant). → Documented.&lt;/p>
&lt;p>&lt;strong>Clause 9 — Evaluation&lt;/strong>. Monitoring: &lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">Langfuse + Tempo + VictoriaMetrics + Grafana&lt;/a>. Mandatory dashboard metrics: F1 per guardrail category over real traffic, statistical drift, RAG faithfulness, refusal rate. Quarterly internal audit with written criteria. Half-yearly management review with signed minutes. → Documented.&lt;/p>
&lt;p>&lt;strong>Clause 10 — Improvement&lt;/strong>. Incident-driven retrain tickets mapped as nonconformities when severity ≥ HIGH. Documented root cause analysis. Effectiveness verified at the next eval gate. → Documented.&lt;/p>
&lt;p>&lt;strong>Annex A — By section&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>A.2 (Policies): AI policy + responsible use policy. → Documented.&lt;/li>
&lt;li>A.3 (Organisation): roles assigned, reporting channel, stakeholder register. → Documented.&lt;/li>
&lt;li>A.4 (Resources): &lt;a href="https://blog.lo0.es/en/posts/greenfield-deployment-phases-on-premise-llm-platform/">seven deployment phases&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/oss-catalogue-llmops-six-stages-tool-by-tool/">OSS catalogue&lt;/a> + training plan + annual budget. → Documented.&lt;/li>
&lt;li>A.5 (Impact): procedure + per-system assessments + fairness metrics applied. → Documented.&lt;/li>
&lt;li>A.6 (Lifecycle): &lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">LLMOps pipeline&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/continuous-fine-tuning-production-real-traffic-deployed-adapter/">continuous fine-tuning&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">retrain&lt;/a>. → Documented.&lt;/li>
&lt;li>A.7 (Data): &lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">data versioning&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/rag-corpus-curation-active-librarian/">RAG corpus curation&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard Vault&lt;/a> + Presidio. → Documented.&lt;/li>
&lt;li>A.8 (Information to parties): &lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">OTel tracing&lt;/a> + Langfuse + &lt;code>gen_ai.guardrail.*&lt;/code> spans + notification to tenants within SLA. → Documented.&lt;/li>
&lt;li>A.9 (Use): &lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">guardrails&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">evals&lt;/a> + responsible use policy. → Documented.&lt;/li>
&lt;li>A.10 (Third parties): &lt;a href="https://blog.lo0.es/en/posts/parallel-catalogue-llmops-stages-open-source-hyperscalers/">OSS vs hyperscalers&lt;/a> with lock-in analysis + Meta contract for the base model + contracts with tenants. → Documented.&lt;/li>
&lt;/ul>
&lt;p>Result of the walkthrough: certifiable. The typical gaps (A.2.2 written policy, A.3 roles, A.5 impact assessment procedure) are covered as formal documents. The operational clauses (8, 9, 10) lean on the blog&amp;rsquo;s technical architecture. The distance between &amp;ldquo;having the architecture&amp;rdquo; and &amp;ldquo;having certification&amp;rdquo; is measured in documentary discipline, not in code.&lt;/p>
&lt;h2 id="cross-mapping-with-the-eu-ai-act-nis2-and-ens">Cross-mapping with the EU AI Act, NIS2 and ENS&lt;/h2>
&lt;h3 id="eu-ai-act-reg-20241689--seven-directly-aligned-articles">EU AI Act (Reg. 2024/1689) — seven directly aligned articles&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>EU AI Act article&lt;/th>
&lt;th>Topic&lt;/th>
&lt;th>Aligned 42001 control&lt;/th>
&lt;th>Applies to&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Art. 9&lt;/td>
&lt;td>Risk management system&lt;/td>
&lt;td>A.5 + clause 6&lt;/td>
&lt;td>High-risk systems&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Art. 10&lt;/td>
&lt;td>Data and data governance&lt;/td>
&lt;td>A.7 (all)&lt;/td>
&lt;td>High-risk systems&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Art. 11&lt;/td>
&lt;td>Technical documentation&lt;/td>
&lt;td>A.6 + A.4.2&lt;/td>
&lt;td>High-risk systems&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Art. 12&lt;/td>
&lt;td>Record-keeping (logs)&lt;/td>
&lt;td>A.8.2 + OTel tracing&lt;/td>
&lt;td>High-risk systems&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Art. 13&lt;/td>
&lt;td>Transparency to deployers&lt;/td>
&lt;td>A.8.5 + A.10.4&lt;/td>
&lt;td>High-risk systems&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Art. 14&lt;/td>
&lt;td>Human oversight&lt;/td>
&lt;td>A.9.2 + documented oversight&lt;/td>
&lt;td>High-risk systems&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Art. 17&lt;/td>
&lt;td>Quality management system&lt;/td>
&lt;td>Clauses 4-10&lt;/td>
&lt;td>High-risk providers&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The main obligations for high-risk systems enter into application on 2 August 2026. Implementing 42001 now builds the management base that deadline demands.&lt;/p>
&lt;p>What is missing for EU AI Act compliance that 42001 does not cover:&lt;/p>
&lt;ul>
&lt;li>CE conformity of high-risk systems (declaration of conformity, marking, registration in the EU database).&lt;/li>
&lt;li>The specific post-market monitoring required by Art. 72.&lt;/li>
&lt;li>Serious incident reporting to authorities within legal deadlines (not only to users).&lt;/li>
&lt;li>Transparency obligations towards users for limited-risk systems (Art. 50): chatbots, deepfakes, generated content.&lt;/li>
&lt;li>The Art. 5 prohibitions (social scoring, manipulation, real-time biometrics with exceptions).&lt;/li>
&lt;/ul>
&lt;h3 id="nis2-dir-20222555--three-pillars-with-overlap">NIS2 (Dir. 2022/2555) — three pillars with overlap&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Asset register&lt;/strong> (Art. 21.2.f): AI systems within NIS2 scope must be in the asset inventory. → Overlaps with A.4 + clause 4.3 (scope).&lt;/li>
&lt;li>&lt;strong>Incident notification&lt;/strong> (Art. 23): significant incidents are notified within 24 h (initial alert) + 72 h (detailed report). → Overlaps with A.3.3 (reporting) + clause 10 (improvement).&lt;/li>
&lt;li>&lt;strong>Supply chain security&lt;/strong> (Art. 21.2.d): security assessment of the digital supply chain. → Overlaps with A.10.3 (suppliers).&lt;/li>
&lt;/ul>
&lt;p>For essential NIS2 entities that also use AI systems, 42001 covers the AI-specific part that NIS2 requires by inference but does not spell out.&lt;/p>
&lt;h3 id="ens-rd-3112022">ENS (RD 311/2022)&lt;/h3>
&lt;p>The Spanish Esquema Nacional de Seguridad already expressly addresses AI in its Annex II (ENS controls). The Basic/Medium/High categories align with the 42001 impact levels. The ENS controls on traceability (op.exp.8), activity logging (op.exp.10) and incident management (op.exp.7) are covered by the same technical artefacts as A.8 and A.5 of 42001. An organisation certified at ENS High category with AI systems is a reasonable effort away from adding 42001.&lt;/p>
&lt;h2 id="the-five-usual-certification-traps">The five usual certification traps&lt;/h2>
&lt;p>&lt;strong>Trap 1 — Confusing 42001 with EU AI Act compliance.&lt;/strong> Passing the 42001 audit does not imply conformity with the European Regulation. They are distinct universes with a 60-70% overlap. The trap surfaces when the client asks for CE marking of the high-risk system and the organisation produces only the 42001 certificate.&lt;/p>
&lt;p>&lt;strong>Trap 2 — Over-documenting.&lt;/strong> Two-hundred-page manuals with procedures copied from templates, with no measurables and no evidence of application. The auditor asks for the last execution of the procedure; with no records, the procedures are ornament. The practical rule: prefer short documents referencing live technical artefacts to long self-contained ones.&lt;/p>
&lt;p>&lt;strong>Trap 3 — Under-measuring.&lt;/strong> Defining AIMS objectives with no operational metrics. &amp;ldquo;Improve model quality&amp;rdquo; is a null objective; &amp;ldquo;F1 per guardrail category ≥ 0.85 over real traffic, measured weekly, reviewed quarterly in the management review&amp;rdquo; is an auditable objective. The blog has insisted on this in every post on &lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">evals&lt;/a>, &lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">guardrails&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">retrain&lt;/a>.&lt;/p>
&lt;p>&lt;strong>Trap 4 — Ignoring A.5 until audit day.&lt;/strong> The impact assessment is the most underrated control and the first one the auditor asks for. Without per-system assessments carried out before deployment, there is no way to demonstrate A.5. The trap surfaces when there is no longer time to produce credible retrospective assessments.&lt;/p>
&lt;p>&lt;strong>Trap 5 — Assuming 27001 covers the AI part.&lt;/strong> Organisations that already have 27001 sometimes think &amp;ldquo;we have half of it done&amp;rdquo;. That is true for Annex SL (structure) and for A.5/A.6/A.7 of 27001 (not of 42001) as far as infosec goes. It is false for A.5 of 42001 (impact assessment), A.7 of 42001 (AI-specific data quality), A.9 (responsible use) and A.10.4 (AI customers). You have to add, not assume.&lt;/p>
&lt;h2 id="what-we-have-not-covered-upcoming-posts">What we have not covered (upcoming posts)&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Concrete templates&lt;/strong> of the seven mandatory documents, with drafting examples and metrics. Material for a post along the lines of &amp;ldquo;The AIMS manual in 7 documents&amp;rdquo; with a reference frame.&lt;/li>
&lt;li>&lt;strong>Detailed article-by-article mapping to the EU AI Act&lt;/strong> with the checklist of technical evidence derivable from the blog&amp;rsquo;s OSS stack. Especially Arts 11 (technical documentation), 14 (human oversight) and 72 (post-market monitoring).&lt;/li>
&lt;li>&lt;strong>The combined ENS High category + 42001 case&lt;/strong>: which ENS controls are covered by which AIMS artefacts, avoiding duplication.&lt;/li>
&lt;li>&lt;strong>NIST AI RMF 1.0 vs 42001 comparison&lt;/strong>: many international clients ask for both. How the same artefacts are recycled to satisfy the two frameworks.&lt;/li>
&lt;li>&lt;strong>42001 for LLM agents and MCP&lt;/strong>: the new dimensions that emerge when the AI system is agentic (excessive agency, tool use, graduated autonomy). The &lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">guardrails&lt;/a> post introduced line 3 (tool GR); 42001 has open gaps in this terrain and SC 42 is working on addenda.&lt;/li>
&lt;/ul>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>ISO/IEC 42001:2023&lt;/strong> — &lt;em>Information technology — Artificial intelligence — Management system&lt;/em>. ISO. &lt;a href="https://www.iso.org/standard/81230.html">https://www.iso.org/standard/81230.html&lt;/a>.&lt;/li>
&lt;li>&lt;strong>ISO/IEC 22989:2022&lt;/strong> — &lt;em>Information technology — Artificial intelligence — Artificial intelligence concepts and terminology&lt;/em>. Define los roles AI provider/producer/customer/partner/subject.&lt;/li>
&lt;li>&lt;strong>ISO/IEC 23894:2023&lt;/strong> — &lt;em>Information technology — Artificial intelligence — Guidance on risk management&lt;/em>. Insumo de A.5.&lt;/li>
&lt;li>&lt;strong>ISO/IEC 38507:2022&lt;/strong> — &lt;em>Governance implications of the use of AI by organizations&lt;/em>. Complemento de gobierno.&lt;/li>
&lt;li>&lt;strong>ISO/IEC 5259&lt;/strong> — &lt;em>Data quality for analytics and machine learning&lt;/em> (familia). Insumo de A.7.&lt;/li>
&lt;li>&lt;strong>EU AI Act (Regulation 2024/1689)&lt;/strong> — texto consolidado en EUR-Lex. Entrada en vigor de obligaciones de alto riesgo: 2 ago 2026.&lt;/li>
&lt;li>&lt;strong>NIS2 (Directive 2022/2555)&lt;/strong> — texto consolidado en EUR-Lex.&lt;/li>
&lt;li>&lt;strong>ENS — Real Decreto 311/2022&lt;/strong> — Esquema Nacional de Seguridad, BOE-A-2022-7191.&lt;/li>
&lt;li>&lt;strong>NIST AI RMF 1.0&lt;/strong> (2023) — &lt;a href="https://www.nist.gov/itl/ai-risk-management-framework">https://www.nist.gov/itl/ai-risk-management-framework&lt;/a>.&lt;/li>
&lt;li>&lt;strong>EUR-Lex EU AI Act consolidated text&lt;/strong> — &lt;a href="https://eur-lex.europa.eu/eli/reg/2024/1689">https://eur-lex.europa.eu/eli/reg/2024/1689&lt;/a>.&lt;/li>
&lt;li>A-LIGN / BSI / Schellman — blogs sobre experiencia de auditoría 42001 con casos reales 2024-2025.&lt;/li>
&lt;/ul>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/hardening-secrets-sovereign-llm-stack-defence-in-depth/">Hardening and secrets in the sovereign LLM stack: defence in depth&lt;/a> — the operational security controls that hold up the AIMS.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">The six-stage LLMOps pipeline&lt;/a> — the operating procedure that materialises A.6 lifecycle with no extra work.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">Anatomy of an LLM request in production&lt;/a> — the forensic case walked through as a 42001 checklist in the &amp;ldquo;applied case&amp;rdquo; section of this post.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/anatomy-on-premise-llm-inference-stack-seven-layers/">Seven layers of the on-premise LLM inference stack&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/greenfield-deployment-phases-on-premise-llm-platform/">seven deployment phases&lt;/a> — direct material for A.4 resources.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/five-maturity-levels-platform-under-llm/">Five maturity levels of the platform&lt;/a> — how to justify the proportionality of the controls against the existing maturity level.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">Data versioning with DVC and lakeFS&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/rag-corpus-curation-active-librarian/">RAG corpus curation&lt;/a> — A.7 data covered in detail.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">LLM tracing with OpenTelemetry GenAI&lt;/a> — A.8 information to interested parties through standardised traceability.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">Guardrails and safety in LLMs&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard&lt;/a> — A.9 responsible use.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">Evals: the layer after tracing&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/llm-as-judge-exam-marker-not-oracle/">LLM-as-judge&lt;/a> — A.6.2.5 verification and validation.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">Retrain: closing the feedback → dataset → adapter loop&lt;/a> — clause 10 continual improvement + the incident-driven loop that feeds formal nonconformities.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/parallel-catalogue-llmops-stages-open-source-hyperscalers/">The parallel catalogue: OSS vs hyperscalers&lt;/a> — A.10.3 supplier assessment with a structural analysis of lock-in and contractual sovereignty; a direct input to the AI supplier register.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/oss-catalogue-llmops-six-stages-tool-by-tool/">The OSS catalogue for LLMOps&lt;/a> — A.10.5 inventory of OSS third parties with documented licence, governance and maturity.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/mlops-for-llms-landscape-2026/">LLM-specific MLOps in 2026: the landscape&lt;/a> — the operational context in which the AIMS runs and is audited.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/eu-ai-act-technical-dossier-on-premise-llm-architecture/">EU AI Act: the technical dossier, article by article&lt;/a> — the sister post on Regulation (EU) 2024/1689; it moves from the management system down to the directly applicable legal obligations, with deadlines, penalties and a control-to-article mapping.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/technical-controls-ens-iso-42001-eu-ai-act-cross-mapping/">Technical controls: the cross-mapping of ENS × ISO 42001 × EU AI Act&lt;/a> — the third post of the governance trilogy; it goes down to the detail of the 25 technical controls common to the three frameworks, with the master table of triple compliance and the labelling of evidence.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/incident-response-runbooks-llm-inference-kafka-keep/">Incident response runbooks for LLMs with Keep + Kafka&lt;/a> — the operational materialisation of clause 10 (continual improvement) and the WORM trace A.8.2 demands: every incident opens a nonconformity, triggers a postmortem, updates the runbook and is recorded in the Kafka &lt;code>audit.actions&lt;/code> topic.&lt;/p>
&lt;/li>
&lt;/ul></description></item><item><title>EU AI Act: the technical dossier, article by article, over the blog's on-premise LLM architecture</title><link>https://blog.lo0.es/en/posts/eu-ai-act-technical-dossier-on-premise-llm-architecture/</link><pubDate>Mon, 01 Jun 2026 05:30:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/eu-ai-act-technical-dossier-on-premise-llm-architecture/</guid><description>&lt;blockquote>
&lt;p>Sister post to the &lt;a href="https://blog.lo0.es/en/posts/iso-42001-aims-on-premise-llm-platform/">mapping onto ISO/IEC 42001&lt;/a>. That one broke down the AI management system, the certifiable standard. This one breaks down the binding legal regulation that applies without any certification: the EU AI Act is direct law in all 27 Member States, with no transposition and with explicit penalties of up to 35 million euros or 7% of worldwide turnover. The main obligations for high-risk systems enter into force on 2 August 2026; each article applies from its own date, not from the date the organisation gets certified.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>Regulation (EU) 2024/1689 (the EU AI Act, &amp;ldquo;Reglamento Europeo de Inteligencia Artificial&amp;rdquo;), published in the Official Journal on 12 July 2024, sets obligations by risk tier (prohibited, high, limited, minimal) and by role (provider, deployer, importer, distributor, authorised representative). The obligations for high-risk systems (Annex III: biometrics, critical infrastructure, education, employment, essential public and private services, law enforcement, migration, justice, democratic processes) enter into force on 2 August 2026 and are the category that applies to most LLM projects in a medium-to-large enterprise. This post maps, article by article, the obligations relevant to a high-risk LLM system deployed on-premise: each article states its requirement, identifies which blog post already describes the technical piece that materialises it, and closes with an auditable checklist that a provider hands to a national supervisory authority. The Annex IV technical dossier is reconstructed by pointing its nine mandatory sections at the corresponding technical runbooks. Also covered: the Art. 6 classification and how to decide whether a system falls in as high risk, the Art. 5 prohibitions (what is excluded by construction), the GPAI obligations of Art. 53 that affect anyone building on base models (Llama, Mistral, DeepSeek, Qwen) rather than their own, the full timetable of application dates (5 Aug 2024 entry into force, 2 Feb 2025 prohibitions, 2 Aug 2025 GPAI, 2 Aug 2026 high risk Annex III, 2 Aug 2027 high risk Annex I and systemic GPAI), the Art. 99 penalty table (up to 35 M€ or 7% of worldwide turnover for Art. 5 violations, 15 M€ or 3% for high risk, 7.5 M€ or 1% for incorrect information) and the five frequent compliance traps. The editorial thesis: the technical architecture described in this blog directly covers between 70% and 85% of the Regulation&amp;rsquo;s technical requirements; the rest is documentary and procedural discipline (FRIA, CE marking, signed declaration of conformity, EU database registration, incident reporting within legal deadlines) built on top of technical artefacts that already exist but running on its own circuit.&lt;/p>
&lt;h2 id="the-analogy-the-type-approval-dossier-for-a-new-vehicle">The analogy: the type-approval dossier for a new vehicle&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 360" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="EU AI Act as a vehicle type-approval dossier">
&lt;style>
.h-veh{fill:#7aafff;stroke:#444;stroke-width:1.4;rx:8}
.h-doss{fill:#ffd76b;stroke:#444;stroke-width:1.4;rx:8}
.h-auth{fill:#a8e6a3;stroke:#444;stroke-width:1.4;rx:8}
.h-mkt{fill:#ff8a4c;stroke:#444;stroke-width:1.4;rx:8}
.hl{font:600 13px sans-serif;fill:#222}
.hs{font:400 11px sans-serif;fill:#555}
.hn{font:italic 11px sans-serif;fill:#555}
.hr{stroke:#666;stroke-width:1.6;fill:none;marker-end:url(#mh1)}
&lt;/style>
&lt;defs>&lt;marker id="mh1" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;rect x="20" y="20" width="160" height="60" class="h-veh"/>
&lt;text x="100" y="40" text-anchor="middle" class="hl">High-risk AI system&lt;/text>
&lt;text x="100" y="58" text-anchor="middle" class="hs">scoring, biometrics, HR,&lt;/text>
&lt;text x="100" y="72" text-anchor="middle" class="hs">health, justice (the new car)&lt;/text>
&lt;rect x="220" y="20" width="180" height="60" class="h-doss"/>
&lt;text x="310" y="40" text-anchor="middle" class="hl">Annex IV dossier&lt;/text>
&lt;text x="310" y="58" text-anchor="middle" class="hs">technical documentation +&lt;/text>
&lt;text x="310" y="72" text-anchor="middle" class="hs">QMS + FRIA (the WVTA file)&lt;/text>
&lt;rect x="440" y="20" width="160" height="60" class="h-auth"/>
&lt;text x="520" y="40" text-anchor="middle" class="hl">Conformity Assessment&lt;/text>
&lt;text x="520" y="58" text-anchor="middle" class="hs">notified body or self-assess.&lt;/text>
&lt;text x="520" y="72" text-anchor="middle" class="hs">(type approval)&lt;/text>
&lt;rect x="640" y="20" width="160" height="60" class="h-mkt"/>
&lt;text x="720" y="40" text-anchor="middle" class="hl">CE marking + EU DB&lt;/text>
&lt;text x="720" y="58" text-anchor="middle" class="hs">declaration of conformity&lt;/text>
&lt;text x="720" y="72" text-anchor="middle" class="hs">(E mark, type approval)&lt;/text>
&lt;path class="hr" d="M180,50 L220,50"/>
&lt;path class="hr" d="M400,50 L440,50"/>
&lt;path class="hr" d="M600,50 L640,50"/>
&lt;rect x="20" y="130" width="780" height="80" class="h-doss"/>
&lt;text x="410" y="152" text-anchor="middle" class="hl">The 9 sections of the Annex IV dossier (what the file has to contain)&lt;/text>
&lt;text x="410" y="172" text-anchor="middle" class="hs">1 General description · 2 Design and development · 3 Capabilities and limitations · 4 Data · 5 Monitoring · 6 QMS plan · 7 FRIA&lt;/text>
&lt;text x="410" y="190" text-anchor="middle" class="hs">8 Log records · 9 Declaration of conformity — all signed by the provider, available to authorities for 10 years&lt;/text>
&lt;rect x="20" y="230" width="780" height="80" class="h-mkt"/>
&lt;text x="410" y="252" text-anchor="middle" class="hl">Post-market monitoring (Art. 72) + Serious incident reporting (Art. 73)&lt;/text>
&lt;text x="410" y="272" text-anchor="middle" class="hs">The vehicle is tracked after sale: servicing, recalls for defects, records of serious accidents&lt;/text>
&lt;text x="410" y="290" text-anchor="middle" class="hs">Legal deadlines: 15 days (general), 10 days (death), 2 days (critical infra or widespread infringement)&lt;/text>
&lt;text x="410" y="340" text-anchor="middle" class="hn">The vehicle leaves the factory with a dossier; it gets the E mark; it travels with a service book; serious accidents are reported to the ministry.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>A vehicle manufacturer cannot sell a new car in the EU without passing WVTA (Whole Vehicle Type Approval). The process is public and standardised: the manufacturer prepares a technical dossier with dozens of chapters (brakes, emissions, active and passive safety, lighting, noise, weight, dimensions, recyclable materials, driver assistance devices), submits it to a type-approval authority or a notified technical service, which audits the file and, if everything adds up, issues the type approval. The manufacturer then stamps the E mark (E1 Germany, E9 Spain, and so on) and the CE/UNECE plate on every vehicle of that type produced in series. Each vehicle also carries a service book with mandatory inspections, and serious accidents or systemic defects are reported to the competent ministry, which can order recalls.&lt;/p>
&lt;p>The EU AI Act adapts exactly this industrial model to high-risk AI software. The provider prepares the Annex IV dossier (nine mandatory sections) with the full technical documentation of the system, runs a conformity assessment (self-assessment for most Annex III cases, notified body for the more sensitive Annex I ones), signs the EU declaration of conformity, registers the system in the European database, and applies the CE marking to the system when it is placed on the market. From there on it must keep a live post-market monitoring system and report serious incidents to the market surveillance authorities of each Member State within legal deadlines ranging from 2 to 15 days depending on severity. Failing to comply, penalties reach 35 million euros or 7% of worldwide turnover depending on which article was breached.&lt;/p>
&lt;p>The analogy matters because it bounds expectations: this is not a one-off compliance job, nor a badge you buy. It is an industrial type-approval process, with cadences, evidence, signatures and criminal liability. And most of the technical evidence the dossier asks for already exists in any serious system described in this blog: data lineage, OTel tracing, continuous evals, guardrails, incident-driven retrain, a responsible-use policy. What is missing is assembling it in the correct legal format.&lt;/p>
&lt;h2 id="how-it-fits-with-iso-42001-nis2-and-ens">How it fits with ISO 42001, NIS2 and ENS&lt;/h2>
&lt;p>Before dropping into the articles, a reminder of the editorial position from the &lt;a href="https://blog.lo0.es/en/posts/iso-42001-aims-on-premise-llm-platform/">post on ISO 42001&lt;/a>:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Piece&lt;/th>
&lt;th>Nature&lt;/th>
&lt;th>Who operates it&lt;/th>
&lt;th>Coverage&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>EU AI Act&lt;/strong>&lt;/td>
&lt;td>Direct EU law&lt;/td>
&lt;td>Provider + deployer + national surveillance authorities&lt;/td>
&lt;td>AI systems on the EU market, segmented by risk&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ISO/IEC 42001&lt;/strong>&lt;/td>
&lt;td>Certifiable management standard&lt;/td>
&lt;td>Organisation + certification body&lt;/td>
&lt;td>AIMS, organisational governance&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>NIS2&lt;/strong>&lt;/td>
&lt;td>Transposed cyber directive&lt;/td>
&lt;td>Essential/important entities&lt;/td>
&lt;td>Asset register, incident notification, supply chain&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>ENS&lt;/strong> (Esquema Nacional de Seguridad, Spain&amp;rsquo;s national security framework, RD 311/2022)&lt;/td>
&lt;td>Spanish security regulation&lt;/td>
&lt;td>Public sector + its suppliers&lt;/td>
&lt;td>Categories B/M/A, certifiable&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Implementing 42001 makes it easier to demonstrate articles 9-17 of the EU AI Act, but it is not equivalent to legal compliance. The table of legal obligations and the chain of criminal liability come from the Regulation, not from the standard. An organisation certified against 42001 that deploys a high-risk system without CE marking, without registration in the EU database, without a signed declaration of conformity and without a documented FRIA breaches the Regulation even with the certificate on the wall.&lt;/p>
&lt;h2 id="the-four-risk-categories-and-the-art-6-classification">The four risk categories and the Art. 6 classification&lt;/h2>
&lt;p>The Regulation classifies systems into four risk tiers. The choice of category belongs to the provider and must be documented in the dossier, with technical and legal justification.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Category&lt;/th>
&lt;th>Article&lt;/th>
&lt;th>Examples&lt;/th>
&lt;th>Consequence&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Prohibited&lt;/strong>&lt;/td>
&lt;td>Art. 5&lt;/td>
&lt;td>Social scoring, manipulation, real-time biometrics with exceptions, indiscriminate facial scraping&lt;/td>
&lt;td>Cannot operate in the EU under any circumstances&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>High risk&lt;/strong>&lt;/td>
&lt;td>Art. 6 + Annex I + Annex III&lt;/td>
&lt;td>Credit scoring, HR, education, critical infrastructure, non-real-time biometrics, justice, migration, health&lt;/td>
&lt;td>Complies with Arts. 9-17, Annex IV dossier, CE marking, EU DB registration&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Limited risk&lt;/strong>&lt;/td>
&lt;td>Art. 50&lt;/td>
&lt;td>Chatbots with humans as users, deepfakes, synthetic content&lt;/td>
&lt;td>Transparency obligations towards the user&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Minimal risk&lt;/strong>&lt;/td>
&lt;td>Everything else&lt;/td>
&lt;td>Spam filters, video game NPCs, content suggestions&lt;/td>
&lt;td>No specific obligations; a voluntary code of conduct is recommended&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The Art. 6 test for deciding whether a system is high risk:&lt;/p>
&lt;ol>
&lt;li>Is it in Annex I? (products regulated by the listed harmonisation legislation: machinery, lifts, toys, medical devices, and so on). If the AI system is a safety component of an Annex I product, it is high risk.&lt;/li>
&lt;li>Is it in Annex III? (eight areas: biometrics, critical infrastructure, education, employment, essential public/private services, law enforcement, migration/asylum, justice/democratic processes). If the system falls in any of those areas it is high risk, except if the Art. 6.3 exception is invoked (systems performing a narrow procedural task, improving the result of a previous human activity, detecting decision patterns without influencing the final decision, preparatory tasks).&lt;/li>
&lt;/ol>
&lt;p>The Art. 6.3 exception requires formal documentation justifying why it does not apply. In other words, even falling outside is not free: you have to demonstrate why.&lt;/p>
&lt;p>For the typical LLM systems covered in this blog:&lt;/p>
&lt;ul>
&lt;li>Customer support chatbot for banking, insurance or healthcare: probably high risk if it automates contractual decisions about the customer, limited risk if it only informs.&lt;/li>
&lt;li>Internal HR assistant (CV screening): high risk (Annex III, employment area).&lt;/li>
&lt;li>Medical assistant (diagnostic support): high risk (Annex III, healthcare services area).&lt;/li>
&lt;li>Fraud detection system: high risk (Annex III, financial services area if it affects access to credit).&lt;/li>
&lt;li>Code copilot for developers: minimal risk (does not affect third parties&amp;rsquo; fundamental rights).&lt;/li>
&lt;li>Internal LLM as a service with no productive use: minimal risk.&lt;/li>
&lt;/ul>
&lt;p>The category decision is not an opinion: it is documented and justified in the dossier.&lt;/p>
&lt;h2 id="timetable-of-application">Timetable of application&lt;/h2>
&lt;p>The obligations arrive in stages. The dates are inflexible:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Date&lt;/th>
&lt;th>Applies&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>1 Aug 2024&lt;/strong>&lt;/td>
&lt;td>The Regulation enters into force&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>2 Feb 2025&lt;/strong>&lt;/td>
&lt;td>Art. 5 prohibitions + Art. 4 AI literacy obligations&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>2 Aug 2025&lt;/strong>&lt;/td>
&lt;td>GPAI obligations (Art. 53) + governance + general penalties + designated national authorities&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>2 Aug 2026&lt;/strong>&lt;/td>
&lt;td>&lt;strong>Main high-risk obligations for Annex III + Art. 50 transparency to users&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>2 Aug 2027&lt;/strong>&lt;/td>
&lt;td>High risk under Annex I (components of regulated products) + GPAI with systemic risk&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>2 August 2026 is the date that matters to most enterprise LLM projects. We are in June 2026: less than two months to go.&lt;/p>
&lt;h2 id="article-by-article-mapping">Article-by-article mapping&lt;/h2>
&lt;p>The sections below follow the structure of the Regulation. For each article the requirement is stated, the blog&amp;rsquo;s technical artefact that covers it is identified, and it closes with an auditable checklist.&lt;/p>
&lt;h3 id="art-5--prohibited-practices-in-force-since-2-feb-2025">Art. 5 — Prohibited practices (in force since 2 Feb 2025)&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> It prohibits placing on the market, putting into service or using AI systems that:&lt;/p>
&lt;ul>
&lt;li>Manipulate behaviour through subliminal or deceptive techniques that distort decision-making.&lt;/li>
&lt;li>Exploit vulnerabilities related to age, disability or socio-economic situation.&lt;/li>
&lt;li>Implement social scoring by public authorities.&lt;/li>
&lt;li>Perform individual predictive policing based on profiling.&lt;/li>
&lt;li>Perform indiscriminate facial scraping to build facial recognition databases.&lt;/li>
&lt;li>Infer emotions in workplaces or education, except for medical or safety reasons.&lt;/li>
&lt;li>Perform biometric categorisation inferring sensitive attributes (race, political opinion, sexual orientation, and so on).&lt;/li>
&lt;li>Real-time remote biometric identification in public spaces by law enforcement, with strict exceptions (terrorism, kidnapping, missing persons) subject to prior judicial authorisation.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong> No piece of the blog facilitates these practices; the OSS catalogue described is oriented towards legitimate tasks. But the provider must explicitly document why the system does not fall under these prohibitions. It cannot be assumed.&lt;/p>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Prohibition analysis documented per system, with a written statement of non-applicability.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> If the system uses facial biometrics or emotion analysis: specific legal analysis with a formal opinion.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Annual legal review, or on any change of functionality.&lt;/li>
&lt;/ul>
&lt;h3 id="art-6--classification-of-high-risk-systems">Art. 6 — Classification of high-risk systems&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> Determine whether the system is high risk by being in Annex I (safety component of a regulated product) or Annex III (8 areas). If it is in Annex III, assess the Art. 6.3 exception where applicable.&lt;/p>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong> The &lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">forensic post&lt;/a> and the &lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">six-stage pipeline&lt;/a> describe systems that are typically high risk (multi-tenant chatbot affecting service decisions in a regulated sector).&lt;/p>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Art. 6 analysis signed by the organisation&amp;rsquo;s legal officer.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> If Art. 6.3 is invoked: formal documentation of the exception.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Re-assessment if the functionality changes (for example, the assistant moves from informing to deciding).&lt;/li>
&lt;/ul>
&lt;h3 id="art-9--risk-management-system">Art. 9 — Risk management system&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> An iterative system, planned and executed across the whole lifecycle. Identify foreseeable risks, estimate risks under normal use and foreseeable misuse, evaluate emerging risks in post-market monitoring, adopt mitigation measures, communicate residual risks. Effectiveness testing is done under realistic conditions.&lt;/p>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">The six-stage LLMOps pipeline&lt;/a>, the iterative lifecycle.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">Evals: the layer after tracing&lt;/a>, testing under realistic conditions with golden sets.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">Guardrails and safety in LLMs&lt;/a>, operational mitigation measures.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">Retrain: closing the loop&lt;/a>, management of emerging risks via incident-driven retrain.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Risk management document per system, with identification, mitigation, and accepted residual risks signed off.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Periodic review procedure (at least annual, or on substantial change).&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Link to the documented improvement loop.&lt;/li>
&lt;/ul>
&lt;h3 id="art-10--data-and-data-governance">Art. 10 — Data and data governance&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> Training, validation and testing datasets that are relevant, representative, as free of errors and as complete as possible, taking into account the characteristics of the intended purpose. Document:&lt;/p>
&lt;ul>
&lt;li>Data collection and selection.&lt;/li>
&lt;li>Processing and annotation.&lt;/li>
&lt;li>Biases identified as likely to affect fundamental rights or cause discrimination; measures to prevent them.&lt;/li>
&lt;li>Identification of data gaps and how they are addressed.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">Data versioning with DVC and lakeFS&lt;/a>, the four data artefacts plus end-to-end lineage.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/rag-corpus-curation-active-librarian/">RAG corpus curation: the active librarian&lt;/a>, five layers: schema, dedup, PII, anti-contamination, lineage.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard: Vault and Anonymize&lt;/a>, runtime anonymisation with restitution.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Data governance document per dataset (training / RAG corpus / golden eval / enriched retrain).&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Bias analysis by protected category with metrics (parity ratio, equalized odds, calibration).&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> PII / anonymisation / pseudonymisation procedure with measured F1.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Justification of representativeness for the intended context.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Verifiable chunk→trace lineage.&lt;/li>
&lt;/ul>
&lt;h3 id="art-11--annex-iv--technical-documentation">Art. 11 + Annex IV — Technical documentation&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> A technical dossier with the nine Annex IV sections, drawn up before the system is placed on the market, maintained during operation, and available to authorities for ten years after the last operation. The nine sections:&lt;/p>
&lt;ol>
&lt;li>General description of the system: name, version, purpose, integrator, intended hardware, instructions for use.&lt;/li>
&lt;li>Detailed description of design and development: architecture, base models, training methods, design decisions with justification.&lt;/li>
&lt;li>Information on monitoring, functioning and control: capabilities, limitations, expected accuracy, behaviour under normal use and foreseeable misuse.&lt;/li>
&lt;li>Information on data: datasets used, sources, preparation methods, biases addressed.&lt;/li>
&lt;li>Description of the monitoring system and metrics: traces, logs, dashboards.&lt;/li>
&lt;li>Description of the QMS and the Art. 17 procedures.&lt;/li>
&lt;li>FRIA where applicable (Fundamental Rights Impact Assessment, Art. 27).&lt;/li>
&lt;li>Automatically generated logs that the system stores (Art. 12).&lt;/li>
&lt;li>EU declaration of conformity under Art. 47, included.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong> It serves as a direct input for each section:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Annex IV section&lt;/th>
&lt;th>Technical input from the blog&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>1. General description&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/en/posts/anatomy-on-premise-llm-inference-stack-seven-layers/">Anatomy of the stack: 7 layers&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/greenfield-deployment-phases-on-premise-llm-platform/">seven deployment phases&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>2. Design and development&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">Six-stage LLMOps pipeline&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/continuous-fine-tuning-production-real-traffic-deployed-adapter/">continuous fine-tuning&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/modern-alignment-dpo-kto-orpo-simpo/">modern alignment&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>3. Capabilities and limitations&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">Evals&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/llm-as-judge-exam-marker-not-oracle/">LLM-as-judge&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>4. Data&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">Data versioning&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/rag-corpus-curation-active-librarian/">RAG corpus curation&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>5. Monitoring&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">LLM tracing with OTel GenAI&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/prompt-versioning-contract-five-word-change/">prompt versioning&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>6. QMS&lt;/td>
&lt;td>Procedures derived from &lt;a href="https://blog.lo0.es/en/posts/iso-42001-aims-on-premise-llm-platform/">ISO 42001&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>7. FRIA&lt;/td>
&lt;td>Gap, see Art. 27 below&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>8. Logs&lt;/td>
&lt;td>&lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">OTel tracing&lt;/a> + retention and policies&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>9. Declaration of conformity&lt;/td>
&lt;td>Documentary gap, see Art. 47 below&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Annex IV dossier complete, versioned, dated, signed.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> 10-year retained access guaranteed (immutable / WORM storage).&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Update procedure on substantial change.&lt;/li>
&lt;/ul>
&lt;h3 id="art-12--art-19--record-keeping-logs">Art. 12 + Art. 19 — Record-keeping (logs)&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> A high-risk system must be technically capable of generating automatic logs during its operation. Those logs must allow:&lt;/p>
&lt;ul>
&lt;li>Tracing how the system behaved over time.&lt;/li>
&lt;li>Facilitating post-market monitoring (Art. 72).&lt;/li>
&lt;li>Enabling investigation of serious incidents (Art. 73).&lt;/li>
&lt;li>Supporting audits.&lt;/li>
&lt;/ul>
&lt;p>The provider must retain the logs for at least six months (or longer if national laws or the QMS require it). For remote biometric identification systems there are additional specific requirements.&lt;/p>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">LLM tracing with OpenTelemetry GenAI&lt;/a>, the canonical substrate. Every request emits a span with &lt;code>trace_id&lt;/code>, &lt;code>gen_ai.*&lt;/code> attributes, costs, latencies and guardrail decisions.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/prompt-versioning-contract-five-word-change/">Prompt versioning&lt;/a>: &lt;code>prompt_id&lt;/code> + &lt;code>version&lt;/code> travel as attributes.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">Guardrails and safety in LLMs&lt;/a>: &lt;code>gen_ai.guardrail.*&lt;/code> attributes record every decision.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard&lt;/a>: spans per scanner with &lt;code>risk_score&lt;/code> and &lt;code>action&lt;/code>.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">Data versioning&lt;/a>: &lt;code>dataset_hash&lt;/code> and &lt;code>model_version&lt;/code> propagated.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> OTel + backend (Tempo, Jaeger) running in production.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Minimum 6-month retention (24-36 months suggested for financial regulation).&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> WORM / immutable storage.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> PII in logs redacted (via LLM Guard Vault or equivalent). Logs are not exempt from the GDPR.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Forensic query procedure with audited permissions.&lt;/li>
&lt;/ul>
&lt;h3 id="art-13--transparency-to-deployers">Art. 13 — Transparency to deployers&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> The provider gives the deployer instructions for use that are clear, complete and accessible, in comprehensible language, covering:&lt;/p>
&lt;ul>
&lt;li>The provider&amp;rsquo;s identity.&lt;/li>
&lt;li>Characteristics, capabilities, limitations (accuracy by category, technical specifications).&lt;/li>
&lt;li>Foreseen changes to the system and its metrics.&lt;/li>
&lt;li>Human oversight measures (Art. 14).&lt;/li>
&lt;li>Expected computational resources and hardware.&lt;/li>
&lt;li>When it applies, the expected lifetime of the system and of its maintenance.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/oss-catalogue-llmops-six-stages-tool-by-tool/">The OSS catalogue for LLMOps&lt;/a>, which components and with what function.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/parallel-catalogue-llmops-stages-open-source-hyperscalers/">OSS vs hyperscalers&lt;/a>, lock-in analysis and documented dependencies.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">Anatomy of an LLM request&lt;/a>, capabilities and limitations at forensic level.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> User manual in non-technical language for the deployer, plus a detailed technical manual.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Accuracy metrics by category with thresholds.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Change procedure with prior notification.&lt;/li>
&lt;/ul>
&lt;h3 id="art-14--human-oversight">Art. 14 — Human oversight&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> The system is designed to allow effective human oversight throughout the period of use, with interfaces and procedures that make it possible to:&lt;/p>
&lt;ul>
&lt;li>Understand capabilities and limitations.&lt;/li>
&lt;li>Detect malfunctions (automation bias awareness).&lt;/li>
&lt;li>Decide not to use the system&amp;rsquo;s output, to override it, to reverse it.&lt;/li>
&lt;li>For remote biometric identification: human verification before acting, by at least two people.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">Guardrails and safety&lt;/a>, line 3 (Tool GR) with human-in-the-loop for destructive actions.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">Evals&lt;/a>, metrics on a human-accessible dashboard.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">OTel tracing&lt;/a>, Langfuse with auditable human sessions.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Oversight interface documented with use cases.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Override / abort capability with no technical restrictions.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Documented training for oversight personnel.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Metrics of the effectiveness of the oversight (override rate, false-negative rate of the oversight itself).&lt;/li>
&lt;/ul>
&lt;h3 id="art-15--accuracy-robustness-and-cybersecurity">Art. 15 — Accuracy, robustness and cybersecurity&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> High-risk systems are designed and developed to reach an appropriate level of accuracy, robustness and cybersecurity, and to perform consistently throughout their lifecycle. The relevant accuracy metrics are declared in the instructions for use. Resilience against:&lt;/p>
&lt;ul>
&lt;li>Errors, faults and inconsistencies within the environment of use.&lt;/li>
&lt;li>Feedback bias during operation (feedback loops).&lt;/li>
&lt;li>Attacks that try to exploit the system&amp;rsquo;s vulnerabilities (data poisoning, model poisoning, model evasion, confidentiality attacks).&lt;/li>
&lt;li>Technical and organisational measures to detect, respond to and resolve vulnerabilities.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/quantization-llm-inference-fp8-int4-gguf/">Quantisation fundamentals&lt;/a>, accuracy vs efficiency with reported metrics.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/continuous-batching-orca-vllm-chunked-prefill-goodput/">Continuous batching&lt;/a> + &lt;a href="https://blog.lo0.es/en/posts/kv-cache-working-memory-llm-inference/">KV cache&lt;/a>, operational robustness.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">Guardrails: line 1 input + line 2 retrieval&lt;/a>, defence against prompt injection and data poisoning via RAG.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard: PromptGuard 2 + injection scanners&lt;/a>, direct adversarial mitigation.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">Evals: jailbreak resistance + adversarial&lt;/a>, robustness metrics evaluated in CI.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Declared accuracy metrics: F1 by category, accuracy, calibration, RAG faithfulness, hallucination rate.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Robustness plan against adversarial inputs (Garak suite / Promptfoo redteam / PyRIT run periodically).&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Cybersecurity plan: vulnerability management, patching of the stack (vLLM, its deps, CUDA), secrets rotation.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Analysis of potential feedback loops with drift monitoring.&lt;/li>
&lt;/ul>
&lt;h3 id="art-17--quality-management-system-qms">Art. 17 — Quality management system (QMS)&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> The provider has a written, systematic and proportionate QMS covering (without being limited to):&lt;/p>
&lt;ul>
&lt;li>Regulatory compliance strategy.&lt;/li>
&lt;li>Design, verification and quality control of the system.&lt;/li>
&lt;li>Testing and validation procedures.&lt;/li>
&lt;li>Data management.&lt;/li>
&lt;li>The risk management system (Art. 9).&lt;/li>
&lt;li>Post-market monitoring (Art. 72).&lt;/li>
&lt;li>Incident reporting (Art. 73).&lt;/li>
&lt;li>Communication with authorities, deployers and other stakeholders.&lt;/li>
&lt;li>Records: documentation, log maintenance.&lt;/li>
&lt;li>Resource management.&lt;/li>
&lt;li>Accountability: management responsibilities.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong> The QMS is not code, but it leans on code.&lt;/p>
&lt;ul>
&lt;li>ISO/IEC 42001 implemented (previous post) covers practically the whole content of Art. 17.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">Six-stage LLMOps pipeline&lt;/a> as the reference operating procedure.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> QMS manual written, dated, signed, versioned.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Annual internal audit plan with criteria and records.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Management review agenda with signed minutes.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> If 42001 is implemented: documented QMS-42001 mapping.&lt;/li>
&lt;/ul>
&lt;h3 id="art-26--deployer-obligations">Art. 26 — Deployer obligations&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> Whoever deploys the system (uses it under their own authority, not necessarily the developer) has obligations of their own:&lt;/p>
&lt;ul>
&lt;li>Use the system in accordance with the instructions (Art. 13).&lt;/li>
&lt;li>Assign competent, trained human oversight (Art. 14).&lt;/li>
&lt;li>Ensure the input data under their control is appropriate.&lt;/li>
&lt;li>Monitor operation and notify the provider on detecting problems or serious incidents.&lt;/li>
&lt;li>Keep the logs under their control for at least 6 months.&lt;/li>
&lt;li>Inform the affected persons when the system is used on them to make decisions (and, in an employment context, also consult their representatives).&lt;/li>
&lt;li>For some Annex III cases: complete a FRIA (Art. 27).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong> In the case of the &lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">multi-tenant chatbot&lt;/a>, the deployer is the client insurer. It must:&lt;/p>
&lt;ul>
&lt;li>Accept the provider&amp;rsquo;s instructions (the consultancy&amp;rsquo;s) and sign the terms.&lt;/li>
&lt;li>Configure human oversight on its side.&lt;/li>
&lt;li>Notify the provider when it detects drift or a serious complaint.&lt;/li>
&lt;li>Keep its own logs in addition to the provider&amp;rsquo;s.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist (for the deployer).&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Contract with the provider covering SLAs, responsibilities, exit plan.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Training programme for oversight personnel.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Incident notification procedure towards the provider.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Policy for informing affected persons (in the employment context, notification to representatives).&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> FRIA where applicable.&lt;/li>
&lt;/ul>
&lt;h3 id="art-27--fundamental-rights-impact-assessment-fria">Art. 27 — Fundamental Rights Impact Assessment (FRIA)&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> Applicable to deployers that are public bodies or private entities providing public services, or that use systems to assess credit score or life insurance. Before first use, the deployer carries out a FRIA documenting:&lt;/p>
&lt;ul>
&lt;li>A description of the intended use.&lt;/li>
&lt;li>Period and frequency of use.&lt;/li>
&lt;li>Categories of affected persons.&lt;/li>
&lt;li>Specific risks of harm identified.&lt;/li>
&lt;li>Human oversight measures.&lt;/li>
&lt;li>Mitigation measures should the risks materialise.&lt;/li>
&lt;/ul>
&lt;p>The FRIA is notified to the market surveillance authority. Substantial changes force an update.&lt;/p>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong> The FRIA is a governance document, not a technical artefact. The blog does not cover it directly. But the technical input is:&lt;/p>
&lt;ul>
&lt;li>The impact analyses from the &lt;a href="https://blog.lo0.es/en/posts/iso-42001-aims-on-premise-llm-platform/">ISO 42001 post, section A.5&lt;/a> are the natural starting point.&lt;/li>
&lt;li>The metrics from the &lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">evals post&lt;/a> on fairness by category and groundedness feed the risk section of the FRIA.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> FRIA procedure documented, aligned with the AIIA in ISO 42005 (published as a complement).&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> FRIA carried out per system before first use.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Notification to the market surveillance authority.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Periodic review, plus on substantial change.&lt;/li>
&lt;/ul>
&lt;h3 id="art-47--eu-declaration-of-conformity">Art. 47 — EU declaration of conformity&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> The provider draws up a written declaration of conformity per high-risk system, stating:&lt;/p>
&lt;ul>
&lt;li>Identification of the system and the provider.&lt;/li>
&lt;li>A statement that it complies with Arts. 8-15 + Art. 17.&lt;/li>
&lt;li>Reference to the harmonised standards and common specifications applied.&lt;/li>
&lt;li>Where applicable, the notified body and the certificate.&lt;/li>
&lt;li>Place and date, signature and name of the authorised signatory.&lt;/li>
&lt;/ul>
&lt;p>It must be available to authorities for ten years. It is kept up to date.&lt;/p>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong> A pure legal document. Template in Annex V.&lt;/p>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Declaration of conformity signed by an authorised person before market placement.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Languages: at least the official EU language of the market where the system is placed.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Re-signing procedure on substantial change.&lt;/li>
&lt;/ul>
&lt;h3 id="art-48--ce-marking">Art. 48 — CE marking&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> A high-risk system carries the CE marking visibly, legibly and indelibly. For digital systems with no physical part, the marking is included in the documentation or the interface. If a notified body was involved, its number follows.&lt;/p>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong> CE marking on a software system typically materialises in:&lt;/p>
&lt;ul>
&lt;li>The product information or &amp;ldquo;About&amp;rdquo; page.&lt;/li>
&lt;li>The official product documentation delivered to the deployer.&lt;/li>
&lt;li>Metadata of the exposed API (custom HTTP header, OpenAPI info).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> CE marking visible in the product interface or official documentation.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Notified body number attached where applicable.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Update procedure on substantial change.&lt;/li>
&lt;/ul>
&lt;h3 id="art-49--registration-in-the-european-database">Art. 49 — Registration in the European database&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> Before placing on the market or putting into service a high-risk Annex III system (except area 2, critical infrastructure), the provider registers it in the EU database of high-risk AI systems managed by the Commission. Deployers that are public authorities also register their use. Most registration data is public (transparency towards the public).&lt;/p>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong> A pure administrative procedure. No technical part beyond having the dossier ready.&lt;/p>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Registration completed before first productive use.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Update on substantial change.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Portal access maintained (credentials, contact).&lt;/li>
&lt;/ul>
&lt;h3 id="art-50--transparency-to-end-users">Art. 50 — Transparency to end users&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> Applies to limited risk systems (and, complementarily, to some high-risk ones too):&lt;/p>
&lt;ul>
&lt;li>Chatbots and AI assistants: the person interacting must know they are talking to an AI, unless it is obvious from the context.&lt;/li>
&lt;li>Generated synthetic content (text, audio, image, video): marked as AI-generated in the output, in a machine-readable format.&lt;/li>
&lt;li>Deepfakes: explicitly declare that the content is artificially generated or manipulated.&lt;/li>
&lt;li>Emotion detectors or biometric categorisation: inform the affected persons.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong> Technical materialisation:&lt;/p>
&lt;ul>
&lt;li>A UI banner in the chatbot saying &amp;ldquo;You are talking to an AI assistant&amp;rdquo;.&lt;/li>
&lt;li>A disclaimer on every exportable response (PDF, email): &amp;ldquo;Generated by AI&amp;rdquo;.&lt;/li>
&lt;li>Watermarking of the output (perplexity-based, model-fingerprint), optional but useful for deepfakes.&lt;/li>
&lt;li>For generated audio, image or video, standard C2PA metadata.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Mandatory UI banner.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Disclaimer on exportable outputs.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> If it generates visual content: C2PA marking or equivalent.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Procedure for use of the system on a person without their knowledge (for example, automated CV screening).&lt;/li>
&lt;/ul>
&lt;h3 id="art-53--obligations-of-gpai-providers-in-force-since-2-aug-2025">Art. 53 — Obligations of GPAI providers (in force since 2 Aug 2025)&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> Providers of GPAI (general-purpose models trained with vast compute, typically foundational: Llama 4, Mistral, DeepSeek, Qwen, Gemma) must:&lt;/p>
&lt;ul>
&lt;li>Maintain technical documentation of the model, accessible to the AI Office and national authorities.&lt;/li>
&lt;li>Make information available to downstream providers that will integrate it.&lt;/li>
&lt;li>Comply with EU copyright (Art. 4 of Directive 2019/790): an opt-out mechanism for rightsholders.&lt;/li>
&lt;li>Publish a summary of the training content (Annex XI, copyright summary).&lt;/li>
&lt;/ul>
&lt;p>If the model has systemic risk (10^25 FLOPs threshold, or designated by the Commission), additional obligations apply (Art. 55): model evaluation, adversarial testing, incident reporting, cybersecurity.&lt;/p>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong> For an organisation that uses GPAI models (Llama 4, Mistral) and does not train them from scratch:&lt;/p>
&lt;ul>
&lt;li>It is not a GPAI provider; it is a downstream provider integrating GPAI into its system.&lt;/li>
&lt;li>It must hold the GPAI technical documentation (Llama paper, Mistral docs) and reference it in its own Annex IV documentation.&lt;/li>
&lt;li>Analysis of the specific GPAI licence (Llama Community License, Apache 2.0, and so on).&lt;/li>
&lt;li>The &lt;a href="https://blog.lo0.es/en/posts/modern-alignment-dpo-kto-orpo-simpo/">post on modern alignment&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/continuous-fine-tuning-production-real-traffic-deployed-adapter/">continuous fine-tuning&lt;/a> describe how a LoRA adapter on top of the GPAI does not turn the downstream party into a GPAI provider, as long as it does not train a new model from scratch.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist (downstream provider).&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Inventory of GPAI models used, with version, source, licence, referenced documentation.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> EU copyright compliance analysis (Art. 4 of Dir. 2019/790).&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Mapping of responsibilities between the upstream GPAI provider and us as downstream.&lt;/li>
&lt;/ul>
&lt;h3 id="art-72--post-market-monitoring">Art. 72 — Post-market monitoring&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> A documented post-market monitoring system proportionate to the risk, collecting data on performance throughout the lifecycle, including interaction with other AI systems. It allows the provider to:&lt;/p>
&lt;ul>
&lt;li>Assess continuous compliance with Arts. 8-15.&lt;/li>
&lt;li>Adopt the necessary corrective measures.&lt;/li>
&lt;li>Detect trends in real use (drift, abuse).&lt;/li>
&lt;/ul>
&lt;p>The monitoring plan is part of Annex IV and is maintained throughout the system&amp;rsquo;s life. The Commission publishes a template in 2026.&lt;/p>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">OTel tracing + Langfuse&lt;/a>, the base.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">Continuous evals&lt;/a>, operational metrics as online gates.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-as-judge-exam-marker-not-oracle/">LLM-as-judge&lt;/a>, judges over production sampling to detect degradation.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">Incident-driven retrain&lt;/a>, the closed improvement loop.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Post-market monitoring plan documented per system.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Operational metrics defined with thresholds and periodic review.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Corrective action procedure on alert.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Integration with Art. 73 (when an alert equals a serious incident).&lt;/li>
&lt;/ul>
&lt;h3 id="art-73--serious-incident-reporting">Art. 73 — Serious incident reporting&lt;/h3>
&lt;p>&lt;strong>What it requires.&lt;/strong> The definition of a serious incident (Art. 3(49)):&lt;/p>
&lt;ul>
&lt;li>Death or serious harm to health.&lt;/li>
&lt;li>Serious disruption of critical infrastructure.&lt;/li>
&lt;li>Infringement of legal obligations intended to protect fundamental rights.&lt;/li>
&lt;li>Serious damage to property or the environment.&lt;/li>
&lt;/ul>
&lt;p>The provider reports to the market surveillance authority of the Member State where the incident occurred. &lt;strong>Deadlines&lt;/strong>:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Type of incident&lt;/th>
&lt;th>Maximum deadline from becoming aware&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>General&lt;/td>
&lt;td>15 days&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Death&lt;/td>
&lt;td>10 days&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Critical infra affected, or widespread infringement&lt;/td>
&lt;td>2 days&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The first report may be preliminary; the complete one follows later. Internal investigation is mandatory, as is cooperation with the authorities. Corrective action is proportionate.&lt;/p>
&lt;p>&lt;strong>The blog&amp;rsquo;s stack.&lt;/strong> The technical flow:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">OTel tracing&lt;/a> provides the traceability for forensic investigation.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">Guardrails and safety&lt;/a> emits the canonical incident_event (category, severity, trace_id).&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">Incident-driven retrain&lt;/a> materialises the corrective action.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checklist.&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;input disabled="" type="checkbox"> Incident reporting procedure documented with deadlines and templates.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Designated person (typically DPO + AI Risk Owner) with responsibility and training.&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Annual dry-run of the procedure (drill).&lt;/li>
&lt;li>&lt;input disabled="" type="checkbox"> Technical integration between the guardrails / tracing layer and the notification channel.&lt;/li>
&lt;/ul>
&lt;h2 id="the-assembled-annex-iv-dossier-svg-of-the-complete-file">The assembled Annex IV dossier: SVG of the complete file&lt;/h2>
&lt;div class="diagram" style="max-width:820px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 820 480" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Annex IV dossier assembled over the blog's OSS stack">
&lt;style>
.x-sect{fill:#ffd76b;stroke:#444;stroke-width:1.4;rx:6}
.x-blog{fill:#a8e6a3;stroke:#444;stroke-width:1.4;rx:6}
.x-hdr{fill:#7aafff;stroke:#444;stroke-width:1.4;rx:8}
.x-out{fill:#ff8a4c;stroke:#444;stroke-width:1.4;rx:6}
.x-gap{fill:#f4b8b8;stroke:#444;stroke-width:1.4;rx:6}
.xl{font:600 12px sans-serif;fill:#222}
.xs{font:400 10px sans-serif;fill:#444}
.xn{font:italic 10px sans-serif;fill:#555}
&lt;/style>
&lt;rect x="20" y="20" width="780" height="40" class="x-hdr"/>
&lt;text x="410" y="42" text-anchor="middle" class="xl">Annex IV dossier (Art. 11) — nine mandatory sections + derived outputs&lt;/text>
&lt;text x="410" y="55" text-anchor="middle" class="xs">yellow = Annex IV section · green = blog artefact covering it · red = documentary gap&lt;/text>
&lt;rect x="20" y="80" width="200" height="40" class="x-sect"/>
&lt;text x="120" y="100" text-anchor="middle" class="xl">1. General description&lt;/text>
&lt;text x="120" y="114" text-anchor="middle" class="xs">purpose, version, hardware, deployer&lt;/text>
&lt;rect x="240" y="80" width="560" height="40" class="x-blog"/>
&lt;text x="520" y="100" text-anchor="middle" class="xl">Seven stack layers + seven deployment phases + request anatomy + OSS catalogue&lt;/text>
&lt;text x="520" y="114" text-anchor="middle" class="xs">complete technical description accessible to the authority&lt;/text>
&lt;rect x="20" y="130" width="200" height="40" class="x-sect"/>
&lt;text x="120" y="150" text-anchor="middle" class="xl">2. Design and development&lt;/text>
&lt;text x="120" y="164" text-anchor="middle" class="xs">architecture, base models, methods&lt;/text>
&lt;rect x="240" y="130" width="560" height="40" class="x-blog"/>
&lt;text x="520" y="150" text-anchor="middle" class="xl">Six-stage LLMOps pipeline + Continuous fine-tuning + Alignment + Multi-LoRA + Quantisation&lt;/text>
&lt;text x="520" y="164" text-anchor="middle" class="xs">architecture decisions with technical justification&lt;/text>
&lt;rect x="20" y="180" width="200" height="40" class="x-sect"/>
&lt;text x="120" y="200" text-anchor="middle" class="xl">3. Capabilities and limitations&lt;/text>
&lt;text x="120" y="214" text-anchor="middle" class="xs">accuracy, expected behaviour&lt;/text>
&lt;rect x="240" y="180" width="560" height="40" class="x-blog"/>
&lt;text x="520" y="200" text-anchor="middle" class="xl">Evals with golden sets + LLM-as-judge + F1 by category + groundedness + faithfulness&lt;/text>
&lt;text x="520" y="214" text-anchor="middle" class="xs">metrics declared and measured in CI&lt;/text>
&lt;rect x="20" y="230" width="200" height="40" class="x-sect"/>
&lt;text x="120" y="250" text-anchor="middle" class="xl">4. Data&lt;/text>
&lt;text x="120" y="264" text-anchor="middle" class="xs">datasets, sources, preparation, bias&lt;/text>
&lt;rect x="240" y="230" width="560" height="40" class="x-blog"/>
&lt;text x="520" y="250" text-anchor="middle" class="xl">Data versioning DVC + lakeFS + RAG corpus curation + Presidio + LLM Guard Vault&lt;/text>
&lt;text x="520" y="264" text-anchor="middle" class="xs">four versioned data artefacts with end-to-end lineage&lt;/text>
&lt;rect x="20" y="280" width="200" height="40" class="x-sect"/>
&lt;text x="120" y="300" text-anchor="middle" class="xl">5. Monitoring&lt;/text>
&lt;text x="120" y="314" text-anchor="middle" class="xs">metrics, traces, dashboards&lt;/text>
&lt;rect x="240" y="280" width="560" height="40" class="x-blog"/>
&lt;text x="520" y="300" text-anchor="middle" class="xl">OTel GenAI tracing + Langfuse + Prompt versioning + gen_ai.guardrail.* spans&lt;/text>
&lt;text x="520" y="314" text-anchor="middle" class="xs">per-request traceability with retention &amp;gt;= 6 months (WORM)&lt;/text>
&lt;rect x="20" y="330" width="200" height="40" class="x-sect"/>
&lt;text x="120" y="350" text-anchor="middle" class="xl">6. QMS (Art. 17)&lt;/text>
&lt;text x="120" y="364" text-anchor="middle" class="xs">quality management system procedures&lt;/text>
&lt;rect x="240" y="330" width="560" height="40" class="x-blog"/>
&lt;text x="520" y="350" text-anchor="middle" class="xl">ISO/IEC 42001 implemented (clauses 4-10) + LLMOps pipeline procedures&lt;/text>
&lt;text x="520" y="364" text-anchor="middle" class="xs">signed QMS manual + internal audit plan + management minutes&lt;/text>
&lt;rect x="20" y="380" width="200" height="40" class="x-gap"/>
&lt;text x="120" y="400" text-anchor="middle" class="xl">7. FRIA (Art. 27)&lt;/text>
&lt;text x="120" y="414" text-anchor="middle" class="xs">if public deployer / scoring applies&lt;/text>
&lt;rect x="240" y="380" width="560" height="40" class="x-gap"/>
&lt;text x="520" y="400" text-anchor="middle" class="xl">Dedicated FRIA procedure (input: A.5 ISO 42001 + fairness eval metrics)&lt;/text>
&lt;text x="520" y="414" text-anchor="middle" class="xs">governance document, not technical — it has to be written expressly&lt;/text>
&lt;rect x="20" y="430" width="200" height="40" class="x-sect"/>
&lt;text x="120" y="450" text-anchor="middle" class="xl">8. Logs (Art. 12)&lt;/text>
&lt;text x="120" y="464" text-anchor="middle" class="xs">automatic logs + retention&lt;/text>
&lt;rect x="240" y="430" width="560" height="40" class="x-blog"/>
&lt;text x="520" y="450" text-anchor="middle" class="xl">OTel tracing + Tempo / Jaeger + WORM storage + PII redaction policy&lt;/text>
&lt;text x="520" y="464" text-anchor="middle" class="xs">6+ month retention, WORM, PII redacted by LLM Guard Vault&lt;/text>
&lt;/svg>
&lt;/div>
&lt;p>The diagram shows the blog&amp;rsquo;s editorial asymmetry: seven of the nine Annex IV sections are covered directly by the technical stack. Only the FRIA (section 7) and the signed declaration of conformity (section 9, not shown in the SVG because it is a one-page document) are documentary gaps needing express administrative work. The main compliance effort is assembling and signing, not building from scratch.&lt;/p>
&lt;h2 id="applied-case-the-multi-tenant-chatbot-assessed-against-the-ai-act">Applied case: the multi-tenant chatbot assessed against the AI Act&lt;/h2>
&lt;p>We take the system from the &lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">forensic post&lt;/a>, a multi-tenant customer support chatbot for insurers, and walk through it as the provider of the system.&lt;/p>
&lt;p>Art. 6 classification: the chatbot helps customers understand products, check status and open tickets. It does not automate contractual decisions (it does not approve claims, it does not calculate premiums). Does it fall into Annex III area 8 (essential private services)? That depends on the use. If the insurer uses it only for informational support, it is limited risk (Art. 50 applies). If it uses it to assess claim declarations, it is high risk. Documenting the analysis is mandatory.&lt;/p>
&lt;p>We assume high risk for the complete walkthrough:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Art. 5 prohibitions&lt;/strong>: written statement of non-applicability. ✓&lt;/li>
&lt;li>&lt;strong>Art. 9 risk management&lt;/strong>: per-system document with identified risks (hallucination, dialect bias, PII leakage, jailbreak), applied mitigations (RAG over a curated corpus, guardrails with 4 lines, LLM Guard Vault, continuous evals) and signed accepted residuals. ✓ (input: pipeline + guardrails + evals).&lt;/li>
&lt;li>&lt;strong>Art. 10 data governance&lt;/strong>: governance of the four datasets (training adapter, insurer RAG corpus, golden eval, enriched retrain) with biases analysed, PII anonymised, lineage in place. ✓ (input: data-versioning + rag-corpus-curation + LLM Guard).&lt;/li>
&lt;li>&lt;strong>Art. 11 + Annex IV&lt;/strong>: dossier with 9 sections drafted, signed, accessible for 10 years in a WORM bucket. ✓ (7 sections from the blog, 2 new).&lt;/li>
&lt;li>&lt;strong>Art. 12 + Art. 19 logs&lt;/strong>: OTel + Tempo + Langfuse with 24-month retention, PII redacted by Vault. ✓&lt;/li>
&lt;li>&lt;strong>Art. 13 transparency to deployers&lt;/strong>: user manual for the insurer plus a technical manual. ✓ (input: OSS catalogue + request anatomy).&lt;/li>
&lt;li>&lt;strong>Art. 14 human oversight&lt;/strong>: Langfuse + Grafana dashboard, human escalation protocol for critical cases, training for the insurer&amp;rsquo;s staff. ✓&lt;/li>
&lt;li>&lt;strong>Art. 15 accuracy, robustness, cybersecurity&lt;/strong>: F1 by category declared, Promptfoo redteam adversarial suite run monthly, stack cybersecurity plan (vLLM patching, secrets rotation). ✓&lt;/li>
&lt;li>&lt;strong>Art. 17 QMS&lt;/strong>: ISO 42001 implemented and certified. ✓&lt;/li>
&lt;li>&lt;strong>Art. 26 deployer&lt;/strong>: the contract with the insurer includes the deployer&amp;rsquo;s obligations. ✓&lt;/li>
&lt;li>&lt;strong>Art. 27 FRIA&lt;/strong>: the insurer carries out a FRIA before first use (it is a private essential-service entity). ✓ (deployer&amp;rsquo;s responsibility, the provider assists).&lt;/li>
&lt;li>&lt;strong>Art. 47 declaration of conformity&lt;/strong>: signed by the consultancy&amp;rsquo;s CTO before market placement. ✓&lt;/li>
&lt;li>&lt;strong>Art. 48 CE marking&lt;/strong>: visible in the chatbot interface and in the official documentation. ✓&lt;/li>
&lt;li>&lt;strong>Art. 49 EU DB registration&lt;/strong>: completed before first productive use. ✓&lt;/li>
&lt;li>&lt;strong>Art. 50 transparency to users&lt;/strong>: UI banner &amp;ldquo;You are talking to an AI assistant&amp;rdquo;, disclaimer on exportable responses. ✓&lt;/li>
&lt;li>&lt;strong>Art. 53 GPAI&lt;/strong>: Llama 4 documentation (the base model) referenced, plus EU copyright analysis and responsibility mapping. ✓&lt;/li>
&lt;li>&lt;strong>Art. 72 post-market monitoring&lt;/strong>: documented plan, OTel + continuous evals + retrain already running. ✓&lt;/li>
&lt;li>&lt;strong>Art. 73 incident reporting&lt;/strong>: procedure, designated owner, annual dry-run. ✓&lt;/li>
&lt;/ul>
&lt;p>Result: certifiable and deployable on the EU market on 2 August 2026. The key gaps (FRIA, CE marking, EU DB registration, declaration of conformity) are documentary work over technical artefacts that already exist, not new technical projects.&lt;/p>
&lt;h2 id="penalties-art-99">Penalties (Art. 99)&lt;/h2>
&lt;p>The penalty table is proportional to worldwide turnover or to an absolute cap, whichever is higher:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Violation&lt;/th>
&lt;th>Penalty cap&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Art. 5 (prohibited practices)&lt;/td>
&lt;td>Up to 35 M€ or 7% of annual worldwide turnover&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Other obligations (Arts. 8-22, 26-50, 72-73, and so on)&lt;/td>
&lt;td>Up to 15 M€ or 3% of annual worldwide turnover&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Incorrect or misleading information to authorities&lt;/td>
&lt;td>Up to 7.5 M€ or 1% of annual worldwide turnover&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>For SMEs and startups the caps are the lower of the two figures (not the higher). The proportionality mitigation exists but requires formal demonstration.&lt;/p>
&lt;p>In addition, national authorities can order:&lt;/p>
&lt;ul>
&lt;li>Immediate suspension of the system on the market.&lt;/li>
&lt;li>A mandatory recall.&lt;/li>
&lt;li>Mandatory public communication of the penalty.&lt;/li>
&lt;/ul>
&lt;h2 id="the-five-frequent-compliance-traps">The five frequent compliance traps&lt;/h2>
&lt;p>&lt;strong>Trap 1 — Assuming the GPAI model used already covers the obligations.&lt;/strong> The downstream provider remains responsible for the integrated system: neither Meta nor Mistral nor DeepSeek takes on the obligations of whoever builds on their models. The trap surfaces when the authority asks for the dossier and the team points at the Llama model card as if that were enough.&lt;/p>
&lt;p>&lt;strong>Trap 2 — Confusing ISO 42001 with EU AI Act conformity.&lt;/strong> Holding a 42001 certificate &lt;strong>does not imply&lt;/strong> conformity: certification is not a FRIA, not CE marking, not EU database registration, not a signed declaration of conformity. Standardisation is progressing, but until ISO 42001 is published as a harmonised standard (it was not at the end of 2025), there is no presumption of conformity. The table of legal obligations runs on its own circuit.&lt;/p>
&lt;p>&lt;strong>Trap 3 — Forgetting incident reporting within the legal deadline.&lt;/strong> Fifteen days sounds long until an incident lands in the middle of the summer holidays. Two days for critical infrastructure is not negotiable. Without a documented procedure and an annual drill, the deadline is missed in silence. A penalty for misleading information (1% of worldwide turnover) follows if the incomplete reporting is discovered.&lt;/p>
&lt;p>&lt;strong>Trap 4 — Underestimating the deployer.&lt;/strong> The AI Act assigns obligations to both the provider and the &lt;strong>deployer&lt;/strong>. A company that &lt;strong>uses&lt;/strong> a hosted LLM integrated into its service (without developing it) is still a deployer with obligations of its own (Art. 26): human oversight, FRIA where applicable, notification to affected persons. The trap surfaces when the deployer assumes that &amp;ldquo;responsibility lies with the model provider&amp;rdquo;. It does not, not entirely.&lt;/p>
&lt;p>&lt;strong>Trap 5 — Leaving the FRIA until last.&lt;/strong> The FRIA (Art. 27) requires a fundamental rights impact analysis with qualitative dimensions (discrimination, privacy, social rights). It is not an afternoon&amp;rsquo;s document. It is carried out before first use, not after a problem is detected. Public deployers and private essential-service providers who leave it for &amp;ldquo;whenever they ask&amp;rdquo; take 4-8 weeks to produce a credible one, time they typically do not have when the inspection arrives.&lt;/p>
&lt;h2 id="what-we-have-not-covered-upcoming-posts">What we have not covered (upcoming posts)&lt;/h2>
&lt;ul>
&lt;li>Concrete templates for each document: EU declaration of conformity (Annex V), full Annex IV dossier section by section, FRIA, post-market monitoring plan, initial serious incident report. Material for a post along the lines of &amp;ldquo;The EU AI Act compliance folder in 12 templates&amp;rdquo;.&lt;/li>
&lt;li>Voluntary codes of practice published by the Commission under Art. 56, useful for limited-risk systems that want to demonstrate good behaviour without a legal obligation.&lt;/li>
&lt;li>Comparative analysis of notified bodies for systems requiring third-party conformity assessment (mainly Annex I).&lt;/li>
&lt;li>How compliance changes for LLM agents, systems with graduated autonomy, tool calling and the capacity to act. SC 42 and the AI Office are working on specific guidance.&lt;/li>
&lt;li>The complete map of national authorities designated under Art. 70, plus the sectoral authorities that retain jurisdiction (AEPD for privacy, CNMV for financial services, Banco de España for banking, AESA for aviation).&lt;/li>
&lt;/ul>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>Regulation (EU) 2024/1689 (EU AI Act) — Texto consolidado en EUR-Lex: &lt;a href="https://eur-lex.europa.eu/eli/reg/2024/1689">https://eur-lex.europa.eu/eli/reg/2024/1689&lt;/a>. Diario Oficial L 1689/12.7.2024.&lt;/li>
&lt;li>EU AI Act Explorer (AI Act Service Desk, Comisión Europea): &lt;a href="https://ai-act-service-desk.ec.europa.eu/en/ai-act-explorer">https://ai-act-service-desk.ec.europa.eu/en/ai-act-explorer&lt;/a>.&lt;/li>
&lt;li>AI Act Text portal (artificialintelligenceact.eu): artículos individuales con anotaciones.&lt;/li>
&lt;li>Anexo IV — Technical documentation: estructura de los nueve apartados obligatorios.&lt;/li>
&lt;li>Anexo V — EU declaration of conformity: plantilla obligatoria.&lt;/li>
&lt;li>Anexo III — High-risk AI systems: las ocho áreas que clasifican un sistema como alto riesgo.&lt;/li>
&lt;li>Anexo XI — Copyright training summary template: para GPAI providers.&lt;/li>
&lt;li>NIST AI RMF 1.0 (2023) — &lt;a href="https://www.nist.gov/itl/ai-risk-management-framework">https://www.nist.gov/itl/ai-risk-management-framework&lt;/a>.&lt;/li>
&lt;li>ISO/IEC 42001:2023 — sistema de gestión, complemento facilitador.&lt;/li>
&lt;li>ISO/IEC 42005 — Impact assessment AI (publicada 2025 como guía técnica para FRIA).&lt;/li>
&lt;li>Draft Commission guidance on serious incident reporting (2025) — borrador en consulta para Art. 73.&lt;/li>
&lt;/ul>
&lt;h2 id="see-also">See also&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/hardening-secrets-sovereign-llm-stack-defence-in-depth/">Hardening and secrets in the sovereign LLM stack: defence in depth&lt;/a> — the technical hardening measures that back the dossier.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/iso-42001-aims-on-premise-llm-platform/">ISO/IEC 42001: the operations manual for the AI system&lt;/a> — the sister post on the management system that makes Arts. 9, 10, 11 and 17 of the Regulation easier to demonstrate.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">The six-stage LLMOps pipeline&lt;/a> — the lifecycle that operationally materialises Arts. 9 (risk management) and 17 (QMS).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">Anatomy of an LLM request in production&lt;/a> — the forensic case used as a compliance checklist in this post.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">Data versioning with DVC and lakeFS&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/rag-corpus-curation-active-librarian/">RAG corpus curation&lt;/a> — Art. 10 data governance.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/llm-tracing-opentelemetry-genai/">LLM tracing with OpenTelemetry GenAI&lt;/a> — Arts. 12 + 19 (record-keeping) with canonical OTel.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">Guardrails and safety in LLMs&lt;/a> — Arts. 14 (human oversight) and 15 (cybersecurity/robustness).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/llm-guard-anatomy-scanners-langfuse-vllm-litellm/">LLM Guard: the sworn translator with a book of equivalences&lt;/a> — Art. 10 + Art. 12 with PII redacted on the runtime path.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">Evals: the layer after tracing&lt;/a> — Art. 15 (accuracy) and Art. 72 (post-market monitoring).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">Retrain: closing the feedback → dataset → adapter loop&lt;/a> — Art. 72 continuous improvement + Art. 73 corrective action after an incident.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/parallel-catalogue-llmops-stages-open-source-hyperscalers/">The parallel catalogue: OSS vs hyperscalers&lt;/a> — Art. 53 (GPAI obligations) and analysis of integrated GPAI providers.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/oss-catalogue-llmops-six-stages-tool-by-tool/">The OSS catalogue for LLMOps&lt;/a> — Art. 13 (transparency to deployers) with a complete component inventory.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/modern-alignment-dpo-kto-orpo-simpo/">Modern alignment: DPO, KTO, ORPO and SimPO&lt;/a> — responsible adapter design (Art. 9 mitigation + Art. 15 robustness).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/technical-controls-ens-iso-42001-eu-ai-act-cross-mapping/">Technical controls: the cross-mapping of ENS × ISO 42001 × EU AI Act&lt;/a> — the technical zoom into the overlap of the three frameworks. A single tracing piece, a single guardrails piece and a single versioning piece materialise the technical requirements of ENS + 42001 + AI Act simultaneously when labelled with a common vocabulary.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a href="https://blog.lo0.es/en/posts/incident-response-runbooks-llm-inference-kafka-keep/">Incident response runbooks for LLMs with Keep + Kafka&lt;/a> — the operationalisation of Art. 73 (reporting of serious incidents) with 2/10/15-day deadlines by severity, Keep YAML workflows and the WORM Kafka topic &lt;code>audit.actions&lt;/code> as evidence for the competent authority.&lt;/p>
&lt;/li>
&lt;/ul></description></item><item><title>The parallel catalogue: the six LLMOps stages in open source and on the hyperscalers (AWS, GCP, Azure)</title><link>https://blog.lo0.es/en/posts/parallel-catalogue-llmops-stages-open-source-hyperscalers/</link><pubDate>Sat, 23 May 2026 07:00:00 +0200</pubDate><guid>https://blog.lo0.es/en/posts/parallel-catalogue-llmops-stages-open-source-hyperscalers/</guid><description>&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>The &lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">previous forensic post&lt;/a> used a single request to walk the six stages of the LLMOps pipeline and the two cross-cutting components. This post walks the same stages but crosses them with three extra columns: how each stage is built in &lt;strong>open source on-premise&lt;/strong>, and what the equivalent services are on &lt;strong>AWS&lt;/strong>, &lt;strong>GCP&lt;/strong> and &lt;strong>Azure&lt;/strong>. It is not a migration guide nor a cost benchmark: it is a &lt;strong>catalogue of equivalences&lt;/strong> with its gaps. The general pattern you will see: OSS gives you control, sovereignty and free composition in exchange for expensive operations; the hyperscalers give you integration and time-to-market in exchange for lock-in on margins, data contracts and political dependency. For scenarios subject to ENS / NIS2 with critical customer data, OSS wins by default; for discovery projects where time-to-market is the deciding metric, the hyperscaler wins by default. The interesting part is in the middle. As a concrete thread, at the end we take the multi-tenant chatbot from the previous post and port it to AWS piece by piece to show what disappears, what appears, and where the lock-in materialises.&lt;/p>
&lt;h2 id="you-are-here-the-same-six-stages-but-by-column">You are here: the same six stages, but by column&lt;/h2>
&lt;p>This post shares a map with the previous one, the six stages and the two cross-cutting pieces are all active, but it changes the cut: instead of following a request horizontally, it makes the vertical cut and shows which tools live in each stage depending on the deployment model.&lt;/p>
&lt;div class="diagram" style="max-width:840px;margin:1rem auto;">
&lt;svg viewBox="0 0 840 310" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="the six LLMOps stages with their OSS and hyperscaler equivalents">
&lt;style>.box{stroke:#444;stroke-width:1.4;rx:6}.active{fill:#ff8a4c;stroke-width:2}.cross{fill:#ffe9d6;stroke:#c66;stroke-width:1.4;rx:6}.lbl{font:600 12px sans-serif;fill:#222}.sm{font:10px sans-serif;fill:#333}.tiny{font:9px sans-serif;fill:#222}.row{font:600 10px sans-serif;fill:#333}.oss{fill:#dfe9f5;stroke:#356}.aws{fill:#fde6c8;stroke:#a65}.gcp{fill:#dceaf8;stroke:#369}.azu{fill:#dde2f8;stroke:#447}.foot{font:9px sans-serif;fill:#666}&lt;/style>
&lt;text x="420" y="20" text-anchor="middle" class="lbl">Parallel catalogue: open source on-premise vs managed hyperscalers&lt;/text>
&lt;rect x="60" y="35" width="125" height="30" class="box active"/>&lt;text x="122.5" y="54" text-anchor="middle" class="lbl">1 · Data&lt;/text>
&lt;rect x="190" y="35" width="125" height="30" class="box active"/>&lt;text x="252.5" y="54" text-anchor="middle" class="lbl">2 · Tune&lt;/text>
&lt;rect x="320" y="35" width="125" height="30" class="box active"/>&lt;text x="382.5" y="54" text-anchor="middle" class="lbl">3 · Eval&lt;/text>
&lt;rect x="450" y="35" width="125" height="30" class="box active"/>&lt;text x="512.5" y="54" text-anchor="middle" class="lbl">4 · Deploy&lt;/text>
&lt;rect x="580" y="35" width="125" height="30" class="box active"/>&lt;text x="642.5" y="54" text-anchor="middle" class="lbl">5 · Observe&lt;/text>
&lt;rect x="710" y="35" width="125" height="30" class="box active"/>&lt;text x="772.5" y="54" text-anchor="middle" class="lbl">6 · Retrain&lt;/text>
&lt;text x="52" y="98" text-anchor="end" class="row">OSS&lt;/text>
&lt;rect x="60" y="80" width="125" height="30" class="box oss"/>&lt;text x="122.5" y="99" text-anchor="middle" class="tiny">DVC · lakeFS · Qdrant&lt;/text>
&lt;rect x="190" y="80" width="125" height="30" class="box oss"/>&lt;text x="252.5" y="99" text-anchor="middle" class="tiny">PEFT · MLflow · Ray&lt;/text>
&lt;rect x="320" y="80" width="125" height="30" class="box oss"/>&lt;text x="382.5" y="99" text-anchor="middle" class="tiny">DeepEval · Promptfoo&lt;/text>
&lt;rect x="450" y="80" width="125" height="30" class="box oss"/>&lt;text x="512.5" y="99" text-anchor="middle" class="tiny">vLLM · KServe · Operators&lt;/text>
&lt;rect x="580" y="80" width="125" height="30" class="box oss"/>&lt;text x="642.5" y="99" text-anchor="middle" class="tiny">OTel · Tempo · Langfuse&lt;/text>
&lt;rect x="710" y="80" width="125" height="30" class="box oss"/>&lt;text x="772.5" y="99" text-anchor="middle" class="tiny">Airflow · Argo · Kubeflow&lt;/text>
&lt;text x="52" y="138" text-anchor="end" class="row">AWS&lt;/text>
&lt;rect x="60" y="120" width="125" height="30" class="box aws"/>&lt;text x="122.5" y="139" text-anchor="middle" class="tiny">S3 · OpenSearch · MSK&lt;/text>
&lt;rect x="190" y="120" width="125" height="30" class="box aws"/>&lt;text x="252.5" y="139" text-anchor="middle" class="tiny">SageMaker · Bedrock&lt;/text>
&lt;rect x="320" y="120" width="125" height="30" class="box aws"/>&lt;text x="382.5" y="139" text-anchor="middle" class="tiny">Bedrock Eval · Guardrails&lt;/text>
&lt;rect x="450" y="120" width="125" height="30" class="box aws"/>&lt;text x="512.5" y="139" text-anchor="middle" class="tiny">Bedrock · SM Endpoints&lt;/text>
&lt;rect x="580" y="120" width="125" height="30" class="box aws"/>&lt;text x="642.5" y="139" text-anchor="middle" class="tiny">CloudWatch · X-Ray · ADOT&lt;/text>
&lt;rect x="710" y="120" width="125" height="30" class="box aws"/>&lt;text x="772.5" y="139" text-anchor="middle" class="tiny">SM Pipelines · GT&lt;/text>
&lt;text x="52" y="178" text-anchor="end" class="row">GCP&lt;/text>
&lt;rect x="60" y="160" width="125" height="30" class="box gcp"/>&lt;text x="122.5" y="179" text-anchor="middle" class="tiny">GCS · BQ · Vertex VS&lt;/text>
&lt;rect x="190" y="160" width="125" height="30" class="box gcp"/>&lt;text x="252.5" y="179" text-anchor="middle" class="tiny">Vertex Training · Tuning&lt;/text>
&lt;rect x="320" y="160" width="125" height="30" class="box gcp"/>&lt;text x="382.5" y="179" text-anchor="middle" class="tiny">Vertex Eval · Model Armor&lt;/text>
&lt;rect x="450" y="160" width="125" height="30" class="box gcp"/>&lt;text x="512.5" y="179" text-anchor="middle" class="tiny">Vertex Pred · Gemini API&lt;/text>
&lt;rect x="580" y="160" width="125" height="30" class="box gcp"/>&lt;text x="642.5" y="179" text-anchor="middle" class="tiny">Cloud Trace · Monitoring&lt;/text>
&lt;rect x="710" y="160" width="125" height="30" class="box gcp"/>&lt;text x="772.5" y="179" text-anchor="middle" class="tiny">Vertex Pipelines&lt;/text>
&lt;text x="52" y="218" text-anchor="end" class="row">Azure&lt;/text>
&lt;rect x="60" y="200" width="125" height="30" class="box azu"/>&lt;text x="122.5" y="219" text-anchor="middle" class="tiny">ADLS · AI Search · ADF&lt;/text>
&lt;rect x="190" y="200" width="125" height="30" class="box azu"/>&lt;text x="252.5" y="219" text-anchor="middle" class="tiny">Azure ML · AOAI tuning&lt;/text>
&lt;rect x="320" y="200" width="125" height="30" class="box azu"/>&lt;text x="382.5" y="219" text-anchor="middle" class="tiny">AI Eval · Content Safety&lt;/text>
&lt;rect x="450" y="200" width="125" height="30" class="box azu"/>&lt;text x="512.5" y="219" text-anchor="middle" class="tiny">AOAI · ML Endpoints&lt;/text>
&lt;rect x="580" y="200" width="125" height="30" class="box azu"/>&lt;text x="642.5" y="219" text-anchor="middle" class="tiny">App Insights · Monitor&lt;/text>
&lt;rect x="710" y="200" width="125" height="30" class="box azu"/>&lt;text x="772.5" y="219" text-anchor="middle" class="tiny">Azure ML Pipelines&lt;/text>
&lt;rect x="60" y="245" width="385" height="26" class="cross"/>
&lt;text x="252.5" y="262" text-anchor="middle" class="sm">Prompt versioning: Langfuse · MLflow ↔ Bedrock · Vertex · Foundry&lt;/text>
&lt;rect x="450" y="245" width="385" height="26" class="cross"/>
&lt;text x="642.5" y="262" text-anchor="middle" class="sm">Data versioning: DVC · lakeFS · OpenLineage ↔ S3 · Dataplex · Purview&lt;/text>
&lt;text x="420" y="295" text-anchor="middle" class="foot">The OSS stack runs on-premise; AWS / GCP / Azure show the managed equivalents per stage.&lt;/text>
&lt;/svg>
&lt;/div>
&lt;h2 id="the-analogy-your-own-bakery-and-the-franchise">The analogy: your own bakery and the franchise&lt;/h2>
&lt;p>A baker opens a business. There are two possible models.&lt;/p>
&lt;p>He can open &lt;strong>his own bakery&lt;/strong>: rent the premises, buy the oven, choose the flour suppliers, hire his master baker, write his recipes, set the prices, dress the shop window. The day he wants to launch an organic rye sourdough loaf, he asks nobody for permission. The day the price of flour goes up, he finds another supplier. But he pays for all of it: the initial investment, the risk, the daily operations, the months when he misreads the neighbourhood. The bakery is his.&lt;/p>
&lt;p>Or he can join a &lt;strong>franchise&lt;/strong>: the franchisor hands over the turnkey premises, the oven with a maintenance contract, the suppliers already negotiated, the operating manuals, the written recipes, the centralised marketing, the loyalty app, the POS system. The learning curve is weeks, not years. But the recipes belong to the franchisor, so do the suppliers, the price of bread is in the catalogue, and the day the croissant formula changes he gets an informational email, not a business decision.&lt;/p>
&lt;p>Both bakeries turn out bread. Both meet health regulations and generate income. The operational difference is enormous and it is not about technology: it is about &lt;strong>ownership, control and timescale&lt;/strong>.&lt;/p>
&lt;p>The parallel with LLMOps is direct. The on-premise OSS stack is the bakery you own. The managed stack on the hyperscalers is the franchise. The &lt;strong>pieces&lt;/strong> that appear in each stage are functionally equivalent, at the end of the day both solve the same technical problem, but the governance model, the operational cost, the lock-in and the compliance guarantees are different. This post builds the parallel catalogue so that the choice is not made by default.&lt;/p>
&lt;h2 id="quick-recap-of-the-previous-post">Quick recap of the previous post&lt;/h2>
&lt;p>In the &lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">forensic post&lt;/a> we followed one specific request: a premium-es user of an insurance company asking &lt;em>&amp;ldquo;How do I cancel my premium subscription?&amp;rdquo;&lt;/em> to the multi-tenant support chatbot of the SaaS provider that hosts it. The route crossed the &lt;strong>six stages of the LLMOps pipeline&lt;/strong>, Data, Tune, Eval, Deploy, Observe, Retrain, plus the &lt;strong>two cross-cutting components&lt;/strong>, prompt versioning and data versioning, over an on-premise infrastructure: RKE2 with Cilium BGP, a 4×H100 SXM cluster, an RTX 4090 for development, vLLM on Kubernetes, Langfuse + OTel + Prometheus + Tempo, Postgres + Qdrant, DVC + lakeFS + MinIO, Kafka and MLflow. The system meets ENS / NIS2 and keeps &lt;code>trace_id&lt;/code> propagated end to end.&lt;/p>
&lt;p>What comes now is that same system, piece by piece, showing for each box which tool does the job if you are on public cloud, because the integrator&amp;rsquo;s question is rarely &amp;ldquo;OSS yes or no?&amp;rdquo;: it is &amp;ldquo;what do I lose and what do I gain if I take this box managed?&amp;rdquo;. And the answer differs box by box.&lt;/p>
&lt;h2 id="stage-1--data">Stage 1 — Data&lt;/h2>
&lt;p>&lt;strong>The problem.&lt;/strong> There are three sub-problems the Data stage solves, frequently confused with one another. First, &lt;strong>versioning and identity&lt;/strong> of the corpus and of the training datasets (making sure a &lt;code>dataset_id, dataset_version&lt;/code> exists and propagates). Second, &lt;strong>storage and serving&lt;/strong> of the operational corpus (object store + vector index + structured text). Third, &lt;strong>streams and ingestion&lt;/strong> from source systems with CDC, transformation and stable schemas (Schema Registry).&lt;/p>
&lt;p>&lt;strong>Reference OSS stack.&lt;/strong> Versioning lives in &lt;strong>DVC&lt;/strong> (pointers in git, content in an object store) combined with &lt;strong>lakeFS&lt;/strong> for branch/merge semantics over data. The &lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">post on data versioning&lt;/a> goes into the functional difference. The object store is &lt;strong>MinIO&lt;/strong> or &lt;strong>Ceph&lt;/strong>. The vector index is &lt;strong>Qdrant&lt;/strong> or &lt;strong>Milvus&lt;/strong> for large corpora (millions of chunks) and &lt;strong>pgvector on Postgres 18&lt;/strong> for small cases where having one component fewer pays off operationally. The stream layer is &lt;strong>Kafka&lt;/strong> (pure Apache or &lt;strong>Redpanda&lt;/strong>) with a &lt;strong>Schema Registry&lt;/strong> (Confluent or &lt;strong>Karapace&lt;/strong> OSS), CDC with &lt;strong>Debezium&lt;/strong> or &lt;strong>Flink CDC&lt;/strong>, transformation with &lt;strong>Flink&lt;/strong> or &lt;strong>Spark Structured Streaming&lt;/strong>. The catalogue / lineage is &lt;strong>DataHub&lt;/strong>, &lt;strong>Apache Atlas&lt;/strong> or &lt;strong>OpenMetadata&lt;/strong> with &lt;strong>OpenLineage&lt;/strong> events between systems. The &lt;a href="https://blog.lo0.es/en/posts/postgresql-qdrant-rag-ingestion-postman-syncing-two-worlds/">post on PostgreSQL + Qdrant ingestion&lt;/a> and the &lt;a href="https://blog.lo0.es/en/posts/rag-on-kafka-reference-architecture-streaming-datalakes/">post on RAG over Kafka&lt;/a> cover the detailed operations.&lt;/p>
&lt;p>&lt;strong>Hyperscaler equivalents.&lt;/strong> On &lt;strong>AWS&lt;/strong>, the corpus lives in &lt;strong>S3&lt;/strong> (with versioning enabled, which is the cheap substitute for serious data versioning), tabular queries in &lt;strong>Athena&lt;/strong> or &lt;strong>Redshift&lt;/strong>, the vector index in &lt;strong>Amazon OpenSearch&lt;/strong> with the vector plug-in or in &lt;strong>Amazon Aurora pgvector&lt;/strong>. The stream layer is &lt;strong>MSK&lt;/strong> (managed Kafka) or &lt;strong>Kinesis Data Streams&lt;/strong>, CDC with &lt;strong>AWS DMS&lt;/strong>, transformation with &lt;strong>Glue Streaming&lt;/strong> or &lt;strong>MSK Connect&lt;/strong>. The catalogue is &lt;strong>AWS Glue Data Catalog&lt;/strong> + &lt;strong>AWS Lake Formation&lt;/strong> for data governance. And for the RAG case there is also &lt;strong>Amazon Bedrock Knowledge Bases&lt;/strong>, the managed shortcut: you give it S3, it indexes into OpenSearch or Aurora pgvector, exposes a retrieval API and the operations end there, in exchange for paying per indexed chunk and per query.&lt;/p>
&lt;p>On &lt;strong>GCP&lt;/strong>, the corpus lives in &lt;strong>Cloud Storage&lt;/strong> (with object versioning), the analytical warehouse is &lt;strong>BigQuery&lt;/strong> (with &lt;strong>BigQuery Vector Search&lt;/strong> already integrated), the dedicated vector store is &lt;strong>Vertex AI Vector Search&lt;/strong> (formerly Matching Engine). The stream layer is &lt;strong>Pub/Sub&lt;/strong> + &lt;strong>Dataflow&lt;/strong>, CDC with &lt;strong>Datastream&lt;/strong>. Catalogue and lineage are &lt;strong>Dataplex&lt;/strong> (which in 2024-2025 absorbed Data Catalog and added automatic lineage). The managed equivalent of Knowledge Bases is &lt;strong>Vertex AI Search&lt;/strong> (formerly Discovery Engine).&lt;/p>
&lt;p>On &lt;strong>Azure&lt;/strong>, the corpus lives in &lt;strong>ADLS Gen2&lt;/strong>, tabular queries in &lt;strong>Microsoft Fabric&lt;/strong> / &lt;strong>Azure Synapse&lt;/strong>, the vector index in &lt;strong>Azure AI Search&lt;/strong> (vector mode) or &lt;strong>Azure Cosmos DB for PostgreSQL&lt;/strong> with pgvector. The stream layer is &lt;strong>Event Hubs&lt;/strong> + &lt;strong>Stream Analytics&lt;/strong> or &lt;strong>Microsoft Fabric Real-Time Intelligence&lt;/strong>, CDC with &lt;strong>Azure Data Factory&lt;/strong>. The catalogue is &lt;strong>Microsoft Purview&lt;/strong>, which covers catalogue, lineage and data governance integrated with Entra ID.&lt;/p>
&lt;p>&lt;strong>Summary table — Data stage.&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Functional piece&lt;/th>
&lt;th>OSS on-premise&lt;/th>
&lt;th>AWS&lt;/th>
&lt;th>GCP&lt;/th>
&lt;th>Azure&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Object store&lt;/td>
&lt;td>MinIO, Ceph&lt;/td>
&lt;td>S3&lt;/td>
&lt;td>Cloud Storage&lt;/td>
&lt;td>ADLS Gen2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Dataset versioning&lt;/td>
&lt;td>DVC, lakeFS&lt;/td>
&lt;td>S3 Versioning (limited), Lake Formation&lt;/td>
&lt;td>GCS Versioning, Dataplex&lt;/td>
&lt;td>ADLS versioning, Purview&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Vector index&lt;/td>
&lt;td>Qdrant, Milvus, pgvector&lt;/td>
&lt;td>OpenSearch, Aurora pgvector, Bedrock KB&lt;/td>
&lt;td>Vertex Vector Search, BigQuery VS&lt;/td>
&lt;td>Azure AI Search, Cosmos pgvector&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Stream + CDC&lt;/td>
&lt;td>Kafka + Debezium + Flink&lt;/td>
&lt;td>MSK / Kinesis + DMS + Glue&lt;/td>
&lt;td>Pub/Sub + Datastream + Dataflow&lt;/td>
&lt;td>Event Hubs + ADF&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Schema Registry&lt;/td>
&lt;td>Karapace, Confluent OSS&lt;/td>
&lt;td>Glue Schema Registry&lt;/td>
&lt;td>Pub/Sub schemas&lt;/td>
&lt;td>Schema Registry (Event Hubs)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Catalogue + lineage&lt;/td>
&lt;td>DataHub, Atlas, OpenLineage&lt;/td>
&lt;td>Glue Catalog + Lake Formation&lt;/td>
&lt;td>Dataplex&lt;/td>
&lt;td>Purview&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Managed end-to-end RAG&lt;/td>
&lt;td>— (you build it)&lt;/td>
&lt;td>Bedrock Knowledge Bases&lt;/td>
&lt;td>Vertex AI Search&lt;/td>
&lt;td>Azure AI Studio Knowledge&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Where the names mislead.&lt;/strong> &lt;em>S3 Versioning&lt;/em> is not DVC. It keeps object versions but has no notion of a &lt;strong>dataset&lt;/strong> (which objects together make up version 3 of the enriched set?), does not propagate &lt;code>dataset_hash&lt;/code> to the trainer, does not integrate with experiment tracking, and does not fail a CI run if a dataset breaks schema. Covering that properly on AWS requires combining it with Lake Formation, Glue Data Catalog and your own records in SageMaker Experiments. The same applies on GCP with Dataplex and on Azure with Purview. The gap is real and is paid for in operations or in broken lineage.&lt;/p>
&lt;h2 id="stage-2--tune">Stage 2 — Tune&lt;/h2>
&lt;p>&lt;strong>The problem.&lt;/strong> Producing a new &lt;code>model_id, model_version&lt;/code>, typically a LoRA adapter over a stable base, as the &lt;a href="https://blog.lo0.es/en/posts/continuous-fine-tuning-production-real-traffic-deployed-adapter/">post on continuous fine-tuning&lt;/a> explains, with lineage back to the dataset that trained it and experiment tracking that lets you reproduce it six months later.&lt;/p>
&lt;p>&lt;strong>OSS stack.&lt;/strong> Technical core: &lt;strong>HuggingFace Transformers + PEFT&lt;/strong> (LoRA, QLoRA), &lt;strong>bitsandbytes&lt;/strong> for quantisation, &lt;strong>DeepSpeed&lt;/strong> or &lt;strong>FSDP&lt;/strong> for parallelism. Experiment tracking: &lt;strong>MLflow&lt;/strong> (authoritative) or &lt;strong>Weights &amp;amp; Biases self-hosted&lt;/strong>. Convenience frameworks: &lt;strong>Axolotl&lt;/strong> and &lt;strong>Llama Factory&lt;/strong> wrap the machinery above in declarative configuration. Distributed orchestration: &lt;strong>Kubeflow Training Operator&lt;/strong> or &lt;strong>Ray Train&lt;/strong>. On small infrastructures, direct scripts with &lt;strong>Slurm&lt;/strong> or &lt;strong>K8s Jobs&lt;/strong> over GPU pools. The lineage chain &lt;code>dataset → run → model&lt;/code> is closed by registering the dataset as an MLflow input artefact.&lt;/p>
&lt;p>&lt;strong>Hyperscaler equivalents.&lt;/strong> On &lt;strong>AWS&lt;/strong>, &lt;strong>SageMaker Training Jobs&lt;/strong> covers most workloads, &lt;strong>SageMaker HyperPod&lt;/strong> covers large training runs with resilience to node failures, and &lt;strong>SageMaker JumpStart&lt;/strong> offers click-to-train fine-tuning over a catalogue of pre-curated models. For fine-tuning Bedrock models (hosted Claude, Llama, Mistral) there is &lt;strong>Bedrock Custom Models&lt;/strong>: you upload the dataset to S3, Bedrock trains, and it returns a private endpoint with provisioned throughput. The equivalent experiment tracking is &lt;strong>SageMaker Experiments&lt;/strong> or &lt;strong>managed MLflow in SageMaker&lt;/strong> (yes, AWS has officially hosted MLflow since 2024).&lt;/p>
&lt;p>On &lt;strong>GCP&lt;/strong>, &lt;strong>Vertex AI Custom Training&lt;/strong> runs any container with GPUs or TPUs; &lt;strong>Vertex AI Tuning&lt;/strong> is the managed API for fine-tuning Gemini and Model Garden models. Experiment tracking in &lt;strong>Vertex AI Experiments&lt;/strong> (with MLflow compatibility).&lt;/p>
&lt;p>On &lt;strong>Azure&lt;/strong>, &lt;strong>Azure ML Training Jobs&lt;/strong> on your own clusters or managed compute; &lt;strong>Azure OpenAI fine-tuning&lt;/strong> for fine-tuning GPT and the o-series; &lt;strong>Azure ML Experiments&lt;/strong> with MLflow natively integrated since 2022.&lt;/p>
&lt;p>&lt;strong>Summary table — Tune stage.&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Functional piece&lt;/th>
&lt;th>OSS on-premise&lt;/th>
&lt;th>AWS&lt;/th>
&lt;th>GCP&lt;/th>
&lt;th>Azure&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Training framework&lt;/td>
&lt;td>HF Transformers + PEFT&lt;/td>
&lt;td>SageMaker SDK&lt;/td>
&lt;td>Vertex AI SDK&lt;/td>
&lt;td>Azure ML SDK&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Quantisation / parallelism&lt;/td>
&lt;td>bitsandbytes, DeepSpeed, FSDP&lt;/td>
&lt;td>SageMaker libs + HF support&lt;/td>
&lt;td>Vertex + HF support&lt;/td>
&lt;td>Azure ML + HF support&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Managed fine-tuning (black box)&lt;/td>
&lt;td>—&lt;/td>
&lt;td>Bedrock Custom Models, JumpStart&lt;/td>
&lt;td>Vertex Tuning (Gemini)&lt;/td>
&lt;td>Azure OpenAI fine-tuning&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Distributed on a cluster&lt;/td>
&lt;td>Kubeflow, Ray Train, Slurm&lt;/td>
&lt;td>SageMaker HyperPod&lt;/td>
&lt;td>Vertex AI Training (multi-node)&lt;/td>
&lt;td>Azure ML compute clusters&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Experiment tracking&lt;/td>
&lt;td>MLflow, W&amp;amp;B self-hosted&lt;/td>
&lt;td>SageMaker Experiments, managed MLflow&lt;/td>
&lt;td>Vertex Experiments&lt;/td>
&lt;td>Azure ML + MLflow&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Access to the base model&lt;/td>
&lt;td>Whatever you download (Llama, Mistral, Qwen)&lt;/td>
&lt;td>Bedrock catalog + HF Hub&lt;/td>
&lt;td>Vertex Model Garden + HF Hub&lt;/td>
&lt;td>Azure ML model catalog + HF Hub&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Where the names mislead.&lt;/strong> The &lt;em>managed&lt;/em> fine-tunings (Bedrock Custom, Vertex Tuning, AOAI fine-tuning) are a &lt;strong>black box&lt;/strong>: you do not choose hyperparameters beyond a handful, you do not see the trainer&amp;rsquo;s detailed logs, and you cannot inspect the dataset once it is inside their pipeline. The experiment tracking they offer is not comparable to MLflow sitting next to the trainer, where you can capture any metric and artefact. For scenarios where &lt;em>operationally&lt;/em> you do not need inspection this is liberating; for ENS / NIS2 scenarios where you have to prove what trained what, the black box fails by construction.&lt;/p>
&lt;h2 id="stage-3--eval">Stage 3 — Eval&lt;/h2>
&lt;p>&lt;strong>The problem.&lt;/strong> Validating candidates before and after promotion against a golden set, with operational metrics (faithfulness to the RAG, tone, format compliance, toxicity, jailbreak resistance, PII leakage) run as gates in CI and as online sampling. Covered in the &lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">post on evals&lt;/a> and the one on &lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">guardrails&lt;/a>.&lt;/p>
&lt;p>&lt;strong>OSS stack.&lt;/strong> Eval suites: &lt;strong>DeepEval&lt;/strong>, &lt;strong>RAGAS&lt;/strong> (specialised in RAG), &lt;strong>Promptfoo&lt;/strong> (declarative, ideal for CI), &lt;strong>lm-eval-harness&lt;/strong> (academic), &lt;strong>HELM&lt;/strong>. Evals integrated with tracing: &lt;strong>Langfuse Evals&lt;/strong>, &lt;strong>Phoenix Arize OSS&lt;/strong>. LLM-as-judge judges: any local OSS model; in serious systems, two different judges to reduce bias. Safety and guardrails: &lt;strong>NeMo Guardrails&lt;/strong> (NVIDIA), &lt;strong>Guardrails AI&lt;/strong>, &lt;strong>LlamaGuard&lt;/strong> + &lt;strong>PromptGuard&lt;/strong> (Meta), &lt;strong>ShieldGemma&lt;/strong> (Google, open weights), &lt;strong>PII detectors&lt;/strong> such as &lt;strong>Presidio&lt;/strong> (Microsoft) on-prem.&lt;/p>
&lt;p>&lt;strong>Hyperscaler equivalents.&lt;/strong> On &lt;strong>AWS&lt;/strong>, &lt;strong>Bedrock Model Evaluation&lt;/strong> offers automatic evals (toxicity, accuracy, robustness) and human-in-the-loop, &lt;strong>Bedrock Guardrails&lt;/strong> covers the safety layer (denied topics, PII, prompt injection, contextual grounding check), and &lt;strong>SageMaker Clarify&lt;/strong> adds bias and explainability over general models.&lt;/p>
&lt;p>On &lt;strong>GCP&lt;/strong>, &lt;strong>Vertex AI Evaluation Service&lt;/strong> runs evals with automatic metrics and an LLM judge, &lt;strong>Vertex AI Model Armor&lt;/strong> and the &lt;strong>safety filters&lt;/strong> built into the Gemini API cover the guardrails layer. &lt;strong>Vertex AI Studio&lt;/strong> exposes interactive Eval for prompt iteration.&lt;/p>
&lt;p>On &lt;strong>Azure&lt;/strong>, the &lt;strong>Azure AI Evaluation SDK&lt;/strong> runs offline evals against datasets, &lt;strong>Azure AI Content Safety&lt;/strong> covers safety (Prompt Shields against jailbreak, &lt;strong>Groundedness detection&lt;/strong>, content categories, &lt;strong>PII detection&lt;/strong>). All accessible from &lt;strong>Azure AI Foundry&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Summary table — Eval stage.&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Functional piece&lt;/th>
&lt;th>OSS on-premise&lt;/th>
&lt;th>AWS&lt;/th>
&lt;th>GCP&lt;/th>
&lt;th>Azure&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Automatic eval suite&lt;/td>
&lt;td>DeepEval, RAGAS, Promptfoo&lt;/td>
&lt;td>Bedrock Model Evaluation&lt;/td>
&lt;td>Vertex AI Evaluation Service&lt;/td>
&lt;td>Azure AI Evaluation SDK&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LLM-as-judge&lt;/td>
&lt;td>Any OSS model&lt;/td>
&lt;td>Bedrock judge models&lt;/td>
&lt;td>Vertex judge (Gemini)&lt;/td>
&lt;td>Azure OpenAI judges&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Golden set management&lt;/td>
&lt;td>Langfuse datasets, manual&lt;/td>
&lt;td>SageMaker Ground Truth datasets&lt;/td>
&lt;td>Vertex Datasets&lt;/td>
&lt;td>Azure ML Datasets&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Guardrails (jailbreak, PII, prompt injection)&lt;/td>
&lt;td>NeMo Guardrails, LlamaGuard, Presidio&lt;/td>
&lt;td>Bedrock Guardrails&lt;/td>
&lt;td>Vertex Model Armor + Gemini safety&lt;/td>
&lt;td>Azure AI Content Safety (Prompt Shields, Groundedness)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Eval in CI&lt;/td>
&lt;td>Promptfoo + GitHub Actions&lt;/td>
&lt;td>Bedrock Eval API + CodeBuild&lt;/td>
&lt;td>Vertex Eval API + Cloud Build&lt;/td>
&lt;td>Azure AI Eval + Azure Pipelines&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Where the names mislead.&lt;/strong> Managed guardrails are convenient but &lt;strong>opaque&lt;/strong>: the Bedrock Guardrails rules are configurable but the detection implementation cannot be inspected; the same goes for Azure AI Content Safety. In OSS, NeMo Guardrails shows you the Colang graph and Presidio shows you the recognisers, auditable and modifiable. For regulated systems where an auditor asks &lt;em>&amp;ldquo;how exactly does it detect PII?&amp;rdquo;&lt;/em>, OSS answers with code; the cloud answers with documentation.&lt;/p>
&lt;h2 id="stage-4--deploy">Stage 4 — Deploy&lt;/h2>
&lt;p>&lt;strong>The problem.&lt;/strong> Serving tokens to the end user with predictable latency and throughput, a decent cost / token ratio, hot-swap adapter support, and multi-tenancy if the business demands it. Covered in the posts on &lt;a href="https://blog.lo0.es/en/posts/kv-cache-working-memory-llm-inference/">KV cache&lt;/a>, &lt;a href="https://blog.lo0.es/en/posts/pagedattention-vllm-block-manager/">PagedAttention&lt;/a>, &lt;a href="https://blog.lo0.es/en/posts/disaggregated-serving-prefill-decode-specialised-pods/">disaggregated serving&lt;/a>, &lt;a href="https://blog.lo0.es/en/posts/vllm-kubernetes-llm-inference-that-scales/">vLLM on K8s&lt;/a>, &lt;a href="https://blog.lo0.es/en/posts/llm-inference-operators-kubernetes/">LLM operators&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/gpu-cluster-as-multi-tenant-platform/">multi-tenant cluster&lt;/a>.&lt;/p>
&lt;p>&lt;strong>OSS stack.&lt;/strong> Inference engine: &lt;strong>vLLM&lt;/strong> (PagedAttention, prefix caching, LoRA hot-swap, OpenAI-compatible API) as the reference, &lt;strong>TensorRT-LLM&lt;/strong> for maximum optimisation on Hopper / Ada, &lt;strong>SGLang&lt;/strong> for workloads with heavy prompt restructuring, &lt;strong>TGI&lt;/strong> (Hugging Face) as a mature alternative, &lt;strong>llama.cpp&lt;/strong> for edge and CPUs, &lt;strong>NVIDIA Dynamo&lt;/strong> for multi-node disaggregated serving on large clusters. Orchestration on Kubernetes: &lt;strong>KServe&lt;/strong>, &lt;strong>KubeRay&lt;/strong>, dedicated operators such as &lt;strong>llm-d&lt;/strong>, &lt;strong>vLLM Production Stack&lt;/strong> and &lt;strong>KAITO&lt;/strong>. Gateway / control plane: &lt;strong>Envoy AI Gateway&lt;/strong>, &lt;strong>LiteLLM Proxy&lt;/strong>, &lt;strong>Portkey AI Gateway&lt;/strong>, &lt;strong>Kong AI Gateway&lt;/strong>. Triton Inference Server covers mixed workloads (LLM + traditional) where a single backend matters.&lt;/p>
&lt;p>&lt;strong>Hyperscaler equivalents.&lt;/strong> On &lt;strong>AWS&lt;/strong>, two different routes. The &lt;em>managed-by-model&lt;/em> route is &lt;strong>Amazon Bedrock&lt;/strong>: a catalogue of hosted models (Claude, Llama, Mistral, Cohere, Titan), pay per token or &lt;strong>Provisioned Throughput&lt;/strong> with an SLA, &lt;strong>Bedrock Prompt Caching&lt;/strong> as the conceptual equivalent of vLLM&amp;rsquo;s prefix caching, and integrated &lt;strong>Bedrock Agents&lt;/strong> and &lt;strong>Bedrock Knowledge Bases&lt;/strong>. The &lt;em>managed-by-infrastructure&lt;/em> route is &lt;strong>SageMaker Endpoints&lt;/strong> (real-time, async, serverless, batch) with &lt;strong>Inference Components&lt;/strong> to densify multiple models on one instance. Own hardware: &lt;strong>AWS Inferentia&lt;/strong> and &lt;strong>Trainium&lt;/strong> via the &lt;strong>Neuron&lt;/strong> chip, an alternative to NVIDIA with a better cost / token on stable workloads if you compile your model.&lt;/p>
&lt;p>On &lt;strong>GCP&lt;/strong>, &lt;strong>Vertex AI Prediction Endpoints&lt;/strong> runs your containers or models from the &lt;strong>Model Garden&lt;/strong>, the &lt;strong>Gemini API&lt;/strong> via Vertex AI offers the managed Gemini models, and &lt;strong>Cloud TPU v5e / v5p / Trillium (v6)&lt;/strong> is the in-house hardware competing with the H100 for training and inference. For sovereignty there is &lt;strong>Google Distributed Cloud air-gapped&lt;/strong>, which brings Vertex AI to a certifiable on-premise rack.&lt;/p>
&lt;p>On &lt;strong>Azure&lt;/strong>, &lt;strong>Azure OpenAI Service&lt;/strong> serves OpenAI models (GPT-4.1, o-series, GPT-image), &lt;strong>Azure ML Managed Online Endpoints&lt;/strong> runs any model (OSS included, via container), and &lt;strong>Azure AI Foundry models&lt;/strong> absorbed in 2025 the catalogue of open models served as-a-service. Hardware: &lt;strong>Azure ND H100 v5&lt;/strong>, &lt;strong>ND H200 v5&lt;/strong>, &lt;strong>ND GB200 v6&lt;/strong> and the in-house bet &lt;strong>Microsoft Maia 100&lt;/strong> for internal inference.&lt;/p>
&lt;p>&lt;strong>Summary table — Deploy stage.&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Functional piece&lt;/th>
&lt;th>OSS on-premise&lt;/th>
&lt;th>AWS&lt;/th>
&lt;th>GCP&lt;/th>
&lt;th>Azure&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Inference engine&lt;/td>
&lt;td>vLLM, TensorRT-LLM, SGLang, TGI&lt;/td>
&lt;td>Bedrock (managed model), SM Endpoints (your container)&lt;/td>
&lt;td>Vertex Prediction, Gemini API&lt;/td>
&lt;td>Azure OpenAI, Azure ML Endpoints&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Prefix / prompt caching&lt;/td>
&lt;td>vLLM native&lt;/td>
&lt;td>Bedrock Prompt Caching&lt;/td>
&lt;td>Vertex AI context caching&lt;/td>
&lt;td>Azure OpenAI prompt caching&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Adapter hot-swap (LoRA)&lt;/td>
&lt;td>vLLM &lt;code>--enable-lora&lt;/code>, S-LoRA&lt;/td>
&lt;td>Bedrock Custom Models endpoints&lt;/td>
&lt;td>Vertex Tuning endpoints&lt;/td>
&lt;td>Azure OpenAI fine-tuned deployments&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Disaggregated serving&lt;/td>
&lt;td>NVIDIA Dynamo, vLLM PD-disagg&lt;/td>
&lt;td>— (managed internally, not exposed)&lt;/td>
&lt;td>— (managed internally, not exposed)&lt;/td>
&lt;td>— (managed internally, not exposed)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Accelerator hardware&lt;/td>
&lt;td>NVIDIA H100/H200/B200, AMD MI300&lt;/td>
&lt;td>Inferentia, Trainium, NVIDIA&lt;/td>
&lt;td>TPU v5/v6, NVIDIA&lt;/td>
&lt;td>Maia, NVIDIA&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>AI Gateway / proxy&lt;/td>
&lt;td>Envoy AI Gateway, LiteLLM, Portkey, Kong&lt;/td>
&lt;td>API Gateway + Bedrock&lt;/td>
&lt;td>Vertex AI + Apigee&lt;/td>
&lt;td>Azure API Management + AOAI&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>K8s orchestration&lt;/td>
&lt;td>KServe, KubeRay, llm-d, KAITO&lt;/td>
&lt;td>EKS + SageMaker Operators&lt;/td>
&lt;td>GKE + Vertex AI&lt;/td>
&lt;td>AKS + KAITO&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Where the names mislead.&lt;/strong> Bedrock Prompt Caching and Vertex context caching &lt;strong>sound&lt;/strong> equivalent to vLLM&amp;rsquo;s prefix caching, but operationally they are different: the cache lives on the hyperscaler&amp;rsquo;s plane, its eviction policy is opaque, its cost is billed separately, and you cannot easily see the hit ratio per tenant. In vLLM you see the hit ratio in Prometheus metrics and you decide the policy. The same applies to disaggregated serving: the hyperscalers implement it internally to cut their own cost of serving, but they &lt;strong>do not expose&lt;/strong> prefill/decode control to the user. If you need your workload&amp;rsquo;s TTFT to be controlled separately from TPS, that lever is not available.&lt;/p>
&lt;h2 id="stage-5--observe">Stage 5 — Observe&lt;/h2>
&lt;p>&lt;strong>The problem.&lt;/strong> End-to-end LLM traces with &lt;code>trace_id&lt;/code> propagated through every component, runtime metrics per tenant, online scoring (LLM judge over sampling), statistical drift, and safety / guardrails monitoring. Covered in the posts on &lt;a href="https://blog.lo0.es/en/posts/agentsight-llm-tracing-ebpf-zero-instrumentation/">AgentSight tracing&lt;/a>, &lt;a href="https://blog.lo0.es/en/posts/mcp-deep-observability-opentelemetry/">MCP observability with OTel&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/ebpf-local-inference-statistical-drift-detection/">eBPF + drift&lt;/a>.&lt;/p>
&lt;p>&lt;strong>OSS stack.&lt;/strong> Base standard: &lt;strong>OpenTelemetry&lt;/strong> (specification + collector + SDKs) with the &lt;strong>gen_ai semantic conventions&lt;/strong> that stabilised in 2025. Backends: &lt;strong>Tempo&lt;/strong> or &lt;strong>Jaeger&lt;/strong> for traces, &lt;strong>Prometheus&lt;/strong> for metrics, &lt;strong>Loki&lt;/strong> for logs, &lt;strong>Grafana&lt;/strong> as the common UI. LLM-specific layer: &lt;strong>Langfuse&lt;/strong> (self-hosted with an optional EE licence) and &lt;strong>Phoenix Arize OSS&lt;/strong>. eBPF layer for low-level observability: &lt;strong>Pixie&lt;/strong>, &lt;strong>Hubble&lt;/strong>, and &lt;strong>Cilium Tetragon&lt;/strong> for runtime security. Drift: &lt;strong>Evidently AI&lt;/strong>, &lt;strong>NannyML&lt;/strong>, &lt;strong>Alibi Detect&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Hyperscaler equivalents.&lt;/strong> On &lt;strong>AWS&lt;/strong>, &lt;strong>CloudWatch&lt;/strong> (metrics + logs) + &lt;strong>AWS X-Ray&lt;/strong> (traces) form the base, &lt;strong>CloudWatch Application Signals&lt;/strong> adds OTel-compatible APM, and &lt;strong>Amazon Managed Prometheus&lt;/strong> and &lt;strong>Amazon Managed Grafana&lt;/strong> serve the plane if you want to keep Prom + Grafana without operating them. &lt;strong>Bedrock logging&lt;/strong> integrates with CloudWatch and S3. &lt;strong>ADOT&lt;/strong> (AWS Distro for OpenTelemetry) is the official collector.&lt;/p>
&lt;p>On &lt;strong>GCP&lt;/strong>, &lt;strong>Cloud Monitoring&lt;/strong> + &lt;strong>Cloud Logging&lt;/strong> + &lt;strong>Cloud Trace&lt;/strong> + &lt;strong>Cloud Profiler&lt;/strong> form the quintet, all OTel compatible. &lt;strong>Vertex AI Model Monitoring&lt;/strong> offers drift detection (feature skew, prediction drift) integrated with runs.&lt;/p>
&lt;p>On &lt;strong>Azure&lt;/strong>, &lt;strong>Azure Monitor&lt;/strong> + &lt;strong>Application Insights&lt;/strong> + &lt;strong>Log Analytics&lt;/strong> cover the APM stack with native OTel, &lt;strong>Azure ML Model Monitor&lt;/strong> adds drift and data quality, and &lt;strong>Azure OpenAI diagnostic logs&lt;/strong> enrich the traces with token and model metadata.&lt;/p>
&lt;p>&lt;strong>Summary table — Observe stage.&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Functional piece&lt;/th>
&lt;th>OSS on-premise&lt;/th>
&lt;th>AWS&lt;/th>
&lt;th>GCP&lt;/th>
&lt;th>Azure&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Traces (OTel)&lt;/td>
&lt;td>OTel + Tempo / Jaeger&lt;/td>
&lt;td>X-Ray + ADOT, App Signals&lt;/td>
&lt;td>Cloud Trace&lt;/td>
&lt;td>App Insights + Azure Monitor&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Metrics&lt;/td>
&lt;td>Prometheus + Grafana&lt;/td>
&lt;td>CloudWatch + AMP / AMG&lt;/td>
&lt;td>Cloud Monitoring&lt;/td>
&lt;td>Azure Monitor Metrics&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Logs&lt;/td>
&lt;td>Loki, ELK&lt;/td>
&lt;td>CloudWatch Logs&lt;/td>
&lt;td>Cloud Logging&lt;/td>
&lt;td>Log Analytics&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>LLM-specific (prompt, scores, sessions)&lt;/td>
&lt;td>Langfuse, Phoenix Arize OSS&lt;/td>
&lt;td>Bedrock logging + CW + custom&lt;/td>
&lt;td>Vertex AI tracing + custom&lt;/td>
&lt;td>App Insights + AOAI logs + custom&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Drift detection&lt;/td>
&lt;td>Evidently, NannyML, Alibi Detect&lt;/td>
&lt;td>SageMaker Model Monitor&lt;/td>
&lt;td>Vertex AI Model Monitoring&lt;/td>
&lt;td>Azure ML Model Monitor&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>eBPF / runtime&lt;/td>
&lt;td>Pixie, Hubble, Tetragon&lt;/td>
&lt;td>— (no direct equivalent)&lt;/td>
&lt;td>GKE Dataplane v2 / Cloud Service Mesh&lt;/td>
&lt;td>Azure CNI + Defender for Cloud&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Where the names mislead.&lt;/strong> The classic cloud APM tools (X-Ray, Cloud Trace, App Insights) do not understand &lt;strong>prompt versioning&lt;/strong> or &lt;strong>adapter id&lt;/strong> as native concepts. They accept the &lt;code>gen_ai.*&lt;/code> attributes as dimensions, but the UIs do not prioritise those views. Langfuse and Phoenix do, because they are designed for LLM. In cloud, the usual pattern is to send dual: APM to the managed service for infrastructure + self-hosted Langfuse / Phoenix for the LLM plane. That pays off.&lt;/p>
&lt;h2 id="stage-6--retrain--cross-cutting-pieces">Stage 6 — Retrain + cross-cutting pieces&lt;/h2>
&lt;p>&lt;strong>The problem (Retrain).&lt;/strong> Closing the loop feedback → triage → enriched dataset → new adapter, with a mixed cadence (quarterly + incident-driven). Covered in the &lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">Retrain post&lt;/a>.&lt;/p>
&lt;p>&lt;strong>OSS Retrain stack.&lt;/strong> Orchestration: &lt;strong>Apache Airflow&lt;/strong>, &lt;strong>Prefect&lt;/strong>, &lt;strong>Dagster&lt;/strong> or &lt;strong>Argo Workflows&lt;/strong> and &lt;strong>Kubeflow Pipelines&lt;/strong> for K8s-native. Feature store where applicable: &lt;strong>Feast&lt;/strong>. Annotation and human-in-the-loop: &lt;strong>Argilla&lt;/strong>, &lt;strong>Label Studio&lt;/strong>, &lt;strong>Trubrics&lt;/strong>. Structured feedback capture: your own &lt;strong>Postgres&lt;/strong> table + &lt;strong>Langfuse scores&lt;/strong> + &lt;strong>Phoenix annotations&lt;/strong>. Closed-loop lineage: &lt;strong>OpenLineage&lt;/strong> tying dataset → run → model → deployment → feedback → next dataset.&lt;/p>
&lt;p>&lt;strong>Hyperscaler Retrain equivalents.&lt;/strong> On &lt;strong>AWS&lt;/strong>, &lt;strong>SageMaker Pipelines&lt;/strong> orchestrates the cycle, &lt;strong>SageMaker Ground Truth&lt;/strong> and &lt;strong>A2I&lt;/strong> (Augmented AI) manage annotation and HiL, and &lt;strong>SageMaker Model Monitor&lt;/strong> fires alerts that can invoke retrain pipelines. &lt;strong>AWS Step Functions&lt;/strong> serves as a more general alternative orchestrator.&lt;/p>
&lt;p>On &lt;strong>GCP&lt;/strong>, &lt;strong>Vertex AI Pipelines&lt;/strong> (based on Kubeflow Pipelines, compatible) orchestrates, &lt;strong>Vertex AI Data Labeling Service&lt;/strong> annotates, &lt;strong>Vertex AI Feature Store&lt;/strong> manages features, and &lt;strong>Workflows&lt;/strong> or &lt;strong>Cloud Composer&lt;/strong> (managed Airflow) act as orchestration alternatives.&lt;/p>
&lt;p>On &lt;strong>Azure&lt;/strong>, &lt;strong>Azure ML Pipelines&lt;/strong> orchestrates, &lt;strong>Azure ML Data Labeling&lt;/strong> annotates, &lt;strong>Azure ML Feature Store&lt;/strong> manages features.&lt;/p>
&lt;p>&lt;strong>The problem (cross-cutting: prompt + data versioning).&lt;/strong> Making sure &lt;code>prompt_id, prompt_version&lt;/code> and &lt;code>dataset_id, dataset_version&lt;/code> propagate through the whole system and show up in spans, runs and metrics. Covered in the posts on &lt;a href="https://blog.lo0.es/en/posts/prompt-versioning-contract-five-word-change/">prompt versioning&lt;/a> and &lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">data versioning&lt;/a>.&lt;/p>
&lt;p>&lt;strong>Prompt versioning equivalents.&lt;/strong> OSS: &lt;strong>Langfuse Prompts&lt;/strong>, &lt;strong>MLflow Prompt Registry&lt;/strong>. AWS: &lt;strong>Bedrock Prompt Management&lt;/strong> (catalogue, versions, labels, integrated A/B testing) and &lt;strong>SageMaker Prompt Hub&lt;/strong>. GCP: &lt;strong>Vertex AI Prompt Management&lt;/strong> inside Vertex AI Studio. Azure: &lt;strong>Azure AI Foundry Prompt flow&lt;/strong> and prompt versioning in &lt;strong>Azure OpenAI deployments&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Data versioning equivalents.&lt;/strong> OSS: DVC + lakeFS (already covered under Data). AWS: S3 Versioning + Lake Formation + Glue Catalog (they are not DVC but together they cover part of it). GCP: Cloud Storage versioning + Dataplex (same). Azure: ADLS Gen2 versioning + Purview (same). The &lt;strong>real gap&lt;/strong> here is that no hyperscaler offers DVC natively, so dataset-as-first-class-citizen operations still require a layer of your own.&lt;/p>
&lt;p>&lt;strong>Summary table — Retrain stage + cross-cutting pieces.&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Functional piece&lt;/th>
&lt;th>OSS on-premise&lt;/th>
&lt;th>AWS&lt;/th>
&lt;th>GCP&lt;/th>
&lt;th>Azure&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>ML pipeline orchestration&lt;/td>
&lt;td>Airflow, Dagster, Argo, Kubeflow&lt;/td>
&lt;td>SageMaker Pipelines, Step Functions&lt;/td>
&lt;td>Vertex AI Pipelines, Cloud Composer&lt;/td>
&lt;td>Azure ML Pipelines&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Feature store&lt;/td>
&lt;td>Feast&lt;/td>
&lt;td>SageMaker Feature Store&lt;/td>
&lt;td>Vertex AI Feature Store&lt;/td>
&lt;td>Azure ML Feature Store&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Annotation / HiL&lt;/td>
&lt;td>Argilla, Label Studio&lt;/td>
&lt;td>SageMaker Ground Truth, A2I&lt;/td>
&lt;td>Vertex Data Labeling&lt;/td>
&lt;td>Azure ML Data Labeling&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Feedback capture&lt;/td>
&lt;td>Postgres + Langfuse scores&lt;/td>
&lt;td>Bedrock + custom + Ground Truth&lt;/td>
&lt;td>Vertex + custom&lt;/td>
&lt;td>App Insights + custom&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Prompt versioning&lt;/td>
&lt;td>Langfuse Prompts, MLflow Prompts&lt;/td>
&lt;td>Bedrock Prompt Management&lt;/td>
&lt;td>Vertex Prompt Management&lt;/td>
&lt;td>Azure AI Foundry Prompt flow&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Data versioning&lt;/td>
&lt;td>DVC + lakeFS + OpenLineage&lt;/td>
&lt;td>S3 Versioning + Lake Formation&lt;/td>
&lt;td>GCS + Dataplex&lt;/td>
&lt;td>ADLS + Purview&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Cross-system lineage&lt;/td>
&lt;td>OpenLineage + DataHub&lt;/td>
&lt;td>SageMaker Lineage Tracking&lt;/td>
&lt;td>Dataplex lineage&lt;/td>
&lt;td>Purview&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="the-chatbot-from-the-previous-post-ported-to-aws">The chatbot from the previous post ported to AWS&lt;/h2>
&lt;p>To stop the catalogue being abstract, we take the full scenario from the &lt;a href="https://blog.lo0.es/en/posts/anatomy-llm-request-production-tour-six-stages/">previous post&lt;/a>, the multi-tenant support chatbot for insurers on an on-premise OSS stack, and describe it component by component with an AWS stack. It is not an executable migration; it is the &lt;strong>map of what disappears, what appears and where the lock-in shows up&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>The network plane.&lt;/strong> Edge LB and WAF: &lt;strong>AWS WAF + CloudFront&lt;/strong>. Ingress to the cluster: &lt;strong>AWS Load Balancer Controller&lt;/strong> over &lt;strong>EKS&lt;/strong>. What was Cilium BGP + RKE2 is replaced by EKS with &lt;strong>VPC CNI&lt;/strong> (or Cilium on EKS, which is possible). The conceptual equivalent of Tetragon is &lt;strong>Amazon GuardDuty for EKS&lt;/strong> + optional &lt;strong>Falco&lt;/strong>. Moderate lock-in: network control couples to VPC.&lt;/p>
&lt;p>&lt;strong>The chat gateway and auth.&lt;/strong> What was a bespoke API gateway with JWT verification materialises as &lt;strong>Amazon API Gateway&lt;/strong> + &lt;strong>Amazon Cognito&lt;/strong> (or IAM Identity Center if it is B2B). The gateway&amp;rsquo;s AI-aware routing is covered with &lt;strong>Bedrock&lt;/strong> + per-customer tags, or with &lt;strong>AWS API Gateway custom authorizers&lt;/strong> invoking a Lambda for tenant resolution. High lock-in on the identity layer if Cognito is chosen.&lt;/p>
&lt;p>&lt;strong>The inference engine.&lt;/strong> Three different options, with a clear trade-off.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Bedrock with a managed model&lt;/strong> (Claude / Llama / Mistral): all the operations of vLLM, K8s Operators, KV cache and disaggregated serving disappear. You move to &lt;strong>Provisioned Throughput&lt;/strong> for a latency guarantee. You gain time-to-market; you lose control over prefill/decode, over custom LoRA adapters (Bedrock accepts Bedrock-managed fine-tunes but not arbitrary LoRAs), and you enter model lock-in (moving from Claude to Llama means changing API).&lt;/li>
&lt;li>&lt;strong>SageMaker Endpoints with your own vLLM container&lt;/strong>: you keep vLLM and its optimisations, but K8s disappears and SageMaker replaces it as the orchestration plane. Inference Components allow you to densify multiple adapters. KV cache, prefix caching and LoRA hot-swap work the same. Moderate lock-in on the SageMaker SDK and on the Inference Components format.&lt;/li>
&lt;li>&lt;strong>EKS with vLLM&lt;/strong> (the minimalist option): basically the OSS stack but with EKS instead of RKE2 and EBS/EFS instead of Ceph. Low lock-in, limited benefit from the cloud.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Data layer.&lt;/strong> The corpus moves to &lt;strong>S3&lt;/strong> with versioning, the embeddings to &lt;strong>Amazon OpenSearch Service&lt;/strong> or to &lt;strong>Aurora pgvector&lt;/strong>. The radical managed option is &lt;strong>Bedrock Knowledge Bases&lt;/strong>: you upload documents to S3, it indexes them, it exposes a retrieval API. We remove Qdrant, we remove manual embedding pipelines, we remove part of Kafka + Flink. But control over custom reranking, fine-grained per-chunk ACL and the possibility of re-embedding with a new encoder of your own disappears. Bedrock KB uses the Titan or Cohere embedders available in Bedrock, and changing them means changing the whole index. ENS compliance: you have to validate that the buckets and the index live in EU regions and that the embedding model does too.&lt;/p>
&lt;p>&lt;strong>Stream + CDC.&lt;/strong> Kafka + Debezium is replaced by &lt;strong>MSK&lt;/strong> + &lt;strong>MSK Connect&lt;/strong> or by &lt;strong>Kinesis + DMS&lt;/strong>. Schema Registry: &lt;strong>Glue Schema Registry&lt;/strong>. The events remain functionally equivalent. Moderate lock-in if you go to Kinesis (Kinesis is not Kafka), low if you go to MSK (Kafka compatibility).&lt;/p>
&lt;p>&lt;strong>Data versioning.&lt;/strong> Here the gap is clear. S3 Versioning + Lake Formation + Glue Catalog &lt;strong>is not DVC&lt;/strong>. To preserve the discipline of the previous post, &lt;code>(dataset_id, dataset_version, sha256_hash)&lt;/code> propagated as an input artefact to the trainer, you can keep DVC over S3 (DVC works perfectly with S3 as a remote) or accept the limitation and register lineage manually in SageMaker Lineage Tracking. The first option preserves the practice; the second accepts degradation.&lt;/p>
&lt;p>&lt;strong>Tune stage.&lt;/strong> The LoRA adapter &lt;code>customer_support_v7&lt;/code> is trained with &lt;strong>SageMaker Training Jobs&lt;/strong> on &lt;strong>ml.p5.48xlarge&lt;/strong> instances (8× H100), using a standard HuggingFace + PEFT container. MLflow managed by SageMaker or your own MLflow on EC2 covers tracking. Alternative: if the black box is acceptable, &lt;strong>Bedrock Custom Models&lt;/strong> with a dataset in S3 produces a fine-tuned Bedrock model without instantiating a GPU manually, at the cost of not being able to inspect the run.&lt;/p>
&lt;p>&lt;strong>Eval stage.&lt;/strong> Promptfoo + RAGAS in CI runs the same over &lt;strong>CodeBuild&lt;/strong>. &lt;strong>Bedrock Model Evaluation&lt;/strong> replaces a good part of the automatic eval suite. &lt;strong>Bedrock Guardrails&lt;/strong> replaces NeMo Guardrails + Presidio + LlamaGuard, with the loss of transparency noted earlier.&lt;/p>
&lt;p>&lt;strong>Deploy stage.&lt;/strong> If Bedrock was chosen as the engine, this stage vanishes: Bedrock serves. If SageMaker Endpoints + vLLM was chosen, KServe is replaced by SageMaker Operators (or KServe is kept over EKS). The AI Gateway that in OSS was Envoy AI Gateway or LiteLLM becomes &lt;strong>API Gateway&lt;/strong> + &lt;strong>Bedrock&lt;/strong> or &lt;strong>API Gateway&lt;/strong> + &lt;strong>Lambda&lt;/strong> + &lt;strong>SageMaker&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Observe stage.&lt;/strong> The OTel Collector remains the standard. Traces to &lt;strong>AWS X-Ray&lt;/strong> + &lt;strong>CloudWatch Application Signals&lt;/strong>. Metrics to &lt;strong>Amazon Managed Prometheus&lt;/strong>. Logs to &lt;strong>CloudWatch Logs&lt;/strong> + optionally &lt;strong>OpenSearch&lt;/strong> for search. &lt;strong>Langfuse&lt;/strong> is hosted on &lt;strong>ECS Fargate&lt;/strong> or &lt;strong>EKS&lt;/strong> because the cloud has no native equivalent of integrated prompt + traces + scores. Drift: &lt;strong>SageMaker Model Monitor&lt;/strong> replaces Evidently / NannyML. eBPF (Pixie / Hubble / Tetragon) has &lt;strong>no direct equivalent&lt;/strong> in managed AWS, so Falco or installing Tetragon on EKS is still the route.&lt;/p>
&lt;p>&lt;strong>Retrain stage.&lt;/strong> &lt;strong>SageMaker Pipelines&lt;/strong> orchestrates the quarterly cycle. &lt;strong>SageMaker Ground Truth&lt;/strong> + &lt;strong>A2I&lt;/strong> replace Argilla. The &lt;code>feedback_signals&lt;/code> table in Postgres stays as it is (RDS Postgres) or moves to DynamoDB for large scales.&lt;/p>
&lt;p>&lt;strong>How much the lock-in weighs.&lt;/strong> The component with the highest lock-in is Bedrock + Bedrock Knowledge Bases + Bedrock Guardrails: leaving that requires rewriting the inference plane and reindexing the whole RAG. Next is the SageMaker SDK (Pipelines, Endpoints, Training), where leaving costs but means rewriting scripts, not data. Data in S3 is portable (S3 → MinIO with &lt;code>rclone&lt;/code> works). OTel observability is portable at almost no cost if the collector is kept as the abstraction. The auth gateway is the other high lock-in point if it uses Cognito.&lt;/p>
&lt;p>&lt;strong>What you gain.&lt;/strong> A dramatic reduction in GPU infrastructure operations, K8s patching, CUDA driver management, prefill/decode sizing, Ceph / MinIO management. A very short start-up curve: one request served in less than a sprint versus several weeks of bring-up for the OSS stack. Explicit provider SLAs.&lt;/p>
&lt;p>&lt;strong>What you lose.&lt;/strong> Contractual data sovereignty (the data stays in EU regions if configured that way, but the operator is a US third party under the Cloud Act). Visibility of the whole stack (Bedrock is a black box from the model downwards). Roadmap independence (the decision to discontinue a model, raise prices or change guardrails is not controlled by the customer). Fine-grained cost-per-token optimisation (the levers are the ones the provider exposes). For ENS customers under a HIGH declaration or NIS2 essential category, several of these points are &lt;strong>non-compliance&lt;/strong>, not preference.&lt;/p>
&lt;h2 id="master-table-the-entire-parallel-catalogue">Master table: the entire parallel catalogue&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Stage / component&lt;/th>
&lt;th>OSS on-premise (the blog&amp;rsquo;s reference)&lt;/th>
&lt;th>AWS&lt;/th>
&lt;th>GCP&lt;/th>
&lt;th>Azure&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Data&lt;/strong>&lt;/td>
&lt;td>DVC + lakeFS + MinIO + Qdrant + Kafka + Debezium&lt;/td>
&lt;td>S3 + Lake Formation + OpenSearch / Aurora pgvector + MSK + DMS&lt;/td>
&lt;td>GCS + Dataplex + Vertex Vector Search + Pub/Sub + Datastream&lt;/td>
&lt;td>ADLS Gen2 + Purview + Azure AI Search + Event Hubs + ADF&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Data versioning (cross-cutting)&lt;/strong>&lt;/td>
&lt;td>DVC + lakeFS + OpenLineage&lt;/td>
&lt;td>S3 Versioning + Lake Formation + Glue Catalog&lt;/td>
&lt;td>GCS Versioning + Dataplex lineage&lt;/td>
&lt;td>ADLS versioning + Purview&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Tune&lt;/strong>&lt;/td>
&lt;td>HF Transformers + PEFT + bitsandbytes + MLflow + Ray/Kubeflow&lt;/td>
&lt;td>SageMaker Training + HyperPod + Bedrock Custom + SM Experiments&lt;/td>
&lt;td>Vertex AI Training + Vertex Tuning + Vertex Experiments&lt;/td>
&lt;td>Azure ML Training + Azure OpenAI fine-tuning + Azure ML + MLflow&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Eval&lt;/strong>&lt;/td>
&lt;td>DeepEval + RAGAS + Promptfoo + Langfuse Evals + NeMo Guardrails&lt;/td>
&lt;td>Bedrock Model Evaluation + Bedrock Guardrails + SageMaker Clarify&lt;/td>
&lt;td>Vertex AI Evaluation Service + Model Armor + Gemini safety&lt;/td>
&lt;td>Azure AI Evaluation SDK + Content Safety (Prompt Shields, Groundedness)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Deploy&lt;/strong>&lt;/td>
&lt;td>vLLM + KServe + LLM Operators + Envoy AI Gateway&lt;/td>
&lt;td>Bedrock + SageMaker Endpoints (+ Inferentia / Trainium)&lt;/td>
&lt;td>Vertex AI Prediction + Gemini API (+ TPU)&lt;/td>
&lt;td>Azure OpenAI + Azure ML Endpoints (+ Maia)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Observe&lt;/strong>&lt;/td>
&lt;td>OTel + Tempo + Prometheus + Loki + Langfuse + Phoenix + Hubble&lt;/td>
&lt;td>CloudWatch + X-Ray + ADOT + AMP/AMG + SM Model Monitor&lt;/td>
&lt;td>Cloud Monitoring + Cloud Trace + Vertex Model Monitoring&lt;/td>
&lt;td>Azure Monitor + App Insights + Azure ML Model Monitor&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Retrain&lt;/strong>&lt;/td>
&lt;td>Airflow / Argo / Kubeflow Pipelines + Argilla + Feast&lt;/td>
&lt;td>SageMaker Pipelines + Ground Truth + A2I + SM Feature Store&lt;/td>
&lt;td>Vertex AI Pipelines + Data Labeling + Vertex Feature Store&lt;/td>
&lt;td>Azure ML Pipelines + Data Labeling + Azure ML Feature Store&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Prompt versioning (cross-cutting)&lt;/strong>&lt;/td>
&lt;td>Langfuse Prompts + MLflow Prompt Registry&lt;/td>
&lt;td>Bedrock Prompt Management + SM Prompt Hub&lt;/td>
&lt;td>Vertex AI Prompt Management&lt;/td>
&lt;td>Azure AI Foundry Prompt flow&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="when-to-choose-each-side--the-real-decision">When to choose each side — the real decision&lt;/h2>
&lt;p>The right question is not &lt;em>&amp;ldquo;OSS or cloud?&amp;rdquo;&lt;/em>. It is stage by stage.&lt;/p>
&lt;p>The &lt;strong>OSS side wins by default&lt;/strong> when there is:&lt;/p>
&lt;ul>
&lt;li>Data subject to ENS HIGH category, NIS2 essential sectors or equivalents (identifiable health data, regulated banking, critical infrastructure). Here provider traceability and the processing contract are not negotiable; using a service whose operator is subject to the Cloud Act, FISA 702 or equivalent compromises the legal basis.&lt;/li>
&lt;li>A requirement for auditable inspection of the model, the guardrails and the whole pipeline. If a regulator asks &lt;em>&amp;ldquo;how exactly does it detect PII?&amp;rdquo;&lt;/em> and an answer that ends in open code is mandatory.&lt;/li>
&lt;li>Large volumes with stable workloads. Above a certain threshold of tokens per month, the cost of Bedrock / AOAI / Vertex diverges from the amortised cost of your own GPU cluster. The threshold depends on the workload, but it typically sits between 5 and 50 billion tokens per month for models in the Llama 70B range.&lt;/li>
&lt;li>Roadmap independence as a priority. The day the provider discontinues a model or raises the price by 40 %, the organisation has to be able to ignore it.&lt;/li>
&lt;/ul>
&lt;p>The &lt;strong>hyperscaler side wins by default&lt;/strong> when there is:&lt;/p>
&lt;ul>
&lt;li>Critical time-to-market, an MVP in weeks. The operational weight of the OSS stack is too much for a project that has not yet proved product-market fit.&lt;/li>
&lt;li>A small team with no SREs / MLEs specialised in GPU inference. Operating KServe + vLLM + KV cache + multi-tenancy is not trivial; if the team cannot sustain it, hosting is the way.&lt;/li>
&lt;li>Variable workloads / unpredictable spikes. Bedrock on-demand and SageMaker serverless charge for what you use; your own cluster pays for the GPU whether it is busy or not.&lt;/li>
&lt;li>A need for specific proprietary models (Claude, GPT-4.1, Gemini Pro) with no acceptable OSS equivalent for the case.&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Mixed stages&lt;/strong> are frequent and reasonable. In practice, a common pattern in 2026 is: data, observe and retrain in self-hosted OSS (lineage and sovereignty), tune in OSS on your own cluster, eval in OSS + managed guardrails depending on the safety profile, deploy managed for proprietary models and self-hosted for open models. The question to ask for each stage is: &lt;em>&amp;ldquo;if the provider raises prices by 50 % or discontinues a component tomorrow, how much does it cost to move it?&amp;rdquo;&lt;/em>. The parallel catalogue in this post gives the answer for each box.&lt;/p>
&lt;h2 id="what-we-have-not-covered-yet">What we have not covered (yet)&lt;/h2>
&lt;p>Some pieces still deserve a post of their own:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>OpenAI / Anthropic API directly&lt;/strong> (not through Bedrock or AOAI): another level of managed, another contract.&lt;/li>
&lt;li>&lt;strong>Serious hybrids&lt;/strong>: AWS Outposts, GCP Distributed Cloud, Azure Stack HCI / Azure Local, the hyperscaler in your own room.&lt;/li>
&lt;li>&lt;strong>Per-tenant cost accounting&lt;/strong> compared OSS vs cloud: how the bill is built and where attribution breaks.&lt;/li>
&lt;li>&lt;strong>A real migration&lt;/strong> OSS → cloud or cloud → OSS: steps, scripts, gotchas.&lt;/li>
&lt;li>&lt;strong>Concrete European sovereignty&lt;/strong>: GAIA-X, EuroHPC, the European cloud offering (OVHcloud, Scaleway, IONOS, Aruba), compared with the big three for ENS / NIS2 cases.&lt;/li>
&lt;li>&lt;strong>AWS Inferentia / Trainium, GCP TPU v6 Trillium, Azure Maia&lt;/strong>: in-house chips and how they change the cost / token calculation.&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/anatomy-llm-request-production-tour-six-stages/">Anatomy of an LLM request in production&lt;/a> — the forensic walk of a request through the six stages, the thread from which this post makes the vertical cut.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/oss-catalogue-llmops-six-stages-tool-by-tool/">The OSS catalogue for LLMOps in six stages: tool by tool&lt;/a> — the zoom into the open source side of this post&amp;rsquo;s master table: a card of about 150 words per core OSS tool (vLLM, Langfuse, DVC, Qdrant, Airflow, NeMo Guardrails, Presidio…), licence and governance, a decision matrix per stage and a diagram of the connected OSS stack.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/six-stage-llmops-pipeline-architecture-and-deep-dive/">The six-stage LLMOps pipeline&lt;/a> — the master map that this catalogue complements.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/mlops-for-llms-landscape-2026/">MLOps specific to LLMs in 2026&lt;/a> — general context on why LLMOps is not classic MLOps.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/data-versioning-llmops-dvc-lakefs-reproducible-golden-dataset/">Data versioning with DVC and lakeFS&lt;/a> — the deep-dive on the Data cross-cutting piece.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/prompt-versioning-contract-five-word-change/">Prompt versioning with Langfuse and MLflow&lt;/a> — the deep-dive on the Prompt cross-cutting piece.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/continuous-fine-tuning-production-real-traffic-deployed-adapter/">Continuous fine-tuning in production&lt;/a> — Tune in detail.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/evals-llm-layer-after-tracing/">Evals: the layer after tracing&lt;/a> — Eval in detail.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/llm-guardrails-safety-four-lines-of-defence/">Guardrails and safety in LLMs&lt;/a> — the safety layer in detail.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/kv-cache-working-memory-llm-inference/">KV cache&lt;/a> · &lt;a href="https://blog.lo0.es/en/posts/pagedattention-vllm-block-manager/">PagedAttention&lt;/a> · &lt;a href="https://blog.lo0.es/en/posts/disaggregated-serving-prefill-decode-specialised-pods/">Disaggregated serving&lt;/a> — Deploy from the inside.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/vllm-kubernetes-llm-inference-that-scales/">vLLM on Kubernetes&lt;/a> · &lt;a href="https://blog.lo0.es/en/posts/llm-inference-operators-kubernetes/">LLM operators on K8s&lt;/a> · &lt;a href="https://blog.lo0.es/en/posts/gpu-cluster-as-multi-tenant-platform/">Multi-tenant GPU cluster&lt;/a> — Deploy in operation.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/agentsight-llm-tracing-ebpf-zero-instrumentation/">AgentSight LLM tracing&lt;/a> · &lt;a href="https://blog.lo0.es/en/posts/mcp-deep-observability-opentelemetry/">MCP observability with OTel&lt;/a> · &lt;a href="https://blog.lo0.es/en/posts/ebpf-local-inference-statistical-drift-detection/">eBPF + drift&lt;/a> — Observe in its layers.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/retrain-closing-the-loop-feedback-dataset-adapter/">Retrain: closing the loop feedback → dataset → adapter&lt;/a> — Retrain in detail.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/en/posts/iso-42001-aims-on-premise-llm-platform/">ISO/IEC 42001: the operations manual of the AI system&lt;/a> — the lock-in and contractual sovereignty analysis in this post feeds directly into Annex A.10 (third parties and customer relationships) of the AIMS; the AI supplier register demands documenting exactly what is compared here column by column.&lt;/li>
&lt;/ul>
&lt;h2 id="references">References&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://docs.vllm.ai/">vLLM documentation&lt;/a> and &lt;a href="https://github.com/vllm-project/production-stack">vLLM Production Stack&lt;/a> — the reference OSS inference engine.&lt;/li>
&lt;li>&lt;a href="https://docs.aws.amazon.com/bedrock/">Amazon Bedrock documentation&lt;/a> — AWS managed model catalogue, Knowledge Bases, Guardrails and Prompt Management.&lt;/li>
&lt;li>&lt;a href="https://docs.aws.amazon.com/sagemaker/">Amazon SageMaker AI&lt;/a> — training, endpoints, pipelines, model monitoring.&lt;/li>
&lt;li>&lt;a href="https://cloud.google.com/vertex-ai/docs">Google Vertex AI documentation&lt;/a> — training, prediction, evaluation, model monitoring.&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/azure/ai-foundry/">Azure AI Foundry documentation&lt;/a> — Microsoft&amp;rsquo;s unified plane for AI applications.&lt;/li>
&lt;li>&lt;a href="https://learn.microsoft.com/azure/ai-services/openai/">Azure OpenAI Service documentation&lt;/a> — OpenAI models hosted on Azure.&lt;/li>
&lt;li>&lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/">OpenTelemetry GenAI semantic conventions&lt;/a> — the standard that stitches observability across the OSS / cloud boundary.&lt;/li>
&lt;li>&lt;a href="https://langfuse.com/docs">Langfuse documentation&lt;/a> and &lt;a href="https://docs.arize.com/phoenix">Arize Phoenix&lt;/a> — the reference OSS LLM observability tools.&lt;/li>
&lt;li>&lt;a href="https://dvc.org/">DVC&lt;/a> and &lt;a href="https://lakefs.io/">lakeFS&lt;/a> — OSS data versioning.&lt;/li>
&lt;li>&lt;a href="https://docs.nvidia.com/nemo/guardrails/">NeMo Guardrails&lt;/a> — OSS safety + dialog policy.&lt;/li>
&lt;li>ENS (Esquema Nacional de Seguridad) and NIS2 (Network and Information Security Directive 2) — the compliance frameworks that have the last word in the OSS vs cloud choice for regulated EU customers.&lt;/li>
&lt;/ul></description></item></channel></rss>