Skip to main content

BentoML: Model Serving, Runners & Bento Packaging

BentoML standardizes how you package and serve ML models. Define a Service once, get an HTTP API, Docker image, and cloud deployment — with automatic batching and horizontal scaling built in.

✓ Framework-agnostic serving ✓ Adaptive batching for GPU efficiency ✓ Runner-based horizontal scaling ✓ One-command Docker packaging
Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

$9/mo after trial · Cancel anytime

Want the full toolkit?

Get DevPlaybook Pro

Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →

Defining a BentoML Service

Install with pip install bentoml. A Service is a Python class decorated with @bentoml.service:

import bentoml
import numpy as np
from pydantic import BaseModel

# Save your model to BentoML's model store first:
# bentoml.sklearn.save_model("iris_classifier", clf)

class ClassifyInput(BaseModel):
    sepal_length: float
    sepal_width: float
    petal_length: float
    petal_width: float

@bentoml.service(
    resources={"cpu": "2", "memory": "1Gi"},
    traffic={"timeout": 10},
)
class IrisClassifier:
    # Model is loaded once at startup and reused across requests
    model_ref = bentoml.models.get("iris_classifier:latest")

    def __init__(self):
        import joblib
        self.model = joblib.load(self.model_ref.path_of("model.pkl"))

    @bentoml.api(input_spec=ClassifyInput)
    def classify(self, input_data: ClassifyInput) -> dict:
        features = np.array([[
            input_data.sepal_length, input_data.sepal_width,
            input_data.petal_length, input_data.petal_width
        ]])
        pred = self.model.predict(features)[0]
        prob = self.model.predict_proba(features)[0]
        return {"species": str(pred), "confidence": float(prob.max())}

Run locally with bentoml serve service:IrisClassifier. The service exposes POST /classify and a Swagger UI at /docs.

Runner API: Scaling Inference Separately

Runners decouple inference from your HTTP server, enabling independent scaling of each model component:

import bentoml
from PIL import Image
import io

# Define runners from saved models
embedding_runner = bentoml.pytorch.get("clip-vit:latest").to_runner()
classifier_runner = bentoml.sklearn.get("image-classifier:latest").to_runner()

@bentoml.service
class ImagePipelineService:
    # Runners are injected and managed by BentoML
    runners = [embedding_runner, classifier_runner]

    @bentoml.api()
    async def predict(self, image: bytes) -> dict:
        img = Image.open(io.BytesIO(image)).convert("RGB")

        # Async calls — HTTP server is never blocked
        embedding = await embedding_runner.encode.async_run(img)
        label = await classifier_runner.predict.async_run(embedding)

        return {"label": label, "embedding_dim": len(embedding)}

# Scale runners independently in bentofile.yaml:
# runners:
#   clip-vit: {replicas: 2, resources: {gpu: 1}}
#   image-classifier: {replicas: 4, resources: {cpu: 1}}

Building & Containerizing a Bento

Package your service and all dependencies into a Bento, then convert to Docker:

# bentofile.yaml — defines what goes into the Bento
service: "service:ImagePipelineService"
include:
  - "service.py"
  - "*.py"
python:
  packages:
    - torch==2.2.0
    - torchvision
    - Pillow
    - scikit-learn
docker:
  base_image: "pytorch/pytorch:2.2.0-cuda11.8-cudnn8-runtime"
  cuda_version: "11.8"

# Build the Bento (bundles model artifacts + code + deps)
$ bentoml build

# Output: Successfully built Bento(tag="image-pipeline:a3f9b2c1")

# Convert to Docker image (optimized, layered)
$ bentoml containerize image-pipeline:a3f9b2c1

# Deploy anywhere
$ docker run --gpus all -p 3000:3000 image-pipeline:a3f9b2c1

BentoML vs Ray Serve vs TorchServe vs Triton

Feature BentoML Ray Serve TorchServe Triton
Framework supportAll (agnostic)All (agnostic)PyTorch onlyTensorRT/ONNX/TF/PT
Adaptive batchingBuilt-inManualPartialDynamic batching
Pipeline DAGRunner compositionDeployment graphNoEnsemble models
PackagingBento (artifact bundle)Docker + Ray cluster.mar archiveModel repository
GPU throughputGoodGoodGoodBest (TensorRT)
Learning curveLowMediumMediumHigh
Best forMulti-framework APIsDistributed servingPyTorch productionMax GPU throughput

Frequently Asked Questions

What is BentoML and why use it for model serving?▼

BentoML is an open-source framework that standardizes how ML models are packaged and served. It solves the 'packaging hell' problem: instead of writing custom Flask/FastAPI wrappers for each model, BentoML provides a unified abstraction. You define a Service with input/output schemas, and BentoML generates an HTTP API, handles serialization, manages model loading, and supports batching automatically.

What is a Bento and how is it different from a Docker image?▼

A Bento is BentoML's deployment artifact — a self-contained package that bundles your model artifacts, Python dependencies, service code, and runner configurations. bento build creates a Bento from your project. Unlike a raw Docker image, a Bento is platform-aware: bentoml containerize converts it into an optimized Docker image with the right Python environment, CUDA drivers, and BentoML runtime.

What is a Runner in BentoML?▼

A Runner is BentoML's abstraction for model inference that enables horizontal scaling. Each Runner runs in a separate process pool optimized for its workload — CPU-bound Runners can scale to multiple processes, GPU Runners lock to a GPU device. Your Service calls runners asynchronously via await runner.async_run(), so the HTTP server process is never blocked by inference.

How does BentoML Adaptive Batching work?▼

Adaptive Batching automatically collects concurrent inference requests and processes them as a batch, improving GPU utilization without changing your model code. BentoML dynamically adjusts batch size based on observed latency and throughput. You set max_batch_size and max_latency_ms thresholds, and the batcher optimizes within those bounds. This is critical for transformer models where batch inference is 5-10x more efficient than single-sample.

How do I deploy a BentoML service to production?▼

Options include: (1) Docker — bentoml containerize MyService:latest produces an image deployable to Kubernetes, ECS, or any container platform. (2) BentoCloud — managed serving platform by BentoML team with autoscaling and built-in monitoring. (3) Serverless — community deployers for AWS Lambda, Google Cloud Run. For Kubernetes, BentoML provides a Helm chart and supports KEDA-based autoscaling on queue depth.

Can BentoML serve models from multiple frameworks simultaneously?▼

Yes. A single BentoML Service can include Runners for different frameworks: a PyTorch image encoder, an ONNX text model, and a scikit-learn post-processor can all run as separate Runners in the same service. The Service orchestrates calls between them. This enables building multi-stage pipelines (preprocess → embed → classify → postprocess) as a single deployable unit.