Model Claim#
Warning
ModelClaim is experimental. Read Limitations before you rely on it.
ModelClaim serves several models from the same GPUs. You deploy a warm pool, which is a Deployment of GPU Pods that run the AIBrix runtime agent, and create one ModelClaim for each model. The controller starts each model as its own engine process, on a Pod whose GPU has room for it. The AIBrix gateway then routes requests to that engine by model name. A pool can also put idle models to sleep, which frees GPU memory for the models in use, and the next request for a sleeping model wakes it.
ModelClaim suits models that are busy only part of the time and would otherwise each need a GPU of their own.
How it works#
flowchart LR
subgraph P["Warm pool Pod"]
R["Runtime agent"] --> E1["Engine for model A"]
R --> E2["Engine for model B"]
end
U["Client"] --> G["AIBrix gateway"]
G -->|"requests for model A"| E1
M["ModelClaims"] --> C["ModelClaim controller"]
C -->|"start, sleep, wake, KV limits"| R
C -.->|"routes, as Pod annotations"| G
A warm pool is a Deployment whose Pods run the ModelClaim runtime image and request a fixed number of GPUs. In each Pod, the runtime agent starts, stops, sleeps and wakes engine processes for the controller.
A ModelClaim names a model and the pool it runs in. It also sets the engine arguments, and declares how much GPU memory one instance needs.
The ModelClaim controller places each claim on a Pod whose GPU has room for it, and has the runtime agent start the engine there. The engines on a GPU share its memory for their KV caches through kvcached, and the controller gives each engine a KV cache limit. Once an engine is ready, the controller writes its port in an annotation on the Pod, which this guide calls the engine’s route.
The AIBrix gateway reads the routes and sends each request to the engine of its model. When the model is asleep, the gateway asks the controller to wake it and tells the client to retry.
Limitations#
One engine per claim.
replicasmust be 1, so a claim runs one engine on one Pod.Tested setups. ModelClaim has been tested with vLLM on NVIDIA H20 GPUs, with tensor parallelism 1 and 2 in one Pod. Pipeline parallelism, tensor parallelism above 2, Pods with more than two GPUs and other GPU types have not been tested. Data parallelism is not supported.
One GPU layout per pool. A vLLM claim must use all the GPUs of its Pod, so models with different parallelism go in separate pools.
Host memory is not managed. ModelClaim puts engines to sleep with vLLM’s sleep level 1, which moves a model’s weights from the GPU to host memory until the engine wakes. The controller does not count host memory when it places claims or puts engines to sleep. If many models sleep on one node, the node can run out of memory, and engines can be killed. Size the pool’s memory request for all its models asleep at once.
Sleep and wake are per pool, and on request. Only vLLM engines sleep. The sleep policy applies to every model in a pool, so a model that must stay awake needs a pool without idle sleep. A sleeping model wakes only when a request for it reaches the AIBrix gateway, which answers that request with 503 instead of holding it, so clients must retry.
Declared GPU memory. The controller trusts the figures that a claim declares in
perGPU, and does not measure a model’s footprint. kvcached limits only the KV cache, so an engine that needs more than its declared footprint can run the GPU out of memory.No live migration. A claim moves to another Pod when its engine fails permanently or cannot wake where it is. A new engine then loads the model there, and the model serves nothing until that engine is ready.
Runtime image. The published runtime image contains vLLM and is built for
linux/amd64only. It is published frommainand is not part of AIBrix releases.engine: sglangneeds your own image with SGLang and kvcached, and has not been tested.Model sources. Only Hugging Face artifacts (
huggingface://) have been tested.Claim names. A claim’s name can be at most 63 characters, because it is part of annotation keys on the Pod.
No access control. The runtime agent’s API on port 8080 has no authentication, and the engines accept vLLM’s own sleep and wake calls on their ports, from 20000 to 20999. Anything that can reach a pool Pod can start, stop, sleep or wake its engines. Limit access to these ports, for example with a NetworkPolicy that admits only the ModelClaim controller, Envoy and Prometheus.
Before you begin#
A Kubernetes cluster with NVIDIA GPU nodes on
linux/amd64and the NVIDIA device plugin.The AIBrix control plane. Its standard installation includes ModelClaim’s CRD and controller, and the gateway’s support for it. See Installation.
Access from the GPU nodes to Docker Hub for the runtime image, and to wherever your models are stored.
Check that the control plane is ready:
kubectl get crd modelclaims.model.aibrix.ai
kubectl -n aibrix-system rollout status deployment/aibrix-controller-manager --timeout=10m
kubectl -n aibrix-system rollout status deployment/aibrix-gateway-plugins --timeout=10m
Quick start#
The samples run two small Qwen models on one GPU, in the default
namespace. Each claim declares a 6 GiB footprint and a 1 GiB KV floor, so the
GPU needs at least 14 GiB, and the Pod requests 16 GiB of host memory. Run the
commands from the repository root.
Create the warm pool and wait for its Pod:
kubectl apply -f samples/modelclaim/warm-runtime-pool.yaml
kubectl -n default rollout status deployment/warm-runtime-pool --timeout=15m
Create the two claims and watch them start:
kubectl apply -f samples/modelclaim/modelclaims.yaml
kubectl -n default get modelclaims -w
Each claim reads Activating once it is placed, and Active once its
engine serves. The first start downloads the model, so it can take a few
minutes:
NAME PHASE DESIRED READY ENGINE ARTIFACT AGE
qwen25-0-5b Active 1 1 vllm huggingface://Qwen/Qwen2.5-0.5B-Instruct 3m
qwen3-0-6b Active 1 1 vllm huggingface://Qwen/Qwen3-0.6B 3m
To send requests through the gateway, keep a port-forward to Envoy running in another terminal:
ENVOY=$(kubectl -n envoy-gateway-system get service \
--selector=gateway.envoyproxy.io/owning-gateway-namespace=aibrix-system,gateway.envoyproxy.io/owning-gateway-name=aibrix-eg \
-o jsonpath='{.items[0].metadata.name}')
kubectl -n envoy-gateway-system port-forward "service/$ENVOY" 8888:80
Then send a request with a claim’s modelName as the model. The
routing-strategy header is needed, as Requests explains:
curl -s localhost:8888/v1/chat/completions \
-H 'Content-Type: application/json' -H 'routing-strategy: random' \
-d '{"model": "qwen3-0.6b", "messages": [{"role": "user", "content": "Say hello."}], "max_tokens": 32}'
Let idle models sleep by giving the pool a sleep policy:
kubectl -n default annotate deployment/warm-runtime-pool --overwrite \
'claim.model.aibrix.ai/pool-policy={"lifecycle":{"sleepAfterSeconds":60}}'
Once a model has had no requests for 60 seconds, the controller puts it to
sleep, and its claim shows Sleeping. The next request for it gets 503
with Retry-After: 10, and the controller wakes the model. These small
models wake in a few seconds, so a retry after those 10 seconds is normally
served.
To clean up, delete the claims first, so that the controller stops their engines:
kubectl delete -f samples/modelclaim/modelclaims.yaml
kubectl delete -f samples/modelclaim/warm-runtime-pool.yaml
Warm pools#
A warm pool is a Deployment. The sample runs one Pod with one GPU:
# A warm pool for ModelClaim: one Pod with one GPU. Claims select it by the
# claim.model.aibrix.ai/pool label.
apiVersion: apps/v1
kind: Deployment
metadata:
name: warm-runtime-pool
namespace: default
labels:
claim.model.aibrix.ai/pool: pool-a
claim.model.aibrix.ai/enabled: "true"
spec:
replicas: 1
selector:
matchLabels:
app: warm-runtime-pool
template:
metadata:
labels:
app: warm-runtime-pool
claim.model.aibrix.ai/pool: pool-a
claim.model.aibrix.ai/enabled: "true"
spec:
containers:
- name: aibrix-runtime
# The runtime agent, vLLM and kvcached. The agent starts one engine
# process for each claim placed on this Pod. Pin the tag to the full
# SHA of the commit your control plane runs.
image: aibrix/kvcached-runtime:nightly
imagePullPolicy: IfNotPresent
ports:
- name: runtime
containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
env:
- name: ENABLE_KVCACHED
# The agent must not load kvcached itself. It turns kvcached on
# in each engine it starts.
value: "false"
- name: KVCACHED_AUTOPATCH
value: "0"
- name: AIBRIX_WEIGHT_CACHE_DIR
value: /root/.cache/huggingface
resources:
requests:
# A sleeping engine keeps its model's weights in host memory, and
# ModelClaim does not account for host memory. Request enough for
# all the models on this Pod asleep at once.
memory: 16Gi
limits:
nvidia.com/gpu: "1"
volumeMounts:
- name: dshm
mountPath: /dev/shm
- name: hf-cache
mountPath: /root/.cache/huggingface
- name: engine-registry
mountPath: /var/run/aibrix
volumes:
- name: dshm
emptyDir:
medium: Memory
sizeLimit: 16Gi
# Weights downloaded by the engines stay on the node, for the next Pod.
- name: hf-cache
hostPath:
path: /var/cache/aibrix/huggingface
type: DirectoryOrCreate
# The agent's list of engines, which it reads again after a restart.
- name: engine-registry
emptyDir: {}
---
# Exposes the runtime's /metrics for the ModelClaim ServiceMonitor in
# observability/monitor/service_monitor_modelclaim_runtime.yaml.
apiVersion: v1
kind: Service
metadata:
name: warm-runtime-pool-metrics
namespace: default
labels:
app: warm-runtime-pool
claim.model.aibrix.ai/pool: pool-a
aibrix.ai/metrics: modelclaim-runtime
spec:
selector:
app: warm-runtime-pool
ports:
- name: runtime
port: 8080
targetPort: runtime
What its fields do:
claim.model.aibrix.ai/poolandclaim.model.aibrix.ai/enabledlabelsClaims select a pool by its
poollabel. The controller places claims only on Pods that also haveenabled: "true". A claim already on a Pod stays there when the Pod loses either label, until the Pod stops running or is deleted.nvidia.com/gpuThe number of GPUs in each Pod. A vLLM claim runs only on Pods whose GPU count equals its tensor parallel size times its pipeline parallel size. For a model that needs two GPUs, create another pool whose Pods request two, and give the claim
--tensor-parallel-size: "2".imageaibrix/kvcached-runtimecontains the runtime agent, vLLM and kvcached, on top ofghcr.io/ovg-project/kvcached-vllm:kvcached-v0.1.6-vllm-v0.30.0. CI publishes it on every push tomain, taggednightlyand with the commit’s full SHA. With a control plane built frommain, use the image of the same commit, pinned by its SHA tag. Withnightlyand the sample’sIfNotPresent, a node keeps the firstnightlyit pulled. AIBrix releases do not publish this image. For a release, or for another base image, build the image yourself from a checkout of the commit your control plane runs:export AIBRIX_CONTAINER_REGISTRY_NAMESPACE=<your registry>/aibrix export KVCACHED_RUNTIME_BASE_IMAGE=<base image with vLLM and kvcached> export IMAGE_TAG="$(git rev-parse HEAD)" IS_MAIN_BRANCH=false make docker-build-kvcached-runtime docker-push-kvcached-runtime
resources.requests.memoryHost memory for the runtime agent and its engines. A sleeping engine keeps its model’s weights in host memory, so size this for all the Pod’s models asleep at once.
/dev/shmA memory-backed volume that kvcached and vLLM use to share memory between processes. The sample allows it 16 GiB.
- The
hf-cachevolume andAIBRIX_WEIGHT_CACHE_DIR The volume is a directory on the node, mounted at the Hugging Face cache,
/root/.cache/huggingface, where engines download their models. A Pod that is recreated on that node therefore does not download them again.AIBRIX_WEIGHT_CACHE_DIRpoints the runtime agent at the same directory, where it notes the models it has served. The controller prefers Pods that have served a model before./var/run/aibrixThe runtime agent keeps its list of engines here. When the agent restarts, it reads the list and takes its engines back without restarting them.
ENABLE_KVCACHED: "false"andKVCACHED_AUTOPATCH: "0"These apply to the runtime agent, which must not load kvcached itself. The agent turns kvcached on in each engine it starts.
- Other environment variables
Engines inherit the container’s environment. Set
HF_TOKENorHF_ENDPOINTon the container when your models need them.readinessProbeThe Pod is Ready once the runtime agent answers on
/healthz. The gateway sends requests only to Ready Pods.- The metrics Service
Its
aibrix.ai/metrics: modelclaim-runtimelabel and its port namedruntimelet Prometheus find the runtime agent’s metrics. See Monitoring.
Changing the Pod template, for example its image, replaces the Pods. Their engines stop with them, and the controller places their claims again.
ModelClaims#
The sample claims:
# Two small models that share the GPU of the warm-runtime-pool sample.
apiVersion: model.aibrix.ai/v1alpha1
kind: ModelClaim
metadata:
name: qwen3-0-6b
namespace: default
spec:
modelName: qwen3-0.6b
podSelector:
matchLabels:
claim.model.aibrix.ai/pool: pool-a
artifactURL: huggingface://Qwen/Qwen3-0.6B
engine: vllm
replicas: 1
# What one instance needs on a GPU: its footprint besides the KV cache, and
# the KV cache for one request of the maximum length. Both leave room to
# spare for this model with these engine arguments.
perGPU:
maximumFootprint: 6Gi
kvFloor: 1Gi
engineConfig:
args:
--max-model-len: "2048"
---
apiVersion: model.aibrix.ai/v1alpha1
kind: ModelClaim
metadata:
name: qwen25-0-5b
namespace: default
spec:
modelName: qwen2.5-0.5b
podSelector:
matchLabels:
claim.model.aibrix.ai/pool: pool-a
artifactURL: huggingface://Qwen/Qwen2.5-0.5B-Instruct
engine: vllm
replicas: 1
perGPU:
maximumFootprint: 6Gi
kvFloor: 1Gi
engineConfig:
args:
--max-model-len: "2048"
Field |
Meaning |
|---|---|
|
The name clients send as |
|
Required. Selects the pool, usually by |
|
Required. Where the model comes from, such as
|
|
|
|
Must be 1, the default. |
|
Engine flags and their values, as strings. An empty string adds a flag without a value. |
|
Needed for placement. The most GPU memory one instance holds on a GPU apart from its KV cache. That covers weights, CUDA graphs, activations and memory the allocator keeps. |
|
Needed for placement. The KV cache one instance needs on a GPU to serve a single request of its maximum length. |
Only perGPU can change after a claim is created. To change anything else,
delete the claim and create it again.
The runtime agent sets --model, --served-model-name, --host,
--port and --enable-sleep-mode itself, so leave them out of
engineConfig.args. Leave out --gpu-memory-utilization as well, since
kvcached manages the KV cache. A vLLM claim with that flag fails with
InvalidEngineConfig. So does one whose parallel sizes are not positive
whole numbers, or whose data parallel size is not 1.
Declare what a model needs on a GPU#
The controller places a claim only on a Pod whose GPU has room for its
maximumFootprint plus its kvFloor. Engines already on that GPU count
with their footprints and their KV caches, and each KV cache counts as at
least its KV floor. With tensor or pipeline parallelism, give the figures for
the GPU that holds the most.
The controller does not measure models, so measure them yourself:
Create the claim with generous figures, on a GPU with room to spare.
Send it requests of its maximum length. While it serves them, the runtime snapshot (see Status) shows its engine’s GPU memory in
hbm_peak_bytesand its KV cache inkv_used_bytes. The footprint is abouthbm_peak_bytesminuskv_used_bytes.hbm_peak_bytesreads 0 when the runtime agent cannot match GPU processes to the engine. Then use how far the GPU’shbm_free_bytesfell when the engine started.Lower
maximumFootprintto what you measured, with some margin. For a model on one GPU,kvFlooris about 2 × layers × KV heads × head size × bytes per value × the maximum model length.
Declaring more than a model needs wastes room, while declaring less can run the GPU out of memory.
A claim without perGPU, or with a figure that is not positive, stays
Pending, and its Scheduled condition reads InvalidPerGPU. A claim
that is larger than every GPU in its pool stays Pending with
TooLargeForAnyCard.
Placement and GPU memory#
A Pod can take a claim when it is in the claim’s namespace, matches its
podSelector, has claim.model.aibrix.ai/enabled: "true", is running
with an IP address, and has as many GPUs as the claim needs. The controller
places the claim on such a Pod whose GPU has room for it. Among those, it
prefers a Pod that has served the model before, since the weights are already
on the node. After that, it prefers the Pod with the fewest models awake, then
the one with the most room.
When no Pod has room, the claim stays Pending. Its Scheduled condition
reads NoMatchingPods, and its message names the Pod that came closest and
what it lacks. The controller tries again after 10, 20 and 40 seconds and then
every minute. It also tries at once when room may have appeared, for example
when another claim is deleted or goes to sleep.
The controller divides each GPU among the engines on it. Each engine keeps its
declared footprint and at least its KV floor. The rest of the GPU’s usable
memory goes to the KV caches of the engines that are awake, and the busy ones
get more. A GPU’s usable memory is its total memory less what the driver
reserves. kvcached
keeps each engine within its KV limit, which the claim records in
status.instances[].kvLimitBytes. The limits change when engines start,
stop, sleep or wake, and when the load moves between them.
An engine goes on the route only once it is ready and runs within its KV limit. If something else raises an engine’s KV limit, the controller sets it back, and keeps the engine off the route until then.
Sleep and wake#
A pool’s sleep policy is a JSON annotation on its Deployment,
claim.model.aibrix.ai/pool-policy, and it applies to every model in the
pool:
kubectl -n default annotate deployment/warm-runtime-pool --overwrite \
'claim.model.aibrix.ai/pool-policy={"lifecycle":{"sleepAfterSeconds":300,"noWakeReserveWhileAsleep":true}}'
Field |
Meaning |
|---|---|
|
How long a model must be idle before the controller puts it to sleep. A
model is idle while it has no request running or waiting. Leave the
field out, or set it to 0, to keep models awake unless room is needed.
That is allowed only with |
|
|
|
Used only with |
A policy with an unknown field or an invalid value is turned off, and the
Deployment gets an InvalidPoolPolicy Event. The policy also accepts a
deprecated reclaim section, which has no effect on claims that declare
perGPU.
The controller puts a model to sleep once it has been idle for
sleepAfterSeconds. It first takes the model off the route and checks again
that no request is in progress. If one is, the model stays awake. Otherwise,
the engine sleeps with vLLM’s sleep level 1, which moves its weights to host
memory and frees its KV cache for the models that are awake. The claim shows
Sleeping.
When a request for a sleeping model reaches the gateway, the gateway answers 503 and writes a wake request on the Pod, unless one is there already. The controller then wakes the engine, and puts it back on the route once it is ready. Many requests for one sleeping model cause a single wake. The controller removes the wake request once the model serves, when the wake fails, or after five minutes.
By default, a sleeping model keeps its room on the GPU, so that it can wake where it is. Only its KV cache above its KV floor goes to the other models on the GPU, and they give it back when it wakes.
With noWakeReserveWhileAsleep, a sleeping model reserves only what it still
holds on the GPU, which the runtime agent measures after each sleep. The rest
can go to other models, so a pool can hold more models than fit awake at once.
A model that wakes then needs room again:
If its GPU has room, it wakes at once.
Otherwise, the controller puts other models on the GPU to sleep, the longest idle first and one at a time, until the waking model fits. It takes only models that have been idle for
sleepToMakeRoomAfterSeconds. Meanwhile, the waking model’s claim readsWaitingForRoom, and each model put to sleep gets aSleptToMakeRoomEvent.If no model can be put to sleep for it, and another Pod has room, the model moves there. A new engine loads it on that Pod.
Otherwise, it waits for room for up to five minutes, and the next request asks again.
In such a pool, a new claim that no Pod has room for gets room the same way.
The controller puts idle models to sleep on one Pod, and the claim’s
Scheduled condition reads MakingRoom until the claim is placed.
If the runtime agent cannot measure what a sleeping engine holds, the engine
keeps its full room, and its claim gets a SleepingFootprintUnknown Event.
While noWakeReserveWhileAsleep is on, do not wake engines through the
runtime agent’s API, because such a wake skips the controller’s check for
room.
Failures#
Each engine is a separate process, so a crash stops only that engine. The runtime agent restarts it on the same port after 2, 4, 8, 16 and 32 seconds, up to five times over the engine’s life. While it restarts, the model is off the route, and the other models on the Pod keep serving.
An engine that crashes again after its fifth restart has failed permanently,
and its claim gets an EngineFailed Event. If another Pod has room, the
controller stops the engine and starts the claim there at once. Otherwise, the
claim reads Failed with EngineFailed, and its Scheduled condition
says why it cannot move. The engine’s last_error in the runtime snapshot
says why it crashed.
If the runtime agent crashes, its engines keep running. The image restarts the
agent, which takes the engines back from /var/run/aibrix with their ports
unchanged.
If a Pod goes away, its engines go with it, and the controller places their claims again.
Requests#
Send requests to the AIBrix gateway with a claim’s modelName as the model,
and with a routing strategy, such as the header routing-strategy: random.
The gateway can also take a default strategy from its ROUTING_ALGORITHM
environment variable. Without either, it looks for an HTTPRoute for the model,
which ModelClaim does not create, and answers 503.
The gateway sends each request to the engine’s port on its Pod, once the engine is on the route, and only while the Pod is Ready. When a model cannot serve, the gateway answers at once instead of holding the request. The error message gives the model’s state, and the reason when there is one.
The model is |
The gateway answers |
|---|---|
starting, asleep, failed permanently, or waiting to be placed |
503 with |
waiting for room to wake, or getting room made for it |
503 with |
moving to another Pod |
503 with |
held back by |
503 without |
unknown to the gateway |
400 |
Deleting claims#
When a claim is deleted, the controller first takes it off the route, so the gateway stops sending it requests. It then gives the engine up to 90 seconds from the deletion to finish the requests in progress, and asks the runtime agent to stop it. A sleeping or starting engine is stopped at once. Delete claims before their pool, so that the controller can stop their engines.
Status#
kubectl get modelclaims shows each claim’s phase:
Phase |
Meaning |
|---|---|
|
Not placed yet. The |
|
The engine is starting or restarting, or waits for its KV limit. It is off the route. |
|
The engine serves requests. |
|
The engine is asleep, and a request wakes it. |
|
The engine failed permanently ( |
The Scheduled condition reads Placed once the claim has an engine.
Until then, it gives one of these reasons:
Reason |
Meaning |
|---|---|
|
No Pod can take the claim now. The message says why. |
|
No GPU in the pool is large enough for the claim. |
|
|
|
Idle models are being put to sleep to make room for the claim. |
The Ready condition is True with ModelClaimActive while the claim
serves. Otherwise, its reason says what holds the claim back:
Reason |
Meaning |
|---|---|
|
The engine is starting or restarting, or waits for its KV limit. |
|
The engine is asleep. |
|
The engine was asked to wake, and its GPU has no room yet. |
|
The engine could not wake where it is, and the claim is moving to another Pod. |
|
The engine failed permanently. |
|
The runtime agent could not start the engine. The controller tries again. |
|
The engine arguments are invalid, and the claim has to be recreated. |
|
The claim lost its engine, for example with its Pod, and waits to be placed again. |
Claims also record what happens to them as Events, such as Activated,
Sleeping, Waking, Woken, KVLimitSet, Rescheduled and
DrainTimedOut. The controller’s Kubernetes client sends at most 25 Events
for a claim in a burst, and then one every five minutes, so a busy claim can
miss some. Its phase and conditions stay current.
For more detail, read the claim, the route on its Pod, and the runtime
snapshot. The snapshot lists every engine on the Pod with its phase, port, KV
limit, KV use and last_error:
kubectl -n default describe modelclaim qwen3-0-6b
POD=$(kubectl -n default get modelclaim qwen3-0-6b -o jsonpath='{.status.instances[0].pod}')
kubectl -n default get pod "$POD" -o jsonpath='{.metadata.annotations.route\.claim\.model\.aibrix\.ai/qwen3-0-6b}'
kubectl -n default exec "$POD" -c aibrix-runtime -- curl -s localhost:8080/v1/runtime/snapshot
A route looks like this:
{"model":"qwen3-0.6b","port":20000,"state":"active","wakeByRequest":true}
Its state is active, activating, sleeping or failed, and
its port is 0 unless the state is active. A route that waits can also give
a reason, such as WaitingForRoom. wakeByRequest tells the gateway
to ask the controller for a wake.
Labels and annotations#
You set the first four keys. AIBrix writes the others, so leave them alone.
Key |
On |
Set by |
Purpose |
|---|---|---|---|
|
Label on the pool’s Deployment and Pods |
You |
Names the pool. Claims select it in |
|
Label on the pool’s Pods |
You |
Lets the controller place claims on the Pod. |
|
Annotation on the pool’s Deployment |
You |
The sleep policy, as JSON. See Sleep and wake. |
|
Label on the pool’s metrics Service |
You |
Lets the ModelClaim ServiceMonitor find the Service. |
|
Annotation on a pool Pod |
Controller |
The claim’s route on that Pod, as JSON with |
|
Annotation on a pool Pod |
Gateway |
Asks the controller to wake the claim’s sleeping engine, and holds the time of the request. The controller removes it. |
|
Finalizer on ModelClaims |
Controller |
Keeps a deleted claim until the controller has asked the runtime agent to stop its engine. |
Monitoring#
Apply observability/monitor/service_monitor_modelclaim_runtime.yaml, so
that Prometheus scrapes the runtime agent of every Service labeled
aibrix.ai/metrics: modelclaim-runtime. The controller’s metrics need
observability/monitor/service_monitor_controller_manager.yaml as well. Then
import observability/grafana/AIBrix_ModelClaim_Runtime_Dashboard.json into
Grafana. observability/grafana/README.md lists the metrics the dashboard
uses and their labels.
The controller exports these metrics:
aibrix_modelclaim_desired_replicasandaibrix_modelclaim_ready_replicas, for each claim;aibrix_modelclaim_activating, 1 while a claim’s engine starts;aibrix_modelclaim_activation_total, engine starts by result;aibrix_modelclaim_no_ready_duration_seconds, how long a claim has not been ready;aibrix_modelclaim_pool_policy_valid, whether a pool’s sleep policy is valid;aibrix_modelclaim_pool_policy_evaluations_totalandaibrix_modelclaim_pool_policy_actions_total, for each pool.
The runtime agent exports these:
aibrix:modelclaim_models_resident, the engines running in the Pod;aibrix:modelclaim_engine_state, the Pod’s engines that are active, sleeping, restarting or failed;aibrix:modelclaim_kv_used_bytesandaibrix:modelclaim_kv_total_bytes;aibrix:modelclaim_kv_limit_requested_bytes,aibrix:modelclaim_kv_limit_applied_bytesandaibrix:modelclaim_kv_limit_operations_total;aibrix:modelclaim_hbm_peak_bytes, the GPU memory of each engine, which reads 0 when the runtime agent cannot match GPU processes to the engine;aibrix:modelclaim_sleeping_footprint_bytes, what a sleeping engine still holds on the GPU;aibrix:modelclaim_lifecycle_operations_totalandaibrix:modelclaim_lifecycle_operation_duration_seconds, for sleeps and wakes;aibrix:modelclaim_engine_restart_attempts_total,aibrix:modelclaim_engine_restart_budget_exhausted_totalandaibrix:modelclaim_engine_re_adoptions_total.
Troubleshooting#
To find the Pod of a claim, run
kubectl get modelclaim <claim> -o jsonpath='{.status.instances[0].pod}'.
- Requests get 400,
model ... does not exist Use the claim’s
modelName, such asqwen3-0.6b, rather than the claim’s name,qwen3-0-6b.- Requests get 503 with
error on getting pods The model is on the route, but its Pod is not Ready. The gateway sends requests only to Ready Pods.
- A claim stays
Pending Read its
Scheduledcondition:kubectl get modelclaim <claim> -o jsonpath='{.status.conditions[?(@.type=="Scheduled")]}'
no available candidate warm podmeans that no Pod qualifies. Check that the Pod has both labels, is running, and has as many GPUs as the claim needs. A message about GPU memory names the Pod that came closest and what it lacks. ForInvalidPerGPUorTooLargeForAnyCard, correctperGPU.- A claim reads
FailedwithInvalidEngineConfig Remove
--gpu-memory-utilization, and check the parallel sizes. Engine arguments cannot be changed, so delete the claim and create it again.- A claim stays
Activating The engine may still be downloading or loading its model. Check the runtime agent’s log, and the engine’s
last_errorin the runtime snapshot:kubectl logs <pod> -c aibrix-runtime kubectl exec <pod> -c aibrix-runtime -- curl -s localhost:8080/v1/runtime/snapshot
- A Pod is turned away because an engine there “answers to no claim”
The controller cannot tell what such an engine holds on the GPU, so it places nothing on that Pod. Stop the engine through the runtime agent’s API, or restart the Pod:
kubectl exec <pod> -c aibrix-runtime -- curl -s localhost:8080/v1/runtime/models/deactivate \ -H 'Content-Type: application/json' -d '{"model_name": "<model>", "mode": "stop"}'
- Engines are gone after the runtime agent restarted
Check that the Pod runs the runtime image as published, which restarts the agent inside the container, and that it mounts
/var/run/aibrix.
Validation#
To check ModelClaim on your own GPUs, follow the validation runbook. It runs these samples and covers placement and GPU memory division, sleep and wake, recovery from engine faults, and deletion.
Further reading: