|
| 1 | +# GKE GB200 (A4X) Networking Prerequisites |
| 2 | + |
| 3 | +For the **GB200 GKE COS** recipes (`gb200-gke-cos-training`, |
| 4 | +`gb200-gke-cos-training-kubeflow`, `gb200-gke-cos-training-slurm`, |
| 5 | +`gb200-gke-cos-inference`, and `gb200-gke-cos-inference-dynamo`, all on |
| 6 | +`a4x-highgpu-4g` nodes), |
| 7 | +GPUDirect-RDMA over RoCE enables high-speed inter-node GPU communication on |
| 8 | +GKE. The recipe's NCCL workloads set `NCCL_NET=gIB` explicitly (see |
| 9 | +`recipes/components/gke-gb200-rdma/manifests/nccl-gib-installer-arm64.yaml`) |
| 10 | +rather than letting NCCL auto-select a plugin, so a missing or |
| 11 | +misconfigured RDMA fabric doesn't silently fall back to a slower network |
| 12 | +path: it fails outright. |
| 13 | + |
| 14 | +GPUDirect RDMA on `a4x-highgpu-4g` is also incompatible with NCCL Fast |
| 15 | +Socket and the GPUDirect TCPX/TCPXO plugin (see |
| 16 | +[GKE TCPXO Networking](gke-tcpxo-networking.md) for that alternative, |
| 17 | +non-RDMA path); don't enable either on a cluster that uses RDMA. |
| 18 | + |
| 19 | +## Infrastructure Prerequisites |
| 20 | + |
| 21 | +GKE clusters must have multi-networking configured before deploying AICR bundles: |
| 22 | + |
| 23 | +- Multi-networking enabled (1 gVNIC + 4 RDMA NICs per `a4x-highgpu-4g` node) |
| 24 | +- `Network` + `GKENetworkParamSet` CRs for the gVNIC and 4 RDMA NICs (cluster-specific |
| 25 | + VPC/subnet values, but fixed object names; see below, not managed by AICR) |
| 26 | +- `nccl-rdma-installer` DaemonSet on GPU nodes (included in the AICR bundle) |
| 27 | +- Each GPUDirect-RDMA workload Pod must request all 4 GPUs and use all 4 RDMA NICs |
| 28 | + on a single node; RDMA can't be shared between Pods on the same node (a GKE |
| 29 | + `a4x-highgpu-4g` constraint, not an AICR-specific one). AICR's own recipes |
| 30 | + already request whole nodes this way; a custom workload built against this |
| 31 | + component must too. |
| 32 | + |
| 33 | +The `nccl-rdma-installer` DaemonSet ships in the AICR bundle. The `Network`/ |
| 34 | +`GKENetworkParamSet` CRs and the multi-networking/VPC fabric underneath them |
| 35 | +are **cluster provisioning**: AICR's `gke-gb200-rdma` health check detects |
| 36 | +them but does not create them. |
| 37 | + |
| 38 | +### Provisioning multi-networking |
| 39 | + |
| 40 | +These steps are ordered, following Google's |
| 41 | +[A4X custom setup guide](https://docs.cloud.google.com/ai-hypercomputer/docs/create/gke-ai-hypercompute-custom-a4x): |
| 42 | + |
| 43 | +1. **Create the VPCs and subnets**: two VPCs in the cluster's region, one for |
| 44 | + the gVNIC (with one subnet) and one RDMA VPC (with four subnets, one per |
| 45 | + RDMA NIC); five subnets total across the two VPCs, not five separate VPCs. |
| 46 | +2. **Create the cluster** with multi-networking enabled (HIPPO's `GKECluster` CR |
| 47 | + does this via `spec.networks.managed.gb200NetworkStrategy`). |
| 48 | +3. **Create the GPU node pool** on an `a4x-highgpu-4g` machine type, attaching |
| 49 | + the five network/subnet pairs as `additionalNodeNetworkConfigs` (the RDMA |
| 50 | + VPC repeated across its four subnets, plus the gVNIC VPC/subnet). |
| 51 | +4. **Apply the `Network` and `GKENetworkParamSet` CRs**: one pair per NIC, |
| 52 | + binding each additional node network into the cluster so pods can reference |
| 53 | + it. Unlike TCPXO (see [GKE TCPXO Networking](gke-tcpxo-networking.md)), the |
| 54 | + **object names are fixed, not cluster-specific**: `gvnic-1` for the gVNIC and |
| 55 | + `rdma-0` through `rdma-3` for the RDMA NICs. Only the `vpc`/`vpcSubnet` fields |
| 56 | + inside each `GKENetworkParamSet` vary per cluster (they name the VPC/subnet |
| 57 | + your cluster actually has): |
| 58 | + |
| 59 | +```yaml |
| 60 | +apiVersion: networking.gke.io/v1 |
| 61 | +kind: GKENetworkParamSet |
| 62 | +metadata: |
| 63 | + name: gvnic-1 |
| 64 | +spec: |
| 65 | + vpc: "PREFIX-gvnic" |
| 66 | + vpcSubnet: "PREFIX-gvnic" |
| 67 | + deviceMode: NetDevice |
| 68 | +--- |
| 69 | +apiVersion: networking.gke.io/v1 |
| 70 | +kind: Network |
| 71 | +metadata: |
| 72 | + name: gvnic-1 |
| 73 | +spec: |
| 74 | + type: "Device" |
| 75 | + parametersRef: |
| 76 | + group: networking.gke.io |
| 77 | + kind: GKENetworkParamSet |
| 78 | + name: gvnic-1 |
| 79 | +``` |
| 80 | +
|
| 81 | + Repeat for `rdma-0` through `rdma-3`, pointing `vpc` at the single RDMA VPC |
| 82 | + from step 1 (the same value for all four) and `vpcSubnet` at that VPC's |
| 83 | + four subnets (`PREFIX-rdma-sub-0` through `PREFIX-rdma-sub-3`, or whatever |
| 84 | + names your subnets were given in step 1, with `PREFIX` replaced by your |
| 85 | + own), and set **`deviceMode: RDMA`** on all four, not `NetDevice` (that |
| 86 | + value is only correct for `gvnic-1` above). |
| 87 | + |
| 88 | +> **The fixed naming is a requirement, not a convention.** AICR's |
| 89 | +> `checks/gke-gb200-rdma/health-check.yaml` asserts these five objects by exact |
| 90 | +> name (`gvnic-1`, `rdma-0`..`rdma-3`), including `spec.deviceMode` and |
| 91 | +> `spec.parametersRef` linkage. A cluster provisioned with different `Network` |
| 92 | +> names passes Google's own setup guide but fails this check; rename to match |
| 93 | +> before running `aicr validate`. |
| 94 | + |
| 95 | +AICR installs the `nccl-rdma-installer` DaemonSet and detects the CRs; it does |
| 96 | +not provision the networking itself. These steps are a summary of the |
| 97 | +prerequisite AICR depends on, not a complete provisioning runbook; follow |
| 98 | +Google's guide above for the full procedure, including firewall rules and |
| 99 | +supported GKE version floors. |
| 100 | + |
| 101 | +Separately from GKE's own networking version floor, all AICR GB200 GKE |
| 102 | +recipes (including `gb200-gke-cos-training-slurm`, which inherits it from |
| 103 | +`gb200-gke-cos-training`) enforce `K8s.server.version >= 1.34`: NVLS |
| 104 | +provisions the IMEX channel through a DRA `ComputeDomain`, which requires |
| 105 | +the GA `resource.k8s.io/v1` API. `aicr validate` fails readiness on an |
| 106 | +older control plane with this constraint by name. |
| 107 | + |
| 108 | +### Verifying |
| 109 | + |
| 110 | +```shell |
| 111 | +kubectl get network.networking.gke.io \ |
| 112 | + -o custom-columns='NAME:.metadata.name,PARAMETERS-REF:.spec.parametersRef.name' |
| 113 | +kubectl get gkenetworkparamset.networking.gke.io \ |
| 114 | + -o custom-columns='NAME:.metadata.name,DEVICE-MODE:.spec.deviceMode' |
| 115 | +``` |
| 116 | + |
| 117 | +Expect `gvnic-1` and `rdma-0` through `rdma-3` (the five prerequisite |
| 118 | +`Network`s from step 4), each bound to its `GKENetworkParamSet` via |
| 119 | +`spec.parametersRef` (shown in the `PARAMETERS-REF` column above). Fewer |
| 120 | +than five, or a `GKENetworkParamSet` with the wrong `DEVICE-MODE`, means |
| 121 | +the prerequisite is incomplete or misconfigured; `aicr validate` (via the |
| 122 | +`gke-gb200-rdma` health check) reports the shortfall by name. |
| 123 | + |
| 124 | +You'll also see a `default` network/`GKENetworkParamSet` pair in the same |
| 125 | +output; that one is GKE-managed (created automatically once |
| 126 | +multi-networking is enabled), not part of this prerequisite, and isn't |
| 127 | +checked by name. |
| 128 | + |
| 129 | +## Driver Installer |
| 130 | + |
| 131 | +`a4x-highgpu-4g` recipes generated with `--profile gpuStack=driver-installer` |
| 132 | +(see [GKE GPU Setup](gke-gpu-setup.md#alternative-let-gpu-operator-manage-the-device-plugin)) |
| 133 | +need Google's standalone `nvidia-driver-installer` DaemonSet applied before |
| 134 | +GPU workloads can schedule; this presumes the node-pool prerequisite |
| 135 | +(pools created with `gpu-driver-version=disabled` plus the |
| 136 | +`gke-no-default-nvidia-gpu-device-plugin=true` label) is already in place. |
| 137 | +The manifest below is Google's generic upstream COS driver-installer |
| 138 | +DaemonSet (`daemonset-preloaded.yaml`, including its `partition-gpus` |
| 139 | +init container, Google's `nvidia-partition-gpu` MIG tool, carried over |
| 140 | +unchanged and a no-op here since this recipe allocates whole GPUs per |
| 141 | +node rather than configuring MIG), adapted two ways for GB200: the |
| 142 | +`nodeAffinity` also requires the `gke-no-default-nvidia-gpu-device-plugin` |
| 143 | +label (the `driver-installer` profile's node-pool prerequisite, which the |
| 144 | +plain upstream manifest doesn't check), and the install step pins an |
| 145 | +explicit |
| 146 | +[COS-qualified driver version](https://cloud.google.com/kubernetes-engine/docs/how-to/gpus#cos) |
| 147 | +instead of letting `cos-gpu-installer` pick its own default: |
| 148 | + |
| 149 | +```yaml |
| 150 | +apiVersion: apps/v1 |
| 151 | +kind: DaemonSet |
| 152 | +metadata: |
| 153 | + name: nvidia-driver-installer |
| 154 | + namespace: kube-system |
| 155 | + labels: |
| 156 | + k8s-app: nvidia-driver-installer |
| 157 | +spec: |
| 158 | + selector: |
| 159 | + matchLabels: |
| 160 | + k8s-app: nvidia-driver-installer |
| 161 | + updateStrategy: |
| 162 | + type: RollingUpdate |
| 163 | + template: |
| 164 | + metadata: |
| 165 | + labels: |
| 166 | + name: nvidia-driver-installer |
| 167 | + k8s-app: nvidia-driver-installer |
| 168 | + spec: |
| 169 | + priorityClassName: system-node-critical |
| 170 | + affinity: |
| 171 | + nodeAffinity: |
| 172 | + requiredDuringSchedulingIgnoredDuringExecution: |
| 173 | + nodeSelectorTerms: |
| 174 | + - matchExpressions: |
| 175 | + - key: cloud.google.com/gke-accelerator |
| 176 | + operator: Exists |
| 177 | + - key: cloud.google.com/gke-gpu-driver-version |
| 178 | + operator: DoesNotExist |
| 179 | + - key: gke-no-default-nvidia-gpu-device-plugin |
| 180 | + operator: In |
| 181 | + values: ["true"] |
| 182 | + - key: cloud.google.com/gke-confidential-nodes-instance-type |
| 183 | + operator: DoesNotExist |
| 184 | + tolerations: |
| 185 | + - operator: Exists |
| 186 | + hostNetwork: true |
| 187 | + hostPID: true |
| 188 | + volumes: |
| 189 | + - name: dev |
| 190 | + hostPath: |
| 191 | + path: /dev |
| 192 | + - name: vulkan-icd-mount |
| 193 | + hostPath: |
| 194 | + path: /home/kubernetes/bin/nvidia/vulkan/icd.d |
| 195 | + - name: nvidia-install-dir-host |
| 196 | + hostPath: |
| 197 | + path: /home/kubernetes/bin/nvidia |
| 198 | + - name: root-mount |
| 199 | + hostPath: |
| 200 | + path: / |
| 201 | + - name: cos-tools |
| 202 | + hostPath: |
| 203 | + path: /var/lib/cos-tools |
| 204 | + - name: nvidia-config |
| 205 | + hostPath: |
| 206 | + path: /etc/nvidia |
| 207 | + initContainers: |
| 208 | + - image: "cos-nvidia-installer:fixed" |
| 209 | + imagePullPolicy: Never |
| 210 | + name: nvidia-driver-installer |
| 211 | + resources: |
| 212 | + requests: |
| 213 | + cpu: 150m |
| 214 | + securityContext: |
| 215 | + privileged: true |
| 216 | + env: |
| 217 | + - name: NVIDIA_INSTALL_DIR_HOST |
| 218 | + value: /home/kubernetes/bin/nvidia |
| 219 | + - name: NVIDIA_INSTALL_DIR_CONTAINER |
| 220 | + value: /usr/local/nvidia |
| 221 | + - name: VULKAN_ICD_DIR_HOST |
| 222 | + value: /home/kubernetes/bin/nvidia/vulkan/icd.d |
| 223 | + - name: VULKAN_ICD_DIR_CONTAINER |
| 224 | + value: /etc/vulkan/icd.d |
| 225 | + - name: ROOT_MOUNT_DIR |
| 226 | + value: /root |
| 227 | + - name: COS_TOOLS_DIR_HOST |
| 228 | + value: /var/lib/cos-tools |
| 229 | + - name: COS_TOOLS_DIR_CONTAINER |
| 230 | + value: /build/cos-tools |
| 231 | + volumeMounts: |
| 232 | + - name: nvidia-install-dir-host |
| 233 | + mountPath: /usr/local/nvidia |
| 234 | + - name: vulkan-icd-mount |
| 235 | + mountPath: /etc/vulkan/icd.d |
| 236 | + - name: dev |
| 237 | + mountPath: /dev |
| 238 | + - name: root-mount |
| 239 | + mountPath: /root |
| 240 | + - name: cos-tools |
| 241 | + mountPath: /build/cos-tools |
| 242 | + command: |
| 243 | + - bash |
| 244 | + - -c |
| 245 | + - | |
| 246 | + echo "Checking for existing GPU driver modules" |
| 247 | + if lsmod | grep nvidia; then |
| 248 | + echo "GPU driver is already installed, skipping installation" |
| 249 | + exit 0 |
| 250 | + else |
| 251 | + echo "No GPU driver module detected, installing 580.126.20" |
| 252 | + /cos-gpu-installer install --version=580.126.20 || exit 1 |
| 253 | + chmod 755 /root/home/kubernetes/bin/nvidia |
| 254 | + fi |
| 255 | + - image: "gcr.io/gke-release/nvidia-partition-gpu@sha256:de12f85ebfb4fb6c1893cd30c23aab662a72fa0448f97ef74fccb82d7522ef17" |
| 256 | + name: partition-gpus |
| 257 | + env: |
| 258 | + - name: LD_LIBRARY_PATH |
| 259 | + value: /usr/local/nvidia/lib64 |
| 260 | + resources: |
| 261 | + requests: |
| 262 | + cpu: 150m |
| 263 | + securityContext: |
| 264 | + privileged: true |
| 265 | + volumeMounts: |
| 266 | + - name: nvidia-install-dir-host |
| 267 | + mountPath: /usr/local/nvidia |
| 268 | + - name: dev |
| 269 | + mountPath: /dev |
| 270 | + - name: nvidia-config |
| 271 | + mountPath: /etc/nvidia |
| 272 | + containers: |
| 273 | + - image: "gke.gcr.io/pause:3.8@sha256:880e63f94b145e46f1b1082bb71b85e21f16b99b180b9996407d61240ceb9830" |
| 274 | + name: pause |
| 275 | +``` |
| 276 | + |
| 277 | +Re-pin the driver version (`580.126.20` above) and the `partition-gpus` image |
| 278 | +digest to whatever your GKE version's COS driver table and Google's release |
| 279 | +notes currently list; both drift over time and are not managed by AICR. |
| 280 | + |
| 281 | +### Validate before deploying the rest of the bundle |
| 282 | + |
| 283 | +Once the driver installer and the RDMA `Network`/`GKENetworkParamSet` CRs |
| 284 | +are applied, confirm both before running the bundle's full `deploy.sh`: |
| 285 | + |
| 286 | +```shell |
| 287 | +aicr validate --recipe recipe.yaml --phase deployment --fail-fast |
| 288 | +``` |
| 289 | + |
| 290 | +`check-nvidia-smi` and the `gke-gb200-rdma` health check only need the GPU |
| 291 | +nodes to exist, not the rest of the bundle deployed, so this catches a |
| 292 | +missing driver or un-applied CRs in seconds instead of surfacing them deep |
| 293 | +into a 20-component deploy, for example as a DRA-driver pod stuck |
| 294 | +`Init:0/1` waiting on a driver that was never installed. `--fail-fast` |
| 295 | +stops there instead of continuing on to conformance and performance (see |
| 296 | +[Validation](../user/validation.md)). |
| 297 | + |
| 298 | +## Storage Prerequisites |
| 299 | + |
| 300 | +`a4x-highgpu-4g` nodes can't attach Persistent Disk at all (regional or |
| 301 | +zonal, any type, including `pd-balanced`); only Hyperdisk. On a stock GKE |
| 302 | +Standard cluster the default StorageClass is `standard-rwo` |
| 303 | +(`pd.csi.storage.gke.io`, `pd-balanced`), but "default" isn't inherent to |
| 304 | +GKE Standard itself: a cluster admin can repoint the |
| 305 | +`storageclass.kubernetes.io/is-default-class` annotation to any |
| 306 | +StorageClass. Run `kubectl get storageclass` first and check which one is |
| 307 | +annotated `(default)`, its `PROVISIONER`, and (via `kubectl get |
| 308 | +storageclass -o yaml`) its `parameters.type`; don't assume it's |
| 309 | +`standard-rwo`/`pd-balanced`. Any PVC scheduled onto a GB200 node with no |
| 310 | +`storageClassName` set (which binds it to the cluster default) fails |
| 311 | +this way unless that default's `parameters.type` is already |
| 312 | +Hyperdisk-backed: `pd-balanced disk type cannot be used by |
| 313 | +a4x-highgpu-4g machine type` (or the equivalent for whatever `pd-*` type |
| 314 | +the default actually provisions). |
| 315 | + |
| 316 | +This includes the `inference-perf` validator's model-weights cache PVC |
| 317 | +when `AICR_INFERENCE_PERF_MODEL_CACHE_STORAGE_CLASS` (see |
| 318 | +[Validation](../user/validation.md)) is left unset, it then falls back |
| 319 | +to the cluster default too. Set that variable to name a Hyperdisk-backed |
| 320 | +StorageClass explicitly (for example `hyperdisk-balanced`, applied below) |
| 321 | +and the cache PVC uses it directly via `storageClassName`, independent of |
| 322 | +whatever the cluster default resolves to. |
| 323 | + |
| 324 | +If the cluster default isn't already Hyperdisk-backed, apply one. Like |
| 325 | +the RDMA CRs above, this is a cluster prerequisite AICR does not |
| 326 | +provision: |
| 327 | + |
| 328 | +```yaml |
| 329 | +apiVersion: storage.k8s.io/v1 |
| 330 | +kind: StorageClass |
| 331 | +metadata: |
| 332 | + name: hyperdisk-balanced |
| 333 | +provisioner: pd.csi.storage.gke.io |
| 334 | +parameters: |
| 335 | + type: hyperdisk-balanced |
| 336 | +volumeBindingMode: WaitForFirstConsumer |
| 337 | +allowVolumeExpansion: true |
| 338 | +``` |
| 339 | + |
| 340 | +Apply it once per cluster, then point the validator's model cache at it via |
| 341 | +an `AICR_INFERENCE_PERF_MODEL_CACHE_STORAGE_CLASS=hyperdisk-balanced` entry |
| 342 | +on the `inference-perf` catalog entry's `env` (or a catalog overlay in the |
| 343 | +`aicr validate --data <dir>` directory). |
| 344 | + |
| 345 | +## Running the NCCL Benchmark |
| 346 | + |
| 347 | +The GB200 GKE training recipe (`gb200-gke-cos-training`) selects the |
| 348 | +NVLS-variant performance check (`nccl-all-reduce-bw-nvls`): MNNVL across the |
| 349 | +A4X nodes' IMEX domain is the fabric that carries all-reduce traffic; gIB is the |
| 350 | +transport driver underneath, not the NCCL algorithm itself. Run it via: |
| 351 | + |
| 352 | +```shell |
| 353 | +aicr validate --recipe recipes/overlays/gb200-gke-cos-training.yaml \ |
| 354 | + --phase performance |
| 355 | +``` |
| 356 | + |
| 357 | +## References |
| 358 | + |
| 359 | +- [GKE A4X custom setup guide](https://docs.cloud.google.com/ai-hypercomputer/docs/create/gke-ai-hypercompute-custom-a4x) |
| 360 | +- [Component Catalog](../user/component-catalog.md) |
| 361 | +- [Validation readiness gate](../user/validation.md) |
| 362 | +- [GKE TCPXO Networking](gke-tcpxo-networking.md) |
0 commit comments