Move from LeaderWorkerSet to OMENative Unreleased: coming in v1.3
Move a multi-node InferenceService off the deprecated MultiNode mode, which runs on LeaderWorkerSet, onto OMENative, and delete the LeaderWorkerSet that OME leaves behind.
Move a multi-node component off the deprecated MultiNode deployment mode, which runs it on a LeaderWorkerSet, and onto OMENative, OME's own way of running an inference component. OMENative runs each replica as an Instance: a leader pod and its workers, which OME creates, updates, repairs, moves and removes as one unit. On OMENative:
- Updates bring a new Instance up and serving before the old one drains, by default. Rollout groups can roll out prefill and decode together, by canary, blue-green or rolling update.
- A revision that keeps failing is held until you change the component or release the revision.
- When one of an Instance's pods fails, OME rebuilds the whole Instance, by default.
- You can move an Instance off a node, and OME starts its replacement first. The alpha Alfred recommends moves and, when you let it, requests them. On
MultiNode, it only advises, with the reasonLWSMigrationUnsupported. - The component reports per-Instance status, and can have an autoscaler and a PodDisruptionBudget.
- Gang scheduling works with the PodGroup CRD and a gang-aware scheduler, such as the alpha OME scheduler, which needs Kubernetes 1.35.
The steps move the engine of the InferenceService deepseek-r1, then delete its old LeaderWorkerSet.
What changes when a component moves¶
When a component's ome.io/deploymentMode annotation changes from MultiNode to OMENative, OME:
- Creates the 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-enginerunning 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:
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'NAMESPACE NAME INFERENCESERVICE COMPONENT RUNTIME
deepseek-r1 lws-deepseek-r1-engine deepseek-r1 engine srt-deepseek-rdmaDo 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 <InferenceService>-<component>, has already moved: finish its move from Step 2. List the InferenceReplicas:
kubectl get inferencereplicas --all-namespacesNo resources foundStep 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, use servingruntime and -n with the InferenceService's namespace in this step's commands.
kubectl get clusterservingruntime srt-deepseek-rdma -o yaml \
| grep -oE 'LWS_[A-Z_]+|leaderworkerset\.sigs\.k8s\.io/[a-z-]+' | LC_ALL=C sort -uLWS_GROUP_SIZE
LWS_LEADER_ADDRESS
LWS_WORKER_INDEX
leaderworkerset.sigs.k8s.io/worker-indexIf 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:
kubectl get clusterservingruntime srt-deepseek-rdma -o yaml > srt-deepseek-rdma-omenative.yamlThe command prints nothing. OMENative pods use OME's names, which 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:
Replace
metadatawith a new name:metadata: name: srt-deepseek-rdma-omenativeDelete
status, if the file has one.Make the replacements in the table. In
srt-deepseek-rdma, the variables are in the leader and workercommand, and the label is in the router's--selector. The catalog's PD runtimes,srt-deepseek-rdma-pdandsrt-kimi-k2-pd, have the label in--prefill-selectorand--decode-selector.Delete any other
leaderworkerset.sigs.k8s.iokey, which OMENative ignores. In place ofexclusive-topology, the component'stopologyKeykeeps each Instance's pods in one domain, which other pods can share.Delete
ome.io/deploymentMode: MultiNodefromengineConfig.annotationsanddecoderConfig.annotations, if it's there.Set
autoSelect: falseon each entry insupportedModelFormats, assrt-deepseek-rdmaalready does, so that OME picks the copy only for InferenceServices that name it.
Check that nothing from LeaderWorkerSet is left:
grep -c 'LWS_\|leaderworkerset' srt-deepseek-rdma-omenative.yaml0Create the new runtime:
kubectl create -f srt-deepseek-rdma-omenative.yamlclusterservingruntime.ome.io/srt-deepseek-rdma-omenative createdStep 3: Move the component to OMENative¶
Change three things in the InferenceService:
spec.runtime.name: the runtime from Step 2.- The
ome.io/deploymentModeannotation inspec.engine.annotations: set it toOMENative, in place of theMultiNodepin. spec.engine.leaderandspec.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 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:
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":{}}}}'inferenceservice.ome.io/deepseek-r1 patchedapiVersion: 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: {}kubectl apply -f deepseek-r1.yamlinferenceservice.ome.io/deepseek-r1 configuredStep 4: Check the new Instances¶
List the component's InferenceReplica. Its columns count Instances, not pods:
kubectl get inferencereplicas -n deepseek-r1NAME COMPONENT DESIRED CURRENT READY AVAILABLE AGE
deepseek-r1-engine engine 1 1 1 1 3mList the component's pods. The LeaderWorkerSet's pods, which have no ome.io/runner label, still run next to the new ones:
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'NAME RUNNER PHASE
deepseek-r1-engine-0-leader-0 leader Running
deepseek-r1-engine-0-worker-0 worker Running
lws-deepseek-r1-engine-0 <none> Running
lws-deepseek-r1-engine-0-1 <none> RunningIf 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:
kubectl wait --for=condition=Ready inferenceservice/deepseek-r1 -n deepseek-r1 --timeout=60minferenceservice.ome.io/deepseek-r1 condition metStep 5: Delete the old LeaderWorkerSet¶
Delete the LeaderWorkerSet:
kubectl delete leaderworkerset lws-deepseek-r1-engine -n deepseek-r1leaderworkerset.leaderworkerset.x-k8s.io "lws-deepseek-r1-engine" deleted from deepseek-r1 namespaceKubernetes deletes its pods with it. Once they've terminated, none are left:
kubectl get pods -n deepseek-r1 -l leaderworkerset.sigs.k8s.io/name=lws-deepseek-r1-engineNo resources found in deepseek-r1 namespace.Uninstall LeaderWorkerSet¶
Once no component runs on a LeaderWorkerSet, you can uninstall LeaderWorkerSet as its docs describe. The manager checks for its CRD only when it starts, so restart the manager afterwards:
kubectl rollout restart deployment ome-controller-manager -n omedeployment.apps/ome-controller-manager restartedPin 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 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:
kubectl patch inferenceservice deepseek-r1 -n deepseek-r1 --type merge \
-p '{"spec":{"engine":{"annotations":{"ome.io/deploymentMode":"MultiNode"}}}}'inferenceservice.ome.io/deepseek-r1 patchedPin 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:
kubectl delete inferencereplica deepseek-r1-engine -n deepseek-r1inferencereplica.ome.io "deepseek-r1-engine" deleted from deepseek-r1 namespaceTroubleshooting¶
The new pods never become ready¶
The usual cause is a runtime that still passes LWS_* variables. Check which ones the leader pod uses:
kubectl get pod deepseek-r1-engine-0-leader-0 -n deepseek-r1 -o yaml \
| grep -oE 'LWS_[A-Z_]+' | LC_ALL=C sort -uLWS_GROUP_SIZE
LWS_LEADER_ADDRESS
LWS_WORKER_INDEXPoint the component at a runtime that uses OME's names, as in Steps 2 and 3, or go back to the LeaderWorkerSet.
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:
kubectl get service deepseek-r1-engine -n deepseek-r1 -o jsonpath='{.spec.selector}{"\n"}'{"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:
InvalidDeploymentModeCombination: engine deploymentMode="OMENative" does not match decoder deploymentMode="MultiNode"; when either is OMENative, both must use the same valueThe 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:
kubectl get inferenceservice deepseek-r1 -n deepseek-r1 \
-o jsonpath='{.spec.engine.annotations.ome\.io/deploymentMode}{"\n"}'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 until you're ready.
Next steps¶
- Serve a multi-node model: run a new multi-node model on OMENative from the start.
- OMENative and LeaderWorkerSet: everything a component gains on OMENative.
- OMENative update strategies: choose how OME replaces the Instances' pods when the component changes.
- Let Alfred migrate Instances: let the alpha Alfred move Instances off fragmented or unhealthy nodes.