# AI tools
Source: https://docs.baseten.co/ai-tools
Connect your AI coding tools to Baseten so they can operate your workspace and answer with grounded knowledge.
Baseten plugs into AI coding tools like Claude Code, Cursor, and VS Code through our skill [(repo)](https://github.com/basetenlabs/baseten-skills) and two MCP servers. Your assistant can operate your workspace and ground its answers in current documentation. Baseten annotates each operation as read-only or mutating (deploy, promote, delete), so your agent can iterate quickly and safely in auto-mode.
Our [evaluations](https://github.com/basetenlabs/baseten-skills/blob/main/evals/baseten/README.md) show the toolkit with MCPs completes tasks with fewer tokens and less wall-clock time than calling the REST API or CLI directly.
**Skill vs. MCP:** the MCP servers let your agent *act* on a Baseten workspace and *search* the docs; the skill teaches it how to do that effectively. Use both.
## What you can do
Once connected, your agent can drive the full model lifecycle from inside your editor:
* **Author and deploy:** create and push a custom Truss model or a multi-step Chain; deploy a pre-optimized model from the library; or call a hosted model through Model APIs.
* **Optimize:** pick the right runtime (TRT-LLM, BEI), tune the config, and iterate on a live deployment with `truss watch`.
* **Debug:** pull build and deployment logs, trace a failure, get a fix.
* **Operate:** promote across environments, adjust autoscaling, activate or deactivate, run a test prediction.
* **Observe:** status across models, deployments, training jobs, and environments.
* **Train and fine-tune:** launch and monitor training jobs (SFT, RL, LoRA), manage checkpoints, and deploy the result.
* **Q\&A over docs and best practices:** answers grounded in current documentation, guides, and examples.
## Set up
Create an API key with management permissions in your [API key settings](https://app.baseten.co/settings/api_keys) and set it in your shell so the installer can read it:
```bash macOS / Linux theme={"system"}
export BASETEN_MCP_KEY=...
```
```powershell Windows theme={"system"}
$env:BASETEN_MCP_KEY = "..."
```
To persist the key, add the line to your shell profile (`~/.zshrc` or `~/.bashrc`; on Windows, run `setx BASETEN_MCP_KEY "..."`).
Then install the skill and both MCP servers (requires Node 18+):
```md Agentic wrap theme={"system"}
# Copy this into your agent of choice:
Install the Baseten agent toolkit following the instructions at github.com/basetenlabs/baseten-skills, all global (-g -y):
the `baseten` skill, the backend MCP https://api.baseten.co/mcp with header "Authorization: Bearer $BASETEN_MCP_KEY", and the docs MCP https://docs.baseten.co/mcp.
Run the commands in a shell where BASETEN_MCP_KEY is set; don't print the key. Then tell me how to verify and whether to restart.
```
```bash macOS / Linux theme={"system"}
npx skills add basetenlabs/baseten-skills -g -y
npx add-mcp https://api.baseten.co/mcp -g -y --header "Authorization: Bearer ${BASETEN_MCP_KEY}"
npx add-mcp https://docs.baseten.co/mcp -n baseten_docs -g -y
```
```powershell Windows theme={"system"}
npx skills add basetenlabs/baseten-skills -g -y
npx add-mcp https://api.baseten.co/mcp -g -y --header "Authorization: Bearer $env:BASETEN_MCP_KEY"
npx add-mcp https://docs.baseten.co/mcp -n baseten_docs -g -y
```
* `-g` installs for every detected tool
* `-y` skips prompts.
Restart your agent, then confirm both servers connected. In Claude Code, run `/mcp`:
```
baseten ✔ connected
baseten_docs ✔ connected
```
Then start prompting, or invoke the skill with `/baseten`.
Its API key scopes each MCP instance to one workspace. To work with multiple workspaces, install additional instances under different names with different keys.
## Set up for a specific agent
To wire up a tool by hand, add the MCP servers to its config (the docs server needs no auth, so omit its header for a docs-only setup) and install the skill with `npx skills add basetenlabs/baseten-skills`.
```json Cursor (mcp.json) theme={"system"}
{
"mcpServers": {
"baseten": {
"type": "http",
"url": "https://api.baseten.co/mcp",
"headers": { "Authorization": "Bearer ${BASETEN_MCP_KEY}" }
},
"baseten-docs": { "type": "http", "url": "https://docs.baseten.co/mcp" }
}
}
```
```json VS Code (.vscode/mcp.json) theme={"system"}
{
"servers": {
"baseten": {
"type": "http",
"url": "https://api.baseten.co/mcp",
"headers": { "Authorization": "Bearer ${BASETEN_MCP_KEY}" }
},
"baseten-docs": { "type": "http", "url": "https://docs.baseten.co/mcp" }
}
}
```
```bash Claude Code theme={"system"}
npx skills add basetenlabs/baseten-skills
claude mcp add --transport http baseten https://api.baseten.co/mcp --header "Authorization: Bearer ${BASETEN_MCP_KEY}"
claude mcp add --transport http baseten-docs https://docs.baseten.co/mcp
```
`npx add-mcp` and `npx skills add` also detect Codex, Antigravity, Goose, Windsurf, and other supported agents. For GUI clients like Claude Desktop, add the server URL under their connector settings. Any MCP-compatible tool works with the URLs above.
## Pull docs into your agent
* **Direct URLs:** agents can append `.md` to any page URL for clean, low-token content, or use [llms.txt](https://docs.baseten.co/llms.txt) (page index) and [llms-full.txt](https://docs.baseten.co/llms-full.txt).
* **Context menu:** the "Copy page" button at the top-right of any page copies it as Markdown (or opens it as plain text) to paste into your agent:
These docs also auto-host a lightweight single-file skill at [docs.baseten.co/skill.md](https://docs.baseten.co/skill.md) (`npx skills add https://docs.baseten.co`). The `baseten` skill above supersedes it; reach for the docs skill only where you can't install from the repo.
# Cancel a queued async request.
Source: https://docs.baseten.co/api-reference/cancel-a-queued-async-request
/reference/inference-api/inference-api-spec.json delete /async_request/{request_id}
Cancels an async request. Only requests with `QUEUED` status may be canceled. Rate limited to 20 requests per second.
# Get the status of an async request.
Source: https://docs.baseten.co/api-reference/get-the-status-of-an-async-request
/reference/inference-api/inference-api-spec.json get /async_request/{request_id}
Returns the current status of an async model or chain request. Rate limited to 20 requests per second.
# Asynchronously call a named environment of a chain.
Source: https://docs.baseten.co/api-reference/non-regional/asynchronously-call-a-named-environment-of-a-chain
/reference/inference-api/inference-api-spec.json post /environments/{env_name}/async_run_remote
# Asynchronously call a named environment of a model.
Source: https://docs.baseten.co/api-reference/non-regional/asynchronously-call-a-named-environment-of-a-model
/reference/inference-api/inference-api-spec.json post /environments/{env_name}/async_predict
# Asynchronously call a specific deployment of a chain.
Source: https://docs.baseten.co/api-reference/non-regional/asynchronously-call-a-specific-deployment-of-a-chain
/reference/inference-api/inference-api-spec.json post /deployment/{deployment_id}/async_run_remote
# Asynchronously call a specific deployment of a model.
Source: https://docs.baseten.co/api-reference/non-regional/asynchronously-call-a-specific-deployment-of-a-model
/reference/inference-api/inference-api-spec.json post /deployment/{deployment_id}/async_predict
# Asynchronously call the development deployment of a chain.
Source: https://docs.baseten.co/api-reference/non-regional/asynchronously-call-the-development-deployment-of-a-chain
/reference/inference-api/inference-api-spec.json post /development/async_run_remote
# Asynchronously call the development deployment of a model.
Source: https://docs.baseten.co/api-reference/non-regional/asynchronously-call-the-development-deployment-of-a-model
/reference/inference-api/inference-api-spec.json post /development/async_predict
# Asynchronously call the production environment of a chain.
Source: https://docs.baseten.co/api-reference/non-regional/asynchronously-call-the-production-environment-of-a-chain
/reference/inference-api/inference-api-spec.json post /production/async_run_remote
Enqueues an asynchronous request for the chain deployment promoted to the production environment.
# Asynchronously call the production environment of a model.
Source: https://docs.baseten.co/api-reference/non-regional/asynchronously-call-the-production-environment-of-a-model
/reference/inference-api/inference-api-spec.json post /production/async_predict
Enqueues an asynchronous predict request for the deployment promoted to the production environment. Returns a request ID that can be used to poll for status or cancel the request.
# Call a specific chain deployment by deployment ID.
Source: https://docs.baseten.co/api-reference/non-regional/call-a-specific-chain-deployment-by-deployment-id
/reference/inference-api/inference-api-spec.json post /deployment/{deployment_id}/run_remote
# Call a specific deployment of a model by deployment ID.
Source: https://docs.baseten.co/api-reference/non-regional/call-a-specific-deployment-of-a-model-by-deployment-id
/reference/inference-api/inference-api-spec.json post /deployment/{deployment_id}/predict
Sends a synchronous predict request to the specified deployment.
# Call the chain deployment associated with a specified environment.
Source: https://docs.baseten.co/api-reference/non-regional/call-the-chain-deployment-associated-with-a-specified-environment
/reference/inference-api/inference-api-spec.json post /environments/{env_name}/run_remote
# Call the development deployment of a chain.
Source: https://docs.baseten.co/api-reference/non-regional/call-the-development-deployment-of-a-chain
/reference/inference-api/inference-api-spec.json post /development/run_remote
# Call the development deployment of a model.
Source: https://docs.baseten.co/api-reference/non-regional/call-the-development-deployment-of-a-model
/reference/inference-api/inference-api-spec.json post /development/predict
Sends a synchronous predict request to the development deployment.
# Call the model deployment associated with a specified environment.
Source: https://docs.baseten.co/api-reference/non-regional/call-the-model-deployment-associated-with-a-specified-environment
/reference/inference-api/inference-api-spec.json post /environments/{env_name}/predict
Sends a synchronous predict request to the deployment promoted to the specified environment.
# Call the production environment of a chain.
Source: https://docs.baseten.co/api-reference/non-regional/call-the-production-environment-of-a-chain
/reference/inference-api/inference-api-spec.json post /production/run_remote
Sends a synchronous request to the chain deployment promoted to the production environment. The request body is forwarded to the chain's `run_remote` entrypoint.
# Call the production environment of a model.
Source: https://docs.baseten.co/api-reference/non-regional/call-the-production-environment-of-a-model
/reference/inference-api/inference-api-spec.json post /production/predict
Sends a synchronous predict request to the deployment promoted to the production environment. The request body is forwarded directly to the model's `predict` function.
# Get async queue status for a named environment.
Source: https://docs.baseten.co/api-reference/non-regional/get-async-queue-status-for-a-named-environment
/reference/inference-api/inference-api-spec.json get /environments/{env_name}/async_queue_status
# Get async queue status for a specific deployment.
Source: https://docs.baseten.co/api-reference/non-regional/get-async-queue-status-for-a-specific-deployment
/reference/inference-api/inference-api-spec.json get /deployment/{deployment_id}/async_queue_status
# Get async queue status for the development deployment.
Source: https://docs.baseten.co/api-reference/non-regional/get-async-queue-status-for-the-development-deployment
/reference/inference-api/inference-api-spec.json get /development/async_queue_status
# Get async queue status for the production environment.
Source: https://docs.baseten.co/api-reference/non-regional/get-async-queue-status-for-the-production-environment
/reference/inference-api/inference-api-spec.json get /production/async_queue_status
Returns the number of queued and in-progress async requests for the deployment promoted to the production environment. Rate limited to 20 requests per second.
# Wake a named environment of a model.
Source: https://docs.baseten.co/api-reference/non-regional/wake-a-named-environment-of-a-model
/reference/inference-api/inference-api-spec.json post /environments/{env_name}/wake
# Wake a specific deployment of a model by deployment ID.
Source: https://docs.baseten.co/api-reference/non-regional/wake-a-specific-deployment-of-a-model-by-deployment-id
/reference/inference-api/inference-api-spec.json post /deployment/{deployment_id}/wake
# Wake the development deployment of a model.
Source: https://docs.baseten.co/api-reference/non-regional/wake-the-development-deployment-of-a-model
/reference/inference-api/inference-api-spec.json post /development/wake
# Wake the production environment of a model.
Source: https://docs.baseten.co/api-reference/non-regional/wake-the-production-environment-of-a-model
/reference/inference-api/inference-api-spec.json post /production/wake
Triggers a wake for the deployment promoted to the production environment. Returns immediately with 202 Accepted.
# Asynchronously call a regional environment of a chain.
Source: https://docs.baseten.co/api-reference/regional/asynchronously-call-a-regional-environment-of-a-chain
/reference/inference-api/inference-api-spec.json post /async_run_remote
Enqueues an asynchronous run_remote request via a regional hostname. The environment is determined by the hostname, not the path.
# Asynchronously call a regional environment of a model.
Source: https://docs.baseten.co/api-reference/regional/asynchronously-call-a-regional-environment-of-a-model
/reference/inference-api/inference-api-spec.json post /async_predict
Enqueues an asynchronous predict request via a regional hostname. The environment is determined by the hostname, not the path.
# Call a regional environment of a chain.
Source: https://docs.baseten.co/api-reference/regional/call-a-regional-environment-of-a-chain
/reference/inference-api/inference-api-spec.json post /run_remote
Sends a synchronous run_remote request via a regional hostname. The environment is determined by the hostname, not the path.
# Call a regional environment of a model.
Source: https://docs.baseten.co/api-reference/regional/call-a-regional-environment-of-a-model
/reference/inference-api/inference-api-spec.json post /predict
Sends a synchronous predict request via a regional hostname. The environment is determined by the hostname, not the path.
# Get async queue status for a regional environment.
Source: https://docs.baseten.co/api-reference/regional/get-async-queue-status-for-a-regional-environment
/reference/inference-api/inference-api-spec.json get /async_queue_status
# Wake a regional environment of a model.
Source: https://docs.baseten.co/api-reference/regional/wake-a-regional-environment-of-a-model
/reference/inference-api/inference-api-spec.json post /wake
# How Baseten works
Source: https://docs.baseten.co/concepts/howbasetenworks
The moving parts behind training, deployment, request routing, autoscaling, and environment promotion on Baseten.
Two paths get a model into production on Baseten: deploy an existing model with `truss push`, or train a new one and deploy from a checkpoint. This page covers the pieces that handle both paths: the build pipelines, request routing, autoscaling, cold starts, and environments. For the product surface and positioning, see the [overview](/overview) and [Why Baseten](/concepts/whybaseten).
## Multi-cloud Capacity Management (MCM)
Behind every GPU workload on Baseten is the Multi-cloud Capacity Management (MCM) system. MCM is the infrastructure control plane that unifies GPUs across cloud providers and geographic regions.
When you request a resource (an H100 in US-East-1 or a cluster of B200s in a private region), MCM provisions the hardware, configures networking, and monitors health. It abstracts the differences between cloud providers so the Baseten training and inference stack runs identically on any underlying infrastructure.
MCM also powers Baseten's high availability. Deployments run active-active across clusters and clouds, and if a region or provider faces a capacity crunch or outage, MCM re-routes and re-provisions workloads to maintain service continuity.
## Deploy an existing model
To deploy a model, package it with [Truss](https://pypi.org/project/truss/), Baseten's open-source model packaging tool. Describe the model in a `config.yaml` (for supported architectures) or a small Python `Model` class (for custom code), then run `truss push` to ship it.
`truss push` validates your `config.yaml`, archives your project directory, and uploads it to cloud storage. Baseten receives the archive and starts the build.
For [Engine-Builder-LLM](/engines/engine-builder-llm/overview), Baseten downloads model weights from the source repository (Hugging Face, S3, or GCS) and compiles them with TensorRT-LLM. Compilation builds optimized CUDA kernels for the target GPU architecture, applies quantization if configured, and sets up tensor parallelism across multiple GPUs.
Baseten packages the compiled engine, runtime configuration, and serving infrastructure into a container, deploys it to GPU infrastructure, and exposes it as an API endpoint.
`truss push` returns once the upload finishes. For engine-based deployments, compilation can take several minutes. Watch progress in the deployment logs, or wait for the dashboard to show "Active."
For [custom model code](/development/model/model-class) deployments, the build is faster: Baseten installs your Python dependencies, packages your `Model` class into a container, and deploys it. Inference optimization is on you in custom builds.
Each push produces a container image identified by a content hash and stored in Baseten's container registry. The image is immutable, and an unchanged project reuses the cached image instead of triggering a new build.
## Train a model
To train a model, define the training job in a Python config and submit it with `truss train push`. Baseten provisions GPUs through MCM, runs your training container, and syncs checkpoints to storage as the job progresses.
`truss train push config.py` packages your training config, uploads it to Baseten, and starts the job on the hardware you specified (H100 or H200, single-node or multi-node). Your training code can use Axolotl, TRL, VeRL, Megatron, or any other framework you bundle into the container.
Baseten runs your training container on the provisioned GPUs. As your training code writes checkpoints to the configured directory, Baseten uploads them to durable storage. If the job fails or you stop it, the most recent checkpoint is still available.
`truss train deploy_checkpoints --job-id ` constructs a Truss `config.yaml` from the checkpoint, packages it as a deployment, and exposes an API endpoint. From there, the deployment behaves like any other model on Baseten.
For a fully managed training path with Tinker-compatible Python, see [Loops](/loops/overview). For the full training lifecycle, see [Training overview](/training/overview).
## Request routing
Each deployment gets a dedicated subdomain: `https://model-{model_id}.api.baseten.co/`. The URL path determines which deployment handles the request. Requests to `/production/predict` go to the production environment, `/development/predict` goes to the development deployment, and you can also target a specific deployment by ID or a custom environment by name.
From the URL path, Baseten resolves the environment and routes the request to an active replica. If the deployment has scaled to zero, Baseten starts a replica and parks the request until the model loads. The caller receives the response regardless of whether the model was warm or cold-started.
Engine-based deployments serve an [OpenAI-compatible API](/reference/inference-api/chat-completions) at the `/v1/chat/completions` path, so any code written for the OpenAI SDK works without modification. Custom model deployments use the [predict API](/reference/inference-api/overview), which accepts and returns arbitrary JSON.
For long-running workloads, [async requests](/inference/async) return a request ID immediately. The request enters a queue managed by an async request service. A background worker then calls your model and delivers the result through a webhook. Sync requests get priority when capacity is tight, so background work doesn't starve real-time traffic.
## Autoscaling
Baseten's autoscaler matches replica count to in-flight request load, keeping each replica below its [concurrency target](/deployment/autoscaling/overview).
Scale-up is immediate. When the average load over the autoscaling window (default 60 seconds) crosses the target utilization (default 70%), the autoscaler adds replicas, up to the configured maximum.
Scale-down is deliberate. When load drops, the autoscaler waits one `scale_down_delay` (default 900 seconds), then removes excess replicas at a pace capped by `max_scale_down_rate` (half of running replicas by default). The timer resets, and the cycle repeats until the deployment reaches its target size. This staircase pattern prevents thrashing when traffic briefly dips and recovers.
Set [`min_replica`](/deployment/autoscaling/overview) to 0 for scale-to-zero: the deployment incurs no GPU cost when idle, but the next request triggers a cold start. Set `min_replica` to 1 or higher to keep warm capacity ready, trading cost for lower latency.
## Cold starts and the Baseten Delivery Network
The slowest part of a cold start is loading model weights, which can reach hundreds of gigabytes. Baseten addresses this with the [Baseten Delivery Network (BDN)](/development/model/bdn), a multi-tier caching system for model weights.
When you first deploy, BDN mirrors your model weights from the source repository to Baseten's own blob storage. After that, no cold start depends on an upstream service like Hugging Face or S3. When a new replica starts, the BDN agent on the node fetches a manifest for the weights, downloads them through an in-cluster cache (shared across all replicas in the cluster), and stores them in a node-level cache (shared across replicas on the same node). Identical files across different models are deduplicated, so a fine-tune that shares most weights with the base model only downloads the delta.
Subsequent cold starts on the same node or in the same cluster are significantly faster than the first. Container images use streaming, so the model begins loading weights before the image download completes.
BDN serves training jobs the same way. Mount weights and training data into your training container from any supported source, and BDN caches them so subsequent jobs start faster.
## Environments and promotion
Every model starts as a development deployment with scale-to-zero and live reload, configured for fast iteration. When the model is ready for production traffic, promote it to a named [environment](/deployment/environments) like production, staging, or canary.
Each environment has its own stable URL, autoscaling settings, and metrics. Promoting a new deployment swaps it in for the previous one and inherits the environment's autoscaling settings. The endpoint URL stays constant when you promote, so your application code doesn't need to change. Baseten demotes the previous deployment and scales it to zero, so you can roll back by re-promoting it.
Promotion reuses the image the deployment was already built with, so it never rebuilds or re-pulls your base image. Rollback works the same way: re-promoting a previous deployment reuses its existing image.
To skip the development stage, push directly to an environment with `truss push --environment staging`. Only one promotion can be active per environment at a time, which prevents conflicting updates. See [Deployment concepts](/deployment/concepts) for the full set of resource and CI/CD options.
These pieces work the same whether you deploy an existing model or train a new one, so the path from prototype to production stays consistent.
## Next steps
Package a model with Truss and deploy it with a single config file.
Run a fine-tune or pre-train and deploy the checkpoint to an endpoint.
# Why Baseten
Source: https://docs.baseten.co/concepts/whybaseten
Production training and inference on dedicated infrastructure, for teams that have outgrown shared API endpoints.
Baseten runs production-grade training and inference for AI teams that have outgrown shared API endpoints. Bring an open-source model, fine-tune one on dedicated H100 or H200 GPUs, or train from scratch. Deploy the result with one command and serve it on inference engines tuned for your model's architecture.
If you want to use a popular open-source model like DeepSeek, Qwen, or GLM, you can [point the OpenAI SDK at Model APIs](/inference/model-apis/overview) and skip deployment entirely. For everything else, the rest of this page covers what you get when you run your own model on Baseten.
## Production inference
Inference is the core of your product. When it fails, your application stops working. Baseten is built for mission-critical workloads with [high availability](https://status.baseten.co/), low latency, and performance at any scale.
### Engines built for your model architecture
An engine is the optimization runtime that compiles and serves your model. Baseten writes the engine layer so you don't have to: each engine handles quantization, tensor parallelism, KV cache management, and batching for a specific class of model.
Pick an engine based on your model's architecture:
* **[Engine-Builder-LLM](/engines/engine-builder-llm/overview):** Dense text-generation models compiled with TensorRT-LLM. Use for most open-source LLMs.
* **[BIS-LLM](/engines/bis-llm/overview):** Mixture-of-experts models like DeepSeek R1 and Qwen3 MoE, with KV-aware routing and distributed inference.
* **[BEI](/engines/bei/overview):** Embedding, reranking, and classification models with up to 1,400 client embeddings per second.
Select the engine in your `config.yaml`, or let Baseten pick one based on your model architecture. See the [engine selection guide](/engines) for Baseten's engines, or run a different inference server like vLLM or SGLang as a [custom Docker container](/development/model/custom-server).
### Multi-cloud Capacity Management
GPUs are scarce, and any single cloud can run out of them in any given region. [Multi-cloud Capacity Management (MCM)](/concepts/howbasetenworks#multi-cloud-capacity-management-mcm) is Baseten's control plane for provisioning capacity across clouds and regions. Deployments run active-active so a regional outage or capacity crunch doesn't take your endpoint offline.
For the mechanics, see [How Baseten works](/concepts/howbasetenworks).
## Deployment modes
Baseten's training and inference stack runs the same way regardless of where the GPUs sit. Pick the mode that matches your data residency and operational posture.
### Baseten Cloud
Fully managed, multi-cloud inference and training. The fastest path to production, with horizontal scale and global latency optimization. Baseten runs the infrastructure so you can focus on your models.
### Self-hosted
The full Baseten stack inside your own VPC. Use this when you have strict data security, privacy, or sovereignty requirements. You keep full control over your data and networking while still getting Baseten's autoscaling and performance optimizations.
### Hybrid
Run core workloads in your VPC and burst to Baseten Cloud on demand. Combines strict compliance with elastic flex capacity.
[Talk to us](https://www.baseten.co/talk-to-us/) to set up a self-hosted or hybrid deployment.
## Training and customization
You can train models on Baseten too. Run a fine-tune or a from-scratch pre-train on dedicated H100 or H200 GPUs with Axolotl, TRL, VeRL, Megatron, or your own training code. Checkpoints sync to durable storage as training progresses, so a job failure doesn't cost you a run.
When training completes, [`truss train deploy_checkpoints`](/training/deployment) deploys the checkpoint as an inference endpoint in one command. The same engines, autoscaling, and observability that serve fresh deployments also serve your trained model. No separate platform, no glue code between training and serving.
### Loops
[Loops](/loops/overview) is Baseten's training SDK for workflows that don't fit a single batch job. It supports long-sequence training, async reinforcement learning, and one-click checkpoint deploys to the Baseten inference stack. Use Loops when you're iterating on a sampler-and-trainer loop or running RL against your own environment.
See [Training overview](/training/overview) for the full lifecycle and [Loops overview](/loops/overview) for the SDK.
## Operate in production
Keep models healthy at scale with built-in observability, autoscaling, and secrets management.
* **[Observability](/observability/logs):** Real-time logs, metrics, and request traces for every deployment. Export to Datadog, Prometheus, Grafana, or New Relic, or read it all through Baseten's own dashboards.
* **[Autoscaling](/deployment/autoscaling/overview):** Per-deployment concurrency targets, min and max replicas, scale-to-zero, and bounded scale-down delay.
* **[Secrets and API keys](/organization/secrets):** Encrypted secret storage scoped to your workspace. Reference secrets in your `config.yaml` without checking them into a repo.
* **Compliance posture:** [SOC 2 Type II](https://www.baseten.co/blog/soc-2-type-2) and [HIPAA](https://www.baseten.co/blog/baseten-announces-hipaa-compliance), plus [regional environments](/deployment/regional-environments) for data-residency requirements like GDPR.
## Serve models to your own customers
Baseten lets you serve models running on the platform to your own customers through [Frontier Gateway](/frontier-gateway/overview). The gateway sits between your customers and your dedicated deployment. Represent each customer (or plan, or project) as a group, mint API keys scoped to that group, set rate and usage limits that inherit through the tree, and receive usage events as billing webhooks. Calls hit your branded domain, not Baseten's.
Frontier Gateway is enabled for your workspace by a Baseten engineer. To turn it on, [talk to us](https://www.baseten.co/talk-to-us/).
## Next steps
The mechanics behind training, deployment, request routing, and autoscaling.
Make your first inference call in under two minutes.
# Cold starts
Source: https://docs.baseten.co/deployment/autoscaling/cold-starts
Learn what makes a cold start slow and how to shrink it for your model.
A *cold start* is the time a fresh replica spends starting up before it can accept traffic. A request that triggers one waits in the queue until the replica is ready, so the cold-start duration sets the latency floor for that request. The following diagram traces a deployment through that cycle, from **Scaled to zero** to **Active** and back, with the startup steps that add up to the wait.
## Cold start triggers
Every new replica cold-starts before it can serve traffic, no matter why it was created.
*Scale-from-zero* applies when a deployment's `min_replica` is 0. Once traffic stays at zero for the full [`scale_down_delay`](/deployment/autoscaling/overview#how-autoscaling-works), the autoscaler shuts down every replica. The next request finds nothing running and waits for a full startup, so users feel this cold start directly.
*Scaling events* happen while a deployment is already serving traffic. When load crosses the scaling threshold, the autoscaler adds replicas, and each one cold-starts before it can serve traffic. The replicas already running keep serving in the meantime, so users notice only when load grows faster than new replicas can start up.
## Contributing factors
A new replica works through these steps in order, and their durations add up to the cold-start time:
| Step | What happens |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Container pull | The replica downloads your Docker image layers. |
| Weight load | Model weights (often 10s to 100s of GB) move from storage into GPU memory. |
| Engine initialization | Your model's setup code runs. For inference engines like vLLM and SGLang, this includes capturing CUDA graphs, compiling kernels with `torch.compile`, and profiling the KV cache. |
Baseten provides the [Baseten Delivery Network (BDN)](/development/model/bdn), which speeds up weight load by mirroring your weights and caching them next to your replicas. Each scale-up then reads them from a nearby cache instead of re-downloading hundreds of gigabytes from the source. Baseten also streams your container image in the background, so container pull rarely dominates.
That leaves engine initialization as the step you usually own. It dominates for small models (a few billion parameters or fewer), where CUDA graph capture and `torch.compile` can run well over a minute, and Baseten doesn't cache those artifacts unless you opt in. For the largest models (70B+ parameters or large mixture-of-experts), even BDN can't make hundreds of gigabytes instant, so weight load stays the dominant step.
Cold start time isn't a fixed number. It varies with model size and the GPU you run on, so benchmark your own model rather than relying on a single figure.
## Reduce cold starts
The biggest win comes from shrinking whichever step dominates startup. When that isn't enough, keep replicas warm so requests skip the cold start entirely.
### Faster weight loading
BDN runs automatically on engine-builder deployments. On any other deployment, turn it on by adding a [`weights`](/development/model/bdn) block to your config.
### Compilation caching
`torch.compile` and CUDA graph capture rerun on every fresh replica unless their output is cached. [Torch compile caching](/development/model/runtime-caching#torch-compile-caching), built on [b10cache](/development/model/runtime-caching), persists those artifacts so a new replica loads them instead of recompiling, which cuts compilation from minutes to roughly 5 to 20 seconds.
### Warm replicas
`min_replica` sets a floor on running replicas. Keep it at 1 or higher so a replica stays warm to serve the first request. You pay for that replica while it's idle, but the request no longer waits for a startup. Set it in the dashboard or through the [autoscaling settings API](/reference/management-api/deployments/autoscaling/updates-a-deployments-autoscaling-settings):
```json Autoscaling settings theme={"system"}
{
"min_replica": 1
}
```
For production redundancy, set `min_replica` to 2 or higher so one replica can fail during maintenance without causing cold starts.
Your replica floor trades cost against latency:
| Approach | Cost | Latency | Best for |
| -------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------- |
| Scale to zero (`min_replica: 0`) | No charge while idle; wake-up minutes are [billed](/organization/billing) | First request waits for a full cold start | Batch jobs, development, and spiky low-volume traffic |
| Always on (`min_replica` ≥ 1) | Pay for idle replicas | No cold start from idle, though new replicas still cold-start | Latency-sensitive production traffic |
Start warm for production, and scale to zero only when an occasional slow first request is acceptable.
### Pre-warming
For predictable traffic spikes, raise `min_replica` ahead of the expected load:
```bash Terminal theme={"system"}
# 10-15 minutes before expected spike
curl -X PATCH \
https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/autoscaling_settings \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{"min_replica": 5}'
```
After traffic stabilizes, reset to your normal minimum.
### Scale-down delay
A longer scale-down delay keeps replicas warm through brief traffic dips. The default is 15 minutes (900 seconds); this example doubles it to 30 minutes:
```json Autoscaling settings theme={"system"}
{
"scale_down_delay": 1800
}
```
A replica that's still warm when traffic returns serves immediately, with no cold start.
## Next steps
* [Request lifecycle](/deployment/autoscaling/request-lifecycle): What happens to requests during cold starts, including queuing and timeout behavior.
* [Autoscaling](/deployment/autoscaling/overview): Configure `min_replica`, `scale_down_delay`, and the rest of the scaling settings.
* [Traffic patterns](/deployment/autoscaling/traffic-patterns): Pre-warming strategies for different traffic types.
* [Billing and usage](/organization/billing): How cold-start time is metered.
* [Troubleshooting](/troubleshooting/deployments#autoscaling-issues): Diagnose cold start issues.
# Autoscaling
Source: https://docs.baseten.co/deployment/autoscaling/overview
Configure autoscaling to dynamically adjust replicas based on traffic while minimizing idle compute costs.
Without autoscaling, you'd choose between two bad options: pay for enough GPUs to handle your peak traffic 24/7, or accept that requests fail when load exceeds your fixed capacity. Autoscaling eliminates this tradeoff by adjusting the number of **replicas** backing a deployment based on demand. When traffic rises, the autoscaler adds replicas. When it falls, it removes them. The goal is to match capacity to load so you pay for what you use without sacrificing latency.
Baseten [bills per minute](/organization/billing) for every minute a replica is observed as up, including the builder workload after `truss push` and any training workloads. A deployment scaled to zero replicas incurs no charges, but model load on a fresh replica is metered. See [Billing and usage](/organization/billing) for the full lifecycle breakdown, and [Cold starts](/deployment/autoscaling/cold-starts) for techniques to minimize startup time.
Baseten provides default settings that work for most workloads.
Tune your autoscaling settings based on your model and traffic.
| Parameter | Default | Range | What it controls |
| ------------------- | ------- | -------- | -------------------------------------------- |
| Min replicas | 0 | ≥ 0 | Baseline capacity (0 = scale to zero). |
| Max replicas | 1 | ≥ 1 | Cost/capacity ceiling. |
| Autoscaling window | 60s | 10-3600s | Time window for traffic analysis. |
| Scale-down delay | 900s | 0-3600s | Wait time before removing idle replicas. |
| Max scale-down rate | 50% | 1-50% | Cap on replicas removed per scale-down step. |
| Concurrency target | 1 | ≥ 1 | Requests per replica before scaling. |
| Target utilization | 70% | 1-100% | Headroom before scaling triggers. |
You can configure autoscaling settings through the Baseten UI or API. To apply
them to a live deployment from a script, see
[Scale a deployment](/deployment/manage/scaling).
**To configure autoscaling**:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the sidebar, then select your model.
2. Select your deployment.
3. Under **Replicas** for your production environment, choose **Configure**.
4. Configure the autoscaling settings.
5. Choose **Update**.
Send a PATCH request to the autoscaling settings endpoint:
```bash Request theme={"system"}
curl -X PATCH \
https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/autoscaling_settings \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"min_replica": 2,
"max_replica": 10,
"concurrency_target": 32,
"target_utilization_percentage": 70,
"autoscaling_window": 60,
"scale_down_delay": 900,
"max_scale_down_rate": 50
}'
```
For more information, see the [API reference](/reference/management-api/deployments/autoscaling/updates-a-deployments-autoscaling-settings).
Use the `requests` library to send the same PATCH:
```python update_autoscaling.py theme={"system"}
import requests
import os
API_KEY = os.environ.get("BASETEN_API_KEY")
response = requests.patch(
"https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/autoscaling_settings",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"min_replica": 2,
"max_replica": 10,
"concurrency_target": 32,
"target_utilization_percentage": 70,
"autoscaling_window": 60,
"scale_down_delay": 900,
"max_scale_down_rate": 50
}
)
print(response.json())
```
For more information, see the [API reference](/reference/management-api/deployments/autoscaling/updates-a-deployments-autoscaling-settings).
## How autoscaling works
The autoscaler matches replica count to demand by continuously sampling in-flight requests into a sliding window that spans `autoscaling_window` (60 seconds by default). It averages the load over that window, divides by each replica's effective capacity (`concurrency_target` × `target_utilization_percentage`), and rounds up to set the desired replica count. Scaling up happens at the next decision, but scaling down is deliberately patient: load has to stay below the threshold for an entire `scale_down_delay` before the autoscaler removes replicas, and each step removes at most `max_scale_down_rate` of the running replicas (half by default) rather than dropping them all at once. That asymmetry is what keeps the deployment from oscillating when traffic dips and recovers.
The simulator below runs that exact loop on live traffic: every scale-up fires at the moment the windowed average crosses the scale threshold. Start from a scenario to stage a cold start or oscillation, or stay in the sandbox and shape the traffic yourself. Every parameter is live, and the meters track what your settings cost in idle capacity and queued requests.
To put numbers on it, consider a deployment with `concurrency_target` set to 10 and `target_utilization_percentage` at 70%. Each replica's effective capacity is 7 concurrent requests (10 × 0.70). If the windowed average rises from 5 to 25 in-flight requests, the autoscaler computes ⌈25 / 7⌉ = 4 desired replicas at the next decision and starts provisioning the difference. Scale-up continues until the deployment reaches `max_replica`; beyond that ceiling, additional load queues until capacity frees up.
Scale-down is slower by design. When the windowed average drops below the threshold, the autoscaler waits a full `scale_down_delay` (900 seconds by default), removes the excess at a pace capped by `max_scale_down_rate` (50% of running replicas by default), and resets the timer. At the default rate, a deployment with eight excess replicas drains to four, then two, then one, with a full delay between steps. If traffic recovers inside the delay, no scale event fires and the replicas stay warm. Scale-down stops at `min_replica`; production deployments typically hold it at two or more so a healthy replica is always available.
## Replicas
Each replica is an independent instance of your model, running on its own hardware and capable of serving requests in parallel with other replicas. The autoscaler controls how many replicas are active at any given time, but you set the boundaries.
The floor for your deployment's capacity. The autoscaler won't scale below this number.
**Range:** ≥ 0
The default of 0 enables *scale-to-zero*: when no requests arrive for long enough, all replicas shut down and your deployment incurs no charges. The tradeoff is that the next request triggers a [cold start](/deployment/autoscaling/cold-starts), which can take minutes for large models. During that wake-up period, [billing is per minute](/organization/billing) even though the replica isn't yet serving responses.
For production deployments, set `min_replica` to at least 2. This eliminates cold starts and provides redundancy if one replica fails.
The ceiling for your deployment's capacity. The autoscaler won't scale above this number.
**Range:** ≥ 1
This setting protects against runaway scaling and unexpected costs. If traffic exceeds what your maximum replicas can handle, requests queue rather than triggering new replicas. See [Request lifecycle](/deployment/autoscaling/request-lifecycle) for details on queuing and load shedding behavior. The default of 1 effectively disables autoscaling: you get exactly one replica regardless of load.
Estimate max replicas:
$$
(peak\_requests\_per\_second / throughput\_per\_replica) + buffer
$$
For high-volume workloads requiring guaranteed capacity, [contact Baseten](mailto:support@baseten.co) about reserved capacity options.
## Scaling triggers
The autoscaler decides when a replica is "full" by comparing in-flight requests against a per-replica threshold. `concurrency_target` caps how many simultaneous requests each replica accepts, and `target_utilization_percentage` cuts the threshold lower so the autoscaler can trigger a scale-up before any replica is completely saturated, leaving room for new replicas to come online without queueing requests in the meantime. Scale-up fires when:
$$
load > replicas \times concurrency\_target \times target\_utilization
$$
The following diagram shows a replica with `concurrency_target` of 8 and `target_utilization` of 50%, so the per-replica threshold sits at 4. The first four requests fill capacity within headroom; the fifth crosses the threshold, and the autoscaler provisions a second replica to absorb the overflow before the remaining slots saturate.
How many requests each replica can handle simultaneously. This directly determines replica count for a given load.
**Range:** ≥ 1
Given the current load, the autoscaler calculates desired replicas:
$$
desired\_replicas = \lceil in\_flight\_requests / (concurrency\_target \times target\_utilization) \rceil
$$
*In-flight requests* are requests sent to your model that haven't returned a response (for streaming, until the stream completes). [Async inference requests](/inference/async) are not included in this count. This count is exposed as [`baseten_concurrent_requests`](/observability/export-metrics/supported-metrics#baseten_concurrent_requests) in the metrics dashboard and metrics export.
The right value depends on how your model uses hardware. Image generation models that consume all GPU memory per request can only process one at a time, so a concurrency target of 1 is correct. LLMs and embedding models batch requests internally and can handle dozens simultaneously, so higher targets (32 or more) reduce cost by packing more work onto each replica.
**Tradeoff:** Higher concurrency = fewer replicas (lower cost) but more per-replica queueing (higher latency). Lower concurrency = more replicas (higher cost) but less queueing (lower latency).
**Starting points by model type:**
| Model type | Starting concurrency |
| ----------------------- | -------------------- |
| Standard Truss model | 1 |
| vLLM / LLM inference | 32-128 |
| SGLang | 32 |
| Text embeddings (TEI) | 32 |
| BEI embeddings | 96+ (min ≥ 8) |
| Whisper (async batch) | 256 |
| Image generation (SDXL) | 1 |
For engine-specific guidance, see [Autoscaling engines](/engines/performance-concepts/autoscaling-engines).
**Concurrency target** controls requests sent *to* a replica and triggers autoscaling.
**predict\_concurrency** (Truss config.yaml) controls requests processed *inside* the container.
Concurrency target should be less than or equal to predict\_concurrency.
See the `predict_concurrency` field in the [Truss configuration reference](/reference/truss-configuration) for details.
Headroom before scaling triggers. The autoscaler scales when utilization reaches this percentage of the concurrency target, not when replicas are fully loaded.
**Range:** 1-100%
The effective threshold is:
$$
concurrency\_target × target\_utilization
$$
With a concurrency target of 10 and utilization of 70%, scaling triggers at 7 concurrent requests (10 × 0.70), leaving 30% headroom for absorbing spikes while new replicas start.
Lower values (50-60%) provide more headroom for spikes but cost more. Higher values (80%+) are cost-efficient for steady traffic but absorb spikes less effectively.
Target utilization is **not** GPU utilization. It measures request slot usage relative to your concurrency target, not hardware utilization.
## Scaling dynamics
Once the autoscaler decides to scale, the settings here control the pace. `autoscaling_window` determines how much history feeds into each decision, so a longer window averages out short spikes while a shorter one reacts to traffic changes faster. `scale_down_delay` gates removal in the other direction by holding replicas warm even after load drops, so a brief dip does not trigger a teardown that the next request would have to wait through. `max_scale_down_rate` caps how much capacity each scale-down step can remove once that delay has passed. Together, these settings tune the tradeoff between responsiveness and stability. The diagram below shows traffic falling to zero, the idle timer filling up, and the replica being reclaimed only once the timer crosses the `scale_down_delay` threshold.
How far back (in seconds) the autoscaler looks when measuring traffic. Traffic is averaged over this window to make scaling decisions.
**Range:** 10-3600 seconds
A 60-second window smooths out momentary spikes by averaging load over the past minute. Shorter windows (30-60s) react quickly to traffic changes, which suits bursty workloads. Longer windows (2-5 min) ignore short-lived fluctuations and prevent the autoscaler from chasing noise.
How long (in seconds) the autoscaler waits after load drops before removing replicas.
**Range:** 0-3600 seconds
When load drops, the autoscaler starts a countdown. If load stays low for the full delay, it removes replicas in steps, each capped by `max_scale_down_rate`, with a fresh delay between steps. If traffic returns before the countdown finishes, the replicas stay active and the countdown resets.
This is your primary lever for preventing *oscillation*. If replicas repeatedly scale up and down, increase this value first.
The maximum percentage of running replicas the autoscaler can remove in one scale-down step.
**Range:** 1-50%
Each time a `scale_down_delay` elapses, the autoscaler removes at most this percentage of running replicas. The default of 50% produces the halve-and-wait pattern described above. Lower values release capacity more gradually, which keeps more replicas warm when traffic tends to rebound shortly after it drops.
A **short window** with a **long delay** gives you fast scale-up while maintaining capacity during temporary dips. This is a good starting configuration for most workloads.
## Development deployments
Development deployments are designed for iteration, not production traffic. Replicas are fixed at 0-1 to match the [`truss watch`](/reference/cli/truss/watch) workflow, where you're testing changes on a single instance rather than handling concurrent users. You can still adjust timing and concurrency settings.
| Setting | Value | Modifiable |
| ------------------ | ----------- | ---------- |
| Min replicas | 0 | No |
| Max replicas | 1 | No |
| Autoscaling window | 60 seconds | Yes |
| Scale-down delay | 900 seconds | Yes |
| Concurrency target | 1 | Yes |
| Target utilization | 70% | Yes |
To enable full autoscaling with configurable replica settings, [promote the deployment to production](/deployment/deployments).
## Next steps
Identify your traffic pattern and get recommended starting settings.
Understand cold starts and how to minimize their impact.
Complete autoscaling API documentation.
Recommended settings for BEI and Engine-Builder-LLM with dynamic batching.
## Troubleshooting
Having issues with autoscaling? See [Autoscaling troubleshooting](/troubleshooting/deployments#autoscaling-issues) for solutions to common problems like oscillation, slow scale-up, and unexpected costs.
# Request lifecycle
Source: https://docs.baseten.co/deployment/autoscaling/request-lifecycle
What happens to a request from submission to response, including routing, queuing, the 1200-second sync predict timeout, and error handling.
When you send an inference request, it doesn't go straight to model code. Whether you use [Model APIs](/inference/model-apis/overview), an OpenAI-compatible endpoint for a deployment you manage, or the [predict API](/inference/calling-your-model), the request passes through authentication, routing, and replica selection first. For Truss deployments with custom model code, your `predict` function runs only after those steps. These layers exist so that Baseten can manage replicas on your behalf: scaling them up when traffic spikes, scaling them down when it drops, and distributing requests across them without any load-balancing code on your side. Understanding what each layer does helps you reason about latency, interpret status codes, and debug production issues.
## How a request reaches your model
Your request first hits Baseten's inference gateway, which authenticates it against your [API key](/organization/api-keys). If authentication fails, the gateway returns a `401 Unauthorized` before the request reaches any model infrastructure.
Once authenticated, the request moves to the routing layer, which decides which replica should handle it. Baseten routes requests to the least-utilized replica based on how full each one is relative to its [concurrency target](/deployment/autoscaling/overview#concurrency-target). Rather than spreading requests evenly across all replicas, the router prefers replicas that already have headroom, which keeps the total number of active replicas low. This matters because you're [billed per minute](/organization/billing) for each running replica.
When the router finds a replica with available capacity, it forwards the request. The replica runs inference. For deployments that use the predict API, your `predict` function executes here. The response flows back through the same path to the client. For most requests, the routing overhead is negligible compared to your model's inference time. The sections below cover what happens when this straightforward path breaks down: when no replica is available, when replicas are overloaded, and when requests fail partway through.
## What happens when no replica is available
If your deployment has scaled to zero, or all existing replicas are at capacity and the autoscaler is still bringing up new ones, incoming requests have nowhere to go. Rather than rejecting them immediately, Baseten parks the request at the routing layer and waits for a replica to become available. Once one is ready, the parked request is forwarded and processed normally. From the client's perspective, the response simply takes longer: the wait time is added on top of the normal inference time.
This parking behavior is what makes [scale-to-zero](/deployment/autoscaling/overview#min_replica) practical. You don't need to build retry logic into your client just because your deployment was idle; the request waits for you. But the wait isn't indefinite. If no replica becomes available before the predict timeout (1200 seconds by default) expires, the parked request fails with a `500`. For large models that take several minutes to load weights, you may want to keep [minimum replicas](/deployment/autoscaling/overview#min_replica) above zero so requests always have somewhere to go.
[Async requests](/inference/async) follow a different pattern. The first async request parks and waits, just like a sync request. But subsequent async requests that arrive while there's still no capacity receive an immediate `429` with a `CAPACITY_EXCEEDED` error instead of the `202 Accepted` they'd normally get. This prevents a situation where your client thinks a request was accepted and starts polling for results, when it's actually still waiting for a replica to start.
For strategies to reduce cold start latency, including warm replicas, pre-warming, and the Baseten Delivery Network, see [Cold starts](/deployment/autoscaling/cold-starts).
## Request queuing and load shedding
Even when replicas are running, they can fill up. When all replicas are at their [concurrency target](/deployment/autoscaling/overview#concurrency-target) and the autoscaler hasn't yet finished adding new ones, incoming requests queue at the routing layer. This queuing is automatic: you don't configure it and your client doesn't see it. The request simply waits until a slot opens up on a replica.
Baseten has a **load shedding** safety valve that rejects new requests with a `429` if queued payloads exceed a memory threshold, but this threshold is high enough that it rarely triggers under normal conditions. The more likely issue you'll encounter is requests waiting a long time during traffic spikes, not requests being rejected. Because your client has no visibility into the queue, a request that's waiting for capacity looks the same as a request that's taking a long time to run inference. If you don't want requests to hang indefinitely in this situation, set a client-side timeout so your application can fail fast and either retry or surface an error to the user.
To reduce queuing overall, increase your [max replicas](/deployment/autoscaling/overview#max_replica) so the autoscaler can add capacity faster. Adjusting your [concurrency target](/deployment/autoscaling/overview#concurrency-target) also helps, since a higher target means each replica absorbs more requests before the queue starts filling.
## Internal retries
When a request reaches a replica but the replica returns a `502`, `503`, or `504`, the routing layer doesn't surface the error to your client immediately. Instead, it retries the request automatically using exponential backoff, starting at 500 milliseconds and growing by a factor of 1.5 up to 60 seconds between attempts. For status code errors like these, retries continue until the request deadline or 15 minutes of total elapsed time, whichever comes first. Connection-level failures, where the replica is completely unreachable, are capped at 16 attempts instead. [Async requests](/inference/async) are not retried.
From your client's perspective, retries show up as added latency rather than errors. A request that would have failed on the first attempt may succeed on the second or third, but take noticeably longer than usual. If you're investigating occasional latency spikes where requests take much longer than expected but eventually succeed, you can check the `X-BASETEN-MODEL-PREDICTION-ATTEMPTS` response header: a value greater than 1 confirms that at least one retry happened. Under memory pressure (above 80% utilization on the routing layer), a circuit breaker disables retries entirely to protect stability, resuming them after a 30-second cooldown once memory drops. If a request was pinned to a specific replica through sticky session and that replica returns a `503`, the retry routes to a different replica rather than trying the same one again.
## Timeouts
The **predict timeout** controls how long a sync request can take from the moment it's forwarded to a replica until a response must be returned. If your model's inference exceeds this window, the request is cancelled and the client receives a `504`. The server-side default is 1200 seconds (20 minutes). If you need requests to fail faster than that, set a client-side timeout in your HTTP client.
The **async predict timeout** is 3600 seconds (1 hour) and works the same way for [async requests](/inference/async), except that instead of returning a `504` to the caller, the request is marked as failed with a `MODEL_PREDICT_TIMEOUT` error status and your webhook receives the error payload.
The **parking timeout**, which governs how long a request waits in the queue when no replica is available, is set equal to the predict timeout. The logic behind this is that if a request wouldn't have time to complete inference even if a replica appeared right now, there's no benefit to holding it in the queue any longer. One practical consequence is that the predict timeout also determines how long your deployment can take to cold-start before parked requests begin failing.
For **streaming responses**, timeouts behave differently because the HTTP headers, including the `200` status code, are sent when the stream begins. If the timeout expires mid-stream, the stream stops and the connection closes without an error code, since the status was already written. Most HTTP clients surface this as a connection reset or incomplete response rather than a timeout error.
## HTTP status codes
The inference API returns a specific set of status codes, and the sections above explain the conditions that produce each one. This table is a reference for quick lookup.
For what each error means, how to tell a model failure from a Baseten-side issue, and where to look next, see [Inference errors](/inference/errors).
| Code | Meaning | When it occurs | What to do |
| ----- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200` | Success | Normal predict response. | None. |
| `202` | Accepted | Async predict request queued successfully. | Poll for results or wait for your [webhook](/inference/async). |
| `401` | Unauthorized | Invalid or missing API key. | Check your [API key](/organization/api-keys). |
| `429` | Too Many Requests | Load shedding triggered, capacity was unavailable when the request arrived, or a subsequent async request arrived while there was still no capacity. | Retry with exponential backoff. If persistent, increase [max replicas](/deployment/autoscaling/overview#max_replica) or [concurrency target](/deployment/autoscaling/overview#concurrency-target). |
| `499` | Client Closed Request | Client disconnected before the response was written. | No server-side action needed. Review client-side timeout configuration if unexpected. |
| `500` | Internal Server Error | A sync request's parking timeout expired before a replica became available. | Retry after a brief wait. If persistent, increase [max replicas](/deployment/autoscaling/overview#max_replica) or keep [minimum replicas](/deployment/autoscaling/overview#min_replica) above zero. |
| `502` | Bad Gateway | The request was cancelled, or the model became unavailable during inference. | Retry. If persistent, check model logs for crashes or errors in your `predict` function. |
| `503` | Service Unavailable | The routing layer couldn't find a replica endpoint, typically during a deployment rollout or immediately after a replica failure. | Retry. If persistent, check deployment status in the Baseten dashboard. |
| `504` | Gateway Timeout | The request exceeded the server-side predict timeout (1200 seconds). | Optimize your model's inference speed. If you're seeing this consistently, contact support about adjusting the timeout. |
A `500` from a sync request during a cold start can mean the parking timeout expired before a replica finished starting. Retrying after a brief wait of 30 seconds to a minute often succeeds once the replica is ready.
## Request cancellation
When a client disconnects before the response is written, the routing layer detects the closed connection and cancels the in-flight work. The server logs this as a `499`. In the common case, such as a user closing a browser tab or a client-side timeout firing, this is harmless and the `499` is informational rather than an error.
The more important question is whether cancellation propagates all the way to the GPU. If a client disconnects during a long generation and the model keeps running, you're paying for GPU time that produces tokens nobody will read. Baseten cancels in-flight work automatically so this doesn't happen. When the routing layer detects a disconnect, it signals the inference engine, which aborts the running request and frees GPU resources. This works across engines including TRT-LLM and vLLM.
If you're using a custom model server, you can implement cancellation yourself using Truss request objects. See [Request handling](/development/model/streaming-and-endpoints#request-handling) for code examples.
## Next steps
Reduce cold start latency with warm replicas and pre-warming strategies.
Configure concurrency targets, replica counts, and scaling dynamics.
Fire-and-forget inference with webhook delivery.
Diagnose common deployment issues including autoscaling problems.
# Traffic patterns
Source: https://docs.baseten.co/deployment/autoscaling/traffic-patterns
Identify your traffic pattern and configure autoscaling settings to match.
Different traffic patterns require different autoscaling configurations.
Identify your pattern below for recommended starting settings.
These are **starting points**, not final answers. Monitor your
deployment's performance and adjust based on observed behavior. See
[Autoscaling](/deployment/autoscaling/overview) for parameter details.
## Identify your pattern
Not sure which pattern you have? Check your metrics:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the sidebar, then select your model.
2. Choose the **Metrics** tab.
3. Look at **Inference volume** and **Replicas** over the past week.
4. Compare to the patterns below.
| You see... | Your pattern is... |
| ----------------------------------------------------- | ------------------------------- |
| Frequent small spikes that quickly return to baseline | [Jittery](#jittery-traffic) |
| Sharp jumps that stay high for a while | [Bursty](#bursty-traffic) |
| Long flat periods with occasional large bursts | [Scheduled](#scheduled-traffic) |
| Gradual rises and falls, smooth curves | [Steady](#steady-traffic) |
Some workloads are a mix of patterns. If your traffic has both smooth diurnal patterns AND occasional bursts, optimize for the bursts (they cause the most pain) and accept slightly higher cost during steady periods.
## Jittery traffic
Small, frequent spikes that quickly return to baseline.
### Characteristics
* Baseline replica count is steady, but **spikes up by 2x several times per hour**.
* Spikes are short-lived and return to baseline quickly.
* Often not real load growth, just temporary surges causing overreaction.
### Common causes
* Consumer products with intermittent usage bursts.
* Traffic splitting or A/B testing with low percentages.
* Polling clients with synchronized intervals.
### Recommended settings
| Parameter | Value | Why |
| ------------------ | ----------------- | ----------------------------------------------- |
| Autoscaling window | **2-5 minutes** | Smooth out noise, avoid reacting to every spike |
| Scale-down delay | **300-600s** | Moderate stability |
| Target utilization | **70%** | Default is fine |
| Concurrency target | Benchmarked value | Start conservative |
A longer autoscaling window averages out the jitter so the autoscaler doesn't chase every small spike. You're trading reaction speed for stability, which is acceptable when the spikes aren't sustained load increases.
If you're still seeing oscillation with these settings, increase the scale-down delay before lowering target utilization.
## Bursty traffic
### Characteristics
* Traffic **jumps sharply** (2x+ within 60 seconds).
* Stays high for a sustained period before dropping.
* The "pain" is queueing and latency spikes while new replicas start.
### Common causes
* Daily morning ramp-up (users starting their day).
* Marketing events, product launches, viral moments.
* Top-of-hour scheduled jobs or cron-triggered traffic.
### Recommended settings
| Parameter | Value | Why |
| ------------------ | ---------- | --------------------------------------------- |
| Autoscaling window | **30-60s** | React quickly to genuine load increases |
| Scale-down delay | **900s+** | Handle back-to-back waves without thrashing |
| Target utilization | **50-60%** | More headroom absorbs the burst while scaling |
| Min replicas | **≥2** | Redundancy + reduces cold start impact |
Short window means fast reaction. Long delay prevents scaling down between waves. Lower utilization gives you buffer capacity while new replicas start.
### Pre-warming for predictable bursts
To pre-warm before a predictable burst (morning ramp, scheduled events):
1. Before the expected spike, bump min replicas:
```bash Request theme={"system"}
curl -X PATCH \
https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/autoscaling_settings \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{"min_replica": 5}'
```
2. After the burst subsides, reset to your normal minimum:
```bash Request theme={"system"}
curl -X PATCH \
https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/autoscaling_settings \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{"min_replica": 2}'
```
Automate pre-warming with cron jobs or your orchestration system.
Bumping min replicas 10-15 minutes before known peaks avoids cold starts for the first requests after the spike.
## Scheduled traffic
### Characteristics
* **Long periods of low or zero traffic**.
* Large bursts tied to job schedules (hourly, daily, weekly).
* Traffic patterns are predictable but infrequent.
### Common causes
* ETL pipelines and data processing jobs.
* Embedding backfills and batch inference.
* Periodic evaluation or testing jobs.
* Document processing triggered by user uploads.
### Recommended settings
| Parameter | Value | Why |
| ------------------ | --------------------------------------------------------------- | ----------------------------------------- |
| Min replicas | **0** (if cold starts acceptable) or **1** (during job windows) | Cost savings when idle |
| Scale-down delay | **Moderate to high** | Jobs often come in waves |
| Autoscaling window | **60-120s** | Don't overreact to the first few requests |
| Target utilization | **70%** | Default is fine |
Scale-to-zero saves significant cost during idle periods. The moderate window prevents overreacting to the initial requests of a batch. If jobs come in waves, a longer delay keeps replicas warm between them.
### Scheduled pre-warming
To pre-warm for predictable batch jobs, use cron + API:
1. Five minutes before the hourly job, scale up:
```bash Terminal theme={"system"}
0 * * * * curl -X PATCH \
https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/autoscaling_settings \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{"min_replica": 3}'
```
2. Thirty minutes after the job completes, scale back down:
```bash Terminal theme={"system"}
30 * * * * curl -X PATCH \
https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/autoscaling_settings \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{"min_replica": 0}'
```
If you use scale-to-zero, the first request of each batch will experience a [cold start](/deployment/autoscaling/cold-starts). For latency-sensitive batch jobs, keep min replicas at 1 during expected job windows.
## Steady traffic
### Characteristics
* Traffic **rises and falls gradually** over the day.
* Classic diurnal pattern with no sharp edges.
* Predictable, cyclical behavior.
### Common causes
* Always-on inference APIs with consistent user base.
* B2B applications with business-hours usage.
* Production workloads with stable, mature traffic.
### Recommended settings
| Parameter | Value | Why |
| ------------------ | ------------ | ------------------------------ |
| Target utilization | **70-80%** | Can run replicas hotter safely |
| Autoscaling window | **60-120s** | Moderate reaction speed |
| Scale-down delay | **300-600s** | Moderate |
| Min replicas | **≥2** | Redundancy for production |
Without sudden spikes, you don't need as much headroom. You can run replicas at higher utilization (lower cost) because load changes are gradual and predictable. The autoscaler has time to react.
Smooth traffic is the easiest to tune. Start with defaults, monitor for a week, then optimize for cost by gradually raising target utilization while watching p95 latency.
## Next steps
* [Autoscaling](/deployment/autoscaling/overview): Full parameter documentation.
* [Scale a deployment](/deployment/manage/scaling): Apply these settings from the CLI or API.
* [Troubleshooting autoscaling](/troubleshooting/deployments#autoscaling-issues): Diagnose and fix common problems.
* [Truss configuration reference](/reference/truss-configuration): Configure predict\_concurrency in your model.
# CI/CD
Source: https://docs.baseten.co/deployment/ci-cd
Automate Truss deployments with GitHub Actions.
Manual `truss push` works when one person deploys one model. When your model code lives in a shared repository with multiple contributors, deploys drift out of sync: someone pushes from a stale branch, a config change skips review, a broken model reaches production because nobody ran a predict check first.
The [Truss Push GitHub Action](https://github.com/marketplace/actions/truss-push) ties deployment to your Git workflow. Every push or pull request can trigger a deploy, validate the model with a predict request, and clean up automatically. The action supports both Truss models and [chains](/development/chain/deploy).
## What happens during a run
The action runs through four phases, each in a collapsible log group in the GitHub Actions UI:
1. **Load config**: For models, reads `config.yaml` from the Truss directory and extracts `model_metadata.example_model_input` for the predict step (unless you override it with `predict-payload`). For chains, detects the entrypoint class from the `.py` file.
2. **Deploy**: Pushes the model or chain to Baseten and streams deployment logs directly into the GitHub Actions output. You don't need to open the Baseten dashboard to watch the build. The action names each deployment from git context: `PR-42_abc1234` for pull requests, `abc1234` for direct pushes (customizable with `deployment-name`).
3. **Predict**: Sends a predict request and reports latency. For streaming models (when the payload includes `"stream": true`), reports time-to-first-byte, token count, and tokens per second.
4. **Cleanup**: Deactivates the newly created deployment if `cleanup: true`. Set `cleanup: false` when deploying to an environment or when you want to inspect the deployment manually.
After every run, the action writes a summary table to the GitHub Actions job summary with deploy time, predict metrics, and a direct link to the deployment logs on Baseten.
## Prerequisites
Store your Baseten API key as an [encrypted secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets) named `BASETEN_API_KEY` in your repository or organization settings. See [API keys](/organization/api-keys) for how to generate one.
## Deploy to an environment on merge
Deploy a validated model to a specific environment every time code merges to `main`.
Create `.github/workflows/deploy.yml` and add the following:
```yaml .github/workflows/deploy.yml theme={"system"}
name: Deploy to production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: basetenlabs/action-truss-push@v0.1
with:
truss-directory: "./my-model"
baseten-api-key: ${{ secrets.BASETEN_API_KEY }}
environment: "production"
cleanup: false
```
Setting `environment` publishes the deployment to the specified environment. Setting `cleanup: false` keeps the deployment active so it can serve traffic.
## Validate on pull request
Catch model regressions before they reach production. The action deploys, runs a predict request, and tears down the deployment inside the PR check.
Create `.github/workflows/validate-model.yml` and add the following:
```yaml .github/workflows/validate-model.yml theme={"system"}
name: Validate model
on:
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: basetenlabs/action-truss-push@v0.1
with:
truss-directory: "./my-model"
baseten-api-key: ${{ secrets.BASETEN_API_KEY }}
```
The action reads `model_metadata.example_model_input` from your `config.yaml` to build the predict request. With the default (`cleanup: true`), the deployment is deactivated after validation, so no resources are left running.
## Deploy a chain
Deploy a Baseten chain from a Python source file. The action auto-detects chains when `truss-directory` points to a `.py` file:
```yaml theme={"system"}
- uses: basetenlabs/action-truss-push@v0.1
with:
truss-directory: "./chains/my_chain.py"
baseten-api-key: ${{ secrets.BASETEN_API_KEY }}
model-name: "my-rag-chain"
cleanup: false
predict-payload: '{"query": "What is Baseten?"}'
```
For chains, the predict payload must be provided explicitly with `predict-payload` because there's no `config.yaml` to read example input from.
## Deploy multiple models
Use a matrix strategy to deploy each model in your repository as a separate job.
Create `.github/workflows/deploy-all.yml` and add the following:
```yaml .github/workflows/deploy-all.yml theme={"system"}
name: Deploy models
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
matrix:
model:
- path: models/text-classifier
- path: models/image-generator
- path: models/embeddings
steps:
- uses: actions/checkout@v4
- uses: basetenlabs/action-truss-push@v0.1
with:
truss-directory: ${{ matrix.model.path }}
baseten-api-key: ${{ secrets.BASETEN_API_KEY }}
environment: "production"
cleanup: false
```
Each matrix entry runs as a separate job. If one model fails, the others still deploy.
## Custom predict validation
Override the default predict payload when your model needs a specific input shape that differs from `model_metadata.example_model_input`:
```yaml theme={"system"}
- uses: basetenlabs/action-truss-push@v0.1
with:
truss-directory: "./my-model"
baseten-api-key: ${{ secrets.BASETEN_API_KEY }}
predict-payload: '{"prompt": "Hello, world!", "max_new_tokens": 128}'
predict-timeout: 60
```
If neither `predict-payload` nor `model_metadata.example_model_input` is set, the action skips the predict step entirely and the deployment isn't validated.
## Deploy with labels
Attach metadata labels to track deployments in your CI pipeline:
```yaml theme={"system"}
- uses: basetenlabs/action-truss-push@v0.1
with:
truss-directory: "./my-model"
baseten-api-key: ${{ secrets.BASETEN_API_KEY }}
labels: '{"team": "ml-platform", "triggered-by": "ci"}'
```
## Override model name
Set a custom model name instead of using the name from `config.yaml`:
```yaml theme={"system"}
- uses: basetenlabs/action-truss-push@v0.1
with:
truss-directory: "./my-model"
baseten-api-key: ${{ secrets.BASETEN_API_KEY }}
model-name: "my-custom-name"
```
## Use action outputs
The action exposes outputs you can reference in downstream steps. This example posts the deploy time as a PR comment:
```yaml theme={"system"}
steps:
- uses: actions/checkout@v4
- uses: basetenlabs/action-truss-push@v0.1
id: deploy
with:
truss-directory: "./my-model"
baseten-api-key: ${{ secrets.BASETEN_API_KEY }}
- name: Comment on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `Model deployed in ${{ steps.deploy.outputs.deploy-time-seconds }}s. Status: ${{ steps.deploy.outputs.status }}`
})
```
See the full list of inputs and outputs in the [Truss Push GitHub Action reference](/reference/ci/github-action).
## Next steps
Promote validated deployments to production without downtime.
Manage staging and production environments for your models.
Scale, pause, promote, and clean up deployments from the CLI or API.
## Troubleshooting
**`deploy_timeout`:** The default timeout is 45 minutes, which accommodates large builds like TRT-LLM. For smaller models, reduce `deploy-timeout-minutes` to fail faster. If your model legitimately needs more time, increase the value.
**`deploy_failed`:** Check your `config.yaml` for syntax errors and verify the `BASETEN_API_KEY` secret is set correctly. The action logs the full build output in collapsible sections. Expand them in the GitHub Actions UI to see the exact error.
**`predict_failed`:** Verify the predict payload shape matches what your model expects. Check `model_metadata.example_model_input` in `config.yaml`, or override it with `predict-payload`. For chains, the predict payload must be provided explicitly.
**`cleanup_failed`:** The deployment may still be running. Deactivate it manually from the [Baseten dashboard](https://app.baseten.co).
**`429 Too Many Requests`:** The action calls management API endpoints that are rate limited per API key. Matrix jobs that fan out across many models can exceed the per-endpoint limits. See [management API rate limits](/reference/management-api/rate-limits) for thresholds and backoff guidance.
**No predict output:** If neither `predict-payload` nor `model_metadata.example_model_input` is configured, the action skips prediction entirely. The deployment runs but isn't validated. Add an example input to your `config.yaml` (models) or set `predict-payload` (chains) to enable validation.
**`Team selection required but running in a non-interactive context`:** Your API key has access to multiple teams and Truss can't infer a single target team without a prompt. Pass the team explicitly with the `team` input on the action (or `--team ` if you invoke `truss push` directly):
```yaml theme={"system"}
- uses: basetenlabs/action-truss-push@v0.1
with:
truss-directory: "./my-model"
baseten-api-key: ${{ secrets.BASETEN_API_KEY }}
team: "ml-platform"
```
See [Deploy to a team](/organization/teams#use-the-truss-cli) for the team-resolution rules. Requires Truss `0.18.3` or later.
# Concepts
Source: https://docs.baseten.co/deployment/concepts
Deployments, environments, resources, autoscaling, and CI/CD on Baseten.
When you run `truss push`, Baseten creates a [deployment](/deployment/deployments): a running instance of your model on GPU infrastructure with an API endpoint. This page explains how deployments are managed, versioned, and scaled. For the operations themselves, see [Manage deployments](/deployment/manage/overview).
## Deployments
A [deployment](/deployment/deployments) is a single version of your model running on specific hardware. Every `truss push` creates a new deployment. You can have multiple deployments of the same model running simultaneously, which is how you test new versions without affecting production traffic. Deployments can be deactivated to stop serving (and stop incurring cost) or deleted permanently when they're no longer needed.
For rapid iteration, use `truss push --watch` to create a **development deployment**, a mutable instance that live-reloads as you edit your model code. Development deployments can't be promoted to an environment.
## Environments
As your model matures, you'll want a way to manage releases. [Environments](/deployment/environments) provide stable endpoints that persist across deployments. A typical setup has a development environment for testing and a production environment for live traffic. Each environment maintains its own autoscaling settings, metrics, and endpoint URL. When a new deployment is ready, you promote it to an environment, and traffic shifts to the new version without changing the endpoint your application calls.
## Resources
Every deployment runs on a specific [instance type](/deployment/resources) that defines its GPU, CPU, and memory allocation. Choosing the right instance balances inference speed against cost. You'll set the instance type in your `config.yaml` before deployment, or adjust it later through the Baseten UI. Smaller models run well on an L4 (24 GB VRAM), while large LLMs may need A100s or H100s with tensor parallelism across multiple GPUs.
## Autoscaling
You don't manage replicas manually. [Autoscaling](/deployment/autoscaling/overview) adjusts the number of running instances based on incoming traffic. You'll configure a minimum and maximum replica count, a concurrency target, and a scale-down delay. When traffic drops, replicas scale down (optionally to zero, eliminating all cost). When traffic spikes, new replicas spin up automatically. [Cold start optimization](/deployment/autoscaling/cold-starts) and network acceleration keep response times fast even when scaling from zero.
For the mechanics of how the autoscaler tracks in-flight requests and adjusts replicas, see [How Baseten works](/concepts/howbasetenworks#autoscaling). For engine-specific autoscaling settings (BEI and Engine-Builder-LLM), see [Autoscaling engines](/engines/performance-concepts/autoscaling-engines).
## Request lifecycle
When a request reaches your deployment, it passes through authentication, routing, and replica selection before your model code executes. Understanding this path helps you diagnose errors and configure timeouts. See [Request lifecycle](/deployment/autoscaling/request-lifecycle) for the full journey of a request, including queuing, load shedding, and HTTP status codes.
## CI/CD
When your model code lives in a Git repository, you can automate deployments with CI/CD. The [Truss Push GitHub Action](/deployment/ci-cd) deploys your model, validates it with a predict request, and optionally promotes it to production. You'll configure the trigger (such as pushes or pull requests to specific branches) in your GitHub Actions workflow file.
# Deployments
Source: https://docs.baseten.co/deployment/deployments
Understand deployments, development deployments, environments, and promotion on Baseten.
A *deployment* in Baseten is a containerized instance of a model that serves inference requests through an API endpoint. Deployments exist independently but can be promoted to an environment for structured access and scaling.
Baseten automatically wraps every deployment in a REST API. Once deployed, query your model with an HTTP request:
```python predict.py theme={"system"}
import requests
import os
resp = requests.post(
"https://model-abc123.api.baseten.co/deployment/def456/predict",
headers={"Authorization": f"Bearer {os.environ['BASETEN_API_KEY']}"},
json={"text": "Hello my name is Baseten"},
)
print(resp.json())
```
For the full request and response format, see [running inference on your deployment](/inference/calling-your-model).
## Development deployment
A *development deployment* is a mutable instance designed for rapid iteration. Create one with `truss push --watch` (for models) or `truss chains push --watch` (for Chains). It stays in the development state until promoted and can't be renamed.
Key characteristics:
* Live reload enables direct updates without redeployment.
* Single replica, scales to zero when idle to conserve compute resources.
* No autoscaling or zero-downtime updates.
* Can be promoted to create a persistent deployment.
Once promoted, the development deployment transitions to a deployment and can optionally be promoted to an environment.
## Environments and promotion
Environments provide logical isolation for managing deployments but aren't required for a deployment to function. You can run a deployment independently or promote it to an environment for controlled traffic allocation and scaling.
* The production environment exists by default.
* Custom environments (for example, staging) can be created for specific workflows.
* Promoting a deployment doesn't modify its behavior, only its routing and lifecycle management.
### Rolling deployments
Rolling deployments replace replicas incrementally when promoting a deployment to an environment. Instead of swapping all traffic at once, rolling deployments scale up the candidate, shift traffic proportionally, and scale down the previous deployment in controlled steps. You can pause, resume, cancel, or force-complete a rolling deployment at any point. Rolling deployments are disabled by default; enable them per environment.
For more information, see [Rolling deployments](/deployment/rolling-deployments).
### Canary deployments (deprecated)
Canary deployments are deprecated. Use [rolling deployments](/deployment/rolling-deployments) for incremental traffic shifting with finer control over replica provisioning and rollback.
Canary deployments support incremental traffic shifting to a new deployment in 10 evenly distributed stages over a configurable time window. Enable or cancel canary rollouts from the UI or [REST API](/reference/management-api/environments/update-an-environments-settings).
## Manage deployments
To scale, promote, deactivate, delete, or inspect deployments, see
[Manage deployments](/deployment/manage/overview).
### Name deployments
By default, deployments of a model are named `deployment-1`,
`deployment-2`, and so forth sequentially.
**To name a deployment**:
Name it at deploy time with
[`truss push --deployment-name`](/reference/cli/truss/push):
```bash theme={"system"}
truss push --deployment-name my-deployment
```
Rename it later in the console:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. Select the deployment under **Deployments**.
4. Choose **Rename deployment**.
5. Type the new name, then choose **Rename deployment** to confirm.
Names are purely cosmetic and don't affect API paths, which work by model
and deployment IDs.
### Label deployments
Labels are JSON key-value metadata you attach to a deployment to organize and track it, for example by team, environment, or the pipeline that created it. Set them at deploy time with the `--labels` flag on `truss push` (`truss push --labels '{"team": "ml-platform", "env": "staging"}'`), the `labels` argument to [`truss.push()`](/reference/sdk/truss/push), or the `labels` input on the [deploy GitHub Action](/reference/ci/github-action). To attach labels automatically in a CI pipeline, see [Deploy with labels](/deployment/ci-cd#deploy-with-labels).
### Deactivate a deployment
Deactivate a deployment to suspend inference while preserving its
configuration; requests fail with a `400` error until you activate it again.
To deactivate or activate, see
[Manage the deployment lifecycle](/deployment/manage/lifecycle). For
demand-driven deployments, consider
[scale to zero](/deployment/manage/scaling#scale-to-zero) instead.
### Delete deployments
Deletion is permanent: compute is released, requests return a `404` error,
and the deployment leaves the dashboard (usage logs are retained). A
deployment that's associated with an environment, or is the only deployment
of a model, can't be deleted; promote a replacement first. To delete, see
[Manage the deployment lifecycle](/deployment/manage/lifecycle#delete-a-deployment).
# Environments
Source: https://docs.baseten.co/deployment/environments
Manage your model's release cycles with environments.
Environments provide structured management for deployments, ensuring controlled rollouts, stable endpoints, and autoscaling. They help teams stage, test, and release models without affecting production traffic.
Deployments can be promoted to an environment (for example, "staging") to validate outputs before moving to production, allowing for safer model iteration and evaluation.
## Deployment management
Environments support structured validation before promoting a deployment, including:
* Automated tests and evaluations.
* Manual testing in pre-production.
* Gradual traffic shifts with canary deployments.
* Shadow serving for real-world analysis.
Promoting a deployment ensures it inherits environment-specific scaling and monitoring settings:
* Dedicated API endpoint. For more information, see [Predict endpoints](/reference/inference-api/overview#predict-endpoints).
* Autoscaling controls: Scale behavior is managed per environment.
* Traffic ramp-up: Supports [rolling deployments](/deployment/rolling-deployments) for incremental traffic shifting.
* Monitoring and metrics: scope [logs](/observability/logs#scope-by-environment-or-deployment) and [metrics](/observability/metrics) to the environment in the dashboard, or [export environment metrics](/observability/export-metrics/overview) to your own observability stack.
The production environment operates like any other environment but has restrictions:
* It can't be deleted unless the entire model is removed.
* You can't create additional environments named "production."
## Custom environments
In addition to the standard production environment, you can create as many custom environments as needed, either from the model management page on the Baseten dashboard or through the [create environment endpoint](/reference/management-api/environments/create-an-environment) in the management API.
## Deployment promotion
When you promote a deployment to an environment, Baseten associates the deployment with that environment and applies the environment's autoscaling settings. If the deployment can be reused directly, promotion completes without creating new resources. Otherwise, Baseten creates a new deployment with a unique ID, initializes its resources, and replaces the existing deployment in that environment.
A new deployment is created when:
* The deployment is already associated with another environment.
* The environment has a different instance type or resource profile.
* [Re-deploy on promotion](#re-deploy-on-promotion) is enabled.
If a previous deployment existed in the environment, the new one inherits its autoscaling settings and the old deployment is demoted.
In every case, promotion reuses the image the deployment was already built with. Even when Baseten creates a new deployment, it copies that existing image rather than rebuilding or re-pulling your base image.
### Published deployment promotion
If a published deployment (not a development deployment) is promoted, its autoscaling settings are updated to match the environment.
Previous deployments are demoted but remain in the system.
## Direct deployment to an environment
You can deploy directly to a named environment by specifying `--environment` in `truss push`:
```sh Terminal theme={"system"}
cd my_model/
truss push --environment {environment_name}
```
Only one active promotion per environment is allowed at a time.
## Environment access in code
The environment name is available in `model.py` through the `environment` keyword argument:
```python model/model.py theme={"system"}
def __init__(self, **kwargs):
self._environment = kwargs["environment"]
```
You can use the environment in your `load()` method to configure per-environment behavior:
```python model/model.py theme={"system"}
def load(self):
if self._environment.get("name") == "production":
self.setup_sentry()
self.model = self.load_production_weights()
else:
self.model = self.load_default_weights()
```
If you use environment-specific configuration in `load()`, you'll need to enable re-deploy on promotion to ensure the environment is correctly initialized after each promotion. See [Re-deploy on promotion](#re-deploy-on-promotion) for details.
The `environment` keyword argument is only available to Python Truss models. Custom servers read the environment name from the filesystem instead. See [Environment name](/development/model/custom-server#environment-name).
## Re-deploy on promotion
By default, promoting a deployment reuses the existing deployment when possible. This is the fastest promotion path, but it means `load()` doesn't re-run. Any environment-specific configuration set during the original `load()` call persists, even if the deployment moves to a different environment.
You can configure an environment to create a fresh deployment every time you promote to it. The new deployment reuses the same image and re-runs `load()` with the target environment's context, so environment-specific configuration takes effect.
Enable this if your `load()` method uses `kwargs["environment"]` to configure per-environment behavior, or if you promote the same source deployment to multiple environments and want each to get a fresh deployment.
To enable re-deploy on promotion:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the sidebar, then select your model.
2. Open the environment's settings.
3. Toggle **Re-deploy when promoting**.
You can also set it through the [update environment settings endpoint](/reference/management-api/environments/update-an-environments-settings).
If you promote a deployment that's already associated with an environment, Baseten creates a new deployment regardless of this setting.
## Regional environments
Regional environments restrict inference traffic to a specific geographic region for data residency compliance. When your organization enables regional environments, each environment gets a dedicated regional endpoint that routes directly to infrastructure in the designated region.
Your Baseten account team configures regional environments at the organization level. Contact them to enable regional environments.
### Regional endpoint format
Regional endpoints embed the environment name in the hostname instead of the URL path:
Call a model's regional endpoint with `/predict` or `/async_predict`.
```
https://model-{model_id}-{env_name}.api.baseten.co/predict
```
For example, a model with ID `abc123` in the `prod-us` environment:
```
https://model-abc123-prod-us.api.baseten.co/predict
```
Call a chain's regional endpoint with `/run_remote` or `/async_run_remote`.
```
https://chain-{chain_id}-{env_name}.api.baseten.co/run_remote
```
Connect to a regional WebSocket endpoint for models or chains.
```
wss://model-{model_id}-{env_name}.api.baseten.co/websocket
wss://chain-{chain_id}-{env_name}.api.baseten.co/websocket
```
Connect to a regional gRPC endpoint using the `grpc.api.baseten.co` subdomain.
```
model-{model_id}-{env_name}.grpc.api.baseten.co:443
```
The regional endpoint URL appears in your model's API endpoint section in the Baseten dashboard once your organization has regional environments enabled.
### API restrictions on regional endpoints
Regional endpoints derive the environment exclusively from the hostname. Path-based routing (`/environments/`, `/production/`, `/deployment/`) is rejected. For gRPC, don't set `x-baseten-environment` or `x-baseten-deployment` metadata headers.
## Environment deletion
You can delete environments, except for production. To remove a production deployment, first promote another deployment to production or delete the entire model.
* Deleted environments are removed from the overview but remain in billing history.
* They don't consume resources after deletion.
* API requests to a deleted environment return a 404 error.
Deletion is permanent. Consider deactivation instead.
# Manage the deployment lifecycle
Source: https://docs.baseten.co/deployment/manage/lifecycle
Promote, deactivate, activate, and delete deployments from the console, CLI, or Management API.
Manage your deployment through its lifecycle. After you push a model,
promote its deployment to serve an environment, deactivate it to stop
compute spend, activate it to bring it back, and delete it when you no
longer need it. For what each deployment state means, see
[Deployments](/deployment/deployments).
## Promote to an environment
Promote a validated deployment to production (or a custom environment) to
route that environment's traffic to it. What happens to the previously
promoted deployment is controlled by the target environment's promotion
cleanup strategy; see [Environments](/deployment/environments) for the
concepts and [Rolling deployments](/deployment/rolling-deployments) for
incremental traffic shifting.
**To promote a deployment**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. Select the deployment under **Deployments**.
4. Choose **Promote to...**.
5. In the **Promote deployment** dialog, choose the target environment.
6. Choose **Promote** to confirm.
**To promote a deployment**:
Promote to production (the CLI's default target):
```bash Command theme={"system"}
baseten model deployment promote --model-id --deployment-id
```
```txt Output theme={"system"}
Promoted deployment to environment production
```
Promote to another environment with `--environment `:
```bash Command theme={"system"}
baseten model deployment promote --model-id --deployment-id --environment staging
```
```txt Output theme={"system"}
Promoted deployment to environment staging
```
**To promote a deployment**:
Promote to production:
```bash Request theme={"system"}
curl -X POST "https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/promote" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{
"id": "{deployment_id}",
"name": "deployment-2",
"model_id": "{model_id}",
"is_production": true,
"is_development": false,
"status": "ACTIVE",
"active_replica_count": 1,
"environment": "production",
"instance_type_name": "1x2 - 1 vCPU, 2 GiB RAM",
"autoscaling_settings": {
"min_replica": 0,
"max_replica": 1,
"autoscaling_window": 60,
"scale_down_delay": 900,
"concurrency_target": 1,
"target_utilization_percentage": 70
},
"created_at": "2026-07-07T18:29:11.147Z",
"labels": {}
}
```
Promote to another environment by POSTing the deployment ID to that
environment's own promote endpoint:
```bash Request theme={"system"}
curl -X POST "https://api.baseten.co/v1/models/{model_id}/environments/staging/promote" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"deployment_id": "{deployment_id}"}'
```
```json Response theme={"system"}
{
"id": "{deployment_id}",
"name": "deployment-2",
"model_id": "{model_id}",
"is_production": false,
"is_development": false,
"status": "ACTIVE",
"active_replica_count": 1,
"environment": "staging",
"instance_type_name": "1x2 - 1 vCPU, 2 GiB RAM",
"autoscaling_settings": {
"min_replica": 0,
"max_replica": 1,
"autoscaling_window": 60,
"scale_down_delay": 900,
"concurrency_target": 1,
"target_utilization_percentage": 70
},
"created_at": "2026-07-07T18:29:11.147Z",
"labels": {}
}
```
**To promote from CI/CD**:
Each push to `main` deploys the Truss and promotes the new deployment to
production:
```yaml .github/workflows/deploy.yml theme={"system"}
name: Deploy to production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: basetenlabs/action-truss-push@v0.1
with:
truss-directory: "./my-model"
baseten-api-key: ${{ secrets.BASETEN_API_KEY }}
environment: "production"
cleanup: false
```
For more information, see [CI/CD](/deployment/ci-cd) and the
[Truss Push GitHub Action reference](/reference/ci/github-action).
Promotion routes the environment's traffic to the new deployment. Verify
it:
**To verify the promotion**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. On the model's overview, confirm the environment card's
**Current deployment** shows your deployment.
**To verify the promotion**:
```bash Command theme={"system"}
baseten model deployment describe --model-id --deployment-id --jq '.environment'
```
```json Output theme={"system"}
"production"
```
**To verify the promotion**:
```bash Request theme={"system"}
curl "https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{
"id": "def456",
"environment": "production",
"is_production": true,
"status": "ACTIVE",
...
}
```
For more information, see the
[promote deployment](/reference/management-api/deployments/promote/promotes-a-deployment-to-production)
and [promote to environment](/reference/management-api/deployments/promote/promotes-a-deployment-to-an-environment)
endpoints.
## Deactivate a deployment
Deactivate a deployment to stop compute spend without deleting it. The
deployment keeps its configuration and stays visible in the dashboard, but
releases its replicas. Requests to a deactivated deployment fail with a
`400` error; see
[troubleshooting](/troubleshooting/deployments#issue-requests-fail-with-model-version-is-deactivated)
for the error and recovery.
**To deactivate a deployment**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. Select the deployment under **Deployments**.
4. Choose **Deactivate deployment**.
5. Choose **Yes, deactivate** to confirm.
**To deactivate a deployment**:
```bash Command theme={"system"}
baseten model deployment deactivate --model-id --deployment-id
```
```txt Output theme={"system"}
Deactivated deployment
```
The CLI prompts for confirmation; pass `--yes` to skip it (required when
scripting).
**To deactivate a deployment**:
```bash Request theme={"system"}
curl -X POST "https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/deactivate" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{"success": true}
```
The deployment's `status` moves to `INACTIVE` within a few seconds.
If you want the deployment to keep serving but stop paying for idle replicas,
[scale to zero](/deployment/manage/scaling#scale-to-zero) instead.
## Activate a deployment
Activate an inactive deployment to bring it back. Activation redeploys the
model, so the deployment passes through `DEPLOYING` before reaching `ACTIVE`.
**To activate a deployment**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. Select the inactive deployment under **Deployments**.
4. Choose **Activate deployment**.
**To activate a deployment**:
```bash Command theme={"system"}
baseten model deployment activate --model-id --deployment-id
```
```txt Output theme={"system"}
Activated deployment
```
**To activate a deployment**:
```bash Request theme={"system"}
curl -X POST "https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/activate" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{"success": true}
```
For more information, see
[`activate`](/reference/cli/baseten/model-deployment#activate)
and the [activate endpoint](/reference/management-api/deployments/activate/activates-a-deployment).
## Delete a deployment
Delete deployments to clean up finished experiments and stale versions.
Deletion is irreversible, and requests to a deleted deployment return `404`.
[Deactivate](#deactivate-a-deployment) instead if you might need the
deployment again.
A deployment that's associated with an environment, or is the only deployment
of a model, can't be deleted;
[push a new deployment](/reference/cli/truss/push) and promote it first, or
delete the whole model.
**To delete a deployment**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. Select the deployment under **Deployments**.
4. Choose **Delete deployment** and confirm.
The button is disabled while the deployment serves an environment or is
the model's only deployment.
**To delete a deployment**:
```bash Command theme={"system"}
baseten model deployment delete --model-id --deployment-id
```
```txt Output theme={"system"}
Deleted deployment
```
**To delete a deployment**:
```bash Request theme={"system"}
curl -X DELETE "https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{"id": "{deployment_id}", "deleted": true, "model_id": "{model_id}"}
```
You can also delete the model itself, which removes all of its deployments:
**To delete a model**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. On the model's overview, choose **Actions**, then **Delete model**.
4. Type the model's name to confirm, then choose **Delete**.
**To delete a model**:
```bash Command theme={"system"}
baseten model delete --model-id
```
```txt Output theme={"system"}
Deleted model ()
```
**To delete a model**:
```bash Request theme={"system"}
curl -X DELETE "https://api.baseten.co/v1/models/{model_id}" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{"id": "{model_id}", "deleted": true}
```
For more information, see the
[delete deployment](/reference/management-api/deployments/deletes-a-models-deployment-by-id)
and [delete model](/reference/management-api/models/deletes-a-model-by-id)
endpoints, and the CLI's [`delete`](/reference/cli/baseten/model-deployment#delete)
reference.
## Next steps
With lifecycle transitions scripted, the same commands slot into CI jobs,
cron cleanups, and incident runbooks.
* [Scale a deployment](/deployment/manage/scaling) to change replica behavior
without a lifecycle change.
* [Pull logs and metrics](/deployment/manage/logs-and-metrics) to verify a
deployment's health after a transition.
* [CI/CD](/deployment/ci-cd) to run deploys and promotions from GitHub
Actions.
# Pull logs and metrics
Source: https://docs.baseten.co/deployment/manage/logs-and-metrics
Fetch and stream logs and metrics for a deployment or an environment from the CLI or Management API for debugging and scripting.
When a deployment misbehaves, start with its logs and metrics. This page
covers reading both from the console, the CLI, and the Management API. For
metric definitions and Prometheus-style export, see
[Metrics](/observability/metrics) and the other Observability pages.
## Fetch and stream logs
Pull logs to debug an incident, grep for an error, or pipe context into an
agent. Fetch a time window (up to 7 days back):
**To view logs**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. Select the deployment under **Deployments**.
4. Choose **Logs**.
Filter by level or replica, search, or follow live output with the
controls at the top of the logs view.
**To fetch logs**:
```bash Command theme={"system"}
baseten model deployment logs --model-id --deployment-id --since 1h
```
```txt Output theme={"system"}
[2026-07-07 10:33:58]: Deploy was a success.
[2026-07-07 10:33:44]: (nvsmp) Completed model.load() execution in 6 ms
[2026-07-07 10:33:44]: (nvsmp) Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
```
The parenthesized prefix on each line is the replica that emitted it.
**To stream live logs**:
Pass `--tail` instead of a time window:
```bash Command theme={"system"}
baseten model deployment logs --model-id --deployment-id --tail
```
```txt Output theme={"system"}
[2026-07-07 10:33:44]: (nvsmp) Completed model.load() execution in 6 ms
[2026-07-07 10:33:58]: Deploy was a success.
...
```
Streaming continues until the deployment leaves a runnable state or you
interrupt with Ctrl-C. For machine-readable output, add `--output jsonl`
to stream one JSON log entry per line:
```bash Command theme={"system"}
baseten model deployment logs --model-id --deployment-id --tail --output jsonl
```
```json Output theme={"system"}
{"level":"INFO","message":"Completed model.load() execution in 6 ms","replica":"nvsmp","timestamp":"1783445624380306972"}
{"level":"INFO","message":"Deploy was a success.","replica":"","timestamp":"1783445638726940011"}
...
```
Narrow the stream with `--jq`: `--jq '.message'` extracts one field per
line, and `--jq 'select(.level=="ERROR") | .message'` keeps only error
lines.
**To fetch logs**:
```bash Request theme={"system"}
curl "https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/logs" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{
"logs": [
{
"timestamp": "1783445638726940011",
"message": "Deploy was a success.",
"replica": "",
"request_id": null,
"level": "INFO"
},
{
"timestamp": "1783445624380306972",
"message": "Completed model.load() execution in 6 ms",
"replica": "nvsmp",
"request_id": null,
"level": "INFO"
}
]
}
```
The window defaults to the last 30 minutes; pass `start_epoch_millis`
to widen it, up to 7 days. Timestamps are epoch nanoseconds. The API
fetches fixed windows only; to follow logs live, use the CLI's `--tail`.
For more information about filters like log level, search patterns, and
request ID, see [`logs`](/reference/cli/baseten/model-deployment#logs)
and the [logs endpoint](/reference/management-api/deployments/get-deployment-logs)
(beta).
## Fetch metrics
Pull metrics to check a deployment's health from a script: replica count,
request volume, and end-to-end latency quantiles.
**To view metrics**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. Select the deployment under **Deployments**.
4. Choose **Metrics**.
The metrics view charts request rates by status code, latency
percentiles, and replica counts. Set the window with the time-range
selector at the top.
**To fetch metrics**:
```bash Command theme={"system"}
baseten model deployment metrics --model-id --deployment-id
```
```txt Output theme={"system"}
METRIC QUANTILE STAT VALUE
baseten_replicas_active 1
baseten_end_to_end_response_time_seconds 0.5 -
baseten_end_to_end_response_time_seconds 0.9 -
baseten_end_to_end_response_time_seconds 0.95 -
baseten_end_to_end_response_time_seconds 0.99 -
baseten_end_to_end_response_time_seconds avg -
```
Latency values show `-` until the deployment has served requests in the
window; once it has, a STATUS column also breaks out request counts per
response code. The default is a current snapshot; pass `--mode summary`
or `--mode series` with `--since` to aggregate over a window instead.
**To fetch metrics**:
```bash Request theme={"system"}
curl "https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/metrics" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{
"start_epoch_millis": 1783445679297,
"end_epoch_millis": 1783445679297,
"mode": "CURRENT",
"step_seconds": null,
"metric_descriptors": [
{
"name": "baseten_replicas_active",
"unit_hint": "COUNT",
"kind": "GAUGE",
"label_sets": [{}]
}
],
"metric_values": [
{
"start_epoch_millis": 1783445679297,
"values": [[1.0]]
}
]
}
```
`metric_values` lines up with `metric_descriptors` by position: the
first value array belongs to the first metric, the second to the second,
and so on.
For more information about modes, windows, and metric selection, see
[`metrics`](/reference/cli/baseten/model-deployment#metrics)
and the [metrics endpoint](/reference/management-api/deployments/get-deployment-metrics)
(beta). For what each metric means, see
[Metrics](/observability/metrics).
## Fetch environment logs and metrics
Deployment scope answers "what is this specific deployment doing?"
Environment scope answers "what is production doing right now?", which is
usually the more useful operational question. An
[environment](/deployment/environments) spans every deployment that has
served it, so environment logs and metrics stay continuous across
promotions: during and after a rollout, one view covers the outgoing and
incoming deployments. Fetch them from any surface:
**To view an environment's logs and metrics**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. On the model overview, choose **Logs** or **View metrics** for the
environment.
You can also reach the same views from any deployment's logs or metrics
page: choose the environment from the dropdown in the upper left.
**To fetch environment logs**:
Target the environment by name instead of a deployment ID:
```bash Command theme={"system"}
baseten model environment logs --model-id --environment production --since 1h
```
```txt Output theme={"system"}
[2026-07-09 08:49:09]: (w44tt) Application shutdown complete.
[2026-07-09 08:48:41]: Scaling down replicas due to inactivity
[2026-07-09 08:34:09]: (67wjm) Terminated
...
```
The same windows and filters as deployment logs apply, including
`--tail` for live streaming and `--output jsonl` for machine-readable
output.
**To fetch environment metrics**:
```bash Command theme={"system"}
baseten model environment metrics --model-id --environment production
```
```txt Output theme={"system"}
METRIC QUANTILE STAT VALUE
baseten_replicas_active 1
baseten_end_to_end_response_time_seconds 0.5 -
baseten_end_to_end_response_time_seconds 0.9 -
baseten_end_to_end_response_time_seconds 0.95 -
baseten_end_to_end_response_time_seconds 0.99 -
baseten_end_to_end_response_time_seconds avg -
```
The default is a current snapshot; pass `--mode summary` or
`--mode series` with `--since` to aggregate over a window. In series
mode the window splits at each promotion, so every point reflects the
deployment serving the environment at that time.
**To fetch environment logs**:
```bash Request theme={"system"}
curl "https://api.baseten.co/v1/models/{model_id}/environments/production/logs" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{
"logs": [
{
"timestamp": "1783612149447676900",
"message": "Application shutdown complete.",
"replica": "w44tt",
"request_id": null,
"level": "INFO"
}
]
}
```
**To fetch environment metrics**:
```bash Request theme={"system"}
curl "https://api.baseten.co/v1/models/{model_id}/environments/production/metrics" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{
"start_epoch_millis": 1783968056212,
"end_epoch_millis": 1783968056212,
"mode": "CURRENT",
"metric_descriptors": [
{
"name": "baseten_replicas_active",
"unit_hint": "COUNT",
"kind": "GAUGE",
"label_sets": [{}]
},
...
],
"metric_values": [
{
"start_epoch_millis": 1783968056212,
"values": [[1.0]]
}
]
}
```
Both endpoints take the same windows and modes as their deployment
counterparts.
For more information, see
[`environment logs`](/reference/cli/baseten/model-environment#logs) and
[`environment metrics`](/reference/cli/baseten/model-environment#metrics),
and the
[environment logs](/reference/management-api/environments/get-environment-logs)
and [environment metrics](/reference/management-api/environments/get-environment-metrics)
(beta) endpoints.
## Next steps
Logs and metrics are the read half of every management workflow: check them
before and after a mutation.
* [Terminate a stuck replica](/troubleshooting/deployments#issue-a-single-replica-is-stuck-or-unhealthy)
you found misbehaving in the logs.
* [Scale a deployment](/deployment/manage/scaling) if metrics show sustained
queueing or idle replicas.
* [Metrics](/observability/metrics) for dashboards and metric definitions.
# Manage deployments
Source: https://docs.baseten.co/deployment/manage/overview
Scale, promote, inspect, deactivate, and delete Baseten deployments from the console, the Baseten CLI, or the Management API.
Learn to operate deployments on Baseten. After you push a model, you'll
scale it ahead of traffic, promote new versions, inspect logs and metrics,
and deactivate or delete deployments you no longer need.
You can manage your deployments using any of the following methods:
* **[Console](https://app.baseten.co)**: the Baseten dashboard, for
interactive changes and checking state.
* **[Baseten CLI](/reference/cli/baseten/overview)**: terminal commands for
scripting and automation. Every command supports `--output json` and
`--jq` filtering.
* **[Management API](/reference/management-api/overview)**: REST endpoints
for managing models, deployments, and environments from your own code.
* **[CI/CD](/deployment/ci-cd)**: automated deploys and promotions from
GitHub Actions.
## Authenticate
Sign in with your Baseten account and create an
[API key](/organization/api-keys):
**To sign in**:
1. Go to [app.baseten.co](https://app.baseten.co).
2. Enter your email and choose **Continue**, or choose
**Continue with Google** or **Continue with GitHub**.
**To create an API key**:
1. In your [workspace](https://app.baseten.co), open **API keys** in
your settings.
2. Choose **Create API key**.
**To sign in**:
```bash Command theme={"system"}
baseten auth login
```
```txt Output theme={"system"}
? How would you like to authenticate?
> Login with Baseten credentials (browser)
Paste an API key
Browser opened to authenticate...
If it didn't open, visit:
https://login.baseten.co/device?user_code=ABCD-1234
Verification code: ABCD-1234
```
Complete the sign-in in the browser; the CLI stores a profile that
authenticates later commands.
For more information, see
[`baseten auth login`](/reference/cli/baseten/auth#login).
**To create an API key**:
Create a personal key, tied to your account and its permissions, for
local development and testing:
```bash Command theme={"system"}
baseten org api-key create --type personal --name
Create a team key, not tied to any one user and optionally scoped to
specific models, for production and shared automation:
```bash Command theme={"system"}
baseten org api-key create --type workspace-invoke --name
For more information, see
[`baseten org api-key`](/reference/cli/baseten/org-api-key). Commands
also accept the key through the
`BASETEN_API_KEY` environment variable instead of a profile.
**To authenticate API requests**:
The API doesn't sign in; every request carries an API key. Create your
first key in the console or CLI.
1. Set the key as an environment variable, or store it in your secret
manager:
```bash macOS/Linux theme={"system"}
export BASETEN_API_KEY=
```
```powershell Windows theme={"system"}
setx BASETEN_API_KEY
```
2. Pass the key in the `Authorization` header on every request:
```bash Request theme={"system"}
curl "https://api.baseten.co/v1/models" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{
"models": [
{
"id": "abc123",
"name": "HelloWorld",
"deployments_count": 1,
"production_deployment_id": "def456",
...
}
]
}
```
**To create an API key with the API**:
Once you have a first key, create more over the API. `type` takes
`PERSONAL`, `WORKSPACE_MANAGE_ALL`, `WORKSPACE_INVOKE`, or
`WORKSPACE_EXPORT_METRICS`:
```bash Request theme={"system"}
curl -X POST "https://api.baseten.co/v1/api_keys" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "PERSONAL", "name": "
For more information, see the
[create API key endpoint](/reference/management-api/api-keys/creates-an-api-key).
For enterprise authentication, including identity-provider sign-in and SCIM
user provisioning, see [SSO and SCIM](/organization/sso-and-scim) or
[contact support](mailto:support@baseten.co) to enable it for your workspace.
## Find your model and deployment IDs
Each deployment has an ID, associated with the model that owns it. Use the
two IDs together to target every operation on these pages. Both appear in
the model's page URL:
**To find your IDs in the console**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. Choose **Copy ID** next to the model name.
4. Select the deployment under **Deployments**. The deployment ID is the
last segment of the page URL.
**To list your models and deployments**:
1. List your deployed models:
```bash Command theme={"system"}
baseten model list
```
```txt Output theme={"system"}
ID NAME TEAM DEPLOYMENTS CREATED
abc123 HelloWorld Baseten 1 2026-05-28T15:52:18Z
```
2. Then list your model's deployments:
```bash Command theme={"system"}
baseten model deployment list --model-id
```
```txt Output theme={"system"}
ID NAME ENVIRONMENT STATUS INSTANCE REPLICAS CREATED
def456 deployment-1 production ACTIVE 1x2 - 1 vCPU, 2 GiB RAM 1 2026-05-28T15:52:19Z
```
Commands that take `--model-id` and `--deployment-id` also accept
`--model-name` and `--deployment-name` to target by name instead.
For more information, see
[`model list`](/reference/cli/baseten/model#list) and
[`deployment list`](/reference/cli/baseten/model-deployment#list).
**To list your models and deployments**:
1. List your deployed models:
```bash Request theme={"system"}
curl "https://api.baseten.co/v1/models" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{
"models": [
{
"id": "abc123",
"name": "HelloWorld",
"deployments_count": 1,
"production_deployment_id": "def456",
...
}
]
}
```
2. Then list your model's deployments:
```bash Request theme={"system"}
curl "https://api.baseten.co/v1/models/{model_id}/deployments" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{
"deployments": [
{
"id": "def456",
"name": "deployment-1",
"model_id": "abc123",
"is_production": true,
"status": "ACTIVE",
...
}
]
}
```
For more information, see the
[list models](/reference/management-api/models/gets-all-models)
and [list deployments](/reference/management-api/deployments/gets-all-deployments-of-a-model)
endpoints.
## Find your task
* [Scale a deployment](/deployment/manage/scaling): change how much compute
a serving deployment uses. Pre-scale before a traffic spike, scale to
zero, or wake a scaled-to-zero deployment.
* [Manage the deployment lifecycle](/deployment/manage/lifecycle): change
whether and where a deployment serves. Promote, deactivate, activate, and
delete deployments.
* [Terminate a stuck replica](/troubleshooting/deployments#issue-a-single-replica-is-stuck-or-unhealthy):
fix one bad copy inside a deployment and let the autoscaler replace it.
* [Pull logs and metrics](/deployment/manage/logs-and-metrics): read what a
deployment, or a whole environment, is doing. Fetch or stream logs and
read metrics from scripts.
## Next steps
For the concepts behind these operations, see
[Deployments](/deployment/deployments) and
[Environments](/deployment/environments). For incremental promotion, see
[Rolling deployments](/deployment/rolling-deployments).
# Scale a deployment
Source: https://docs.baseten.co/deployment/manage/scaling
Update a live deployment's autoscaling settings, scale to zero, and wake a scaled-to-zero deployment with the Management API.
Scale your deployments as traffic hits your endpoint. Traffic can change
faster than you can react to it: pre-scale ahead of the spikes you can see
coming, and let the [autoscaler](/deployment/autoscaling/overview) absorb
the ones you can't.
This page covers applying scaling changes to a live deployment. To choose
the values themselves, start with the
[autoscaling overview](/deployment/autoscaling/overview) and match the
settings to your [traffic patterns](/deployment/autoscaling/traffic-patterns).
## Update autoscaling settings
Update autoscaling settings in place to pre-scale for a known traffic spike,
raise a replica ceiling, or tune scale-down behavior. The change applies to
the running deployment; replicas adjust without a new deploy.
**To update autoscaling settings**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. On the model's overview, choose **Configure** under the environment's
**Replicas**.
4. Adjust your settings:
* **Replicas**: the minimum and maximum replica count. Baseten
scales within this range based on traffic.
* **Autoscaling window**: how far back the autoscaler looks when
averaging traffic for scaling decisions.
* **Scale down delay**: how long the autoscaler waits after traffic
drops before removing replicas.
* **Max scale down rate**: the largest percentage of active replicas
the autoscaler removes in a single scale-down step.
* **Concurrency target**: the number of concurrent requests each
replica handles before the autoscaler adds another.
* **Target utilization percentage**: the share of the concurrency
target at which scaling triggers.
5. Choose **Update** to apply the changes.
The same dialog is in the environment card's **⋯** menu as
**Configure autoscaling**.
**To update autoscaling settings**:
Use the `autoscaling_settings` endpoint to apply autoscaling updates.
It accepts any subset of the fields, so send only the ones you're
changing; the update applies asynchronously:
```bash Request theme={"system"}
curl -X PATCH "https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/autoscaling_settings" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"min_replica": 2,
"max_replica": 8,
"autoscaling_window": 60,
"scale_down_delay": 900,
"concurrency_target": 2,
"target_utilization_percentage": 70
}'
```
```json Response theme={"system"}
{
"status": "ACCEPTED",
"message": "Your request to update autoscaling settings has been accepted. Query for deployment {deployment_id}'s status to see when the updates have been applied."
}
```
Now that your deployment's settings are updated, verify the changes reached
the deployment. The update applies asynchronously, so the new values can
take a moment to land:
**To verify your settings**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. Select the deployment under **Deployments**.
4. Review the current values on the **Autoscaling settings** card:
replicas, autoscaling window, scale down delay, concurrency target,
and target utilization.
**To verify your settings**:
```bash Command theme={"system"}
baseten model deployment describe --model-id --deployment-id --jq '.autoscaling_settings'
```
```json Output theme={"system"}
{
"autoscaling_window": 60,
"concurrency_target": 2,
"max_replica": 8,
"min_replica": 2,
"scale_down_delay": 900,
"target_utilization_percentage": 70
}
```
**To verify your settings**:
```bash Request theme={"system"}
curl "https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{
"id": "def456",
"autoscaling_settings": {
"min_replica": 2,
"max_replica": 8,
"autoscaling_window": 60,
"scale_down_delay": 900,
"concurrency_target": 2,
"target_utilization_percentage": 70
},
...
}
```
For more information, see the
[update autoscaling settings endpoint](/reference/management-api/deployments/autoscaling/updates-a-deployments-autoscaling-settings).
To update settings on whatever deployment an environment currently serves, use
the [environment settings endpoint](/reference/management-api/environments/update-an-environments-settings)
instead.
## Scale back down
After a spike passes, lower `min_replica` back to its normal floor with the
same [update procedure](#update-autoscaling-settings). Two settings control
how fast replicas drain:
* **Scale down delay** (`scale_down_delay`): how long the autoscaler waits
after traffic drops before removing replicas.
* **Max scale down rate**: the largest percentage of active replicas removed
in a single step. Set it in the console's **Configure autoscaling**
dialog.
For example, drop the floor back to one replica and keep a 15-minute drain
delay:
```bash Request theme={"system"}
curl -X PATCH "https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/autoscaling_settings" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"min_replica": 1, "scale_down_delay": 900}'
```
```json Response theme={"system"}
{
"status": "ACCEPTED",
"message": "Your request to update autoscaling settings has been accepted. Query for deployment {deployment_id}'s status to see when the updates have been applied."
}
```
Replicas above the new floor drain gradually; traffic keeps flowing to the
replicas that remain.
## Scale to zero
Set `min_replica` to `0` to let an idle deployment release all its replicas
and stop billing for compute.
Scaling to zero isn't recommended for production endpoints: the first
request after an idle period pays a [cold start](/deployment/autoscaling/cold-starts).
Reserve it for development and staging deployments, or for workloads that
tolerate the delay.
**To scale to zero**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. On the model's overview, choose **Configure** under the environment's
**Replicas**.
4. Set **Min** replicas to `0`.
5. Choose **Update** to apply the changes.
**To scale to zero**:
```bash Request theme={"system"}
curl -X PATCH "https://api.baseten.co/v1/models/{model_id}/deployments/{deployment_id}/autoscaling_settings" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"min_replica": 0}'
```
```json Response theme={"system"}
{
"status": "ACCEPTED",
"message": "Your request to update autoscaling settings has been accepted. Query for deployment {deployment_id}'s status to see when the updates have been applied."
}
```
The model's endpoint stays live, and the next request spins a replica back
up. To stop serving entirely,
[deactivate the deployment](/deployment/manage/lifecycle#deactivate-a-deployment)
instead.
## Wake a scaled-to-zero deployment
Wake a scaled-to-zero deployment before you need it, for example ahead of a
demo or a batch job, so the first real request doesn't pay the cold start.
**To wake a deployment**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. Select the deployment under **Deployments**.
4. Choose **Wake deployment**.
The environment card has the same action as **Wake**.
**To wake a deployment**:
```bash Request theme={"system"}
curl -i -X POST "https://model-{model_id}.api.baseten.co/deployment/{deployment_id}/wake" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```txt Response theme={"system"}
HTTP/2 202
content-length: 0
```
The deployment
starts a replica in the background, moving from `SCALED_TO_ZERO` through
`WAKING_UP` to `ACTIVE`; poll
[`baseten model deployment describe`](/reference/cli/baseten/model-deployment#describe)
until `status` is `ACTIVE`.
A woken deployment with `min_replica: 0` scales back down after
`scale_down_delay` if no requests arrive, so wake it close to when you need
it, or [raise `min_replica`](#update-autoscaling-settings) to hold it warm.
## Next steps
Scaling changes take effect on the running deployment, so pair them with a
quick status check before and after.
* [Autoscaling overview](/deployment/autoscaling/overview) to choose replica
counts, concurrency targets, and scale-down behavior.
* [Manage the deployment lifecycle](/deployment/manage/lifecycle) to
deactivate, promote, or delete deployments.
* [Pull logs and metrics](/deployment/manage/logs-and-metrics) to confirm how
a scaling change lands.
# Regional environments
Source: https://docs.baseten.co/deployment/regional-environments
Guarantee inference data stays in a specific geographic region with regional environments.
Regional environments route inference traffic for a deployment exclusively to workload planes within a designated geographic region. Use regional environments to meet data residency and compliance requirements, such as GDPR, without managing separate models per region.
Regional environments require initial configuration by Baseten.
[Contact support](mailto:support@baseten.co) to set up regional restrictions for your environments.
## How regional environments work
Regional environments build on [environments](/deployment/environments) and [restricted environments](/organization/restricted-environments) to add region-level routing guarantees. When Baseten configures regional restrictions for an environment, two things happen:
1. **Replicas are constrained** to workload planes within the designated region. Deployments promoted to that environment only run in the allowed region.
2. **A regional inference endpoint** becomes available that routes traffic directly to the region-specific workload plane, guaranteeing data stays in the designated region.
### Compare regional and standard endpoints
Standard environment endpoints don't guarantee regional routing.
Traffic may pass through a workload plane outside the intended region depending on DNS resolution.
Regional endpoints use a different URL format that maps directly to a region-specific workload plane:
| Endpoint type | URL format | Regional guarantee |
| :------------ | :------------------------------------------------------------------------ | :----------------- |
| Standard | `https://model-{model_id}.api.baseten.co/environments/{env_name}/predict` | No |
| Regional | `https://model-{model_id}-{env_name}.api.baseten.co/predict` | Yes |
The standard endpoint continues to function after you enable regional environments.
However, it doesn't guarantee that traffic stays within the restricted region.
If you use regional environments, migrate your calling code to the regional endpoint to maintain compliance.
The standard endpoint routes traffic through the original CNAME, which may point to a workload plane outside the restricted region.
### Call a regional endpoint
Regional endpoints accept the same request format as standard predict endpoints:
**To call a regional endpoint**:
Create an `httpx.Client` with the regional endpoint as the `base_url`. Reuse the client across requests for connection pooling. See [Configure HTTP clients](/inference/http-client-configuration) for recommended timeout and pool settings.
```python predict.py theme={"system"}
import httpx
import os
model_id = ""
env_name = "prod-us"
client = httpx.Client(
base_url=f"https://model-{model_id}-{env_name}.api.baseten.co",
headers={"Authorization": f"Bearer {os.environ['BASETEN_API_KEY']}"},
)
response = client.post("/predict", json={"prompt": "Hello, world!"})
print(response.json())
```
**To call a regional endpoint**:
Send a POST request to the regional endpoint with your API key in the `Authorization` header:
```sh Request theme={"system"}
curl -X POST https://model-{model_id}-{env_name}.api.baseten.co/predict \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"prompt": "Hello, world!"}'
```
**To call a regional endpoint**:
Use the built-in `fetch` API to call the regional endpoint. Replace `modelId` and `envName` with your model ID and environment name:
```javascript predict.js theme={"system"}
const modelId = "";
const envName = "prod-us";
const resp = await fetch(
`https://model-${modelId}-${envName}.api.baseten.co/predict`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BASETEN_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ prompt: "Hello, world!" }),
}
);
const data = await resp.json();
console.log(data);
```
## Set up regional environments
To set up regional environments:
1. Create environments with region-specific names (for example, `prod-us`, `prod-eu`, `staging-eu`). Use [restricted environments](/organization/restricted-environments) to control access.
2. [Contact Baseten support](mailto:support@baseten.co) to configure regional restrictions for your environments. We'll work with you to set them up per your required specs.
3. Update your calling code to use the regional endpoint format: `https://model-{model_id}-{env_name}.api.baseten.co/predict`.
### Environment naming requirements
Environment names used with regional environments must be valid DNS subdomain labels:
* Lowercase alphanumeric characters and hyphens only.
* Can't start or end with a hyphen.
* Maximum 40 characters.
* `development` is a reserved name and can't be used.
Regional environments apply across all models in a team. If you name an environment `prod-us` on one model, creating `prod-us` on another model in the same team applies the same regional restrictions.
## Deploy to regional environments
Deploy and promote to regional environments the same way as standard environments:
```sh Terminal theme={"system"}
truss push --environment prod-us
```
Replicas spin up only in workload planes within the allowed region.
### Promotion behavior
When you promote a deployment to a regional environment, Baseten ensures regional restrictions are enforced. If the deployment was previously running without regional restrictions, a forced redeploy occurs to ensure compliance. This happens even when "turn off redeploy on promotion" is on for the model.
## Supported regions
Baseten can configure regional restrictions for a variety of geographic regions, including US, EU, UK, and Australia. [Contact support](mailto:support@baseten.co) to discuss your specific regional requirements.
# Resources
Source: https://docs.baseten.co/deployment/resources
Manage and configure model resources
Every AI/ML model on Baseten runs on an **instance**, a dedicated set of hardware allocated to the model server. Selecting the right instance type ensures **optimal performance** while controlling **compute costs**.
* **Insufficient resources**: Slow inference or failures.
* **Excess resources**: Higher costs without added benefit.
## Instance type resource components
* **Instance**: The allocated hardware for inference.
* **Node**: The compute unit within an instance, comprising 8 GPUs with associated vCPU, RAM, and VRAM.
* **vCPU**: Virtual CPU cores for general computing.
* **RAM**: Memory available to the CPU.
* **GPU**: Specialized hardware for accelerated ML workloads.
* **VRAM**: Dedicated GPU memory for model execution.
## Configure model resources
Define resources **before deployment** in Truss or **adjust them later** through the Baseten UI.
### Define resources in Truss
Define resource requirements in [`config.yaml`](/development/model/configuration) before running `truss push`.
* **Published deployment** (`truss push`): Creates a new deployment (named sequentially: `deployment-1`, `deployment-2`, and so on) using the resources in [`config.yaml`](/development/model/configuration).
* **Development deployment** (`truss push --watch`): Overwrites the existing development deployment with the specified resource configuration and starts watching for changes. Use [`truss watch`](/development/model/deploy-and-iterate) to resume watching an existing development deployment.
* **Production deployment** (`truss push --promote`): Creates a new deployment and promotes it to production, replacing the active deployment.
* **Environment deployment** (`truss push --environment `): Deploys directly to a [custom environment](/deployment/environments) like staging.
Changes to `config.yaml` only affect new deployments. To update resources on an existing published deployment, edit resources in the [Baseten UI](#update-resources-in-the-baseten-ui).
You can configure resources in two ways:
#### Individual resource fields
```yaml config.yaml theme={"system"}
resources:
accelerator: L4
cpu: "4"
memory: 16Gi
```
Baseten provisions the **smallest instance that meets the specified constraints**:
* cpu: "3" or "4" → Maps to a 4-core instance.
* cpu: "5" to "8" → Maps to an 8-core instance.
`Gi` in `resources.memory` refers to **Gibibytes**, which are slightly larger
than **Gigabytes**.
#### Exact instance type
An instance type is the full SKU name that uniquely identifies a specific hardware configuration. When you specify individual resource fields like `cpu` and `accelerator`, Baseten selects the smallest instance that meets your requirements. With `instance_type`, you specify exactly which instance you want, no guessing required.
Use `instance_type` when you:
* Know the exact hardware configuration you need.
* Want to ensure consistent instance selection across deployments.
* Are following a recommendation for a specific model (for example, "use an L4 with 4 vCPUs and 16 GiB RAM").
```yaml config.yaml theme={"system"}
resources:
instance_type: "L4:4x16"
```
The format encodes the hardware specs. For example, `L4:4x16` means an L4 GPU with 4 vCPUs and 16 GiB of RAM. Naming conventions vary by GPU family, so copy the exact instance type from the [instance type reference](#instance-type-reference). When `instance_type` is specified, other resource fields (`cpu`, `memory`, `accelerator`, `use_gpu`) are ignored.
### Update resources in the Baseten UI
Once deployed, you can only update resource configurations **through the Baseten UI**. Changing the instance type deploys a copy of the deployment using the specified instance type.
For a list of available instance types, see the [instance type reference](/deployment/resources#instance-type-reference).
## Instance type reference
Specs and benchmarks for every Baseten instance type.
### CPU-only instances
Cost-effective options for lighter workloads. No GPU.
* **Starts at**: \$0.00058/min
* **Best for**: Transformers pipelines, small QA models, text embeddings
| Instance | \$/min | vCPU | RAM |
| -------- | --------- | ---- | ------ |
| `1x2` | \$0.00058 | 1 | 2 GiB |
| `1x4` | \$0.00086 | 1 | 4 GiB |
| `2x8` | \$0.00173 | 2 | 8 GiB |
| `4x16` | \$0.00346 | 4 | 16 GiB |
| `8x32` | \$0.00691 | 8 | 32 GiB |
| `16x64` | \$0.01382 | 16 | 64 GiB |
To select a CPU-only instance, use the bare `x` SKU (for example, `instance_type: "4x16"`).
**Example workloads:**
* `1x2`: Text classification (for example, Truss quickstart)
* `4x16`: LayoutLM Document QA
* `4x16+`: Sentence Transformers embeddings on larger corpora
### GPU instances
Accelerated inference for LLMs, diffusion models, and Whisper.
| Instance | \$/min | vCPU | RAM | GPU | VRAM |
| ---------------- | --------- | ---- | -------- | ---------------------- | -------- |
| `T4x4x16` | \$0.01052 | 4 | 16 GiB | 1 NVIDIA T4 | 16 GiB |
| `T4x8x32` | \$0.01504 | 8 | 32 GiB | 1 NVIDIA T4 | 16 GiB |
| `T4x16x64` | \$0.02408 | 16 | 64 GiB | 1 NVIDIA T4 | 16 GiB |
| `T4:2x24x96` | \$0.03912 | 24 | 96 GiB | 2 NVIDIA T4s | 32 GiB |
| `T4:4x48x192` | \$0.07824 | 48 | 192 GiB | 4 NVIDIA T4s | 64 GiB |
| `L4:4x16` | \$0.01414 | 4 | 16 GiB | 1 NVIDIA L4 | 24 GiB |
| `L4:2x24x96` | \$0.04002 | 24 | 96 GiB | 2 NVIDIA L4s | 48 GiB |
| `L4:4x48x192` | \$0.08003 | 48 | 192 GiB | 4 NVIDIA L4s | 96 GiB |
| `A10Gx4x16` | \$0.02012 | 4 | 16 GiB | 1 NVIDIA A10G | 24 GiB |
| `A10Gx8x32` | \$0.02424 | 8 | 32 GiB | 1 NVIDIA A10G | 24 GiB |
| `A10Gx16x64` | \$0.03248 | 16 | 64 GiB | 1 NVIDIA A10G | 24 GiB |
| `A10G:2x24x96` | \$0.05672 | 24 | 94 GiB | 2 NVIDIA A10Gs | 48 GiB |
| `A10G:4x48x192` | \$0.11344 | 48 | 188 GiB | 4 NVIDIA A10Gs | 96 GiB |
| `A10G:8x192x768` | \$0.32576 | 192 | 750 GiB | 8 NVIDIA A10Gs | 192 GiB |
| `A100:12x144` | \$0.06667 | 12 | 144 GiB | 1 NVIDIA A100 | 80 GiB |
| `A100:2x24x288` | \$0.13334 | 24 | 288 GiB | 2 NVIDIA A100s | 160 GiB |
| `A100:3x36x432` | \$0.20000 | 36 | 432 GiB | 3 NVIDIA A100s | 240 GiB |
| `A100:4x48x576` | \$0.26668 | 48 | 576 GiB | 4 NVIDIA A100s | 320 GiB |
| `A100:5x60x720` | \$0.33333 | 60 | 720 GiB | 5 NVIDIA A100s | 400 GiB |
| `A100:6x72x864` | \$0.40000 | 72 | 864 GiB | 6 NVIDIA A100s | 480 GiB |
| `A100:7x84x1008` | \$0.46667 | 84 | 1008 GiB | 7 NVIDIA A100s | 560 GiB |
| `A100:8x96x1152` | \$0.53333 | 96 | 1152 GiB | 8 NVIDIA A100s | 640 GiB |
| `H100` | \$0.10833 | 16 | 118 GiB | 1 NVIDIA H100 | 80 GiB |
| `H100:2` | \$0.21666 | 32 | 236 GiB | 2 NVIDIA H100s | 160 GiB |
| `H100:4` | \$0.43332 | 64 | 472 GiB | 4 NVIDIA H100s | 320 GiB |
| `H100:8` | \$0.86664 | 128 | 944 GiB | 8 NVIDIA H100s | 640 GiB |
| `H100MIG` | \$0.06250 | 8 | 59 GiB | Fractional NVIDIA H100 | 40 GiB |
| `H200` | \$0.12500 | 16 | 200 GiB | 1 NVIDIA H200 | 141 GiB |
| `H200:2` | \$0.25000 | 32 | 400 GiB | 2 NVIDIA H200s | 282 GiB |
| `H200:4` | \$0.50000 | 64 | 800 GiB | 4 NVIDIA H200s | 564 GiB |
| `H200:8` | \$1.00000 | 128 | 1600 GiB | 8 NVIDIA H200s | 1128 GiB |
| `B200` | \$0.16633 | 16 | 224 GiB | 1 NVIDIA B200 | 180 GiB |
| `B200:2` | \$0.33266 | 32 | 448 GiB | 2 NVIDIA B200s | 360 GiB |
| `B200:4` | \$0.66532 | 64 | 896 GiB | 4 NVIDIA B200s | 720 GiB |
| `B200:8` | \$1.33064 | 128 | 1792 GiB | 8 NVIDIA B200s | 1440 GiB |
| `RTX-PRO-6000` | \$0.06667 | 16 | 116 GiB | 1 NVIDIA RTX-PRO-6000 | 96 GiB |
| `RTX-PRO-6000:2` | \$0.13334 | 32 | 233 GiB | 2 NVIDIA RTX-PRO-6000s | 192 GiB |
| `RTX-PRO-6000:4` | \$0.26668 | 64 | 466 GiB | 4 NVIDIA RTX-PRO-6000s | 384 GiB |
| `RTX-PRO-6000:8` | \$0.53336 | 128 | 931 GiB | 8 NVIDIA RTX-PRO-6000s | 768 GiB |
H200 and B200 instances are available on request. [Contact us](mailto:support@baseten.co) to get access.
To select a GPU instance with `instance_type`:
* **Single L4 or A100**: `:x` (for example, `"L4:4x16"`).
* **Single T4 or A10G**: `xx`, with no colon (for example, `"T4x4x16"`, `"A10Gx8x32"`).
* **Multi-GPU**: `:xx` (for example, `"A100:2x24x288"`).
* **H100/H200/B200/RTX-PRO-6000**: `` or `:` (for example, `"H100:2"`, `"RTX-PRO-6000:4"`).
* **Fractional H100**: `"H100MIG"`.
Naming is not uniform across GPU families, so copy the exact SKU from the tables above.
### GPU details and workloads
#### T4
Turing-series GPU
* 2,560 CUDA / 320 Tensor cores
* 16 GiB VRAM
* **Best for:** Whisper, small LLMs like StableLM 3B
#### L4
Ada Lovelace-series GPU
* 7,680 CUDA / 240 Tensor cores
* 24 GiB VRAM, 300 GiB/s
* 121 TFLOPS (fp16)
* **Best for**: Stable Diffusion XL
* **Limit**: Not suitable for LLMs due to bandwidth
#### A10G
Ampere-series GPU
* 9,216 CUDA / 288 Tensor cores
* 24 GiB VRAM, 600 GiB/s
* 70 TFLOPS (fp16)
* **Best for**: Mistral 7B, Whisper, Stable Diffusion/SDXL
#### A100
Ampere-series GPU
* 6,912 CUDA / 432 Tensor cores
* 80 GiB VRAM, 1.94 TB/s
* 312 TFLOPS (fp16)
* **Best for**: Mixtral, Llama 2 70B (2 A100s), Falcon 180B (5 A100s), SDXL
#### H100
Hopper-series GPU
* 16,896 CUDA / 640 Tensor cores
* 80 GiB VRAM, 3.35 TB/s
* 990 TFLOPS (fp16)
* **Best for**: Mixtral 8x7B, Llama 2 70B (2xH100), SDXL
#### H100MIG
Fractional H100 (3/7 compute, ½ memory)
* 7,242 CUDA cores, 40 GiB VRAM
* 1.675 TB/s bandwidth
* **Best for**: Efficient LLM inference at lower cost than A100
#### RTX Pro 6000
Blackwell-series GPU
* 96 GiB VRAM
* **Best for**: vision-language models and mid-size LLMs at lower cost than a datacenter GPU
# Rolling deployments
Source: https://docs.baseten.co/deployment/rolling-deployments
Gradually shift traffic to a new deployment with replica-based rolling deployments.
Rolling deployments replace replicas incrementally when promoting a deployment to an environment.
Instead of swapping all traffic at once, rolling deployments scale up the candidate deployment, shift traffic proportionally, and scale down the previous deployment in controlled steps.
Autoscaling continues throughout the rollout for environments where `min_replica < max_replica`, so both deployments scale up to meet traffic demand as it shifts between them.
Use rolling deployments when you need zero-downtime updates with the ability to pause, cancel, or force-complete the deployment at any point.
Rolling deployments are not supported for [Chains](/chains/overview). This feature is available for individual model deployments only.
## How rolling deployments work
A rolling deployment follows a repeating three-step cycle:
1. **Scale up** candidate deployment replicas by the configured percentage.
2. **Shift traffic** proportionally to match the new replica ratio.
3. **Scale down** the previous deployment replicas by the same percentage.
This cycle repeats until all traffic and replicas run on the candidate deployment, at which point it becomes the active deployment in the environment.
The following diagram shows this cycle in action. The tab strip mirrors the promotion lifecycle: a promotion enters `RELEASING` when it starts, sits in `RAMPING_UP` while replicas scale and traffic shifts, can pause as `PAUSED`, and lands at `SUCCEEDED` once the candidate serves all traffic. Select any status to freeze the simulation on that stage, then select it again to resume.
Adjust the values and choose **Apply** to restart the simulation with your configuration.
### Provisioning modes
Rolling deployments support two mutually exclusive provisioning modes:
* `max_surge_percent`: Scales up candidate replicas before scaling down previous replicas.
* `max_unavailable_percent`: Scales down previous replicas before scaling up candidate replicas.
Set exactly one mode to a non-zero value and the other to `0`.
## Enable rolling deployments
Rolling deployments are disabled by default. Enable them per environment
in its promotion settings:
**To enable rolling deployments**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. On the model's overview, open the environment card's **⋯** menu.
4. Choose **Configure promotion**.
5. Turn on **Rolling deploys**.
6. Choose **Update** to apply the changes.
**To enable rolling deployments**:
```bash Request theme={"system"}
curl -X PATCH "https://api.baseten.co/v1/models/{model_id}/environments/production" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"promotion_settings": {
"rolling_deploy": true,
"rolling_deploy_config": {
"max_surge_percent": 10,
"max_unavailable_percent": 0,
"stabilization_time_seconds": 60,
"replica_overhead_percent": 0
}
}
}'
```
```json Response theme={"system"}
{
"status": "ACCEPTED",
"message": "Your request to update environment settings is complete"
}
```
Once rolling deployments are enabled, any subsequent
[promotion to the environment](/deployment/manage/lifecycle#promote-to-an-environment)
uses the rolling deployment workflow.
## Configuration reference
Configure rolling deployments through the `rolling_deploy_config` object in the environment's `promotion_settings`.
Percentage of additional replicas to provision during each step. Set to `0` to use max unavailable mode instead.
**Range:** 0-50
Percentage of replicas that can be unavailable during each step. Set to `0` to use max surge mode instead.
**Range:** 0-50
Seconds to wait after each traffic shift before proceeding to the next step. Use this to monitor metrics between steps.
**Range:** 0-3600
Percentage of additional replicas to pre-provision on the current deployment before the rolling deployment starts. Useful for environments without autoscaling (`min_replica == max_replica`) or as a buffer for anticipated traffic spikes during the rollout.
**Range:** 0-500
Additional promotion settings configured at the `promotion_settings` level:
Enables rolling deployments for the environment.
## Deployment statuses
The `in_progress_promotion` field on the [environment detail endpoint](/reference/management-api/environments/get-an-environments-details) tracks the current state of a rolling deployment. It's separate from each deployment's own `status`.
| Status | Description |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `RELEASING` | Candidate deployment is building and initializing replicas. |
| `RAMPING_UP` | Scaling up candidate replicas and shifting traffic. |
| `PAUSED` | Rolling deployment is paused at its current traffic split. No further promotion steps run, but in-flight replica changes and autoscaling continue. |
| `RAMPING_DOWN` | Graceful cancel in progress. Traffic is shifting back to the previous deployment. |
| `SUCCEEDED` | Rolling deployment completed. The candidate is now the active deployment. |
| `FAILED` | Rolling deployment failed. Traffic remains on the previous deployment. |
| `CANCELED` | Rolling deployment was canceled. Traffic returned to the previous deployment. |
The `in_progress_promotion` object also includes `percent_traffic_to_new_version`, which reports the current percentage of traffic routed to the candidate deployment.
To watch metrics through a rollout, use the
[environment metrics endpoint](/reference/management-api/environments/get-environment-metrics)
(beta): it aggregates both deployments, and in series mode it splits the
window at each promotion so every point reflects the deployments serving at
that time.
## Deployment control actions
Pause, resume, and force roll forward act on the rolling deployment between steps, not immediately. Replica changes already in progress finish before the action takes effect, so the rolling deployment can keep scaling for a short time after you trigger the action.
For example, if the candidate deployment is at 20% traffic and has just been told to scale from 2 to 4 replicas, choosing **Pause** lets the candidate finish scaling to 4 replicas. The traffic split stays pinned at 20% until you resume.
### Pause
Pause the rolling deployment to inspect metrics or logs before proceeding:
**To pause a rolling deployment**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. On the model's overview, find the environment's promotion banner.
4. Choose **Pause**.
**To pause a rolling deployment**:
```bash Request theme={"system"}
curl -X POST "https://api.baseten.co/v1/models/{model_id}/environments/production/pause_promotion" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{"success": true}
```
### Resume
Resume a paused rolling deployment from where it left off:
**To resume a rolling deployment**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. On the model's overview, find the environment's promotion banner.
4. Choose **Resume**.
**To resume a rolling deployment**:
```bash Request theme={"system"}
curl -X POST "https://api.baseten.co/v1/models/{model_id}/environments/production/resume_promotion" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{"success": true}
```
### Cancel
Gracefully cancel the rolling deployment. Traffic ramps back to the previous
deployment and candidate replicas scale down:
**To cancel a rolling deployment**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. On the model's overview, find the environment's promotion banner.
4. Choose **Cancel**.
**To cancel a rolling deployment**:
```bash Request theme={"system"}
curl -X POST "https://api.baseten.co/v1/models/{model_id}/environments/production/cancel_promotion" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{"status": "RAMPING_DOWN", "message": "..."}
```
Returns a `status` of `CANCELED` (instant cancel for non-rolling deployments) or `RAMPING_DOWN` (graceful rollback for rolling deployments).
### Force cancel
Immediately cancel the rolling deployment and return all traffic to the
previous deployment when you can't wait for the graceful ramp-down.
Force canceling may cause brief service disruption if the previous deployment
is under-provisioned.
**To force cancel a rolling deployment**:
```bash Request theme={"system"}
curl -X POST "https://api.baseten.co/v1/models/{model_id}/environments/production/force_cancel_promotion" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{"success": true}
```
### Force roll forward
Immediately complete the rolling deployment, shifting all traffic to the
candidate deployment. This works even while the deployment is rolling back:
Force rolling forward may promote an under-provisioned deployment if the
candidate has not finished scaling up.
**To force roll forward**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. On the model's overview, find the environment's promotion banner.
4. Choose **Force promote**.
**To force roll forward**:
```bash Request theme={"system"}
curl -X POST "https://api.baseten.co/v1/models/{model_id}/environments/production/force_roll_forward_promotion" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Response theme={"system"}
{"success": true}
```
## Autoscaling during rolling deployments
For environments configured with autoscaling (`min_replica < max_replica`), Baseten continues to scale your deployment during a rolling deployment to meet traffic demand. Both the previous and candidate deployments scale up based on combined demand, and new capacity is distributed proportionally to the current traffic split. When demand drops, Baseten scales down both deployments the same way, removing replicas split by the current traffic ratio without changing the traffic split itself.
For example, with traffic split 60/40 between the previous and candidate deployments, an additional 10 replicas of demand provisions 6 replicas to the previous deployment and 4 to the candidate. A drop of 10 replicas removes 6 from the previous deployment and 4 from the candidate the same way.
A few constraints apply during the rolling deployment:
* Autoscaling adds and removes replicas throughout the rollout to track combined demand. Each deployment that is still part of the rollout keeps at least one replica, and the combined replica count stays within the environment's `min_replica` and `max_replica`.
* Capacity management continues during a `PAUSED` rolling deployment. Pausing stops the traffic shift, not capacity management. If demand changes while paused, both deployments still scale up or down.
## Dynamic replica admission
Rolling deployments adapt to candidate replicas as they become ready. Rather than assuming a full batch of replicas will be available immediately, Baseten adjusts the rollout based on live capacity.
For example, with 100 previous replicas and `max_unavailable_percent` set to `25`, Baseten requests 25 new replicas. If only 5 become ready, Baseten only removes 5 previous replicas to stay within your unavailable limit:
```text theme={"system"}
Max unavailable 25%
Requested: Previous 75 replicas Candidate 25 requested
Actually ready: Previous 75 replicas Candidate 5 ready
Next adjustment: Previous 70 replicas Candidate 5 ready
```
The same adaptive behavior applies to `max_surge_percent`. With 100 previous replicas and a 25% surge limit, if only 5 of the 25 requested candidate replicas become ready, Baseten scales down 5 previous replicas before requesting the next batch. This ensures the rollout progresses based on actual ready capacity.
```text theme={"system"}
Max surge 25%
Requested: Previous 100 replicas Candidate 25 requested
Actually ready: Previous 100 replicas Candidate 5 ready
Next adjustment: Previous 95 replicas Candidate 5 ready
```
In both modes, rollouts continue from live, ready capacity to ensure your environment remains stable throughout the transition.
## Environments without autoscaling
Environments where `min_replica == max_replica` have no autoscaling configured, so replica counts stay pinned during the rolling deployment. To pre-provision additional headroom for traffic spikes, set `replica_overhead_percent` to add replicas to the previous deployment before any traffic shifts. Use `stabilization_time_seconds` to wait between steps and monitor metrics before the next traffic shift.
## Deployment cleanup
After a rolling deployment completes, the `promotion_cleanup_strategy` setting controls what happens to the previous deployment.
* `SCALE_TO_ZERO`: Scales the previous deployment to zero replicas. It remains available for reactivation. This is the default.
* `KEEP`: Leaves the previous deployment running at its current replica count.
* `DEACTIVATE`: Deactivates the previous deployment. It stops serving traffic and releases all resources.
**To set the cleanup strategy**:
1. Sign in to your workspace at
[app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the
sidebar.
2. Select your model.
3. On the model's overview, open the environment card's **⋯** menu.
4. Choose **Configure promotion**.
5. For **After promotion**, select the action taken on the previous
deployment.
6. Choose **Update** to apply the changes.
**To set the cleanup strategy**:
```bash Request theme={"system"}
curl -X PATCH "https://api.baseten.co/v1/models/{model_id}/environments/production" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"promotion_settings": {
"promotion_cleanup_strategy": "DEACTIVATE"
}
}'
```
```json Response theme={"system"}
{
"status": "ACCEPTED",
"message": "Your request to update environment settings is complete"
}
```
## Next steps
A rolling deployment starts from an ordinary promotion, so the same
lifecycle operations drive it.
* [Manage the deployment lifecycle](/deployment/manage/lifecycle) to run the
promotion that starts a rolling deployment.
* [Environments](/deployment/environments) for the concepts behind promotion
settings.
* [Scale a deployment](/deployment/manage/scaling) for the autoscaling
settings a rollout respects.
# Binary I/O
Source: https://docs.baseten.co/development/chain/binaryio
Performant serialization of numeric data
Numeric data or audio/video are most efficiently transmitted as bytes.
Other representations such as JSON or base64 encoding lose precision, add
significant parsing overhead and increase message sizes (for example, \~33% increase
for base64 encoding).
Chains extends the JSON-centred pydantic ecosystem with two ways how you can
include binary data: numpy array support and raw bytes.
## Numpy `ndarray` support
Once you have your data represented as a numpy array, you can (and
often without copying) convert it to `torch`, `tensorflow`, or other common
numeric libraries' objects.
To include numpy arrays in a pydantic model, chains has a special field type
implementation `NumpyArrayField`. For example:
```python data_model.py theme={"system"}
import numpy as np
import pydantic
from truss_chains import pydantic_numpy
class DataModel(pydantic.BaseModel):
some_numbers: pydantic_numpy.NumpyArrayField
other_field: str
...
numbers = np.random.random((3, 2))
data = DataModel(some_numbers=numbers, other_field="Example")
print(data)
# some_numbers=NumpyArrayField(shape=(3, 2), dtype=float64, data=[
# [0.39595027 0.23837526]
# [0.56714894 0.61244946]
# [0.45821942 0.42464844]])
# other_field='Example'
```
`NumpyArrayField` is a wrapper around the actual numpy array. Inside your
python code, you can work with its `array` attribute:
```python theme={"system"}
data.some_numbers.array += 10
# some_numbers=NumpyArrayField(shape=(3, 2), dtype=float64, data=[
# [10.39595027 10.23837526]
# [10.56714894 10.61244946]
# [10.45821942 10.42464844]])
# other_field='Example'
```
The interesting part is how it serializes when communicating between Chainlets
or with a client.
It can work in two modes: JSON and binary.
### Binary
As a JSON alternative that supports byte data, Chains uses `msgpack` (with
`msgpack_numpy`) to serialize the dict representation.
For Chainlet-Chainlet RPCs this is done automatically for you by enabling binary
mode of the dependency Chainlets, see
[all options](/reference/sdk/chains#function-truss_chains-depends):
```python binary_rpc.py theme={"system"}
import truss_chains as chains
class Worker(chains.ChainletBase):
async def run_remote(self, data: DataModel) -> DataModel:
data.some_numbers.array += 10
return data
class Consumer(chains.ChainletBase):
def __init__(self, worker=chains.depends(Worker, use_binary=True)):
self._worker = worker
async def run_remote(self):
numbers = np.random.random((3, 2))
data = DataModel(some_numbers=numbers, other_field="Example")
result = await self._worker.run_remote(data)
```
Now the data is transmitted in a fast and compact way between Chainlets
which often gives performance increases.
### Binary client
If you want to send such data as input to a chain or parse binary output
from a chain, you have to add the `msgpack` serialization client-side:
```python binary_client.py theme={"system"}
import requests
import msgpack
import msgpack_numpy
msgpack_numpy.patch() # Register hook for numpy.
# Dump to "python" dict and then to binary.
data_dict = data.model_dump(mode="python")
data_bytes = msgpack.dumps(data_dict)
# Set binary content type in request header.
headers = {
"Content-Type": "application/octet-stream", "Authorization": ...
}
response = requests.post(url, data=data_bytes, headers=headers)
response_dict = msgpack.loads(response.content)
response_model = ResponseModel.model_validate(response_dict)
```
The steps of dumping from a pydantic model and validating the response dict
into a pydantic model can be skipped, if you prefer working with raw dicts
on the client.
The implementation of `NumpyArrayField` only needs `pydantic`, no other Chains
dependencies. So you can take that implementation code in isolation and
integrate it in your client code.
Some version combinations of `msgpack` and `msgpack_numpy` give errors, we
know that `msgpack = ">=1.0.2"` and `msgpack-numpy = ">=0.4.8"` work.
### JSON
The JSON-schema to represent the array is a dict of `shape (tuple[int]),
dtype (str), data_b64 (str)`. For example,
```python theme={"system"}
print(data.model_dump_json())
'{"some_numbers":{"shape":[3,2],"dtype":"float64", "data_b64":"30d4/rnKJEAsvm...'
```
The base64 data corresponds to `np.ndarray.tobytes()`.
To get back to the array from the JSON string, use the model's
`model_validate_json` method.
As discussed in the beginning, this schema is not performant for numeric data
and only offered as a compatibility layer (JSON does not allow bytes);
generally prefer the binary format.
## Simple `bytes` fields
It is possible to add a `bytes` field to a pydantic model used in a chain,
or as a plain argument to `run_remote`. This can be useful to include
non-numpy data formats such as images or audio/video snippets.
In this case, the "normal" JSON representation does not work and all
involved requests or Chainlet-Chainlet-invocations must use binary mode.
The same steps as for arrays [above](#binary-client) apply: construct dicts
with `bytes` values and keys corresponding to the `run_remote` argument
names or the field names in the pydantic model. Then use `msgpack` to
serialize and deserialize those dicts.
Don't forget to add `Content-type` headers and that `response.json()` will
not work.
# Concepts
Source: https://docs.baseten.co/development/chain/concepts
Glossary of Chains concepts and terminology
This glossary defines the core Chains concepts you'll work with: Chainlets, their remote configuration and initialization, the `run_remote()` interface, entrypoints, and typed I/O. Read it alongside the [getting started guide](/development/chain/getting-started) when you build your first Chain.
## Chainlet
A Chainlet is the basic building block of Chains. A Chainlet is a Python class
that specifies:
* A set of compute resources.
* A Python environment with software dependencies.
* A typed interface [`run_remote()`](/development/chain/concepts#run-remote-chaining-chainlets) for other Chainlets to call.
This is the simplest possible Chainlet. Only the
[`run_remote()`](/development/chain/concepts#run-remote-chaining-chainlets) method is
required, and we can layer in other concepts to create a more capable Chainlet.
```python theme={"system"}
import truss_chains as chains
class SayHello(chains.ChainletBase):
async def run_remote(self, name: str) -> str:
return f"Hello, {name}"
```
You can modularize your code by creating your own chainlet sub-classes,
refer to our [subclassing guide](/development/chain/subclassing).
### TrussChainlet
`TrussChainlet` enables existing Truss models to participate in a Chain as non-entry leaf chainlets without rewriting them as `ChainletBase` subclasses. This is useful for integrating models that use custom servers (like vLLM) or existing `model.py` implementations directly.
To use a Truss as a chainlet, define a class that inherits from `chains.TrussChainlet` and set the `truss_dir` class variable to the path of the Truss directory.
```python theme={"system"}
import truss_chains as chains
class STT(chains.TrussChainlet):
truss_dir = "./a_truss_model"
```
`TrussChainlet` cannot be used as an entrypoint and cannot declare its own dependencies. It is designed to be used as a dependency within a `ChainletBase`.
### Remote configuration
Chainlets are meant for deployment as remote services. Each Chainlet specifies
its own requirements for compute hardware (CPU count, GPU type and count, etc)
and software dependencies (Python libraries or system packages). This
configuration is built into a Docker image automatically as part of the
deployment process.
When no configuration is provided, the Chainlet will be deployed on a basic
instance with one vCPU, 2GB of RAM, no GPU, and a standard set of Python and
system packages.
Configuration is set using the
[`remote_config`](/reference/sdk/chains#remote-configuration) class variable
within the Chainlet:
```python theme={"system"}
import truss_chains as chains
class MyChainlet(chains.ChainletBase):
remote_config = chains.RemoteConfig(
docker_image=chains.DockerImage(
pip_requirements=["torch==2.3.0", ...]
),
compute=chains.Compute(gpu="H100", ...),
assets=chains.Assets(secret_keys=["hf_access_token"], ...),
)
```
To select an exact instance type instead of specifying individual resource fields, use `instance_type`:
```python theme={"system"}
compute=chains.Compute(instance_type="H100:8x80")
```
When `instance_type` is specified, `cpu_count`, `memory`, and `gpu` fields are ignored.
See the
[remote configuration reference](/reference/sdk/chains#remote-configuration)
for a complete list of options.
### Build commands
Use `build_commands` to run shell commands during the Docker image build, after system packages are installed and before your Chainlet code is added. Useful for cloning repositories, pre-downloading model weights, or other setup work you want cached at build time so it does not run on every cold start.
```python theme={"system"}
import truss_chains as chains
class ComfyChainlet(chains.ChainletBase):
remote_config = chains.RemoteConfig(
compute=chains.Compute(gpu="A100"),
build_commands=[
"git clone https://github.com/comfyanonymous/ComfyUI.git",
"cd ComfyUI && pip install -r requirements.txt",
],
)
```
Each entry runs as a separate shell command in the order listed. This is the Chains equivalent of the Truss [`build_commands`](/development/model/dependencies#build-commands) field in `config.yaml`.
### Initialization
Chainlets are implemented as classes because we often want to set up expensive
static resources once at startup and then re-use it with each invocation of the
Chainlet. For example, we only want to initialize an AI model and download its
weights once then re-use it every time we run inference.
We do this setup in `__init__()`, which is run exactly once when the Chainlet is
deployed or scaled up.
```python theme={"system"}
import truss_chains as chains
class PhiLLM(chains.ChainletBase):
def __init__(self) -> None:
import torch
import transformers
self._model = transformers.AutoModelForCausalLM.from_pretrained(
PHI_HF_MODEL,
torch_dtype=torch.float16,
device_map="auto",
)
self._tokenizer = transformers.AutoTokenizer.from_pretrained(
PHI_HF_MODEL,
)
```
Chainlet initialization also has two important features: context and dependency
injection of other Chainlets, explained below.
#### Context (access information)
You can add a
[`DeploymentContext`](/reference/sdk/chains#class-truss_chains-deploymentcontext)
object as an optional argument to the `__init__`-method of a Chainlet.
This allows you to use secrets within your Chainlet, such as using
a `hf_access_token` to access a gated model on Hugging Face (note that when
using secrets, they also need to be added to the `assets`).
```python theme={"system"}
import truss_chains as chains
class MistralLLM(chains.ChainletBase):
remote_config = chains.RemoteConfig(
...
assets = chains.Assets(secret_keys=["hf_access_token"], ...),
)
def __init__(
self,
# Adding the `context` argument, allows us to access secrets
context: chains.DeploymentContext = chains.depends_context(),
) -> None:
import transformers
# Using the secret from context to access a gated model on HF
self._model = transformers.AutoModelForCausalLM.from_pretrained(
"mistralai/Mistral-7B-Instruct-v0.2",
use_auth_token=context.secrets["hf_access_token"],
)
```
#### Depends (call other Chainlets)
The Chains framework uses the
[`chains.depends()`](/reference/sdk/chains#function-truss_chains-depends) function in
Chainlets' `__init__()` method to track the dependency relationship between
different Chainlets within a Chain.
This syntax, inspired by dependency injection, is used to translate local Python
function calls into calls to the remote Chainlets in production.
Once a dependency Chainlet is added with
[`chains.depends()`](/reference/sdk/chains#function-truss_chains-depends), its
[`run_remote()`](/development/chain/concepts#run-remote-chaining-chainlets) method can
call this dependency Chainlet, for example, below `HelloAll` we can make calls to
`SayHello`:
```python theme={"system"}
import truss_chains as chains
class HelloAll(chains.ChainletBase):
def __init__(self, say_hello_chainlet=chains.depends(SayHello)) -> None:
self._say_hello = say_hello_chainlet
async def run_remote(self, names: list[str]) -> str:
output = []
for name in names:
output.append(self._say_hello.run_remote(name))
return "\n".join(output)
```
## Run remote (chaining Chainlets)
The `run_remote()` method is run each time the Chainlet is called. It is the
sole public interface for the Chainlet (though you can have as many private
helper functions as you want) and its inputs and outputs must have type
annotations.
In `run_remote()` you implement the actual work of the Chainlet, such as model
inference or data chunking:
```python theme={"system"}
import truss_chains as chains
class PhiLLM(chains.ChainletBase):
async def run_remote(self, messages: Messages) -> str:
import torch
model_inputs = await self._tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
inputs = await self._tokenizer(model_inputs, return_tensors="pt")
input_ids = inputs["input_ids"].to("cuda")
with torch.no_grad():
outputs = await self._model.generate(
input_ids=input_ids, **self._generate_args)
output_text = await self._tokenizer.decode(
outputs[0], skip_special_tokens=True)
return output_text
```
We recommend implementing this as an `async` method and using async APIs for
doing all the work (for example, downloads, vLLM or TRT inference).
It is possible to stream results back, see our
[streaming guide](/development/chain/streaming).
If `run_remote()` makes calls to other Chainlets, for example, invoking a dependency
Chainlet for each element in a list, you can benefit from concurrent
execution, by making the `run_remote()` an `async` method and starting the
calls as concurrent tasks
`asyncio.create_task(self._dep_chainlet.run_remote(...))`.
## Entrypoint
The entrypoint is called directly from the deployed Chain's API endpoint and
kicks off the entire chain. The entrypoint is also responsible for returning the
final result back to the client.
Using the
[`@chains.mark_entrypoint`](/reference/sdk/chains#function-truss_chains-mark_entrypoint)
decorator, one Chainlet within a file is set as the entrypoint to the chain.
```python theme={"system"}
@chains.mark_entrypoint
class HelloAll(chains.ChainletBase):
```
Optionally you can also set a Chain display name (not to be confused with
Chainlet display name) with this decorator:
```python theme={"system"}
@chains.mark_entrypoint("My Awesome Chain")
class HelloAll(chains.ChainletBase):
```
## I/O and `pydantic` data types
To make orchestrating multiple remotely deployed services possible, Chains
relies heavily on typed inputs and outputs. Values must be serialized to a safe
exchange format to be sent over the network.
The Chains framework uses the type annotations to infer how data should be
serialized and currently is restricted to types that are JSON compatible. Types
can be:
* Direct type annotations for simple types such as `int`, `float`,
or `list[str]`.
* Pydantic models to define a schema for nested data structures or multiple
arguments.
An example of pydantic input and output types for a Chainlet is given below:
```python theme={"system"}
import enum
import pydantic
class Modes(enum.Enum):
MODE_0 = "MODE_0"
MODE_1 = "MODE_1"
class SplitTextInput(pydantic.BaseModel):
data: str
num_partitions: int
mode: Modes
class SplitTextOutput(pydantic.BaseModel):
parts: list[str]
part_lens: list[int]
```
Refer to the [pydantic docs](https://docs.pydantic.dev/latest/) for more
details on how
to define custom pydantic data models.
Also refer to the [guide](/development/chain/binaryio) about efficient integration
of binary and numeric data.
## Chains compared to Truss
Chains is an alternate SDK for packaging and deploying AI models. It carries over many features and concepts from Truss and gives you access to the benefits of Baseten (resource provisioning, autoscaling, fast cold starts, etc), but it is not a 1-1 replacement for Truss.
Here are some key differences:
* Rather than running `truss init` and creating a Truss in a directory, a Chain
is a single file, giving you more flexibility for implementing multi-step
model inference. Create an example with `truss chains init`.
* Configuration is done inline in typed Python code rather than in a
`config.yaml` file.
* While Chainlets are converted to Truss models when run on Baseten,
`Chainlet != TrussModel`.
Chains is designed for compatibility and incremental adoption, with a stub
function for wrapping existing deployed models. You can also use
`TrussChainlet` to integrate existing Truss directories directly into a Chain
without rewriting them as `ChainletBase` subclasses.
# Deploy
Source: https://docs.baseten.co/development/chain/deploy
Deploy your Chain on Baseten
Deploying a Chain is an atomic action that deploys every Chainlet
within the Chain. Each Chainlet specifies its own remote
environment: hardware resources, Python and system dependencies, autoscaling
settings.
## Published deployment
By default, pushing a Chain creates a published deployment:
```sh Terminal theme={"system"}
truss chains push ./my_chain.py
```
Where `my_chain.py` contains the entrypoint Chainlet for your Chain.
Published deployments have access to full autoscaling settings. Each time you
push, a new deployment is created.
## Development
To create a development deployment for rapid iteration, use `--watch`:
```sh Terminal theme={"system"}
truss chains push ./my_chain.py --watch
```
Development deployments are intended for testing and can't scale past one
replica. Each time you make a development deployment, it overwrites the existing
development deployment.
Development deployments support rapid iteration with live code patching. See the
[watch guide](/development/chain/watch).
## Environments
To deploy a Chain to an environment, run:
```sh Terminal theme={"system"}
truss chains push ./my_chain.py --environment {env_name}
```
Environments are intended for live traffic and have access to full
autoscaling settings. Each time you deploy to an environment, a new deployment is
created. Once the new deployment is live, it replaces the previous deployment,
which is relegated to the published deployments list.
[Learn more](/deployment/environments) about environments.
# Architecture and design
Source: https://docs.baseten.co/development/chain/design
How to structure your Chainlets
A Chain is composed of multiple connected Chainlets working together to perform
a task.
For example, the Chain in the following diagram takes a large audio file as input.
Then it splits it into smaller chunks, transcribes each chunk in parallel
(reducing the end-to-end latency), and finally aggregates and returns the
results.
To build an efficient Chain, we recommend drafting your high level
structure as a flowchart or diagram. This can help you identify
parallelizable units of work and steps that need different (model/hardware)
resources.
If one Chainlet creates many "sub-tasks" by calling other dependency
Chainlets (for example, in a loop over partial work items),
these calls should be done as `asyncio`-tasks that run concurrently.
That way you get the most out of the parallelism that Chains offers. This
design pattern is extensively used in the
[audio transcription example](/examples/chains-audio-transcription).
While using `asyncio` is essential for performance, it can also be tricky.
Here are a few caveats to look out for:
* Executing operations in an async function that block the event loop for
more than a fraction of a second. This hinders the "flow" of processing
requests concurrently and starting RPCs to other Chainlets. Ideally use
native async APIs. Frameworks like vLLM or triton server offer such APIs,
similarly file downloads can be made async and you might find
[`AsyncBatcher`](https://github.com/hussein-awala/async-batcher) useful.
If there is no async support, consider running blocking code in a
thread/process pool (as an attribute of a Chainlet).
* Creating async tasks (for example, with `asyncio.create_task`) does not start
the task *immediately*. In particular, when starting several tasks in a loop,
`create_task` must be alternated with operations that yield to the event
loop that, so the task can be started. If the loop is not `async for` or
contains other `await` statements, a "dummy" await can be added, for example
`await asyncio.sleep(0)`. This allows the tasks to be started concurrently.
# Engine Builder LLM models
Source: https://docs.baseten.co/development/chain/engine-builder-models
Engine-Builder LLM models are pre-trained models that are optimized for specific inference tasks.
Baseten's [Engine-Builder](/engines/engine-builder-llm/overview) enables the deployment of optimized model inference engines. Currently, it supports TensorRT-LLM. Truss Chains lets you use these engines as Chainlets.
## Llama 7B example
Use the `EngineBuilderLLMChainlet` baseclass to configure an LLM engine. The additional `engine_builder_config` field specifies model architecture, repository, engine parameters, and more; the full options are detailed in the [Engine-Builder configuration guide](/engines/engine-builder-llm/engine-builder-config).
Define the engine-backed Chainlet:
```python llama_7b_chainlet.py theme={"system"}
import truss_chains as chains
from truss.base import trt_llm_config, truss_config
class Llama7BChainlet(chains.EngineBuilderLLMChainlet):
remote_config = chains.RemoteConfig(
compute=chains.Compute(gpu=truss_config.Accelerator.H100),
assets=chains.Assets(secret_keys=["hf_access_token"]),
)
engine_builder_config = truss_config.TRTLLMConfiguration(
build=trt_llm_config.TrussTRTLLMBuildConfiguration(
base_model=trt_llm_config.TrussTRTLLMModel.LLAMA,
checkpoint_repository=trt_llm_config.CheckpointRepository(
source=trt_llm_config.CheckpointSource.HF,
repo="meta-llama/Llama-3.1-8B-Instruct",
),
max_batch_size=8,
max_seq_len=4096,
tensor_parallel_count=1,
)
)
```
## Differences from standard Chainlets
* No `run_remote` implementation: Unlike regular Chainlets, `EngineBuilderLLMChainlet` doesn't require users to implement `run_remote()`. Instead, it automatically wires into the deployed engine's API. All LLM Chainlets have the same function signature: `chains.EngineBuilderLLMInput` as input and a stream (`AsyncIterator`) of strings as output. Likewise, `EngineBuilderLLMChainlet`s can only be used as dependencies, but can't have dependencies themselves.
* No `run_local` ([guide](/development/chain/localdev)) or `watch` ([guide](/development/chain/watch)). Standard Chains support a local debugging mode and watch; however, when using `EngineBuilderLLMChainlet`, local execution isn't available, and testing must be done after deployment.
For a faster dev loop of the rest of your chain (everything except the engine-builder Chainlet), you can substitute those Chainlets with stubs, as you can for an already-deployed Truss model ([guide](/development/chain/stub)).
## Integrate the Engine-Builder chainlet
After defining an `EngineBuilderLLMChainlet` like `Llama7BChainlet` above, you can use it as a dependency in other conventional Chainlets:
```python controller.py theme={"system"}
from typing import AsyncIterator
import truss_chains as chains
@chains.mark_entrypoint
class TestController(chains.ChainletBase):
"""Example using the Engine-Builder Chainlet in another Chainlet."""
def __init__(self, llm=chains.depends(Llama7BChainlet)) -> None:
self._llm = llm
async def run_remote(self, prompt: str) -> AsyncIterator[str]:
messages = [{"role": "user", "content": prompt}]
llm_input = chains.EngineBuilderLLMInput(messages=messages)
async for chunk in self._llm.run_remote(llm_input):
yield chunk
```
# Error handling
Source: https://docs.baseten.co/development/chain/errorhandling
Understanding and handling Chains errors
Error handling in Chains follows the principle that the root cause bubbles
up to the entrypoint, which returns an error response. This works like
Python stack traces, which contain all the layers from where an exception was
raised up to the main function.
Consider the case of a Chain where the entrypoint calls `run_remote` of a
Chainlet named `TextToNum` and this in turn invokes `TextReplicator`. The
respective `run_remote` methods might also use other helper functions that
appear in the call stack.
Below is an example stack trace that shows how the root cause (a
`ValueError`) is propagated up to the entrypoint's `run_remote` method (this
is what you would see as an error log):
```text theme={"system"}
Chainlet-Traceback (most recent call last):
File "/packages/itest_chain.py", line 132, in run_remote
value = self._accumulate_parts(text_parts.parts)
File "/packages/itest_chain.py", line 144, in _accumulate_parts
value += self._text_to_num.run_remote(part)
ValueError: (showing chained remote errors, root error at the bottom)
├─ Error in dependency Chainlet `TextToNum`:
│ Chainlet-Traceback (most recent call last):
│ File "/packages/itest_chain.py", line 87, in run_remote
│ generated_text = self._replicator.run_remote(data)
│ ValueError: (showing chained remote errors, root error at the bottom)
│ ├─ Error in dependency Chainlet `TextReplicator`:
│ │ Chainlet-Traceback (most recent call last):
│ │ File "/packages/itest_chain.py", line 52, in run_remote
│ │ validate_data(data)
│ │ File "/packages/itest_chain.py", line 36, in validate_data
│ │ raise ValueError(f"This input is too long: {len(data)}.")
╰ ╰ ValueError: This input is too long: 100.
```
## Exception handling and retries
The stack trace above is what you see if you don't catch the exception. It is
possible to add error handling around each remote Chainlet invocation.
Chains tries to raise the same exception class on the *caller* Chainlet as was
raised in the *dependency* Chainlet.
* Builtin exceptions (for example, `ValueError`) always work.
* Custom or third-party exceptions (for example, from `torch`) can be only raised
in the caller if they are included in the dependencies of the caller as
well. If the exception class cannot be resolved, a
`GenericRemoteException` is raised instead.
The *message* of re-raised exceptions is the concatenation
of the original message and the formatted stack trace of the dependency
Chainlet.
Retry a remote invocation when it fails for transient reasons such as networking. Configure retries with `depends` [options](/reference/sdk/chains#function-truss_chains-depends).
Below example shows how you can add automatic retries and error handling for
the call to `TextReplicator` in `TextToNum`:
```python text_to_num.py theme={"system"}
import truss_chains as chains
class TextToNum(chains.ChainletBase):
def __init__(
self,
replicator: TextReplicator = chains.depends(TextReplicator, retries=3),
) -> None:
self._replicator = replicator
async def run_remote(self, data: ...):
try:
generated_text = await self._replicator.run_remote(data)
except ValueError:
... # Handle error.
```
## Stack filtering
The stack trace is intended to show the user implemented code in
`run_remote` (and user implemented helper functions). Under the
hood, the calls from one Chainlet to another go through an HTTP
connection, managed by the Chains framework. And each Chainlet itself is
run as a FastAPI server with several layers of request handling code "above".
To provide concise, readable stacks, all of this non-user code is
filtered out.
# Your first Chain
Source: https://docs.baseten.co/development/chain/getting-started
Build and deploy two example Chains
This quickstart guide contains instructions for creating two Chains:
1. A simple CPU-only "hello world"-Chain.
2. A Chain that implements Phi-3 Mini and uses it to write poems.
## Prerequisites
You need [uv](https://docs.astral.sh/uv/) installed and a [Baseten account](https://app.baseten.co/signup) with an [API key](https://app.baseten.co/settings/account/api_keys).
## Hello World
Chains are written in Python files. In your working directory,
create `hello_chain/hello.py`:
```sh Terminal theme={"system"}
mkdir hello_chain
cd hello_chain
touch hello.py
```
In the file, we'll specify a basic Chain. It has two Chainlets:
* `HelloWorld`, the entrypoint, which handles the input and output.
* `RandInt`, which generates a random integer. It is used a as a dependency
by `HelloWorld`.
Through the entrypoint, the Chain takes a maximum value and returns the string
"Hello World!" repeated a variable number of times.
```python hello.py theme={"system"}
import random
import truss_chains as chains
class RandInt(chains.ChainletBase):
async def run_remote(self, max_value: int) -> int:
return random.randint(1, max_value)
@chains.mark_entrypoint
class HelloWorld(chains.ChainletBase):
def __init__(self, rand_int=chains.depends(RandInt, retries=3)) -> None:
self._rand_int = rand_int
async def run_remote(self, max_value: int) -> str:
num_repetitions = await self._rand_int.run_remote(max_value)
return "Hello World! " * num_repetitions
```
### The Chainlet class-contract
Exactly one Chainlet must be marked as the entrypoint with
the [`@chains.mark_entrypoint`](/reference/sdk/chains#function-truss_chains-mark_entrypoint)
decorator. This Chainlet is responsible for
handling public-facing input and output for the whole Chain in response to an
API call.
A Chainlet class has a single public method,
[`run_remote()`](/development/chain/concepts#run-remote-chaining-chainlets), which is
the API
endpoint for the entrypoint Chainlet and the function that other Chainlets can
use as a dependency. The
[`run_remote()`](/development/chain/concepts#run-remote-chaining-chainlets)
method must be fully type-annotated
with primitive python
types
or [pydantic models](https://docs.pydantic.dev/latest/).
Chainlets cannot be naively instantiated. The only correct usages are:
1. Make one Chainlet depend on another one through the
[`chains.depends()`](/reference/sdk/chains#function-truss_chains-depends) directive
as an `__init__`-argument as shown above for the `RandInt` Chainlet.
2. In the [local debugging mode](/development/chain/localdev#test-a-chain-locally).
Beyond that, you can structure your code as you like, with private methods,
imports from other files, and so forth.
Keep in mind that Chainlets are intended for distributed, replicated, remote
execution, so using global variables, global state, and certain Python
features like importing modules dynamically at runtime should be avoided as
they may not work as intended.
### Deploy your Chain to Baseten
To deploy your Chain to Baseten, run:
```bash Terminal theme={"system"}
truss chains push --watch hello.py
```
The deploy command results in an output like this:
```text Output theme={"system"}
⛓️ HelloWorld - Chainlets ⛓️
╭──────────────────────┬─────────────────────────┬─────────────╮
│ Status │ Name │ Logs URL │
├──────────────────────┼─────────────────────────┼─────────────┤
│ 💚 ACTIVE │ HelloWorld (entrypoint) │ https://... │
├──────────────────────┼─────────────────────────┼─────────────┤
│ 💚 ACTIVE │ RandInt (dep) │ https://... │
╰──────────────────────┴─────────────────────────┴─────────────╯
Deployment succeeded.
You can run the chain with:
curl -X POST 'https://chain-.../run_remote' \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d ''
```
Wait for the status to turn to `ACTIVE` and test invoking your Chain (replace
`$INVOCATION_URL` in below command):
```bash Request theme={"system"}
curl -X POST $INVOCATION_URL \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{"max_value": 10}'
# "Hello World! Hello World! Hello World! "
```
## Poetry with LLMs
Our second example also has two Chainlets, but is somewhat more complex and
realistic. The Chainlets are:
* `PoemGenerator`, the entrypoint, which handles the input and output and
orchestrates calls to the LLM.
* `PhiLLM`, which runs inference on Phi-3 Mini.
This Chain takes a list of words and returns a poem about each word, written by
Phi-3. Here's the architecture:
We build this Chain in a new working directory (if you are still inside
`hello_chain/`, go up one level with `cd ..` first):
```sh Terminal theme={"system"}
mkdir poetry_chain
cd poetry_chain
touch poems.py
```
A similar end-to-end code example, using Mistral as an LLM, is available in
the [examples
repo](https://github.com/basetenlabs/model/tree/main/truss-chains/examples/mistral).
### Build the LLM Chainlet
The main difference between this Chain and the previous one is that we now have
an LLM that needs a GPU and more complex dependencies.
Copy the following code into `poems.py`:
```python poems.py theme={"system"}
import asyncio
from typing import List
import pydantic
import truss_chains as chains
from truss import truss_config
PHI_HF_MODEL = "microsoft/Phi-3-mini-4k-instruct"
PHI_MOUNT = "/models/phi"
# Mount the model weights from Hugging Face into the Chainlet container at runtime.
PHI_WEIGHTS = truss_config.WeightsSource(
source=f"hf://{PHI_HF_MODEL}@main",
mount_location=PHI_MOUNT,
allow_patterns=["*.json", "*.safetensors", ".model"],
)
class Messages(pydantic.BaseModel):
messages: List[dict[str, str]]
class PhiLLM(chains.ChainletBase):
# `remote_config` defines the resources required for this chainlet.
remote_config = chains.RemoteConfig(
docker_image=chains.DockerImage(
# The phi model needs some extra python packages.
pip_requirements=[
"accelerate==0.30.1",
"einops==0.8.0",
"transformers==4.41.2",
"torch==2.3.0",
]
),
# The phi model needs a GPU and more CPUs.
compute=chains.Compute(cpu_count=2, gpu="T4"),
# Mount the model weights at runtime through BDN.
assets=chains.Assets(weights=[PHI_WEIGHTS]),
)
def __init__(self) -> None:
# Note the imports of the *specific* python requirements are
# pushed down to here. This code will only be executed on the
# remotely deployed Chainlet, not in the local environment,
# so we don't need to install these packages in the local
# dev environment.
import torch
import transformers
self._model = transformers.AutoModelForCausalLM.from_pretrained(
PHI_MOUNT,
torch_dtype=torch.float16,
device_map="auto",
)
self._tokenizer = transformers.AutoTokenizer.from_pretrained(
PHI_MOUNT,
)
self._generate_args = {
"max_new_tokens" : 512,
"temperature" : 1.0,
"top_p" : 0.95,
"top_k" : 50,
"repetition_penalty" : 1.0,
"no_repeat_ngram_size": 0,
"use_cache" : True,
"do_sample" : True,
"eos_token_id" : self._tokenizer.eos_token_id,
"pad_token_id" : self._tokenizer.pad_token_id,
}
async def run_remote(self, messages: Messages) -> str:
import torch
model_inputs = self._tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
inputs = self._tokenizer(model_inputs, return_tensors="pt")
input_ids = inputs["input_ids"].to("cuda")
with torch.no_grad():
outputs = self._model.generate(
input_ids=input_ids, **self._generate_args)
output_text = self._tokenizer.decode(
outputs[0], skip_special_tokens=True)
return output_text
```
### Build the entrypoint
Now that we have an LLM, we can use it in a poem generator Chainlet. Add the
following code to `poems.py`:
```python poems.py theme={"system"}
import asyncio
@chains.mark_entrypoint
class PoemGenerator(chains.ChainletBase):
def __init__(self, phi_llm: PhiLLM = chains.depends(PhiLLM)) -> None:
self._phi_llm = phi_llm
async def run_remote(self, words: list[str]) -> list[str]:
tasks = []
for word in words:
messages = Messages(
messages=[
{
"role" : "system",
"content": (
"You are poet who writes short, "
"lighthearted, amusing poetry."
),
},
{"role": "user", "content": f"Write a poem about {word}"},
]
)
tasks.append(
asyncio.create_task(self._phi_llm.run_remote(messages)))
await asyncio.sleep(0) # Yield to event loop, to allow starting tasks.
return list(await asyncio.gather(*tasks))
```
We use `asyncio.create_task` around each RPC to the LLM chainlet.
This makes the current python process start these remote calls concurrently,
that is, the next call is started before the previous one has finished and we can
minimize our overall runtime. To await the results of all calls,
`asyncio.gather` is used which gives us back normal python objects.
If the LLM is hit with many concurrent requests, it can auto-scale up (if
autoscaling is configured). More advanced LLM models have batching capabilities,
so for those even a single instance can serve concurrent request.
### Deploy your Chain to Baseten
To deploy your Chain to Baseten, run:
```bash Terminal theme={"system"}
truss chains push --watch poems.py
```
Wait for the status to turn to `ACTIVE` and test invoking your Chain (replace
`$INVOCATION_URL` in below command):
```bash Request theme={"system"}
curl -X POST $INVOCATION_URL \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{"words": ["bird", "plane", "superman"]}'
#[[
#" [INST] Generate a poem about: bird [/INST] In the quiet hush of...",
#" [INST] Generate a poem about: plane [/INST] In the vast, boundless...",
#" [INST] Generate a poem about: superman [/INST] In the realm where..."
#]]
```
# Invocation
Source: https://docs.baseten.co/development/chain/invocation
Call your deployed Chain
Once your Chain is deployed, you can call it through its API endpoint. Chains use
the same inference API as models:
* [Environment endpoint](/reference/inference-api/predict-endpoints/environments-run-remote)
* [Development endpoint](/reference/inference-api/predict-endpoints/development-run-remote)
* [Endpoint by ID](/reference/inference-api/predict-endpoints/deployment-run-remote)
Here's an example which calls the development deployment:
```python call_chain.py theme={"system"}
import requests
import os
# From the Chain overview page on Baseten
# E.g. "https://chain-.api.baseten.co/development/run_remote"
CHAIN_URL = ""
baseten_api_key = os.environ["BASETEN_API_KEY"]
# JSON keys and types match the `run_remote` method signature.
data = {...}
resp = requests.post(
CHAIN_URL,
headers={"Authorization": f"Bearer {baseten_api_key}"},
json=data,
)
print(resp.json())
```
## How to pass chain input
The data schema of the inference request corresponds to the function
signature of [`run_remote()`](/development/chain/concepts#run-remote-chaining-chainlets)
in your entrypoint Chainlet.
For example, for the Hello Chain, `HelloAll.run_remote()`:
```python theme={"system"}
async def run_remote(self, names: list[str]) -> str:
```
You'd pass the following JSON payload:
```json Payload theme={"system"}
{ "names": ["Marius", "Sid", "Bola"] }
```
That is, the keys in the JSON record match the argument names, and values
match the types of `run_remote`.
## Async chain inference
Like Truss models, Chains support async invocation. The [guide for
models](/inference/async) applies largely. In particular for how to wrap the
input and set up the webhook to process results.
The following additional points are chains specific:
* Use chain-based URLS:
* `https://chain-{chain}.api.baseten.co/production/async_run_remote`
* `https://chain-{chain}.api.baseten.co/development/async_run_remote`
* `https://chain-{chain}.api.baseten.co/deployment/{deployment}/async_run_remote`.
* `https://chain-{chain}.api.baseten.co/environments/{env_name}/async_run_remote`.
* Only the entrypoint is invoked asynchronously. Internal Chainlet-Chainlet
calls run synchronously.
# Local development
Source: https://docs.baseten.co/development/chain/localdev
Iterating, Debugging, Testing, Mocking
Chains run in production as replicated remote deployments, but you can develop
and test them locally first.
Chains exists to help you build multi-step, multi-model pipelines. The
abstractions that Chains introduces are based on six opinionated principles:
three for architecture and three for developer experience.
**Architecture principles**
Each step in the pipeline can set its own hardware requirements and
software dependencies, separating GPU and CPU workloads.
Each component has independent autoscaling parameters for targeted
resource allocation, removing bottlenecks from your pipelines.
Components specify a single public interface for flexible-but-safe
composition and are reusable between projects
**Developer experience principles**
Eliminate entire taxonomies of bugs by writing typed Python code and
validating inputs, outputs, module initializations, function signatures,
and even remote server configurations.
Seamless local testing and cloud deployments: test Chains locally with
support for mocking the output of any step and simplify your cloud
deployment loops by separating large model deployments from quick
updates to glue code.
Use Chains to orchestrate existing model deployments, like pre-packaged
models from Baseten’s model library, alongside new model pipelines built
entirely within Chains.
Locally, a Chain is just Python files in a source tree. While that gives you a
lot of flexibility in how you structure your code, there are some constraints
and rules to follow to ensure successful distributed, remote execution in
production.
The best thing you can do while developing locally with Chains is to run your
code frequently, even if you do not have a `__main__` section: the Chains
framework runs various validations at
module initialization to help
you catch issues early.
Additionally, running `mypy` and fixing reported type errors can help you
find problems early in a rapid feedback loop, before attempting a (much
slower) deployment.
Complementary to the purely local development Chains also has a "watch" mode,
like Truss, see the [watch guide](/development/chain/watch).
## Test a Chain locally
Let's revisit our "Hello World" Chain:
```python hello_chain/hello.py theme={"system"}
import asyncio
import truss_chains as chains
# This Chainlet does the work
class SayHello(chains.ChainletBase):
async def run_remote(self, name: str) -> str:
return f"Hello, {name}"
# This Chainlet orchestrates the work
@chains.mark_entrypoint
class HelloAll(chains.ChainletBase):
def __init__(self, say_hello_chainlet=chains.depends(SayHello)) -> None:
self._say_hello = say_hello_chainlet
async def run_remote(self, names: list[str]) -> str:
tasks = []
for name in names:
tasks.append(asyncio.create_task(
self._say_hello.run_remote(name)))
return "\n".join(await asyncio.gather(*tasks))
# Test the Chain locally
if __name__ == "__main__":
with chains.run_local():
hello_chain = HelloAll()
result = asyncio.run(hello_chain.run_remote(["Marius", "Sid", "Bola"]))
print(result)
```
When the `__main__()` module is run, local instances of the Chainlets are
created, allowing you to test functionality of your chain just by executing the
Python file:
```bash Terminal theme={"system"}
cd hello_chain
python hello.py
# Hello, Marius
# Hello, Sid
# Hello, Bola
```
## Mock execution of GPU Chainlets
Using `run_local()` to run your code locally requires that your development
environment have the compute resources and dependencies that each Chainlet
needs. But that often isn't possible when building with AI models.
Chains offers a workaround, mocking, to let you test the coordination and
business logic of your multi-step inference pipeline without worrying about
running the model locally.
The second example in the [getting started guide](/development/chain/getting-started)
implements a Truss Chain for generating poems with Phi-3.
This Chain has two Chainlets:
1. The `PhiLLM` Chainlet, which can run on NVIDIA GPUs such as the L4.
2. The `PoemGenerator` Chainlet, which easily runs on a CPU.
If you have an NVIDIA T4 under your desk, good for you. For the rest of us, we
can mock the `PhiLLM` Chainlet that is infeasible to run locally so that we can
quickly test the `PoemGenerator` Chainlet.
To do this, we define a mock Phi-3 model in our `__main__` module and give it
a [`run_remote()`](/development/chain/concepts#run-remote-chaining-chainlets) method that
produces a test output that matches the output type we expect from the real
Chainlet. Then, we inject an instance of this mock Chainlet into our Chain:
```python poems.py theme={"system"}
if __name__ == "__main__":
class FakePhiLLM:
async def run_remote(self, prompt: str) -> str:
return f"Here's a poem about {prompt.split(' ')[-1]}"
with chains.run_local():
poem_generator = PoemGenerator(phi_llm=FakePhiLLM())
result = asyncio.run(poem_generator.run_remote(words=["bird", "plane", "superman"]))
print(result)
```
And run your Python file:
```bash Terminal theme={"system"}
python poems.py
# ['Here's a poem about bird', 'Here's a poem about plane', 'Here's a poem about superman']
```
### Typing of mocks
You may notice that the argument `phi_llm` expects a type `PhiLLM`, while we
pass an instance of `FakePhiLLM`. These aren't the same, which is formally a
type error.
However, this works at runtime because we constructed `FakePhiLLM` to
implement the same *protocol* as the real thing. We can make this explicit by
defining a `Protocol` as a type annotation:
```python theme={"system"}
from typing import Protocol
class PhiProtocol(Protocol):
def run_remote(self, data: str) -> str:
...
```
and changing the argument type in `PoemGenerator`:
```python theme={"system"}
@chains.mark_entrypoint
class PoemGenerator(chains.ChainletBase):
def __init__(self, phi_llm: PhiProtocol = chains.depends(PhiLLM)) -> None:
self._phi_llm = phi_llm
```
The `Protocol` annotation is optional; it makes the typing consistency explicit.
# Overview
Source: https://docs.baseten.co/development/chain/overview
Chains is a framework for building robust, performant multi-step and multi-model
inference pipelines and deploying them to production. It addresses the common
challenges of managing latency, cost and dependencies for complex workflows,
while leveraging Truss' existing battle-tested performance, reliability and
developer toolkit.
## User guides
Guides focus on specific features and use cases. Also refer to
[getting started](/development/chain/getting-started) and
[general concepts](/development/chain/concepts).
How to structure your Chainlets, concurrency, file structure
Iterating, Debugging, Testing, Mocking
Deploy your Chain on Baseten
Call your deployed Chain
Live-patch deployed code
Modularize and re-use Chainlet implementations
Streaming outputs, reducing latency, SSEs
Performant serialization of numeric data
Understanding and handling Chains errors
Integrate deployed Truss models with stubs
## From model to system
Some models are actually pipelines (for example, invoking a LLM involves sequentially
tokenizing the input, predicting the next token, and then decoding the predicted
tokens). These pipelines generally make sense to bundle together in a monolithic
deployment because they have the same dependencies, require the same compute
resources, and have a robust ecosystem of tooling to improve efficiency and
performance in a single deployment.
Many other pipelines and systems do not share these properties. Some examples
include:
* Running multiple different models in sequence.
* Chunking/partitioning a set of files and concatenating/organizing results.
* Pulling inputs from or saving outputs to a database or vector store.
Each step in these workflows has different hardware requirements, software
dependencies, and scaling needs so it doesn't make sense to bundle them in a
monolithic deployment. That's where Chains comes in.
## Principles behind Chains
Chains exists to help you build multi-step, multi-model pipelines. The
abstractions that Chains introduces are based on six opinionated principles:
three for architecture and three for developer experience.
**Architecture principles**
Each step in the pipeline can set its own hardware requirements and
software dependencies, separating GPU and CPU workloads.
Each component has independent autoscaling parameters for targeted
resource allocation, removing bottlenecks from your pipelines.
Components specify a single public interface for flexible-but-safe
composition and are reusable between projects
**Developer experience principles**
Eliminate entire taxonomies of bugs by writing typed Python code and
validating inputs, outputs, module initializations, function signatures,
and even remote server configurations.
Seamless local testing and cloud deployments: test Chains locally with
support for mocking the output of any step and simplify your cloud
deployment loops by separating large model deployments from quick
updates to glue code.
Use Chains to orchestrate existing model deployments, like pre-packaged
models from Baseten’s model library, alongside new model pipelines built
entirely within Chains.
## Hello World with Chains
Here's a simple Chain that says "hello" to each person in a list of provided
names:
```python hello_chain/hello.py theme={"system"}
import asyncio
import truss_chains as chains
# This Chainlet does the work.
class SayHello(chains.ChainletBase):
async def run_remote(self, name: str) -> str:
return f"Hello, {name}"
# This Chainlet orchestrates the work.
@chains.mark_entrypoint
class HelloAll(chains.ChainletBase):
def __init__(self, say_hello_chainlet=chains.depends(SayHello)) -> None:
self._say_hello = say_hello_chainlet
async def run_remote(self, names: list[str]) -> str:
tasks = []
for name in names:
tasks.append(asyncio.create_task(
self._say_hello.run_remote(name)))
return "\n".join(await asyncio.gather(*tasks))
```
This is a toy example, but it shows how Chains can be used to separate
preprocessing steps like chunking from workload execution steps. If SayHello
were an LLM instead of a simple string template, we could do a much more complex
action for each person on the list.
## What to build with Chains
Connect to vector databases and augment LLM results with additional
context information without introducing overhead to the model inference
step.
Try it yourself: [RAG Chain](/examples/chains-build-rag).
Transcribe large audio files by splitting them into smaller chunks and
processing them in parallel. We've used this approach to process 10-hour
files in minutes.
Try it yourself: [Audio Transcription Chain](/examples/chains-audio-transcription).
Build powerful experiences with optimal scaling in each step like:
* AI phone calling (transcription + LLM + speech synthesis)
* Multi-step image generation (SDXL + LoRAs + ControlNets)
* Multimodal chat (LLM + vision + document parsing + audio)
Since each stage runs on its hardware with independent auto-scaling,
you can achieve better hardware utilization and save costs.
Get started by
[building and deploying your first chain](/development/chain/getting-started).
# Streaming
Source: https://docs.baseten.co/development/chain/streaming
Streaming outputs, reducing latency, SSEs
Streaming outputs is useful for returning partial results to the client, before
all data has been processed.
For example, LLM text generation happens in incremental text chunks, so the
beginning of the reply can be sent to the client before the whole
prediction is complete.
Similarly, transcribing audio to text happens in \~30 second chunks, and the
first ones can be returned before all are complete.
In general, this doesn't reduce the overall processing time (still the same
amount of work must be done), but the initial latency to get some response
can be reduced significantly.
In some cases it might even reduce overall time: when streaming results
internally in a Chain allows subsequent processing steps to start sooner,
that is, pipelining the operations in a more efficient way.
## Low-level streaming
At a low level, streaming works by sending byte chunks (unicode strings are
implicitly encoded) over HTTP. The most primitive way of doing this in Chains
is by implementing `run_remote` as a bytes- or string-iterator, for example:
```python streamlet.py theme={"system"}
from typing import AsyncIterator
import truss_chains as chains
class Streamlet(chains.ChainletBase):
async def run_remote(self, inputs: ...) -> AsyncIterator[str]:
async for text_chunk in make_incremental_outputs(inputs):
yield text_chunk
```
Choose what data to represent in the byte or string chunks: it
could be raw text generated by an LLM, a JSON string, bytes, or
anything else.
## Server-sent events (SSEs)
A possible choice is to generate chunks that comply with the
[specification](https://html.spec.whatwg.org/multipage/server-sent-events.html)
of server-sent events.
Concretely, sending JSON strings with `data`, `event` and potentially
other fields and content-type `text/event-stream`.
However, the SSE specification is not opinionated regarding what exactly is
encoded in `data` and what `event`-types exist. You have to make up your schema
that is useful for the client that consumes the data.
## Pydantic and Chainlet-Chainlet streams
While above low-level streaming is stable, the following helper APIs for typed
streaming are only stable for intra-Chain streaming.
If you want to use them for end clients, please reach out to Baseten support,
so we can discuss the stable solutions.
Unlike above "raw" stream example, Chains takes the general opinion that
input and output types should be definite, so that divergence and type
errors can be avoided.
Like you type-annotate Chainlet inputs and outputs in the non-streaming
case, and use pydantic to manage more complex data structures, we built
tooling to bring the same benefits to streaming.
## Headers and footers
This also helps to solve another challenge of streaming: you might want to
send data of different kinds at the beginning or end of a stream than in
the "main" part.
For example if you transcribe an audio file, you might want
to send many transcription segments in a stream and at the end send some
aggregate information such as duration, detected languages etc.
We model typed streaming like this:
* \[optionally] send a chunk that conforms to the schema of a `Header` pydantic
model.
* Send 0 to N chunks each conforming to the schema of an `Item` pydantic
model.
* \[optionally] send a chunk that conforms to the schema of a `Footer` pydantic
model.
## APIs
### StreamTypes
To have a single source of truth for the types that can be shared between
the producing Chainlet and the consuming client (either a Chainlet in the
Chain or an external client), the chains framework uses a `StreamType`-object:
```python stream_types.py theme={"system"}
import pydantic
from truss_chains import streaming
class MyDataChunk(pydantic.BaseModel):
words: list[str]
STREAM_TYPES = streaming.stream_types(
MyDataChunk, header_type=..., footer_type=...)
```
Header and footer types are optional and can be left out:
```python stream_types.py theme={"system"}
STREAM_TYPES = streaming.stream_types(MyDataChunk)
```
### StreamWriter
Use the `STREAM_TYPES` to create a matching stream writer:
```python stream_writer.py theme={"system"}
from typing import AsyncIterator
import pydantic
import truss_chains as chains
from truss_chains import streaming
class MyDataChunk(pydantic.BaseModel):
words: list[str]
STREAM_TYPES = streaming.stream_types(MyDataChunk)
class Streamlet(chains.ChainletBase):
async def run_remote(self, inputs: ...) -> AsyncIterator[bytes]:
stream_writer = streaming.stream_writer(STREAM_TYPES)
async for item in make_pydantic_items(inputs):
yield stream_writer.yield_item(item)
```
If your stream types have header or footer types, corresponding
`yield_header` and `yield_footer` methods are available on the writer.
The writer serializes the pydantic data to `bytes`, so you can also
efficiently represent numeric data (see the
[binary IO guide](/development/chain/binaryio)).
### StreamReader
To consume the stream on either another Chainlet or in the external client, a
matching `StreamReader` is created form your `StreamTypes`. Besides the
types, you connect the reader to the bytes generator that you obtain from the
remote invocation of the streaming Chainlet:
```python stream_reader.py theme={"system"}
import truss_chains as chains
from truss_chains import streaming
class Consumer(chains.ChainletBase):
def __init__(self, streamlet=chains.depends(Streamlet)):
self._streamlet = streamlet
async def run_remote(self, data: ...):
byte_stream = self._streamlet.run_remote(data)
reader = streaming.stream_reader(STREAM_TYPES, byte_stream)
chunks = []
async for data in reader.read_items():
chunks.append(data)
```
If you use headers or footers, the reader has async `read_header` and
`read_footer` methods.
The stream can only be consumed once and you have to consume
header, items and footer in order.
The implementation of `StreamReader` only needs `pydantic`, no other Chains
dependencies. So you can take that implementation code in isolation and
integrate it in your client code.
# Truss integration
Source: https://docs.baseten.co/development/chain/stub
Integrate deployed Truss models with stubs
Chains can be combined with existing Truss models using Stubs.
A Stub acts as a substitute (client-side proxy) for a remotely deployed
dependency, either a Chainlet or a Truss model. The Stub performs the remote
invocations as if it were local by taking care of the transport layer,
authentication, data serialization and retries.
Stubs can be integrated into Chainlets by passing in a URL of the deployed
model. They also require
[`context`](/development/chain/concepts#context-access-information) to be initialized
(for authentication).
The following Chainlet wraps a deployed model with a Stub:
```python my_chainlet.py theme={"system"}
import truss_chains as chains
class LLMClient(chains.StubBase):
async def run_remote(self, prompt: str) -> str:
# Call the deployed model
resp = await self.predict_async(inputs={
"messages": [{"role": "user", "content": prompt}],
"stream" : False
})
# Return a string with the model output
return resp["output"]
LLM_URL = ...
class MyChainlet(chains.ChainletBase):
def __init__(
self,
context: chains.DeploymentContext = chains.depends_context(),
):
self._llm = LLMClient.from_url(LLM_URL, context)
```
There are various ways how you can make a call to the other deployment:
* Input as JSON dict (like above) or pydantic model.
* Automatic parsing of the response into a pydantic model using the
`output_model` argument.
* `predict_async` (recommended) or `predict_sync`.
* Streaming responses using `predict_async_stream` which returns an async
bytes iterator.
* Customized with `RPCOptions`.
See the
[StubBase reference](/reference/sdk/chains#class-truss_chains-stubbase)
for all APIs.
### TrussChainlet and TrussHandle
You can integrate existing Truss models directly into a chain without rewriting them as `ChainletBase` subclasses. Use `TrussChainlet` to wrap a Truss directory and `TrussHandle` to manage connections to it.
`TrussChainlet` wraps an existing Truss directory as a non-entry leaf chainlet. This is useful for including models that use custom servers (like vLLM) or existing `model.py` implementations.
```python my_chainlet.py theme={"system"}
import truss_chains as chains
class STT(chains.TrussChainlet):
truss_dir = "./a_truss_model"
```
To call a `TrussChainlet` from another chainlet, use `chains.depends()` as a default argument in `__init__`. At runtime this provides a `TrussHandle`, which supplies the arguments for raw HTTP or WebSocket calls. `TrussChainlet` cannot be used as an entrypoint and cannot declare its own dependencies.
```python my_chainlet.py theme={"system"}
from truss_chains.remote_chainlet.truss_chainlet import TrussHandle
class MyChainlet(chains.ChainletBase):
def __init__(self, stt: TrussHandle = chains.depends(STT)):
self._stt = stt
async def run_remote(self, audio_data: bytes) -> str:
# Get HTTP call arguments
url, headers = self._stt.http_call_args()
# Use url and headers with your preferred HTTP client
...
```
`TrussHandle` supports Bring Your Own Client (BYOC) scenarios by exposing raw connection details:
* `http_call_args()`: Returns the URL and headers for HTTP requests. Use `prefer_internal=True` to use the workload-plane URL with the correct `Host` header, or `sync_path` to rewrite the URL for platform passthrough endpoints.
* `ws_call_args()`: Returns the URL and headers for WebSocket connections.
# Subclassing
Source: https://docs.baseten.co/development/chain/subclassing
Modularize and re-use Chainlet implementations
Sometimes you want to write one "main" implementation of a complicated inference
task, but then re-use it for similar variations. For example:
* Deploy it on different hardware and with different concurrency.
* Replace a dependency (for example, silence detection in audio files) with a
different implementation of that step, while keeping all other processing
the same.
* Deploy the same inference flow, but exchange the model weights used. For example, for
a large and small version of an LLM or different model weights fine-tuned to
domains.
* Add an adapter to convert between a different input/output schema.
In all of those cases, you can create lightweight subclasses of your main
chainlet.
These patterns can be combined with each other.
## Example base class
Define the base Chainlet and verify its behavior locally:
```python base_chainlet.py theme={"system"}
import asyncio
import truss_chains as chains
class Preprocess2x(chains.ChainletBase):
async def run_remote(self, number: int) -> int:
return 2 * number
class MyBaseChainlet(chains.ChainletBase):
remote_config = chains.RemoteConfig(
compute=chains.Compute(cpu_count=1, memory="100Mi"),
options=chains.ChainletOptions(enable_b10_tracing=True),
)
def __init__(self, preprocess=chains.depends(Preprocess2x)):
self._preprocess = preprocess
async def run_remote(self, number: int) -> float:
return 1.0 / await self._preprocess.run_remote(number)
# Assert base behavior.
with chains.run_local():
chainlet = MyBaseChainlet()
result = asyncio.run(chainlet.run_remote(4))
assert result == 1 / (4 * 2)
```
## Adapter for different I/O
The base class `MyBaseChainlet` works with integer inputs and returns floats. If
you want to reuse the computation, but provide an alternative interface (for example,
for a different client with different request/response schema), you can create
a subclass which does the I/O conversion. The actual computation is delegated to
the base classes above:
```python string_io_adapter.py theme={"system"}
class ChainletStringIO(MyBaseChainlet):
async def run_remote(self, number: str) -> str:
return str(await super().run_remote(int(number)))
# Assert new behavior.
with chains.run_local():
chainlet_string_io = ChainletStringIO()
result = asyncio.run(chainlet_string_io.run_remote("4"))
assert result == "0.125"
```
## Chain with substituted dependency
The base class `MyBaseChainlet` uses preprocessing that doubles the input. If
you want to use a different variant of preprocessing, while keeping
`MyBaseChainlet.run_remote` and everything else as is, you can define a shallow
subclass of `MyBaseChainlet` that uses a different dependency,
`Preprocess8x`, which multiplies by 8 instead of 2:
```python substituted_dependency.py theme={"system"}
class Preprocess8x(chains.ChainletBase):
async def run_remote(self, number: int) -> int:
return 8 * number
class Chainlet8xPreprocess(MyBaseChainlet):
def __init__(self, preprocess=chains.depends(Preprocess8x)):
super().__init__(preprocess=preprocess)
# Assert new behavior.
with chains.run_local():
chainlet_8x_preprocess = Chainlet8xPreprocess()
result = asyncio.run(chainlet_8x_preprocess.run_remote(4))
assert result == 1 / (4 * 8)
```
## Override remote config
If you want to re-deploy a chain, but change some deployment options, for example, run
on different hardware, you can create a subclass and override `remote_config`:
```python override_config.py theme={"system"}
class Chainlet16Core(MyBaseChainlet):
remote_config = chains.RemoteConfig(
compute=chains.Compute(cpu_count=16, memory="100Mi"),
options=chains.ChainletOptions(enable_b10_tracing=True),
)
```
Be aware that `remote_config` is a class variable. In the example above we
created a completely new `RemoteConfig` value, because changing fields
*inplace* would also affect the base class.
If you want to share config between the base class and subclasses, you can
define them in additional variables for example, for the image:
```python shared_config.py theme={"system"}
DOCKER_IMAGE = chains.DockerImage(pip_requirements=[...], ...)
class MyBaseChainlet(chains.ChainletBase):
remote_config = chains.RemoteConfig(docker_image=DOCKER_IMAGE, ...)
class Chainlet16Core(MyBaseChainlet):
remote_config = chains.RemoteConfig(docker_image=DOCKER_IMAGE, ...)
```
# Watch
Source: https://docs.baseten.co/development/chain/watch
Live-patch deployed code
The [watch command](/reference/cli/chains/chains-cli#watch) (`truss chains watch`) combines
the best of local development and full deployment. `watch` lets you run on an
exact copy of the production hardware and interface but gives you live code
patching that lets you test changes in seconds without creating a new
deployment.
**To use `truss chains watch`**:
1. Push a chain in development mode with `truss chains push --watch SOURCE`.
This creates a development deployment and starts watching in one step.
2. Edit a file and save the changes. The watcher patches the remote
deployments, which might take a moment but is generally *much* faster than
creating a new deployment.
3. Call the chain with test data using `cURL` or the playground dialogue
in the UI and check the result and logs.
4. Repeat steps 2 and 3 until your chain behaves the way you want.
If you already created the development deployment, run
`truss chains watch SOURCE` to attach the watcher instead of pushing with
`--watch`.
By default, `watch` keeps your development Chainlets warm so they don't scale to
zero while you iterate. On startup, the watcher wakes any scaled-to-zero
Chainlets, waits for them to be ready before applying the first patch, then keeps
them warm for the rest of the session. This avoids the readiness wait and the
occasional patch failures that happen when a Chainlet falls asleep between edits.
The `--no-sleep` flag controls this keepalive and is on by default. To let idle
Chainlets scale to zero during a long watch session, opt out with
`truss chains watch my_chain.py --no-sleep=false`. When you watch through `push`,
pass `--watch-no-sleep=false` instead:
`truss chains push my_chain.py --watch --watch-no-sleep=false`.
## Selective watch
Some large ML models might have a slow cycle time to reload (for example, if the
weights are huge). For this case, we provide a "selective" watch option. For
example, if your chain has such a heavy model Chainlet and other Chainlets
that contain only business logic, you can iterate on those, while not patching
and reloading the heavy model Chainlet.
This feature is useful for advanced use cases, but must be used with
caution. If you change the code of a Chainlet not watched, in particular I/O types,
you get an inconsistent deployment.
Add the Chainlet names you want to watch as a comma-separated list:
```bash Terminal theme={"system"}
truss chains watch ... --experimental-chainlet-names=ChainletA,ChainletB
```
# Baseten Delivery Network
Source: https://docs.baseten.co/development/model/bdn
Optimize cold starts with multi-tier caching and data delivery
Baseten Delivery Network (BDN) reduces cold start times by mirroring your model weights to Baseten's infrastructure and caching them close to your replicas.
Instead of downloading hundreds of gigabytes from sources like Hugging Face, Amazon S3, or Google Cloud Storage on every scale-up, BDN mirrors weights once and serves them from multi-tier caches.
Configure BDN using the `weights` key in your config.
This works with both `Model` class deployments and [custom Docker images](/development/model/custom-server).
Add weights to a new model
Use with vLLM, SGLang, and more
Move from `model_cache`
BDN mirrors any [supported source](#source-types-and-authentication) the same way. If your weights are only on local disk, [bundle them with your Truss](/development/model/model-class#bundled-data) for small models, or push them to a private Hugging Face repository for large ones.
## Quick start
Add a `weights` section to your `config.yaml`. The example highlights it inside a complete config; expand to see the full file:
```yaml config.yaml expandable {5-12} theme={"system"}
model_name: qwen-3-8b
resources:
accelerator: H100
use_gpu: true
weights:
- source: "hf://Qwen/Qwen3-8B@b968826d9c46dd6066d109eabc6255188de91218" # Pin a commit SHA for reproducible deploys
mount_location: "/models/qwen"
auth:
auth_method: CUSTOM_SECRET
auth_secret_name: "hf_access_token" # Required for private or gated repos
allow_patterns: ["*.safetensors", "*.json", "tokenizer.*"]
ignore_patterns: ["*.md", "*.txt"]
```
* [`source`](#param-source): Where to fetch weights from. Supports [Hugging Face](#hugging-face), [Baseten Training](#baseten-training), [S3](#aws-s3), [GCS](#google-cloud-storage), [R2](#cloudflare-r2), [CoreWeave](#coreweave-ai-object-storage), and [Azure Blob Storage](#azure-blob-storage).
* [`mount_location`](#param-mount-location): Absolute path where the weights appear in your container.
* [`auth`](#param-auth): Credentials for private or gated sources.
* [`allow_patterns`](#param-allow-patterns): Download only the files matching these patterns, to skip large files you don't need.
* [`ignore_patterns`](#param-ignore-patterns): Skip files matching these patterns, like docs or unused formats.
BDN authenticates private or gated repos through this per-source `auth` block, which is separate from the top-level [`secrets`](/development/model/secrets) config. A `secrets` entry alone does not authenticate weight mirroring. Create the secret (here, `hf_access_token` with your Hugging Face token) in your [workspace settings](https://app.baseten.co/settings/secrets), then reference it by name. Public sources need no `auth`.
### Access weights in your model
When your model starts, weights are already downloaded and available at your `mount_location`.
The directory structure from the source is preserved:
```text theme={"system"}
/models/qwen/ # your mount_location
├── config.json
├── model-00001-of-00004.safetensors
├── model-00002-of-00004.safetensors
├── ...
├── model.safetensors.index.json
├── tokenizer.json
└── tokenizer_config.json
```
Load weights directly from this path in your `load()` method. No download code needed:
```python model.py theme={"system"}
from transformers import AutoModelForCausalLM
class Model:
def load(self):
# Weights are already available at mount_location
self._model = AutoModelForCausalLM.from_pretrained(
"/models/qwen",
torch_dtype="auto",
device_map="auto"
)
```
The mount is read-only.
Weights are fetched during `truss push` and cached, so cold starts only read from local or nearby caches.
## Custom servers
[Custom Docker servers](/development/model/custom-server) like vLLM and SGLang work directly with BDN. BDN pre-mounts files at `mount_location` before the container starts, so the `start_command` reads weights without a separate download step.
```yaml config.yaml theme={"system"}
base_image:
image: lmsysorg/sglang:v0.5.8.post1
docker_server:
start_command: python3 -m sglang.launch_server --model-path /models/qwen
--served-model-name Qwen/Qwen2.5-3B-Instruct --host 0.0.0.0 --port 8000
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
weights:
- source: "hf://Qwen/Qwen2.5-3B-Instruct@aa8e72537993ba99e69dfaafa59ed015b17504d1"
mount_location: "/models/qwen"
```
For complete worked examples, see [Deploy LLMs with SGLang](/examples/sglang) or [Deploy LLMs with vLLM](/examples/vllm).
## Configuration reference
### `weights`
A list of weight sources to mount into your model container.
```yaml config.yaml theme={"system"}
weights:
- source: "hf://Qwen/Qwen3-8B@main"
mount_location: "/models/qwen"
auth:
auth_method: CUSTOM_SECRET
auth_secret_name: "hf_access_token"
allow_patterns: ["*.safetensors", "*.json", "tokenizer.*"]
ignore_patterns: ["*.md", "*.txt"]
```
URI specifying where to fetch weights from. Supported schemes:
* `hf://`: Hugging Face Hub.
* `bt://`: Baseten Training.
* `s3://`: AWS S3.
* `gs://`: Google Cloud Storage.
* `r2://`: Cloudflare R2.
* `cw://`: CoreWeave AI Object Storage.
* `azure://`: Azure Blob Storage.
For Hugging Face sources, specify a revision using `@revision` suffix (branch, tag, or commit SHA).
Absolute path where weights will be mounted in your container. **Must start with `/`**.
```yaml theme={"system"}
mount_location: "/models/qwen" # Correct
mount_location: "models/qwen" # Wrong - not absolute
```
Authentication configuration for accessing private weight sources. See [Source types and authentication](#source-types-and-authentication) for the expected format for each source type.
* `auth_method`: The authentication method. Use `CUSTOM_SECRET` for secret-based auth, `AWS_OIDC` for AWS OIDC, or `GCP_OIDC` for GCP OIDC.
* `auth_secret_name`: Name of a [Baseten secret](/development/model/secrets) holding the credentials. Required when `auth_method` is `CUSTOM_SECRET`.
File patterns to include. Uses Unix shell-style wildcards. Only matching files will be downloaded.
```yaml theme={"system"}
allow_patterns:
- "*.safetensors"
- "config.json"
- "tokenizer.*"
```
Patterns like `*.safetensors` only match files at the top level. Use `**/*.safetensors` to match files in subdirectories.
File patterns to exclude. Uses Unix shell-style wildcards. Matching files will be skipped.
```yaml theme={"system"}
ignore_patterns:
- "*.md"
- "*.txt"
- "*.bin" # Skip PyTorch .bin files if using safetensors
```
## Source types and authentication
For private weight sources, create a [Baseten secret](/development/model/secrets) with the appropriate credentials.
Manage secrets in your [Baseten settings](https://app.baseten.co/settings/secrets).
### Hugging Face
Download weights from Hugging Face Hub repositories.
```yaml config.yaml theme={"system"}
weights:
- source: "hf://Qwen/Qwen3-8B@main"
mount_location: "/models/qwen"
auth:
auth_method: CUSTOM_SECRET
auth_secret_name: "hf_access_token" # Required for private/gated repos
allow_patterns: ["*.safetensors", "config.json"]
```
**Format:** `hf://owner/repo@revision`
* `owner/repo`: The Hugging Face repository.
* `@revision`: Branch, tag, or commit SHA.
**Revision pinning:** When you use a branch name like `@main`, Baseten resolves it to the specific commit SHA at deploy time and mirrors those exact files. Your deployment stays pinned to that version. Subsequent scale-ups won't pick up new commits. To update to newer weights, push a new deployment.
**Authentication:** Hugging Face API token (plain text)
| Secret Name | Secret Value |
| ----------------- | ------------------------ |
| `hf_access_token` | `hf_xxxxxxxxxxxxxxxx...` |
Get your token from [Hugging Face settings](https://huggingface.co/settings/tokens).
### Baseten Training
Load weights from a [Baseten Training](/training/overview) checkpoint.
```yaml config.yaml theme={"system"}
weights:
- source: "bt://my-training-project@job123/checkpoint-1"
mount_location: "/models/trained"
```
**Format:** `bt://project[@revision][/checkpoint]`
* `project`: The name of your Baseten Training project.
* `@revision`: Optional. A training job ID or `latest`. Defaults to `latest`.
* `/checkpoint`: Optional. The checkpoint name within the training job. If omitted, uses the latest checkpoint.
Baseten automatically authenticates with your training project.
### AWS S3
Download weights from a private S3 bucket.
If your model is small (a few GB or less), you can also [bundle weights directly with your Truss](/development/model/model-class#bundled-data) instead of fetching them from a remote source.
#### Pick an auth method
AWS S3 supports two authentication paths, both first-class:
* **IAM credentials**: Use this if you have an AWS access key pair and want the simplest setup. Skip ahead to the [quick start](#quick-start-with-iam-credentials).
* **AWS OIDC**: Use this if you want short-lived, narrowly scoped tokens and are comfortable configuring an IAM trust policy in your AWS account. See [AWS OIDC](#aws-oidc).
#### Quick start with IAM credentials
Use this path when you already have an AWS access key pair for an IAM user or role with read access to your bucket.
**To authenticate to S3 with IAM credentials**:
1. Create the secret in Baseten: in your [secrets settings](https://app.baseten.co/settings/secrets), add a secret named `aws_credentials` with this JSON value:
```json theme={"system"}
{
"aws_access_key_id": "AKIA...",
"aws_secret_access_key": "...",
"aws_region": "us-west-2"
}
```
Use these exact key names. Common variations like `access_key_id` (without the `aws_` prefix) cause authentication failures.
2. Reference the secret from your `config.yaml`:
```yaml config.yaml theme={"system"}
weights:
- source: "s3://my-bucket/models/custom-weights"
mount_location: "/models/custom"
auth:
auth_method: CUSTOM_SECRET
auth_secret_name: "aws_credentials"
```
3. Grant the IAM user the minimum required permissions on the bucket:
```json theme={"system"}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::my-bucket"
},
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::my-bucket/models/custom-weights/*"
}
]
}
```
The mirror lists objects under your prefix and downloads each file once. No write permissions are needed.
4. Push the model. The first deploy mirrors weights to Baseten's blob storage; subsequent deploys reuse the mirror unless the source or filters change.
For the full IAM credentials field reference, including optional fields, see [IAM credentials](#iam-credentials).
#### AWS OIDC
OIDC provides short-lived, narrowly scoped tokens for secure authentication without managing long-lived credentials.
**To authenticate to S3 with AWS OIDC**:
1. [Configure AWS to trust the Baseten OIDC provider](/organization/oidc#aws-setup) and create an IAM role with S3 permissions.
2. Add the OIDC configuration to your `config.yaml`:
```yaml config.yaml theme={"system"}
weights:
- source: "s3://my-bucket/models/custom-weights"
mount_location: "/models/custom"
auth:
auth_method: AWS_OIDC
aws_oidc_role_arn: arn:aws:iam:::role/baseten-s3-access
aws_oidc_region: us-west-2
```
No secrets needed. The `aws_oidc_role_arn` and `aws_oidc_region` are not sensitive and can be committed to your repository.
See the [OIDC authentication guide](/organization/oidc) for detailed setup instructions and best practices.
#### IAM credentials
```yaml config.yaml theme={"system"}
weights:
- source: "s3://my-bucket/models/custom-weights"
mount_location: "/models/custom"
auth:
auth_method: CUSTOM_SECRET
auth_secret_name: "aws_credentials"
```
**Format:** `s3://bucket/path`
**Authentication:** JSON with AWS credentials
| Field | Required | Description |
| ----------------------- | -------- | -------------------------------------------------------------------------------------- |
| `aws_access_key_id` | Yes | Access key ID for the IAM user or role. |
| `aws_secret_access_key` | Yes | Secret access key paired with the access key ID. |
| `aws_region` | No | Region of the bucket. Defaults to `us-east-1`. |
| `aws_session_token` | No | Session token for temporary credentials, such as those issued by AWS STS or `aws sso`. |
Example secret value with all fields:
```json theme={"system"}
{
"aws_access_key_id": "AKIA...",
"aws_secret_access_key": "...",
"aws_region": "us-west-2",
"aws_session_token": "..."
}
```
The required fields must use the exact names `aws_access_key_id` and `aws_secret_access_key`. Using `access_key_id` or `secret_access_key` (without the `aws_` prefix) causes authentication failures.
For the minimum required IAM policy, see the [quick start](#quick-start-with-iam-credentials).
### Google Cloud Storage
Download weights from a GCS bucket.
GCP supports using either [service accounts](https://cloud.google.com/iam/docs/service-account-overview) or OIDC for GCS authentication.
#### GCP OIDC (recommended)
OIDC provides short-lived, narrowly scoped tokens for secure authentication without managing long-lived credentials.
**To authenticate to GCS with GCP OIDC**:
1. [Configure GCP Workload Identity](/organization/oidc#google-cloud-setup) to trust the Baseten OIDC provider and grant GCS permissions.
2. Add the OIDC configuration to your `config.yaml`:
```yaml config.yaml theme={"system"}
weights:
- source: "gs://my-bucket/models/weights"
mount_location: "/models/gcs-weights"
auth:
auth_method: GCP_OIDC
gcp_oidc_service_account: baseten-oidc@my-project.iam.gserviceaccount.com
gcp_oidc_workload_id_provider: projects/123456789/locations/global/workloadIdentityPools/baseten-pool/providers/baseten-provider
```
No secrets needed. The service account and workload identity provider are not sensitive and can be committed to your repository.
See the [OIDC authentication guide](/organization/oidc) for detailed setup instructions and best practices.
#### Service account
```yaml config.yaml theme={"system"}
weights:
- source: "gs://my-bucket/models/weights"
mount_location: "/models/gcs-weights"
auth:
auth_method: CUSTOM_SECRET
auth_secret_name: "gcp_service_account"
```
**Format:** `gs://bucket/path`
**Authentication:** GCP service account JSON key
| Secret Name | Secret Value |
| --------------------- | ------------------------------------------------------- |
| `gcp_service_account` | `{"type": "service_account", "project_id": "...", ...}` |
Download from GCP Console under IAM & Admin > Service Accounts.
### Cloudflare R2
Download weights from a Cloudflare R2 bucket.
```yaml config.yaml theme={"system"}
weights:
- source: "r2://abc123def.my-bucket/models/weights"
mount_location: "/models/r2-weights"
auth:
auth_method: CUSTOM_SECRET
auth_secret_name: "r2_credentials"
```
**Format:** `r2://account_id.bucket/path`
* `account_id`: Your Cloudflare account ID.
* `bucket`: R2 bucket name, separated from account\_id by a period.
* `path`: Path prefix within the bucket.
**Authentication:** JSON with R2 API credentials
| Secret Name | Secret Value |
| ---------------- | -------------------------------------------------------------- |
| `r2_credentials` | `{"aws_access_key_id": "...", "aws_secret_access_key": "..."}` |
Get your R2 API tokens from the Cloudflare dashboard under R2 > Manage R2 API Tokens.
### CoreWeave AI Object Storage
Download weights from CoreWeave AI Object Storage (CAIOS), an S3-compatible object store.
```yaml config.yaml theme={"system"}
weights:
- source: "cw://my-bucket/models/weights"
mount_location: "/models/cw-weights"
auth:
auth_method: CUSTOM_SECRET
auth_secret_name: "cw_credentials"
```
**Format:** `cw://bucket/path`
* `bucket`: Your CoreWeave AI Object Storage bucket name.
* `path`: Path prefix within the bucket.
Unlike R2, the CoreWeave URI has no account ID.
**Authentication:** JSON with S3-style credentials
| Secret Name | Secret Value |
| ---------------- | ----------------------------------------------------------------------------------- |
| `cw_credentials` | `{"aws_access_key_id": "...", "aws_secret_access_key": "...", "aws_region": "..."}` |
`aws_region` is required. CoreWeave uses availability-zone-style regions such as `US-EAST-04A`. OIDC is not supported for CoreWeave sources; use a secret.
### Azure Blob Storage
Download weights from Azure Blob Storage.
```yaml config.yaml theme={"system"}
weights:
- source: "azure://myaccount/container/weights"
mount_location: "/models/azure-weights"
auth:
auth_method: CUSTOM_SECRET
auth_secret_name: "azure_credentials"
```
**Format:** `azure://account/container/path`
* `account`: Your Azure storage account name.
* `container`: Blob container name within the storage account.
* `path`: Path prefix within the container. Optional.
**Authentication:** JSON with account key
| Secret Name | Secret Value |
| ------------------- | ------------------------ |
| `azure_credentials` | `{"account_key": "..."}` |
The account name comes from the URI, so the secret needs only `account_key`. Azure sources don't support OIDC, and the secret is required even for public containers.
Get your [account key](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage) from the Azure portal under Security + networking > Access keys.
## Best practices
### Pin to specific commits
Avoid using branch names like `@main` in production. While Baseten pins to the commit SHA at deploy time, using `@main` means each new deployment may get different weights, making debugging and rollbacks difficult.
Always pin to a specific commit SHA for reproducible deployments:
```yaml config.yaml theme={"system"}
# Recommended - reproducible across deploys
weights:
- source: "hf://Qwen/Qwen3-8B@"
mount_location: "/models/qwen"
# Not recommended for production - each new deployment resolves to a different commit
weights:
- source: "hf://Qwen/Qwen3-8B@main"
mount_location: "/models/qwen"
```
To find the current commit SHA for a Hugging Face repo:
```bash Terminal theme={"system"}
# Using the Hugging Face CLI
huggingface-cli repo-info Qwen/Qwen3-8B --revision main
```
### Filter files with patterns
Only download what you need to minimize cold start time:
```yaml config.yaml theme={"system"}
weights:
- source: "hf://Qwen/Qwen3-8B@main"
mount_location: "/models/qwen"
allow_patterns:
- "*.safetensors" # Model weights
- "config.json" # Model config
- "tokenizer.*" # Tokenizer files
ignore_patterns:
- "*.bin" # Skip PyTorch format if using safetensors
- "*.md" # Skip documentation
- "*.txt" # Skip text files
```
Patterns like `*.safetensors` only match files at the top level of the source. To match files in subdirectories, use `**/*.safetensors`.
### Use absolute mount paths
The `mount_location` must be an absolute path (starting with `/`):
```yaml config.yaml theme={"system"}
# Correct
mount_location: "/models/qwen"
mount_location: "/app/model_cache/my-model"
# Wrong - will fail validation
mount_location: "models/qwen"
mount_location: "./my-model"
```
### Keep mount locations unique
Each weight source must have a unique `mount_location`:
```yaml config.yaml theme={"system"}
# Correct - different paths
weights:
- source: "hf://Qwen/Qwen3-8B@main"
mount_location: "/models/qwen"
- source: "hf://sentence-transformers/all-MiniLM-L6-v2@main"
mount_location: "/models/embeddings"
# Wrong - duplicate paths will fail
weights:
- source: "hf://model-a@main"
mount_location: "/models/shared"
- source: "hf://model-b@main"
mount_location: "/models/shared"
```
### When weights are re-mirrored
Baseten caches weights based on a hash of their configuration and reuses cached weights when possible to avoid redundant downloads.
**Deduplication and mutation detection:**
Baseten deduplicates files based on their etag (a content hash), not just filename, and only re-mirrors files that have been mutated since the last pull. Unchanged files are reused from blob storage, even across deployments.
#### Weight access
A deployment reads only the weight sources it declares in its `weights` config. Caching and deduplication happen behind the scenes and never grant another deployment or organization access to your data. Private sources like S3, GCS, R2, CoreWeave, and Azure stay within your organization. Public sources like Hugging Face are already public, so Baseten can serve them from a shared cache across organizations.
**Changes that trigger re-mirroring:**
| Field | Re-mirrors? | Why |
| ----------------- | ----------- | ---------------------------------------------------------------------- |
| `source` | ✅ Yes | Different repository, revision, or path |
| `allow_patterns` | ✅ Yes | Different files will be downloaded |
| `ignore_patterns` | ✅ Yes | Different files will be downloaded |
| `auth` | ✅ Yes | Changing the auth secret name or method changes the configuration hash |
**Changes that do NOT trigger re-mirroring:**
| Field | Re-mirrors? | Why |
| ---------------- | ----------- | --------------------------------------------------- |
| `mount_location` | ❌ No | Only affects where weights appear in your container |
To force a fresh download of weights that haven't changed, modify the `source` to point to a specific commit SHA instead of a branch name, or add a trivial change to `allow_patterns`.
## How it works
You own the source, and Baseten holds a mirror of it. On `truss push`, BDN reads your `weights` config, mirrors the files into Baseten's secure blob storage, and writes a manifest of content hashes. Files are keyed by hash, so a file BDN already holds is never transferred again, and each deployment mounts only the files in its own manifest.
Your `truss push` returns immediately. Mirroring runs in the background, and your model deploys to the workload plane only after mirroring completes, so weights are in place before your replica starts.
### What happens on cold start
Baseten runs workload planes across regions and clusters, each with its own cache tiers. When a replica starts, weights flow from blob storage through the in-cluster cache and the node cache, then are mounted read-only. Each tier serves the one below it, so later replicas read from a warm cache instead of downloading again.
### Key benefits
* **Non-blocking push** → `truss push` returns immediately; mirroring happens in the background.
* **One-time mirroring** → Weights are mirrored to Baseten storage once, not on every cold start.
* **No upstream dependency at runtime** → Once mirrored, scale-ups and inference never contact the original source.
* **Multi-tier caching** → In-cluster cache prevents redundant downloads; node cache provides instant access for subsequent replicas.
* **Deduplication** → Identical weight files are stored once and shared through hardlinks.
* **Parallel downloads** → Large models download faster with concurrent chunk fetching.
## BDN proxy
BDN proxy is available by request. [Contact us](mailto:support@baseten.co) to enable it for your organization.
If your model downloads weights in application code rather than using the `weights` config, BDN proxy can accelerate those downloads. When enabled, Baseten routes your model container's outbound HTTP(S) requests through a distributed caching proxy that caches downloads across cluster nodes. Subsequent replicas and scale-ups serve from cache instead of re-downloading from the origin.
BDN proxy is transparent. You don't need to change your model code. Baseten sets the following environment variables on your container:
| Environment variable | Purpose |
| -------------------- | ------------------------------------------------------ |
| `BDN_PROXY` | Proxy address. |
| `REQUESTS_CA_BUNDLE` | CA bundle for Python `requests` and other TLS clients. |
| `SSL_CERT_FILE` | CA bundle for general SSL/TLS clients. |
| `PIP_CERT` | CA bundle for pip. |
BDN proxy does not set `HTTP_PROXY` or `HTTPS_PROXY`. If your model code requires an explicit proxy, use the `BDN_PROXY` environment variable.
## Troubleshooting
| Error | Cause | Fix |
| ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `aws_access_key_id and aws_secret_access_key are required in S3 credentials` | Secret JSON uses incorrect key names like `access_key_id` instead of `aws_access_key_id`. | Use the exact key names `aws_access_key_id`, `aws_secret_access_key`, and `aws_region` in your secret JSON. |
| `secret_id is required` | Your `weights:` source is `s3://`, `r2://`, or `cw://` but the config has no `auth:` block, so the mirror can't resolve credentials. Less commonly, the named secret was deleted or hasn't propagated yet. | Add an `auth:` block to the source, like `auth: { auth_method: CUSTOM_SECRET, auth_secret_name: }`. See [AWS S3](#aws-s3), [Cloudflare R2](#cloudflare-r2), or [CoreWeave AI Object Storage](#coreweave-ai-object-storage) for the per-source format. If the `auth:` block is already present, recreate the secret with a new name and redeploy. |
| `no credentials configured: need either OIDC config or secret_id` | Your `weights:` source is `gs://` but the config has no `auth:` block. | Add an `auth:` block with either `auth_method: GCP_OIDC` and the OIDC fields, or `auth_method: CUSTOM_SECRET` and an `auth_secret_name`. See [Google Cloud Storage](#google-cloud-storage). |
| `no credentials configured: Azure sources require a secret_id (OIDC is not supported for Azure)` | Your `weights:` source is `azure://` but the config has no `auth:` block. | Add an `auth:` block with `auth_method: CUSTOM_SECRET` and an `auth_secret_name` referencing a secret like `{"account_key": "..."}`. See [Azure Blob Storage](#azure-blob-storage). |
| Weights download silently skips files in subdirectories | `allow_patterns` uses a flat glob like `*.safetensors` that only matches at the top level. | Use `**/*.safetensors` for recursive matching across subdirectories. |
| Weights download completes but model fails to load | Required files like `config.json` or tokenizer files are excluded by patterns. | Add `config.json` and `tokenizer.*` to `allow_patterns`. |
## Migration from `model_cache`
`model_cache` is deprecated. Migrate to `weights` for faster cold starts through multi-tier caching.
### Automated migration with `truss migrate`
The `truss migrate` CLI command automatically converts `model_cache` configurations:
```bash Terminal theme={"system"}
# Run in your Truss directory
truss migrate
# Or specify a directory
truss migrate /path/to/truss
```
The command will:
1. Show a colorized diff of the proposed changes.
2. Prompt for confirmation before applying.
3. Create a backup of your original `config.yaml`.
4. Warn about any `model.py` path changes needed.
### Manual migration reference
**From `model_cache` to `weights`:**
| `model_cache` | `weights` |
| ----------------------- | ----------------------------------------- |
| `repo_id: "owner/repo"` | `source: "hf://owner/repo@rev"` |
| `revision: "main"` | Included in source URI as `@main` |
| `kind: "s3"` | Prefix: `s3://bucket/path` |
| `kind: "gcs"` | Prefix: `gs://bucket/path` |
| `kind: "azure"` | Prefix: `azure://account/container/path` |
| `volume_folder: "name"` | `mount_location: "/app/model_cache/name"` |
| `runtime_secret_name` | `auth_secret_name` |
| `allow_patterns` | `allow_patterns` (same) |
| `ignore_patterns` | `ignore_patterns` (same) |
**Example migration:**
```yaml config.yaml theme={"system"}
weights:
- source: "hf://Qwen/Qwen3-8B@main"
mount_location: "/app/model_cache/qwen"
allow_patterns:
- "*.safetensors"
- "config.json"
auth:
auth_method: CUSTOM_SECRET
auth_secret_name: hf_access_token
```
```yaml config.yaml theme={"system"}
model_cache:
- repo_id: Qwen/Qwen3-8B
revision: main
use_volume: true
volume_folder: qwen
allow_patterns:
- "*.safetensors"
- "config.json"
runtime_secret_name: hf_access_token
```
### Chains migration
For Truss Chains, update `Assets.cached` to `Assets.weights` in your Python code:
```python theme={"system"}
import truss_chains as chains
from truss.base import truss_config
class MyChainlet(chains.ChainletBase):
remote_config = chains.RemoteConfig(
assets=chains.Assets(
weights=[
truss_config.WeightsSource(
source="hf://Qwen/Qwen3-8B@main",
mount_location="/app/model_cache/qwen",
auth_secret_name="hf_access_token",
allow_patterns=["*.safetensors", "config.json"],
)
],
secret_keys=["hf_access_token"],
),
)
```
```python theme={"system"}
import truss_chains as chains
from truss.base import truss_config
class MyChainlet(chains.ChainletBase):
remote_config = chains.RemoteConfig(
assets=chains.Assets(
cached=[
truss_config.ModelRepo(
repo_id="Qwen/Qwen3-8B",
revision="main",
use_volume=True,
volume_folder="qwen",
allow_patterns=["*.safetensors", "config.json"],
runtime_secret_name="hf_access_token",
)
],
secret_keys=["hf_access_token"],
),
)
```
**Key changes:**
* `ModelRepo` → `WeightsSource`.
* `repo_id` + `revision` → `source` URI with `@revision` suffix.
* `volume_folder` → `mount_location` (must be absolute path).
* `runtime_secret_name` → `auth.auth_secret_name` (inside an `auth` block with `auth_method: CUSTOM_SECRET`).
* Remove `use_volume` and `kind` (inferred from URI scheme).
### Custom server migration
When migrating an existing custom server deployment from `model_cache` to `weights`:
1. **Remove `truss-transfer-cli`** from your `start_command`. Files are pre-mounted before the container starts.
2. **Update file paths** from `/app/model_cache/{volume_folder}` to your new `mount_location`.
```yaml config.yaml theme={"system"}
docker_server:
# No truss-transfer-cli needed - weights are pre-mounted
start_command: text-embeddings-router --port 7997
--model-id /models/jina --max-client-batch-size 128
weights:
- source: "hf://jinaai/jina-embeddings-v2-base-code@516f4baf..."
mount_location: "/models/jina"
ignore_patterns: ["*.onnx"]
```
```yaml config.yaml theme={"system"}
docker_server:
# Required truss-transfer-cli to download weights
start_command: bash -c "truss-transfer-cli && text-embeddings-router --port 7997
--model-id /app/model_cache/my_jina --max-client-batch-size 128"
model_cache:
- repo_id: jinaai/jina-embeddings-v2-base-code
revision: 516f4baf13dec4ddddda8631e019b5737c8bc250
use_volume: true
volume_folder: my_jina
ignore_patterns: ["*.onnx"]
```
The [Custom servers](#custom-servers) section shows the pattern for new deployments.
## Automatic use with engine builders
Engine-builder deployments use BDN automatically. No `weights` block is required, and no configuration changes are needed when migrating an existing engine-builder deployment.
| Engine | When BDN is used |
| ------------------------------------------------------------------- | ---------------- |
| [BEI](/engines/bei/overview) | Every deploy. |
| [Briton (Engine-Builder-LLM)](/engines/engine-builder-llm/overview) | Every deploy. |
| [BIS-LLM (V2)](/engines/bis-llm/overview) | Every deploy. |
Build artifacts are mirrored once and served from the same multi-tier caches described in [How it works](#how-it-works).
## Next steps
* [Secrets](/development/model/secrets): Store credentials for private weight sources.
* [Custom Docker images](/development/model/custom-server): Deploy vLLM, SGLang, and other inference servers.
* [Autoscaling](/deployment/autoscaling): Configure replica scaling and cold start behavior.
* [Configuration reference](/reference/truss-configuration#weights): Full list of `weights` options.
# Build your model
Source: https://docs.baseten.co/development/model/build-your-first-model
Deploy a model to Baseten with just a config file. Pick an open-source model from Hugging Face, choose a GPU, and get an endpoint in minutes.
Baseten deploys models from a single `config.yaml` file. You point to a model on Hugging Face, choose a GPU, and Baseten builds a TensorRT-optimized container with an OpenAI-compatible API. No Python code, no Dockerfile, no container management.
This tutorial deploys [Qwen 2.5 3B Instruct](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct) to a production-ready endpoint on an L4 GPU.
## Install and sign in
Before you begin, [sign up](https://app.baseten.co/signup) or [sign in](https://app.baseten.co/login) to Baseten, then install [uv](https://docs.astral.sh/uv/), a fast Python package manager.
Install the Truss CLI and connect it to your Baseten account. Browser login opens a tab to approve this device, so there's no API key to copy and paste.
**Install Truss**
```bash Terminal theme={"system"}
uv tool install truss
```
**Sign in**
```bash Terminal theme={"system"}
truss login --browser
```
Prefer not to install? Run `uvx truss login --browser` to use the same flow without a permanent install, and use `uvx truss …` for the rest of this guide.
## Create the config
Create a project directory with a `config.yaml`:
```bash Terminal theme={"system"}
mkdir qwen-2.5-3b && cd qwen-2.5-3b
```
Create a `config.yaml` file with the following contents:
```yaml config.yaml theme={"system"}
model_name: Qwen-2.5-3B
resources:
accelerator: L4
model_metadata:
tags:
- openai-compatible
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "Qwen/Qwen2.5-3B-Instruct"
max_seq_len: 8192
quantization_type: fp8
tensor_parallel_count: 1
num_builder_gpus: 1
```
What each field does:
* `resources.accelerator: L4` runs inference on a single L4 (24 GB VRAM).
* `trt_llm` switches on [Engine-Builder-LLM](/engines/engine-builder-llm/overview), which compiles the model with TensorRT-LLM.
* `checkpoint_repository` points to weights on Hugging Face. Qwen 2.5 3B Instruct is ungated, so no token is needed.
* `quantization_type: fp8` halves weight memory by quantizing to 8-bit floats.
* `num_builder_gpus: 1` sets the GPU count for the engine-build job. Without it, the CLI warns that FP8 builds can OOM at build time.
## Deploy
Push to Baseten:
```bash Terminal theme={"system"}
truss push
```
You should see:
```output theme={"system"}
✨ Model Qwen-2.5-3B was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
Baseten now downloads the model weights, compiles them with TensorRT-LLM, and deploys the resulting container to an L4 GPU. You can watch progress in the logs linked above. When the deployment status shows "Active" in the dashboard, it's ready for requests.
## Call your model
Engine-based deployments serve an OpenAI-compatible API, so any code that works with the OpenAI SDK works with your model. Replace `{model_id}` with your model ID from the deployment output.
The endpoint follows this shape:
Install the OpenAI SDK if you don't have it:
```bash Terminal theme={"system"}
uv pip install openai
```
Create a chat completion:
```python call_model.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="Qwen-2.5-3B",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```bash Request theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "Qwen-2.5-3B",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
You should see a response like:
```output theme={"system"}
Machine learning is a branch of artificial intelligence where systems learn
patterns from data to make predictions or decisions without being explicitly
programmed for each task...
```
## What just happened
From one config file, Baseten:
1. Downloaded the Qwen 2.5 3B Instruct weights from Hugging Face.
2. Compiled them with TensorRT-LLM and FP8 quantization.
3. Packaged the engine into a container on an L4 GPU.
4. Exposed an OpenAI-compatible API at the model's URL.
No `model.py`, no Dockerfile, no inference server configuration. The same pattern works for most popular open-source LLMs, including Llama, Qwen, Mistral, Gemma, and Phi.
## Next steps
Tune max sequence length, batch size, quantization, and runtime settings for your deployment.
Add custom Python when you need preprocessing, postprocessing, or unsupported model architectures.
Configure replicas, concurrency targets, and scale-to-zero for production traffic.
Move from development to production with `truss push --promote`.
# Configuration
Source: https://docs.baseten.co/development/model/configuration
Configure model dependencies, resources, and build environment in config.yaml
ML models depend on external libraries, data files, and specific hardware. The `config.yaml` file defines all of this for your model. This guide covers the most common options.
## Environment variables
To set environment variables in the model serving environment, use the `environment_variables` key:
```yaml config.yaml theme={"system"}
environment_variables:
MY_ENV_VAR: my_value
```
## Python packages
Specify Python packages in `config.yaml` using either `requirements` (an inline list) or `requirements_file` (a path to a file). These two options are mutually exclusive.
## Inline list
List packages directly in `config.yaml`:
```yaml config.yaml theme={"system"}
requirements:
- package_name
- package_name2
```
Pin package versions with `==`:
```yaml config.yaml theme={"system"}
requirements:
- package_name==1.0.0
- package_name2==2.0.0
```
## Requirements file
Point `requirements_file` at a dependency file. Truss supports three formats:
Use a standard pip requirements file for full control over pip options and repositories.
```yaml config.yaml theme={"system"}
requirements_file: ./requirements.txt
```
Use a `pyproject.toml` to install dependencies from the `[project.dependencies]` table.
```yaml config.yaml theme={"system"}
requirements_file: ./pyproject.toml
```
Truss reads only the `[project.dependencies]` list. Optional dependency groups are ignored.
Use a `uv.lock` file for fully pinned, reproducible installs managed by [uv](https://docs.astral.sh/uv/).
```yaml config.yaml theme={"system"}
requirements_file: ./uv.lock
```
The `uv.lock` file must have a sibling `pyproject.toml` in the same directory. Truss copies both files into the build context.
### Dependency constraints
Truss uses a `constraints.txt` file to enforce version bounds on base server dependencies. If you specify a package that overlaps with base dependencies (for example, `numpy` or `fastapi`), your version is respected but must fall within the bounds defined in `constraints.txt`. If you specify a version outside these bounds, the build will fail with an unsatisfiable error. This applies to both `requirements` (inline list) and `requirements_file`.
### Chains
Chains supports the same three formats through `DockerImage.requirements_file`. Use [`make_abs_path_here`](/reference/sdk/chains#function-truss_chains-make_abs_path_here) to resolve the path relative to the source file:
```python chainlet.py theme={"system"}
import truss_chains as chains
class MyChainlet(chains.ChainletBase):
remote_config = chains.RemoteConfig(
docker_image=chains.DockerImage(
requirements_file=chains.make_abs_path_here("requirements.txt"),
),
)
```
`pyproject.toml` and `uv.lock` work the same way:
```python chainlet.py theme={"system"}
docker_image=chains.DockerImage(
requirements_file=chains.make_abs_path_here("pyproject.toml"),
)
```
```python chainlet.py theme={"system"}
docker_image=chains.DockerImage(
requirements_file=chains.make_abs_path_here("uv.lock"),
)
```
`pip_requirements_file` is deprecated. Use `requirements_file` instead. You can't combine `pip_requirements` with `pyproject.toml` or `uv.lock` files; manage all dependencies in your `pyproject.toml`.
## System packages
Truss supports installing apt-installable Debian packages. To add system packages to your model serving environment, add them to your `config.yaml` file:
```yaml config.yaml theme={"system"}
system_packages:
- package_name
- package_name2
```
For example, to install Tesseract OCR:
```yaml config.yaml theme={"system"}
system_packages:
- tesseract-ocr
```
## Resources
Specify hardware resources in the `resources` section.
### Individual resource fields
For a CPU model:
```yaml config.yaml theme={"system"}
resources:
cpu: "1"
memory: 2Gi
```
For a GPU model:
```yaml config.yaml theme={"system"}
resources:
accelerator: "L4"
```
When you push your model, it's assigned an instance type matching the required specifications.
### Exact instance type
```yaml config.yaml theme={"system"}
resources:
instance_type: "L4:4x16"
```
Using `instance_type` lets you select an exact SKU. When specified, other resource fields are ignored.
See the [Resources](/deployment/resources) page for more information on
options available.
## Advanced configuration
Your model has many other configuration options. See the related guides:
* [Secrets](/development/model/secrets)
* [Data](/development/model/model-class#bundled-data)
* [Custom build commands](/development/model/dependencies#build-commands)
* [Base Docker images](/development/model/dependencies#base-images)
* [Custom servers](/development/model/custom-server)
* [Custom health checks](/development/model/health-checks)
# Custom Docker containers
Source: https://docs.baseten.co/development/model/custom-server
Deploy custom Docker containers to run inference servers like vLLM, SGLang, Triton, or any containerized application.
By default, Truss wraps your `Model` class with the [Truss server base image](https://hub.docker.com/r/baseten/truss-server-base/tags). To deploy a pre-built container instead (vLLM, SGLang, Triton, NIM, or your own), point Truss at the image and tell it how to run.
## How the build works
When you deploy a standard custom server, Baseten builds a new image from your `base_image`: it layers in the reverse proxy and process supervisor, validates that the base image is [Debian-based with Python on `PATH`](#base-image-requirements), and pushes the result to its container registry. Your server runs behind that proxy, which is why [port 8080 is reserved](#runtime-environment) and [containers run as a non-root user](#non-root-user). To run your image unmodified instead, use [no-build](#no-build-deployment).
## Configure a custom container
Set [`base_image`](/reference/truss-configuration#base-image-image) to your image and use `docker_server` to specify how to start it:
```yaml config.yaml theme={"system"}
base_image:
image: your-registry/your-image:latest
docker_server:
start_command: your-server-start-command
server_port: 8000
predict_endpoint: /predict
readiness_endpoint: /health
liveness_endpoint: /health
```
* `image`: The Docker image to use.
* `start_command`: The command to start the server. This overrides the base image's default entrypoint.
* `server_port`: The port to listen on.
* `predict_endpoint`: The endpoint to forward requests to.
* `readiness_endpoint`: The endpoint to check if the server is ready.
* `liveness_endpoint`: The endpoint to check if the server is alive.
Port 8080 is reserved by Baseten's internal reverse proxy. If your server binds to port 8080, the deployment fails with `[Errno 98] address already in use`.
For the full list of fields, see the
[configuration reference](/reference/truss-configuration#docker_server).
### Non-root user
Containers run as a non-root user by default:
| Property | Value |
| -------------- | ----------- |
| Username | `app` |
| UID / GID | `60000` |
| Home directory | `/home/app` |
If your base image expects a specific non-root UID, set `run_as_user_id` under `docker_server`:
```yaml config.yaml theme={"system"}
base_image:
image: your-registry/your-image:latest
docker_server:
start_command: your-server-start-command
server_port: 8000
predict_endpoint: /predict
readiness_endpoint: /health
liveness_endpoint: /health
run_as_user_id: 1000
```
The UID must already exist in the base image. Values `0` (root) and `60000` (platform default) are not allowed.
Many NVIDIA base images, including NIM and Triton, run as user ID `1000`. Set `run_as_user_id: 1000` when using these images.
Baseten automatically sets ownership of `/app`, `/workspace`, the packages directory, and `$HOME` to this UID. If your server writes to directories outside of these, ensure they are writable by the specified UID in your base image or through `build_commands`.
While `predict_endpoint` maps your server's inference route to Baseten's
`/predict` endpoint, you can access any route exposed by your server using the
[sync endpoint](/inference/calling-your-model#sync-api-endpoints).
| Baseten endpoint | Maps to |
| ------------------------------------------- | ----------------------------- |
| `/environments/production/predict` | Your `predict_endpoint` route |
| `/environments/production/sync/{any/route}` | `/{any/route}` in your server |
**Example:** If you set `predict_endpoint: /v1/chat/completions`:
| Baseten endpoint | Maps to |
| ----------------------------------------- | ---------------------- |
| `/environments/production/predict` | `/v1/chat/completions` |
| `/environments/production/sync/v1/models` | `/v1/models` |
All other paths reach your server unchanged, including routes like `/metrics` and `/health`. If your server doesn't handle a requested path, the reverse proxy returns whatever response your server returns (often its own 404).
## Container filesystem
### Writable directories
Your server process can write to these paths:
| Path | Purpose |
| ------------ | ------------------------------------------------------------------- |
| `/app` | Application root, including your `config.yaml` and optional `data/` |
| `/home/app` | Home directory (`$HOME`) |
| `/tmp` | Temporary files |
| `/workspace` | General-purpose scratch space |
| `/packages` | Bundled [packages](/development/model/dependencies#python-packages) |
Paths outside this list are root-owned and not writable by your process. If you need to write elsewhere, change permissions during the build with `build_commands`, or set `run_as_user_id` so Baseten chowns the managed paths to your UID.
### Working directory
Truss does not set a `WORKDIR` for custom server builds. The effective working directory is whatever your base image defines (often `/`).
If your server expects a specific working directory, set it in your `start_command`:
```yaml config.yaml theme={"system"}
docker_server:
start_command: sh -c "cd /app && ./my-server"
```
### Secrets
Secrets declared in `config.yaml` are mounted as read-only files at `/secrets/{secret_name}`. See [Secrets in custom Docker images](/development/model/secrets#use-secrets-in-custom-docker-images) for usage.
## Runtime environment
Baseten sets specific environment variables in every custom-server container to route traffic to your server, identify the container in logs and traces, and keep its runtime path intact. These names are reserved. If you set any of them in `environment_variables`, Baseten drops the value before deploying the container:
* `PORT`, `HOST`, `HOSTNAME`
* `*_SERVICE_HOST`, `*_SERVICE_PORT*`
* `KUBERNETES_*`
* `K_SERVICE`, `K_REVISION`, `K_CONFIGURATION`
* `PATH`
Truss warns when it loads your config if you set `PORT` or `HOSTNAME`.
`PORT` is set to `8080` inside every container. Baseten's reverse proxy listens on that port, so every container inherits `PORT=8080` regardless of what your server binds to.
If your server code reads `os.environ.get("PORT", 8000)` (or similar), it gets `8080` instead of your default. Bind your server directly to `docker_server.server_port`, or read the port from an environment variable you control (for example, `MY_SERVER_PORT`).
### Platform-injected environment variables
Baseten sets these in every container at runtime:
| Variable | Value |
| ------------------------ | ------------------------------------------- |
| `APP_HOME` | `/app` |
| `HOME` | `/home/app` (or `/root` if running as root) |
| `PYTHON_EXECUTABLE` | Path to `python3` in the base image |
| `BT_MODEL_ID` | The model's ID |
| `BT_MODEL_DEPLOYMENT_ID` | The deployment's ID |
Read `BT_MODEL_ID` and `BT_MODEL_DEPLOYMENT_ID` from your server process to tag logs, metrics, or cache keys with deployment identity.
### Environment name
The [`environment` keyword argument](/deployment/environments#environment-access-in-code) is only available to Python Truss models. A custom server reads its environment from the filesystem instead. For deployments associated with an environment, `/etc/b10_dynamic_config/environment` contains a JSON object with the environment name:
```json /etc/b10_dynamic_config/environment theme={"system"}
{ "name": "production" }
```
Read it at runtime, handling the case where the file is absent or empty (a deployment not associated with an environment):
```python theme={"system"}
import json
from pathlib import Path
def get_environment_name():
p = Path("/etc/b10_dynamic_config/environment")
if p.exists():
contents = p.read_text()
if contents:
return json.loads(contents).get("name")
return None
```
Use the environment name to configure per-environment behavior, such as enabling monitoring or selecting which weights to load:
```python theme={"system"}
environment = get_environment_name()
if environment == "production":
setup_sentry()
model = load_production_weights()
else:
model = load_default_weights()
```
### Base image environment variables
Environment variables baked into your base image (`ENV UV_EXTRA_INDEX_URL=...`, `ENV PIP_CONSTRAINT=...`, and so on) are visible to your server process at runtime. If your `start_command` or anything it invokes runs `uv` or `pip`, these inherited settings take effect. They don't affect how Truss builds the container's internal Python environment that runs the reverse proxy and process supervisor.
If you want a clean install environment inside `start_command`, unset the inherited variables before invoking `uv` or `pip`.
## Base image requirements
Standard (non-`no_build`) custom-server builds require:
* A **Debian-based** base image (`ID=debian` or `ID_LIKE=debian` in `/etc/os-release`).
* **Python 3.x** on `PATH`. The minor version is validated at build time.
[No-build](#no-build-deployment) mode has no base image restrictions: your image is used as-is.
## Per-request logging
Baseten assigns a unique request ID to every predict call and returns it in the `X-Baseten-Request-Id` response header. You can use this ID to [filter your model's logs](/observability/logs) down to a single request.
For standard Truss models, request ID logging is automatic. For custom HTTP servers, you'll need to extract the request ID from the incoming request header and include it in your JSON log output.
Extract the request ID from the `X-Baseten-Request-Id` header:
```python server.py theme={"system"}
import json
import logging
import sys
from fastapi import FastAPI, Request
class JSONFormatter(logging.Formatter):
"""Formats logs as JSON with request_id for Baseten log filtering."""
def format(self, record):
log_record = {
"level": record.levelname,
"message": record.getMessage(),
}
if getattr(record, "request_id", None):
log_record["request_id"] = record.request_id
return json.dumps(log_record)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JSONFormatter())
logger = logging.getLogger(__name__)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
app = FastAPI()
@app.post("/predict")
async def predict(request: Request):
request_id = request.headers.get("x-baseten-request-id")
logger.info("Predict called", extra={"request_id": request_id})
# ... your inference logic ...
logger.info("Predict complete", extra={"request_id": request_id})
return {"result": "..."}
```
```python server.py theme={"system"}
import json
import logging
import sys
from flask import Flask, request
class JSONFormatter(logging.Formatter):
"""Formats logs as JSON with request_id for Baseten log filtering."""
def format(self, record):
log_record = {
"level": record.levelname,
"message": record.getMessage(),
}
if getattr(record, "request_id", None):
log_record["request_id"] = record.request_id
return json.dumps(log_record)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JSONFormatter())
logger = logging.getLogger(__name__)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
app = Flask(__name__)
@app.route("/predict", methods=["POST"])
def predict():
request_id = request.headers.get("x-baseten-request-id")
logger.info("Predict called", extra={"request_id": request_id})
# ... your inference logic ...
logger.info("Predict complete", extra={"request_id": request_id})
return {"result": "..."}
```
Logs must be JSON formatted and written to stdout. The `request_id` field must be a top-level key in the JSON object.
## No-build deployment
For security-hardened images that must remain completely unmodified, use [`no_build`](/reference/truss-configuration#no_build) to skip the build step entirely. Baseten copies the image to its container registry without running `docker build`.
No-build is only available for custom server deployments. Your Truss must use `docker_server` configuration. Standard Truss models with a `model.py` don't support `no_build`.
Point `base_image` at your hardened image and configure `docker_server` in `config.yaml`:
```yaml config.yaml theme={"system"}
base_image:
image: your-registry/your-hardened-image:latest
docker_server:
no_build: true
server_port: 8000
predict_endpoint: /predict
readiness_endpoint: /health
liveness_endpoint: /health
```
Set `no_build: true` and configure your server's port and endpoints. Since the image runs unmodified, it must include its own HTTP server and health check endpoints.
`start_command` is optional with `no_build`. If omitted, the image's original `ENTRYPOINT` runs. If your image needs a different startup command, set `start_command` to override the entrypoint.
### Runtime contract differences
No-build containers bypass Baseten's reverse proxy and process supervisor, which changes a few things relative to a standard build:
* **Port `8080` is not reserved.** Your server can bind to any port, including `8080`.
* **Your server is directly exposed** on `docker_server.server_port`.
* **Path routing is 1:1.** See [Routing](#routing) below.
* **The `data/` directory is still copied** to `/app/data` if present in your Truss.
### Routing
No-build deployments skip the URL remapping that standard custom server deployments use. All paths exposed by your server are accessible directly through Baseten's routing. For example, if your server exposes `/v2/listen/stream`, you can reach it at:
```txt theme={"system"}
https://model-.api.baseten.co/environments/production/sync/v2/listen/stream
```
`predict_endpoint` has no effect on no-build deployments because Baseten does not remap paths. However, it's still a required field, so setting it correctly serves as useful documentation of your server's primary inference route.
### Constraints
* Requires a custom server deployment with `docker_server` configuration. Standard Truss models with a `model.py` don't support `no_build`.
* Development mode is not supported. Deploy with `truss push` (published deployments are the default).
* Truss config fields beyond `docker_server`, `base_image`, `environment_variables`, `secrets`, and `data` are not available. Pass any additional configuration as environment variables.
* If your image runs as a specific user, set `run_as_user_id` to that UID.
### Pass configuration as environment variables
Since Truss config fields aren't injected into no-build containers, use `environment_variables` to pass configuration:
```yaml config.yaml theme={"system"}
base_image:
image: your-registry/your-hardened-image:latest
docker_server:
no_build: true
server_port: 8000
predict_endpoint: /predict
readiness_endpoint: /health
liveness_endpoint: /health
environment_variables:
MODEL_NAME: my-model
MAX_BATCH_SIZE: "32"
```
Access these in your server code with `os.environ["MODEL_NAME"]`.
## Next steps
* [Private registries](/development/model/dependencies#private-registries): Pull images from AWS ECR, Google Artifact Registry, or Docker Hub
* [Secrets](/development/model/secrets#custom-docker-images): Access API keys and tokens in your container
* [WebSockets](/development/model/websockets#websocket-usage-with-custom-servers): Enable WebSocket connections
* [vLLM](/examples/vllm), [SGLang](/examples/sglang), [TensorRT-LLM](/examples/tensorrt-llm): Deploy LLMs with popular inference servers
# Dependencies
Source: https://docs.baseten.co/development/model/dependencies
Declare everything your model needs to build and run: Python packages, build commands, base images, and private registries.
Your `config.yaml` is the declarative surface for everything your model bundles, builds, and pulls. Use it to include custom Python packages, run shell commands during the build, swap in a custom Docker base image, and authenticate to private registries.
## Python packages
Truss lets you include custom modules or third-party packages not available on PyPI using two methods:
1. **The `packages` directory**: for bundling small, Truss-specific packages.
2. **The `external_package_dirs` configuration**: for sharing packages across multiple Trusses.
### Use the `packages` directory
Each Truss includes a `packages/` directory where you place Python modules to include at build time. Use this method for lightweight, Truss-specific packages.
**Example directory structure:**
```text Project structure theme={"system"}
stable-diffusion/
packages/
package_1/
subpackage/
script.py
package_2/
utils.py
model/
model.py
__init__.py
config.yaml
```
**Importing a package in `model.py`:**
```python model.py theme={"system"}
from package_1.subpackage.script import run_script
from package_2.utils import RandomClass
class Model:
def __init__(self, **kwargs):
self.random_class = RandomClass()
def load(self):
run_script()
```
### Use `external_package_dirs`
If multiple Trusses need access to the same external package, define `external_package_dirs` in `config.yaml`. A package here refers to an importable directory with Python source code.
**Example directory structure:**
```text Project structure theme={"system"}
stable-diffusion/
model/
model.py
__init__.py
config.yaml
super_cool_awesome_plugin/
plugin1/
script.py
plugin2/
run.py
```
**Configuring `external_package_dirs` in `config.yaml`:**
```yaml config.yaml theme={"system"}
external_package_dirs:
- ../super_cool_awesome_plugin/
```
Paths must be relative to `config.yaml`.
Include any requirements for these packages in your Truss configuration.
**Referencing external packages in `model.py`:**
```python model.py theme={"system"}
from plugin1.script import cool_constant
from plugin2.run import AwesomeRunner
class Model:
def __init__(self, **kwargs):
self.awesome_runner = AwesomeRunner()
def load(self):
self.awesome_runner.run(cool_constant)
```
## Build commands
The `build_commands` feature runs custom Docker commands during the **build stage**, enabling advanced caching, dependency management, and environment setup.
**Use cases:**
* Clone GitHub repositories.
* Install dependencies.
* Create directories.
* Pre-download model weights.
### Run build commands in `config.yaml`
Add `build_commands` to your `config.yaml`:
```yaml config.yaml theme={"system"}
build_commands:
- git clone https://github.com/comfyanonymous/ComfyUI.git
- cd ComfyUI && git checkout b1fd26fe9e55163f780bf9e5f56bf9bf5f035c93 && pip install -r requirements.txt
model_name: Build Commands Demo
python_version: py310
resources:
accelerator: A100
```
This clones the GitHub repository, checks out the specified commit, and installs dependencies. Everything is cached at build time, reducing deployment cold starts.
### Create directories
Use `build_commands` to create directories directly in the container. This is useful for large codebases requiring additional structure.
```yaml config.yaml theme={"system"}
build_commands:
- git clone https://github.com/comfyanonymous/ComfyUI.git
- cd ComfyUI && mkdir ipadapter
- cd ComfyUI && mkdir instantid
```
### Cache model weights efficiently
For large weights (10GB+), use the [Baseten Delivery Network (BDN)](/development/model/bdn) instead of baking them into the image.
For smaller weights, use `wget` in `build_commands`:
```yaml config.yaml theme={"system"}
build_commands:
- git clone https://github.com/comfyanonymous/ComfyUI.git
- cd ComfyUI && pip install -r requirements.txt
- cd ComfyUI/models/controlnet && wget -O control-lora-canny-rank256.safetensors https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-canny-rank256.safetensors
- cd ComfyUI/models/controlnet && wget -O control-lora-depth-rank256.safetensors https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-depth-rank256.safetensors
model_name: Build Commands Demo
python_version: py310
resources:
accelerator: A100
system_packages:
- wget
```
Preloading model weights during the build stage reduces startup time and ensures availability without runtime downloads.
### Run any shell command
`build_commands` runs any shell command at build time and caches the result, so it doesn't re-run on every cold start.
## Base images
Use a custom base image when you need specific system packages or a different runtime than the default Truss image provides.
### Set a base image in `config.yaml`
Specify a custom base image in `config.yaml`:
```yaml config.yaml theme={"system"}
base_image:
image:
python_executable_path:
```
* `image`: the Docker image to use.
* `python_executable_path`: the path to the Python binary inside the container.
#### NVIDIA NeMo model
Use a custom image to deploy the [NVIDIA NeMo TitaNet](https://catalog.ngc.nvidia.com/orgs/nvidia/teams/nemo/models/titanet_large) model:
```yaml config.yaml theme={"system"}
base_image:
image: nvcr.io/nvidia/nemo:23.03
python_executable_path: /usr/bin/python
apply_library_patches: true
requirements:
- PySoundFile
resources:
accelerator: T4
cpu: 2500m
memory: 4512Mi
secrets: {}
system_packages:
- python3.8-venv
```
### Use private base images
If your base image is private, configure your model to use a [private registry](#private-registries).
### Create a custom base image
Build a new base image using Truss's base images as a foundation. Available images are listed on [Docker Hub](https://hub.docker.com/r/baseten/truss-server-base/tags).
#### Customize a Truss base image
```Dockerfile Dockerfile theme={"system"}
FROM baseten/truss-server-base:3.11-gpu-v0.7.16
RUN pip uninstall cython -y
RUN pip install cython==0.29.30
```
#### Build and push your custom image
Ensure Docker is installed and running. Then build, tag, and push your image:
```sh Terminal theme={"system"}
docker build -t my-custom-base-image:0.1 .
docker tag my-custom-base-image:0.1 your-docker-username/my-custom-base-image:0.1
docker push your-docker-username/my-custom-base-image:0.1
```
## Private registries
When deploying a [custom base image](#base-images) or [custom server](/development/model/custom-server) from a private registry, grant Baseten access to pull the image.
For AWS ECR and Google Cloud registries, configure `docker_auth` in `config.yaml` with OIDC (recommended), IAM, or a service account. For every other registry, store credentials as a Baseten secret named `DOCKER_REGISTRY_`, where `` matches the hostname in your `image` URL. For a registry not listed below, see [Other registries](#other-registries).
The value of a `DOCKER_REGISTRY_*` secret must be the Base64 encoding of `username:password`; a raw token or personal access token won't work. Baseten validates the value when you save the secret and rejects values that aren't Base64 or don't decode to a `username:password` pair.
### AWS Elastic Container Registry (ECR)
AWS supports three authentication methods: [OIDC](#aws-oidc-recommended) (recommended), [IAM service accounts](#aws-iam-service-accounts), and [access tokens](#access-token).
#### AWS OIDC (Recommended)
OIDC provides short-lived, narrowly scoped tokens for secure authentication without managing long-lived credentials.
**To authenticate to ECR with AWS OIDC**:
1. [Configure AWS to trust the Baseten OIDC provider](/organization/oidc#aws-setup) and create an IAM role with ECR permissions.
2. Add the OIDC configuration to your `config.yaml`:
```yaml config.yaml theme={"system"}
base_image:
image: .dkr.ecr..amazonaws.com/path/to/image
docker_auth:
auth_method: AWS_OIDC
aws_oidc_role_arn: arn:aws:iam:::role/baseten-ecr-access
aws_oidc_region:
registry: .dkr.ecr..amazonaws.com
```
No secrets needed. The `aws_oidc_role_arn` and `aws_oidc_region` are not sensitive and can be committed to your repository.
See the [OIDC authentication guide](/organization/oidc) for detailed setup instructions and best practices.
#### AWS IAM service accounts
**To authenticate to ECR with an IAM service account** (long-lived access):
1. Get an `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` from the AWS dashboard.
2. Add these as [secrets](https://app.baseten.co/settings/secrets) in Baseten. Name them `aws_access_key_id` and `aws_secret_access_key`.
3. Configure `docker_auth` in your `config.yaml`:
```yaml config.yaml theme={"system"}
...
base_image:
image: .dkr.ecr..amazonaws.com/path/to/image
docker_auth:
auth_method: AWS_IAM
registry: .dkr.ecr..amazonaws.com
secrets:
aws_access_key_id: null
aws_secret_access_key: null
...
```
The `registry` value must match the hostname portion of the `image` URL.
To use different secret names, configure the `aws_access_key_id_secret_name` and `aws_secret_access_key_secret_name` options under `docker_auth`:
```yaml config.yaml theme={"system"}
...
base_image:
...
docker_auth:
auth_method: AWS_IAM
registry: .dkr.ecr..amazonaws.com
aws_access_key_id_secret_name: custom_aws_access_key_secret
aws_secret_access_key_secret_name: custom_aws_secret_key_secret
secrets:
custom_aws_access_key_secret: null
custom_aws_secret_key_secret: null
```
#### Access token
**To authenticate to ECR with an access token**:
1. Get the **Base64-encoded** secret:
```sh Terminal theme={"system"}
PASSWORD=`aws ecr get-login-password --region `
echo -n "AWS:$PASSWORD" | base64
```
2. Add a new [secret](https://app.baseten.co/settings/secrets) to Baseten named `DOCKER_REGISTRY_.dkr.ecr..amazonaws.com` with the Base64-encoded secret as the value.
3. Add the secret name to the `secrets` section of `config.yaml`:
```yaml config.yaml theme={"system"}
secrets:
DOCKER_REGISTRY_.dkr.ecr..amazonaws.com: null
```
ECR authorization tokens expire after 12 hours, so later builds fail until you update the secret. For a durable setup, use [OIDC](#aws-oidc-recommended) or an [IAM service account](#aws-iam-service-accounts) instead.
### Google Cloud Artifact Registry
GCP supports three authentication methods: [OIDC](#gcp-oidc-recommended) (recommended), [service accounts](#service-account), and [access tokens](#access-token-1).
All three methods also work with Google Container Registry (`gcr.io`, `.gcr.io`).
#### GCP OIDC (Recommended)
OIDC provides short-lived, narrowly scoped tokens for secure authentication without managing long-lived credentials.
**To authenticate to Artifact Registry with GCP OIDC**:
1. [Configure GCP Workload Identity](/organization/oidc#google-cloud-setup) to trust the Baseten OIDC provider and grant Artifact Registry permissions.
2. Add the OIDC configuration to your `config.yaml`:
```yaml config.yaml theme={"system"}
base_image:
image: gcr.io/my-project/my-image:latest
docker_auth:
auth_method: GCP_OIDC
gcp_oidc_service_account: baseten-oidc@my-project.iam.gserviceaccount.com
gcp_oidc_workload_id_provider: projects//locations/global/workloadIdentityPools/baseten-pool/providers/baseten-provider
registry: gcr.io
```
No secrets needed. The service account and workload identity provider are not sensitive and can be committed to your repository.
See the [OIDC authentication guide](/organization/oidc) for detailed setup instructions and best practices.
#### Service account
**To authenticate to Artifact Registry with a service account**:
1. Get your [service account key](https://cloud.google.com/artifact-registry/docs/docker/authentication#json-key) as a JSON key blob.
2. Add a new [secret](https://app.baseten.co/settings/secrets) to Baseten named `gcp-service-account` (or similar) with the JSON key blob as the value.
3. Add the secret name to the `secrets` section of `config.yaml`:
```yaml config.yaml theme={"system"}
secrets:
gcp-service-account: null
```
4. Configure the `docker_auth` section of your `base_image` to use service account authentication:
```yaml config.yaml theme={"system"}
base_image:
...
docker_auth:
auth_method: GCP_SERVICE_ACCOUNT_JSON
secret_name: gcp-service-account
registry: -docker.pkg.dev
```
`secret_name` must match the secret you created in step 2.
#### Access token
**To authenticate to Artifact Registry with an access token**:
1. Get the **Base64-encoded** secret. The username is the literal string `oauth2accesstoken`:
```sh Terminal theme={"system"}
echo -n "oauth2accesstoken:$(gcloud auth print-access-token)" | base64
```
2. Add a new [secret](https://app.baseten.co/settings/secrets) to Baseten named `DOCKER_REGISTRY_-docker.pkg.dev` with the Base64-encoded secret as the value.
3. Add the secret name to the `secrets` section of `config.yaml`:
```yaml config.yaml theme={"system"}
secrets:
DOCKER_REGISTRY_-docker.pkg.dev: null
```
GCP access tokens expire after about an hour, so later builds fail until you update the secret. For a durable setup, use [OIDC](#gcp-oidc-recommended) or a [service account](#service-account) instead.
### Docker Hub
**To authenticate to Docker Hub**:
1. Get the **Base64-encoded** secret:
```sh Terminal theme={"system"}
echo -n 'username:password' | base64
```
2. Add a new [secret](https://app.baseten.co/settings/secrets) to Baseten named `DOCKER_REGISTRY_https://index.docker.io/v1/` with the Base64-encoded secret as the value.
```yaml theme={"system"}
Name: DOCKER_REGISTRY_https://index.docker.io/v1/
Token:
```
3. Add the secret name to the `secrets` section of `config.yaml`:
```yaml config.yaml theme={"system"}
secrets:
DOCKER_REGISTRY_https://index.docker.io/v1/: null
```
### GitHub Container Registry (GHCR)
**To authenticate to GHCR**:
1. Create a GitHub [Personal Access Token](https://github.com/settings/tokens) with the `read:packages` scope. Use a **classic** token, not fine-grained.
2. Get the **Base64-encoded** secret:
```sh Terminal theme={"system"}
echo -n 'github_username:ghp_your_personal_access_token' | base64
```
3. Add a new [secret](https://app.baseten.co/settings/secrets) to Baseten named `DOCKER_REGISTRY_ghcr.io` with the Base64-encoded secret as the value.
```yaml theme={"system"}
Name: DOCKER_REGISTRY_ghcr.io
Token:
```
4. Add the secret name to the `secrets` section of `config.yaml`:
```yaml config.yaml theme={"system"}
base_image:
image: ghcr.io/your-org/your-image:tag
secrets:
DOCKER_REGISTRY_ghcr.io: null
```
### NVIDIA NGC
**To authenticate to NVIDIA NGC**:
1. Generate an [NGC API Key](https://org.ngc.nvidia.com/setup/api-key) from your NVIDIA NGC account.
2. Get the **Base64-encoded** secret:
```sh Terminal theme={"system"}
echo -n '$oauthtoken:your_ngc_api_key' | base64
```
The username `$oauthtoken` is a literal string, not a variable. Use it exactly as shown.
3. Add a new [secret](https://app.baseten.co/settings/secrets) to Baseten named `DOCKER_REGISTRY_nvcr.io` with the Base64-encoded secret as the value.
```yaml theme={"system"}
Name: DOCKER_REGISTRY_nvcr.io
Token:
```
4. Add the secret name to the `secrets` section of `config.yaml`:
```yaml config.yaml theme={"system"}
base_image:
image: nvcr.io/nvidia/pytorch:24.01-py3
secrets:
DOCKER_REGISTRY_nvcr.io: null
```
### Other registries
Any registry that supports `docker login` with a username and password works with a `DOCKER_REGISTRY_*` secret.
**To authenticate to any other private registry**:
1. Get the **Base64-encoded** secret:
```sh Terminal theme={"system"}
echo -n ':' | base64
```
If your registry issues tokens or personal access tokens instead of passwords, pair the token with the username your registry's documentation specifies for token logins. This is often a literal placeholder: NVIDIA NGC uses `$oauthtoken`, and some registries use `-`. Encoding the token by itself, without a username and colon, won't authenticate.
2. Add a new [secret](https://app.baseten.co/settings/secrets) to Baseten named `DOCKER_REGISTRY_`, where `` matches the hostname in your `image` URL:
```yaml theme={"system"}
Name: DOCKER_REGISTRY_registry.example.com
Token:
```
3. Add the secret name to the `secrets` section of `config.yaml`:
```yaml config.yaml theme={"system"}
base_image:
image: registry.example.com/your-org/your-image:tag
secrets:
DOCKER_REGISTRY_registry.example.com: null
```
## Next steps
The full set of `config.yaml` options for packages, resources, and the build environment.
Store and reference API keys and registry credentials securely.
Run your own server image instead of the default Truss server.
Set up OIDC for short-lived, credential-free registry authentication.
# Deploy and iterate
Source: https://docs.baseten.co/development/model/deploy-and-iterate
Use development deployments with live patching for rapid iteration, then promote to production.
Development deployments let you iterate on your model without redeploying from scratch each time you make a change. When you save a file, Truss detects the change, calculates a patch, and applies it to the running deployment in seconds.
If `truss push --watch` isn't a good fit, [SSH access](/inference/ssh) lets you connect to a running deployment with standard SSH. SSH works well for custom servers that watch doesn't patch and for longer-lived interactive sessions. Pair it with a non-zero `min_replicas` and a code-syncing tool to iterate on a live container.
## Start a development deployment
Create a development deployment and start watching for changes:
```sh Terminal theme={"system"}
truss push --watch
```
Truss creates a development deployment, waits for it to build, and begins watching your project directory for file changes. Once the deployment reaches the `LOADING_MODEL` stage, Truss enters watch mode early so you can start iterating while the model finishes loading.
```output theme={"system"}
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
👀 Watching for changes to truss...
```
To apply model code changes without restarting the inference server, add the `--watch-hot-reload` flag:
```sh Terminal theme={"system"}
truss push --watch --watch-hot-reload
```
See [What gets live-patched](#what-gets-live-patched) for details and caveats about hot reload.
## Re-attach to a development deployment
If you stop the watch session (Ctrl+C), re-attach to the existing development deployment with:
```sh Terminal theme={"system"}
truss watch
```
You should see:
```output theme={"system"}
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
🚰 Attempting to sync truss with remote
No changes observed, skipping patching.
👀 Watching for new changes.
```
`truss watch` syncs any changes made while disconnected, then resumes watching. It requires an existing development deployment. If you don't have one, use `truss push --watch` to create it.
To apply model code changes without restarting, add the `--hot-reload` flag:
```sh Terminal theme={"system"}
truss watch --hot-reload
```
## What gets live-patched
Truss monitors your project directory (respecting `.trussignore` patterns) and applies patches for the following changes without a full rebuild:
| Change type | Examples |
| --------------------- | -------------------------------------------------------------------------------------------------------------- |
| Model code | Files in the `model/` directory: `model.py`, helper modules, utilities, and binary files (like `.so`, `.png`). |
| Bundled packages | Files in the `packages/` directory, including binary files (like `.pyd`, `.so`). |
| Python requirements | Adding, removing, or updating packages in `requirements` or a requirements file. |
| Environment variables | Adding, removing, or updating values in `environment_variables`. |
| External data | Adding or removing entries in `external_data`. |
| Config values | Most `config.yaml` changes (except those listed below). |
With the `--watch-hot-reload` or `--hot-reload` flags, Truss hot-reloads model code changes by swapping the model class in-process without restarting the inference server. This preserves in-memory state like loaded weights and caches. If a patch includes non-model changes (such as requirements or config), Truss falls back to a standard restart.
Hot reload re-imports your module and updates `__class__` on the existing model instance. It does not re-run `__init__()` or `load()`. If you add new instance state in those methods that `predict()` depends on, `predict()` calls will fail to see it. When your changes involve new instance state, stop the watch session and do a full reload with `truss push --watch`.
## What requires a full redeploy
The patch system doesn't support some changes. When you make these changes, stop the watch session and run `truss push` (or `truss push --watch` to start a new development deployment):
| Change type | Why |
| ----------------------------- | ------------------------------------------------------- |
| `resources` (GPU type, count) | Requires a new instance. |
| `python_version` | Requires a new base image. |
| `system_packages` | Requires apt installation in the container. |
| `live_reload` | Changes the deployment mode. |
| Data directory (`data/`) | The patch system doesn't track file changes in `data/`. |
If a patch fails, Truss prints an error and continues watching. Fix the issue in your source files and save again. For persistent failures, run `truss push --watch` to start fresh.
## Limitations
Development deployments optimize for iteration, not production traffic:
* **Single replica**: Fixed at 0 minimum, 1 maximum. No autoscaling beyond one replica.
* **No gRPC**: Trusses with gRPC transport require a published deployment.
* **No TRT-LLM engine builds**: TRT-LLM build flow requires a published deployment.
See [Development deployments](/deployment/autoscaling/overview#development-deployments) for the full autoscaling constraints.
## Deploy to production
When you're done iterating, deploy a published version:
```sh Terminal theme={"system"}
truss push
```
By default, `truss push` creates a published deployment with full autoscaling support. Published deployments can scale to multiple replicas and are suitable for production traffic.
To deploy and promote directly to the production environment:
```sh Terminal theme={"system"}
truss push --promote
```
Full list of options for the push command.
Full list of options for the watch command.
Configure replicas, concurrency targets, and scale-to-zero for production.
Manage staging, production, and custom environments.
# Access model environments
Source: https://docs.baseten.co/development/model/environments
Configure model behavior based on environment
Model environments help configure behavior based on deployment stage (for example, production vs. staging). You can access the environment details through `kwargs` in the `Model` class.
## Retrieve the environment
Access the environment in `__init__`:
```python model/model.py theme={"system"}
def __init__(self, **kwargs):
self._environment = kwargs["environment"]
```
## Configure behavior per environment
Use the environment in your `load()` method to set up environment-specific behavior:
```python model/model.py theme={"system"}
def load(self):
if self._environment.get("name") == "production":
self.setup_sentry()
self.setup_logging(level="INFO")
self.load_production_weights()
else:
self.setup_logging(level="DEBUG")
self.load_default_weights()
```
This lets you:
* Customize logging levels.
* Load environment-specific model weights.
* Enable monitoring tools (for example, Sentry).
When you promote a deployment without re-deploying, `load()` doesn't re-run, so environment-specific configuration from the original deployment persists. You can configure an environment to create a fresh deployment on every promotion. See [Re-deploy on promotion](/deployment/environments#re-deploy-on-promotion) for details.
## Next steps
* [The Model class](/development/model/model-class): Read configuration, secrets, and runtime information in `__init__` and `load`.
* [Environments](/deployment/environments): Promote deployments across stages and configure re-deploy on promotion.
# gRPC
Source: https://docs.baseten.co/development/model/grpc
Invoke your model over gRPC.
gRPC is a high-performance, open-source remote procedure call (RPC) framework that uses HTTP/2 for transport and Protocol Buffers for serialization. Unlike traditional HTTP APIs, gRPC provides strong type safety, high performance, and built-in support for streaming and bidirectional communication. Run a gRPC server on Baseten when you want these properties for model inference.
gRPC offers:
* **Type safety**: Protocol Buffers enforce strong typing and contract validation between client and server.
* **Ecosystem integration**: Integrate Baseten with existing gRPC-based services.
* **Streaming support**: Built-in server streaming, client streaming, and bidirectional streaming.
* **Language interoperability**: Generate client libraries for multiple programming languages from a single `.proto` file.
## gRPC on Baseten
gRPC models run as [custom servers](/development/model/custom-server). Your own server process handles gRPC requests directly, instead of going through the standard Truss `load()` and `predict()` methods.
For this to work, you must first package your gRPC server code into a Docker image.
Once that is done, you can set up your Truss `config.yaml` to configure your deployment
and push the server to Baseten.
## Setup
### Installation
**To install the prerequisites**:
1. Install [uv](https://docs.astral.sh/uv/) if you don't have it. This guide uses `uvx` to run [Truss](https://pypi.org/project/truss/) commands without a separate install step.
2. Install the Protocol Buffer compiler:
```bash Terminal theme={"system"}
# On macOS
brew install protobuf
# On Ubuntu/Debian
sudo apt-get install protobuf-compiler
# On other systems, see: https://protobuf.dev/getting-started/
```
3. Set up a virtual environment and install gRPC tools:
```bash Terminal theme={"system"}
uv venv && source .venv/bin/activate
uv pip install grpcio-tools
```
### Protocol buffer definition
Your gRPC service starts with a `.proto` file that defines the service interface and message types. Create an `example.proto` file in your project root:
```protobuf example.proto theme={"system"}
syntax = "proto3";
package example;
// The greeting service definition
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name
message HelloRequest {
string name = 1;
}
// The response message containing the greeting
message HelloReply {
string message = 1;
}
```
#### Generate Protocol Buffer code
Generate the Python code from your `.proto` file:
```bash Terminal theme={"system"}
python -m grpc_tools.protoc --python_out=. --grpc_python_out=. --proto_path . example.proto
```
This generates the necessary Python files (`example_pb2.py` and `example_pb2_grpc.py`) for your gRPC service. For more information about Protocol Buffers, see the [official documentation](https://protobuf.dev/).
### Model implementation
Create your gRPC server implementation in a file called `model.py`. Here's a basic example:
```python model.py theme={"system"}
import grpc
from concurrent import futures
import time
import example_pb2
import example_pb2_grpc
from grpc_health.v1 import health_pb2
from grpc_health.v1 import health_pb2_grpc
from grpc_health.v1.health import HealthServicer
class GreeterServicer(example_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
response = example_pb2.HelloReply()
response.message = f"Hello, {request.name}!"
return response
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
example_pb2_grpc.add_GreeterServicer_to_server(GreeterServicer(), server)
# The gRPC health check service must be used in order for Baseten
# to consider the gRPC server healthy.
health_servicer = HealthServicer()
health_pb2_grpc.add_HealthServicer_to_server(health_servicer, server)
health_servicer.set(
"example.GreeterService", health_pb2.HealthCheckResponse.SERVING
)
# Ensure the server runs on port 50051
server.add_insecure_port("[::]:50051")
server.start()
print("gRPC server started on port 50051")
# Keep the server running
try:
while True:
time.sleep(86400)
except KeyboardInterrupt:
print("Shutting down server...")
server.stop(0)
if __name__ == "__main__":
serve()
```
## Deployment
### Create a Dockerfile
Since gRPC on Baseten requires a custom server setup, you'll need to create a `Dockerfile` that bundles your gRPC server code and dependencies. Here's a basic skeleton:
```dockerfile Dockerfile theme={"system"}
FROM debian:latest
RUN apt-get update && apt-get install -y \
build-essential \
python3 \
python3-pip \
python3-venv \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY model.py ./model.py
COPY example_pb2.py example_pb2_grpc.py ./
EXPOSE 8080
CMD ["python", "model.py"]
```
Create a `requirements.txt` file with your gRPC dependencies:
```txt requirements.txt theme={"system"}
grpcio
grpcio-health-checking
grpcio-tools
protobuf
```
### Build and push Docker image
Build and push your Docker image to a container registry:
```bash Terminal theme={"system"}
docker build -t your-registry/truss-grpc-demo:latest . --platform linux/amd64
docker push your-registry/truss-grpc-demo:latest
```
Replace `your-registry` with your actual container registry (for example, Docker Hub, Google Container Registry, AWS ECR). You can create a Docker Hub container registry by [following their documentation](https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-a-registry/#try-it-out).
### Configure your Truss
Update your `config.yaml` to use the custom Docker image and configure the gRPC server:
```yaml config.yaml theme={"system"}
model_name: "gRPC Model Example"
base_image:
image: your-registry/truss-grpc-demo:latest
docker_server:
start_command: python model.py
# 50051 is the only supported server port.
server_port: 50051
# The _endpoint fields are ignored for gRPC models.
predict_endpoint: /
readiness_endpoint: /
liveness_endpoint: /
resources:
accelerator: L4 # or your preferred GPU
use_gpu: true
runtime:
transport:
kind: "grpc"
```
### Deploy with Truss
Deploy your model using the Truss CLI. gRPC models aren't supported in development deployments, so use the default published deployment or `--promote` to also promote to production.
```bash Terminal theme={"system"}
uvx truss push --promote
```
For more detailed information about Truss deployment, see the [truss push documentation](/reference/cli/truss/push).
## Call your model
### Use a gRPC client
Once deployed, you can call your model using any gRPC client. Here's an example Python client:
```python client.py theme={"system"}
import grpc
import example_pb2
import example_pb2_grpc
def run():
channel = grpc.secure_channel(
"model-{MODEL_ID}.grpc.api.baseten.co:443",
grpc.ssl_channel_credentials(),
)
stub = example_pb2_grpc.GreeterStub(channel)
request = example_pb2.HelloRequest(name="World")
metadata = [
("baseten-authorization", "Api-Key {API_KEY}"),
("baseten-model-id", "model-{MODEL_ID}"),
]
response = stub.SayHello(request, metadata=metadata)
print(response.message)
if __name__ == "__main__":
run()
```
### Inference for specific environments and deployments
To target a specific environment or deployment, add the corresponding header to your `metadata` list:
```python client.py theme={"system"}
metadata = [
('baseten-authorization', 'Api-Key {API_KEY}'),
('baseten-model-id', 'model-{MODEL_ID}'),
# To target a specific environment:
('x-baseten-environment', 'staging'),
# Or, to target a specific deployment instead:
# ('x-baseten-deployment', 'your-deployment-id'),
]
```
### Inference for regional environments
If your organization uses [regional environments](/deployment/environments#regional-environments), use the regional hostname as the gRPC target. The environment is derived from the hostname, so do not set `x-baseten-environment` or `x-baseten-deployment` headers.
```python client.py theme={"system"}
channel = grpc.secure_channel(
"model-{MODEL_ID}-{ENV_NAME}.grpc.api.baseten.co:443",
grpc.ssl_channel_credentials(),
)
metadata = [
('baseten-authorization', 'Api-Key {API_KEY}'),
('baseten-model-id', 'model-{MODEL_ID}'),
]
```
### Test your deployment
Run your client to test the deployed model:
```bash Terminal theme={"system"}
python client.py
```
## Per-request logging
Baseten assigns a unique request ID to every predict call and returns it in the `x-baseten-request-id` response metadata. You can use this ID to [filter your model's logs](/observability/logs) down to a single request.
For standard Truss models, request ID logging is automatic. For custom gRPC servers, you'll need to extract the request ID from the incoming metadata and include it in your JSON log output.
Extract the request ID from the `x-baseten-request-id` metadata key:
```python model.py theme={"system"}
import json
import logging
import sys
import example_pb2
import example_pb2_grpc
class JSONFormatter(logging.Formatter):
"""Formats logs as JSON with request_id for Baseten log filtering."""
def format(self, record):
log_record = {
"level": record.levelname,
"message": record.getMessage(),
}
if getattr(record, "request_id", None):
log_record["request_id"] = record.request_id
return json.dumps(log_record)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JSONFormatter())
logger = logging.getLogger(__name__)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
class GreeterServicer(example_pb2_grpc.GreeterServicer):
def SayHello(self, request, context):
metadata = dict(context.invocation_metadata())
request_id = metadata.get("x-baseten-request-id")
logger.info(
f"Received greeting request for {request.name}",
extra={"request_id": request_id},
)
response = example_pb2.HelloReply()
response.message = f"Hello, {request.name}!"
logger.info("Request complete", extra={"request_id": request_id})
return response
```
Logs must be JSON formatted and written to stdout. The `request_id` field must be a top-level key in the JSON object.
## Full example
See this [GitHub repository](https://github.com/basetenlabs/truss-examples/tree/main/grpc) for a full example.
## Scaling
While many gRPC requests follow the traditional request-response pattern, gRPC also supports
bidirectional streaming and long-lived connections. The implication of this is that
a single long-lived connection, even if no data is being sent, counts
against the concurrency target for the deployment.
## Promotion
Like HTTP deployments, you can promote a gRPC deployment to an environment through the REST API or UI. For more information, see [Environments](/deployment/environments).
When you promote a gRPC deployment, new connections are routed to the new deployment, but existing
connections stay on the current deployment until they terminate.
Depending on the length of the connection, old deployments can take longer to scale down
than HTTP deployments.
## Monitoring
As with HTTP deployments, Baseten exposes performance metrics for gRPC deployments.
### Inference volume
Baseten tracks inference volume as the number of RPCs per minute. The platform publishes these metrics *after* the request completes.
See [gRPC status codes](https://grpc.io/docs/guides/status-codes/) for a full list
of codes.
### End-to-end response time
Measured at different percentiles (p50, p90, p95, p99):
End-to-end response time includes cold starts, queuing, and inference (excludes client-side latency). Reflects real-world performance.
## Next steps
* [Custom servers](/development/model/custom-server): Configure `docker_server` and `no_build` for container-based deployments.
* [truss push](/reference/cli/truss/push): Deploy and promote your gRPC model.
* [WebSockets](/development/model/websockets): Another transport for real-time, bidirectional communication.
# Health checks
Source: https://docs.baseten.co/development/model/health-checks
Customize the health of your deployments.
Baseten runs health checks every 10 seconds on each replica of your deployment. When a health check fails long enough to cross a configured threshold, Baseten takes action: stopping traffic to the replica, restarting it, or both.
You can customize health checks in two ways:
* [**Configure failure thresholds**](#health-check-configuration) to control when Baseten stops traffic or restarts a replica.
* [**Write custom health check logic**](#custom-health-check-logic) to define what "healthy" means for your model (for example, mark unhealthy after repeated 5xx errors or a specific CUDA error).
## Health probes
Baseten uses three Kubernetes health probes: startup, readiness, and liveness. Each serves a different purpose in the replica lifecycle.
### Startup probe
The startup probe confirms your model has finished initializing. For Truss models, initialization is complete when `load()` finishes and the optional `is_healthy()` check passes. For [custom servers](/development/model/custom-server), the readiness endpoint must return a successful response. All readiness and liveness probes are delayed until the startup probe succeeds.
The startup phase runs for 30 minutes by default. Extend it with `startup_threshold_seconds` up to 50 minutes (`3000` seconds) for models that need more time to load. The startup probe uses the same endpoint as the readiness probe. You can't configure a separate startup endpoint.
### Readiness probe
The readiness probe determines whether a replica can accept traffic. When it fails, Kubernetes stops routing requests to the replica but doesn't restart it. Configure the failure window with `stop_traffic_threshold_seconds`.
### Liveness probe
The liveness probe determines whether a replica is still functioning. When it fails, Kubernetes restarts the replica to recover from deadlocks or hung processes. Configure the failure window with `restart_threshold_seconds`.
For most models, using the same endpoint (like `/health`) for both readiness and liveness probes is sufficient. The difference is the action taken: readiness controls traffic routing, liveness controls container lifecycle.
## Health check configuration
### Parameters
Customize health checks by setting these parameters:
How long the startup phase runs before marking the replica as unhealthy. During this phase, readiness and liveness probes don't run.
`startup_threshold_seconds` must be between `10` and `3000` seconds, inclusive. Defaults to 30 minutes (`1800` seconds).
How long health checks must continuously fail before Baseten stops traffic to the failing replica.
`stop_traffic_threshold_seconds` must be between `10` and `3000` seconds, inclusive. Defaults to 30 minutes (`1800` seconds).
How long health checks must continuously fail before Baseten restarts the failing replica.
`restart_threshold_seconds` must be between `10` and `3000` seconds, inclusive. Defaults to 30 minutes (`1800` seconds).
How long to wait before running health checks. Must be between `0` and `3000` seconds, inclusive.
`restart_check_delay_seconds` is deprecated. Use `startup_threshold_seconds` instead. The startup probe delays all health checks until your model passes its first readiness check, preventing unnecessary restarts during initialization.
The combined value of `restart_check_delay_seconds` and `restart_threshold_seconds` can't exceed `3000` seconds.
### Choose threshold values
The platform defaults (30 minutes for each threshold) are deliberately conservative. Most deployments are better served by tighter values that fail faster when something goes wrong. The rules below give you a starting point anchored to one observable input: how long your model takes to become ready.
To find your cold start time, open the model's [Metrics tab](/observability/metrics) and use the worst observed cold start across the environments you care about (dev, staging, production, and any size variants). Cold starts can vary widely by environment, so a single average is misleading.
Set to **2× your worst observed cold start**.
Bias high. A value that's too low can kill replicas mid-load, which then restart and try to load again, and the cycle compounds on GPU-saturated clusters where a new replica may not get scheduled immediately.
Start at **60 seconds** (six consecutive failed checks).
Bias low. Every second past first failure is a request that may land on a degraded replica. Raise this only if your `is_healthy()` deliberately reports unhealthy for stretches you want to ride out, for example a self-healing transient or a planned warmup buffer.
Set to **1.5× `stop_traffic_threshold_seconds`** (90 seconds with the default above).
Lower it, or invert the order so restarts happen before stop-traffic, when a restart is your fastest recovery path. Raise it when restarts are expensive, for example a long re-load with weight downloads.
These are starting points, not final answers. Watch the [Restarts metric](/observability/metrics#restarts) after changing thresholds to confirm the behavior matches what you expect.
### Model and custom server deployments
Configure health checks in your `config.yaml`.
```yaml config.yaml theme={"system"}
runtime:
health_checks:
startup_threshold_seconds: 2400
restart_threshold_seconds: 600
stop_traffic_threshold_seconds: 300
```
You can also specify custom health check endpoints for custom servers. See [Custom servers](/development/model/custom-server) for details.
### Chains
Use `remote_config` to configure health checks for your chainlet classes.
```python chain.py theme={"system"}
class CustomHealthChecks(chains.ChainletBase):
remote_config = chains.RemoteConfig(
options=chains.ChainletOptions(
health_checks=truss_config.HealthChecks(
startup_threshold_seconds=2400,
restart_threshold_seconds=600,
stop_traffic_threshold_seconds=300,
)
)
)
```
## Custom health check logic
You can write custom health checks in both **model deployments** and **chain
deployments**.
Custom health checks aren't supported in development deployments.
### Custom health checks in models
```python model.py theme={"system"}
class Model:
def is_healthy(self) -> bool:
# Add custom health check logic for your model here
pass
```
### Custom health checks in chains
Health checks can be customized for each chainlet in your chain.
```python chain.py theme={"system"}
@chains.mark_entrypoint
class CustomHealthChecks(chains.ChainletBase):
def is_healthy(self) -> bool:
# Add custom health check logic for your chainlet here
pass
```
## Health checks in action
### Observe probe behavior
The model's [Metrics tab](/observability/metrics) surfaces probe activity in production through the [Restarts graph](/observability/metrics#restarts), which counts container restarts, including those triggered by failed liveness probes. Use it to confirm that threshold changes have the effect you expect, or to spot probe failures that aren't surfaced anywhere else.
If you [export metrics](/observability/export-metrics/overview), [`baseten_pod_readiness`](/observability/export-metrics/supported-metrics#baseten_pod_readiness) splits pods by their Ready condition, so you can see when a readiness probe pulls traffic from a replica.
### 5xx error detection
Create a custom health check to identify 5xx errors:
```python model.py theme={"system"}
class Model:
def __init__(self):
...
self._is_healthy = True
def load(self):
# Perform load
# Your custom health check won't run until after load completes
...
def is_healthy(self):
return self._is_healthy
def predict(self, input):
try:
# Perform inference
...
except Some5xxError:
self._is_healthy = False
raise
```
A custom health check failure produces this log:
```md Example health check failure log line theme={"system"}
Jan 27 10:36:03pm md2pg Health check failed.
```
A restart from health check failure produces this log:
```md Example restart log line theme={"system"}
Jan 27 12:02:47pm zgbmb Model terminated unexpectedly. Exit code: 0, reason: Completed, restart count: 1
```
## FAQs
### Is there a rule of thumb for configuring thresholds for stopping traffic and restarting?
For starting values anchored to your model's cold start time, see [Choose threshold values](#choose-threshold-values).
The relative ordering of `stop_traffic_threshold_seconds` and `restart_threshold_seconds` depends on your health check implementation. If your health check relies on conditions that only change during inference (for example, `_is_healthy` is set in `predict`), restarting before stopping traffic is generally better, as it allows recovery without disrupting traffic.
Stopping traffic first may be preferable if a failing replica is actively degrading performance or causing inference errors, as it prevents the failing replica from affecting the overall deployment while allowing time for debugging or recovery.
### When should I configure `startup_threshold_seconds`?
The default startup phase is 30 minutes. Increase `startup_threshold_seconds` if your model takes longer to load weights or initialize. The maximum is 50 minutes (`3000` seconds).
`restart_check_delay_seconds` is deprecated. If you're currently using it, switch to `startup_threshold_seconds`, which delays health checks until your model is ready.
### Why am I seeing two health check failure logs in my logs?
These refer to two separate health checks we run every 10 seconds:
* One to determine when to stop traffic to a replica.
* The other to determine when to restart a replica.
### Does stopped traffic or replica restarts affect autoscaling?
Yes, both can impact autoscaling. If traffic stops or replicas restart, the
remaining replicas handle more load. If the load exceeds the concurrency target
during the autoscaling window, additional replicas are spun up. Similarly, when
traffic stabilizes, excess replicas are scaled down after the scale down delay.
See [how autoscaling works](/deployment/autoscaling/overview#how-autoscaling-works) for details.
### How do health checks affect billing?
You're billed for the uptime of your deployment. This includes the time a
replica is running, even if it's failing health checks, until it scales down.
### Will failing health checks cause my deployment to stay up forever?
No. If your deployment is configured with a scale down delay and the minimum
number of replicas is set to 0, the replicas will scale down once the model is
no longer receiving traffic for the duration of the scale down delay. This
applies even if the replicas are failing health checks.
See [scale to zero](/deployment/autoscaling/overview#scale-to-zero) for details.
### What happens when my deployment is loading?
When your deployment is loading, your custom health check won't be running.
Once `load()` is completed, we'll start using your custom `is_healthy()` health
check.
# Cached weights
Source: https://docs.baseten.co/development/model/model-cache
Accelerate cold starts and availability by prefetching and caching your weights.
### Migrate to `weights`
`model_cache` is superseded by the new [BDN (Baseten Delivery Network)](/development/model/bdn), which offers faster cold starts through multi-tier caching (in-cluster + node-level).
Use `truss migrate` to automatically convert your configuration:
```bash Terminal theme={"system"}
truss migrate
```
See [Baseten Delivery Network (BDN)](/development/model/bdn) for the new approach.
**When `model_cache` may still be needed:**
* Quantization workflows where you need to process weights after download
* Custom download timing through `lazy_data_resolver.block_until_download_complete()`
* Prototyping and iterating using direct downloads.
### Cold starts
"Cold start" is a term used to describe the time taken when a request is received when the model is scaled to 0 until it is ready to handle the first request. This process is a critical factor in allowing your deployments to be responsive to traffic while maintaining your SLAs and lowering your costs.
To optimize cold starts, we will go over the following strategies: Downloading them in a background thread in Rust that runs during the module import, caching weights in a distributed filesystem, and moving weights into the docker image.
In practice, this reduces the cold start for large models to just a few seconds. For example, Stable Diffusion XL can take a few minutes to boot up without caching. With caching, it takes just under 10 seconds.
## Enable prefetching for a model
To enable caching, simply add `model_cache` to your `config.yaml` with a valid `repo_id`. The `model_cache` has a few key configurations:
* `repo_id` (required): The repo name from Hugging Face or bucket/container from GCS, S3, or Azure.
* `revision` (required for Hugging Face): The revision of the huggingface repo, such as the sha or branch name such as `refs/pr/1` or `main`. Not needed for GCS, S3, or Azure.
* `use_volume`: Boolean flag to determine if the weights are downloaded to the Baseten Distributed Filesystem at runtime (recommended) or bundled into the container image (legacy, not recommended).
* `volume_folder`: string, folder name under which the model weights appear. Setting it to `my-llama-model` will mount the repo to `/app/model_cache/my-llama-model` at runtime.
* `allow_patterns`: Only cache files that match specified patterns. Utilize Unix shell-style wildcards to denote these patterns.
* `ignore_patterns`: Conversely, you can also denote file patterns to ignore, hence streamlining the caching process.
* `runtime_secret_name`: The name of your secret containing the credentials for a private repository or bucket, such as a `hf_access_token` or `gcs_service_account`.
* `kind`: The storage provider type for the model weights.
* `"hf"` (default): Hugging Face
* `"gcs"`: Google Cloud Storage
* `"s3"`: AWS S3
* `"azure"`: Azure Blob Storage
Here is an example of a well written `model_cache` for Stable Diffusion XL. Note how it only pulls the model weights that it needs using `allow_patterns`.
```yaml config.yaml theme={"system"}
model_cache:
- repo_id: madebyollin/sdxl-vae-fp16-fix
revision: 207b116dae70ace3637169f1ddd2434b91b3a8cd
use_volume: true
volume_folder: sdxl-vae-fp16
allow_patterns:
- config.json
- diffusion_pytorch_model.safetensors
- repo_id: stabilityai/stable-diffusion-xl-base-1.0
revision: 462165984030d82259a11f4367a4eed129e94a7b
use_volume: true
volume_folder: stable-diffusion-xl-base
allow_patterns:
- "*.json"
- "*.fp16.safetensors"
- sd_xl_base_1.0.safetensors
- repo_id: stabilityai/stable-diffusion-xl-refiner-1.0
revision: 5d4cfe854c9a9a87939ff3653551c2b3c99a4356
use_volume: true
volume_folder: stable-diffusion-xl-refiner
allow_patterns:
- "*.json"
- "*.fp16.safetensors"
- sd_xl_refiner_1.0.safetensors
```
Many Hugging Face repos have model weights in different formats (`.bin`, `.safetensors`, `.h5`, `.msgpack`, etc.). You usually need only one format. To minimize cold starts, cache only the weights you need.
### Weight pre-fetching
With `model_cache`, weights are pre-fetched by downloading your weights ahead of time in a dedicated Rust thread.
This means, you can perform all kinds of preparation work (importing libraries, jit compilation of torch/triton modules), until you need access to the files.
In practice, executing statements like `import tensorrt_llm` typically take 10-15 seconds. By that point, the first 5-10GB of the weights will have already been downloaded.
To use the `model_cache` config with truss, we require you to actively interact with the `lazy_data_resolver`.
Before using any of the downloaded files, you must call the `lazy_data_resolver.block_until_download_complete()`. This will block until all files in the `/app/model_cache` directory are downloaded & ready to use.
This call must be either part of your `__init__` or `load` implementation.
```python model.py theme={"system"}
# <- download is invoked before here.
import torch # this line usually takes 2-5 seconds.
import tensorrt_llm # this line usually takes 10-15 seconds
import onnxruntime # this line usually takes 5-10 seconds
class Model:
"""example usage of `model_cache` in truss"""
def __init__(self, *args, **kwargs):
# `lazy_data_resolver` is passed as keyword-argument in init
self._lazy_data_resolver = kwargs["lazy_data_resolver"]
def load(self):
# work that does not require the download may be done beforehand
random_vector = torch.randn(1000)
# important to collect the download before using any incomplete data
self._lazy_data_resolver.block_until_download_complete()
# after the call, you may use the /app/model_cache directory and the contents
torch.load(
"/app/model_cache/stable-diffusion-xl-base/model.fp16.safetensors"
)
```
## Private repositories/cloud storage
### Private Hugging Face repositories
For any public Hugging Face repo, you don't need to do anything else. Adding the `model_cache` key with an appropriate `repo_id` should be enough.
However, if you want to deploy a model from a gated repo like [Gemma](https://huggingface.co/google/gemma-3-27b-it) to Baseten, there are a few steps you need to take:
[Grab an API key](https://huggingface.co/settings/tokens) from Hugging Face with `read` access. Make sure you have access to the model you want to serve.
Paste your API key in your [secrets manager in Baseten](https://app.baseten.co/settings/secrets) under the specified key, such as `hf_access_token`. You can read more about secrets [here](/development/model/secrets).
In your Truss's `config.yaml`, add the secret key under `runtime_secret_name`:
```yaml config.yaml theme={"system"}
model_cache:
- repo_id: your-org/your-private-repo
revision: main # refs/pr/1
runtime_secret_name: hf_access_token
```
On the recommended `weights` API, `runtime_secret_name` becomes the per-source [`auth`](/development/model/bdn#param-auth) block (`auth_method: CUSTOM_SECRET`, `auth_secret_name`). See the [migration mapping](/development/model/bdn#migration-from-model_cache).
Once your truss is pushed, we resolve the sha behind your branch (main), and protect the deployment against changes on this branch.
If you continue to hit issues, contact [Baseten support](mailto:support@baseten.co).
### Private GCS buckets
If you want to deploy a model from a private GCS bucket to Baseten, there are a few steps you need to take:
Create a [service account key](https://cloud.google.com/iam/docs/keys-create-delete#creating) in your GCS account for the project which contains the model weights.
Paste the contents of the `service_account.json` in your [secrets manager in Baseten](https://app.baseten.co/settings/secrets) under the specified key, for example, `gcs_service_account`. You can read more about secrets [here](/development/model/secrets).
At a minimum, you should have these credentials:
```json gcs_service_account theme={"system"}
{
"private_key_id": "xxxxxxx",
"private_key": "-----BEGIN PRIVATE KEY-----\nMI",
"client_email": "b10-some@xxx-example.iam.gserviceaccount.com"
}
```
In your Truss's `config.yaml`, make sure to add the `runtime_secret_name` to your `model_cache` matching the above secret name:
```yaml config.yaml theme={"system"}
model_cache:
- repo_id: gs://your-private-bucket
use_volume: true
volume_folder: your-model-weights
runtime_secret_name: gcs_service_account
kind: "gcs"
ignore_patterns: "*.protobuf"
```
Note: S3/Azure/GCS Buckets are immutable. Once the truss is pushed, you may no longer delete or modify files as they are referenced as required files for a model startup.
If you continue to hit issues, contact [Baseten support](mailto:support@baseten.co).
### Private S3 buckets
If you want to deploy a model from a private S3 bucket to Baseten, there are a few steps you need to take:
[Get your `aws_access_key_id` and `aws_secret_access_key`](https://aws.amazon.com/blogs/security/how-to-find-update-access-keys-password-mfa-aws-management-console/) in your AWS account for the bucket that contains the model weights.
Paste the following `json` in your [secrets manager in Baseten](https://app.baseten.co/settings/secrets) under the specified key, for example, `aws_secret_json`. You can read more about secrets [here](/development/model/secrets).
```json aws_secret_json theme={"system"}
{
"aws_access_key_id": "XXXXX",
"aws_secret_access_key": "xxxxx/xxxxxx",
"aws_region": "us-west-2"
}
```
In your Truss's `config.yaml`, make sure to add the `runtime_secret_name` to your `model_cache` matching the above secret name:
```yaml config.yaml theme={"system"}
model_cache:
- repo_id: s3://your-bucket-west-2-name/path/to/model/
use_volume: true
volume_folder: your-model-weights # sync of s3 path/to/model/* to /app/model_cache/your-model-weights/*
runtime_secret_name: aws_secret_json
kind: "s3"
ignore_patterns: "*.protobuf"
```
Note: S3/Azure/GCS Buckets are immutable. Once the truss is pushed, you may no longer delete or modify files as they are referenced as required files for a model startup.
If you continue to hit issues, contact [Baseten support](mailto:support@baseten.co).
### Private Azure containers
If you want to deploy a model from a private Azure container to Baseten, there are a few steps you need to take:
Get your [account key](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage) from the Azure portal under Security + networking > Access keys for the storage account that holds the model weights.
Paste the following `json` in your [secrets manager in Baseten](https://app.baseten.co/settings/secrets) under the specified key, for example, `azure_secret_json`. You can read more about secrets [here](/development/model/secrets).
```json azure_secret_json theme={"system"}
{
"account_key": "xxxxx"
}
```
In your Truss's `config.yaml`, make sure to add the `runtime_secret_name` to your `model_cache` matching the above secret name:
```yaml config.yaml theme={"system"}
model_cache:
- repo_id: azure://your-account/your-container/path/to/model/
use_volume: true
volume_folder: your-model-weights
runtime_secret_name: azure_secret_json
kind: "azure"
ignore_patterns: "*.protobuf"
```
Note: S3/Azure/GCS Buckets are immutable. Once the truss is pushed, you may no longer delete or modify files as they are referenced as required files for a model startup.
If you continue to hit issues, contact [Baseten support](mailto:support@baseten.co).
## `model_cache` within Chains
To use `model_cache` for [chains](/development/chain/getting-started) - use the `Assets` specifier. In the example below, we will download `llama-3.2-1B`.
As this model is a gated huggingface model, we are setting the mounting token as part of the assets `chains.Assets(..., secret_keys=["hf_access_token"])`.
The model is quite small - in many cases, we will be able to download the model while `from transformers import pipeline` and `import torch` are running.
```python chain_cache.py theme={"system"}
import random
import truss_chains as chains
try:
# imports on global level for PoemGeneratorLM, to save time during the download.
from transformers import pipeline
import torch
except ImportError:
# RandInt does not have these dependencies.
pass
class RandInt(chains.ChainletBase):
async def run_remote(self, max_value: int) -> int:
return random.randint(1, max_value)
@chains.mark_entrypoint
class PoemGeneratorLM(chains.ChainletBase):
from truss import truss_config
LLAMA_CACHE = truss_config.ModelRepo(
repo_id="meta-llama/Llama-3.2-1B-Instruct",
revision="c4219cc9e642e492fd0219283fa3c674804bb8ed",
use_volume=True,
volume_folder="llama_mini",
ignore_patterns=["*.pth", "*.onnx"]
)
remote_config = chains.RemoteConfig(
docker_image=chains.DockerImage(
# The phi model needs some extra python packages.
pip_requirements=[
"transformers==4.48.0",
"torch==2.6.0",
]
),
compute=chains.Compute(
gpu="L4"
),
# The phi model needs a GPU and more CPUs.
# compute=chains.Compute(cpu_count=2, gpu="T4"),
# Cache the model weights in the image
assets=chains.Assets(cached=[LLAMA_CACHE], secret_keys=["hf_access_token"]),
)
# <- Download happens before __init__ is called.
def __init__(self, rand_int=chains.depends(RandInt, retries=3)) -> None:
self._rand_int = rand_int
print("loading cached llama_mini model")
self.pipeline = pipeline(
"text-generation",
model=f"/app/model_cache/llama_mini",
)
async def run_remote(self, max_value: int = 3) -> str:
num_repetitions = await self._rand_int.run_remote(max_value)
print("writing poem with num_repetitions", num_repetitions)
poem = str(self.pipeline(
text_inputs="Write a beautiful and descriptive poem about the ocean. Focus on its vastness, movement, and colors.",
max_new_tokens=150,
do_sample=True,
return_full_text=False,
temperature=0.7,
top_p=0.9,
)[0]['generated_text'])
return poem * num_repetitions
```
## `model_cache` for custom servers
If you are not using Python's `model.py` and [custom servers](/development/model/custom-server) such as [vllm](/examples/vllm), TEI or [sglang](/examples/sglang),
you are required to use the `truss-transfer-cli` command, to force population of the `/app/model_cache` location. The command will block until the weights are downloaded.
Here is an example for how to use text-embeddings-inference on a L4 to populate a jina embeddings model from huggingface into the model\_cache.
```yaml config.yaml theme={"system"}
base_image:
image: baseten/text-embeddings-inference-mirror:89-1.6
docker_server:
liveness_endpoint: /health
predict_endpoint: /v1/embeddings
readiness_endpoint: /health
server_port: 7997
# using `truss-transfer-cli` to download the weights to `cached_model`
start_command: bash -c "truss-transfer-cli && text-embeddings-router --port 7997
--model-id /app/model_cache/my_jina --max-client-batch-size 128 --max-concurrent-requests
128 --max-batch-tokens 16384 --auto-truncate"
model_cache:
- repo_id: jinaai/jina-embeddings-v2-base-code
revision: 516f4baf13dec4ddddda8631e019b5737c8bc250
use_volume: true
volume_folder: my_jina
ignore_patterns: ["*.onnx"]
model_metadata:
example_model_input:
encoding_format: float
input: text string
model: model
model_name: TEI-jinaai-jina-embeddings-v2-base-code-truss-example
resources:
accelerator: L4
```
# The Model class
Source: https://docs.baseten.co/development/model/model-class
Write custom Python in model/model.py to control how your model loads, runs inference, and shapes responses.
The `Model` class in `model/model.py` is the imperative surface you reach for when `config.yaml` alone can't express your logic. It gives you a Python class with lifecycle methods (`__init__`, `load`, and `predict`) that control how your model initializes, loads weights, and handles each request. When you need custom preprocessing, postprocessing, response shaping, or want to run an architecture that Baseten's built-in engines don't support, you write that logic here.
## When to write a Model class
Most deployments don't need custom Python. If you're deploying a supported open-source model, the config-only approach in [Build your first model](/development/model/build-your-first-model) is faster. Write a custom `Model` class when you need to:
* Run a model architecture that Baseten's engines don't support.
* Add custom preprocessing or postprocessing around inference.
* Combine multiple models or libraries in a single endpoint.
* Control the HTTP response directly, including status codes and streaming.
You define this logic in a `model/model.py` file. The simplest project structure is:
```text theme={"system"}
model/
model.py
config.yaml
```
## The class skeleton
`model.py` must contain a class with three methods:
```python model.py theme={"system"}
class Model:
def __init__(self, **kwargs):
pass
def load(self):
pass
def predict(self, model_input):
return model_input
```
* `__init__` runs when the class is created. Read configuration parameters and runtime information here.
* `load` runs once at startup, before any requests. Download model weights or load them onto a GPU here. Separating this from `__init__` keeps expensive operations out of the request path.
* `predict` runs on every API request. Process input, run inference, and return the response.
`load` and `predict` don't run on the same thread, which matters for GPU workloads where state can be tied to the creating thread (such as CUDA contexts). With sync `predict` and the default `predict_concurrency` of 1, successive `predict` calls often reuse the same worker thread, but Baseten doesn't guarantee it.
### `__init__`
The `__init__` method initializes the `Model` class. Use it to read configuration parameters and runtime information.
The simplest signature accepts nothing:
```python model.py theme={"system"}
def __init__(self):
pass
```
If you need more information, define `__init__` to accept these parameters:
```python model.py theme={"system"}
def __init__(self, config: dict, data_dir: str, secrets: dict, environment: dict):
pass
```
* `config`: A dictionary containing the `config.yaml` for the model.
* `data_dir`: A string containing the path to the data directory for the model.
* `secrets`: A dictionary containing the secrets for the model. At runtime, these are populated with the actual values stored on Baseten.
* `environment`: A dictionary containing the environment for the model, if the model has been deployed to an environment. `None` otherwise.
Save these as attributes to use them elsewhere in your model:
```python model.py theme={"system"}
def __init__(self, config: dict, data_dir: str, secrets: dict, environment: dict):
self._config = config
self._data_dir = data_dir
self._secrets = secrets
self._environment = environment
```
You can also accept these through `**kwargs` and pull out only what you need:
```python model.py theme={"system"}
def __init__(self, **kwargs):
self._data_dir = kwargs["data_dir"]
self._secrets = kwargs.get("secrets")
```
### `load`
The `load` method initializes the model. This might include downloading model weights or loading them onto the GPU. Unlike the other methods, `load` accepts no parameters:
```python model.py theme={"system"}
def load(self):
pass
```
After you deploy your model, the deployment isn't considered "Ready" until `load` completes successfully. There is a **timeout of 30 minutes** for this, after which the deployment is marked as failed if `load` hasn't completed.
### `predict`
The `predict` method runs inference. The simplest signature returns a value directly:
```python model.py theme={"system"}
def predict(self, model_input) -> str:
return "Hello"
```
The return type of `predict` must be JSON-serializable, so it can be a `dict`, `list`, or `str`. See [Response objects](#response-objects) for stricter typing and direct control over the HTTP response.
#### Async vs. sync
The `predict` method is synchronous by default. If your inference depends on APIs that require `asyncio`, write `predict` as a coroutine:
```python model.py theme={"system"}
import asyncio
async def predict(self, model_input) -> dict:
# Async logic here.
await asyncio.sleep(1)
return {"value": "Hello"}
```
If you use `asyncio` in `predict`, do not perform blocking operations such as a synchronous file download. This can degrade performance.
#### Pre/post-processing
To separate I/O from inference and maximize throughput, define optional `preprocess` and `postprocess` methods alongside `predict`. Tasks like downloading images or formatting responses then run without blocking GPU or CPU execution:
```python model.py theme={"system"}
class Model:
def __init__(self, **kwargs): ...
def load(self): ...
def preprocess(self, request):
# Handle I/O before inference, such as downloading images.
...
def predict(self, request):
# Perform model inference.
...
def postprocess(self, response):
# Handle I/O after inference, such as formatting outputs.
...
```
Pre/post-processing runs in separate threads and isn't subject to Truss's concurrency limits, so I/O-heavy tasks don't bottleneck compute resources.
Truss enforces concurrency limits on `predict` to prevent GPU or CPU overload:
```yaml config.yaml theme={"system"}
runtime:
predict_concurrency: 5
```
If the model receives 10 requests with `predict_concurrency: 5`, all 10 start preprocessing concurrently, but only 5 run inference at a time. The rest wait until a slot frees up.
#### Streaming
Truss also supports streaming output incrementally instead of waiting for the full response. For the full pattern, see [Streaming output and endpoints](/development/model/streaming-and-endpoints).
## Response objects
By default, Truss wraps prediction results into an HTTP response. For advanced use cases, create response objects manually to:
* Control HTTP status codes.
* Use server-sent events (SSEs) for streaming responses.
To return a more strictly typed object than a `dict`, `list`, or `str`, return a Pydantic model:
```python model.py theme={"system"}
from pydantic import BaseModel
class Result(BaseModel):
value: str
class Model:
def predict(self, model_input) -> Result:
return Result(value="Hello")
```
To control the raw HTTP response, return any subclass of `starlette.responses.Response`:
```python model.py theme={"system"}
import fastapi
class Model:
def predict(self, inputs) -> fastapi.Response:
return fastapi.Response(...)
```
For server-sent events, return a `StreamingResponse`. See [Streaming output and endpoints](/development/model/streaming-and-endpoints) for a complete SSE example.
You can return a response from `predict` or `postprocess`, but not both. If `predict` returns a response or a generator, `postprocess` cannot be used.
Response headers aren't fully propagated. Include any metadata in the response body.
To handle raw incoming requests, see [Using request objects](/development/model/streaming-and-endpoints#request-handling).
## Bundled data
Most models need additional files at runtime, such as weights, tokenizers, configs, or reference datasets. For local files under \~1 GB total, bundle them in your Truss's `data/` directory. The contents are copied into your container image at build time and mounted at `/app/data` at runtime.
Access them from `model.py` through `kwargs["data_dir"]`:
```python model.py theme={"system"}
class Model:
def __init__(self, **kwargs):
self._data_dir = kwargs["data_dir"]
def load(self):
self.tokenizer = AutoTokenizer.from_pretrained(str(self._data_dir))
```
A bundled Truss might lay its `data/` directory out like this Stable Diffusion 2.1 example:
```text theme={"system"}
data/
scheduler/
scheduler_config.json
text_encoder/
config.json
diffusion_pytorch_model.bin
tokenizer/
merges.txt
tokenizer_config.json
vocab.json
unet/
config.json
diffusion_pytorch_model.bin
vae/
config.json
diffusion_pytorch_model.bin
model_index.json
```
Use the `data/` directory only when it's under \~1 GB total. The files ship inside the container image, so every cold start re-pulls them, not just the first deploy. Larger bundles compound into slower scale-ups, and `truss push` itself slows down as the bundle grows.
For larger weights or remote sources (Hugging Face, S3, GCS, R2), use the [Baseten Delivery Network (BDN)](/development/model/bdn) instead. BDN mirrors weights once and serves them from caches close to your replicas, so cold starts read from local or nearby caches instead of pulling from the source on every scale-up.
### Download files at runtime
Use this pattern when you need fine-grained control over the download, such as decrypting files on the fly or lazily fetching a subset of a larger dataset. The example below loads weights from a private S3 bucket using `boto3`.
To load private S3 weights at deploy time, prefer [BDN with IAM credentials](/development/model/bdn#quick-start-with-iam-credentials). BDN mirrors the weights once and serves them from a multi-tier cache; the pattern below re-downloads on every cold start unless you add caching.
Define AWS secrets in `config.yaml`:
```yaml config.yaml theme={"system"}
secrets:
aws_access_key_id: null
aws_secret_access_key: null
aws_region: null # for example, us-east-1
aws_bucket: null
```
Do not store actual credentials in `config.yaml`. Add them securely to the [Baseten secrets manager](https://app.baseten.co/settings/secrets).
Authenticate with AWS in `model.py`, then deploy with `truss push --watch`:
```python model.py theme={"system"}
import boto3
class Model:
def __init__(self, **kwargs):
self._config = kwargs.get("config")
secrets = kwargs.get("secrets")
self.s3_client = boto3.client(
"s3",
aws_access_key_id=secrets["aws_access_key_id"],
aws_secret_access_key=secrets["aws_secret_access_key"],
region_name=secrets["aws_region"],
)
self.s3_bucket = secrets["aws_bucket"]
```
If your model downloads weights at runtime using custom code, [BDN proxy](/development/model/bdn#bdn-proxy) can cache those downloads across replicas. Available by request.
## Next steps
* [HTTP endpoints](/development/model/streaming-and-endpoints#v1-endpoints): Add `chat_completions`, `completions`, `embeddings`, `messages`, or `responses` to serve matching `/v1/*` routes.
* [Streaming output and endpoints](/development/model/streaming-and-endpoints): Return generated output incrementally.
* [Custom health checks](/development/model/health-checks): Define readiness and liveness behavior.
* [Configuration](/development/model/configuration): Full reference for `config.yaml` options.
* [Model weights](/development/model/bdn): Fetch large weights through BDN instead of bundling them, and cache runtime-written files with [runtime caching](/development/model/runtime-caching).
# Develop a model on Baseten
Source: https://docs.baseten.co/development/model/overview
Package, configure, and iterate on a model with Truss at whatever level of control your model needs.
This section covers building and deploying your own models on dedicated infrastructure. You package a model with [Truss](https://github.com/basetenlabs/truss), our open-source CLI, then push it to Baseten for deployment, autoscaling, and observability. For hosted open-source models with no deployment step, see [Model APIs](/inference/model-apis/overview) in the Inference section.
## How you develop a model
You work with a Truss at increasing levels of control, and you only go as deep as your model requires:
* **The CLI** runs the loop you live in. `truss push --watch` creates a development deployment, `truss watch` live-patches your changes in seconds, and `truss push --promote` ships to production. See [The development loop](/development/model/deploy-and-iterate).
* **`config.yaml`** declares your runtime: GPU, dependencies, base image, and weights. Most popular open-source LLMs deploy from config alone, served on TensorRT-LLM with an OpenAI-compatible API and no code required. See [Configuration](/development/model/configuration) and [Dependencies](/development/model/dependencies).
* **`model/model.py`** holds custom code. Write a Python `Model` class with `load` and `predict` when configuration can't express your logic, such as custom preprocessing, postprocessing, or an unsupported architecture. See [The Model class](/development/model/model-class).
If none of these fit, for example you bring a pre-built container like vLLM, SGLang, or Triton, drop to a [custom Docker server](/development/model/custom-server).
## Pick a starting point
* **Config-only:** Deploy a model from a single `config.yaml`. Start with [Build your first model](/development/model/build-your-first-model).
* **Custom Python:** Write a `Model` class with `__init__`, `load`, and `predict`. Start with [The Model class](/development/model/model-class).
* **Custom Docker:** Bring your own container. See [Custom Docker servers](/development/model/custom-server).
## The development cycle
Whichever surface you use, the iteration workflow is the same: push a development deployment, make changes with live reload, and publish when you're ready for production traffic.
1. **Push to development.** Run `truss push --watch` to create a development deployment, a single-replica instance with live reload enabled, designed for fast iteration rather than production traffic.
2. **Iterate with live reload.** Run `truss watch` to start a file watcher that syncs local changes to your development deployment in seconds, without rebuilding the container. Edit, save, and see the result in the deployment logs.
3. **Publish to production.** Run `truss push` to create an immutable, production-ready deployment with full autoscaling. Promote it to an [environment](/deployment/environments) for a stable endpoint URL that doesn't change between versions.
Development deployments run slightly slower than published deployments and are limited to one replica. They exist to give you a fast feedback loop, not to serve real traffic. See [The development loop](/development/model/deploy-and-iterate) for the full workflow.
## Build multi-model systems
When your workflow spans multiple models or steps that need different hardware, like a RAG pipeline with separate retrieval and generation stages, orchestrate them with [Chains](/development/chain/overview). Each step runs on its own hardware with its own scaling rules. Many projects start with a single self-deployed model and wrap it in a Chain as the system grows.
# Performance optimization
Source: https://docs.baseten.co/development/model/performance-optimization
Optimize model latency, throughput, and cost with Baseten engines
Model performance means optimizing every layer of your model serving infrastructure to balance four goals:
* **Latency**: How quickly does each user get output from the model?
* **Throughput**: How many requests can the deployment handle at once?
* **Cost**: How much does a standardized unit of work cost?
* **Quality**: Does your model consistently deliver high-quality output after optimization?
## Performance engines
Baseten provides three managed inference engines. Pick the one that matches your model architecture:
### [Engine-Builder-LLM](/engines/engine-builder-llm/overview): dense models
* **Best for**: Llama, Mistral, Qwen, and other causal language models.
* **Features**: TensorRT-LLM optimization, lookahead decoding, quantization.
* **Performance**: Tuned for low-latency, high-throughput dense LLM inference.
### [BIS-LLM](/engines/bis-llm/overview): MoE models
* **Best for**: DeepSeek, Mixtral, and other mixture-of-experts models.
* **Features**: V2 inference stack, expert routing, structured outputs.
* **Performance**: Tuned for large-scale MoE inference.
### [BEI](/engines/bei/overview): embedding models
* **Best for**: Sentence transformers, rerankers, classification models.
* **Features**: OpenAI-compatible API, optimized batching.
* **Performance**: Tuned for high-throughput embedding inference.
## Performance concepts
Detailed optimization guides live in the [performance concepts](/engines/performance-concepts/quantization-guide) section:
* [Quantization guide](/engines/performance-concepts/quantization-guide): FP8 and FP4 trade-offs and hardware requirements.
* [Structured outputs](/inference/structured-outputs): JSON schema validation and controlled generation.
* [Function calling](/inference/function-calling): tool use and function selection.
* [Performance client](/inference/performance-client): high-throughput client library.
* [Deploy from cloud storage](/engines/performance-concepts/cloud-storage-deployment): GCS, S3, and Azure with Engine-Builder-LLM.
* [Deploy with inference engines](/training/deploy-with-engine-builder): Baseten Training checkpoints with TRT-LLM.
## Quick performance wins
### Quantization
Reduce weight memory and improve throughput with post-training quantization:
```yaml config.yaml theme={"system"}
trt_llm:
build:
quantization_type: fp8 # FP8 weights, 16-bit KV cache
```
See the [quantization guide](/engines/performance-concepts/quantization-guide) for all supported modes (`fp8`, `fp8_kv`, `fp4`, `fp4_kv`, `fp4_mlp_only`).
### Lookahead decoding
Accelerate inference for predictable content like code or JSON:
```yaml config.yaml theme={"system"}
trt_llm:
build:
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
lookahead_windows_size: 3
```
### Performance client
Use the Rust-based client for high-throughput batched requests:
```bash Terminal theme={"system"}
uv pip install baseten-performance-client
```
## Where to start
1. **Choose your engine**: [Engine selection](/engines)
2. **Configure your model**: Engine-specific configuration guides
3. **Optimize performance**: [Performance concepts](/engines/performance-concepts/quantization-guide)
4. **Deploy and monitor**: Use [performance client](/inference/performance-client) for maximum throughput
Start with the default engine configuration, then apply quantization and other optimizations based on your specific performance requirements.
# Runtime caching
Source: https://docs.baseten.co/development/model/runtime-caching
Cache files your model writes at runtime so other replicas reuse them
b10cache stores files your model writes at runtime, such as `torch.compile` artifacts, so other replicas and deployments can reuse them. It's the supported path for runtime-written files that benefit from sharing. For read-only weights known at deploy time, use [BDN](/development/model/bdn) instead.
## How b10cache works
Deployments sometimes produce files that are useful to other replicas. Using `torch.compile`, for example, produces a cache that can speed up future `torch.compile` calls on the same function, reducing cold start time for other replicas.
b10cache stores these files. It's a volume mounted over the network onto each of your replicas, with two scopes:
### Organization scope: `/cache/org/`
Shared across every replica you deploy in your organization. Move a file into this directory and any replica can read it.
### Deployment scope: `/cache/model/`
Shared across every replica within a single deployment. Use this scope to keep deployment filesystems isolated.
### Not persistent object storage
b10cache is reliable, but treat it as a cache, not a database. Always have a fallback path that runs if the file isn't there yet. For example, the first replica of a new deployment writes to b10cache rather than reading from it.
## Torch compile caching
PyTorch's `torch.compile` can cut inference time by up to 40%, but compiling the model adds latency to cold starts: it must compile before serving its first request.
This overhead compounds in production, where:
* Models scale up and down with demand.
* New replicas spawn to handle traffic spikes.
* Each new replica repeats the compilation from scratch.
Torch compile caching persists compilation artifacts across deployments and replica restarts in b10cache, so a new replica loads them instead of recompiling. The library handles large scale-ups, managing race conditions and staying fault-tolerant on the shared cache.
In practice, this strategy reduces compilation latencies to roughly 5 to 20 seconds, depending on the model.
### Implementation options
There are two different deployment patterns that benefit from torch compile caching:
* **Truss models**: a `model.py` that calls `torch.compile`. See [Truss models](#truss-models-model-py).
* **vLLM servers**: a vLLM custom server. See [vLLM servers](#vllm-servers-cli-tool).
### Truss models (`model.py`)
#### API reference
We expose two API calls that return an `OperationStatus` object to help you control program flow based on the result.
If you have previously saved compilation cache for this model, load it to speed up the compilation for the model on this replica.
**Returns:**
* `OperationStatus.SUCCESS` → successful load
* `OperationStatus.SKIPPED` → if torch compilation artifacts already exist on the replica
* `OperationStatus.ERROR` → general catch-all errors
* `OperationStatus.DOES_NOT_EXIST` → if no cache file was found
Save your model's torch compilation cache for future use. This should be called after running prompts to warm up your model and trigger compilation.
**Returns:**
* `OperationStatus.SUCCESS` → successful save
* `OperationStatus.SKIPPED` → skipped because compile cache already exists in shared directory
* `OperationStatus.ERROR` → general catch-all errors
#### Implementation example
Here is an example of compile caching for Flux, an image generation model. Note how we save the result of `load_compile_cache` to inform on whether to `save_compile_cache`.
##### Update `config.yaml`
Under requirements, add `b10-transfer`:
```yaml config.yaml theme={"system"}
requirements:
- b10-transfer
```
##### Update `model.py`
Import the library and use the two functions to speed up torch compilation time:
```python model.py theme={"system"}
from b10_transfer import load_compile_cache, save_compile_cache, OperationStatus
class Model:
def load(self):
self.pipe = FluxPipeline.from_pretrained(
self.model_name, torch_dtype=torch.bfloat16, token=self.hf_access_token
).to("cuda")
# Try to load compile cache
cache_loaded: OperationStatus = load_compile_cache()
if cache_loaded == OperationStatus.ERROR:
logging.info("Run in eager mode, skipping torch compile")
else:
logging.info("Compiling the model for performance optimization")
self.pipe.transformer = torch.compile(
self.pipe.transformer, mode="max-autotune-no-cudagraphs", dynamic=False
)
self.pipe.vae.decode = torch.compile(
self.pipe.vae.decode, mode="max-autotune-no-cudagraphs", dynamic=False
)
seed = random.randint(0, MAX_SEED)
generator = torch.Generator().manual_seed(seed)
start_time = time.time()
# Warmup the model with dummy prompts, also triggering compilation
self.pipe(
prompt="dummy prompt",
prompt_2=None,
guidance_scale=0.0,
max_sequence_length=256,
num_inference_steps=4,
width=1024,
height=1024,
output_type="pil",
generator=generator
)
end_time = time.time()
logging.info(
f"Warmup completed in {(end_time - start_time)} seconds. "
"This is expected to take a few minutes on the first run."
)
if cache_loaded != OperationStatus.SUCCESS:
# Save compile cache for future runs
outcome: OperationStatus = save_compile_cache()
```
See the [full example](https://github.com/basetenlabs/truss-examples/tree/main/flux/schnell).
### vLLM servers (CLI tool)
Use this whenever you enable compile options with vLLM (compiling is the default on vLLM V1). The CLI tool runs automatically: it loads the compile cache if you've saved one before, and saves it otherwise.
Make two changes in `config.yaml`:
#### Add requirements
Under requirements, add `b10-transfer`:
```yaml config.yaml theme={"system"}
requirements:
- b10-transfer
```
#### Update start command
Under start command, add `b10-compile-cache &` right before the `vllm serve` call:
```yaml config.yaml theme={"system"}
start_command: "... b10-compile-cache & vllm serve ..."
```
See the [full example](https://github.com/basetenlabs/truss-examples/tree/main/mistral/mistral-small-3.1).
### Advanced configuration
The torch compile caching library supports several environment variables for fine-tuning behavior in production environments:
#### Cache directory configuration
**`TORCHINDUCTOR_CACHE_DIR`** (optional)
* **Default**: `/tmp/torchinductor_`
* **Description**: Directory where PyTorch stores compilation artifacts locally
* **Allowed prefixes**: `/tmp/`, `/cache/`, `~/.cache`
* **Usage**: Set this if you need to customize where torch compilation artifacts are stored on the local filesystem
**`B10FS_CACHE_DIR`** (optional)
* **Default**: Derived from b10cache mount point + `/compile_cache`
* **Description**: Directory in b10cache where compilation artifacts are persisted across deployments
* **Usage**: Typically doesn't need to be changed as it's automatically configured based on your b10cache setup
**`LOCAL_WORK_DIR`** (optional)
* **Default**: `/app`
* **Description**: Local working directory for temporary operations
* **Allowed prefixes**: `/app/`, `/tmp/`, `/cache/`
#### Performance and resource limits
**`MAX_CACHE_SIZE_MB`** (optional)
* **Default**: `1024` (1GB)
* **Cap**: Limited by `MAX_CACHE_SIZE_CAP_MB` for safety
* **Description**: Maximum size of a single cache archive in megabytes
* **Usage**: Increase for larger models with extensive compilation artifacts, decrease to save storage
**`MAX_CONCURRENT_SAVES`** (optional)
* **Default**: `50`
* **Cap**: Limited by `MAX_CONCURRENT_SAVES_CAP` for safety
* **Description**: Maximum number of concurrent save operations allowed
* **Usage**: Tune based on your deployment's concurrency requirements and storage performance
#### Cleanup and maintenance
**`CLEANUP_LOCK_TIMEOUT_SECONDS`** (optional)
* **Default**: `30`
* **Cap**: Limited by `LOCK_TIMEOUT_CAP_SECONDS`
* **Description**: Timeout for cleaning up stale lock files, to prevent deadlocks. They may occur when a replica holding the lock crashes.
* **Usage**: Decrease if you're experiencing deadlocks in high-load scenarios
**`CLEANUP_INCOMPLETE_TIMEOUT_SECONDS`** (optional)
* **Default**: `60`
* **Cap**: Limited by `INCOMPLETE_TIMEOUT_CAP_SECONDS`
* **Description**: Timeout for cleaning up incomplete cache files
* **Usage**: Increase for slower storage systems or larger cache files
#### Example configuration
```yaml config.yaml theme={"system"}
environment_variables:
MAX_CACHE_SIZE_MB: "2048"
MAX_CONCURRENT_SAVES: "25"
CLEANUP_LOCK_TIMEOUT_SECONDS: "45"
```
The defaults suit most workloads. Tune them if a model needs a larger cache archive or hits contention on concurrent saves.
To understand implementation details, read the [PyTorch torch compile caching tutorial](https://docs.pytorch.org/tutorials/recipes/torch_compile_caching_tutorial.html).
## Next steps
Cache read-only weights known at deploy time
Reduce latency and cold starts across your deployment
# Secrets
Source: https://docs.baseten.co/development/model/secrets
Use secrets securely in your models
Truss manages API keys, access tokens, passwords, and other secrets so you don't have to expose them in code.
## Create a secret
**To create a secret**:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and go to [**Secrets**](https://app.baseten.co/settings/secrets) in your workspace settings.
2. Enter a name for the secret, for example `hf_access_token`.
3. Enter the secret value.
4. Choose **Add secret**.
**To create a secret**:
```bash Request theme={"system"}
curl --request POST \
--url https://api.baseten.co/v1/secrets \
--header "Authorization: Bearer $BASETEN_API_KEY" \
--data '{
"name": "hf_access_token",
"value": "hf_..."
}'
```
```json Response theme={"system"}
{
"id": "3kZ9xqd",
"created_at": "2026-07-10T00:00:00Z",
"name": "hf_access_token",
"team_name": "My team"
}
```
For more information, see [Upsert a secret](/reference/management-api/secrets/upserts-a-secret).
Secrets named `DOCKER_REGISTRY_` authenticate image pulls from [private registries](/development/model/dependencies#private-registries). Their value must be the Base64 encoding of `username:password`; Baseten validates this when you save the secret.
## Use secrets in your model
Once you've created a secret, declare it in your `config.yaml` and access it in your model code.
Never store actual secret values in `config.yaml`. Use `null` as a placeholder.
The secret in your `config.yaml` is a reference to the key in the secret manager.
Specify the reference to the secret in `config.yaml`:
```yaml config.yaml theme={"system"}
secrets:
hf_access_token: null
```
Secrets are passed as keyword arguments to the `Model` class. To access them, store the secrets in `__init__`:
```python model/model.py theme={"system"}
def __init__(self, **kwargs):
self._secrets = kwargs["secrets"]
```
Then use the secret in your model's `load` or `predict` method by accessing it with the key:
```python model/model.py theme={"system"}
def load(self):
self._model = pipeline(
"fill-mask",
model="baseten/docs-example-gated-model",
use_auth_token=self._secrets["hf_access_token"]
)
```
This pattern works when your `model.py` downloads the weights itself. To authenticate weights loaded through the [Baseten Delivery Network](/development/model/bdn) (the `weights:` config), reference the secret from the per-source [`auth`](/development/model/bdn#param-auth) block instead. A `secrets:` entry alone does not authenticate weight mirroring.
## Use secrets in custom Docker images
When using [custom Docker images](/development/model/custom-server), Truss
injects secrets into your container at `/secrets/{secret_name}` instead of
passing them through `kwargs`.
You must specify the reference to the secret and then access it in your `start_command` or application code.
Specify the reference to the secret in `config.yaml`:
```yaml config.yaml theme={"system"}
secrets:
hf_access_token: null
```
### Read secrets in your `start_command`
To read a secret in your `start_command`:
```yaml config.yaml theme={"system"}
docker_server:
start_command: sh -c "HF_TOKEN=$(cat /secrets/hf_access_token) my-server --port 8000"
```
### Read secrets in application code
To read a secret in application code:
```python model/model.py theme={"system"}
with open("/secrets/hf_access_token", "r") as f:
hf_token = f.read().strip()
```
# Streaming and endpoints
Source: https://docs.baseten.co/development/model/streaming-and-endpoints
Stream model output, expose /v1 HTTP endpoints, and handle raw requests in custom Truss model code.
Clients reach your custom model through the server's HTTP routes. A standard Truss model serves `POST /predict` with arbitrary JSON, and your `predict` method can return a single JSON response or a generator that streams output as it's produced. You can also expose OpenAI- and Anthropic-style `/v1` endpoints by implementing the matching methods, and access the raw request object when you need to customize deserialization or cancel long-running predictions.
## Streaming
Streaming returns results as they're generated instead of waiting for the full response, which cuts wait time for generative models.
* **Faster response time:** Get initial results in under 1 second instead of waiting 10 or more seconds.
* **Improved user experience:** Partial outputs are immediately usable.
To stream, return a generator from `predict` that yields chunks as they're produced. The following sections walk through deploying Falcon 7B with streaming enabled.
### Initialize Truss
Create a new Truss for the model:
```sh Terminal theme={"system"}
truss init falcon-7b && cd falcon-7b
```
### Implement the model without streaming
This first version loads the Falcon 7B model without streaming:
```python model/model.py theme={"system"}
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig
from typing import Dict
CHECKPOINT = "tiiuae/falcon-7b-instruct"
DEFAULT_MAX_NEW_TOKENS = 150
DEFAULT_TOP_P = 0.95
class Model:
def __init__(self, **kwargs) -> None:
self.tokenizer = None
self.model = None
def load(self):
self.tokenizer = AutoTokenizer.from_pretrained(CHECKPOINT)
self.model = AutoModelForCausalLM.from_pretrained(
CHECKPOINT, torch_dtype=torch.bfloat16, trust_remote_code=True, device_map="auto"
)
def predict(self, request: Dict) -> Dict:
prompt = request["prompt"]
inputs = self.tokenizer(prompt, return_tensors="pt", max_length=512, truncation=True, padding=True)
input_ids = inputs["input_ids"].to("cuda")
generation_config = GenerationConfig(temperature=1, top_p=DEFAULT_TOP_P, top_k=40)
with torch.no_grad():
return self.model.generate(
input_ids=input_ids,
generation_config=generation_config,
return_dict_in_generate=True,
output_scores=True,
pad_token_id=self.tokenizer.eos_token_id,
max_new_tokens=DEFAULT_MAX_NEW_TOKENS,
)
```
### Add streaming support
To enable streaming:
* Use `TextIteratorStreamer` to stream tokens as they're generated.
* Run `generate()` in a separate thread to prevent blocking.
* Return a generator that streams results.
```python model/model.py theme={"system"}
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig, TextIteratorStreamer
from threading import Thread
from typing import Dict
CHECKPOINT = "tiiuae/falcon-7b-instruct"
class Model:
def __init__(self, **kwargs) -> None:
self.tokenizer = None
self.model = None
def load(self):
self.tokenizer = AutoTokenizer.from_pretrained(CHECKPOINT)
self.model = AutoModelForCausalLM.from_pretrained(
CHECKPOINT, torch_dtype=torch.bfloat16, trust_remote_code=True, device_map="auto"
)
def predict(self, request: Dict):
prompt = request["prompt"]
inputs = self.tokenizer(prompt, return_tensors="pt", max_length=512, truncation=True, padding=True)
input_ids = inputs["input_ids"].to("cuda")
streamer = TextIteratorStreamer(self.tokenizer)
generation_config = GenerationConfig(temperature=1, top_p=0.95, top_k=40)
def generate():
self.model.generate(
input_ids=input_ids,
generation_config=generation_config,
return_dict_in_generate=True,
output_scores=True,
pad_token_id=self.tokenizer.eos_token_id,
max_new_tokens=150,
streamer=streamer,
)
thread = Thread(target=generate)
thread.start()
def stream_output():
for text in streamer:
yield text
thread.join()
return stream_output()
```
### Configure `config.yaml`
```yaml config.yaml theme={"system"}
model_name: falcon-streaming
requirements:
- torch==2.0.1
- peft==0.4.0
- scipy==1.11.1
- sentencepiece==0.1.99
- accelerate==0.21.0
- bitsandbytes==0.41.1
- einops==0.6.1
- transformers==4.31.0
resources:
cpu: "4"
memory: 16Gi
use_gpu: true
accelerator: L4
```
### Deploy and invoke
Deploy the model:
```sh Terminal theme={"system"}
truss push --watch
```
Invoke with:
```sh Terminal theme={"system"}
truss predict -d '{"prompt": "Tell me about falcons", "do_sample": true}'
```
## /v1 endpoints
Custom Truss models normally serve `POST /predict` with arbitrary JSON. To also support additional HTTP routes, define the matching methods on your `Model` class. Use these methods when you want custom Python logic but still want clients to call your model through the server's built-in HTTP endpoints.
If you deploy a custom Docker container, Baseten can forward requests to any route exposed by the underlying server. See [Custom Docker containers](/development/model/custom-server).
### Which method to implement
| Method | Endpoint | Use it for |
| ------------------ | ---------------------- | ------------------------------------------------------------- |
| `chat_completions` | `/v1/chat/completions` | Chat-style payloads with a `messages` array. |
| `completions` | `/v1/completions` | Prompt-style payloads with a `prompt` field. |
| `embeddings` | `/v1/embeddings` | Embedding requests from text or token inputs. |
| `messages` | `/v1/messages` | Server-specific message payloads exposed by your deployment. |
| `responses` | `/v1/responses` | Server-specific response payloads exposed by your deployment. |
Implement any subset of these methods, depending on the interface you want to expose.
### API families
| Endpoint | Family |
| ---------------------- | ----------------------------- |
| `/v1/chat/completions` | OpenAI-style chat completions |
| `/v1/completions` | OpenAI-style text completions |
| `/v1/embeddings` | OpenAI-style embeddings |
| `/v1/responses` | OpenAI-style responses |
| `/v1/messages` | Anthropic-style messages |
This page uses HTTP endpoints as the umbrella term because Truss can expose endpoints from more than one API family.
### chat\_completions
Implement `chat_completions` when your model should accept chat requests.
```python model/model.py theme={"system"}
from typing import Any, Dict
class Model:
def __init__(self, **kwargs):
pass
def load(self):
pass
async def predict(self, model_input: Dict[str, Any]):
return {"output": model_input}
async def chat_completions(self, model_input: Dict[str, Any], request):
# Reuse your main inference path so /predict and /v1/chat/completions stay aligned.
return await self.predict(model_input)
```
The request body follows the chat schema, so `model_input` typically includes fields like:
* `messages`
* `model`
* `stream`
* sampling parameters such as `temperature` and `max_tokens`
If you already have a `predict` method that handles the same payload shape, `chat_completions` can simply delegate to it.
### completions
Implement `completions` when your model should accept prompt-style completion requests.
```python model/model.py theme={"system"}
from typing import Any, Dict
class Model:
def __init__(self, **kwargs):
pass
def load(self):
pass
async def completions(self, model_input: Dict[str, Any], request):
prompt = model_input["prompt"]
return {
"id": "cmpl-example",
"object": "text_completion",
"choices": [
{
"index": 0,
"text": f"You sent: {prompt}",
"finish_reason": "stop",
}
],
}
```
Use `completions` for workloads such as autocomplete, prompt continuation, or fine-tuned models that are designed to extend text instead of following chat-style instructions.
### embeddings, messages, and responses
Implement `embeddings`, `messages`, or `responses` when your deployment should expose those HTTP endpoints from custom model code.
```python model/model.py theme={"system"}
from typing import Any, Dict
class Model:
def __init__(self, **kwargs):
pass
def load(self):
pass
def embeddings(self, model_input: Dict[str, Any], request):
return {"output": "embeddings"}
def messages(self, model_input: Dict[str, Any], request):
return {"output": "messages"}
def responses(self, model_input: Dict[str, Any], request):
return {"output": "responses"}
```
These methods are forwarded directly to the matching `/v1/*` route, so your implementation can return whatever JSON shape that endpoint expects.
`messages` maps to the Anthropic-style `/v1/messages` route. `embeddings` and `responses` map to OpenAI-style `/v1/embeddings` and `/v1/responses` routes.
### Request and response expectations
* These methods receive the parsed JSON payload as `model_input`.
* If you include a second argument annotated as `fastapi.Request`, you can inspect disconnects or request metadata just like in `predict`. See [Request handling](#request-handling).
* Return JSON that matches the endpoint you expose. Baseten does not automatically convert an arbitrary `predict` response into a different response object for custom model code.
### Endpoint paths
When these methods are defined, your deployment serves the matching HTTP routes in addition to `/predict`.
```text theme={"system"}
/environments/{env}/sync/v1/chat/completions
/environments/{env}/sync/v1/completions
/environments/{env}/sync/v1/embeddings
/environments/{env}/sync/v1/messages
/environments/{env}/sync/v1/responses
```
For production, replace `{env}` with `production`. For development deployments, use `development`.
## Request handling
Truss extracts and validates payloads for you. Access the raw request object when you need to:
* Customize payload deserialization, for example binary protocol buffers.
* Handle disconnections and cancel long-running predictions.
You can mix request objects with standard inputs, or use only the request.
### Use request objects in Truss
You can define request objects in `preprocess`, `predict`, and `postprocess`:
```python model/model.py theme={"system"}
import fastapi
class Model:
def preprocess(self, request: fastapi.Request):
...
def predict(self, inputs, request: fastapi.Request):
...
def postprocess(self, inputs, request: fastapi.Request):
...
```
### Rules for using requests
* The request must be type-annotated as `fastapi.Request`.
* If you use only the request, Truss skips payload extraction for better performance.
* If you use both the request and standard inputs:
* The request must be the second argument.
* Preprocessing transforms the inputs, but the request object stays unchanged.
* `postprocess` can't take only the request; it must receive the model's output.
* If `predict` uses only the request, you can't use `preprocess`.
The following example streams output while checking for client disconnects, returning early to cancel the prediction:
```python model/model.py theme={"system"}
import fastapi, asyncio, logging
class Model:
async def predict(self, inputs, request: fastapi.Request):
await asyncio.sleep(1)
if await request.is_disconnected():
logging.warning("Cancelled before generation.")
return # Cancel request on the model engine here.
for i in range(5):
await asyncio.sleep(1.0)
logging.warning(i)
yield str(i) # Streaming response
if await request.is_disconnected():
logging.warning("Cancelled during generation.")
return # Cancel request on the model engine here.
```
You must implement request cancellation at the model level, which varies by framework.
### Cancel requests in specific frameworks
#### TRT-LLM (polling-based cancellation)
For TensorRT-LLM, use `response_iterator.cancel()` to terminate streaming requests:
```python model/model.py theme={"system"}
async for request_output in response_iterator:
if await is_cancelled_fn():
logging.info("Request cancelled. Cancelling Triton request.")
response_iterator.cancel()
return
```
See full example in [TensorRT-LLM Docs](https://developer.nvidia.com/tensorrt-llm).
#### vLLM (abort API)
For vLLM, use `engine.abort()` to stop processing:
```python model/model.py theme={"system"}
async for request_output in results_generator:
if await request.is_disconnected():
await engine.abort(request_id)
return
```
See full example in [vLLM Docs](https://docs.vllm.ai/en/latest/dev/engine/async_llm_engine.html#vllm.AsyncLLMEngine.generate).
### Unsupported request features
* **Streaming file uploads**: Use URLs instead of embedding large data in the request.
* **Client-side headers**: Most headers are stripped; include necessary metadata in the payload.
## Next steps
* [The Model class](/development/model/model-class): Write the `predict`, `chat_completions`, and request-handling methods these endpoints call.
* [Custom Docker servers](/development/model/custom-server): Forward requests to any route your own container exposes.
# WebSockets
Source: https://docs.baseten.co/development/model/websockets
Enable real-time, streaming, bidirectional communication using WebSockets for Truss models and Chains.
WebSockets provide a persistent, full-duplex communication channel between clients and server-side models or chains. Full duplex means chunks of data can flow client→server and server→client simultaneously and repeatedly, without reopening the connection.
Use cases include real-time audio transcription, AI phone calls, and agents with turn-based interactions. WebSockets are also a fit when you need server-side state: requests in the same session always route to the replica holding that state.
## WebSockets in Truss models
A Truss WebSocket model implements a single `websocket` method in place of the usual `preprocess`, `predict`, and `postprocess` methods. All input and output flows through the WebSocket object itself, not through arguments or return values. `load` still works as it does for HTTP models.
**To build a WebSocket model**:
1. Initialize your Truss:
```bash Terminal theme={"system"}
truss init websocket-model
```
2. Replace the `predict` method in `model/model.py` with a `websocket` method. For example:
```python model/model.py theme={"system"}
import fastapi
class Model:
async def websocket(self, websocket: fastapi.WebSocket):
try:
while True:
message = await websocket.receive_text()
await websocket.send_text(f"WS obtained: {message}")
except fastapi.WebSocketDisconnect:
pass
```
3. Set `runtime.transport.kind=websocket` in `config.yaml`:
```yaml config.yaml theme={"system"}
...
runtime:
transport:
kind: websocket
```
4. Deploy the model:
```bash Terminal theme={"system"}
truss push
```
This creates a published deployment. For live-reload during development, use `truss push --watch`.
For more information, see [`truss init`](/reference/cli/truss/init) and [`truss push`](/reference/cli/truss/push).
### Constraints and behavior
* Message exchange runs in a loop until the client disconnects. To close the connection from the server, call `websocket.close()`.
* WebSockets support bidirectional streaming, so you don't need multiple HTTP round-trips.
* Don't implement `predict`, `preprocess`, or `postprocess`. Baseten doesn't call them.
* Baseten accepts the connection for you, so don't call `websocket.accept()`. You can close the connection yourself when you're done; otherwise Baseten closes it after your `websocket` method returns.
### Call the model
Use [websocat](https://github.com/vi/websocat) to call the model:
```bash Terminal theme={"system"}
websocat -H="Authorization: Bearer $BASETEN_API_KEY" \
wss://model-{MODEL_ID}.api.baseten.co/environments/production/websocket
Hello # Your input.
WS obtained: Hello # Echoed from model.
# ctrl+c to close connection.
```
The path depends on the environment or deployment you're calling:
* **Environment:** `wss://model-{MODEL_ID}.api.baseten.co/environments/{ENVIRONMENT_NAME}/websocket`
* **Deployment:** `wss://model-{MODEL_ID}.api.baseten.co/deployment/{DEPLOYMENT_ID}/websocket`
* **Regional environment:** `wss://model-{MODEL_ID}-{ENV_NAME}.api.baseten.co/websocket`. See [Regional environments](/deployment/environments#regional-environments).
See the [WebSocket endpoint reference](/reference/inference-api/predict-endpoints/environments-websocket) for full details.
## WebSockets in Chains
Chains wrap WebSockets in a reduced `WebSocketProtocol` object. Processing happens in `run_remote` as usual, but inputs and outputs both flow through the WebSocket itself using async `send_*` and `receive_*` methods (`text`, `bytes`, and `json` variants). A convenience `receive` method handles both `str` and `bytes`.
### Example chainlet
```python chainlet.py theme={"system"}
import fastapi
import truss_chains as chains
class Dependency(chains.ChainletBase):
async def run_remote(self, name: str) -> str:
return f"Hello from dependency, {name}."
@chains.mark_entrypoint
class WSEntrypoint(chains.ChainletBase):
def __init__(self, dependency=chains.depends(Dependency)):
self._dependency = dependency
async def run_remote(self, websocket: chains.WebSocketProtocol) -> None:
try:
while True:
message = await websocket.receive_text()
if message == "dep":
response = await self._dependency.run_remote("WSEntrypoint")
else:
response = f"You said: {message}"
await websocket.send_text(response)
except fastapi.WebSocketDisconnect:
print("Disconnected.")
```
### Constraints and behavior
* Your `run_remote` signature must use `WebSocketProtocol`. It mirrors `fastapi.WebSocket`, except you can't call `accept()`. Baseten has already accepted the connection by the time your chainlet runs.
* `run_remote` accepts no other arguments when using WebSockets.
* The return type must be `None`. Send any data back to the client through the WebSocket instead.
* WebSockets are only supported on the *entrypoint* chainlet, not on dependencies.
* Unlike Truss models, Chains don't require you to set `runtime.transport.kind`.
### Call the chain
Use [websocat](https://github.com/vi/websocat) to call the chain:
```bash Terminal theme={"system"}
websocat -H="Authorization: Bearer $BASETEN_API_KEY" \
wss://chain-{CHAIN_ID}.api.baseten.co/environments/production/websocket
```
Like models, chains accept WebSocket connections on either a deployment or environment path. For regional environments, use `wss://chain-{CHAIN_ID}-{ENV_NAME}.api.baseten.co/websocket`. See [Regional environments](/deployment/environments#regional-environments).
See the [WebSocket endpoint reference](/reference/inference-api/predict-endpoints/environments-websocket) for full details.
## WebSockets with custom servers
Deploy a WebSocket server from a custom Docker image using the [`docker_server`](/development/model/custom-server) configuration. This fits when you already have a WebSocket server packaged as a container, or when you need a runtime Baseten's managed images don't provide.
### Configuration
Set the following in `config.yaml`:
```yaml config.yaml theme={"system"}
base_image:
image: bryanzhang2/custom_ws:v0.0.4
docker_server:
start_command: /app/server
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/websocket
server_port: 8081
model_name: custom_ws
runtime:
transport:
kind: "websocket"
```
### Required fields
* `predict_endpoint`: The WebSocket endpoint path on your server, for example `/v1/websocket` or `/ws`.
* `runtime.transport.kind`: Must be `"websocket"`.
* `start_command`: Command that starts your WebSocket server.
* `readiness_endpoint`: HTTP path for readiness probes.
* `liveness_endpoint`: HTTP path for liveness probes.
### Call the model
Use [websocat](https://github.com/vi/websocat) to connect to your custom server:
```bash Terminal theme={"system"}
websocat -H="Authorization: Bearer $BASETEN_API_KEY" \
wss://model-{MODEL_ID}.api.baseten.co/environments/production/websocket
```
Baseten routes the connection to the `predict_endpoint` path on your server.
For more on custom server deployment, see [Custom servers](/development/model/custom-server).
## Deployment and concurrency considerations
### Scheduling
Baseten schedules new WebSocket connections onto the least-utilized replica until every replica holds `maxConcurrency - 1` concurrent connections. At that point, Baseten adds replicas up to the `maxReplica` limit.
Baseten scales down when the replica count exceeds `minReplica` and at least one replica has zero connections. Idle replicas are removed one at a time.
Two factors matter more for WebSockets than for HTTP:
* **Resource utilization:** HTTP requests are stateless, so Baseten can rebalance them freely. WebSocket connections stay pinned to a replica for their lifetime and count against that replica's concurrency target even when idle. Manage connection efficiency on the client side.
* **Stateful complexity:** WebSocket handlers often hold server-side state, which adds lifecycle work (disconnects, cleanup, reconnection logic).
### Lifetime guarantees
Baseten guarantees every WebSocket connection lasts at least 1 hour. In practice, connections run much longer. The 1-hour floor exists so Baseten can restart and rebalance internal services without breaking long-lived sessions.
### Concurrency changes
Lowering `maxConcurrency` doesn't close existing connections. Open WebSockets keep running until they close naturally, even if a replica ends up above the new target.
For example, if a replica holds 10 active WebSockets and you change `maxConcurrency` from 10 to 5, Baseten leaves all 10 open. They drain naturally as clients disconnect, or when the 1-hour lifetime guarantee triggers an internal restart.
### Promotion
You can promote a WebSocket model or chain to an environment through the REST API or UI, the same way you promote HTTP deployments. For more information, see [Environments](/deployment/environments).
On promotion, Baseten routes new connections to the new deployment, but existing connections stay on the previous deployment until they terminate. This means older deployments can take longer to scale down than HTTP deployments: their connections outlive the promotion.
### Maximum message size
Baseten enforces a 100 MiB limit on individual messages sent over a WebSocket. Both clients and models are capped at 100 MiB per outgoing message. There's no cap on the total data sent over a connection's lifetime.
## Monitoring
WebSocket deployments expose the same performance metrics as HTTP deployments. The rest of this section covers the differences that matter: status codes reported on connection close, how connection duration is measured, and what counts toward input and output size.
### Inference volume
The Metrics page tracks inference volume as the number of connections per minute. Baseten publishes each data point *after* the connection closes, so every point carries the status the connection ended with.
Two families of status codes appear for WebSocket deployments:
* **HTTP status codes** for connections that failed before the WebSocket upgrade completed.
* **WebSocket close codes** for connections that completed the upgrade and later closed.
#### HTTP status codes
| Code | Label | What it means |
| ----- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `408` | Request timeout | The WebSocket upgrade request timed out before a replica accepted it. |
| `504` | Gateway timeout | No replica became available in time. Typically indicates a cold start that exceeded the configured timeout, or a saturated deployment. |
#### WebSocket close codes
[RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455#section-7.4) defines the full set of WebSocket close codes. The Metrics page surfaces this subset:
| Code | Label | What it means |
| ------ | --------------------------- | ------------------------------------------------------------------------------------------------------- |
| `1000` | Normal closure | Either side closed the connection cleanly. This is normal, expected traffic. |
| `1001` | Going away | One side is going away, for example a replica restarting or a browser navigating away. |
| `1002` | Protocol error | One side sent a frame that violates the WebSocket protocol. |
| `1003` | Unsupported data | One side received a frame type it cannot accept, for example binary data on a text-only endpoint. |
| `1005` | No status received | The connection closed without a status code. Reserved and not sent on the wire. |
| `1006` | Abnormal closure | The connection dropped without a close frame. Usually caused by a network failure or a replica crash. |
| `1007` | Invalid frame payload data | A message payload was inconsistent with its declared type, for example non-UTF-8 bytes in a text frame. |
| `1008` | Policy violation | One side closed the connection for a policy reason it did not want to publish. |
| `1009` | Message too big | A message exceeded the 100 MiB per-message limit. See [Maximum message size](#maximum-message-size). |
| `1010` | Mandatory extension missing | The client expected a WebSocket extension that the server did not negotiate. |
| `1011` | Internal error | The server side hit an unexpected error that forced the connection to close. Check your model logs. |
| `1012` | Service restart | The replica is restarting. |
| `1013` | Try again later | The replica is temporarily overloaded. |
| `1014` | Bad gateway | An upstream gateway returned an invalid response. |
| `1015` | TLS handshake failure | Reserved and not sent on the wire. |
For the full specification, see [CloseEvent codes on MDN](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code).
The Metrics page filters out codes that don't help with debugging model behavior, such as rate-limit responses and connections that never reached a terminal state. Grafana and other lower-level tools might show these codes anyway.
### End-to-end connection duration
Duration is measured from when the connection opens to when it closes, and published after the connection ends. Reported at p50, p90, p95, and p99.
### Connection input and output size
Cumulative bytes transferred over the connection's lifetime, reported at p50, p90, p95, and p99:
* **Connection input size:** Bytes sent by the client to the server.
* **Connection output size:** Bytes sent by the server to the client.
# BEI-Bert
Source: https://docs.baseten.co/engines/bei/bei-bert
Bidirectional encoder embeddings with cold-start optimization
BEI-Bert is a variant of Baseten Embeddings Inference for BERT-family architectures. It runs at `FP16` or `BF16`, optimizes cold-start latency, and supports bidirectional attention for sub-4B-parameter encoders.
**Bidirectional attention** means each token in the input can attend to every other token, in both directions. BERT-family encoders use this pattern, which generally produces better embeddings because each token sees the full context. Causal models like GPT use the opposite pattern: each token attends only to earlier tokens, never to later ones. Some Qwen and Llama checkpoints (the `*Bidirectional` model variants listed below) are causal LLMs adapted to run in bidirectional mode specifically for embedding use.
## BEI vs BEI-Bert
Both variants run on the same engine binary. Pick the variant that matches your base architecture.
| Feature | BEI-Bert | BEI |
| ------------ | ------------------------------------ | --------------------------------- |
| Architecture | BERT-based (bidirectional) | Causal (unidirectional) |
| Precision | `FP16` (16-bit) | `BF16`, `FP16`, `FP8`, `FP4` |
| Cold-start | Optimized for fast initialization | Standard startup |
| Quantization | Not supported | `FP8`, `FP4` supported |
| Memory usage | Lower for small models | Higher or equal |
| Throughput | 600-900 embeddings/sec | 800-1400 embeddings/sec |
| Best for | Small BERT models, accuracy-critical | Large models, throughput-critical |
## When to use BEI-Bert
Choose BEI-Bert when any of these apply:
* **BERT-family base architecture**: `BertModel`, `RobertaModel`, `ModernBertModel`, `XLMRobertaModel`, or a `*Bidirectional` adapted checkpoint.
* **Cold-start matters**: first-request latency is critical for your traffic shape.
* **Small to medium models**: under 4B parameters where `FP8`/`FP4` quantization isn't needed.
* **16-bit precision**: workloads where `FP16` accuracy is preferred over quantized throughput.
* **Token-level classification**: NER and other `/predict_tokens` endpoints run on BEI-Bert only.
For models over 4B parameters, causal embedders, or workloads that need `FP8`/`FP4` quantization, use BEI. See the [BEI overview](/engines/bei/overview).
## Supported model families
BEI-Bert runs the following base architectures: `BertModel`, `RobertaModel`, `ModernBertModel`, `XLMRobertaModel`, `Gemma3Bidirectional`, `Qwen2Bidirectional`, `Qwen3Bidirectional`, `LLama3Bidirectional`.
### Sentence-transformers
The most common BERT-based embedding models, optimized for semantic similarity.
* `sentence-transformers/all-MiniLM-L6-v2` (384D, 22M params)
* `sentence-transformers/all-mpnet-base-v2` (768D, 110M params)
* `sentence-transformers/multi-qa-mpnet-base-dot-v1` (768D, 110M params)
### Jina AI
Jina's BERT-based models for general and code-specific domains.
* `jinaai/jina-embeddings-v2-base-en` (512D, 137M params)
* `jinaai/jina-embeddings-v2-base-code` (512D, 137M params)
* `jinaai/jina-embeddings-v2-base-es` (512D, 137M params)
### Nomic AI
Nomic's models with specialized training for text and code.
* `nomic-ai/nomic-embed-text-v1.5` (768D, 137M params)
* `nomic-ai/nomic-embed-code-v1.5` (768D, 137M params)
### Alibaba GTE and Qwen (bidirectional)
Multilingual models with instruction-tuning and long-context support.
* `Alibaba-NLP/gte-Qwen2-7B-instruct` (top-ranked multilingual)
* `Alibaba-NLP/gte-Qwen2-1.5B-instruct` (cost-effective alternative)
* `intfloat/multilingual-e5-large-instruct`
### Bidirectional LLM variants
Some Qwen and Llama checkpoints run in **bidirectional mode**: each token attends to the full input, which often improves embedding quality over causal pooling.
* **Qwen2Bidirectional**: `Alibaba-NLP/gte-Qwen2-7B-instruct`
* **Qwen3Bidirectional**: `voyageai/voyage-4-nano` ([contact Baseten](mailto:support@baseten.co) for deploy config)
* **Llama3Bidirectional**: `nvidia/llama-embed-nemotron-8b`
Set `base_model: encoder_bert`. The build applies bidirectional attention automatically.
#### Checkpoint requirements
BEI-Bert builds standard Hugging Face checkpoints only. Repos that require `trust_remote_code` fail at build time. Pin `checkpoint_repository.revision` when the model maintainer publishes a compatible config on a non-default branch.
For `voyageai/voyage-4-nano`, the default Hugging Face branch is not compatible with BEI-Bert. [Contact your Baseten representative](mailto:support@baseten.co) for the current `checkpoint_repository` settings before you deploy.
### Reranking
BEI-Bert runs cross-encoder rerankers through `/rerank`. Recommended:
* `BAAI/bge-reranker-large` (XLM-RoBERTa)
* `BAAI/bge-reranker-base` (XLM-RoBERTa base)
* `Alibaba-NLP/gte-multilingual-reranker-base`
* `Alibaba-NLP/gte-reranker-modernbert-base`
### Classification
BEI-Bert runs sequence classifiers through `/predict`. The classifier head needs an `id2label` dictionary in the Hugging Face config. Recommended:
* `SamLowe/roberta-base-go_emotions` (sentiment)
* `papluca/xlm-roberta-base-language-detection` (language ID)
### Named entity recognition
Token-level entity classification routes to `/predict_tokens` and runs on BEI-Bert only. Recommended:
* `dslim/bert-base-NER-uncased` ([Truss example](https://github.com/basetenlabs/truss-examples/tree/main/custom-server/BEI-Bert-dslim-bert-base-ner-uncased))
* `tanaos/tanaos-NER-v1`
For the full request/response format and Python example, see [Named entity recognition](/engines/bei/ner).
## Model selection by constraint
Choose based on your primary constraint:
**Balanced cost and performance:**
* `Alibaba-NLP/gte-Qwen2-7B-instruct`: instruction-tuned, ranked #1 for multilingual.
* `Alibaba-NLP/gte-Qwen2-1.5B-instruct`: 1/5 the size, still top-tier.
* `Snowflake/snowflake-arctic-embed-m-v2.0`: multilingual-optimized, MRL support.
**Lightweight (under 500M params):**
* `google/embeddinggemma-300m`: 300M params, 100+ languages.
* `nomic-ai/nomic-embed-text-v1.5`: 137M, minimal latency.
* `sentence-transformers/all-MiniLM-L6-v2`: 22M, legacy standard.
**Specialized:**
* Code: `jinaai/jina-embeddings-v2-base-code`.
* Long sequences: `Alibaba-NLP/gte-large-en-v1.5`.
* Reranking: `BAAI/bge-reranker-large`, `Alibaba-NLP/gte-reranker-modernbert-base`.
## Minimal configuration
BEI-Bert deployments set `base_model: encoder_bert` and `quantization_type: no_quant`. Pull weights from Hugging Face by default.
```yaml theme={"system"}
trt_llm:
inference_stack: v1
build:
base_model: encoder_bert
checkpoint_repository:
source: HF
repo: "sentence-transformers/all-MiniLM-L6-v2"
quantization_type: no_quant
runtime:
webserver_default_route: /v1/embeddings
```
For the full schema, including `max_num_tokens`, GPU support, and complete examples for sentence-transformers, Jina, Nomic, and bidirectional LLM variants, see the [BEI configuration reference](/engines/bei/bei-reference).
## Related
* [BEI overview](/engines/bei/overview): Causal embeddings, reranking, and OpenAI-compatible inference.
* [BEI configuration reference](/engines/bei/bei-reference): Full `trt_llm` schema, pooling matrix, hardware support, and complete configuration examples.
* [Named entity recognition](/engines/bei/ner): `/predict_tokens` request and response format.
* [Embedding examples](/examples/bei): Concrete deployment examples.
* [Performance Client](/inference/performance-client): High-throughput batch inference for embeddings and reranking.
# Configuration reference
Source: https://docs.baseten.co/engines/bei/bei-reference
Complete reference config for BEI and BEI-Bert engines
This reference covers all configuration options for BEI and BEI-Bert deployments. All settings use the `trt_llm` section in `config.yaml`.
## Configuration structure
```yaml theme={"system"}
trt_llm:
inference_stack: v1 # Always v1 for BEI
build:
base_model: encoder | encoder_bert
checkpoint_repository: {...}
max_num_tokens: 16384
quantization_type: no_quant | fp8 | fp4 | fp4_mlp_only
quantization_config: {...}
plugin_configuration: {...}
runtime:
webserver_default_route: /v1/embeddings | /rerank | /predict
```
## Build configuration
Fields are tagged **Required**, **Optional**, or **Computed**. Computed fields are set by the engine; do not configure them manually.
The `build` section configures model compilation and optimization settings.
**Required.** The base model architecture determines which BEI variant to use.
**Options:**
* `encoder`: BEI - for causal embedding models (Llama, Mistral, Qwen, Gemma)
* `encoder_bert`: BEI-Bert - for BERT-based models (BERT, RoBERTa, Jina, Nomic)
```yaml theme={"system"}
build:
base_model: encoder
```
**Required.** Specifies where to find the model checkpoint. Repository must follow the standard HuggingFace structure.
**Source options:**
* `HF`: Hugging Face Hub (default)
* `GCS`: Google Cloud Storage
* `S3`: AWS S3
* `AZURE`: Azure Blob Storage
* `REMOTE_URL`: HTTP URL to tar.gz file
* `BASETEN_TRAINING`: Baseten Training checkpoints
For training checkpoint deployment, see [Deploy with optimized inference engines](/training/deploy-with-engine-builder). For cloud storage sources (GCS, S3, Azure), see [Deploy from cloud storage](/engines/performance-concepts/cloud-storage-deployment).
```yaml theme={"system"}
checkpoint_repository:
source: HF
repo: "BAAI/bge-large-en-v1.5"
revision: main
runtime_secret_name: hf_access_token # Optional, for private repos
```
`checkpoint_repository` is the weight source for BEI models, and Baseten mirrors it to the [Baseten Delivery Network](/development/model/bdn) automatically for fast cold starts. Don't add a top-level `weights:` section to a BEI config: `checkpoint_repository` already handles weight loading, so using `weights:` directly is discouraged.
**Optional.** Maximum number of tokens that can be processed in a single batch. BEI defaults to `16384`; BEI-Bert defaults to `8192`. BEI and BEI-Bert run without chunked-prefill for performance reasons. This limits the effective context length to the `max_position_embeddings` value.
**Range:** 65 to 1048576 (`gt=64, le=1048576` in schema). Use higher values for long context models. Most models use 16384 as default.
```yaml theme={"system"}
build:
max_num_tokens: 16384
```
**Computed.** Not supported for BEI engines. Leave this value unset. BEI automatically sets it and truncates if context length is exceeded.
**Optional.** Specifies the quantization format for model weights. `FP8` quantization maintains accuracy within 1% of `FP16` for embedding models.
**Options for BEI:**
* `no_quant`: `FP16`/`BF16` precision
* `fp8`: `FP8` weights + 16-bit KV cache
* `fp4`: `FP4` weights + 16-bit KV cache (B200 only)
* `fp4_mlp_only`: `FP4` MLP weights only (B200 only)
**Options for BEI-Bert:**
* `no_quant`: `FP16` precision (only option)
For detailed quantization guidance, see [Quantization guide](/engines/performance-concepts/quantization-guide).
```yaml theme={"system"}
build:
quantization_type: fp8
```
**Optional.** Configuration for post-training quantization calibration.
**Fields:**
* `calib_size`: Size of calibration dataset (64-16384, multiple of 64)
* `calib_dataset`: HuggingFace dataset for calibration
* `calib_max_seq_length`: Maximum sequence length for calibration
```yaml theme={"system"}
quantization_config:
calib_size: 1024
calib_dataset: "abisee/cnn_dailymail"
calib_max_seq_length: 1536
```
**Computed.** BEI automatically configures optimal TensorRT-LLM plugin settings. Manual configuration is not required or supported.
**Automatic optimizations:**
* XQA kernels for maximum throughput
* Dynamic batching for optimal utilization
* Memory-efficient attention mechanisms
* Hardware-specific optimizations
**Note:** Plugin configuration is only available for Engine-Builder-LLM engine.
## Runtime configuration
The `runtime` section configures serving behavior.
**Optional.** The default API endpoint for the deployment.
**Options:**
* `/v1/embeddings`: OpenAI-compatible embeddings endpoint
* `/rerank`: Reranking endpoint
* `/predict`: Classification/prediction endpoint
BEI automatically detects embedding models and sets `/v1/embeddings`. Classification models default to `/predict`.
```yaml theme={"system"}
runtime:
webserver_default_route: /v1/embeddings
```
**Computed.** Available but has no effect for BEI embedding models, which do not use a KV cache. Only relevant for generative (decoder) models.
**Computed.** Available but has no effect for BEI embedding models. Only relevant for generative (decoder) models.
**Computed.** Available but has no effect for BEI embedding models. Only relevant for generative (decoder) models.
## HuggingFace model repository structure
All model sources (S3, GCS, HuggingFace, or tar.gz) must follow the standard HuggingFace repository structure. Files must be in the root directory, similar to running:
```bash theme={"system"}
git clone https://huggingface.co/michaelfeil/bge-small-en-v1.5
```
### Model configuration
**config.json**
* `max_position_embeddings`: Limits maximum context size (content beyond this is truncated)
* `id2label`: Required dictionary mapping IDs to labels for classification models.
* **Note**: Needs to have len of the shape of the last dense layer. Each dense output needs a `name` for the json response.
* `architecture`: Must be `ModelForSequenceClassification` or similar (cannot be `ForCausalLM`)
* **Note**: Remote code execution is not supported; architecture is inferred automatically
* `torch_dtype`: Default inference dtype (BEI-Bert: always `fp16`, BEI: `float16`, `bfloat16`)
* **Note**: We don't support `pre-quantized` loading, meaning your weights need to be `float16`, `bfloat16` or `float32` for all engines.
* `quant_config`: Not allowed, as no `pre-quantized` weights.
#### Model weights
**model.safetensors** (preferred)
* Or: `model.safetensors.index.json` + `model-xx-of-yy.safetensors` (sharded)
* **Note**: Convert to safetensors if you encounter issues with other formats
#### Tokenizer files
**tokenizer\_config.json** and **tokenizer.json**
* Must be "FAST" tokenizers compatible with Rust
* Typically cannot contain custom Python code, will be unread.
#### Embedding model files (sentence-transformers)
**1\_Pooling/config.json**
* Required for embedding models to define pooling strategy
**modules.json**
* Required for embedding models
* Shows available pooling layers and configurations
At build time, BEI reads pooling mode from `modules.json` and `1_Pooling/config.json` and maps it to one of the modes below.
| Flag in `1_Pooling/config.json` | Pooling mode | BEI | BEI-Bert |
| -------------------------------- | ----------------------- | --- | -------- |
| `pooling_mode_cls_token: true` | CLS token (first token) | ✅ | ✅ |
| `pooling_mode_mean_tokens: true` | Mean tokens | ✅ | ✅ |
| `pooling_mode_lasttoken: true` | Last token | ✅ | ✅ |
If either file is missing on an embedding checkpoint, the build fails with a clear error naming the missing path. Sequence classification and reranking models skip pooling detection and use the classification head instead.
### Pooling layer support
| **Engine** | **Classification Layers** | **Pooling Types** | **Notes** |
| ------------ | -------------------------- | --------------------------------------------- | ------------------------ |
| **BEI** | 1 layer maximum | Last token, first token | Limited pooling options |
| **BEI-Bert** | Multiple layers or 1 layer | Last token, first token, mean, SPLADE pooling | Advanced pooling support |
## Throughput benchmarks
Measured against TEI and vLLM on the same hardware. Token throughput uses 500 tokens per request; request throughput uses 5 tokens per request. For the full methodology, see [Run Qwen3 Embedding on NVIDIA Blackwell GPUs](https://www.baseten.co/blog/run-qwen3-embedding-on-nvidia-blackwell-gpus/#bei-provides-the-fastest-embeddings-inference-on-b200s).
| Framework | Precision | GPU | Max tokens/s | Max requests/s |
| --------- | --------- | ---- | ------------ | -------------- |
| TEI | FP16 | H100 | 34,055 | 824.25 |
| BEI-Bert | FP16 | H100 | 36,520 | 841.05 |
| vLLM | BF16 | H100 | 36,625 | 155.23 |
| BEI | BF16 | H100 | 47,549 | 761.44 |
| BEI | FP8 | H100 | 77,107 | 855.96 |
| BEI | FP8 | B200 | 121,443 | 1,310.52 |
## Quantization impact
| Quantization | Speed improvement | Memory reduction | Accuracy impact |
| -------------- | ----------------- | ---------------- | --------------- |
| FP16/BF16 vLLM | Baseline | None | None |
| FP16/BF16 BEI | 1.3x | None | None |
| FP8 BEI | 2x | 50% | \~1% |
| FP4 BEI | 3.5x | 75% | 1-2% |
## Hardware support
| GPU | BEI | BEI-Bert | Recommended for |
| ---------- | ---- | -------- | -------------------------- |
| L4 | Full | Full | Cost-effective deployments |
| A10G, A100 | Full | Full | Legacy support |
| T4 | No | Full | Legacy support |
| H100 | Full | Full | Maximum performance |
| B200 | Full | Full | `FP4` quantization |
## Complete configuration examples
### BEI with `FP8` quantization (embedding model)
```yaml theme={"system"}
model_name: BEI-BGE-Large-FP8
resources:
accelerator: H100
use_gpu: true
trt_llm:
build:
base_model: encoder
checkpoint_repository:
source: HF
repo: "Qwen/Qwen3-Embedding-8B"
revision: main
max_num_tokens: 16384
quantization_type: fp8
quantization_config:
calib_size: 1536
calib_dataset: "abisee/cnn_dailymail"
calib_max_seq_length: 1536
# plugin_configuration is auto-configured for BEI models.
# Encoder models disable paged_kv_cache and use_paged_context_fmha automatically.
runtime:
webserver_default_route: /v1/embeddings
```
### BEI-Bert for small BERT model
```yaml theme={"system"}
model_name: BEI-Bert-MiniLM-L6
resources:
accelerator: L4
use_gpu: true
trt_llm:
build:
base_model: encoder_bert
checkpoint_repository:
source: HF
repo: "sentence-transformers/all-MiniLM-L6-v2"
revision: main
max_num_tokens: 8192
quantization_type: no_quant
# plugin_configuration is auto-configured for BEI-Bert models.
# paged_kv_cache and use_paged_context_fmha are disabled automatically.
runtime:
webserver_default_route: /v1/embeddings
```
### BEI for reranking model
```yaml theme={"system"}
model_name: BEI-BGE-Reranker
resources:
accelerator: H100
use_gpu: true
trt_llm:
build:
base_model: encoder
checkpoint_repository:
source: HF
repo: "BAAI/bge-reranker-large"
revision: main
max_num_tokens: 16384
quantization_type: fp8
quantization_config:
calib_size: 1024
calib_dataset: "abisee/cnn_dailymail"
calib_max_seq_length: 2048
runtime:
webserver_default_route: /rerank
```
### BEI-Bert for classification model
```yaml theme={"system"}
model_name: BEI-Bert-Language-Detection
resources:
accelerator: L4
use_gpu: true
trt_llm:
build:
base_model: encoder_bert
checkpoint_repository:
source: HF
repo: "papluca/xlm-roberta-base-language-detection"
revision: main
max_num_tokens: 8192
quantization_type: no_quant
runtime:
webserver_default_route: /predict
```
### BEI-Bert for code embeddings (Jina)
```yaml theme={"system"}
model_name: BEI-Bert-Jina-Code
resources:
accelerator: H100
use_gpu: true
trt_llm:
build:
base_model: encoder_bert
checkpoint_repository:
source: HF
repo: "jinaai/jina-embeddings-v2-base-code"
revision: main
max_num_tokens: 8192
quantization_type: no_quant
runtime:
webserver_default_route: /v1/embeddings
kv_cache_free_gpu_mem_fraction: 0.9
batch_scheduler_policy: guaranteed_no_evict
```
### BEI-Bert for bidirectional Qwen2 (long sequences)
```yaml theme={"system"}
model_name: BEI-Bert-GTE-Qwen-1.5B
resources:
accelerator: L4
use_gpu: true
trt_llm:
build:
base_model: encoder_bert
checkpoint_repository:
source: HF
repo: "Alibaba-NLP/gte-Qwen2-1.5B-instruct"
revision: main
max_num_tokens: 8192
quantization_type: no_quant
runtime:
webserver_default_route: /v1/embeddings
kv_cache_free_gpu_mem_fraction: 0.85
batch_scheduler_policy: guaranteed_no_evict
```
## Common configuration errors
**Warning:** Briton logs: "Compling `encoder` with a kv-cache dtype is a alpha feature. This may fail."
* **Cause:** Using a KV quantization type (`fp8_kv`, `fp4_kv`) with an encoder model. Encoders do not use a KV cache, so these variants are alpha and may fail the build.
* **Fix:** Use `fp8` or `no_quant` instead.
**Error:** `FP8 quantization is only supported on L4, H100, H200, B200`
* **Cause:** Using `FP8` quantization on unsupported GPU.
* **Fix:** Use H100 or newer GPU, or use `no_quant`.
**Error:** `FP4 quantization is only supported on B200`
* **Cause:** Using `FP4` quantization on unsupported GPU.
* **Fix:** Use B200 GPU or `FP8` quantization.
# Named entity recognition
Source: https://docs.baseten.co/engines/bei/ner
Token-level entity classification on BEI-Bert with /predict_tokens
Named entity recognition (NER) classifies each token in an input string into entity categories such as person (`PER`), organization (`ORG`), location (`LOC`), and miscellaneous (`MISC`). NER models use the `ForTokenClassification` architecture and the `/predict_tokens` endpoint. NER requires BEI-Bert (`base_model: encoder_bert`); BEI does not support token-level outputs.
## Recommended models
* `dslim/bert-base-NER-uncased`: fast, compact NER for English. ([Truss example](https://github.com/basetenlabs/truss-examples/tree/main/custom-server/BEI-Bert-dslim-bert-base-ner-uncased))
* `tanaos/tanaos-NER-v1`: general-purpose NER.
## Configuration
Add to `config.yaml`:
```yaml theme={"system"}
trt_llm:
build:
base_model: encoder_bert
checkpoint_repository:
source: HF
repo: "baseten-admin/bert-base-ner-uncased"
revision: main
max_num_tokens: 16384
runtime:
webserver_default_route: /predict_tokens
```
## Request format
```json theme={"system"}
{
"inputs": ["Apple is looking at buying U.K. startup for $1 billion"],
"truncate": true,
"raw_scores": false,
"aggregation_strategy": "max"
}
```
| Field | Type | Description |
| ---------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `inputs` | list of strings | Batched text inputs to classify. Each string is classified independently, and the response contains one list of entities per input. |
| `raw_scores` | boolean | When `true`, returns raw logit scores for all labels per token. When `false`, returns the top predicted label with its probability. |
| `truncate` | boolean | Truncates inputs that exceed the model's max sequence length. |
| `truncation_direction` | string | Controls which end is truncated. Defaults to `"Right"`. |
| `aggregation_strategy` | string | Merges sub-word tokens into entity spans. Accepts `"none"`, `"simple"`, `"first"`, `"average"`, or `"max"`. Use `"max"` to match `transformers.pipeline("ner", aggregation_strategy="max")`. Use `"none"` for token-level predictions. |
## Response format
With `aggregation_strategy: "max"` (recommended for production):
```json theme={"system"}
[
[
{"token": "Apple", "token_id": 0, "start": 0, "end": 5, "results": {"ORG": 0.9975586}},
{"token": "U.K.", "token_id": 0, "start": 27, "end": 31, "results": {"LOC": 0.9980469}}
]
]
```
With `aggregation_strategy: "none"` and `raw_scores: true` (token-level with BIO labels):
```json theme={"system"}
[
[
{
"token": "Apple",
"token_id": 6207,
"start": 0,
"end": 5,
"results": {
"B-ORG": 6.7578125,
"O": -1.7929688,
"B-LOC": 0.6015625,
"B-MISC": 0.2467041,
"B-PER": 0.17675781,
"I-ORG": -0.6484375,
"I-MISC": -1.9873047,
"I-LOC": -1.3808594,
"I-PER": -2.21875
}
}
]
]
```
Token-level labels follow the [BIO tagging scheme](https://en.wikipedia.org/wiki/Inside%E2%80%93outside%E2%80%93beginning_\(tagging\)): `B-` marks the beginning of an entity, `I-` marks a continuation, and `O` means outside any entity.
## Python example
Using the Baseten [Performance Client](/inference/performance-client):
```python theme={"system"}
from baseten_performance_client import PerformanceClient
import os
client = PerformanceClient(
api_key=os.environ['BASETEN_API_KEY'],
base_url="https://model-xxxxxx.api.baseten.co/environments/production/sync"
)
response = client.batch_post(
url_path="/predict_tokens",
payloads=[{
"inputs": ["Apple is looking at buying U.K. startup for $1 billion"],
"truncate": True,
"raw_scores": False,
"aggregation_strategy": "max"
}]
)
for entity in response.data[0]:
label = next(iter(entity["results"]))
score = entity["results"][label]
print(f"{entity['token']}: {label} ({score:.4f})")
```
NER models do not expose an OpenAI-compatible endpoint. Call `/predict_tokens` directly. The route also supports [async inference](/inference/async).
## Related
* [BEI-Bert overview](/engines/bei/bei-bert): Bidirectional encoder engine that hosts NER deployments.
* [BEI configuration reference](/engines/bei/bei-reference): Full `trt_llm` schema for build and runtime fields.
# Overview
Source: https://docs.baseten.co/engines/bei/overview
Production-grade embeddings, reranking, and classification models
Baseten Embeddings Inference (BEI) serves embedding, classification, and reranking models on TensorRT-LLM, with sub-millisecond response times and up to 1,400 client embeddings per second on H100. Builds mirror to the [Baseten Delivery Network](/development/model/bdn) so cold starts stay fast.
## Inference stack
BEI runs on the v1 inference stack. In `config.yaml`, set `inference_stack: v1` and `base_model: encoder` for causal architectures (Llama, Mistral, Qwen, Gemma) or `base_model: encoder_bert` for BERT-family encoders. Configuration lives entirely in the Truss `config.yaml`; the `llm_config` Management API block applies only to v2. For MoE text generation on v2, see [BIS-LLM](/engines/bis-llm/overview).
## Architectures
BEI runs causal embedding architectures (Llama, Mistral, Qwen, Gemma) with `FP8` and `FP4` quantization for maximum throughput. For bidirectional encoders like BERT, RoBERTa, Jina, Nomic, and ModernBERT, BEI ships a more specialized variant called BEI-Bert. BEI-Bert runs at `FP16` or `BF16` and is optimized for cold-start sensitive workloads and models under 4B parameters.
Causal embeddings with `FP8`/`FP4` quantization. Up to 1,400 embeddings per second on H100, 121K tokens/s on B200.
Bidirectional BERT-family encoders at `FP16` or `BF16`. Tuned for fast cold-start on models under 4B parameters.
## Workflows
BEI handles three common workflows: embeddings (`/v1/embeddings`), reranking and classification (`/rerank` and `/predict`), and named entity recognition (`/predict_tokens`, BEI-Bert only). All three share the same `trt_llm` configuration block; the route and `base_model` change per workflow.
### Embeddings
Causal embedders (Llama, Mistral, Qwen, Gemma) deploy on BEI with `base_model: encoder` and pull weights from Hugging Face by default.
```yaml theme={"system"}
trt_llm:
inference_stack: v1
build:
base_model: encoder
checkpoint_repository:
source: HF
repo: "BAAI/bge-large-en-v1.5"
quantization_type: fp8
runtime:
webserver_default_route: /v1/embeddings
```
For embedding models, BEI reads pooling strategy from the Hugging Face repo at build time using `modules.json` and `1_Pooling/config.json`. You do not set pooling in `config.yaml`. See [Pooling layer support](/engines/bei/bei-reference#pooling-layer-support) for the full matrix including SPLADE on BEI-Bert.
### Reranking and classification
Reranking and classification models route to `/rerank` or `/predict` and use the same `trt_llm` block.
```yaml theme={"system"}
trt_llm:
inference_stack: v1
build:
base_model: encoder
checkpoint_repository:
source: HF
repo: "BAAI/bge-reranker-v2-m3"
max_num_tokens: 16384
runtime:
webserver_default_route: /rerank
```
POST query-document pairs to `/rerank`:
```json theme={"system"}
{
"query": "What is the best way to invest money?",
"texts": [
"Index funds offer diversified market exposure.",
"Day trading requires active monitoring."
]
}
```
The response is `[{"index": 0, "score": 0.92}, {"index": 1, "score": 0.14}]`, sorted by `score` descending, so the first entry is the best match. Each `index` refers to the position of that text in the input `texts`. Some rerankers (such as `michaelfeil/Qwen3-Reranker-8B-seq`) expect chat-style prompt templates and need `webserver_default_route: /predict` instead; use the [Performance Client](/inference/performance-client) so it applies the right template and autoscaling counts load correctly.
For classification models, set `base_model: encoder_bert` and `webserver_default_route: /predict`. The classifier head needs an `id2label` dictionary in the Hugging Face config; the build fails with a clear error if it is missing.
### Named entity recognition
Token-level entity classification deploys on BEI-Bert only and routes to `/predict_tokens`. The full request/response format and Python example live on [Named entity recognition](/engines/bei/ner).
## OpenAI compatibility
BEI deployments expose `/v1/embeddings` and work with the standard OpenAI client:
```python theme={"system"}
from openai import OpenAI
import os
client = OpenAI(
api_key=os.environ['BASETEN_API_KEY'],
base_url="https://model-xxxxxx.api.baseten.co/environments/production/sync/v1"
)
embedding = client.embeddings.create(
input=["Baseten Embeddings are fast.", "Embed this sentence!"],
model="not-required"
)
```
For maximum throughput on batched workloads, use the [Baseten Performance Client](/inference/performance-client) instead. It manages concurrency and batching for you.
## Related
* [BEI configuration reference](/engines/bei/bei-reference): Full `trt_llm` schema, pooling matrix, hardware support, and throughput benchmarks.
* [BEI-Bert](/engines/bei/bei-bert): BERT-specific configuration, model recommendations, and cold-start guidance.
* [Named entity recognition](/engines/bei/ner): `/predict_tokens` request and response format.
* [Embedding examples](/examples/bei): Concrete deployment examples.
* [Performance Client](/inference/performance-client): High-throughput batch inference for embeddings and reranking.
# Advanced features for BIS-LLM
Source: https://docs.baseten.co/engines/bis-llm/advanced-features
KV-aware routing, disaggregated serving, and speculative decoding
BIS-LLM ships three Enterprise-gated production features that target distinct bottlenecks in large-scale LLM serving: KV-aware routing reduces time-to-first-token on repeated prefixes, disaggregated serving prevents long prefills from blocking decode latency, and speculative decoding raises throughput on a single replica. Each section uses the same shape: how it works, configuration, when to use it, and the metric to watch.
All three are configured through the BIS-LLM Management API (`POST /v1/llm_models`) under the `llm_config` block, not through Truss `config.yaml`. To enable any of them on your deployment, [contact your Baseten representative](mailto:support@baseten.co).
## KV-aware routing
Long prompts repeat context across requests. Without cache-aware routing, each worker rebuilds KV state from scratch on every request, even when another worker in the deployment already has the prefix cached. The KV-aware router maintains a real-time index of every worker's KV cache contents and picks the worker most likely to serve a request from cache.
### How it works
The router runs as a stateful service in front of the BIS-LLM worker pool. For each incoming request:
1. The frontend tokenizes the prompt and calls the router for a worker assignment.
2. The router scores each worker against the prompt's tokens using a radix tree that indexes every worker's KV cache.
3. The router returns the worker most likely to serve the request from cache, balanced against current worker load.
4. The frontend sends the request directly to that worker.
Workers publish KV cache block events as blocks are added or evicted; the router consumes those events to keep its index in sync. The router periodically writes index snapshots to persistent storage so it can recover state on restart without replaying every event.
### Configuration
Settings live under `b10_routing_config`. Defaults match production Model APIs and rarely need to change.
```json theme={"system"}
{
"b10_routing_config": {
"router_queue_policy": "fcfs",
"router_overlap_score_weight": 3.5,
"router_temperature": 0.05
}
}
```
How queued requests are ordered when all workers are saturated. Queueing rarely triggers under normal load.
* `fcfs`: First-come, first-served with priority bumps. Optimizes tail TTFT and provides fairness.
* `wspt`: Weighted shortest processing time. Prioritizes cheaper requests (high cache hit, short prompts). Risks starving costly requests; use when average TTFT matters more than tail TTFT.
Bias toward cache hits versus load balance. Higher values bias toward cache hits at the cost of balance; lower values bias toward balance at the cost of hits. [Contact us](mailto:support@baseten.co) before changing in production.
Randomness in worker selection. Higher values spread load across more workers; lower values concentrate hits on fewer workers. [Contact us](mailto:support@baseten.co) before changing in production.
A single active router becomes a bottleneck above roughly 50 workers. For larger deployments, the router can run as multiple active replicas that share in-flight request state. [Contact us](mailto:support@baseten.co) to add router replicas.
### When to use
KV-aware routing is on by default for BIS-LLM deployments and pays off whenever prompts share prefixes: agent loops, chat with long system messages, RAG pipelines reusing retrieved context, and code completion. Workloads with no prefix overlap (unique single-turn prompts) see only the load-balancing benefit.
### Monitoring
| Metric | What it measures | What to look for |
| ------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `kv_cache_hit_rate` | Actual KV cache hit rate observed by workers. | Baseline varies by model and traffic. Track changes over time, not absolute values. |
| `kv_cache_hit_rate_skew` | Router's estimated hit rate minus actual hit rate. | Typically slightly positive (\~+10%). Large positive: high cache churn. Large negative: missed event stream. |
| `kv_cache_best_prefix_hit_rate` | Best hit rate the router could have selected given its index. | Upper bound of routing quality for the current index. |
| `kv_cache_hit_rate_efficiency` | Ratio of actual hit rate to best possible. | Typically 90-100%. Lower values mean the router is trading hits for balance. |
## Disaggregated serving
In a standard deployment, each replica handles both prefill (prompt processing) and decode (token generation). When a long prompt arrives, the replica must finish prefill before it can decode any tokens, blocking shorter requests queued behind it.
### How it works
Disaggregated serving splits prefill and decode into separate replica groups:
* **Prefill replicas** process input prompts and transfer the resulting KV cache to decode replicas.
* **Decode replicas** receive KV cache from prefill replicas and generate output tokens.
Each phase scales independently based on its own load. A long prefill never blocks decode latency on other replicas.
### Configuration
Set `is_disaggregated` and `b10_disagg_config` in the `llm_config` block:
```json theme={"system"}
{
"is_disaggregated": true,
"b10_disagg_config": {
"prefill_workers_per_replica": 1,
"decode_workers_per_replica": 2
}
}
```
Enables disaggregated serving. Must be `true` for `b10_disagg_config` to take effect. Setting `b10_disagg_config` without `is_disaggregated: true` fails validation.
Prefill worker pods per replication unit. Must be an integer >= 1.
Decode worker pods per replication unit. Must be an integer >= 1.
The two worker counts define a **replication unit**: the smallest independently scalable group. A `prefill: 1, decode: 2` configuration means each unit has one prefill pod and two decode pods. The autoscaler scales the number of units, not individual pods.
The backend rejects deployments where `is_disaggregated` is `false` or absent but `b10_disagg_config` is set, and rejects deployments where `is_disaggregated` is `true` but either worker count is missing or less than one.
### When to use
Disaggregated serving fits deployments with at least one of these traits:
* **Mismatched prefill and decode resource profiles.** Long-context models (128K+ tokens) have compute-heavy prefills and memory-bound decodes. Separate scaling right-sizes each phase.
* **Strict TTFT targets.** Isolating prefill on dedicated replicas prevents decode requests from queuing behind long prompts.
* **Variable prompt lengths.** Mixed short/long workloads benefit more than uniform traffic.
For consistent prompt lengths or workloads where TTFT is not a bottleneck, aggregated serving is simpler and sufficient.
### Monitoring
Watch [BIS-LLM autoscaling metrics](/engines/performance-concepts/autoscaling-engines#bis-llm) on each replica group. Token-based autoscaling sizes prefill and decode independently using their own in-flight token counts.
## Speculative decoding
Speculative decoding accelerates inference by drafting several future tokens cheaply, then verifying them against the main model in a single forward pass. Accepted tokens advance the output; rejected tokens are discarded and the model resumes autoregressive decoding from the last accepted token.
### How it works
BIS-LLM speculative decoding uses a fast draft mechanism (a lightweight Eagle head, the model's own MTP layers, or n-gram automata) to generate candidate tokens. The main model then verifies these candidates in a single batched forward pass. Higher acceptance rates yield more tokens per forward pass and lower latency.
This is a different system from v1 [lookahead decoding](/engines/engine-builder-llm/lookahead-decoding), which uses n-gram patterns within a single model and is configured with `trt_llm.build.speculator`. The v2 stack rejects `trt_llm.build.speculator`; use `speculative_config` instead.
| Decoding type | How it works | Best for |
| ------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `Eagle` | Separate Eagle head drafts tokens from a hidden-state representation. | Models with trained Eagle checkpoints. |
| `MTP` | The model's own multi-token-prediction layers draft multiple tokens per step. | Models with MTP heads built in (DeepSeek-V3). |
| `NGram` | N-gram automata predict tokens from pattern matching without model computation. | High-throughput workloads where latency matters more than acceptance rate. |
All three share the same loop: the draft mechanism proposes a run of tokens, the model verifies the whole run in one forward pass, and the matching tokens are accepted together. Only the draft source changes. Switch it to compare:
### Configuration
Set `speculative_config` in the `llm_config` block. The required fields depend on `decoding_type`.
Speculative strategy. One of `Eagle`, `MTP`, or `NGram` (case-insensitive).
Required when `decoding_type` is `Eagle`. Path to the Eagle head weights directory. BDN mirrors this as a standalone weight volume, separate from the main model weights.
Required when `decoding_type` is `MTP`. Number of next-token prediction layers in the model architecture.
Optional. Maximum number of tokens the draft proposes per step. Raise it for more aggressive speculation, lower it if acceptance is poor.
Optional, `Eagle` only. Run the Eagle3 draft head and the target model as a single fused model. Set to `true` for Eagle3 checkpoints that support it.
Eagle example:
```json theme={"system"}
{
"speculative_config": {
"decoding_type": "Eagle",
"speculative_model_dir": "/models/eagle",
"max_draft_len": 3,
"eagle3_one_model": true
}
}
```
MTP example:
```json theme={"system"}
{
"speculative_config": {
"decoding_type": "MTP",
"num_nextn_predict_layers": 1
}
}
```
NGram example:
```json theme={"system"}
{
"speculative_config": {
"decoding_type": "NGram"
}
}
```
### When to use
Pick by model architecture, not preference. Use `MTP` for DeepSeek-V3 and other models that ship MTP heads. Use `Eagle` when you have a trained Eagle head for the target model. Use `NGram` for high-throughput workloads where any acceleration helps and no draft model is available.
### Monitoring
The BIS-LLM dashboard exposes `speculation_rate` when speculative decoding is active: the percentage of draft tokens accepted by the main model.
* **Above 80%**: Draft is well-aligned with the main model. Speculation is effective.
* **40-80%**: Some rejections. Consider tuning the draft model or switching decoding types.
* **Below 40%**: Speculation likely costs more than it saves. Disable it or reduce draft length.
## Related
* [BIS-LLM overview](/engines/bis-llm/overview): Engine fundamentals and supported model families.
* [BIS-LLM configuration](/engines/bis-llm/bis-llm-config): Truss `config.yaml` reference for the build step.
* [Autoscaling BIS-LLM](/engines/performance-concepts/autoscaling-engines#bis-llm): Token-based autoscaling for prefill, decode, and aggregated replicas.
* [Lookahead decoding (v1)](/engines/engine-builder-llm/lookahead-decoding): N-gram speculation for Engine-Builder-LLM, when you need the v1 path.
# Configuration reference
Source: https://docs.baseten.co/engines/bis-llm/bis-llm-config
Complete reference config for v2 inference stack and MoE models
This reference covers the full Truss `config.yaml` schema for BIS-LLM (Baseten Inference Stack v2). The v2 stack simplifies the `build:` section and moves runtime fields out of build.
For translating an Engine-Builder-LLM (v1) configuration to BIS-LLM, see [Migrate from Engine-Builder-LLM](/engines/bis-llm/migrate-from-v1).
## Configuration structure
```yaml theme={"system"}
trt_llm:
inference_stack: v2 # Always v2 for BIS-LLM
build:
checkpoint_repository: {...}
quantization_type: no_quant | fp8 | fp8_kv | fp4 | fp4_kv | fp4_mlp_only
quantization_config: {...}
num_builder_gpus: 1
skip_build_result: false
runtime:
max_seq_len: 32768
max_batch_size: 256
max_num_tokens: 8192
tensor_parallel_size: 1
enable_chunked_prefill: true
served_model_name: "model-name"
patch_kwargs: {...}
```
## Build configuration
The `build` section configures model compilation and optimization settings.
Specifies where to find the model checkpoint. Same structure as v1 with v2-specific optimizations.
For training checkpoint deployment, see [Deploy with optimized inference engines](/training/deploy-with-engine-builder). For cloud storage sources (GCS, S3, Azure), see [Deploy from cloud storage](/engines/performance-concepts/cloud-storage-deployment).
```yaml theme={"system"}
checkpoint_repository:
source: HF | GCS | S3 | AZURE | REMOTE_URL | BASETEN_TRAINING
repo: "model-repository-name"
revision: main # Optional, only for HF
runtime_secret_name: hf_access_token # Optional, for private repos
```
Quantization format for model weights (simplified from v1).
**Options:**
* `no_quant`: precision of the repo (fp16 or bf16). BIS-LLM also supports quantized checkpoints from nvidia-modelopt libraries.
* `fp8`: FP8 weights + 16-bit KV cache
* `fp8_kv`: FP8 weights + FP8 KV cache
* `fp4`: FP4 weights + 16-bit KV cache (B200 only)
* `fp4_kv`: FP4 weights + FP8 KV cache (B200 only)
* `fp4_mlp_only`: FP4 MLP layers only + 16-bit KV cache (B200 only)
For detailed quantization guidance including hardware requirements, calibration strategies, and model-specific recommendations, see [Quantization guide](/engines/performance-concepts/quantization-guide).
```yaml theme={"system"}
build:
quantization_type: fp8
```
Configuration for post-training quantization calibration.
```yaml theme={"system"}
quantization_config:
calib_size: 1024
calib_dataset: "abisee/cnn_dailymail"
calib_max_seq_length: 2048
```
Number of GPUs to use during the build process. Auto-detected from resources when unset. Minimum: 1, with no fixed maximum.
```yaml theme={"system"}
build:
num_builder_gpus: 4 # For large models or complex quantization
```
Skip the engine build step and use a pre-built model that does not require quantization. Use when you have a pre-built engine from model cache.
```yaml theme={"system"}
build:
skip_build_result: true
```
## Runtime configuration
The `runtime` section configures inference engine behavior.
Maximum sequence length (context) for single requests. Range: 1 to 1048576.
```yaml theme={"system"}
runtime:
max_seq_len: 131072 # 128K context
```
Maximum number of input sequences processed concurrently. Range: 1 to 2048.
```yaml theme={"system"}
runtime:
max_batch_size: 128 # Lower for better latency
```
Maximum number of batched input tokens after padding removal. Range: 65 to 131072.
```yaml theme={"system"}
runtime:
max_num_tokens: 16384 # Higher for better throughput
```
Number of GPUs to use for tensor parallelism. Auto-detected from resources. Minimum: 1, with no fixed maximum (set it to the number of GPUs in your `accelerator` setting).
```yaml theme={"system"}
runtime:
tensor_parallel_size: 4 # For large models
```
Enable chunked prefilling for long sequences.
```yaml theme={"system"}
runtime:
enable_chunked_prefill: true
```
Model name returned in API responses.
```yaml theme={"system"}
runtime:
served_model_name: "gpt-oss-120b"
```
Preview. Pass-through configuration patches for the v2 inference stack. Fields under `patch_kwargs` may change without notice; keys that overlap standard runtime fields (or `build_config`) are logged as errors at deploy time — set those through the standard `runtime:` fields instead.
```yaml theme={"system"}
runtime:
patch_kwargs:
custom_setting: "value"
advanced_config:
nested_setting: true
```
## Complete configuration examples
### Qwen3-30B-A3B-Instruct-2507 MoE with FP4 on B200
```yaml theme={"system"}
model_name: Qwen3-30B-A3B-Instruct-2507-FP4
resources:
accelerator: B200:1
cpu: '4'
memory: 40Gi
use_gpu: true
trt_llm:
inference_stack: v2
build:
checkpoint_repository:
source: HF
repo: "Qwen/Qwen3-Coder-30B-A3B-Instruct"
revision: main
quantization_type: fp4
quantization_config:
calib_size: 2048
calib_dataset: "abisee/cnn_dailymail"
calib_max_seq_length: 4096
num_builder_gpus: 1
runtime:
max_seq_len: 65536
max_batch_size: 256
max_num_tokens: 8192
tensor_parallel_size: 1
enable_chunked_prefill: true
served_model_name: "Qwen3-30B-A3B-Instruct-2507"
```
### GPT-OSS 120B on B200:1 with no\_quant
This example deploys GPT-OSS with default settings. For production throughput with Eagle speculative decoding on B200, see [Speculative decoding for BIS-LLM](/engines/bis-llm/advanced-features#speculative-decoding) and [Advanced features for BIS-LLM](/engines/bis-llm/advanced-features).
```yaml theme={"system"}
model_name: gpt-oss-120b-b200
resources:
accelerator: B200:1
cpu: '4'
memory: 40Gi
use_gpu: true
trt_llm:
inference_stack: v2
build:
checkpoint_repository:
source: HF
repo: "openai/gpt-oss-120b"
revision: main
runtime_secret_name: hf_access_token
quantization_type: no_quant
quantization_config:
calib_size: 1024
calib_dataset: "abisee/cnn_dailymail"
calib_max_seq_length: 2048
runtime:
max_seq_len: 131072
max_batch_size: 256
max_num_tokens: 16384
tensor_parallel_size: 1
enable_chunked_prefill: true
served_model_name: "gpt-oss-120b"
```
### DeepSeek V3
This example deploys a pre-quantized ModelOpt checkpoint with `no_quant`. For higher throughput on DeepSeek V3 family models, use multi-GPU B200 layouts with MTP speculative decoding or disaggregated serving. See [Speculative decoding for BIS-LLM](/engines/bis-llm/advanced-features#speculative-decoding) and [Disaggregated serving](/engines/bis-llm/advanced-features#disaggregated-serving).
```yaml theme={"system"}
model_name: nvidia/DeepSeek-V3.1-NVFP4
resources:
accelerator: B200:4
cpu: '8'
memory: 80Gi
use_gpu: true
trt_llm:
inference_stack: v2
build:
checkpoint_repository:
source: HF
repo: "nvidia/DeepSeek-V3.1-NVFP4"
revision: main
runtime_secret_name: hf_access_token
quantization_type: no_quant # nvidia/DeepSeek-V3.1-NVFP4 is already modelopt compatible
quantization_config:
calib_size: 1024
calib_dataset: "abisee/cnn_dailymail"
calib_max_seq_length: 2048
runtime:
max_seq_len: 131072
max_batch_size: 256
max_num_tokens: 16384
tensor_parallel_size: 4
enable_chunked_prefill: true
served_model_name: "nvidia/DeepSeek-V3.1-NVFP4"
```
## Hardware selection
**GPU recommendations for v2:**
* **B200**: Best for FP4 quantization and next-gen performance
* **H100**: Best for FP8 quantization and production workloads
* **Multi-GPU**: Required for large MoE models (>30B parameters)
**Configuration guidelines:**
| **Model Size** | **Recommended GPU** | **Quantization** | **Tensor Parallel** |
| -------------- | ------------------- | ---------------- | ------------------- |
| `<30B` MoE | H100:2-4 | FP8 | 2-4 |
| 30-100B MoE | H100:4-8 | FP8 | 4-8 |
| 100B+ MoE | B200:4-8 | FP4 | 4-8 |
| Dense >30B | H100:2-4 | FP8 | 2-4 |
## Related
* [BIS-LLM overview](/engines/bis-llm/overview): Main engine documentation.
* [Migrate from Engine-Builder-LLM](/engines/bis-llm/migrate-from-v1): Translate a v1 configuration to BIS-LLM (v2).
* [Advanced features for BIS-LLM](/engines/bis-llm/advanced-features): KV-aware routing, disaggregated serving, and speculative decoding.
* [Structured outputs for BIS-LLM](/inference/structured-outputs): JSON schema validation.
* [Model deployment examples](/examples/overview): Concrete deployment examples.
# Migrate from Engine-Builder-LLM
Source: https://docs.baseten.co/engines/bis-llm/migrate-from-v1
Translate a v1 Engine-Builder-LLM configuration to BIS-LLM (v2), including the autoscaling, speculation, and routing changes that aren't just renames
Engine-Builder-LLM is the v1 inference stack. BIS-LLM is the v2 stack. The two share much of the same `trt_llm` schema but differ in what counts as build configuration, what counts as runtime configuration, and how autoscaling, speculation, and routing work. This page covers the field-by-field translation and the semantic changes that aren't just renames.
## The shape change
v2 simplifies the `build:` section to five fields (`checkpoint_repository`, `quantization_type`, `quantization_config`, `num_builder_gpus`, `skip_build_result`) and moves everything else to `runtime:`. The build validator rejects v1-only fields under `build:` with explicit error messages.
**v1 (Engine-Builder-LLM):**
```yaml config.yaml theme={"system"}
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "Qwen/Qwen3-4B"
max_seq_len: 32768
max_batch_size: 256
max_num_tokens: 8192
quantization_type: fp8_kv
tensor_parallel_count: 1
plugin_configuration:
paged_kv_cache: true
use_paged_context_fmha: true
use_fp8_context_fmha: true
runtime:
kv_cache_free_gpu_mem_fraction: 0.9
enable_chunked_context: true
```
**v2 (BIS-LLM):**
```yaml config.yaml theme={"system"}
trt_llm:
inference_stack: v2
build:
checkpoint_repository:
source: HF
repo: "Qwen/Qwen3-4B"
quantization_type: fp8_kv
runtime:
max_seq_len: 32768
max_batch_size: 256
max_num_tokens: 8192
tensor_parallel_size: 1
enable_chunked_prefill: true
```
## Migration steps
**To translate a v1 build configuration to v2**:
The order matters only for step 1 (the inference stack declaration must come first); the rest are independent.
1. Add `inference_stack: v2` at the top of `trt_llm:`.
2. Remove `base_model`. v2 detects the architecture from the checkpoint automatically.
3. Move `max_seq_len`, `max_batch_size`, and `max_num_tokens` from `build:` to `runtime:`.
4. Rename `tensor_parallel_count` to `tensor_parallel_size` and move it to `runtime:`.
5. Remove `plugin_configuration`. v2 handles `paged_kv_cache`, `use_paged_context_fmha`, and `use_fp8_context_fmha` automatically.
6. Remove `speculator`. v1 lookahead decoding is not supported in v2; see [Speculative decoding moves to the Management API](#speculative-decoding-moves-to-the-management-api) below.
7. Replace `enable_chunked_context: true` with `enable_chunked_prefill: true` if it was set.
## Semantic changes (not just renames)
The field translation above keeps your deployment running, but four behaviors change in ways that affect how you should configure and operate the v2 deployment.
### Speculative decoding moves to the Management API
v1 lookahead decoding lives in `config.yaml` under `trt_llm.build.speculator`. v2 doesn't support lookahead. Instead, BIS-LLM offers Eagle, MTP, and N-gram speculative decoding through the Management API `speculative_config` block, not through `config.yaml`. See [Speculative decoding](/engines/bis-llm/advanced-features#speculative-decoding) for the configuration shape. Eagle and MTP require Enterprise; [contact your Baseten representative](mailto:support@baseten.co) to enable.
### Autoscaling switches to token-based
v1 deployments use Baseten's [standard request-concurrency autoscaler](/deployment/autoscaling/overview): replicas scale based on `concurrency_target` and `target_utilization_percentage`. v2 deployments use [token-based autoscaling](/engines/performance-concepts/autoscaling-engines#bis-llm) instead: scale on `target_in_flight_tokens`. The v2 deployment API rejects `concurrency_target` and `target_utilization_percentage`. Convert your v1 concurrency target to a token target using:
```math theme={"system"}
target\_in\_flight\_tokens = concurrency\_target × average\_tokens\_per\_request
```
For a model averaging 4K input and 1K output tokens at v1 `concurrency_target` of 10, the v2 token target is roughly 50,000.
### KV-aware routing becomes available
v1 has no equivalent. Workloads with prefix-overlapping requests (long shared system prompts, multi-turn conversations, agentic loops) can enable [KV-aware routing](/engines/bis-llm/advanced-features#kv-aware-routing) on the v2 deployment to substantially reduce time-to-first-token through cache reuse. KV-aware routing requires Enterprise.
### Disaggregated serving becomes available
v1 has no equivalent. Workloads with high prefill-to-decode imbalance (long-context inference, mixed-length traffic) can use [disaggregated serving](/engines/bis-llm/advanced-features#disaggregated-serving) to split prefill and decode onto independent replica groups. Disaggregated serving requires Enterprise.
## Validation errors you might see
The v2 build validator rejects v1-only fields with explicit errors. The most common during migration:
| Error | Cause | Fix |
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `Field trt_llm.build.base_model is not allowed to be set when using v2 inference stack` | `base_model` left in `build:` | Remove. v2 auto-detects from the checkpoint. |
| `Field trt_llm.build. is not allowed to be set when using v2 inference stack` | v1 runtime fields (`max_seq_len`, `max_batch_size`, `max_num_tokens`, `tensor_parallel_count`, `plugin_configuration`) still in `build:` | Move them to `runtime:`. Rename `tensor_parallel_count` to `tensor_parallel_size`. |
| `Field trt_llm.build.speculator is not allowed to be set when using v2 inference stack` | `speculator` block kept from v1 | Remove. Use the Management API `speculative_config` block instead. |
## After migrating
Watch these metrics during and after the cutover:
* `tps_per_request` and `concurrent_requests` should stay similar or improve.
* `autoscaler_in_flight_tokens` is the new load signal. Tune `target_in_flight_tokens` based on observed values; aim for the [50,000-150,000 starting range](/engines/performance-concepts/autoscaling-engines#set-target-in-flight-tokens).
* `speculation_rate` is available once Eagle or MTP is configured through the Management API.
See [BIS-LLM observability](/engines/bis-llm/overview#observability) for the full metric set across the three monitoring domains.
## Related
* [BIS-LLM overview](/engines/bis-llm/overview): Main engine documentation.
* [BIS-LLM configuration](/engines/bis-llm/bis-llm-config): Complete v2 YAML reference.
* [Engine-Builder-LLM configuration](/engines/engine-builder-llm/engine-builder-config): v1 reference for comparison.
* [Token-based autoscaling](/engines/performance-concepts/autoscaling-engines#bis-llm): v2 autoscaling configuration.
* [Speculative decoding](/engines/bis-llm/advanced-features#speculative-decoding): v2 speculative decoding (Eagle, MTP, N-gram).
# Overview
Source: https://docs.baseten.co/engines/bis-llm/overview
Token-based autoscaling, KV-aware routing, disaggregated serving, and speculative decoding for MoE and large dense models
BIS-LLM (Baseten Inference Stack v2) is the engine for Mixture of Experts (MoE) models and large dense LLMs. It targets MoE families (DeepSeek V3.x, Qwen3MoE, Kimi-K2, Llama 4, GLM-4.7, GPT-OSS 120B) and the largest dense models, where the standard request-based autoscaler and a single-server inference engine both leave performance on the table. The v2 stack adds token-based autoscaling, KV-aware routing, disaggregated serving, expert parallel load balancing, and DP attention. Deployments mirror build artifacts to the [Baseten Delivery Network](/development/model/bdn) so cold starts stay fast.
## Production features
BIS-LLM ships four features that the standard inference path doesn't include. Token-based autoscaling lives on the [Autoscaling engines](/engines/performance-concepts/autoscaling-engines#bis-llm) page; the other three are documented together in [Advanced features for BIS-LLM](/engines/bis-llm/advanced-features).
Scales replicas on `target_in_flight_tokens` rather than request concurrency, so mixed-length prompt workloads scale on real compute load.
Routes requests to the worker most likely to serve them from KV cache. Lower time-to-first-token on prefix-overlapping traffic.
Splits prefill and decode onto independent worker groups that scale separately.
Eagle, MTP, and N-gram speculation. Multiple tokens per forward pass on supported architectures.
## A canonical configuration
The `trt_llm` block in `config.yaml` configures the build and runtime. A pre-quantized DeepSeek V3 deployment on B200 looks like:
```yaml config.yaml theme={"system"}
model_name: deepseek-v3-1-nvfp4
resources:
accelerator: B200:4
use_gpu: true
trt_llm:
inference_stack: v2
build:
checkpoint_repository:
source: HF
repo: "nvidia/DeepSeek-V3.1-NVFP4"
runtime_secret_name: hf_access_token
quantization_type: no_quant # ModelOpt-quantized checkpoint
runtime:
max_seq_len: 131072
max_batch_size: 256
tensor_parallel_size: 4
enable_chunked_prefill: true
served_model_name: "deepseek-v3"
```
After `truss push`, the build compiles the engine, the BDN mirrors weights to GPU-local storage, and the deployment exposes OpenAI-compatible `/v1/chat/completions`. The four production features above each plug in through their own configuration blocks; see [BIS-LLM configuration](/engines/bis-llm/bis-llm-config) for the complete reference and additional examples (GPT-OSS 120B, Qwen3-MoE, Llama 3.3 70B).
For tuning advice on a specific or fine-tuned model, [contact your Baseten representative](mailto:support@baseten.co).
## OpenAI-compatible inference
BIS-LLM deployments expose `/v1/chat/completions`, `/v1/completions`, and `/v1/embeddings` (where applicable). Standard OpenAI client SDKs work without modification:
```python theme={"system"}
from openai import OpenAI
import os
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-xxxxxx.api.baseten.co/environments/production/sync/v1"
)
response = client.chat.completions.create(
model="not-required",
messages=[{"role": "user", "content": "Explain mixture of experts in two sentences."}],
)
```
[Structured outputs](/inference/structured-outputs) and [function calling](/inference/function-calling) are supported through the standard OpenAI parameters and have their own reference pages.
## Observability
BIS-LLM emits metrics from three components. Each has its own dashboard section:
| Domain | Metric prefix | Page |
| -------------------- | -------------------------- | ----------------------------------------------------------------------------------- |
| Autoscaler decisions | `autoscaler_*` | [Autoscaling engines](/engines/performance-concepts/autoscaling-engines#monitoring) |
| Router and KV cache | `kv_cache_*` | [KV-aware routing](/engines/bis-llm/advanced-features#kv-aware-routing) |
| Engine and request | engine-level metrics below | This page |
Engine-level metrics, available on every BIS-LLM deployment:
| Metric | What it measures |
| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `tps_per_request` | Tokens per second per request. |
| `input_tokens` / `output_tokens` | Total token throughput across the deployment. |
| `input_tokens_per_request` / `output_tokens_per_request` | Per-request token averages. |
| `concurrent_requests` | Currently in-flight request count. |
| `speculation_rate` | Draft-token acceptance rate when speculative decoding is active. High rates indicate the draft model is well-aligned. |
| `cpu_usage` / `memory_usage` / `gpu_usage` / `gpu_memory_usage` | Resource utilization per replica. |
| `replica_count_by_status` | Replica counts grouped by lifecycle status. |
Start with `tps_per_request` to confirm replicas handle load as expected. If you run Enterprise features, add `kv_cache_hit_rate` (KV-aware routing, in the router domain) or `speculation_rate` (Eagle/MTP) next. See [Advanced features for BIS-LLM](/engines/bis-llm/advanced-features#speculative-decoding) for speculative-decoding configuration that produces `speculation_rate`.
## Migrate from Engine-Builder-LLM
Engine-Builder-LLM is the v1 stack. Migrating to BIS-LLM is mostly moving runtime fields out of `build:`, renaming `tensor_parallel_count` to `tensor_parallel_size`, and removing fields v2 handles automatically (`plugin_configuration`, `base_model`). Autoscaling, speculation, and routing also change in ways that aren't just renames. See [Migrate from Engine-Builder-LLM](/engines/bis-llm/migrate-from-v1) for the field-by-field mapping, the semantic changes, and the validation errors you might see during cutover.
## Related
* [BIS-LLM configuration reference](/engines/bis-llm/bis-llm-config): Complete v2 configuration options.
* [Migrate from Engine-Builder-LLM](/engines/bis-llm/migrate-from-v1): Translate a v1 configuration to BIS-LLM.
* [Advanced features for BIS-LLM](/engines/bis-llm/advanced-features): KV-aware routing, disaggregated serving, and speculative decoding.
* [Autoscaling engines](/engines/performance-concepts/autoscaling-engines#bis-llm): Configure target in-flight tokens for BIS-LLM deployments.
* [Structured outputs](/inference/structured-outputs): JSON schema validation.
* [Examples section](/examples/overview): Concrete deployment examples.
# Custom engine builder
Source: https://docs.baseten.co/engines/engine-builder-llm/custom-engine-builder
Implement custom model.py for business logic, logging, and advanced inference patterns
Implement custom business logic, request handling, and inference patterns in `model.py` while maintaining TensorRT-LLM performance. Custom engine builder enables billing integration, request tracing, fan-out generation, and multi-response workflows.
## Overview
The custom engine builder lets you:
* **Implement business logic**: Billing, usage tracking, access control.
* **Add custom logging**: Request tracing, performance monitoring, audit trails.
* **Create advanced inference patterns**: Fan-out generation, custom chat templates.
* **Integrate external services**: APIs, databases, monitoring systems.
* **Optimize performance**: Concurrent processing, custom batching strategies.
## When to use custom engine builder
### Ideal use cases
**Business logic integration:**
* **Usage tracking**: Monitor token usage per customer/request.
* **Access control**: Implement custom authentication/authorization.
* **Rate limiting**: Custom rate limiting based on user tiers.
* **Audit logging**: Compliance and security requirements.
**Advanced inference patterns:**
* **Fan-out generation**: Generate multiple responses from one request.
* **Custom chat templates**: Domain-specific conversation formats.
* **Multi-response workflows**: Parallel processing of variations.
* **Conditional generation**: Business rule-based output modification.
**Performance and monitoring:**
* **Custom logging**: Request tracing, performance metrics.
* **Concurrent processing**: Parallel generation for improved throughput.
* **Usage analytics**: Track patterns and optimize accordingly.
* **Error handling**: Custom error responses and fallback logic.
## Implementation
### Fan-out generation example
Multi-generation fan-out generates multiple texts from a single request. Running them sequentially ensures the KV cache is created before subsequent generations.
```python model/model.py theme={"system"}
# model/model.py
import copy
import asyncio
from typing import Any, Dict, List, Optional, Tuple
from fastapi import HTTPException, Request
from starlette.responses import JSONResponse, StreamingResponse
Message = Dict[str, str] # {"role": "...", "content": "..."}
class Model:
def __init__(self, trt_llm, **kwargs) -> None:
self._secrets = kwargs["secrets"]
self._engine = trt_llm["engine"]
async def predict(self, model_input: Dict[str, Any], request: Request) -> Any:
# Validate request structure
if not isinstance(model_input, dict):
raise HTTPException(status_code=400, detail="Request body must be a JSON object.")
# Enforce non-streaming for this example
if bool(model_input.get("stream", False)):
raise HTTPException(status_code=400, detail="stream=true is not supported here; set stream=false.")
# Extract base messages and fan-out tasks
prompt_key, base_messages = self._get_base_messages(model_input)
n, suffix_tasks = self._parse_fanout(model_input)
# Build reusable request (don't forward fan-out params to engine)
base_req = copy.deepcopy(model_input)
base_req.pop("suffix_messages", None)
# Extract debug ID for logging/tracing
debug_id = request.headers.get("X-Debug-ID", "")
# Run sequential generations
per_gen_payloads: List[Any] = []
async def run_generation(i: int) -> Any:
msgs_i = copy.deepcopy(base_messages)
if suffix_tasks is not None:
msgs_i.extend(suffix_tasks[i])
base_req[prompt_key] = msgs_i
# Debug logging
if debug_id:
print(f"Running generation {debug_id} {i} with messages: {msgs_i}")
# Time the generation
start_time = asyncio.get_event_loop().time()
resp = await self._engine.chat_completions(request=request, model_input=base_req)
end_time = asyncio.get_event_loop().time()
# Debug logging
if debug_id:
duration = end_time - start_time
print(f"Result Generation {debug_id} {i} response: {resp} (took {duration:.3f}s)")
# Validate response type
if isinstance(resp, StreamingResponse) or hasattr(resp, "body_iterator"):
raise HTTPException(status_code=400, detail="Engine returned streaming but stream=false was requested.")
return resp
# Run first generation
payload = await run_generation(0)
per_gen_payloads.append(payload)
# Run remaining generations concurrently
if n > 1:
results = await asyncio.gather(*(run_generation(i) for i in range(1, n)))
per_gen_payloads.extend(results)
# Convert to OpenAI-ish multi-choice response
out = self._to_openai_choices(per_gen_payloads)
return JSONResponse(content=out.model_dump())
# Helper methods
def _get_base_messages(self, model_input: Dict[str, Any]) -> Tuple[str, List[Message]]:
"""Extract and validate base messages from request."""
if "prompt" in model_input:
raise HTTPException(status_code=400, detail='Use "messages" instead of "prompt" for chat models.')
if "messages" not in model_input:
raise HTTPException(status_code=400, detail='Request must include "messages" field.')
key = "messages"
msgs = model_input.get(key)
if not isinstance(msgs, list):
raise HTTPException(status_code=400, detail=f'"{key}" must be a list of messages.')
for m in msgs:
if not isinstance(m, dict) or "role" not in m or "content" not in m:
raise HTTPException(status_code=400, detail=f'Each item in "{key}" must have role+content.')
return key, msgs
def _parse_fanout(self, model_input: Dict[str, Any]) -> Tuple[int, Optional[List[List[Message]]]]:
"""Parse and validate fan-out configuration."""
suffix = model_input.get("suffix_messages", None)
if not isinstance(suffix, list) or any(not isinstance(t, list) for t in suffix):
raise HTTPException(status_code=400, detail='"suffix_messages" must be a list of tasks (each task is a list of messages).')
if len(suffix) < 1 or len(suffix) > 256:
raise HTTPException(status_code=400, detail='"suffix_messages" must have between 1 and 256 tasks.')
for task in suffix:
for m in task:
if not isinstance(m, dict) or "role" not in m or "content" not in m:
raise HTTPException(status_code=400, detail="Each suffix message must have role+content.")
return len(suffix), suffix
def _to_openai_choices(self, payloads: List[Any]) -> Any:
"""Convert multiple payloads to OpenAI-style choices."""
base = payloads[0]
if hasattr(base, "choices") and hasattr(base, "model_dump"):
new_choices = []
for i, p in enumerate(payloads):
c0 = p.choices[0]
# Ensure index matches OpenAI n semantics
try:
c0.index = i
except Exception:
c0 = c0.model_copy(update={"index": i})
new_choices.append(c0)
# Aggregate usage statistics
base.usage.completion_tokens += p.usage.completion_tokens
base.usage.prompt_tokens += p.usage.prompt_tokens
base.usage.total_tokens += p.usage.total_tokens
base.choices = new_choices
return base
raise HTTPException(status_code=500, detail=f"Unsupported engine response type for fanout. {type(base)}")
async def chat_completions( # if you need to use /v1/completions use def completions(..)
self,
model_input: Dict[str, Any],
request: Request,
) -> Any:
# alias to predict, so that both /predict and (/sync)/v1/chat/completions work
return await self.predict(model_input, request)
```
### Fan-out generation configuration
To deploy the above example, create a new directory, for example, `fanout` and create a `fanout/model/model.py` file.
Then create the following `config.yaml` at `fanout/config.yaml`
```yaml config.yaml theme={"system"}
model_name: Multi-Generation-LLM
resources:
accelerator: H100
cpu: '2'
memory: 20Gi
use_gpu: true
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "meta-llama/Llama-3.1-8B-Instruct"
quantization_type: fp8
runtime:
served_model_name: "Multi-Generation-LLM"
```
Finally, push the model with `truss push`. TRT-LLM engine builds require a published deployment and do not support `--watch` mode.
## How routing works
Custom engine builder exposes two endpoint paths:
* **`/predict`**: calls your `predict()` method. Use this for custom request formats, business logic, or non-OpenAI patterns.
* **`/v1/chat/completions`**: calls `chat_completions()` if defined, otherwise falls back to `predict()`. Use this for OpenAI-compatible clients.
To make both paths work, define `chat_completions` as an alias to `predict` (as shown in the fan-out example above). If you only define `predict`, the `/v1/chat/completions` endpoint still works but goes through your `predict` method with the raw OpenAI-format input.
## Limitations and considerations
### What custom engine builder cannot do
**Custom tokenization:**
* Cannot modify the underlying tokenizer implementation
* Cannot add custom vocabulary or special tokens
* Must use the model's native tokenization
**Model architecture changes:**
* Cannot modify the TensorRT-LLM engine structure
* Cannot change attention mechanisms or model layers
* Cannot add custom model components
### When to use standard engine instead
* Standard chat completions without special requirements
* No need for business logic integration
## Monitoring and debugging
### Request tracing
```python theme={"system"}
import uuid
import os
from contextlib import asynccontextmanager
class Model:
def __init__(self, trt_llm, **kwargs):
self._engine = trt_llm["engine"]
self._trace_enabled = os.environ.get("enable_tracing", True)
@asynccontextmanager
async def _trace_request(self, request_id: str):
"""Context manager for request tracing."""
if self._trace_enabled:
print(f"[TRACE] Start: {request_id}")
start_time = time.time()
try:
yield
finally:
if self._trace_enabled:
duration = time.time() - start_time
print(f"[TRACE] End: {request_id} (duration: {duration:.3f}s)")
async def predict(self, model_input: Dict[str, Any], request: Request) -> Any:
request_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))
async with self._trace_request(request_id):
# Main logic here
response = await self._engine.chat_completions(request=request, model_input=model_input)
return response
```
## Related
* [Engine-Builder-LLM overview](/engines/engine-builder-llm/overview): Main engine documentation.
* [Engine-Builder-LLM configuration](/engines/engine-builder-llm/engine-builder-config): Complete reference config.
* [Examples section](/examples/overview): Deployment examples.
* [Chains documentation](/development/chain/overview): Multi-model workflows.
# Configuration reference
Source: https://docs.baseten.co/engines/engine-builder-llm/engine-builder-config
Complete reference config for dense text generation models
This reference covers all build and runtime options for Engine-Builder-LLM deployments. All settings use the `trt_llm` section in `config.yaml`.
## Configuration structure
```yaml theme={"system"}
trt_llm:
inference_stack: v1 # Always v1 for Engine-Builder-LLM
build:
base_model: decoder
checkpoint_repository: {...}
max_seq_len: 131072
max_batch_size: 256
max_num_tokens: 8192
quantization_type: no_quant | fp8 | fp8_kv | fp4 | fp4_kv | fp4_mlp_only
quantization_config: {...}
tensor_parallel_count: 1
plugin_configuration: {...}
speculator: {...} # Optional for lookahead decoding
runtime:
kv_cache_free_gpu_mem_fraction: 0.9
enable_chunked_context: true
batch_scheduler_policy: guaranteed_no_evict
served_model_name: "model-name"
total_token_limit: 500000
```
## Build configuration
Fields are tagged **Required**, **Optional**, or **Computed**. Computed fields are set by the engine; do not configure them manually.
The `build` section configures model compilation and optimization settings.
**Required.** The base model architecture for your model checkpoint.
**Options:**
* `decoder`: For CausalLM models (Llama, Mistral, Qwen, Gemma, Phi)
```yaml theme={"system"}
build:
base_model: decoder
```
**Required.** Specifies where to find the model checkpoint. Repository must be a valid Hugging Face model repository with the standard structure (config.json, tokenizer files, model weights).
**Source options:**
* `HF`: Hugging Face Hub (default)
* `GCS`: Google Cloud Storage
* `S3`: AWS S3
* `AZURE`: Azure Blob Storage
* `REMOTE_URL`: HTTP URL to tar.gz file
* `BASETEN_TRAINING`: Baseten Training checkpoints
For training checkpoint deployment, see [Deploy with optimized inference engines](/training/deploy-with-engine-builder). For cloud storage sources (GCS, S3, Azure), see [Deploy from cloud storage](/engines/performance-concepts/cloud-storage-deployment).
```yaml theme={"system"}
checkpoint_repository:
source: HF
repo: "meta-llama/Llama-3.3-70B-Instruct"
revision: main
runtime_secret_name: hf_access_token
```
**Optional.** Maximum sequence length (context) for single requests. Range: 1 to 1048576.
```yaml theme={"system"}
build:
max_seq_len: 131072 # 128K context
```
**Optional.** Maximum number of input sequences processed concurrently. Range: 1 to 2048.
Keep this at 256. It only affects performance when lookahead decoding is enabled.
Recommended not to be set below 8 to keep performance dynamic for various problems.
```yaml theme={"system"}
build:
max_batch_size: 256
```
**Optional.** Maximum number of batched input tokens after padding removal in each batch. Range: 65 to 1048576 (`gt=64, le=1048576` in schema).
If `enable_chunked_context: false`, this also limits the `max_seq_len` that can be processed. Recommended: `8192` or `16384`.
```yaml theme={"system"}
build:
max_num_tokens: 16384
```
**Optional.** Specifies the quantization format for model weights.
**Options:**
* `no_quant`: `FP16`/`BF16` precision
* `fp8`: `FP8` weights + 16-bit KV cache
* `fp8_kv`: `FP8` weights + `FP8` KV cache
* `fp4`: `FP4` weights + 16-bit KV cache (B200 only)
* `fp4_kv`: `FP4` weights + `FP8` KV cache (B200 only)
* `fp4_mlp_only`: `FP4` MLP only + 16-bit KV (B200 only)
For detailed quantization guidance, see [Quantization Guide](/engines/performance-concepts/quantization-guide).
```yaml theme={"system"}
build:
quantization_type: fp8_kv
```
**Optional.** Configuration for post-training quantization calibration.
**Fields:**
* `calib_size`: Size of calibration dataset (64-16384, multiple of 64). Defines how many rows of the train split with text column to take.
* `calib_dataset`: HuggingFace dataset for calibration. Dataset must have 'text' column (str type) for samples, or 'train' split as subsection.
* `calib_max_seq_length`: Maximum sequence length for calibration (default: 2048).
```yaml theme={"system"}
build:
quantization_type: fp8
quantization_config:
calib_size: 1536
calib_dataset: "abisee/cnn_dailymail"
calib_max_seq_length: 1536
```
**Optional.** Number of GPUs to use for tensor parallelism. Minimum: 1, with no fixed maximum. Must equal the number of GPUs in your `accelerator` resource setting.
```yaml theme={"system"}
build:
tensor_parallel_count: 4 # For 70B+ models
```
**Optional.** TensorRT-LLM plugin configuration for performance optimization.
**Fields:**
* `paged_kv_cache`: Enable paged KV cache (recommended: true)
* `use_paged_context_fmha`: Enable paged context FMHA (recommended: true)
* `use_fp8_context_fmha`: Enable `FP8` context FMHA (requires `fp8_kv` or `fp4_kv` quantization)
The engine auto-selects GEMM plugin settings from your model architecture and quantization type.
```yaml theme={"system"}
build:
plugin_configuration:
paged_kv_cache: true
use_paged_context_fmha: true
use_fp8_context_fmha: true # For FP8_KV quantization
```
**Optional.** Configuration for speculative decoding with lookahead. For detailed configuration, see [Lookahead decoding](/engines/engine-builder-llm/lookahead-decoding).
**Fields:**
* `speculative_decoding_mode`: `LOOKAHEAD_DECODING` (recommended)
* `lookahead_windows_size`: Window size for speculation (minimum 1)
* `lookahead_ngram_size`: N-gram size for patterns (minimum 1)
* `lookahead_verification_set_size`: Verification buffer size (minimum 1)
* `enable_b10_lookahead`: Enable Baseten's lookahead algorithm
```yaml theme={"system"}
build:
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
lookahead_windows_size: 3
lookahead_ngram_size: 8
lookahead_verification_set_size: 3
enable_b10_lookahead: true
```
**Optional.** Number of GPUs to use during the build job. Only set this if you encounter errors during the build job. It has no impact once the model reaches the deploying stage. If not set, equals `tensor_parallel_count`.
```yaml theme={"system"}
build:
num_builder_gpus: 2
```
## Runtime configuration
The `runtime` section configures inference engine behavior.
**Optional.** Fraction of GPU memory to reserve for KV cache. Set a value between 0 and 1.
```yaml theme={"system"}
runtime:
kv_cache_free_gpu_mem_fraction: 0.85
```
**Optional.** Enable chunked prefilling for long sequences.
```yaml theme={"system"}
runtime:
enable_chunked_context: true
```
**Optional.** Policy for scheduling requests in batches.
**Options:**
* `max_utilization`: Maximize GPU utilization (may evict requests)
* `guaranteed_no_evict`: Guarantee request completion (recommended)
```yaml theme={"system"}
runtime:
batch_scheduler_policy: guaranteed_no_evict
```
**Optional.** Model name returned in API responses.
```yaml theme={"system"}
runtime:
served_model_name: "Llama-3.3-70B-Instruct"
```
**Optional.** Default maximum number of tokens to generate per request when not specified by the client. If not set, the engine uses its own default.
```yaml theme={"system"}
runtime:
request_default_max_tokens: 4096
```
**Optional.** Number of bytes to reserve on host (CPU) memory for KV cache offloading. Set to a high value to enable KV cache offloading from GPU to host memory. Only set this if you need to support longer contexts than GPU memory alone can handle.
```yaml theme={"system"}
runtime:
kv_cache_host_memory_bytes: 10000000000 # ~10GB host memory for KV cache
```
**Optional.** Maximum number of tokens that can be scheduled at once.
```yaml theme={"system"}
runtime:
total_token_limit: 1000000
```
## Configuration examples
### Llama 3.3 70B
```yaml theme={"system"}
model_name: Llama-3.3-70B-Instruct
resources:
accelerator: H100:4
cpu: '4'
memory: 40Gi
use_gpu: true
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "meta-llama/Llama-3.3-70B-Instruct"
revision: main
runtime_secret_name: hf_access_token
max_seq_len: 131072
max_batch_size: 256
max_num_tokens: 8192
quantization_type: fp8_kv
tensor_parallel_count: 4
plugin_configuration:
paged_kv_cache: true
use_paged_context_fmha: true
use_fp8_context_fmha: true
quantization_config:
calib_size: 1024
calib_dataset: "abisee/cnn_dailymail"
calib_max_seq_length: 2048
runtime:
kv_cache_free_gpu_mem_fraction: 0.9
enable_chunked_context: true
batch_scheduler_policy: guaranteed_no_evict
served_model_name: "Llama-3.3-70B-Instruct"
```
After `truss push`, the build compiles the model with TensorRT-LLM (typically 10-30 minutes for a 70B model). Once deployed, the model is available at your production endpoint with OpenAI-compatible chat completions.
### Qwen 2.5 32B with lookahead decoding
```yaml theme={"system"}
model_name: Qwen-2.5-32B-Lookahead
resources:
accelerator: H100:2
cpu: '2'
memory: 20Gi
use_gpu: true
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "Qwen/Qwen2.5-32B-Instruct"
revision: main
max_seq_len: 32768
max_batch_size: 128
max_num_tokens: 8192
quantization_type: fp8_kv
tensor_parallel_count: 2
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
lookahead_windows_size: 3
lookahead_ngram_size: 8
lookahead_verification_set_size: 3
enable_b10_lookahead: true
plugin_configuration:
paged_kv_cache: true
use_paged_context_fmha: true
use_fp8_context_fmha: true
runtime:
kv_cache_free_gpu_mem_fraction: 0.85
enable_chunked_context: true
batch_scheduler_policy: guaranteed_no_evict
served_model_name: "Qwen-2.5-32B-Instruct"
```
After `truss push`, the build compiles with lookahead decoding enabled. Lookahead works best with batch sizes under 32. The configuration above sets `max_batch_size: 128` to allow burst capacity while keeping typical load in the optimal range.
### Small model on L4
```yaml theme={"system"}
model_name: Llama-3.2-3B-Instruct
resources:
accelerator: L4
cpu: '1'
memory: 10Gi
use_gpu: true
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "meta-llama/Llama-3.2-3B-Instruct"
revision: main
max_seq_len: 8192
max_batch_size: 256
max_num_tokens: 4096
quantization_type: fp8
tensor_parallel_count: 1
plugin_configuration:
paged_kv_cache: true
use_paged_context_fmha: true
use_fp8_context_fmha: false
runtime:
kv_cache_free_gpu_mem_fraction: 0.9
enable_chunked_context: true
batch_scheduler_policy: guaranteed_no_evict
served_model_name: "Llama-3.2-3B-Instruct"
```
After `truss push`, the build completes in a few minutes on L4. The deployed model serves chat completions at your production sync URL.
### B200 with `FP4` quantization
```yaml theme={"system"}
model_name: Qwen-2.5-32B-FP4
resources:
accelerator: B200
cpu: '2'
memory: 20Gi
use_gpu: true
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "Qwen/Qwen2.5-32B-Instruct"
revision: main
max_seq_len: 32768
max_batch_size: 256
max_num_tokens: 8192
quantization_type: fp4_kv
tensor_parallel_count: 1
plugin_configuration:
paged_kv_cache: true
use_paged_context_fmha: true
use_fp8_context_fmha: true
quantization_config:
calib_size: 1024
calib_dataset: "abisee/cnn_dailymail"
calib_max_seq_length: 2048
runtime:
kv_cache_free_gpu_mem_fraction: 0.9
enable_chunked_context: true
batch_scheduler_policy: guaranteed_no_evict
served_model_name: "Qwen-2.5-32B-Instruct"
```
## Version overrides
**Optional.** Pin specific component versions to override the backend's current defaults. This is useful for debugging or matching a known-working configuration.
```yaml theme={"system"}
trt_llm:
version_overrides:
briton_version: "0.20.0_v0.1.5rc1"
engine_builder_version: "0.20.0.post8.dev1"
bei_version: "1.8.7"
```
| Field | What it pins |
| ------------------------ | -------------------------------------------- |
| `engine_builder_version` | **Optional.** Engine-Builder-LLM build image |
| `briton_version` | **Optional.** Briton server image |
| `bei_version` | **Optional.** BEI server image |
| `v2_llm_version` | **Optional.** BIS-LLM (v2) server image |
The `engine_builder_version`, `briton_version`, and `bei_version` strings must start with a digit. This rule does not apply to `v2_llm_version`. If unset, the backend inserts the current default at deploy time (**Computed**).
## Validation and troubleshooting
### Common errors
**Error:** `FP8 quantization is only supported on L4, H100, H200, B200`
* **Cause:** Using `FP8` quantization on unsupported GPU.
* **Fix:** Use H100 or newer GPU, or use `no_quant`.
**Error:** `FP4 quantization is only supported on B200`
* **Cause:** Using `FP4` quantization on unsupported GPU.
* **Fix:** Use B200 GPU or `FP8` quantization.
**Error:** `Using fp8 context fmha requires fp8 kv, or fp4 with kv cache dtype`
* **Cause:** Mismatch between quantization and context FMHA settings.
* **Fix:** Use `fp8_kv` quantization or disable `use_fp8_context_fmha`.
**Error:** `Tensor parallelism and GPU count must be the same`
* **Cause:** Mismatch between `tensor_parallel_count` and GPU count.
* **Fix:** Ensure `tensor_parallel_count` matches `accelerator` count.
### Performance tuning
**For lowest latency:**
* Reduce `max_batch_size` and `max_num_tokens`.
* Use `batch_scheduler_policy: guaranteed_no_evict`.
* Consider smaller models or quantization.
**For highest throughput:**
* Increase `max_batch_size` and `max_num_tokens`.
* Use `batch_scheduler_policy: max_utilization`.
* Enable quantization on supported hardware.
**For cost optimization:**
* Use L4 GPUs with `FP8` quantization.
* Choose appropriately sized models.
* Tune `max_seq_len` to your actual requirements.
## Model repository structure
All model sources (S3, GCS, HuggingFace, or tar.gz) must follow the standard HuggingFace repository structure. Files must be in the root directory, similar to running:
```bash theme={"system"}
git clone https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct
```
### Required files
**Model configuration (`config.json`):**
* `max_position_embeddings`: Limits maximum context size (content beyond this is truncated).
* `vocab_size`: Vocabulary size for the model.
* `architectures`: Must include `LlamaForCausalLM`, `MistralForCausalLM`, or similar causal LM architectures. Custom code is typically not read.
* `torch_dtype`: Default inference dtype (`float16` or `bfloat16`). Cannot be a pre-quantized model.
**Model weights (`model.safetensors`):**
* Or: `model.safetensors.index.json` + `model-xx-of-yy.safetensors` (sharded).
* Convert to safetensors if you encounter issues with other formats.
* Cannot be a pre-quantized model. Model must be an `fp16`, `bf16`, or `fp32` checkpoint.
**Tokenizer files (`tokenizer_config.json` and `tokenizer.json`):**
* For maximum compatibility, use "FAST" tokenizers compatible with Rust.
* Cannot contain custom Python code.
* For chat completions: must contain `chat_template`, a Jinja2 template.
### Architecture support
| **Model family** | **Supported architectures** | **Notes** |
| ---------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Llama** | `LlamaForCausalLM` | Full support for Llama 3. For Llama 4, use BIS-LLM. |
| **Mistral** | `MistralForCausalLM` | Including v0.3 and Small variants. |
| **Qwen** | `Qwen2ForCausalLM`, `Qwen3ForCausalLM` | Including Qwen 2.5 and Qwen 3 series. |
| **QwenMoE** | `Qwen3MoEForCausalLM` | Specific support for Qwen3MoE. |
| **Gemma** | `GemmaForCausalLM` | Including Gemma 2 and Gemma 3 series, **`bf16` only**. Gemma uses int8 GEMM kernels that are incompatible with FP8 quantization. Use `no_quant` (BF16) or deploy on BIS-LLM for FP8 support. |
## Best practices
### Model size and GPU selection
| **Model size** | **Recommended GPU** | **Quantization** | **Tensor parallel** |
| -------------- | ------------------- | ---------------- | ------------------- |
| `<8B` | H100\_40GB / H100 | `FP8_KV` | 1 |
| 8B-30B | H100 / B200 | `FP8` / `FP8_KV` | 1 |
| 30B-70B | H100 | `FP8` / `FP8_KV` | 2-4 |
| `70B+` | H100 / B200 | `FP8` / `FP4` | 4-8 |
### Production recommendations
* Use `quantization_type: fp8_kv` for best performance/accuracy balance.
* Set `max_batch_size` based on your expected traffic patterns.
* Enable `paged_kv_cache` and `use_paged_context_fmha` for optimal performance.
### Development recommendations
* Use `quantization_type: no_quant` for fastest iteration.
* Set smaller `max_seq_len` to reduce build time.
* Use `batch_scheduler_policy: guaranteed_no_evict` for predictable behavior.
# Speculative decoding
Source: https://docs.baseten.co/engines/engine-builder-llm/lookahead-decoding
Lookahead decoding on Engine-Builder-LLM (v1) for code generation and predictable content
Lookahead decoding is a speculative decoding technique that provides 2x-4x faster inference for suitable workloads by predicting future tokens using n-gram patterns. It's particularly effective for coding agents and content with predictable patterns.
## Overview
Lookahead decoding identifies n-gram patterns in the input context and past tokens, speculates on future tokens by generating candidate sequences, verifies those predictions against the model's actual output, and accepts the verified tokens in a single step. The model still produces every token: it accepts the longest run of guessed tokens that matches its own output, and at the first mismatch it keeps that prefix and falls back to its own next token.
The output is identical to decoding token by token: the accepted tokens are exactly what the big model would have produced on its own, so speculative decoding changes only how many tokens clear per pass, not the result. The drafted run length depends on `lookahead_ngram_size`, `lookahead_windows_size`, and `lookahead_verification_set_size`, documented under [Configuration parameters](#configuration-parameters).
The technique works with any model compatible with Engine-Builder-LLM. Baseten's B10 Lookahead implementation searches up to 10M past tokens for n-gram matches across language patterns.
## When to use lookahead decoding
Lookahead decoding excels at code generation where programming language syntax creates predictable patterns, and function signatures, variable names, and common idioms all benefit. It also accelerates prompt lookup scenarios where you provide example completions in the prompt, and general low-latency use cases where you can trade slightly decreased throughput for faster individual responses.
### Limitations
* Lookahead is supported on A10G, L4, A100, H100\_40GB, H200, and H100.
* During speculative decoding, sampling is disabled and temperature is set to 0.0.
* Speculative decoding does not affect output quality. The output depends only on model weights and prompt.
* Speculative decoding generates multiple tokens at a time. Structured output (xgrammar, outlines) with state-machine guarantees (enforced json through `response_format`) isn't possible when lookahead decoding is enabled. Structured outputs are supported in standard Engine-Builder-LLM deployments without speculative decoding.
* Chunked prefill isn't supported with lookahead decoding. Baseten disables it automatically when lookahead is enabled.
## Configuration
### Basic lookahead configuration
Add a `speculator` section to your build configuration:
```yaml theme={"system"}
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "Qwen/Qwen2.5-7B-Instruct"
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
lookahead_windows_size: 3
lookahead_ngram_size: 8
lookahead_verification_set_size: 3
enable_b10_lookahead: true
```
### Configuration parameters
**`speculative_decoding_mode`**: Set to `LOOKAHEAD_DECODING` to enable Baseten's lookahead decoding algorithm.
**`lookahead_ngram_size`**: Size of n-gram patterns for speculation. Minimum: 1, with no fixed maximum. Use `4` for simple patterns, `8` for general use (recommended), or `16-32` for complex, highly predictable patterns.
**`lookahead_verification_set_size`**: Size of the verification buffer for speculation. Minimum: 1. Use `1` for high-confidence patterns, `3` for general use (recommended), or `5` for complex patterns requiring more verification.
**`lookahead_windows_size`**: Size of the speculation window. Minimum: 1. Pair it with `lookahead_verification_set_size` for your workload, as in the examples below.
**`enable_b10_lookahead`**: Enable Baseten's optimized lookahead algorithm. Default: `false`. Set it to `true` to use Baseten's B10 lookahead, recommended for the configurations on this page.
### Performance tuning
**For coding agents:** Use smaller window sizes with moderate n-gram sizes:
```yaml theme={"system"}
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
lookahead_windows_size: 1
lookahead_ngram_size: 8
lookahead_verification_set_size: 3
enable_b10_lookahead: true
```
**For general text generation:** Use balanced window and n-gram sizes:
```yaml theme={"system"}
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
lookahead_windows_size: 3
lookahead_ngram_size: 8
lookahead_verification_set_size: 3
enable_b10_lookahead: true
```
**For highly predictable content:** Use larger n-gram sizes with conservative verification:
```yaml theme={"system"}
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
lookahead_windows_size: 1
lookahead_ngram_size: 32
lookahead_verification_set_size: 1
enable_b10_lookahead: true
```
## Performance impact
### Batch size considerations
Lookahead decoding performs best with smaller batch sizes. Set `max_batch_size` to 32 or 64, depending on your use case.
### Memory overhead
Lookahead decoding doesn't require additional GPU memory.
## Production best practices
### Recommended configurations
**Standard (general purpose):** Balanced settings for general-purpose text generation:
```yaml theme={"system"}
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
lookahead_windows_size: 3
lookahead_ngram_size: 8
lookahead_verification_set_size: 3
enable_b10_lookahead: true
```
**Dynamic content (less predictable):**
Setting `enable_b10_lookahead: true` and `lookahead_windows_size: 1 + lookahead_verification_set_size: 1` will enable dynamic length speculation.
The speculated length will depend on the quality of the lookup match. By default we will speculate "a n-gram of k tokens for a k token suffix match".
```yaml theme={"system"}
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
lookahead_windows_size: 1
lookahead_ngram_size: 32
lookahead_verification_set_size: 1
enable_b10_lookahead: true
```
**Code generation (highly predictable):** Code has predictable syntax patterns, so you can use larger windows:
```yaml theme={"system"}
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
lookahead_windows_size: 7
lookahead_ngram_size: 5
lookahead_verification_set_size: 7
enable_b10_lookahead: true
```
### Build configuration
Set `max_batch_size` to control batch size limits:
```yaml theme={"system"}
trt_llm:
build:
max_batch_size: 64 # Recommended for lookahead decoding
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
# ... other speculator config
```
### Engine optimization
* Use smaller batch sizes for maximum benefit (1-8 requests)
* Monitor memory overhead and adjust KV cache allocation
* Test with your specific workload for optimal parameters
## Examples
### Code generation example
Deploy a coding model with lookahead decoding on an H100:
```yaml theme={"system"}
model_name: Qwen-Coder-7B-Lookahead
resources:
accelerator: H100
cpu: '1'
memory: 10Gi
use_gpu: true
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "Qwen/Qwen2.5-7B-Instruct"
quantization_type: fp8
max_batch_size: 64
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
lookahead_windows_size: 1
lookahead_ngram_size: 8
lookahead_verification_set_size: 1
enable_b10_lookahead: true
runtime:
served_model_name: "Qwen-Coder-7B"
```
### Python integration
Generate code using the chat completions API:
```python theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ['BASETEN_API_KEY'],
base_url="https://model-xxxxxx.api.baseten.co/environments/production/sync/v1"
)
# Generate Python function refactor with lookahead decoding
code = "def hello_world(name):\n print(42)"
response = client.chat.completions.create(
model="not-required",
messages=[
{
"role": "system",
"content": "You are a Python programming assistant. Write clean, efficient code."
},
{
"role": "user", # By providing the code anywhere in the prompt, the generation is much faster.
"content": f"Please refactor the following function to have docstrings. {code}"
}
],
temperature=0.0,
max_tokens=200
)
print(response.choices[0].message.content)
```
## Monitoring and troubleshooting
### Performance monitoring
Track tokens/second with and without lookahead to measure speed improvement, verification accuracy to see how often speculations succeed, and memory usage to catch overhead. If speed improvement diminishes, reduce batch size. Adjust window size based on content predictability and ngram size based on verification accuracy.
### Troubleshooting
**Common issues:**
**Low speed improvement:**
* Check if content is suitable for lookahead decoding
* Reduce batch size for better performance
* Adjust window and ngram sizes
**Blackwell support**
* Lookahead isn't fully supported in Engine-Builder-LLM, check [BIS-LLM overview](/engines/bis-llm/overview) for Blackwell support.
## Deprecation: DRAFT\_TOKENS\_EXTERNAL mode
`DRAFT_TOKENS_EXTERNAL` (external draft speculation) is discontinued in favor of `LOOKAHEAD_DECODING`, which yields better performance. If you set `speculative_decoding_mode: DRAFT_TOKENS_EXTERNAL`, the build fails with an error directing you to switch.
For model-based speculation (Eagle, MTP), use [BIS-LLM speculative decoding](/engines/bis-llm/advanced-features#speculative-decoding) instead. These methods are not available on Engine-Builder-LLM.
## Related
* [Engine-Builder-LLM overview](/engines/engine-builder-llm/overview): Main engine documentation.
* [Engine-Builder-LLM configuration](/engines/engine-builder-llm/engine-builder-config): Complete reference config.
* [BIS-LLM speculative decoding](/engines/bis-llm/advanced-features#speculative-decoding): Eagle, MTP, and NGram on v2.
* [Structured outputs documentation](/inference/structured-outputs): JSON schema validation.
* [Examples section](/examples/speculative-decoding): Deployment examples.
# LoRA support
Source: https://docs.baseten.co/engines/engine-builder-llm/lora-support
Multi-LoRA adapters for Engine-Builder-LLM engine
Engine-Builder-LLM supports multi-LoRA deployments with runtime adapter switching. Share base model weights across fine-tuned variants and switch adapters without redeployment.
## Overview
Deploy multiple LoRA adapters on a single base model and switch between them at inference time. The engine shares base model weights across all adapters for memory efficiency.
## Configuration
### Basic LoRA configuration
```yaml theme={"system"}
model_name: Qwen2.5-Coder-LoRA
resources:
accelerator: H100
cpu: '2'
memory: 20Gi
use_gpu: true
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "Qwen/Qwen2.5-Coder-1.5B-Instruct"
revision: "2e1fd397ee46e1388853d2af2c993145b0f1098a"
lora_adapters:
lora1:
repo: "ai-blond/Qwen-Qwen2.5-Coder-1.5B-Instruct-lora"
revision: "9cde18d8ed964b0519fb481cca6acd936b2ca811"
source: "HF"
lora_configuration:
max_lora_rank: 16
runtime:
served_model_name: "Qwen2.5-Coder-base"
```
## Limitations
* **Same rank and same modules**: All adapters in one deployment must share the same rank and target modules.
* **Build time availability**: The engine relies on numpy-style weights. These need to be pre-converted during deployment and distributed to each replica. For Engine-Builder-LLM, these repos must be known ahead of time.
* **Inference performance**: If you're using only one LoRA adapter, merging the adapter into the base weights provides better performance. Additional LoRA adapters add complexity to kernel selection and fundamentally increase flops.
## LoRA adapter configuration
### Adapter repository structure
LoRA adapters must follow the standard HuggingFace repository structure:
```
adapter-repo/
├── adapter_config.json
├── adapter_model.safetensors
└── README.md
```
### Required files
**adapter\_config.json**
```yaml theme={"system"}
# same base model for all configs
"base_model_name_or_path": "Qwen/Qwen2.5-Coder-1.5B-Instruct",
# same target modules among all lora adapters
"target_modules": [
"attn_q",
"attn_k",
"attn_v",
"attn_dense",
"mlp_h_to_4h",
"mlp_4h_to_h",
"mlp_gate"
],
# same rank among all lora adapters
"r": 16
```
**adapter\_model.safetensors**
* The LoRA adapter weights in safetensors format.
You don't create or upload any `.npy` files. The engine builder converts your adapter into its internal format (`model.lora_weights.npy`, `model.lora_config.npy`) server-side at build time, deriving the rank and target modules from `adapter_config.json`. Supply a standard Hugging Face adapter repo (`adapter_config.json` plus `adapter_model.safetensors`).
## Build configuration options
### `lora_adapters`
Dictionary of LoRA adapters to load during build. Adapter names must match the pattern `^[a-zA-Z0-9_\-\.:]+$`: letters, digits, underscores, hyphens, dots, and colons only.
```yaml theme={"system"}
lora_adapters:
adapter_name:
repo: "username/model-name"
revision: "main"
source: "HF" # or "GCS", "S3", "AZURE"
```
### `max_lora_rank`
Maximum LoRA rank for all adapters. Default: **64**. Set this to exactly the rank `r` you use across all adapters. A higher value wastes memory; a lower value truncates weights.
```yaml theme={"system"}
max_lora_rank: 16 # Match the r value in your adapter_config.json
```
### `lora_configuration`
LoRA-specific configuration nested under `build`:
```yaml theme={"system"}
lora_configuration:
max_lora_rank: 16
lora_target_modules: [] # Auto-detected from adapter_config.json
```
**Fields:**
* `max_lora_rank`: Maximum LoRA rank across all adapters. Default: 64.
* `lora_target_modules`: Target modules for LoRA. Usually auto-detected from adapter config.
## Engine inference configuration
The model parameter in OpenAI-format requests selects which adapter to use. For the above example, valid model names are `Qwen2.5-Coder-base` or `lora1`.
This lets you select different adapters at runtime through the OpenAI client.
## Related
* [Engine-Builder-LLM overview](/engines/engine-builder-llm/overview): Main engine documentation.
* [Engine-Builder-LLM configuration](/engines/engine-builder-llm/engine-builder-config): Complete reference config.
* [Custom engine builder](/engines/engine-builder-llm/custom-engine-builder): Custom model.py implementation.
* [Quantization guide](/engines/performance-concepts/quantization-guide): Performance optimization.
# Overview
Source: https://docs.baseten.co/engines/engine-builder-llm/overview
Dense LLM text generation with lookahead decoding and structured outputs
Engine-Builder-LLM optimizes dense text generation models with [TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM), delivering up to 4000 tokens/second for code generation with [lookahead decoding](/engines/engine-builder-llm/lookahead-decoding). The engine supports [structured outputs](/inference/structured-outputs) for JSON schema validation.
Engine-Builder-LLM deployments mirror build artifacts to the [Baseten Delivery Network](/development/model/bdn) automatically.
## Use cases
**Model families:**
* **Llama**: `meta-llama/Llama-3.3-70B-Instruct`, `meta-llama/Llama-3.2-3B-Instruct`. For Llama 4, use [BIS-LLM](/engines/bis-llm/overview).
* **Qwen**: `Qwen/Qwen3-235B-A22B-Instruct-2507-FP8`, `Qwen/Qwen2.5-72B-Instruct`.
* **Mistral**: `mistralai/Mistral-Small-24B-Instruct-2501`, `mistralai/Mistral-7B-Instruct-v0.3`.
* **GPT-OSS**: `openai/gpt-oss-20b`.
* **Nemotron**: `nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4`.
* **Gemma**: `google/gemma-3-27b-it`, `google/gemma-3-12b-it`.
* **Microsoft**: `microsoft/Phi-4`.
Engine-Builder-LLM handles high-throughput dialogue systems, coding assistants with lookahead decoding, and content generation with structured outputs. The engine's speculative decoding accelerates code generation by 2-4x, making it ideal for coding agents and JSON-heavy workloads.
### LoRA support
Engine-Builder-LLM serves multiple [LoRA adapters](/engines/engine-builder-llm/lora-support) per deployment with engine-level adapter switching. Define adapters at build time and select between them per request.
### Structured outputs
Engine-Builder-LLM supports OpenAI-compatible [structured outputs](/inference/structured-outputs) with JSON schema validation, including nested schemas and complex types.
### Key benefits
TensorRT-LLM compilation optimizes time-to-first-token.
Batching and kernel optimization maximize tokens per second.
Speculative decoding accelerates coding agents and predictable content.
JSON schema validation for controlled text generation.
## Architecture support
### Supported architectures
Engine-Builder-LLM auto-detects the Hugging Face `architectures` field from your checkpoint. The build maps each architecture to an optimized TensorRT-LLM backend:
| Hugging Face architecture | Backend | Example models |
| ---------------------------------------- | ------------ | ---------------------------------------------- |
| `LlamaForCausalLM`, `LLaMAForCausalLM` | LLaMA | Llama 3.2, Llama 3.3 |
| `MistralForCausalLM` | LLaMA | Mistral 7B, Mistral Small |
| `AquilaForCausalLM`, `AquilaModel` | LLaMA | Aquila family |
| `InternLMForCausalLM` | LLaMA | InternLM |
| `XverseForCausalLM` | LLaMA | Xverse |
| `Qwen2ForCausalLM` | Qwen | Qwen 2.5 dense |
| `Qwen2MoeForCausalLM` | Qwen | Qwen 2 MoE (prefer BIS-LLM for production MoE) |
| `Qwen3ForCausalLM` | Qwen3 | Qwen 3 dense |
| `Qwen3MoeForCausalLM` | Qwen3 | Qwen 3 MoE (for example, Qwen3-235B-A22B) |
| `Palmyra4ForCausalLM` | Qwen | Writer Palmyra |
| `Gemma2ForCausalLM`, `Gemma3ForCausalLM` | Gemma | Gemma 2/3 (`bf16` only) |
| `DeciLMForCausalLM` | Nemotron NAS | NVIDIA Nemotron NAS |
**Architectures not in this table:** If the checkpoint's `architectures` value is not listed (including `Phi3ForCausalLM` and other `ForCausalLM` variants), the build still uses `base_model: decoder` and auto-detects the architecture, logging a warning that it may miss model-specific optimizations. The legacy named `base_model` values (`llama`, `qwen`, `mistral`, `deepseek`) are no longer accepted and raise an error on push. Prefer checkpoints with explicit architecture metadata.
**Not on Engine-Builder-LLM:** Llama 4, DeepSeek MoE, Kimi, and GLM MoE use different architectures. Deploy them with [BIS-LLM](/engines/bis-llm/overview).
### Model size support
| **Model Size** | **Single GPU** | **Tensor Parallel** | **Recommended GPU** |
| -------------- | ---------------------- | ------------------- | -------------------------------- |
| `<8B` | H100\_40GB, H100, B200 | N/A | H100\_40GB (cost-effective) |
| 8B-30B | H100, B200 | TP1 | H100 |
| 30B-70B | H100 | TP2-TP4 | H100 (4 GPUs) |
| `70B+` | H100, B200 | TP4-TP8 | H100 (8 GPUs) or B200 (2-4 GPUs) |
## Advanced features
### Lookahead decoding
Lookahead decoding accelerates inference for code generation, JSON output, and templated content by speculating on future tokens using n-gram patterns.
**Best for:**
* **Code generation**: Highly predictable patterns in code.
* **Structured content**: Reliable JSON, YAML, XML generation.
* **Mathematical expressions**: Predictable mathematical notation.
* **Template completion**: Filling in predictable templates.
Enable lookahead decoding by adding a `speculator` section:
```yaml theme={"system"}
trt_llm:
build:
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
lookahead_windows_size: 1
lookahead_ngram_size: 8
lookahead_verification_set_size: 1
enable_b10_lookahead: true
```
**Performance impact:**
* **Speed improvement**: Up to 2x faster for code and structured content.
* **Prompt lookup**: Up to 10x faster for prompt-lookup workloads like code apply, reaching 4000 tokens/s per request on Qwen-3-8B with a single H100.
* **Optimal batch size**: Less than 32 requests for best performance.
### Structured outputs
Generate text that conforms to JSON schemas for reliable data extraction and controlled generation.
**Use cases:**
* **Data extraction**: Extract structured information from unstructured text.
* **API response generation**: Generate JSON responses for APIs.
* **Configuration generation**: Create structured configuration files.
* **Content validation**: Ensure generated content meets specific criteria.
Structured outputs work out of the box. Define a Pydantic schema:
```python theme={"system"}
import os
from pydantic import BaseModel
from openai import OpenAI
class User(BaseModel):
name: str
age: int
email: str
client = OpenAI(
api_key=os.environ['BASETEN_API_KEY'],
base_url="https://model-xxxxxx.api.baseten.co/environments/production/sync/v1"
)
response = client.beta.chat.completions.parse(
model="not-required",
messages=[
{"role": "user", "content": "Extract user info from: John is 25 years old and his email is john@example.com"}
],
response_format=User
)
user = response.choices[0].message.parsed
print(f"Name: {user.name}, Age: {user.age}, Email: {user.email}")
```
### Quantization options
Engine-Builder-LLM supports multiple [quantization](/engines/performance-concepts/quantization-guide) formats. For the full GPU support matrix, model-specific recommendations, and calibration guidance, see the [quantization guide](/engines/performance-concepts/quantization-guide).
| **Quantization** | **Minimum GPU** | **Memory reduction** |
| --------------------------------- | --------------- | -------------------- |
| `no_quant` | A100 | None |
| `fp8` | L4 | \~50% |
| `fp8_kv` | L4 | \~60% |
| `fp4` / `fp4_kv` / `fp4_mlp_only` | B200 | \~75% |
## Configuration examples
### Basic Llama 3.3 70B deployment
Llama 3.3 70B on H100 GPUs with `FP8` quantization:
```yaml theme={"system"}
model_name: Llama-3.3-70B-Instruct
resources:
accelerator: H100:4 # 4 GPUs for 70B model
cpu: '4'
memory: 40Gi
use_gpu: true
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "meta-llama/Llama-3.3-70B-Instruct"
revision: main
runtime_secret_name: hf_access_token
max_seq_len: 131072
max_batch_size: 256
max_num_tokens: 8192
quantization_type: fp8_kv
tensor_parallel_count: 4
plugin_configuration:
paged_kv_cache: true
use_paged_context_fmha: true
use_fp8_context_fmha: true
quantization_config:
calib_size: 1024
calib_dataset: "abisee/cnn_dailymail"
calib_max_seq_length: 2048
runtime:
kv_cache_free_gpu_mem_fraction: 0.9
enable_chunked_context: true
batch_scheduler_policy: guaranteed_no_evict
served_model_name: "Llama-3.3-70B-Instruct"
```
### Qwen 2.5 32B with lookahead decoding
Qwen 2.5 32B with speculative decoding for faster inference. See [Lookahead decoding](/engines/engine-builder-llm/lookahead-decoding) for the full configuration reference.
```yaml theme={"system"}
model_name: Qwen-2.5-32B-Lookahead
resources:
accelerator: H100:1
cpu: '2'
memory: 20Gi
use_gpu: true
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "Qwen/Qwen2.5-Coder-32B-Instruct"
revision: main
max_seq_len: 32768
max_batch_size: 128
max_num_tokens: 8192
quantization_type: fp8 # no fp8_kv for qwen2.5 models
tensor_parallel_count: 1
num_builder_gpus: 2 # Loaded in BF16 for quantization; requires ~2x32GB (2 H100s)
speculator:
speculative_decoding_mode: LOOKAHEAD_DECODING
lookahead_windows_size: 3
lookahead_ngram_size: 8
lookahead_verification_set_size: 3
enable_b10_lookahead: true
plugin_configuration:
paged_kv_cache: true
use_paged_context_fmha: true
use_fp8_context_fmha: true
runtime:
kv_cache_free_gpu_mem_fraction: 0.85
enable_chunked_context: true
batch_scheduler_policy: guaranteed_no_evict
served_model_name: "Qwen-2.5-Coder-32B-Instruct"
```
### Small model for cost-effective deployment
Llama 3.2 3B on an L4 GPU for cost efficiency:
```yaml theme={"system"}
model_name: Llama-3.2-3B-Instruct
resources:
accelerator: L4
cpu: '1'
memory: 10Gi
use_gpu: true
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "meta-llama/Llama-3.2-3B-Instruct"
revision: main
max_seq_len: 8192
max_batch_size: 256
max_num_tokens: 4096
quantization_type: fp8
tensor_parallel_count: 1
plugin_configuration:
paged_kv_cache: true
use_paged_context_fmha: true
use_fp8_context_fmha: false
runtime:
kv_cache_free_gpu_mem_fraction: 0.9
enable_chunked_context: true
batch_scheduler_policy: guaranteed_no_evict
served_model_name: "Llama-3.2-3B-Instruct"
```
## Integration examples
Engine-Builder-LLM deployments are OpenAI compatible. Point `base_url` to your model's production endpoint and use the standard OpenAI SDK:
```python theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ['BASETEN_API_KEY'],
base_url="https://model-xxxxxx.api.baseten.co/environments/production/sync/v1"
)
response = client.chat.completions.create(
model="not-required",
messages=[{"role": "user", "content": "Explain quantum computing in simple terms."}],
temperature=0.7,
max_tokens=500
)
print(response.choices[0].message.content)
```
For high-throughput batch processing, use the [Performance Client](/inference/performance-client). For [structured outputs](/inference/structured-outputs) and [function calling](/inference/function-calling), see their dedicated pages.
## Sizing and tuning
Throughput, latency, and cost depend on four levers: model size, quantization (`FP8` on H100 cuts memory roughly in half, `FP4` on B200 by 75%), tensor parallelism, and whether [lookahead decoding](/engines/engine-builder-llm/lookahead-decoding) earns its keep for your workload. For the full GPU support matrix and calibration guidance, see the [quantization guide](/engines/performance-concepts/quantization-guide). For per-flag detail on `max_seq_len`, `max_batch_size`, KV cache, and chunked prefill, see the [Engine-Builder-LLM configuration reference](/engines/engine-builder-llm/engine-builder-config).
## Related
* [Configure Engine-Builder-LLM deployments](/engines/engine-builder-llm/engine-builder-config): Complete build and runtime options.
* [Set up structured outputs](/inference/structured-outputs): JSON schema validation and controlled generation.
* [Enable lookahead decoding](/engines/engine-builder-llm/lookahead-decoding): Speculative decoding for coding agents.
* [Build custom inference logic](/engines/engine-builder-llm/custom-engine-builder): Custom model.py implementation.
* [Choose a quantization format](/engines/performance-concepts/quantization-guide): FP8/FP4 trade-offs and hardware requirements.
* [Deploy LoRA adapters](/engines/engine-builder-llm/lora-support): Multi-LoRA with runtime switching.
* [Scale Engine-Builder-LLM replicas](/engines/performance-concepts/autoscaling-engines#engine-builder-llm): Autoscaling settings and concurrency targets.
# Overview
Source: https://docs.baseten.co/engines/index
Inference engines for embeddings, dense LLMs, MoE models, and Enterprise serving
Baseten engines optimize model inference for specific architectures using [TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM). All engines mirror build artifacts to the [Baseten Delivery Network](/development/model/bdn) automatically.
* **[BEI](/engines/bei/overview):** Embedding, reranking, and classification models on causal architectures with `FP8` and `FP4` quantization.
* **[BEI-Bert](/engines/bei/bei-bert):** Bidirectional BEI variant tuned for BERT-family encoders and cold-start-sensitive models under 4B parameters.
* **[Engine-Builder-LLM](/engines/engine-builder-llm/overview):** Dense text generation for Llama, Qwen, Mistral, and Gemma with lookahead decoding and multi-LoRA support.
* **[BIS-LLM](/engines/bis-llm/overview):** MoE and Enterprise serving with KV-aware routing, disaggregated prefill/decode, and Eagle/MTP speculation.
## Choose an engine
Pick the row below that matches what you're deploying. Cost, quality, and latency targets drive later choices (GPU, quantization, autoscaling) inside that engine.
* **Embedding, reranking, classification, or NER models:** use [BEI](/engines/bei/overview) for decoder embedders (`Qwen3-Embedding`, `BAAI/bge`, `LlamaForSequenceClassification`) or [BEI-Bert](/engines/bei/bei-bert) for BERT-family encoders (`BERT`, `ModernBERT`, `EuroBERT`, `XLM-RoBERTa`). NER lives on [`BEI-Bert /predict_tokens`](/engines/bei/ner).
* **Dense text-generation LLMs** (`Llama 3` or `4`, `Qwen 3` or `3.5`, `Mistral`, `Gemma`, `Phi`, `GPT-OSS-20B`): use [Engine-Builder-LLM](/engines/engine-builder-llm/overview), with [lookahead decoding](/engines/engine-builder-llm/lookahead-decoding) and [multi-LoRA](/engines/engine-builder-llm/lora-support) available.
* **MoE models** (`GLM 5.x`, `Kimi K2.5` or `K2.6`, `DeepSeek V3`, `R1`, or `V4`, `MiniMax 2.5`, `Qwen3 MoE`, `GPT-OSS-120B`) **or workloads that need KV-cache-aware routing or disaggregated prefill/decode:** use [BIS-LLM](/engines/bis-llm/overview). Currently a co-engineering pilot.
* **Speech, image, video, or custom Python models:** ship a custom Truss. Browse [model examples](/examples/overview) for Whisper, Orpheus, Flux, and other pre-built deployments, or see [build your first model](/development/model/build-your-first-model) for custom inference logic.
If your workload doesn't fit one of the rows above (custom architectures, hybrid pipelines, BIS-LLM pilot access, sizing for unusual traffic shapes), email [support@baseten.co](mailto:support@baseten.co) and an engineer will route you.
## Performance and operations
* [Quantization guide](/engines/performance-concepts/quantization-guide): `FP8` and `FP4` trade-offs, GPU support, and per-engine options.
* [Autoscaling engines](/engines/performance-concepts/autoscaling-engines): Token-based and request-based scaling for engine deployments.
* [Cloud storage deployment](/engines/performance-concepts/cloud-storage-deployment): Deploy engines from S3 or GCS instead of Hugging Face.
* [Specialized model examples](/examples/overview): Pre-built Truss examples for Whisper, Orpheus, Flux, and other dedicated deployments.
## Compare engines
| Feature | BIS-LLM | Engine-Builder-LLM | BEI | BEI-Bert | Notes |
| ------------------------------------ | ------- | ------------------ | --- | -------- | -------------------------------------------------------------------------------- |
| **Quantization** | ✅ | ✅ | ✅ | ❌ | BEI-Bert: `FP16`/`BF16` only. |
| **KV quantization** | ✅ | ✅ | ⚠️ | ⚠️ | `FP8_KV`, `FP4_KV` supported. |
| **Lookahead decoding** | ❌ | ✅ | ❌ | ❌ | Engine-Builder-LLM (v1) only; BIS-LLM uses MTP/Eagle/N-gram speculation instead. |
| **Self-serviceable** | 🔒 | ✅ | ✅ | ✅ | BIS-LLM requires Enterprise; other engines are self-serve. |
| **KV-routing** | 🔒 | ❌ | ❌ | ❌ | BIS-LLM only. |
| **Disaggregated serving** | 🔒 | ❌ | ❌ | ❌ | BIS-LLM Enterprise. |
| **Tool calling & structured output** | ✅ | ✅ | ❌ | ❌ | Function calling support. |
| **Classification models** | ❌ | ❌ | ✅ | ✅ | Sequence classification. |
| **Embedding models** | ❌ | ❌ | ✅ | ✅ | Embedding generation. |
| **Mixture-of-experts** | ✅ | ⚠️ (Qwen3MoE only) | ❌ | ❌ | MoE models like `DeepSeek-R1`. |
| **MTP / Eagle / N-gram speculation** | 🔒 | ❌ | ❌ | ❌ | v2 speculative decoding with `speculative_config`. |
| **HTTP request cancellation** | ✅ | ⚠️ | ✅ | ✅ | Engine-Builder-LLM: within the first 10ms only. |
| **MultiModal Inputs** | 🔒 | ❌ | ⚠️ | ❌ | Selected architectures only. |
# Autoscaling engines
Source: https://docs.baseten.co/engines/performance-concepts/autoscaling-engines
Engine-specific autoscaling settings for BEI, Engine-Builder-LLM, and BIS-LLM
BEI, Engine-Builder-LLM, and BIS-LLM batch requests for throughput, so they need different autoscaling settings than standard models. BEI and Engine-Builder-LLM scale on **request concurrency** with engine-tuned targets. BIS-LLM scales on **target in-flight tokens** to account for the wide variance in LLM request size.
## Quick reference
| Setting | BEI | Engine-Builder-LLM |
| -------------------------- | ----------------------------------------------- | ----------------------------- |
| **Target utilization** | 25% | 40-50% |
| **Concurrency target** | 96+ (min >= 8) | 32-256 |
| **Special considerations** | Use Performance client for multi-payload routes | Never exceed max\_batch\_size |
BIS-LLM uses a token-aware metric instead of request concurrency. See the [BIS-LLM](#bis-llm) section.
For general autoscaling concepts, see [Autoscaling](/deployment/autoscaling/overview).
***
## BEI
BEI provides millisecond-range inference times and scales differently than other models. With too few replicas, backpressure can build up quickly.
### Recommendations
| Setting | Value | Why |
| ------------------ | ------------------ | ----------------------------------------------- |
| Target utilization | **25%** | Low target provides headroom for traffic spikes |
| Concurrency target | **96+** (min >= 8) | High concurrency allows maximum throughput |
| Autoscaling | **Enabled** | Required for variable traffic |
### Multi-payload routes
The `/rerank` and `/v1/embeddings` routes can send multiple items per request, which challenges request-based autoscaling. Each API call counts as one request regardless of how many items it contains.
Use the [Performance client](/inference/performance-client) for optimal scaling with multi-payload routes.
***
## Engine-Builder-LLM
Engine-Builder-LLM uses dynamic batching similar to BEI but doesn't face the multi-payload challenge.
### Recommendations
| Setting | Value | Why |
| ------------------ | ---------- | -------------------------------------- |
| Target utilization | **40-50%** | Accommodates dynamic batching behavior |
| Concurrency target | **32-256** | Match or stay below max\_batch\_size |
| Min concurrency | **>= 8** | Optimal performance floor |
### Concurrency target vs `max_batch_size`
`concurrency_target` tells the autoscaler how many concurrent requests each replica should handle. `max_batch_size` tells the engine how many sequences to batch in a single forward pass. They measure different things: concurrency is a scaling signal, batch size is an engine limit.
Setting `concurrency_target` higher than `max_batch_size` causes on-replica queueing. The autoscaler sends more requests than the engine can batch, and excess requests wait instead of scaling to a new replica. Always keep `concurrency_target` at or below `max_batch_size`.
### Lookahead decoding
If using lookahead decoding, set concurrency target to the same or slightly below `max_batch_size`. This allows lookahead to perform optimizations. This guidance applies to all Engine-Builder-LLM deployments, not just those using lookahead.
***
## BIS-LLM
BIS-LLM autoscales differently from Baseten's other engines. The [standard Baseten autoscaler](/deployment/autoscaling/overview) divides **in-flight requests** by a per-replica concurrency target to decide how many replicas to run. That works when requests cost about the same to serve, but LLM requests don't. One prompt might decode 50 tokens; the next might decode 10,000. Counting them as equal load over-provisions on short prompts and under-provisions on long ones.
The BIS-LLM engine scales on **target in-flight tokens** instead. An in-flight token is any token a replica is currently working on. The deployment API rejects `concurrency_target` and `target_utilization_percentage`. Configure scaling with `target_in_flight_tokens` only (replica bounds in the table below).
### How in-flight tokens are counted
The Planner's load measure is the sum of two per-worker counts:
* **Prefill tokens:** the uncached input tokens currently being processed across active requests. Tokens served from KV cache reuse do not count.
* **Decode tokens:** the full sequence length (input plus tokens generated so far) for every request currently decoding.
This is why request count alone misses the real load: a long-context decode with a 100K-token KV cache contributes 100K to the load measure even though it is "just one request."
The total across the deployment roughly equals `active_requests × average_tokens_per_request`, which makes targets easy to derive from request-based intuition.
### What you configure
You configure four standard fields plus a token target. All five are editable from the deployment's autoscaling settings in the Baseten UI.
```yaml config.yaml theme={"system"}
autoscaling_settings:
min_replica: 1
max_replica: 4
autoscaling_window: 300 # seconds; recommended 300 (5 minutes)
scale_down_delay: 300 # seconds; recommended 300 (5 minutes)
additional_autoscaling_config:
metrics:
- name: in_flight_tokens
target: 40000
```
| Setting | What it controls |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `min_replica` / `max_replica` | Replica bounds. Scale-to-zero is not supported. `min_replica` defaults to `1` when omitted. Set `max_replica` to cap scale-up during cold starts. |
| `autoscaling_window` | Sliding window (in seconds) used to average in-flight tokens before making a scaling decision. Longer windows smooth out short spikes; shorter windows react faster. A 5-minute (300s) window is a reasonable default. |
| `scale_down_delay` | Waiting period (in seconds) before removing replicas after load drops. |
| `metrics.target` | Target in-flight tokens per replica. This is the primary knob to tune. |
### Set target in-flight tokens
For most LLMs, a target in the **50,000 to 150,000** range is a sensible starting point. From there:
* **Lower target:** more replicas at a given load. More headroom, higher cost.
* **Higher target:** fewer replicas at a given load. Less headroom, lower cost.
If you're coming from another engine and already have a request concurrency target in mind, convert it directly. In-flight tokens roughly equals `active_requests × average_tokens_per_request`, so:
```math theme={"system"}
target = concurrency\_target × average\_tokens\_per\_request
```
`average_tokens_per_request` is approximately `average_input_tokens + average_output_tokens`. For a model averaging 4K input and 1K output tokens at a concurrency of 10:
```math theme={"system"}
target = 5{,}000 × 10 = 50{,}000
```
Once a target is set, the autoscaler computes desired replicas as:
```math theme={"system"}
desired\_replicas = avg\_in\_flight\_tokens / target\_in\_flight\_tokens
```
Start conservatively and adjust based on observed latency.
### Graceful scale-down with `scale_down_half_life_seconds`
Kubernetes (through Knative) allows scale-down of up to **50% of replicas per step**. For most services this is fine, but BIS-LLM deployments hold KV cache state on each worker. A sudden 50% drop in replica count means a 50% loss of KV cache space, which causes a wave of cache misses and TTFT spikes for cache-sensitive workloads (long shared system prompts, multi-turn conversations).
`scale_down_half_life_seconds` applies **exponential decay** to the current replica count, lowering it gradually over the configured half-life rather than allowing a single large drop. The default is **900 seconds (15 minutes)**, which keeps KV cache erosion gradual. Set it shorter to release capacity faster and shed KV cache state more abruptly; set it longer to keep replicas (and their cache) around for more reuse.
This setting lives in `b10_autoscaling_config` in the `llm_config` block of the Management API (`POST /v1/llm_models`), not in Truss `config.yaml`. It is not configurable from the UI.
```json theme={"system"}
{
"b10_autoscaling_config": {
"scale_down_half_life_seconds": 900
}
}
```
Recommended range: 600-1800 seconds. Setting it shorter risks the same abrupt KV cache loss the setting exists to prevent. Setting it longer wastes GPU cost.
### Known issues
Two failure modes are structural to the autoscaling loop, not configuration mistakes.
**Scale-up overshoot during rapid load increase.** Workers take time to start (model loading and warmup). Until they are healthy, they are not counted in the autoscaler's worker pool, so the Planner continues to see high per-worker load and keeps requesting more replicas. By the time all the new workers are healthy, the deployment may be over-provisioned.
Mitigation: set `max_replica` to cap the overshoot. Cold start time is the underlying constraint; there is no way to fully prevent this without reducing it.
**Scale-down thrashing and KV cache loss.** When workers scale down, their KV cache disappears with them. Aggressive or frequent scale-down forces full prefill on requests that would otherwise have hit cache (higher TTFT), and if many replicas drop at once a large fraction of total KV cache space vanishes simultaneously.
Mitigation: set `scale_down_half_life_seconds` to 600-1800 seconds and keep `scale_down_delay` modest. The half-life exists specifically to prevent abrupt large-scale downscales.
### Monitoring
The Planner emits autoscaler metrics directly. Start with `autoscaler_in_flight_tokens` to see what the autoscaler is currently observing, then reach for the averaged and policy-applied metrics when tuning.
| Metric | Type | What it measures |
| ---------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `autoscaler_in_flight_tokens` | Gauge | Instantaneous in-flight tokens across all workers. The primary product-visible metric, labeled with `exported_namespace` and `model_version_id`. |
| `autoscaler_avg_in_flight_tokens` | Gauge | Sliding-window average used for scaling decisions. |
| `autoscaler_avg_num_requests` | Gauge | Sliding-window average request count across all workers. |
| `autoscaler_avg_num_workers` | Gauge | Sliding-window average healthy worker count. The denominator for per-worker load. |
| `autoscaler_desired_scale` | Gauge | Raw desired scale from the token-based autoscaler, before policy. |
| `autoscaler_policy_desired_scale` | Gauge | Desired scale after policy is applied. |
| `autoscaler_rounded_desired_scale` | Gauge | Final integer scale sent to Kubernetes. |
What to watch:
* `autoscaler_rounded_desired_scale` pinned at `max_replica` for extended periods means the deployment is capacity-constrained. Raise the cap or the target.
* A large persistent gap between `autoscaler_desired_scale` and the actual replica count means scaling is too slow in one direction. Tune `autoscaling_window` for scale-up or `scale_down_half_life_seconds` for scale-down.
***
## Related
* [Configure autoscaling parameters](/deployment/autoscaling/overview): Full parameter reference.
* [Match autoscaling to your traffic pattern](/deployment/autoscaling/traffic-patterns): Pattern-specific settings.
* [Deploy BEI embedding models](/engines/bei/overview): General BEI documentation.
* [Deploy Engine-Builder-LLM models](/engines/engine-builder-llm/overview): Generation model details.
* [Deploy BIS-LLM models](/engines/bis-llm/overview): MoE and advanced LLM engine details.
* [Maximize throughput with the Performance Client](/inference/performance-client): Client usage for batch processing.
# Deploy from cloud storage
Source: https://docs.baseten.co/engines/performance-concepts/cloud-storage-deployment
Connect your S3 bucket, GCS bucket, Azure container, or Hugging Face repository to Baseten's TRT-LLM inference engines and deploy without re-uploading weights.
Deploying from cloud storage lets you use your existing infrastructure. The engine pulls weights from your storage at build time, compiles them with TensorRT-LLM, and serves the result as a production endpoint. You don't need to move or re-upload anything.
[Engine-Builder-LLM](/engines/engine-builder-llm/overview), [BEI](/engines/bei/overview), and [BIS-LLM](/engines/bis-llm/overview) all support this workflow.
To deploy from Baseten Training checkpoints instead, see [Deploy with optimized inference engines](/training/deploy-with-engine-builder).
## Storage sources
The `checkpoint_repository` field in your config specifies where the engine pulls weights from. The `source` field accepts the following providers:
* `S3`: Amazon S3 buckets.
* `GCS`: Google Cloud Storage.
* `AZURE`: Azure Blob Storage.
* `HF`: Hugging Face repositories.
The `revision` field pins a specific commit or branch. For Hugging Face repos, this is a git ref (branch name, tag, or commit SHA). If unset, the engine uses the default branch. For cloud storage sources (S3, GCS, Azure), `revision` is not applicable. The repo path points to a specific prefix.
Here's a minimal example using S3:
```yaml config.yaml theme={"system"}
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: S3 # or GCS, AZURE, HF
repo: s3://your-bucket/path/to/model/
```
## Private storage credentials
To access private storage, add a JSON secret to your [Baseten secrets manager](https://app.baseten.co/settings/secrets) and reference it with `runtime_secret_name` in your config.
Add a secret with your AWS credentials:
```json theme={"system"}
{
"aws_access_key_id": "XXXXX",
"aws_secret_access_key": "xxxxx/xxxxxx",
"aws_region": "us-west-2"
}
```
Then reference the secret in your config:
```yaml config.yaml theme={"system"}
secrets:
aws_secret_json: "set token in baseten workspace"
trt_llm:
build:
checkpoint_repository:
source: S3
repo: s3://your-bucket/path/to/model
runtime_secret_name: aws_secret_json
```
See [AWS S3 authentication](/development/model/bdn#aws-s3) for full setup details including OIDC.
Add a secret with your GCP service account credentials:
```json theme={"system"}
{
"private_key_id": "xxxxxxx",
"private_key": "-----BEGIN PRIVATE KEY-----\nMI",
"client_email": "b10-some@xxx-example.iam.gserviceaccount.com"
}
```
Then reference the secret in your config:
```yaml config.yaml theme={"system"}
secrets:
gcp_service_account: "set token in baseten workspace"
trt_llm:
build:
checkpoint_repository:
source: GCS
repo: gs://your-bucket/path/to/model
runtime_secret_name: gcp_service_account
```
See [Google Cloud Storage authentication](/development/model/bdn#google-cloud-storage) for full setup details including GCP OIDC.
Add a secret with your Azure account key:
```json theme={"system"}
{
"account_key": "xxxxx"
}
```
Then reference the secret in your config:
```yaml config.yaml theme={"system"}
secrets:
azure_secret_json: "set token in baseten workspace"
trt_llm:
build:
checkpoint_repository:
source: AZURE
repo: az://your-container/path/to/model
runtime_secret_name: azure_secret_json
```
Public repositories don't require a secret. For private or gated repositories, add your Hugging Face API token as a plain-text secret, then reference it in your config:
```yaml config.yaml theme={"system"}
secrets:
hf_access_token: "set token in baseten workspace"
trt_llm:
build:
checkpoint_repository:
source: HF
repo: meta-llama/Llama-3.1-8B
runtime_secret_name: hf_access_token
```
Get your token from [Hugging Face settings](https://huggingface.co/settings/tokens). The `runtime_secret_name` field defaults to `hf_access_token`, so you can omit it for public repos.
## Related
* [Configure Engine-Builder-LLM deployments](/engines/engine-builder-llm/engine-builder-config): Complete build and runtime options for LLMs.
* [Configure BEI deployments](/engines/bei/bei-reference): Complete configuration for encoder models.
* [Set up cloud storage authentication](/development/model/bdn): OIDC and service account authentication for cloud storage.
* [Manage deployment secrets](/development/model/secrets): Configure credentials for private storage.
# Quantization guide
Source: https://docs.baseten.co/engines/performance-concepts/quantization-guide
FP8 and FP4 trade-offs and hardware requirements for all engines
*Quantization* trades precision for speed and memory efficiency. This guide covers Baseten's supported formats, hardware requirements, and model-specific recommendations.
Two facts bound a format choice: which GPU families run it, and how much weight memory it saves. The matrix below shows both at once. Each row is a format, the columns mark which GPUs support it, and the bar on the right is its weight footprint measured against `FP16`. `FP8` runs everywhere and halves the footprint, while the `FP4` formats reach a quarter but require a B200.
A ✓ marks the GPU families that run a format, so you can rule out the ones your hardware cannot run before weighing memory. Each bar divides the format's bit width by the 16-bit `FP16` baseline: 8-bit formats land at half, 4-bit formats at a quarter. The bar measures model weights only. It excludes KV cache and activation memory, and it shows neither end-to-end memory savings nor any accuracy trade-off, both of which depend on the model and workload. `FP4_MLP_ONLY` is mixed precision, so its bar sits between `FP8` and `FP4` rather than at a single clean ratio.
## Quantization options
Quantization type availability depends on the engine and GPU.
### Engine support
| **Quantization** | [**BIS-LLM**](/engines/bis-llm/overview) | [**Engine-Builder-LLM**](/engines/engine-builder-llm/overview) | [**BEI**](/engines/bei/overview) |
| ---------------------- | ---------------------------------------- | -------------------------------------------------------------- | -------------------------------- |
| `FP8` | ✅ | ✅ | ✅ |
| `FP8_KV` | ✅ | ✅ | ⚠️ |
| `FP4` | ✅ | ✅ | ⚠️ |
| `FP4_KV` | ✅ | ✅ | ⚠️ |
| `FP4_MLP_ONLY` | ✅ | ✅ | ✅ |
| `no_quant` | ✅ | ✅ | ✅ |
| `INT8` / `SmoothQuant` | ❌ | ✅ | ❌ |
`_KV` quantization formats (`FP8_KV`, `FP4_KV`) store compressed KV cache state. Encoder models (BEI, BEI-Bert) do not use a decoder-style KV cache, so these formats are not applicable. The ⚠️ cells above mark that limitation, not partial support.
`INT8` and `SmoothQuant` quantization types are supported on Engine-Builder-LLM (v1) but rejected on BIS-LLM (v2). The v2 build raises an error at build time: use `FP8` or `FP4` instead, which provide better accuracy-to-compression ratios on modern GPUs.
### `no_quant` and pre-quantized checkpoints
Setting `quantization_type: no_quant` tells the engine to skip post-training quantization and use the checkpoint's native precision. This is the right choice in two scenarios:
1. **Unquantized FP16/BF16 checkpoints.** The engine uses the model's native dtype without any calibration step. This is the default for development and accuracy-critical deployments.
2. **Pre-quantized ModelOpt checkpoints.** Some Hugging Face repos ship with NVIDIA ModelOpt quantization already applied (indicated by an `hf_quant_config.json` file in the repo). For these checkpoints, set `quantization_type: no_quant`. The engine detects the ModelOpt config and applies the pre-baked quantization automatically. Attempting to re-quantize a ModelOpt checkpoint with a different `quantization_type` causes a build error.
**Example: deploying a pre-quantized ModelOpt checkpoint on BIS-LLM**
```yaml theme={"system"}
trt_llm:
inference_stack: v2
build:
checkpoint_repository:
source: HF
repo: "nvidia/DeepSeek-V3.1-NVFP4"
quantization_type: no_quant # ModelOpt quantization detected from hf_quant_config.json
```
Non-ModelOpt pre-quantized checkpoints (for example, GPTQ or AWQ safetensors) are not supported. The build rejects them with an error.
### GPU support
| **GPU type** | `FP8` | `FP8_KV` | `FP4` | `FP4_KV` | `FP4_MLP_ONLY` |
| ------------ | ----- | -------- | ----- | -------- | -------------- |
| **L4** | ✅ | ✅ | ❌ | ❌ | ❌ |
| **H100** | ✅ | ✅ | ❌ | ❌ | ❌ |
| **H200** | ✅ | ✅ | ❌ | ❌ | ❌ |
| **B200** | ✅ | ✅ | ✅ | ✅ | ✅ |
## Model recommendations
Some model families have specific quantization requirements that affect accuracy.
### Qwen2 models
Qwen2 retains QKV projection bias (attention bias), while Qwen3, Llama3, Llama2, and most other models remove it. This makes Qwen2 sensitive to symmetric KV cache quantization, so `FP8_KV` causes quality degradation. Use regular `FP8` instead and increase calibration size to 1024 or greater for better accuracy.
### Llama models
Llama variants work well with `FP8_KV` and standard calibration sizes (1024-1536). For B200 deployments, use `FP4_MLP_ONLY` for the best balance of speed and quality.
### BEI models (embeddings)
Use `FP8` for causal embedding models. Skip quantization for smaller models since the overhead isn't worth the minimal benefit and Bert is not supported. BEI doesn't support `FP8_KV` or other `_KV` formats because encoder models have no KV cache to quantize.
## Calibration
Quantization requires calibration data to determine optimal scaling factors. Larger models generally need more calibration samples.
### Calibration datasets
The default dataset is `cnn_dailymail` (general news text). For specialized models, or fine-tunes specific to a chat template, use domain-specific datasets when available.
For using a custom dataset, reference the huggingface name under `calib_dataset`, and make sure the dataset has a `train` split with a `text`/`messages` column.
When using the `messages` column, we require the tokenizer of your model to have a `apply_chat_template()` function on which we can apply `apply_chat_template(row["messages"]) for row in rows`.
If you want to use a dataset without preprocessing, you can provide a `text` column.
For chat-based calibration with thinking , we open-sourced [`baseten/quant_calibration_dataset_v1`](https://huggingface.co/datasets/baseten/quant_calibration_dataset_v1), to showcase an example.
### Calibration configuration
```yaml theme={"system"}
quantization_config:
calib_size: 768 # Number of samples
calib_dataset: "abisee/cnn_dailymail" # Dataset name
calib_max_seq_length: 1024 # Max sequence length
```
Increase `calib_size` for larger models. Use domain-specific datasets when available for better accuracy on specialized tasks.
## Hardware requirements
`FP4` quantization requires B200 GPUs. `FP8` runs on L4 and above.
| **Quantization** | **Minimum GPU** | **Recommended GPU** | **Memory reduction** |
| ---------------- | --------------- | ------------------- | -------------------- |
| `FP16`/`BF16` | A100 | H100 | None |
| `FP8` | L4 | H100 | \~50% |
| `FP8_KV` | L4 | H100 | \~60% |
| `FP4` | B200 | B200 | \~75% |
| `FP4_KV` | B200 | B200 | \~80% |
### Configuration examples
**Engine-Builder-LLM:**
```yaml theme={"system"}
trt_llm:
build:
base_model: decoder
quantization_type: fp8
quantization_config:
calib_size: 1024
```
**BIS-LLM:**
```yaml theme={"system"}
trt_llm:
inference_stack: v2
build:
quantization_type: fp8
quantization_config:
calib_size: 1024
runtime:
max_seq_len: 32768
```
**BEI:**
```yaml theme={"system"}
trt_llm:
build:
base_model: encoder
quantization_type: fp8
max_num_tokens: 16384
```
Set `quantization_type` in the build section and add `quantization_config` to customize calibration. BIS-LLM uses `inference_stack: v2` while Engine-Builder-LLM uses `base_model: decoder`.
## Best practices
### When to use quantization
Use `FP8` for production deployments to achieve cost-effective scaling. For memory-constrained environments, `FP8_KV` or `FP4` variants provide additional memory reduction. Quantization becomes essential for models over 15B parameters where memory and cost savings are significant.
### When to avoid quantization
Skip quantization when maximum accuracy is critical. Use `FP16`/`BF16` instead. Small models under 8B parameters see minimal benefit from quantization. BEI-Bert models don't support quantization at all. During research and development, `FP16` provides faster iteration without calibration overhead.
### Optimization tips
Use calibration datasets that match your domain for best accuracy. Test quantized models with your specific data before production deployment. Monitor the accuracy vs. performance trade-off and consider your hardware constraints when selecting quantization type.
## Related
* [Configure Engine-Builder-LLM quantization](/engines/engine-builder-llm/engine-builder-config): Dense model build options.
* [Configure BIS-LLM quantization](/engines/bis-llm/bis-llm-config): MoE model build options.
* [Configure BEI quantization](/engines/bei/bei-reference): Embedding model build options.
# Serve embeddings with BEI
Source: https://docs.baseten.co/examples/bei
Deploy embedding, reranking, and classification models on Baseten Embeddings Inference.
Baseten Embeddings Inference is Baseten's solution for production grade inference on embedding, classification and reranking models using TensorRT-LLM.
With Baseten Embeddings Inference you get the following benefits:
* Lowest-latency inference across any embedding solution (vLLM, SGlang, Infinity, TEI, Ollama)1
* Highest-throughput inference across any embedding solution (vLLM, SGlang, Infinity, TEI, Ollama) - thanks to XQA kernels, FP8 and dynamic batching.2
* High parallelism: up to 1400 client embeddings per second
* Cached model weights for fast vertical scaling and high availability - no Hugging Face hub dependency at runtime
* Ahead-of-time compilation, memory allocation and fp8 post-training quantization
### Get started with embedding models:
Embedding models are LLMs without a lm\_head for language generation.
Typical architectures that are supported for embeddings are `LlamaModel`, `BertModel`, `RobertaModel` or `Gemma2Model`, and contain the safetensors, config, tokenizer and sentence-transformer config files.
A good example is the repo [BAAI/bge-multilingual-gemma2](https://huggingface.co/BAAI/bge-multilingual-gemma2).
To deploy a model for embeddings, set the following config in your local directory.
```yaml config.yaml theme={"system"}
model_name: BEI-Linq-Embed-Mistral
resources:
accelerator: H100_40GB
use_gpu: true
trt_llm:
build:
base_model: encoder
checkpoint_repository:
# for a different model, change the repo to e.g. to "Salesforce/SFR-Embedding-Mistral"
# "BAAI/bge-en-icl" or "BAAI/bge-m3"
repo: "Linq-AI-Research/Linq-Embed-Mistral"
revision: main
source: HF
# only Llama, Mistral and Qwen Models support quantization.
# others, use: "quantization_type: no_quant"
quantization_type: fp8
```
With `config.yaml` in your local directory, you can deploy the model to Baseten.
```bash theme={"system"}
truss push --promote
```
Deployed embedding models are OpenAI compatible without any additional settings.
You may use the client code below to consume the model.
```python theme={"system"}
from openai import OpenAI
import os
client = OpenAI(
api_key=os.environ['BASETEN_API_KEY'],
# add the deployment URL
base_url="https://model-xxxxxx.api.baseten.co/environments/production/sync/v1"
)
embedding = client.embeddings.create(
input=["Baseten Embeddings are fast.", "Embed this sentence!"],
model="not-required"
)
```
### Example deployment of classification, reranking, and classification models
Besides embedding models, BEI deploys high-throughput rerank and classification models.
You can identify suitable architectures by their `ForSequenceClassification` suffix in the Hugging Face repo.
The use-case for these models is either Reward Modeling, Reranking documents in RAG or tasks like content moderation.
```yaml theme={"system"}
model_name: BEI-mixedbread-rerank-large-v2-fp8
resources:
accelerator: H100_40GB
cpu: '1'
memory: 10Gi
use_gpu: true
trt_llm:
build:
base_model: encoder
checkpoint_repository:
repo: michaelfeil/mxbai-rerank-large-v2-seq
revision: main
source: HF
# only Llama, Mistral and Qwen Models support quantization
quantization_type: fp8
```
As OpenAI does not offer reranking or classification, we are sending a simple request to the endpoint.
Depending on the model, you might want to apply a specific prompt template first.
```python theme={"system"}
import requests
import os
headers = {
f"Authorization": f"Bearer {os.environ['BASETEN_API_KEY']}"
}
# model specific prompt for mixedbread's reranker v2.
prompt = (
"<|endoftext|><|im_start|>system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.\n<|im_end|>\n<|im_start|>user\n"
"query: {query} \ndocument: {doc} \nYou are a search relevance expert who evaluates how well documents match search queries. For each query-document pair, carefully analyze the semantic relationship between them, then provide your binary relevance judgment (0 for not relevant, 1 for relevant).\nRelevance:<|im_end|>\n<|im_start|>assistant\n"
).format(query="What is Baseten?",doc="Baseten is a fast inference provider.")
requests.post(
headers=headers,
url="https://model-xxxxxx.api.baseten.co/environments/production/sync/predict",
json={
"inputs": prompt,
"raw_scores": True,
}
)
```
### Benchmarks and performance optimizations
Embedding models on BEI are fast, and offer currently the fastest implementation for embeddings across all open-source and closed-source providers.
The team behind the implementation is the authors of [infinity](https://github.com/michaelfeil/infinity).
We recommend using fp8 quantization for Llama, Mistral, and Qwen2 models on L4 or newer (L4, H100, H200, and B200).
Quality difference between fp8 and bfloat16 is often negligible: embedding models often retain >99% cosine similarity between both precisions,
and reranking models retain the ranking order despite a difference in the retained output.
For more details, check out the [technical launch post](https://www.baseten.co/blog/how-we-built-high-throughput-embedding-inference-with-tensorrt-llm/).
The team at Baseten has additional options for sharing cached model weights across replicas - for very fast horizontal scaling.
Please contact us to enable this option.
### Deploy custom or fine-tuned models on BEI
We support the deployment of the below models, as well as all finetuned variants of these models (same architecture & customized weights).
The following repositories are supported - this list is not exhaustive.
| Model Repository | Architecture | Function |
| ------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ------------------- |
| [`Salesforce/SFR-Embedding-Mistral`](https://huggingface.co/Salesforce/SFR-Embedding-Mistral) | MistralModel | embedding |
| [`BAAI/bge-m3`](https://huggingface.co/BAAI/bge-m3) | BertModel | embedding |
| [`BAAI/bge-multilingual-gemma2`](https://huggingface.co/BAAI/bge-multilingual-gemma2) | Gemma2Model | embedding |
| [`mixedbread-ai/mxbai-embed-large-v1`](https://huggingface.co/mixedbread-ai/mxbai-embed-large-v1) | BertModel | embedding |
| [`BAAI/bge-large-en-v1.5`](https://huggingface.co/BAAI/bge-large-en-v1.5) | BertModel | embedding |
| [`allenai/Llama-3.1-Tulu-3-8B-RM`](https://huggingface.co/allenai/Llama-3.1-Tulu-3-8B-RM) | LlamaForSequenceClassification | classifier |
| [`ncbi/MedCPT-Cross-Encoder`](https://huggingface.co/ncbi/MedCPT-Cross-Encoder) | BertForSequenceClassification | reranker/classifier |
| [`SamLowe/roberta-base-go_emotions`](https://huggingface.co/SamLowe/roberta-base-go_emotions) | XLMRobertaForSequenceClassification | classifier |
| [`mixedbread/mxbai-rerank-large-v2-seq`](https://huggingface.co/michaelfeil/mxbai-rerank-large-v2-seq) | Qwen2ForSequenceClassification | reranker/classifier |
| [`BAAI/bge-en-icl`](https://huggingface.co/BAAI/bge-en-icl) | LlamaModel | embedding |
| [`BAAI/bge-reranker-v2-m3`](https://huggingface.co/BAAI/bge-reranker-v2-m3) | BertForSequenceClassification | reranker/classifier |
| [`Skywork/Skywork-Reward-Llama-3.1-8B-v0.2`](https://huggingface.co/Skywork/Skywork-Reward-Llama-3.1-8B-v0.2) | LlamaForSequenceClassification | classifier |
| [`Snowflake/snowflake-arctic-embed-l`](https://huggingface.co/Snowflake/snowflake-arctic-embed-l) | BertModel | embedding |
| [`nomic-ai/nomic-embed-code`](https://huggingface.co/nomic-ai/nomic-embed-code) | Qwen2Model | embedding |
1 measured on H100-HBM3 (bert-large-335M, for BAAI/bge-en-icl: 9ms)
2 measured on H100-HBM3 (leading model architecture on MTEB, MistralModel-7B)
# Transcribe audio with Chains
Source: https://docs.baseten.co/examples/chains-audio-transcription
Process hours of audio in seconds using efficient chunking, distributed inference, and optimized GPU resources.
This guide walks through building an audio transcription pipeline using Chains. You'll break down large media files, distribute transcription tasks across autoscaling deployments, and leverage high-performance GPUs for rapid inference.
# Overview
This Chain enables fast, high-quality transcription by:
* **Partitioning** long files (10+ hours) into smaller segments.
* **Detecting silence** to optimize split points.
* **Parallelizing inference** across multiple GPU-backed deployments.
* **Batching requests** to maximize throughput.
* **Using range downloads** for efficient data streaming.
* Leveraging `asyncio` for concurrent execution.
# Chain structure
Transcription is divided into two processing layers:
1. **Macro chunks:** Large segments (\~300s) split from the source media file. These are processed in parallel to handle massive files efficiently.
2. **Micro chunks:** Smaller segments (\~5–30s) extracted from macro chunks and sent to the Whisper model for transcription.
# Implement the Chainlets
## `Transcribe` (Entrypoint Chainlet)
Handles transcription requests and dispatches tasks to worker Chainlets.
Function signature:
```python theme={"system"}
async def run_remote(
self,
media_url: str,
params: data_types.TranscribeParams
) -> data_types.TranscribeOutput:
```
**Steps:**
* Validates that the media source supports **range downloads**.
* Uses **FFmpeg** to extract metadata and duration.
* Splits the file into **macro chunks**, optimizing split points at silent sections.
* Dispatches **macro chunk tasks** to the MacroChunkWorker for processing.
* Collects **micro chunk transcriptions**, merges results, and returns the final text.
**Example request:**
```bash theme={"system"}
curl -X POST $INVOCATION_URL \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d ''
```
```json theme={"system"}
{
"media_url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4",
"params": {
"micro_chunk_size_sec": 30,
"macro_chunk_size_sec": 300
}
}
```
## `MacroChunkWorker` (Processing Chainlet)
Processes **macro chunks** by:
* **Extracting** relevant time segments using **FFmpeg**.
* **Streaming audio** instead of downloading full files for low latency.
* **Splitting segments** at silent points.
* **Encoding** audio in base64 for efficient transfer.
* **Distributing micro chunks** to the Whisper model for transcription.
This Chainlet **runs in parallel** with multiple instances autoscaled dynamically.
## `WhisperModel` (Inference Model)
A separately deployed **Whisper** model Chainlet handles speech-to-text transcription.
* Deployed **independently** to allow fast iteration on business logic without redeploying the model.
* Used **across different Chains** or accessed directly as a standalone model.
* Supports **multiple environments** (for example, dev, prod) using the same instance.
Whisper can also be deployed as a **standard Truss model**, separate from the Chain.
# Optimize performance
Even for very large files, **processing time remains bounded** by parallel execution.
## Key performance tuning parameters:
* `micro_chunk_size_sec` → Balance GPU utilization and inference latency.
* `macro_chunk_size_sec` → Adjust chunk size for optimal parallelism.
* **Autoscaling settings** → Tune concurrency and replica counts for load balancing.
Example speedup:
```json theme={"system"}
{
"input_duration_sec": 734.26,
"processing_duration_sec": 82.42,
"speedup": 8.9
}
```
# Deploy and run the Chain
## Deploy WhisperModel first:
```bash theme={"system"}
truss chains push whisper_chainlet.py
```
Copy the **invocation URL** and update `WHISPER_URL` in `transcribe.py`.
## Deploy the transcription Chain:
```bash theme={"system"}
truss chains push transcribe.py
```
## Run transcription on a sample file:
```bash theme={"system"}
curl -X POST $INVOCATION_URL \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d ''
```
***
# Next steps
* Learn more about [Chains](/development/chain/overview).
* Optimize GPU **autoscaling** for peak efficiency.
* Extend the pipeline with **custom business logic**.
# Build a RAG pipeline with Chains
Source: https://docs.baseten.co/examples/chains-build-rag
Combine retrieval and generation into a single compound workflow.
[Learn more about Chains](/development/chain/overview)
## Prerequisites
You need [uv](https://docs.astral.sh/uv/) installed and a [Baseten account](https://app.baseten.co/signup) with an [API key](https://app.baseten.co/settings/account/api_keys).
If you want to run this example in
[local debugging mode](/development/chain/localdev#test-a-chain-locally), you'll also need to
install chromadb:
```shell theme={"system"}
uv pip install chromadb
```
The complete code used in this tutorial can also be found in the
[Chains examples repo](https://github.com/basetenlabs/truss/tree/main/truss-chains/examples/rag).
# Overview
Retrieval-augmented generation (RAG) is a multi-model pipeline for generating
context-aware answers from LLMs.
There are a number of ways to build a RAG system. This tutorial shows a minimum
viable implementation with a basic vector store and retrieval function. It's
intended as a starting point to show how Chains helps you flexibly combine model
inference and business logic.
In this tutorial, we'll build a simple RAG pipeline for a hypothetical alumni
matching service for a university. The system:
1. Takes a bio with information about a new graduate
2. Uses a vector database to retrieve semantically similar bios of other alums
3. Uses an LLM to explain why the new graduate should meet the selected alums
4. Returns the writeup from the LLM
## Build the Chain
Create a file `rag.py` in a new directory with:
```sh theme={"system"}
mkdir rag
touch rag/rag.py
cd rag
```
Our RAG Chain is composed of three parts:
* `VectorStore`, a Chainlet that implements a vector database with a retrieval
function.
* `LLMClient`, a Stub for connecting to a deployed LLM.
* `RAG`, the entrypoint Chainlet that orchestrates the RAG pipeline and
has `VectorStore` and `LLMClient` as dependencies.
We'll examine these components one by one and then see how they all work
together.
### Vector store Chainlet
A real production RAG system would use a hosted vector database with a massive
number of stored embeddings. For this example, we're using a small local vector
store built with `chromadb` to stand in for a more complex system.
The Chainlet has three parts:
* [`remote_config`](/reference/sdk/chains#remote-configuration), which
configures a Docker image on deployment with dependencies.
* `__init__()`, which runs once when the Chainlet is spun up, and creates the
vector database with ten sample bios.
* [`run_remote()`](/development/chain/concepts#run-remote-chaining-chainlets), which runs
each time the Chainlet is called and is the sole public interface for the
Chainlet.
```python rag/rag.py theme={"system"}
import truss_chains as chains
# Create a Chainlet to serve as our vector database.
class VectorStore(chains.ChainletBase):
# Add chromadb as a dependency for deployment.
remote_config = chains.RemoteConfig(
docker_image=chains.DockerImage(
pip_requirements=["chromadb"]
)
)
# Runs once when the Chainlet is deployed or scaled up.
def __init__(self):
# Import Chainlet-specific dependencies in init, not at the top of
# the file.
import chromadb
self._chroma_client = chromadb.EphemeralClient()
self._collection = self._chroma_client.create_collection(name="bios")
# Sample documents are hard-coded for your convenience
documents = [
"Angela Martinez is a tech entrepreneur based in San Francisco. As the founder and CEO of a successful AI startup, she is a leading figure in the tech community. Outside of work, Angela enjoys hiking the trails around the Bay Area and volunteering at local animal shelters.",
"Ravi Patel resides in New York City, where he works as a financial analyst. Known for his keen insight into market trends, Ravi spends his weekends playing chess in Central Park and exploring the city's diverse culinary scene.",
"Sara Kim is a digital marketing specialist living in San Francisco. She helps brands build their online presence with creative strategies. Outside of work, Sara is passionate about photography and enjoys hiking the trails around the Bay Area.",
"David O'Connor calls New York City his home and works as a high school teacher. He is dedicated to inspiring the next generation through education. In his free time, David loves running along the Hudson River and participating in local theater productions.",
"Lena Rossi is an architect based in San Francisco. She designs sustainable and innovative buildings that contribute to the city's skyline. When she's not working, Lena enjoys practicing yoga and exploring art galleries.",
"Akio Tanaka lives in Tokyo and is a software developer specializing in mobile apps. Akio is an avid gamer and enjoys attending eSports tournaments. He also has a passion for cooking and often experiments with new recipes in his spare time.",
"Maria Silva is a nurse residing in New York City. She is dedicated to providing compassionate care to her patients. Maria finds joy in gardening and often spends her weekends tending to her vibrant flower beds and vegetable garden.",
"John Smith is a journalist based in San Francisco. He reports on international politics and has a knack for uncovering compelling stories. Outside of work, John is a history buff who enjoys visiting museums and historical sites.",
"Aisha Mohammed lives in Tokyo and works as a graphic designer. She creates visually stunning graphics for a variety of clients. Aisha loves to paint and often showcases her artwork in local exhibitions.",
"Carlos Mendes is an environmental engineer in San Francisco. He is passionate about developing sustainable solutions for urban areas. In his leisure time, Carlos enjoys surfing and participating in beach clean-up initiatives."
]
# Add all documents to the database
self._collection.add(
documents=documents,
ids=[f"id{n}" for n in range(len(documents))]
)
# Runs each time the Chainlet is called
async def run_remote(self, query: str) -> list[str]:
# This call to includes embedding the query string.
results = self._collection.query(query_texts=[query], n_results=2)
if results is None or not results:
raise ValueError("No bios returned from the query")
if not results["documents"] or not results["documents"][0]:
raise ValueError("Bios are empty")
return results["documents"][0]
```
### LLM inference stub
Now that we can retrieve relevant bios from the vector database, we need to pass
that information to an LLM to generate our final output.
Chains can integrate previously deployed models using a Stub. Like Chainlets,
Stubs implement
[`run_remote()`](/development/chain/concepts#run-remote-chaining-chainlets), but as a call
to the deployed model.
For our LLM, we'll use Phi-3 Mini Instruct, a small-but-mighty open source LLM.
One-click model deployment from Baseten's model library.
While the model is deploying, be sure to note down the models' invocation URL from
the model dashboard for use in the next step.
To use our deployed LLM in the RAG Chain, we define a Stub:
```python rag/rag.py theme={"system"}
class LLMClient(chains.StubBase):
# Runs each time the Stub is called
async def run_remote(self, new_bio: str, bios: list[str]) -> str:
# Use the retrieved bios to augment the prompt -- here's the "A" in RAG!
prompt = f"""You are matching alumni of a college to help them make connections. Explain why the person described first would want to meet the people selected from the matching database.
Person you're matching: {new_bio}
People from database: {" ".join(bios)}"""
# Call the deployed model.
resp = await self._remote.predict_async(json_payload={
"messages": [{"role": "user", "content": prompt}],
"stream" : False
})
return resp["output"][len(prompt) :].strip()
```
### RAG entrypoint Chainlet
The entrypoint to a Chain is the Chainlet that specifies the public-facing input
and output of the Chain and orchestrates calls to dependencies.
The `__init__` function in this Chainlet takes two new arguments:
* Add dependencies to any Chainlet with
[`chains.depends()`](/reference/sdk/chains#function-truss_chains-depends). Only
Chainlets, not Stubs, need to be added in this fashion.
* Use
[`chains.depends_context()`](/reference/sdk/chains#function-truss_chains-depends_context)
to inject a context object at runtime. This context object is required to
initialize the `LLMClient` stub.
* Visit your [baseten workspace](https://app.baseten.co/models) to find your
the URL of the previously deployed Phi-3 model and insert if as value
for `LLM_URL`.
```python rag/rag.py theme={"system"}
# Insert the URL from the previously deployed Phi-3 model.
LLM_URL = ...
@chains.mark_entrypoint
class RAG(chains.ChainletBase):
# Runs once when the Chainlet is spun up
def __init__(
self,
# Declare dependency chainlets.
vector_store: VectorStore = chains.depends(VectorStore),
context: chains.DeploymentContext = chains.depends_context(),
):
self._vector_store = vector_store
# The stub needs the context for setting up authentication.
self._llm = LLMClient.from_url(LLM_URL, context)
# Runs each time the Chain is called
async def run_remote(self, new_bio: str) -> str:
# Use the VectorStore Chainlet for context retrieval.
bios = await self._vector_store.run_remote(new_bio)
# Use the LLMClient Stub for augmented generation.
contacts = await self._llm.run_remote(new_bio, bios)
return contacts
```
## Test locally
Because our Chain uses a Stub for the LLM call, we can run the whole Chain
locally without any GPU resources.
Before running the Chainlet, make sure to set your Baseten API key as an
environment variable `BASETEN_API_KEY`.
```python rag/rag.py theme={"system"}
if __name__ == "__main__":
import os
import asyncio
with chains.run_local(
# This secret is needed even locally, because part of this chain
# calls the separately deployed Phi-3 model. Only the Chainlets
# actually run locally.
secrets={"baseten_chain_api_key": os.environ["BASETEN_API_KEY"]}
):
rag_client = RAG()
result = asyncio.run(rag_client.run_remote(
"""
Sam just moved to Manhattan for his new job at a large bank.
In college, he enjoyed building sets for student plays.
"""
))
print(result)
```
We can run our Chain locally:
```sh theme={"system"}
python rag.py
```
After a few moments, we should get a recommendation for why Sam should meet the
alumni selected from the database.
## Deploy to production
Once we're satisfied with our Chain's local behavior, we can deploy it to
Baseten. To deploy the Chain, run:
```sh theme={"system"}
truss chains push rag.py
```
This deploys the Chain as a published deployment. Once it's running, call it
from its API endpoint.
You can do this in the console with cURL:
```sh theme={"system"}
curl -X POST 'https://chain-abc123.api.baseten.co/production/run_remote' \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{"new_bio": "Sam just moved to Manhattan for his new job at a large bank.In college, he enjoyed building sets for student plays."}'
```
Alternatively, you can also integrate this in a Python application:
```python call_chain.py theme={"system"}
import requests
import os
# Insert the URL from the deployed rag chain. You can get it from the CLI
# output or the status page, e.g.
# "https://chain-abc123.api.baseten.co/production/run_remote".
RAG_CHAIN_URL = ""
baseten_api_key = os.environ["BASETEN_API_KEY"]
if not RAG_CHAIN_URL:
raise ValueError("Please insert the URL for the RAG chain.")
new_bio = (
"Sam just moved to Manhattan for his new job at a large bank. "
"In college, he enjoyed building sets for student plays."
)
resp = requests.post(
RAG_CHAIN_URL,
headers={"Authorization": f"Bearer {baseten_api_key}"},
json={"new_bio": new_bio},
)
print(resp.json())
```
The published deployment has access to full autoscaling settings and will
scale to zero when not in use.
To iterate on the Chain during development, use `truss chains push --watch rag.py`
to create a development deployment with live code patching.
# Create a model with the REST API
Source: https://docs.baseten.co/examples/create-a-model-with-rest
Deploy a model archive programmatically using the management API, without the Truss CLI.
The management API deploys a model from a Truss archive over REST, the same deployment you'd get from [`truss push`](/reference/cli/truss/push) but without a Python dependency. Use it from a service or CI pipeline that can't run the Python Truss CLI, such as a Go or JavaScript backend. If you're already working in Python, `truss push` is the simpler path.
Deploying over REST follows the same path each time:
1. **Prepare**: [`POST /v1/prepare_model_upload`](/reference/management-api/models/prepare-model-upload) validates the payload and returns temporary credentials scoped to an S3 location.
2. **Upload**: push your Truss archive to that location.
3. **Create**: [`POST /v1/models`](/reference/management-api/models/creates-a-model-from-a-source) commits the upload as a new model.
## Prepare the upload
Send a Truss config as a JSON object with a model `name`. Add a [`weights` block](/development/model/bdn#weights) to load weights through the Baseten Delivery Network. Set `dry_run` to `true` to validate without issuing credentials. The response carries the upload credentials and the S3 location to upload to:
```bash Request theme={"system"}
curl https://api.baseten.co/v1/prepare_model_upload \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "my-model",
"deployment": {
"config": { "model_name": "my-model", "resources": { "accelerator": "A10G" }, "weights": [{ "source": "hf://meta-llama/Llama-3.1-8B@main", "mount_location": "/models/llama" }] }
}
}'
```
```json 200 theme={"system"}
{
"creds": {
"aws_access_key_id": "ASIA...",
"aws_secret_access_key": "...",
"aws_session_token": "..."
},
"s3_bucket": "baseten-user-models-xxxx",
"s3_key": "organizations/.../models/.../model.tgz",
"s3_region": "us-west-2"
}
```
## Upload the archive
Package your Truss as a gzipped tar archive, then upload it to the returned `s3_bucket` and `s3_key` using the temporary credentials:
```python upload.py theme={"system"}
import boto3
# resp is the JSON returned by the prepare step
creds = resp["creds"]
session = boto3.Session(
aws_access_key_id=creds["aws_access_key_id"],
aws_secret_access_key=creds["aws_secret_access_key"],
aws_session_token=creds["aws_session_token"],
region_name=resp["s3_region"],
)
session.client("s3").upload_file("model.tgz", resp["s3_bucket"], resp["s3_key"])
```
A successful upload returns nothing. `boto3` raises an exception if the temporary credentials have expired or the `s3_key` doesn't match the one from the prepare step.
## Create the model
Commit the upload with `source.kind` set to `model_archive`, the same `deployment` payload you validated, and the `s3_key` from the prepare step. The response returns the created model and its first deployment:
```bash Request theme={"system"}
curl https://api.baseten.co/v1/models \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": {
"kind": "model_archive",
"name": "my-model",
"s3_key": "organizations/.../models/.../model.tgz",
"deployment": {
"config": { "model_name": "my-model", "resources": { "accelerator": "A10G" }, "weights": [{ "source": "hf://meta-llama/Llama-3.1-8B@main", "mount_location": "/models/llama" }] }
}
}
}'
```
```json 200 theme={"system"}
{
"model": { "id": "abcd123", "name": "my-model" },
"deployment": { "id": "1q2w3e4", "status": "BUILDING" }
}
```
The deployment starts at `BUILDING` and isn't ready when the call returns. Poll [`GET /v1/models/{model_id}/deployments/{deployment_id}`](/reference/management-api/deployments/gets-a-models-deployment-by-id) until its `status` is `ACTIVE`:
```bash Request theme={"system"}
curl https://api.baseten.co/v1/models/abcd123/deployments/1q2w3e4 \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json 200 theme={"system"}
{
"id": "1q2w3e4",
"model_id": "abcd123",
"status": "ACTIVE",
"environment": "production",
"active_replica_count": 1
}
```
## Call the model
Once the deployment is `ACTIVE`, send inference requests to the model's predict endpoint, using the model `id` from the create response and your API key. The request and response shapes match whatever your model's `predict` method accepts and returns:
```bash Request theme={"system"}
curl https://model-abcd123.api.baseten.co/environments/production/predict \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello!"}'
```
```json 200 theme={"system"}
{ "output": "Hello! How can I help you today?" }
```
## Next steps
Stream responses, send async requests, and use the other inference transports.
Push a new deployment to the model you created.
# Customize a model
Source: https://docs.baseten.co/examples/customize-a-model
Deploy a model with custom Python code using the Truss Model class.
Most models on Baseten deploy with just a `config.yaml` and an inference engine. But when you need custom preprocessing, postprocessing, or want to run a model architecture that the built-in engines don't support, you can write Python code in a `model.py` file. Truss provides a `Model` class with three methods (`__init__`, `load`, and `predict`) that give you full control over how your model initializes, loads weights, and handles requests.
This guide walks through deploying [Phi-3-mini-4k-instruct](https://huggingface.co/microsoft/Phi-3-mini-4k-instruct), a 3.8B parameter LLM, using custom Python code. If you haven't deployed a config-only model yet, start with [Deploy your first model](/examples/deploy-your-first-model).
## Install and sign in
Before you begin, [sign up](https://app.baseten.co/signup) or [sign in](https://app.baseten.co/login) to Baseten, then install [uv](https://docs.astral.sh/uv/), a fast Python package manager.
Install the Truss CLI and connect it to your Baseten account. Browser login opens a tab to approve this device, so there's no API key to copy and paste.
**Install Truss**
```sh theme={"system"}
uv tool install truss
```
**Sign in**
```sh theme={"system"}
truss login --browser
```
Prefer not to install? Run `uvx truss login --browser` to use the same flow without a permanent install, and use `uvx truss …` for the rest of this guide.
***
## Create a Truss project
Create a new Truss:
```sh theme={"system"}
truss init phi-3-mini && cd phi-3-mini
```
When prompted, give your Truss a name like `Phi 3 Mini`.
This command scaffolds a project with the following structure:
```
phi-3-mini/
model/
__init__.py
model.py
config.yaml
data/
packages/
```
The key files are:
* `model/model.py`: Your model code with `load()` and `predict()` methods.
* `config.yaml`: Dependencies, resources, and deployment settings.
* `data/`: Optional directory for data files bundled with your model.
* `packages/`: Optional directory for local Python packages.
Truss uses this structure to build and deploy your model automatically. You
define your model in `model.py` and your infrastructure in `config.yaml`, no
Dockerfiles or container management required.
***
## Implement model code
Replace the contents of `model/model.py` with the following code. This loads [Phi-3-mini-4k-instruct](https://huggingface.co/microsoft/Phi-3-mini-4k-instruct) using the `transformers` library and PyTorch:
```python model/model.py theme={"system"}
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
class Model:
def __init__(self, **kwargs):
self._model = None
self._tokenizer = None
def load(self):
self._model = AutoModelForCausalLM.from_pretrained(
"microsoft/Phi-3-mini-4k-instruct",
device_map="cuda",
torch_dtype="auto"
)
self._tokenizer = AutoTokenizer.from_pretrained(
"microsoft/Phi-3-mini-4k-instruct"
)
def predict(self, request):
messages = request.pop("messages")
model_inputs = self._tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
inputs = self._tokenizer(model_inputs, return_tensors="pt").to("cuda")
with torch.no_grad():
outputs = self._model.generate(input_ids=inputs["input_ids"], max_length=256)
return {"output": self._tokenizer.decode(outputs[0], skip_special_tokens=True)}
```
Truss models follow a three-method pattern that separates initialization from inference:
| Method | When it's called | What to do here |
| ---------- | ------------------------------------ | ---------------------------------------------------------- |
| `__init__` | Once when the class is created | Initialize variables, store configuration, set secrets. |
| `load` | Once at startup, before any requests | Load model weights, tokenizers, and other heavy resources. |
| `predict` | On every API request | Process input, run inference, return response. |
The `load` method runs during the container's cold start, before your model receives traffic. This keeps expensive operations (like downloading large model weights) out of the request path.
### Understand the request/response flow
The `predict` method receives `request`, a dictionary containing the JSON body from the API call:
```python theme={"system"}
# API call with: {"messages": [{"role": "user", "content": "Hello"}]}
def predict(self, request):
messages = request.pop("messages") # Extract from request
# ... run inference ...
return {"output": result} # Return dict becomes JSON response
```
Whatever dictionary you return becomes the API response. You control the input parameters and output format.
### GPU and memory patterns
A few patterns in this code are common across GPU models:
* **`device_map="cuda"`**: Loads model weights directly to GPU.
* **`.to("cuda")`**: Moves input tensors to GPU for inference.
* **`torch.no_grad()`**: Disables gradient tracking to save memory (gradients aren't needed for inference).
***
## Configure dependencies and GPU
The `config.yaml` file defines your model's environment and compute resources.
### Set Python version and dependencies
```yaml config.yaml theme={"system"}
python_version: py311
requirements:
- six==1.17.0
- accelerate==0.30.1
- einops==0.8.0
- transformers==4.41.2
- torch==2.3.0
```
**Key configuration options:**
| Field | Purpose | Example |
| ----------------- | ----------------------------------------- | --------------------------------- |
| `python_version` | Python version for your container. | `py39`, `py310`, `py311`, `py312` |
| `requirements` | Python packages to install (pip format). | `torch==2.3.0` |
| `system_packages` | System-level dependencies (apt packages). | `ffmpeg`, `libsm6` |
For the complete list of configuration options, see the [Truss reference config](/reference/truss-configuration).
Always pin exact versions (such as `torch==2.3.0`, not `torch>=2.0`). This ensures reproducible builds and your model behaves the same way every time it's deployed.
### Allocate a GPU
The `resources` section specifies what hardware your model runs on:
```yaml config.yaml theme={"system"}
resources:
accelerator: T4
use_gpu: true
```
Match your GPU to your model's VRAM requirements. For Phi-3-mini (approximately 7.6 GB), a T4 (16 GB) provides headroom for inference.
| GPU | VRAM | Good for |
| ---- | -------- | -------------------------------------------- |
| T4 | 16 GB | Small models, embeddings, fine-tuned models. |
| L4 | 24 GB | Medium models (7B parameters). |
| A10G | 24 GB | Medium models, image generation. |
| A100 | 40/80 GB | Large models (13B-70B parameters). |
| H100 | 80 GB | Very large models, high throughput. |
A rough rule for estimating VRAM: 2 GB per billion parameters for float16 models. A 7B model needs approximately 14 GB VRAM minimum.
***
## Deploy the model
Push your model to Baseten:
```sh theme={"system"}
truss push --watch
```
You should see:
```output theme={"system"}
✨ Model Phi 3 Mini was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
***
## Call the model API
After the deployment shows "Active" in the dashboard, call the model API:
From your Truss project directory, run:
```sh theme={"system"}
truss predict --data '{"messages": [{"role": "user", "content": "What is AGI?"}]}'
```
You should see:
```output theme={"system"}
Calling predict on development deployment...
{
"output": "AGI stands for Artificial General Intelligence..."
}
```
The Truss CLI uses your saved credentials and automatically targets the correct deployment.
Replace `YOUR_MODEL_ID` with your model ID (for example, `abc1d2ef`):
```sh theme={"system"}
curl -X POST https://model-YOUR_MODEL_ID.api.baseten.co/development/predict \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "What is AGI?"}]}'
```
You should see:
```output theme={"system"}
{"output": "AGI stands for Artificial General Intelligence..."}
```
Replace `YOUR_MODEL_ID` with your model ID:
```python main.py theme={"system"}
import requests
import os
model_id = "YOUR_MODEL_ID" # Replace with your model ID (for example, "abc1d2ef")
baseten_api_key = os.environ["BASETEN_API_KEY"]
resp = requests.post(
f"https://model-{model_id}.api.baseten.co/development/predict",
headers={"Authorization": f"Bearer {baseten_api_key}"},
json={
"messages": [
{"role": "user", "content": "What is AGI?"}
]
}
)
print(resp.json())
```
You should see:
```output theme={"system"}
{"output": "AGI stands for Artificial General Intelligence..."}
```
***
## Use live reload for development
To avoid long deploy times when testing changes, use live reload:
```sh theme={"system"}
truss watch
```
You should see:
```output theme={"system"}
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
🚰 Attempting to sync truss with remote
No changes observed, skipping patching.
👀 Watching for changes to truss...
```
When you save changes to `model.py`, Truss automatically patches the deployed model:
```output theme={"system"}
Changes detected, creating patch...
Created patch to update model code file: model/model.py
Model Phi 3 Mini patched successfully.
```
This saves time by patching only the updated code without rebuilding Docker containers or restarting the model server.
***
## Promote to production
Once you're happy with the model, deploy it to production:
```sh theme={"system"}
truss push --promote
```
This changes the API endpoint from `/development/predict` to `/production/predict`:
```sh theme={"system"}
curl -X POST https://model-YOUR_MODEL_ID.api.baseten.co/production/predict \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"messages": [{"role": "user", "content": "What is AGI?"}]}'
```
Your model ID is printed in the `truss push` output. You can also find it in your [Baseten dashboard](https://app.baseten.co/models/).
***
## Next steps
Full reference for dependencies, secrets, resources, and deployment settings.
Core `Model` lifecycle, method signatures, and sync vs. async inference patterns.
Add `chat_completions`, `completions`, `embeddings`, `messages`, or `responses` when custom model code should serve matching HTTP routes.
Return generated tokens incrementally for lower perceived latency.
Configure probe thresholds and define custom readiness or liveness logic.
Scale GPU replicas based on demand with configurable concurrency targets.
Deploy a model with just a config file, no custom Python needed.
# Deploy a Hugging Face model
Source: https://docs.baseten.co/examples/deploy-a-hugging-face-model
Deploy Gemma 4 26B on Baseten with vLLM, BDN-cached weights, EAGLE3 speculative decoding, and prefix caching.
Deploy open-source LLMs from [Hugging Face](https://huggingface.co/) on Baseten using vLLM and Truss. You write a `config.yaml`, push with the Truss CLI, and get an OpenAI-compatible API endpoint. No custom Python code or Dockerfile required.
This guide walks through deploying [Gemma 4 26B Instruct](https://huggingface.co/RedHatAI/gemma-4-26B-A4B-it-FP8-Dynamic) on two H100 GPUs with vLLM, using EAGLE3 speculative decoding and prefix caching. You'll add a Hugging Face token, write a config, deploy to Baseten, and call the model's OpenAI-compatible endpoint. Weights mirror once through the Baseten Delivery Network (BDN), so replicas scale up without re-downloading from Hugging Face.
## Install and sign in
Before you begin, [sign up](https://app.baseten.co/signup) or [sign in](https://app.baseten.co/login) to Baseten, then install [uv](https://docs.astral.sh/uv/), a fast Python package manager.
Install the Truss CLI and connect it to your Baseten account. Browser login opens a tab to approve this device, so there's no API key to copy and paste.
**Install Truss**
```sh theme={"system"}
uv tool install truss
```
**Sign in**
```sh theme={"system"}
truss login --browser
```
Prefer not to install? Run `uvx truss login --browser` to use the same flow without a permanent install, and use `uvx truss …` for the rest of this guide.
***
## Add a Hugging Face access token
Gemma is gated and requires a license click-through:
1. Accept Google's license terms on the [Gemma model page](https://huggingface.co/google/gemma-4-26B-A4B-it). The weights in this example come from RedHatAI's FP8 fork; your Hugging Face token grants access to both repos.
2. Create a read-only [user access token](https://huggingface.co/docs/hub/en/security-tokens).
3. Save the token as a secret named `hf_access_token` in your [Baseten workspace](https://app.baseten.co/settings/secrets).
***
## Create a Truss project
Create a directory for your project:
```sh theme={"system"}
mkdir gemma-4-26b && cd gemma-4-26b
```
vLLM server deployments only need a `config.yaml`. No custom Python code is required, and the `model/` directory (used for [custom preprocessing or postprocessing](/examples/customize-a-model)) isn't needed here.
***
## Write the config
Create a `config.yaml` with:
```yaml config.yaml theme={"system"}
model_name: Gemma 4 26B Instruct
model_metadata:
example_model_input:
model: google/gemma-4-26B-A4B-it
messages:
- role: user
content: "What does Gemma stand for?"
stream: true
max_tokens: 512
temperature: 1.0
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.21.0
weights:
- source: "hf://RedHatAI/gemma-4-26B-A4B-it-FP8-Dynamic@main"
mount_location: "/app/checkpoint/gemma"
auth_secret_name: "hf_access_token"
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/gemma
--tensor-parallel-size $GPU_COUNT
--served-model-name google/gemma-4-26B-A4B-it
--max-num-seqs 16
--max-model-len auto
--gpu-memory-utilization 0.9
--enable-prefix-caching
--speculative-config.model RedHatAI/gemma-4-26B-A4B-it-speculator.eagle3
--speculative-config.num_speculative_tokens 3
--speculative-config.method eagle3
--trust-remote-code
--enable-auto-tool-choice
--reasoning-parser gemma4
--tool-call-parser gemma4"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
environment_variables:
VLLM_LOGGING_LEVEL: INFO
resources:
accelerator: H100:2
use_gpu: true
secrets:
hf_access_token: null
runtime:
predict_concurrency: 8
health_checks:
startup_threshold_seconds: 300
restart_threshold_seconds: 300
stop_traffic_threshold_seconds: 120
```
Here's what each setting does:
* `weights` tells BDN which Hugging Face checkpoint to mirror and where to mount it inside the container. `auth_secret_name` uses your `hf_access_token` secret for the gated download.
* `base_image` and `docker_server` run vLLM as the serving process: `start_command` launches the server, and the endpoint fields tell Baseten which routes to forward for predictions and health checks.
* `--enable-prefix-caching` reuses the KV cache when requests share a prompt prefix, such as a system prompt, RAG context, or multi-turn history.
* The `--speculative-config.*` flags enable EAGLE3 speculative decoding, which runs a small draft model alongside the main model and accepts matching token predictions to cut decode latency.
* `resources` provisions two H100 GPUs; `start_command` reads the GPU count with `nvidia-smi` and sets vLLM's tensor parallelism to match.
* `runtime.health_checks` gives vLLM time to load weights before Baseten routes traffic or restarts the replica.
* `model_metadata` supplies the example request for the dashboard **Try** panel, and `secrets` declares which workspace secrets the container can read.
***
## Deploy
Push the model to Baseten:
```sh theme={"system"}
truss push
```
You should see:
```output theme={"system"}
✨ Model Gemma 4 26B Instruct was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
The first deploy takes 5-10 minutes while Baseten pulls the vLLM base image and BDN mirrors the FP8 weights and the EAGLE3 speculator from Hugging Face. Subsequent scale-ups reuse the cached image and weights. You can watch progress in the logs linked above.
***
## Call the model
Once the deployment shows `Active` in the dashboard, call it with a [Baseten API key](https://app.baseten.co/settings/api_keys). The endpoint follows this shape:
Export your key before sending the request:
```sh theme={"system"}
export BASETEN_API_KEY="paste-your-api-key-here"
```
Replace `{model_id}` in the examples below with your model ID from the deploy output.
Send a streaming chat completion with the OpenAI SDK. Save the following as `call_model.py`:
```python call_model.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
api_key=os.environ["BASETEN_API_KEY"],
)
stream = client.chat.completions.create(
model="google/gemma-4-26B-A4B-it",
messages=[
{"role": "user", "content": "Explain prefix caching in two sentences."}
],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
```
Run the script with `uv`, which pulls the OpenAI SDK on the fly:
```sh theme={"system"}
uv run --with openai python call_model.py
```
Send a streaming chat completion from the command line:
```sh theme={"system"}
curl -N -X POST "https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemma-4-26B-A4B-it",
"messages": [
{"role": "user", "content": "Explain prefix caching in two sentences."}
],
"max_tokens": 200,
"stream": true
}'
```
Tokens stream back as Server-Sent Events, one `data:` chunk at a time.
You should see the response stream back token by token:
```output theme={"system"}
Prefix caching is an optimization technique that stores the processed computational states (KV cache) of common prompt prefixes to avoid redundant processing. By reusing these cached states for similar subsequent requests, it significantly reduces latency and computational costs during inference.
```
The `model` argument in your request must match the `--served-model-name` flag in `start_command`, or the API returns a 400.
Any code that works with the OpenAI SDK works with your deployment: point `base_url` at your model's endpoint. To route traffic through a third-party OpenAI-compatible gateway, see [External LLM gateways](/inference/calling-your-model#external-llm-gateways).
***
## Adapt to another model
The same pattern works across model families: BDN handles weight delivery, vLLM serves the model, and Baseten handles replicas, routing, and monitoring. Port the template incrementally, changing and validating one layer before moving to the next.
* **Weights**: Point `weights[].source` at the new repo and update the path in `start_command`. Keep `auth_secret_name` for gated models, and pin a revision (for example, `@main` or a commit hash) for reproducibility.
* **Served model name**: Set `--served-model-name` to the public model ID your clients will send, and update the `model` field in `example_model_input` to match.
* **Model-specific vLLM flags**: Swap or drop reasoning and tool-call parsers (the `gemma4` parsers only apply to Gemma 4). Remove the `--speculative-config.*` flags if no EAGLE3 speculator is published for your target.
* **Hardware**: Resize `resources.accelerator` for the new checkpoint's memory footprint. Confirm utilization in the deployment logs and `nvidia-smi`.
* **Runtime tuning**: Tune `runtime.predict_concurrency` alongside `--max-num-seqs` once you know your traffic pattern.
* **Rollback**: Promote a working config to a separate [environment](/deployment/environments) and roll forward only after smoke tests pass.
***
## Next steps
Configure replicas, concurrency targets, and scale-to-zero for production traffic.
Add custom Python when you need preprocessing, postprocessing, or unsupported architectures.
# Build and deploy an LLM
Source: https://docs.baseten.co/examples/deploy-a-llm
Package and deploy an LLM with Truss, from model setup to inference.
This guide walks through deploying Mistral-7B, a powerful large language model (LLM), using Truss. You'll configure the model, set up inference, allocate resources, and deploy it as an API endpoint.
# Set up your model
Start by importing the necessary libraries:
```python model/model.py theme={"system"}
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
```
Specify the Hugging Face model checkpoint:
```python model/model.py theme={"system"}
CHECKPOINT = "mistralai/Mistral-7B-v0.1"
```
# Define the model class
Create a `Model` class that loads Mistral-7B and its tokenizer when the server starts:
```python model/model.py theme={"system"}
class Model:
def __init__(self, **kwargs) -> None:
self.tokenizer = None
self.model = None
def load(self):
self.model = AutoModelForCausalLM.from_pretrained(
CHECKPOINT, torch_dtype=torch.float16, device_map="auto"
)
self.tokenizer = AutoTokenizer.from_pretrained(CHECKPOINT)
```
# Implement inference
The `predict` function handles inference by tokenizing input, generating text, and decoding the output.
```python model/model.py theme={"system"}
def predict(self, request: dict):
prompt = request.pop("prompt")
generate_args = {
"max_new_tokens": request.get("max_new_tokens", 128),
"temperature": request.get("temperature", 1.0),
"top_p": request.get("top_p", 0.95),
"top_k": request.get("top_k", 50),
"repetition_penalty": 1.0,
"use_cache": True,
"do_sample": True,
"eos_token_id": self.tokenizer.eos_token_id,
"pad_token_id": self.tokenizer.pad_token_id,
}
input_ids = self.tokenizer(prompt, return_tensors="pt").input_ids.cuda()
with torch.no_grad():
output = self.model.generate(input_ids=input_ids, **generate_args)
return self.tokenizer.decode(output[0])
```
# Configure your deployment
## Define dependencies
Specify the necessary Python packages in `config.yaml`:
```yaml config.yaml theme={"system"}
model_name: Mistral 7B
python_version: py311
requirements:
- transformers==4.42.3
- sentencepiece==0.1.99
- accelerate==0.23.0
- torch==2.0.1
- numpy==1.26.4
```
## Allocate compute resources
Mistral-7B requires an NVIDIA A10G GPU for efficient inference:
```yaml config.yaml theme={"system"}
resources:
accelerator: A10G
use_gpu: true
```
# Deploy the model
Push your Truss to Baseten:
```bash theme={"system"}
$ truss push
```
Once deployed, call the model using the Truss CLI:
```bash theme={"system"}
$ truss predict --published -d '{"prompt": "What is a large language model?"}'
```
Or send a request to the API endpoint:
```python theme={"system"}
import requests
response = requests.post(
"https://model-{yourmodelid}.api.baseten.co/production/predict",
headers={"Authorization": "Bearer EMPTY"},
json={"prompt": "Explain quantum computing in simple terms"}
)
print(response.json())
```
# Check for optimized engine support
For optimized performance we have open-source and Baseten optimized engines, such as Baseten's TensorRT-LLM, Baseten-Embeddings-Inference, vLLM and SGLang.
# Deploy your first model
Source: https://docs.baseten.co/examples/deploy-your-first-model
Deploy an open-source LLM to Baseten with just a config file and get an OpenAI-compatible API endpoint.
Deploying a model to Baseten turns a Hugging Face model into a production-ready API endpoint. You write a `config.yaml` that specifies the model, the hardware, and the engine, then `uvx truss push` builds a TensorRT-optimized container and deploys it. No Python code, no Dockerfile, no container management.
This guide walks through deploying [Qwen 2.5 3B Instruct](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct), a small but capable LLM, from a config file to a production API. You'll set up Truss, write a config, deploy to Baseten, and call the model's OpenAI-compatible endpoint.
## Install and sign in
Before you begin, [sign up](https://app.baseten.co/signup) or [sign in](https://app.baseten.co/login) to Baseten, then install [uv](https://docs.astral.sh/uv/), a fast Python package manager.
Install the Truss CLI and connect it to your Baseten account. Browser login opens a tab to approve this device, so there's no API key to copy and paste.
**Install Truss**
```sh theme={"system"}
uv tool install truss
```
**Sign in**
```sh theme={"system"}
truss login --browser
```
Prefer not to install? Run `uvx truss login --browser` to use the same flow without a permanent install, and use `uvx truss …` for the rest of this guide.
***
## Create a Truss project
Create a directory for your project:
```sh theme={"system"}
mkdir qwen-2.5-3b && cd qwen-2.5-3b
```
TRT-LLM engine deployments only need a `config.yaml`. No custom Python code is required, and the `model/` directory (used for [custom preprocessing or postprocessing](/examples/customize-a-model)) isn't needed here.
***
## Write the config
Create a `config.yaml` with:
```yaml config.yaml theme={"system"}
model_metadata:
tags:
- openai-compatible
model_name: Qwen-2.5-3B
resources:
accelerator: L4
use_gpu: true
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "Qwen/Qwen2.5-3B-Instruct"
max_seq_len: 8192
quantization_type: fp8
tensor_parallel_count: 1
num_builder_gpus: 2
```
That's the entire deployment specification.
* `model_name` identifies the model in your Baseten dashboard.
* `resources` selects an L4 GPU (24 GB VRAM), which is plenty for a 3B parameter model.
* `trt_llm` tells Baseten to use [Engine-Builder-LLM](/engines/engine-builder-llm/overview), which compiles the model with TensorRT-LLM for optimized inference.
* `checkpoint_repository` points to the model weights on Hugging Face. Qwen 2.5 3B Instruct is ungated, so no access token is needed.
* `quantization_type: fp8` compresses weights to 8-bit floating point, cutting memory usage roughly in half with negligible quality loss.
* `max_seq_len: 8192` sets the maximum context length for requests.
* `num_builder_gpus: 2` uses two GPUs during the build phase. FP8 quantization requires more GPU memory at build time than at inference time, so a single L4 runs out of memory during compilation without this setting.
***
## Deploy
Push the model to Baseten:
Engine-based deployments (TRT-LLM) use published deployments by default. The `--watch` flag, which creates a development deployment with live reload, is not supported for TRT-LLM models. For custom Python models, see [Customize a model](/examples/customize-a-model) where `--watch` enables a faster development loop.
```sh theme={"system"}
truss push
```
You should see:
```output theme={"system"}
✨ Model Qwen 2.5 3B was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
Baseten downloads the model weights from Hugging Face, compiles them with TensorRT-LLM, and deploys the resulting container to an L4 GPU. This build step takes roughly 10-20 minutes for the first deploy. You can watch progress in the logs linked above.
***
## Call the model
Engine-based deployments serve an OpenAI-compatible API. Once the deployment shows "Active" in the dashboard, call it using the OpenAI SDK or cURL. Replace `{model_id}` with your model ID from the deployment output.
The endpoint follows this shape:
Install the OpenAI SDK if you don't have it:
```sh theme={"system"}
uv pip install openai
```
Create a chat completion:
```python theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="Qwen-2.5-3B",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "Qwen-2.5-3B",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
You should see a response like:
```output theme={"system"}
Machine learning is a branch of artificial intelligence where systems learn
patterns from data to make predictions or decisions without being explicitly
programmed for each task...
```
Any code that works with the OpenAI SDK works with your deployment. Just point the `base_url` at your model's endpoint.
***
## Next steps
Tune max sequence length, batch size, quantization, and runtime settings.
Add custom Python code when you need preprocessing, postprocessing, or unsupported model architectures.
Configure replicas, concurrency targets, and scale-to-zero for production traffic.
# Deploy a Dockerized model
Source: https://docs.baseten.co/examples/docker
Deploy any model in a pre-built Docker container.
In this example, we deploy a dockerized model for [infinity embedding server](https://github.com/michaelfeil/infinity), a high-throughput, low-latency REST API server for serving vector embeddings.
# Set up the `config.yaml`
To deploy a dockerized model, all you need is a `config.yaml`. It specifies how to build your Docker image, start the server, and manage resources. Let’s break down each section.
## Base image
Sets the foundational Docker image to a lightweight Python 3.11 environment.
```yaml config.yaml theme={"system"}
base_image:
image: python:3.11-slim
```
## Docker server configuration
Configures the server's startup command, health check endpoints, prediction endpoint, and the port on which the server will run.
```yaml config.yaml theme={"system"}
docker_server:
start_command: sh -c "HF_TOKEN=$(cat /secrets/hf_access_token) infinity_emb v2 --batch-size 64 --model-id BAAI/bge-small-en-v1.5 --revision main"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /embeddings
server_port: 7997
```
## Build commands (optional)
Pre-downloads model weights during the build phase to ensure the model is ready at container startup.
```yaml config.yaml theme={"system"}
build_commands: # optional step to download the weights of the model into the image
- sh -c "HF_TOKEN=$(cat /secrets/hf_access_token) infinity_emb v2 --preload-only --no-model-warmup --model-id BAAI/bge-small-en-v1.5 --revision main"
```
## Configure resources
Note that we need an L4 to run this model.
```yaml config.yaml theme={"system"}
resources:
accelerator: L4
use_gpu: true
```
## Requirements
Lists the Python package dependencies required for the infinity embedding server.
```yaml config.yaml theme={"system"}
requirements:
- infinity-emb[all]==0.0.77
```
## Runtime settings
Sets the server to handle up to 40 concurrent inferences to manage load efficiently.
```yaml config.yaml theme={"system"}
runtime:
predict_concurrency: 40
```
## Environment variables
Defines essential environment variables including the Hugging Face access token, request batch size, queue size limit, and a flag to disable tracking.
```yaml config.yaml theme={"system"}
environment_variables:
hf_access_token: null
# constrain api to at most 256 sentences per request, for better load-balancing
INFINITY_MAX_CLIENT_BATCH_SIZE: 256
# constrain model to a max backpressure of INFINITY_MAX_CLIENT_BATCH_SIZE * predict_concurrency = 10241 requests
INFINITY_QUEUE_SIZE: 10241
DO_NOT_TRACK: 1
```
# Deploy dockerized model
Deploy the model like you would other Trusses, with:
```bash theme={"system"}
truss push infinity-embedding-server
```
`docker_server` configs deploy as published deployments and don't support development mode. Using `truss push --watch` with a `docker_server` config returns an error. Use `truss push` without `--watch` to deploy to production.
# Generate images with Flux
Source: https://docs.baseten.co/examples/image-generation
Deploy Flux Schnell as a text-to-image endpoint.
In this example, we go through a Truss that serves a text-to-image model. We
use Flux Schnell, which is one of the highest performing text-to-image models out
there today.
# Set up imports and torch settings
In this example, we use the Hugging Face diffusers library to build our text-to-image model.
```python model/model.py theme={"system"}
import base64
import math
import random
import logging
from io import BytesIO
import numpy as np
import torch
from diffusers import FluxPipeline
from PIL import Image
logging.basicConfig(level=logging.INFO)
MAX_SEED = np.iinfo(np.int32).max
```
# Define the `Model` class and load function
In the `load` function of the Truss, we implement logic involved in
downloading and setting up the model. For this model, we use the
`FluxPipeline` class in `diffusers` to instantiate our Flux pipeline,
and configure a number of relevant parameters.
See the [diffusers docs](https://huggingface.co/docs/diffusers/index) for details
on all of these parameters.
```python model/model.py theme={"system"}
class Model:
def __init__(self, **kwargs):
self.pipe = None
self.weights_dir = "/models/flux"
def load(self):
self.pipe = FluxPipeline.from_pretrained(self.weights_dir, torch_dtype=torch.bfloat16).to("cuda")
```
This is a utility function for converting a PIL image to base64.
```python model/model.py theme={"system"}
def convert_to_b64(self, image: Image) -> str:
buffered = BytesIO()
image.save(buffered, format="JPEG")
img_b64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
return img_b64
```
# Define the predict function
The `predict` function contains the actual inference logic. The steps here are:
* Setting up the generation params. These include things like the prompt, image width, image height, number of inference steps, etc.
* Running the Diffusion Pipeline
* Convert the resulting image to base64 and return it
```python model/model.py theme={"system"}
def predict(self, model_input):
seed = model_input.get("seed")
prompt = model_input.get("prompt")
prompt2 = model_input.get("prompt2")
max_sequence_length = model_input.get(
"max_sequence_length", 256
) # 256 is max for FLUX.1-schnell
guidance_scale = model_input.get(
"guidance_scale", 0.0
) # 0.0 is the only value for FLUX.1-schnell
num_inference_steps = model_input.get(
"num_inference_steps", 4
) # schnell is timestep-distilled
width = model_input.get("width", 1024)
height = model_input.get("height", 1024)
if not math.isclose(guidance_scale, 0.0):
logging.warning(
"FLUX.1-schnell does not support guidance_scale other than 0.0"
)
guidance_scale = 0.0
if not seed:
seed = random.randint(0, MAX_SEED)
if len(prompt.split()) > max_sequence_length:
logging.warning(
"FLUX.1-schnell does not support prompts longer than 256 tokens, truncating"
)
tokens = prompt.split()
prompt = " ".join(tokens[: min(len(tokens), max_sequence_length)])
generator = torch.Generator().manual_seed(seed)
image = self.pipe(
prompt=prompt,
guidance_scale=guidance_scale,
max_sequence_length=max_sequence_length,
num_inference_steps=num_inference_steps,
width=width,
height=height,
output_type="pil",
generator=generator,
).images[0]
b64_results = self.convert_to_b64(image)
return {"data": b64_results}
```
# Set up the `config.yaml`
Running Flux Schnell requires a handful of Python libraries, including
`diffusers`, `transformers`, and others.
```yaml config.yaml theme={"system"}
external_package_dirs: []
weights:
- source: "hf://black-forest-labs/FLUX.1-schnell@main"
mount_location: "/models/flux"
allow_patterns:
- "*.json"
- "*.safetensors"
ignore_patterns:
- "flux1-schnell.safetensors"
model_metadata:
example_model_input: {"prompt": 'black forest gateau cake spelling out the words "FLUX SCHNELL", tasty, food photography, dynamic shot'}
model_name: Flux.1-schnell
python_version: py311
requirements:
- git+https://github.com/huggingface/diffusers.git@v0.32.2
- transformers
- accelerate
- sentencepiece
- protobuf
resources:
accelerator: H100_40GB
use_gpu: true
secrets: {}
system_packages:
- ffmpeg
- libsm6
- libxext6
```
## Configure resources for Flux Schnell
Note that we need an H100 40GB GPU to run this model.
```yaml config.yaml theme={"system"}
resources:
accelerator: H100_40GB
use_gpu: true
secrets: {}
```
## System packages
Running diffusers requires `ffmpeg` and a couple other system
packages.
```yaml config.yaml theme={"system"}
system_packages:
- ffmpeg
- libsm6
- libxext6
```
## Enable caching
Flux Schnell is a large model, and downloading it from Hugging Face on every cold start would take several minutes. The [Baseten Delivery Network (BDN)](/development/model/bdn) mirrors weights to Baseten's infrastructure once and serves them from multi-tier caches close to your replicas, so cold starts read from a nearby cache instead of re-downloading from upstream.
To enable BDN, add a `weights` block to your config:
```yaml theme={"system"}
weights:
- source: "hf://black-forest-labs/FLUX.1-schnell@main"
mount_location: "/models/flux"
allow_patterns:
- "*.json"
- "*.safetensors"
ignore_patterns:
- "flux1-schnell.safetensors"
```
The `model.py` `load()` method then reads weights from `mount_location` instead of pulling from Hugging Face.
# Deploy the model
Deploy the model like you would other Trusses, with:
```bash theme={"system"}
truss push flux/schnell
```
# Run an inference
Use a Python script to call the model once it's deployed and parse its response. We parse the resulting base64-encoded string output into an actual image file: `output_image.jpg`.
```python infer.py theme={"system"}
import httpx
import os
import base64
from PIL import Image
from io import BytesIO
# Replace the empty string with your model id below
model_id = ""
baseten_api_key = os.environ["BASETEN_API_KEY"]
# Function used to convert a base64 string to a PIL image
def b64_to_pil(b64_str):
return Image.open(BytesIO(base64.b64decode(b64_str)))
data = {
"prompt": 'red velvet cake spelling out the words "FLUX SCHNELL", tasty, food photography, dynamic shot'
}
# Call model endpoint
res = httpx.post(
f"https://model-{model_id}.api.baseten.co/production/predict",
headers={"Authorization": f"Bearer {baseten_api_key}"},
json=data
)
# Get output image
res = res.json()
output = res.get("data")
# Convert the base64 model output to an image
img = b64_to_pil(output)
img.save("output_image.jpg")
```
# Qwen3 Embedding
Source: https://docs.baseten.co/examples/models/embedding/qwen3-embedding
Qwen3 Embedding recipes: 3 variants (0.6B, 4B, 8B), Dense architecture.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
Pick the model you want to deploy. Each tab is a self-contained recipe.
[Qwen/Qwen3-Embedding-0.6B](https://huggingface.co/Qwen/Qwen3-Embedding-0.6B) is a 0.6B-parameter dense model.
This preset serves Qwen3 Embedding 0.6B on a single L4 through [Baseten Embeddings Inference](/engines/bei/overview) (BEI) with FP8 weights, optimized for embedding throughput on low-cost hardware.
L4TRT-LLM
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3-embedding-0.6b-throughput && cd qwen3-embedding-0.6b-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_metadata:
example_model_input:
input:
- Baseten is a fast inference provider
- Embeddings let you do semantic search.
model: qwen3-embedding-0.6b
model_name: "model:qwen3-embedding-0.6b preset:throughput"
python_version: py39
resources:
accelerator: L4
cpu: '1'
memory: 10Gi
use_gpu: true
trt_llm:
build:
base_model: encoder
checkpoint_repository:
repo: michaelfeil/Qwen3-Embedding-0.6B-auto
revision: main
source: HF
max_num_tokens: 32768
num_builder_gpus: 1
quantization_type: fp8
runtime:
webserver_default_route: /v1/embeddings
```
This config tells Baseten to build a BEI (Baseten Embeddings Inference) engine for Qwen3 Embedding 0.6B on a single L4, drawing FP8 weights from `michaelfeil/Qwen3-Embedding-0.6B-auto`, a mirror of the official checkpoint with an architecture string compatible with BEI's encoder build path. FP8 quantization on an L4 keeps per-embedding cost low while dynamic batching sustains high throughput.
## Key parameters
[Baseten Embeddings Inference](/engines/bei/overview) (BEI) reads these fields from the `trt_llm` block. Each one shapes how the engine is built and served:
| Parameter | Value |
| --------------- | --------- |
| Quantization | `fp8` |
| Base model type | `encoder` |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3-embedding-0.6b-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
Your **model ID** is printed in the `truss push` output (`abcd1234` in the example). Use it wherever you see `{model_id}` in the next section.
## Call the model
Your deployment serves an OpenAI-compatible embeddings API at `/v1/embeddings`. Replace `{model_id}` with your model ID and make sure `BASETEN_API_KEY` is set.
Now call your deployment to generate embeddings:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.embeddings.create(
model="qwen3-embedding-0.6b",
input=[
"Baseten is a fast inference provider.",
"Embeddings power semantic search and RAG.",
],
)
for item in response.data:
print(len(item.embedding), item.embedding[:4])
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/embeddings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "qwen3-embedding-0.6b",
"input": [
"Baseten is a fast inference provider.",
"Embeddings power semantic search and RAG."
]
}'
```
For higher throughput, use the [Baseten Performance Client](https://www.baseten.co/blog/your-client-code-matters-10x-higher-embedding-throughput-with-python-and-rust/), which batches and pipelines requests automatically.
[Qwen/Qwen3-Embedding-4B](https://huggingface.co/Qwen/Qwen3-Embedding-4B) is a 4B-parameter dense model.
This preset serves Qwen3 Embedding 4B on a single H100 through [Baseten Embeddings Inference](/engines/bei/overview) (BEI) with FP8 weights, optimized for embedding throughput.
H100TRT-LLM
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3-embedding-4b-throughput && cd qwen3-embedding-4b-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_metadata:
example_model_input:
input:
- Baseten is a fast inference provider
- Embeddings let you do semantic search.
model: qwen3-embedding-4b
model_name: "model:qwen3-embedding-4b preset:throughput"
python_version: py39
resources:
accelerator: H100
cpu: '1'
memory: 10Gi
use_gpu: true
trt_llm:
build:
base_model: encoder
checkpoint_repository:
repo: michaelfeil/Qwen3-Embedding-4B-auto
revision: main
source: HF
max_num_tokens: 32768
num_builder_gpus: 1
quantization_type: fp8
runtime:
webserver_default_route: /v1/embeddings
```
This config tells Baseten to build a BEI (Baseten Embeddings Inference) engine for Qwen3 Embedding 4B on a single H100, drawing FP8 weights from `michaelfeil/Qwen3-Embedding-4B-auto`, a mirror of the official checkpoint with an architecture string compatible with BEI's encoder build path. FP8 quantization and dynamic batching keep throughput high for indexing and RAG ingest workloads.
## Key parameters
[Baseten Embeddings Inference](/engines/bei/overview) (BEI) reads these fields from the `trt_llm` block. Each one shapes how the engine is built and served:
| Parameter | Value |
| --------------- | --------- |
| Quantization | `fp8` |
| Base model type | `encoder` |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3-embedding-4b-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
Your **model ID** is printed in the `truss push` output (`abcd1234` in the example). Use it wherever you see `{model_id}` in the next section.
## Call the model
Your deployment serves an OpenAI-compatible embeddings API at `/v1/embeddings`. Replace `{model_id}` with your model ID and make sure `BASETEN_API_KEY` is set.
Now call your deployment to generate embeddings:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.embeddings.create(
model="qwen3-embedding-4b",
input=[
"Baseten is a fast inference provider.",
"Embeddings power semantic search and RAG.",
],
)
for item in response.data:
print(len(item.embedding), item.embedding[:4])
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/embeddings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "qwen3-embedding-4b",
"input": [
"Baseten is a fast inference provider.",
"Embeddings power semantic search and RAG."
]
}'
```
For higher throughput, use the [Baseten Performance Client](https://www.baseten.co/blog/your-client-code-matters-10x-higher-embedding-throughput-with-python-and-rust/), which batches and pipelines requests automatically.
[Qwen/Qwen3-Embedding-8B](https://huggingface.co/Qwen/Qwen3-Embedding-8B) is an 8B-parameter dense model.
H100TRT-LLM
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3-embedding-8b && cd qwen3-embedding-8b
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_metadata:
example_model_input:
input:
- Baseten is a fast inference provider
- Embeddings let you do semantic search.
model: qwen3-embedding-8b
model_name: "model:qwen3-embedding-8b preset:throughput"
python_version: py39
resources:
accelerator: H100
cpu: '1'
memory: 10Gi
use_gpu: true
trt_llm:
build:
base_model: encoder
checkpoint_repository:
repo: michaelfeil/Qwen3-Embedding-8B-auto
revision: main
source: HF
max_num_tokens: 40960
num_builder_gpus: 1
quantization_type: fp8
runtime:
webserver_default_route: /v1/embeddings
```
## Key parameters
[Baseten Embeddings Inference](/engines/bei/overview) (BEI) reads these fields from the `trt_llm` block. Each one shapes how the engine is built and served:
| Parameter | Value |
| --------------- | --------- |
| Quantization | `fp8` |
| Base model type | `encoder` |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3-embedding-8b was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible embeddings API at `/v1/embeddings`.
Now call your deployment to generate embeddings:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.embeddings.create(
model="qwen3-embedding-8b",
input=[
"Baseten is a fast inference provider.",
"Embeddings power semantic search and RAG.",
],
)
for item in response.data:
print(len(item.embedding), item.embedding[:4])
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/embeddings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "qwen3-embedding-8b",
"input": [
"Baseten is a fast inference provider.",
"Embeddings power semantic search and RAG."
]
}'
```
For higher throughput, use the [Baseten Performance Client](https://www.baseten.co/blog/your-client-code-matters-10x-higher-embedding-throughput-with-python-and-rust/), which batches and pipelines requests automatically.
# Qwen3 Reranker
Source: https://docs.baseten.co/examples/models/embedding/qwen3-reranker
Qwen3 Reranker recipes: 3 variants (0.6B, 4B, 8B), Dense architecture.
## Setup
Sign in to Baseten with Truss, then install the Python `requests` library.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install requests**
```sh theme={"system"}
uv pip install requests
```
Pick the model you want to deploy. Each tab is a self-contained recipe.
[Qwen/Qwen3-Reranker-0.6B](https://huggingface.co/Qwen/Qwen3-Reranker-0.6B) is a 0.6B-parameter dense model.
This preset serves Qwen3 Reranker 0.6B on a single L4 through [Baseten Embeddings Inference](/engines/bei/overview) (BEI) with FP8 weights, optimized for reranking throughput on low-cost hardware.
L4TRT-LLM
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3-reranker-0.6b-throughput && cd qwen3-reranker-0.6b-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
# this file was autogenerated by `generate_templates.py` - please do change via template only
model_metadata:
example_model_input:
inputs:
- - Baseten is a fast inference provider
- - Classify this separately.
raw_scores: true
truncate: true
truncation_direction: Right
model_name: "model:qwen3-reranker-0.6b preset:throughput"
python_version: py39
resources:
accelerator: L4
cpu: '1'
memory: 10Gi
use_gpu: true
trt_llm:
build:
base_model: encoder
checkpoint_repository:
repo: michaelfeil/Qwen3-Reranker-0.6B-seq
revision: main
source: HF
max_num_tokens: 32768
num_builder_gpus: 1
quantization_type: fp8
runtime:
webserver_default_route: /predict
```
This config tells Baseten to build a BEI (Baseten Embeddings Inference) engine for Qwen3 Reranker 0.6B on a single L4, drawing FP8 weights from `michaelfeil/Qwen3-Reranker-0.6B-seq`, a sequence-classification conversion of the official checkpoint compatible with BEI's encoder build path. The deployment scores query-passage pairs on the `/predict` route with dynamic batching keeping throughput high.
## Key parameters
[Baseten Embeddings Inference](/engines/bei/overview) (BEI) reads these fields from the `trt_llm` block. Each one shapes how the engine is built and served:
| Parameter | Value |
| --------------- | --------- |
| Quantization | `fp8` |
| Base model type | `encoder` |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3-reranker-0.6b-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
Your **model ID** is printed in the `truss push` output (`abcd1234` in the example). Use it wherever you see `{model_id}` in the next section.
## Call the model
Your deployment exposes a cross-encoder scoring endpoint at `/predict`. Replace `{model_id}` with your model ID and make sure `BASETEN_API_KEY` is set.
Now call your deployment to score candidates:
```python main.py theme={"system"}
import os
import requests
response = requests.post(
"https://model-{model_id}.api.baseten.co/environments/production/sync/predict",
headers={"Authorization": f"Bearer {os.environ['BASETEN_API_KEY']}"},
json={
"query": "fast inference platform",
"texts": [
"Baseten serves models on dedicated GPUs.",
"The Eiffel Tower is in Paris.",
"Cold-start latency matters for autoscaling.",
],
},
)
for hit in response.json():
print(hit["score"], hit["text"])
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/predict \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"query": "fast inference platform",
"texts": [
"Baseten serves models on dedicated GPUs.",
"The Eiffel Tower is in Paris.",
"Cold-start latency matters for autoscaling."
]
}'
```
For batch scoring at higher throughput, use the [Baseten Performance Client](https://www.baseten.co/blog/your-client-code-matters-10x-higher-embedding-throughput-with-python-and-rust/).
[Qwen/Qwen3-Reranker-4B](https://huggingface.co/Qwen/Qwen3-Reranker-4B) is a 4B-parameter dense model.
This preset serves Qwen3 Reranker 4B on a single H100 through [Baseten Embeddings Inference](/engines/bei/overview) (BEI) with FP8 weights, optimized for reranking throughput.
H100TRT-LLM
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3-reranker-4b-throughput && cd qwen3-reranker-4b-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
# this file was autogenerated by `generate_templates.py` - please do change via template only
model_metadata:
example_model_input:
inputs:
- - Baseten is a fast inference provider
- - Classify this separately.
raw_scores: true
truncate: true
truncation_direction: Right
model_name: "model:qwen3-reranker-4b preset:throughput"
python_version: py39
resources:
accelerator: H100
cpu: '1'
memory: 10Gi
use_gpu: true
trt_llm:
build:
base_model: encoder
checkpoint_repository:
repo: michaelfeil/Qwen3-Reranker-4B-seq
revision: main
source: HF
max_num_tokens: 32768
num_builder_gpus: 1
quantization_type: fp8
runtime:
webserver_default_route: /predict
```
This config tells Baseten to build a BEI (Baseten Embeddings Inference) engine for Qwen3 Reranker 4B on a single H100, drawing FP8 weights from `michaelfeil/Qwen3-Reranker-4B-seq`, a sequence-classification conversion of the official checkpoint compatible with BEI's encoder build path. The deployment scores query-passage pairs on the `/predict` route with dynamic batching keeping throughput high.
## Key parameters
[Baseten Embeddings Inference](/engines/bei/overview) (BEI) reads these fields from the `trt_llm` block. Each one shapes how the engine is built and served:
| Parameter | Value |
| --------------- | --------- |
| Quantization | `fp8` |
| Base model type | `encoder` |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3-reranker-4b-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
Your **model ID** is printed in the `truss push` output (`abcd1234` in the example). Use it wherever you see `{model_id}` in the next section.
## Call the model
Your deployment exposes a cross-encoder scoring endpoint at `/predict`. Replace `{model_id}` with your model ID and make sure `BASETEN_API_KEY` is set.
Now call your deployment to score candidates:
```python main.py theme={"system"}
import os
import requests
response = requests.post(
"https://model-{model_id}.api.baseten.co/environments/production/sync/predict",
headers={"Authorization": f"Bearer {os.environ['BASETEN_API_KEY']}"},
json={
"query": "fast inference platform",
"texts": [
"Baseten serves models on dedicated GPUs.",
"The Eiffel Tower is in Paris.",
"Cold-start latency matters for autoscaling.",
],
},
)
for hit in response.json():
print(hit["score"], hit["text"])
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/predict \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"query": "fast inference platform",
"texts": [
"Baseten serves models on dedicated GPUs.",
"The Eiffel Tower is in Paris.",
"Cold-start latency matters for autoscaling."
]
}'
```
For batch scoring at higher throughput, use the [Baseten Performance Client](https://www.baseten.co/blog/your-client-code-matters-10x-higher-embedding-throughput-with-python-and-rust/).
[Qwen/Qwen3-Reranker-8B](https://huggingface.co/Qwen/Qwen3-Reranker-8B) is an 8B-parameter dense model.
H100TRT-LLM
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3-reranker-8b && cd qwen3-reranker-8b
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
# this file was autogenerated by `generate_templates.py` - please do change via template only
model_metadata:
example_model_input:
inputs:
- - Baseten is a fast inference provider
- - Classify this separately.
raw_scores: true
truncate: true
truncation_direction: Right
model_name: "model:qwen3-reranker-8b preset:throughput"
python_version: py39
resources:
accelerator: H100
cpu: '1'
memory: 10Gi
use_gpu: true
trt_llm:
build:
base_model: encoder
checkpoint_repository:
repo: michaelfeil/Qwen3-Reranker-8B-seq
revision: main
source: HF
max_num_tokens: 40960
num_builder_gpus: 1
quantization_type: fp8
runtime:
webserver_default_route: /predict
```
## Key parameters
[Baseten Embeddings Inference](/engines/bei/overview) (BEI) reads these fields from the `trt_llm` block. Each one shapes how the engine is built and served:
| Parameter | Value |
| --------------- | --------- |
| Quantization | `fp8` |
| Base model type | `encoder` |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3-reranker-8b was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment exposes a cross-encoder scoring endpoint at `/predict`.
Now call your deployment to score candidates:
```python main.py theme={"system"}
import os
import requests
response = requests.post(
"https://model-{model_id}.api.baseten.co/environments/production/sync/predict",
headers={"Authorization": f"Bearer {os.environ['BASETEN_API_KEY']}"},
json={
"query": "fast inference platform",
"texts": [
"Baseten serves models on dedicated GPUs.",
"The Eiffel Tower is in Paris.",
"Cold-start latency matters for autoscaling.",
],
},
)
for hit in response.json():
print(hit["score"], hit["text"])
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/predict \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"query": "fast inference platform",
"texts": [
"Baseten serves models on dedicated GPUs.",
"The Eiffel Tower is in Paris.",
"Cold-start latency matters for autoscaling."
]
}'
```
For batch scoring at higher throughput, use the [Baseten Performance Client](https://www.baseten.co/blog/your-client-code-matters-10x-higher-embedding-throughput-with-python-and-rust/).
# FLUX.1
Source: https://docs.baseten.co/examples/models/image-gen/flux1
FLUX.1 recipes: 2 variants (dev, schnell), diffusion-transformer architecture.
## Setup
Sign in to Baseten with Truss, then install the Python `requests` library.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install requests**
```sh theme={"system"}
uv pip install requests
```
Pick the model you want to deploy. Each tab is a self-contained recipe.
[black-forest-labs/FLUX.1-dev](https://huggingface.co/black-forest-labs/FLUX.1-dev) is a 12B-parameter diffusion transformer model.
This preset serves FLUX.1 dev on H100 40GB, tuned for text-to-image throughput.
H100\_40GB
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir flux1-dev-throughput && cd flux1-dev-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
external_package_dirs: []
model_metadata:
output_media:
- json_path: "data"
media_type: "image/jpeg"
encoding: "base64"
label: "Generated Image"
example_model_input: {"prompt": 'black forest gateau cake spelling out the words "FLUX DEV", tasty, food photography, dynamic shot'}
repo_id: black-forest-labs/FLUX.1-dev
model_name: "model:flux1-dev preset:throughput"
python_version: py311
requirements:
- git+https://github.com/huggingface/diffusers.git@fc6a91e3834c35e57b398ad1c0d99f6f83557e04
- transformers>=4.0.0,<5.0.0
- accelerate
- sentencepiece
- protobuf
weights:
- source: "hf://black-forest-labs/FLUX.1-dev@main"
mount_location: "/models/FLUX.1-dev"
auth_secret_name: "hf_access_token"
resources:
accelerator: H100_40GB
use_gpu: true
secrets:
hf_access_token: null
system_packages:
- ffmpeg
- libsm6
- libxext6
```
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model flux1-dev-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Use the `/predict` endpoint to generate your model's images.
The deployment returns the generated image as base64-encoded bytes. Decode the response to write the image to disk.
```python main.py theme={"system"}
import base64
import os
import requests
response = requests.post(
"https://model-{model_id}.api.baseten.co/environments/production/sync/predict",
headers={"Authorization": f"Bearer {os.environ['BASETEN_API_KEY']}"},
json={"prompt": "black forest gateau cake spelling out the words \"FLUX DEV\", tasty, food photography, dynamic shot"},
)
image_b64 = response.json()["data"]
with open("output.png", "wb") as f:
f.write(base64.b64decode(image_b64))
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/predict \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{"prompt": "black forest gateau cake spelling out the words \"FLUX DEV\", tasty, food photography, dynamic shot"}' \
| jq -r '.data' | base64 --decode > output.png
```
[black-forest-labs/FLUX.1-schnell](https://huggingface.co/black-forest-labs/FLUX.1-schnell) is a 12B-parameter diffusion transformer model.
This preset serves FLUX.1 schnell on H100 40GB. The step-distilled model delivers the fastest FLUX image generation per dollar.
H100\_40GB
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir flux1-schnell-throughput && cd flux1-schnell-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
external_package_dirs: []
model_metadata:
output_media:
- json_path: "data"
media_type: "image/jpeg"
encoding: "base64"
label: "Generated Image"
example_model_input: {"prompt": 'black forest gateau cake spelling out the words "FLUX SCHNELL", tasty, food photography, dynamic shot'}
repo_id: black-forest-labs/FLUX.1-schnell
model_name: "model:flux1-schnell preset:throughput"
python_version: py311
requirements:
- git+https://github.com/huggingface/diffusers.git@fc6a91e3834c35e57b398ad1c0d99f6f83557e04
- transformers>=4.0.0,<5.0.0
- accelerate
- sentencepiece
- protobuf
- b10-transfer
weights:
- source: "hf://black-forest-labs/FLUX.1-schnell@main"
mount_location: "/models/FLUX.1-schnell"
auth_secret_name: "hf_access_token"
resources:
accelerator: H100_40GB
use_gpu: true
secrets:
hf_access_token: null
system_packages:
- ffmpeg
- libsm6
- libxext6
```
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model flux1-schnell-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Use the `/predict` endpoint to generate your model's images.
The deployment returns the generated image as base64-encoded bytes. Decode the response to write the image to disk.
```python main.py theme={"system"}
import base64
import os
import requests
response = requests.post(
"https://model-{model_id}.api.baseten.co/environments/production/sync/predict",
headers={"Authorization": f"Bearer {os.environ['BASETEN_API_KEY']}"},
json={"prompt": "black forest gateau cake spelling out the words \"FLUX SCHNELL\", tasty, food photography, dynamic shot"},
)
image_b64 = response.json()["data"]
with open("output.png", "wb") as f:
f.write(base64.b64decode(image_b64))
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/predict \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{"prompt": "black forest gateau cake spelling out the words \"FLUX SCHNELL\", tasty, food photography, dynamic shot"}' \
| jq -r '.data' | base64 --decode > output.png
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Krea 2 Turbo
Source: https://docs.baseten.co/examples/models/image-gen/krea-2-turbo
Krea 2 Turbo is Krea's 12B flow-matching image generation model: a single-stream MMDiT backbone paired with a Qwen3-VL text encoder and the Qwen-Image VAE.
Krea 2 Turbo is Krea's 12B flow-matching image generation model: a single-stream MMDiT backbone paired with a Qwen3-VL text encoder and the Qwen-Image VAE. The Turbo variant is distilled to 8 denoising steps with classifier-free guidance (CFG) disabled, generating a 1024px image in roughly 1.5 to 2 seconds and a 2048px image in 6 to 8 seconds on an H100, with native output resolutions from 1024 to 2048. For example prompts and prompting techniques, see the [Krea 2 prompting guide](https://github.com/krea-ai/krea-2/blob/main/docs/prompting.md). The weights ship under the Krea 2 Community License, which requires an [Enterprise License from Krea](https://huggingface.co/krea/Krea-2-Turbo) for commercial use by companies over \$1M in annual revenue.
## Setup
Sign in to Baseten with Truss, then install the client library for the tab you'll use: `requests` for the Python tab, or the OpenAI SDK for the OpenAI tab.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install requests**
```sh theme={"system"}
uv pip install requests
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
This preset serves Krea 2 Turbo on a single H100, with the full bfloat16 weights loaded in GPU memory and the SGLang diffusion stack's 8-step distilled sampling for fast image generation.
H100SGLang 1.3
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir krea-2-turbo-lossy && cd krea-2-turbo-lossy
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:krea-2-turbo preset:lossy"
base_image:
image: baseten/sglang-diffusion-h100:v1.3
weights:
- source: hf://krea/Krea-2-Turbo
mount_location: /app/model_cache/krea-2-turbo
auth_secret_name: hf_access_token
docker_server:
start_command: /app/start_sglang.sh
readiness_endpoint: /health_generate
liveness_endpoint: /health_generate
predict_endpoint: /v1/images/generations
server_port: 8000
model_metadata:
output_media:
- json_path: "data[*].b64_json"
media_type: "image/png"
encoding: "base64"
label: "Generated Image"
visual_gen:
extra_sglang_args:
enable_fp8: "0"
enable_fp4_dit: "0"
enable_cache_dit: "0"
dit_cpu_offload: "false"
text_encoder_cpu_offload: "false"
image_encoder_cpu_offload: "false"
vae_cpu_offload: "false"
trust_remote_code: "false"
b10_cpu_memory_saving: "0"
num_gpus: "1"
warmup_resolutions: "1024x1024 2048x2048"
lora_path: ""
example_model_input:
prompt: "immense rocket launch exhaust as seen from extremely close up"
n: 1
size: "1024x1024"
response_format: "b64_json"
resources:
accelerator: H100
use_gpu: true
secrets:
hf_access_token: null
runtime:
health_checks:
startup_threshold_seconds: 1200
restart_threshold_seconds: 300
stop_traffic_threshold_seconds: 300
```
This deployment loads the 34 GB bfloat16 checkpoint of [krea/Krea-2-Turbo](https://huggingface.co/krea/Krea-2-Turbo) on a single H100 and serves it with the SGLang diffusion stack. The server exposes an OpenAI-compatible images API at `/v1/images/generations` that returns base64-encoded images.
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model krea-2-turbo-lossy was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Use the `/v1/images/generations` endpoint to generate your model's images.
The deployment returns the generated image as base64-encoded bytes. Decode the response to write the image to disk. The `size` parameter sets the output resolution in pixels.
```python main.py theme={"system"}
import base64
import os
import requests
response = requests.post(
"https://model-{model_id}.api.baseten.co/environments/production/sync/v1/images/generations",
headers={"Authorization": f"Bearer {os.environ['BASETEN_API_KEY']}"},
json={
"prompt": "immense rocket launch exhaust as seen from extremely close up",
"size": "1024x1024",
"response_format": "b64_json",
},
)
image_b64 = response.json()["data"][0]["b64_json"]
with open("output.png", "wb") as f:
f.write(base64.b64decode(image_b64))
```
```python main.py theme={"system"}
import base64
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
image = client.images.generate(
prompt="immense rocket launch exhaust as seen from extremely close up",
size="1024x1024",
response_format="b64_json",
)
with open("output.png", "wb") as f:
f.write(base64.b64decode(image.data[0].b64_json))
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/images/generations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{"prompt": "immense rocket launch exhaust as seen from extremely close up", "size": "1024x1024", "response_format": "b64_json"}' \
| jq -r '.data[0].b64_json' | base64 --decode > output.png
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
Example prompts and prompting techniques from the Krea team
# DiffusionGemma
Source: https://docs.baseten.co/examples/models/llm/diffusiongemma
Google's DiffusionGemma diffusion language model (26B total, 4B active), served from an FP8 quantized checkpoint.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
[RedHatAI/diffusiongemma-26B-A4B-it-FP8-dynamic](https://huggingface.co/RedHatAI/diffusiongemma-26B-A4B-it-FP8-dynamic) is a 26B-parameter diffusion transformer model (4B active per token) with up to 8K context.
This preset serves DiffusionGemma 26B A4B on a single H100 with FP8 weights and dynamic activation quantization, optimized for low-latency diffusion decoding.
H100vLLM (nightly-2c9c07c8... build)8K8
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir diffusiongemma-26B-A4B-it-latency && cd diffusiongemma-26B-A4B-it-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:diffusiongemma-26B-A4B-it preset:latency"
base_image:
image: vllm/vllm-openai:nightly-2c9c07c85e56c799afffd5a671a8a0bace377a39
model_metadata:
repo_id: RedHatAI/diffusiongemma-26B-A4B-it-FP8-dynamic
example_model_input:
model: google/diffusiongemma-26B-A4B-it
messages:
- role: user
content: Explain how diffusion language models differ from autoregressive ones.
stream: true
max_tokens: 512
tags:
- openai-compatible
weights:
- source: "hf://RedHatAI/diffusiongemma-26B-A4B-it-FP8-dynamic@main"
mount_location: "/app/checkpoint/diffusiongemma"
auth_secret_name: "hf_access_token"
build_commands:
- apt-get update && apt-get install -y --no-install-recommends git ca-certificates && git clone --filter=blob:none https://github.com/vllm-project/vllm.git /opt/vllm-dgemma && cd /opt/vllm-dgemma && git checkout d25326b1fcfbcdfdc4133e7263b0d95ec31c9b87
- cd /opt/vllm-dgemma && VLLM_USE_PRECOMPILED=1 VLLM_PRECOMPILED_WHEEL_LOCATION='https://wheels.vllm.ai/3d300aecb1e6639872b698bd74ed38fb81d9603e/vllm-0.22.1rc1.dev373%2Bg3d300aecb-cp38-abi3-manylinux_2_28_x86_64.whl' pip install --no-deps --force-reinstall .
docker_server:
start_command: >-
sh -c "vllm serve /app/checkpoint/diffusiongemma
--served-model-name google/diffusiongemma-26B-A4B-it
--tensor-parallel-size 1
--attention-backend TRITON_ATTN
--generation-config vllm
--hf-overrides.diffusion_sampler entropy_bound
--hf-overrides.diffusion_entropy_bound 0.1
--diffusion-config.canvas_length 256
--enable-chunked-prefill
--enable-prefix-caching
--max-model-len 8192
--max-num-seqs 8
--gpu-memory-utilization 0.85
--trust-remote-code"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
environment_variables:
VLLM_USE_V2_MODEL_RUNNER: "1"
VLLM_LOGGING_LEVEL: INFO
PYTORCH_CUDA_ALLOC_CONF: expandable_segments:True
resources:
accelerator: H100
use_gpu: true
secrets:
hf_access_token: null
runtime:
health_checks:
restart_check_delay_seconds: 300
restart_threshold_seconds: 300
stop_traffic_threshold_seconds: 120
predict_concurrency: 8
```
This config serves the `RedHatAI/diffusiongemma-26B-A4B-it-FP8-dynamic` checkpoint on a single H100 with vLLM built from the DiffusionGemma pull-request branch, because diffusion support has not yet landed in a vLLM release. Setting `max-num-seqs` to 8 and `gpu-memory-utilization` to 0.85 leaves the headroom that diffusion warmup needs for its logits buffers, and the deployment exposes an OpenAI-compatible chat completions endpoint under the served name `google/diffusiongemma-26B-A4B-it`.
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| ---------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `1` | Number of GPUs to shard the model across. |
| `--attention-backend` | `TRITON_ATTN` | Attention kernel backend vLLM uses. **TRITON\_ATTN:** Triton-based attention kernels, required by some model architectures not yet supported by the default backend. |
| `--generation-config` | `vllm` | Source of the default generation (sampling) settings. **vllm:** Use vLLM's own defaults instead of the checkpoint's `generation_config.json`. |
| `--hf-overrides.diffusion_sampler` | `entropy_bound` | Sampler the diffusion language model uses to unmask tokens during denoising. |
| `--hf-overrides.diffusion_entropy_bound` | `0.1` | Entropy threshold for the entropy-bound diffusion sampler. |
| `--diffusion-config.canvas_length` | `256` | Number of tokens in the diffusion canvas, the block the model denoises per step. |
| `--enable-chunked-prefill` | (no value) | Process long prompts in chunks so decode requests keep running. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--max-model-len` | `8192` | Maximum context length (tokens) the server accepts per request. |
| `--max-num-seqs` | `8` | Maximum number of concurrent sequences in the batch. |
| `--gpu-memory-utilization` | `0.85` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model diffusiongemma-26B-A4B-it-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
Your **model ID** is printed in the `truss push` output (`abcd1234` in the example). Use it wherever you see `{model_id}` in the next section.
## Call the model
Your deployment serves an OpenAI-compatible API. Replace `{model_id}` with your model ID and make sure `BASETEN_API_KEY` is set.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="google/diffusiongemma-26B-A4B-it",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "google/diffusiongemma-26B-A4B-it",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
# Gemma 4
Source: https://docs.baseten.co/examples/models/llm/gemma-4
Gemma 4 recipes: 4 variants (E2B, E4B, 26B A4B, 31B), Dense and MoE architectures.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
Pick the model you want to deploy. Each tab is a self-contained recipe.
[google/gemma-4-E2B-it](https://huggingface.co/google/gemma-4-E2B-it) is a 2B-parameter dense model with up to 125K context.
This preset serves Gemma 4 E2B on a single L4, the lowest-cost deployment in the Model Library.
L4vLLM (0.22.0-cu129 build)125K8
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir gemma-4-E2B-it-latency && cd gemma-4-E2B-it-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: model:gemma-4-E2B-it preset:latency
model_metadata:
description: >-
Gemma 4 multimodal instruct (preview E2B), OpenAI-compatible chat with vision via vLLM on L4.
repo_id: google/gemma-4-E2B-it
example_model_input:
model: google/gemma-4-E2B-it
messages:
- role: user
content:
- type: text
text: "Describe this image in one sentence."
- type: image_url
image_url:
url: "https://picsum.photos/id/237/200/300"
stream: true
max_tokens: 512
temperature: 1.0
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
weights:
- source: "hf://google/gemma-4-E2B-it@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--tensor-parallel-size $GPU_COUNT
--served-model-name google/gemma-4-E2B-it
--max-num-seqs 16
--max-model-len auto
--limit-mm-per-prompt.image 1
--gpu-memory-utilization 0.9
--async-scheduling
--trust-remote-code
--enable-auto-tool-choice
--enable-prefix-caching
--reasoning-parser gemma4
--tool-call-parser gemma4
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
resources:
accelerator: L4
use_gpu: true
runtime:
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
predict_concurrency: 8
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| ----------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--max-num-seqs` | `16` | Maximum number of concurrent sequences in the batch. |
| `--max-model-len` | `auto` | Maximum context length (tokens) the server accepts per request. |
| `--limit-mm-per-prompt.image` | `1` | Maximum number of image inputs per prompt. |
| `--gpu-memory-utilization` | `0.9` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--async-scheduling` | (no value) | Overlap scheduling with GPU execution to hide scheduler latency. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--reasoning-parser` | `gemma4` | Server-side parser that separates reasoning output into `reasoning_content`. |
| `--tool-call-parser` | `gemma4` | Server-side parser that emits structured `tool_calls` on the response. |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model gemma-4-E2B-it-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="google/gemma-4-E2B-it",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "google/gemma-4-E2B-it",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
The server parses the model's chain of thought into a separate `reasoning_content` field on the response. Read it alongside the final answer:
```python theme={"system"}
response = client.chat.completions.create(
model="google/gemma-4-E2B-it",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="google/gemma-4-E2B-it",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
[google/gemma-4-E4B-it](https://huggingface.co/google/gemma-4-E4B-it) is a 4B-parameter dense model with up to 125K context.
This preset serves Gemma 4 E4B on a single H100.
H100vLLM (0.22.0-cu129 build)125K8
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir gemma-4-E4B-it-latency && cd gemma-4-E4B-it-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: model:gemma-4-E4B-it preset:latency
model_metadata:
description: >-
Gemma 4 multimodal instruct (preview E4B), OpenAI-compatible chat with vision via vLLM.
repo_id: google/gemma-4-E4B-it
example_model_input:
model: google/gemma-4-E4B-it
messages:
- role: user
content:
- type: text
text: "Describe this image in one sentence."
- type: image_url
image_url:
url: "https://picsum.photos/id/237/200/300"
stream: true
max_tokens: 512
temperature: 1.0
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
weights:
- source: "hf://google/gemma-4-E4B-it@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--tensor-parallel-size $GPU_COUNT
--served-model-name google/gemma-4-E4B-it
--max-num-seqs 16
--max-model-len auto
--limit-mm-per-prompt.image 1
--gpu-memory-utilization 0.9
--async-scheduling
--trust-remote-code
--enable-auto-tool-choice
--enable-prefix-caching
--reasoning-parser gemma4
--tool-call-parser gemma4
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
resources:
accelerator: H100
use_gpu: true
runtime:
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
predict_concurrency: 8
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| ----------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--max-num-seqs` | `16` | Maximum number of concurrent sequences in the batch. |
| `--max-model-len` | `auto` | Maximum context length (tokens) the server accepts per request. |
| `--limit-mm-per-prompt.image` | `1` | Maximum number of image inputs per prompt. |
| `--gpu-memory-utilization` | `0.9` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--async-scheduling` | (no value) | Overlap scheduling with GPU execution to hide scheduler latency. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--reasoning-parser` | `gemma4` | Server-side parser that separates reasoning output into `reasoning_content`. |
| `--tool-call-parser` | `gemma4` | Server-side parser that emits structured `tool_calls` on the response. |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model gemma-4-E4B-it-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="google/gemma-4-E4B-it",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "google/gemma-4-E4B-it",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
The server parses the model's chain of thought into a separate `reasoning_content` field on the response. Read it alongside the final answer:
```python theme={"system"}
response = client.chat.completions.create(
model="google/gemma-4-E4B-it",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="google/gemma-4-E4B-it",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
[google/gemma-4-26B-A4B-it](https://huggingface.co/google/gemma-4-26B-A4B-it) is a 26B-parameter MoE model (4B active per token) with up to 256K context.
This preset serves Gemma 4 26B A4B on H100:2 with FP8 dynamic quantization.
H100 × 2vLLM (0.22.0-cu129 build)256K8
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir gemma-4-26B-A4B-it-latency && cd gemma-4-26B-A4B-it-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: model:gemma-4-26B-A4B-it preset:latency
model_metadata:
description: >-
Gemma 4 multimodal instruct (26B MOE FP8 dynamique), speculative decoding Eagle3 via vLLM.
repo_id: RedHatAI/gemma-4-26B-A4B-it-FP8-Dynamic
example_model_input:
model: google/gemma-4-26B-A4B-it
messages:
- role: user
content:
- type: text
text: "Describe this image in one sentence."
- type: image_url
image_url:
url: "https://picsum.photos/id/237/200/300"
stream: true
max_tokens: 512
temperature: 1.0
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
weights:
- source: "hf://RedHatAI/gemma-4-26B-A4B-it-FP8-Dynamic@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--tensor-parallel-size $GPU_COUNT
--served-model-name google/gemma-4-26B-A4B-it
--max-num-seqs 16
--max-model-len auto
--limit-mm-per-prompt.image 1
--gpu-memory-utilization 0.9
--enable-prefix-caching
--speculative-config.model RedHatAI/gemma-4-26B-A4B-it-speculator.eagle3
--speculative-config.num_speculative_tokens 3
--speculative-config.method eagle3
--trust-remote-code
--enable-auto-tool-choice
--reasoning-parser gemma4
--tool-call-parser gemma4
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
resources:
accelerator: H100:2
use_gpu: true
runtime:
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
predict_concurrency: 8
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--max-num-seqs` | `16` | Maximum number of concurrent sequences in the batch. |
| `--max-model-len` | `auto` | Maximum context length (tokens) the server accepts per request. |
| `--limit-mm-per-prompt.image` | `1` | Maximum number of image inputs per prompt. |
| `--gpu-memory-utilization` | `0.9` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--speculative-config.model` | `RedHatAI/gemma-4-26B-A4B-it-speculator.eagle3` | Hugging Face repo for the draft speculator checkpoint. |
| `--speculative-config.num_speculative_tokens` | `3` | Number of tokens the draft speculator proposes per step. |
| `--speculative-config.method` | `eagle3` | Speculative decoding method. **eagle3:** EAGLE v3 speculative decoding. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--reasoning-parser` | `gemma4` | Server-side parser that separates reasoning output into `reasoning_content`. |
| `--tool-call-parser` | `gemma4` | Server-side parser that emits structured `tool_calls` on the response. |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model gemma-4-26B-A4B-it-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="google/gemma-4-26B-A4B-it",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "google/gemma-4-26B-A4B-it",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
The server parses the model's chain of thought into a separate `reasoning_content` field on the response. Read it alongside the final answer:
```python theme={"system"}
response = client.chat.completions.create(
model="google/gemma-4-26B-A4B-it",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="google/gemma-4-26B-A4B-it",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
[google/gemma-4-31B-it](https://huggingface.co/google/gemma-4-31B-it) is a 31B-parameter dense model with up to 256K context.
This variant ships in 2 presets tuned for different goals: **Latency** for lowest time-to-first-token, and **Throughput** for highest tokens per second. Pick the tab that matches your workload.
This preset serves Gemma 4 31B on H100:2 with FP8 block quantization.
H100 × 2vLLM (0.22.0-cu129 build)256K8
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir gemma-4-31B-it-latency && cd gemma-4-31B-it-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: model:gemma-4-31B-it preset:latency
model_metadata:
description: >-
Gemma 4 multimodal instruct (FP8), OpenAI-compatible chat with vision via vLLM.
repo_id: RedHatAI/gemma-4-31B-it-FP8-block
example_model_input:
model: google/gemma-4-31B-it
messages:
- role: user
content:
- type: text
text: "Describe this image in one sentence."
- type: image_url
image_url:
url: "https://picsum.photos/id/237/200/300"
stream: true
max_tokens: 512
temperature: 1.0
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
weights:
- source: "hf://RedHatAI/gemma-4-31B-it-FP8-block@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--tensor-parallel-size $GPU_COUNT
--served-model-name google/gemma-4-31B-it
--max-num-seqs 16
--max-model-len auto
--limit-mm-per-prompt.image 1
--gpu-memory-utilization 0.9
--enable-prefix-caching
--speculative-config.model RedHatAI/gemma-4-31B-it-speculator.eagle3
--speculative-config.num_speculative_tokens 3
--speculative-config.method eagle3
--trust-remote-code
--enable-auto-tool-choice
--reasoning-parser gemma4
--tool-call-parser gemma4
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
resources:
accelerator: H100:2
use_gpu: true
runtime:
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
predict_concurrency: 8
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--max-num-seqs` | `16` | Maximum number of concurrent sequences in the batch. |
| `--max-model-len` | `auto` | Maximum context length (tokens) the server accepts per request. |
| `--limit-mm-per-prompt.image` | `1` | Maximum number of image inputs per prompt. |
| `--gpu-memory-utilization` | `0.9` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--speculative-config.model` | `RedHatAI/gemma-4-31B-it-speculator.eagle3` | Hugging Face repo for the draft speculator checkpoint. |
| `--speculative-config.num_speculative_tokens` | `3` | Number of tokens the draft speculator proposes per step. |
| `--speculative-config.method` | `eagle3` | Speculative decoding method. **eagle3:** EAGLE v3 speculative decoding. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--reasoning-parser` | `gemma4` | Server-side parser that separates reasoning output into `reasoning_content`. |
| `--tool-call-parser` | `gemma4` | Server-side parser that emits structured `tool_calls` on the response. |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model gemma-4-31B-it-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="google/gemma-4-31B-it",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "google/gemma-4-31B-it",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
The server parses the model's chain of thought into a separate `reasoning_content` field on the response. Read it alongside the final answer:
```python theme={"system"}
response = client.chat.completions.create(
model="google/gemma-4-31B-it",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="google/gemma-4-31B-it",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
RTX\_PRO\_6000vLLM 0.22.1128K64
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir gemma-4-31B-it && cd gemma-4-31B-it
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: model:gemma-4-31B-it preset:throughput
model_metadata:
description: >-
Gemma 4 multimodal instruct (NVFP4), OpenAI-compatible chat with vision via vLLM on RTX PRO 6000.
repo_id: nvidia/Gemma-4-31B-IT-NVFP4
example_model_input:
model: google/gemma-4-31B-it
messages:
- role: user
content:
- type: text
text: "Describe this image in one sentence."
- type: image_url
image_url:
url: "https://picsum.photos/id/237/200/300"
stream: true
max_tokens: 512
temperature: 1.0
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.1
weights:
- source: "hf://nvidia/Gemma-4-31B-IT-NVFP4@main"
mount_location: "/app/checkpoint/gemma"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/gemma
--tensor-parallel-size $GPU_COUNT
--served-model-name google/gemma-4-31B-it
--max-num-seqs 64
--max-model-len 131072
--kv-cache-dtype fp8
--enable-chunked-prefill
--limit-mm-per-prompt.image 1
--enable-prefix-caching
--trust-remote-code
--enable-auto-tool-choice
--reasoning-parser gemma4
--tool-call-parser gemma4
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
resources:
accelerator: RTX_PRO_6000
use_gpu: true
runtime:
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
predict_concurrency: 64
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| ----------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--max-num-seqs` | `64` | Maximum number of concurrent sequences in the batch. |
| `--max-model-len` | `131072` | Maximum context length (tokens) the server accepts per request. |
| `--kv-cache-dtype` | `fp8` | KV cache numeric precision. **fp8:** \~2× KV cache density with negligible quality impact on most models. |
| `--enable-chunked-prefill` | (no value) | Process long prompts in chunks so decode requests keep running. |
| `--limit-mm-per-prompt.image` | `1` | Maximum number of image inputs per prompt. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--reasoning-parser` | `gemma4` | Server-side parser that separates reasoning output into `reasoning_content`. |
| `--tool-call-parser` | `gemma4` | Server-side parser that emits structured `tool_calls` on the response. |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model gemma-4-31B-it was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="google/gemma-4-31B-it",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "google/gemma-4-31B-it",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
The server parses the model's chain of thought into a separate `reasoning_content` field on the response. Read it alongside the final answer:
```python theme={"system"}
response = client.chat.completions.create(
model="google/gemma-4-31B-it",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="google/gemma-4-31B-it",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# GLM-4.7
Source: https://docs.baseten.co/examples/models/llm/glm-4.7
GLM-4.7 recipes: 2 variants (Standard, Flash), MoE architecture.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
Pick the model you want to deploy. Each tab is a self-contained recipe.
[zai-org/GLM-4.7](https://huggingface.co/zai-org/GLM-4.7) is a MoE model with up to 198K context.
This preset serves GLM-4.7 from an FP4 checkpoint on B200:4, delivering frontier-class reasoning at single-node cost.
B200 × 4TRT-LLM v2198K64
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir glm-4.7-latency && cd glm-4.7-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:glm-4.7 preset:latency"
resources:
accelerator: B200:4
cpu: "1"
memory: 10Gi
use_gpu: true
model_metadata:
tags:
- openai-compatible
example_model_input:
model: glm47
messages:
- role: user
content: "Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target. You may assume that each input would have exactly one solution, and you may not use the same element twice. You can return the answer in any order. class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]:"
stream: true
max_tokens: 2048
temperature: 0.5
secrets:
hf_access_token: null
weights:
- source: "hf://baseten-admin/glm-4.7-fp4@main"
mount_location: "/app/model_cache/glm47"
auth_secret_name: "hf_access_token"
allow_patterns:
- "*.safetensors"
- "*.json"
- "*.model"
- "*.jinja"
- "*.py"
ignore_patterns:
- "original/*"
- "*.pth"
- "*.bin"
runtime:
predict_concurrency: 64
trt_llm:
build:
checkpoint_repository:
# repo: baseten-admin/glm-4.7-fp4
repo: michaelfeil/empty-model
revision: main
source: HF
inference_stack: v2
runtime:
enable_chunked_prefill: true
max_batch_size: 64
max_num_tokens: 8192
max_seq_len: 202752
tensor_parallel_size: 4
served_model_name: glm47
patch_kwargs:
disable_overlap_scheduler: True
moe_expert_parallel_size: 4
moe_config:
use_low_precision_moe_combine: true
backend: TRTLLM
kv_cache_config:
free_gpu_memory_fraction: 0.8
enable_block_reuse: true
cuda_graph_config:
enable_padding: true
max_batch_size: 64
speculative_config:
decoding_type: MTP
num_nextn_predict_layers: 3
autotuner_enabled: false
model_path: /app/model_cache/glm47
reasoning_parser: glm47
tool_call_parser: glm47
```
## Key parameters
[Baseten Inference Stack](/engines/bis-llm/overview) (BIS) reads these fields from the `trt_llm` block. Each one shapes how the engine is built and served:
| Parameter | Value |
| -------------------- | --------- |
| Tensor parallel size | `4` |
| Max sequence length | `202752` |
| Max batch size | `64` |
| Max batched tokens | `8192` |
| Chunked prefill | `enabled` |
| Inference stack | `v2` |
| Served model name | `glm47` |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model glm-4.7-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="glm47",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "glm47",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
[zai-org/GLM-4.7-Flash](https://huggingface.co/zai-org/GLM-4.7-Flash) is a MoE model with up to 128K context.
This preset serves GLM-4.7 Flash on H100:2 with the glm47 tool-call parser enabled, tuned for latency-sensitive agent workflows.
H100 × 2vLLM (0.22.0-cu129 build)128K32
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir glm-4.7-flash-latency && cd glm-4.7-flash-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:glm-4.7-flash preset:latency"
model_metadata:
description: >-
Zhipu GLM-4.7 Flash via vLLM (H100 × 2 TP), fast GLM tool calling and auto tool choice from BDN-mounted weights.
repo_id: zai-org/GLM-4.7-Flash
example_model_input:
model: zai-org/GLM-4.7-Flash
messages:
- role: system
content: "You are a helpful assistant."
- role: user
content: "What is the meaning of life?"
stream: true
max_tokens: 32768
temperature: 0.7
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
weights:
- source: "hf://zai-org/GLM-4.7-Flash@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--tensor-parallel-size $GPU_COUNT
--gpu-memory-utilization 0.8
--tool-call-parser glm47
--enable-auto-tool-choice
--served-model-name zai-org/GLM-4.7-Flash
--host 0.0.0.0
--port 8000
--trust-remote-code
--max-model-len auto
--enable-prefix-caching
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
resources:
accelerator: H100:2
cpu: "1"
memory: 2Gi
use_gpu: true
runtime:
is_websocket_endpoint: false
predict_concurrency: 32
transport:
kind: http
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--gpu-memory-utilization` | `0.8` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--tool-call-parser` | `glm47` | Server-side parser that emits structured `tool_calls` on the response. |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--max-model-len` | `auto` | Maximum context length (tokens) the server accepts per request. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model glm-4.7-flash-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="zai-org/GLM-4.7-Flash",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "zai-org/GLM-4.7-Flash",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="zai-org/GLM-4.7-Flash",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# GLM-5
Source: https://docs.baseten.co/examples/models/llm/glm-5
Z.ai's GLM-5 reasoning model, served from an FP8 checkpoint on B200:8.
Z.ai's GLM-5 reasoning model, served from an FP8 checkpoint on B200:8.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
This preset serves GLM-5 FP8 on B200:8, Z.ai's frontier model tuned for the lowest time-to-first-token available.
B200 × 8vLLM 0.22.0128K64
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir glm-5-latency && cd glm-5-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_metadata:
example_model_input:
messages:
- role: system
content: "You are a helpful assistant."
- role: user
content: "What is the meaning of life?"
stream: true
model: zai-org/GLM-5
max_tokens: 32768
temperature: 0.7
tags:
- openai-compatible
model_name: "model:glm-5 preset:latency"
base_image:
image: vllm/vllm-openai:v0.22.0
docker_server:
start_command: >
sh -c "VLLM_DEEP_GEMM_WARMUP=relax python3 -m vllm.entrypoints.openai.api_server
--model /models/GLM-5-FP8
--chat-template /models/GLM-5-FP8/chat_template.jinja
--host 0.0.0.0 --port 8000
--served-model-name zai-org/GLM-5
--tensor-parallel-size 8
--trust-remote-code
--load-format runai_streamer
--disable-log-stats
--max-num-seqs 64
--max-num-batched-tokens 8192
--tool-call-parser glm47
--reasoning-parser glm45
--enable-auto-tool-choice
--speculative-config.method mtp
--speculative-config.num_speculative_tokens 1"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
weights:
- source: "hf://zai-org/GLM-5-FP8@main"
mount_location: "/models/GLM-5-FP8"
ignore_patterns:
- "*.md"
- "*.txt"
resources:
accelerator: B200:8
use_gpu: true
runtime:
predict_concurrency: 64
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `--model` | `/models/GLM-5-FP8` | Path (or HF repo) the engine loads the model from. |
| `--chat-template` | `/models/GLM-5-FP8/chat_template.jinja` | Path to a Jinja chat template file that overrides the checkpoint's default. |
| `--tensor-parallel-size` | `8` | Number of GPUs to shard the model across. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
| `--disable-log-stats` | (no value) | Suppress periodic engine stats logging. |
| `--max-num-seqs` | `64` | Maximum number of concurrent sequences in the batch. |
| `--max-num-batched-tokens` | `8192` | Maximum total tokens processed per scheduler step. |
| `--tool-call-parser` | `glm47` | Server-side parser that emits structured `tool_calls` on the response. |
| `--reasoning-parser` | `glm45` | Server-side parser that separates reasoning output into `reasoning_content`. |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--speculative-config.method` | `mtp` | Speculative decoding method. **mtp:** Multi-token prediction head speculation. |
| `--speculative-config.num_speculative_tokens` | `1` | Number of tokens the draft speculator proposes per step. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model glm-5-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="zai-org/GLM-5",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "zai-org/GLM-5",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
The server parses the model's chain of thought into a separate `reasoning_content` field on the response. Read it alongside the final answer:
```python theme={"system"}
response = client.chat.completions.create(
model="zai-org/GLM-5",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="zai-org/GLM-5",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# GPT-OSS
Source: https://docs.baseten.co/examples/models/llm/gpt-oss
GPT-OSS recipes: 2 variants (20B, 120B), Dense and MoE architectures.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
Pick the model you want to deploy. Each tab is a self-contained recipe.
[openai/gpt-oss-20b](https://huggingface.co/openai/gpt-oss-20b) is a 20B-parameter dense model with up to 128K context.
This preset serves GPT-OSS 20B on a single H100 using the Harmony response format, tuned for low time-to-first-token.
H100TRT-LLM v2128K64
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir gpt-oss-20b-latency && cd gpt-oss-20b-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:gpt-oss-20b preset:latency"
build_commands:
- python -c 'from openai_harmony import load_harmony_encoding; load_harmony_encoding("HarmonyGptOss")'
model_metadata:
repo_id: openai/gpt-oss-20b
example_model_input:
{
"model": "openai/gpt-oss-20b",
"messages":
[
{
"role": "user",
"content": "Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target. You may assume that each input would have exactly one solution, and you may not use the same element twice. You can return the answer in any order. class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]:",
},
],
"stream": true,
"max_tokens": 4096,
"temperature": 0.5,
}
tags:
- openai-compatible
resources:
accelerator: H100
cpu: "1"
memory: 10Gi
use_gpu: true
weights:
- source: "hf://openai/gpt-oss-20b@main"
mount_location: "/app/model_cache/trt_model"
trt_llm:
build:
checkpoint_repository:
repo: michaelfeil/empty-model
revision: main
source: HF
inference_stack: v2
runtime:
enable_chunked_prefill: true
max_batch_size: 64
max_num_tokens: 8192
max_seq_len: 131072
patch_kwargs:
model_path: /app/model_cache/trt_model
chat_processor: harmony
moe_expert_parallel_size: 1
backend: pytorch
cuda_graph_config:
enable_padding: true
disable_overlap_scheduler: 1
enable_autotuner: 0
enable_iter_perf_stats: 0
enable_trtllm_sampler: 1
guided_decoding_backend: xgrammar
kv_cache_config:
enable_block_reuse: true
free_gpu_memory_fraction: 0.8
event_buffer_max_size: 1024
max_beam_width: 1
max_input_len: 131072
model_level_stop_words:
- "<|call|>"
tokenizer_limit_length: 131072
trust_remote_code: 1
moe_config:
backend: CUTLASS
served_model_name: openai/gpt-oss-20b
tensor_parallel_size: 1
version_overrides:
v2_llm_version: null
```
## Key parameters
[Baseten Inference Stack](/engines/bis-llm/overview) (BIS) reads these fields from the `trt_llm` block. Each one shapes how the engine is built and served:
| Parameter | Value |
| -------------------- | -------------------- |
| Tensor parallel size | `1` |
| Max sequence length | `131072` |
| Max batch size | `64` |
| Max batched tokens | `8192` |
| Chunked prefill | `enabled` |
| Inference stack | `v2` |
| Served model name | `openai/gpt-oss-20b` |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model gpt-oss-20b-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="openai/gpt-oss-20b",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "openai/gpt-oss-20b",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
[openai/gpt-oss-120b](https://huggingface.co/openai/gpt-oss-120b) is a 120B-parameter MoE model with up to 128K context.
This variant ships in 2 presets tuned for different goals: **H100 Throughput** for high throughput on H100 hardware, and **Throughput** for highest tokens per second. Pick the tab that matches your workload.
This preset serves GPT-OSS 120B on H100:4 for deployments that don't have Blackwell capacity.
H100 × 4vLLM (0.22.0-cu129 build)16K256
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir gpt-oss-120b-h100-throughput && cd gpt-oss-120b-h100-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:gpt-oss-120b preset:h100-throughput"
model_metadata:
description: >-
GPT-OSS 120B on vLLM H100 × 4 throughput; weights from BDN, async scheduling and prefix caching.
repo_id: openai/gpt-oss-120b
tags:
- openai-compatible
example_model_input:
messages:
- role: system
content: "You are a helpful assistant."
- role: user
content: "Write FizzBuzz in Python"
stream: true
model: "openai/gpt-oss-120b"
max_tokens: 4096
temperature: 0.5
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
build_commands:
- mkdir -p /opt/tiktoken
- curl -fsSL https://openaipublic.blob.core.windows.net/encodings/o200k_base.tiktoken -o /opt/tiktoken/o200k_base.tiktoken
- curl -fsSL https://openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktoken -o /opt/tiktoken/cl100k_base.tiktoken
weights:
- source: "hf://openai/gpt-oss-120b@b5c939de8f754692c1647ca79fbf85e8c1e70f8a"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
ignore_patterns: ["original/*", "metal/model.bin"]
secrets:
hf_access_token: null
environment_variables:
TIKTOKEN_ENCODINGS_BASE: "/opt/tiktoken"
TIKTOKEN_RS_CACHE_DIR: "/opt/tiktoken"
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--host 0.0.0.0
--port 8000
--served-model-name openai/gpt-oss-120b
--tensor-parallel-size $GPU_COUNT
--gpu-memory-utilization 0.90
--max-model-len 16384
--max-num-batched-tokens 16384
--max-num-seqs 256
--stream-interval 20
--enable-chunked-prefill
--enable-prefix-caching
--async-scheduling
--trust-remote-code
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
resources:
accelerator: H100:4
use_gpu: true
runtime:
predict_concurrency: 256
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| -------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--gpu-memory-utilization` | `0.90` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--max-model-len` | `16384` | Maximum context length (tokens) the server accepts per request. |
| `--max-num-batched-tokens` | `16384` | Maximum total tokens processed per scheduler step. |
| `--max-num-seqs` | `256` | Maximum number of concurrent sequences in the batch. |
| `--stream-interval` | `20` | Tokens emitted per streaming chunk. |
| `--enable-chunked-prefill` | (no value) | Process long prompts in chunks so decode requests keep running. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--async-scheduling` | (no value) | Overlap scheduling with GPU execution to hide scheduler latency. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model gpt-oss-120b-h100-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="openai/gpt-oss-120b",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "openai/gpt-oss-120b",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
This preset serves GPT-OSS 120B on B200:4 with FP8 KV cache and FlashInfer MXFP4+MXFP8 MoE kernels, optimized for maximum throughput on Blackwell.
B200 × 4vLLM 0.22.08K256
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir gpt-oss-120b-throughput && cd gpt-oss-120b-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:gpt-oss-120b preset:throughput"
model_metadata:
description: >-
GPT-OSS 120B on vLLM Blackwell (B200 × 4), Harmony recipe with FlashInfer MoE MXFP paths.
repo_id: openai/gpt-oss-120b
tags:
- openai-compatible
example_model_input:
messages:
- role: user
content: "Write FizzBuzz in Python."
stream: true
model: openai/gpt-oss-120b
max_tokens: 4096
temperature: 0.5
base_image:
image: vllm/vllm-openai:v0.22.0
build_commands:
- mkdir -p /opt/tiktoken
- curl -fsSL https://openaipublic.blob.core.windows.net/encodings/o200k_base.tiktoken -o /opt/tiktoken/o200k_base.tiktoken
- curl -fsSL https://openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktoken -o /opt/tiktoken/cl100k_base.tiktoken
weights:
- source: "hf://openai/gpt-oss-120b@b5c939de8f754692c1647ca79fbf85e8c1e70f8a"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
ignore_patterns: ["original/*", "metal/model.bin"]
secrets:
hf_access_token: null
environment_variables:
TIKTOKEN_ENCODINGS_BASE: "/opt/tiktoken"
TIKTOKEN_RS_CACHE_DIR: "/opt/tiktoken"
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
VLLM_USE_FLASHINFER_MOE_MXFP4_MXFP8: "1"
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--host 0.0.0.0
--port 8000
--served-model-name gpt-oss-120b
--tensor-parallel-size $GPU_COUNT
--gpu-memory-utilization 0.95
--max-model-len 8192
--max-num-batched-tokens 8192
--max-num-seqs 256
--cuda-graph-capture-size 2048
--stream-interval 20
--kv-cache-dtype fp8
--enable-prefix-caching
--async-scheduling
--trust-remote-code
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
resources:
accelerator: B200:4
use_gpu: true
runtime:
predict_concurrency: 256
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--gpu-memory-utilization` | `0.95` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--max-model-len` | `8192` | Maximum context length (tokens) the server accepts per request. |
| `--max-num-batched-tokens` | `8192` | Maximum total tokens processed per scheduler step. |
| `--max-num-seqs` | `256` | Maximum number of concurrent sequences in the batch. |
| `--cuda-graph-capture-size` | `2048` | Batch size ceiling for CUDA graph capture (improves decode latency). |
| `--stream-interval` | `20` | Tokens emitted per streaming chunk. |
| `--kv-cache-dtype` | `fp8` | KV cache numeric precision. **fp8:** \~2× KV cache density with negligible quality impact on most models. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--async-scheduling` | (no value) | Overlap scheduling with GPU execution to hide scheduler latency. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model gpt-oss-120b-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="gpt-oss-120b",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "gpt-oss-120b",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Holo 3.1
Source: https://docs.baseten.co/examples/models/llm/holo-3.1
H Company's Holo 3.1 is a 35B-parameter MoE vision-language model with 3B active parameters per token, built on the Qwen3.6-35B-A3B base.
H Company's Holo 3.1 is a 35B-parameter MoE vision-language model with 3B active parameters per token, built on the Qwen3.6-35B-A3B base. It accepts image input and returns reasoning output and native tool calls.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
This preset serves Holo 3.1 35B-A3B on H100 GPUs with FP8 weights, optimized for high-concurrency throughput on agent and vision workloads.
H100vLLM (0.20.2-cu129 build)256K1000
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir holo-3.1-35b-a3b-throughput && cd holo-3.1-35b-a3b-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:holo-3.1-35b-a3b preset:throughput"
model_metadata:
description: >-
Holo-3.1-35B-A3B (FP8), H Company's computer-use / GUI-agent vision-language
model built on the Qwen3.6-35B-A3B MoE base. OpenAI-compatible multimodal chat
with image input and native function calling, served via vLLM.
repo_id: Hcompany/Holo-3.1-35B-A3B-FP8
example_model_input:
model: Hcompany/Holo-3.1-35B-A3B-FP8
messages:
- role: user
content:
- type: text
text: "Describe this image in one sentence."
- type: image_url
image_url:
url: "https://picsum.photos/id/237/200/300"
stream: true
max_tokens: 512
temperature: 1.0
tags:
- openai-compatible
- vllm
- holo3.1
- fp8
- h100
- multimodal
base_image:
image: vllm/vllm-openai:v0.20.2-cu129
weights:
- source: "hf://Hcompany/Holo-3.1-35B-A3B-FP8@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
build_commands: []
environment_variables:
PYTORCH_ALLOC_CONF: "expandable_segments:True"
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--tensor-parallel-size $GPU_COUNT
--served-model-name Hcompany/Holo-3.1-35B-A3B-FP8
--host 0.0.0.0
--port 8000
--gpu-memory-utilization 0.95
--max-model-len 262144
--max-num-batched-tokens 32768
--dtype auto
--enable-chunked-prefill
--enable-prefix-caching
--max-num-seqs 512
--limit-mm-per-prompt.image 2
--reasoning-parser qwen3
--enable-auto-tool-choice
--tool-call-parser qwen3_coder
--trust-remote-code"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
runtime:
predict_concurrency: 1000
health_checks:
restart_check_delay_seconds: 1500
restart_threshold_seconds: 1500
stop_traffic_threshold_seconds: 120
resources:
accelerator: H100
use_gpu: true
secrets:
hf_access_token: null
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| ----------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--gpu-memory-utilization` | `0.95` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--max-model-len` | `262144` | Maximum context length (tokens) the server accepts per request. |
| `--max-num-batched-tokens` | `32768` | Maximum total tokens processed per scheduler step. |
| `--dtype` | `auto` | Weight precision loaded at runtime. **auto:** Match the model's checkpoint dtype (default). |
| `--enable-chunked-prefill` | (no value) | Process long prompts in chunks so decode requests keep running. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--max-num-seqs` | `512` | Maximum number of concurrent sequences in the batch. |
| `--limit-mm-per-prompt.image` | `2` | Maximum number of image inputs per prompt. |
| `--reasoning-parser` | `qwen3` | Server-side parser that separates reasoning output into `reasoning_content`. **qwen3:** Qwen3-family thinking format (used by Qwen3, Qwen3.5, and Qwen3.6). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--tool-call-parser` | `qwen3_coder` | Server-side parser that emits structured `tool_calls` on the response. **qwen3\_coder:** Qwen3-Coder tool format. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model holo-3.1-35b-a3b-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="Hcompany/Holo-3.1-35B-A3B-FP8",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "Hcompany/Holo-3.1-35B-A3B-FP8",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
To access the model's chain of thought, enable thinking mode. The server parses the reasoning output into a separate `reasoning_content` field on the response:
```python theme={"system"}
response = client.chat.completions.create(
model="Hcompany/Holo-3.1-35B-A3B-FP8",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
extra_body={"chat_template_kwargs": {"enable_thinking": True}},
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="Hcompany/Holo-3.1-35B-A3B-FP8",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Laguna
Source: https://docs.baseten.co/examples/models/llm/laguna
Poolside's Laguna M.1 is a Mixture-of-Experts reasoning model tuned for agentic coding and extended reasoning, served from an FP8 checkpoint.
Poolside's Laguna M.1 is a Mixture-of-Experts reasoning model tuned for agentic coding and extended reasoning, served from an FP8 checkpoint.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
This preset serves Laguna M.1 on H100:4 with FP8 weights, optimized for low time-to-first-token on interactive reasoning and coding workloads.
H100 × 4vLLM 0.21.0256K64
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir laguna-m.1-latency && cd laguna-m.1-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:laguna-m.1 preset:latency"
model_metadata:
description: >-
Laguna M.1 FP8 MoE reasoning model from Poolside, served with vLLM (H100 TP=4),
OpenAI-compatible chat with tool calling and extended reasoning support.
Latency-optimized: low max-num-seqs to minimize head-of-line blocking from long thinking traces.
repo_id: poolside/Laguna-M.1-FP8
trust_remote_code: true
tags:
- openai-compatible
- vllm
- moe
- reasoning
- agentic-coding
- fp8
example_model_input:
model: poolside/laguna-m.1
messages:
- role: user
content: "Write a Python retry wrapper with exponential backoff."
stream: true
temperature: 1.0
top_k: 20
# ---------------------------------------------------------------------------
# Base image — vLLM with Laguna support (requires vLLM >= 0.21.0)
# ---------------------------------------------------------------------------
base_image:
image: vllm/vllm-openai:v0.21.0
python_executable_path: /usr/bin/python3
# ---------------------------------------------------------------------------
# Weights — FP8 quantized checkpoint (~225 GB, fits in 4× H100 / 320 GB)
# Quantization is detected automatically from the checkpoint's
# quantization_config — no extra vLLM flags needed.
# ---------------------------------------------------------------------------
weights:
- source: "hf://poolside/Laguna-M.1-FP8"
mount_location: "/models/laguna-m1"
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
# ---------------------------------------------------------------------------
# Docker server — vLLM OpenAI-compatible endpoint
# ---------------------------------------------------------------------------
docker_server:
start_command: >
vllm serve /models/laguna-m1
--served-model-name poolside/laguna-m.1
--host 0.0.0.0
--port 8000
--tool-call-parser poolside_v1
--reasoning-parser poolside_v1
--enable-auto-tool-choice
--default-chat-template-kwargs '{"enable_thinking": true}'
--tensor-parallel-size 4
--max-model-len 262144
--max-num-seqs 64
--gpu-memory-utilization 0.95
--trust-remote-code
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
# ---------------------------------------------------------------------------
# Resources
# FP8 ~225 GB → 4× H100 (320 GB total VRAM) with comfortable headroom
# ---------------------------------------------------------------------------
resources:
accelerator: H100:4
cpu: "8"
memory: 32Gi
use_gpu: true
# ---------------------------------------------------------------------------
# Runtime
# ---------------------------------------------------------------------------
runtime:
predict_concurrency: 64
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 600
stop_traffic_threshold_seconds: 180
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| -------------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `--tool-call-parser` | `poolside_v1` | Server-side parser that emits structured `tool_calls` on the response. |
| `--reasoning-parser` | `poolside_v1` | Server-side parser that separates reasoning output into `reasoning_content`. |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--default-chat-template-kwargs` | `{"enable_thinking": true}` | Default keyword arguments applied to the chat template, used to set behaviors like enabling reasoning by default. |
| `--tensor-parallel-size` | `4` | Number of GPUs to shard the model across. |
| `--max-model-len` | `262144` | Maximum context length (tokens) the server accepts per request. |
| `--max-num-seqs` | `64` | Maximum number of concurrent sequences in the batch. |
| `--gpu-memory-utilization` | `0.95` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model laguna-m.1-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="poolside/laguna-m.1",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "poolside/laguna-m.1",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
The server parses the model's chain of thought into a separate `reasoning_content` field on the response. Read it alongside the final answer:
```python theme={"system"}
response = client.chat.completions.create(
model="poolside/laguna-m.1",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="poolside/laguna-m.1",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Llama 3.1
Source: https://docs.baseten.co/examples/models/llm/llama-3.1
Meta's Llama 3.1 8B instruction-tuned model.
Meta's Llama 3.1 8B instruction-tuned model. Runs on a single B200 from NVIDIA's FP8 checkpoint with EAGLE3 speculative decoding for high concurrent throughput.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
This preset serves Llama 3.1 8B Instruct on a single B200 through [Baseten Inference Stack](/engines/bis-llm/overview) (TensorRT-LLM) with FP8 weights, an FP8 KV cache, and EAGLE3 speculative decoding. It targets high concurrent throughput.
B200TRT-LLM v2128K512
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir llama-3.1-8b-instruct-throughput && cd llama-3.1-8b-instruct-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:llama-3.1-8b-instruct preset:throughput"
model_metadata:
example_model_input:
messages:
- role: user
content: "Write FizzBuzz in Python"
stream: true
model: "nvidia/Llama-3.1-8B-Instruct-FP8"
max_tokens: 512
temperature: 0.5
tags:
- openai-compatible
resources:
accelerator: B200
cpu: "1"
memory: 10Gi
use_gpu: true
weights:
- source: "hf://nvidia/Llama-3.1-8B-Instruct-FP8@main"
mount_location: "/app/model_cache/trt_model"
auth_secret_name: "hf_access_token"
- source: "hf://yuhuili/EAGLE3-LLaMA3.1-Instruct-8B@main"
mount_location: "/app/model_cache/eagle3_draft"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
trt_llm:
build:
checkpoint_repository:
repo: michaelfeil/empty-model
revision: main
source: HF
inference_stack: v2
runtime:
enable_chunked_prefill: true
max_batch_size: 512
max_num_tokens: 16384
max_seq_len: 131072
tensor_parallel_size: 1
served_model_name: nvidia/Llama-3.1-8B-Instruct-FP8
patch_kwargs:
model_path: /app/model_cache/trt_model
backend: pytorch
sampler_type: TorchSampler
guided_decoding_backend: xgrammar
max_beam_width: 1
max_input_len: 131072
trust_remote_code: 1
cuda_graph_config:
enable_padding: true
max_batch_size: 512
kv_cache_config:
dtype: fp8
enable_block_reuse: true
free_gpu_memory_fraction: 0.9
speculative_config:
decoding_type: Eagle
max_draft_len: 3
speculative_model_dir: /app/model_cache/eagle3_draft
eagle3_one_model: true
version_overrides:
v2_llm_version: null
runtime:
predict_concurrency: 512
```
This config tells Baseten to compile a TensorRT-LLM engine for Llama 3.1 8B Instruct on a single B200, pulling FP8 weights from `nvidia/Llama-3.1-8B-Instruct-FP8` and an EAGLE3 draft speculator from `yuhuili/EAGLE3-LLaMA3.1-Instruct-8B`. The runtime is tuned for high concurrent throughput: 512 in-flight requests, chunked prefill, an FP8 KV cache, and CUDA graphs sized to the same batch ceiling so the engine stays hot under load.
## Key parameters
[Baseten Inference Stack](/engines/bis-llm/overview) (BIS) reads these fields from the `trt_llm` block. Each one shapes how the engine is built and served:
| Parameter | Value |
| -------------------- | ---------------------------------- |
| Tensor parallel size | `1` |
| Max sequence length | `131072` |
| Max batch size | `512` |
| Max batched tokens | `16384` |
| Chunked prefill | `enabled` |
| Inference stack | `v2` |
| Served model name | `nvidia/Llama-3.1-8B-Instruct-FP8` |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model llama-3.1-8b-instruct-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="nvidia/Llama-3.1-8B-Instruct-FP8",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "nvidia/Llama-3.1-8B-Instruct-FP8",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Llama 3.2
Source: https://docs.baseten.co/examples/models/llm/llama-3.2
Meta's compact Llama 3.2 instruction-tuned model.
Meta's compact Llama 3.2 instruction-tuned model. Runs on a single H100 40GB for low-cost chat and edge-adjacent workloads.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
This preset serves Llama 3.2 3B Instruct on a single H100 40GB through [Baseten Inference Stack](/engines/bis-llm/overview) (TensorRT-LLM), optimized for the lowest Llama 3.2 latency on Baseten.
H100\_40GBTRT-LLM128K
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir llama-3.2-3b-instruct-latency && cd llama-3.2-3b-instruct-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_metadata:
example_model_input:
max_tokens: 512
messages:
- content: Tell me everything you know about optimized inference.
role: user
stream: true
temperature: 0.5
tags:
- openai-compatible
model_name: "model:llama-3.2-3b-instruct preset:latency"
python_version: py39
resources:
accelerator: H100_40GB
cpu: "1"
memory: 10Gi
use_gpu: true
trt_llm:
build:
base_model: decoder
checkpoint_repository:
repo: meta-llama/Llama-3.2-3B-Instruct
revision: main
source: HF
max_seq_len: 131072
quantization_type: fp8_kv
tensor_parallel_count: 1
runtime:
enable_chunked_context: true
```
## Key parameters
[Baseten Inference Stack](/engines/bis-llm/overview) (BIS) reads these fields from the `trt_llm` block. Each one shapes how the engine is built and served:
| Parameter | Value |
| ------------------- | --------- |
| Max sequence length | `131072` |
| Chunked prefill | `enabled` |
| Quantization | `fp8_kv` |
| Base model type | `decoder` |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model llama-3.2-3b-instruct-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Llama 3.3
Source: https://docs.baseten.co/examples/models/llm/llama-3.3
Meta's Llama 3.3 70B instruction-tuned model.
Meta's Llama 3.3 70B instruction-tuned model. Runs on H100:4 through Baseten Inference Stack from NVIDIA's FP8 checkpoint, tuned for low time-to-first-token.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
This preset serves Llama 3.3 70B Instruct on H100:4 through [Baseten Inference Stack](/engines/bis-llm/overview) (TensorRT-LLM) with FP8 weights and tensor parallelism. It targets low time-to-first-token on the 70B chat model.
H100 × 4TRT-LLM v2128K128
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir llama-3.3-70b-instruct-latency && cd llama-3.3-70b-instruct-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:llama-3.3-70b-instruct preset:latency"
model_metadata:
tags:
- openai-compatible
example_model_input:
stream: true
model: nvidia/Llama-3.3-70B-Instruct-FP8
messages:
- role: user
content: Tell me everything you know about optimized inference.
max_tokens: 512
temperature: 0.5
python_version: py313
secrets:
hf_access_token: null
weights:
- source: hf://nvidia/Llama-3.3-70B-Instruct-FP8@main
allow_patterns:
- "*.safetensors"
- "*.json"
- "*.model"
- tokenizer.model
- "*.tiktoken"
- "*.jinja"
mount_location: /app/model_cache/llama-3-3-70b-instruct
ignore_patterns:
- original/*
- "*.pth"
auth_secret_name: hf_access_token
resources:
cpu: "4"
memory: 40Gi
use_gpu: true
accelerator: H100:4
data_dir: data
runtime:
predict_concurrency: 128
streaming_read_timeout: 60
trt_llm:
build:
checkpoint_repository:
repo: michaelfeil/empty-model
source: HF
revision: main
runtime_secret_name: hf_access_token
runtime:
max_seq_len: 131072
patch_kwargs:
model_path: /app/model_cache/llama-3-3-70b-instruct
model_path_for_tokenizer: /app/model_cache/llama-3-3-70b-instruct
cuda_graph_config:
enable_padding: true
max_batch_size: 128
max_batch_size: 128
max_num_tokens: 8192
served_model_name: nvidia/Llama-3.3-70B-Instruct-FP8
tensor_parallel_size: 4
enable_chunked_prefill: true
inference_stack: v2
version_overrides:
v2_llm_version: null
```
This config tells Baseten to compile a TensorRT-LLM engine for Llama 3.3 70B Instruct on four H100 GPUs, sharding FP8 weights from `nvidia/Llama-3.3-70B-Instruct-FP8` across the four ranks. The runtime targets low time-to-first-token at moderate concurrency: 128 in-flight requests, chunked prefill, and CUDA graphs sized to the batch ceiling so each new request hits a warm engine.
## Key parameters
[Baseten Inference Stack](/engines/bis-llm/overview) (BIS) reads these fields from the `trt_llm` block. Each one shapes how the engine is built and served:
| Parameter | Value |
| -------------------- | ----------------------------------- |
| Tensor parallel size | `4` |
| Max sequence length | `131072` |
| Max batch size | `128` |
| Max batched tokens | `8192` |
| Chunked prefill | `enabled` |
| Inference stack | `v2` |
| Served model name | `nvidia/Llama-3.3-70B-Instruct-FP8` |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model llama-3.3-70b-instruct-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="nvidia/Llama-3.3-70B-Instruct-FP8",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "nvidia/Llama-3.3-70B-Instruct-FP8",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Llama 4
Source: https://docs.baseten.co/examples/models/llm/llama-4
Meta's Llama 4 Scout is a 17B-active MoE with native multimodal support and a 10M token context window.
Meta's Llama 4 Scout is a 17B-active MoE with native multimodal support and a 10M token context window.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
This preset serves Llama 4 Scout on H100:4 with a 128K serving context and native multimodal support.
H100 × 4vLLM (0.22.0-cu129 build)128K256
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir llama-4-scout-latency && cd llama-4-scout-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:llama-4-scout preset:latency"
model_metadata:
description: >-
Llama 4 Scout 17B multimodal instruct (RedHat FP8-dynamic), long-context with TP=4 FP8 KV,
OpenAI-compatible chat via vLLM.
repo_id: RedHatAI/Llama-4-Scout-17B-16E-Instruct-FP8-dynamic
example_model_input:
model: llama
messages:
- role: user
content: "Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target. You may assume that each input would have exactly one solution, and you may not use the same element twice. You can return the answer in any order. class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]:"
stream: true
max_tokens: 512
temperature: 0.5
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
weights:
- source: "hf://RedHatAI/Llama-4-Scout-17B-16E-Instruct-FP8-dynamic@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--served-model-name llama
--host 0.0.0.0
--port 8000
--trust-remote-code
--max-model-len 131072
--tensor-parallel-size $GPU_COUNT
--distributed-executor-backend mp
--gpu-memory-utilization 0.95
--kv-cache-dtype fp8
--limit-mm-per-prompt.image 10
--override-generation-config.attn_temperature_tuning true
--enable-prefix-caching
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
resources:
accelerator: H100:4
use_gpu: true
runtime:
predict_concurrency: 256
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| ------------------------------------------------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------- |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--max-model-len` | `131072` | Maximum context length (tokens) the server accepts per request. |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--distributed-executor-backend` | `mp` | How vLLM coordinates tensor-parallel workers across processes. **mp:** Python multiprocessing (single-node default). |
| `--gpu-memory-utilization` | `0.95` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--kv-cache-dtype` | `fp8` | KV cache numeric precision. **fp8:** \~2× KV cache density with negligible quality impact on most models. |
| `--limit-mm-per-prompt.image` | `10` | Maximum number of image inputs per prompt. |
| `--override-generation-config.attn_temperature_tuning` | `true` | Sets the `attn_temperature_tuning` field in the model's generation config. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model llama-4-scout-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="llama",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "llama",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Mellum2
Source: https://docs.baseten.co/examples/models/llm/mellum2
JetBrains' Mellum2 open MoE code model (12B total, 2.5B active) with a 131k-token context window and tool calling.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
[JetBrains/Mellum2-12B-A2.5B-Instruct](https://huggingface.co/JetBrains/Mellum2-12B-A2.5B-Instruct) is a 12B-parameter MoE model (2.5B active per token) with up to 128K context.
This preset serves Mellum2 12B A2.5B Instruct on a single H100 through vLLM, optimized for low-latency code generation.
H100vLLM 0.23.0128K128
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir mellum2-12b-a2.5b-instruct-latency && cd mellum2-12b-a2.5b-instruct-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: model:mellum2-12b-a2.5b-instruct preset:latency
model_metadata:
example_model_input:
model: "JetBrains/Mellum2-12B-A2.5B-Instruct"
messages:
- role: user
content: "Write a Python function to reverse a string."
stream: true
max_tokens: 4096
temperature: 0.6
top_p: 0.95
tags:
- openai-compatible
- code
- moe
base_image:
image: vllm/vllm-openai:v0.23.0
weights:
- source: "hf://JetBrains/Mellum2-12B-A2.5B-Instruct@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
resources:
accelerator: H100
use_gpu: true
runtime:
predict_concurrency: 128
health_checks:
startup_threshold_seconds: 1800
restart_threshold_seconds: 600
stop_traffic_threshold_seconds: 120
environment_variables:
HF_HUB_ENABLE_HF_TRANSFER: "1"
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
secrets:
hf_access_token: null
docker_server:
# No --reasoning-parser: Instruct answers directly without blocks.
# Remove --enable-auto-tool-choice / --tool-call-parser if you don't need tool use.
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--tensor-parallel-size $GPU_COUNT
--served-model-name JetBrains/Mellum2-12B-A2.5B-Instruct
--host 0.0.0.0
--port 8000
--max-model-len auto
--enable-prefix-caching
--enable-auto-tool-choice
--tool-call-parser hermes
--trust-remote-code
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
```
This config runs the official `vllm/vllm-openai:v0.23.0` image, the release that adds `MellumForCausalLM` support, and streams weights from `JetBrains/Mellum2-12B-A2.5B-Instruct` with the Run:ai streamer. The Hermes tool-call parser enables OpenAI-compatible function calling, and a concurrency ceiling of 128 keeps the deployment throughput-friendly for coding assistants.
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--max-model-len` | `auto` | Maximum context length (tokens) the server accepts per request. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--tool-call-parser` | `hermes` | Server-side parser that emits structured `tool_calls` on the response. **hermes:** Hermes-style function calls. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model mellum2-12b-a2.5b-instruct-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
Your **model ID** is printed in the `truss push` output (`abcd1234` in the example). Use it wherever you see `{model_id}` in the next section.
## Call the model
Your deployment serves an OpenAI-compatible API. Replace `{model_id}` with your model ID and make sure `BASETEN_API_KEY` is set.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="JetBrains/Mellum2-12B-A2.5B-Instruct",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "JetBrains/Mellum2-12B-A2.5B-Instruct",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="JetBrains/Mellum2-12B-A2.5B-Instruct",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
# MiniMax M2.5
Source: https://docs.baseten.co/examples/models/llm/minimax-m2.5
Large MoE model with native reasoning and tool calling.
Large MoE model with native reasoning and tool calling. Uses the MiniMax-specific append-think reasoning format.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
This preset serves MiniMax M2.5 on H100:4 with expert-parallel sharding and Runai Streamer weight loading, optimized for maximum batch throughput.
H100 × 4vLLM (0.22.0-cu129 build)200K64
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir minimax-m2.5-throughput && cd minimax-m2.5-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:minimax-m2.5 preset:throughput"
model_metadata:
description: >-
MiniMax-M2.5 Mixture-of-Experts (Run:AI streamer loading), throughput on H100 × 4 with MiniMax parsers.
repo_id: MiniMaxAI/MiniMax-M2.5
example_model_input:
messages:
- role: system
content: "You are a helpful assistant."
- role: user
content: "What is the meaning of life?"
stream: true
model: MiniMaxAI/MiniMax-M2.5
max_tokens: 32768
temperature: 0.7
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
weights:
- source: "hf://MiniMaxAI/MiniMax-M2.5@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
ignore_patterns:
- "*.md"
- "*.txt"
secrets:
hf_access_token: null
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && SAFETENSORS_FAST_GPU=1 vllm serve /app/checkpoint/model
--host 0.0.0.0
--port 8000
--served-model-name MiniMaxAI/MiniMax-M2.5
--tensor-parallel-size $GPU_COUNT
--enable-expert-parallel
--trust-remote-code
--load-format runai_streamer
--disable-log-stats
--max-num-seqs 64
--max-num-batched-tokens 8192
--tool-call-parser minimax_m2
--reasoning-parser minimax_m2_append_think
--enable-auto-tool-choice
--enable-prefix-caching"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
resources:
accelerator: H100:4
use_gpu: true
runtime:
predict_concurrency: 64
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--enable-expert-parallel` | (no value) | Shard MoE expert weights across tensor-parallel ranks instead of replicating them, reducing per-GPU memory for large MoE models. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
| `--disable-log-stats` | (no value) | Suppress periodic engine stats logging. |
| `--max-num-seqs` | `64` | Maximum number of concurrent sequences in the batch. |
| `--max-num-batched-tokens` | `8192` | Maximum total tokens processed per scheduler step. |
| `--tool-call-parser` | `minimax_m2` | Server-side parser that emits structured `tool_calls` on the response. **minimax\_m2:** MiniMax M2 tool format. |
| `--reasoning-parser` | `minimax_m2_append_think` | Server-side parser that separates reasoning output into `reasoning_content`. **minimax\_m2\_append\_think:** MiniMax M2 append-think format. |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model minimax-m2.5-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="MiniMaxAI/MiniMax-M2.5",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "MiniMaxAI/MiniMax-M2.5",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
The server parses the model's chain of thought into a separate `reasoning_content` field on the response. Read it alongside the final answer:
```python theme={"system"}
response = client.chat.completions.create(
model="MiniMaxAI/MiniMax-M2.5",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="MiniMaxAI/MiniMax-M2.5",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Nemotron 3
Source: https://docs.baseten.co/examples/models/llm/nemotron-3
NVIDIA's Nemotron 3 Super 120B A12B Mixture-of-Experts model.
NVIDIA's Nemotron 3 Super 120B A12B Mixture-of-Experts model. Runs on B200:4 through Baseten Inference Stack with MTP speculative decoding and the NVFP4-quantized checkpoint, tuned for high-throughput reasoning.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
This preset serves Nemotron 3 Super 120B A12B on B200:4 through [Baseten Inference Stack](/engines/bis-llm/overview) (TensorRT-LLM) with NVFP4 weights, expert parallelism, and MTP speculative decoding. It targets high-throughput reasoning.
B200 × 4TRT-LLM v2128K32
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir nemotron-3-super-120b-a12b-throughput && cd nemotron-3-super-120b-a12b-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: model:nemotron-3-super-120b-a12b preset:throughput
model_metadata:
example_model_input:
model: "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4"
max_tokens: 512
messages:
- role: user
content: Tell me everything you know about optimized inference.
stream: true
temperature: 0.5
tags:
- openai-compatible
resources:
accelerator: B200:4
cpu: "1"
memory: 10Gi
use_gpu: true
environment_variables:
PYTORCH_CUDA_ALLOC_CONF: "expandable_segments:True"
TRTLLM_ENABLE_PDL: "1"
BAD_TOKEN_ID_SEQ_CHECK_ENABLED: "1"
ENABLE_B10_LOOKAHEAD: "0"
secrets:
hf_access_token: null
trt_llm:
inference_stack: v2
build:
checkpoint_repository:
repo: nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4
revision: main
source: HF
runtime_secret_name: hf_access_token
runtime:
enable_chunked_prefill: true
max_batch_size: 32
max_num_tokens: 16384
max_seq_len: 131072
tensor_parallel_size: 4
served_model_name: nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4
patch_kwargs:
reasoning_parser: nemotron3
tool_call_parser: qwen3_coder
tokenizer_limit_length: 131072
arguments_as_json: true
engine_config:
backend: pytorch
enable_chunked_prefill: true
enable_iter_perf_stats: true
max_batch_size: 32
max_beam_width: 1
max_input_len: 131072
max_num_tokens: 16384
max_seq_len: 131072
trust_remote_code: true
moe_expert_parallel_size: 4
cuda_graph_config:
enable_padding: true
max_batch_size: 32
kv_cache_config:
dtype: fp8
enable_block_reuse: false
free_gpu_memory_fraction: 0.8
mamba_ssm_cache_dtype: float32
moe_config:
backend: TRTLLM
speculative_config:
decoding_type: MTP
num_nextn_predict_layers: 3
allow_advanced_sampling: true
```
This config tells Baseten to compile a TensorRT-LLM engine for Nemotron 3 Super 120B A12B on four B200 GPUs with NVFP4-quantized weights, wiring the Qwen3-coder tool-call parser and Nemotron 3 reasoning parser into the engine config. Expert parallelism across all four GPUs, MTP speculative decoding with three draft tokens, and chunked prefill combine to push high reasoning throughput, serving up to a 128K context window.
## Key parameters
[Baseten Inference Stack](/engines/bis-llm/overview) (BIS) reads these fields from the `trt_llm` block. Each one shapes how the engine is built and served:
| Parameter | Value |
| -------------------- | ------------------------------------------------ |
| Tensor parallel size | `4` |
| Max sequence length | `131072` |
| Max batch size | `32` |
| Max batched tokens | `16384` |
| Chunked prefill | `enabled` |
| Inference stack | `v2` |
| Served model name | `nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4` |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model nemotron-3-super-120b-a12b-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Qwen3
Source: https://docs.baseten.co/examples/models/llm/qwen3
Sparse MoE model with 235B total parameters (22B active per token).
Sparse MoE model with 235B total parameters (22B active per token). FP8-quantized checkpoint for production-scale reasoning and agentic workflows.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
This preset serves Qwen3-235B FP8 on H100:8 with TensorRT-LLM, optimized for low time-to-first-token on single-request reasoning at this scale.
H100 × 8TRT-LLM v2256K256
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3-235b-latency && cd qwen3-235b-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_metadata:
example_model_input: # Loads sample request into Baseten playground
messages:
- role: system
content: "You are a helpful assistant."
- role: user
content: "What does Tongyi Qianwen mean?"
stream: false
model: "Qwen/Qwen3-235B-A22B-Instruct-2507-FP8"
max_tokens: 512
temperature: 0.6
tags:
- openai-compatible
repo_id: Qwen/Qwen3-235B-A22B-Instruct-2507-FP8
model_name: "model:qwen3-235b preset:latency"
weights:
- source: "hf://Qwen/Qwen3-235B-A22B-Instruct-2507-FP8@main"
mount_location: "/app/model_cache/trt_model"
resources:
accelerator: H100:8
cpu: "1"
memory: 10Gi
use_gpu: true
trt_llm:
build:
checkpoint_repository:
repo: michaelfeil/empty-model
revision: main
source: HF
inference_stack: v2
runtime:
enable_chunked_prefill: true
max_batch_size: 256
max_num_tokens: 8192
max_seq_len: 262144
served_model_name: Qwen/Qwen3-235B-A22B-Instruct-2507-FP8
tensor_parallel_size: 8
patch_kwargs:
disable_overlap_scheduler: True
model_path: /app/model_cache/trt_model
moe_expert_parallel_size: 8
cuda_graph_config:
enable_padding: true
max_batch_size: 256
enable_autotune: false
guided_decoding_backend: "xgrammar"
enable_iter_perf_stats: 0
kv_cache_config:
enable_block_reuse: true
free_gpu_memory_fraction: 0.8
version_overrides:
v2_llm_version: null
```
## Key parameters
[Baseten Inference Stack](/engines/bis-llm/overview) (BIS) reads these fields from the `trt_llm` block. Each one shapes how the engine is built and served:
| Parameter | Value |
| -------------------- | ---------------------------------------- |
| Tensor parallel size | `8` |
| Max sequence length | `262144` |
| Max batch size | `256` |
| Max batched tokens | `8192` |
| Chunked prefill | `enabled` |
| Inference stack | `v2` |
| Served model name | `Qwen/Qwen3-235B-A22B-Instruct-2507-FP8` |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3-235b-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="Qwen/Qwen3-235B-A22B-Instruct-2507-FP8",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "Qwen/Qwen3-235B-A22B-Instruct-2507-FP8",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Qwen3-VL
Source: https://docs.baseten.co/examples/models/llm/qwen3-vl
Qwen3-VL-32B-Instruct is a 32B-parameter dense vision-language model.
Qwen3-VL-32B-Instruct is a 32B-parameter dense vision-language model. This recipe serves the RedHatAI NVFP4 quantization with image input and native tool calling.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
This preset serves the RedHatAI NVFP4 quantization of Qwen3-VL-32B-Instruct on a single RTX PRO 6000 Blackwell GPU, optimized for throughput on vision-language workloads.
RTX\_PRO\_6000vLLM (0.22.0-cu129 build)8
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3-vl-32b-throughput && cd qwen3-vl-32b-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:qwen3-vl-32b preset:throughput"
model_metadata:
description: >-
Qwen3-VL-32B-Instruct (NVFP4), an OpenAI-compatible multimodal chat model with
vision served via vLLM.
repo_id: RedHatAI/Qwen3-VL-32B-Instruct-NVFP4
example_model_input:
model: Qwen/Qwen3-VL-32B-Instruct
messages:
- role: user
content:
- type: text
text: "Describe this image in one sentence."
- type: image_url
image_url:
url: "https://picsum.photos/id/237/200/300"
stream: true
max_tokens: 512
temperature: 1.0
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
weights:
- source: "hf://RedHatAI/Qwen3-VL-32B-Instruct-NVFP4@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--tensor-parallel-size $GPU_COUNT
--served-model-name Qwen/Qwen3-VL-32B-Instruct
--max-num-seqs 16
--max-model-len auto
--limit-mm-per-prompt.image 2
--gpu-memory-utilization 0.9
--enable-prefix-caching
--trust-remote-code
--enable-auto-tool-choice
--tool-call-parser hermes
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
resources:
accelerator: RTX_PRO_6000
use_gpu: true
runtime:
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
predict_concurrency: 8
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| ----------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--max-num-seqs` | `16` | Maximum number of concurrent sequences in the batch. |
| `--max-model-len` | `auto` | Maximum context length (tokens) the server accepts per request. |
| `--limit-mm-per-prompt.image` | `2` | Maximum number of image inputs per prompt. |
| `--gpu-memory-utilization` | `0.9` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--tool-call-parser` | `hermes` | Server-side parser that emits structured `tool_calls` on the response. **hermes:** Hermes-style function calls. |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3-vl-32b-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="Qwen/Qwen3-VL-32B-Instruct",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "Qwen/Qwen3-VL-32B-Instruct",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="Qwen/Qwen3-VL-32B-Instruct",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Qwen3.5
Source: https://docs.baseten.co/examples/models/llm/qwen3.5
Qwen3.5 recipes: 4 variants (4B, 9B, 35B, 122B), Dense, Hybrid MoE, and MoE architectures.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
Pick the model you want to deploy. Each tab is a self-contained recipe.
[Qwen/Qwen3.5-4B](https://huggingface.co/Qwen/Qwen3.5-4B) is a 4B-parameter dense model with up to 256K context.
This preset serves Qwen3.5-4B with BF16 weights on a single H100, optimized for low time-to-first-token.
H100 × 1vLLM (0.22.0-cu129 build)32K128
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3.5-4b-latency && cd qwen3.5-4b-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:qwen3.5-4b preset:latency"
model_metadata:
description: >-
Qwen 3.5 4B instruct (dense), OpenAI-compatible chat via vLLM with Qwen tooling parsers.
repo_id: Qwen/Qwen3.5-4B
example_model_input:
model: "Qwen/Qwen3.5-4B"
messages:
- role: user
content: "What is the capital of France?"
stream: true
max_tokens: 100
temperature: 0.7
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
weights:
- source: "hf://Qwen/Qwen3.5-4B@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model --tensor-parallel-size $GPU_COUNT
--served-model-name Qwen/Qwen3.5-4B
--host 0.0.0.0
--port 8000
--gpu-memory-utilization 0.95
--max-model-len 32768
--dtype bfloat16
--reasoning-parser qwen3
--enable-auto-tool-choice
--tool-call-parser qwen3_coder
--trust-remote-code
--enable-prefix-caching
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
runtime:
predict_concurrency: 128
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
resources:
accelerator: H100:1
use_gpu: true
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--gpu-memory-utilization` | `0.95` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--max-model-len` | `32768` | Maximum context length (tokens) the server accepts per request. |
| `--dtype` | `bfloat16` | Weight precision loaded at runtime. **bfloat16:** BF16 weights, no quantization. |
| `--reasoning-parser` | `qwen3` | Server-side parser that separates reasoning output into `reasoning_content`. **qwen3:** Qwen3-family thinking format (used by Qwen3, Qwen3.5, and Qwen3.6). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--tool-call-parser` | `qwen3_coder` | Server-side parser that emits structured `tool_calls` on the response. **qwen3\_coder:** Qwen3-Coder tool format. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3.5-4b-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="Qwen/Qwen3.5-4B",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "Qwen/Qwen3.5-4B",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
To access the model's chain of thought, enable thinking mode. The server parses the reasoning output into a separate `reasoning_content` field on the response:
```python theme={"system"}
response = client.chat.completions.create(
model="Qwen/Qwen3.5-4B",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
extra_body={"chat_template_kwargs": {"enable_thinking": True}},
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="Qwen/Qwen3.5-4B",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
[Qwen/Qwen3.5-9B](https://huggingface.co/Qwen/Qwen3.5-9B) is a 9B-parameter dense model with up to 256K context.
This preset serves Qwen3.5-9B with BF16 weights on a single H100. It's the smallest dense Qwen3.5 deployment that keeps reasoning and tool calling enabled.
H100 × 1vLLM (0.22.0-cu129 build)32K128
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3.5-9b-latency && cd qwen3.5-9b-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:qwen3.5-9b preset:latency"
model_metadata:
description: >-
Qwen 3.5 9B instruct (dense), OpenAI-compatible chat via vLLM with Qwen tooling parsers.
repo_id: Qwen/Qwen3.5-9B
example_model_input:
model: "Qwen/Qwen3.5-9B"
messages:
- role: user
content: "What is the capital of France?"
stream: true
max_tokens: 100
temperature: 0.7
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
weights:
- source: "hf://Qwen/Qwen3.5-9B@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model --tensor-parallel-size $GPU_COUNT
--served-model-name Qwen/Qwen3.5-9B
--host 0.0.0.0
--port 8000
--gpu-memory-utilization 0.95
--max-model-len 32768
--dtype bfloat16
--reasoning-parser qwen3
--enable-auto-tool-choice
--tool-call-parser qwen3_coder
--trust-remote-code
--enable-prefix-caching
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
runtime:
predict_concurrency: 128
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
resources:
accelerator: H100:1
use_gpu: true
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--gpu-memory-utilization` | `0.95` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--max-model-len` | `32768` | Maximum context length (tokens) the server accepts per request. |
| `--dtype` | `bfloat16` | Weight precision loaded at runtime. **bfloat16:** BF16 weights, no quantization. |
| `--reasoning-parser` | `qwen3` | Server-side parser that separates reasoning output into `reasoning_content`. **qwen3:** Qwen3-family thinking format (used by Qwen3, Qwen3.5, and Qwen3.6). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--tool-call-parser` | `qwen3_coder` | Server-side parser that emits structured `tool_calls` on the response. **qwen3\_coder:** Qwen3-Coder tool format. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3.5-9b-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="Qwen/Qwen3.5-9B",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "Qwen/Qwen3.5-9B",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
To access the model's chain of thought, enable thinking mode. The server parses the reasoning output into a separate `reasoning_content` field on the response:
```python theme={"system"}
response = client.chat.completions.create(
model="Qwen/Qwen3.5-9B",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
extra_body={"chat_template_kwargs": {"enable_thinking": True}},
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="Qwen/Qwen3.5-9B",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
[Qwen/Qwen3.5-35B-A3B](https://huggingface.co/Qwen/Qwen3.5-35B-A3B) is a 35B-parameter hybrid MoE model (3B active per token) with up to 256K context.
This variant ships in 2 presets tuned for different goals: **Latency** for lowest time-to-first-token, and **Throughput** for highest tokens per second. Pick the tab that matches your workload.
This preset serves Qwen3.5-35B with FP8 weights on H100:2, optimized for low time-to-first-token on interactive chat and short-horizon agent workflows.
H100 × 2vLLM (0.22.0-cu129 build)32K128
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3.5-35b-latency && cd qwen3.5-35b-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:qwen3.5-35b preset:latency"
model_metadata:
description: >-
Qwen 3.5 35B A3B MoE instruct FP8 weights, TP=2 latency preset with Qwen parsers.
repo_id: Qwen/Qwen3.5-35B-A3B-FP8
example_model_input:
model: "Qwen/Qwen3.5-35B-A3B-FP8"
messages:
- role: user
content: "What is the capital of France?"
stream: true
max_tokens: 100
temperature: 0.7
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
weights:
- source: "hf://Qwen/Qwen3.5-35B-A3B-FP8@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--served-model-name Qwen/Qwen3.5-35B-A3B-FP8
--host 0.0.0.0
--port 8000
--gpu-memory-utilization 0.95
--max-model-len 32768
--kv-cache-dtype fp8
--tensor-parallel-size $GPU_COUNT
--reasoning-parser qwen3
--enable-auto-tool-choice
--tool-call-parser qwen3_coder
--trust-remote-code
--enable-prefix-caching
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
runtime:
predict_concurrency: 128
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
resources:
accelerator: H100:2
use_gpu: true
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--gpu-memory-utilization` | `0.95` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--max-model-len` | `32768` | Maximum context length (tokens) the server accepts per request. |
| `--kv-cache-dtype` | `fp8` | KV cache numeric precision. **fp8:** \~2× KV cache density with negligible quality impact on most models. |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--reasoning-parser` | `qwen3` | Server-side parser that separates reasoning output into `reasoning_content`. **qwen3:** Qwen3-family thinking format (used by Qwen3, Qwen3.5, and Qwen3.6). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--tool-call-parser` | `qwen3_coder` | Server-side parser that emits structured `tool_calls` on the response. **qwen3\_coder:** Qwen3-Coder tool format. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3.5-35b-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="Qwen/Qwen3.5-35B-A3B-FP8",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "Qwen/Qwen3.5-35B-A3B-FP8",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
To access the model's chain of thought, enable thinking mode. The server parses the reasoning output into a separate `reasoning_content` field on the response:
```python theme={"system"}
response = client.chat.completions.create(
model="Qwen/Qwen3.5-35B-A3B-FP8",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
extra_body={"chat_template_kwargs": {"enable_thinking": True}},
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="Qwen/Qwen3.5-35B-A3B-FP8",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
This preset serves Qwen3.5-35B FP8 on a single B200, with prefix caching and chunked prefill enabled. It maximizes aggregate throughput at high concurrency with minor quality impact from FP8.
B200vLLM 0.22.0256K1000
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3.5-35b-throughput && cd qwen3.5-35b-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
########################################################
# Remove ( --language-model-only ) from the start command to turn on multimodal mode
########################################################
model_name: "model:qwen3.5-35b preset:throughput"
model_metadata:
description: >-
Qwen 3.5 35B A3B FP8 MoE throughput on B200, language-only mode (--language-model-only) optional.
repo_id: Qwen/Qwen3.5-35B-A3B-FP8
example_model_input:
model: "Qwen/Qwen3.5-35B-A3B-FP8"
messages:
- role: user
content: "What is the capital of France?"
stream: true
max_tokens: 100
temperature: 0.7
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.0
weights:
- source: "hf://Qwen/Qwen3.5-35B-A3B-FP8@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
VLLM_USE_FLASHINFER_MOE_FP8: "0"
PYTORCH_ALLOC_CONF: "expandable_segments:True"
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--tensor-parallel-size $GPU_COUNT
--served-model-name Qwen/Qwen3.5-35B-A3B-FP8
--host 0.0.0.0
--port 8000
--language-model-only
--gpu-memory-utilization 0.95
--kv-cache-dtype fp8
--reasoning-parser qwen3
--enable-chunked-prefill
--enable-prefix-caching
--max-num-seqs 512
--trust-remote-code
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
runtime:
predict_concurrency: 1000
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
resources:
accelerator: B200
use_gpu: true
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| -------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--language-model-only` | (no value) | Disable the multimodal path; text-only serving. Remove to enable image/video inputs. |
| `--gpu-memory-utilization` | `0.95` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--kv-cache-dtype` | `fp8` | KV cache numeric precision. **fp8:** \~2× KV cache density with negligible quality impact on most models. |
| `--reasoning-parser` | `qwen3` | Server-side parser that separates reasoning output into `reasoning_content`. **qwen3:** Qwen3-family thinking format (used by Qwen3, Qwen3.5, and Qwen3.6). |
| `--enable-chunked-prefill` | (no value) | Process long prompts in chunks so decode requests keep running. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--max-num-seqs` | `512` | Maximum number of concurrent sequences in the batch. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3.5-35b-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="Qwen/Qwen3.5-35B-A3B-FP8",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "Qwen/Qwen3.5-35B-A3B-FP8",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
To access the model's chain of thought, enable thinking mode. The server parses the reasoning output into a separate `reasoning_content` field on the response:
```python theme={"system"}
response = client.chat.completions.create(
model="Qwen/Qwen3.5-35B-A3B-FP8",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
extra_body={"chat_template_kwargs": {"enable_thinking": True}},
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
[Qwen/Qwen3.5-122B-A10B](https://huggingface.co/Qwen/Qwen3.5-122B-A10B) is a 122B-parameter MoE model (10B active per token) with up to 256K context.
This preset serves Qwen3.5-122B with FP8 weights on H100:4. It keeps time-to-first-token low while fitting the full model on a single H100 node.
H100 × 4vLLM (0.22.0-cu129 build)32K128
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3.5-122b-latency && cd qwen3.5-122b-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:qwen3.5-122b preset:latency"
model_metadata:
description: >-
Qwen 3.5 122B A10B MoE instruct FP8 weights, TP=4 latency preset via vLLM with Qwen parsers.
repo_id: Qwen/Qwen3.5-122B-A10B-FP8
example_model_input:
model: "Qwen/Qwen3.5-122B-A10B-FP8"
messages:
- role: user
content: "What is the capital of France?"
stream: true
max_tokens: 100
temperature: 0.7
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
weights:
- source: "hf://Qwen/Qwen3.5-122B-A10B-FP8@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
secrets:
hf_access_token: null
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--served-model-name Qwen/Qwen3.5-122B-A10B-FP8
--host 0.0.0.0
--port 8000
--gpu-memory-utilization 0.95
--max-model-len 32768
--kv-cache-dtype fp8
--tensor-parallel-size $GPU_COUNT
--reasoning-parser qwen3
--enable-auto-tool-choice
--tool-call-parser qwen3_coder
--trust-remote-code
--enable-prefix-caching
--load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
environment_variables:
VLLM_LOGGING_LEVEL: WARNING
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
runtime:
predict_concurrency: 128
health_checks:
restart_check_delay_seconds: 1800
restart_threshold_seconds: 1200
stop_traffic_threshold_seconds: 120
resources:
accelerator: H100:4
use_gpu: true
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--gpu-memory-utilization` | `0.95` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--max-model-len` | `32768` | Maximum context length (tokens) the server accepts per request. |
| `--kv-cache-dtype` | `fp8` | KV cache numeric precision. **fp8:** \~2× KV cache density with negligible quality impact on most models. |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--reasoning-parser` | `qwen3` | Server-side parser that separates reasoning output into `reasoning_content`. **qwen3:** Qwen3-family thinking format (used by Qwen3, Qwen3.5, and Qwen3.6). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--tool-call-parser` | `qwen3_coder` | Server-side parser that emits structured `tool_calls` on the response. **qwen3\_coder:** Qwen3-Coder tool format. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3.5-122b-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="Qwen/Qwen3.5-122B-A10B-FP8",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "Qwen/Qwen3.5-122B-A10B-FP8",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
To access the model's chain of thought, enable thinking mode. The server parses the reasoning output into a separate `reasoning_content` field on the response:
```python theme={"system"}
response = client.chat.completions.create(
model="Qwen/Qwen3.5-122B-A10B-FP8",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
extra_body={"chat_template_kwargs": {"enable_thinking": True}},
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="Qwen/Qwen3.5-122B-A10B-FP8",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Qwen3.6
Source: https://docs.baseten.co/examples/models/llm/qwen3.6
Qwen3.6 recipes: 2 variants (27B, 35B-A3B), Dense and Hybrid MoE architectures.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
Pick the model you want to deploy. Each tab is a self-contained recipe.
[Qwen/Qwen3.6-27B](https://huggingface.co/Qwen/Qwen3.6-27B) is a 27B-parameter dense model with up to 256K context.
This preset serves Qwen3.6-27B on H100:4 with MTP speculative decoding, optimized for low time-to-first-token on interactive chat and agent workflows.
H100 × 4vLLM 0.20.0256K64
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3.6-27b-latency && cd qwen3.6-27b-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:qwen3.6-27b preset:latency"
model_metadata:
example_model_input:
model: "Qwen/Qwen3.6-27B"
messages:
- role: user
content: "What is the capital of France?"
stream: true
max_tokens: 512
temperature: 1.0
top_p: 0.95
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.20.0
weights:
- source: "hf://Qwen/Qwen3.6-27B@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
resources:
accelerator: H100:4
use_gpu: true
runtime:
predict_concurrency: 64
environment_variables:
HF_HUB_ENABLE_HF_TRANSFER: "1"
VLLM_LOGGING_LEVEL: WARNING
secrets:
hf_access_token: null
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--served-model-name Qwen/Qwen3.6-27B
--host 0.0.0.0
--port 8000
--trust-remote-code
--tensor-parallel-size $GPU_COUNT
--max-model-len 262144
--language-model-only
--reasoning-parser qwen3
--enable-auto-tool-choice
--tool-call-parser qwen3_coder
--speculative_config.method mtp
--speculative_config.num_speculative_tokens 2"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--max-model-len` | `262144` | Maximum context length (tokens) the server accepts per request. |
| `--language-model-only` | (no value) | Disable the multimodal path; text-only serving. Remove to enable image/video inputs. |
| `--reasoning-parser` | `qwen3` | Server-side parser that separates reasoning output into `reasoning_content`. **qwen3:** Qwen3-family thinking format (used by Qwen3, Qwen3.5, and Qwen3.6). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--tool-call-parser` | `qwen3_coder` | Server-side parser that emits structured `tool_calls` on the response. **qwen3\_coder:** Qwen3-Coder tool format. |
| `--speculative_config.method` | `mtp` | Speculative decoding method. **mtp:** Multi-token prediction head speculation. |
| `--speculative_config.num_speculative_tokens` | `2` | Number of tokens the draft speculator proposes per step. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3.6-27b-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="Qwen/Qwen3.6-27B",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "Qwen/Qwen3.6-27B",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
To access the model's chain of thought, enable thinking mode. The server parses the reasoning output into a separate `reasoning_content` field on the response:
```python theme={"system"}
response = client.chat.completions.create(
model="Qwen/Qwen3.6-27B",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
extra_body={"chat_template_kwargs": {"enable_thinking": True}},
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="Qwen/Qwen3.6-27B",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
[Qwen/Qwen3.6-35B-A3B](https://huggingface.co/Qwen/Qwen3.6-35B-A3B) is a 35B-parameter hybrid MoE model (3B active per token) with up to 256K context.
This variant ships in 2 presets tuned for different goals: **Latency** for lowest time-to-first-token, and **Throughput** for highest tokens per second. Pick the tab that matches your workload.
This preset serves Qwen3.6-35B-A3B on H100:4 with MTP speculative decoding, optimized for low time-to-first-token on interactive chat and short-horizon agent workflows.
H100 × 4vLLM 0.20.0256K64
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3.6-35b-a3b-latency && cd qwen3.6-35b-a3b-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:qwen3.6-35b-a3b preset:latency"
model_metadata:
example_model_input:
model: "Qwen/Qwen3.6-35B-A3B"
messages:
- role: user
content: "What is the capital of France?"
stream: true
max_tokens: 512
temperature: 1.0
top_p: 0.95
tags:
- openai-compatible
base_image:
image: vllm/vllm-openai:v0.20.0
weights:
- source: "hf://Qwen/Qwen3.6-35B-A3B@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
resources:
accelerator: H100:4
use_gpu: true
runtime:
predict_concurrency: 64
environment_variables:
HF_HUB_ENABLE_HF_TRANSFER: "1"
VLLM_LOGGING_LEVEL: WARNING
secrets:
hf_access_token: null
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--served-model-name Qwen/Qwen3.6-35B-A3B
--host 0.0.0.0
--port 8000
--trust-remote-code
--tensor-parallel-size $GPU_COUNT
--max-model-len 262144
--language-model-only
--reasoning-parser qwen3
--enable-auto-tool-choice
--tool-call-parser qwen3_coder
--speculative_config.method mtp
--speculative_config.num_speculative_tokens 2"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--max-model-len` | `262144` | Maximum context length (tokens) the server accepts per request. |
| `--language-model-only` | (no value) | Disable the multimodal path; text-only serving. Remove to enable image/video inputs. |
| `--reasoning-parser` | `qwen3` | Server-side parser that separates reasoning output into `reasoning_content`. **qwen3:** Qwen3-family thinking format (used by Qwen3, Qwen3.5, and Qwen3.6). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--tool-call-parser` | `qwen3_coder` | Server-side parser that emits structured `tool_calls` on the response. **qwen3\_coder:** Qwen3-Coder tool format. |
| `--speculative_config.method` | `mtp` | Speculative decoding method. **mtp:** Multi-token prediction head speculation. |
| `--speculative_config.num_speculative_tokens` | `2` | Number of tokens the draft speculator proposes per step. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3.6-35b-a3b-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="Qwen/Qwen3.6-35B-A3B",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "Qwen/Qwen3.6-35B-A3B",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
To access the model's chain of thought, enable thinking mode. The server parses the reasoning output into a separate `reasoning_content` field on the response:
```python theme={"system"}
response = client.chat.completions.create(
model="Qwen/Qwen3.6-35B-A3B",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
extra_body={"chat_template_kwargs": {"enable_thinking": True}},
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="Qwen/Qwen3.6-35B-A3B",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
This preset serves the RedHatAI NVFP4 quantization of Qwen3.6-35B-A3B on a single B200, with FlashInfer MoE kernels, chunked prefill, and prefix caching enabled. It maximizes aggregate throughput at high concurrency.
B200vLLM (nightly build)256K1000
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3.6-35b-a3b-throughput && cd qwen3.6-35b-a3b-throughput
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:qwen3.6-35b-a3b preset:throughput"
model_metadata:
example_model_input:
model: "RedHatAI/Qwen3.6-35B-A3B-NVFP4"
messages:
- role: user
content: "What is the capital of France?"
max_tokens: 100
temperature: 0.7
tags:
- openai-compatible
- vllm
- qwen3.6
- nvfp4
- b200
base_image:
image: vllm/vllm-openai:nightly
weights:
- source: "hf://RedHatAI/Qwen3.6-35B-A3B-NVFP4@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
build_commands: []
environment_variables:
PYTORCH_ALLOC_CONF: "expandable_segments:True"
VLLM_FLASHINFER_MOE_BACKEND: throughput
VLLM_USE_FLASHINFER_MOE_FP4: 1
VLLM_USE_FLASHINFER_MOE_FP8: 1
docker_server:
start_command: >-
sh -c "GPU_COUNT=$(nvidia-smi --list-gpus | wc -l) && vllm serve /app/checkpoint/model
--tensor-parallel-size $GPU_COUNT
--served-model-name RedHatAI/Qwen3.6-35B-A3B-NVFP4
--host 0.0.0.0
--port 8000
--gpu-memory-utilization 0.95
--max-model-len 262144
--max-num-batched-tokens 32768
--dtype auto
--enable-chunked-prefill
--enable-prefix-caching
--max-num-seqs 512
--reasoning-parser qwen3
--enable-auto-tool-choice
--tool-call-parser qwen3_coder
--moe_backend flashinfer_cutlass
--speculative-config '{\"method\":\"qwen3_5_mtp\",\"num_speculative_tokens\":3}'
--trust-remote-code"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
runtime:
predict_concurrency: 1000
health_checks:
restart_check_delay_seconds: 1500
restart_threshold_seconds: 1500
stop_traffic_threshold_seconds: 120
resources:
accelerator: B200
use_gpu: true
secrets:
hf_access_token: null
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| --------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `$GPU_COUNT` | Number of GPUs to shard the model across. |
| `--gpu-memory-utilization` | `0.95` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--max-model-len` | `262144` | Maximum context length (tokens) the server accepts per request. |
| `--max-num-batched-tokens` | `32768` | Maximum total tokens processed per scheduler step. |
| `--dtype` | `auto` | Weight precision loaded at runtime. **auto:** Match the model's checkpoint dtype (default). |
| `--enable-chunked-prefill` | (no value) | Process long prompts in chunks so decode requests keep running. |
| `--enable-prefix-caching` | (no value) | Reuse KV cache across requests that share a prefix. |
| `--max-num-seqs` | `512` | Maximum number of concurrent sequences in the batch. |
| `--reasoning-parser` | `qwen3` | Server-side parser that separates reasoning output into `reasoning_content`. **qwen3:** Qwen3-family thinking format (used by Qwen3, Qwen3.5, and Qwen3.6). |
| `--enable-auto-tool-choice` | (no value) | Let the model choose when to call tools without requiring `tool_choice: "required"`. |
| `--tool-call-parser` | `qwen3_coder` | Server-side parser that emits structured `tool_calls` on the response. **qwen3\_coder:** Qwen3-Coder tool format. |
| `--moe_backend` | `flashinfer_cutlass` | MoE expert dispatch kernel. Engine-specific values select between routing implementations tuned for different hardware or model layouts. |
| `--speculative-config` | `{"method":"qwen3_5_mtp","num_speculative_tokens":3}` | Speculative decoding configuration as a JSON object. The dotted form (`--speculative-config.method`, `--speculative-config.num_speculative_tokens`, ...) sets the same fields one at a time. |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3.6-35b-a3b-throughput was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible API.
Now call your deployment to run inference:
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="RedHatAI/Qwen3.6-35B-A3B-NVFP4",
messages=[
{"role": "user", "content": "What is machine learning?"}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "RedHatAI/Qwen3.6-35B-A3B-NVFP4",
"messages": [
{"role": "user", "content": "What is machine learning?"}
]
}'
```
To access the model's chain of thought, enable thinking mode. The server parses the reasoning output into a separate `reasoning_content` field on the response:
```python theme={"system"}
response = client.chat.completions.create(
model="RedHatAI/Qwen3.6-35B-A3B-NVFP4",
messages=[
{"role": "user", "content": "How many r's in strawberry?"}
],
extra_body={"chat_template_kwargs": {"enable_thinking": True}},
)
print(response.choices[0].message.reasoning_content) # chain of thought
print(response.choices[0].message.content) # final answer
```
To let the model call tools, pass a `tools` array. The server returns structured `tool_calls` on the response:
```python theme={"system"}
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"location": {"type": "string"}},
"required": ["location"],
},
},
}]
response = client.chat.completions.create(
model="RedHatAI/Qwen3.6-35B-A3B-NVFP4",
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
],
tools=tools,
)
print(response.choices[0].message.tool_calls)
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Qwen3-ASR
Source: https://docs.baseten.co/examples/models/transcription/qwen3-asr
Alibaba's Qwen3-ASR is a compact 1.7B speech-to-text model with multilingual transcription support.
Alibaba's Qwen3-ASR is a compact 1.7B speech-to-text model with multilingual transcription support.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
This preset serves Qwen3-ASR on a single H100 40GB through vLLM, tuned for fast multilingual transcription.
H100\_40GB × 1vLLM (0.22.0-cu129 build)256
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir qwen3-asr-1.7b-latency && cd qwen3-asr-1.7b-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:qwen3-asr-1.7b preset:latency"
model_metadata:
repo_id: Qwen/Qwen3-ASR-1.7B
example_model_input:
stream: false
messages:
- role: user
content:
- type: audio_url
audio_url:
url: https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-ASR-Repo/asr_en.wav
tags:
- openai-compatible
secrets:
hf_access_token: null
weights:
- source: "hf://Qwen/Qwen3-ASR-1.7B@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
docker_server:
start_command: sh -c "vllm serve /app/checkpoint/model --tensor-parallel-size 1 --served-model-name Qwen/Qwen3-ASR-1.7B --gpu-memory-utilization 0.8 --host 0.0.0.0 --port 8000 --load-format runai_streamer"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
resources:
accelerator: H100_40GB:1
cpu: "1"
memory: 10Gi
use_gpu: true
requirements:
- vllm[audio]
- librosa
- torch
- torchaudio
- pynvml
- ffmpeg-python
system_packages:
- python3.10-venv
- ffmpeg
- openmpi-bin
- libopenmpi-dev
runtime:
predict_concurrency: 256
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| -------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------- |
| `--tensor-parallel-size` | `1` | Number of GPUs to shard the model across. |
| `--gpu-memory-utilization` | `0.8` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--load-format` | `runai_streamer` | Weight loading backend. **runai\_streamer:** Stream weights from object storage without materializing to disk. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model qwen3-asr-1.7b-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
Your deployment serves an OpenAI-compatible chat completions API at `/v1/chat/completions` that accepts audio inputs.
Send audio as an `audio_url` content item on a chat message. The model returns the transcription as the assistant message content.
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="Qwen/Qwen3-ASR-1.7B",
messages=[
{
"role": "user",
"content": [
{
"type": "audio_url",
"audio_url": {
"url": "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-ASR-Repo/asr_en.wav"
},
}
],
}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "Qwen/Qwen3-ASR-1.7B",
"messages": [
{"role": "user", "content": [
{"type": "audio_url", "audio_url": {"url": "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-ASR-Repo/asr_en.wav"}}
]}
]
}'
```
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# VibeVoice
Source: https://docs.baseten.co/examples/models/transcription/vibevoice
Microsoft's VibeVoice-ASR speech-to-text model, returning JSON segments with speaker labels and timestamps through an OpenAI-compatible API.
## Setup
Sign in to Baseten with Truss, then install the OpenAI SDK.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
[microsoft/VibeVoice-ASR](https://huggingface.co/microsoft/VibeVoice-ASR) is a multimodal speech-to-text model.
This preset serves VibeVoice-ASR on a single H100 through vLLM with an OpenAI-compatible chat completions endpoint, tuned for low-latency transcription with speaker labels and timestamps.
H100vLLM 0.14.132K32
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir vibevoice-asr-latency && cd vibevoice-asr-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:vibevoice-asr preset:latency"
python_version: py310
model_metadata:
repo_id: microsoft/VibeVoice-ASR
tags:
- openai-compatible
- audio
- asr
- speech-to-text
example_model_input:
model: vibevoice
messages:
- role: system
content: You are a helpful assistant that transcribes audio input into text output in JSON format.
- role: user
content:
- type: audio_url
audio_url:
url: https://github.com/ggerganov/whisper.cpp/raw/master/samples/jfk.wav
- type: text
text: Transcribe this audio.
max_tokens: 64
temperature: 0.0
base_image:
image: vllm/vllm-openai:v0.14.1
python_executable_path: /usr/bin/python3
environment_variables:
HF_HOME: /cache/org
HF_HUB_CACHE: /cache/org
TRANSFORMERS_CACHE: /cache/org
VIBEVOICE_FFMPEG_MAX_CONCURRENCY: "64"
VLLM_MEDIA_LOADING_THREAD_COUNT: "16"
PYTORCH_ALLOC_CONF: "expandable_segments:True"
requirements:
- transformers==4.57.6
- accelerate>=0.30.0
- safetensors
- huggingface-hub>=0.23.0
- librosa>=0.10.0
- soundfile
- scipy
- pydub
- diffusers
- git+https://github.com/microsoft/VibeVoice.git@main
resources:
accelerator: H100
cpu: "4"
memory: 32Gi
use_gpu: true
runtime:
predict_concurrency: 32
secrets:
hf_access_token: null
system_packages:
- ffmpeg
- git
# Weights are pre-downloaded at build time and mounted at /models/vibevoice-asr,
# so cold starts skip the 9.2 GB HF download entirely.
weights:
- source: "hf://microsoft/VibeVoice-ASR@main"
mount_location: "/models/vibevoice-asr"
auth_secret_name: "hf_access_token"
# Pass-through mode: no model.py, Truss just runs vllm serve and proxies
# /predict requests to /v1/chat/completions on the container's localhost.
docker_server:
server_port: 8000
predict_endpoint: /v1/chat/completions
readiness_endpoint: /v1/models
liveness_endpoint: /v1/models
start_command: |
bash -c '
set -e
echo "[entrypoint] Applying microsoft/VibeVoice plugin patches..."
python3 /app/data/patch.py
echo "[entrypoint] Generating tokenizer files..."
python3 -m vllm_plugin.tools.generate_tokenizer_files --output /models/vibevoice-asr
echo "[entrypoint] Starting vLLM serve..."
exec vllm serve /models/vibevoice-asr \
--served-model-name vibevoice \
--trust-remote-code \
--dtype bfloat16 \
--max-num-seqs 16 \
--max-model-len 32768 \
--gpu-memory-utilization 0.85 \
--num-gpu-blocks-override 4096 \
--no-enable-prefix-caching \
--enable-chunked-prefill \
--chat-template-content-format openai \
--allowed-local-media-path /app \
--media-io-kwargs "{\"audio\": {\"target_sr\": 24000}}" \
--enforce-eager \
--skip-mm-profiling \
--host 0.0.0.0 \
--port 8000
'
```
This config runs the `vllm/vllm-openai:v0.14.1` image with Microsoft's VibeVoice plugin patches applied at startup, serving weights pre-mounted at `/models/vibevoice-asr` so cold starts skip the 9.2 GB Hugging Face download. The server runs in eager mode with a 32k context and up to 16 concurrent sequences, exposing the model as `vibevoice` on the chat completions endpoint.
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| -------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--trust-remote-code` | (no value) | Execute model-specific Python from the checkpoint (required for many Qwen, Phi, and custom architectures). |
| `--dtype` | `bfloat16` | Weight precision loaded at runtime. **bfloat16:** BF16 weights, no quantization. |
| `--max-num-seqs` | `16` | Maximum number of concurrent sequences in the batch. |
| `--max-model-len` | `32768` | Maximum context length (tokens) the server accepts per request. |
| `--gpu-memory-utilization` | `0.85` | Fraction of GPU memory vLLM may use for weights and KV cache. |
| `--num-gpu-blocks-override` | `4096` | Overrides vLLM's profiled KV cache size with a fixed number of GPU blocks. |
| `--no-enable-prefix-caching` | (no value) | Disable prefix caching, so repeated prompts do not reuse cached KV blocks. |
| `--enable-chunked-prefill` | (no value) | Process long prompts in chunks so decode requests keep running. |
| `--chat-template-content-format` | `openai` | Format the chat template uses to render message content. **openai:** OpenAI-style content parts (list of typed segments) rather than a plain string. |
| `--allowed-local-media-path` | `/app` | Filesystem path the server may read local media files from when resolving multimodal inputs. |
| `--media-io-kwargs` | `{"audio": {"target_sr": 24000}}` | Options passed to the multimodal media loaders as a JSON object, for example the target sample rate for audio inputs. |
| `--enforce-eager` | (no value) | Run the model in eager mode instead of capturing CUDA graphs. |
| `--skip-mm-profiling` | (no value) | Skip multimodal memory profiling at startup, reducing startup time. |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model vibevoice-asr-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
Your **model ID** is printed in the `truss push` output (`abcd1234` in the example). Use it wherever you see `{model_id}` in the next section.
## Call the model
Your deployment serves an OpenAI-compatible chat completions API at `/v1/chat/completions` that accepts audio inputs. Replace `{model_id}` with your model ID and make sure `BASETEN_API_KEY` is set.
Send audio as an `audio_url` content item on a chat message. The model returns the transcription as the assistant message content.
```python main.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://model-{model_id}.api.baseten.co/environments/production/sync/v1",
)
response = client.chat.completions.create(
model="vibevoice",
messages=[
{
"role": "user",
"content": [
{
"type": "audio_url",
"audio_url": {
"url": "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-ASR-Repo/asr_en.wav"
},
}
],
}
],
)
print(response.choices[0].message.content)
```
```sh theme={"system"}
curl -s https://model-{model_id}.api.baseten.co/environments/production/sync/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "vibevoice",
"messages": [
{"role": "user", "content": [
{"type": "audio_url", "audio_url": {"url": "https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-ASR-Repo/asr_en.wav"}}
]}
]
}'
```
# Voxtral
Source: https://docs.baseten.co/examples/models/transcription/voxtral
Mistral's Voxtral Mini Realtime is a 4B speech-to-text model tuned for real-time streaming transcription.
Mistral's Voxtral Mini Realtime is a 4B speech-to-text model tuned for real-time streaming transcription.
## Setup
Sign in to Baseten with Truss, then install the `websockets` library.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install websockets**
```sh theme={"system"}
uv pip install websockets
```
This preset serves Voxtral Mini Realtime on H100 40GB, tuned for low-latency streaming transcription.
H100\_40GB × 1vLLM (0.22.0-cu129 build)
## Write the config
Create and move into the project directory:
```sh theme={"system"}
mkdir voxtral-mini-4b-latency && cd voxtral-mini-4b-latency
```
Then create a file named `config.yaml` and paste the following:
```yaml config.yaml theme={"system"}
model_name: "model:voxtral-mini-4b preset:latency"
model_metadata:
repo_id: mistralai/Voxtral-Mini-4B-Realtime-2602
secrets:
hf_access_token: null
weights:
- source: "hf://mistralai/Voxtral-Mini-4B-Realtime-2602@main"
mount_location: "/app/checkpoint/model"
auth_secret_name: "hf_access_token"
environment_variables:
VLLM_DISABLE_COMPILE_CACHE: "1"
base_image:
image: vllm/vllm-openai:v0.22.0-cu129
docker_server:
start_command: >-
sh -c "VLLM_DISABLE_COMPILE_CACHE=1 vllm serve /app/checkpoint/model
--tensor-parallel-size 1
--served-model-name mistralai/Voxtral-Mini-4B-Realtime-2602
--host 0.0.0.0
--port 8000
--compilation-config '{\"cudagraph_mode\": \"PIECEWISE\"}'"
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/realtime
server_port: 8000
resources:
accelerator: H100_40GB:1
cpu: "1"
memory: 10Gi
use_gpu: true
requirements:
- vllm[audio]
- librosa
- torch
- torchaudio
- pynvml
- ffmpeg-python
- websockets
system_packages:
- python3.10-venv
- ffmpeg
- openmpi-bin
- libopenmpi-dev
runtime:
is_websocket_endpoint: true
transport:
kind: websocket
ping_interval_seconds: null
ping_timeout_seconds: null
```
## Flags
The `start_command` passes these flags to the engine. Each one controls a runtime or serving behavior:
| Flag | Value | What it does |
| ------------------------ | --------------------------------- | ----------------------------------------------------------- |
| `--tensor-parallel-size` | `1` | Number of GPUs to shard the model across. |
| `--compilation-config` | `{"cudagraph_mode": "PIECEWISE"}` | vLLM compilation passes (op fusion, dead-code elimination). |
## Deploy
Push the config to Baseten:
```sh theme={"system"}
uvx truss push
```
You should see output similar to:
```output theme={"system"}
✨ Model voxtral-mini-4b-latency was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
`truss push` prints your **model ID** (`abc1d2ef` in the example). The examples below use it wherever you see `{model_id}`, and read your API key from the `BASETEN_API_KEY` environment variable.
## Call the model
This preset exposes a WebSocket streaming endpoint at `/v1/realtime` for low-latency, incremental transcription. See the [streaming transcription API reference](/reference/inference-api/predict-endpoints/streaming-transcription-api) for the message protocol, Python client example, and supported audio formats.
## Next steps
Endpoint anatomy, authentication, and sync versus async inference
Scale replicas with traffic, including scale to zero
# Deploy LLMs with Ollama
Source: https://docs.baseten.co/examples/ollama
Run LLMs on Ollama as a custom Docker server.
[Ollama](https://ollama.com/) is a popular lightweight LLM inference server, similar to vLLM or SGLang. This guide deploys an Ollama model as a custom Docker server on Baseten.
This configuration serves [TinyLlama](https://ollama.com/library/tinyllama) with Ollama on a CPU instance. The deployment process is the same for larger Ollama models. Adjust the `resources` and the `ollama pull` target in `start_command` to match your model's requirements.
## Set up your environment
This guide uses `uvx` to run [Truss](https://pypi.org/project/truss/) commands without a separate install step. Sign in to Baseten and install `requests` to call the deployed model from Python. Browser login opens a tab to approve this device, so there's no API key to copy and paste.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install requests**
```sh theme={"system"}
uv pip install requests
```
## Configure the model
Create a directory with a `config.yaml` file:
```sh theme={"system"}
mkdir tinyllama-ollama
touch tinyllama-ollama/config.yaml
```
Copy the following configuration into `config.yaml`:
```yaml config.yaml theme={"system"}
model_name: ollama-tinyllama
base_image:
image: python:3.11-slim
build_commands:
- apt-get update && apt-get install -y curl ca-certificates zstd
- curl -fsSL https://ollama.com/install.sh | sh
docker_server:
start_command: sh -c "ollama serve & sleep 5 && ollama pull tinyllama && wait"
readiness_endpoint: /api/tags
liveness_endpoint: /api/tags
predict_endpoint: /api/generate
server_port: 11434
resources:
cpu: "4"
memory: 8Gi
```
The `base_image` is a lightweight Python image. The `build_commands` install the system packages that the Ollama install script requires (`curl`, `ca-certificates`, and `zstd`), then download and install Ollama. The slim base image doesn't include these packages by default.
The `start_command` launches the Ollama server, waits for it to initialize, and then pulls the TinyLlama model. The `readiness_endpoint` and `liveness_endpoint` both point to `/api/tags`, which returns successfully when Ollama is running. The `predict_endpoint` maps Baseten's `/predict` route to Ollama's `/api/generate` endpoint.
This example only needs 4 CPUs and 8 GB of memory. For a complete list of resource options, see the [Resources](/deployment/resources) page.
## Deploy the model
Push the model to Baseten to start the deployment:
```sh theme={"system"}
uvx truss push tinyllama-ollama
```
You should see output like:
```output theme={"system"}
✨ Model ollama-tinyllama was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
Copy the model ID from the output for the next step.
The first deploy can take several minutes while Baseten pulls the base image and Ollama downloads TinyLlama on container start. Subsequent scale-ups reuse the cached image and start much faster.
## Call the model
Ollama's `/api/generate` is mapped to Baseten's `/predict` route, so you can call the deployed model with any HTTP client:
To run inference with Truss, use the `predict` command:
```sh theme={"system"}
truss predict -d '{"model": "tinyllama", "prompt": "Write a short story about a robot dreaming", "stream": false, "options": {"num_predict": 50}}'
```
To run inference with cURL, use the following command:
```sh theme={"system"}
curl -s -X POST "https://model-MODEL_ID.api.baseten.co/production/predict" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{"model": "tinyllama", "prompt": "Write a short story about a robot dreaming", "stream": false, "options": {"num_predict": 50}}' \
| jq -j '.response'
```
To run inference with Python, use the following:
```python call_model.py theme={"system"}
import os
import requests
model_id = "MODEL_ID"
baseten_api_key = os.environ["BASETEN_API_KEY"]
response = requests.post(
f"https://model-{model_id}.api.baseten.co/production/predict",
headers={"Authorization": f"Bearer {baseten_api_key}"},
json={
"model": "tinyllama",
"prompt": "Write a short story about a robot dreaming",
"stream": False,
"options": {"num_predict": 50},
},
)
print(response.json()["response"])
```
Replace `MODEL_ID` with the model ID from your deployment output.
You should see:
```output theme={"system"}
It was a dreary, grey day when the robots started to dream.
They had been programmed to think like humans, but it wasn't until they began to dream that they realized just how far apart they actually were.
```
***
## Next steps
For higher-throughput serving on GPUs with OpenAI-compatible endpoints, see the vLLM and SGLang examples.
Serve open-source LLMs on vLLM with prefix caching and the OpenAI-compatible API.
Serve open-source LLMs on SGLang's high-performance runtime with the OpenAI-compatible API.
# Build with Baseten
Source: https://docs.baseten.co/examples/overview
These examples walk through common ways to deploy and serve models on Baseten. Each section below covers a different packaging approach, so pick whichever fits your model and workflow. If you're new to Baseten, start with [Deploy your first model](/examples/deploy-your-first-model).
## Engines
Config-only deploys on Baseten's optimized inference engines. This is the fastest path for LLMs, embeddings, and other common architectures, with no Python or Dockerfile required. See [engines](/engines) for architecture support, quantization options, and performance guidance.
## Custom Docker servers
Bring your own inference server, such as vLLM, SGLang, or anything that speaks HTTP. Baseten runs the container, and you own the serving stack. See [Docker server](/development/model/custom-server) for configuration.
## Custom Python models
Write the Truss `Model` class for full control over load and predict. Use when no engine or open-source server fits your architecture. See [custom model code](/development/model/model-class) for the API.
## Chains
Compose multi-step AI workflows across models, routing, parallelism, and post-processing. See [Chains](/development/chain/overview) for the SDK.
## Training
Train and fine-tune models with Baseten's scalable training infrastructure. From [fine-tuning large language models](/training/getting-started) to training custom models, our platform provides the tools and compute you need.
Our training infrastructure supports popular frameworks including VERL, Megatron, and Unsloth, as well as models trained directly with Hugging Face Transformers.
# Deploy LLMs with SGLang
Source: https://docs.baseten.co/examples/sglang
Run LLMs on SGLang's high-performance serving framework.
[SGLang](https://docs.sglang.ai/) is a high-performance serving framework for LLMs that supports a wide range of models and optimization techniques. This guide deploys an SGLang model as a custom Docker server on Baseten.
This configuration serves [Qwen 2.5 3B](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct) with SGLang on an L4 GPU. The deployment process is the same for larger models like [GLM-4.7](https://huggingface.co/zai-org/GLM-4.7). Adjust the `resources` and `start_command` to match your model's requirements.
## Set up your environment
This guide uses `uvx` to run [Truss](https://pypi.org/project/truss/) commands without a separate install step. Sign in to Baseten and install the OpenAI SDK. Browser login opens a tab to approve this device, so there's no API key to copy and paste.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
**Hugging Face access for gated models.** Some models require that you accept terms and conditions on Hugging Face before deployment. To prevent issues:
1. Accept the license for any gated models you wish to access, like [Gemma 3](https://huggingface.co/google/gemma-3-27b-it).
2. Create a read-only [user access token](https://huggingface.co/docs/hub/en/security-tokens) from your Hugging Face account.
3. Add the `hf_access_token` secret [to your Baseten workspace](https://app.baseten.co/settings/secrets).
4. Reference it from the weight source's `auth` block (below). The secret alone does not authenticate weight mirroring, so without `auth` a gated repo fails to deploy with a `401`.
## Configure the model
Create a directory with a `config.yaml` file:
```sh theme={"system"}
mkdir qwen-2-5-3b-sglang
touch qwen-2-5-3b-sglang/config.yaml
```
Copy the following configuration into `config.yaml`:
```yaml config.yaml theme={"system"}
model_metadata:
example_model_input:
messages:
- role: system
content: "You are a helpful assistant."
- role: user
content: "What does Tongyi Qianwen mean?"
stream: true
model: Qwen/Qwen2.5-3B-Instruct
max_tokens: 512
temperature: 0.6
tags:
- openai-compatible
model_name: Qwen 2.5 3B SGLang
base_image:
image: lmsysorg/sglang:v0.5.8.post1
docker_server:
start_command: python3 -m sglang.launch_server --model-path /models/qwen --served-model-name Qwen/Qwen2.5-3B-Instruct --host 0.0.0.0 --port 8000
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
weights:
- source: "hf://Qwen/Qwen2.5-3B-Instruct@aa8e72537993ba99e69dfaafa59ed015b17504d1"
mount_location: "/models/qwen"
auth:
auth_method: CUSTOM_SECRET
auth_secret_name: "hf_access_token" # Required for private or gated repos
resources:
accelerator: L4
use_gpu: true
runtime:
predict_concurrency: 32
health_checks:
restart_threshold_seconds: 300
stop_traffic_threshold_seconds: 120
```
The `base_image` specifies the [SGLang Docker image](https://hub.docker.com/r/lmsysorg/sglang/tags). The `weights` block uses the [Baseten Delivery Network](/development/model/bdn) to mirror the model from Hugging Face and mount it at `/models/qwen` before the container starts. SGLang reads weights directly from that path and serves the model with `--served-model-name`, which sets the model identifier for the OpenAI-compatible API. The `readiness_endpoint` and `liveness_endpoint` use `/health`, which returns 200 once the server is running. The `health_checks` settings control how Baseten monitors the server after it passes the [startup probe](/development/model/health-checks).
## Deploy the model
Push the model to Baseten to start the deployment:
```sh theme={"system"}
uvx truss push qwen-2-5-3b-sglang
```
You should see output like:
```output theme={"system"}
✨ Model Qwen 2.5 3B SGLang was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
Copy the model URL from the output for the next step.
The first deploy can take several minutes while Baseten pulls the SGLang base image (around 18 GB). Subsequent scale-ups reuse the cached image and start much faster.
## Call the model
Call the deployed model with the OpenAI client:
```python call_model.py theme={"system"}
import os
from openai import OpenAI
model_url = "https://model-XXXXXXX.api.baseten.co/environments/production/sync/v1"
client = OpenAI(
base_url=model_url,
api_key=os.environ.get("BASETEN_API_KEY"),
)
stream = client.chat.completions.create(
model="Qwen/Qwen2.5-3B-Instruct",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What does Tongyi Qianwen mean?"}
],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="")
```
Replace the `model_url` with the URL from your deployment output.
## Monitor your deployment
Once the model is serving traffic, open the Metrics tab in the model dashboard to watch how it performs. Baseten detects the SGLang engine and surfaces engine-native graphs such as generation throughput, time per output token, cache hit rate, and queue depth alongside the standard metrics. See [vLLM and SGLang metrics](/observability/metrics#vllm-and-sglang-metrics).
# Stream LLM responses
Source: https://docs.baseten.co/examples/streaming
Stream LLM output token by token.
In this example, we go through a Truss that serves the Qwen 7B Chat LLM, and streams the output to the client.
# Why streaming?
LLMs generate tokens in sequence, so you can return useful output to users before the full response is
ready. Truss supports streaming output to do this.
# Set up the imports
In this example, we use the HuggingFace transformers library to build a text generation model.
```python model/model.py theme={"system"}
from threading import Thread
from typing import Dict
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
from transformers.generation import GenerationConfig
```
# Define the load function
In the `load` function of the Truss, we implement logic
involved in downloading the chat version of the Qwen 7B model and loading it into memory.
```python model/model.py theme={"system"}
class Model:
def __init__(self, **kwargs):
self.model = None
self.tokenizer = None
def load(self):
self.tokenizer = AutoTokenizer.from_pretrained(
"Qwen/Qwen-7B-Chat", trust_remote_code=True
)
self.model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen-7B-Chat", device_map="auto", trust_remote_code=True
).eval()
```
# Define the preprocess function
In the `preprocess` function of the Truss, we set up a `generate_args` dictionary with some generation arguments from the inference request to be used in the `predict` function.
```python model/model.py theme={"system"}
def preprocess(self, request: dict) -> dict:
generate_args = {
"max_new_tokens": request.get("max_new_tokens", 512),
"temperature": request.get("temperature", 0.5),
"top_p": request.get("top_p", 0.95),
"top_k": request.get("top_k", 40),
"repetition_penalty": 1.0,
"no_repeat_ngram_size": 0,
"use_cache": True,
"do_sample": True,
"eos_token_id": self.tokenizer.eos_token_id,
"pad_token_id": self.tokenizer.pad_token_id,
}
request["generate_args"] = generate_args
return request
```
# Define the predict function
In the `predict` function of the Truss, we implement the actual
inference logic.
The two main steps are:
* Tokenize the input
* Call the model's `generate` function if we're not streaming the output, otherwise call the `stream` helper function
```python model/model.py theme={"system"}
def predict(self, request: Dict):
stream = request.pop("stream", False)
prompt = request.pop("prompt")
generation_args = request.pop("generate_args")
input_ids = self.tokenizer(prompt, return_tensors="pt").input_ids.cuda()
if stream:
return self.stream(input_ids, generation_args)
with torch.no_grad():
output = self.model.generate(inputs=input_ids, **generation_args)
return self.tokenizer.decode(output[0])
```
## Define the `stream` helper function
In this helper function, we'll instantiate the `TextIteratorStreamer` object, which we'll later use for
returning the LLM output to users.
```python model/model.py theme={"system"}
def stream(self, input_ids: list, generation_args: dict):
streamer = TextIteratorStreamer(self.tokenizer)
```
When creating the generation parameters, ensure to pass the `streamer` object
that we created previously.
```python model/model.py theme={"system"}
generation_config = GenerationConfig(**generation_args)
generation_kwargs = {
"input_ids": input_ids,
"generation_config": generation_config,
"return_dict_in_generate": True,
"output_scores": True,
"max_new_tokens": generation_args["max_new_tokens"],
"streamer": streamer,
}
```
Spawn a thread to run the generation, so that it does not block the main
thread.
```python model/model.py theme={"system"}
with torch.no_grad():
# Begin generation in a separate thread
thread = Thread(target=self.model.generate, kwargs=generation_kwargs)
thread.start()
```
In Truss, the way to achieve streaming output is to return a generator
that yields content. In this example, we yield the output of the `streamer`,
which produces output and yields it until the generation is complete.
We define this `inner` function to create our generator.
```python model/model.py theme={"system"}
# Yield generated text as it becomes available
def inner():
for text in streamer:
yield text
thread.join()
return inner()
```
# Set up the `config.yaml`
Running Qwen 7B requires torch, transformers,
and a few other related libraries.
```yaml config.yaml theme={"system"}
model_name: qwen-7b-chat
model_metadata:
example_model_input:
prompt: What is the meaning of life?
requirements:
- accelerate==0.23.0
- tiktoken==0.5.1
- einops==0.6.1
- scipy==1.11.3
- transformers_stream_generator==0.0.4
- peft==0.5.0
- deepspeed==0.11.1
- torch==2.0.1
- transformers==4.32.0
```
## Configure resources for Qwen
We will use an L4 to run this model.
```yaml config.yaml theme={"system"}
resources:
accelerator: L4
cpu: "4"
memory: 16Gi
use_gpu: true
```
# Deploy Qwen 7B Chat
Deploy the model like you would other Trusses, with:
```bash theme={"system"}
truss push qwen-7b-chat
```
# Add system packages
Source: https://docs.baseten.co/examples/system-packages
Deploy a model with both Python and system dependencies.
In this example, we build a Truss with a model that requires specific system packages.
To add system packages to your model serving environment, open `config.yaml` and
update the `system_packages` key with a list of apt-installable Debian packages:
```yaml config.yaml theme={"system"}
system_packages:
- tesseract-ocr
```
For this example, we use the [LayoutLM Document QA](https://huggingface.co/impira/layoutlm-document-qa) model,
a multimodal model that answers questions about provided invoice documents. This model requires a system
package, tesseract-ocr, which needs to be included in the model serving environment.
# Set up the model.py
For this model, we use the HuggingFace transformers library, and the document-question-answering task.
```python model/model.py theme={"system"}
from transformers import pipeline
class Model:
def __init__(self, **kwargs) -> None:
self._model = None
def load(self):
self._model = pipeline(
"document-question-answering",
model="impira/layoutlm-document-qa",
)
def predict(self, model_input):
return self._model(model_input["url"], model_input["prompt"])
```
# Set up the config.yaml file
The main items that need to be configured in `config.yaml` are the `requirements`
and `system_packages` sections.
Pin exact versions for your Python dependencies so a new release can't
introduce a breaking change between deploys.
```yaml config.yaml theme={"system"}
environment_variables: {}
external_package_dirs: []
model_metadata:
example_model_input:
{
"url": "https://templates.invoicehome.com/invoice-template-us-neat-750px.png",
"prompt": "What is the invoice number?",
}
model_name: LayoutLM Document QA
python_version: py39
requirements:
- Pillow==10.0.0
- pytesseract==0.3.10
- torch==2.0.1
- transformers==4.30.2
resources:
cpu: "4"
memory: 16Gi
use_gpu: false
accelerator: null
secrets: {}
system_packages:
- tesseract-ocr
```
# Deploy the model
From the Truss directory, deploy the model with:
```bash theme={"system"}
$ truss push ./my-truss
```
You can then invoke the model with:
```bash theme={"system"}
$ truss predict --published -d '{"url": "https://templates.invoicehome.com/invoice-template-us-neat-750px.png", "prompt": "What is the invoice number?"}'
```
# Deploy LLMs with TensorRT-LLM
Source: https://docs.baseten.co/examples/tensorrt-llm
Optimize LLMs for low latency and high throughput.
To get the best performance, we recommend using our [TensorRT-LLM Engine-Builder](/engines/engine-builder-llm/overview) when deploying LLMs. Models deployed with the Engine-Builder are [OpenAI compatible](/inference/calling-your-model), support [structured output](/inference/structured-outputs) and [function calling](/inference/function-calling), and offer deploy-time post-training quantization to FP8 with Hopper GPUs and NVFP4 with Blackwell GPUs.
The Engine-Builder supports LLMs from the following families, both foundation models and fine-tunes:
* Llama 3.0 and later (including DeepSeek-R1 distills)
* Qwen 2.5 and later (including Math, Coder, and DeepSeek-R1 distills)
* Mistral (all LLMs)
You can find preset Engine-Builder configs for common models in the [Engine-Builder reference](/engines/engine-builder-llm/engine-builder-config).
The Engine-Builder does not support vision-language models like Llama 3.2 11B or Pixtral. For these models, we recommend [vLLM](/examples/vllm).
## Example: Deploy Qwen 2.5 3B on an H100
This configuration builds an inference engine to serve [Qwen 2.5 3B](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct) on an H100 GPU. Running this model is fast and cheap, making it a good example for documentation, but the process of deploying it is very similar to larger models like [GLM-4.7](https://huggingface.co/zai-org/GLM-4.7).
## Setup
This guide uses `uvx` to run [Truss](https://pypi.org/project/truss/) commands without a separate install step. Sign in to Baseten and install the OpenAI SDK. Browser login opens a tab to approve this device, so there's no API key to copy and paste.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
**Hugging Face access for gated models.** Some models require that you accept terms and conditions on Hugging Face before deployment. To prevent issues:
1. Accept the license for any gated models you wish to access, like [Gemma 3](https://huggingface.co/google/gemma-3-27b-it).
2. Create a read-only [user access token](https://huggingface.co/docs/hub/en/security-tokens) from your Hugging Face account.
3. Add the `hf_access_token` secret [to your Baseten workspace](https://app.baseten.co/settings/secrets).
## Configuration
Start with an empty configuration file.
```sh theme={"system"}
mkdir qwen-2-5-3b-engine
touch qwen-2-5-3b-engine/config.yaml
```
This configuration file specifies model information and Engine-Builder arguments. You can find details on each config option in the [Engine-Builder reference](/engines/engine-builder-llm/engine-builder-config).
Below is an example for Qwen 2.5 3B.
```yaml config.yaml theme={"system"}
model_metadata:
tags:
- openai-compatible
example_model_input: # Loads sample request into Baseten playground
messages:
- role: system
content: "You are a helpful assistant."
- role: user
content: "What does Tongyi Qianwen mean?"
stream: true
max_tokens: 512
temperature: 0.6 # Check recommended temperature per model
repo_id: Qwen/Qwen2.5-3B-Instruct
model_name: Qwen 2.5 3B Instruct
python_version: py39
resources: # Engine-Builder GPU cannot be changed post-deployment
accelerator: H100
use_gpu: true
secrets: {}
trt_llm:
build:
base_model: decoder
checkpoint_repository:
repo: Qwen/Qwen2.5-3B-Instruct
source: HF
num_builder_gpus: 1
quantization_type: no_quant # `fp8_kv` often recommended for large models
max_seq_len: 32768 # vary the max sequence length, for example 131072 for Llama models
tensor_parallel_count: 1 # Set equal to number of GPUs
plugin_configuration:
use_paged_context_fmha: true
use_fp8_context_fmha: false # Set to true when using `fp8_kv`
paged_kv_cache: true
runtime:
batch_scheduler_policy: max_utilization
enable_chunked_context: true
request_default_max_tokens: 32768 # 131072 for Llama models
```
## Deployment
Pushing the model to Baseten kicks off a multi-stage build and deployment process.
```sh theme={"system"}
uvx truss push qwen-2-5-3b-engine
```
Upon deployment, check your terminal logs or Baseten account to find the URL for the model server.
## Inference
This model is OpenAI compatible and can be called using the OpenAI client.
```python theme={"system"}
import os
from openai import OpenAI
# https://model-XXXXXXX.api.baseten.co/environments/production/sync/v1
model_url = ""
client = OpenAI(
base_url=model_url,
api_key=os.environ.get("BASETEN_API_KEY"),
)
stream = client.chat.completions.create(
model="baseten",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What does Tongyi Qianwen mean?"}
],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="")
```
Check the [Engine-Builder reference](/engines/engine-builder-llm/engine-builder-config) for details on each config option.
# Generate speech with Kokoro
Source: https://docs.baseten.co/examples/text-to-speech
Deploy Kokoro as a text-to-speech endpoint.
In this example, you'll deploy [Kokoro](https://huggingface.co/hexgrad/Kokoro-82M) as a Truss endpoint. Kokoro is an open-weight TTS model with 82 million parameters that runs on a single T4 GPU. Version 1.0 ships American and British English voices out of the box, with additional languages available by adding the corresponding `misaki` extras. The endpoint returns 24 kHz mono audio as a base64-encoded WAV file.
By the end of this tutorial, you'll be able to generate audio like this:
# Set up imports
Kokoro exposes two classes: `KModel` (the weights and forward pass) and `KPipeline` (G2P and voice management). By default both download from Hugging Face on first use. This Truss uses the [Baseten Delivery Network](/development/model/bdn) to mirror the weights to a local mount instead, so cold starts skip the download and `load` points `KModel` and `KPipeline` at that mount.
```python model/model.py theme={"system"}
import base64
import io
import logging
from pathlib import Path
import numpy as np
import scipy.io.wavfile as wav
import torch
from kokoro import KModel, KPipeline
logger = logging.getLogger(__name__)
SAMPLE_RATE = 24000
DEFAULT_VOICE = "af_heart"
REPO_ID = "hexgrad/Kokoro-82M"
WEIGHTS_DIR = Path("/weights/kokoro")
```
# Define the `Model` class and `load` function
Load `KModel` from the BDN-mounted `config.json` and `kokoro-v1_0.pth`, then read every voicepack from `/weights/kokoro/voices/` into memory. Each `KPipeline` reuses the shared model and inherits the preloaded voicepacks, so no request ever reaches Hugging Face.
The base `kokoro` package only ships English G2P. To use Japanese or Mandarin voices, add `misaki[ja]` or `misaki[zh]` to the `requirements` block in `config.yaml`. Spanish, French, Hindi, Italian, and Portuguese voices use the `espeak-ng` fallback, which is already installed below.
```python model/model.py theme={"system"}
class Model:
def __init__(self, **kwargs):
self._pipelines: dict[str, KPipeline] = {}
self._device = "cuda" if torch.cuda.is_available() else "cpu"
self._km: KModel | None = None
self._voicepacks: dict[str, torch.FloatTensor] = {}
def load(self):
logger.info(f"Loading Kokoro from {WEIGHTS_DIR} on {self._device}.")
self._km = (
KModel(
repo_id=REPO_ID,
config=str(WEIGHTS_DIR / "config.json"),
model=str(WEIGHTS_DIR / "kokoro-v1_0.pth"),
)
.to(self._device)
.eval()
)
for voice_file in (WEIGHTS_DIR / "voices").glob("*.pt"):
self._voicepacks[voice_file.stem] = torch.load(
str(voice_file), weights_only=True
)
self._pipelines["a"] = self._make_pipeline("a")
logger.info(f"Kokoro loaded with {len(self._voicepacks)} voicepacks.")
def _make_pipeline(self, lang_code: str) -> KPipeline:
pipeline = KPipeline(lang_code=lang_code, repo_id=REPO_ID, model=self._km)
pipeline.voices.update(self._voicepacks)
return pipeline
def _pipeline_for(self, lang_code: str) -> KPipeline:
if lang_code not in self._pipelines:
self._pipelines[lang_code] = self._make_pipeline(lang_code)
return self._pipelines[lang_code]
```
# Define the `predict` function
`KPipeline` is a generator that yields one `(graphemes, phonemes, audio)` tuple per chunk. It splits English on phoneme boundaries (510-phoneme chunks) and non-English on sentence boundaries, so you don't need to pre-chunk long input. Concatenate the per-chunk audio tensors and encode the result as a base64 WAV.
The full set of voices is listed in the model's [VOICES.md](https://huggingface.co/hexgrad/Kokoro-82M/blob/main/VOICES.md). Voice names follow the pattern `_`, for example `af_heart` (American female), `bm_lewis` (British male), or `ef_dora` (Spanish female).
```python model/model.py theme={"system"}
def predict(self, model_input):
text = str(model_input.get("text", "Hi, I'm Kokoro."))
voice = str(model_input.get("voice", DEFAULT_VOICE))
speed = float(model_input.get("speed", 1.0))
pipeline = self._pipeline_for(voice[0])
chunks = []
for _, _, audio in pipeline(text, voice=voice, speed=speed):
if audio is None:
continue
if hasattr(audio, "cpu"):
audio = audio.cpu().numpy()
chunks.append(audio)
if not chunks:
raise ValueError("No audio generated; check the input text and voice.")
audio = np.concatenate(chunks)
buffer = io.BytesIO()
wav.write(buffer, SAMPLE_RATE, audio)
return {"base64": base64.b64encode(buffer.getvalue()).decode("utf-8")}
```
# Set up the `config.yaml`
The `kokoro` package pulls `torch` and `transformers` as transitive dependencies, so the requirements list stays short. Use the `weights` block to specify the Hugging Face source and a `mount_location` for the model files. This uses [BDN](/development/model/bdn), which mirrors the weights once and serves them from multi-tier caches on every cold start.
```yaml config.yaml theme={"system"}
environment_variables: {}
model_metadata:
example_model_input:
text: "Kokoro is an open-weight TTS model with 82 million parameters that delivers comparable quality to larger models while being significantly faster and more cost-efficient."
voice: af_heart
speed: 1.0
model_name: kokoro
python_version: py311
requirements:
- kokoro>=0.9.4
- numpy
- scipy
resources:
accelerator: T4
use_gpu: true
runtime:
predict_concurrency: 1
secrets: {}
weights:
- source: "hf://hexgrad/Kokoro-82M@f3ff3571791e39611d31c381e3a41a3af07b4987"
mount_location: "/weights/kokoro"
allow_patterns:
- "config.json"
- "kokoro-v1_0.pth"
- "voices/*.pt"
system_packages:
- espeak-ng
```
## Configure resources for Kokoro
A T4 GPU runs Kokoro's 82M parameters with room to spare.
```yaml config.yaml theme={"system"}
resources:
accelerator: T4
use_gpu: true
```
## System packages
Kokoro uses `espeak-ng` as a fallback grapheme-to-phoneme backend for out-of-dictionary words and non-English languages.
```yaml config.yaml theme={"system"}
system_packages:
- espeak-ng
```
# Deploy the model
Deploy the model like you would any other Truss:
```bash theme={"system"}
truss push kokoro
```
# Generate a WAV file
Call the deployed model and decode the base64 response to a `.wav` file.
```python infer.py theme={"system"}
import httpx
import base64
import os
# Set model_id to your deployed model's ID.
model_id = ""
baseten_api_key = os.environ["BASETEN_API_KEY"]
with httpx.Client() as client:
resp = client.post(
f"https://model-{model_id}.api.baseten.co/production/predict",
headers={"Authorization": f"Bearer {baseten_api_key}"},
json={"text": "Hello world", "voice": "af_heart", "speed": 1.0},
timeout=None,
)
response_data = resp.json()
audio_bytes = base64.b64decode(response_data["base64"])
with open("output.wav", "wb") as f:
f.write(audio_bytes)
print("Audio saved to output.wav")
```
Running `infer.py` decodes the base64 response into `output.wav` in your working directory. Select the file in your file browser, then select play to hear Kokoro speak the text from your request.
The first inference call after a cold start takes a few seconds while Kokoro compiles its CUDA kernels. Subsequent calls return audio in under a second.
# Other TTS options
For higher-throughput or streaming use cases, see:
* [Orpheus 3B WebSocket TTS](https://github.com/basetenlabs/truss-examples/tree/main/orpheus-3b-websockets): real-time streaming over WebSocket with TensorRT-LLM on an H100.
* [Chatterbox TTS](https://github.com/basetenlabs/truss-examples/tree/main/chatterbox-tts): voice cloning from a reference audio clip.
* [Piper TTS](https://github.com/basetenlabs/truss-examples/tree/main/piper-tts): CPU-only TTS for low-latency, low-cost deployments.
# Deploy LLMs with vLLM
Source: https://docs.baseten.co/examples/vllm
Run any open-source LLM on vLLM's serving framework.
[vLLM](https://docs.vllm.ai/) supports a wide range of models and performance optimizations. This guide deploys a vLLM model as a custom Docker server on Baseten.
This configuration serves [Qwen 2.5 3B](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct) with vLLM on an L4 GPU. The deployment process is the same for larger models like [GLM-4.7](https://huggingface.co/zai-org/GLM-4.7). Adjust the `resources` and `start_command` to match your model's requirements.
## Set up your environment
This guide uses `uvx` to run [Truss](https://pypi.org/project/truss/) commands without a separate install step. Sign in to Baseten and install the OpenAI SDK. Browser login opens a tab to approve this device, so there's no API key to copy and paste.
**Sign in to Baseten**
```sh theme={"system"}
uvx truss login --browser
```
**Install the OpenAI SDK**
```sh theme={"system"}
uv pip install openai
```
**Hugging Face access for gated models.** Some models require that you accept terms and conditions on Hugging Face before deployment. To prevent issues:
1. Accept the license for any gated models you wish to access, like [Gemma 3](https://huggingface.co/google/gemma-3-27b-it).
2. Create a read-only [user access token](https://huggingface.co/docs/hub/en/security-tokens) from your Hugging Face account.
3. Add the `hf_access_token` secret [to your Baseten workspace](https://app.baseten.co/settings/secrets).
4. Reference it from the weight source's `auth` block (below). The secret alone does not authenticate weight mirroring, so without `auth` a gated repo fails to deploy with a `401`.
## Configure the model
Create a directory with a `config.yaml` file:
```sh theme={"system"}
mkdir qwen-2-5-3b-vllm
touch qwen-2-5-3b-vllm/config.yaml
```
Copy the following configuration into `config.yaml`:
```yaml config.yaml theme={"system"}
model_metadata:
example_model_input:
messages:
- role: system
content: "You are a helpful assistant."
- role: user
content: "What does Tongyi Qianwen mean?"
stream: true
model: Qwen/Qwen2.5-3B-Instruct
max_tokens: 512
temperature: 0.6
tags:
- openai-compatible
model_name: Qwen 2.5 3B vLLM
base_image:
image: vllm/vllm-openai:v0.12.0
docker_server:
start_command: vllm serve /models/qwen --served-model-name Qwen/Qwen2.5-3B-Instruct --host 0.0.0.0 --port 8000 --enable-prefix-caching
readiness_endpoint: /health
liveness_endpoint: /health
predict_endpoint: /v1/chat/completions
server_port: 8000
weights:
- source: "hf://Qwen/Qwen2.5-3B-Instruct@aa8e72537993ba99e69dfaafa59ed015b17504d1"
mount_location: "/models/qwen"
auth:
auth_method: CUSTOM_SECRET
auth_secret_name: "hf_access_token" # Required for private or gated repos
resources:
accelerator: L4
use_gpu: true
runtime:
predict_concurrency: 256
health_checks:
restart_threshold_seconds: 300
stop_traffic_threshold_seconds: 120
```
The `base_image` specifies the [vLLM Docker image](https://hub.docker.com/r/vllm/vllm-openai/tags). The `weights` block uses the [Baseten Delivery Network](/development/model/bdn) to mirror the model from Hugging Face and mount it at `/models/qwen` before the container starts. vLLM reads weights directly from that path and serves the model with `--served-model-name`, which sets the model identifier for the OpenAI-compatible API. The `health_checks` settings control how Baseten monitors the server after it passes the [startup probe](/development/model/health-checks).
## Deploy the model
Push the model to Baseten to start the deployment:
```sh theme={"system"}
uvx truss push qwen-2-5-3b-vllm
```
You should see output like:
```output theme={"system"}
✨ Model Qwen 2.5 3B vLLM was successfully pushed ✨
Model ID: abc1d2ef
Deployment ID: xyz123
Endpoint: model-abc1d2ef.api.baseten.co
Logs: https://app.baseten.co/models/abc1d2ef/logs/xyz123
```
Copy the model URL from the output for the next step.
The first deploy can take several minutes while Baseten pulls the vLLM base image. Subsequent scale-ups reuse the cached image and start much faster.
## Call the model
Call the deployed model with the OpenAI client:
```python call_model.py theme={"system"}
import os
from openai import OpenAI
model_url = "https://model-XXXXXXX.api.baseten.co/environments/production/sync/v1"
client = OpenAI(
base_url=model_url,
api_key=os.environ.get("BASETEN_API_KEY"),
)
stream = client.chat.completions.create(
model="Qwen/Qwen2.5-3B-Instruct",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What does Tongyi Qianwen mean?"}
],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="")
```
Replace the `model_url` with the URL from your deployment output.
## Monitor your deployment
Once the model is serving traffic, open the Metrics tab in the model dashboard to watch how it performs. Baseten detects the vLLM engine and surfaces engine-native graphs such as token throughput, inter-token latency, KV cache usage, and queue depth alongside the standard metrics. See [vLLM and SGLang metrics](/observability/metrics#vllm-and-sglang-metrics).
## Route through an external LLM gateway
To route traffic from a third-party OpenAI-compatible gateway to this deployment, see [External LLM gateways](/inference/calling-your-model#external-llm-gateways). The `model` value the gateway sends must match the `--served-model-name` in the `start_command` above.
# Manage groups and API keys
Source: https://docs.baseten.co/frontier-gateway/api-keys
Walk the full lifecycle: create groups, build a hierarchy, mint and revoke API keys, and delete groups when a customer churns.
In Frontier Gateway, every API key belongs to a **group**: the resource that owns one billable entity's external identifier, model set, rate and usage limits, and place in your organizational hierarchy. You manage groups and their keys yourself. The Frontier Gateway section of the Baseten console covers creating groups and managing endpoint access, including per-model rate and usage limits (limit fields accept `1k`, `60k`, `1M`, and `1B` shorthand). Minting and revoking keys happens through the [REST API](/reference/gateway/overview), which also supports everything the console does. This page walks the full lifecycle over the API: creating a group, building a hierarchy, minting a key, listing and revoking keys, and deleting a group.
## Concepts
A **group** is one node in your hierarchy. The group owns:
* A **`metadata.external_entity_id`**: a stable identifier you choose, unique within your workspace. Use it to map the group back to your own system. The same value is included as `externalEntityId` on every [billing webhook](/frontier-gateway/billing-webhooks) event for the group's keys.
* A **`metadata.name`**: an optional human-readable display name.
* A **model set** (`models[]`): the [endpoint](/frontier-gateway/endpoints) slugs the group is allowed to call, each with optional rate and usage limits.
* A **`hierarchy`** block: a `limit_enforcement` mode (one of `INDEPENDENT` or `CASCADING`) and an optional `parent_group_id`. Both fields are **immutable** after creation.
A **federated API key** is a credential bound to one group. Keys are minted under the group; rotating credentials for a customer means revoking and reissuing the key without touching the group. Each key has a **prefix** (the substring before the `.` in the full key string) used as the path parameter in every per-key URL. The plaintext secret after the `.` is shown once at creation and is never retrievable; lose it and you must revoke and reissue. A key's model access and limits are derived entirely from its group's effective config; keys don't carry per-key overrides.
To see how limits compose across a hierarchy, see [Rate and usage limits](/frontier-gateway/rate-limits).
## Create a group
Create a group to represent one billable entity, such as a customer, plan, or project, along with the model set and limits its keys inherit. The body specifies the group's metadata, its complete model configuration, and a `hierarchy` block declaring the inheritance mode and an optional parent. The `models` list defines the group's complete model set with **set semantics**. Slugs added to the list are added to the group, and slugs absent from the list on a later update are removed (cascading to existing keys' access). The response is the new group; save the `id`, which is the path parameter for every per-group operation that follows.
```bash Request theme={"system"}
curl --request POST \
--url https://api.baseten.co/v1/gateway/groups \
--header "Authorization: Api-Key $BASETEN_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"metadata": {
"name": "Acme prod",
"external_entity_id": "cust_42"
},
"models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 1000000 },
{ "type": "REQUEST", "unit": "MINUTE", "threshold": 100 }
],
"usage_limits": [
{ "type": "TOKEN", "unit": "DAY", "threshold": 10000000 }
]
}
],
"hierarchy": {
"limit_enforcement": "INDEPENDENT",
"parent_group_id": null
}
}'
```
```json Output theme={"system"}
{
"id": "abc123hash",
"metadata": {
"name": "Acme prod",
"external_entity_id": "cust_42"
},
"models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 1000000 },
{ "type": "REQUEST", "unit": "MINUTE", "threshold": 100 }
],
"usage_limits": [
{ "type": "TOKEN", "unit": "DAY", "threshold": 10000000 }
]
}
],
"effective_models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 1000000, "source_group": "abc123hash" },
{ "type": "REQUEST", "unit": "MINUTE", "threshold": 100, "source_group": "abc123hash" }
],
"usage_limits": [
{ "type": "TOKEN", "unit": "DAY", "threshold": 10000000, "source_group": "abc123hash" }
]
}
],
"hierarchy": {
"limit_enforcement": "INDEPENDENT",
"parent_group_id": null
},
"created_at": "2026-05-13T12:00:00Z"
}
```
The `models` list must be non-empty on create. To clear models from an existing group later, send `"models": []` on `PATCH`; to remove the group entirely, see [Delete a group](#delete-a-group). For the limit-shape reference, see [Rate and usage limits](/frontier-gateway/rate-limits).
For more information, see [`POST /v1/gateway/groups`](/reference/gateway/groups/create-a-group).
## Build a hierarchy
Build a hierarchy to nest groups (for example, a customer with per-team subgroups) so limits flow from parents down to children. To nest a group under an existing one, pass the parent's `id` as `hierarchy.parent_group_id`. The child's `limit_enforcement` mode must match the root of its subtree. Pick the mode when you create the root, then every descendant uses the same mode. The child group's response includes the same `models` it was configured with, plus an `effective_models` block showing the limits the runtime enforces after walking up the tree.
```bash Request theme={"system"}
curl --request POST \
--url https://api.baseten.co/v1/gateway/groups \
--header "Authorization: Api-Key $BASETEN_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"metadata": {
"name": "Acme prod / engineering",
"external_entity_id": "cust_42_engineering"
},
"models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 700000 }
]
}
],
"hierarchy": {
"limit_enforcement": "INDEPENDENT",
"parent_group_id": "abc123hash"
}
}'
```
```json Output theme={"system"}
{
"id": "def456hash",
"metadata": {
"name": "Acme prod / engineering",
"external_entity_id": "cust_42_engineering"
},
"models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 700000 }
],
"usage_limits": []
}
],
"effective_models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 700000, "source_group": "def456hash" }
],
"usage_limits": []
}
],
"hierarchy": {
"limit_enforcement": "INDEPENDENT",
"parent_group_id": "abc123hash"
},
"created_at": "2026-05-13T12:05:00Z"
}
```
Each limit in `effective_models` carries a `source_group` field pointing to the ancestor (or self) that the limit was anchored to. For a worked example, see [Effective limits and inheritance](/frontier-gateway/rate-limits#effective-limits-and-inheritance).
For more information, see [`POST /v1/gateway/groups`](/reference/gateway/groups/create-a-group).
## List groups
List groups to see every group you've provisioned, or to look up a specific customer by external identifier. Results are cursor-paginated. Pass `?external_entity_id=` to look up a single group by its external identifier. The response includes a `pagination` block with `has_more` and a `cursor` you pass back to fetch the next page.
```bash Request theme={"system"}
curl --request GET \
--url https://api.baseten.co/v1/gateway/groups \
--header "Authorization: Api-Key $BASETEN_API_KEY"
```
```json Output theme={"system"}
{
"items": [
{
"id": "abc123hash",
"metadata": { "name": "Acme prod", "external_entity_id": "cust_42" },
"models": [ /* ... */ ],
"effective_models": [ /* ... */ ],
"hierarchy": { "limit_enforcement": "INDEPENDENT", "parent_group_id": null },
"created_at": "2026-05-13T12:00:00Z"
}
],
"pagination": {
"has_more": true,
"cursor": "aVd2Yk54T2d2V0dFWE13R1l4R2k5UVE="
}
}
```
To fetch the next page, pass the previous response's cursor. You've drained the result set when the response has `"has_more": false` and `"cursor": null`.
```bash Request theme={"system"}
curl --request GET \
--url "https://api.baseten.co/v1/gateway/groups?cursor=aVd2Yk54T2d2V0dFWE13R1l4R2k5UVE=" \
--header "Authorization: Api-Key $BASETEN_API_KEY"
```
```json Output theme={"system"}
{
"items": [
{
"id": "def456hash",
"metadata": { "name": "Acme prod / engineering", "external_entity_id": "cust_42_engineering" },
"models": [ /* ... */ ],
"effective_models": [ /* ... */ ],
"hierarchy": { "limit_enforcement": "INDEPENDENT", "parent_group_id": "abc123hash" },
"created_at": "2026-05-13T12:05:00Z"
}
],
"pagination": {
"has_more": false,
"cursor": null
}
}
```
For more information, see [`GET /v1/gateway/groups`](/reference/gateway/groups/list-groups).
## Update a group
Update a group to change its display name or adjust the model set and limits its keys inherit. You can change `metadata.name` and the `models` configuration; the `hierarchy` block (parent and enforcement mode) is immutable after creation. At least one of `metadata.name` or `models` must be provided.
Replacing `models` follows the same set semantics as create: every slug currently on the group but absent from the new list is removed (cascading to existing keys' access), and new slugs are added.
If the group sits in a cascading hierarchy, the new `models` block is validated against both the group's ancestors and its descendants. A `PATCH` that would raise the group above an ancestor's threshold, or lower the group below a descendant's threshold, is rejected with `400 Bad Request: "Child group exceeds parent group limit."`. See [Cascading mode](/frontier-gateway/rate-limits#cascading-mode) for the full ordering rules.
The response is the updated group, with refreshed `effective_models` reflecting the new limits and any downstream inheritance.
```bash Request theme={"system"}
curl --request PATCH \
--url https://api.baseten.co/v1/gateway/groups/abc123hash \
--header "Authorization: Api-Key $BASETEN_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 1500000 }
]
}
]
}'
```
```json Output theme={"system"}
{
"id": "abc123hash",
"metadata": { "name": "Acme prod", "external_entity_id": "cust_42" },
"models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 1500000 }
],
"usage_limits": []
}
],
"effective_models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 1500000, "source_group": "abc123hash" }
],
"usage_limits": []
}
],
"hierarchy": { "limit_enforcement": "INDEPENDENT", "parent_group_id": null },
"created_at": "2026-05-13T12:00:00Z"
}
```
For more information, see [`PATCH /v1/gateway/groups/{group_id}`](/reference/gateway/groups/update-a-group).
## Mint an API key
Mint an API key to give a customer a credential bound to the group, inheriting its model set and limits. The path parameter is the group's internal `id` (not its `external_entity_id`). The body has a single optional `name` field: a display label for the key. Keys inherit the group's effective model set and limits; you can't restrict a key to a subset of the group's slugs or attach per-key limits.
The response contains the plaintext key, returned exactly once.
```bash Request theme={"system"}
curl --request POST \
--url https://api.baseten.co/v1/gateway/groups/abc123hash/api_keys \
--header "Authorization: Api-Key $BASETEN_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"name": "prod-key-1"
}'
```
```json Output theme={"system"}
{
"api_key": "sky_sCqhBwEy4kPd.",
"prefix": "sky_sCqhBwEy4kPd",
"name": "prod-key-1"
}
```
This is the only time the key is returned in plaintext. Save it now: Baseten doesn't store the secret portion and can't show it to you again. If you lose it, revoke the key and mint a new one.
To rotate a customer's credentials without changing their access or limits, mint a new key under the same group, hand the new key to the customer, then revoke the old one once they've cut over.
For more information, see [`POST /v1/gateway/groups/{group_id}/api_keys`](/reference/gateway/api-keys/create-an-api-key).
## Register an existing API key
Register an existing API key when you already mint keys on your own platform and want their traffic to flow through Frontier Gateway without forcing your downstream customers to rotate. The registered key inherits the group's effective model set and limits, exactly like a key produced by [Mint an API key](#mint-an-api-key).
Supply the plaintext key in the `key` field. Baseten validates that the value is between 32 and 128 characters and has at least 3.0 bits of Shannon entropy per character; any cryptographically secure random key clears the entropy check.
Because this endpoint accepts a key you control, Baseten requires a signature on each request. Sign the exact bytes of the body with your workspace's Ed25519 private key, base64-encode the result, and pass it in the `X-Baseten-Signature` header. Register your public key with Baseten first; until a key is on file, every call returns `400 Bad Request`. See [Register an API key](/reference/gateway/api-keys/register-an-api-key#request-signing) for keypair generation and the full signing steps.
The response confirms the registration. Baseten doesn't echo the key back, so save it on your side before calling.
```bash Request theme={"system"}
BODY='{"name":"acme-prod-key-1","key":""}'
SIGNATURE=$(printf '%s' "$BODY" | openssl pkeyutl -sign -inkey priv.pem -rawin | openssl base64 -A)
curl --request POST \
--url https://api.baseten.co/v1/gateway/groups/abc123hash/api_keys/register \
--header "Authorization: Api-Key $BASETEN_API_KEY" \
--header "Content-Type: application/json" \
--header "X-Baseten-Signature: $SIGNATURE" \
--data "$BODY"
```
```json Output theme={"system"}
{
"ok": true
}
```
The first 16 characters of the supplied key become the stored `prefix` and must be unique within your workspace. Use that prefix as the path parameter when you fetch or revoke the key later. For the full constraint list and error semantics, see [Register an API key](/reference/gateway/api-keys/register-an-api-key).
Baseten stores only the hashed key. Once registered, the plaintext value is unrecoverable from our side. Handle it through your own secure channel before calling this endpoint.
For more information, see [`POST /v1/gateway/groups/{group_id}/api_keys/register`](/reference/gateway/api-keys/register-an-api-key).
## List a group's keys
List a group's keys to see which credentials are active for a customer. Results are cursor-paginated with the same shape as the [group list](#list-groups).
```bash Request theme={"system"}
curl --request GET \
--url https://api.baseten.co/v1/gateway/groups/abc123hash/api_keys \
--header "Authorization: Api-Key $BASETEN_API_KEY"
```
```json Output theme={"system"}
{
"items": [
{
"prefix": "sky_sCqhBwEy4kPd",
"name": "prod-key-1"
}
],
"pagination": {
"has_more": false,
"cursor": null
}
}
```
Per-key responses carry only the `prefix` and `name`. To inspect the model access and limits the key resolves to, fetch its [group](#list-groups) and read the `effective_models` block.
To fetch a single key by prefix, use `GET /v1/gateway/groups/{group_id}/api_keys/{api_key_prefix}`:
```bash Request theme={"system"}
curl --request GET \
--url https://api.baseten.co/v1/gateway/groups/abc123hash/api_keys/sky_sCqhBwEy4kPd \
--header "Authorization: Api-Key $BASETEN_API_KEY"
```
```json Output theme={"system"}
{
"prefix": "sky_sCqhBwEy4kPd",
"name": "prod-key-1"
}
```
For more information, see [`GET /v1/gateway/groups/{group_id}/api_keys`](/reference/gateway/api-keys/list-api-keys-for-a-group) and [`GET /v1/gateway/groups/{group_id}/api_keys/{api_key_prefix}`](/reference/gateway/api-keys/get-an-api-key).
## Revoke a key
Revoke a key to cut off a single credential, for example when a customer rotates out or a key leaks. Other keys under the same group are unaffected.
```bash Request theme={"system"}
curl --request DELETE \
--url https://api.baseten.co/v1/gateway/groups/abc123hash/api_keys/sky_sCqhBwEy4kPd \
--header "Authorization: Api-Key $BASETEN_API_KEY"
```
```json Output theme={"system"}
{
"prefix": "sky_sCqhBwEy4kPd"
}
```
Revocation is irreversible. After this call, the key can't authenticate any request and can't be restored. To restore access for the same downstream customer, mint a new key under the same group.
For more information, see [`DELETE /v1/gateway/groups/{group_id}/api_keys/{api_key_prefix}`](/reference/gateway/api-keys/revoke-an-api-key).
## Delete a group
Delete a group when a downstream customer churns. The call removes the group, revokes every API key in the group, and recursively removes every descendant group and their keys. The `external_entity_id` is freed for reuse; you can call `POST /v1/gateway/groups` again with the same value to provision a fresh group.
```bash Request theme={"system"}
curl --request DELETE \
--url https://api.baseten.co/v1/gateway/groups/abc123hash \
--header "Authorization: Api-Key $BASETEN_API_KEY"
```
```json Output theme={"system"}
{
"id": "abc123hash",
"metadata": {
"name": "Acme prod",
"external_entity_id": "cust_42"
},
"deleted_at": "2026-05-13T12:34:56Z"
}
```
To revoke a single key without churning the whole group, use [Revoke a key](#revoke-a-key) instead.
For more information, see [`DELETE /v1/gateway/groups/{group_id}`](/reference/gateway/groups/delete-a-group).
## Next steps
* **[Rate and usage limits](/frontier-gateway/rate-limits)**: Token and request thresholds, inheritance modes, and 429 behavior.
* **[Billing webhooks](/frontier-gateway/billing-webhooks)**: Stream signed per-request usage events into your billing pipeline.
# Billing webhooks
Source: https://docs.baseten.co/frontier-gateway/billing-webhooks
Receive signed per-request usage events from Frontier Gateway and pipe them into your billing provider out-of-band from the inference path.
For each inference request through Baseten Frontier Gateway, Baseten emits a signed webhook to your endpoint with token counts, the calling group's external identifier, the API key that made the request, and request metadata. You can consume these events to meter usage in Stripe, Orb, or your own billing system without sitting in the request path. Webhook delivery is configured per workspace during managed onboarding: your Baseten team provisions the target URL and webhook signing secret before your first event ships.
## Payload
Baseten POSTs a JSON body to your configured webhook URL. Every payload uses the standard Baseten envelope, where `type` is the discriminator and `data` holds the event-specific fields. Frontier Gateway emits the `API_BILLING_USAGE` event type; future event types may share the same envelope.
The `data.events` array can contain one or more events per delivery. Each event corresponds to a single inference request. Payloads may carry fields beyond those documented here; ignore them, as they're internal and can change without notice.
```json theme={"system"}
{
"type": "API_BILLING_USAGE",
"data": {
"events": [
{
"idempotencyKey": "01J9X7Y0Z3K4M5N6P7Q8R9S0T1",
"timestamp": "2025-07-07T23:40:35.905Z",
"requestId": "5e4a8c1a-2b3c-4d5e-9f0a-1b2c3d4e5f6a",
"requestMetadata": {},
"modelSlug": "your-org/your-model",
"externalEntityId": "cust_42",
"apiKeyPrefix": "sky_sCqhBwEy4kPd",
"tokens": {
"inputTokens": 100,
"outputTokens": 200,
"cachedInputTokens": 300
}
}
]
}
}
```
Fields on each event:
Stable identifier for the event. Use this to deduplicate on your side.
ISO 8601 UTC timestamp of the inference request.
Per-request identifier, useful for correlating billing events with platform logs.
Freeform JSON object passed through from the inference request. May be `null` when no metadata is supplied.
The model slug invoked, in `your-org/your-model` form.
The `metadata.external_entity_id` you set on the group that owns the key used for the request, the same value you write when you [create or update the group](/frontier-gateway/api-keys#create-a-group).
Prefix of the federated API key that made the request (the substring before the `.` in the full key string). The group identifies your customer; the prefix identifies which of that customer's [keys](/frontier-gateway/api-keys) drove the usage.
Token counts for the request.
* **inputTokens** (`integer`, required): Prompt tokens.
* **outputTokens** (`integer`, required): Generated tokens.
* **cachedInputTokens** (`integer`, required): Prompt tokens served from cache, when applicable.
## Headers
Baseten sets two headers on every delivery:
* `X-Baseten-Signature`: HMAC signature of the raw request body. For more information, see [Verify the signature](#verify-the-signature).
* `X-Baseten-Request-ID`: UUID generated per outbound delivery. Log this on your receiver as a correlation ID for debugging against Baseten platform logs. Use `idempotencyKey`, not this header, to dedupe events on your side; the same `requestId` is reused across retry attempts of a single delivery.
## Verify the signature
The `X-Baseten-Signature` header has the format `v1=`, where `` is the HMAC-SHA256 of the raw request body computed with your workspace's webhook signing secret. Verify the signature on every request before trusting the payload.
Two requirements:
* Verify against the **raw bytes** of the request body, not a re-serialized version. JSON re-serialization changes whitespace and field order and breaks the HMAC.
* Use a constant-time comparison (`hmac.compare_digest` in Python, `crypto.timingSafeEqual` in Node.js) to avoid timing attacks.
**To verify the signature**:
```python verify.py theme={"system"}
import hmac
import hashlib
import os
def verify_signature(request) -> bool:
signing_secret = os.getenv("BASETEN_WEBHOOK_SIGNING_SECRET")
signature = request.headers.get("X-Baseten-Signature")
body = request.data
mac = hmac.new(signing_secret.encode("utf-8"), body, hashlib.sha256)
expected_signature = f"v1={mac.hexdigest()}"
return hmac.compare_digest(expected_signature, signature)
```
**To verify the signature**:
```javascript verify.js theme={"system"}
import crypto from "node:crypto";
export function verifySignature(rawBody, signatureHeader) {
const signingSecret = process.env.BASETEN_WEBHOOK_SIGNING_SECRET;
const mac = crypto.createHmac("sha256", signingSecret);
mac.update(rawBody);
const expected = `v1=${mac.digest("hex")}`;
const expectedBuf = Buffer.from(expected);
const actualBuf = Buffer.from(signatureHeader ?? "");
if (expectedBuf.length !== actualBuf.length) {
return false;
}
return crypto.timingSafeEqual(expectedBuf, actualBuf);
}
```
Webhook signing secrets are a general Baseten primitive shared across products that emit signed webhooks. Your Frontier Gateway secret is provisioned during onboarding. For rotation behavior, see [Secure webhooks](/inference/async#secure-webhooks).
## Delivery semantics
Baseten retries failed deliveries with exponential backoff so a transient blip on your endpoint doesn't drop billing events. Use these numbers to size your endpoint SLOs and to know when a failure is terminal.
* **Per-attempt timeout**: 10 seconds. If your endpoint doesn't respond within this window, Baseten cancels the attempt and treats it as a failure.
* **Backoff**: Exponential, starting at 1 second between attempts and capping at 5 seconds.
* **Maximum elapsed time**: 15 seconds. After this, Baseten stops retrying and routes the event to a dead-letter queue. The retry window is tight: the realistic budget is one or two attempts.
* **4xx responses are terminal**: Any 4xx status from your endpoint stops retries immediately. Only 5xx responses, network errors, and timeouts trigger a retry.
Events that exhaust retries land in the dead-letter queue and are not redelivered automatically. Contact your Baseten team to recover events from the DLQ.
## Recommended consumption pattern
Treat the webhook handler as an ingestion endpoint, not a billing pipeline. The handler's job is to durably accept the event and return as fast as possible:
1. Verify the signature.
2. Persist the event to your own queue or database, keyed on `idempotencyKey`.
3. Return a 2xx response.
4. Process and forward to your billing provider asynchronously.
This separates two failure modes: receiving the event from Baseten, and reconciling it with your billing provider. If your billing provider is slow or down, you don't drop events or block the gateway's retry timer.
Acknowledge fast. If your handler runs billing logic inline and exceeds the 10-second per-attempt timeout, Baseten retries the delivery and you risk double-billing your customer. The total retry window is only 15 seconds, so a slow handler that survives the first timeout often misses the retry budget entirely and lands in the DLQ. Always return 2xx before doing slow work, and dedupe on `idempotencyKey` to handle the at-least-once delivery guarantee.
## Next steps
* **[Manage groups and API keys](/frontier-gateway/api-keys)**: Create groups, build a hierarchy, mint and revoke keys, and delete groups.
* **[Rate and usage limits](/frontier-gateway/rate-limits)**: Cap per-group, per-model token and request volume.
# Call your model
Source: https://docs.baseten.co/frontier-gateway/calling-your-model
Make your first inference call through Baseten Frontier Gateway with a federated API key issued by your AI lab.
If an AI lab has given you a federated API key for their model, this guide shows you how to call that model through Baseten Frontier Gateway. The gateway is OpenAI-compatible, so any OpenAI SDK or HTTP client works with two changes: the base URL and the auth header.
The gateway accepts the OpenAI Chat Completions API. If your code already targets OpenAI, point the base URL at Baseten and swap the key. No other changes required.
## Base URL
The default base URL is:
```http theme={"system"}
https://inference.baseten.co/v1
```
If your lab uses a branded domain for the gateway (for example, `https://api.your-lab.com/v1`), use that URL instead. Your lab will tell you which URL to use; the request shape is the same.
## Authentication
Pass your federated API key in the `Authorization` header using the `Api-Key` scheme, **not** `Bearer`:
```http theme={"system"}
Authorization: Api-Key YOUR_API_KEY
```
If your client defaults to `Authorization: Bearer ...`, override it. Federated keys sent as Bearer tokens are rejected.
The key was issued to you by your lab through Baseten's federated key management. You don't manage rotation or limits; those are configured on the lab's side. Treat the key like any other API secret: store it in an environment variable or secret manager, never in source control.
## OpenAI SDK example
Make a chat completion request with the federated key your lab gave you. Replace `YOUR_API_KEY` with that key, and `your-org/your-model` with the model slug your lab gave you.
Install the OpenAI SDK:
```bash theme={"system"}
pip install openai
```
Make a chat completion request:
```python chat.py theme={"system"}
from openai import OpenAI
client = OpenAI(
base_url="https://inference.baseten.co/v1",
api_key="YOUR_API_KEY",
)
response = client.chat.completions.create(
model="your-org/your-model",
messages=[{"role": "user", "content": "Hello, world!"}],
)
print(response.choices[0].message.content)
```
Install the OpenAI SDK:
```bash theme={"system"}
npm install openai
```
Make a chat completion request:
```javascript chat.js theme={"system"}
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://inference.baseten.co/v1",
apiKey: "YOUR_API_KEY",
});
const response = await client.chat.completions.create({
model: "your-org/your-model",
messages: [{ role: "user", content: "Hello, world!" }],
});
console.log(response.choices[0].message.content);
```
The response follows the standard OpenAI Chat Completions schema:
```json Output theme={"system"}
{
"id": "chatcmpl-...",
"object": "chat.completion",
"model": "your-org/your-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I help you today?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 9,
"total_tokens": 19
}
}
```
## curl example
For raw HTTP usage:
```bash Request theme={"system"}
curl --request POST \
--url https://inference.baseten.co/v1/chat/completions \
--header "Content-Type: application/json" \
--header "Authorization: Api-Key YOUR_API_KEY" \
--data '{
"model": "your-org/your-model",
"messages": [
{"role": "user", "content": "Hello, world!"}
]
}'
```
```json Output theme={"system"}
{
"id": "chatcmpl-...",
"object": "chat.completion",
"model": "your-org/your-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I help you today?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 9,
"total_tokens": 19
}
}
```
## Model slug format
Model slugs are formatted as `your-org/your-model` (for example, `acme/llama-3-70b`). Pass the slug as the `model` parameter on every request. Your lab will tell you which slug or slugs your key has access to; a single key can be authorized for one or more models.
## Streaming, structured outputs, and tool calling
The gateway supports streaming, JSON-schema structured outputs, and tool calling through standard OpenAI parameters (`stream`, `response_format`, `tools`). The configuration and usage patterns are identical to any OpenAI-compatible endpoint:
* For more information on streaming responses, see [Streaming](/inference/streaming).
* For more information on JSON-schema and structured generation, see [Structured outputs](/inference/structured-outputs).
* For more information on tool calling and function definitions, see [Function calling](/inference/function-calling).
## Rate limits
Your federated key has rate and usage limits set by your lab. When a limit is exceeded, the gateway returns `429 Too Many Requests`. For more information on the limit shape, daily reset behavior, and 429 handling, see [Rate and usage limits](/frontier-gateway/rate-limits).
## Run a lab serving a model?
If you're the lab issuing federated keys (rather than a developer consuming them), the [Frontier Gateway overview](/frontier-gateway/overview) covers group and key management, rate limits, and billing webhooks.
# Manage endpoints
Source: https://docs.baseten.co/frontier-gateway/endpoints
Create and manage the endpoints that route Frontier Gateway traffic to your Baseten deployments.
An **endpoint** is the routing slug your customers call, like `my-org/glm-5.2`, plus the target it points to. When a request reaches the gateway with that slug, the gateway routes it to the target. You manage endpoints yourself through the [REST API](/reference/gateway/overview), so you can stand up a new slug, re-point it at a different deployment, or retire it.
To enable Frontier Gateway for your workspace, [talk to us](https://www.baseten.co/talk-to-us/).
## Concepts
An endpoint has two parts:
* **Slug**: a globally-unique routing identifier of the form `{org_prefix}/{name}`, such as `my-org/glm-5.2`, where the prefix is one your organization owns. You can rename a slug as long as the new value is globally unique and keeps a prefix your organization owns.
* **Target**: where the slug routes. A target is either a Baseten deployment (`provider: BASETEN`, with the `model_id` it should serve and optional `environment_name`) or an external model provider such as Anthropic (`provider: ANTHROPIC`) or OpenAI (`provider: OPENAI`). An endpoint takes a list of targets but holds exactly one today.
### Endpoints and groups
Endpoints and [groups](/frontier-gateway/api-keys) answer two different questions:
* An **endpoint** defines *what a slug routes to*: which deployment serves traffic for `my-org/glm-5.2`.
* A **group** defines *who can call a slug and how much*: the model slugs a federated key may call, plus its rate and usage limits.
To serve a model to a customer, create an endpoint for the slug, then grant a group access to that same slug and mint the customer a key under it. The slug ties the two together.
## Create an endpoint
Create an endpoint to give your customers a stable slug to call that routes to one of your deployments. The request body takes the `slug` and a `targets` list with one Baseten target. Omit `environment_name` to use production, or pass a non-production environment such as `staging`. The response is the new endpoint; save the `id`, which is the path parameter for every per-endpoint operation that follows.
```bash Request theme={"system"}
curl --request POST \
--url https://api.baseten.co/v1/gateway/endpoints \
--header "Authorization: Api-Key $BASETEN_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"slug": "my-org/glm-5.2",
"targets": [
{
"provider": "BASETEN",
"model_id": "3kZ9xqd",
"environment_name": "staging"
}
]
}'
```
```json Output theme={"system"}
{
"id": "abc123hash",
"slug": "my-org/glm-5.2",
"targets": [
{
"provider": "BASETEN",
"model_id": "3kZ9xqd",
"environment_name": "staging"
}
],
"created_at": "2026-06-17T12:00:00Z",
"updated_at": "2026-06-17T12:00:00Z"
}
```
For more information, see [`POST /v1/gateway/endpoints`](/reference/gateway/endpoints/create-an-endpoint).
## List endpoints
List your endpoints to see every slug you've published and where each one routes. Results are cursor-paginated: pass `limit` and `cursor` query parameters to page through results, and follow `pagination.cursor` while `pagination.has_more` is `true`.
```bash Request theme={"system"}
curl --request GET \
--url https://api.baseten.co/v1/gateway/endpoints \
--header "Authorization: Api-Key $BASETEN_API_KEY"
```
```json Output theme={"system"}
{
"items": [
{
"id": "abc123hash",
"slug": "my-org/glm-5.2",
"targets": [
{
"provider": "BASETEN",
"model_id": "3kZ9xqd",
"environment_name": "staging"
}
],
"created_at": "2026-06-17T12:00:00Z",
"updated_at": "2026-06-17T12:00:00Z"
}
],
"pagination": {
"has_more": true,
"cursor": "aVd2Yk54T2d2V0dFWE13R1l4R2k5UVE="
}
}
```
To fetch the next page, pass the previous response's cursor. You've drained the result set when the response has `"has_more": false` and `"cursor": null`.
```bash Request theme={"system"}
curl --request GET \
--url "https://api.baseten.co/v1/gateway/endpoints?cursor=aVd2Yk54T2d2V0dFWE13R1l4R2k5UVE=" \
--header "Authorization: Api-Key $BASETEN_API_KEY"
```
```json Output theme={"system"}
{
"items": [
{
"id": "def456hash",
"slug": "my-org/glm-5.2-canary",
"targets": [
{
"provider": "BASETEN",
"model_id": "7mP2wqe",
"environment_name": "staging"
}
],
"created_at": "2026-06-17T12:05:00Z",
"updated_at": "2026-06-17T12:05:00Z"
}
],
"pagination": {
"has_more": false,
"cursor": null
}
}
```
For more information, see [`GET /v1/gateway/endpoints`](/reference/gateway/endpoints/list-endpoints).
## Get an endpoint
Get an endpoint by its `id` to check where a single slug currently routes, for example to confirm a change took effect.
```bash Request theme={"system"}
curl --request GET \
--url https://api.baseten.co/v1/gateway/endpoints/abc123hash \
--header "Authorization: Api-Key $BASETEN_API_KEY"
```
```json Output theme={"system"}
{
"id": "abc123hash",
"slug": "my-org/glm-5.2",
"targets": [
{
"provider": "BASETEN",
"model_id": "3kZ9xqd",
"environment_name": "staging"
}
],
"created_at": "2026-06-17T12:00:00Z",
"updated_at": "2026-06-17T12:00:00Z"
}
```
For more information, see [`GET /v1/gateway/endpoints/{endpoint_id}`](/reference/gateway/endpoints/get-an-endpoint).
## Re-point an endpoint
Re-point an endpoint to move a live slug to a different deployment, like promoting a new model version, without asking your customers to change the name they call. The slug stays the same; you replace the endpoint's full target list. The gateway syncs endpoints every 60 seconds, so a change can take up to a minute to take effect.
```bash Request theme={"system"}
curl --request PATCH \
--url https://api.baseten.co/v1/gateway/endpoints/abc123hash \
--header "Authorization: Api-Key $BASETEN_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"targets": [
{
"provider": "BASETEN",
"model_id": "7mP2wqe",
"environment_name": "staging"
}
]
}'
```
```json Output theme={"system"}
{
"id": "abc123hash",
"slug": "my-org/glm-5.2",
"targets": [
{
"provider": "BASETEN",
"model_id": "7mP2wqe",
"environment_name": "staging"
}
],
"created_at": "2026-06-17T12:00:00Z",
"updated_at": "2026-06-17T13:30:00Z"
}
```
For more information, see [`PATCH /v1/gateway/endpoints/{endpoint_id}`](/reference/gateway/endpoints/replace-endpoint-targets).
## Delete an endpoint
Delete an endpoint to take a slug out of service, whether you're retiring a model or freeing the slug for reuse. The gateway stops routing the slug.
```bash Request theme={"system"}
curl --request DELETE \
--url https://api.baseten.co/v1/gateway/endpoints/abc123hash \
--header "Authorization: Api-Key $BASETEN_API_KEY"
```
```json Output theme={"system"}
{
"id": "abc123hash",
"slug": "my-org/glm-5.2"
}
```
For more information, see [`DELETE /v1/gateway/endpoints/{endpoint_id}`](/reference/gateway/endpoints/delete-an-endpoint).
## Next steps
* **[Manage groups and API keys](/frontier-gateway/api-keys)**: Grant a group access to your endpoint's slug and mint a key for it.
* **[Rate and usage limits](/frontier-gateway/rate-limits)**: Control per-group, per-model usage on the slug.
* **[Endpoints API reference](/reference/gateway/endpoints/create-an-endpoint)**: Full request and response shapes for every endpoint operation.
# Get started
Source: https://docs.baseten.co/frontier-gateway/get-started
Create an endpoint, create a group, mint an API key, and call your model through the gateway.
By the end of this guide, you'll have created an endpoint that routes a slug to your deployment, created a Frontier Gateway group for one of your downstream customers, minted an API key bound to that group, and called your Dedicated deployment through the gateway with the key. From here, you can build a deeper group hierarchy, configure additional rate and usage limits, set up billing webhooks, and explore the full lifecycle.
## Prerequisites
* A [Dedicated deployment](/deployment/concepts) of your model on Baseten.
* A [Baseten workspace API key](/organization/api-keys) with management scope, exported as `BASETEN_API_KEY`.
* Completed Frontier Gateway onboarding with your Baseten team.
This guide assumes you've finished managed onboarding: your workspace is provisioned for federated keys, and your webhook signing secret is in place. If you haven't started yet, [talk to us](https://www.baseten.co/talk-to-us/). The `/v1/gateway/` endpoints used here return `403` to workspaces that aren't onboarded.
## Create an endpoint
An **endpoint** maps a routing slug to one of your deployments. Your customers call the slug, and the gateway routes the request to the target you set here. The slug has the form `{org_prefix}/{name}`, where `org_prefix` is a prefix your organization owns. The Baseten team registers your prefixes during onboarding; registering or updating a prefix isn't a self-service action.
Create an endpoint with `POST /v1/gateway/endpoints`. The body takes the `slug` and a `targets` list with one Baseten target: the `model_id` of the deployment that should serve the slug. The response includes the endpoint `id` and confirms the slug now routes to your deployment; you'll reference this same slug when you create a group.
```bash Request theme={"system"}
curl --request POST \
--url https://api.baseten.co/v1/gateway/endpoints \
--header "Authorization: Api-Key $BASETEN_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"slug": "your-org/your-model",
"targets": [
{
"provider": "BASETEN",
"model_id": "3kZ9xqd"
}
]
}'
```
```json Output theme={"system"}
{
"id": "abc123hash",
"slug": "your-org/your-model",
"targets": [
{ "provider": "BASETEN", "model_id": "3kZ9xqd" }
],
"created_at": "2026-06-17T12:00:00Z",
"updated_at": "2026-06-17T12:00:00Z"
}
```
For the full lifecycle, including how to re-point or delete an endpoint, see [Endpoints](/frontier-gateway/endpoints).
## Create a group
A **group** is the resource you create per customer, plan, project, or whichever unit of your organizational hierarchy maps to a billing or access boundary. The group owns an external identifier (your stable ID for this entity), the model slugs it's allowed to call, and the rate and usage limits enforced on every call. List the slug from the endpoint you created earlier so the group can call it. API keys are minted under the group next.
Create a group with `POST /v1/gateway/groups`. The request takes a `metadata` block (display name plus the external identifier), a non-empty `models` list pairing each model slug with its rate and usage limits, and a `hierarchy` block declaring the inheritance mode and an optional parent. This example creates a top-level (root) group with independent enforcement. The response is the new group, including the internal `id` you'll use as the path parameter when minting keys.
```bash Request theme={"system"}
curl --request POST \
--url https://api.baseten.co/v1/gateway/groups \
--header "Authorization: Api-Key $BASETEN_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"metadata": {
"name": "Acme prod",
"external_entity_id": "cust_42"
},
"models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 1000000 },
{ "type": "REQUEST", "unit": "MINUTE", "threshold": 100 }
],
"usage_limits": [
{ "type": "TOKEN", "unit": "DAY", "threshold": 10000000 }
]
}
],
"hierarchy": {
"limit_enforcement": "INDEPENDENT",
"parent_group_id": null
}
}'
```
```json Output theme={"system"}
{
"id": "abc123hash",
"metadata": {
"name": "Acme prod",
"external_entity_id": "cust_42"
},
"models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 1000000 },
{ "type": "REQUEST", "unit": "MINUTE", "threshold": 100 }
],
"usage_limits": [
{ "type": "TOKEN", "unit": "DAY", "threshold": 10000000 }
]
}
],
"effective_models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 1000000, "source_group": "abc123hash" },
{ "type": "REQUEST", "unit": "MINUTE", "threshold": 100, "source_group": "abc123hash" }
],
"usage_limits": [
{ "type": "TOKEN", "unit": "DAY", "threshold": 10000000, "source_group": "abc123hash" }
]
}
],
"hierarchy": {
"limit_enforcement": "INDEPENDENT",
"parent_group_id": null
},
"created_at": "2026-05-13T12:00:00Z"
}
```
Save the `id`. You'll need it when you mint a key. The `effective_models` block shows the limits the runtime enforces after inheritance; for a root group it matches `models` exactly. See [Rate and usage limits](/frontier-gateway/rate-limits#effective-limits-and-inheritance) for how this changes once you add a parent.
## Mint an API key for the group
Issue a new API key under the group with `POST /v1/gateway/groups/{group_id}/api_keys`. The key inherits the group's effective model set and limits; you don't configure either on the key itself. The response contains the plaintext key, returned exactly once.
```bash Request theme={"system"}
curl --request POST \
--url https://api.baseten.co/v1/gateway/groups/abc123hash/api_keys \
--header "Authorization: Api-Key $BASETEN_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"name": "prod-key-1"
}'
```
```json Output theme={"system"}
{
"api_key": "sky_sCqhBwEy4kPd.",
"prefix": "sky_sCqhBwEy4kPd",
"name": "prod-key-1"
}
```
This is the only time the key is returned in plaintext. Save it now: Baseten doesn't store the secret portion and can't show it to you again. If you lose it, revoke the key and mint a new one.
The string before the `.` (here, `sky_sCqhBwEy4kPd`) is the **prefix**. You'll use the prefix, not the full key, when fetching or revoking the key later.
## Call your model through the gateway
Use the API key you minted to call your model. Frontier Gateway is OpenAI-compatible, so the OpenAI SDK works with the gateway base URL. Replace `YOUR_API_KEY` in the examples below with the value you saved from the mint-key response.
Install the OpenAI SDK:
```bash theme={"system"}
pip install openai
```
Make a chat completion request:
```python chat.py theme={"system"}
from openai import OpenAI
client = OpenAI(
base_url="https://inference.baseten.co/v1",
api_key="YOUR_API_KEY",
)
response = client.chat.completions.create(
model="your-org/your-model",
messages=[{"role": "user", "content": "Hello, world!"}],
)
print(response.choices[0].message.content)
```
```bash theme={"system"}
curl --request POST \
--url https://inference.baseten.co/v1/chat/completions \
--header "Content-Type: application/json" \
--header "Authorization: Api-Key YOUR_API_KEY" \
--data '{
"model": "your-org/your-model",
"messages": [
{"role": "user", "content": "Hello, world!"}
]
}'
```
The response follows the standard OpenAI Chat Completions schema:
```json Output theme={"system"}
{
"id": "chatcmpl-...",
"object": "chat.completion",
"model": "your-org/your-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I help you today?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 9,
"total_tokens": 19
}
}
```
The base URL is `https://inference.baseten.co/v1` today. Once white-label routing is provisioned for your workspace, the base URL becomes the branded domain you configure with your Baseten team, and your downstream customers call your domain instead.
## Next steps
* **[Manage groups and API keys](/frontier-gateway/api-keys)**: Build a multi-level hierarchy, mint and revoke keys, and delete groups.
* **[Rate and usage limits](/frontier-gateway/rate-limits)**: Tune per-group, per-model thresholds and pick an inheritance mode.
* **[Billing webhooks](/frontier-gateway/billing-webhooks)**: Stream signed per-request usage events into your billing pipeline.
# Baseten Frontier Gateway
Source: https://docs.baseten.co/frontier-gateway/overview
A managed API gateway for AI labs to serve hosted models under a branded URL with hierarchical groups, inherited rate and usage limits, and billing webhooks.
You have a model deployed on Baseten and want to give your own customers access through your branded domain, with credentials you control and usage you meter. Baseten Frontier Gateway is the managed API gateway that makes this possible. It adds a hierarchical group resource model, per-group rate and usage limits with inheritance, billing webhooks, and white-label routing on top of your Dedicated deployment, so your customers call your model through your domain with keys you mint and revoke through the Baseten REST API.
To enable Frontier Gateway for your workspace, [talk to us](https://www.baseten.co/talk-to-us/).
## How Frontier Gateway works
Frontier Gateway sits on top of an existing Dedicated deployment. You publish **endpoints** to map your routing slugs to deployments, and you model your customers, plans, and projects as a tree of **groups**.
An **endpoint** is a routing slug (for example `my-org/glm-5.2`) and the target it points to. You create, re-point, and delete endpoints yourself through the REST API. For more information, see [Endpoints](/frontier-gateway/endpoints).
Each **group** owns an external identifier (`metadata.external_entity_id`), the set of model slugs it's allowed to call, and the rate and usage limits enforced on every call. Groups can nest under a parent group, and limits flow down the tree according to the group's `limit_enforcement` mode. You then mint one or more API keys under any group; those keys are what your customer uses. Every key inherits the effective config of its group, so rotating credentials never changes what the customer can spend.
When a request hits the gateway with one of your federated keys, Baseten validates the key, walks up the owning group's hierarchy to compute effective limits, and enforces them per model slug. Valid requests route to the deployment the slug's endpoint points to, and the response returns to the caller. For each request, Baseten emits a signed billing event out-of-band to your webhook endpoint with token counts and request metadata, so your billing pipeline runs independently of the inference path.
## Key features
* **Self-service endpoints**: Map a routing slug to a Baseten deployment, re-point it, or retire it through the REST API. For more information, see [Endpoints](/frontier-gateway/endpoints).
* **Hierarchical groups**: Model your organization however your billing structure fits, whether that's orgs and projects, plans and customers, or tenants and seats. Groups carry the model set and the limits; keys hang off groups and inherit them. For more information, see [Manage groups and API keys](/frontier-gateway/api-keys).
* **Two inheritance modes**: Pick an enforcement mode per hierarchy. An independent hierarchy lets children override their parents and meters each group's usage separately; a cascading hierarchy makes a group's usage count against every ancestor at once. For more information, see [Inheritance modes](/frontier-gateway/rate-limits#inheritance-modes).
* **Per-group, per-model rate and usage limits**: Configure `TOKEN` or `REQUEST` limits on each group, scoped per model slug. Every key minted under the group inherits the group's effective limits.
* **Billing webhooks**: Receive signed per-request token usage events you can pipe into Stripe, Orb, or your own billing system. For more information, see [Billing webhooks](/frontier-gateway/billing-webhooks).
* **White-label routing** (coming soon): Serve inference traffic from your branded domain so downstream customers never see the Baseten URL. Contact your onboarding engineer for current availability.
## Frontier Gateway versus Model APIs
Frontier Gateway and Model APIs are distinct products with separate APIs. Frontier Gateway management lives under `/v1/gateway/` and is gated to Frontier Gateway customers; public Model APIs customers authenticate with their workspace API key and call inference at `/v1/chat/completions` directly. Use the table below to confirm which product you need.
| | Frontier Gateway | Model APIs |
| -------------- | -------------------------------------------------------------- | -------------------------------------------------- |
| Who it's for | AI labs serving their own hosted model to downstream customers | App developers calling a Baseten-hosted open model |
| Authentication | Federated API keys you mint per group | Your workspace API key |
| Compute | Your Dedicated deployment | Shared Baseten infrastructure |
| Documentation | [Frontier Gateway](/frontier-gateway/overview) | [Model APIs](/inference/model-apis/overview) |
## Next steps
* **[Get started](/frontier-gateway/get-started)**: Walk through your first endpoint, group, API key, and inference call.
* **[Endpoints](/frontier-gateway/endpoints)**: Map routing slugs to deployments and manage them through the REST API.
* **[Manage groups and API keys](/frontier-gateway/api-keys)**: Create groups, build a hierarchy, and mint or revoke keys.
* **[Rate and usage limits](/frontier-gateway/rate-limits)**: Control per-group, per-model usage and pick an inheritance mode.
* **[Billing webhooks](/frontier-gateway/billing-webhooks)**: Meter usage by consuming signed per-request events.
# Rate and usage limits
Source: https://docs.baseten.co/frontier-gateway/rate-limits
Per-group, per-model token and request limits, two inheritance modes, and how Frontier Gateway computes the effective limits the runtime enforces.
In Frontier Gateway, rate and usage limits live on the **group**, not on individual API keys. Every key minted under a group inherits the group's effective limits, so rotating a customer's credentials doesn't change what they can spend. Rate limits cap short-window throughput (per second or per minute), and usage limits cap total consumption per daily window. Both are scoped to a single (group, model slug) pair, so a group can carry separate limits for every model its keys are allowed to call.
You configure both kinds of limit by passing them inside `models[].rate_limits` and `models[].usage_limits` when you call [`POST /v1/gateway/groups`](/reference/gateway/groups/create-a-group) or [`PATCH /v1/gateway/groups/{group_id}`](/reference/gateway/groups/update-a-group). Workspace API keys and the shared Model APIs product use a different limit model; for the comparison, see [Frontier Gateway versus Model APIs limits](#frontier-gateway-versus-model-apis-limits).
## Rate limits
A rate limit caps short-window throughput. You attach one or more rate limits to each model slug on a group.
| Field | Values | Description |
| ----------- | ------------------ | --------------------------------------------------------------------- |
| `type` | `TOKEN`, `REQUEST` | Whether the limit counts tokens (prompt plus completion) or requests. |
| `unit` | `SECOND`, `MINUTE` | The window the threshold applies to. |
| `threshold` | Integer `>= 1` | The maximum count allowed per window. |
You can set both a `TOKEN` and a `REQUEST` rate limit on the same model slug, but you can't set two rate limits with the same `type`.
```json theme={"system"}
{
"metadata": { "external_entity_id": "cust_42" },
"models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 1000000 },
{ "type": "REQUEST", "unit": "MINUTE", "threshold": 100 }
]
}
],
"hierarchy": { "limit_enforcement": "INDEPENDENT", "parent_group_id": null }
}
```
In this example, the group can spend up to one million prompt-plus-completion tokens per minute on `your-org/your-model`, and up to 100 requests per minute against the same model. Both ceilings are enforced; whichever the caller hits first triggers a `429 Too Many Requests` response.
## Usage limits
A usage limit caps how much a group can spend in a daily window. Usage limits are optional. You can attach a usage limit to any model slug the group is allowed to call.
| Field | Values | Description |
| ----------- | ------------------ | ------------------------------------------------------------------------ |
| `type` | `TOKEN`, `REQUEST` | Whether the limit counts tokens or requests. |
| `unit` | `DAY` | The window the threshold applies to. Daily is the only supported window. |
| `threshold` | Integer `>= 1` | The maximum count allowed per daily window. |
Both `TOKEN` and `REQUEST` are supported as the `type` for a usage limit:
```json theme={"system"}
{
"models": [
{
"slug": "your-org/your-model",
"usage_limits": [
{ "type": "TOKEN", "unit": "DAY", "threshold": 10000000 },
{ "type": "REQUEST", "unit": "DAY", "threshold": 5000 }
]
}
]
}
```
In this example, the group can spend up to ten million tokens per day and up to 5,000 requests per day on `your-org/your-model`. Whichever ceiling the caller hits first triggers a `429 Too Many Requests` response for the rest of the daily window.
## Per-model scope
Limits are scoped per (group, model slug) pair. A group can be authorized for multiple model slugs, and each slug carries its own independent rate-limit and usage-limit buckets. Spending tokens against one model doesn't draw down another model's budget on the same group.
```json theme={"system"}
{
"models": [
{
"slug": "your-org/your-model",
"rate_limits": [
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 1000000 }
],
"usage_limits": [
{ "type": "TOKEN", "unit": "DAY", "threshold": 10000000 }
]
},
{
"slug": "your-org/your-other-model",
"rate_limits": [
{ "type": "REQUEST", "unit": "SECOND", "threshold": 20 }
]
}
]
}
```
In this example, `your-org/your-model` carries a per-minute token rate limit and a daily token usage limit, while `your-org/your-other-model` carries only a per-second request rate limit. The two slugs are independent.
## Inheritance modes
Every group declares an enforcement mode at creation by setting `hierarchy.limit_enforcement` to one of two values: `INDEPENDENT` or `CASCADING`. The mode controls how a child group's usage interacts with its ancestors. The mode is fixed for the whole hierarchy: children must declare the same mode as their parent, and the field is immutable after creation. Hierarchies are capped at five levels deep.
### Independent mode
In an independent hierarchy, a child group inherits any limit its ancestors set when the child omits it, but the child's usage is metered separately from its ancestors. A child can override an inherited threshold upward or downward. A sibling's traffic never draws down another sibling's budget.
Think of an independent hierarchy as a template. The parent group establishes default limits, and children opt out of them by declaring their own. Consumption is bucketed per group, with no cross-group accounting.
Worked example. A root group `free-tier` has:
```json theme={"system"}
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 100000000 }
```
A child group `john` under `free-tier` declares no limits. The runtime enforces 100M TPM on `john`, sourced from `free-tier`. If you later raise `free-tier`'s threshold to 150M TPM, `john` automatically gets 150M TPM too.
A sibling child group `sally` under `free-tier` declares its own ceiling:
```json theme={"system"}
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 120000000 }
```
The runtime enforces 120M TPM on `sally`, sourced from `sally`. `john`'s traffic doesn't draw down `sally`'s budget, and `sally`'s traffic doesn't draw down `john`'s.
### Cascading mode
In a cascading hierarchy, a child group's usage counts against every ancestor at the same time. A request that fits the child's own limit can still be rejected if an ancestor is exhausted.
Think of a cascading hierarchy as a shared pool. An ancestor establishes a hard cap on the subtree's total consumption, and children divide it. Siblings can compete for the same pool: one sibling spending heavily reduces what's available to the others.
Children in a cascading hierarchy can't declare a threshold higher than any ancestor's threshold for the same (slug, type, unit) tuple. Frontier Gateway enforces this at write time, on both create and update. Any of the following requests fails with `400 Bad Request: "Child group exceeds parent group limit."`:
* Creating a child whose declared threshold exceeds an ancestor's threshold for the same (slug, type, unit).
* Raising a descendant's threshold past an ancestor's with `PATCH`.
* Lowering an ancestor's threshold with `PATCH` below the highest existing descendant threshold.
To raise a subtree's ceiling, raise the ancestor first, then the descendants. To lower an ancestor below a descendant, lower the descendant first. Each direction is rejected if you do it out of order.
Worked example. A root group `org` has:
```json theme={"system"}
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 100000000 }
```
Two children `finance` and `engineering` under `org` each declare:
```json theme={"system"}
{ "type": "TOKEN", "unit": "MINUTE", "threshold": 70000000 }
```
Each child's `effective_models` shows 70M TPM sourced from itself, but the runtime also enforces the 100M TPM ceiling sourced from `org` against the **combined** traffic of `finance` and `engineering`. If `finance` consumes 70M in a given minute, `engineering` has only 30M of headroom left in that minute, regardless of its own declared 70M ceiling. The 70M + 70M over-provisioning is allowed at create time because each individual child threshold (70M) stays at or below the parent's (100M); only a single child threshold that exceeded the parent's would be rejected.
The following chart traces that same minute. `finance` consumes its full 70M ceiling, dropping the `org` pool from 100M to 30M, then `engineering` hits `429` after 30M of accepted traffic with 40M of its own 70M ceiling still untouched.
### Effective limits and inheritance
Every group response carries two parallel blocks:
* **`models`**: the configuration you wrote on this specific group, as if you were reading the row alone.
* **`effective_models`**: the limits the runtime enforces on this group after walking the hierarchy. Each limit carries a `source_group` field pointing to the group (this one or an ancestor) the limit is anchored to.
In an independent hierarchy, `effective_models` resolves each (slug, type, unit) tuple by taking the closest ancestor (including self) that declared it.
In a cascading hierarchy, `effective_models` lists every distinct ancestor limit the request is subject to. Read it as the full set of ceilings that gate this group's traffic.
`effective_models` is read-only. To change what a group enforces, update the `models` block on the group itself (or on an ancestor) with `PATCH /v1/gateway/groups/{group_id}`.
## Enforcement and reset
When a request from one of a group's keys exceeds any limit on the request's `effective_models` for the requested model slug, the platform rejects the request with `429 Too Many Requests`. The 429 fires for the first limit hit: if a group has a `TOKEN/MINUTE` rate limit and a `REQUEST/DAY` usage limit, either can trigger rejection. In a cascading hierarchy, the limit hit can be one anchored on an ancestor rather than the calling group's own configuration.
Daily usage windows reset at midnight UTC. After reset, a group's consumption for each `DAY` limit returns to zero and the group can spend up to the threshold again over the next 24 hours.
Rate-limit windows (per second, per minute) are short rolling windows enforced inline on every request and don't have a reset timestamp you need to track.
## Current consumption
To inspect a group's usage against its configured `usage_limits` without waiting for a 429, call `GET /v1/gateway/groups/{group_id}/usage`. The response returns one entry per `(model slug, type, unit)` tuple the group has a usage limit on, with the configured `threshold`, the `current_usage` in the active daily window, and the `reset_at` timestamp for that window.
```bash Request theme={"system"}
curl --request GET \
--url https://api.baseten.co/v1/gateway/groups/abc123hash/usage \
--header "Authorization: Api-Key $BASETEN_API_KEY"
```
```json Output theme={"system"}
{
"customer_id": "cust_42",
"usage": {
"your-org/your-model": [
{
"type": "TOKEN",
"unit": "DAY",
"threshold": 10000000,
"current_usage": 4231899,
"reset_at": "2026-05-21T00:00:00Z"
}
]
}
}
```
Only models that have `usage_limits` configured on the group's effective configuration appear in the response. Rate-limit consumption isn't surfaced through this endpoint; rate limits are short rolling windows and don't carry a stored counter. For the full response shape, see [Get group usage](/reference/gateway/groups/get-group-usage).
## Frontier Gateway versus Model APIs limits
Frontier Gateway and the shared Model APIs product use different limit models:
* **Frontier Gateway** limits are **per group, per model slug**, with an inheritance mode picked at the root. You configure `TOKEN`/`REQUEST` rate limits (`SECOND` or `MINUTE`) and optional `TOKEN`/`REQUEST` usage limits (`DAY`) on the group, and every key minted under the group inherits the group's effective config.
* **Model APIs** limits are **account-tier RPM/TPM** ceilings that apply to your workspace API key as a whole, regardless of which Model APIs model you're calling.
For more information on Model APIs limits, see [Rate limits and budgets](/inference/model-apis/rate-limits-and-budgets).
## Next steps
* **[Manage groups and API keys](/frontier-gateway/api-keys)**: Configure limits when you create or update a group, and rotate keys without changing them.
# Async inference
Source: https://docs.baseten.co/inference/async
Run asynchronous inference on deployed models
Async inference is a *fire and forget* pattern for model requests. Instead of
waiting for a response, you receive a request ID immediately while inference
runs in the background. When complete, results are delivered to your webhook
endpoint.
Async requests work with any dedicated deployment. You don't need code changes.
Requests can queue for up to 72 hours and run for up to 1 hour. Async inference is not
compatible with streaming output, and isn't available on [Model APIs](/inference/model-apis/overview).
Use async inference for:
* **Long-running tasks** that would otherwise hit request timeouts.
* **Batch processing** where you don't need immediate responses.
* **Priority queuing** to serve VIP customers faster.
Baseten does not store model outputs. If webhook delivery fails after all retries,
your data is lost. See [Webhook delivery](#webhook-delivery) for mitigation
strategies.
## Quick start
**To make your first async request**:
1. Set up a webhook endpoint: create an HTTPS endpoint to receive results, deployed to any service that can receive POST requests.
2. Call your model's `/async_predict` endpoint with your webhook URL:
```python theme={"system"}
import requests
import os
model_id = "YOUR_MODEL_ID"
webhook_endpoint = "YOUR_WEBHOOK_ENDPOINT"
baseten_api_key = os.environ["BASETEN_API_KEY"]
# Call the async_predict endpoint of the production deployment
resp = requests.post(
f"https://model-{model_id}.api.baseten.co/production/async_predict",
headers={"Authorization": f"Bearer {baseten_api_key}"},
json={
"model_input": {"prompt": "hello world!"},
"webhook_endpoint": webhook_endpoint,
# "priority": 0,
# "max_time_in_queue_seconds": 600,
},
)
print(resp.json())
```
You'll receive a `request_id` immediately.
When inference completes, Baseten sends a POST request to your webhook with the model output.
See [Webhook payload](#webhook-payload) for the response format.
**Chains** support async inference through `async_run_remote`.
Inference requests to the entrypoint are queued, but internal Chainlet-to-Chainlet calls run synchronously.
## How async works
Async inference decouples request submission from processing, letting you queue work without waiting for results.
### Request lifecycle
When you submit an async request:
1. You call `/async_predict` and immediately receive a `request_id`.
2. Your request enters a queue managed by the Async Request Service.
3. A background worker picks up your request and calls your model's predict endpoint.
4. Your model runs inference and returns a response.
5. Baseten sends the response to your webhook URL using POST.
The `max_time_in_queue_seconds` parameter controls how long a request waits
before expiring. It defaults to 10 minutes but can extend to 72 hours.
### Autoscaling behavior
The async queue is decoupled from model scaling. Requests queue successfully
even when your model has zero replicas.
When your model is scaled to zero:
1. Your request enters the queue while the model has no running replicas.
2. The queue processor attempts to call your model, triggering the autoscaler.
3. Your request waits while the model cold-starts.
4. Once the model is ready, inference runs and completes.
5. Baseten delivers the result to your webhook.
If the model doesn't become ready within `max_time_in_queue_seconds`, the
request expires with status `EXPIRED`. Set this parameter to account for your
model's startup time. For models with long cold starts, consider keeping minimum
replicas running using
[autoscaling settings](/deployment/autoscaling/overview).
### Async priority
Async requests are subject to two levels of priority: how they compete with sync
requests for model capacity, and how they're ordered relative to other async
requests in the queue.
#### Sync vs async concurrency
Sync and async requests share your model's concurrency pool, controlled by
`predict_concurrency` in your model configuration:
```yaml config.yaml theme={"system"}
runtime:
predict_concurrency: 10
```
The `predict_concurrency` setting defines how many requests your model can
process simultaneously per replica. When both sync and async requests are in
flight, sync requests take priority. The queue processor monitors your model's
capacity and backs off when it receives 429 responses, ensuring sync traffic
isn't starved.
For example, if your model has `predict_concurrency=10` and 8 sync requests are
running, only 2 slots remain for async requests. The remaining async requests
stay queued until capacity frees up.
#### Async queue priority
Within the async queue itself, you can control processing order using the
`priority` parameter. This is useful for serving specific requests faster or
ensuring critical batch jobs run before lower-priority work. Set the `priority` field when you submit the request:
```python async_predict.py theme={"system"}
import requests
import os
model_id = "YOUR_MODEL_ID"
webhook_endpoint = "YOUR_WEBHOOK_URL"
baseten_api_key = os.environ["BASETEN_API_KEY"]
resp = requests.post(
f"https://model-{model_id}.api.baseten.co/production/async_predict",
headers={"Authorization": f"Bearer {baseten_api_key}"},
json={
"webhook_endpoint": webhook_endpoint,
"model_input": {"prompt": "hello world!"},
"priority": 0,
},
)
print(resp.json())
```
The `priority` parameter accepts values 0, 1, or 2. Lower values indicate higher
priority: a request with `priority: 0` is processed before requests with
`priority: 1` or `priority: 2`. If you don't specify a priority, requests
default to priority 0.
Because unspecified requests default to priority 0, use the `priority` parameter
mainly to *demote* less urgent work: set background or batch jobs to `priority: 1`
or `priority: 2` so they yield to default-priority traffic. Marking every request
with the same priority has no effect on ordering.
### Watch the queue
Two things decide how quickly an async request clears: its `priority` against the
other requests in the queue, and how it competes with sync traffic for replica
capacity. The simulation below shows both. Requests pile up in the async queue, a
load balancer pulls the front of the queue and sends each request to a free replica,
and the replica runs it and frees up for the next.
Requests arrive at the back of the queue and cut ahead of every lower-priority
request, so a `priority` 0 jumps to the front. The load balancer clears the queue as
fast as it can, always dispatching the front request, so higher-priority work reaches
a replica first. The figure shows each replica handling one request at a time. Switch
to **Sync contention** to add sync traffic: sync and async share replica capacity, and
sync takes precedence. Sync requests go straight to the replicas, so when sync surges
the load balancer cannot place async work and backs off when it hits a `429`. As the
surge recedes, the load balancer drains the backlog across the freed replicas.
## Webhooks
Baseten delivers async results to your webhook endpoint when inference completes.
### Request format
When inference completes, Baseten sends a POST request to your webhook with these headers and body:
```text HTTP request theme={"system"}
POST /your-webhook-path HTTP/2.0
Content-Type: application/json
X-BASETEN-REQUEST-ID: 9876543210abcdef1234567890fedcba
X-BASETEN-SIGNATURE: v1=abc123...
```
The `X-BASETEN-REQUEST-ID` header contains the request ID for correlating webhooks with your original requests.
The `X-BASETEN-SIGNATURE` header is only included if a [webhook secret](#secure-webhooks) is configured.
Webhook endpoints must use HTTPS (except `localhost` for development). Baseten
supports HTTP/2 and HTTP/1.1 connections.
The body is a JSON object like this:
```json Webhook payload theme={"system"}
{
"request_id": "9876543210abcdef1234567890fedcba",
"model_id": "abc123",
"deployment_id": "def456",
"type": "async_request_completed",
"time": "2024-04-30T01:01:08.883423Z",
"data": { "output": "model response here" },
"errors": []
}
```
The body contains the `request_id` matching your original `/async_predict`
response, along with `model_id` and `deployment_id` identifying which deployment
ran the request. The `data` field contains your model output, or `null` if an
error occurred. The `errors` array is empty on success, or contains error
objects on failure. For what each status code means and how to respond, see [Inference errors](/inference/errors).
### Webhook delivery
If all delivery attempts fail, your model output is permanently lost.
Baseten delivers webhooks on a best-effort basis with automatic retries:
| Setting | Value |
| --------------- | --------------------------------- |
| Total attempts | 2 (1 initial + 1 retry). |
| Backoff | About 2 seconds before the retry. |
| Timeout | 10 seconds per attempt. |
| Retryable codes | 500, 502, 503, 504. |
**To prevent data loss**:
1. **Save outputs in your model.** Use the `postprocess()` function to write to
cloud storage:
```python model/model.py theme={"system"}
import json
import boto3
class Model:
# ...
def postprocess(self, model_output):
s3 = boto3.client("s3")
s3.put_object(
Bucket="my-bucket",
Key=f"outputs/{self.context.get('request_id')}.json",
Body=json.dumps(model_output)
)
return model_output
```
The `postprocess` method runs after inference completes. Use
`self.context.get('request_id')` to access the async request ID for correlating
outputs with requests.
2. **Use a reliable endpoint.** Deploy your webhook to a highly available
service like a cloud function or message queue.
### Secure webhooks
Create a webhook secret in the
[Secrets tab](https://app.baseten.co/settings/secrets) to verify requests are
from Baseten.
When configured, Baseten includes an `X-BASETEN-SIGNATURE` header:
```text HTTP header theme={"system"}
X-BASETEN-SIGNATURE: v1=abc123...
```
To validate, compute an HMAC-SHA256 of the request body using your secret and compare:
```python verify_signature.py theme={"system"}
import hashlib
import hmac
def verify_signature(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
actual = signature.replace("v1=", "").split(",")[0]
return hmac.compare_digest(expected, actual)
```
The function computes an HMAC-SHA256 hash of the raw request body using your
webhook secret. It extracts the signature value after `v1=` and uses
`compare_digest` for timing-safe comparison to prevent timing attacks.
Rotate secrets periodically. During rotation, both old and new secrets remain
valid for 24 hours.
## Manage requests
You can check the status of async requests or cancel them while they're queued.
### Check request status
To check the status of an async request, call the status endpoint with your request ID:
```python theme={"system"}
import requests
import os
model_id = "YOUR_MODEL_ID"
request_id = "YOUR_REQUEST_ID"
baseten_api_key = os.environ["BASETEN_API_KEY"]
resp = requests.get(
f"https://model-{model_id}.api.baseten.co/async_request/{request_id}",
headers={"Authorization": f"Bearer {baseten_api_key}"}
)
print(resp.json())
```
Status is available for 1 hour after completion. See the
[status API reference](/reference/inference-api/status-endpoints/get-async-request-status)
for details.
The status and cancel endpoints take a model ID and a `request_id` only; there's no environment segment in the path. A `request_id` is globally unique, so you don't need to know which environment or deployment originally accepted the request to look it up.
| Status | Description |
| ---------------- | ------------------------------------------------ |
| `QUEUED` | Waiting in queue. |
| `IN_PROGRESS` | Currently processing. |
| `SUCCEEDED` | Completed successfully. |
| `FAILED` | Failed after retries. |
| `EXPIRED` | Exceeded `max_time_in_queue_seconds`. |
| `CANCELED` | Canceled by user. |
| `WEBHOOK_FAILED` | Inference succeeded but webhook delivery failed. |
### Cancel a request
Only `QUEUED` requests can be canceled. Once a request enters `IN_PROGRESS`, the cancel endpoint can't stop it: the request runs to completion (or fails). If you need to bound how long a request can wait before running, set [`max_time_in_queue_seconds`](#request-lifecycle) on the request so stale requests transition to `EXPIRED` instead of executing.
To cancel a queued request, call the cancel endpoint with your request ID:
```python cancel_request.py theme={"system"}
import requests
import os
model_id = "YOUR_MODEL_ID"
request_id = "YOUR_REQUEST_ID"
baseten_api_key = os.environ["BASETEN_API_KEY"]
resp = requests.delete(
f"https://model-{model_id}.api.baseten.co/async_request/{request_id}",
headers={"Authorization": f"Bearer {baseten_api_key}"}
)
print(resp.json())
```
For more information, see the [cancel async request API reference](/reference/inference-api/predict-endpoints/cancel-async-request).
## Error codes
When inference fails, the webhook payload returns an `errors` array:
```json Webhook payload theme={"system"}
{
"errors": [{ "code": "MODEL_PREDICT_ERROR", "message": "Details here" }]
}
```
| Code | HTTP | Description | Retried |
| ----------------------- | ------- | -------------------------------- | ------- |
| `MODEL_NOT_READY` | 400 | Model is loading or starting. | Yes |
| `MODEL_DOES_NOT_EXIST` | 404 | Model or deployment not found. | No |
| `MODEL_INVALID_INPUT` | 422 | Invalid input format. | No |
| `MODEL_PREDICT_ERROR` | 500 | Exception in `model.predict()`. | Yes |
| `MODEL_UNAVAILABLE` | 502/503 | Model crashed or scaling. | Yes |
| `MODEL_PREDICT_TIMEOUT` | 504 | Inference exceeded timeout. | Yes |
| `INTERNAL_SERVER_ERROR` | N/A | Something went wrong on Baseten. | Yes |
### Inference retries
When inference fails with a retryable error, Baseten automatically retries the
request using exponential backoff. Configure this behavior with
`inference_retry_config`:
```python async_predict.py theme={"system"}
import requests
import os
model_id = "YOUR_MODEL_ID"
webhook_endpoint = "YOUR_WEBHOOK_URL"
baseten_api_key = os.environ["BASETEN_API_KEY"]
resp = requests.post(
f"https://model-{model_id}.api.baseten.co/production/async_predict",
headers={"Authorization": f"Bearer {baseten_api_key}"},
json={
"model_input": {"prompt": "hello world!"},
"webhook_endpoint": webhook_endpoint,
"inference_retry_config": {
"max_attempts": 3,
"initial_delay_ms": 1000,
"max_delay_ms": 5000
}
},
)
print(resp.json())
```
| Parameter | Range | Default | Description |
| ------------------ | -------- | ------- | ------------------------------------------------ |
| `max_attempts` | 1-10 | 3 | Total inference attempts including the original. |
| `initial_delay_ms` | 0-10,000 | 1000 | Delay before the first retry (ms). |
| `max_delay_ms` | 0-60,000 | 5000 | Maximum delay between retries (ms). |
Retries use exponential backoff with a multiplier of 2. With the default
configuration, delays progress as: 1s → 2s → 4s → 5s (capped at `max_delay_ms`).
Only requests that fail with retryable error codes (500, 502, 503, 504) are
retried. Non-retryable errors like invalid input (422) or model not found (404)
fail immediately.
Inference retries are distinct from [webhook delivery retries](#webhook-delivery).
Inference retries happen when calling your model fails. Webhook retries happen
when delivering results to your endpoint fails.
## Rate limits
There are rate limits for the async predict endpoint and the status polling endpoint.
If you exceed these limits, you'll receive a 429 status code.
| Endpoint | Limit |
| -------------------------------------------- | ----------------------------------- |
| Predict endpoint requests (`/async_predict`) | 12,000 requests/minute (org-level). |
| Status polling | 100 requests/second. |
| Cancel request | 100 requests/second. |
Use webhooks instead of polling to avoid status endpoint limits. Contact
[support@baseten.co](mailto:support@baseten.co) to request increases.
## Observability
Async metrics are available on the
[Metrics tab](/observability/metrics#async-queue-metrics) of your model
dashboard:
* **Inference latency/volume**: includes async requests.
* **Time in async queue**: time spent in `QUEUED` state.
* **Async queue size**: number of queued requests.
## Related
Configure webhook secrets in your Baseten settings to secure webhook delivery.
# Call your model
Source: https://docs.baseten.co/inference/calling-your-model
Run inference on deployed models
This page covers calling self-deployed models with your workspace API key. For hosted open-source models with no deployment step, see [Model APIs](/inference/model-apis/overview).
Once deployed, your model is accessible through an [API endpoint](/reference/inference-api/overview). To make an inference request, you'll need:
* **Model ID**: Found in the Baseten dashboard or returned when you deploy.
* **[API key](/organization/api-keys)**: Authenticates your requests.
* **JSON-serializable model input**: The data your model expects.
The model ID (and the deployment ID, when you need to target a specific
deployment) comes from the model's page URL in your workspace:
We recommend server-side calls to your model. Client-side code may expose your Baseten API key.
Dedicated deployment endpoints don't currently include CORS response headers, so browser-based calls may be blocked.
## Authentication
The predict endpoint lives on your model's own subdomain:
Include your API key in the `Authorization` header:
```bash Request theme={"system"}
curl -X POST https://model-YOUR_MODEL_ID.api.baseten.co/environments/production/predict \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello, world!"}'
```
In Python with requests:
```python predict.py theme={"system"}
import requests
import os
api_key = os.environ["BASETEN_API_KEY"]
model_id = "YOUR_MODEL_ID"
response = requests.post(
f"https://model-{model_id}.api.baseten.co/environments/production/predict",
headers={"Authorization": f"Bearer {api_key}"},
json={"prompt": "Hello, world!"},
)
print(response.json())
```
Baseten also accepts the legacy `Authorization: Api-Key ` scheme on every endpoint, so existing scripts continue to work:
```bash Request theme={"system"}
curl -X POST https://model-YOUR_MODEL_ID.api.baseten.co/environments/production/predict \
-H "Authorization: Api-Key $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello, world!"}'
```
## Predict API endpoints
Baseten provides multiple endpoints for different inference modes:
* [`/predict`](/reference/inference-api/overview#predict-endpoints): Standard synchronous inference.
* [`/async_predict`](/reference/inference-api/overview#predict-endpoints): Asynchronous inference for long-running tasks.
Endpoints are available for environments and all deployments. See the [API reference](/reference/inference-api/overview) for details.
## Sync API endpoints
Custom servers support both `predict` endpoints and a special `sync` endpoint. Use the `sync` endpoint to call different routes in your custom server:
```text URL theme={"system"}
https://model-{model-id}.api.baseten.co/environments/{production}/sync/{route}
```
These examples show how the sync endpoint maps to the custom server's routes:
* `https://model-{model_id}.../sync/health` -> `/health`
* `https://model-{model_id}.../sync/items` -> `/items`
* `https://model-{model_id}.../sync/items/123` -> `/items/123`
## OpenAI SDK
When you deploy a model with Engine-Builder, you'll get an OpenAI-compatible server. If you already use one of the OpenAI SDKs, update the base URL to your Baseten model URL and include your Baseten API key:
```python openai_client.py theme={"system"}
import os
from openai import OpenAI
model_id = "abcdef" # TODO: replace with your model id
api_key = os.environ.get("BASETEN_API_KEY")
model_url = f"https://model-{model_id}.api.baseten.co/environments/production/sync/v1"
client = OpenAI(
base_url=model_url,
api_key=api_key,
)
stream = client.chat.completions.create(
model="Qwen/Qwen2.5-3B-Instruct", # must match --served-model-name in the deployment
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the capital of France?"}
],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content is not None:
print(chunk.choices[0].delta.content, end="")
```
## External LLM gateways
Any LLM gateway that speaks the OpenAI protocol, such as LiteLLM or OpenRouter, can route traffic to a Baseten deployment. Configure the gateway with three values:
* **Base URL**: `https://model-{model_id}.api.baseten.co/environments/production/sync/v1`, using the model ID for your deployment. Choose **API endpoint** on the model page in the Baseten dashboard to copy the full URL.
* **Model name**: The value of `--served-model-name` from your deployment's `start_command`. See the [vLLM example](/examples/vllm) for where this is set. When a single gateway routes to several deployments, use an `org/model` naming convention (for example, `acme/llama-3-70b`) to keep routing unambiguous.
* **API key**: A [Baseten API key](/organization/api-keys) with access to the deployment.
The gateway sends requests to `{base_url}/chat/completions` with `model` set to the served model name and an `Authorization: Bearer ` header.
## Alternative invocation methods
* **Baseten CLI**: [`baseten model predict`](/reference/cli/baseten/model)
* **Model Dashboard**: "Playground" button in the Baseten UI
# Inference errors
Source: https://docs.baseten.co/inference/errors
What each inference error means and where to look next.
When an inference request fails, the error message alone often isn't enough to tell you what to fix. The status code is the first clue. It tells you the broad category, and because your request passes through Baseten's inference gateway before it reaches your model, it also points at where the failure happened: your request, the Baseten platform, or your model's own code.
Every failed response is JSON, in the form `{"error": ""}`. This page maps each status code to its likely source and the fastest way to confirm it. Start with the [quick reference](#quick-reference), then read the section for the status code you received. Streaming and async requests report failures differently. See [Streaming and async errors](#streaming-and-async-errors).
## How to read an inference error
A failed response has two parts worth reading:
* **The status code**: the broad category, returned in the HTTP response (`502`, `503`, and so on).
* **The error body**: `{"error": ""}`. The message is either a short string Baseten generates (such as `Error making prediction`) or, when your model itself returned the error, the raw response your model produced, passed through unchanged.
One distinction resolves most failures: did your model return the error, or was your model unreachable? See [Is it my model or Baseten?](#is-it-my-model-or-baseten).
## Quick reference
| Status | What it usually means | Where to look |
| ----------------------------- | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `400` / `401` / `403` / `404` | Malformed request, invalid API key, or wrong model ID | Your request: [check the endpoint and API key](#400-401-403-404-request-and-authentication-errors) |
| `402` | Billing or payment issue on the account | [Billing and usage](/organization/billing) |
| `413` | Request body too large: 100 MB edge cap (all requests), or 256 KiB async default | Send large inputs by [file or URL](/inference/output-format/files), not inline |
| `429` | Rate limit exceeded: Model APIs request or token limits, or async endpoint limits | Back off and retry; see [429](#429-too-many-requests) |
| `502` | Your container crashed or restarted mid-request, or a Baseten-side error | [Model logs](/observability/logs) for a crash or `OOMKilled`; if clean, retry |
| `503` | Container not available yet: draining or routing | Mostly transient: retry with exponential backoff |
| `504` | The prediction exceeded the request timeout (1200s sync) | Profile the model, raise resources, or use [async](/inference/async) |
## Is it my model or Baseten?
When a request reaches your model and your model server responds with an error, Baseten passes that response through to you. When the request never gets a real answer from your model, Baseten returns its own error instead. Telling these apart is the fastest way to know where to debug.
**Your model returned the error**: the status code and body come from your model server. Your handler raised an exception, returned a non-2xx status, or timed out internally. Debug it like any application bug, starting from the error body and your [model logs](/observability/logs).
**Your model was unreachable**: the request failed in front of your container, so you get a message like `Error making prediction` with a `502` or `503`. The container is down, restarting, was killed (for example, out of memory), or is still cold-starting.
The most common version of the second case is memory pressure. When you increase your payload or batch sizes, each request uses more memory, and the container can be killed mid-request (`OOMKilled`). Confirm it by checking your [model logs](/observability/logs) for `OOMKilled` or repeated restarts, and your [metrics](/observability/metrics) for memory pressure and replica restarts. To fix it, reduce per-request memory with smaller batches or payloads, or move to a larger instance type.
## Errors by status code
Each section below covers what the status code means, its common causes, and what to check first.
### 400, 401, 403, 404: request and authentication errors
These point to the request itself, not your model.
* **`401` / `403`**: the API key is missing or invalid. Use a valid [Baseten API key](/organization/api-keys) in the `Authorization` header.
* **`404`**: the model or deployment ID doesn't exist, or the model was deleted. Confirm the ID and that you're calling the right [predict endpoint](/inference/calling-your-model).
* **`400`**: the request or URL is malformed. Check the request body is valid JSON and the path is correct.
For Truss CLI authentication errors (such as a missing key in `~/.trussrc`), see [Troubleshooting inference](/troubleshooting/inference).
### 402: payment required
The account has an unresolved billing or payment issue, such as exhausted credits with no payment method on file. Check your [billing and usage](/organization/billing) settings, or contact your account owner.
### 413: payload too large
The request body exceeded a size limit. Two limits can return a `413`:
* **Edge limit**: the ingress proxy caps every inbound request body at [100 MB](/reference/inference-api/overview#request-size) and rejects larger requests before they reach your model or chain. It covers the full HTTP body, including the JSON envelope and any base64-encoded media, and isn't configurable.
* **Async limit**: [async requests](/inference/async) have a smaller per-organization cap, 256 KiB (262,144 bytes) by default. The message reports both sizes: `payload size of X bytes exceeds maximum size of Y bytes`.
The async payload limit is set per organization. [Contact support](mailto:support@baseten.co) to raise it for your organization. The 100 MB edge limit is fixed.
For large inputs, send a file or URL the model fetches instead of inlining the bytes in the request body. See [Model I/O with files](/inference/output-format/files). A payload that fits under the edge limit but exhausts the container's memory returns a `502`, not a `413`. See [Is it my model or Baseten?](#is-it-my-model-or-baseten).
### 429: too many requests
The request exceeded a rate limit. Where the limit lives depends on the surface you're calling:
* **Model APIs**: your workspace exceeded its requests-per-minute or tokens-per-minute limit. Cached input tokens count toward the token limit at full weight. See [Rate limits and budgets](/inference/model-apis/rate-limits-and-budgets) for the default limits per tier and how to request an increase.
* **Async endpoints**: `/async_predict` allows 12,000 requests per minute per organization, and the status and cancel endpoints allow 100 requests per second. See [Async inference](/inference/async#rate-limits).
Retry with exponential backoff. A short backoff usually clears a burst; a persistent stream of `429`s means your steady-state traffic exceeds the limit, which backoff can't fix.
### 502: bad gateway
A `502` has two meanings, and they need different responses:
* **Your container was unreachable**: the most common case. The container crashed, restarted, or was killed (for example, `OOMKilled`) mid-request. The body is a short message like `Error making prediction`. Check your [model logs](/observability/logs) for a crash or restart. See [Is it my model or Baseten?](#is-it-my-model-or-baseten).
* **A Baseten-side error**: a transient problem in the gateway or routing layer. If your model logs are clean and show no crash, retry with exponential backoff.
You might also see a `502` reported as `client closed connection`. That means the client disconnected before the response finished, not a Baseten or model failure.
### 503: service unavailable
The container isn't available to take the request yet. This is usually transient. Retry with exponential backoff. Common causes:
* **Draining**: an instance is shutting down during a deploy or scale-down. The message asks you to retry on another instance.
* **Routing**: the request couldn't be routed to a workload plane, or a circuit breaker is open to protect an unhealthy upstream.
A request that arrives while the deployment is scaling up from zero doesn't return a `503`. Baseten holds it at the routing layer until a replica is ready, then forwards it. See [Request lifecycle](/deployment/autoscaling/request-lifecycle).
Async requests return `503` rather than `502` when the async service isn't set up on the workload plane yet. See [Async inference](/inference/async).
### 504: gateway timeout
The prediction ran longer than the [request timeout](/reference/inference-api/overview#timeouts) (1200 seconds for sync predict). Common causes are a model that's too slow for the payload, an under-provisioned instance, or a hung request. Profile the model, raise its resources, or move long-running work to the [async API](/inference/async), which allows up to 3600 seconds.
If you see a timeout sooner than 1200 seconds, check your client's own timeout. Set it to match your model's expected response time. See [Configure HTTP clients](/inference/http-client-configuration#set-timeouts).
## Streaming and async errors
Not every failure arrives as a status code with a JSON body.
**Streaming responses**: a streaming response starts with a `200` and an open connection, so a failure partway through (a timeout, or the model becoming unavailable) can't be sent as a `5xx` with an error body. The stream ends early instead. If a stream stops before you receive the end of the response, treat it as a failed request: check your [model logs](/observability/logs) and retry.
**Async requests**: a failure isn't reported on the submit response. The submit returns once the request is queued, and any error is reported later in the result payload's `errors` array, which is empty on success. See [Async inference](/inference/async).
## Where to look next
When an error isn't self-explanatory, these are the fastest places to confirm a cause:
* **[Model logs](/observability/logs)**: crashes, restarts, `OOMKilled`, and your model's own error output.
* **[Metrics](/observability/metrics)**: memory pressure, replica count and restarts, and queue depth.
* **[Request lifecycle](/deployment/autoscaling/request-lifecycle)**: how queuing, cold starts, and concurrency affect request handling.
* **[Async inference](/inference/async)**: for payloads or runtimes that shouldn't go through the synchronous path.
If you're still stuck, contact support with your model or deployment ID, the timestamp, the status code, and the request ID.
# Function calling
Source: https://docs.baseten.co/inference/function-calling
Tool selection and structured function calls with LLMs
*Function calling* (also called *tool calling*) lets a model choose a tool and produce its arguments from a user request. The model doesn't run the tool itself: your application runs it and can send the result back to the model to produce a final, user-facing response. This fits [chains](/development/chain/overview) and other orchestrators.
Baseten engines including [BIS-LLM](/engines/bis-llm/overview) and [Engine-Builder-LLM](/engines/engine-builder-llm/overview) support function calling, as do [Model APIs](/inference/model-apis/overview) for instant access. Other inference frameworks like [vLLM](/examples/vllm) and [SGLang](/examples/sglang) also support it.
## How tool calling works
A typical tool-calling loop looks like:
1. Send the user message and a list of tools.
2. The model returns normal text or one or more tool calls (a name and JSON arguments).
3. Execute the tool calls in your application.
4. Send the tool output back to the model.
5. Receive a final response or additional tool calls.
## Define tools
Tools can be anything: API calls, database queries, or internal scripts.
Docstrings matter. Models use them to decide which tool to call and how to fill parameters:
```python tools.py theme={"system"}
def multiply(a: float, b: float):
"""Multiply two numbers.
Args:
a: The first number.
b: The second number.
"""
return a * b
def divide(a: float, b: float):
"""Divide two numbers.
Args:
a: The dividend.
b: The divisor (must be non-zero).
"""
return a / b
def add(a: float, b: float):
"""Add two numbers.
Args:
a: The first number.
b: The second number.
"""
return a + b
def subtract(a: float, b: float):
"""Subtract two numbers.
Args:
a: The number to subtract from.
b: The number to subtract.
"""
return a - b
```
### Tool-writing tips
Design small, single-purpose tools and document constraints in docstrings (units, allowed values, required fields). Treat model-provided arguments as untrusted input and validate before execution.
## Serialize functions
Convert functions into JSON-schema tool definitions (OpenAI-compatible format):
```python serialize.py theme={"system"}
from transformers.utils import get_json_schema
calculator_functions = {
"multiply": multiply,
"divide": divide,
"add": add,
"subtract": subtract,
}
tools = [get_json_schema(f) for f in calculator_functions.values()]
```
## Call the model
Include the `tools` array in your request. The payload is the same on both surfaces; Model APIs take a model slug and a shared endpoint, while dedicated deployments use your model's own URL:
**To call the model with tools**:
```python call_model.py theme={"system"}
import requests
BASETEN_API_KEY = ""
url = "https://inference.baseten.co/v1/chat/completions"
payload = {
"model": "deepseek-ai/DeepSeek-V4-Pro",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is 3.14 + 3.14?"},
],
"tools": tools,
"tool_choice": "auto", # default
}
resp = requests.post(
url,
headers={"Authorization": f"Bearer {BASETEN_API_KEY}"},
json=payload,
)
```
**To call the model with tools**:
```python call_model.py theme={"system"}
import requests
MODEL_ID = ""
BASETEN_API_KEY = ""
url = f"https://model-{MODEL_ID}.api.baseten.co/production/predict"
payload = {
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is 3.14 + 3.14?"},
],
"tools": tools,
"tool_choice": "auto", # default
}
resp = requests.post(
url,
headers={"Authorization": f"Bearer {BASETEN_API_KEY}"},
json=payload,
)
```
## Control tool selection
Set `tool_choice` to control how the model uses tools. With `auto` (default), the model can respond with text or tool calls. With `required`, the model must return at least one tool call. With `none`, the model returns plain text only. To force a specific tool:
```python tool_choice.py theme={"system"}
"tool_choice": {"type": "function", "function": {"name": "subtract"}}
```
## Parse and execute tool calls
Depending on the engine and model, tool calls are typically returned in an assistant message under `tool_calls`:
```python parse_tool_calls.py theme={"system"}
import json
data = resp.json()
message = data["choices"][0]["message"]
tool_calls = message.get("tool_calls") or []
for tool_call in tool_calls:
name = tool_call["function"]["name"]
args = json.loads(tool_call["function"]["arguments"])
# Validate args in production.
result = calculator_functions[name](**args)
print(result)
```
### Full loop: send tool output back for a final answer
If you want the model to turn raw tool output into a user-facing response, append the assistant message and a tool response with the matching `tool_call_id`:
```python full_loop.py theme={"system"}
# Continue the conversation
messages = payload["messages"]
messages.append(message) # assistant tool call message
# Example: respond to the first tool call
tool_call = tool_calls[0]
name = tool_call["function"]["name"]
args = json.loads(tool_call["function"]["arguments"])
result = calculator_functions[name](**args)
messages.append({
"role": "tool",
"tool_call_id": tool_call["id"],
"content": json.dumps({"result": result}),
})
final_payload = {
**payload,
"messages": messages,
}
final_resp = requests.post(
url,
headers={"Authorization": f"Bearer {BASETEN_API_KEY}"},
json=final_payload,
)
print(final_resp.json()["choices"][0]["message"].get("content"))
```
## Practical tips
Use low temperature (0.0-0.3) for reliable tool selection and argument values. Add `enum` and `required` constraints in your JSON schema to guide model outputs. Consider parallel tool calls only if your model supports them. Always validate and sanitize inputs before calling real systems.
## Related
* [Chains](/development/chain/overview): Orchestrate multi-step workflows.
* [Custom engine builder](/engines/engine-builder-llm/custom-engine-builder): Advanced configuration options.
# Configure HTTP clients
Source: https://docs.baseten.co/inference/http-client-configuration
Configure connection pooling, retries, and timeouts for reliable inference requests at scale.
When calling Baseten at scale, HTTP client configuration directly affects reliability and throughput.
Misconfigured clients cause `Connection refused` and `Client closed connection` errors that look like platform issues but originate client-side.
To tell a client-side error from a Baseten or model error, see [Inference errors](/inference/errors#is-it-my-model-or-baseten).
For a drop-in solution, use the [Performance Client](/inference/performance-client), which handles connection pooling, retries, and concurrency automatically.
## Reuse client sessions
Creating a new HTTP client per request is the most common misconfiguration. Each
new client opens a fresh TCP connection, performs a full TLS handshake, and then
discards the connection after a single use. Under load, this pattern quickly
exhausts available ports and produces `Connection refused` errors that appear
intermittent and difficult to diagnose.
A reused client maintains a pool of open connections that are ready for
subsequent requests. This eliminates per-request connection overhead and keeps
your throughput stable as concurrency increases.
Create a single client session and reuse it for all requests:
```python predict.py theme={"system"}
# Correct: reuse a client session
client = httpx.Client(
base_url=f"https://model-{model_id}.api.baseten.co",
headers={"Authorization": f"Bearer {api_key}"},
)
def predict(payload):
response = client.post("/environments/production/predict", json=payload)
return response.json()
```
Creating a new client session for each request opens a fresh TCP connection every time:
```python predict.py theme={"system"}
# Anti-pattern: new client per request
def predict(payload):
response = httpx.post(
url, json=payload, headers=headers
) # New connection every time
return response.json()
```
## Choose an HTTP client
Your choice of HTTP client library determines which connection management
features are available to you. The [httpx](https://www.python-httpx.org/)
library is recommended over
[requests](https://requests.readthedocs.io/en/latest/) for Baseten workloads
because it provides built-in connection pooling, native async support, and
optional HTTP/2. The `requests` library can achieve connection reuse through its
[`Session`](https://requests.readthedocs.io/en/latest/user/advanced/#session-objects) object, but lacks async support and requires more manual
configuration.
The OpenAI Python SDK uses httpx internally, so if you're already using it, you
benefit from httpx's connection handling by default.
Create a basic [`httpx.Client`](https://www.python-httpx.org/api/#client):
```python client.py theme={"system"}
import httpx
client = httpx.Client(
base_url=f"https://model-{model_id}.api.baseten.co",
headers={"Authorization": f"Bearer {api_key}"},
)
```
## Configure connection pooling
Connection pooling keeps a set of open TCP connections ready for reuse. When
your client sends a request, it draws from this pool instead of opening a new
connection. This avoids the cost of repeated TCP handshakes and TLS
negotiations, which can add 50-100ms of latency per request.
The default httpx pool limits (100 total connections, 20 per host) work for
moderate workloads, but high-throughput applications that send hundreds of
concurrent requests will exhaust these limits. When the pool is full, new
requests block until a connection becomes available, resulting in [`PoolTimeout`](https://www.python-httpx.org/exceptions/#pooltimeout)
errors or increased latency.
Increase the pool limits based on your peak concurrency using [`httpx.Limits`](https://www.python-httpx.org/advanced/resource-limits/). The
`max_keepalive_connections` setting controls how many idle connections stay
open, and `keepalive_expiry` controls how long idle connections persist before
closing. Baseten keeps connections alive for 60-120 seconds, so setting
your client's expiry below the server minimum avoids hitting dead connections. Set the limits when you create the client:
```python client.py theme={"system"}
import httpx
limits = httpx.Limits(
max_connections=256,
max_keepalive_connections=128,
keepalive_expiry=30,
)
client = httpx.Client(
base_url=f"https://model-{model_id}.api.baseten.co",
headers={"Authorization": f"Bearer {api_key}"},
limits=limits,
)
```
### Recommended values
| Setting | Default (httpx) | Recommended |
| ------------------------- | --------------- | ----------- |
| Max connections | 100 | 256 |
| Max keepalive connections | 20 | 128 |
| Keep-alive idle timeout | 5s | 30s |
| Keep-alives | Enabled | Enabled |
These values apply when calling a single Baseten model endpoint.
If you call multiple models, increase max connections proportionally.
Keep-alives are always enabled on Baseten.
## Set timeouts
httpx applies a default 5-second timeout to all operations, which is too short
for most inference workloads. LLM generation, image processing, and other model
inference tasks routinely take tens of seconds to minutes. Without properly
configured timeouts, your client will close connections before the model
finishes processing.
Set client timeouts based on your model's expected response time. Baseten's
ingress proxy allows up to 20 minutes (1200 seconds) for synchronous predict
requests, but your client-side timeouts should reflect your actual workload
rather than matching the server maximum.
httpx lets you configure four separate timeout values with [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/). Separating connect and
read timeouts prevents slow network conditions from being confused with slow
model responses. Configure the timeouts when you create the client:
```python client.py theme={"system"}
import httpx
timeout = httpx.Timeout(
connect=10.0, # Time to establish connection
read=1200.0, # Time to receive response
write=30.0, # Time to send request body
pool=10.0, # Time to acquire a connection from the pool
)
client = httpx.Client(
base_url=f"https://model-{model_id}.api.baseten.co",
headers={"Authorization": f"Bearer {api_key}"},
timeout=timeout,
)
```
### Timeout guidance by use case
| Use case | Connect | Read | Notes |
| ------------------------ | ------- | ----- | ------------------------- |
| LLM inference (sync) | 10s | 1200s | Long generation times |
| Embedding/classification | 10s | 60s | Faster response |
| Async predict (submit) | 10s | 30s | Just submitting the job |
| Streaming | 10s | 1200s | Keep open for full stream |
For long-running requests that exceed sync timeouts, use [async inference](/inference/async) with polling.
## Implement retries
Transient errors happen at scale and can negatively impact your application's reliability and throughput.
Retry with exponential backoff using libraries like [tenacity](https://tenacity.readthedocs.io/en/stable/).
Only retry on transient errors. Retrying client errors like 400 or 401 wastes
time and can mask bugs in your request payload.
Retry on these status codes and connection errors:
* **429** (rate limited)
* **500** (internal server error)
* **502** (bad gateway)
* **503** (service unavailable)
* **504** (gateway timeout)
* Connection errors ([`ConnectError`](https://www.python-httpx.org/exceptions/), `ReadTimeout`)
Don't retry on these status codes:
* **400** (bad request)
* **401** (unauthorized)
* **403** (forbidden)
* **404** (not found)
* **422** (validation error)
The following example uses httpx with tenacity to retry failed requests with exponential backoff:
```python predict.py theme={"system"}
import httpx
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception
def is_retryable(exception):
if isinstance(exception, httpx.HTTPStatusError):
return exception.response.status_code in (429, 500, 502, 503, 504)
return isinstance(exception, (httpx.ConnectError, httpx.ReadTimeout))
@retry(
retry=retry_if_exception(is_retryable),
wait=wait_exponential(multiplier=1, min=1, max=30),
stop=stop_after_attempt(5),
)
def predict(client, payload):
response = client.post("/environments/production/predict", json=payload)
response.raise_for_status()
return response.json()
```
## Handle errors
Many errors that look like platform outages actually originate from client-side
misconfiguration. Before opening a support ticket, check whether your error
matches one of these common patterns. If you see `PoolTimeout` or
`Connection refused` under high concurrency, the issue is almost always your
client's pool configuration, not Baseten's servers.
| Error | Likely cause | Resolution |
| -------------------- | ----------------------------------- | ---------------------------------------- |
| `PoolTimeout` | Connection pool exhausted | Increase pool size or reduce concurrency |
| `ConnectTimeout` | Network issue or server unavailable | Check network, then retry |
| `ReadTimeout` | Model taking longer than expected | Increase read timeout for your use case |
| `Connection refused` | Client-side port or pool exhaustion | Increase pool limits, check NAT config |
## Monitor connections
Connection problems tend to surface as intermittent failures rather than
complete outages, making them difficult to diagnose without proper monitoring. A
gradually exhausting connection pool won't cause errors until it's completely
full, at which point requests start failing unpredictably.
Watch for these signals:
* **Rising p99 latency** without changes to model performance, which often indicates pool contention.
* **Sporadic `Connection refused` errors** under load, which point to port or pool exhaustion.
* **TCP retransmits** increasing over time, which suggest connections are being dropped and recreated.
If you route traffic through a NAT gateway, monitor port utilization.
Each outbound connection consumes a port, and high-concurrency workloads can exhaust the available port range, causing intermittent connection failures that are difficult to distinguish from server-side issues.
## Use with proxies
Enterprise deployments often route traffic through HTTP proxies for security, logging, or network policy enforcement. httpx supports proxy configuration at the client level, so connection pooling and keep-alives continue to work through the proxy.
You may need to increase your pool limits when using a proxy, since the additional network hop increases per-request latency, which means connections are held open longer and the pool drains faster under the same concurrency. Pass the proxy URL when you create the client:
```python client.py theme={"system"}
import httpx
client = httpx.Client(
base_url=f"https://model-{model_id}.api.baseten.co",
headers={"Authorization": f"Bearer {api_key}"},
proxy="http://corporate-proxy.example.com:8080",
limits=httpx.Limits(max_connections=300),
)
```
## Further reading
* [Performance Client](/inference/performance-client): Handles connection pooling, retries, and concurrency automatically.
* [Async inference](/inference/async): For long-running requests that exceed sync timeout limits.
* [Streaming](/inference/streaming): For streaming model responses.
# Integrations
Source: https://docs.baseten.co/inference/integrations
Integrate your models with tools and use Baseten anywhere
Baseten works with the tools you already use. These integrations let you call Baseten Model APIs and deployed models from IDEs, agent frameworks, LLM gateways, and data platforms.
Run Claude Code CLI with Baseten Model APIs.
Run open-source frontier models in your IDE with Baseten and Cline.
Use Baseten models within your data analytics workflows with Fused.
Build agents with human-in-the-loop powered by Baseten LLMs and HumanLayer.
Use your Baseten models with LangChain to build workflows and agents.
Add real-time web search to your Baseten models with Linkup.
Use your Baseten models in LiteLLM projects.
Build real-time voice agents with TTS models hosted on Baseten.
Use Baseten models within your RAG applications with LlamaIndex.
Secure agentic harnesses with NemoClaw.
Run agentic workflows powered by open-source models with OpenClaw.
Run open-source frontier models in your IDE with Baseten and Roo Code.
Power your Next.js web apps using Baseten models through AI SDK v5.
Want to integrate Baseten with your platform or project? Reach out to
[support@baseten.co](mailto:support@baseten.co) and we'll help with building
and marketing the integration.
# JSON mode
Source: https://docs.baseten.co/inference/json-mode
Constrain model output to syntactically valid JSON
JSON mode forces a model to emit syntactically valid JSON. It's a feature of the OpenAI Chat Completions API, enabled by setting `response_format` to `{"type": "json_object"}`. Use JSON mode when you need parseable JSON but don't need to enforce a specific schema.
For most production use cases, prefer [structured outputs](/inference/structured-outputs). Structured outputs guarantee that the response matches a JSON schema you provide, which is stricter, type-safe, and removes the need to retry or validate after the fact.
## How it works
JSON mode tells the server to constrain the output to valid JSON. You still describe the fields you want in the prompt; the server only enforces well-formedness, not shape.
```python json_mode.py theme={"system"}
import os
from openai import OpenAI
client = OpenAI(
base_url="https://inference.baseten.co/v1",
api_key=os.environ["BASETEN_API_KEY"],
)
response = client.chat.completions.create(
model="deepseek-ai/DeepSeek-V4-Pro",
messages=[
{"role": "system", "content": "You are a helpful assistant that responds only in JSON."},
{"role": "user", "content": "List three planets with their distance from the sun in km. Respond as a JSON object with a 'planets' array."},
],
response_format={"type": "json_object"},
)
print(response.choices[0].message.content)
```
Ask for JSON in the prompt so the model produces a useful shape. The server constrains output to valid JSON but doesn't infer the schema from the request.
## JSON mode versus structured outputs
| Feature | JSON mode | Structured outputs |
| ------------------ | ---------------------------------------- | -------------------------------------------------- |
| Output guarantee | Valid JSON | Valid JSON that matches your schema |
| Schema enforcement | None | Strict (server rejects non-conforming generations) |
| Setup | Set `response_format` to `json_object` | Provide a JSON schema or Pydantic model |
| Best for | Lightweight extraction, ad hoc responses | Production data extraction, typed pipelines |
Reach for JSON mode when you don't want to define a schema and the downstream consumer can tolerate flexible field sets. Otherwise, use structured outputs.
## Model support
JSON mode and structured outputs are supported on a per-model basis. See the feature support table on the [Model APIs overview](/inference/model-apis/overview#feature-support) for which models support each.
## Related
* [Structured outputs](/inference/structured-outputs): Schema-enforced JSON output.
* [Model APIs overview](/inference/model-apis/overview): Supported models and feature matrix.
* [Chat Completions reference](/reference/inference-api/chat-completions): Full request and response schema.
# Deprecation
Source: https://docs.baseten.co/inference/model-apis/deprecation
Baseten's deprecation policy for Model APIs
Open-source models advance rapidly. Baseten prioritizes serving the highest-quality models and deprecates specific Model APIs when stronger alternatives become available. When a model is selected for deprecation, Baseten follows this process:
1. **Announcement**
* Deprecations are announced approximately two weeks before the deprecation date.
* Documentation is updated to identify the model being deprecated and recommend a replacement.
* Affected users are contacted by email.
2. **Transition**
* The deprecated model remains fully functional until the deprecation date. You have approximately two weeks to transition using one of these options:
1. Migrate to a dedicated deployment with the deprecated model weights. [Contact us](https://www.baseten.co/talk-to-us/deprecation-inquiry/) for assistance.
2. Update your code to use an active model (a recommendation is provided in the deprecation announcement).
3. **Deprecation date**
* The model ID for the deprecated model becomes inactive and returns an error for all requests.
* A changelog notification is published with the recommended replacement.
## Planned deprecations
| Model | Slug | Deprecation date | Recommended replacement |
| -------------- | --------------------------- | ---------------- | ----------------------------------------------------------- |
| GLM 5 | `zai-org/GLM-5` | July 24, 2026 | GLM 5.2 (`zai-org/GLM-5.2`) |
| GLM 5.1 | `zai-org/GLM-5.1` | July 24, 2026 | GLM 5.2 (`zai-org/GLM-5.2`) |
| Kimi K2.5 | `moonshotai/Kimi-K2.5` | July 24, 2026 | Kimi K2.6 (`moonshotai/Kimi-K2.6`) |
| Nemotron Super | `nvidia/Nemotron-120B-A12B` | July 24, 2026 | Nemotron Ultra (`nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B`) |
## Next steps
See the models currently available through Model APIs
Deploy a model with Truss to keep using specific weights
# Model APIs
Source: https://docs.baseten.co/inference/model-apis/overview
OpenAI-compatible endpoints for high-performance LLMs
Model APIs provide instant access to high-performance LLMs through endpoints that are compatible with both the [OpenAI Chat Completions API](/reference/inference-api/chat-completions) and the [Anthropic Messages API](/reference/inference-api/messages) (beta). Point your existing OpenAI or Anthropic SDK at Baseten's inference endpoint and start making calls, no model deployment required.
Unlike [dedicated deployments](/development/model/build-your-first-model), where you'd configure hardware, engines, and scaling yourself, Model APIs run on shared infrastructure that Baseten manages. You get a fixed set of popular models with optimized serving out of the box. When you need a model that isn't in the supported list, or want dedicated GPUs with custom scaling, deploy your own with [Truss](/development/model/overview).
## Supported models
[Run inference](#run-inference) against any Model API to get started.
Context and output limits reflect Baseten's live serving configuration, which can differ from a model's advertised native maximum. We extend limits as they meet our performance bar; this table and [`/v1/models`](#list-available-models) always reflect what's currently served.
## Pricing
Model APIs bill per million tokens. For current per-model rates, see the [Model APIs pricing page](https://www.baseten.co/pricing).
Cached input tokens are prompt tokens served from the KV cache, billed at a discounted rate. Every request participates in caching automatically, with no flags or opt-in steps.
## Feature support
All models support [tool calling](/inference/function-calling) (also known as function calling), [structured outputs](/inference/structured-outputs), and [JSON mode](/inference/json-mode). See the table below for per-model coverage of reasoning and vision. For reasoning-specific configuration, see [Reasoning](/inference/model-apis/reasoning). For image and video inputs, see [Vision](/inference/model-apis/vision).
GLM models, Nemotron Super, and Nemotron Ultra also support `top_p` and `top_k` sampling parameters.
## Run inference
Model APIs support both OpenAI's Chat Completions and Anthropic's Messages APIs. Set your base URL, API key, and [model name](#supported-models) to start making requests.
### Use the OpenAI SDK
Call supported models using the [OpenAI Chat Completions API](/reference/inference-api/chat-completions) at `https://inference.baseten.co/v1/chat/completions`.
**To call a model with the Chat Completions API**:
```python chat_completions.py theme={"system"}
from openai import OpenAI
import os
client = OpenAI(
base_url="https://inference.baseten.co/v1",
api_key=os.environ["BASETEN_API_KEY"],
)
response = client.chat.completions.create(
model="deepseek-ai/DeepSeek-V4-Pro",
messages=[
{"role": "system", "content": "You are a concise technical writer."},
{"role": "user", "content": "What is gradient descent?"},
{"role": "assistant", "content": "An optimization algorithm that iteratively adjusts model parameters by moving in the direction of steepest decrease in the loss function."},
{"role": "user", "content": "How does the learning rate affect it?"}
],
)
print(response.choices[0].message.content)
```
**To call a model with the Chat Completions API**:
```javascript chat_completions.js theme={"system"}
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://inference.baseten.co/v1",
apiKey: process.env.BASETEN_API_KEY,
});
const response = await client.chat.completions.create({
model: "deepseek-ai/DeepSeek-V4-Pro",
messages: [
{ role: "system", content: "You are a concise technical writer." },
{ role: "user", content: "What is gradient descent?" },
{ role: "assistant", content: "An optimization algorithm that iteratively adjusts model parameters by moving in the direction of steepest decrease in the loss function." },
{ role: "user", content: "How does the learning rate affect it?" }
],
});
console.log(response.choices[0].message.content);
```
**To call a model with the Chat Completions API**:
```bash Request theme={"system"}
curl https://inference.baseten.co/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "deepseek-ai/DeepSeek-V4-Pro",
"messages": [
{"role": "system", "content": "You are a concise technical writer."},
{"role": "user", "content": "What is gradient descent?"},
{"role": "assistant", "content": "An optimization algorithm that iteratively adjusts model parameters by moving in the direction of steepest decrease in the loss function."},
{"role": "user", "content": "How does the learning rate affect it?"}
]
}'
```
Replace the model slug with any model from the supported models table.
### Use the Anthropic SDK
Call supported models using the [Anthropic Messages API](/reference/inference-api/messages) at `https://inference.baseten.co/v1/messages`.
Anthropic Messages API support is in **beta**. Behavior may change before general availability. For production workloads, use the [OpenAI Chat Completions API](/reference/inference-api/chat-completions).
**To call a model with the Messages API**:
```python messages_api.py theme={"system"}
import anthropic
import os
API_KEY = os.environ["BASETEN_API_KEY"]
client = anthropic.Anthropic(
base_url="https://inference.baseten.co",
api_key=API_KEY,
default_headers={"Authorization": f"Bearer {API_KEY}"},
)
response = client.messages.create(
model="deepseek-ai/DeepSeek-V4-Pro",
max_tokens=4096,
system="You are a concise technical writer.",
messages=[
{"role": "user", "content": "What is gradient descent?"},
{"role": "assistant", "content": "An optimization algorithm that iteratively adjusts model parameters by moving in the direction of steepest decrease in the loss function."},
{"role": "user", "content": "How does the learning rate affect it?"}
],
)
for block in response.content:
if block.type == "text":
print(block.text)
```
**To call a model with the Messages API**:
```javascript messages_api.js theme={"system"}
import Anthropic from "@anthropic-ai/sdk";
const apiKey = process.env.BASETEN_API_KEY;
const client = new Anthropic({
baseURL: "https://inference.baseten.co",
apiKey: apiKey,
defaultHeaders: { Authorization: `Bearer ${apiKey}` },
});
const response = await client.messages.create({
model: "deepseek-ai/DeepSeek-V4-Pro",
max_tokens: 4096,
system: "You are a concise technical writer.",
messages: [
{ role: "user", content: "What is gradient descent?" },
{ role: "assistant", content: "An optimization algorithm that iteratively adjusts model parameters by moving in the direction of steepest decrease in the loss function." },
{ role: "user", content: "How does the learning rate affect it?" }
],
});
for (const block of response.content) {
if (block.type === "text") console.log(block.text);
}
```
**To call a model with the Messages API**:
```bash Request theme={"system"}
curl https://inference.baseten.co/v1/messages \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "deepseek-ai/DeepSeek-V4-Pro",
"max_tokens": 4096,
"system": "You are a concise technical writer.",
"messages": [
{"role": "user", "content": "What is gradient descent?"},
{"role": "assistant", "content": "An optimization algorithm that iteratively adjusts model parameters by moving in the direction of steepest decrease in the loss function."},
{"role": "user", "content": "How does the learning rate affect it?"}
]
}'
```
The Anthropic SDK sends the API key as `x-api-key` by default. Baseten reads `Authorization`, so override `default_headers` as shown.
## List available models
Query the `/v1/models` endpoint for the current list of models with metadata including pricing, context windows, and supported features:
```bash Request theme={"system"}
curl https://inference.baseten.co/v1/models \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
## Migrate
To migrate to Baseten, change the base URL, API key, and model name.
**To migrate from the OpenAI SDK**:
1. Replace your OpenAI API key with a [Baseten API key](https://app.baseten.co/settings/api_keys).
2. Change the base URL to `https://inference.baseten.co/v1`.
3. Update the model name to a Baseten model slug.
```python migrate.py theme={"system"}
from openai import OpenAI
import os
client = OpenAI(
base_url="https://inference.baseten.co/v1", # [!code ++]
api_key=os.environ["BASETEN_API_KEY"] # [!code ++]
)
response = client.chat.completions.create(
model="deepseek-ai/DeepSeek-V4-Pro", # [!code ++]
messages=[{"role": "user", "content": "Hello"}]
)
```
**To migrate from the Anthropic SDK**:
1. Replace your Anthropic API key with a [Baseten API key](https://app.baseten.co/settings/api_keys).
2. Change the base URL to `https://inference.baseten.co`.
3. Override `default_headers` so the SDK sends `Authorization` instead of `x-api-key`.
4. Update the model name to a [supported Baseten model slug](#supported-models).
```python migrate.py theme={"system"}
import anthropic
import os
API_KEY = os.environ["BASETEN_API_KEY"]
client = anthropic.Anthropic(
base_url="https://inference.baseten.co", # [!code ++]
api_key=API_KEY, # [!code ++]
default_headers={"Authorization": f"Bearer {API_KEY}"}, # [!code ++]
)
response = client.messages.create(
model="deepseek-ai/DeepSeek-V4-Pro", # [!code ++]
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}]
)
```
## Handle errors
Model APIs return standard HTTP error codes:
| Code | Meaning |
| ---- | --------------------------------------- |
| 400 | Invalid request (check your parameters) |
| 401 | Invalid or missing API key |
| 402 | Payment required |
| 404 | Model not found |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
Each error response includes a JSON body with details about the issue and suggested resolutions.
## Next steps
Control extended thinking for complex tasks
Send images and videos alongside text
Understand and configure rate limits
Complete parameter documentation
# Rate limits and budgets
Source: https://docs.baseten.co/inference/model-apis/rate-limits-and-budgets
Rate limits and usage budgets for Model APIs
Baseten enforces two rate limits to ensure fair use and system stability:
* **Request rate limits**: Maximum API requests per minute.
* **Token rate limits**: Maximum tokens processed per minute (input + output combined). Cached input tokens count at full weight: a token served from the KV cache costs less but counts the same toward the token rate limit as an uncached one.
Default limits vary by account status.
| Account | RPM | TPM |
| :--------------------- | ------------------------------------------: | ------------------------------------------: |
| **Basic** (unverified) | 15 | 100,000 |
| **Basic** (verified) | 120 | 500,000 |
| **Pro** | 120 | 1,000,000 |
| **Enterprise** | [Custom](https://www.baseten.co/talk-to-us) | [Custom](https://www.baseten.co/talk-to-us) |
If your workspace is on the Basic (unverified) tier and you need the higher Basic (verified) limits, [contact us](https://www.baseten.co/talk-to-us/increase-rate-limits/) to request verification. To move to the Pro or Enterprise tier, contact us through the same form.
If you exceed these limits, the API returns a `429 Too Many Requests` error. See [Inference errors](/inference/errors#429-too-many-requests) for how to respond.
To request a rate limit increase, [contact us](https://www.baseten.co/talk-to-us/increase-rate-limits/).
## Set budgets
Budgets let you control Model API usage and avoid unexpected costs. Budgets apply only to Model APIs, not dedicated deployments. Your team receives email notifications at 75%, 90%, and 100% of budget.
### Enforce budgets
Budgets can be enforced or non-enforced:
* **Enforced**: Requests are rejected when the budget is reached.
* **Not enforced**: You receive notifications but remain responsible for costs over the budget.
## Next steps
Handle `429 Too Many Requests` and other status codes
Supported models, pricing, and feature support
# Reasoning
Source: https://docs.baseten.co/inference/model-apis/reasoning
Control extended thinking for reasoning-capable models
Some Model APIs support *extended thinking*, where the model reasons through a problem before producing a final answer. The reasoning process generates additional tokens that appear in a separate `reasoning_content` field, distinct from the final response.
## Supported models
| Model | Slug | Reasoning |
| --------------- | ------------------------------------------ | ----------------------------------- |
| DeepSeek V4 Pro | `deepseek-ai/DeepSeek-V4-Pro` | Enabled by default |
| OpenAI GPT 120B | `openai/gpt-oss-120b` | Enabled by default |
| GLM 5.2 | `zai-org/GLM-5.2` | Enabled by default |
| Kimi K2.5 | `moonshotai/Kimi-K2.5` | Opt-in through `chat_template_args` |
| Kimi K2.6 | `moonshotai/Kimi-K2.6` | Opt-in through `chat_template_args` |
| Kimi K2.7 Code | `moonshotai/Kimi-K2.7-Code` | Opt-in through `chat_template_args` |
| GLM 4.7 | `zai-org/GLM-4.7` | Opt-in through `chat_template_args` |
| GLM 5 | `zai-org/GLM-5` | Opt-in through `chat_template_args` |
| GLM 5.1 | `zai-org/GLM-5.1` | Opt-in through `chat_template_args` |
| Nemotron Super | `nvidia/Nemotron-120B-A12B` | Opt-in through `chat_template_args` |
| Nemotron Ultra | `nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B` | Opt-in through `chat_template_args` |
DeepSeek V4 Pro, OpenAI GPT 120B, and GLM 5.2 also support [`reasoning_effort`](#control-reasoning-depth).
Models not listed here don't support reasoning.
## Enable thinking
For models marked opt-in in the table above, enable thinking by passing `chat_template_args`.
Pass `chat_template_args` through `extra_body` since it extends the standard OpenAI API:
```python enable_thinking.py theme={"system"}
response = client.chat.completions.create(
model="moonshotai/Kimi-K2.5",
messages=[{"role": "user", "content": "What is the sum of the first 100 prime numbers?"}],
extra_body={"chat_template_args": {"enable_thinking": True}},
max_tokens=4096,
stream=True,
)
```
Include `chat_template_args` directly in the request options:
```javascript enable_thinking.js theme={"system"}
const response = await client.chat.completions.create({
model: "moonshotai/Kimi-K2.5",
messages: [{ role: "user", content: "What is the sum of the first 100 prime numbers?" }],
chat_template_args: { enable_thinking: true },
max_tokens: 4096,
stream: true,
});
```
Include `chat_template_args` in the JSON request body:
```bash Request theme={"system"}
curl https://inference.baseten.co/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "moonshotai/Kimi-K2.5",
"messages": [{"role": "user", "content": "What is the sum of the first 100 prime numbers?"}],
"chat_template_args": {"enable_thinking": true},
"max_tokens": 4096,
"stream": false
}'
```
## Control reasoning depth
The `reasoning_effort` parameter controls how thoroughly the model reasons through a problem. DeepSeek V4 Pro, OpenAI GPT 120B, and GLM 5.2 support this parameter. Supported values vary by model:
| Model | Supported values |
| --------------- | -------------------------------------------------------------------- |
| DeepSeek V4 Pro | `none`, `minimal`, `low`, `medium` (default), `high`, `xhigh`, `max` |
| OpenAI GPT 120B | `none`, `minimal`, `low`, `medium` (default), `high`, `xhigh`, `max` |
| GLM 5.2 | `none`, `high`, `max` |
Lower values return faster responses with less thorough reasoning; higher values reason longer and cost more output tokens. `none` disables reasoning entirely. GLM 5.2 returns a `400` error for values outside its set.
Some model templates also read `reasoning_effort` from inside `chat_template_args` (GLM 5.2 honors both placements). Use the top-level parameter: the API validates it and returns a `400` for invalid values, but doesn't validate `chat_template_args` contents, so mistakes there fail silently.
A successful request doesn't mean `reasoning_effort` took effect. Models not listed in this table accept the parameter but ignore it.
Pass `reasoning_effort` through `extra_body` since it extends the standard OpenAI API:
```python reasoning_effort.py theme={"system"}
from openai import OpenAI
import os
client = OpenAI(
base_url="https://inference.baseten.co/v1",
api_key=os.environ.get("BASETEN_API_KEY")
)
response = client.chat.completions.create(
model="deepseek-ai/DeepSeek-V4-Pro",
messages=[
{"role": "user", "content": "What is the sum of the first 100 prime numbers?"}
],
extra_body={"reasoning_effort": "high"} # [!code ++]
)
print(response.choices[0].message.content)
```
Include `reasoning_effort` directly in the request options:
```javascript reasoning_effort.js theme={"system"}
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://inference.baseten.co/v1",
apiKey: process.env.BASETEN_API_KEY,
});
const response = await client.chat.completions.create({
model: "deepseek-ai/DeepSeek-V4-Pro",
messages: [
{ role: "user", content: "What is the sum of the first 100 prime numbers?" }
],
reasoning_effort: "high" // [!code ++]
});
console.log(response.choices[0].message.content);
```
Include `reasoning_effort` in the JSON request body:
```bash Request theme={"system"}
curl https://inference.baseten.co/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "deepseek-ai/DeepSeek-V4-Pro",
"messages": [{"role": "user", "content": "What is the sum of the first 100 prime numbers?"}],
"reasoning_effort": "high"
}'
```
Pass `reasoning_effort` through `extra_body` since it extends the standard OpenAI API:
```python reasoning_effort.py theme={"system"}
from openai import OpenAI
import os
client = OpenAI(
base_url="https://inference.baseten.co/v1",
api_key=os.environ.get("BASETEN_API_KEY")
)
response = client.chat.completions.create(
model="openai/gpt-oss-120b",
messages=[
{"role": "user", "content": "What is the sum of the first 100 prime numbers?"}
],
extra_body={"reasoning_effort": "high"} # [!code ++]
)
print(response.choices[0].message.content)
```
Include `reasoning_effort` directly in the request options:
```javascript reasoning_effort.js theme={"system"}
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://inference.baseten.co/v1",
apiKey: process.env.BASETEN_API_KEY,
});
const response = await client.chat.completions.create({
model: "openai/gpt-oss-120b",
messages: [
{ role: "user", content: "What is the sum of the first 100 prime numbers?" }
],
reasoning_effort: "high" // [!code ++]
});
console.log(response.choices[0].message.content);
```
Include `reasoning_effort` in the JSON request body:
```bash Request theme={"system"}
curl https://inference.baseten.co/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "openai/gpt-oss-120b",
"messages": [{"role": "user", "content": "What is the sum of the first 100 prime numbers?"}],
"reasoning_effort": "high"
}'
```
Pass `reasoning_effort` through `extra_body` since it extends the standard OpenAI API:
```python reasoning_effort.py theme={"system"}
from openai import OpenAI
import os
client = OpenAI(
base_url="https://inference.baseten.co/v1",
api_key=os.environ.get("BASETEN_API_KEY")
)
response = client.chat.completions.create(
model="zai-org/GLM-5.2",
messages=[
{"role": "user", "content": "What is the sum of the first 100 prime numbers?"}
],
extra_body={"reasoning_effort": "high"} # [!code ++]
)
print(response.choices[0].message.content)
```
Include `reasoning_effort` directly in the request options:
```javascript reasoning_effort.js theme={"system"}
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://inference.baseten.co/v1",
apiKey: process.env.BASETEN_API_KEY,
});
const response = await client.chat.completions.create({
model: "zai-org/GLM-5.2",
messages: [
{ role: "user", content: "What is the sum of the first 100 prime numbers?" }
],
reasoning_effort: "high" // [!code ++]
});
console.log(response.choices[0].message.content);
```
Include `reasoning_effort` in the JSON request body:
```bash Request theme={"system"}
curl https://inference.baseten.co/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "zai-org/GLM-5.2",
"messages": [{"role": "user", "content": "What is the sum of the first 100 prime numbers?"}],
"reasoning_effort": "high"
}'
```
Reasoning improves quality for tasks that benefit from step-by-step thinking: mathematical calculations, multi-step logic problems, code generation with complex requirements, and analysis requiring multiple considerations.
For straightforward tasks like simple Q\&A or text generation, reasoning adds latency and token cost without improving quality. In these cases, use a model without reasoning support or set `reasoning_effort` to `low`.
### Parse the response
The model's thinking process appears in `reasoning_content`, separate from the final answer in `content`. Both fields are returned on the message object.
Read `reasoning_content` and `content` directly off the message object:
```python parse_reasoning.py theme={"system"}
from openai import OpenAI
import os
client = OpenAI(
base_url="https://inference.baseten.co/v1",
api_key=os.environ.get("BASETEN_API_KEY"),
)
response = client.chat.completions.create(
model="moonshotai/Kimi-K2.6",
messages=[{"role": "user", "content": "Is 91 a prime number? Answer in one sentence."}],
extra_body={"chat_template_args": {"enable_thinking": True}},
)
message = response.choices[0].message
print("Reasoning:", message.reasoning_content)
print("Answer:", message.content)
```
Read `reasoning_content` and `content` from the returned message:
```javascript parse_reasoning.js theme={"system"}
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://inference.baseten.co/v1",
apiKey: process.env.BASETEN_API_KEY,
});
const response = await client.chat.completions.create({
model: "moonshotai/Kimi-K2.6",
messages: [{ role: "user", content: "Is 91 a prime number? Answer in one sentence." }],
chat_template_args: { enable_thinking: true },
});
const message = response.choices[0].message;
console.log("Reasoning:", message.reasoning_content);
console.log("Answer:", message.content);
```
Pipe the response through `jq` to extract each field:
```bash Request theme={"system"}
curl https://inference.baseten.co/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "moonshotai/Kimi-K2.6",
"messages": [{"role": "user", "content": "Is 91 a prime number? Answer in one sentence."}],
"chat_template_args": {"enable_thinking": true}
}' | jq '.choices[0].message | {reasoning: .reasoning_content, answer: .content}'
```
The response body contains both fields on the assistant message:
```json Response theme={"system"}
{
"choices": [
{
"message": {
"role": "assistant",
"reasoning_content": "The user is asking whether 91 is a prime number... 91 = 7 × 13, so it is not prime...",
"content": "No, 91 is not a prime number because it can be factored as $7 \\times 13$."
}
}
],
"usage": {
"prompt_tokens": 21,
"completion_tokens": 203,
"total_tokens": 224
}
}
```
Reasoning tokens are included in `completion_tokens` and count toward your total usage and billing.
## Next steps
Supported models, pricing, and the feature support matrix
Constrain reasoning models to a JSON schema
# Vision
Source: https://docs.baseten.co/inference/model-apis/vision
Send images and videos alongside text to vision-capable models
Model APIs support both text and vision inputs, but multimodal capability depends on the underlying model. Vision-capable models accept images alongside text in the same request, using the OpenAI-compatible `image_url` content type. The model processes both modalities together, so it can answer questions about image content, compare multiple images, or extract structured data from screenshots.
Not all models support vision. Check the table below before sending image inputs.
## Supported models
| Model | Slug |
| -------------- | --------------------------- |
| Kimi K2.5 | `moonshotai/Kimi-K2.5` |
| Kimi K2.6 | `moonshotai/Kimi-K2.6` |
| Kimi K2.7 Code | `moonshotai/Kimi-K2.7-Code` |
## Send a vision request
Use the `image_url` content type to include images in your messages.
Baseten retrieves image URLs **from the inference service**, so the URL must be reachable over HTTPS from Baseten's environment (for example your own object storage, Hugging Face artifact links, or other hosts that allow server-side fetches). Prefer stable, direct HTTPS links.
Optional `image_url.detail` controls preprocessing resolution: `low`, `high`, `original`, or `auto` (OpenAI-compatible). When in doubt, use `auto`. Send an image alongside a text prompt like this:
```python vision.py theme={"system"}
from openai import OpenAI
import os
client = OpenAI(
base_url="https://inference.baseten.co/v1",
api_key=os.environ["BASETEN_API_KEY"],
)
response = client.chat.completions.create(
model="moonshotai/Kimi-K2.6",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "Describe the natural environment in the image.",
},
{
"type": "image_url",
"image_url": {
"url": "https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/seashore.png",
"detail": "auto",
},
},
],
}
],
)
print(response.choices[0].message.content)
```
```javascript vision.js theme={"system"}
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://inference.baseten.co/v1",
apiKey: process.env.BASETEN_API_KEY,
});
const response = await client.chat.completions.create({
model: "moonshotai/Kimi-K2.6",
messages: [
{
role: "user",
content: [
{
type: "text",
text: "Describe the natural environment in the image.",
},
{
type: "image_url",
image_url: {
url: "https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/seashore.png",
detail: "auto",
},
},
],
},
],
});
console.log(response.choices[0].message.content);
```
```bash Request theme={"system"}
curl https://inference.baseten.co/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "moonshotai/Kimi-K2.6",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "Describe the natural environment in the image."
},
{
"type": "image_url",
"image_url": {
"url": "https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/seashore.png",
"detail": "auto"
}
}
]
}
]
}'
```
## Image and video limits
Each vision-capable model enforces its own per-request limits on media count and size. The current limits for Kimi are:
| Limit | Kimi K2.5 | Kimi K2.6 | Kimi K2.7 Code |
| -------------------------------------- | --------: | --------: | -------------: |
| Max images per request | 96 | 96 | 96 |
| Max videos per request | 12 | 12 | 12 |
| Max total media size per request (URL) | 240 MB | 240 MB | 240 MB |
| Max size per image (URL) | 90 MB | 80 MB | 80 MB |
| Max request body (base64) | 5 MB | 5 MB | 5 MB |
Pass images by URL whenever you can. The model's vision encoder fetches each URL and enforces the per-image and total-media caps directly, while the request body stays small. Base64-encoded images travel inside the request body and hit the 5 MB cap quickly.
Other Model APIs models set their own limits. Confirm the values for a given slug in the Baseten app or through [`/v1/models`](/inference/model-apis/overview#list-available-models).
## Pricing
There is no additional per-image fee. Images are converted to input tokens and priced at the model's standard input rate. Higher resolution images produce more tokens and cost more to process.
The exact conversion from pixels to tokens depends on the model. Kimi K2.5 and Kimi K2.6 divide each image into 14×14 pixel tiles where each tile becomes one input token. The cost table below uses Kimi K2.5's uncached input rate (\$0.60 per million tokens); for Kimi K2.6 and other models, use the rates on the [Model APIs pricing page](https://www.baseten.co/pricing).
| Image resolution | Tiles | Input tokens | Cost at \$0.60/M |
| ---------------- | -----: | -----------: | ---------------: |
| 256×256 | 324 | 324 | \$0.0002 |
| 512×512 | 1,296 | 1,296 | \$0.0008 |
| 1024×1024 | 5,329 | 5,329 | \$0.0032 |
| 1920×1080 | 10,234 | 10,234 | \$0.0061 |
For videos, token count scales with both resolution and the number of sampled frames.
## Next steps
Supported models, pricing, and the feature support matrix
Full request and response schema for the `image_url` content type
# Model I/O in binary
Source: https://docs.baseten.co/inference/output-format/binary
Decode and save binary model output
Baseten and Truss natively support model I/O in binary and use msgpack encoding for efficiency.
## Deploy a basic Truss for binary I/O
If you need a deployed model to try the invocation examples below, follow these steps to create and deploy a minimal Truss that accepts and returns binary data. The Truss performs no operations and is purely illustrative.
**To create and deploy the example Truss**:
1. Create a Truss:
```sh Terminal theme={"system"}
truss init binary_test
```
This creates a Truss in a new directory `binary_test`. By default, newly created Trusses implement an identity function that returns the exact input they are given.
2. Optionally, modify `binary_test/model/model.py` to log that the data received is of type `bytes`:
```python binary_test/model/model.py theme={"system"}
def predict(self, model_input):
# Run model inference here
print(f"Input type: {type(model_input['byte_data'])}")
return model_input
```
3. Deploy the Truss to Baseten:
```sh Terminal theme={"system"}
truss push --watch
```
## Send raw bytes as model input
To send binary data as model input:
1. Set the `content-type` HTTP header to `application/octet-stream`.
2. Use `msgpack` to encode the data or file.
3. Make a POST request to the model.
This code sample assumes you have a file `Gettysburg.mp3` in the current working directory. You can download the [11-second file from our CDN](https://cdn.baseten.co/docs/production/Gettysburg.mp3) or replace it with your own file.
```python call_model.py theme={"system"}
import os
import requests
import msgpack
model_id = "MODEL_ID" # Replace this with your model ID
deployment = "development" # `development`, `production`, or a deployment ID
baseten_api_key = os.environ["BASETEN_API_KEY"]
# Specify the URL to which you want to send the POST request
url = f"https://model-{model_id}.api.baseten.co/{deployment}/predict"
headers={
"Authorization": f"Bearer {baseten_api_key}",
"content-type": "application/octet-stream",
}
with open('Gettysburg.mp3', 'rb') as file:
response = requests.post(
url,
headers=headers,
data=msgpack.packb({'byte_data': file.read()})
)
print(response.status_code)
print(response.headers)
```
To support certain types like numpy and datetime values, you may need to
extend client-side `msgpack` encoding with the same [encoder and decoder used
by
Truss](https://github.com/basetenlabs/truss/blob/main/truss/templates/shared/serialization.py).
## Parse raw bytes from model output
To use the output of a non-streaming model response, decode the response content:
```python call_model.py theme={"system"}
# Continues `call_model.py` from above
binary_output = msgpack.unpackb(response.content)
# Change extension if not working with mp3 data
with open('output.mp3', 'wb') as file:
file.write(binary_output["byte_data"])
```
## Streaming binary outputs
You can also stream output as binary. This is useful for sending large files or reading binary output as it is generated.
In the `model.py`, you must create a streaming output.
```python model/model.py theme={"system"}
# Replace the predict function in your Truss
def predict(self, model_input):
import os
current_dir = os.path.dirname(__file__)
file_path = os.path.join(current_dir, "tmpfile.txt")
with open(file_path, mode="wb") as file:
file.write(bytes(model_input["text"], encoding="utf-8"))
def iterfile():
# Get the directory of the current file
current_dir = os.path.dirname(__file__)
# Construct the full path to the .wav file
file_path = os.path.join(current_dir, "tmpfile.txt")
with open(file_path, mode="rb") as file_like:
yield from file_like
return iterfile()
```
Then, in your client, use the streaming output directly without decoding:
```python stream_model.py theme={"system"}
import os
import requests
import json
model_id = "MODEL_ID" # Replace this with your model ID
deployment = "development" # `development`, `production`, or a deployment ID
baseten_api_key = os.environ["BASETEN_API_KEY"]
# Specify the URL to which you want to send the POST request
url = f"https://model-{model_id}.api.baseten.co/{deployment}/predict"
headers={
"Authorization": f"Bearer {baseten_api_key}",
}
s = requests.Session()
with s.post(
# Endpoint for production deployment, see API reference for more
f"https://model-{model_id}.api.baseten.co/{deployment}/predict",
headers={"Authorization": f"Bearer {baseten_api_key}"},
data=json.dumps({"text": "Lorem Ipsum"}),
# Include stream=True as an argument so the requests library knows to stream
stream=True,
) as response:
for token in response.iter_content(1):
print(token) # Prints bytes
```
## Next steps
Pass files and URLs as model input and save output to disk
Stream text responses token by token
# Model I/O with files
Source: https://docs.baseten.co/inference/output-format/files
Call models by passing a file or URL
Baseten supports file-based input and output during inference, whether the file is local or remote and whether you handle it in the Truss or in your client code.
## Files as input
### Send a file with JSON-serializable content
The Truss CLI has a `-f` flag to pass file input. If you're using the API endpoint from Python, get file contents with the standard `f.read()` function.
```sh Truss CLI theme={"system"}
truss predict -f input.json
```
```python call_model.py theme={"system"}
import os
import json
import requests
model_id = ""
# Read secrets from environment variables
baseten_api_key = os.environ["BASETEN_API_KEY"]
# Read input as JSON
with open("input.json", "r") as f:
data = json.load(f)
resp = requests.post(
# Endpoint for production deployment, see API reference for more
f"https://model-{model_id}.api.baseten.co/production/predict",
headers={"Authorization": f"Bearer {baseten_api_key}"},
json=data,
)
print(resp.json())
```
### Send a file with non-serializable content
The `-f` flag for `truss predict` only applies to JSON-serializable content. For other files, like the audio files required by MusicGen Melody, base64-encode the file content before you send it:
```python call_model.py theme={"system"}
import os
import base64
import requests
model_id = ""
# Read secrets from environment variables
baseten_api_key = os.environ["BASETEN_API_KEY"]
# Open a local file and base64-encode it (mono WAV, 48kHz sample rate)
with open("mymelody.wav", "rb") as f:
encoded_str = base64.b64encode(f.read()).decode("utf-8")
# Build a JSON-serializable payload
data = {"prompts": ["happy rock", "energetic EDM", "sad jazz"], "melody": encoded_str, "duration": 8}
resp = requests.post(
# Endpoint for production deployment, see API reference for more
f"https://model-{model_id}.api.baseten.co/production/predict",
headers={"Authorization": f"Bearer {baseten_api_key}"},
json=data,
)
# Decode the base64 audio clips in the response and save them
for idx, clip in enumerate(resp.json()["data"]):
with open(f"clip_{idx}.wav", "wb") as f:
f.write(base64.b64decode(clip))
```
### Send a URL to a public file
Rather than encoding and serializing a file to send in the HTTP request, write a Truss that takes a URL as input and loads the content in the `preprocess()` function. Here's an example from [Whisper in the model library](https://www.baseten.co/library/whisper/):
```python model/model.py theme={"system"}
from tempfile import NamedTemporaryFile
import requests
# Get file content without blocking GPU
def preprocess(self, request):
resp = requests.get(request["url"])
return {"content": resp.content}
# Use file content in model inference
def predict(self, model_input):
with NamedTemporaryFile() as fp:
fp.write(model_input["content"])
result = whisper.transcribe(
self._model,
fp.name,
temperature=0,
best_of=5,
beam_size=5,
)
segments = [
{"start": r["start"], "end": r["end"], "text": r["text"]}
for r in result["segments"]
]
return {
"language": whisper.tokenizer.LANGUAGES[result["language"]],
"segments": segments,
"text": result["text"],
}
```
## Files as output
### Save model output to a local file
Saving model output to a local file needs no Baseten-specific code. Use the standard `>` operator in bash or the `file.write()` function in Python:
```sh Truss CLI theme={"system"}
truss predict -d '"Model input!"' > output.json
```
```python call_model.py theme={"system"}
import os
import json
import requests
model_id = ""
# Read secrets from environment variables
baseten_api_key = os.environ["BASETEN_API_KEY"]
# Call model
resp = requests.post(
# Endpoint for production deployment, see API reference for more
f"https://model-{model_id}.api.baseten.co/production/predict",
headers={"Authorization": f"Bearer {baseten_api_key}"},
json="Model input!",
)
# Serialize the JSON response and write it to file
with open("output.json", "w") as f:
f.write(json.dumps(resp.json()))
```
Output for some models, like image and audio generation models, may need to be decoded before you save it. See our [image generation example](/examples/image-generation) for how to parse base64 output.
# Overview
Source: https://docs.baseten.co/inference/overview
Inference on Baseten: Model APIs, self-deployed models, how responses are delivered, structured outputs, tool calling, and client configuration.
Inference on Baseten is the path from your application to a model running in Baseten's infrastructure, whether you use [Model APIs](/inference/model-apis/overview) for hosted models or deploy your own with [Truss](/development/model/overview). You don't provision GPUs or build your own routing layer: Baseten authenticates each request, matches it to a deployment environment, and runs it on a replica. This page assumes you already have a Baseten account and an API key.
To call popular open models without a Truss project first, use the public OpenAI-compatible endpoint at `https://inference.baseten.co/v1` with your [Baseten API key](/organization/api-keys) and the OpenAI SDK pointed at that base URL. The [Model APIs](/inference/model-apis/overview) documentation lists models, pricing, and feature support. For what happens after the gateway (routing, replicas, queuing, retries, cold starts), see [Request lifecycle](/deployment/autoscaling/request-lifecycle).
If you're an AI lab serving your own hosted model to your own customers under a branded URL, with federated keys and per-customer billing, see [Frontier Gateway](/frontier-gateway/overview).
## Inference API
When you deploy your own model, pick an interface that matches your payloads. Engine-Builder-LLM, BIS-LLM, and BEI expose `/v1/chat/completions` (or `/v1/embeddings` for BEI) on your deployment's own endpoint, `https://model-.api.baseten.co/environments/production/sync/v1`, with OpenAI-compatible parameters for structured outputs, tool calling, reasoning, and streaming. Custom Truss code can use `/predict` for arbitrary JSON when chat or embeddings are not a good fit. Use the [Inference API reference](/reference/inference-api/overview) for paths, methods, and errors.
## Synchronous inference
Synchronous calls return a full response in one round trip, which fits interactive use (chat, code completion, classification, embeddings) where the client can wait for the answer. See [Call your model](/inference/calling-your-model) for predict-style URLs across development, environment, and published deployments.
## Streaming
Streaming sends tokens as they are generated over server-sent events, which suits long generations and UIs where partial output beats a blank wait. See [Streaming](/inference/streaming) for client patterns and engine notes.
## Asynchronous inference
Async inference returns a request ID quickly and completes later through webhook or polling, which suits batch work, long documents, or any case where the caller should not hold a connection open for minutes. See [Async inference](/inference/async) for webhooks, status endpoints, and failures.
## Structured outputs and tool calling
Structured outputs constrain the model to a JSON schema you define; tool calling lets the model invoke your functions and continue the turn. Both align with OpenAI SDK parameters where supported on [Model APIs](/inference/model-apis/overview) and engine-backed deployments. Read [Structured outputs](/inference/structured-outputs) and [Function calling](/inference/function-calling) for implementation details.
## Client configuration
For sustained load, tune connection reuse, timeouts, and parallelism. The Baseten Performance Client covers common cases; see [Performance client](/inference/performance-client) and [HTTP client configuration](/inference/http-client-configuration) for direct HTTP tuning.
# Performance client
Source: https://docs.baseten.co/inference/performance-client
High-performance client library for embeddings, reranking, classification, and generic batch requests
Built in Rust and integrated with Python, Node.js, and native Rust, the *Performance Client* handles concurrent POST requests efficiently.
It releases the Python GIL while executing requests, enabling simultaneous sync and async usage.
[Benchmarks](https://www.baseten.co/blog/your-client-code-matters-10x-higher-embedding-throughput-with-python-and-rust/) show the Performance Client reaches 1200+ requests per second per client.
Use it with **Baseten deployments** or **third-party providers** like OpenAI.
## Install the client
Install the Performance Client:
```bash Terminal theme={"system"}
uv pip install baseten_performance_client>=0.1.0
```
To install the Performance Client for JavaScript, use npm:
```bash Terminal theme={"system"}
npm install @basetenlabs/performance-client
```
## Get started
To initialize the Performance Client in Python, import the class and provide your base URL and API key:
```python quickstart.py theme={"system"}
from baseten_performance_client import PerformanceClient
client = PerformanceClient(
base_url="https://model-YOUR_MODEL_ID.api.baseten.co/environments/production/sync",
api_key="YOUR_API_KEY"
)
```
To initialize the Performance Client with JavaScript, require the package and create a new instance:
```javascript quickstart.js theme={"system"}
const { PerformanceClient } = require("@basetenlabs/performance-client");
const client = new PerformanceClient(
"https://model-YOUR_MODEL_ID.api.baseten.co/environments/production/sync",
process.env.BASETEN_API_KEY
);
```
The client also works with third-party providers like OpenAI by replacing the `base_url`.
### Advanced setup
Configure HTTP version selection and *connection pooling* for optimal performance.
To configure HTTP version and connection pooling in Python, use the `http_version` parameter and `HttpClientWrapper`:
```python advanced_setup.py theme={"system"}
from baseten_performance_client import PerformanceClient, HttpClientWrapper
# HTTP/1.1 (default, better for high concurrency)
client_http1 = PerformanceClient(BASE_URL, API_KEY, http_version=1)
# HTTP/2 (not recommended on Baseten)
client_http2 = PerformanceClient(BASE_URL, API_KEY, http_version=2)
# Connection pooling for multiple clients
wrapper = HttpClientWrapper(http_version=1)
client1 = PerformanceClient(base_url="https://api1.example.com", client_wrapper=wrapper)
client2 = PerformanceClient(base_url="https://api2.example.com", client_wrapper=wrapper)
```
To configure HTTP version and connection pooling with JavaScript, pass the version as the third argument and use `HttpClientWrapper`:
```javascript advanced_setup.js theme={"system"}
const { PerformanceClient, HttpClientWrapper } = require('@basetenlabs/performance-client');
// HTTP/1.1 (default, better for high concurrency)
const clientHttp1 = new PerformanceClient(BASE_URL, API_KEY, 1);
// HTTP/2
const clientHttp2 = new PerformanceClient(BASE_URL, API_KEY, 2);
// Connection pooling for multiple clients
const wrapper = new HttpClientWrapper(1);
const pooledClient1 = new PerformanceClient(BASE_URL_1, API_KEY, 1, wrapper);
const pooledClient2 = new PerformanceClient(BASE_URL_2, API_KEY, 1, wrapper);
```
## Core features
### Embeddings
The client provides efficient embedding requests with configurable *batching*, concurrency, and latency optimizations. Compatible with [BEI](/engines/bei/overview).
To generate embeddings with Python, configure a `RequestProcessingPreference` and call `client.embed()`:
```python embed.py theme={"system"}
from baseten_performance_client import PerformanceClient, RequestProcessingPreference
client = PerformanceClient(base_url=BASE_URL, api_key=API_KEY)
texts = ["Hello world", "Example text", "Another sample"] * 10
preference = RequestProcessingPreference(
batch_size=16,
max_concurrent_requests=256,
max_chars_per_request=10000,
hedge_delay=0.5,
timeout_s=360,
total_timeout_s=600,
extra_headers={"x-custom-header": "value"}
)
response = client.embed(
input=texts,
model="my_model",
preference=preference
)
print(f"Model used: {response.model}")
print(f"Total tokens used: {response.usage.total_tokens}")
print(f"Total time: {response.total_time:.4f}s")
# Convert to numpy array (requires numpy)
numpy_array = response.numpy()
print(f"Embeddings shape: {numpy_array.shape}")
```
For async usage, call `await client.async_embed(input=texts, model="my_model", preference=preference)`.
To generate embeddings with JavaScript, configure a `RequestProcessingPreference` and call `client.embed()`:
```javascript embed.js theme={"system"}
const { PerformanceClient, RequestProcessingPreference } = require('@basetenlabs/performance-client');
const client = new PerformanceClient(BASE_URL, API_KEY);
const texts = ["Hello world", "Example text", "Another sample"];
const preference = new RequestProcessingPreference(
32, // maxConcurrentRequests
4, // batchSize
360.0, // timeoutS
10000, // maxCharsPerRequest
0.5 // hedgeDelay
);
const response = await client.embed(
texts, // input
"my_model", // model
null, // encodingFormat
null, // dimensions
null, // user
preference // preference parameter
);
console.log(`Model used: ${response.model}`);
console.log(`Total tokens used: ${response.usage.total_tokens}`);
console.log(`Total time: ${response.total_time.toFixed(4)}s`);
```
### Generic batch POST
Send HTTP requests to any URL with any JSON payload. Compatible with [Engine-Builder-LLM](/engines/engine-builder-llm/overview) and other models. Set `stream=False` for SSE endpoints.
To send batch POST requests with Python, define your payloads and call `client.batch_post()`:
```python batch_post.py theme={"system"}
from baseten_performance_client import PerformanceClient, RequestProcessingPreference
client = PerformanceClient(base_url=BASE_URL, api_key=API_KEY)
payloads = [
{"model": "my_model", "prompt": "Batch request 1", "stream": False},
{"model": "my_model", "prompt": "Batch request 2", "stream": False}
] * 10
preference = RequestProcessingPreference(
max_concurrent_requests=96,
timeout_s=720,
hedge_delay=0.5,
extra_headers={"x-custom-header": "value"}
)
response = client.batch_post(
url_path="/v1/completions",
payloads=payloads,
preference=preference,
method="POST"
)
print(f"Total time: {response.total_time:.4f}s")
```
Supported methods: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`.
For async usage, call `await client.async_batch_post(url_path, payloads, preference, method)`.
To send batch POST requests with JavaScript, define your payloads and call `client.batchPost()`:
```javascript batch_post.js theme={"system"}
const { PerformanceClient, RequestProcessingPreference } = require('@basetenlabs/performance-client');
const client = new PerformanceClient(BASE_URL, API_KEY);
const payloads = [
{ model: "my_model", prompt: "Batch request 1", stream: false },
{ model: "my_model", prompt: "Batch request 2", stream: false }
];
const preference = new RequestProcessingPreference(
32, // maxConcurrentRequests
undefined, // batchSize
360.0, // timeoutS
undefined, // maxCharsPerRequest
0.5 // hedgeDelay
);
const response = await client.batchPost(
"/v1/completions",
payloads,
preference,
"POST"
);
console.log(`Total time: ${response.total_time.toFixed(4)}s`);
```
### Reranking
Rerank documents by relevance to a query. Compatible with [BEI](/engines/bei/overview), [BEI-Bert](/engines/bei/overview), and text-embeddings-inference reranking endpoints.
To rerank documents with Python, provide a query and list of documents to `client.rerank()`:
```python rerank.py theme={"system"}
from baseten_performance_client import PerformanceClient, RequestProcessingPreference
client = PerformanceClient(base_url=BASE_URL, api_key=API_KEY)
query = "What is the best framework?"
documents = ["Doc 1 text", "Doc 2 text", "Doc 3 text"]
preference = RequestProcessingPreference(
batch_size=16,
max_concurrent_requests=32,
timeout_s=360,
max_chars_per_request=256000,
hedge_delay=0.5,
extra_headers={"x-rerank-header": "value"}
)
response = client.rerank(
query=query,
texts=documents,
model="rerank-model",
return_text=True,
preference=preference
)
for res in response.data:
print(f"Index: {res.index} Score: {res.score}")
```
For async usage, call `await client.async_rerank(query, texts, model, return_text, preference)`.
To rerank documents with JavaScript, provide a query and list of documents to `client.rerank()`:
```javascript rerank.js theme={"system"}
const { PerformanceClient, RequestProcessingPreference } = require('@basetenlabs/performance-client');
const client = new PerformanceClient(BASE_URL, API_KEY);
const query = "What is the best framework?";
const documents = ["Doc 1 text", "Doc 2 text", "Doc 3 text"];
const preference = new RequestProcessingPreference(
32, // maxConcurrentRequests
16, // batchSize
360.0, // timeoutS
undefined, // maxCharsPerRequest
0.5 // hedgeDelay
);
const response = await client.rerank(query, documents, "rerank-model", true, preference);
response.data.forEach(res => console.log(`Index: ${res.index} Score: ${res.score}`));
```
### Classification
Classify text inputs into categories. Compatible with [BEI](/engines/bei/overview) and text-embeddings-inference classification endpoints.
To classify text with Python, provide a list of inputs to `client.classify()`:
```python classify.py theme={"system"}
from baseten_performance_client import PerformanceClient, RequestProcessingPreference
client = PerformanceClient(base_url=BASE_URL, api_key=API_KEY)
texts_to_classify = [
"This is great!",
"I did not like it.",
"Neutral experience."
]
preference = RequestProcessingPreference(
batch_size=16,
max_concurrent_requests=32,
timeout_s=360.0,
max_chars_per_request=256000,
hedge_delay=0.5,
extra_headers={"x-classify-header": "value"}
)
response = client.classify(
inputs=texts_to_classify,
model="classification-model",
preference=preference
)
for group in response.data:
for result in group:
print(f"Label: {result.label}, Score: {result.score}")
```
For async usage, call `await client.async_classify(inputs, model, preference)`.
To classify text with JavaScript, provide a list of inputs to `client.classify()`:
```javascript classify.js theme={"system"}
const { PerformanceClient, RequestProcessingPreference } = require('@basetenlabs/performance-client');
const client = new PerformanceClient(BASE_URL, API_KEY);
const texts = ["This is great!", "I did not like it.", "Neutral experience."];
const preference = new RequestProcessingPreference(32, 16, 360.0, 256000, 0.5);
const response = await client.classify(texts, "classification-model", preference);
response.data.forEach(group => {
group.forEach(result => console.log(`Label: ${result.label}, Score: ${result.score}`));
});
```
## Advanced features
### Configure RequestProcessingPreference
The `RequestProcessingPreference` class provides unified configuration for all request processing parameters.
To configure request processing in Python, create a `RequestProcessingPreference` instance:
```python preference.py theme={"system"}
from baseten_performance_client import RequestProcessingPreference
preference = RequestProcessingPreference(
max_concurrent_requests=64,
batch_size=32,
timeout_s=30.0,
hedge_delay=0.5,
hedge_budget_pct=0.15,
retry_budget_pct=0.08,
max_retries=3,
initial_backoff_ms=250,
non_retryable_status_codes={529},
total_timeout_s=300.0
)
```
To configure request processing with JavaScript, create a `RequestProcessingPreference` instance:
```javascript preference.js theme={"system"}
const { RequestProcessingPreference } = require('@basetenlabs/performance-client');
const preference = new RequestProcessingPreference(
64, // maxConcurrentRequests
32, // batchSize
30.0, // timeoutS
undefined, // maxCharsPerRequest
0.5, // hedgeDelay
300.0, // totalTimeoutS
0.15, // hedgeBudgetPct
0.08, // retryBudgetPct
3, // maxRetries
250 // initialBackoffMs
);
```
#### Parameter reference
| Parameter | Type | Default | Range | Description |
| ---------------------------- | --------- | ------- | ----------- | ---------------------------------------------------------- |
| `max_concurrent_requests` | int | 128 | 1-1024 | Maximum parallel requests |
| `batch_size` | int | 128 | 1-1024 | Items per batch |
| `timeout_s` | float | 3600.0 | 1.0-7200.0 | Per-request timeout in seconds |
| `hedge_delay` | float | None | 0.2-30.0 | *Hedge delay* in seconds (see below) |
| `hedge_budget_pct` | float | 0.10 | 0.0-3.0 | Percentage of requests allowed for hedging |
| `retry_budget_pct` | float | 0.05 | 0.0-3.0 | Percentage of requests allowed for retries |
| `max_retries` | int | 5 | 0-6 | Maximum HTTP status-code retries per request |
| `initial_backoff_ms` | int | 125 | 50-45000 | Initial retry backoff in milliseconds |
| `non_retryable_status_codes` | Set\[int] | None | - | HTTP status codes to exclude from the default retry policy |
| `total_timeout_s` | float | None | ≥timeout\_s | Total operation timeout |
| `extra_headers` | dict | None | - | Custom headers to include with all requests |
*Hedge delay* sends duplicate requests after a specified delay to reduce p99 latency. After the delay, the request is cloned and raced against the original. The 429 and 5xx errors are always retried automatically.
#### Retry configuration
HTTP status-code retries are controlled by `max_retries` (`maxRetries` in JavaScript), which is separate from `retry_budget_pct`. By default, `408`, `409`, `429`, and `500` through `599` are retried. Set `max_retries` to 0 to disable these retries.
Use `non_retryable_status_codes` (`nonRetryableStatusCodes` in JavaScript) to opt specific status codes out of the default policy. For example, pass `{529}` in Python or `[529]` in JavaScript to stop retrying 529 responses.
Backoff starts at `initial_backoff_ms` (`initialBackoffMs` in JavaScript), multiplies by 4 after each retry, caps at 45000 milliseconds, and adds up to 99 milliseconds of jitter.
### Automatic timeout headers
The Performance Client sends timeout headers with every request so the server can cancel work that exceeds the client's timeout and return an error before the client gives up.
Two headers are derived from the `timeout_s` setting in `RequestProcessingPreference`:
* **`Request-Timeout-Ms`**: relative timeout in milliseconds, rounded up.
* **`Request-Deadline-Ms`**: absolute deadline as a Unix timestamp in milliseconds.
For example, with `timeout_s=30.5`, the client sends:
```text theme={"system"}
Request-Timeout-Ms: 30500
Request-Deadline-Ms: 1715812345678
```
### Select HTTP version
HTTP/1.1 is recommended for high concurrency workloads.
To select the HTTP version in Python, use the `http_version` parameter:
```python http_version.py theme={"system"}
from baseten_performance_client import PerformanceClient
# HTTP/1.1 (default, better for high concurrency)
client_http1 = PerformanceClient(BASE_URL, API_KEY, http_version=1)
# HTTP/2 (better for single requests)
client_http2 = PerformanceClient(BASE_URL, API_KEY, http_version=2)
```
To select the HTTP version with JavaScript, pass the version as the third argument:
```javascript http_version.js theme={"system"}
const { PerformanceClient } = require('@basetenlabs/performance-client');
// HTTP/1.1 (default, better for high concurrency)
const clientHttp1 = new PerformanceClient(BASE_URL, API_KEY, 1);
// HTTP/2 (better for single requests)
const clientHttp2 = new PerformanceClient(BASE_URL, API_KEY, 2);
```
### Share connection pools
Share connection pools across multiple client instances to reduce overhead when connecting to multiple endpoints.
To share a connection pool in Python, create an `HttpClientWrapper` and pass it to each client:
```python shared_pool.py theme={"system"}
from baseten_performance_client import PerformanceClient, HttpClientWrapper
wrapper = HttpClientWrapper(http_version=1)
client1 = PerformanceClient(base_url="https://api1.example.com", client_wrapper=wrapper)
client2 = PerformanceClient(base_url="https://api2.example.com", client_wrapper=wrapper)
```
To share a connection pool with JavaScript, create an `HttpClientWrapper` and pass it to each client:
```javascript shared_pool.js theme={"system"}
const { PerformanceClient, HttpClientWrapper } = require('@basetenlabs/performance-client');
const wrapper = new HttpClientWrapper(1);
const client1 = new PerformanceClient(BASE_URL_1, API_KEY, 1, wrapper);
const client2 = new PerformanceClient(BASE_URL_2, API_KEY, 1, wrapper);
```
### Cancel operations
Cancel long-running operations using `CancellationToken`. The token provides immediate cancellation, resource cleanup, Ctrl+C support, token sharing across operations, and status checking with `is_cancelled()`.
To cancel operations in Python, create a `CancellationToken` and pass it to your preference:
```python cancel.py theme={"system"}
from baseten_performance_client import (
PerformanceClient,
CancellationToken,
RequestProcessingPreference
)
import threading
import time
client = PerformanceClient(base_url=BASE_URL, api_key=API_KEY)
cancel_token = CancellationToken()
preference = RequestProcessingPreference(
max_concurrent_requests=32,
batch_size=16,
timeout_s=360.0,
cancel_token=cancel_token
)
def long_operation():
try:
response = client.embed(
input=["large batch"] * 1000,
model="embedding-model",
preference=preference
)
print("Operation completed")
except ValueError as e:
if "cancelled" in str(e):
print("Operation was cancelled")
threading.Thread(target=long_operation).start()
time.sleep(2)
cancel_token.cancel()
```
To cancel operations with JavaScript, create a `CancellationToken` and pass it to your preference:
```javascript cancel.js theme={"system"}
const { PerformanceClient, CancellationToken, RequestProcessingPreference } = require('@basetenlabs/performance-client');
const client = new PerformanceClient(BASE_URL, API_KEY);
const cancelToken = new CancellationToken();
const preference = new RequestProcessingPreference(
32, 16, 360.0, undefined, undefined, undefined,
undefined, undefined, undefined, undefined, cancelToken
);
const operation = client.embed(
["large batch"].concat(Array(1000).fill("sample")),
"model",
undefined,
undefined,
undefined,
preference
);
setTimeout(() => cancelToken.cancel(), 2000);
try {
const response = await operation;
console.log("Operation completed");
} catch (error) {
if (error.message.includes("cancelled")) {
console.log("Operation was cancelled");
}
}
```
## Handle errors
The client raises standard exceptions for error conditions:
* **`HTTPError`**: Authentication failures (403), server errors (5xx), endpoint not found (404).
* **`Timeout`**: Request or total operation timeout based on `timeout_s` or `total_timeout_s`.
* **`ValueError`**: Invalid input parameters (empty input list, invalid batch size, inconsistent embedding dimensions).
To handle errors in Python, catch the appropriate exception types:
```python handle_errors.py theme={"system"}
import requests
from baseten_performance_client import PerformanceClient, RequestProcessingPreference
client = PerformanceClient(base_url=BASE_URL, api_key=API_KEY)
preference = RequestProcessingPreference(timeout_s=30.0)
try:
response = client.embed(input=["text"], model="model", preference=preference)
print(f"Model used: {response.model}")
except requests.exceptions.HTTPError as e:
print(f"HTTP error: {e}, status code: {e.response.status_code}")
except requests.exceptions.Timeout as e:
print(f"Timeout error: {e}")
except ValueError as e:
print(f"Input error: {e}")
```
To handle errors with JavaScript, use a try-catch block and inspect the error object:
```javascript handle_errors.js theme={"system"}
const { PerformanceClient, RequestProcessingPreference } = require('@basetenlabs/performance-client');
const client = new PerformanceClient(BASE_URL, API_KEY);
const preference = new RequestProcessingPreference(undefined, undefined, 30.0);
try {
const response = await client.embed(texts, "model", undefined, undefined, undefined, preference);
console.log("Success:", response.model);
} catch (error) {
if (error.response) {
console.log(`HTTP error: ${error.response.status}`);
} else if (error.code === 'TIMEOUT') {
console.log("Timeout error");
} else {
console.log(`Error: ${error.message}`);
}
}
```
## Configure the client
### Environment variables
* **`BASETEN_API_KEY`**: Your Baseten API key. Also checks `OPENAI_API_KEY` as fallback.
* **`PERFORMANCE_CLIENT_LOG_LEVEL`**: Logging level. Overrides `RUST_LOG`. Valid values: `trace`, `debug`, `info`, `warn`, `error`. Default: `warn`.
* **`PERFORMANCE_CLIENT_REQUEST_ID_PREFIX`**: Custom prefix for request IDs. Default: `perfclient`.
### Configure logging
To set the logging level, use the `PERFORMANCE_CLIENT_LOG_LEVEL` environment variable:
```bash Terminal theme={"system"}
PERFORMANCE_CLIENT_LOG_LEVEL=info python script.py
PERFORMANCE_CLIENT_LOG_LEVEL=debug cargo run
```
The `PERFORMANCE_CLIENT_LOG_LEVEL` variable takes precedence over `RUST_LOG`.
## Use with Rust
The Performance Client is also available as a native Rust library.
To use the Performance Client in Rust, add the dependencies and create a `PerformanceClientCore` instance:
```rust main.rs theme={"system"}
use baseten_performance_client_core::{PerformanceClientCore, ClientError};
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box> {
let api_key = std::env::var("BASETEN_API_KEY").expect("BASETEN_API_KEY not set");
let base_url = "https://model-YOUR_MODEL_ID.api.baseten.co/environments/production/sync";
let client = PerformanceClientCore::new(base_url, Some(api_key), None, None);
// Generate embeddings
let texts = vec!["Hello world".to_string(), "Example text".to_string()];
let embedding_response = client.embed(
texts,
"my_model".to_string(),
Some(16),
Some(32),
Some(360.0),
Some(256000),
Some(0.5),
Some(360.0),
).await?;
println!("Model: {}", embedding_response.model);
println!("Total tokens: {}", embedding_response.usage.total_tokens);
// Send batch POST requests
let payloads = vec![
serde_json::json!({"model": "my_model", "input": ["Rust sample 1"]}),
serde_json::json!({"model": "my_model", "input": ["Rust sample 2"]}),
];
let batch_response = client.batch_post(
"/v1/embeddings".to_string(),
payloads,
Some(32),
Some(360.0),
Some(0.5),
Some(360.0),
None,
).await?;
println!("Batch POST total time: {:.4}s", batch_response.total_time);
Ok(())
}
```
Add these dependencies to your `Cargo.toml`:
```toml Cargo.toml theme={"system"}
[dependencies]
baseten_performance_client_core = "0.1.4"
tokio = { version = "1.0", features = ["full"] }
serde_json = "1.0"
```
## Related
* [GitHub: baseten-performance-client](https://github.com/basetenlabs/truss/tree/main/baseten-performance-client): Complete source code and additional examples.
* [Performance benchmarks blog](https://www.baseten.co/blog/your-client-code-matters-10x-higher-embedding-throughput-with-python-and-rust/): Detailed performance analysis and comparisons.
# SSH access
Source: https://docs.baseten.co/inference/ssh
Connect to running model deployments directly from your terminal with standard SSH.
SSH into any running model deployment on Baseten and get a full terminal inside the model container: debug, inspect files, run commands, edit code, or transfer data with `scp` and `sftp`. Standard OpenSSH tooling works unchanged, so anything that speaks SSH, from your terminal to your IDE, can reach the container.
## Prerequisites
Inference SSH must be enabled for your organization. [Contact support](mailto:support@baseten.co) to request access.
* **Baseten account**: [Sign up](https://app.baseten.co/) and generate an [API key](https://app.baseten.co/settings/account/api_keys).
* **Baseten CLI**: [Install the CLI](/reference/cli/baseten/overview#install) and log in:
```sh Terminal theme={"system"}
baseten auth login
```
* **OpenSSH client**: Pre-installed on macOS and Linux. On Windows, use the OpenSSH optional feature or WSL.
## Set up your machine
Run setup once per machine. It generates an SSH keypair and adds a managed block to `~/.ssh/config` that routes `*.ssh.baseten.co` connections through the CLI.
**To set up SSH access**:
```sh Terminal theme={"system"}
baseten ssh setup
```
```text Output theme={"system"}
Generated SSH keypair: /Users//.ssh/baseten/id_ed25519
SSH config updated.
Connect with:
deployment: ssh model--.ssh.baseten.co
environment: ssh .model-.ssh.baseten.co
training job: ssh training-job--.ssh.baseten.co
```
Connections authenticate with the profile pinned at setup time. To pin a different profile, pass `--profile `. Re-running setup refreshes the keypair and the managed config block.
If you previously ran `truss ssh setup`, remove its block (between `# --- baseten-ssh ---` and `# --- end baseten-ssh ---`) from `~/.ssh/config` first; setup refuses to touch a file that already configures `*.ssh.baseten.co` hosts outside its own managed block.
**To set up SSH access**:
```sh Terminal theme={"system"}
uvx truss ssh setup
```
```text Output theme={"system"}
SSH keypair: /Users//.ssh/baseten/id_ed25519
Proxy script: /Users//.ssh/baseten/proxy-command.py
SSH config updated: ~/.ssh/config
Default remote:
SSH access configured. Connect to a running workload with:
Training job: ssh training-job--.ssh.baseten.co
Inference model: ssh model--.ssh.baseten.co
```
The Truss setup doesn't support the `.model-` hostname form; use the Baseten CLI to connect by environment name.
## Enable SSH on a deployment
SSH is enabled per deployment in `config.yaml`:
1. Set [`runtime.remote_ssh.enabled`](/reference/truss-configuration#param-remote-ssh) to `true` in your model's `config.yaml`:
```yaml config.yaml {3-5} theme={"system"}
model_name: my-model
runtime:
remote_ssh:
enabled: true
resources:
accelerator: H100
use_gpu: true
```
2. Push the model:
```sh Terminal theme={"system"}
baseten model push
```
SSH access is available as soon as the deployment is `ACTIVE`. Re-deploying without this field disables SSH for the new deployment. Active SSH sessions don't block scale-to-zero or scale-down; for longer interactive sessions, set a non-zero `min_replicas` so your replica isn't reclaimed mid-session.
SSH requires the default container user (`app`, uid `60000`). Setting `docker_server.run_as_user_id` to a different value is incompatible with SSH and the push will fail validation.
## Connect
**To connect to a running deployment**:
1. Find the model and deployment IDs:
```sh Terminal theme={"system"}
baseten model deployment list --model-id abc12345
```
```text Output theme={"system"}
ID NAME ENVIRONMENT STATUS INSTANCE REPLICAS CREATED
def4567 deployment-31 production ACTIVE 4x16 - 4 vCPUs, 16 GiB RAM 1 2026-07-03T22:03:00Z
...
```
Find model IDs with [`baseten model list`](/reference/cli/baseten/model#list).
2. SSH in with the deployment hostname:
```sh Terminal theme={"system"}
ssh model-abc12345-def4567.ssh.baseten.co
```
You're connected when you see a shell prompt inside the model container. Your container runs as the non-root `app` user.
To connect to whatever deployment is currently live in an environment, put the environment name in front of the model ID instead of naming a deployment:
```sh Terminal theme={"system"}
ssh production.model-abc12345.ssh.baseten.co
```
The CLI resolves the environment to its current deployment on every connection, so the same hostname keeps working across promotions. This form requires setup through the Baseten CLI.
## How it works
When you connect to a `*.ssh.baseten.co` hostname, the managed `~/.ssh/config` block runs the CLI. The CLI calls Baseten's signing API to issue a short-lived SSH certificate scoped to that deployment, then relays the connection to a running replica's container. Certificates refresh automatically on every connection, so you never need to manage keys or tokens manually. Authorization uses your existing model permissions, so only users who can manage the model can SSH into it.
## Hostname formats
SSH hostnames take two forms, by deployment or by environment:
```text Hostname theme={"system"}
model--[-].ssh.baseten.co
.model-.ssh.baseten.co
```
| Segment | Description | Example |
| --------------- | ----------------------------------------------------------------------------------------------------------- | ------------ |
| `model_id` | Model ID (8 lowercase alphanumeric characters). Find it with `baseten model list` or in the deployment URL. | `abc12345` |
| `deployment_id` | Deployment ID (7 lowercase alphanumeric characters). Each new push creates a new deployment. | `def4567` |
| `replica_id` | Optional. Suffix that uniquely identifies one replica when the deployment has multiple. | `xyz9a` |
| `environment` | Environment name. Targets the environment's current deployment; not combinable with a deployment ID. | `production` |
Examples:
```sh Terminal theme={"system"}
# Connect to any running replica of this deployment
ssh model-abc12345-def4567.ssh.baseten.co
# Connect to a specific replica by suffix
ssh model-abc12345-def4567-xyz9a.ssh.baseten.co
# Connect to the current deployment in the production environment
ssh production.model-abc12345.ssh.baseten.co
```
## IDE integration
Because setup configures standard OpenSSH, tools that speak SSH can connect with the same hostnames:
* **VS Code**: Install the [Remote - SSH](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh) extension, then connect to `model--.ssh.baseten.co`.
* **Cursor**: Use the built-in SSH remote feature with `model--.ssh.baseten.co`.
## Target a specific replica
Deployments with [autoscaling](/deployment/autoscaling/overview) can have many replicas. By default, Baseten routes your SSH session to one running replica. To pin to a specific replica (useful when reproducing a bug that only appears on one replica), append a unique replica-name suffix to the hostname:
```sh Terminal theme={"system"}
ssh model-abc12345-def4567-xyz9a.ssh.baseten.co
```
Find replica names in the deployment's logs view in the Baseten dashboard or with [`baseten model deployment replica list`](/reference/cli/baseten/model-deployment-replica#list).
Active SSH sessions don't protect a replica from being scaled down. If the autoscaler removes the replica you're connected to, your session is terminated along with it.
## File transfer
Use `scp` or `sftp` with the same hostname to transfer files:
```sh Terminal theme={"system"}
# Copy a file into the model container
scp ./data.json model-abc12345-def4567.ssh.baseten.co:/tmp/data.json
# Copy a file out of the container
scp model-abc12345-def4567.ssh.baseten.co:/tmp/output.json ./output.json
# Interactive file browser
sftp model-abc12345-def4567.ssh.baseten.co
```
## Multiple workspaces
With the Baseten CLI, connections authenticate with the [profile](/reference/cli/baseten/auth) pinned at setup time. To pin a different profile, re-run setup with `--profile `; to override for a single connection, set `BASETEN_PROFILE`:
```sh Terminal theme={"system"}
BASETEN_PROFILE=staging ssh model-abc12345-def4567.ssh.baseten.co
```
With the Truss CLI, include the remote name from `~/.trussrc` in the hostname instead: `model-abc12345-def4567..ssh.baseten.co`.
## Troubleshooting
### "SSH access is not enabled for your organization"
Inference SSH is gated per organization. [Contact support](mailto:support@baseten.co) to request access.
### "ssh proxy rejected the connection; is the workload running with SSH enabled?"
The deployment was pushed without `runtime.remote_ssh.enabled: true`, or it isn't `ACTIVE` with at least one running replica. Add the config field and re-push to create a new deployment with SSH enabled; existing deployments can't be changed in place. If the deployment is scaled to zero, send a request to wake it, or set a non-zero `min_replicas` while debugging.
### "no SSH keypair found; run `baseten ssh setup` first"
Run `baseten ssh setup` to configure your machine.
### "already configures \*.ssh.baseten.co hosts outside the managed block"
Another tool (typically `truss ssh setup`) has already added Baseten SSH entries to `~/.ssh/config`. Remove them and re-run `baseten ssh setup`. The Truss block sits between `# --- baseten-ssh ---` and `# --- end baseten-ssh ---`.
### "baseten was not found on your PATH"
The managed config block invokes `baseten` at connect time. Make sure the binary is on your `PATH` in the environment where you run `ssh` (including inside IDEs, which might launch with a reduced `PATH`).
### Truss CLI: "No api\_key for remote"
Truss 0.17.2 through 0.18.17 stored your API key in the OS keyring, which the Truss SSH proxy can't read. Truss 0.18.18 and later keep the key in `~/.trussrc`. Re-run login on the latest version to move it back:
```sh Terminal theme={"system"}
uvx truss login
```
### Truss CLI: TLS errors
The Truss proxy script requires Python 3.10 or newer. If you see TLS errors, re-run setup with a newer Python interpreter:
```sh Terminal theme={"system"}
uvx truss ssh setup --python $(which python3.12)
```
## Next steps
SSH gives you the fastest loop for debugging a live deployment; pair it with the rest of the deployment tooling:
* [`baseten ssh`](/reference/cli/baseten/ssh): full command reference, including profile pinning and the managed config block.
* [Autoscaling](/deployment/autoscaling/overview): control replica lifecycle so sessions aren't reclaimed mid-debug.
* [SSH for training jobs](/training/ssh): the same workflow for training containers.
# Streaming
Source: https://docs.baseten.co/inference/streaming
Return model output token by token as it is generated.
Streaming refers to returning a model's output incrementally, token by token, as it is generated, rather than holding the response until generation finishes. The caller reads the output as it builds, so the first tokens arrive after the time to first token (TTFT) instead of after the entire response.
Baseten supports streaming across a range of inference surfaces: [Model APIs](/inference/model-apis/overview) (hosted, OpenAI- and Anthropic-compatible endpoints), [BIS-LLM](/engines/bis-llm/overview), and dedicated deployments of models packaged with [Truss](/development/model/overview). [Custom Docker containers](/development/model/custom-server) that expose an OpenAI-compatible API, such as vLLM and SGLang, stream the same way.
Use streaming when:
* Generating the complete output takes a relatively long time.
* The first tokens are useful without the rest of the output.
* Reducing the time to first token improves the user experience.
Chat applications backed by LLMs are the clearest example.
## Enable streaming
Streaming is a per-request flag: set it on your call, then read the response as it arrives. The flag is the same everywhere; only the base URL and model slug differ.
```python Truss theme={"system"}
# Self-deployed Truss model: stream from the model's predict endpoint
import os
import requests
model_id = "YOUR_MODEL_ID"
with requests.post(
f"https://model-{model_id}.api.baseten.co/production/predict",
headers={"Authorization": f"Bearer {os.environ['BASETEN_API_KEY']}"},
json={"prompt": "Write a haiku about the ocean.", "stream": True},
stream=True,
) as resp:
for chunk in resp.iter_content():
print(chunk.decode("utf-8"), end="", flush=True)
```
```python OpenAI theme={"system"}
# Model APIs: OpenAI-compatible endpoint at inference.baseten.co
import os
from openai import OpenAI
client = OpenAI(
base_url="https://inference.baseten.co/v1",
api_key=os.environ["BASETEN_API_KEY"],
)
stream = client.chat.completions.create(
model="deepseek-ai/DeepSeek-V4-Pro",
messages=[{"role": "user", "content": "Write a haiku about the ocean."}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="", flush=True)
```
```python Anthropic theme={"system"}
# Model APIs: Anthropic-compatible endpoint (beta) at inference.baseten.co
import os
import anthropic
api_key = os.environ["BASETEN_API_KEY"]
client = anthropic.Anthropic(
base_url="https://inference.baseten.co",
api_key=api_key,
default_headers={"Authorization": f"Bearer {api_key}"},
)
with client.messages.stream(
model="deepseek-ai/DeepSeek-V4-Pro",
max_tokens=4096,
messages=[{"role": "user", "content": "Write a haiku about the ocean."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
```
```bash cURL theme={"system"}
# Model APIs: add "stream": true and keep the connection open with --no-buffer
curl https://inference.baseten.co/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "deepseek-ai/DeepSeek-V4-Pro",
"messages": [{"role": "user", "content": "Write a haiku about the ocean."}],
"stream": true
}' \
--no-buffer
```
Streaming changes when the caller sees output, not how much the model produces. The following diagram puts both delivery modes on one clock.
The top lane streams: after a short prefill, tokens fill in one at a time from the first-token mark (TTFT). The bottom lane is non-streaming: it stays empty through the same generation, then the whole response lands at once at the end. Both finish together, so the only difference is when the caller first sees output. Token timing here is illustrative, not a measured latency.
# Structured outputs
Source: https://docs.baseten.co/inference/structured-outputs
JSON schema validation and controlled text generation across all engines
Structured outputs let you generate text that conforms to specific JSON schemas, providing reliable data extraction and controlled text generation. [Model APIs](/inference/model-apis/overview) support structured outputs. For self-deployed models, Baseten engines like [BIS-LLM](/engines/bis-llm/overview) and [Engine-Builder-LLM](/engines/engine-builder-llm/overview) support them, as do other inference frameworks like [vLLM](/examples/vllm) and [SGLang](/examples/sglang).
## Quick start
Structured outputs require two components: a Pydantic schema defining your expected output format, and an API call that enforces that schema.
### Define a schema
Define a Pydantic model whose fields describe the output you want:
```python schema.py theme={"system"}
from pydantic import BaseModel
class Task(BaseModel):
title: str
priority: str # "low", "medium", "high"
due_date: str
description: str
```
Each field requires a type annotation. The model's response will conform to these types exactly.
### Generate structured output
Pass the schema to the parse method and read the typed result. The code is the same on both surfaces; only the base URL and model name differ.
Point the client at `https://inference.baseten.co/v1` and pass a model slug from the [supported models table](/inference/model-apis/overview#supported-models):
```python structured_output.py theme={"system"}
import os
from pydantic import BaseModel
from openai import OpenAI
class Task(BaseModel):
title: str
priority: str
due_date: str
description: str
client = OpenAI(
api_key=os.environ['BASETEN_API_KEY'],
base_url="https://inference.baseten.co/v1"
)
response = client.beta.chat.completions.parse(
model="moonshotai/Kimi-K2.6",
messages=[
{"role": "user", "content": "Create a task for: Review the quarterly report by next Friday"}
],
response_format=Task
)
task = response.choices[0].message.parsed
print(f"Task: {task.title}")
print(f"Priority: {task.priority}")
```
Point the client at your deployment's production endpoint; the `model` field is required by the SDK but ignored by the deployment:
```python structured_output.py theme={"system"}
import os
from pydantic import BaseModel
from openai import OpenAI
class Task(BaseModel):
title: str
priority: str
due_date: str
description: str
client = OpenAI(
api_key=os.environ['BASETEN_API_KEY'],
base_url="https://model-xxxxxx.api.baseten.co/environments/production/sync/v1"
)
response = client.beta.chat.completions.parse(
model="not-required",
messages=[
{"role": "user", "content": "Create a task for: Review the quarterly report by next Friday"}
],
response_format=Task
)
task = response.choices[0].message.parsed
print(f"Task: {task.title}")
print(f"Priority: {task.priority}")
```
Running either version prints the parsed fields:
```text Output theme={"system"}
Task: Review the quarterly report
Priority: high
```
Pass your Pydantic class to `response_format` and use `beta.chat.completions.parse` instead of the regular `create` method.
The response includes a `parsed` attribute with your data already converted to a `Task` object, so no JSON parsing is needed.
### Use with LangChain
Because Baseten exposes an OpenAI-compatible endpoint, you can use LangChain's `ChatOpenAI` with `with_structured_output` by pointing `base_url` at Baseten:
```python langchain_structured_output.py theme={"system"}
import os
from pydantic import BaseModel
from langchain_openai import ChatOpenAI
class Task(BaseModel):
title: str
priority: str
due_date: str
description: str
# Model APIs: use "https://inference.baseten.co/v1" and a model slug.
# Dedicated deployment: use your production sync endpoint; the model field is
# required by the SDK but ignored by the deployment.
llm = ChatOpenAI(
api_key=os.environ["BASETEN_API_KEY"],
base_url="https://inference.baseten.co/v1",
model="moonshotai/Kimi-K2.6",
)
structured_llm = llm.with_structured_output(Task)
task = structured_llm.invoke("Create a task for: Review the quarterly report by next Friday")
print(f"Task: {task.title}")
print(f"Priority: {task.priority}")
```
See the [LangChain integration](/inference/integrations) for other ways to use Baseten with LangChain.
## Engine support
Structured outputs are compatible with:
* **Engine-Builder-LLM**, except when Lookahead speculative decoding is configured.
* **BIS-LLM**, except in some configurations, such as when the overlap scheduler is enabled.
### Model support
All Engine-Builder-LLM and BIS-LLM models support structured outputs with no extra configuration.
## Best practices
### Schema design
* **Keep schemas simple**: two to three levels of nesting for best results.
* **Use basic types**: str, int, float, bool when possible.
* **Set defaults**: Provide reasonable default values for optional fields.
* **Descriptive names**: Use clear, descriptive field names.
### Prompt engineering
* **Low temperature**: Use 0.1 to 0.3 for consistent outputs.
* **Provide schema**: Dump the model schema and few-shot examples into context.
* **Provide context**: Give background for complex schemas.
## Related
* [Engine-Builder-LLM overview](/engines/engine-builder-llm/overview): Dense model documentation.
* [BIS-LLM overview](/engines/bis-llm/overview): MoE model documentation.
* [Quantization guide](/engines/performance-concepts/quantization-guide): `FP8`/`FP4` trade-offs.
# Concepts
Source: https://docs.baseten.co/loops/concepts
How Loops sessions, trainers, samplers, and checkpoints fit together.
A Loops session pairs a trainer server with a sampling server so that trained weights move to the sampler as soon as they exist. The trainer runs forward, backward, and optimizer steps; the sampler generates from current weights. Both live inside the same session and share a weight-sync path from the moment you provision them.
Unlike offline training, where you finish a run, save a checkpoint, and then reload weights into a separate inference process, Loops keeps the sampler in sync throughout. When the trainer saves weights, the sampling server picks them up without restarting. The sampler you query at step 100 is running the same weights the trainer committed.
## Sessions
A Loops session is the container resource that scopes a training project's work. It holds the trainer server and sampling server for a given base model and links them to a Baseten training project. Everything you create within a session (trainer servers, sampling servers, checkpoints) is queryable through that session's ID. For the full route reference, see the [Loops API overview](/reference/loops-api/overview).
## Trainer servers
A trainer server is the process that runs the training computation: forward pass, backward pass, and optimizer step. It owns the model weights for the duration of the session and writes checkpoints to a dedicated storage path under a `bt://loops:…` URI; the [Checkpoints](#checkpoints) section covers the format. There is one trainer per session per base model.
You don't size the trainer yourself. It defaults to the longest sequence length the base model supports, and Baseten picks the GPU type, GPU count, and node topology (single-node or multi-node) to match. When you call [`POST /v1/loops/runs`](/reference/loops-api/runs/create-a-run), Baseten provisions the trainer alongside its paired sampler and returns both resource IDs.
The API route calls a trainer a "run". Both the HTTP API and the SDK identify it by its run ID: the API takes a `run_id` query parameter, and the SDK exposes the same value as [`TrainingClient.run_id`](/reference/sdk/loops/training-client).
## Sampling servers
A sampling server runs inference from the trainer's current weights. It's provisioned alongside the trainer and linked to it at creation time. The sampler receives new weights through the weight-sync runtime whenever the trainer saves them. See [How weight sync works](#how-weight-sync-works) for the mechanism. Because the sampler doesn't restart during a session, generation latency stays low even as weights change, and you can interleave training steps and rollout calls without coordinating reloads.
## Checkpoints
Every time the trainer saves weights, Loops creates a checkpoint identified by a `bt://loops:/(weights|sampler_weights)/` URI. The URI encodes the run ID, the checkpoint target (trainer weights or sampler weights), and the checkpoint name, for example, `bt://loops:k4q95w5/weights/step-100`. You pass this URI to create a trainer or sampler server from a prior checkpoint, or to deploy weights to inference.
Checkpoints are stored as folders on disk, not as single archives. Listing checkpoint files returns a paginated response of presigned URLs, one URL per file in the folder, controlled by `page_size` and `page_token` query parameters. This differs from Tinker's single-archive download shape: Tinker returns one URL you download and unpack; Loops returns a page of per-file URLs you fetch individually. If your client code unpacks a Tinker archive today, you'll need to adapt it to iterate the paginated file list instead. The route is [`GET /v1/loops/checkpoints/{checkpoint_id}/files`](/reference/loops-api/checkpoints/get-checkpoint-files).
## Deployments
A Loops deployment is the trainer and sampler you create at the start of a session. They stay live as you train, and weights you commit stream into the sampler in place, with no separate deploy step for inference.
Start a deployment with [`truss loops push `](/reference/cli/loops/loops-cli#push). Shut it down with [`truss loops deactivate `](/reference/cli/loops/loops-cli#deactivate), using the deployment ID from [`truss loops view`](/reference/cli/loops/loops-cli#view).
## Reuse infrastructure across sessions
By default, every new `ServiceClient` creates a fresh session, which provisions a new trainer and sampler. Each re-run of a script pays the full cold-start cost.
A session can opt in to reusing a prior session's trainer and sampler instead of provisioning new ones. Three equivalent surfaces:
* **SDK kwarg**: `tinker.ServiceClient(reuse_from_session_id="2qjl22w")`.
* **Environment variable**: `LOOPS_REUSE_FROM_SESSION_ID=2qjl22w`. `ServiceClient` reads this when no kwarg is passed.
* **HTTP request**: `reuse_from_session_id` field on [`POST /v1/loops/runs`](/reference/loops-api/runs/create-a-run) and [`POST /v1/loops/samplers`](/reference/loops-api/samplers/create-a-sampler).
Reuse is best-effort. The named session must belong to the same team. If the prior trainer is stopped, failed, or unhealthy, the backend falls back to provisioning fresh and the call still succeeds. See [Skip the cold start on re-runs](/loops/quickstart#skip-the-cold-start-on-re-runs) for the script workflow.
## How weight sync works
When a trainer saves weights, the paired sampling server picks them up through a vLLM plugin. The plugin handles the sync without restarting the sampler, so generation can resume immediately at the new weights.
## Supported base models
Loops supports a curated set of Hugging Face base models with verified LoRA configurations. See [Supported base models](/loops/supported-models) for the current list and sequence-length limits.
# Deploy a checkpoint
Source: https://docs.baseten.co/loops/deploy-checkpoints
Turn a Loops sampler checkpoint into a dedicated inference deployment and call it.
A sampler checkpoint deploys to a dedicated Baseten inference deployment that serves the base model with your LoRA adapter loaded. Deployment works whether or not the training session is still running: checkpoints outlive the session, so you can [shut the trainer and sampler down](/loops/quickstart#shut-down-the-session) first and deploy later.
Deploying needs an `hf_access_token` in [workspace secrets](/organization/secrets), because the deployment downloads the base weights from Hugging Face.
## Deploy from the CLI
Run [`truss loops checkpoints deploy`](/reference/cli/loops/loops-cli#checkpoints-deploy) with the checkpoint's globally unique `id` (the `id` field from [listing checkpoints](/loops/quickstart#list-checkpoints), not the checkpoint name):
```bash theme={"system"}
uvx truss loops checkpoints deploy --checkpoint-ids
```
The `--checkpoint-ids` flag skips the checkpoint picker, but the command still prompts for a model name, GPU type, GPU count, and the Hugging Face secret name (default `hf_access_token`). A successful deploy prints the IDs you need to call the model:
```output theme={"system"}
Successfully created deployment: deployment-1
Model ID: wnpkzp03
Deployment ID: qrpg2r0
Deployment succeeded.
Set the model parameter on each request to the Loops checkpoint name (e.g. step-100).
```
The deployment downloads base weights and starts the inference server, so several minutes pass before it reaches `ACTIVE`.
To inspect the generated Truss config without deploying, pass `--dry-run`.
## Deploy from the dashboard
Every Loop has a page in the dashboard at `app.baseten.co/training/loop/`, reachable from the Training tab. Its Checkpoints table lists each saved checkpoint with its `bt://` path, type, and size, and is where you deploy one; the flow matches [deploying job checkpoints](/training/getting-started#deploy-your-trained-model).
## Call the deployed model
Call the deployment's OpenAI-compatible chat completions route. The URL includes the deployment ID, and `model` is the checkpoint *name* (`checkpoint_id`), not its globally unique `id`:
```bash Request theme={"system"}
curl -X POST "https://model-.api.baseten.co/deployment//sync/v1/chat/completions" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "step-1",
"messages": [{"role": "user", "content": "What is the capital of France?"}]
}'
```
```json Output theme={"system"}
{
"model": "step-1",
"choices": [
{
"message": {
"role": "assistant",
"content": "The capital of France is **Paris**.\n\nLocated in northeastern France…"
},
"finish_reason": "length"
}
]
}
```
For request options, streaming, and client libraries, see [calling your model](/inference/calling-your-model).
## Delete the deployment
The deployment bills for its GPU while it's live. Delete it with [`DELETE /v1/models/{model_id}`](/reference/management-api/models/deletes-a-model-by-id) when you're done:
```bash Request theme={"system"}
curl -X DELETE "https://api.baseten.co/v1/models/" \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Output theme={"system"}
{"id": "wnpkzp03", "deleted": true}
```
Deleting the deployment doesn't touch the checkpoint. You can redeploy it any time.
## Next steps
* **[Train on a dataset](/loops/train-on-your-data)**: Move from the quickstart's single example to a real training loop.
* **[Loops concepts](/loops/concepts)**: How checkpoints relate to sessions, trainers, and samplers.
# Loss functions
Source: https://docs.baseten.co/loops/loss-functions
Loss functions supported by the Loops trainer, with data shapes and minimal snippets.
The Loops trainer accepts several loss functions through the `loss_fn` argument on [`forward_backward()`](/reference/sdk/loops/training-client). Each loss reads a different set of per-position fields from `Datum.loss_fn_inputs`, and some take a `loss_fn_config` dict to tune hyperparameters.
## `cross_entropy`
Standard supervised fine-tuning. Each `Datum` carries per-token targets under `target_tokens`, with `-100` marking positions the loss should ignore (typically the prompt). `forward_backward` does not shift labels internally, so shift when you tokenize: the label at position `i` is the token at position `i+1`.
```python theme={"system"}
from baseten.loops import Datum, ModelInput, TensorData
datum = Datum(
model_input=ModelInput.from_ints(tokens),
loss_fn_inputs={
"target_tokens": TensorData(
data=targets, dtype="int64", shape=[len(targets)]
),
},
)
fb = training_client.forward_backward(data=[datum]).result(timeout=600.0)
```
`cross_entropy` takes no `loss_fn_config`. See the [quickstart](/loops/quickstart) for the full tokenization step.
## `importance_sampling`, `ppo`, `dppo`, `cispo`, `dro`
On-policy reinforcement learning. Each `Datum`'s `loss_fn_inputs` must include per-position `logprobs` (from the behavior policy at rollout time) and `advantages` (from your reward model or return-to-go).
```python theme={"system"}
L = len(tokens)
datum = Datum(
model_input=ModelInput.from_ints(tokens),
loss_fn_inputs={
"logprobs": TensorData(data=behavior_logprobs, dtype="float32", shape=[L]),
"advantages": TensorData(data=advantages, dtype="float32", shape=[L]),
},
)
fb = training_client.forward_backward(
data=[datum],
loss_fn="ppo", # or "importance_sampling"
).result(timeout=600.0)
```
All five take the same `loss_fn_inputs` shape and differ in clipping and regularization: `importance_sampling` doesn't clip the ratio; `ppo` clips it; `dppo`, `cispo`, and `dro` add further variants. All are typically driven by a rollout loop that samples from a paired [`SamplingClient`](/reference/sdk/loops/sampling-client) and scores each trajectory before calling `forward_backward`.
The trainer accepts `dppo` and `dpo`, but they aren't in the SDK's `LossFnType` literal yet, so type-checked code needs `# type: ignore[arg-type]` on those calls.
## `dpo`
Direct Preference Optimization. Trains on chosen/rejected preference pairs against a frozen reference policy. There are three additional pieces compared to SFT:
1. **Snapshot the reference before any `optim_step`.** The initial weights are the reference for offline DPO.
2. **Interleave the batch** as `[chosen_0, rejected_0, chosen_1, rejected_1, ...]`. Pairing is by position, and the SDK rejects odd-length batches before submitting anything.
3. **Score reference logprobs once** with [`attach_reference_logprobs`](/reference/sdk/loops/helpers) and reuse across every step.
Each `Datum` needs three `loss_fn_inputs` fields. You build the first two yourself when tokenizing; the third comes from `attach_reference_logprobs`:
* `target_tokens`: labels shifted by one, `-100` over the prompt positions.
* `weights`: `0.0` over the prompt, `1.0` over the response. The DPO log-ratio sums logprobs weighted by this mask.
* `ref_logprobs`: frozen reference logprobs, one per position. Populated automatically by `attach_reference_logprobs`.
```python theme={"system"}
from baseten.loops import AdamParams, Datum
from baseten.loops.helpers.datum import attach_reference_logprobs
# 1. Snapshot the reference policy. This blocks until the snapshot is
# published and returns a SamplingClient directly.
reference = training_client.save_weights_and_get_sampling_client(name="dpo-ref")
# 2. Build interleaved chosen/rejected datums with target_tokens and
# weights set as above, then score them once.
datums = attach_reference_logprobs(datums, reference)
# 3. Train.
fb = training_client.forward_backward(
data=datums,
loss_fn="dpo",
loss_fn_config={"beta": 0.1}, # optionally: "label_smoothing": 0.0
).result(timeout=600.0)
training_client.optim_step(AdamParams(learning_rate=1e-5)).result(timeout=600.0)
```
**`loss_fn_config` keys**:
* `beta` (float, default `0.1`): KL-penalty strength against the reference.
* `label_smoothing` (float, default `0.0`, range `[0, 0.5]`): enables conservative DPO (cDPO).
**Metrics on `fb.metrics`**: the DPO pass reports `loss`, the mean DPO loss over the batch. A fresh policy starts near `ln 2` (about `0.693`) and decreases as the policy learns to prefer chosen responses.
## Next steps
* [`TrainingClient` reference](/reference/sdk/loops/training-client) for the full `forward_backward()` signature.
* [Helpers reference](/reference/sdk/loops/helpers) for `attach_reference_logprobs`.
* [Loops concepts](/loops/concepts) explains the paired trainer + sampler model that makes reference-policy snapshots possible.
# Loops
Source: https://docs.baseten.co/loops/overview
Run Tinker-compatible SFT and async RL at long sequence lengths, then deploy checkpoints to the Baseten Inference Stack.
Loops is in early access. [Fill out the signup form](https://www.baseten.co/talk-to-us/loops-signup/) to request access for your workspace.
Loops is a Tinker-compatible training SDK for post-training large models at long sequence lengths. It lets you deploy dedicated training and sampling servers for any [supported base model](/loops/supported-models), then run your existing Tinker scripts with minimal changes.
## How Loops works
A Loops session pairs a trainer with a sampler. The trainer runs forward, backward, and optimizer steps; the sampler generates from the latest weights the trainer publishes. They scale independently, so RL rollouts don't compete with training for compute, and you can await weight transfers synchronously or asynchronously to stay on-policy or run bounded off-policy algorithms.
Checkpoints are yours: download them as presigned URLs or deploy them to the Baseten Inference Stack through the UI, CLI, or API.
The [Training overview](/training/overview) compares Loops with Training Jobs (the bring-your-own-container alternative) side by side.
## Where to go next
The [Loops quickstart](/loops/quickstart) runs the full session lifecycle: train a step, sample from the tuned weights, list the checkpoint, and shut the servers down.
The [Loops concepts](/loops/concepts) page covers sessions, trainers, samplers, and checkpoints, and how weight sync keeps the trainer and sampler in step.
The [Tinker compatibility](/loops/tinker-compatibility) page lists which Tinker API calls work unchanged in Loops and what behaves differently.
# Quickstart
Source: https://docs.baseten.co/loops/quickstart
Train one step with Loops, sample from the tuned weights, and shut the session down.
Use the Loops Python SDK to create a LoRA training run, save a checkpoint, generate text from those weights, and list the checkpoint from both Python and the HTTP API. At the end, you shut down the servers you provisioned. The base model throughout is `Qwen/Qwen3.5-2B`, one of the [supported base models](/loops/supported-models).
## Prerequisites
* **Python 3.12+ and [uv](https://docs.astral.sh/uv/)**: The quickstart uses `uv` to install the Loops client and run the training script.
* **API key**: A [workspace API key](/organization/api-keys) with org access to Loops, exported as `BASETEN_API_KEY`.
Loops is in early access. To enable it for your workspace, [fill out the signup form](https://www.baseten.co/talk-to-us/loops-signup/).
## Install
Install `baseten-loops` with the `[tinker]` extra into a uv project. Create one first if you don't have it:
```bash theme={"system"}
uv init loops-quickstart
cd loops-quickstart
uv add 'baseten-loops[tinker]'
```
The `[tinker]` extra pulls in `baseten-loops-tinker`, which re-exports the public API under the `tinker` namespace so existing `import tinker` scripts run unchanged.
Verify the install by running `uv run python train_loops.py`:
```python train_loops.py theme={"system"}
import tinker
from importlib.metadata import version
print(tinker.ServiceClient)
print("baseten-loops-tinker", version("baseten-loops-tinker"))
```
```output Output theme={"system"}
baseten-loops-tinker x.x.x
```
The printed class path and resolved `baseten-loops-tinker` version confirm Baseten's Tinker compatibility package is installed, not the upstream `tinker` package.
## Provision a trainer
A Loops session pairs a trainer server (forward, backward, and optimizer steps) with a sampling server (generates from current weights). Constructing a [`ServiceClient`](/reference/sdk/loops/service-client) and calling [`create_lora_training_client()`](/reference/sdk/loops/service-client) provisions both and returns a [`TrainingClient`](/reference/sdk/loops/training-client). The call blocks until the trainer is ready, which takes several minutes for a small base model like this one and can reach tens of minutes for the largest supported models. The SDK gives up waiting after an hour.
Replace the contents of `train_loops.py` with the provision step:
```python train_loops.py theme={"system"}
import tinker
BASE_MODEL = "Qwen/Qwen3.5-2B"
service_client = tinker.ServiceClient()
training_client = service_client.create_lora_training_client(
base_model=BASE_MODEL,
rank=16,
)
print(f"session_id={service_client.session_id}")
print(f"run_id={training_client.run_id}")
```
You'll append the training, sampling, and listing steps to this same file in the next three sections, then run the whole thing once at the end.
Provisioning starts GPU servers in your workspace that keep running after your script exits, so plan to finish with the [shut down step](#shut-down-the-session) below.
## Run a training round trip
The smallest complete round trip is one forward pass, one backward pass, one optimizer step, and one weight save. The block below mirrors the canonical supervised fine-tuning (SFT) example: it tokenizes a prompt-and-answer pair, masks the prompt positions from the loss, runs the round trip, and saves a named checkpoint.
Append to `train_loops.py`:
```python train_loops.py theme={"system"}
def build_sft_datum(tokenizer, prompt, answer):
p = tokenizer.encode(prompt, add_special_tokens=False)
a = tokenizer.encode(answer, add_special_tokens=False)
# Loops/tinker forward_backward does NOT shift labels internally (unlike HF
# Trainer). Shift here so logits at position i predict token i+1:
# drop the final input token and the first prompt mask position.
full = p + a
tokens = full[:-1]
targets = [-100] * (len(p) - 1) + list(a) # mask prompt, supervise answer
return tokens, targets
tokens, targets = build_sft_datum(
training_client.get_tokenizer(),
prompt="What is the capital of France?\nAnswer:",
answer=" Paris",
)
datum = tinker.Datum(
model_input=tinker.ModelInput.from_ints(tokens),
loss_fn_inputs={
"target_tokens": tinker.TensorData(
data=targets, dtype="int64", shape=[len(targets)]
)
},
)
fb = training_client.forward_backward(data=[datum]).result(timeout=600.0)
print(f"loss={fb.loss:.6f}")
optim = training_client.optim_step(
tinker.AdamParams(learning_rate=4e-5)
).result(timeout=600.0)
print(f"optim_metrics={optim.metrics}")
save_resp = training_client.save_weights_for_sampler(name="step-1").result(timeout=600.0)
print(f"saved checkpoint at {save_resp.path}")
```
[`forward_backward()`](/reference/sdk/loops/training-client) is the first training operation you submit after provisioning. [`save_weights_for_sampler()`](/reference/sdk/loops/training-client) publishes a sampler checkpoint under `sampler_weights/` that you can deploy to inference. This checkpoint omits optimizer state, so you can't resume training from it; use [`save_state()`](/reference/sdk/loops/training-client) when you need a resumable checkpoint.
## Sample from the tuned weights
The checkpoint you saved is already on the paired sampler, so you can generate from it without deploying anything. [`create_sampling_client()`](/reference/sdk/loops/training-client) takes the [`bt://` URI](/loops/concepts#checkpoints) that `save_weights_for_sampler()` returned and binds a [`SamplingClient`](/reference/sdk/loops/sampling-client) to those weights. Append to `train_loops.py`:
```python train_loops.py theme={"system"}
tokenizer = training_client.get_tokenizer()
sampling_client = training_client.create_sampling_client(model_path=save_resp.path)
sample = sampling_client.sample(
prompt=tinker.ModelInput.from_ints(
tokenizer.encode("What is the capital of France?\nAnswer:", add_special_tokens=False)
),
num_samples=1,
sampling_params=tinker.SamplingParams(max_tokens=8),
)
print(f"completion={tokenizer.decode(sample.sequences[0].tokens)!r}")
```
One optimizer step barely changes a 2B model, so the completion reads like base-model output. Still, the sampler served the `step-1` weights your trainer published seconds earlier, without a restart or a deploy step in between. In a longer run, this same call is how you evaluate checkpoints mid-training.
## List checkpoints
Every `save_weights_for_sampler()` call creates a checkpoint. The bound `TrainingClient` lists them with [`list_checkpoints()`](/reference/sdk/loops/training-client), no arguments needed. Append to `train_loops.py`:
```python train_loops.py theme={"system"}
for ckpt in training_client.list_checkpoints():
print(ckpt.id, ckpt.checkpoint_id, ckpt.created_at)
```
Now run the full script. Output values vary, but a successful run prints a session ID, run ID, loss, optimizer metrics, saved checkpoint URI, a sampled completion, and one listed checkpoint:
```bash theme={"system"}
uv run python train_loops.py
```
```output theme={"system"}
session_id=2qjl22w
run_id=e3mvjo3
loss=2.456638
optim_metrics={'step': 1.0, 'learning_rate': 4e-05, 'lr': 4e-05, 'grad_norm': 74.11, ...}
saved checkpoint at bt://loops:e3mvjo3/sampler_weights/step-1
completion=' 1. France is a country in'
VqKXRGB step-1 2026-07-01 20:50:39.854000+00:00
```
You might also see warnings from `transformers` about PyTorch being unavailable and from the Hugging Face Hub about unauthenticated requests. Both are harmless here: the client only uses `transformers` for tokenization, and the tokenizer download works without a token.
The HTTP API returns the same listing for scripts and CI pipelines that don't run Python. Use the `run_id` your script printed when provisioning. The response includes the same globally unique `id` and checkpoint name:
```bash Request theme={"system"}
curl --request GET \
--url "https://api.baseten.co/v1/loops/checkpoints?run_id=" \
--header "Authorization: Bearer $BASETEN_API_KEY"
```
```json Output theme={"system"}
{
"checkpoints": [
{
"checkpoint_id": "step-1",
"created_at": "2026-07-01T20:50:39.854Z",
"checkpoint_type": "lora",
"base_model": "Qwen/Qwen3.5-2B",
"lora_adapter_config": {
"r": 16
},
"size_bytes": 33638400,
"sync_status": null,
"id": "VqKXRGB",
"run_id": "e3mvjo3",
"target": "sampler"
}
]
}
```
To fetch the weight files, call [`get_checkpoint_archive_url()`](/reference/sdk/loops/training-client) with the globally unique `id` value as the `checkpoint_id` argument. From a separate Python session where `training_client` isn't in scope, construct `tinker.ServiceClient()` and call the same method on it. If the checkpoint files live in S3, export `S3_REGION` to that bucket's AWS region first, for example `export S3_REGION=us-west-2`.
## Skip the cold start on re-runs
Your first run provisioned a trainer and sampler. The second run doesn't have to. Grab the `session_id` your script printed (`session_id=2qjl22w` in the example output above), point the next run at it, and Loops reuses the same trainer and sampler:
```bash theme={"system"}
export LOOPS_REUSE_FROM_SESSION_ID=2qjl22w
uv run python train_loops.py
```
You can also pass the ID directly in code, which wins if both the kwarg and the environment variable are set:
```python theme={"system"}
service_client = tinker.ServiceClient(reuse_from_session_id="2qjl22w")
```
From the HTTP API, send `reuse_from_session_id` in the body of [`POST /v1/loops/runs`](/reference/loops-api/runs/create-a-run) or [`POST /v1/loops/samplers`](/reference/loops-api/samplers/create-a-sampler).
Reuse is best-effort. If the prior trainer is stopped, failed, or unhealthy, Loops provisions a fresh one and your script still runs.
## Shut down the session
You're billed for the trainer and sampler's GPUs until you deactivate them. When you're done experimenting, check what's live and shut it down:
```bash theme={"system"}
uvx truss loops view
uvx truss loops deactivate --yes
```
[`truss loops view`](/reference/cli/loops/loops-cli#view) lists the deployments that are still running, with the ID, base model, and status of each. Pass the `Deployment ID` to [`deactivate`](/reference/cli/loops/loops-cli#deactivate). Your checkpoints survive the shutdown: you can still list them, fetch their files, and deploy them to inference afterward.
## Next steps
* **[Deploy a checkpoint](/loops/deploy-checkpoints)**: Serve `step-1` as a dedicated inference deployment and call it over the OpenAI-compatible route.
* **[Train on a dataset](/loops/train-on-your-data)**: Replace the single example with a batched loop, resumable checkpoints, and mid-training evals.
* **[Loops concepts](/loops/concepts)**: Sessions, trainers, samplers, checkpoints, and how weight sync works.
* **[Tinker compatibility](/loops/tinker-compatibility)**: What carries over from Tinker unchanged and what differs: checkpoint layout, authentication, and cluster routing.
* **[Loops API reference](/reference/loops-api/overview)**: Every HTTP route, for scripting deployments and CI pipelines.
# Supported base models
Source: https://docs.baseten.co/loops/supported-models
Hugging Face base models Loops accepts, with sequence-length limits.
Each row below is a Hugging Face repo ID you can pass as `base_model` when starting a Loops run, along with the maximum supported sequence length. Baseten adds rows as it validates new models end to end.
## Models
| Model | Max sequence length |
| ------------------------------------------------ | ------------------- |
| `Qwen/Qwen3-0.6B` | 8,192 |
| `Qwen/Qwen3-4B-Instruct-2507` | 40,960 |
| `Qwen/Qwen3-8B` | 40,960 |
| `Qwen/Qwen3-30B-A3B-Instruct-2507` | 131,072 |
| `Qwen/Qwen3.5-0.8B` | 131,072 |
| `Qwen/Qwen3.5-2B` | 131,072 |
| `Qwen/Qwen3.5-4B` | 131,072 |
| `Qwen/Qwen3.5-9B` | 131,072 |
| `Qwen/Qwen3.5-27B` | 131,072 |
| `Qwen/Qwen3.5-35B-A3B` | 131,072 |
| `Qwen/Qwen3.5-122B-A10B` | 131,072 |
| `Qwen/Qwen3.5-397B-A17B` | 131,072 |
| `Qwen/Qwen3.6-27B` | 131,072 |
| `Qwen/Qwen3.6-35B-A3B` | 131,072 |
| `moonshotai/Kimi-K2.6` | 131,072 |
| `moonshotai/Kimi-K2.7-Code` | 131,072 |
| `nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16` | 262,144 |
| `nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4` | 262,144 |
| `zai-org/GLM-5.2-FP8` | 131,072 |
## List supported models
Query the `/v1/loops/capabilities` endpoint for the current list of base models and their maximum sequence lengths:
```bash Request theme={"system"}
curl https://api.baseten.co/v1/loops/capabilities \
-H "Authorization: Bearer $BASETEN_API_KEY"
```
```json Output theme={"system"}
{
"supported_models": [
{"model_name": "Qwen/Qwen3-0.6B", "max_context_length": 8192},
{"model_name": "Qwen/Qwen3-30B-A3B-Instruct-2507", "max_context_length": 131072},
...
]
}
```
The endpoint lists the base models your workspace has access to, so its response is the source of truth for what you can pass as `base_model`, even where it differs from the table above. See [`GET /v1/loops/capabilities`](/reference/loops-api/server/get-capabilities) for the full route reference.
## Pass a model to Loops
Pass the table value verbatim as `base_model` through any of the following entry points:
* The Python SDK, using `tinker.ServiceClient.create_lora_training_client(base_model=...)`. See the [Loops quickstart](/loops/quickstart).
* The HTTP API, using [`POST /v1/loops/runs`](/reference/loops-api/runs/create-a-run).
* The CLI, using [`truss loops push `](/reference/cli/loops/loops-cli#push), which provisions a session, run, and paired sampler in one call.
The minimal HTTP call provisions a run and its paired sampler against an existing session. Replace `2qjl22w` with the `session.id` returned by `POST /v1/loops/sessions`:
```bash theme={"system"}
curl --request POST \
--url https://api.baseten.co/v1/loops/runs \
--header "Authorization: Bearer $BASETEN_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"session_id": "2qjl22w",
"base_model": "Qwen/Qwen3.5-9B"
}'
```
For the full request body, response shape, and an interactive playground, see [`POST /v1/loops/runs`](/reference/loops-api/runs/create-a-run) in the Loops API reference.
## Request a model
To request a base model that isn't listed, [contact support](mailto:support@baseten.co).
# Tinker compatibility
Source: https://docs.baseten.co/loops/tinker-compatibility
Most Tinker code runs on Loops with one install change, apart from paginated checkpoints, auth, and cluster routing.
Most Tinker code runs on Loops with one install change. The forward pass, backward pass, optimizer step, sampling, and all shared types carry over without modification. Checkpoints come back as paginated presigned URLs rather than a single archive, authentication uses a `BASETEN_API_KEY` instead of a Thinking Machines key, and cluster routing resolves to your Baseten org rather than a Tinker tenant.
## Compatibility at a glance
The `baseten-loops[tinker]` extra installs a `tinker` namespace package so existing imports work unchanged. The table below shows where the two systems align and where they diverge.
| Aspect | Tinker | Loops |
| ------------------------------ | --------------------------- | ----------------------------------------------------- |
| Import | `import tinker` | `import tinker` (provided by `baseten-loops[tinker]`) |
| ServiceClient construction | `tinker.ServiceClient(...)` | `tinker.ServiceClient()` |
| Forward / backward / optimizer | identical | identical |
| Checkpoint download | single archive | paginated presigned URLs |
| Authentication | Thinking Machines key | `BASETEN_API_KEY` |
| Cluster scope | per Tinker tenant | per Baseten org |
## Install the Tinker compatibility package
Add `baseten-loops` with the `[tinker]` extra to your project. The extra pulls in `baseten-loops-tinker`, which provides the `tinker` namespace. If you don't have a uv project yet, initialize one first:
```bash theme={"system"}
uv init loops-app
cd loops-app
uv add 'baseten-loops[tinker]'
```
Once installed, existing code that starts with `import tinker` works without modification:
```python theme={"system"}
import tinker # provided by baseten-loops-tinker
```
### Use with tinker-cookbook
`tinker-cookbook` depends on the original Thinking Machines `tinker` package. Without an override, installing `tinker-cookbook` first pulls in that package, and its files conflict with the `tinker` namespace provided by `baseten-loops[tinker]` when that extra is added later. The fix is to declare a `uv` override in `pyproject.toml` before installing any dependencies. There is no CLI command for this step.
Add the override first:
```toml theme={"system"}
[tool.uv]
override-dependencies = [
"tinker ; python_version < '0'",
]
```
Then add the dependencies:
```bash theme={"system"}
uv init
uv add tinker-cookbook
uv add 'baseten-loops[tinker]'
```
The `baseten-loops[tinker]` extra provides the `tinker` namespace instead of the original package.
## What's the same
The training loop API is call-compatible between the two systems. `forward`, `backward`, `optim_step`, `save_weights`, and the sampling interface share the same method names, and the shared types (`Datum`, `ModelInput`, `TensorData`, `SamplingParams`, and `AdamParams`) are all available under `tinker.types` with the same field names and semantics.
## What's different
### Checkpoints come back as folders
Tinker returns a single archive URL for a checkpoint. Loops returns a folder of files behind paginated presigned URLs, because weight sync writes an unzipped folder rather than a compressed archive. Consumer code paginates using `?page_token=` and `?page_size=` query parameters instead of downloading a single file. See [`GET /v1/loops/checkpoints/{checkpoint_id}/files`](/reference/loops-api/checkpoints/get-checkpoint-files) for the route.
### Authentication is a Baseten API key
Set `BASETEN_API_KEY` in your environment before constructing `ServiceClient`; the SDK reads it by default. Pass `api_key=...` only when you need to override the environment variable. The Thinking Machines key used by Tinker is not accepted. See [API keys](/organization/api-keys) for how to generate one.
### Cluster routing is per-org
Loops sessions resolve to the caller's Baseten org and the cluster configured for that org. Tinker uses per-tenant scoping, where the tenant determines the cluster. In practice this means you don't choose a cluster when creating a session. Your org's configuration determines it automatically. Loops must be enabled for your organization before sessions can start; [fill out the signup form](https://www.baseten.co/talk-to-us/loops-signup/) to request access.
## Run tinker-cookbook recipes on Loops
The cookbook recipes contain self-contained examples covering supervised fine-tuning, reinforcement learning from human feedback, distillation, and sampling. They run on Loops without modification to training logic. Running a recipe end to end is the fastest way to confirm that your environment is configured correctly and that the `tinker` namespace is resolving to the Loops compatibility package.
# Train on a dataset
Source: https://docs.baseten.co/loops/train-on-your-data
Move from the quickstart's single example to a batched SFT loop with resumable checkpoints and mid-training evals.
The [quickstart](/loops/quickstart) trains on one hardcoded prompt-and-answer pair. This guide runs the same round trip over a real dataset: batch the data into `Datum` lists, loop over it, save checkpoints you can resume from, and evaluate against the live sampler between steps.
The example dataset is [pirate-ultrachat-10k](https://huggingface.co/datasets/winglian/pirate-ultrachat-10k), chat-format conversations that teach the model pirate dialect. It's the same dataset the [Training Jobs tutorial](/training/getting-started) uses, so you can compare the two paths on identical work.
## Turn a dataset into training data
Each training example becomes a [`Datum`](/reference/sdk/loops/types): input tokens plus loss targets that mask the prompt and supervise the answer, with the same label shift the quickstart uses. For chat data, render the conversation with the tokenizer's chat template and treat the final assistant message as the answer.
Add `datasets` to your project (`uv add datasets`) and start `train_dataset.py`:
```python train_dataset.py theme={"system"}
import tinker
from datasets import load_dataset
BASE_MODEL = "Qwen/Qwen3.5-2B"
service_client = tinker.ServiceClient()
training_client = service_client.create_lora_training_client(
base_model=BASE_MODEL,
rank=16,
)
tokenizer = training_client.get_tokenizer()
def to_datum(example):
messages = example["messages"]
prompt = tokenizer.apply_chat_template(
messages[:-1], tokenize=False, add_generation_prompt=True
)
p = tokenizer.encode(prompt, add_special_tokens=False)
a = tokenizer.encode(messages[-1]["content"], add_special_tokens=False)
full = p + a
tokens = full[:-1]
targets = [-100] * (len(p) - 1) + list(a)
return tinker.Datum(
model_input=tinker.ModelInput.from_ints(tokens),
loss_fn_inputs={
"target_tokens": tinker.TensorData(
data=targets, dtype="int64", shape=[len(targets)]
)
},
)
dataset = load_dataset("winglian/pirate-ultrachat-10k", split="train[:64]")
data = [to_datum(ex) for ex in dataset]
print(f"prepared {len(data)} examples")
```
The `train[:64]` slice keeps this guide's run short. Use the full split for a real fine-tune.
## Run the training loop
Each iteration is the quickstart's round trip over a batch: one `forward_backward()` on a list of `Datum`, one `optim_step()`. Append:
```python train_dataset.py theme={"system"}
BATCH_SIZE = 8
for step, start in enumerate(range(0, len(data), BATCH_SIZE), 1):
batch = data[start : start + BATCH_SIZE]
fb = training_client.forward_backward(data=batch).result(timeout=600.0)
training_client.optim_step(
tinker.AdamParams(learning_rate=4e-5)
).result(timeout=600.0)
print(f"step {step} loss={fb.loss:.4f}")
```
## Save a resumable checkpoint
The quickstart's `save_weights_for_sampler()` publishes weights for sampling and deployment but omits optimizer state. For a checkpoint you can resume training from, use [`save_state()`](/reference/sdk/loops/training-client); to publish the same point for sampling, save both. Append:
```python train_dataset.py theme={"system"}
state = training_client.save_state(name="epoch-1").result(timeout=600.0)
save_resp = training_client.save_weights_for_sampler(name="epoch-1").result(timeout=600.0)
print(f"resumable state at {state.path}")
print(f"sampler weights at {save_resp.path}")
```
To resume later, provision a training client and call [`load_state_with_optimizer()`](/reference/sdk/loops/training-client) with the saved `state.path`.
## Evaluate against the live sampler
The sampler already has your published weights, so an eval between epochs is one call, no deploy step. Append:
```python train_dataset.py theme={"system"}
sampling_client = training_client.create_sampling_client(model_path=save_resp.path)
prompt = tokenizer.apply_chat_template(
[{"role": "user", "content": "How do I learn Python?"}],
tokenize=False,
add_generation_prompt=True,
)
sample = sampling_client.sample(
prompt=tinker.ModelInput.from_ints(
tokenizer.encode(prompt, add_special_tokens=False)
),
num_samples=1,
sampling_params=tinker.SamplingParams(max_tokens=48),
)
print(tokenizer.decode(sample.sequences[0].tokens))
```
Run the script with `uv run python train_dataset.py`. Values vary, but a successful run prints falling losses, both checkpoint paths, and a completion:
```output theme={"system"}
prepared 64 examples
step 1 loss=2.3548
step 2 loss=2.3185
...
step 8 loss=2.0167
resumable state at bt://loops:v31yx93/weights/epoch-1
sampler weights at bt://loops:v31yx93/sampler_weights/epoch-1
Learning Python is one of the fastest and most rewarding ways to start programming. ...
```
Loss falls across the eight steps, but the completion still reads like the base model: 64 examples isn't enough to change its dialect. Training on the full split is what makes the model answer like the dataset. When you're done, [shut down the session](/loops/quickstart#shut-down-the-session).
## Next steps
* **[Deploy a checkpoint](/loops/deploy-checkpoints)**: Serve `epoch-1` as a production endpoint.
* **RL and advanced recipes**: The [Tinker cookbook](https://github.com/thinking-machines-lab/tinker-cookbook) recipes run on Loops; see [Tinker compatibility](/loops/tinker-compatibility) for setup.
# Export to Datadog
Source: https://docs.baseten.co/observability/export-metrics/datadog
Export metrics from Baseten to Datadog
The Baseten metrics endpoint can be integrated with [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) by configuring a Prometheus receiver that scrapes the endpoint. This allows Baseten metrics to be pushed to a variety of popular exporters. See the [OpenTelemetry registry](https://opentelemetry.io/ecosystem/registry/?component=exporter) for a full list.
**Using OpenTelemetry Collector to push to Datadog**
```yaml config.yaml theme={"system"}
receivers:
# Configure a Prometheus receiver to scrape the Baseten metrics endpoint.
prometheus:
config:
scrape_configs:
- job_name: 'baseten'
scrape_interval: 60s
metrics_path: '/metrics'
scheme: https
authorization:
type: "Api-Key"
credentials: "{BASETEN_API_KEY}"
static_configs:
- targets: ['app.baseten.co']
processors:
batch:
exporters:
# Configure a Datadog exporter.
datadog:
api:
key: "{DATADOG_API_KEY}"
service:
pipelines:
metrics:
receivers: [prometheus]
processors: [batch]
exporters: [datadog]
```
# Export to Grafana Cloud
Source: https://docs.baseten.co/observability/export-metrics/grafana
Export metrics from Baseten to Grafana Cloud
The Baseten + Grafana Cloud integration enables you to get real-time inference metrics within your existing Grafana setup.
## Video tutorial
See below for step-by-step details from the video.
## Set up the integration
Before you start, generate a metrics-only workspace [API key](/organization/api-keys) in your Baseten account.
To set up the integration:
1. Sign in to your Grafana Cloud account and go to **Home > Connections > Add new connection**.
2. In the search bar, type `Metrics Endpoint` and select it.
3. Give your scrape job a name like `baseten_metrics_scrape`.
4. Set the scrape job URL to `https://app.baseten.co/metrics`.
5. Leave the scrape interval set to **Every minute**.
6. Select **Bearer** for authentication credentials.
7. Enter the Bearer Token as `Api-Key abcd.1234567890`, replacing the latter value with your API key.
8. Choose **Test Connection** to confirm everything is entered correctly.
9. Choose **Save Scrape Job**.
10. Choose **Install**.
11. In your integrations list, select your new export and go through the **Enable** flow shown in the video.
Navigate to the **Dashboards** tab to see your data. Data can take a couple of minutes to arrive, and only new data is scraped, not historical metrics.
## Build a Grafana dashboard
Importing the data is the first step, but you need a dashboard to visualize the incoming information.
We've prepared a basic dashboard to get you started.
To import it:
1. Download `baseten_grafana_dashboard.json` from [this GitHub Gist](https://gist.github.com/philipkiely-baseten/9952e7592775ce1644944fb644ba2a9c).
2. Select **New > Import** from the dropdown in the top-right corner of the Dashboard page.
3. Drop in the provided JSON file.
For a visual reference, see the video above.
# Export to New Relic
Source: https://docs.baseten.co/observability/export-metrics/new-relic
Export metrics from Baseten to New Relic
Export Baseten metrics to New Relic by integrating with [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/). This involves configuring a Prometheus receiver that scrapes Baseten's metrics endpoint and configuring a New Relic exporter to send the metrics to your observability backend.
**Using OpenTelemetry Collector to push to New Relic**
```yaml config.yaml theme={"system"}
receivers:
# Configure a Prometheus receiver to scrape the Baseten metrics endpoint.
prometheus:
config:
scrape_configs:
- job_name: 'baseten'
scrape_interval: 60s
metrics_path: '/metrics'
scheme: https
authorization:
type: "Api-Key"
credentials: "{BASETEN_API_KEY}"
static_configs:
- targets: ['app.baseten.co']
processors:
batch:
exporters:
# Configure a New Relic exporter. Visit New Relic documentation to get your regional otlp endpoint.
otlphttp/newrelic:
endpoint: https://otlp.nr-data.net
headers:
api-key: "{NEW_RELIC_KEY}"
service:
pipelines:
metrics:
receivers: [prometheus]
processors: [batch]
exporters: [otlphttp/newrelic]
```
# Overview
Source: https://docs.baseten.co/observability/export-metrics/overview
Export metrics from Baseten to your observability stack
Baseten provides a metrics endpoint in Prometheus format, allowing integration with observability tools like Prometheus, OpenTelemetry Collector, Datadog Agent, and Vector.
## Set up metrics scraping
Use the Authorization header with a [Baseten API key](https://app.baseten.co/settings/api_keys):
```json theme={"system"}
{"Authorization": "Bearer YOUR_API_KEY"}
```
Recommended 1-minute interval (metrics update every 30 seconds).
## Supported integrations
Baseten metrics can be collected through [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) and exported to:
* [Prometheus](/observability/export-metrics/prometheus)
* [Datadog](/observability/export-metrics/datadog)
* [Grafana](/observability/export-metrics/grafana)
* [New Relic](/observability/export-metrics/new-relic)
For available metrics, see the [supported metrics reference](/observability/export-metrics/supported-metrics).
## Rate limits
* **6 requests per minute per organization**
* Exceeding this limit results in **HTTP 429 (Too Many Requests)** responses.
* To stay within limits, use a **1-minute scrape interval**.
# Export to Prometheus
Source: https://docs.baseten.co/observability/export-metrics/prometheus
Export metrics from Baseten to Prometheus
To integrate with Prometheus, specify the Baseten metrics endpoint in a scrape config. For example:
```yaml prometheus.yml theme={"system"}
global:
scrape_interval: 60s
scrape_configs:
- job_name: 'baseten'
metrics_path: '/metrics'
authorization:
type: "Api-Key"
credentials: "{BASETEN_API_KEY}"
static_configs:
- targets: ['app.baseten.co']
scheme: https
```
See the Prometheus docs for more details on [getting started](https://prometheus.io/docs/prometheus/latest/getting_started/) and [configuration options](https://prometheus.io/docs/prometheus/latest/configuration/configuration/).
# Metrics support matrix
Source: https://docs.baseten.co/observability/export-metrics/supported-metrics
Every metric you can export from Baseten, with its type and labels
This page lists every metric the Baseten [metrics export endpoint](/observability/export-metrics/overview) exposes, with each metric's name, type, and labels.
Baseten serves these metrics in Prometheus format at `https://app.baseten.co/metrics`. For the endpoint URL, authentication, scrape interval, and supported integrations (Prometheus, Datadog, Grafana, New Relic), see the [export overview](/observability/export-metrics/overview).
## How to read this page
Each metric is listed by its Prometheus name, with:
* **Type:** `counter` (a cumulative total that only increases), `gauge` (a point-in-time value), or `histogram` (a distribution you can compute percentiles from).
* **Labels:** the dimensions you can filter and group by. Common labels are `model_id`, `model_name`, and `deployment_id`; `environment` and `rollout_phase` appear only for deployments tied to an [environment](/deployment/deployments#environments-and-promotion). Some metrics, such as the engine metrics, use a smaller label set.
These are the same measurements shown in the dashboard [Metrics tab](/observability/metrics), exposed here for export. The two sets overlap but aren't identical: a few values are graphed in the dashboard without being exportable, and some exported metrics aren't graphed.
## Availability
Some metrics are emitted only for certain deployments:
* **BIS-LLM metrics** (`baseten_llm_*`) appear for each [BIS-LLM](/engines/bis-llm/overview) deployment. See [BIS-LLM metrics](#bis-llm-metrics).
* **vLLM and SGLang metrics** appear when Baseten detects that engine on your deployment. See [vLLM and SGLang metrics](/observability/metrics#vllm-and-sglang-metrics).
* **Pod health metrics** (`baseten_container_restarts_total` and `baseten_pod_readiness`) roll out behind a feature flag. Contact your account team if they aren't yet visible for your organization.
***
## `baseten_inference_requests_total`
Cumulative number of requests to the model.
Type: `counter`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The status code of the response.
Whether the request was an [async inference request](/inference/async).
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_end_to_end_response_time_seconds`
End-to-end response time in seconds.
Type: `histogram`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The status code of the response.
Whether the request was an [async inference request](/inference/async).
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_container_cpu_usage_seconds_total`
Cumulative CPU time consumed by the container in core-seconds.
Type: `counter`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The ID of the replica.
The environment that the deployment corresponds to. Empty if the deployment is
not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_replicas_active`
Number of replicas ready to serve model requests.
Type: `gauge`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The environment that the deployment corresponds to. Empty if the deployment is
not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_replicas_starting`
Number of replicas starting up--that is, either waiting for resources to be available or loading the model.
Type: `gauge`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The environment that the deployment corresponds to. Empty if the deployment is
not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_container_restarts_total`
Cumulative number of times the model container has been restarted. Restarts are typically caused by application crashes, out-of-memory kills, or failed liveness probes. See [custom health checks](/development/model/health-checks) for how liveness affects restart behavior.
Type: `counter`
This metric rolls out behind a feature flag. Contact your account team if it's not yet visible for your organization.
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_pod_readiness`
Number of pods grouped by their Kubernetes Ready condition. A pod with `condition="true"` is serving traffic; `condition="false"` means the pod is starting up, failing its readiness probe, or shutting down.
Type: `gauge`
This metric rolls out behind a feature flag. Contact your account team if it's not yet visible for your organization.
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The Kubernetes Ready condition for the pods in this sample.
Possible values:
* `"true"`: Pods are ready and serving traffic.
* `"false"`: Pods are starting up, failing readiness probes, or shutting down.
* `"unknown"`: The Ready condition can't be determined (for example, the kubelet hasn't reported recently).
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_container_cpu_memory_working_set_bytes`
Working set memory usage of the container in bytes.
Type: `gauge`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The ID of the replica.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_request_size_bytes`
Request size in bytes. Proxy for input tokens.
Type: `histogram`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The status code of the response.
Whether the request was an [async inference request](/inference/async).
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_response_size_bytes`
Response size in bytes. Proxy for generated tokens.
Type: `histogram`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The status code of the response.
Whether the request was an [async inference request](/inference/async).
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_time_to_first_byte_seconds`
Time to first byte/write in seconds. Proxy for time-to-first-token (TTFT).
Type: `histogram`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The status code of the response.
Whether the request was an [async inference request](/inference/async).
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_time_in_async_queue_seconds`
Time async requests spend queued before processing.
Type: `histogram`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_async_queue_size`
Number of queued async requests over time.
Type: `gauge`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_async_webhook_requests_total`
Cumulative number of [async inference](/inference/async) webhook delivery requests sent.
Type: `counter`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_async_webhook_latency_seconds`
Latency of [async inference](/inference/async) webhook delivery requests in seconds.
Type: `histogram`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_gpu_memory_used`
GPU memory used in MiB.
Type: `gauge`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The ID of the replica.
The ID of the GPU.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_gpu_utilization`
GPU utilization as a ratio (between 0 and 1). Reported for Hopper-architecture GPUs and newer; on older GPUs, use `baseten_gpu_utilization_legacy`, which reports a percentage (between 0 and 100).
Type: `gauge`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The ID of the replica.
The ID of the GPU.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_ongoing_websocket_connections`
Number of ongoing websocket connections.
Type: `gauge`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_concurrent_requests`
Total in-flight inference requests for a deployment, including both requests currently being serviced by replicas and requests waiting to be processed. [Async inference requests](/inference/async) are not included in this metric. This is the primary signal that drives [autoscaling](/deployment/autoscaling/overview) decisions.
Type: `gauge`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## BIS-LLM metrics
[BIS-LLM](/engines/bis-llm/overview) deployments export engine-level and autoscaler metrics with the `baseten_llm_*` prefix, alongside the standard platform metrics above.
## `baseten_llm_input_tokens_total`
Total number of input tokens processed.
Type: `counter`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
A hashed identifier for the source pod. Use this label to distinguish per-pod series within a deployment without exposing raw pod names.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_llm_output_tokens_total`
Total number of output tokens generated.
Type: `counter`
Dashboard equivalent: `output_tokens`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
A hashed identifier for the source pod. Use this label to distinguish per-pod series within a deployment without exposing raw pod names.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_llm_input_tokens_per_request`
Distribution of input tokens per request.
Type: `histogram`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
A hashed identifier for the source pod. Use this label to distinguish per-pod series within a deployment without exposing raw pod names.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_llm_output_tokens_per_request`
Distribution of output tokens per request.
Type: `histogram`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
A hashed identifier for the source pod. Use this label to distinguish per-pod series within a deployment without exposing raw pod names.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_llm_tokens_per_second_per_request`
Distribution of tokens per second per request.
Type: `histogram`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
A hashed identifier for the source pod. Use this label to distinguish per-pod series within a deployment without exposing raw pod names.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_llm_kv_cache_hit_rate`
Distribution of KV cache hit rates observed by workers. Values are between 0 and 1.
Type: `histogram`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
A hashed identifier for the source pod. Use this label to distinguish per-pod series within a deployment without exposing raw pod names.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_llm_spec_decode_num_accepted_tokens_total`
Total number of accepted tokens from speculative decoding. Only present when speculative decoding is active on the deployment.
Type: `counter`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
A hashed identifier for the source pod. Use this label to distinguish per-pod series within a deployment without exposing raw pod names.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_llm_spec_decode_num_draft_tokens_total`
Total number of draft tokens generated by speculative decoding. Only present when speculative decoding is active on the deployment.
Type: `counter`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
A hashed identifier for the source pod. Use this label to distinguish per-pod series within a deployment without exposing raw pod names.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_llm_in_flight_tokens`
Instantaneous number of in-flight tokens across the deployment, including worker load and router-queued tokens.
Type: `gauge`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
A hashed identifier for the source pod. Use this label to distinguish per-pod series within a deployment without exposing raw pod names.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_llm_avg_in_flight_tokens`
Trailing average of in-flight tokens over the deployment's `autoscaling_window`.
Type: `gauge`
Dashboard equivalent: `autoscaler_avg_in_flight_tokens`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
A hashed identifier for the source pod. Use this label to distinguish per-pod series within a deployment without exposing raw pod names.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## `baseten_llm_num_requests`
Instantaneous number of concurrent in-flight requests across BIS-LLM workers.
Type: `gauge`
Dashboard equivalent: `concurrent_requests`
Labels:
The ID of the model.
The name of the model.
The ID of the deployment.
A hashed identifier for the source pod. Use this label to distinguish per-pod series within a deployment without exposing raw pod names.
The environment that the deployment corresponds to. Empty if the deployment is not associated with an environment.
The phase of the deployment in the [promote to production process](/deployment/deployments#environments-and-promotion). Empty if the deployment is not associated with an environment.
Possible values:
* `"promoting"`
* `"stable"`
## vLLM and SGLang metrics
When Baseten [detects vLLM or SGLang](/observability/metrics#vllm-and-sglang-metrics) on your deployment, it scrapes your container's `/metrics` endpoint and exports the engine's native metrics alongside Baseten's own. These also appear as graphs in the [Metrics tab](/observability/metrics#vllm-and-sglang-metrics).
The engines define these metrics, not Baseten, and they change between versions. For the complete, current list, always refer to the official [vLLM](https://docs.vllm.ai/en/latest/design/v1/metrics.html) and [SGLang](https://docs.sglang.io/references/production_metrics.html) metrics documentation.
Baseten normalizes these metrics across engine versions and exports the most useful ones. Some exported metrics include tokens per second, time to first token, KV cache usage, and the number of requests running or queued.
Baseten attaches the same two labels to every exported engine metric:
The ID of the deployment.
The ID of the replica.
# Status and health
Source: https://docs.baseten.co/observability/health
Every model deployment in your Baseten workspace has a status to represent its activity and health.
## Model statuses
**Healthy states:**
* **Active**: The deployment is active and available. It can be called with `truss predict` or from its API endpoints.
* **Scaled to zero**: The deployment is active but is not consuming resources. It will automatically start up when called, then scale back to zero after traffic ceases.
* **Starting up**: The deployment is starting up from a scaled to zero state after receiving a request.
* **Inactive**: The deployment is unavailable and is not consuming resources. It may be manually reactivated.
**Error states:**
* **Unhealthy**: The deployment is active but is in an unhealthy state due to errors while running, such as an external service it relies on going down or a problem in your Truss that prevents it from responding to requests.
* **Build failed**: The deployment is not active due to a Docker build failure.
* **Deployment failed**: The deployment is not active due to a model deployment failure.
## Fix unhealthy deployments
If you have an unhealthy or failed deployment, check the model logs to see if there's any indication of what the problem is. You can try deactivating and reactivating your deployment to see if the issue goes away. In the case of an external service outage, you may need to wait for the service to come back up before your deployment works again. For issues inside your Truss, you'll need to diagnose your code to see what is making it unresponsive.
# Logs
Source: https://docs.baseten.co/observability/logs
Scope logs by environment or deployment, then filter by request ID for individual predictions.
Baseten assigns a unique request ID to every predict call and returns it in the `X-Baseten-Request-Id` response header, so you can trace a single prediction through your model's logs.
Per-request log filtering requires Truss version 0.15.5 or later. Upgrade with `pip install --upgrade truss`.
## Scope by environment or deployment
The Logs tab can show entries from a single deployment or from every deployment in an environment. Use the dropdowns at the top of the tab to switch.
Environment scope aggregates logs across every deployment in that environment, including past deployments still serving traffic during a rollout. Use it to follow a request across deployment boundaries or to watch a promotion in progress.
Deployment scope restricts logs to a single deployment ID. Use it to isolate behavior to one version, such as a development deployment.
The same scope applies to live tail and historical search.
## Events
The **volume chart** at the top of the Logs tab always shows markers for the same [platform events](/observability/metrics#events) as Metrics. There's no toggle to enable. Hover a marker for details.
## Get the request ID
The first step is capturing the request ID from the response. Baseten includes it in every predict response, regardless of whether the call is synchronous, asynchronous, or gRPC. The exact location depends on the protocol you're using:
**To get the request ID from an HTTP call**:
When you make a predict call, include the `-sD-` flag to print response headers alongside the body:
```bash theme={"system"}
curl -sD- -X POST "https://model-{MODEL_ID}.api.baseten.co/production/predict" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello"}'
```
The request ID appears as a response header:
```
X-Baseten-Request-Id: 31255019cf83c4d0c7492a5006591e1f502a5
```
**To get the request ID from a gRPC call**:
For gRPC calls, the request ID is in the response trailer metadata rather than an HTTP header. Use the `-vv` flag with `grpcurl` to surface it:
```bash theme={"system"}
grpcurl -vv \
-H "baseten-authorization: Api-Key $BASETEN_API_KEY" \
-H "baseten-model-id: model-{MODEL_ID}" \
-d '{"name": "World"}' \
model-{MODEL_ID}.grpc.api.baseten.co:443 \
example.Greeter/SayHello
```
Look for `x-baseten-request-id` in the trailer metadata at the end of the response:
```
x-baseten-request-id: 31255019cf83c4d0c7492a5006591e1f502a5
```
**To get the request ID from an async call**:
Async predict calls return the request ID in two places: the response header and the JSON body, so you can capture it programmatically without parsing headers:
```bash theme={"system"}
curl -sD- -X POST "https://model-{MODEL_ID}.api.baseten.co/production/async_predict" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello"}'
```
```
X-Baseten-Request-Id: 31255019cf83c4d0c7492a5006591e1f502a5
```
```json theme={"system"}
{"request_id": "31255019cf83c4d0c7492a5006591e1f502a5"}
```
## Filter logs by request ID
Once you have a request ID, open the model's logs page and enter it in the search filter bar using the `requestId:` prefix:
```
requestId:31255019cf83c4d0c7492a5006591e1f502a5
```
The view narrows to show only log entries from that request. Each log line also displays the request ID alongside the replica ID, so you can confirm you're looking at the right trace even when scrolling through mixed output.
## Logging with request context
For standard Truss models, Baseten automatically attaches the request ID to any log emitted through Python's `logging` module during a predict call. No configuration is required. Use a logger:
```python theme={"system"}
import logging
logger = logging.getLogger(__name__)
class Model:
def predict(self, request):
logger.info("Starting prediction") # request_id is added automatically
...
```
## Custom servers
For standard Truss models, Baseten handles request ID logging automatically through the framework's built-in JSON formatter. No configuration is required.
Custom servers don't have this built-in support, so you need to do two things: extract the `x-baseten-request-id` header from incoming requests, and include it as a top-level `request_id` key in your JSON log output. Both steps are covered in the setup guides for [custom HTTP servers](/development/model/custom-server#per-request-logging) and [custom gRPC servers](/development/model/grpc#per-request-logging).
## Download logs
Download a deployment's logs as a file from the **Logs** tab. Baseten runs the export as a background job and saves the file when it's ready, so the download reflects the full time range and filters you selected, not only the lines loaded in the view.
To download logs:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the sidebar, then select your model.
2. Choose the **Logs** tab and set the deployment or environment scope, time range, and any filters (level, request ID, replica, or search).
3. Choose **Download CSV** or **Download JSON**.
The file downloads automatically once it finishes preparing.
A single download covers up to 7 days and 100,000 log lines. If you reach either limit, shorten the time range or add filters and export again.
### Fetch logs from the CLI
To pull logs from a terminal or script, use the Baseten CLI:
```bash theme={"system"}
baseten model deployment logs --model-id --deployment-id --since 1h
```
Scope the window with `--start` and `--end` or `--since`, up to 7 days. Stream live logs with `--tail`, and pass `--output jsonl` for machine-readable output. See the [`baseten model deployment logs`](/reference/cli/baseten/model-deployment#logs) reference for the full set of filters.
## Export logs to an OTLP endpoint
You can stream the same logs that appear in the Baseten UI to any backend that accepts [OTLP over HTTP](https://opentelemetry.io/docs/specs/otlp/#otlphttp), including Honeycomb, Datadog, Grafana Cloud, and Sentry. Once configured, every new log line is forwarded to your endpoint in near real time, so you can build dashboards, alerts, and long-term retention on top of your inference traffic without scraping the UI.
Log export is rolling out gradually. If the **OTEL connection** card isn't visible in your settings, contact Baseten support to enable it for your organization.
### What gets exported
The exporter forwards every log you would see in the Baseten UI, which includes:
* **Build logs:** image builds for new deployments.
* **Deploy and promotion logs:** lifecycle events emitted as a deployment activates, scales, or is promoted to an environment.
* **Serving logs:** stdout and stderr from your model replicas, including anything you write through Python's `logging` module.
Each record is sent as an OTLP `LogRecord` with `service.name = "baseten"` and an allowlisted set of attributes:
| Attribute | Description |
| ------------------ | -------------------------------------------------------------------------------------------- |
| `message` | The log line. |
| `model_id` | Stable ID of the model the log came from. |
| `model_version_id` | Deployment (model version) the log came from. |
| `environment` | Environment name, such as `production` or `staging`, when the deployment is attached to one. |
| `replica` | Replica ID for serving logs. |
| `request_id` | Per-prediction request ID. Matches the `X-Baseten-Request-Id` header. |
| `training_job_id` | Training job ID for training logs. |
| `chainlet_id` | Chainlet ID for [Chains](/development/chain/overview). |
| `exc_info` | Formatted Python traceback, when the log carries an exception. |
Baseten maps the original log level to OTLP `SeverityNumber` and `SeverityText` (`DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`) and strips internal labels that aren't on the allowlist before export, so your backend only receives the same fields you see in the UI.
Exports start from the moment the connection is enabled. Historical logs are not backfilled, and delivery is best-effort: Baseten retries transient failures with exponential backoff, but records can be dropped if your endpoint is unreachable for an extended period.
### Configure a connection
Each Baseten organization can have one OTLP destination at a time.
To configure a connection:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co), then go to **Settings → General** and find the **OTEL connection** card.
2. Choose **Add connection** and fill in:
* **Endpoint URL:** The full URL of your OTLP/HTTP logs receiver, including the path (`/v1/logs` for most receivers). See the integration notes below for per-vendor examples.
* **Header name:** The HTTP header your backend uses to authenticate.
* **Header value:** The credential for that header. The value is stored encrypted and never displayed again after you save it.
3. Save the connection. New log records start flowing to your endpoint within a few seconds.
4. Choose **Test** on the saved connection to send a probe log and confirm the endpoint and credentials are accepted. For an end-to-end check, send a prediction to a deployment and look for its request ID in your backend.
The **Test** button is not supported for custom OTLP endpoints. Logs are still forwarded to your endpoint, but you can't send a probe from the UI to verify the connection.
To rotate credentials or change destinations, use the edit icon on the saved connection. Removing the connection stops exports immediately.
### Integration notes
The endpoint and header values below come from each vendor's OTLP/HTTP documentation. Check those docs for the most current values for your account and region.
Honeycomb accepts OTLP/HTTP at `https://api.honeycomb.io/v1/logs` (or a region-specific host such as `https://api.eu1.honeycomb.io/v1/logs`). Authenticate with an ingest API key:
* **Endpoint URL:** `https://api.honeycomb.io/v1/logs`
* **Header name:** `x-honeycomb-team`
* **Header value:** Your Honeycomb ingest API key.
On Honeycomb environments that route by `service.name`, logs land in a dataset named `baseten`. Honeycomb Classic accounts and other dataset-routing setups route differently. See [Honeycomb's OTLP/HTTP reference](https://docs.honeycomb.io/send-data/logs/honeycomb-exporter/) for dataset routing and regional endpoints.
Datadog accepts OTLP/HTTP logs directly on its intake endpoint, so you don't need to run the Datadog Agent or an OpenTelemetry Collector. Authenticate with a Datadog API key:
* **Endpoint URL:** `https://http-intake.logs..datadoghq.com/v1/logs`, where `` is your Datadog site (`us1`, `us3`, `us5`, `eu`, `ap1`, and so on).
* **Header name:** `dd-api-key`
* **Header value:** An API key from your Datadog **Organization Settings → API Keys** page (at `https://.datadoghq.com/organization-settings/api-keys`).
See [Datadog's OTLP logs intake docs](https://docs.datadoghq.com/opentelemetry/setup/otlp_ingest/logs/) for the per-site endpoint and request format.
Grafana Cloud exposes an OTLP gateway per stack. Use the gateway URL and basic auth token from your stack's **OpenTelemetry** connection page:
* **Endpoint URL:** `https://otlp-gateway-.grafana.net/otlp/v1/logs`
* **Header name:** `Authorization`
* **Header value:** `Basic `
The exported logs appear in Loki and can be queried alongside the rest of your Grafana Cloud telemetry. See [Grafana Cloud's OTLP setup docs](https://grafana.com/docs/grafana-cloud/send-data/otlp/send-data-otlp/) for the exact gateway URL and token format.
Sentry accepts OTLP/HTTP logs on a per-project ingest endpoint. Authenticate with your project's public key:
* **Endpoint URL:** `https://o.ingest.sentry.io/api//integration/otlp/v1/logs`
* **Header name:** `x-sentry-auth`
* **Header value:** `sentry sentry_key=`
Find the org ID, project ID, and public key in your Sentry project under **Settings → Client Keys (DSN)**. See [Sentry's direct OTLP logs docs](https://docs.sentry.io/concepts/otlp/direct/logs/) for details.
Other OTLP/HTTP collectors work the same way. If your backend isn't listed, fill in the endpoint URL and the auth header (name and value) it documents for OTLP, and Baseten will start sending logs to it.
# Metrics
Source: https://docs.baseten.co/observability/metrics
Understand the load and performance of your model
The Metrics tab in the model dashboard tracks model load and performance. Use the dropdowns at the top of the tab to scope by environment, deployment, or time range.
Environment scope aggregates metrics across every deployment in that environment, which helps you watch a rollout or compare trends across the whole environment. Deployment scope restricts metrics to a single deployment ID for diagnosing one version in isolation.
## Customize your view
By default the Metrics tab shows a standard set of graphs. Use the **Customize view** button at the top of the tab to show, hide, and reorder any graph, and your layout is saved per model. A hidden graph stays in the Customize view panel, so you can turn it back on at any time.
## Events
Turn on the **Events** toggle at the top of the Metrics tab to overlay platform events on your graphs. When response time jumps or replica count changes, a marker shows whether a deployment, promotion, or settings change caused it.
Events are available for models, not for shared Model API endpoints or training jobs. The toggle is off by default.
Baseten marks these events:
* **Deployed:** a new deployment, with its target environment.
* **Promoted:** a deployment promoted to an environment.
* **Promotion control action:** a pause, resume, or roll-forward during a promotion.
* **Autoscaling changed:** a new replica range or concurrency target.
* **Activated** and **Deactivated:** a deployment turned on or off.
* **Instance type changed:** a move to a new instance type.
* **Replica terminated:** an individual replica shut down.
* **Environment updated:** a change to an environment.
## Inference volume
Tracks the request rate over time, segmented by HTTP status codes:
* `2xx`: 🟢 Successful requests
* `4xx`: 🟡 Client errors
* `5xx`: 🔴 Server errors (includes model prediction exceptions)
For non-HTTP models and Chains (WebSockets and gRPC), the status codes reflect the status codes for those protocols. For a full list of the WebSocket close codes surfaced here, see [WebSocket status codes](/development/model/websockets#inference-volume).
***
## Response time
Measured at different percentiles (p50, p90, p95, p99):
* **End-to-end response time:** Includes cold starts, queuing, and inference (excludes client-side latency). Reflects real-world performance.
* **Inference time:** Covers only model execution, including pre/post-processing. Useful for optimizing single-replica performance.
* **Time to first byte:** Measures the time-to-first-byte time distribution, including any queueing and routing time. A proxy for TTFT.
***
## Request and response size
Measured at different percentiles (p50, p90, p95, p99):
* **Request size:** Tracks the request size distribution. A proxy for input tokens.
* **Response size:** Tracks the response size distribution. A proxy for generated tokens.
***
## Replicas
Tracks the number of **active** and **starting** replicas:
* **Starting:** Waiting for resources or loading the model.
* **Active:** Ready to serve requests.
* For development deployments, a replica is considered active while running the live reload server.
To see pods split by their Kubernetes Ready condition, for example when a [readiness probe](/development/model/health-checks#readiness-probe) pulls a replica out of traffic, export [`baseten_pod_readiness`](/observability/export-metrics/supported-metrics#baseten_pod_readiness).
***
## Restarts
Tracks the cumulative number of times the model container has been restarted. Restarts are typically caused by application crashes, out-of-memory kills, or failed [liveness probes](/development/model/health-checks#liveness-probe).
Frequent restarts usually indicate one of:
* A crash in `load()` or in your model code.
* An out-of-memory event: check the **Memory usage** graph.
* A liveness probe failing under load: review `restart_threshold_seconds` and any [custom health check logic](/development/model/health-checks#custom-health-check-logic).
***
## Concurrent requests
Total in-flight inference requests across replicas, including both requests currently being serviced and requests waiting to be processed. [Async inference requests](/inference/async) are not included in this metric.
This is the primary signal that drives [autoscaling](/deployment/autoscaling/overview) decisions. For the full metric definition and labels, see [`baseten_concurrent_requests`](/observability/export-metrics/supported-metrics#baseten_concurrent_requests).
This metric is a point-in-time gauge, sampled roughly every 30 seconds, while inference volume counts every request over the full minute. The two relate through Little's Law:
`average concurrency ≈ requests per second × average end-to-end latency`
When requests are fast, that product stays well below 1 even at high volume, so most samples catch the system empty and the gauge reads 0. For example, 600 requests per minute at 80 ms latency averages about 0.8 requests in flight. Autoscaling still responds correctly, because it acts on sustained concurrency rather than sub-second bursts.
***
## CPU usage and memory
Displays resource utilization across replicas. Metrics are averaged and may not capture short spikes.
### Considerations:
* **High CPU/memory usage**: May degrade performance. Consider upgrading to a larger instance type.
* **Low CPU/memory usage**: Possible overprovisioning. Switch to a smaller instance to reduce costs.
***
## GPU usage and memory
Shows GPU utilization across replicas.
* **GPU usage**: Percentage of time a kernel function occupies the GPU.
* **GPU memory**: Total memory used.
### Considerations:
* **High GPU load**: Can slow inference. Check response time metrics.
* **High memory usage**: May cause out-of-memory failures.
* **Low utilization**: May indicate overprovisioning. Consider a smaller GPU.
***
## vLLM and SGLang metrics
When your deployment serves an LLM with [vLLM](/examples/vllm) or [SGLang](/examples/sglang), Baseten surfaces engine-native metrics in the Metrics tab alongside the standard ones. These graphs report what the inference engine itself measures: metrics like tokens per second, time to first token, KV cache usage, and the number of requests running or queued.
### How detection works
You don't turn these graphs on manually. Baseten scrapes your container's `/metrics` endpoint and looks for metrics that match the vLLM or SGLang format. When it finds them, the matching graphs appear in the Metrics tab automatically. No configuration or redeploy is required.
If you don't see the graphs, and they don't appear in the **Customize view** panel either, Baseten was most likely unable to read your container's metrics endpoint. Common causes are that the endpoint isn't exposed, it's blocking Baseten's scrape, or the engine isn't emitting metrics yet. Confirm that your engine serves Prometheus metrics on its `/metrics` route. For [custom servers](/development/model/custom-server), routes like `/metrics` pass through to your server unchanged.
Detection runs on a periodic scrape and results are cached, so a deployment that just started exporting metrics may take a few minutes to show its graphs.
### Show and hide graphs
Many of these engine graphs are hidden by default. Turn them on with [Customize your view](#customize-your-view).
The exact graphs depend on what your engine version emits. The latency graphs are shown at the p50, p90, p95, and p99 percentiles, and counters are summed over the selected time range.
### Export engine metrics
The Metrics tab shows a curated set of graphs. You can also export the underlying vLLM and SGLang metrics, along with a few that aren't graphed in the dashboard, to your own observability stack through the [metrics export endpoint](/observability/export-metrics/overview). See [vLLM and SGLang metrics](/observability/export-metrics/supported-metrics#vllm-and-sglang-metrics) for the labels Baseten adds.
***
## Async queue metrics
* **Time in Async Queue**: Time spent in the async queue before execution (p50, p90, p95, p99).
* **Async Queue Size**: Number of queued async requests.
* **Webhook requests**: Number of [async webhook](/inference/async) delivery requests sent.
* **Webhook latency**: Latency of async webhook delivery requests (p50, p90, p95, p99).
### Considerations:
* Large queue size indicates requests are queued faster than they are processed.
* To improve async throughput, increase the max replicas or adjust autoscaling concurrency.
* Async Queue Size is a point-in-time gauge, like [concurrent requests](#concurrent-requests). When requests spend little time queued, most samples catch an empty queue and it reads 0 even under steady load.
***
## Use metrics for autoscaling
Use these metrics to diagnose autoscaling behavior and tune your settings.
### Key metrics to watch
| Metric | What it tells you |
| --------------------------------- | ------------------------------------------------------------------------------------- |
| **Concurrent requests** | Shows total demand (queued + active). This is the signal driving autoscaling. |
| **Replicas** (active vs starting) | Shows scaling activity. Large gaps indicate cold start delays. |
| **Inference volume** | Shows traffic patterns. Use to identify if you have noisy, bursty, or steady traffic. |
| **Response time** (p95, p99) | Shows if scaling is keeping up. Spikes aligned with replica changes indicate thrash. |
| **Async queue size** | Shows backpressure. Growing queue means you need more capacity. |
### Diagnose autoscaling issues
| You see... | Likely cause | Fix |
| ------------------------------------------------- | --------------------------- | ----------------------------------------------- |
| Latency spikes aligned with replica count changes | Oscillation (thrash) | Increase scale-down delay |
| Replicas at max, latency still degrading | Insufficient capacity | Increase max replicas or concurrency target |
| Large gap between active and starting replicas | Cold start delays | Increase min replicas, check image optimization |
| Traffic high but replicas staying low | Concurrency target too high | Lower concurrency target or target utilization |
| Replicas scaling down too quickly | Scale-down delay too short | Increase scale-down delay |
For solutions to common autoscaling problems, see [Autoscaling troubleshooting](/troubleshooting/deployments#autoscaling-issues).
# Secure model inference
Source: https://docs.baseten.co/observability/security
Keeping your models safe and private
Baseten maintains [SOC 2 Type II certification](https://www.baseten.co/blog/soc-2-type-2) and [HIPAA compliance](https://www.baseten.co/blog/baseten-announces-hipaa-compliance), with robust security measures beyond compliance.
## Data privacy
Baseten does not store model inputs, outputs, or weights by default. This zero data retention (ZDR) posture applies to synchronous inference out of the box.
* **Model inputs/outputs**: Inputs for [async inference](/inference/async) are temporarily stored until processed. Outputs are never stored.
* **Model weights**: Loaded dynamically from sources like Hugging Face, GCS, or S3, moving directly to GPU memory.
* Users can enable caching through Truss. You can permanently delete cached weights on request.
* **KV cache**: The attention KV cache is an in-memory, GPU-resident structure used during inference. It is not persisted to disk and is discarded when a replica restarts or scales down.
* **Postgres data tables**: Existing users may store data in Baseten’s hosted Postgres tables, which can be deleted anytime.
Baseten’s network accelerator optimizes model downloads. [Contact support](mailto:support@baseten.co) to disable it.
To learn more and access official policies and certifications, visit the [Baseten Trust Center](https://trust.baseten.co/).
## View your compliance policy
If Baseten has set a compliance policy for your account, the policy appears in your **Organization** and **Team** settings under the General tab, and on the model environment detail view. The policy shows the boundaries your inference workloads run within:
* **Framework**: the compliance programs your workloads are restricted to.
* **Region**: the geographic regions where your workloads can run.
Compliance policies are read-only and managed by Baseten. To set or change a policy, [contact support](mailto:support@baseten.co).
For Baseten's certifications and official compliance posture, visit the [Baseten Trust Center](https://trust.baseten.co/).
## Workload security
Baseten isolates inference workloads to protect users and Baseten’s infrastructure.
* **Container security**:
* Baseten never shares GPUs across users.
* Continuous monitoring and automated controls to detect and mitigate vulnerabilities.
* Minimal privileges for workloads and nodes to limit incident impact.
* **Network security**:
* Each customer has a dedicated Kubernetes namespace.
* Isolation enforced through [Calico](https://docs.tigera.io/calico/latest/about) and [Cilium](https://docs.cilium.io/en/stable/overview/intro/).
* Nodes run in a private subnet with firewall protections.
* **Pentesting**:
* Annual penetration testing by independent third-party security firms.
* Malicious model deployments tested in a dedicated prod-like environment.
## Self-hosted model inference
Baseten offers single-tenant environments and self-hosted deployments. The cloud version is recommended for ease of setup, cost efficiency, and elastic GPU access.
For self-hosting, [contact support](mailto:support@baseten.co).
# Tracing
Source: https://docs.baseten.co/observability/tracing
Investigate the prediction flow in detail
Baseten’s Truss server includes built-in [OpenTelemetry](https://opentelemetry.io/) (OTEL) instrumentation, with support for custom tracing.
Tracing helps diagnose performance bottlenecks but introduces minor overhead, so it is **disabled by default**.
## Export builtin trace data to Honeycomb
To export built-in trace data to Honeycomb:
1. Create a Honeycomb API key and add it to [Baseten secrets](https://app.baseten.co/settings/secrets).
2. Update `config.yaml` for the target model:
```yaml config.yaml theme={"system"}
environment_variables:
HONEYCOMB_DATASET: your_dataset_name
runtime:
enable_tracing_data: true
secrets:
HONEYCOMB_API_KEY: '***'
```
3. Send requests with `traceparent` headers for distributed tracing. If omitted, Baseten generates random trace IDs.
## Add custom OTEL instrumentation
To define custom spans and events, integrate OTEL directly:
```python model.py theme={"system"}
import time
from typing import Any, Generator
import opentelemetry.exporter.otlp.proto.http.trace_exporter as oltp_exporter
import opentelemetry.sdk.resources as resources
import opentelemetry.sdk.trace as sdk_trace
import opentelemetry.sdk.trace.export as trace_export
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
trace.set_tracer_provider(
TracerProvider(resource=Resource.create({resources.SERVICE_NAME: "UserModel"}))
)
tracer = trace.get_tracer(__name__)
trace_provider = trace.get_tracer_provider()
class Model:
def __init__(self, **kwargs) -> None:
honeycomb_api_key = kwargs["secrets"]["HONEYCOMB_API_KEY"]
honeycomb_exporter = oltp_exporter.OTLPSpanExporter(
endpoint="https://api.honeycomb.io/v1/traces",
headers={
"x-honeycomb-team" : honeycomb_api_key,
"x-honeycomb-dataset": "your_dataset_name",
},
)
honeycomb_processor = sdk_trace.export.BatchSpanProcessor(honeycomb_exporter)
trace_provider.add_span_processor(honeycomb_processor)
@tracer.start_as_current_span("load_model")
def load(self):
...
def preprocess(self, model_input):
with tracer.start_as_current_span("preprocess"):
...
return model_input
@tracer.start_as_current_span("predict")
def predict(self, model_input: Any) -> Generator[str, None, None]:
with tracer.start_as_current_span("start-predict") as span:
def inner():
time.sleep(0.01)
for i in range(5):
span.add_event("yield")
yield str(i)
return inner()
```
Baseten’s built-in tracing **does not interfere** with user-defined OTEL implementations.
# Access control
Source: https://docs.baseten.co/organization/access
Manage access to your Baseten organization with role-based access control.
Baseten uses role-based access control (RBAC) to manage organization access.
Every organization member has one of two roles.
| Permission | Admin | Member |
| :----------------------- | ----- | ------ |
| Manage members | ✅ | ❌ |
| Manage billing | ✅ | ❌ |
| Deploy models and Chains | ✅ | ✅ |
| Call models | ✅ | ✅ |
**Admins** have full control over the organization, including member management and billing.
**Members** can deploy and call models but can't manage organization settings or other users.
If your organization uses multiple teams, see [Teams](/organization/teams) for information about team-level roles and permissions.
If your organization uses [Single sign-on (SSO)](/organization/sso), users provisioned through your identity provider join with the **Member** role by default.
Role and membership changes are recorded in the [audit log](/organization/audit-logs).
# API keys
Source: https://docs.baseten.co/organization/api-keys
Authenticate requests to Baseten for deployment, inference, and management.
API keys authenticate your requests to Baseten. You need an API key to:
* Deploy models, Chains, and training projects with the Truss CLI.
* Call model endpoints for inference.
* Use the management API.
## API key types
Baseten supports two types of API keys:
**Personal API keys** are tied to your user account. Actions performed with a personal key are attributed to you. Use personal keys for local development and testing. Personal keys are revoked when a user is [deprovisioned](/organization/sso-and-scim#deprovisioning), so don't use them for production workloads.
**Team API keys** are not tied to an individual user. When your organization has [teams](/organization/teams) enabled, team keys can be scoped to a specific team. Team keys can have different permission levels:
* **Full access**: Deploy models, call endpoints, and manage resources.
* **Inference only**: Call model endpoints but cannot deploy or manage.
* **Metrics only**: Export metrics but cannot deploy or call models.
Use team keys for CI/CD pipelines, production applications, and shared automation.
If your organization uses [teams](/organization/teams), Team Admins can create team API keys scoped to their team. See [Teams](/organization/teams) for more information.
### Environment-scoped API keys
Environment-scoped API keys are team API keys restricted to specific [environments](/deployment/environments). Use them for least-privilege access when sharing keys with external partners or production integrations.
You can scope a key in two ways:
* **By environment**: The key can only call models in the selected environments (for example, `production` only, or `production` and `staging`).
* **By environment and model**: The key can only call specific models within the selected environments.
To create an environment-scoped key, select **Manage and call all team models** or **Call certain models** when [creating a team API key](#create-an-api-key), then choose the environments from the **Environment access** dropdown.
## Create an API key
**To create a personal API key**:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and go to [**API keys**](https://app.baseten.co/settings/api_keys) in your account settings.
2. Choose **Create API key**.
3. Select **Personal** and choose **Next**.
4. Enter a name for the key (lowercase letters, numbers, and hyphens only).
5. Choose **Create API key**.
**To create a team API key**:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and go to [**API keys**](https://app.baseten.co/settings/api_keys) in your account settings.
2. Choose **Create API key**.
3. Select **Team** and choose **Next**.
4. If your organization has multiple teams, select the team.
5. Enter a name for the key (lowercase letters, numbers, and hyphens only).
6. Select the permission level:
* **Manage and call all team models**: Full access to deploy, call, and manage.
* **Call certain models**: Inference-only access to selected models. Choose **All models** so the key can call every model in the team, including models you add later.
* **Export model metrics**: Metrics-only access.
7. For **Manage and call all team models** or **Call certain models**, optionally use the **Environment access** dropdown to restrict the key to specific environments.
8. Choose **Create API key**.
Copy the key immediately. You won't be able to view it again.
**To create a key from the Baseten CLI**:
Sign in with [`baseten auth login`](/reference/cli/baseten/auth#login), then
create a personal key, tied to your account and its permissions:
```bash Command theme={"system"}
baseten org api-key create --type personal --name
Create a team key, not tied to any one user and optionally scoped to
specific models:
```bash Command theme={"system"}
baseten org api-key create --type workspace-invoke --name
See [`baseten org api-key`](/reference/cli/baseten/org-api-key) for the
other key types (`workspace-manage-all`, `workspace-export-metrics`).
**To create a key from the Management API**:
Creating a key over the API requires an existing key, so create your first
one in the console or CLI. The `type` value is uppercase here: `PERSONAL`,
`WORKSPACE_MANAGE_ALL`, `WORKSPACE_INVOKE`, or `WORKSPACE_EXPORT_METRICS`:
```bash Request theme={"system"}
curl -X POST "https://api.baseten.co/v1/api_keys" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "PERSONAL", "name": "
See the [create API key endpoint](/reference/management-api/api-keys/creates-an-api-key)
for the full request options.
## Use API keys with the CLI
The first time you run `truss push`, the CLI prompts you to choose how to authenticate. Choose **Paste an API key** to use a key from this page, or **Log in via browser (OAuth)** to authenticate without a long-lived secret on disk:
```
$ truss push
💻 Let's add a Baseten remote!
? How would you like to authenticate?
Paste an API key
> Log in via browser (OAuth)
```
You can also log in ahead of time with `truss login` (or its alias `truss auth login`). For details on credential storage, OAuth, and managing multiple remotes, see [`truss auth`](/reference/cli/truss/auth).
To configure or update an API key manually, edit `~/.trussrc`:
```sh theme={"system"}
[baseten]
remote_provider = baseten
api_key = YOUR_API_KEY
```
The [Baseten CLI](/reference/cli/baseten/overview) reads `BASETEN_API_KEY`
from the environment, or a stored profile from
[`baseten auth login`](/reference/cli/baseten/auth#login).
## Use API keys with endpoints
Set your key as an environment variable, or store it in your secret manager:
```bash macOS/Linux theme={"system"}
export BASETEN_API_KEY=
```
```powershell Windows theme={"system"}
setx BASETEN_API_KEY
```
Pass your API key in the `Authorization` header using the `Bearer` scheme:
```sh theme={"system"}
Authorization: Bearer $BASETEN_API_KEY
```
`Bearer` works with OpenAI-style clients and AI gateways such as LiteLLM and OpenRouter without extra configuration. Baseten also accepts the legacy `Api-Key` scheme on every endpoint, so existing scripts using `Authorization: Api-Key ` continue to work:
```sh theme={"system"}
Authorization: Api-Key $BASETEN_API_KEY
```
For runnable examples, see [Call your model](/inference/calling-your-model).
[Frontier Gateway](/frontier-gateway/get-started) federated API keys are the exception: they only accept the `Api-Key` scheme. Workspace API keys used to manage gateway groups still accept either scheme.
## Manage API keys
The [API keys page](https://app.baseten.co/settings/api_keys) shows all your keys with their creation date and last used timestamp. Use this information to identify unused keys.
### View and revoke keys as an organization Admin
Organization Admins see every key in the workspace: all team API keys plus every member's personal API keys. The **Owner / Team** column shows who each key belongs to: the owning member for personal keys, or the team for team keys. Use the **Member**, **Team**, and **Type** filters to narrow the list.
Organization Admins can revoke any key in the workspace, including other members' personal keys. Admins can't rename other members' personal keys. To see who created a team API key, check the [audit log](/organization/audit-logs).
All other roles see their own personal keys and any team keys they created. [Team Admins](/organization/teams#roles-and-permissions) also see every key scoped to their teams.
The [list API keys endpoint](/reference/management-api/api-keys/lists-the-users-api-keys) follows the same visibility rules. Each personal key in the response includes an `owner` object with the owning member's `user_id`, `email`, and `name`, so Admins can tell whose key each one is.
### Rename, rotate, or revoke keys
API keys don't automatically expire. To maintain security, rotate keys periodically and revoke any that are no longer in use.
To rename a key, select the pencil icon next to the key name.
To rotate a key, create a new key, update your applications to use it, then revoke the old key.
To revoke a key, select the trash icon next to the key. Revoked keys cannot be restored.
You can also revoke a key programmatically, by its visible prefix:
[`baseten org api-key delete --prefix `](/reference/cli/baseten/org-api-key)
from the CLI, or the
[delete API key endpoint](/reference/management-api/api-keys/delete-an-api-key).
### Security recommendations
* Store API keys in environment variables or secret managers, not in code.
* Never commit API keys to version control.
* Use [environment-scoped keys](#environment-scoped-api-keys) to limit access to specific environments and models.
* Use team keys with minimal permissions for production applications.
* Rotate keys periodically and revoke unused keys.
* Monitor key creation, deletion, and use through the [audit log](/organization/audit-logs).
# Audit logs
Source: https://docs.baseten.co/organization/audit-logs
Track configuration and access changes across your Baseten organization, and export audit events to your SIEM.
Baseten records administrative and configuration events to an audit log. Organization Admins can review the log directly in the Baseten dashboard, and security-conscious organizations can stream audit events to an external SIEM through WorkOS.
## Activity log in the dashboard
Every Baseten organization has an activity log that captures configuration changes and lifecycle events for models, Chains, secrets, API keys, environments, and users.
To view the activity log:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and open **Organization settings**.
2. Choose the **Activity** tab.
Each entry includes:
* A description of the action and the user who performed it. System-initiated events, such as a deployment deactivated for inactivity, have no user attribution.
* The affected resource (model, Chain, environment, or other entity).
* The timestamp of the event.
You can search the log by text and filter by event type group, user, and date range.
If your organization uses [teams](/organization/teams), only Organization Admins can view the activity log. Members don't see activity log entries in multi-team organizations.
### Filter activity on a model or Chain
Each model and Chain has its own **Activity** tab that shows events scoped to that resource. Open a model or Chain and select the **Activity** tab.
On this tab, you can filter the activity log by:
* **Event type:** one or more groups of event types, such as deployments, promotions, or autoscaling changes.
* **Member:** the user who performed the action.
* **Deployment:** a specific deployment of the model or Chain.
* **API key:** the API key used to perform the action.
* **Date range:** defaults to **All time**.
Each filter is multi-select, and the filters combine: the log shows only entries that match every active filter. Select **Reset** to clear all filters at once.
## Audit log event types
Each audit log entry has an event type. Event types appear as the `action` field in [exported events](#audit-log-export-to-your-siem). In the dashboard, Baseten renders each event type as a human-readable description.
### Models and deployments
* `MODEL_DEPLOYED`
* `MODEL_DEPLOYMENT_ACTIVATED`
* `MODEL_DEPLOYMENT_DEACTIVATED`
* `MODEL_DEPLOYMENT_RETRIED`
* `MODEL_DEPLOYMENT_PROMOTED`
* `MODEL_DEPLOYMENT_AUTOSCALING_SETTINGS_CHANGED`
* `MODEL_DEPLOYMENT_INSTANCE_TYPE_CHANGED`
* `MODEL_DEPLOYMENT_DELETED`
* `MODEL_DELETED`
* `MODEL_PROMOTION_CONTROL_ACTION`
* `REPLICA_TERMINATED`
### Chains
* `CHAIN_DEPLOYED`
* `CHAIN_DEPLOYMENT_ACTIVATED`
* `CHAIN_DEPLOYMENT_DEACTIVATED`
* `CHAIN_DEPLOYMENT_PROMOTED`
* `CHAIN_DEPLOYMENT_DELETED`
* `CHAIN_DELETED`
* `CHAIN_ENVIRONMENT_CREATED`
* `CHAIN_ENVIRONMENT_UPDATED`
* `CHAINLET_AUTOSCALING_SETTINGS_CHANGED`
* `CHAINLET_INSTANCE_TYPE_CHANGED`
### Environments
* `ENVIRONMENT_CREATED`
* `ENVIRONMENT_UPDATED`
* `ENVIRONMENT_DELETED`
### Credentials and secrets
* `API_KEY_CREATED`
* `API_KEY_DELETED`
* `SECRET_UPDATED`
* `SECRET_DELETED`
* `WEBHOOK_SIGNING_SECRET_CREATED`
* `WEBHOOK_SIGNING_SECRET_ROTATED`
* `WEBHOOK_SIGNING_SECRET_DELETED`
* `SSH_CERTIFICATE_SIGNED`
### Users
* `USER_INVITED`
* `USER_JOINED_ORGANIZATION`
* `USER_ROLE_UPDATED`
* `USER_REMOVED`
## Audit log export to your SIEM
For organizations that need audit events in an external system, Baseten can stream the audit log to [WorkOS Audit Logs](https://workos.com/docs/audit-logs), which forwards events to destinations you control.
Supported destinations include:
* Amazon S3
* Datadog
* Splunk
For the full list of destinations and their configuration options, see the [WorkOS Audit Logs documentation](https://workos.com/audit-logs).
Audit log export is available on the Enterprise plan. [Contact support](mailto:support@baseten.co) to enable export for your organization.
### Enable audit log export
To enable audit log export, [contact support](mailto:support@baseten.co) with your organization name and the destination you want to forward events to. Baseten enables export and walks you through configuring your destination, including any destination-specific credentials or webhook URLs.
Once your destination is verified, new audit events are exported on a recurring schedule and appear in your destination shortly after they're recorded. Events that occurred before you enabled export aren't backfilled.
To disable export, [contact support](mailto:support@baseten.co). The activity log in the dashboard continues to reflect every event for your organization regardless of export status.
### Event delivery
Baseten retries transient failures for up to 30 minutes when sending events to your destination. For details on destination-side retry and delivery guarantees, see the [WorkOS Audit Logs documentation](https://workos.com/docs/audit-logs).
## Considerations
* The activity log in the dashboard always reflects every recorded event for your organization, regardless of export status.
* For details on audit log retention, [contact support](mailto:support@baseten.co).
* If you need a custom event schema or a destination not supported by WorkOS, [contact support](mailto:support@baseten.co).
# Billing and usage
Source: https://docs.baseten.co/organization/billing
How Baseten meters per-minute usage, and how to manage payment, credits, and invoices for your workspace.
Manage payment, credits, and invoices for your workspace from the [billing and usage dashboard](https://app.baseten.co/settings/billing). Usage is tracked per deployment and updated hourly. For organizations on teams, usage is aggregated at the organization level and visible only to admins.
## Account billing
### Payment method
Add or update payment details on the [billing dashboard](https://app.baseten.co/settings/billing). Your card and bank information is stored with our payments processor, not by Baseten directly.
### Credits
New workspaces receive free credits for testing and deployment. Credits are applied automatically to your running invoice before any card charge. If your credits run out and you have not added a payment method, Baseten deactivates your models until you add one.
### Invoices and payment cadence
Invoices are issued when usage exceeds \$50 or at the end of the calendar month, whichever comes first. After a history of successful payments, billing moves to a monthly cadence.
You can view past invoices and payments in the billing dashboard. For questions about a specific invoice, [contact support](mailto:support@baseten.co).
### Discounts
Volume discounts are available on the Pro plan. Education and nonprofit ML projects qualify for additional discounts. [Contact support](mailto:support@baseten.co) to apply.
***
## What's billed
Baseten meters usage by the minute. For every minute a replica is observed as up on a node, the per-minute price for its instance type applies. The same rule covers the builder workload that produces your image after a `truss push` and the training workloads that run a fine-tune. The detail that catches people off guard: the builder counts, and failed boots do not.
What is not metered: anything that happens before a workload is observed as up (image pull onto the node, scheduling) or after it terminates (drain, cleanup, recycling).
### Replica lifecycle
When you run `truss push`, Baseten runs your image build as a workload. Like serving replicas and training containers, it is metered from the moment it is observed as up.
Cold starts are billed too. Model load happens after the replica is observed as up but before it is healthy enough to serve, so those minutes are on the bill. This is the cost side of the cold-start tradeoff: keeping `min_replica` at zero saves money during idle periods but pushes load time into your customer's first request after a quiet stretch.
The full mapping:
| Lifecycle phase | Billed |
| --------------------------------------------------------------------------------------- | ---------------------- |
| Image build after `truss push` (runs in a builder workload) | Yes |
| Image pull onto the node | No |
| Cold start and model load | Yes |
| Serving requests | Yes |
| Idle warm replicas (`min_replica` ≥ 1) | Yes |
| Replica terminated by autoscaling | Yes, up to termination |
| Replica killed mid-request (OOM, crash) | Yes, up to termination |
| Failed boot (replica was never observed as up) | No |
| Scaled to zero (`min_replica: 0`, no traffic) | No |
| [Development deployments](/development/model/deploy-and-iterate) (`truss push --watch`) | No |
A few clarifications on the rows that surprise people:
**Why image build costs money.** The build runs as its own workload, metered the same way as your serving replicas. Faster builds save money. Heavy or unnecessary install steps in your `config.yaml` are paying for themselves on every push.
**Why cold starts cost money.** The replica is observed as up during model load, so those minutes are billed. See [Cold starts](/deployment/autoscaling/cold-starts) for techniques to shrink that window.
**Why failed boots are free.** If the replica was never observed as up, no minutes are billed. Image-build failures that happen inside the builder workload, on the other hand, are billed up to the moment the build fails.
**What happens if a replica is killed mid-request.** Usage is billed up to the moment the replica terminates. Partial minutes are rounded up.
### Training and fine-tuning
Training and fine-tuning runs are metered the same way as serving. A run is billed for the wall-clock time between the training workload being observed as up and the job completing or being cancelled. For how training storage works, see [Training storage](/training/concepts/storage).
### Instance pricing
Per-minute prices for every available instance type are listed on the [instance type reference](/deployment/resources#instance-type-reference). To convert per-minute to per-hour, multiply by 60.
***
## The billing and usage dashboard
The [billing and usage dashboard](https://app.baseten.co/settings/billing) shows per-deployment usage updated hourly, your current invoice balance, any credit applied, and historical invoices.
# OpenID Connect (OIDC) authentication
Source: https://docs.baseten.co/organization/oidc
Use short-lived OIDC tokens to securely authenticate to cloud resources
OpenID Connect (OIDC) lets your Baseten deployments authenticate to cloud
resources like S3 buckets and container registries using short-lived tokens
instead of long-lived credentials.
Without OIDC, accessing cloud resources requires long-lived credentials: static
API keys or service account keys stored as secrets in Baseten. These keys don't
expire on their own, so if they're leaked or forgotten, they remain valid until
someone manually rotates them. You're responsible for tracking which keys exist,
where they're used, and when to rotate them.
OIDC takes a different approach. Instead of static keys, Baseten issues
short-lived tokens scoped to a specific deployment. There are no secrets to
store, rotate, or clean up.
Baseten OIDC currently supports:
* **AWS**: Amazon ECR (container images) and Amazon S3 (model weights)
* **Google Cloud**: Artifact Registry, GCR (container images), and Google Cloud Storage (model weights)
## How Baseten OIDC works
Baseten acts as an OIDC identity provider with the following configuration:
* **Issuer**: `https://oidc.baseten.co`
* **Audience**: `oidc.baseten.co`
When you deploy your model, Baseten generates short-lived OIDC tokens that
identify your specific workload. Your cloud provider validates these tokens
against the trust relationship you configure, then grants access to the
specified resources.
## Token structure
Each OIDC token includes standard JWT claims and custom claims that identify the
workload. Here's an example unsigned payload:
```json theme={"system"}
{
"iss": "https://oidc.baseten.co",
"sub": "v=1:org=abcd1234:team=wxyz5678:model=abc123:deployment=def456:environment=production:type=model_container",
"aud": "oidc.baseten.co",
"iat": 1700000000,
"exp": 1700003600,
"jti": "550e8400-e29b-41d4-a716-446655440000",
"org": "abcd1234",
"team": "wxyz5678",
"model": "abc123",
"deployment": "def456",
"environment": "production",
"type": "model_container"
}
```
The `sub` claim uses a structured format that encodes the workload identity:
```text theme={"system"}
v=1:org={org_id}:team={team_id}:model={model_id}:deployment={deployment_id}:environment={environment}:type={workload_type}
```
### Claim components
| Component | Description | Example |
| ------------- | ---------------------------------------------------------------------------------- | ------------- |
| `org` | Your organization ID | `abcd1234` |
| `team` | Team ID within your organization | `wxyz5678` |
| `model` | Model ID | `abc123` |
| `deployment` | Specific deployment/version ID | `def456` |
| `environment` | User-defined environment name (max 40 characters). Defaults to `` if not set | `production` |
| `type` | Workload type: `model_build` or `model_container` | `model_build` |
### Workload types
* **`model_build`**: Token used during model image building (for example, pulling base images from ECR/GCR).
* **`model_container`**: Token used by running model containers (for example, downloading weights from S3/GCS).
## Subject claim patterns
Common patterns for scoping which workloads can access your resources:
* **AWS**: Use these in the IAM role **trust policy** under `Condition.StringLike` for `oidc.baseten.co:sub`. Wildcards (`*`) are supported.
* **GCP**: Use these in the Workload Identity Provider **attribute-condition**. With the mapping `google.subject=assertion.sub` (see [Create a Workload Identity Provider](#create-a-workload-identity-provider)), reference the sub claim as `google.subject`. GCP does not support wildcards; use `startsWith()` (and `contains()` where needed).
### All workloads in a team
To give every workload in your team access to a resource, match on the team ID with a wildcard for everything else.
```text theme={"system"}
v=1:org=abcd1234:team=wxyz5678:*
```
```text theme={"system"}
google.subject.startsWith('v=1:org=abcd1234:team=wxyz5678:')
```
### Specific model, all deployments
To restrict access to a single model while allowing all of its deployments and environments, match on the model ID.
```text theme={"system"}
v=1:org=abcd1234:team=wxyz5678:model=abc123:*
```
```text theme={"system"}
google.subject.startsWith('v=1:org=abcd1234:team=wxyz5678:model=abc123:')
```
### Specific environment, all models
To scope access by environment, match workloads deployed to a specific environment like `production`.
```text theme={"system"}
v=1:org=abcd1234:team=wxyz5678:*:environment=production:*
```
```text theme={"system"}
google.subject.startsWith('v=1:org=abcd1234:team=wxyz5678:') && google.subject.contains('environment=production')
```
### Build-time only access
To limit access to the build phase, like pulling base images from a private registry, match on the `model_build` workload type.
```text theme={"system"}
v=1:org=abcd1234:team=wxyz5678:*:type=model_build
```
```text theme={"system"}
google.subject.startsWith('v=1:org=abcd1234:team=wxyz5678:') && google.subject.endsWith('type=model_build')
```
### Runtime only access
To limit access to running containers, like downloading model weights, match on the `model_container` workload type.
```text theme={"system"}
v=1:org=abcd1234:team=wxyz5678:*:type=model_container
```
```text theme={"system"}
google.subject.startsWith('v=1:org=abcd1234:team=wxyz5678:') && google.subject.endsWith('type=model_container')
```
### Specific model and environment
To apply the most restrictive access, combine model and environment matching so only a specific model in a specific environment can authenticate.
```text theme={"system"}
v=1:org=abcd1234:team=wxyz5678:model=abc123:*:environment=production:*
```
```text theme={"system"}
google.subject.startsWith('v=1:org=abcd1234:team=wxyz5678:model=abc123:') && google.subject.contains('environment=production')
```
## Find your OIDC identifiers
Use [`truss whoami --show-oidc`](/reference/cli/truss/whoami) to view your organization and team IDs, issuer, audience, and subject claim format needed for configuring cloud provider trust policies.
## Cloud provider setup
Run this script to create the OIDC provider, IAM role, and permission policies. Set the variables at the top, then execute the entire script.
**Prerequisites**
* **AWS CLI** [2.x](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html).
* **Bash** 3.2+.
* **AWS credentials** configured for the target account (`aws configure`, environment variables, or an IAM role) with permission to create OIDC identity providers, IAM roles, and inline role policies (for example `iam:CreateOpenIDConnectProvider`, `iam:CreateRole`, `iam:PutRolePolicy`).
Review each variable before running. Replace the empty values with your actual AWS account ID, S3 bucket name, organization ID, and team ID.
```bash theme={"system"}
#!/usr/bin/env bash
set -euo pipefail
require_non_empty() {
local _name="$1"
local _desc="${2:-$1}"
local _val
eval "_val=\${${_name}-}"
if [ -z "$_val" ]; then
echo "error: ${_desc} is empty; set ${_name} in the configuration section." >&2
exit 1
fi
}
# ──────────────────────────────────────────────
# Configuration: replace these with your values
# ──────────────────────────────────────────────
AWS_ACCOUNT_ID="" # Your AWS account ID
S3_BUCKET="" # S3 bucket for model weights
ROLE_NAME="BasetenOIDCRole" # IAM role name
BASETEN_ORG_ID="" # From `truss whoami --show-oidc`
BASETEN_TEAM_ID="" # From `truss whoami --show-oidc`
require_non_empty AWS_ACCOUNT_ID "AWS account ID"
require_non_empty S3_BUCKET "S3 bucket name (model weights)"
require_non_empty BASETEN_ORG_ID "Baseten organization ID"
require_non_empty BASETEN_TEAM_ID "Baseten team ID"
require_non_empty ROLE_NAME "IAM role name"
OIDC_ISSUER="oidc.baseten.co"
OIDC_ISSUER_URL="https://${OIDC_ISSUER}"
# ──────────────────────────────────
# 1. Create the OIDC identity provider
# ──────────────────────────────────
echo "Creating OIDC identity provider..."
if ! output=$(aws iam create-open-id-connect-provider \
--url "${OIDC_ISSUER_URL}" 2>&1); then
if [[ "$output" == *"EntityAlreadyExists"* ]]; then
echo "OIDC provider already exists, continuing..."
else
echo "$output" >&2
exit 1
fi
else
echo "OIDC provider created."
fi
# ──────────────────────────────────
# 2. Create the IAM trust policy
# ──────────────────────────────────
TRUST_POLICY=$(cat <
This creates a single role with both ECR and S3 permissions. If you only need ECR **or** S3 access (not both), comment out or remove the policy section you don't need (step 4 or step 5).
If you prefer to walk through each step manually, or need to customize individual resources, follow the instructions below.
### Create an OIDC identity provider
Register Baseten as a trusted OIDC provider in your AWS account:
1. Sign in to the [AWS IAM Console](https://console.aws.amazon.com/iam/).
2. Go to **Identity providers** → **Add provider**.
3. Select **OpenID Connect**.
4. Configure the provider:
* For **Provider URL**, enter `https://oidc.baseten.co`.
* Choose **Get thumbprint** to verify the provider.
* For **Audience**, enter `oidc.baseten.co`.
5. Choose **Add provider**.
If your AWS account requires `sts.amazonaws.com` as a trusted audience, add it to the OIDC provider first, then add `oidc.baseten.co` as an additional audience.
### Create an IAM role
Create a role that your Baseten workloads can assume through OIDC:
1. Go to **Roles** → **Create role**.
2. Select **Web identity** as the trusted entity type.
3. Choose the OIDC provider you created.
4. For **Audience**, select `oidc.baseten.co`, then choose **Next**.
5. On the next page, attach permissions policies for the resources your models need to access:
#### ECR access (for base images)
Attach this policy to allow pulling container images from ECR.
```json theme={"system"}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage"
],
"Resource": "*"
}
]
}
```
#### S3 access (for model weights)
Attach this policy to allow reading model weights from S3.
```json theme={"system"}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-model-weights-bucket",
"arn:aws:s3:::my-model-weights-bucket/*"
]
}
]
}
```
6. Configure the trust policy to include subject claim conditions: after creating the role, go to the role → **Trust relationships** → **Edit** and use a policy like this:
```json theme={"system"}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam:::oidc-provider/oidc.baseten.co"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.baseten.co:aud": "oidc.baseten.co"
},
"StringLike": {
"oidc.baseten.co:sub": "v=1:org=abcd1234:team=wxyz5678:*"
}
}
}
]
}
```
Replace `` with your AWS account ID, and adjust the `sub` claim pattern to match your security requirements.
Run this script to create the service account, Workload Identity Pool, provider, and role bindings. Set the variables at the top, then execute the entire script.
**Prerequisites**
* **Google Cloud SDK** (`gcloud`) [390.0.0+](https://cloud.google.com/sdk/docs/install).
* **Bash** 3.2+.
* **gcloud** authenticated (`gcloud auth login` and application-default credentials if needed) with permission to create service accounts, Workload Identity Pools and providers, and modify project IAM (for example **Service Account Admin**, **Workload Identity Pool Admin**, and **Project IAM Admin** or a custom role with equivalent actions on the target project).
Review each variable before running. Replace the empty values with your actual GCP project ID, project number, organization ID, and team ID.
```bash theme={"system"}
#!/usr/bin/env bash
set -euo pipefail
require_non_empty() {
local _name="$1"
local _desc="${2:-$1}"
local _val
eval "_val=\${${_name}-}"
if [ -z "$_val" ]; then
echo "error: ${_desc} is empty; set ${_name} in the configuration section." >&2
exit 1
fi
}
# ──────────────────────────────────────────────
# Configuration: replace these with your values
# ──────────────────────────────────────────────
PROJECT_ID="" # Your GCP project ID
PROJECT_NUMBER="" # Your GCP project number
SERVICE_ACCOUNT_NAME="baseten-oidc" # Service account name
POOL_NAME="baseten-pool" # Workload Identity Pool name
PROVIDER_NAME="baseten-provider" # Workload Identity Provider name
BASETEN_ORG_ID="" # From `truss whoami --show-oidc`
BASETEN_TEAM_ID="" # From `truss whoami --show-oidc`
require_non_empty PROJECT_ID "GCP project ID"
require_non_empty PROJECT_NUMBER "GCP project number"
require_non_empty BASETEN_ORG_ID "Baseten organization ID"
require_non_empty BASETEN_TEAM_ID "Baseten team ID"
require_non_empty SERVICE_ACCOUNT_NAME "Service account name"
require_non_empty POOL_NAME "Workload Identity Pool name"
require_non_empty PROVIDER_NAME "Workload Identity Provider name"
OIDC_ISSUER_URL="https://oidc.baseten.co"
OIDC_AUDIENCE="oidc.baseten.co"
SA_EMAIL="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
# ──────────────────────────────────
# 1. Create the service account
# ──────────────────────────────────
echo "Creating service account ${SERVICE_ACCOUNT_NAME}..."
gcloud iam service-accounts create "${SERVICE_ACCOUNT_NAME}" \
--project="${PROJECT_ID}" \
--display-name="Baseten OIDC Service Account"
# ──────────────────────────────────
# 2. Grant Artifact Registry reader
# ──────────────────────────────────
echo "Granting Artifact Registry reader role..."
gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/artifactregistry.reader"
# ──────────────────────────────────
# 3. Grant Cloud Storage object viewer
# ──────────────────────────────────
echo "Granting Cloud Storage object viewer role..."
gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/storage.objectViewer"
# ──────────────────────────────────
# 4. Create the Workload Identity Pool
# ──────────────────────────────────
echo "Creating Workload Identity Pool..."
gcloud iam workload-identity-pools create "${POOL_NAME}" \
--project="${PROJECT_ID}" \
--location="global" \
--display-name="Baseten Workload Identity Pool"
# ──────────────────────────────────
# 5. Create the Workload Identity Provider
# ──────────────────────────────────
ATTRIBUTE_CONDITION="google.subject.startsWith('v=1:org=${BASETEN_ORG_ID}:team=${BASETEN_TEAM_ID}:')"
echo "Creating Workload Identity Provider..."
gcloud iam workload-identity-pools providers create-oidc "${PROVIDER_NAME}" \
--project="${PROJECT_ID}" \
--location="global" \
--workload-identity-pool="${POOL_NAME}" \
--issuer-uri="${OIDC_ISSUER_URL}" \
--allowed-audiences="${OIDC_AUDIENCE}" \
--attribute-mapping="google.subject=assertion.sub" \
--attribute-condition="${ATTRIBUTE_CONDITION}"
# ──────────────────────────────────
# 6. Allow Workload Identity to impersonate the service account
# ──────────────────────────────────
echo "Binding workload identity to service account..."
gcloud iam service-accounts add-iam-policy-binding "${SA_EMAIL}" \
--project="${PROJECT_ID}" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_NAME}/*"
# ──────────────────────────────────
# Done
# ──────────────────────────────────
echo ""
echo "Setup complete. Service account:"
echo " ${SA_EMAIL}"
echo ""
echo "Workload Identity Provider:"
echo " projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_NAME}/providers/${PROVIDER_NAME}"
echo ""
echo "Use these values in your Truss configuration."
```
This grants both Artifact Registry and GCS permissions to the service account. If you only need Artifact Registry **or** GCS access (not both), comment out or remove the role binding you don't need (step 2 or step 3).
To find your GCP project number, run: `gcloud projects describe PROJECT_ID --format="value(projectNumber)"`
If you prefer to walk through each step manually, or need to customize individual resources, follow the instructions below.
### Create a service account
Create a service account that Baseten workloads will impersonate:
```bash theme={"system"}
gcloud iam service-accounts create baseten-oidc \
--display-name="Baseten OIDC Service Account"
```
### Grant permissions to the service account
Grant the service account access to the resources you need. You can grant one or both depending on your use case.
#### Artifact Registry access (for base images)
Grant read access to Artifact Registry for pulling container images:
```bash theme={"system"}
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:baseten-oidc@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/artifactregistry.reader"
```
#### GCS access (for model weights)
Grant read access to Cloud Storage for downloading model weights:
```bash theme={"system"}
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:baseten-oidc@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"
```
### Create a Workload Identity Pool
Create a pool to manage external identities from Baseten:
```bash theme={"system"}
gcloud iam workload-identity-pools create baseten-pool \
--location="global" \
--display-name="Baseten Workload Identity Pool"
```
### Create a Workload Identity Provider
Add Baseten as an OIDC provider in the pool:
```bash theme={"system"}
gcloud iam workload-identity-pools providers create-oidc baseten-provider \
--location="global" \
--workload-identity-pool="baseten-pool" \
--issuer-uri="https://oidc.baseten.co" \
--allowed-audiences="oidc.baseten.co" \
--attribute-mapping="google.subject=assertion.sub" \
--attribute-condition="google.subject.startsWith('v=1:org=abcd1234:team=wxyz5678:')"
```
The **attribute mapping** `google.subject=assertion.sub` maps the OIDC `sub` claim into the `google.subject` attribute. After this mapping, you can use `google.subject` everywhere (including in `attribute-condition`) to reference the subject claim.
GCP doesn't support wildcard subject claims. Use `startsWith()` in `attribute-condition` to match workloads by prefix. Replace the organization and team IDs with your own values.
### Allow the Workload Identity to impersonate the service account
Grant the workload identity pool permission to act as the service account:
```bash theme={"system"}
gcloud iam service-accounts add-iam-policy-binding \
baseten-oidc@PROJECT_ID.iam.gserviceaccount.com \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/baseten-pool/*"
```
## Use OIDC in your Truss configuration
Once you've completed the AWS or GCP setup above, you can configure OIDC authentication in your Truss:
### Private registries (ECR, GCR)
For authenticating to private Docker registries using OIDC, see:
* **[AWS ECR OIDC](/development/model/dependencies#aws-oidc-recommended)**: Configure OIDC for AWS Elastic Container Registry.
* **[GCP Artifact Registry OIDC](/development/model/dependencies#gcp-oidc-recommended)**: Configure OIDC for Google Container Registry / Artifact Registry.
### Model weights (S3, GCS)
For downloading model weights from cloud storage using OIDC, see:
* **[AWS S3 OIDC](/development/model/bdn#aws-oidc)**: Configure OIDC for S3 model weights.
* **[GCS OIDC](/development/model/bdn#gcp-oidc-recommended)**: Configure OIDC for Google Cloud Storage model weights.
## Best practices
### Use least-privilege access
Use the most specific [subject claim pattern](#subject-claim-patterns) that fits your use case. Create separate roles or Workload Identity providers for different environments, workload types, or models rather than one role with broad permissions. Always test your OIDC configuration in a non-production environment first.
Don't grant access to `v=1:org=*:team=*:*`. This allows any Baseten workload to access your resources.
### Monitor and audit
* Enable CloudTrail (AWS) or Cloud Audit Logs (GCP) to track OIDC token usage.
* Set up alerts for unexpected access patterns.
* Regularly review which roles are being used.
## Troubleshooting
### Authentication failures
If your model fails to authenticate:
1. **Verify the trust relationship**: Ensure your IAM role trusts the Baseten OIDC provider (`https://oidc.baseten.co`).
2. **Check the audience**: Confirm the audience is set to `oidc.baseten.co`.
3. **Review subject claim conditions**: Verify your `sub` claim pattern matches the workload identity.
4. **Inspect your identifiers**: Run `truss whoami --show-oidc` to confirm your org and team IDs.
### Permission denied errors
If authentication succeeds but operations fail:
1. **Check IAM policies**: Ensure the role has the necessary permissions (for example, `s3:GetObject`, `ecr:BatchGetImage`).
2. **Verify resource ARNs**: Confirm bucket names, registry URLs, and other resource identifiers are correct.
3. **Review resource policies**: Some resources (like S3 buckets) have their own policies that may block access.
### Common error messages
| Error | Likely Cause | Solution |
| --------------------------------------------------------- | --------------------------------------- | ------------------------------------------- |
| "Not authorized to perform sts:AssumeRoleWithWebIdentity" | Trust policy doesn't match the workload | Check subject claim pattern in trust policy |
| "Access Denied" | Missing permissions in IAM policy | Add required permissions to the role |
| "Invalid identity token" | Issuer or audience mismatch | Verify OIDC provider configuration |
| "Token has expired" | Clock skew or token refresh issue | Contact Baseten support |
### Debug with CloudWatch/Cloud Logging
Enable detailed logging to see exactly why authentication or authorization is failing:
**AWS CloudTrail**: Look for `AssumeRoleWithWebIdentity` events to see token validation attempts.
**GCP Cloud Audit Logs**: Check `iam.googleapis.com` logs for workload identity authentication events.
## Migration from long-lived credentials
If you're currently using long-lived AWS or GCP credentials:
1. Set up OIDC as described above.
2. Update your Truss configuration to use OIDC authentication.
3. Deploy and test your model.
4. Once confirmed working, remove the long-lived credentials.
5. Delete any secrets containing long-lived credentials from Baseten.
Both OIDC and long-lived credential authentication methods are supported. You can migrate gradually, starting with non-production environments.
## Limitations
* OIDC tokens can't be customized.
* Baseten manages token lifetime and claims.
* Only AWS and GCP services are supported.
* GCP doesn't support wildcard subject claims or subject-based scoping in IAM role conditions. Use the Workload Identity Provider `attribute-condition` instead.
* Cloudflare R2, Azure containers, and Hugging Face aren't yet supported.
# Organization settings
Source: https://docs.baseten.co/organization/overview
Manage your Baseten organization's access, security, and resources.
* **[Access control](/organization/access)**: Manage roles and permissions.
* **[Teams](/organization/teams)**: Segment resources across multiple teams (Enterprise).
* **[Single sign-on (SSO)](/organization/sso)**: Authenticate users through your identity provider (Enterprise).
* **[API keys](/organization/api-keys)**: Authenticate requests for deployment, inference, and management.
* **[Secrets](/organization/secrets)**: Store and access sensitive credentials in deployed models.
* **[Restricted environments](/organization/restricted-environments)**: Control environment access.
* **[Audit logs](/organization/audit-logs)**: Track configuration changes and stream events to your SIEM.
# Restricted environments
Source: https://docs.baseten.co/organization/restricted-environments
Control access to sensitive environments like production with environment-level permissions.
Restricted environments let organization Admins lock down specific environments so that
only designated users can modify settings and configurations.
Use restricted environments to prevent unauthorized changes to critical
environments like production.
For more information on user roles, see
[Access control](/organization/access) and
[Environments](/deployment/environments).
## How restricted environments work
By default, environments are unrestricted, meaning any organization member can modify
deployments, autoscaling settings, and other configurations.
When you mark an environment as restricted, only users you explicitly grant access can
make changes.
Restricted environments apply across all models and Chains in your organization.
For example, if you restrict an environment named `production`, that restriction applies to
every model and chain's production environment, not just one specific model or chain.
If your organization uses [teams](/organization/teams), restricted environments are scoped to individual teams.
Team Admins can create and manage restricted environments for their team.
If your organization uses [SCIM](/organization/sso-and-scim#assign-roles-to-directory-groups), an Organization Admin can also grant directory groups access to a restricted environment, in addition to individual users.
### Permissions by access level
| Action | With access | Without access |
| :------------------------------------- | ----------- | -------------- |
| View environment and configuration | ✅ | ✅ (read-only) |
| View metrics | ✅ | ✅ (read-only) |
| Call inference on models and chains | ✅ | ✅ |
| View logs | ✅ | ✅ |
| Modify deployment settings | ✅ | ❌ |
| Change autoscaling configurations | ✅ | ❌ |
| Promote deployments to the environment | ✅ | ❌ |
| Manage environment-specific settings | ✅ | ❌ |
Users without access see a grayed-out UI for restricted actions.
They retain full read access and can still call inference endpoints.
## Manage restricted environments
Only organization **Admins** can create or modify restricted environments.
Members (non-admin users) can only create unrestricted environments and can't change
environment restrictions.
### From the environments page
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co), then open **Settings** and choose **Environments**.
2. Select an existing environment to modify, or choose **Create environment** to create a new one.
3. Set the access level to **Restricted**.
4. Add users by searching by name or by email.
5. Choose **Save changes** or **Create environment**.
### From a model or chain
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and choose **Dedicated Inference** in the sidebar, then select your model or chain.
2. Select an existing environment to modify, or choose **Add environment** then **Create environment** to create a new one.
3. Set the access level to **Restricted**.
4. Add users by searching by name or by email.
5. Choose **Save changes** or **Create environment**.
Only admins can create restricted environments, and all admins have implicit
access to every restricted environment. If an admin is later demoted to a member
role, they lose this implicit access and can be removed from the environment
like any other member.
### With the management API
You can read and update restriction settings programmatically through the [environment groups endpoints](/reference/management-api/environment-groups/list-environment-groups). Each environment group carries a `manage_access` object with its restriction state and the users granted access.
## Regional environments
When you create a restricted environment, you can further work with Baseten to add regional restrictions that guarantee inference traffic stays within a designated geographic region. [Contact support](mailto:support@baseten.co) to configure regional restrictions for your environments. See [Regional environments](/deployment/regional-environments) for more details.
## API behavior
Restricted environments apply the same permission checks to
[API](/reference/management-api/environments/create-an-environment) and
[truss CLI](/reference/cli/truss/push) operations as the UI. API keys inherit
the permissions of their associated user.
Attempting to modify a restricted environment with an API key associated with a
user without access returns a `403 Forbidden` error.
This includes operations like:
* Promoting deployments through the
[promote endpoint](/reference/management-api/deployments/promote/promotes-a-deployment-to-an-environment).
* Updating autoscaling settings through the
[autoscaling endpoint](/reference/management-api/deployments/autoscaling/updates-a-deployments-autoscaling-settings).
* Modifying environment configurations through the
[update environment endpoint](/reference/management-api/environments/update-an-environments-settings).
Users without access can still call inference endpoints, as restrictions only apply to
management operations.
# Secrets
Source: https://docs.baseten.co/organization/secrets
Store and access sensitive credentials in your deployed models.
Secrets store sensitive credentials like API keys, access tokens, and passwords that your models need at runtime.
Secrets are encrypted and injected into your model's environment when it runs.
If your organization uses [teams](/organization/teams), secrets are scoped to individual teams.
Models, Chains, and training projects deployed to a team can only access that team's secrets.
## Create a secret
To create a secret:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and go to [**Secrets**](https://app.baseten.co/settings/secrets) in your workspace settings. If your organization uses [teams](/organization/teams), open the team's settings page instead.
2. Enter a name for the secret.
3. Enter the secret value.
4. Choose **Add secret**.
Secret names follow these rules:
* Non-alphanumeric characters are normalized (for example, `hf_access_token` and `hf-access-token` are treated as the same name).
* Editing a secret's value overwrites the previous value.
* Changes take effect immediately for all deployments using the secret.
## Use secrets in your model
To use secrets in your Truss model, see [Secrets](/development/model/secrets).
## Security recommendations
* Create secrets through the Baseten dashboard, not in code.
* Use descriptive names that indicate the secret's purpose.
* Rotate secrets periodically by updating the value in the dashboard.
* Delete unused secrets to reduce exposure risk.
# SSO and SCIM
Source: https://docs.baseten.co/organization/sso-and-scim
Authenticate Baseten users through your identity provider and automatically provision accounts, directory groups, and roles.
Single sign-on (SSO) and System for Cross-domain Identity Management (SCIM) let your organization wire Baseten to your existing identity provider (IdP). SSO controls authentication (who can sign in). SCIM controls the identity lifecycle (who has an account, and what permissions they have once they sign in).
SSO and SCIM are available on the Enterprise plan.
## How it works
### Single sign-on
When SSO is enabled, sign-ins are routed to a hosted login page that delegates authentication to your IdP and returns the user to Baseten on success.
You don't run anything on Baseten's side. Once your IdP connection is configured, Baseten reads the authenticated identity and either signs the user in or provisions a new user account on the fly.
### SCIM
When SCIM is enabled, Baseten receives directory changes from your IdP through WorkOS. When you change a user, group, or membership in your IdP, those changes flow to Baseten and typically appear within a minute.
Baseten mirrors your IdP groups as **directory groups**. Directory groups are read-only in Baseten: you can't add or remove members or rename a group from the Baseten console. All membership changes happen in your IdP.
## Supported identity providers
Baseten supports any SAML 2.0 IdP and any SCIM 2.0 directory provider through WorkOS, including:
* Okta
* Microsoft Entra ID (Azure AD)
* Google Workspace
For the full list and provider-specific setup steps, see the [WorkOS SSO docs](https://workos.com/docs/sso) and [WorkOS Directory Sync docs](https://workos.com/docs/directory-sync).
## Enable SSO and SCIM
To enable SSO and SCIM, [contact support](mailto:support@baseten.co) with:
* Your Baseten organization name.
* The email address of the person who configures SSO and Directory Sync (usually an IT admin).
* The email domain or domains your users sign in with.
Support sends you a one-time link to the WorkOS admin portal with step-by-step instructions for configuring SSO and SCIM in your IdP. Once both connections are verified, SSO is required for all sign-ins to your organization and your synced directory groups appear in the **Directory Groups** section of the **Members** tab in **Organization settings**.
We also support enabling SSO without SCIM.
## Just-in-time provisioning
When a user signs in to Baseten through SSO for the first time, Baseten provisions a user account for them automatically, or **just-in-time**. Just-in-time provisioned users:
* Join your organization with the **Member** role.
* Are added to the [default team](/organization/teams) with the **Team Member** role.
If your organization has SCIM enabled, just-in-time provisioned users also:
* Join your organization with [effective permissions](#effective-permissions)
* Have their directory group memberships backfilled
Members can deploy and call models. They can't manage organization settings, billing, or other users.
To grant a user a different role or assign them to additional teams, an Organization Admin can update their assignments in **Organization settings** → **Members** after the first sign-in. Admins can also [invite](/organization/teams#invite-members-to-a-team) users directly to assign them specific roles in advance. The invitee still needs to sign in through SSO when opening the invite link.
## Assign roles to directory groups
With SCIM enabled, organization Admins can assign Baseten roles to directory groups. Group membership comes from your IdP; permissions are managed in Baseten.
To see your synced groups, navigate to **Organization settings** and select the **Members** tab. Your synced groups appear in the **Directory Groups** section, which shows each group's name, member count, assigned organization role, and last-synced timestamp.
### Organization roles
You can assign the organization Admin role to a directory group. The Member role is the default for any user who signs in through SSO and isn't granted the Admin role either directly or through a directory group.
**To change a directory group's organization role**:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and open **Organization settings**, then choose the **Members** tab.
2. In the **Directory Groups** section, find the group.
3. Select **Admin** from the group's role dropdown to grant organization-admin permissions to everyone in the group.
For what each organization role can do, see [Organization roles](/organization/teams#organization-roles).
### Team roles
If your organization has multiple teams enabled, an Organization or Team Admin can assign the **Team Admin** or **Team Member** role to a directory group, scoped to a specific team. Team-level group assignments apply only to that team. A user who belongs to multiple teams can have different team roles in each.
For the underlying role definitions, see [Team roles](/organization/teams#team-roles).
### Restricted environments
You can also grant directory groups access to [restricted environments](/organization/restricted-environments). See the restricted environments doc for the assignment flow.
### Effective permissions
A user's effective permissions are the union of their direct role assignments and the permissions inherited from every directory group they belong to. If any group grants a permission, the user has it; permissions can't be explicitly denied through groups. When a direct assignment and a group assignment grant different roles, the more-permissive role wins.
To audit where a user's permissions come from, select the user in **Organization settings** → **Members**. Each role is listed alongside its source, either a direct assignment or a specific directory group.
## Deprovisioning
To deprovision a user, an Organization Admin can delete them from the **Members** tab in **Organization settings**. Deletion revokes any logged-in sessions and personal API keys. Any service or pipeline that uses the user's personal API keys will no longer be able to authenticate.
If your organization has SCIM enabled, Baseten deprovisions users automatically based on changes in your IdP. When you delete a user from your IdP, mark them inactive, or remove them from every synced group, Baseten:
* Deactivates the user's Baseten account. The user can no longer sign in.
* Revokes every API key the user owns.
If you re-add the same user in your IdP later, Baseten restores their account along with their previous team memberships and roles. API keys aren't restored. The user needs to generate new keys after signing back in.
If your CI/CD or production workloads depend on a personal API key, migrate them to a [team API key](/organization/api-keys) so that deprovisioning a user doesn't break your pipelines.
## Require group-based assignment for admin roles
With SCIM enabled, you can require that users only hold the organization Admin role through directory-group membership. This enforces just-in-time admin access: pair it with IdP features like Okta's time-boxed group memberships, and admins gain access when they need it and lose access when their IdP membership expires.
Before you can enable the setting, at least one directory group must already hold the Admin role.
To enable this setting:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and open **Organization settings**, then choose the **Members** tab.
2. Choose **Require group-based assignment for admin roles**.
When you enable the setting:
* Baseten converts every direct Admin to a Member.
* Admin access requires membership in a directory group with the Admin role assigned. Direct Admin assignment is blocked while the setting is enabled.
Plan this change carefully. [Contact support](mailto:support@baseten.co) if you lose admin access and/or need to disable this requirement.
## Considerations
* SSO is enabled at the organization level. You can't selectively enable it for individual users or teams.
* Email domains must match the domains configured in your IdP connection. Users with email addresses outside your configured domains can't sign in through SSO.
* Directory groups are read-only in Baseten. Group membership changes happen in your IdP.
* To disable SSO or SCIM, [contact support](mailto:support@baseten.co). Disabling SCIM removes all directory groups and their role assignments from Baseten. User accounts and direct role assignments aren't affected.
# Teams
Source: https://docs.baseten.co/organization/teams
Organize your organization into multiple teams with isolated resources and granular access control.
Teams let you segment your Baseten organization into multiple isolated
groups, each with its own resources, members, and access controls. Use teams to
separate environments by function, project, or access level.
Teams are available for organizations on our Enterprise tier.
[Contact us](mailto:support@baseten.co) to enable teams for your
organization.
## How teams work
Every organization has a **default team** that contains all existing resources.
In the single-team world, you work within this default team without seeing any
team-specific UI.
When teams are enabled, Organization Admins can create additional teams within the
organization. Each team operates as an isolated unit with its own:
* Models, Chains, and training projects
* Secrets
* Team-level API keys
* Restricted environments
* Team members and roles
Billing remains at the organization level. All teams within an organization
share the same billing account and usage tracking.
## Roles and permissions
Teams introduce a two-level role hierarchy:
* Organization roles
* Team roles
### Organization roles
Organization-level roles determine what a user can do across the entire organization:
| Permission | Admin | Member |
| :-------------------------- | ----- | ------ |
| Manage billing | ✅ | ❌ |
| Manage teams | ✅ | ❌ |
| Manage organization members | ✅ | ❌ |
| View all teams | ✅ | ❌ |
Organization Admins have implicit admin-level access to all teams and all restricted environments.
### Team roles
Team-level roles determine what a user can do within a specific team:
| Permission | Team Admin | Team Member |
| :------------------------------------------- | ---------- | ----------- |
| Manage team members | ✅ | ❌ |
| Create restricted environments | ✅ | ❌ |
| Create team API keys | ✅ | ❌ |
| Deploy models, Chains, and training projects | ✅ | ✅ |
| Call models | ✅ | ✅ |
| View team resources | ✅ | ✅ |
A user can have different roles in different teams. For example, a data scientist might be a Team Admin for the Research team where they run experiments, while having Team Member access to the Inference team to deploy trained models.
If your organization uses [SCIM](/organization/sso-and-scim#assign-roles-to-directory-groups), you can assign these roles to directory groups. Every user in a directory group inherits the group's roles automatically.
## Manage teams
Organization Admins can create and delete teams. Team Admins can manage membership within their teams.
### Create a team
To create a team:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co), then select the dropdown next to the team name in the left navigation and choose **Create new team**.
2. Enter a team name and optionally select an icon.
3. Choose **Create team**.
The default team cannot be deleted, but you can rename it.
### Invite members to a team
To invite a new member and add them to teams:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and open **Organization settings**, then choose the **Members** tab.
2. Choose **Invite member**.
3. Enter the member's email address.
4. Select the organization role: **Admin** or **Member**.
5. Select the teams to add them to.
6. For each team, set their team role: **Team Admin** or **Team Member**.
7. Choose **Invite member**.
The invited user receives an email to join the organization and is automatically added to the selected teams with the specified roles.
To add an existing organization member to a team, navigate to the team's settings page, select the **Members** tab, and add them from there.
### Remove a member
To remove a member from the organization:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and open **Organization settings**, then choose the **Members** tab.
2. Find the member you want to remove.
3. Choose the trash icon next to their name.
Removing a member from the organization removes them from all teams.
To remove a member from a specific team without removing them from the organization, navigate to the team's settings page, select the **Members** tab, and remove them from there.
### Change a member's role
To change a member's organization or team roles:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and open **Organization settings**, then choose the **Members** tab.
2. Choose the pencil icon next to the member's name.
3. Update their organization role or team assignments as needed.
4. Choose **Save changes**.
You can also change a member's team role from the team's settings page by navigating to the **Members** tab.
### Switch between teams
Use the team selector in the navigation to switch between teams.
The team selector displays all teams you have access to.
Selecting a team filters the view to show only that team's resources and settings.
## Team-scoped resources
### Secrets
Secrets are scoped to individual teams.
Each team maintains its own set of secrets, and models deployed to a team can only access that team's secrets.
To manage secrets for a team:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co) and switch to the team using the team selector in the navigation.
2. Open **Settings** and choose **Secrets**.
3. Add or modify secrets for that team.
For more information, see [Best practices for secrets](/organization/secrets).
### API keys
API keys can be personal or team-scoped:
* **Personal API keys** are tied to your user account and provide access to resources across all teams you belong to. Use personal keys for local development and testing.
* **Team API keys** are scoped to a single team and can only access that team's resources. Use team keys for automation and production deployments. Only Team Admins and organization Admins can create team API keys.
Organization Admins can view and revoke every key in the workspace, including each member's personal API keys. See [View and revoke keys as an organization Admin](/organization/api-keys#view-and-revoke-keys-as-an-organization-admin).
To create a team API key:
1. Sign in to your workspace at [app.baseten.co](https://app.baseten.co), then open **Settings** and choose **API Keys**.
2. Choose **Create API Key**.
3. Choose the team to scope the key to.
4. Enter a name for the key.
5. Choose **Create**.
For more information, see [Best practices for API keys](/organization/api-keys).
### Restricted environments
Restricted environments work at the team level. When you create a restricted
environment, it applies to all models and Chains within that team.
For more information, see
[Restricted environments](/organization/restricted-environments).
## Deploy to a team
To deploy to a team, you can use the Truss CLI or the UI.
### Use the Truss CLI
To deploy a model to a specific team, use the `--team` flag with `truss push`:
```sh theme={"system"}
truss push --team your-team-name
```
If you omit the `--team` flag, Truss infers the target team using the following logic:
1. If you belong to only one team, Truss deploys to that team.
2. If a model with the same name exists in only one of your accessible teams, Truss deploys to that team.
3. If there is ambiguity (for example, the same model name exists in multiple teams), Truss prompts you to select a team.
In non-interactive contexts (CI runners, scripts, agent shells, or any invocation with `--non-interactive`), Truss can't prompt. If you belong to multiple teams and rules 1 and 2 above leave the target team ambiguous, `truss push` fails with:
```
Error: Team selection required but running in a non-interactive context. Pass --team (available: ...).
```
Pass `--team ` explicitly whenever you run `truss push` from automation. Requires Truss `0.18.3` or later.
### Use the UI
The team selector determines which team a model belongs to when you create or deploy through the Baseten console.
To deploy to a specific team, switch to that team before creating or deploying resources.
## Considerations
### Model APIs
Model APIs are only available in the default team.
You can't create or access Model APIs from other teams.
### Billing
Billing is managed at the organization level.
There's no team-level billing breakdown or budget controls.
All usage across teams is aggregated in the organization's [billing and usage dashboard](/organization/billing), which is visible only to organization Admins.
### Resource naming
Model and Chain names must be unique within a team.
The same name can exist in different teams, but this may require explicit team specification when using the Truss CLI.
## Migrate to multiple teams
When teams are enabled for your organization, all existing resources remain in the default team.
You can then create additional teams and organize resources based on your needs.
Common team structures include:
* **By organizational structure**: Create teams for distinct departments or groups within your organization using Baseten. The recommended way to manage environments on Baseten is with [deployment environments](/deployment/environments), since this allows for centralized management, promotion workflows, and varying levels of access control.
* **By function**: Separate teams for different projects or use cases (for example, a training team and an inference team).
* **By access level**: Separate teams based on who should have access to modify production resources.
There is no single correct way to structure teams.
Consider your organization's access control needs, how you want to isolate secrets and credentials, and how different groups within your organization work with Baseten.
To move a model or Chain to a different team, redeploy it while switched to the target team. The original resource in the default team can then be deleted if no longer needed.
# Baseten overview
Source: https://docs.baseten.co/overview
Baseten helps you train, deploy, and serve AI models at scale with high performance and cost efficiency.
Baseten is a training and inference platform.
Bring a model (an open-source LLM from Hugging Face, a fine-tuned checkpoint, or a custom model) and Baseten turns it into a production API endpoint with autoscaling, observability, and optimized serving infrastructure.
Baseten handles containerization, GPU scheduling across multiple clouds, and engine-level optimizations like TensorRT-LLM compilation, so you can focus on your model and your application.
If you want to skip deployment entirely and start making inference calls right now, [Model APIs](/inference/model-apis/overview) provide OpenAI-compatible endpoints for models like DeepSeek, Qwen, and GLM.
Point the OpenAI SDK at Baseten's URL to run inference in seconds.
If you're an AI lab serving your own hosted model to your own customers under a branded URL with federated keys and per-customer billing, [Frontier Gateway](/frontier-gateway/overview) is the managed gateway product for that.
Call a model through Model APIs in under two minutes. No deployment, no setup, just an API key and a request.
## Deploy a model
The most common way to deploy a model on Baseten is with [Truss](https://pypi.org/project/truss/), an open-source framework that packages your model into a deployable container.
For supported architectures (most popular open-source LLMs, embedding models, and image generators), you only need a `config.yaml` file.
Specify the model, the hardware, and the engine, and Truss handles the rest.
```yaml config.yaml theme={"system"}
model_name: Qwen-2.5-3B
resources:
accelerator: L4
trt_llm:
build:
base_model: decoder
checkpoint_repository:
source: HF
repo: "Qwen/Qwen2.5-3B-Instruct"
```
Run `truss push` and Baseten builds a TensorRT-optimized container, deploys it to GPU infrastructure, and provides an endpoint.
The model serves an OpenAI-compatible API out of the box.
When you need custom behavior like preprocessing, postprocessing, or a model architecture that the built-in engines don't support, Truss also supports [custom Python model code](/development/model/model-class).
Write a `Model` class with `load` and `predict` methods, and Truss packages it the same way.
Most teams start with config-only deployments and add custom code only when they need it.
Deploy a model to Baseten with just a config file. No custom code needed.
## Inference engines
Baseten optimizes every deployment with an inference engine tuned for your model's architecture. Select the engine that best supports your use case, and it handles the low-level performance work: quantization, tensor parallelism, KV cache management, and batching.
Dense text generation models compiled with TensorRT-LLM. Supports lookahead decoding and structured outputs.
Large mixture-of-experts models like DeepSeek R1 and Qwen3 MoE with KV-aware routing and distributed inference.
Embedding, reranking, and classification models with up to 1,400 client embeddings per second.
Choose the engine through a field in your `config.yaml`, or Baseten selects it automatically based on your model architecture.
## Multi-step workflows with Chains
Some applications need more than a single model call. A RAG pipeline retrieves documents, embeds them, and generates a response. An image generation workflow runs a diffusion model, upscales the result, and applies safety filtering.
[Chains](/development/chain/overview) is Baseten's framework for orchestrating these multi-step pipelines. Each step runs on its own hardware with its own dependencies, and Chains manages the data flow between them. Define the pipeline in Python, and Chains deploys, scales, and monitors each step independently.
## Training
Baseten also provides [training infrastructure](/training/overview) for fine-tuning and pre-training. With Training Jobs, bring your training scripts (Axolotl, TRL, Megatron, or custom code) and run them on H200 or H100 GPUs. [Loops](/loops/overview) is the Tinker-compatible alternative for LoRA fine-tuning and RL. Checkpoints sync automatically during training, and you can deploy a fine-tuned model from checkpoint to production endpoint in a single command with `truss train deploy_checkpoints`.
## Production infrastructure
Every deployment on Baseten runs on autoscaling infrastructure that adjusts replicas based on traffic. Configure minimum and maximum replicas, concurrency targets, and scale-down delays. Or use the defaults, which handle most workloads well. Models scale to zero when idle, eliminating costs during quiet periods, and scale up within seconds when traffic arrives.
Baseten schedules workloads across multiple cloud providers and regions through Multi-cloud Capacity Management (MCM). Your models stay available even during provider-level disruptions, and MCM routes traffic across regions to minimize latency.
Built-in [observability](/observability/metrics) gives you real-time metrics, logs, and request traces for every deployment. Export data to tools like Datadog or Prometheus, and debug behavior with full visibility into inputs, outputs, and errors.
## Find your path
Start with Model APIs and explore features that support production use cases.
* [Model APIs overview](/inference/model-apis/overview)
* [Structured outputs](/inference/structured-outputs)
* [Tool calling](/inference/function-calling)
* [RAG pipeline example](/examples/chains-build-rag)
Deploy models on dedicated infrastructure with a config-only Truss deployment and tune from there.
* [Deploy your first model](/development/model/build-your-first-model)
* [Engine selection](/engines)
* [Autoscaling](/deployment/autoscaling/overview)
* [Performance optimization](/development/model/performance-optimization)
Run training jobs and deploy results directly to production endpoints.
* [Training overview](/training/overview)
* [Get started with training](/training/getting-started)
* [Deploy from checkpoint](/training/deployment)
## Next steps
The build pipeline, request routing, autoscaling, and deployment lifecycle under the hood.
End-to-end guides for deploying and optimizing popular models.
Reference for the inference API, management API, and Truss CLI.
# Quickstart
Source: https://docs.baseten.co/quickstart
Start running inference on Baseten.
Baseten provides inference endpoints you can call directly, with no infrastructure to manage.
Run popular open-source LLMs like DeepSeek V4 Pro, GLM 5.1, and Kimi K2.6 through APIs compatible with the OpenAI and Anthropic SDKs.
For the full list, see [supported models](/inference/model-apis/overview#supported-models).
Set your base URL, set your API key, and send a request to an LLM hosted on Baseten.
## Set up your API key and SDK
Generate a [personal API key](/organization/api-keys#create-an-api-key) from your [Baseten account](https://app.baseten.co/signup) and install a client SDK to call models.
**Export your API key**
```bash theme={"system"}
export BASETEN_API_KEY="paste-your-api-key-here"
```
**Install a client SDK**
```bash Python theme={"system"}
uv pip install openai
```
```bash JavaScript theme={"system"}
npm install openai
```
## Run inference
Every Model API is compatible with the OpenAI SDK, with Anthropic SDK support in beta. Most also support [tool calling, structured outputs, and more](/inference/model-apis/overview#feature-support).
Call a model using the OpenAI SDK. This example uses `zai-org/GLM-5`, but you can swap in any [supported model](/inference/model-apis/overview#supported-models).
Create a chat completion:
```python chat.py {5-6,10} theme={"system"}
from openai import OpenAI
import os
client = OpenAI(
base_url="https://inference.baseten.co/v1",
api_key=os.environ["BASETEN_API_KEY"],
)
response = client.chat.completions.create(
model="zai-org/GLM-5",
messages=[
{"role": "user", "content": "What is inference in machine learning?"}
],
)
print(response.choices[0].message.content)
```
Create a chat completion:
```javascript chat.mjs {4-5,9} theme={"system"}
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://inference.baseten.co/v1",
apiKey: process.env.BASETEN_API_KEY,
});
const response = await client.chat.completions.create({
model: "zai-org/GLM-5",
messages: [
{ role: "user", content: "What is inference in machine learning?" }
],
});
console.log(response.choices[0].message.content);
```
```bash theme={"system"}
curl https://inference.baseten.co/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $BASETEN_API_KEY" \
-d '{
"model": "zai-org/GLM-5",
"messages": [
{"role": "user", "content": "What is inference in machine learning?"}
]
}'
```
Success looks like this:
```output theme={"system"}
Inference in machine learning refers to the process of using a trained model
to make predictions or generate outputs from new input data...
```
## Stream the response
Streaming returns the response token by token as the model generates it, instead of waiting for the full reply. The first tokens appear immediately, which makes chat UIs and other interactive applications feel responsive.
Set `stream=True` to receive tokens as they're generated:
```python stream.py {6} theme={"system"}
stream = client.chat.completions.create(
model="zai-org/GLM-5",
messages=[
{"role": "user", "content": "Write a haiku about machine learning."}
],
stream=True,
)
for chunk in stream:
if not chunk.choices:
continue
content = chunk.choices[0].delta.content
if content:
print(content, end="")
```
Set `stream: true` to receive tokens as they're generated:
```javascript stream.mjs {6} theme={"system"}
const stream = await client.chat.completions.create({
model: "zai-org/GLM-5",
messages: [
{ role: "user", content: "Write a haiku about machine learning." }
],
stream: true,
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) process.stdout.write(content);
}
```
## Explore Model API features
Generate JSON that conforms to a schema you define.
Let the model invoke functions and use the results in its response.
Enable extended thinking for multi-step problem solving.
## Next steps
Deploy models, run multi-step pipelines, train and fine-tune. See everything Baseten offers.
Go beyond Model APIs with a config-only Truss deployment on dedicated GPUs.
# Truss Push GitHub Action
Source: https://docs.baseten.co/reference/ci/github-action
Deploy and validate a Truss model or chain on Baseten from GitHub Actions.
```yaml theme={"system"}
- uses: basetenlabs/action-truss-push@v0.1
with:
truss-directory: "./my-model"
baseten-api-key: ${{ secrets.BASETEN_API_KEY }}
```
Deploys a Truss model or chain to Baseten, waits for the deployment to become active, optionally validates it with a predict request, and cleans up the deployment. For workflow examples, see [CI/CD](/deployment/ci-cd).
**Models** are detected when `truss-directory` points to a directory containing `config.yaml`. **Chains** are detected when `truss-directory` points to a `.py` file containing a `@chains.mark_entrypoint` class.
Pin to a specific release tag. Don't use `@main` because the action API may change between releases.
## Inputs
Path to a model directory containing `config.yaml`, or a `.py` file for chain deployments.
Baseten API key. Store this as an [encrypted secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets). Never hardcode it in your workflow file.
Override the model or chain name. For models, maps to `truss push --model-name`. For chains, sets the `chain_name`. If empty, the action uses `model_name` from `config.yaml` for models, or the entrypoint class name for chains.
Publish to a specific environment. Implies publish. If empty, no environment is set.
Attach git versioning info (SHA, branch, tag) to the deployment.
JSON string of labels as key-value pairs, for example `{"team": "ml", "project": "llm"}`. Attach metadata to track deployments in your CI pipeline.
Name of the deployment. If empty, defaults to `PR-{number}_{sha}` on pull requests or `{sha}` on direct pushes.
Deactivate the newly created deployment after validation. Useful for PR checks where you deploy, validate with a predict request, and tear down. Set to `false` when you want the deployment to remain active for manual inspection or when deploying to an environment.
The activate and deactivate calls this action makes are rate limited to 20 requests/minute per API key. See [management API rate limits](/reference/management-api/rate-limits) if you run high-volume CI.
JSON override for the predict request payload. For models, if empty, the action reads `model_metadata.example_model_input` from `config.yaml`. For chains, the predict payload must be provided explicitly. If neither is set, the predict step is skipped entirely and the deployment isn't validated.
Maximum minutes to wait for the deployment to become active. The default (45 minutes) accommodates large model builds like TRT-LLM. Reduce this for smaller models to fail faster.
Timeout in seconds for the predict request.
## Outputs
Baseten deployment ID. Use this to reference the deployment in downstream steps or API calls.
Baseten model ID. Set for model deployments only.
Baseten chain ID. Set for chain deployments only.
Model or chain name.
Wall-clock seconds from push to active. Useful for tracking build performance over time.
Response body from the predict call, truncated to 4 KB.
Final status of the action run. One of: `success`, `deploy_failed`, `deploy_timeout`, `predict_failed`, `cleanup_failed`.
## Status codes
| Status | Description |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `success` | Deployment active, predict passed (if payload configured), cleanup completed. |
| `deploy_failed` | `truss push` or image build failed. Check `config.yaml` syntax and API key. Build logs appear in collapsible sections in the GitHub Actions output. |
| `deploy_timeout` | Deployment didn't become active within `deploy-timeout-minutes`. Increase the timeout for large models. |
| `predict_failed` | Predict request returned an error or timed out. Verify the payload shape matches what the model expects. |
| `cleanup_failed` | Deployment deactivation failed. The deployment may still be running. Deactivate it manually from the dashboard. |
## Deployment naming
The action generates deployment names from Git context unless you override with `deployment-name`:
* **Pull requests:** `PR-{number}_{short_sha}` (for example, `PR-42_abc1234`).
* **Direct pushes:** `{short_sha}` (for example, `abc1234`).
## Permissions
The action requires only `contents: read` permission. No additional GitHub token permissions are needed.
```yaml theme={"system"}
permissions:
contents: read
```
# baseten api
Source: https://docs.baseten.co/reference/cli/baseten/api
Make raw API requests
Make raw HTTP requests to Baseten management or inference APIs.
The HTTP method defaults to GET, or POST when `--field`, `--raw-field`, or `--input` is provided. JSON responses are pretty-printed by default; non-JSON responses are streamed raw. Use `--jq` to filter JSON responses.
## management
```sh theme={"system"}
baseten api management [OPTIONS]
```
Make raw HTTP requests to the Baseten management API (api.baseten.co).
Paths are relative to /v1/, so 'baseten api management models' requests /v1/models.
### Options
Add a string field (key=value), parsed as JSON value
Add a request header (key:value)
Read request body from file (use - for stdin)
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
HTTP method, defaults to GET or POST if fields are provided
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Add a raw string field (key=value)
Enable verbose logging
### Examples
GET a management resource
```sh theme={"system"}
baseten api management models
```
POST a management resource with fields
```sh theme={"system"}
baseten api management models --field name=my-model
```
### Filter output with `--jq`
List model IDs from /v1/models
```sh theme={"system"}
baseten api management models --jq '.models[].id'
```
### Output
**Text mode (`--output text`):** The HTTP response body, passed through verbatim. JSON responses are pretty-printed; non-JSON responses are streamed raw to stdout.
**JSON mode (`--output json`):** payload type `cmd.JSONUndefined`.
Shape depends on the requested endpoint. See the management API OpenAPI spec at [https://api.baseten.co/v1/spec](https://api.baseten.co/v1/spec).
## inference
```sh theme={"system"}
baseten api inference [OPTIONS]
```
Make raw HTTP requests to a Baseten inference endpoint.
Requires either `--model-id` or `--chain-id` to identify the target. Use `--environment` to target a specific environment (e.g. production).
### Options
Chain ID to target
Environment name (e.g. production)
Add a string field (key=value), parsed as JSON value
Add a request header (key:value)
Read request body from file (use - for stdin)
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
HTTP method, defaults to GET or POST if fields are provided
Model ID to target
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Add a raw string field (key=value)
Enable verbose logging
### Examples
POST a predict body to a model
```sh theme={"system"}
baseten api inference production/predict --model-id --field prompt=hello
```
### Filter output with `--jq`
Filter a JSON predict response
```sh theme={"system"}
baseten api inference production/predict --model-id --field prompt=hello --jq '.result'
```
### Output
**Text mode (`--output text`):** The inference endpoint's response body, passed through verbatim. JSON responses are pretty-printed; non-JSON responses are streamed raw.
**JSON mode (`--output json`):** payload type `cmd.JSONUndefined`.
Shape depends on the model and endpoint. See the inference API OpenAPI spec at [https://api.baseten.co/inference-spec](https://api.baseten.co/inference-spec).
# baseten auth
Source: https://docs.baseten.co/reference/cli/baseten/auth
Manage authentication
Log in, log out, and manage Baseten credentials.
Each set of credentials is stored as a named profile. Select a profile per command with `--profile` or the `BASETEN_PROFILE` environment variable, or set the default with `baseten auth switch`. Credentials are stored in the system keyring when available, with a plaintext fallback in the config directory.
## login
```sh theme={"system"}
baseten auth login [OPTIONS]
```
Log in to Baseten through your browser (OAuth device flow) or API key, storing a named profile.
By default, opens a browser for interactive login. Use `--web` to skip prompts (suitable for non-TTY environments). Use `--with-api-key` to provide an API key (reads from stdin, or prompts interactively if TTY).
Browser logins name the profile after your email; API key logins require an explicit `--profile` name. The new profile becomes current unless `--no-switch` is given.
### Options
Store credentials in plain text instead of system keyring
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Store the profile without making it the current profile
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Baseten remote URL for this profile (default [https://app.baseten.co](https://app.baseten.co))
Use browser login without interactive prompts
Read API key from stdin
Enable verbose logging
### Examples
Browser-based login (OAuth device flow)
```sh theme={"system"}
baseten auth login --web
```
Provide an API key on stdin under a named profile
```sh theme={"system"}
echo $API_KEY | baseten auth login --with-api-key --profile
```
### Filter output with `--jq`
Print just the new profile name
```sh theme={"system"}
baseten auth login --web --jq '.profile'
```
### Output
**Text mode (`--output text`):** Prints "Logged in as `email` (`workspace`) as profile `profile`" to stdout on success.
**JSON mode (`--output json`):** payload type `cmd.AuthLoginResult`.
## logout
```sh theme={"system"}
baseten auth logout [OPTIONS]
```
Remove a stored profile and its credentials. Defaults to the current profile; pass `--profile` to choose another. For OAuth credentials, also revokes the session.
### Options
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Enable verbose logging
### Examples
Log out the current profile
```sh theme={"system"}
baseten auth logout
```
Log out a specific profile
```sh theme={"system"}
baseten auth logout --profile
```
### Filter output with `--jq`
Print just the logged-out profile name
```sh theme={"system"}
baseten auth logout --jq '.profile'
```
### Output
**Text mode (`--output text`):** Prints "Logged out `profile`" to stdout on success.
**JSON mode (`--output json`):** payload type `cmd.AuthLogoutResult`.
## switch
```sh theme={"system"}
baseten auth switch [OPTIONS]
```
Set the current profile used when no profile is selected with `--profile` or `BASETEN_PROFILE`.
### Options
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Enable verbose logging
### Examples
Switch to a specific profile non-interactively
```sh theme={"system"}
baseten auth switch --profile
```
### Filter output with `--jq`
Print just the new current profile
```sh theme={"system"}
baseten auth switch --profile --jq '.profile'
```
### Output
**Text mode (`--output text`):** Prints "Switched to `profile`" to stdout on success.
**JSON mode (`--output json`):** payload type `cmd.AuthSwitchResult`.
## status
```sh theme={"system"}
baseten auth status [OPTIONS]
```
Show the resolved authentication state, including the profile, remote, and auth type.
### Options
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Enable verbose logging
### Examples
Show the current auth status
```sh theme={"system"}
baseten auth status
```
### Filter output with `--jq`
Print just the auth type
```sh theme={"system"}
baseten auth status --jq '.auth_type'
```
### Output
**Text mode (`--output text`):** Summary of the resolved profile: profile name, remote URL, and auth type.
**JSON mode (`--output json`):** payload type `cmd.AuthStatusResult`.
# baseten model
Source: https://docs.baseten.co/reference/cli/baseten/model
Manage Baseten models
Create, list, and push Baseten models.
Authentication is through 'baseten auth login' or the BASETEN\_API\_KEY environment variable.
## push
```sh theme={"system"}
baseten model push [OPTIONS] [--dir DIR]
```
Build a model archive, upload it to Baseten, and create either a new model or a new deployment of an existing model.
The current directory is used by default; pass `--dir` to push a model directory at another path.
The model is identified by the `model_name` field in config.yaml. Use `--override-name` to override that for this push only.
### Options
Deployment timeout as a Go duration (e.g. 30m, 1h); allowed range 10m to 24h.
Human-readable name for the new deployment.
Push as a development deployment: the model's single mutable dev slot, created if absent and overwritten in place otherwise. Incompatible with --environment and --deployment-name.
Model directory to push. Defaults to the current directory.
Disable archive download for the new model. Only valid for new models.
Validate the push and request upload credentials without uploading or creating anything.
Stable environment to push to.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
User-provided labels for the deployment as a JSON object, e.g. '\{"team":"ml","priority":1}'.
Force a full rebuild without using cached layers.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use this deployment's instance type instead of preserving the target environment's. Only meaningful when an environment is targeted.
Override the model\_name from config.yaml for this push only. The on-disk config.yaml is not modified.
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Stream build and runtime logs to stderr after pushing. Logs are always text-formatted; use 'baseten model deployment logs --tail' for structured log streaming.
Team the model belongs to. Only valid for new models.
Block until the deployment is active. Exits non-zero on a terminal-failure status.
After pushing, watch the model directory and live-patch the development deployment on change. Implies --develop.
With --watch, hot-reload the running container when every change is to model code; mixed changes fall back to a cold patch.
With --watch, let the development deployment scale to zero while watching. By default it is kept warm by periodic pings.
Enable verbose logging
### Examples
Push the current directory as a new deployment
```sh theme={"system"}
baseten model push
```
Push and stream build/runtime logs until the deployment is active
```sh theme={"system"}
baseten model push --tail --wait
```
### Filter output with `--jq`
Print the new deployment's predict URL
```sh theme={"system"}
baseten model push --jq '.predict_url'
```
### Output
**Text mode (`--output text`):** Narrative summary on stdout: success banner, deployment facts, then grouped next-step hints for viewing logs, invoking the model, and (when the pushed config enables it) SSH access; hints scoped to an environment are tagged "(once deployed)". Under `--output json` the narrative is redirected to stderr so stdout stays a clean JSON document.
**JSON mode (`--output json`):** payload type `cmd.ModelPushResult`.
Under `--dry-run` no upload or deployment happens; the push is validated, upload credentials are requested, and stdout is the empty JSON object `\{\}`. Otherwise stdout is the full model+deployment result.
## watch
```sh theme={"system"}
baseten model watch [OPTIONS] [--dir DIR]
```
Watch a model directory and patch the model's development deployment in place on every change, skipping a full rebuild.
The current directory is used by default; pass `--dir` to watch a model directory at another path. The model is identified by the `model_name` field in that directory's config.yaml, like `baseten model push`.
The model must already have a development deployment; if it does not, run `baseten model push --develop` (or `baseten model push --watch`) first.
Runs until interrupted. Some changes cannot be expressed as a patch (removing config.yaml, or any change under the data directory); the watcher reports these and you must re-push.
### Options
Model directory to watch. Defaults to the current directory.
Hot-reload the running container when every change is to model code; mixed changes fall back to a cold patch.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Let the development deployment scale to zero while watching. By default it is kept warm by periodic pings.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team the model belongs to. Use to disambiguate when the same model\_name exists in multiple teams.
Enable verbose logging
### Examples
Watch the current directory against its model's development deployment
```sh theme={"system"}
baseten model watch
```
Watch another directory and hot-reload on model-code changes
```sh theme={"system"}
baseten model watch --dir ./my-model --hot-reload
```
### Output
**Text mode (`--output text`):** Streams patch and sync status to stderr as changes are applied. Runs until interrupted and produces no stdout output.
**JSON mode (`--output json`):** payload type `cmd.JSONUndefined`.
## list
```sh theme={"system"}
baseten model list [OPTIONS]
```
List Baseten models.
### Options
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID to scope the listing to. Defaults to all teams the caller can see.
Enable verbose logging
### Examples
List all models accessible to the caller
```sh theme={"system"}
baseten model list
```
List only models in a specific team
```sh theme={"system"}
baseten model list --team my-team
```
### Filter output with `--jq`
Print just the model IDs
```sh theme={"system"}
baseten model list --jq '.models[].id'
```
### Output
**Text mode (`--output text`):** Table with columns: ID, NAME, TEAM, DEPLOYMENTS, CREATED. When no models exist, prints "No models found." to stderr.
**JSON mode (`--output json`):** payload type `managementapi.Models`.
## describe
```sh theme={"system"}
baseten model describe [OPTIONS]
```
Describe a Baseten model.
### Options
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Enable verbose logging
### Examples
Describe a model by ID
```sh theme={"system"}
baseten model describe --model-id
```
Describe a model by name
```sh theme={"system"}
baseten model describe --model-name
```
### Filter output with `--jq`
Print the production deployment ID
```sh theme={"system"}
baseten model describe --model-id --jq '.production_deployment_id'
```
### Output
**Text mode (`--output text`):** Field-per-line summary: ID, Name, Team, Deployments, Instance, Production, Development, Created. Optional fields are omitted when unset.
**JSON mode (`--output json`):** payload type `managementapi.Model`.
## predict
```sh theme={"system"}
baseten model predict [OPTIONS]
```
POST a JSON request to a model and write the response to stdout.
Targets the production environment by default. Use `--environment`, `--deployment-id`, `--deployment-name`, or `--regional` to target something else.
Streaming responses (Transfer-Encoding: chunked) are passed through as they arrive. For machine-readable streaming JSON from OpenAI-compatible models, use `--output jsonl`.
### Options
Inline JSON request body.
Mutually exclusive with other flags in group `predict-input`.
Specific deployment to target. Mutually exclusive with --environment, --deployment-name, and --regional.
Name of the deployment to target. Mutually exclusive with --environment, --deployment-id, and --regional.
Environment to target (e.g. production, development). Defaults to production. Mutually exclusive with --deployment-id, --deployment-name, and --regional.
Path to a JSON file containing the request body. Use '-' for stdin.
Mutually exclusive with other flags in group `predict-input`.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Regional environment name; routes through the regional hostname. Mutually exclusive with --environment, --deployment-id, and --deployment-name.
Team name or ID. Only valid with --model-name.
Use the WebSocket predict endpoint. Sends the body as one frame, reads one frame back, then closes. Not for multi-message or back-and-forth sessions.
Enable verbose logging
### Examples
Send an inline JSON body
```sh theme={"system"}
baseten model predict --model-id --data '{"prompt":"hello"}'
```
Send a request body from a file
```sh theme={"system"}
baseten model predict --model-id --file request.json
```
### Filter output with `--jq`
Extract a field when the model returns JSON
```sh theme={"system"}
baseten model predict --model-id --data '{"x":1}' --jq '.result'
```
### Output
**Text mode (`--output text`):** The model's response body, passed through verbatim. May be JSON, plain text, or binary, and may stream when the model uses chunked transfer encoding or SSE.
**JSON mode (`--output json`):** payload type `cmd.JSONUndefined`.
Under `--output json`, binary frames are base64-encoded under a 'body' key. Under `--output jsonl`, each SSE or binary chunk is emitted as its own record, one per line.
## delete
```sh theme={"system"}
baseten model delete [OPTIONS]
```
Delete a Baseten model and all of its deployments.
Prompts for the model name to confirm the deletion. Pass `--yes` to skip the prompt. When stdin is not a terminal, `--yes` is required.
### Options
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Skip the interactive confirmation prompt. Required when stdin is not a terminal.
Enable verbose logging
### Examples
Delete by ID without confirmation
```sh theme={"system"}
baseten model delete --model-id --yes
```
Delete by name with interactive confirmation
```sh theme={"system"}
baseten model delete --model-name
```
### Filter output with `--jq`
Print the deleted model's ID
```sh theme={"system"}
baseten model delete --model-id --yes --jq '.id'
```
### Output
**Text mode (`--output text`):** On success, prints "Deleted model `name` (`id`)" to stderr; no stdout output.
**JSON mode (`--output json`):** payload type `managementapi.ModelTombstone`.
# baseten model-api
Source: https://docs.baseten.co/reference/cli/baseten/model-api
Manage Model APIs
List and inspect Baseten Model APIs.
Authenticate with `baseten auth login` or the `BASETEN_API_KEY` environment variable.
## describe
```sh theme={"system"}
baseten model-api describe [OPTIONS]
```
Describe a single Model API by name.
### Options
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Name of the Model API to describe.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Enable verbose logging
### Examples
Describe a Model API by name
```sh theme={"system"}
baseten model-api describe --model
```
### Filter output with `--jq`
Print the Model API's invoke URL
```sh theme={"system"}
baseten model-api describe --model --jq '.invoke_url'
```
### Output
**Text mode (`--output text`):** Field-per-line summary of the Model API.
**JSON mode (`--output json`):** payload type `managementapi.ModelAPI`.
## list
```sh theme={"system"}
baseten model-api list [OPTIONS]
```
List the Model APIs in the full visible catalog.
Pass `--added-only` to restrict to just the Model APIs the workspace has added.
CLI v0.3.0 removed `--all` and changed the default: `baseten model-api list` now returns the full catalog instead of just added Model APIs. Scripts that relied on the old default should pass `--added-only`.
### Options
Restrict to the Model APIs the workspace has added instead of the full visible catalog.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Enable verbose logging
### Examples
List the full visible catalog of Model APIs
```sh theme={"system"}
baseten model-api list
```
List only the Model APIs the workspace has added
```sh theme={"system"}
baseten model-api list --added-only
```
### Filter output with `--jq`
Print just the Model API names
```sh theme={"system"}
baseten model-api list --jq '.items[].name'
```
### Output
**Text mode (`--output text`):** Table with columns: NAME, CONTEXT, $/1M IN, $/1M OUT, ADDED. When no Model APIs match, prints "No Model APIs found." to stderr.
**JSON mode (`--output json`):** payload type `cmd.ModelAPIList`.
## predict
```sh theme={"system"}
baseten model-api predict [OPTIONS]
```
POST an inference request to a Model API and write the response to stdout.
The request is sent to `--url`, which defaults to the OpenAI chat-completions endpoint on the shared inference host. Override it for other shapes (e.g. /v1/messages, /v1/embeddings) or different hosts.
`--content` is the simple path: it builds an OpenAI chat-completions body with a single user message and `--model` as the model, and prints just the assistant's reply. It is only valid for OpenAI chat URLs and requires `--model`.
`--data` and `--file` send a request body verbatim, so any format the endpoint accepts works (OpenAI, Anthropic, embeddings, custom). The response is written as-is: JSON is pretty-printed, streams and binary bodies are passed through.
### Options
Single user message; builds an OpenAI chat-completions request and prints the assistant's reply. Only valid for OpenAI chat URLs and requires --model.
Mutually exclusive with other flags in group `predict-input`.
Inline request body, sent verbatim.
Mutually exclusive with other flags in group `predict-input`.
Path to a file containing the request body, sent verbatim. Use '-' for stdin.
Mutually exclusive with other flags in group `predict-input`.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Name of the Model API. Required with --content, where it sets the request's model.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Endpoint to POST the request to. Defaults to [https://inference.baseten.co/v1/chat/completions](https://inference.baseten.co/v1/chat/completions).
Enable verbose logging
### Examples
Send a single user message
```sh theme={"system"}
baseten model-api predict --model --content "hello"
```
Send a full OpenAI-shaped body and stream it as JSONL
```sh theme={"system"}
baseten model-api predict --model --data '{"model":"","messages":[{"role":"user","content":"hi"}],"stream":true}' --output jsonl
```
### Filter output with `--jq`
Extract the assistant's message content
```sh theme={"system"}
baseten model-api predict --model --content "hi" --jq '.choices[0].message.content'
```
### Output
**Text mode (`--output text`):** With `--content`, the assistant message text. With `--data`/`--file`, the response body as-is (pretty-printed JSON, or a raw stream/binary body).
**JSON mode (`--output json`):** payload type `cmd.JSONUndefined`.
Under `--output json`, `--content` emits the full chat-completions response. For `--data`/`--file`, a streamed response becomes one JSON record per chunk under `--output jsonl`, and a binary body is base64-encoded under a 'body' key.
# baseten model deployment
Source: https://docs.baseten.co/reference/cli/baseten/model-deployment
Manage deployments of a model
## activate
```sh theme={"system"}
baseten model deployment activate [OPTIONS]
```
Activate a model deployment.
### Options
ID of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Name of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Enable verbose logging
### Examples
Activate a deployment
```sh theme={"system"}
baseten model deployment activate --model-id --deployment-id
```
### Filter output with `--jq`
Print just the success flag
```sh theme={"system"}
baseten model deployment activate --model-id --deployment-id --jq '.success'
```
### Output
**Text mode (`--output text`):** On success, prints "Activated deployment `id`" to stderr; no stdout output.
**JSON mode (`--output json`):** payload type `managementapi.ActivateResponse`.
## config
```sh theme={"system"}
baseten model deployment config [OPTIONS]
```
Fetch the config of a deployed model.
By default prints the original config.yaml. Use `--output json` to emit the full response \{config, raw\_config} as JSON.
### Options
ID of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Name of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Enable verbose logging
### Examples
Print the deployment's config.yaml
```sh theme={"system"}
baseten model deployment config --model-id --deployment-id
```
### Filter output with `--jq`
Extract the parsed model\_name field
```sh theme={"system"}
baseten model deployment config --model-id --deployment-id --jq '.config.model_name'
```
### Output
**Text mode (`--output text`):** The original config.yaml text (preserving comments and ordering) when available, otherwise the parsed config marshaled as YAML.
**JSON mode (`--output json`):** payload type `managementapi.DeploymentConfigResponse`.
The full \{config, raw\_config} envelope. raw\_config is the original config.yaml text; config is the parsed shape.
## deactivate
```sh theme={"system"}
baseten model deployment deactivate [OPTIONS]
```
Deactivate a model deployment.
Prompts for yes/no confirmation. Pass `--yes` to skip the prompt. When stdin is not a terminal, `--yes` is required.
### Options
ID of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Name of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Skip the interactive confirmation prompt. Required when stdin is not a terminal.
Enable verbose logging
### Examples
Deactivate a deployment without the confirmation prompt
```sh theme={"system"}
baseten model deployment deactivate --model-id --deployment-id --yes
```
### Filter output with `--jq`
Print just the success flag
```sh theme={"system"}
baseten model deployment deactivate --model-id --deployment-id --yes --jq '.success'
```
### Output
**Text mode (`--output text`):** On success, prints "Deactivated deployment `id`" to stderr; no stdout output.
**JSON mode (`--output json`):** payload type `managementapi.DeactivateResponse`.
## download
```sh theme={"system"}
baseten model deployment download [OPTIONS]
```
Download the Truss source for a model deployment as an uncompressed tar.
Exactly one of `--out-file` or `--out-dir` is required. `--out-file` writes the raw tar bytes; `--out-dir` extracts the tar into the directory. Use `--overwrite` to replace an existing file or write into a non-empty directory.
### Options
ID of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Name of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Extract the Truss tar into this directory.
Mutually exclusive with other flags in group `download-out`.
Save the Truss as an uncompressed tar file at this path.
Mutually exclusive with other flags in group `download-out`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Allow overwriting an existing file or non-empty directory.
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Enable verbose logging
### Examples
Save the Truss as a tar file
```sh theme={"system"}
baseten model deployment download --model-id --deployment-id --out-file truss.tar
```
Extract the Truss into a directory
```sh theme={"system"}
baseten model deployment download --model-id --deployment-id --out-dir ./truss
```
### Filter output with `--jq`
Print just the destination path
```sh theme={"system"}
baseten model deployment download --model-id --deployment-id --out-file truss.tar --jq '.out_file'
```
### Output
**Text mode (`--output text`):** Writes the Truss to disk; prints progress and the final destination path to stderr; no stdout output.
**JSON mode (`--output json`):** payload type `cmd.ModelDeploymentDownloadResult`.
On success, stdout is a JSON object with either out\_file or out\_dir set to the path written.
## promote
```sh theme={"system"}
baseten model deployment promote [OPTIONS]
```
Promote a model deployment to an environment.
Defaults to the production environment. Cleanup of the previous deployment is controlled by the target environment's promotion cleanup strategy.
Prompts for yes/no confirmation. Pass `--yes` to skip the prompt. When stdin is not a terminal, `--yes` is required.
### Options
ID of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Name of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Target environment name. Defaults to production.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use this deployment's instance type instead of preserving the target environment's.
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Skip the interactive confirmation prompt. Required when stdin is not a terminal.
Enable verbose logging
### Examples
Promote a deployment to production without the confirmation prompt
```sh theme={"system"}
baseten model deployment promote --model-id --deployment-id --yes
```
Promote to a non-production environment using the deployment's own instance type
```sh theme={"system"}
baseten model deployment promote --model-id --deployment-id --environment staging --override-env-instance-type --yes
```
### Filter output with `--jq`
Print the promoted deployment's status
```sh theme={"system"}
baseten model deployment promote --model-id --deployment-id --yes --jq '.status'
```
### Output
**Text mode (`--output text`):** On success, prints "Promoted deployment `id` to environment `env`" to stderr; no stdout output.
**JSON mode (`--output json`):** payload type `managementapi.Deployment`.
Under `--output json`, the promoted deployment object.
## delete
```sh theme={"system"}
baseten model deployment delete [OPTIONS]
```
Delete a single model deployment.
Deployments associated with an environment (e.g. production, development) and the only deployment of a model cannot be deleted server-side.
Prompts for yes/no confirmation. Pass `--yes` to skip the prompt. When stdin is not a terminal, `--yes` is required.
### Options
ID of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Name of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Skip the interactive confirmation prompt. Required when stdin is not a terminal.
Enable verbose logging
### Examples
Delete a deployment without the confirmation prompt
```sh theme={"system"}
baseten model deployment delete --model-id --deployment-id --yes
```
### Filter output with `--jq`
Print the deleted deployment's ID
```sh theme={"system"}
baseten model deployment delete --model-id --deployment-id --yes --jq '.id'
```
### Output
**Text mode (`--output text`):** On success, prints "Deleted deployment `id`" to stderr; no stdout output.
**JSON mode (`--output json`):** payload type `managementapi.DeploymentTombstone`.
## describe
```sh theme={"system"}
baseten model deployment describe [OPTIONS]
```
Describe a model deployment by ID.
### Options
ID of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Name of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Enable verbose logging
### Examples
Describe a deployment by ID
```sh theme={"system"}
baseten model deployment describe --model-id --deployment-id
```
### Filter output with `--jq`
Print just the deployment status
```sh theme={"system"}
baseten model deployment describe --model-id --deployment-id --jq '.status'
```
### Output
**Text mode (`--output text`):** Field-per-line summary: ID, Name, Model, Environment (optional), Status, Instance (optional), Replicas, Created.
**JSON mode (`--output json`):** payload type `managementapi.Deployment`.
## list
```sh theme={"system"}
baseten model deployment list [OPTIONS]
```
List all deployments of a model.
### Options
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Enable verbose logging
### Examples
List all deployments of a model
```sh theme={"system"}
baseten model deployment list --model-id
```
### Filter output with `--jq`
Print just the deployment IDs
```sh theme={"system"}
baseten model deployment list --model-id --jq '.deployments[].id'
```
### Output
**Text mode (`--output text`):** Table with columns: ID, NAME, ENVIRONMENT, STATUS, INSTANCE, REPLICAS, CREATED. When no deployments exist, prints "No deployments found." to stderr.
**JSON mode (`--output json`):** payload type `managementapi.Deployments`.
## logs
```sh theme={"system"}
baseten model deployment logs [OPTIONS]
```
Fetch logs for a model deployment.
By default returns up to `--limit` lines from the last 30 minutes, newest first, and prints a note to stderr when `--limit` trims older lines. Use `--start`/`--end` or `--since` to scope the window (max 7 days). Use `--tail` to stream live logs until the deployment leaves a runnable state or you interrupt with Ctrl-C.
For machine-readable streaming, prefer `--output jsonl` over `--output json`.
For request-ID tracing, scope, and log export, see [Logs](/observability/logs).
### Options
ID of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Name of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
End of the log time range. Accepts ISO 8601; values without a timezone designator are interpreted in the local timezone. Default is now. Window must be at most 7 days.
Case-sensitive substring; lines containing it are dropped. May be repeated.
Case-sensitive substring that must appear in the log message. May be repeated; all must match.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Maximum number of log lines to return, paging backward from the end of the window. Use 0 for no limit (every log line in the window). Not applicable with --tail.
Only return logs at or above this severity level.
One of: `debug`, `info`, `warning`, `error`
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Only return logs emitted by this replica (5-char short ID).
Only return logs tagged with this inference request ID.
RE2 regular expression matched against the log message. Prefer --includes and --excludes for plain substring matches.
Shortcut for fetching logs from a relative time ago until now. Accepts a Go duration (e.g. '30m', '1h30m') or '`N`d' (e.g. '3d'). Maximum '7d'. Mutually exclusive with --start and --end.
Start of the log time range. Accepts ISO 8601 (e.g. '2026-05-14', '2026-05-14T12:00:00', '2026-05-14T12:00:00Z'). Values without a timezone designator are interpreted in the local timezone. Default is 30 minutes before the end. Window must be at most 7 days.
Stream new logs as they arrive until the deployment leaves a runnable state or you interrupt with Ctrl-C. Cannot be combined with the time-range or filter flags. For machine-readable streaming, prefer --output jsonl over --output json.
Team name or ID. Only valid with --model-name.
Enable verbose logging
### Examples
Print logs for a deployment over the last hour
```sh theme={"system"}
baseten model deployment logs --model-id --deployment-id --since 1h
```
Print logs for a fixed time range
```sh theme={"system"}
baseten model deployment logs --model-id --deployment-id --start 2026-05-14T00:00:00Z --end 2026-05-15T00:00:00Z
```
Tail live logs until the deployment leaves a runnable state
```sh theme={"system"}
baseten model deployment logs --model-id --deployment-id --tail
```
Filter to warnings and above that contain a term
```sh theme={"system"}
baseten model deployment logs --model-id --deployment-id --min-level warning --includes timeout
```
### Filter output with `--jq`
Stream just the log messages as a JSONL stream
```sh theme={"system"}
baseten model deployment logs --model-id --deployment-id --output jsonl --jq '.message'
```
### Output
**Text mode (`--output text`):** One line per log record: "\[YYYY-MM-DD HH:MM:SS]: (replica) message".
**JSON mode (`--output json`):** payload type `managementapi.Log`.
## metrics
```sh theme={"system"}
baseten model deployment metrics [OPTIONS]
```
Fetch metrics for a model deployment. Use `--mode current` for a snapshot, `--mode summary` to aggregate a window, or `--mode series` to plot values over time. Scope the window with `--since` or `--start`/`--end` (max 7 days; only applies to summary and series), and select metrics with one or more `--metric` flags.
### Options
ID of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Name of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
End of the metrics time range. Accepts ISO 8601; values without a timezone designator are interpreted in the local timezone. If omitted, the server defaults the end to now. Window must be at most 7 days.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Name of a metric to return; see [https://docs.baseten.co/observability/export-metrics/supported-metrics](https://docs.baseten.co/observability/export-metrics/supported-metrics) for the available names. May be repeated. When omitted, a default set is returned.
Aggregation mode. 'current' returns an instantaneous snapshot at now; 'summary' aggregates the whole window into one value per metric; 'series' returns evenly-spaced points across the window. --start/--end/--since are only meaningful for summary and series.
One of: `current`, `summary`, `series`
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
For --mode series, emit a per-step table instead of sparklines.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Shortcut for a window from a relative time ago until now. Accepts a Go duration (e.g. '30m', '1h30m') or '`N`d' (e.g. '3d'). Maximum '7d'. Mutually exclusive with --start and --end.
Start of the metrics time range. Accepts ISO 8601 (e.g. '2026-05-14', '2026-05-14T12:00:00', '2026-05-14T12:00:00Z'). Values without a timezone designator are interpreted in the local timezone. If omitted, the server defaults the start to one hour before the end. Window must be at most 7 days.
Team name or ID. Only valid with --model-name.
Enable verbose logging
### Examples
Show a current snapshot of the default metrics
```sh theme={"system"}
baseten model deployment metrics --model-name --deployment-id
```
Summarize request volume and latency over the last hour
```sh theme={"system"}
baseten model deployment metrics --model-id --deployment-id --mode summary --since 1h --metric baseten_inference_requests_total --metric baseten_end_to_end_response_time_seconds
```
Plot a series over the last 6 hours
```sh theme={"system"}
baseten model deployment metrics --model-id --deployment-id --mode series --since 6h
```
### Filter output with `--jq`
Print the metric names returned
```sh theme={"system"}
baseten model deployment metrics --model-id --deployment-id --jq '.metric_descriptors[].name'
```
### Output
**Text mode (`--output text`):** For `current` and `summary`, a table with columns METRIC, one column per label dimension (for example QUANTILE, STAT), and VALUE; summary counter values show "total (rate/s)". For `series`, a sparkline per metric label set with its min-max range and end value, or a per-step table under `--no-chart`.
**JSON mode (`--output json`):** payload type `managementapi.GetModelMetricsResponse`.
The metrics response: metric\_descriptors, index-mapped metric\_values, the resolved mode, and the returned window.
# baseten model deployment replica
Source: https://docs.baseten.co/reference/cli/baseten/model-deployment-replica
Manage replicas of a deployment
## terminate
```sh theme={"system"}
baseten model deployment replica terminate [OPTIONS]
```
Terminate a single replica of a model deployment.
Prompts for yes/no confirmation. Pass `--yes` to skip the prompt. When stdin is not a terminal, `--yes` is required.
### Options
ID of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Name of the deployment.
Mutually exclusive with other flags in group `deployment-ref`.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
ID of the replica.
Team name or ID. Only valid with --model-name.
Skip the interactive confirmation prompt. Required when stdin is not a terminal.
Enable verbose logging
### Examples
Terminate a replica without the confirmation prompt
```sh theme={"system"}
baseten model deployment replica terminate --model-id --deployment-id --replica-id --yes
```
### Filter output with `--jq`
Print just the success flag
```sh theme={"system"}
baseten model deployment replica terminate --model-id --deployment-id --replica-id --yes --jq '.success'
```
### Output
**Text mode (`--output text`):** On success, prints "Terminated replica `id` of deployment `id`" to stderr; no stdout output.
**JSON mode (`--output json`):** payload type `managementapi.TerminateReplicaResponse`.
# baseten model environment
Source: https://docs.baseten.co/reference/cli/baseten/model-environment
Manage environments of a model
## activate
```sh theme={"system"}
baseten model environment activate [OPTIONS]
```
Activate the deployment associated with an environment.
### Options
Name of the environment (e.g. production).
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Enable verbose logging
### Examples
Activate the deployment associated with an environment
```sh theme={"system"}
baseten model environment activate --model-id --environment production
```
### Filter output with `--jq`
Print just the success flag
```sh theme={"system"}
baseten model environment activate --model-id --environment production --jq '.success'
```
### Output
**Text mode (`--output text`):** On success, prints "Activated environment `name`" to stderr; no stdout output.
**JSON mode (`--output json`):** payload type `managementapi.ActivateResponse`.
## deactivate
```sh theme={"system"}
baseten model environment deactivate [OPTIONS]
```
Deactivate the deployment associated with an environment.
Prompts for yes/no confirmation. Pass `--yes` to skip the prompt. When stdin is not a terminal, `--yes` is required.
### Options
Name of the environment (e.g. production).
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Skip the interactive confirmation prompt. Required when stdin is not a terminal.
Enable verbose logging
### Examples
Deactivate an environment without the confirmation prompt
```sh theme={"system"}
baseten model environment deactivate --model-id --environment production --yes
```
### Filter output with `--jq`
Print just the success flag
```sh theme={"system"}
baseten model environment deactivate --model-id --environment production --yes --jq '.success'
```
### Output
**Text mode (`--output text`):** On success, prints "Deactivated environment `name`" to stderr; no stdout output.
**JSON mode (`--output json`):** payload type `managementapi.DeactivateResponse`.
## describe
```sh theme={"system"}
baseten model environment describe [OPTIONS]
```
Describe a model environment by name.
### Options
Name of the environment (e.g. production).
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Enable verbose logging
### Examples
Describe the production environment of a model
```sh theme={"system"}
baseten model environment describe --model-id --environment production
```
### Filter output with `--jq`
Print the current deployment ID
```sh theme={"system"}
baseten model environment describe --model-id --environment production --jq '.current_deployment.id'
```
### Output
**Text mode (`--output text`):** Field-per-line summary: Name, Model, Current Deployment, Status, Candidate Deployment (optional), Created.
**JSON mode (`--output json`):** payload type `managementapi.Environment`.
## list
```sh theme={"system"}
baseten model environment list [OPTIONS]
```
List all environments of a model.
### Options
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID. Only valid with --model-name.
Enable verbose logging
### Examples
List all environments of a model
```sh theme={"system"}
baseten model environment list --model-id
```
### Filter output with `--jq`
Print just the environment names
```sh theme={"system"}
baseten model environment list --model-id --jq '.environments[].name'
```
### Output
**Text mode (`--output text`):** Table with columns: NAME, CURRENT DEPLOYMENT, STATUS. When no environments exist, prints "No environments found." to stderr.
**JSON mode (`--output json`):** payload type `managementapi.Environments`.
## logs
```sh theme={"system"}
baseten model environment logs [OPTIONS]
```
Fetch logs for a model environment, spanning every deployment that was active on the environment across the time range.
By default returns up to `--limit` lines from the last 30 minutes, newest first, and prints a note to stderr when `--limit` trims older lines. Use `--start`/`--end` or `--since` to scope the window (max 7 days). Use `--tail` to stream live logs until the environment's current deployment leaves a runnable state or you interrupt with Ctrl-C.
For machine-readable streaming, prefer `--output jsonl` over `--output json`.
For request-ID tracing, scope, and log export, see [Logs](/observability/logs).
### Options
End of the log time range. Accepts ISO 8601; values without a timezone designator are interpreted in the local timezone. Default is now. Window must be at most 7 days.
Name of the environment (e.g. production).
Case-sensitive substring; lines containing it are dropped. May be repeated.
Case-sensitive substring that must appear in the log message. May be repeated; all must match.
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Maximum number of log lines to return, paging backward from the end of the window. Use 0 for no limit (every log line in the window). Not applicable with --tail.
Only return logs at or above this severity level.
One of: `debug`, `info`, `warning`, `error`
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Only return logs emitted by this replica (5-char short ID).
Only return logs tagged with this inference request ID.
RE2 regular expression matched against the log message. Prefer --includes and --excludes for plain substring matches.
Shortcut for fetching logs from a relative time ago until now. Accepts a Go duration (e.g. '30m', '1h30m') or '`N`d' (e.g. '3d'). Maximum '7d'. Mutually exclusive with --start and --end.
Start of the log time range. Accepts ISO 8601 (e.g. '2026-05-14', '2026-05-14T12:00:00', '2026-05-14T12:00:00Z'). Values without a timezone designator are interpreted in the local timezone. Default is 30 minutes before the end. Window must be at most 7 days.
Stream new logs as they arrive until the deployment leaves a runnable state or you interrupt with Ctrl-C. Cannot be combined with the time-range or filter flags. For machine-readable streaming, prefer --output jsonl over --output json.
Team name or ID. Only valid with --model-name.
Enable verbose logging
### Examples
Print logs for the production environment over the last hour
```sh theme={"system"}
baseten model environment logs --model-id --environment production --since 1h
```
Tail live logs until the environment's current deployment leaves a runnable state
```sh theme={"system"}
baseten model environment logs --model-id --environment production --tail
```
### Filter output with `--jq`
Stream just the log messages as a JSONL stream
```sh theme={"system"}
baseten model environment logs --model-id --environment production --output jsonl --jq '.message'
```
### Output
**Text mode (`--output text`):** One line per log record: "\[YYYY-MM-DD HH:MM:SS]: (replica) message".
**JSON mode (`--output json`):** payload type `managementapi.Log`.
## metrics
```sh theme={"system"}
baseten model environment metrics [OPTIONS]
```
Fetch metrics aggregated across every deployment that was active on the environment over the time range.
Use `--mode current` for a snapshot, `--mode summary` to aggregate a window, or `--mode series` to plot values over time. Scope the window with `--since` or `--start`/`--end` (max 7 days; only applies to summary and series), and select metrics with one or more `--metric` flags. In series mode the window is split at each promotion so every point reflects the deployment(s) serving the environment at that time.
### Options
End of the metrics time range. Accepts ISO 8601; values without a timezone designator are interpreted in the local timezone. If omitted, the server defaults the end to now. Window must be at most 7 days.
Name of the environment (e.g. production).
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Name of a metric to return; see [https://docs.baseten.co/observability/export-metrics/supported-metrics](https://docs.baseten.co/observability/export-metrics/supported-metrics) for the available names. May be repeated. When omitted, a default set is returned.
Aggregation mode. 'current' returns an instantaneous snapshot at now; 'summary' aggregates the whole window into one value per metric; 'series' returns evenly-spaced points across the window. --start/--end/--since are only meaningful for summary and series.
One of: `current`, `summary`, `series`
ID of the model.
Mutually exclusive with other flags in group `model-ref`.
Name of the model. Use --team to disambiguate when the same name exists in multiple teams.
Mutually exclusive with other flags in group `model-ref`.
For --mode series, emit a per-step table instead of sparklines.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Shortcut for a window from a relative time ago until now. Accepts a Go duration (e.g. '30m', '1h30m') or '`N`d' (e.g. '3d'). Maximum '7d'. Mutually exclusive with --start and --end.
Start of the metrics time range. Accepts ISO 8601 (e.g. '2026-05-14', '2026-05-14T12:00:00', '2026-05-14T12:00:00Z'). Values without a timezone designator are interpreted in the local timezone. If omitted, the server defaults the start to one hour before the end. Window must be at most 7 days.
Team name or ID. Only valid with --model-name.
Enable verbose logging
### Examples
Show a current snapshot of the default metrics for the production environment
```sh theme={"system"}
baseten model environment metrics --model-id --environment production
```
Summarize request volume and latency over the last hour
```sh theme={"system"}
baseten model environment metrics --model-id --environment production --mode summary --since 1h --metric baseten_inference_requests_total --metric baseten_end_to_end_response_time_seconds
```
Plot a series over the last 6 hours
```sh theme={"system"}
baseten model environment metrics --model-id --environment production --mode series --since 6h
```
### Filter output with `--jq`
Print the metric names returned
```sh theme={"system"}
baseten model environment metrics --model-id --environment production --jq '.metric_descriptors[].name'
```
### Output
**Text mode (`--output text`):** For `current` and `summary`, a table with columns METRIC, one column per label dimension (for example QUANTILE, STAT), and VALUE; summary counter values show "total (rate/s)". For `series`, a sparkline per metric label set with its min-max range and end value, or a per-step table under `--no-chart`.
**JSON mode (`--output json`):** payload type `managementapi.GetModelMetricsResponse`.
The metrics response: metric\_descriptors, index-mapped metric\_values, the resolved mode, and the returned window.
# baseten org api-key
Source: https://docs.baseten.co/reference/cli/baseten/org-api-key
Manage API keys
## list
```sh theme={"system"}
baseten org api-key list [OPTIONS]
```
List API keys (metadata only; key values are never returned).
### Options
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Enable verbose logging
### Examples
List all API keys in the org
```sh theme={"system"}
baseten org api-key list
```
### Filter output with `--jq`
Print just the prefixes of personal keys
```sh theme={"system"}
baseten org api-key list --jq '.keys[] | select(.type == "PERSONAL") | .prefix'
```
### Output
**Text mode (`--output text`):** Table with columns: NAME, KEY (prefix + \*\*\*\*), TYPE, TEAM. When no keys exist, prints "No API keys found." to stderr.
**JSON mode (`--output json`):** payload type `managementapi.APIKeys`.
## create
```sh theme={"system"}
baseten org api-key create [OPTIONS]
```
Create a new API key. The key value is printed to stdout exactly once and cannot be retrieved later; capture or pipe it on creation. `--model-id` may be repeated to scope the key to specific models and is only valid with `--type` workspace-export-metrics or `--type` workspace-invoke.
### Options
Filter JSON output with a jq expression; implies --output json (or jsonl for streamed commands)
Restrict the key to a specific model. May be repeated. Only valid with --type workspace-export-metrics or workspace-invoke.
Optional human-readable name for the key.
Output format
One of: `text`, `json`, `jsonl`, `none`
Use a specific stored profile for this command, overriding BASETEN\_PROFILE and the current profile
Team name or ID to create the key in. Defaults to the organization's default team.
API key category.
One of: `personal`, `workspace-export-metrics`, `workspace-invoke`, `workspace-manage-all`
Enable verbose logging
### Examples
Create a personal API key
```sh theme={"system"}
baseten org api-key create --type personal --name