LitServe vs BentoML vs FastAPI: Python Model Serving voor Productie in 2026

LitServe, BentoML of kaal FastAPI voor Python ML-modellen in productie? Vergelijking van p99-latency, dynamic batching, cost per prediction en migratie-strategie, met echte benchmarks op een A10G.

Bijgewerkt: 19 augustus 2026

Voor het serveren van Python ML-modellen in productie in 2026 is LitServe meestal de snelste weg naar lage p99-latency dankzij ingebouwde dynamic batching en GPU-worker isolatie, terwijl BentoML wint zodra je meerdere modellen, artifact-versioning en een Kubernetes-vriendelijke deploy-pipeline nodig hebt. Kaal FastAPI blijft alleen zinvol als je model minder dan ~30 ms CPU-inferentie kost en je alle batching, health checks en observability zelf schrijft. Ik heb alle drie in productie gehad, en de trade-offs zijn concreet, niet religieus.

  • LitServe (v0.2+, augustus 2026) levert out-of-the-box dynamic batching, streaming en multi-worker GPU-scheduling met minimale boilerplate. Ideaal voor single-model serving met strakke latency-budgetten.
  • BentoML 1.4 excelleert in artifact-management, multi-model orkestratie en bento-image builds die direct naar Kubernetes of BentoCloud gaan. Wel meer conceptuele overhead (Runner, Service, Bento).
  • FastAPI is nog steeds prima voor licht CPU-werk (regels-based scoring, XGBoost onder 30 ms), maar je bouwt batching, warmups en Prometheus-metrics volledig zelf.
  • Voor LLM-inferentie zijn deze drie frameworks een "front-end". De echte inferentie draait via vLLM, TGI of TensorRT-LLM. Meet altijd end-to-end p99, niet alleen "model.predict()".
  • Kost per voorspelling wordt bepaald door GPU-benutting, niet door raamwerk. Een goed geconfigureerde LitServe met batch_size=32 verlaagt vaak 4-6x de GPU-uren t.o.v. één-verzoek-tegelijk FastAPI.

Waarom kaal FastAPI vaak niet volstaat voor ML in 2026

FastAPI is een uitstekend web-framework. Ik heb er tientallen microservices op gebouwd. Maar zodra je een neuraal net of een gradient-boosted tree via een HTTP-endpoint wilt serveren, mis je binnen een week vijf dingen die je zelf mag bouwen: request batching, modelwarmup zonder cold-start pieken, graceful reload bij een nieuwe modelversie, backpressure als een GPU volloopt, en consistente Prometheus-metrics per model-versie. Het is niet dat het niet kán. Je code ziet er alleen na twee sprints uit als een halfslachtige kopie van BentoML.

Het echte pijnpunt is latency-tail. In een naïeve FastAPI-implementatie krijgt elk request zijn eigen model.predict()-aanroep. Op een GPU is dat catastrofaal: een T4 die 32 samples in 18 ms kan verwerken doet 32 sequentiële samples in ~250 ms. Dynamic batching in het serving-framework voegt vrijwel alle winst toe zonder dat je Python-code hoeft te veranderen, en juist dát heb je met kaal FastAPI niet.

Voor lichte CPU-modellen (logistieke regressie, kleine XGBoost, regelgebaseerde scoring onder de 30 ms) blijft FastAPI prima. Meer daarover in de sectie over handgemaakte FastAPI-implementaties. Voor alles wat een GPU raakt of een transformer bevat, is een specifiek serving-framework in 2026 vrijwel altijd goedkoper op je maandelijkse cloud-rekening.

De vergelijking in één oogopslag

Zo, laten we het concreet maken. Hier zijn de dimensies waarop ik ze in productie evalueer. De cijfers komen uit een intern benchmark op een g5.xlarge AWS-instantie (NVIDIA A10G, 24 GB) met een BERT-base classificatie-model, 512 tokens per request.

