Skip to content

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.

1
2
3
helm upgrade --install <release-name> ./coding-assistant-stack -n <ns> \
  -f ./coding-assistant-stack/examples/root-privileged.yaml \
  <values to fill in>

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 if storage.model is not set. The reference configuration sets the 250Gi PVC created in Secrets and the model store with kind: existingClaim. Size it to the model's weights plus headroom.
  • The chart creates the PVC for storage.compileCache. The chart default size is 200Gi. If storageClass is left empty, the cluster's default StorageClass is used. Decide the size and accessModes before 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.

1
2
3
4
5
storage:
  modelOffload:
    enabled: true
    storageClass: <sc-name>   # Required with kind: pvc. Use a StorageClass with reclaimPolicy: Delete.
    size: 512Gi

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.

kubectl get nodes -o json | jq -r '.items[]
  | "\(.metadata.name)\t\(.status.allocatable.memory)"'

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.