Inference /serve.py

serve.py

The predictor contract: __init__ and predict(data: bytes) → dict, plus three optional hooks.

The class

Two methods are required. Three more are optional, discovered by name; no base class, no decorator, no platform import.

scripts/my_serve.py
class YourPredictor:                          # any name — the yaml points at it

    def __init__(self, **init_args):          # REQUIRED
        # init_args come verbatim from inference.yaml → model.init_args.
        # Build the model, pick a device, load tokenizers. Runs once per replica.
        ...

    def predict(self, data: bytes) -> dict:   # REQUIRED — may be async def
        # `data` is the normalized request body. Return a JSON-serializable dict.
        ...

    def load_weights(self, path: str) -> None:  # optional
        # Called after __init__ IF the yaml sets model.path. `path` is the
        # local file the platform mounted for you.
        ...

    def warmup(self) -> None:                 # optional
        # Hit once per replica after deploy — pre-warm JIT / autotune caches.
        ...

    def health(self) -> dict:                 # optional
        # Returned verbatim on GET /health. Absent → {"status": "ok"}.
        ...

The smallest complete predictor is five lines:

Python
class Echo:
    def __init__(self, prefix: str = "echo: "):
        self.prefix = prefix

    def predict(self, data: bytes) -> dict:
        return {"echo": self.prefix + data.decode("utf-8", errors="replace")}

What `data` is

The platform normalizes every request body into raw bytes before your method sees it, keyed on the request's Content-Type:

Caller sendsContent-Typepredict() receives
raw bytes (the default)image/*, audio/*, video/*, application/octet-stream, or emptythe body verbatim
{"url": "https://…"}application/jsonthe fetched bytes (presigned S3 URLs work; 200 MB cap)
{"data_b64": "…"}application/jsonthe decoded bytes
file uploadmultipart/form-datathe first file field's bytes
warn
application/json is an envelope, not a passthrough: any field other than url / data_b64 is rejected. To send structured JSON (payload plus parameters in one request), POST the JSON bytes as application/octet-stream and call json.loads(data) in your predictor. The SDK's predict_json() does exactly this; details in Calling your model.

A robust predictor tries json.loads first and falls back to treating the bytes as the raw payload. The SAM 2.1 example does this: JSON with prompts, or a bare image.

Returns and errors

  • Return only JSON-serializable values: ints, floats, strings, lists, dicts. Tensors and numpy scalars cause a 500; convert them. Base64-encode binary outputs (the SAM example returns masks as base64 PNGs).
  • Raise ValueError for bad input; the platform maps it to a 400 with a clean error body. Any other exception becomes a 500. Never return an error dict with a 200 status; raise instead.
  • Each request may run for 60 seconds; after that the caller gets 504. A client can't extend it. For slow models (diffusion, long TTS), split the work into shorter requests or contact Support to raise the limit.
note
Concurrency: up to cluster.max_ongoing_requests calls can be inside your predict at once. Either make it thread-safe (model in inference mode, no shared mutable state) or keep the cap low. Exceptions in __init__ or load_weights crash-loop the replica, so fail fast and loudly there.