GPU's zijn duur. Dat is niet echt nieuws.Wat wel opviel: Cast AI publiceerde recent zijn State of Kubernetes Optimization Report en zag gemiddeld ongeveer 5% GPU-utilisation in tienduizenden niet-geoptimaliseerde Kubernetes-clusters.

Een concreet voorbeeld: de GPU Server-n2.14d.g1-EU01 van STACKIT bevat 1× NVIDIA L40S met 48 GB geheugen en stond op 17 september 2026 publiek geprijsd op €1,74 per uur exclusief btw. Bij 5% duty cycle komt dat neer op ongeveer €34,86 per actief GPU-uur. Actief is daarbij nog altijd niet hetzelfde als nuttig of productief werk.
Een reservation of committed-use discount van 30% gaat dat niet oplossen. Dan krijg je korting op capaciteit die je voor 95% niet gebruikt (maar bon, ze stond wel mooi gereserveerd).
Bij CPU's zijn we gewoon om naar utilisation te kijken en daar min of meer conclusies uit te trekken. Bij GPU's is dat minder eenvoudig.
Een metric zoals DCGM_FI_DEV_GPU_UTIL meet hoeveel tijd er minstens één kernel actief was. Ze zegt niet hoeveel Streaming Multiprocessors nuttig werk deden, of de workload vooral op geheugen zat te wachten, hoeveel tokens per seconde je model produceerde, of de job überhaupt iets opleverde.
Een GPU kan volgens die metric druk bezig zijn en toch slecht presteren. Omgekeerd kan een inference workload korte bursts hebben, goede latency halen en toch een lage gemiddelde duty cycle tonen.
GPU-utilisation is dus nuttig, maar niet voldoende. DCGM_FI_PROF_SM_ACTIVE meet welk deel van de tijd minstens één warp actief was op een Streaming Multiprocessor. DCGM_FI_PROF_DRAM_ACTIVE meet hoe actief de device-memory-interface was. Combineer die met framebuffer- of geheugengebruik, power draw en een applicatiemetric zoals tokens per seconde, jobs per uur, queue depth of latency.
Meet dus niet alleen of de GPU actief was. Meet wat je ervoor terugkreeg.
De klassieke NVIDIA device plugin biedt een GPU aan Kubernetes aan als een integer resource: nvidia.com/gpu: 1.
Wanneer een pod die GPU aanvraagt, reserveert Kubernetes de volledige kaart voor die pod. Dat gebeurt voordat de container haar eerste CUDA-call uitvoert. Image pulls, init containers, model downloads en het laden van model weights in GPU memory gebeuren allemaal terwijl de GPU al toegewezen kan zijn.
Daarna blijft de kaart toegewezen zolang de pod bestaat. Ook wanneer de applicatie wacht op requests, een lege queue heeft of gewoon verkeerd geconfigureerd is.
Kubernetes weet dus vrij goed welke pod de GPU bezit. Het weet daarom nog niet of die pod de GPU gebruikt.
Dat verschil tussen "allocated" en "active" ontbreekt in veel Kubernetes- en FinOps-dashboards. CPU requests naast CPU usage zetten we intussen overal. Bij GPU's kijken teams vaak alleen naar hoeveel kaarten ze hebben en hoeveel ze kosten.
Meet daarom GPU-seconds allocated naast GPU-seconds active. Voeg daar initialisation time en de applicatie-output aan toe. Anders optimaliseer je op een cijfer dat maar een kwart van het verhaal vertelt.
Veel verloren GPU-tijd zit vóór de eerste CUDA-call. Kubernetes heeft de kaart dan al toegewezen, terwijl de container nog moet starten en het model nog onderweg is.
Begin saai: bouw slanke images, gebruik multi-stage builds en stop model weights niet in je container image. Een image van tientallen gigabytes wordt niet plots een goed idee omdat je registry snel is.
Zet model weights in object storage in dezelfde regio of op attached block storage. Object storage is eenvoudig en goedkoop, maar de eerste download kost tijd. Block storage start voorspelbaarder, maar kost ook wanneer er geen pod draait. Lokale NVMe, een node-cache of preloading maakt cold starts sneller, in ruil voor extra opslag, cache-invalidatie en capaciteit die je vooraf moet klaarzetten.
De grote drie hebben elk een vorm van lazy loading. Op AWS doet SOCI lazy loading van container images; Run:ai Model Streamer streamt model weights uit S3. GKE heeft image streaming en documenteert Model Streamer met GCS. AKS heeft Artifact Streaming. Dat kan time-to-first-byte verbeteren, maar het is geen vrijgeleide voor gigantische images: alles wat je uiteindelijk nodig hebt, moet nog altijd over het netwerk en door de storage stack.
Voor STACKIT is geen beheerd equivalent gedocumenteerd. Hou images klein, zet de registry dichtbij, gebruik de containerd-cache en pre-stage model storage waar de startup-SLO dat vereist.
Meet dit traject apart: pod scheduled, containers ready, eerste CUDA-call en app ready. Eén metric "initialisation time" zegt anders vooral dat er ergens tijd verdween.
Ja. Alleen betekent "delen" drie verschillende dingen.
Bij time-slicing krijgen meerdere workloads om de beurt rekentijd op dezelfde fysieke GPU. Dit werkt goed voor notebooks, development en bursty inference workloads die veel wachten.
Het geheugen blijft gedeeld. Eén workload kan dus de volledige HBM-pool opsouperen en de andere workloads met een Out Of Memory achterlaten. Er is ook geen harde fault isolation, en bij de klassieke device-pluginconfiguratie kun je DCGM-gebruik niet betrouwbaar per container toewijzen.
Gebruik time-slicing dus voor workloads binnen dezelfde trust boundary, waar variabele latency en beperkte isolatie aanvaardbaar zijn. Niet omdat iemand "fractional GPU" in een offerte heeft geschreven.
Multi-Instance GPU splitst ondersteunde NVIDIA-kaarten op in hardwarematig geïsoleerde stukken, met eigen compute, geheugen en memory bandwidth. Dat maakt MIG veel geschikter voor productie-inference en verschillende tenants.
De keerzijde: niet elke GPU ondersteunt MIG, je moet vooraf profielen kiezen en een verkeerde partitionering laat alsnog capaciteit liggen. Een workload die anderhalve slice nodig heeft, krijgt er nog altijd twee.
Kies MIG wanneer isolatie en voorspelbaarheid belangrijker zijn dan maximale flexibiliteit.
NVIDIA Multi-Process Service laat CUDA-processen gelijktijdig uitvoeren in plaats van ze alleen af te wisselen. Dat kan efficiënter zijn voor kleine, samenwerkende workloads, maar de isolatie is niet vergelijkbaar met MIG en de operationele ondersteuning verschilt per platform.
MPS is bruikbaar wanneer je de workloads vertrouwt en begrijpt. Als "we zetten MPS aan en zien wel" het plan is, is er nog geen plan.
Alle grote public clouds hebben intussen de bouwstenen. Geen van hen factureert alleen het nuttige CUDA-werk. Je betaalt de VM of node zolang die bestaat, ook als de GPU zit te wachten.
EKS ondersteunt DCGM, time-slicing, MIG, MPS en de nieuwere Dynamic Resource Allocation. KEDA kan inference pods schalen; Karpenter kan daarna GPU-nodes bijmaken of verwijderen. Spot Instances zijn bruikbaar voor onderbreekbare training en batch jobs.
Er zitten wel scherpe randen aan de combinaties. De recentste DRA-functionaliteit werkt niet zonder meer samen met dynamische Karpenter-capaciteit of EKS Auto Mode. Time-slicing maakt per-pod attribution moeilijker. MIG is alleen beschikbaar op ondersteunde GPU-families.
Op AWS zou ik beginnen met DCGM plus applicatiemetrics, daarna pods schalen op queue depth of latency, en Karpenter de nodes laten volgen. Pas daarna kies je sharing en aankoopmodel.
GKE heeft momenteel de meest afgewerkte combinatie. Het documenteert time-sharing, MIG en MPS als aparte strategieën en kan DCGM-metrics beheerd naar Cloud Monitoring sturen. GKE toont ook hoeveel accelerator-resources containers aanvragen en wat de gemeten duty cycle is.
Autopilot kan GPU-nodes automatisch provisionen, maar maakt GPU's niet "serverless": GPU workloads worden uiteindelijk op de volledige node afgerekend. Bij time-sharing en MPS ontbreken bovendien bepaalde container-level metrics. MIG heeft dan weer eigen observability-beperkingen.
Gebruik GKE als je zo veel mogelijk van de GPU-stack beheerd wil afnemen. Verwacht niet dat Autopilot slecht gekozen requests voor je oplost.
AKS ondersteunt dezelfde basiskeuzes voor GPU-partitionering: MIG, time-slicing, MPS, DCGM, KEDA en node autoscaling. In zijn observability-richtlijnen voor GPU's raadt Microsoft expliciet aan om GPU-seconds used tegenover GPU-seconds allocated te zetten. Dat is exact de metric die ik in de meeste dashboards mis.
Een deel van de managed GPU node pools, managed MIG en managed observability zit vandaag wel nog in preview. Time-slicing en MPS blijven grotendeels werk voor de NVIDIA GPU Operator.
Gebruik AKS dus gerust als Azure je logische cloud partner is, maar controleer per feature of ze werkelijk supported en production-ready is. Een pagina in Microsoft Learn is nog geen SLA, laat staan een service definitie.
STACKIT heeft GPU-machine types met onder meer A100, L40S en H100, en je kunt die als nodepool in STACKIT Kubernetes Engine gebruiken. De gedocumenteerde aanpak installeert de NVIDIA GPU Operator op Ubuntu GPU-nodes.
Daarmee heb je de NVIDIA device plugin, drivers en DCGM Exporter. Via de Prometheus Operator en een ServiceMonitor of PodMonitor kun je de metrics naar STACKIT Observability sturen en in Grafana tonen.
Dat is een bruikbare basis, maar STACKIT abstraheert vandaag minder van de GPU-stack dan GKE. Fractionele GPU's, managed MIG, time-slicing, MPS, DRA en GPU-specifieke health remediation zijn niet als beheerde STACKIT-productfeatures gedocumenteerd. Een aantal daarvan kun je technisch via de NVIDIA Operator bouwen, maar dan beheer je ze zelf.
Ook automatisch naar nul schalen van een aparte GPU-nodepool is niet duidelijk gedocumenteerd. Volledige clusterhibernatie kan de workers wel naar nul brengen, maar de control plane blijft bestaan en ontwaken duurt enkele minuten. Dat is interessant voor development- of batchclusters, minder voor online inference.
Op STACKIT zou ik dus starten met een aparte GPU-nodepool, taints en tolerations, GPU Operator, DCGM en remote write naar Observability. Test daarna MIG op de exacte A100- of H100-flavor die je gebruikt. Voeg time-slicing alleen toe voor workloads waar gedeeld geheugen en zwakkere isolatie geen probleem zijn.
De DRA-updates in Kubernetes 1.37 maken Dynamic Resource Allocation bruikbaarder. Device taints en tolerations zijn stable, waardoor een driver of administrator één slechte GPU kan markeren zonder de volledige node buiten gebruik te nemen. Bestaande workloads kunnen van zo'n device verwijderd worden, tenzij ze de taint expliciet tolereren.
Daarnaast is er nu een standaard resource.kubernetes.io/numaNode attribuut. Drivers van NVIDIA, AMD en andere leveranciers hoeven dus niet elk hun eigen naam voor dezelfde topology-informatie te verzinnen.
Dat helpt bij health en placement. Het verhoogt de utilisation niet vanzelf. Een perfect beschreven idle GPU blijft idle.
Mijn volgorde zou altijd deze zijn:
Cloudkortingen verlagen je prijs per gereserveerd uur. Ze maken dat uur niet nuttiger.
Bij Krane Labs kijken we daarom niet alleen naar hoeveel GPU-nodes een cluster heeft. We willen weten hoeveel van die capaciteit gereserveerd, actief en productief is, en waarom het verschil bestaat. Pas dan kun je scheduling, sharing en autoscaling correct ontwerpen.
De GPU is misschien duur. De kaart op 5% laten draaien en vervolgens fier melden dat je 30% reservation discount kreeg, is duurder :)