Het Kubernetes-moment van open-weight AI: wat het betekent voor .NET-teams
Open-weight modellen worden een verplaatsbare basislaag, net zoals Kubernetes dat werd. Een .NET-freelancer over self-hosted inference, een OpenAI-compatibele endpoint vanuit C#, en hoe je dit niet verpest.
Jean-Pierre Broeders
Freelance .NET Developer
Tobi Knaup zette vandaag een stelling op de voorpagina van Hacker News die ik maar niet loslaat: open-weight AI beleeft zijn Kubernetes-moment (discussie, 186 punten, 130 reacties). De ondertitel is het stuk dat telt: "Let's not ruin it."
De vergelijking snijdt hout. Kubernetes won niet omdat het de beste scheduler was in een of ander abstract opzicht. Het won omdat het een verplaatsbare basislaag werd waar iedereen op kon bouwen. Zodra je een stabiele API had voor "draai deze container, houd hem draaiend, stuur er verkeer naartoe", groeide er een hele wereld van operators, controllers en platformen bovenop. Die basislaag trok meer innovatie aan dan de makers ooit zelf hadden kunnen leveren.
Knaup zegt: open-weight modellen bereiken hetzelfde kantelpunt. Een downloadbare set gewichten met een stabiele, OpenAI-compatibele serving-interface begint verdacht veel op een container-image te lijken. Verplaatsbaar, self-hostbaar, en een fundament dat anderen uitbouwen. En dat "let's not ruin it" is een waarschuwing. Kubernetes verdronk bijna in zijn eigen complexiteit en in de landjepik van leveranciers. Open weights kunnen dezelfde kant op.
Ik wil dat idee serieus nemen vanuit een .NET-hoek, want de meeste teams waarmee ik werk zijn geen GPU-boeren. Het zijn teams die bedrijfssoftware bouwen en die nu moeten beslissen of "roep de OpenAI-API aan" nog steeds de standaard is, of dat een zelf gehost model achter de eigen firewall opeens de saaie, verstandige keuze is.
Waarom de Kubernetes-vergelijking echt klopt
Denk even na over wat een container-image verplaatsbaar maakte. Geen magie. Een grens: een goed gedefinieerd artefact plus een goed gedefinieerd runtime-contract. Je kon een image bouwen op je laptop, hem naar een registry pushen, en hem draaien op andermans cluster zonder iets van hun hardware te weten.
Open-weight modellen hebben nu allebei die helften. Het artefact is het gewichtenbestand. Het runtime-contract, en dit is het onderschatte deel, is de OpenAI chat-completions API. Als vLLM, Ollama, llama.cpp en TGI allemaal dezelfde vorm van /v1/chat/completions aanbieden, dan stopt je applicatie met zich druk maken over welke engine er achter die endpoint zit. Je wisselt het model zoals je een container-image wisselt, en je aanroepende code verandert niet.
Dat is de hele truc. De interface is de basislaag. Je .NET-service praat met een URL, en achter die URL draai je vandaag een gehoste API, volgend kwartaal een self-hosted model van 70 miljard parameters, en het kwartaal daarna een fijn afgesteld kleiner model. Zelfde code.
De .NET-kant is bijna saai, en dat is precies goed
Dit stuk vind ik mooi. Als je inference-endpoint het OpenAI-protocol spreekt, heb je geen speciale "self-hosted AI" SDK nodig. Je wijst de officiële client naar je eigen adres.
using OpenAI;
using OpenAI.Chat;
using System.ClientModel;
// De endpoint kan vLLM, Ollama of een gehoste API zijn.
// Je code weet het niet en het boeit je code niet.
var options = new OpenAIClientOptions
{
Endpoint = new Uri("https://llm.internal.mijnbedrijf.nl/v1")
};
// Self-hosted engines accepteren meestal elke niet-lege sleutel,
// of een token dat je zelf uitgeeft. Zet hem nooit hard in code.
var apiKey = Environment.GetEnvironmentVariable("LLM_API_KEY")!;
var chat = new ChatClient(
model: "qwen3-72b-instruct",
credential: new ApiKeyCredential(apiKey),
options: options);
ChatCompletion completion = await chat.CompleteChatAsync(
new SystemChatMessage("Je bent een bondige assistent. Antwoord in één zin."),
new UserChatMessage("Vat samen wat een idempotency key is."));
Console.WriteLine(completion.Content[0].Text);
De enige regel die je aan een leverancier bindt is de Endpoint. Dat is de naad. In een nette opzet komt hij uit configuratie, niet uit code. Overstappen van een gehost model naar een self-hosted model is dan een configwijziging en een redeploy.
Bouw je iets groters dan een scriptje? Dan pak ik Microsoft.Extensions.AI in plaats van rechtstreeks met de client te praten. Je krijgt er een provider-onafhankelijke IChatClient mee die je in DI registreert en kunt decoreren.
using Microsoft.Extensions.AI;
using OpenAI;
builder.Services.AddChatClient(sp =>
{
var cfg = sp.GetRequiredService<IConfiguration>();
var openAiClient = new OpenAIClient(
new ApiKeyCredential(cfg["Llm:ApiKey"]!),
new OpenAIClientOptions { Endpoint = new Uri(cfg["Llm:Endpoint"]!) });
return openAiClient
.GetChatClient(cfg["Llm:Model"]!)
.AsIChatClient();
})
.UseLogging()
.UseFunctionInvocation();
Nu injecteert je bedrijfscode een IChatClient en noemt nooit meer OpenAI, vLLM of een URL. Dat is precies de ontkoppeling die de Kubernetes-analogie belooft. De basislaag ligt vast, dus je applicatie zit niet vastgelast aan één leverancier.
Self-hosten loont pas voorbij een echte drempel
Even de spelbreker uithangen, want de Hacker News-draad zat vol mensen die self-hosting probeerden en stilletjes terugkropen naar een gehoste API.
Het model is zelden het probleem. De economie wel. Een self-hosted GPU-node rekent hetzelfde af of hij nu tien verzoeken of tienduizend verzoeken bedient. Een gehoste API rekent per token af. Er is dus een omslagpunt, en daaronder is self-hosten gewoon een duurdere manier om slechtere latency te krijgen. In de cijfers die mensen deelden ligt het break-evenpunt soms bij miljarden tokens per maand voordat een self-hosted node echt goedkoper is dan een endpoint waar je per gebruik voor betaalt.
De eerlijke beslisboom is dus kort. Host je zelf, doe het dan om één van drie redenen: data die je muren wettelijk niet mag verlaten, latency die je op geen andere manier haalt, of volume dat zo hoog is dat prijs-per-token de dikste regel op je cloudrekening is geworden. Klopt geen daarvan? Blijf de gehoste API aanroepen en besteed je engineeringtijd ergens anders aan. GPU's draaien voor de bühne is een hobby, geen architectuur.
Het runtime-contract, in Kubernetes
Kruis je die drempel wel, dan stopt de analogie een metafoor te zijn en wordt hij letterlijke YAML. Het patroon dat is uitgekristalliseerd: een serving-engine zoals vLLM, in een pod met een GPU, die de OpenAI-compatibele API aanbiedt achter een gewone Service.
apiVersion: apps/v1
kind: Deployment
metadata:
name: qwen3-72b
spec:
replicas: 1
selector:
matchLabels: { app: qwen3-72b }
template:
metadata:
labels: { app: qwen3-72b }
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
args:
- "--model=Qwen/Qwen3-72B-Instruct"
- "--served-model-name=qwen3-72b-instruct"
- "--max-model-len=8192"
ports:
- containerPort: 8000
resources:
limits:
nvidia.com/gpu: "2"
readinessProbe:
httpGet: { path: /health, port: 8000 }
initialDelaySeconds: 60
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: qwen3-72b
spec:
selector: { app: qwen3-72b }
ports:
- port: 80
targetPort: 8000
Je .NET-service wijst Llm:Endpoint naar http://qwen3-72b/v1 en klaar. De gewichten staan op persistente opslag, de readiness-probe houdt verkeer weg bij een pod die nog 140GB aan model in het VRAM staat te laden, en het geheel lijkt op elke andere stateful workload op het cluster.
Hier vormt zich ook de nieuwste laag. Tools zoals Modelplane en de vLLM production stack beginnen voor inference te doen wat Deployments en HPA's deden voor stateless webapps: declaratieve model-uitrol, gewogen canary-verkeer tussen twee modelversies, weight caching per node, en OpenAI-compatibele endpoints ongeacht de engine eronder. Dat klinkt als een operator-patroon, en dat is precies de bedoeling. Het inference control plane wordt op de Kubernetes-manier gebouwd, want Kubernetes leerde iedereen al de vorm van het antwoord.
Behandel het model als wankele infrastructuur, want dat is het
Eén gewoonte waar gehoste API's je lui in maken: veerkracht. Een self-hosted node herstart, loopt vol VRAM, of wordt herplant op een node zonder GPU en hangt. Je aanroepende code moet ervan uitgaan dat de endpoint zich misdraagt.
Ik wikkel elke inference-aanroep in een resilience pipeline. Timeouts eerst, want een vastgelopen GPU kan een verzoek eindeloos laten hangen, en een naïeve await blokkeert vrolijk een request-thread tot je thread pool op is.
using Microsoft.Extensions.Http.Resilience;
builder.Services.AddChatClient(/* ... */)
.Services
.AddHttpClient("llm")
.AddResilienceHandler("llm-pipeline", pipeline =>
{
pipeline.AddTimeout(TimeSpan.FromSeconds(30));
pipeline.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 2,
BackoffType = DelayBackoffType.Exponential,
UseJitter = true
});
pipeline.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
SamplingDuration = TimeSpan.FromSeconds(30),
BreakDuration = TimeSpan.FromSeconds(15)
});
});
De circuit breaker telt hier zwaarder dan bij een gehoste API. Als je enige GPU-node omvalt, wil je niet dat duizend verzoeken in de wachtrij elk 30 seconden staan te wachten. Je wilt snel falen, load afwerpen, en het liefst terugvallen op een kleiner model of een gecacht antwoord. Ontwerp voor een endpoint die plat ligt, want op je eigen hardware zal dat gebeuren.
En wat is dan dat "let's not ruin it"
Knaups waarschuwing is de zin die ik op de muur zou plakken. Wat Kubernetes waardevol maakte was het stabiele, saaie contract. Wat het bijna verpestte was iedereen die er propriëtaire complexiteit aan vastschroefde tot verplaatsbaarheid stilletjes doodging.
Open weights staan op dezelfde splitsing. De waarde is de gedeelde interface: elk model, elke engine, achter dezelfde API, verwisselbaar per config. We verpesten het op het moment dat we "werkt geweldig, maar alleen op onze speciale serving-stack, met onze speciale extensies, tegen onze speciale prijs" accepteren. Zodra je code afhangt van de niet-standaard endpoint van één leverancier, heb je de verplaatsbaarheid weggegooid die het geheel interessant maakte.
Voor een .NET-team is de verdediging goedkoop en die zou ik op dag één inbouwen. Laat je applicatie tegen IChatClient praten. Houd de endpoint en de modelnaam in config. Laat een leverancierspecifieke feature nooit voorbij de abstractie lekken zonder een hele goede reden en een hele luide comment. Dan blijft de basislaag een basislaag, en is overstappen van een gehoste API naar een self-hosted model, of tussen twee open-weight modellen, een dinsdagmiddag en geen herschrijving.
Dat is de hele belofte van een Kubernetes-moment. Niet dat iedereen zelf gaat hosten, maar dat je later mag kiezen, zonder dat je code er iets van merkt. Bewaak de naad en je houdt de keuze.
