Hierarchical Placement Walkthrough
Configure topology levels across cloud zones and physical racks, request domain placement for
multi-Pod replicas, and see what a node move and a hierarchy change do.
Contents
- Summary
- Environment
- Step 1: Discover cloud zones
- Step 2: Request zone placement
- Step 3: Publish an inventory snapshot
- Step 4: Request rack placement
- Step 5: Move a node
- Limitations
- Troubleshooting
Summary
Topology-aware scheduling (TAS) places multi-Pod replicas inside one failure domain — a cloud zone
or a physical rack. Configure a read-only cloud-zone TopologySource, then narrow placement to a
static rack inventory snapshot. Moving a node between racks and changing the hierarchy levels have
different effects on existing workloads.
The steps discover infrastructure hierarchies, apply placement constraints to serving workloads, and manage topology updates.
| Object | Scope | Creator | Purpose |
|---|---|---|---|
TopologySource |
cluster | administrator | the hierarchy levels and where each node’s values come from |
ConfigMap |
namespace | administrator | the versioned rack inventory snapshot (Step 3) |
ModelDeployment |
namespace | user | the topology.requiredLevel placement constraint |
Environment
| Item | Value |
|---|---|
| Cluster | Kubernetes with GPUStack installed |
| Nodes | labeled topology.kubernetes.io/region and topology.kubernetes.io/zone for Steps 1 and 2; Steps 3 through 5 publish a ConfigMap rack inventory instead |
InstanceType |
gpustack--nvidia-a10g-linux-amd64 |
| Model | Qwen/Qwen2.5-7B-Instruct |
| Engine | vLLM gpustack/runner:cuda13.0-vllm0.29.0, with llm-d-router |
Step 1: Discover cloud zones
Verify existing node labels and cluster status before configuring topology sources:
kubectl get nodes --show-labels
kubectl get topologysources
kubectl get topologies.kueue.x-k8s.ioWhen nodes already carry cloud provider labels for regions and zones, define a read-only
TopologySource using nodeLabels:
apiVersion: worker.gpustack.ai/v1
kind: TopologySource
metadata:
name: cloud-zones
spec:
nodeSelector:
matchLabels:
node-role.kubernetes.io/worker: ""
levels:
- topology.kubernetes.io/region
- topology.kubernetes.io/zone
nodeLabels: {}The operator validates that labels form a strict tree (each zone belongs to exactly one region) and
computes a profile hash. Nodes receive topology.gpustack.ai/profile=fnv64-<hash>, and Kueue
materializes the matching Topology resource.
Check source readiness:
kubectl get topologysources cloud-zones -o yamlStep 2: Request zone placement
When a serving replica consists of multiple Pods (such as tensor-parallel groups spanning multiple nodes),
request placement within a single availability zone:
apiVersion: worker.gpustack.ai/v1
kind: ModelDeployment
metadata:
name: qwen-distributed
namespace: team-a
spec:
model:
name: Qwen/Qwen2.5-7B-Instruct
engine:
name: vLLM
image: gpustack/runner:cuda13.0-vllm0.29.0
roles:
- name: server
replicas: 1
size: 2
instanceType: gpustack--nvidia-a10g-linux-amd64
resources:
accelerator: "1"
+ topology:
+ requiredLevel: topology.kubernetes.io/zone
Kueue TAS admits the replica only when a single zone contains enough unallocated capacity to host all
2 Pods. If capacity is scattered across zones without filling any single zone, the workload waits in
Pending rather than stranding split Pods.
Step 3: Publish an inventory snapshot
In private data centers without cloud labels, publish rack hierarchies using a versioned snapshot
stored in a ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: topology-inventory
namespace: gpustack-system
data:
snapshot.yaml: |
apiVersion: topology.gpustack.ai/v1alpha1
revision: inventory-42
nodes:
worker-a:
topology.gpustack.ai/region: region-a
topology.gpustack.ai/zone: zone-a
topology.gpustack.ai/rack: rack-01
worker-b:
topology.gpustack.ai/region: region-a
topology.gpustack.ai/zone: zone-a
topology.gpustack.ai/rack: rack-02
---
apiVersion: worker.gpustack.ai/v1
kind: TopologySource
metadata:
name: data-center
spec:
nodeSelector: {}
levels:
- topology.gpustack.ai/region
- topology.gpustack.ai/zone
- topology.gpustack.ai/rack
configMap:
configMapRef:
namespace: gpustack-system
name: topology-inventory
key: snapshot.yaml
maxStaleness: 10mThe operator validates the snapshot atomically, creates NFD NodeFeature resources, and writes the
custom labels to each node.
Step 4: Request rack placement
With rack labels published, narrow the workload constraint from zone-level to rack-level:
apiVersion: worker.gpustack.ai/v1
kind: ModelDeployment
metadata:
name: qwen-distributed
namespace: team-a
spec:
roles:
- name: server
topology:
- requiredLevel: topology.kubernetes.io/zone
+ requiredLevel: topology.gpustack.ai/rack
All Pods within each replica group are placed within the same physical rack, minimizing inter-switch
network latency.
Step 5: Move a node
When a node is cabled into a new rack, update the ConfigMap snapshot with an incremented revision:
apiVersion: v1
kind: ConfigMap
metadata:
name: topology-inventory
namespace: gpustack-system
data:
snapshot.yaml: |
apiVersion: topology.gpustack.ai/v1alpha1
- revision: inventory-42
+ revision: inventory-43
nodes:
worker-a:
topology.gpustack.ai/region: region-a
topology.gpustack.ai/zone: zone-a
topology.gpustack.ai/rack: rack-01
worker-b:
topology.gpustack.ai/region: region-a
topology.gpustack.ai/zone: zone-a
- topology.gpustack.ai/rack: rack-02
+ topology.gpustack.ai/rack: rack-03
The profile is a hash of the ordered level keys, and the keys did not change. The node keeps its profile, so the worker creates no flavor and applies no hold. The node carries the new rack value after NFD publishes it.
A change to the ordered level keys is a different operation that can evict and readmit workloads,
unlike the revision-only change shown above. See
Change a live hierarchy
for the
HoldAndDrain migration sequence and its interruption impact before editing levels.
Limitations
- Single-source node ownership and the hostname-only profile fallback are covered in Hierarchy profiles .
- Domain capacity packing and multi-role gang holds are covered in Troubleshooting .
- Changing the ordered
levelslist on a live hierarchy triggersHoldAndDrainmigration; see Change a live hierarchy .
Troubleshooting
For general failure surfaces (including AwaitingQueueStatus, HoldAndDrain flavor migration, and
domain capacity fragmentation), see Topology-Aware Scheduling Failure Surfaces
and Change a live hierarchy
.
| Symptom | Signal | Check & Mitigation |
|---|---|---|
TopologySource Ready=False |
Reason: InvalidHierarchy / SnapshotInvalid |
Label hierarchy invalid (child key must have unique parent value); fix snapshot syntax or missing node entries |
TopologySource Ready=False |
Reason: OwnershipConflict |
Multiple writable sources target the same node; adjust node selectors to ensure disjoint sets |
TopologySource Valid=False |
Reason: ConfigMapReadFailed |
Verify ConfigMap name and namespace match the worker installation |
Node Topology Profile falls back to single-level hostname |
Ambiguous source ownership | Node selected by 0 or multiple Ready sources; check node selectors across all sources |
Multi-role deployment has 1 Workload reserved but Pod not bound |
Joint admission gang hold | First role fits in domain, but held until secondary role (prefill/decode) secures domain capacity; check other roles |