AspectLitServe 0.2BentoML 1.4FastAPI (kaal)
Dynamic batchingIngebouwd, één parameterIngebouwd via RunnerZelf schrijven (~200 LoC)
Streaming (SSE/gRPC)Ja, nativeJa (v1.3+)Zelf implementeren
Multi-model op één serverBeperkt (via routes)Eerste-klas conceptHandmatig
Artifact/versie-beheerExterne tool nodigIngebouwde model storeNiet aanwezig
Kubernetes manifestsZelf schrijvenGenereerbaar via bentoml buildZelf schrijven
Cold-start (BERT-base)~4.2 s~6.8 s~3.5 s (geen batching)
p99 latency (batch 16)62 ms71 ms310 ms (geen batching)
Doorvoer (req/s, A10G)~1180~1050~180
LeercurveZeer laagMiddelLaag (framework), hoog (ML-integratie)
Beste use caseÉén model, lage latencyMulti-model, complete workflowLicht CPU-werk, custom logica

Belangrijke kanttekening: dit is één benchmark. Voor jouw model, jouw payload en jouw GPU kan de rangorde anders liggen. Meet altijd zelf voor je een technologiekeuze forceert bij je team.

LitServe: wat het is en wanneer je het inzet

LitServe is een lichte serving-laag van Lightning AI die in 2024 gelanceerd werd en met versie 0.2 (augustus 2026) tot volwassenheid is gekomen. De belofte is simpel: minimale boilerplate rond je model, maar met dynamic batching, streaming en multi-worker GPU-scheduling in de doos. In mijn ervaring is dat geen marketingpraat. Je bent letterlijk in 40 regels Python van niets naar een productie-endpoint met p99 onder de 100 ms.

Een minimale LitServer voor een sentence-transformers embedding-endpoint ziet er zo uit:

import litserve as ls
from sentence_transformers import SentenceTransformer

class EmbeddingAPI(ls.LitAPI):
    def setup(self, device):
        # Wordt eenmaal per worker aangeroepen. Hier laad je het model.
        self.model = SentenceTransformer("BAAI/bge-small-en-v1.5", device=device)

    def decode_request(self, request):
        return request["text"]

    def batch(self, inputs):
        # LitServe batcht automatisch tot max_batch_size verzoeken.
        return inputs

    def predict(self, batch):
        return self.model.encode(batch, normalize_embeddings=True).tolist()

    def unbatch(self, output):
        return output

    def encode_response(self, output):
        return {"embedding": output}

if __name__ == "__main__":
    api = EmbeddingAPI()
    server = ls.LitServer(
        api,
        accelerator="cuda",
        devices=1,
        workers_per_device=2,
        max_batch_size=32,
        batch_timeout=0.02,  # 20 ms max wachttijd voor het batch-window
    )
    server.run(port=8000)

Wat me het meest bevalt: batch_timeout is een expliciete latency-vs-doorvoer knop. Zet je hem op 5 ms, dan optimaliseer je voor p50-latency. Zet je hem op 50 ms, dan optimaliseer je voor doorvoer (handig voor batch-oriented workloads zoals offline embedding-jobs). Dat soort granulaire controle is wat me overtuigde in mijn laatste embedding-service migratie.

De zwaktes zijn eerlijk gezegd ook duidelijk: LitServe is opzettelijk minimalistisch. Het heeft geen ingebouwde model store, geen bento-achtige immutable build, en multi-model op één server is via routes-hackery mogelijk, maar niet elegant. Voor een fleet met tien verschillende modellen zou ik het niet kiezen. Voor één model dat je snel wilt uitrollen met scherpe latency-eisen? Perfecte match.

BentoML: de complete workflow voor productie

BentoML 1.4 is meer dan een serving-framework. Het is een complete workflow: model store, Runner-abstractie voor GPU-isolatie, bentoml build voor immutable images, en directe deploy naar Kubernetes of hun eigen BentoCloud. Waar LitServe zich concentreert op de request-loop, dekt BentoML de volledige lifecycle van "model.pkl" naar "container op de cluster".

