---
title: Move from LeaderWorkerSet to OMENative
description: Move a multi-node InferenceService off the deprecated MultiNode mode, which runs on LeaderWorkerSet, onto OMENative, and delete the LeaderWorkerSet that OME leaves behind.
since: v1.3
---
Move a multi-node component off the deprecated `MultiNode` [deployment mode](../../concepts/architecture/deployment-modes.md), which runs it on a LeaderWorkerSet, and onto [OMENative](../../concepts/omenative/overview.md), OME's own way of running an inference component. OMENative runs each replica as an [Instance](../../concepts/omenative/instances.md): a leader pod and its workers, which OME creates, updates, repairs, moves and removes as one unit. On OMENative:
- [Updates](../../concepts/architecture/omenative-update-strategies.md) bring a new Instance up and serving before the old one drains, by default. [Rollout groups](../../concepts/rollouts-and-traffic/rollout-groups.md) can roll out prefill and decode together, by canary, blue-green or rolling update.
- A revision that keeps failing is [held](../roll-out-changes/release-a-held-revision.md) until you change the component or release the revision.
- When one of an Instance's pods fails, OME [rebuilds the whole Instance](../../concepts/omenative/instance-restart-policy.md), by default.
- You can [move an Instance off a node](../../concepts/omenative/migration-and-transient-scale.md), and OME starts its replacement first. The alpha [Alfred](../../concepts/scheduling/alfred.md) recommends moves and, when you let it, requests them. On `MultiNode`, it only advises, with the reason `LWSMigrationUnsupported`.
- The component reports [per-Instance status](../../concepts/architecture/deployment-modes.md#observe-omenative-in-status), and can have an autoscaler and a PodDisruptionBudget.
- [Gang scheduling](../../concepts/serving/gang-scheduling.md) works with the PodGroup CRD and a gang-aware scheduler, such as the alpha [OME scheduler](../../concepts/scheduling/ome-scheduler.md), which needs Kubernetes 1.35.
The steps move the `engine` of the [InferenceService](../../concepts/serving/inference-services.md) `deepseek-r1`, then delete its old LeaderWorkerSet.
!!! danger "Upgrading from v1.2.2 moves multi-node components to OMENative"
When you upgrade from v1.2.2, each engine or decoder that runs on a LeaderWorkerSet moves to OMENative, unless it has an `ome.io/deploymentMode` annotation of its own. The annotation that v1.2.2 added to the InferenceService's `metadata.annotations` doesn't count. The component serves nothing until its new pods are ready, and nothing at all if its runtime uses `LWS_*` variables, as the catalog's `srt-deepseek-rdma` does. To move on your own schedule, [pin each component to `MultiNode`](#pin-a-component-to-multinode) before you upgrade.
- OME v1.3, with the InferenceReplica controller enabled, as it is by default.
- A component that runs on a LeaderWorkerSet. The examples use the catalog sample `deepseek-r1`, in the namespace `deepseek-r1`, with its engine [pinned to `MultiNode`](#pin-a-component-to-multinode). Its runtime, the ClusterServingRuntime `srt-deepseek-rdma`, gives the engine one leader and one worker.
- Permission to create ClusterServingRuntimes, and to patch InferenceServices and delete LeaderWorkerSets in the InferenceService's namespace.
- A time when the component can stop serving, from Step 3 until its new pods are ready. If the move fails, you can [go back](#pin-a-component-to-multinode).
## What changes when a component moves
When a component's `ome.io/deploymentMode` annotation changes from `MultiNode` to `OMENative`, OME:
- Creates the [InferenceReplica](../../reference/api/ome.v1beta1.md#ome-io-v1beta1-InferenceReplica) `deepseek-r1-engine`, which runs the component's Instances.
- Points the component's Service, `deepseek-r1-engine`, at the new pods at once, so the component serves nothing until they're ready.
- Leaves the LeaderWorkerSet `lws-deepseek-r1-engine` running until you delete it. Its pods keep their accelerators.
- Gives the component the ome-resources chart's default PodDisruptionBudget.
The new pods get none of LeaderWorkerSet's `LWS_*` variables or labels. If the runtime uses them, the pods can't find their leader, so Step 2 makes a copy of the runtime that uses OME's names.
## Step 1: Find the components that run on LeaderWorkerSet
List the LeaderWorkerSets that OME created, in every namespace:
```bash
kubectl get leaderworkersets --all-namespaces -l ome.io/inferenceservice \
-o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,INFERENCESERVICE:.metadata.labels.ome\.io/inferenceservice,COMPONENT:.metadata.labels.component,RUNTIME:.metadata.labels.serving-runtime'
```
```output
NAMESPACE NAME INFERENCESERVICE COMPONENT RUNTIME
deepseek-r1 lws-deepseek-r1-engine deepseek-r1 engine srt-deepseek-rdma
```
Do Step 2 once for each runtime in the list, and Steps 3 to 5 for each InferenceService. A component that also has an InferenceReplica, named `-`, has already moved: finish its move from Step 2. List the InferenceReplicas:
```bash
kubectl get inferencereplicas --all-namespaces
```
```output
No resources found
```
## Step 2: Make an OMENative copy of the runtime
Check whether the runtime from Step 1 uses LeaderWorkerSet's variables or labels. For a namespaced [serving runtime](../../concepts/runtimes/serving-runtimes.md), use `servingruntime` and `-n` with the InferenceService's namespace in this step's commands.
```bash
kubectl get clusterservingruntime srt-deepseek-rdma -o yaml \
| grep -oE 'LWS_[A-Z_]+|leaderworkerset\.sigs\.k8s\.io/[a-z-]+' | LC_ALL=C sort -u
```
```output
LWS_GROUP_SIZE
LWS_LEADER_ADDRESS
LWS_WORKER_INDEX
leaderworkerset.sigs.k8s.io/worker-index
```
If the command prints nothing, the runtime works on OMENative as it is: go to Step 3, and leave `spec.runtime` out of the change.
Don't edit the runtime in place: by default, a runtime change reaches every InferenceService that uses it, including those still on a LeaderWorkerSet. Copy it to a file instead:
```bash
kubectl get clusterservingruntime srt-deepseek-rdma -o yaml > srt-deepseek-rdma-omenative.yaml
```
The command prints nothing. OMENative pods use OME's names, which [Pod labels and environment](../../concepts/architecture/deployment-modes.md#pod-labels-and-environment) lists:
| LeaderWorkerSet | OMENative |
| --- | --- |
| `$(LWS_LEADER_ADDRESS)` | `$(OME_LEADER_ADDRESS)` |
| `$(LWS_GROUP_SIZE)` | `$(OME_INSTANCE_POD_COUNT)` |
| `$(LWS_WORKER_INDEX)` | `$(OME_INSTANCE_POD_RANK)` |
| The label `leaderworkerset.sigs.k8s.io/worker-index=0`, to select leaders | The label `ome.io/runner=leader` |
OME sets its variables in the pod's containers only, so an init container can't use them. Edit `srt-deepseek-rdma-omenative.yaml`:
1. Replace `metadata` with a new name:
```yaml
metadata:
name: srt-deepseek-rdma-omenative
```
2. Delete `status`, if the file has one.
3. Make the replacements in the table. In `srt-deepseek-rdma`, the variables are in the leader and worker `command`, and the label is in the router's `--selector`. The catalog's PD runtimes, `srt-deepseek-rdma-pd` and `srt-kimi-k2-pd`, have the label in `--prefill-selector` and `--decode-selector`.
4. Delete any other `leaderworkerset.sigs.k8s.io` key, which OMENative ignores. In place of `exclusive-topology`, the component's `topologyKey` keeps each Instance's pods in one domain, which other pods can share.
5. Delete `ome.io/deploymentMode: MultiNode` from `engineConfig.annotations` and `decoderConfig.annotations`, if it's there.
6. Set `autoSelect: false` on each entry in `supportedModelFormats`, as `srt-deepseek-rdma` already does, so that OME picks the copy only for InferenceServices that name it.
Check that nothing from LeaderWorkerSet is left:
```bash
grep -c 'LWS_\|leaderworkerset' srt-deepseek-rdma-omenative.yaml
```
```output
0
```
Create the new runtime:
```bash
kubectl create -f srt-deepseek-rdma-omenative.yaml
```
```output
clusterservingruntime.ome.io/srt-deepseek-rdma-omenative created
```
## Step 3: Move the component to OMENative
Change three things in the InferenceService:
- `spec.runtime.name`: the runtime from Step 2.
- The `ome.io/deploymentMode` annotation in `spec.engine.annotations`: set it to `OMENative`, in place of the `MultiNode` pin.
- `spec.engine.leader` and `spec.engine.worker`: add them as empty blocks if they're missing. Empty blocks take their settings from the runtime, and make the Service select only the leaders.
Some InferenceServices need more in the same change:
| If the InferenceService | Also |
| --- | --- |
| Has a `decoder` | Set the decoder's annotation to `OMENative` too, and add its empty `leader` and `worker` blocks if they're missing. The webhook rejects the change when only one component is `OMENative`. |
| Sets `spec.runtime.autoSync: false` | Set its `ome.io/runtime-sync` annotation to a new value, so that it [rolls forward](../../concepts/runtimes/runtime-revisions.md#roll-forward-to-the-latest-runtime) to the new runtime. |
| Sets `spec.runtime.revision` | Delete it, with `null` in a merge patch. The webhook rejects a revision that belongs to another runtime. |
Use the tab for how you manage the InferenceService:
=== "Patch"
```bash
kubectl patch inferenceservice deepseek-r1 -n deepseek-r1 --type merge \
-p '{"spec":{"runtime":{"name":"srt-deepseek-rdma-omenative"},"engine":{"annotations":{"ome.io/deploymentMode":"OMENative"},"leader":{},"worker":{}}}}'
```
```output
inferenceservice.ome.io/deepseek-r1 patched
```
=== "Manifest"
```yaml title="deepseek-r1.yaml"
apiVersion: ome.io/v1beta1
kind: InferenceService
metadata:
name: deepseek-r1
namespace: deepseek-r1
spec:
model:
name: deepseek-r1
runtime:
name: srt-deepseek-rdma-omenative
engine:
annotations:
ome.io/deploymentMode: OMENative
minReplicas: 1
maxReplicas: 1
leader: {}
worker: {}
```
```bash
kubectl apply -f deepseek-r1.yaml
```
```output
inferenceservice.ome.io/deepseek-r1 configured
```
## Step 4: Check the new Instances
List the component's InferenceReplica. Its columns count Instances, not pods:
```bash
kubectl get inferencereplicas -n deepseek-r1
```
```output
NAME COMPONENT DESIRED CURRENT READY AVAILABLE AGE
deepseek-r1-engine engine 1 1 1 1 3m
```
List the component's pods. The LeaderWorkerSet's pods, which have no `ome.io/runner` label, still run next to the new ones:
```bash
kubectl get pods -n deepseek-r1 -l ome.io/inferenceservice=deepseek-r1,component=engine \
-o custom-columns='NAME:.metadata.name,RUNNER:.metadata.labels.ome\.io/runner,PHASE:.status.phase'
```
```output
NAME RUNNER PHASE
deepseek-r1-engine-0-leader-0 leader Running
deepseek-r1-engine-0-worker-0 worker Running
lws-deepseek-r1-engine-0 Running
lws-deepseek-r1-engine-0-1 Running
```
If the new pods stay `Pending` with a `FailedScheduling` event, the old pods hold the accelerators: do Step 5 now. Then wait for the InferenceService to be ready:
```bash
kubectl wait --for=condition=Ready inferenceservice/deepseek-r1 -n deepseek-r1 --timeout=60m
```
```output
inferenceservice.ome.io/deepseek-r1 condition met
```
## Step 5: Delete the old LeaderWorkerSet
Delete the LeaderWorkerSet:
```bash
kubectl delete leaderworkerset lws-deepseek-r1-engine -n deepseek-r1
```
```output
leaderworkerset.leaderworkerset.x-k8s.io "lws-deepseek-r1-engine" deleted from deepseek-r1 namespace
```
Kubernetes deletes its pods with it. Once they've terminated, none are left:
```bash
kubectl get pods -n deepseek-r1 -l leaderworkerset.sigs.k8s.io/name=lws-deepseek-r1-engine
```
```output
No resources found in deepseek-r1 namespace.
```
## Uninstall LeaderWorkerSet
Once no component runs on a LeaderWorkerSet, you can uninstall LeaderWorkerSet as [its docs](https://lws.sigs.k8s.io) describe. The manager checks for its CRD only when it starts, so restart the manager afterwards:
```bash
kubectl rollout restart deployment ome-controller-manager -n ome
```
```output
deployment.apps/ome-controller-manager restarted
```
## Pin a component to `MultiNode`
A component stays on its LeaderWorkerSet while its own `ome.io/deploymentMode` annotation says `MultiNode`, as [How OME resolves the mode](../../concepts/architecture/deployment-modes.md#how-ome-resolves-the-mode) explains. Because `MultiNode` is deprecated, a pin is a stopgap. Pin components before you upgrade from v1.2.2, or to go back after a move. To pin the engine of `deepseek-r1`:
```bash
kubectl patch inferenceservice deepseek-r1 -n deepseek-r1 --type merge \
-p '{"spec":{"engine":{"annotations":{"ome.io/deploymentMode":"MultiNode"}}}}'
```
```output
inferenceservice.ome.io/deepseek-r1 patched
```
Pin a decoder the same way, under `decoder`. To pin the InferenceServices that use a runtime, add the annotation to the runtime's `engineConfig.annotations` or `decoderConfig.annotations`. This reaches only those with `spec.runtime.autoSync: true`, the default. If you keep InferenceServices in files, add the annotation there too.
On v1.2.2, the pin restarts the LeaderWorkerSet's pods, unless the InferenceService's `metadata.annotations` already has `ome.io/deploymentMode: MultiNode`. After the upgrade, the pods of each pinned LeaderWorkerSet restart once.
To go back after a move, pin the component and, if you changed it, set `spec.runtime.name` back to the old runtime, handling `autoSync: false` as in Step 3. OME points the Service back at the LeaderWorkerSet, which it creates again if you deleted it. If the component moved when you upgraded, the LeaderWorkerSet's pods restart once. The InferenceReplica's pods keep their accelerators until you delete it:
```bash
kubectl delete inferencereplica deepseek-r1-engine -n deepseek-r1
```
```output
inferencereplica.ome.io "deepseek-r1-engine" deleted from deepseek-r1 namespace
```
## Troubleshooting
### The new pods never become ready
The usual cause is a runtime that still passes `LWS_*` variables. Check which ones the leader pod uses:
```bash
kubectl get pod deepseek-r1-engine-0-leader-0 -n deepseek-r1 -o yaml \
| grep -oE 'LWS_[A-Z_]+' | LC_ALL=C sort -u
```
```output
LWS_GROUP_SIZE
LWS_LEADER_ADDRESS
LWS_WORKER_INDEX
```
Point the component at a runtime that uses OME's names, as in Steps 2 and 3, or [go back to the LeaderWorkerSet](#pin-a-component-to-multinode).
### The Service sends requests to the workers
When only the runtime declares `worker`, the component's Service selects the workers too. The catalog's `srt-deepseek-rdma` gives its workers no readiness probe, so they get requests as soon as they start. Check the Service's selector:
```bash
kubectl get service deepseek-r1-engine -n deepseek-r1 -o jsonpath='{.spec.selector}{"\n"}'
```
```output
{"component":"engine","ome.io/inferenceservice":"deepseek-r1","ome.io/managed-by":"OMENative"}
```
If the selector has no `ome.io/runner`, add empty `leader` and `worker` blocks to `spec.engine`, as in Step 3. OME then adds `"ome.io/runner":"leader"` to it.
### The webhook rejects the change
kubectl prints `admission webhook "inferenceservice.ome-webhook-server.validator" denied the request:`, followed by this message:
```text
InvalidDeploymentModeCombination: engine deploymentMode="OMENative" does not match decoder deploymentMode="MultiNode"; when either is OMENative, both must use the same value
```
The webhook reads each component's mode from the InferenceService, not from the runtime. Give the engine and the decoder the same annotation, in one patch.
### The component still runs on the LeaderWorkerSet
After Step 3, `kubectl get inferencereplicas -n deepseek-r1` shows no InferenceReplica for the component. Check the component's annotation in the InferenceService:
```bash
kubectl get inferenceservice deepseek-r1 -n deepseek-r1 \
-o jsonpath='{.spec.engine.annotations.ome\.io/deploymentMode}{"\n"}'
```
```output
MultiNode
```
| Output | Fix |
| --- | --- |
| `MultiNode` | Set it to `OMENative`, as in Step 3. |
| An empty line | The runtime's `engineConfig.annotations` says `MultiNode`. Set the InferenceService's annotation to `OMENative`, which wins over the runtime's. |
| `OMENative` | If `spec.runtime.autoSync` is `false`, OME still uses the old runtime. Set a new `ome.io/runtime-sync` value, as in Step 3. |
### A component moved to OMENative when you upgraded
The component had no `ome.io/deploymentMode` annotation of its own when you upgraded from v1.2.2, so it now runs on OMENative next to its old LeaderWorkerSet. Finish the move from Step 2, or [pin it to `MultiNode`](#pin-a-component-to-multinode) until you're ready.
## Next steps
- [Serve a multi-node model](serve-a-multi-node-model.md): run a new multi-node model on OMENative from the start.
- [OMENative and LeaderWorkerSet](../../concepts/omenative/overview.md#omenative-and-leaderworkerset): everything a component gains on OMENative.
- [OMENative update strategies](../../concepts/architecture/omenative-update-strategies.md): choose how OME replaces the Instances' pods when the component changes.
- [Let Alfred migrate Instances](../scheduling/let-alfred-migrate-instances.md): let the alpha Alfred move Instances off fragmented or unhealthy nodes.