---
title: Serve your first model
description: Create a ClusterBaseModel, a ClusterServingRuntime and an InferenceService for a small model, then send your first request to it.
---
Serve [Qwen3-0.6B](https://huggingface.co/Qwen/Qwen3-0.6B), a small language model, with SGLang on one GPU, and send it a chat request through its OpenAI-compatible API. You create a [ClusterBaseModel](../concepts/models/base-models.md) that says where the weights are, a [ClusterServingRuntime](../concepts/runtimes/serving-runtimes.md) that describes the serving pods, and an [InferenceService](../concepts/serving/inference-services.md) that serves the model with that runtime.
- OME, installed as in [Install OME](install.md), with the [model agent](../guides/operate-ome/model-agent.md) and its default host directory, `/mnt/data/models`.
- Since v1.3, the Prometheus Operator's PodMonitor CRD. See [The PodMonitor CRD](install.md#the-podmonitor-crd).
- `kubectl`, with the rights to create namespaces and cluster-scoped OME resources.
- An amd64 node with an NVIDIA GPU that Kubernetes schedules as `nvidia.com/gpu`, through the NVIDIA device plugin or the GPU Operator. The engine pod requests 1 GPU, 10 CPUs and 30 GiB of memory. The model agent's pod on the same node requests another 10 CPUs and 100 GiB, which you can lower with `modelAgent.resources`.
- Nodes that can reach Hugging Face, to download the model, and Docker Hub, to pull the SGLang image. Qwen3-0.6B isn't gated, so you need no Hugging Face token.
## Step 1: Check that OME is running
Check that the OME controller and the model agent are ready:
```bash
kubectl wait --for=condition=Available deployment/ome-controller-manager -n ome --timeout=5m
kubectl rollout status daemonset/ome-model-agent-daemonset -n ome --timeout=5m
```
```output
deployment.apps/ome-controller-manager condition met
daemon set "ome-model-agent-daemonset" successfully rolled out
```
If the second command fails with `NotFound`, run [Step 3 of Install OME](install.md#step-3-install-ome) again: its values file turns on the model agent.
## Step 2: Create the model
A ClusterBaseModel is cluster-scoped, so InferenceServices in any namespace can serve it. Save this one as `qwen3-0-6b-model.yaml`:
```yaml title="qwen3-0-6b-model.yaml"
apiVersion: ome.io/v1beta1
kind: ClusterBaseModel
metadata:
name: qwen3-0-6b
spec:
storage:
storageUri: hf://Qwen/Qwen3-0.6B
path: /mnt/data/models/Qwen/Qwen3-0.6B
```
- `storageUri` says where the weights come from: `hf://`, then the model's repository on Hugging Face.
- `path` is the directory on each node that holds the weights. Keep it inside the model agent's host directory, `/mnt/data/models` by default, so that the weights land on the node's disk.
Every node that runs the model agent downloads the model. To keep it on some nodes only, see [Choose the nodes](../concepts/models/base-models.md#choose-the-nodes).
Create the model:
```bash
kubectl apply -f qwen3-0-6b-model.yaml
```
```output
clusterbasemodel.ome.io/qwen3-0-6b created
```
## Step 3: Watch the model become Ready
Watch the model's [state](../concepts/models/base-models.md#model-lifecycle):
```bash
kubectl get clusterbasemodel qwen3-0-6b -w -o custom-columns=NAME:.metadata.name,STATE:.status.state
```
```output
NAME STATE
qwen3-0-6b In_Transit
qwen3-0-6b In_Transit
qwen3-0-6b Ready
```
Press Ctrl+C once the state is `Ready`. `` means that OME hasn't set a state yet.
Meanwhile, the model agent on each node downloads the weights into `/mnt/data/models/Qwen/Qwen3-0.6B`. The model is `Ready` as soon as one node has it. OME reads the model's format and architecture from its files, and fills them in for you. Check [what OME learned](../concepts/models/base-models.md#what-ome-learns-from-the-model):
```bash
kubectl get clusterbasemodel qwen3-0-6b \
-o custom-columns=NAME:.metadata.name,FORMAT:.spec.modelFormat.name,ARCHITECTURE:.spec.modelArchitecture,STATE:.status.state
```
```output
NAME FORMAT ARCHITECTURE STATE
qwen3-0-6b safetensors Qwen3ForCausalLM Ready
```
## Step 4: Create the runtime
The ClusterServingRuntime `srt-qwen3-0-6b`, from OME's catalog, runs SGLang from the image `docker.io/lmsysorg/sglang:v0.5.5.post3-cu129-amd64`, which is built for amd64 and CUDA 12.9. SGLang serves the model on port 8080, under the name `Qwen/Qwen3-0.6B`. The runtime sets `autoSelect: false`, so an InferenceService must name it to use it.
Apply [the runtime](https://github.com/ome-projects/ome/blob/v1.2.2/config/runtimes/srt/Qwen/qwen3-0-6b-rt.yaml) from the v1.2.2 release:
```bash
kubectl apply -f https://raw.githubusercontent.com/ome-projects/ome/v1.2.2/config/runtimes/srt/Qwen/qwen3-0-6b-rt.yaml
```
```output
clusterservingruntime.ome.io/srt-qwen3-0-6b created
```
## Step 5: Create the InferenceService
Create a namespace for the InferenceService:
```bash
kubectl create namespace qwen3-0-6b
```
```output
namespace/qwen3-0-6b created
```
Save this InferenceService as `qwen3-0-6b-isvc.yaml`:
```yaml title="qwen3-0-6b-isvc.yaml"
apiVersion: ome.io/v1beta1
kind: InferenceService
metadata:
name: qwen3-0-6b
namespace: qwen3-0-6b
spec:
model:
name: qwen3-0-6b
runtime:
name: srt-qwen3-0-6b
engine:
minReplicas: 1
maxReplicas: 1
```
- `model.name` names the model from Step 2, and `model.kind` defaults to `ClusterBaseModel`. To serve a BaseModel from the InferenceService's namespace instead, set `model.kind: BaseModel`.
- `runtime.name` names the runtime from Step 4.
- `engine` is the component that runs the model. The runtime supplies its container, and `minReplicas` and `maxReplicas` keep it at one pod.
Create the InferenceService:
```bash
kubectl apply -f qwen3-0-6b-isvc.yaml
```
```output
inferenceservice.ome.io/qwen3-0-6b created
```
## Step 6: Watch the InferenceService become Ready
Wait for the InferenceService to be ready. The first start pulls the SGLang image, which can take several minutes:
```bash
kubectl wait --for=condition=Ready inferenceservice/qwen3-0-6b -n qwen3-0-6b --timeout=30m
```
```output
inferenceservice.ome.io/qwen3-0-6b condition met
```
Then look at the InferenceService:
```bash
kubectl get inferenceservice qwen3-0-6b -n qwen3-0-6b
```
```output
NAME URL READY BASEMODEL RUNTIME AGE
qwen3-0-6b http://qwen3-0-6b.qwen3-0-6b.svc.cluster.local:8080 True qwen3-0-6b srt-qwen3-0-6b 7m12s
```
The chart turns off ingress creation by default, so OME puts the Service `qwen3-0-6b` in front of the engine, and `URL` is its address inside the cluster.
OME runs the engine as the Deployment `qwen3-0-6b-engine`, in the default RawDeployment [deployment mode](../concepts/architecture/deployment-modes.md). Its pod runs on a node where the model is `Ready`, and mounts the weights from that node. List it:
```bash
kubectl get pods -n qwen3-0-6b
```
```output
NAME READY STATUS RESTARTS AGE
qwen3-0-6b-engine-6d8f9c7b5d-x2k4p 1/1 Running 0 7m8s
```
## Step 7: Send a request
The Service `qwen3-0-6b` is a ClusterIP Service, so forward a local port to it, and leave the command running. If you turned on ingress creation, forward to `svc/qwen3-0-6b-engine` instead.
```bash
kubectl port-forward svc/qwen3-0-6b 8080:8080 -n qwen3-0-6b
```
```output
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
```
In a second terminal, send a chat completion request. `model` is the name that the runtime serves the model under. `chat_template_kwargs` turns off Qwen3's thinking mode, which can use up `max_tokens` on `` reasoning before the answer.
```bash
curl -s http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "Qwen/Qwen3-0.6B", "messages": [{"role": "user", "content": "What is Kubernetes? Answer in one sentence."}], "max_tokens": 100, "chat_template_kwargs": {"enable_thinking": false}}'
```
The response is an OpenAI chat completion, with the model's answer in `choices[0].message.content`. If `finish_reason` is `length`, the answer ran out of tokens: raise `max_tokens`.
## Troubleshooting
[Troubleshoot an InferenceService](../guides/troubleshoot/troubleshoot-an-inferenceservice.md) covers more symptoms.
### The model doesn't become Ready
Check the model's [label](../concepts/models/base-models.md#node-labels-and-status) on each node:
```bash
kubectl get nodes -L models.ome.io/clusterbasemodel.qwen3-0-6b
```
```output
NAME STATUS ROLES AGE VERSION CLUSTERBASEMODEL.QWEN3-0-6B
gpu-node-1 Ready 12d v1.33.4 Failed
```
- Empty: no model agent runs on the node. List the agent's pods and their nodes with `kubectl get pods -n ome -l app.kubernetes.io/component=ome-model-agent-daemonset -o wide`. The agent tolerates the `nvidia.com/gpu` taint. For nodes with other taints, add tolerations to `modelAgent.tolerations`.
- `Updating`: the agent is still downloading or reading the model.
- `Failed`: the download or the reading failed, for example because the node can't reach Hugging Face.
For `Updating` and `Failed`, read the agent's logs with `kubectl logs -n ome -l app.kubernetes.io/component=ome-model-agent-daemonset --tail=100`. [Run the model agent](../guides/operate-ome/model-agent.md) covers the agent's settings.
### The InferenceService is rejected or gets no pods {#the-webhook-rejects-the-inferenceservice}
When the webhook rejects the InferenceService, kubectl's error includes `admission webhook "inferenceservice.ome-webhook-server.validator" denied the request:`, followed by a reason:
| The reason includes | Fix |
| --- | --- |
| `referenced model "qwen3-0-6b" not found` | Create the model, as in [Step 2](#step-2-create-the-model). |
| `model format name is required` | Wait for the model to be `Ready`, as in [Step 3](#step-3-watch-the-model-become-ready). |
| `runtime srt-qwen3-0-6b not found` | Create the runtime, as in [Step 4](#step-4-create-the-runtime). |
Then apply `qwen3-0-6b-isvc.yaml` again.
On v1.2.2, the webhook admits the InferenceService in the last two cases. OME creates no pods for it, and records the reason in `RuntimeValidationError` events:
```bash
kubectl get events -n qwen3-0-6b --field-selector reason=RuntimeValidationError
```
```output
LAST SEEN TYPE REASON OBJECT MESSAGE
8s Warning RuntimeValidationError inferenceservice/qwen3-0-6b Runtime srt-qwen3-0-6b does not support model qwen3-0-6b: runtime srt-qwen3-0-6b not found in namespace qwen3-0-6b or at cluster scope
```
Fix the cause, and OME creates the engine on its own.
### The engine pod stays Pending
Look at the pod's events with `kubectl describe pod -n qwen3-0-6b -l ome.io/inferenceservice=qwen3-0-6b`. The scheduler's message says why no node fits:
- `Insufficient nvidia.com/gpu`, `Insufficient cpu` or `Insufficient memory`: no node has a free GPU, 10 CPUs and 30 GiB of memory. On the GPU node, the model agent's pod already takes 10 CPUs and 100 GiB. Lower them with `modelAgent.resources`, or free the GPU.
- `didn't match Pod's node affinity/selector`: the model isn't `Ready` on any node with a free GPU. List the nodes that have it with `kubectl get nodes -l models.ome.io/clusterbasemodel.qwen3-0-6b=Ready`.
- `untolerated taint`: the GPU node has a taint other than `nvidia.com/gpu`, the only one that the runtime tolerates.
### The engine pod isn't ready or restarts
The runtime's startup probe gives SGLang about 16 minutes to load the model before Kubernetes restarts its container. Read SGLang's logs with `kubectl logs -n qwen3-0-6b -l ome.io/inferenceservice=qwen3-0-6b --tail=100`.
- `ErrImagePull` or `ImagePullBackOff`: the node can't pull the SGLang image from Docker Hub. If your nodes pull from a mirror, see [Install from a private registry](private-registries.md).
- SGLang can't find the model's files: check that the model's `path` is inside the model agent's host directory. With a path outside it, the model still becomes `Ready`, but the engine's pod mounts an empty directory.
### The engine pod runs, but the InferenceService doesn't become Ready {since=v1.3}
RawDeployment engines like this one need the Prometheus Operator's PodMonitor CRD. Install it as in [The PodMonitor CRD](install.md#the-podmonitor-crd). The controller looks for the CRD only when it starts, so then restart it with `kubectl rollout restart deployment/ome-controller-manager -n ome`.
## Clean up
[Serve a model on OMENative](../guides/omenative/serve-a-model-on-omenative.md) starts from this model and runtime. If you go on to it, delete only the InferenceService, which frees its GPU. To delete everything:
```bash
kubectl delete inferenceservice qwen3-0-6b -n qwen3-0-6b
kubectl delete namespace qwen3-0-6b
kubectl delete clusterservingruntime srt-qwen3-0-6b
kubectl delete clusterbasemodel qwen3-0-6b
```
```output
inferenceservice.ome.io "qwen3-0-6b" deleted from qwen3-0-6b namespace
namespace "qwen3-0-6b" deleted
clusterservingruntime.ome.io "srt-qwen3-0-6b" deleted
clusterbasemodel.ome.io "qwen3-0-6b" deleted
```
Deleting the model waits until the model agent has deleted `/mnt/data/models/Qwen/Qwen3-0.6B` from each node that has it. To keep the files, see [Deleting a model](../concepts/models/base-models.md#deleting-a-model).
## Next steps
- [Serve a model on OMENative](../guides/omenative/serve-a-model-on-omenative.md): run this model's engine on OMENative, OME's own workload controller, instead of a Deployment. Since v1.3.
- [Expose a service without ingress](../guides/networking/expose-without-ingress.md): reach the model from outside the cluster through a LoadBalancer or NodePort Service.
- [Pre-configured models and runtimes](pre-configured-models.md): serve more models with the `ome-serving` chart. Its `qwen3-0-6b` entry creates resources with the same names as yours, so clean up first if you turn it on.
- [Serving runtimes](../concepts/runtimes/serving-runtimes.md): how a runtime describes the pods that serve a model, and how OME picks one.
- [InferenceService](../concepts/serving/inference-services.md): the engine, decoder and router components, and what OME creates for each.