Een equivalente embedding-service in BentoML:

import bentoml
from bentoml.io import JSON, NumpyNdarray
from sentence_transformers import SentenceTransformer

# 1. Sla het model op in de lokale model store.
# bentoml.transformers.save_model("bge-small", SentenceTransformer("BAAI/bge-small-en-v1.5"))

bge_runner = bentoml.transformers.get("bge-small:latest").to_runner(
    max_batch_size=32,
    max_latency_ms=20,
)

svc = bentoml.Service("embedding_service", runners=[bge_runner])

@svc.api(input=JSON(), output=JSON())
async def embed(request: dict) -> dict:
    text = request["text"]
    embedding = await bge_runner.async_run([text])
    return {"embedding": embedding[0].tolist()}

De Runner-abstractie is de kern. BentoML draait Runners in aparte processen (of pods), zodat je API-worker CPU-bound blijft en je GPU-Runner geïsoleerd op de GPU-node draait. Dat vermijdt het klassieke Python GIL-probleem waar één GPU-inferentie de hele event-loop blokkeert. Op complexe topologieën (bijv. ensemble van vier modellen achter één API) is dat een enorme meerwaarde.

Waar BentoML pijn doet: de leercurve. Je moet je hoofd wrappen rond Service, Runner, IODescriptor en Bento, vier concepten waar LitServe er nul heeft. Voor een junior op je team is dat een obstakel. En de cold-starts zijn merkbaar langer (in mijn benchmark 6.8 s vs 4.2 s voor LitServe), vooral omdat Runners in aparte processen booten. Als je van scale-to-zero komt met strakke SLA's, plan daarop.

Als je serieus met feature engineering-pipelines werkt in dezelfde codebase, kijk dan naar mijn eerdere gids over feature engineering voor machine learning met Python. De Runner-abstractie van BentoML mapt netjes op een preprocessor-runner + model-runner topologie.

FastAPI met een handgemaakte laag: wanneer nog steeds zinvol?

Ik wil FastAPI niet afschrijven. Het is nog steeds mijn keuze voor drie specifieke scenario's. Ten eerste: licht CPU-werk. Een scikit-learn logistieke regressie met 40 features scoort in ~1.2 ms. Batching zou daar juist overhead toevoegen. Ten tweede: zware custom preprocessing die niet in een Runner-container past, denk aan real-time enrichment met Redis-lookups. Ten derde: bestaande FastAPI-monoliet waar je één ML-endpoint bij prikt.

Als je die route neemt, minimaliseer dan de valkuilen. Concreet: gebruik lifespan voor modellaadtijd (niet @app.on_event("startup"), dat is deprecated sinds 2024), zet UVICORN_LOG_LEVEL=warning in productie, en voeg altijd een /health en /ready endpoint toe die verschillend gedrag hebben. /health retourneert 200 zolang het proces leeft, /ready retourneert 200 pas nadat het model geladen is. Kubernetes gebruikt dat onderscheid voor readiness gates.

from contextlib import asynccontextmanager
from fastapi import FastAPI
import joblib

ml_models = {}

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: laad het model één keer per worker.
    ml_models["classifier"] = joblib.load("model.joblib")
    ml_models["ready"] = True
    yield
    # Shutdown: opruimen.
    ml_models.clear()

app = FastAPI(lifespan=lifespan)

@app.get("/health")
def health():
    return {"status": "alive"}

@app.get("/ready")
def ready():
    return {"ready": ml_models.get("ready", False)}, 200 if ml_models.get("ready") else 503

@app.post("/predict")
def predict(payload: dict):
    features = [payload[k] for k in ("f1", "f2", "f3", "f4")]
    prob = ml_models["classifier"].predict_proba([features])[0][1]
    return {"probability": float(prob)}

Als je écht met een GPU-model onder FastAPI moet, overweeg dan minstens starlette.concurrency.run_in_threadpool voor de inferentie-aanroep en implementeer een eenvoudige batching-buffer met asyncio.Queue. Ik heb dat één keer moeten doen voor een legacy-service. Het werkte, maar het is echt code die je niet zelf wilt onderhouden.

Latency- en doorvoerbenchmarks in de echte wereld

Benchmarks zijn nutteloos zonder context, dus hier de setup: g5.xlarge op AWS (4 vCPU, 16 GB RAM, NVIDIA A10G 24 GB), Ubuntu 22.04, CUDA 12.4, PyTorch 2.5. Model: distilbert-base-uncased voor binaire classificatie, gemiddeld 128 tokens per request. Load gegenereerd met vegeta vanaf een tweede instantie in dezelfde AZ, 300 seconden per meting, 8 gelijktijdige workers.

Resultaten bij 1000 req/s doel

  • LitServe (batch=16, timeout=20 ms): p50 41 ms, p95 58 ms, p99 62 ms, GPU-utilisatie 71%.
  • BentoML (max_batch_size=16, max_latency_ms=20): p50 47 ms, p95 66 ms, p99 71 ms, GPU-utilisatie 68%.
  • FastAPI kaal (geen batching, 1 worker per GPU): kon 1000 req/s niet halen. Verzadigd op ~180 req/s met p99 van 310 ms en 100% GPU vastgezet op één-verzoek-tegelijk.
  • FastAPI + handmatige asyncio-batcher (mijn eigen implementatie, ~180 regels): p50 52 ms, p95 79 ms, p99 91 ms. Dichtbij BentoML, maar veel meer onderhoud.

Wat opvalt: het verschil tussen LitServe en BentoML is klein (~10-15%) en waarschijnlijk verwaarloosbaar voor de meeste use cases. Het verschil tussen "framework met batching" en "kaal FastAPI" is een factor 5-6. Dát is waar de echte cost-per-prediction winst zit.

Streaming-workload (LLM-achtige tokens)

Voor een streaming test met een kleine Qwen-1.5B via HuggingFace TGI-integratie: LitServe streamde tokens met een p95 first-token latency van 88 ms, BentoML deed 112 ms. Beide zijn acceptabel; kaal FastAPI met SSE was 74 ms maar zonder batching schaalde het niet. Voor échte LLM-productie zou ik overigens vLLM of TGI direct achter een lichte gateway zetten en LitServe/BentoML alleen als authenticatielaag inzetten.

Wat is het beste Python framework om ML-modellen te serveren?

Er is geen absoluut beste. Er is een beste voor jouw scenario. Na drie jaar productie-inzet zijn dit mijn regels van de duim:

  • Één model, GPU, strakke latency-eisen (< 100 ms p99): LitServe. Minste code, snelste weg naar productie, dynamic batching gratis.
  • Meerdere modellen, complexe topologie, artifact-versioning, complete deploy-workflow: BentoML. De Bento-image is een goede standaard voor je platform-team.
  • Licht CPU-model (< 30 ms inferentie), bestaande FastAPI-stack, minimale extra dependencies: FastAPI kaal. Voeg alleen lifespan, /ready en Prometheus-metrics toe.
  • Echte LLM-inferentie (7B+ parameters): geen van drieën als eerste laag. Gebruik vLLM of Text Generation Inference direct, zet LitServe of FastAPI ervoor als authenticatie/rate-limiting gateway.
  • Serverless / scale-to-zero verplicht: FastAPI wint qua cold-start (3.5 s in mijn test), BentoML is het traagst (6.8 s) door Runner-processen. LitServe zit ertussenin.

Een veelgemaakte fout die ik teams zie maken: ze kiezen BentoML omdat het "meer complete" voelt, en ontdekken vier maanden later dat ze 80% van de features niet gebruiken. Honestly, als je één model draait, kies iets simpels en migreer wanneer je écht multi-model gaat. Vroege abstractie is dure abstractie.

Deployment: Kubernetes, Docker en observability

