Pod spec requirements¶
Goal: Check the permissions, ports, volumes, CPU, and memory that this stack's Pods require, and confirm that cluster policy and the nodes' allocatable resources admit them.
Permissions and run mode¶
Permissions the Pod requires¶
The reference configuration runs the modelserver in the default non-root run mode. In non-root mode, the chart sets the following values on the modelserver Pod and container.
| Item | Default | Description |
|---|---|---|
| Run user | uid 2000 (runAsUser: 2000, runAsGroup: 0) | The image declares USER 2000 |
Container securityContext | runAsNonRoot: true, allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities.drop: [ALL], seccompProfile: RuntimeDefault | Applied to the modelserver container |
| Added capabilities | unset | Under non-root, capabilities.add does not become effective, so adding capabilities only causes policy violations with no effect |
fsGroup | 2000 (fsGroupChangePolicy: OnRootMismatch) | Makes volumes such as the compile cache writable |
The non-root mode satisfies Pod Security Standards baseline. The non-root mode does not satisfy restricted.
Non-root and root¶
In non-root mode, set the memlock limit of the modelserver containers to unlimited in the container runtime of each node (see containerd LimitMEMLOCK drop-in). NIXL pins the KV transfer buffers in memory when it registers them for RDMA (ibv_reg_mr), and pinned memory counts toward RLIMIT_MEMLOCK. Only the KV transfer over RoCE in P/D disaggregation needs the unlimited memlock limit. NPU use itself does not need the unlimited memlock limit.
When RDMA is enabled (devices.rdma.enabled: true), the entrypoint fails at startup when the memlock limit is not unlimited.
When you cannot change the container runtime settings of the nodes, as on managed Kubernetes, apply examples/root-privileged.yaml to the chart with -f. In root mode, the entrypoint raises the memlock limit itself with ulimit -l unlimited, and the stack's namespace must allow privileged Pods. The root mode is Unverified.
Container ports¶
On a cluster where NetworkPolicy or a firewall restricts Pod-to-Pod traffic, allow Pod-to-Pod traffic to the ports in the following table and to the NIXL side-channel ports.
The following table is based on the reference configuration. In the reference configuration, prefill runs with pipeline parallel (PP) degree 4, decode runs with data parallel (DP) degree 4, and a routing sidecar sits in front of decode. The Name column shows the container port names the chart declares. With different parallelism, the number of rank ports changes (see Key chart settings).
| Role | Port | Name | What listens |
|---|---|---|---|
| prefill | 8000 | modelserver | The API server |
| prefill | 8001, 8002, 8003 | vllm-rank1..3 | The port shim, which forwards to 8000 |
| prefill | 5555 | dp-rpc | Declared, but nothing listens |
| prefill | 5600 | nixl | Declared, but nothing listens |
| decode | 8000 | sidecar | The routing sidecar |
| decode | 8200 | modelserver | Rank 0 |
| decode | 8201, 8202, 8203 | vllm-rank1..3 | Ranks 1, 2, and 3 |
| decode | 5555 | dp-rpc | DP rank coordination |
| decode | 5600 | nixl | Declared, but nothing listens |
The port shim receives prefill's 8001, 8002, and 8003. The Endpoint Picker (EPP) and the routing sidecar send requests to prefill on the same port list as decode (8000, 8001, 8002, 8003). However, the API server of a prefill that runs under PP opens only 8000. The chart therefore runs a port shim that forwards requests on 8001, 8002, and 8003 to 8000.
NIXL side-channel address and ports¶
Before a KV transfer, NIXL on prefill and decode exchanges connection information over the side-channel (the handshake). The side-channel and the KV transfer use different networks.
| Traffic | Network |
|---|---|
| Side-channel (handshake) | Pod network |
| KV transfer | RoCE NIC. The NIXL RBLN backend picks the NIC on the same NUMA node as the NPU |
The side-channel port differs per rank. Prefill uses 5559 + 2·rank, and decode uses 5659 + 2·rank. For example, decode in the reference configuration (DP4) opens 5659, 5661, 5663, and 5665. The chart does not declare the side-channel ports as container ports. If you use NetworkPolicy, allow the side-channel ports between the prefill and decode Pods.
The following values change the base port and the bind address of the side-channel.
| Key | Description | Default |
|---|---|---|
devices.rdma.sideChannel.basePort.{prefill,decode} | The base port per role | 5559 / 5659 |
devices.rdma.sideChannel.host | podIP uses the Pod network; an IPv4 address is used verbatim | podIP |
devices.rdma.sideChannel.host is the address the side-channel binds to. The NIXL RBLN backend picks the KV-transfer NIC regardless of this value, so changing it does not change the KV transfer path or bandwidth. Because the side-channel uses the Pod network, the handshake succeeds even while a RoCE NIC is down. Decode then reconnects over the remaining RoCE NIC (see Failure response › RoCE NIC faults).
Volumes¶
The stack mounts the following volumes into each modelserver Pod.
| Volume | Mount | Chart default size | Sharing scope | accessModes |
|---|---|---|---|---|
storage.model | /mnt/shared_data | unset | Every modelserver Pod reads the same volume | ReadWriteMany when the Pods span multiple nodes |
storage.compileCache | /workspace | 200Gi | One PVC per role, shared by that role's replicas | ReadWriteMany (chart default) |
storage.modelOffload | /tmp | Disabled by default (emptyDir with no size limit). 512Gi when enabled | Separate per Pod | ReadWriteOnce (fixed) when enabled with kind: pvc |
storage.dshm | /dev/shm | 32Gi | Separate per Pod (memory emptyDir) | N/A |
The reference configuration places Pods on multiple nodes. storage.model and storage.compileCache therefore need a StorageClass that supports ReadWriteMany (RWX). Set the two volumes as follows.
- Always set
storage.model. Where the weights live differs per cluster, so the chart has no default, and the render fails ifstorage.modelis not set. The reference configuration sets the250GiPVC created in Secrets and the model store withkind: existingClaim. Size it to the model's weights plus headroom. - The chart creates the PVC for
storage.compileCache. The chart default size is200Gi. IfstorageClassis left empty, the cluster's default StorageClass is used. Decide the size andaccessModesbefore the first install (see PVC shape, capacity, and accessModes).
The volume source is picked with kind alone. The allowed kind values differ per volume.
| Volume | pvc | existingClaim | emptyDir | hostPath | nfs |
|---|---|---|---|---|---|
storage.model | ○ | ○ | ○ | ○ | ○ |
storage.compileCache | ○ | ○ | ✕ | ○ | ○ |
storage.modelOffload | ○ | ✕ | ○ | ✕ | ✕ |
The chart does not allow emptyDir for compileCache. The modelserver workloads are Deployments, so there is no per-replica persistent volume, and with emptyDir each Pod restart recompiles.
storage.dshm has no kind, and setting kind fails the render. storage.dshm is always a memory-backed emptyDir. What is written to /dev/shm counts toward the modelserver memory limit. When you raise storage.dshm.size, raise the memory requests and limits in modelserver.resources by the same amount.
storage.modelOffload switch¶
storage.modelOffload is the /tmp volume where the engine offloads model weights during model loading and warm-up. It is a different volume from Rebellions Direct Storage (RDS), which stores the KV cache. The reference configuration does not use offloading (enabled: false).
To use offloading, set the following values. This configuration is Unverified.
The chart sets VLLM_RBLN_DISABLE_OFFLOAD or RBLN_OFFLOAD_DIR to match enabled. If you set either variable directly through serveEnv or extraServeEnv, the render fails.
Resources and the node budget¶
modelserver.resources defaults to 40 CPU cores and 630Gi of memory, with requests equal to limits. Prefill and decode use the same modelserver.resources value. The memory default is 630Gi because decode uses additional host memory when NIXL registers the KV buffers.
Before installing, check that the allocatable memory and CPU of each node satisfy the following formulas.
allocatable(memory) ≥ modelserver memory × (pods on that node) + routing sidecar 256Mi × (decode pods on that node) + EPP 8Gi + gateway ~0.2Gi
allocatable(CPU) ≥ modelserver CPU × (pods on that node) + routing sidecar 0.2 × (decode pods on that node) + EPP 4 + gateway 0.1
Memory: the term most easily missed is the EPP that the chart brings up on its own. Leaving the EPP out of the calculation can overshoot allocatable memory by a few GB and block scheduling. The pre-install memory check is in Node memory budget check.
CPU: for serving, the default of 40 cores is enough. The first compile, however, runs more threads than serving. If the CPU limit is too low, the cgroup delays the compile threads (throttling). The throttling writes nothing to the log and only slows down startup. If the compile runs past the startup budget (see Startup and readiness probes), the startup probe fails and the kubelet restarts the Pod.
On a node with cpuManagerPolicy: static, the modelserver receives dedicated CPUs and is not throttled. For the CPU value of each model, use the values file of that model's recipe (see Chart preparation and the default configuration).
The per-node hardware counts are on the Hardware page.
Node memory budget check¶
Before installing, query the allocatable memory of each node and compare the allocatable memory with the memory formula in Resources and the node budget.
Calculate the memory formula separately for each node that hosts modelserver Pods. In (pods on that node) and (decode pods on that node), put the number of Pods you place on that node. The cluster runs one EPP and one gateway, so include the EPP and gateway terms only when you calculate the node that hosts them.