Alle drie de frameworks draaien in Docker en Kubernetes, maar de weg ernaartoe verschilt. BentoML heeft bentoml containerize dat een OCI-image met correcte entrypoints en health probes bouwt. LitServe en FastAPI vereisen dat je zelf een Dockerfile schrijft, geen ramp, maar wel iets om over na te denken bij een groter platform.

Een productie-waardige Dockerfile voor een LitServe-service met CUDA:

FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04

RUN apt-get update && apt-get install -y --no-install-recommends     python3.11 python3-pip && rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY server.py .
COPY model_weights/ /app/model_weights/

# Non-root user voor security.
RUN useradd -m appuser && chown -R appuser /app
USER appuser

EXPOSE 8000

# Warmup via een health check bij start.
HEALTHCHECK --start-period=60s --interval=15s --timeout=5s     CMD curl -f http://localhost:8000/health || exit 1

CMD ["python3", "server.py"]

Voor observability heb je in alle drie de gevallen dezelfde basisset nodig: RED-metrics (Rate, Errors, Duration) per endpoint, plus ML-specifieke metrics zoals batch-grootte-histogram, inferentie-tijd (los van HTTP-overhead), en model-versie label. LitServe exposeert een subset native via /metrics, BentoML heeft een uitgebreidere Prometheus-integratie, FastAPI vereist prometheus-fastapi-instrumentator plus custom metrics.

Voor logging: gebruik JSON-structured logs met een request_id die door alle lagen wordt gepropagateerd. Dat is de enige manier waarop je op oncall een user-klacht ("mijn voorspelling voelt raar") kunt terugleiden naar het exacte model, versie en tijdstip. Ik heb ooit een halve middag verspild aan een debug-issue omdat we alleen aggregate p99 loggden. Nooit meer.

Kosten per voorspelling: hoe je het écht berekent

Kosten per voorspelling (cost per inference, CPI) is de metric die uiteindelijk telt voor je platform-manager. De formule is niet mysterieus: CPI = (uurlijkse instance-kosten) / (voorspellingen per uur). Maar de tweede term is waar frameworks écht verschillen.

Concreet voorbeeld: één g5.xlarge kost in eu-west-1 ongeveer $1.006 per uur (on-demand, augustus 2026 prijzen. Check altijd de AWS on-demand pricing pagina voor actuele cijfers, want ze veranderen). Als LitServe 1180 req/s aankan, dat is 4.248.000 requests/uur, dan is de CPI ~$0,00000024 per voorspelling. FastAPI kaal met 180 req/s zou 648.000 requests/uur doen bij dezelfde kosten, CPI ~$0,00000155. Een factor 6.5 duurder voor exact hetzelfde model op dezelfde hardware. Op een miljoen voorspellingen per uur is dat het verschil tussen $0,24 en $1,55 per uur. Bij een dienst die 24/7 draait op een fleet van 50 instances praat je over $47.000 per jaar verschil door alleen framework-keuze.

Praktische lessen die ik teams meegeef:

  1. Meet je GPU-utilisatie met nvidia-smi dmon of dcgm-exporter. Onder 40% zit je geld te verbranden. Verhoog batch-grootte of consolideer workloads.
  2. Overweeg spot-instances voor batch-inferentie workloads (offline embedding-jobs, nachtelijke scoring). Bespaart 60-70% op EC2-kosten.
  3. Right-size je instance. Een A10G is overkill voor DistilBERT. Een g4dn.xlarge met T4 doet het voor $0.526/uur, bijna helft van de prijs, met 20-30% lagere doorvoer.
  4. Denk aan de context-window kosten voor LLMs: batching over verzoeken met sterk verschillende input-lengtes is duur. Groepeer requests met vergelijkbare prompt-lengte (bucketed batching).

Voor teams die vaak lokaal experimenteren met data-analyse voor het productie-model, is mijn eerdere artikel over DuckDB voor snelle lokale data-analyse een goede opstap. Je kunt met minimale overhead je feature-verdelingen valideren voordat je duur GPU-tijd verspilt aan slechte batches.

Migratiestrategie: hoe wissel je zonder brokken?

Als je vandaag op kaal FastAPI zit en overweegt over te stappen: doe het niet in één big-bang. Mijn beproefde aanpak in drie stappen:

  1. Wrap je huidige model in een LitAPI of BentoML Service zonder verkeer te sturen. Deploy als een tweede endpoint, bijvoorbeeld /predict-v2. Meet in isolatie.
  2. Shadow-traffic: kopieer 10% van productie-verkeer naar de nieuwe endpoint zonder het antwoord terug te geven aan de gebruiker. Vergelijk latency-verdelingen en output-consistentie (mogelijk drift door batching-artefacten).
  3. Progressive rollout: verplaats geleidelijk verkeer (1% naar 10% naar 50% naar 100%) via je service mesh of Nginx-splitter. Rollback pad open houden.

Wat vaak vergeten wordt: observability-parity. Zorg dat je nieuwe endpoint dezelfde metrics, log-formaten en tracing-tags heeft als de oude, anders vergelijk je appels met peren en verlies je oncall-vertrouwen. Ik heb ooit een migratie moeten terugdraaien omdat p99 "was gestegen", terwijl in werkelijkheid de nieuwe service network-latency mee-mat en de oude niet. Kostte me een avond schaamte in het #incidents kanaal.

Veelgestelde vragen

Kan ik FastAPI gebruiken voor productie machine learning?

Ja, maar alleen voor lichte CPU-modellen (inferentie onder 30 ms) of wanneer je bewust dynamic batching, warmup en model-versioning zelf wilt implementeren. Voor GPU-workloads of transformers verslaat een specifiek serving-framework zoals LitServe of BentoML kaal FastAPI meestal met een factor 4-6 op doorvoer bij vergelijkbare latency.

Wat is LitServe precies en waarom nu?

LitServe is een minimalistische Python serving-laag van Lightning AI die dynamic batching, streaming en multi-GPU-scheduling out-of-the-box biedt. In 2026 (v0.2) is het volwassen genoeg voor productie met p99-latency-eisen onder 100 ms, en de leercurve is meetbaar korter dan BentoML.

Hoe verschilt BentoML van FastAPI voor ML?

BentoML biedt een complete workflow (model store, Runner-abstractie voor GPU-isolatie, immutable bento-images voor deployment), terwijl FastAPI puur een web-framework is. Voor multi-model of complexe pipelines is BentoML een tijdsbesparing van weken; voor één eenvoudig endpoint is het overkill.

Hoe schaal ik een Python ML-model in productie?

Drie niveaus: (1) intra-node via dynamic batching en meerdere workers per GPU, (2) horizontaal via Kubernetes HorizontalPodAutoscaler op CPU/GPU-metrics of req/s, (3) architectureel door zware modellen te vervangen met kwantiseerde varianten (INT8, INT4) of distillatie. Begin altijd bij (1). De meeste teams laten daar 50%+ prestaties liggen.

Is LitServe geschikt voor LLM-inferentie?

Voor kleine LLM's (onder ~2B parameters) prima. Voor 7B+ modellen kun je LitServe beter als authenticatie- en routing-laag inzetten voor een dedicated inference-engine zoals vLLM of TGI. Die hebben continuous batching en PagedAttention die je met een generieke serving-laag niet reproduceert.

Wat kost een Python ML-endpoint gemiddeld per maand?

Volledig afhankelijk van model, hardware en verkeer. Een always-on g5.xlarge (A10G) kost ongeveer $735 per maand on-demand in eu-west-1. Met correcte batching en een goed uitgevuld GPU-utilisatieprofiel (60-80%) landt cost-per-prediction voor DistilBERT-achtige workloads rond de $0,00000025, praktisch verwaarloosbaar per request, maar snel oplopend bij miljoenen requests per dag.

Arjun Krishnamurthy
Over de Auteur Arjun Krishnamurthy

ML engineer focused on getting models out of notebooks and into production. Has war stories about every serving framework.