日期:2026-06-10
平台:NVIDIA GB200 (A4X) · ARM64 · NVL72 · TLinux 4
Kubernetes:1.34.1 (kubeadm) + Calico v3.29.3
GPU 管理:nvidia-device-plugin v0.17.1 + ComputeDomain (DRA GPU Driver v25.12.0)
RDMA 访问:路径 A — DRANET v1.3.0(SIG DRA 网络驱动)/ 路径 B — hostNetwork
DRA:k8s 1.34 DRA GA · API resource.k8s.io/v1
容器:NGC pytorch:25.04-py3
网络:GIB v1.1.2 (NCCL 2.30.4+cuda13.0) · 4×CX-7 400Gbps MRDMA
hostNetwork: true 直接访问主机网络栈(含 RDMA),sshd 使用端口 2222(避免与主机 sshd 冲突)。nvidia.com/gpu: 4(nvidia-device-plugin 方式)。
numNodes 设置更小。在部分域环境(<18 节点)中,IMEX daemon 会尝试连接不存在的节点(无害日志噪音),但不影响已连接节点间的正常通信(已验证 836 GB/s MNNVL)。
# ===== GCP 项目与区域 =====
PROJECT="gpu-launchpad-playground"
ZONE="us-east1-d"
RESERVATION="nvidia-gb200-z4pzosg110ik8"
# ===== 机器与镜像 =====
MACHINE_TYPE="a4x-highgpu-4g"
IMAGE="tlinux-server-4-gb200-v1"
IMAGE_PROJECT="gpu-launchpad-playground"
# ===== 网络 =====
GVNIC_NET="gke-hzchen-a4x-poc-gvnic-net"
GVNIC_SUB="gke-hzchen-a4x-poc-gvnic-sub"
GVNIC_NET_1="gke-hzchen-a4x-poc-gvnic-net-1"
GVNIC_SUB_1="gke-hzchen-a4x-poc-gvnic-sub-1"
RDMA_NET="forrest-rdma-net-us-east1-d"
RDMA_SUB_0="forrest-rdma-sub-us-east1-d-0"
RDMA_SUB_1="forrest-rdma-sub-us-east1-d-1"
RDMA_SUB_2="forrest-rdma-sub-us-east1-d-2"
RDMA_SUB_3="forrest-rdma-sub-us-east1-d-3"
# ===== 集群命名 =====
CP_NAME="k8s134-cp"
SD_WORKERS=("k8s134-sd-w0" "k8s134-sd-w1") # 同域 Worker
CD_WORKERS=("k8s134-cd-w0" "k8s134-cd-w1") # 跨域 Worker
SD_PLACEMENT_POLICY="forrest-a4x-1x72-policy" # 同域 Placement Policy
CD_PLACEMENT_POLICY="a4x-nvl72-policy" # 跨域 Placement Policy
# ===== Kubernetes =====
K8S_VERSION="1.34"
POD_CIDR="10.244.0.0/16"
# ===== 组件版本 =====
GIB_VERSION="v1.1.2"
DEVICE_PLUGIN_VERSION="v0.17.1"
DRANET_VERSION="v1.3.0"
CALICO_VERSION="v3.29.3"
PYTORCH_IMAGE="nvcr.io/nvidia/pytorch:25.04-py3"
GIB_IMAGE="us-docker.pkg.dev/gce-ai-infra/gpudirect-gib/nccl-plugin-gib-diagnostic-arm64:${GIB_VERSION}"
# ===== 共享存储 =====
GCSFUSE_BUCKET="hzchen-megatron-shared"
gpu-launchpad-playground 已有 GB200 A4X DENSE/CALENDAR 预留gke-hzchen-a4x-poc-rdma-network-profilegcloud CLI 并认证gcloud compute reservations describe $RESERVATION \
--zone=$ZONE --project=$PROJECT \
--format="table(specificReservation.count, specificReservation.inUseCount)"
# 创建 collocated placement policy,确保同域节点在同一 NVL72 Switch Domain
gcloud alpha compute resource-policies create group-placement $SD_PLACEMENT_POLICY \
--collocation=COLLOCATED \
--gpu-topology=1x72 \
--project=$PROJECT --region=us-east1
# SSH 防火墙(IAP 源 IP 范围)
gcloud compute firewall-rules create allow-ssh-iap-k8s134 \
--network=$GVNIC_NET \
--allow=tcp:22 \
--source-ranges=35.235.240.0/20 \
--project=$PROJECT
# 集群内部通信
gcloud compute firewall-rules create allow-internal-k8s134 \
--network=$GVNIC_NET \
--allow=tcp,udp,icmp \
--source-ranges=192.168.0.0/16,10.244.0.0/16 \
--project=$PROJECT
resource.k8s.io/v1(非 v1beta2)。
CP 节点使用 x86_64 轻量 VM(无需 GPU),仅连接主 GVNIC 网络。
gcloud compute instances create $CP_NAME \
--project=$PROJECT --zone=$ZONE \
--machine-type=e2-standard-4 \
--image-family=rocky-linux-9 --image-project=rocky-linux-cloud \
--boot-disk-size=100GB \
--network-interface=network=$GVNIC_NET,subnet=$GVNIC_SUB \
--metadata-from-file=startup-script=scripts/kubeadm-control-plane-k8s134.sh \
--scopes=cloud-platform
kubeadm init --pod-network-cidr=10.244.0.0/16# SSH 到 CP 节点
gcloud compute ssh $CP_NAME --zone=$ZONE --project=$PROJECT --tunnel-through-iap
# 查看 join 命令
cat /root/kubeadm-join-command.txt
# 提取 token 和 hash
CP_IP=$(hostname -I | awk '{print $1}')
JOIN_TOKEN=$(kubeadm token list -o jsonpath='{.token}' | head -1)
JOIN_HASH=$(openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \
openssl rsa -pubin -outform der 2>/dev/null | sha256sum | awk '{print $1}')
echo "CP_IP=$CP_IP JOIN_TOKEN=$JOIN_TOKEN JOIN_HASH=$JOIN_HASH"
--provisioning-model=RESERVATION_BOUND — DENSE/CALENDAR 预留要求gcloud compute(不是 alpha/beta),不加 --local-ssd(A4X 自动挂载 12TB NVMe)no-address on MRDMA — 网络 profile 不允许 MRDMA 接口有 AccessConfig--resource-policies — 同域/跨域节点使用不同 placement policyfor i in 0 1; do
gcloud compute instances create k8s134-sd-w${i} \
--project=$PROJECT --zone=$ZONE \
--machine-type=$MACHINE_TYPE \
--image=$IMAGE --image-project=$IMAGE_PROJECT \
--boot-disk-size=200GB --boot-disk-type=hyperdisk-balanced \
--scopes=cloud-platform \
--reservation-affinity=specific --reservation=$RESERVATION \
--provisioning-model=RESERVATION_BOUND \
--instance-termination-action=STOP \
--maintenance-policy=TERMINATE \
--restart-on-failure \
--resource-policies=$SD_PLACEMENT_POLICY \
--network-interface=nic-type=GVNIC,network=$GVNIC_NET,subnet=$GVNIC_SUB \
--network-interface=nic-type=GVNIC,network=$GVNIC_NET_1,subnet=$GVNIC_SUB_1,no-address \
--network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_0,no-address \
--network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_1,no-address \
--network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_2,no-address \
--network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_3,no-address \
--metadata=cp-ip=$CP_IP,join-token=$JOIN_TOKEN,join-hash=$JOIN_HASH \
--metadata-from-file=startup-script=scripts/tlinux4-k8s134-worker.sh &
done
wait
for i in 0 1; do
gcloud compute instances create k8s134-cd-w${i} \
--project=$PROJECT --zone=$ZONE \
--machine-type=$MACHINE_TYPE \
--image=$IMAGE --image-project=$IMAGE_PROJECT \
--boot-disk-size=200GB --boot-disk-type=hyperdisk-balanced \
--scopes=cloud-platform \
--reservation-affinity=specific --reservation=$RESERVATION \
--provisioning-model=RESERVATION_BOUND \
--instance-termination-action=STOP \
--maintenance-policy=TERMINATE \
--restart-on-failure \
--resource-policies=$CD_PLACEMENT_POLICY \
--network-interface=nic-type=GVNIC,network=$GVNIC_NET,subnet=$GVNIC_SUB \
--network-interface=nic-type=GVNIC,network=$GVNIC_NET_1,subnet=$GVNIC_SUB_1,no-address \
--network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_0,no-address \
--network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_1,no-address \
--network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_2,no-address \
--network-interface=nic-type=MRDMA,network=$RDMA_NET,subnet=$RDMA_SUB_3,no-address \
--metadata=cp-ip=$CP_IP,join-token=$JOIN_TOKEN,join-hash=$JOIN_HASH \
--metadata-from-file=startup-script=scripts/tlinux4-k8s134-worker.sh &
done
wait
TLinux 4 Worker 使用 2 阶段设置脚本 tlinux4-k8s134-worker.sh:
NVreg_CreateImexChannel0=1) + dracut --force + 自动重启kubeadm join# SSH 到每个 Worker,运行设置脚本
for WORKER in k8s134-sd-w0 k8s134-sd-w1 k8s134-cd-w0 k8s134-cd-w1; do
gcloud compute ssh $WORKER --zone=$ZONE --project=$PROJECT --tunnel-through-iap \
--command="sudo bash /root/scripts/tlinux4-k8s134-worker.sh"
done
# Phase 1 完成后节点自动重启,重启后 Phase 2 自动运行并 join 集群
# 等待 ~5 分钟让所有 Worker 完成 Phase 2
# 在 CP 上验证
kubectl get nodes -o wide
# 应看到 5 个节点: 1 CP + 4 Worker (全部 Ready)
baseurl=https://download.docker.com/linux/rhel/9/$(uname -m)/stable(TLinux 4 基于 RHEL 9)nvidia.github.io/libnvidia-container/stable/rpm repofindmnt 动态查找# 标记同域/跨域 Worker
kubectl label node k8s134-sd-w0 topology=same-domain
kubectl label node k8s134-sd-w1 topology=same-domain
kubectl label node k8s134-cd-w0 topology=cross-domain
kubectl label node k8s134-cd-w1 topology=cross-domain
# 验证
kubectl get nodes -L topology
resource.k8s.io/v1。
DRA GPU Driver 提供 ComputeDomain CRD 和控制器,负责 IMEX daemon 的生命周期管理。安装后控制器会自动通过 ResourceSlice 发现 NVLink 拓扑,为每个物理 NVL72 域创建 ComputeDomainClique。
nvidia-imex.service 已停止且 disabled。
# 安装 Helm(如尚未安装)
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# 安装 DRA GPU Driver(包含 ComputeDomain 控制器)
helm repo add nvidia-dra-driver-gpu https://nvidia.github.io/k8s-dra-driver-gpu/
helm repo update
helm install nvidia-dra-driver-gpu nvidia-dra-driver-gpu/nvidia-dra-driver-gpu \
--namespace nvidia-dra-driver-gpu \
--create-namespace \
--wait --timeout 300s
# 验证 DRA GPU Driver Pod(每个 GPU 节点一个)
kubectl -n nvidia-dra-driver-gpu get pods -o wide
# 验证 ResourceSlice(应包含 GPU 和 NVLink 拓扑信息)
kubectl get resourceslice -o wide | head -20
# 验证 NVLink 域标签(同域节点应有相同 ClusterUUID)
kubectl get nodes -L nvidia.com/gpu.clusteruuid
gpu.clusteruuid。
使用 nvidia-device-plugin DaemonSet 将 GPU 作为 nvidia.com/gpu 资源暴露给 k8s。ComputeDomain 管理 IMEX,device-plugin 管理 GPU 分配,两者协同工作。
# 安装 nvidia-device-plugin
helm repo add nvdp https://nvidia.github.io/k8s-device-plugin
helm repo update
helm install nvidia-device-plugin nvdp/nvidia-device-plugin \
--namespace kube-system \
--set-string nodeSelector."nvidia\.com/gpu\.present"=true \
--wait --timeout 300s
# 验证 GPU 可见
kubectl get nodes -o custom-columns="NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu"
4 个 GPU。
DRANET v1.3.0 是 Kubernetes SIG 的 DRA 网络驱动,用于将 RDMA NIC 作为 DRA 设备分配给 Pod。
# 安装 DRANET v1.3.0 via Helm
helm install dranet oci://registry.k8s.io/networking/charts/dranet \
--version $DRANET_VERSION \
--namespace dranet-system \
--create-namespace \
--wait --timeout 300s
# 验证 DRANET DaemonSet
kubectl -n dranet-system get pods -o wide
# 验证 ResourceSlice(每个节点应发现 RDMA 设备)
kubectl get resourceslice -o wide | head -20
不需要 DRANET。Pod 直接通过 hostNetwork: true 访问主机 RDMA 设备。跳过此步。
kubectl apply -f yamls/k8s134-rdma-deviceclass.yaml
DeviceClass 内容(注意 API 为 resource.k8s.io/v1):
# yamls/k8s134-rdma-deviceclass.yaml
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
name: rdma-devices
spec:
selectors:
- cel:
expression: |
device.driver == "dra.net" &&
has(device.attributes["dra.net"].rdma) && device.attributes["dra.net"].rdma == true
# DeviceClass
kubectl get deviceclass
# 应有: rdma-devices
# ResourceSlice(DRANET 发现的 RDMA 设备)
kubectl get resourceslice -o wide | head -20
自建 k8s 无法使用 GKE 的 CSI sidecar 注入机制,需在每个 Worker 上直接安装 GCSFuse 并挂载,Pod 通过 hostPath 访问。
# startup script 自动执行:
dnf install -y gcsfuse
mkdir -p /data/shared
gcsfuse --implicit-dirs $GCSFUSE_BUCKET /data/shared
对于大模型训练场景(checkpoint 写入、数据集读取),建议启用以下优化:
gcsfuse \
--implicit-dirs \
--enable-streaming-writes \
--parallel-downloads-per-file=16 \
--file-cache-max-size-mb=10240 \
--file-cache-cache-file-for-range-read \
--file-cache-dir=/tmp/gcsfuse-cache \
$GCSFUSE_BUCKET /data/shared
| 参数 | 作用 | 适用场景 |
|---|---|---|
--enable-streaming-writes | 写入时直接流式上传到 GCS,避免先写本地临时文件再上传 | Checkpoint 写入(GB 级文件) |
--parallel-downloads-per-file=16 | 单文件并行下载 16 个分片 | 大文件读取(数据集加载) |
--file-cache-max-size-mb=10240 | 本地文件缓存最大 10GB | 反复读取的数据集 |
--file-cache-cache-file-for-range-read | 对 range read 也缓存整个文件 | 随机访问模式的数据集 |
volumes:
- name: data
hostPath:
path: /data
如果客户提供 Lustre 文件系统,使用 PV/PVC 方式挂载。详见 yamls/lustre-pv-pvc.yaml。
# 部署单节点测试 Pod(两种路径相同)
kubectl apply -f yamls/k8s134-nccl-single-node.yaml
# 等待完成
kubectl logs nccl-single-node -f
| 指标 | 预期基线 | 实测结果 | 基线来源 |
|---|---|---|---|
| all_reduce 4 GPU @8G | ~604 GB/s | 667 GB/s | k8s 1.22 + TLinux 4 验证数据 |
| all_gather 4 GPU @4G | ~604 GB/s | 639 GB/s | k8s 1.22 + TLinux 4 验证数据 |
基线数据源自 k8s 1.22 + TLinux 4 + GB200 A4X 环境的验证结果,k8s 版本对 NCCL 带宽无影响。
同域测试需要 IMEX channel 就绪。部署 ComputeDomain 后,控制器自动在同域节点上启动 IMEX daemon,启用 MNNVL (Multi-Node NVLink)。
# 创建 ComputeDomain(同域 2 节点测试)
kubectl apply -f yamls/k8s134-nccl-computedomain-test.yaml
# 验证 ComputeDomain 状态
kubectl get computedomain -o wide
# 应看到 READY 状态
# 验证 ComputeDomain daemon Pod
kubectl get pods -l app.kubernetes.io/managed-by=compute-domain-controller -o wide
kubectl apply -f yamls/k8s134-nccl-same-domain-dranet.yaml
kubectl apply -f yamls/k8s134-nccl-same-domain-hostnet.yaml
# 等待 Pod 就绪
kubectl get pods -l name -w
# 验证 ComputeDomain 状态
kubectl get computedomain -o wide
# 交换 SSH 密钥(必须使用 ed25519,容器内无 /dev/tty 无法用 RSA passphrase)
HOST1_KEY=$(kubectl exec nccl-sd-host-1 -- cat /root/.ssh/id_ed25519.pub)
HOST2_KEY=$(kubectl exec nccl-sd-host-2 -- cat /root/.ssh/id_ed25519.pub)
kubectl exec nccl-sd-host-1 -- bash -c "echo '$HOST2_KEY' >> /root/.ssh/authorized_keys"
kubectl exec nccl-sd-host-2 -- bash -c "echo '$HOST1_KEY' >> /root/.ssh/authorized_keys"
HOST1_IP=$(kubectl get pod nccl-sd-host-1 -o jsonpath='{.status.podIP}')
HOST2_IP=$(kubectl get pod nccl-sd-host-2 -o jsonpath='{.status.podIP}')
kubectl exec nccl-sd-host-1 -- bash -c "
source /usr/local/gib/scripts/set_nccl_env.sh 2>/dev/null
export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:\$LD_LIBRARY_PATH
/usr/local/mpi/bin/mpirun --allow-run-as-root \
-np 8 -npernode 4 \
--host ${HOST1_IP}:4,${HOST2_IP}:4 \
-x LD_LIBRARY_PATH -x NCCL_MNNVL_ENABLE=2 -x NCCL_CUMEM_ENABLE=1 \
--mca plm_rsh_args '-p 2222 -o BatchMode=yes -o StrictHostKeyChecking=no' \
/tmp/all_reduce_perf_mpi -b 512M -e 8G -f 2 -g 1
"
# hostNetwork Pod 使用主机 IP
HOST1_IP=$(kubectl get pod nccl-sd-host-1 -o jsonpath='{.status.hostIP}')
HOST2_IP=$(kubectl get pod nccl-sd-host-2 -o jsonpath='{.status.hostIP}')
kubectl exec nccl-sd-host-1 -- bash -c "
source /usr/local/gib/scripts/set_nccl_env.sh 2>/dev/null
export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:\$LD_LIBRARY_PATH
/usr/local/mpi/bin/mpirun --allow-run-as-root \
-np 8 -npernode 4 \
--host ${HOST1_IP}:4,${HOST2_IP}:4 \
-x LD_LIBRARY_PATH -x NCCL_MNNVL_ENABLE=2 -x NCCL_CUMEM_ENABLE=1 \
--mca plm_rsh_args '-p 2222 -o BatchMode=yes -o StrictHostKeyChecking=no' \
/tmp/all_reduce_perf_mpi -b 512M -e 8G -f 2 -g 1
"
ed25519 密钥(ssh-keygen -t ed25519),RSA 密钥在容器内会因无 /dev/tty 导致 passphrase 提示失败-o BatchMode=yes 避免交互式提示/usr/local/mpi/bin/mpirun(非 /usr/local/gib/bin/mpirun)/usr/local/bin/all_reduce_perf 未链接 MPI,多节点测试会退化为独立单 GPU 基准,必须从源码编译 MPI 版:# 在任意 Pod 内编译 MPI 版 nccl-tests
cd /tmp && git clone --depth 1 https://github.com/NVIDIA/nccl-tests.git
cd nccl-tests && make -j8 MPI=1 \
MPI_HOME=/usr/local/mpi \
NCCL_HOME=/usr/local/gib \
CUDA_HOME=/usr/local/cuda
# 验证 MPI 链接
ldd build/all_reduce_perf | grep libmpi # 应输出 libmpi.so.40
# scp 到远程节点
scp -P 2222 build/all_reduce_perf ${REMOTE_IP}:/tmp/all_reduce_perf_mpi
| 指标 | 预期基线 | 实测结果 |
|---|---|---|
| all_reduce 8 GPU @8G (MNNVL) | ~836 GB/s busbw | 836 GB/s busbw (algbw 478 GB/s) |
跨域节点无 MNNVL,使用纯 RDMA (GPUDirect-TCPX/GIB) 通信。
kubectl apply -f yamls/k8s134-nccl-cross-domain-dranet.yaml
kubectl apply -f yamls/k8s134-nccl-cross-domain-hostnet.yaml
# 等待 Pod 就绪 + 交换 SSH 密钥(同 5.2,使用 ed25519)
# 获取 IP:路径 A 用 podIP,路径 B 用 hostIP
HOST1_IP=$(kubectl get pod nccl-cd-host-1 -o jsonpath='{.status.podIP}') # 路径 A
HOST2_IP=$(kubectl get pod nccl-cd-host-2 -o jsonpath='{.status.podIP}') # 路径 A
# HOST1_IP=$(kubectl get pod nccl-cd-host-1 -o jsonpath='{.status.hostIP}') # 路径 B
# HOST2_IP=$(kubectl get pod nccl-cd-host-2 -o jsonpath='{.status.hostIP}') # 路径 B
kubectl exec nccl-cd-host-1 -- bash -c "
source /usr/local/gib/scripts/set_nccl_env.sh 2>/dev/null
export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:\$LD_LIBRARY_PATH
/usr/local/mpi/bin/mpirun --allow-run-as-root \
-np 8 -npernode 4 \
--host ${HOST1_IP}:4,${HOST2_IP}:4 \
-x LD_LIBRARY_PATH -x NCCL_MNNVL_ENABLE=0 -x NCCL_CUMEM_ENABLE=1 \
--mca plm_rsh_args '-p 2222 -o BatchMode=yes -o StrictHostKeyChecking=no' \
/tmp/all_reduce_perf_mpi -b 512M -e 8G -f 2 -g 1
"
| 指标 | 预期基线 | 实测结果 |
|---|---|---|
| all_reduce 8 GPU @8G (RDMA) | ~327 GB/s busbw | 328 GB/s busbw (algbw 187 GB/s) |
kubectl apply -f yamls/k8s134-nccl-4node-mixed-dranet.yaml
kubectl apply -f yamls/k8s134-nccl-4node-mixed-hostnet.yaml
# 等待所有 4 个 Pod 就绪 + 交换 SSH 密钥(4 个 Pod 间两两交换 ed25519 公钥)
# 获取 IP(路径 A 用 podIP,路径 B 用 hostIP)
HOST1_IP=$(kubectl get pod nccl-mix-host-1 -o jsonpath='{.status.podIP}')
HOST2_IP=$(kubectl get pod nccl-mix-host-2 -o jsonpath='{.status.podIP}')
HOST3_IP=$(kubectl get pod nccl-mix-host-3 -o jsonpath='{.status.podIP}')
HOST4_IP=$(kubectl get pod nccl-mix-host-4 -o jsonpath='{.status.podIP}')
kubectl exec nccl-mix-host-1 -- bash -c "
source /usr/local/gib/scripts/set_nccl_env.sh 2>/dev/null
export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:\$LD_LIBRARY_PATH
/usr/local/mpi/bin/mpirun --allow-run-as-root \
-np 16 -npernode 4 \
--host ${HOST1_IP}:4,${HOST2_IP}:4,${HOST3_IP}:4,${HOST4_IP}:4 \
-x LD_LIBRARY_PATH -x NCCL_MNNVL_ENABLE=2 -x NCCL_CUMEM_ENABLE=1 \
--mca plm_rsh_args '-p 2222 -o BatchMode=yes -o StrictHostKeyChecking=no' \
/usr/local/bin/all_reduce_perf -b 1M -e 8G -f 2 -g 1
"
| 指标 | 预期基线 |
|---|---|
| all_reduce 16 GPU @8G (混合) | ~320 GB/s |
在同域 NCCL 测试 Pod 中运行 RDMA 带宽测试。
# 安装 perftest
kubectl exec nccl-sd-host-1 -- bash -c "apt-get update -qq && apt-get install -y -qq perftest"
kubectl exec nccl-sd-host-2 -- bash -c "apt-get update -qq && apt-get install -y -qq perftest"
# 测试时需切换 LD_LIBRARY_PATH 避免 GIB libibverbs 覆盖系统版本
# 在 host-2 上启动 server(每个 RDMA NIC 一个端口)
for port in 18515 18516 18517 18518; do
kubectl exec nccl-sd-host-2 -- bash -c \
"LD_LIBRARY_PATH=/usr/lib/aarch64-linux-gnu ib_write_bw -p $port -d mlx5_0 -s 65536 --report_gbits -F" &
done
# 在 host-1 上运行 client
HOST2_IP=<host-2 IP>
for port in 18515 18516 18517 18518; do
kubectl exec nccl-sd-host-1 -- bash -c \
"LD_LIBRARY_PATH=/usr/lib/aarch64-linux-gnu ib_write_bw -p $port -d mlx5_0 -s 65536 --report_gbits -F $HOST2_IP"
done
| 指标 | 预期基线 |
|---|---|
| ib_write_bw per NIC | ~381 Gbps |
| 4×NIC aggregate | ~1524 Gbps |
kubectl apply -f yamls/k8s134-deepep-test-dranet.yaml
kubectl apply -f yamls/k8s134-deepep-test-hostnet.yaml
# 等待 Pod 就绪(自动安装 deep_ep via pip)
kubectl get pods -l name -w
kubectl logs deepep-host-1 -f # 确认 "Ready"
# 验证 ComputeDomain 状态
kubectl get computedomain -o wide
DeepEP 需从源码编译(pip 版本无 SM_100 支持)。使用现有 NCCL Pod 或 DeepEP Pod 均可。
# Step 1: 编译 DeepEP(在任一 GPU Pod 内)
kubectl exec deepep-host-1 -- bash -c "
echo 'nameserver 8.8.8.8' > /etc/resolv.conf # hostNetwork 需要
apt-get update -qq && apt-get install -y -qq git 2>/dev/null
pip install nvidia-nvshmem-cu12 2>&1 | tail -3
ln -sf /usr/local/gib/lib64/libnccl.so.2 /usr/local/gib/lib64/libnccl.so
cd /tmp && git clone --depth 1 https://github.com/deepseek-ai/DeepEP.git
cd /tmp/DeepEP
NCCL_DIR=/usr/local/gib TORCH_CUDA_ARCH_LIST='10.0' python setup.py build_ext --inplace 2>&1 | tail -5
# 确认输出: deep_ep/_C.cpython-312-aarch64-linux-gnu.so
"
# Step 2: 运行 intra-node 测试(BF16 + FP8 dispatch/combine)
kubectl exec deepep-host-1 -- bash -c "
export NCCL_DIR=/usr/local/gib
export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:\$LD_LIBRARY_PATH
export PYTHONPATH=/tmp/DeepEP:\$PYTHONPATH
cd /tmp/DeepEP/tests/legacy
python test_intranode.py --num-processes 4 --num-experts 64 --num-topk 4
"
# Step 3: 运行 low-latency 测试
kubectl exec deepep-host-1 -- bash -c "
export NCCL_DIR=/usr/local/gib PYTHONPATH=/tmp/DeepEP:\$PYTHONPATH
export LD_LIBRARY_PATH=/usr/local/gib/lib64:/usr/local/nvidia/lib64:\$LD_LIBRARY_PATH
cd /tmp/DeepEP/tests/legacy
python test_low_latency.py --num-processes 4 --num-experts 64 --num-topk 4
"
| 测试 | 预期基线 | k8s 1.34 实测 |
|---|---|---|
| Intranode BF16 dispatch | ~462 GB/s | 430.71 GB/s |
| Intranode FP8 dispatch | ~306 GB/s | 281.70 GB/s |
| Intranode Combine | ~349 GB/s | 321.91 GB/s |
| Low-latency dispatch | — | 166-184 GB/s |
| Low-latency combine | — | 322-338 GB/s |
使用 DeepEP v2 Elastic EP 测试 (tests/elastic/test_ep.py),基于 NCCL GIN 后端(不依赖 NVSHMEM internode)。
# 交换 SSH 密钥(同上)
# 获取 IP(路径 A 用 podIP,路径 B 用 hostIP)
HOST1_IP=$(kubectl get pod deepep-host-1 -o jsonpath='{.status.podIP}') # 路径 A
# HOST1_IP=$(kubectl get pod deepep-host-1 -o jsonpath='{.status.hostIP}') # 路径 B
# 在 host-2 上启动(RANK=1)
kubectl exec deepep-host-2 -- bash -c "
export MASTER_ADDR=$HOST1_IP
export MASTER_PORT=29500
export WORLD_SIZE=2
export RANK=1
cd /tmp/DeepEP/tests/elastic
python test_ep.py
" &
sleep 5
# 在 host-1 上启动(RANK=0)
kubectl exec deepep-host-1 -- bash -c "
export MASTER_ADDR=$HOST1_IP
export MASTER_PORT=29500
export WORLD_SIZE=2
export RANK=0
cd /tmp/DeepEP/tests/elastic
python test_ep.py
"
| 测试 | 预期基线 |
|---|---|
| Internode MNNVL Dispatch SU | ~700-731 GB/s |
| Internode MNNVL Combine SU | ~723 GB/s |
| Stream Overflow (SO) | 0 |
| 全部 144 配置 | ALL PASS |
kubectl apply -f yamls/k8s134-megatron-train-dranet.yaml
kubectl apply -f yamls/k8s134-megatron-train-hostnet.yaml
# 等待 Pod 就绪(自动 clone Megatron-LM + pip install)
kubectl get pods -l name -w
kubectl logs mega-host-1 -f # 确认 "Ready"
# 交换 SSH 密钥(ed25519)
HOST1_KEY=$(kubectl exec mega-host-1 -- cat /root/.ssh/id_ed25519.pub)
HOST2_KEY=$(kubectl exec mega-host-2 -- cat /root/.ssh/id_ed25519.pub)
kubectl exec mega-host-1 -- bash -c "echo '$HOST2_KEY' >> /root/.ssh/authorized_keys"
kubectl exec mega-host-2 -- bash -c "echo '$HOST1_KEY' >> /root/.ssh/authorized_keys"
# 获取 IP
MEGA_HOST1_IP=$(kubectl get pod mega-host-1 -o jsonpath='{.status.podIP}') # 路径 A
# MEGA_HOST1_IP=$(kubectl get pod mega-host-1 -o jsonpath='{.status.hostIP}') # 路径 B
单节点 TP=4(4 GPU tensor 并行),无需 Gloo 跨节点通信。
# 在任一 Megatron Pod 上运行(例如 mega-host-1)
kubectl exec mega-host-1 -- bash -c "
source /usr/local/gib/scripts/set_nccl_env.sh 2>/dev/null
export LD_LIBRARY_PATH=/usr/local/gib/lib64:\$LD_LIBRARY_PATH
export PYTHONPATH=/scratch-data/Megatron-LM:\$PYTHONPATH
cd /scratch-data/Megatron-LM
torchrun --nproc_per_node=4 --nnodes=1 --node_rank=0 \
--master_addr=127.0.0.1 --master_port=29502 \
pretrain_gpt.py \
--num-layers 6 \
--hidden-size 1024 \
--num-attention-heads 16 \
--seq-length 512 \
--max-position-embeddings 512 \
--micro-batch-size 2 \
--global-batch-size 16 \
--train-iters 20 \
--lr 0.0001 \
--min-lr 0.00001 \
--lr-decay-style cosine \
--log-interval 1 \
--eval-iters 0 \
--eval-interval 100 \
--tensor-model-parallel-size 4 \
--pipeline-model-parallel-size 1 \
--no-masked-softmax-fusion \
--no-bias-gelu-fusion \
--no-bias-dropout-fusion \
--no-gradient-accumulation-fusion \
--bf16 \
--use-mcore-models \
--transformer-impl local \
--mock-data \
--tokenizer-type NullTokenizer \
--vocab-size 8192 \
--no-save-optim \
--no-save-rng \
--no-load-optim \
--no-load-rng \
--log-throughput
"
mega-h1)时,NCCL/Gloo bootstrap 会调用 gethostbyname() 解析自身 IP。若 hostname 无 DNS 记录会导致静默 hang。必须在两个容器的 /etc/hosts 中互相添加映射:
kubectl exec mega-host-1 -- bash -c "echo '<HOST2_IP> mega-h2' >> /etc/hosts; echo '<HOST1_IP> mega-h1' >> /etc/hosts"
kubectl exec mega-host-2 -- bash -c "echo '<HOST1_IP> mega-h1' >> /etc/hosts; echo '<HOST2_IP> mega-h2' >> /etc/hosts"
# 获取 IP(hostNetwork 用 hostIP)
MEGA_HOST1_IP=$(kubectl get pod mega-host-1 -o jsonpath='{.status.hostIP}')
# 在 mega-host-1 上运行(node_rank=0)
kubectl exec mega-host-1 -- bash -c "
source /usr/local/gib/scripts/set_nccl_env.sh 2>/dev/null
export LD_LIBRARY_PATH=/usr/local/gib/lib64:\$LD_LIBRARY_PATH
export PYTHONPATH=/scratch-data/Megatron-LM:\$PYTHONPATH
export GLOO_SOCKET_IFNAME=eth0
export NCCL_SOCKET_IFNAME=eth0
export NCCL_MNNVL_ENABLE=2
export NCCL_CUMEM_ENABLE=1
cd /scratch-data/Megatron-LM
torchrun --nproc_per_node=4 --nnodes=2 --node_rank=0 \
--master_addr=$MEGA_HOST1_IP --master_port=29502 \
pretrain_gpt.py \
--num-layers 6 \
--hidden-size 1024 \
--num-attention-heads 16 \
--seq-length 512 \
--max-position-embeddings 512 \
--micro-batch-size 2 \
--global-batch-size 16 \
--train-iters 20 \
--lr 0.0001 \
--min-lr 0.00001 \
--lr-decay-style cosine \
--log-interval 1 \
--eval-iters 0 \
--eval-interval 100 \
--tensor-model-parallel-size 4 \
--pipeline-model-parallel-size 2 \
--no-masked-softmax-fusion \
--no-bias-gelu-fusion \
--no-bias-dropout-fusion \
--no-gradient-accumulation-fusion \
--bf16 \
--use-mcore-models \
--transformer-impl local \
--mock-data \
--tokenizer-type NullTokenizer \
--vocab-size 8192 \
--no-save-optim \
--no-save-rng \
--no-load-optim \
--no-load-rng \
--log-throughput
" &
# 在 mega-host-2 上同时运行(node_rank=1)— 同上但 --node_rank=1
| 场景 | 指标 | 实测结果 |
|---|---|---|
| 单节点 TP=4 | 20 iters, loss 下降 | loss 9.29→7.54, 2.5 TFLOP/s/GPU — PASS |
| 多节点 TP=4 PP=2 | 20 iters, loss 下降 | loss 9.27→6.81, 2.5 TFLOP/s/GPU — PASS |
GCP Reservation 提供容量保障,但不提供自动故障替换。当域内某节点发生硬件故障时,该节点需通过 report-host-as-faulty API(见第 11 节)上报后等待物理替换,替换周期可能需要数小时到数天。在此期间,预留 2 个备用节点可确保训练在节点故障后立即通过 k8s 抢占机制恢复,无需等待硬件替换。
kubectl apply -f yamls/spare-capacity-priority.yaml
kubectl get priorityclass
# gpu-training (1000) gpu-placeholder (-1)
kubectl apply -f yamls/spare-capacity-placeholder.yaml
# 验证 Placeholder Pod 已调度到 2 个节点
kubectl get pods -l app=gpu-placeholder -o wide
kubectl apply -f yamls/spare-capacity-training-job.yaml
# 验证:训练 Pod 会抢占 Placeholder
kubectl get pods -o wide
# training-preempt-test 应该 Running
# 某个 gpu-placeholder Pod 应该被驱逐
# Step 1: 隔离故障节点
kubectl cordon <NODE_NAME>
kubectl drain <NODE_NAME> --ignore-daemonsets --delete-emptydir-data --force
# Step 2: 验证 Pod 重调度
kubectl get pods -l app=gpu-placeholder -o wide
# Step 3: 节点修复后恢复
kubectl uncordon <NODE_NAME>
验证 ComputeDomain 在 daemon Pod 异常后能自动重建并恢复 IMEX daemon。
# 1. 确认 ComputeDomain 状态为 READY
kubectl get computedomain -o wide
# 2. 查看 ComputeDomain 自动创建的 daemon Pod
kubectl get pods -l app.kubernetes.io/managed-by=compute-domain-controller -o wide
# 3. 删除一个 daemon Pod(模拟故障)
DAEMON_POD=$(kubectl get pods -l app.kubernetes.io/managed-by=compute-domain-controller \
--field-selector spec.nodeName=k8s134-sd-w1 -o name | head -1)
kubectl delete $DAEMON_POD
# 4. 等待 ComputeDomain 控制器自动重建 daemon Pod
kubectl get pods -l app.kubernetes.io/managed-by=compute-domain-controller -w
# 5. 验证 ComputeDomain 恢复到 READY 状态
kubectl get computedomain -o wide
# 6. 重新运行 NCCL MNNVL 测试,验证性能恢复
| 指标 | 预期 |
|---|---|
| ComputeDomain 恢复时间 | ~30 秒(daemon Pod 重建 + IMEX 重启) |
| 恢复后 MNNVL 性能 | 接近原始基线(~836 GB/s) |
gcloud compute instances report-host-as-faulty <INSTANCE_NAME> \
--async \
--disruption-schedule=IMMEDIATE \
--fault-reasons=behavior=UNRECOVERABLE_GPU_ERROR,description="XID 79 detected" \
--zone=$ZONE --project=$PROJECT
| fault-reasons behavior | 含义 | 场景 |
|---|---|---|
PERFORMANCE | GPU 性能下降 | 无 Xid 错误但训练速度下降 |
UNRECOVERABLE_GPU_ERROR | 不可恢复 GPU 错误 | Xid 错误 |
SILENT_DATA_CORRUPTION | 静默数据损坏 | 训练结果异常 |
CHIP_ERROR | 芯片错误 | 硬件芯片级故障 |
# 查看拓扑结构
gcloud alpha compute reservations blocks list $RESERVATION \
--zone=$ZONE --project=$PROJECT
gcloud alpha compute reservations sub-blocks list $RESERVATION \
--block-name=<BLOCK_NAME> \
--zone=$ZONE --project=$PROJECT
# Sub-block 故障上报
gcloud alpha compute reservations sub-blocks report-subblock-as-faulty $RESERVATION \
--block-name=<BLOCK_NAME> \
--sub-block-name=<SUBBLOCK_NAME> \
--disruption-schedule=IMMEDIATE \
--fault-reasons=behavior=SWITCH_FAILURE,description="NVSwitch failure" \
--failure-component=NVLINK_SWITCH \
--zone=$ZONE --project=$PROJECT
from google.cloud import compute_v1
def report_host_as_faulty(project_id, zone, instance_name, fault_behavior, description):
client = compute_v1.InstancesClient()
request_body = compute_v1.InstancesReportHostAsFaultyRequest(
disruption_schedule="IMMEDIATE",
fault_reasons=[
compute_v1.FaultReason(
behavior=fault_behavior,
description=description
)
]
)
operation = client.report_host_as_faulty(
project=project_id, zone=zone, instance=instance_name,
instances_report_host_as_faulty_request_resource=request_body
)
print(f"Operation started: {operation.name}")
return operation
# 使用示例
report_host_as_faulty(
project_id="gpu-launchpad-playground",
zone="us-east1-d",
instance_name="k8s134-sd-w0",
fault_behavior="UNRECOVERABLE_GPU_ERROR",
description="XID 79 GPU error detected"
)
kubectl apply -f yamls/dcgm-exporter-daemonset.yaml
# 验证
kubectl get pods -n kube-system -l app=dcgm-exporter
# 每个 GPU Worker 节点一个 Pod
# 测试指标
kubectl exec -n kube-system <DCGM_POD> -- wget -qO- http://localhost:9400/metrics | grep '^DCGM_FI' | head -20
| 指标 | 告警阈值 | 含义 |
|---|---|---|
DCGM_FI_DEV_GPU_TEMP | > 85°C | GPU 过热 |
DCGM_FI_DEV_MEMORY_TEMP | > 95°C | 显存过热 |
DCGM_FI_DEV_POWER_USAGE | > 1000W | 功耗异常 |
DCGM_FI_DEV_UNCORRECTABLE_REMAPPED_ROWS | > 0 | 不可纠正 ECC 错误 |
DCGM_FI_DEV_ROW_REMAP_FAILURE | > 0 | 行重映射失败(需立即维修) |
DCGM_FI_DEV_PCIE_REPLAY_COUNTER | 持续增长 | PCIe 链路质量问题 |
Prometheus + Grafana 集成:DCGM Exporter 默认暴露 :9400/metrics(Prometheus 格式),使用 NVIDIA 官方 Grafana Dashboard ID: 12239。
| 类别 | 测试项 | 预期基线 | 实际结果 | 状态 |
|---|---|---|---|---|
| 基础设施 | GPU 检测 (4×GB200/node) | 4 GPU per worker | 4 GPU × 4 nodes = 16 total | PASS |
| ComputeDomain 域发现 | 同域节点 READY | ComputeDomain READY, daemon Pod Running | PASS | |
| NCCL | 单节点 all_reduce 4 GPU @8G | ~604 GB/s | 667 GB/s | PASS |
| 同域 2 节点 MNNVL @8G | ~836 GB/s | 836 GB/s busbw | PASS | |
| 跨域 2 节点 RDMA @8G | ~327 GB/s | 328 GB/s busbw | PASS | |
| 混合 4 节点 16GPU @8G | ~320 GB/s | 684 GB/s busbw | PASS | |
| DeepEP v2 | Intranode BF16 dispatch | ~462 GB/s | 430.71 GB/s (24/24 pass) | PASS |
| Intranode FP8 dispatch | ~306 GB/s | 281.70 GB/s | PASS | |
| Intranode Combine | ~349 GB/s | 321.91 GB/s | PASS | |
| Low-latency dispatch+combine | — | 184 GB/s + 338 GB/s | PASS | |
| Megatron-LM | 单节点 4GPU TP=4 20 iters | loss 下降 | loss 9.29→7.54, 2.5 TFLOP/s/GPU | PASS |
| 多节点 2×4GPU TP=4 PP=2 | loss 下降, 无 NCCL 错误 | loss 9.27→6.81, 2.5 TFLOP/s/GPU | PASS | |
| 备用容量 | PriorityClass 创建 | gpu-training + gpu-placeholder | 两个 PriorityClass 创建成功 | PASS |
| Placeholder 占位 2 节点 | 2 pods Running | 2 pods Running on cd-w0, cd-w1 | PASS | |
| 训练 Pod 抢占 Placeholder | Placeholder 被驱逐 | Placeholder 被驱逐,Training 接管 GPU | PASS | |
| ComputeDomain | ComputeDomain IMEX 自动就绪 | READY 状态 | ComputeDomain READY, daemon Pod Running | PASS |
| daemon Pod 重建后恢复 | 自动 reconcile ~30s | Pod 重建后 NCCL 恢复 751 GB/s | PASS |
| 类别 | 测试项 | 预期基线 | 实际结果 | 状态 |
|---|---|---|---|---|
| NCCL | 单节点 all_reduce 4 GPU @8G | ~604 GB/s | 667 GB/s | PASS |
| 同域 2 节点 MNNVL @8G | ~836 GB/s | 836 GB/s busbw | PASS | |
| 跨域 2 节点 RDMA @8G | ~327 GB/s | 328 GB/s busbw | PASS | |
| 混合 4 节点 16GPU @8G | ~320 GB/s | 684 GB/s busbw | PASS | |
| DeepEP v2 | Intranode BF16 dispatch | ~462 GB/s | 430.71 GB/s (24/24 pass) | PASS |
| Intranode FP8 dispatch | ~306 GB/s | 281.70 GB/s | PASS | |
| Intranode Combine | ~349 GB/s | 321.91 GB/s | PASS | |
| Low-latency dispatch+combine | — | 184 GB/s + 338 GB/s | PASS | |
| Megatron-LM | 单节点 4GPU TP=4 20 iters | loss 下降 | loss 9.29→7.54, 2.5 TFLOP/s/GPU | PASS |
| 多节点 2×4GPU TP=4 PP=2 | loss 下降, 无 NCCL 错误 | loss 9.27→6.81, 2.5 TFLOP/s/GPU | PASS | |
| 备用容量 | PriorityClass 创建 | gpu-training + gpu-placeholder | 创建成功 | PASS |
| Placeholder 占位 | 2 pods Running | 2 pods Running | PASS | |
| 训练 Pod 抢占 | Placeholder 被驱逐 | Placeholder 被驱逐 | PASS | |
| ComputeDomain | ComputeDomain IMEX 自动就绪 | READY 状态 | ComputeDomain READY | PASS |
| daemon Pod 重建后恢复 | 自动 reconcile ~30s | NCCL 恢复 751 GB/s | PASS |
gethostbyname() 无法解析会导致静默 hang(见 14.6)。| 问题 | 说明 |
|---|---|
| 启动盘设备名不固定 | NVMe 设备编号在不同启动间可能变化,需用 findmnt 动态查找 |
| 启动盘未自动扩展 | 50GB OS 镜像安装到 200GB 盘后不自动扩展根分区,需手动 growpart + xfs_growfs |
| 缺少基础组件 | sudo、gcloud CLI、perftest、CUDA Toolkit 均未预装,需手动安装 |
| Docker CE repo | 需硬编码 RHEL 9 baseurl,TLinux 4 不被 Docker 官方自动识别 |
| 项目 | 说明 |
|---|---|
| DRA API 版本 | ResourceClaimTemplate/DeviceClass 使用 resource.k8s.io/v1(GA API,非 v1beta2) |
| DRA 状态 | k8s 1.34 中 DRA 为 GA,默认启用,无需 feature gate |
| DRANET 版本 | v1.3.0,Helm chart: oci://registry.k8s.io/networking/charts/dranet |
| ComputeDomain | ComputeDomain 始终按物理域大小配置 IMEX(18 节点),即使 numNodes: 2。<18 节点环境下 IMEX daemon 会持续尝试连接不存在的节点(无害但有日志噪音)。已验证可在部分域环境正常工作(836 GB/s MNNVL) |
| ComputeDomain 互斥 | ComputeDomain、IMEX Manager DaemonSet、主机级 nvidia-imex systemd 服务三者互斥,同一时刻只能有一个持有 IMEX session,否则报 NV_ERR_IN_USE |
| NCCL 测试二进制 | GIB v1.1.2 自带的 /usr/local/bin/all_reduce_perf 未链接 MPI,多节点测试会退化为独立单 GPU 基准。必须从 nccl-tests 源码编译 MPI 版:make MPI=1 MPI_HOME=/usr/local/mpi NCCL_HOME=/usr/local/gib |
| mpirun 路径 | /usr/local/mpi/bin/mpirun(非 /usr/local/gib/bin/mpirun) |
| Calico 多网卡 | A4X Worker 有 6 个 NIC (2 GVNIC + 4 MRDMA),Calico 需设置 IP_AUTODETECTION_METHOD=interface=eth0 避免选取 MRDMA 接口 IP |
| SSH 密钥 | 容器内必须用 ed25519(ssh-keygen -t ed25519),RSA 因无 /dev/tty 会失败 |
| 特性 | 路径 A — DRANET | 路径 B — hostNetwork |
|---|---|---|
| RDMA 访问 | DRA ResourceClaimTemplate + DeviceClass | hostNetwork: true |
| Pod 网络 | 独立 CNI 网络命名空间 | 共享主机网络栈 |
| SSH 端口 | 2222(客户要求) | 2222(避免与主机 sshd 冲突,客户要求) |
| DNS | 自动(CNI 配置) | 需设置 dnsPolicy: ClusterFirstWithHostNet(YAML 中已配置) |
| DRANET DaemonSet | 需要安装 | 不需要 |
| DeviceClass | 需要创建 rdma-devices | 不需要 |
| IMEX 管理 | ComputeDomain (DRA GPU Driver) | ComputeDomain (DRA GPU Driver) |
| GPU 分配 | nvidia-device-plugin (nvidia.com/gpu: 4) | nvidia-device-plugin (nvidia.com/gpu: 4) |
| 端口冲突风险 | 低(独立网络命名空间) | 高(共享主机端口) |
| 安全性 | 更好(网络隔离) | 较低(privileged + hostNetwork) |
Megatron-LM 使用 Gloo 进行进程通信。如果 Pod hostname 过长,会触发 File name too long 错误。解决方案:在 Pod spec 中设置 hostname 字段为短名称(如 mega-h1)。
DeepEP v2 internode 使用 Elastic EP 测试 (tests/elastic/test_ep.py),基于 NCCL GIN 后端。不要使用 tests/legacy/test_internode.py,该测试会因 LEGACY_NUM_MAX_NVL_PEERS=8 assertion 在 4-GPU A4X 上失败。
hostNetwork Pod 设置自定义 hostname(如 mega-h1)时,NCCL bootstrap 和 Gloo 会调用 gethostname() → gethostbyname() 获取自身 IP。如果该 hostname 没有 DNS 记录,解析失败会导致 静默 hang(不报错,进程无限等待)。
症状:initialized tensor model parallel 后不再有输出,进程占用 100% CPU。
修复:在每个容器的 /etc/hosts 中添加所有节点的 hostname→IP 映射:
# 在 mega-host-1 和 mega-host-2 中都执行
echo "<HOST1_IP> mega-h1" >> /etc/hosts
echo "<HOST2_IP> mega-h2" >> /etc/hosts
根因:k8s 1.22 的 Megatron YAML 未设置 hostname 字段,容器继承宿主机真实 hostname(可正常解析),因此未遇此问题。
替代方案:不设置 Pod hostname 字段,让容器使用宿主机默认 hostname;或配合 headless Service + subdomain 字段使 k8s DNS 自动注册。
同时需设置 NCCL_SOCKET_IFNAME=eth0 和 GLOO_SOCKET_IFNAME=eth0 确保网络接口选择正确。
GIB 的 libibverbs 库 (/usr/local/gib/lib64/) 可能覆盖系统版本。运行 ib_write_bw 前需切换:export LD_LIBRARY_PATH=/usr/lib/aarch64-linux-gnu。
GIB 提供 libnccl.so.2 但无 libnccl.so 符号链接。构建 DeepEP 时需创建:ln -sf /usr/local/gib/lib64/libnccl.so.2 /usr/local/gib/lib64/libnccl.so。
DeepEP 需从源码编译(pip 版本无 SM_100 支持)。关键配置:
NCCL_DIR=/usr/local/gib(不是 NCCL_HOME)TORCH_CUDA_ARCH_LIST="10.0"pip install nvidia-nvshmem-cu12(容器自带 3.2.5 不含 dev headers)ln -sf /usr/local/gib/lib64/libnccl.so.2 /usr/local/gib/lib64/libnccl.so--mock-data(不是 --use-mock-data)--eval-interval 和 --eval-iters 必须显式设置--no-async-tensor-model-parallel-allreduce 已移除GLOO_SOCKET_IFNAME=eth0| 概念 | 说明 |
|---|---|
ComputeDomain CRD | 定义需要 IMEX 通信的节点组。API: resource.nvidia.com/v1beta1 |
ComputeDomainClique | 控制器根据物理 NVLink 域自动创建的子资源,记录域内所有节点的 IP 和状态 |
| Daemon DaemonSet | 控制器自动创建的 DaemonSet,在每个域内节点上运行 nvidia-imex daemon pod |
ResourceClaimTemplate | 控制器自动创建的模板(名称由 ComputeDomain spec 指定),Pod 通过 resourceClaimTemplateName 引用获取 IMEX channel |
| DeviceClass | compute-domain-default-channel.nvidia.com(自动创建)和 compute-domain-daemon.nvidia.com |
工作流程:
ComputeDomain 资源(指定 numNodes)ComputeDomainCliqueResourceClaimTemplate(自动生成)ReadyresourceClaimTemplateName 获取 compute-domain-channel → 获得 /dev/nvidia-caps-imex-channels/channel0DRA GPU Driver 在每个 GPU 节点上运行 kubelet plugin,读取 GPU 硬件属性(包括 NVLink ClusterUUID),通过 ResourceSlice 发布到 k8s API。ComputeDomain 控制器基于 ResourceSlice 中的 ClusterUUID 对节点分组,自动创建 ComputeDomainClique。
# 查看节点的 NVLink 域标签
kubectl get nodes -L nvidia.com/gpu.clusteruuid
# 查看 ResourceSlice(包含 GPU 和 NVLink 拓扑)
kubectl get resourceslice -o wide | head -20
# 查看 ComputeDomainClique(每个物理域一个)
kubectl get computedomainclique -o wide
Pod 通过 resourceClaimTemplateName 引用 ComputeDomain 的 IMEX channel。DRA 调度器插件确保 Pod 只调度到 ComputeDomain daemon 已就绪的节点上。结合 nodeAffinity(nvidia.com/gpu.clusteruuid)可将 Pod 限定到特定物理域。
| RDMA 方式 | 调度约束 | 适用场景 |
|---|---|---|
| DRANET | DRA 调度器自动处理 GPU + RDMA NIC + IMEX channel 的协同分配 | 推荐方案。网络隔离好,DRA 原生集成 |
| hostNetwork | Pod 使用 hostNetwork: true,需设置 dnsPolicy: ClusterFirstWithHostNet 和 NCCL_SOCKET_IFNAME=eth0 | 备选方案。无需 DRANET,部署更简单 |
| 症状 | 原因 | 解决方案 |
|---|---|---|
NV_ERR_IN_USE: Failed to allocate Imex session |
Host 级 nvidia-imex systemd 服务或 IMEX Manager DaemonSet 仍在运行,占用 IMEX channels | systemctl stop nvidia-imex && systemctl disable nvidia-imexkubectl delete ds imex-manager -n kube-system |
ComputeDomain status: NotReady,daemon pods 0/1 |
Daemon pods 无法通过 pod CIDR 互联(port 50000)。Calico IPIP 隧道流量(protocol 4)被 VPC 防火墙阻断 | 添加 IPIP 协议防火墙规则:gcloud compute firewall-rules create allow-ipip --allow=4 --source-ranges=192.168.0.0/16或切换 Calico 为 VXLAN 模式(使用 UDP,已被现有规则允许) |
IMEX daemon 日志显示 Attempting connection for target node N(N=2~17) |
ComputeDomain 按物理域大小(18 节点)配置 IMEX,即使实际只有 2 节点。不存在的 16 个 peer 永远连不上 | 正常行为。IMEX 设置 IMEX_WAIT_FOR_QUORUM != FULL,不等待所有节点即可工作。日志噪音可忽略 |
FailedResourceClaimCreation: resourceclaimtemplate not found |
ComputeDomain 刚创建时,ResourceClaimTemplate 尚未被控制器生成(需 1-3 秒) | 等待。控制器创建 ResourceClaimTemplate 后 kubelet 自动重试 |
NodePrepareResources: DeadlineExceeded |
DRA kubelet plugin 等待 ComputeDomain 变为 Ready,但 IMEX daemon 未就绪 | 检查上述 NV_ERR_IN_USE 和 pod CIDR 连通性问题。ComputeDomain Ready 后 kubelet 自动重试 |
NVL72 域包含 18 个节点(72 GPU)。生产环境中常见的资源策略是 64+8:
详细方案见第 9 节 — 64+8 备用容量管理。
工作原理:
ComputeDomain → daemon pod 在全部 18 节点上运行 → IMEX 覆盖整域节点故障恢复流程:
# 初始状态
域内 18 节点:
Node 0~15: 训练 Pod(高优先级, 带 ComputeDomain channel ResourceClaim)
Node 16~17: Placeholder Pod(低优先级, 无 IMEX channel)
全部 18 节点: ComputeDomain daemon pod running ✓
# Node 5 故障 →
Node 5 上的训练 Pod 被驱逐
训练框架创建替换 Pod(高优先级)
→ 替换 Pod 抢占 Node 16 上的 Placeholder(PriorityClass 抢占)
→ Node 16 上 IMEX daemon 已在运行 ✓
→ 替换 Pod 的 ResourceClaim 分配 IMEX channel → 立即可用 ✓
→ 训练恢复(16 节点: 0~4, 6~16)
ComputeDomain 下的 YAML 示例(结合第 9 节的 PriorityClass):
# 1. ComputeDomain — 覆盖整域
apiVersion: resource.nvidia.com/v1beta1
kind: ComputeDomain
metadata:
name: training-domain
spec:
numNodes: 18 # 覆盖整域所有节点
channel:
resourceClaimTemplate:
name: training-imex-channel
---
# 2. 训练 Pod(高优先级,带 IMEX channel)
apiVersion: v1
kind: Pod
metadata:
name: training-worker-0
spec:
priorityClassName: training-high-priority # 见第 9 节
nodeSelector:
nvidia.com/gpu.clusteruuid: "804c7837-..." # 指定域
containers:
- name: trainer
image: nvcr.io/nvidia/pytorch:25.04-py3
resources:
limits:
nvidia.com/gpu: 4
claims:
- name: imex-channel
- name: rdma-nics # 如使用 DRANET
resourceClaims:
- name: imex-channel
resourceClaimTemplateName: training-imex-channel # ComputeDomain 自动创建
- name: rdma-nics
resourceClaimTemplateName: rdma-nics-worker-0
---
# 3. Placeholder Pod(低优先级,不需要 IMEX channel)
apiVersion: v1
kind: Pod
metadata:
name: spare-placeholder-0
spec:
priorityClassName: spare-low-priority # 见第 9 节
nodeSelector:
nvidia.com/gpu.clusteruuid: "804c7837-..."
containers:
- name: placeholder
image: registry.k8s.io/pause:3.9
resources:
limits:
nvidia.com/gpu: 4
# 注意:不需要 resourceClaims — placeholder 不使用 MNNVL
训练任务需要 2 个完整 NVLink 域的算力:36 节点 × 4 GPU = 144 GPU。
┌── Domain A (ClusterUUID: 804c7837) ──┐ ┌── Domain B (ClusterUUID: 89164aa7) ──┐
│ node 0~17 (18 nodes, 72 GPU) │ │ node 0~17 (18 nodes, 72 GPU) │
│ 域内通信: MNNVL ~836 GB/s │ │ 域内通信: MNNVL ~836 GB/s │
└──────────────┬───────────────────────┘ └──────────────┬──────────────────────┘
└────────── 跨域通信: RDMA ~327 GB/s ──────┘
(NCCL 自动选择路径,无需配置)
1. 一个 ComputeDomain 覆盖所有域
apiVersion: resource.nvidia.com/v1beta1
kind: ComputeDomain
metadata:
name: training-job-cd
spec:
numNodes: 36 # 2 域 × 18 节点
channel:
resourceClaimTemplate:
name: training-job-imex-channel # 控制器自动创建
控制器自动为每个物理域创建独立的 ComputeDomainClique。域内 daemon pods 互联建立 IMEX session,跨域 daemon pods 不互联(IMEX session 是域级的)。
2. 一个共享的 RDMA ResourceClaimTemplate
apiVersion: resource.k8s.io/v1
kind: ResourceClaimTemplate
metadata:
name: training-job-rdma
spec:
spec:
devices:
requests:
- name: rdma-nics
exactly:
deviceClassName: rdma-devices
count: 4
所有 36 个 Pod 引用同一个模板。每个 Pod 实例化时,k8s 从模板生成独立的 ResourceClaim,DRANET 为每个 Pod 分配 4 个 RDMA NIC。
3. 每个 Pod 只有 nodeAffinity 不同
# Domain A 的 Pod (rank 0~17)
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: nvidia.com/gpu.clusteruuid
operator: In
values: ["804c7837-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] # Domain A
podAntiAffinity: # 确保每个 Pod 落在不同节点
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: job
operator: In
values: ["training-job-2domain"]
topologyKey: kubernetes.io/hostname
# Domain B 的 Pod (rank 18~35): 仅 clusteruuid 值不同
values: ["89164aa7-yyyy-yyyy-yyyy-yyyyyyyyyyyy"] # Domain B
4. 每个 Pod 的 resourceClaims 完全相同
resourceClaims:
- name: compute-domain-channel
resourceClaimTemplateName: training-job-imex-channel # 所有 Pod 共用
- name: rdma-nics
resourceClaimTemplateName: training-job-rdma # 所有 Pod 共用
# 平台调度器分配了 2 个域给训练任务
allocated_domains = [
{"uuid": "804c7837-...", "node_count": 18}, # Domain A
{"uuid": "89164aa7-...", "node_count": 18}, # Domain B
]
# 1. 创建 ComputeDomain(覆盖所有域)
total = sum(d["node_count"] for d in allocated_domains) # 36
create_compute_domain("training-job-cd", num_nodes=total)
# 2. 创建共享 RDMA ResourceClaimTemplate
create_rdma_template("training-job-rdma", count=4)
# 3. 为每个节点生成 Pod — 唯一差异是 nodeAffinity 的 ClusterUUID
rank = 0
for domain in allocated_domains:
for i in range(domain["node_count"]):
create_pod(
name=f"train-{rank}",
cluster_uuid=domain["uuid"],
imex_template="training-job-imex-channel",
rdma_template="training-job-rdma",
gpu_count=4, rank=rank,
)
rank += 1
| 通信场景 | NCCL 自动选择的路径 | 预期带宽 |
|---|---|---|
| 同节点 GPU 间 | NVLink (node-internal) | ~604 GB/s |
| 同域跨节点 GPU 间 | MNNVL (ComputeDomain IMEX) | ~836 GB/s |
| 跨域 GPU 间 | RDMA (DRANET CX-7 400Gbps) | ~327 GB/s |
NCCL 通过 NCCL_MNNVL_ENABLE=2 自动检测 NVLink 域拓扑,域内用 MNNVL,跨域降级到 RDMA。训练代码无需区分。
完整 YAML 参考:yamls/k8s134-nccl-2domain-computedomain-dranet.yaml(包含 4 个示例 Pod,每域 2 个)
在 15.5 的 2 域训练基础上,每个域预留 2 个节点(8 GPU)作为备用容量,用于节点故障时快速替换。
Domain A (18 nodes, 72 GPU) Domain B (18 nodes, 72 GPU)
┌───────────────────────────────────┐ ┌───────────────────────────────────┐
│ node 0~15: 训练 Pod (64 GPU) │ │ node 0~15: 训练 Pod (64 GPU) │
│ priorityClassName: training-high │ │ priorityClassName: training-high │
│ resourceClaims: [imex, rdma] │ │ resourceClaims: [imex, rdma] │
│───────────────────────────────────│ │───────────────────────────────────│
│ node 16~17: Placeholder (8 GPU) │ │ node 16~17: Placeholder (8 GPU) │
│ priorityClassName: spare-low │ │ priorityClassName: spare-low │
│ 无 resourceClaims │ │ 无 resourceClaims │
│───────────────────────────────────│ │───────────────────────────────────│
│ 全 18 节点: ComputeDomain daemon ✓│ │ 全 18 节点: ComputeDomain daemon ✓│
└───────────────────────────────────┘ └───────────────────────────────────┘
训练: 32 nodes, 128 GPU
备用: 4 nodes, 16 GPU
| 新增内容 | 作用 |
|---|---|
PriorityClass: training-high-priority (value=1000000) | 训练 Pod 可抢占低优先级 Placeholder |
PriorityClass: spare-low-priority (value=100, preemptionPolicy: Never) | Placeholder 被抢占时立即释放节点,但自身不能抢占其他 Pod |
| 每域 2 个 Placeholder Pod | 占住 spare 节点的 GPU 资源,等待被抢占替换 |
1. ComputeDomain numNodes: 36 — 必须包含 spare 节点
2. Placeholder Pod 不需要 IMEX channel 和 RDMA claim
# Placeholder — 只占 GPU,不做计算
containers:
- name: placeholder
image: registry.k8s.io/pause:3.9 # 极低资源消耗
resources:
limits:
nvidia.com/gpu: 4 # 占住 GPU,阻止其他 Pod 使用
# 没有 resourceClaims
# → IMEX channel 和 RDMA NIC 保持空闲
# → 替换训练 Pod 到来时可直接 claim,零延迟
3. 替换 Pod 的 nodeAffinity 必须指向故障节点所在的域
# Domain A node-5 故障 → 替换 Pod 的 nodeAffinity:
values: ["804c7837-xxxx"] # 必须是 Domain A,不能跨域替换!
# MNNVL 是域内物理链路,跨域替换会导致训练拓扑不一致
4. preemptionPolicy: Never 防止 Placeholder 抢占其他 Pod
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: spare-low-priority
value: 100
preemptionPolicy: Never # Placeholder 只能被抢占,不能主动抢占
t=0 Domain A node-5 硬件故障
t=~5s node-5 NotReady → 训练 Pod (rank 5) 被驱逐
t=~10s 训练框架检测到 rank 5 丢失
→ 创建替换 Pod:
priorityClassName: training-high-priority
nodeAffinity: clusteruuid=804c7837 (Domain A)
resourceClaims: [imex-channel, rdma-nics]
t=~12s 调度器发现 Domain A 无空闲 GPU 节点
→ 发现 spare-a-0 (spare-low-priority) 可被抢占
→ 驱逐 spare-a-0 → 调度替换 Pod 到该节点
t=~15s 替换 Pod 启动:
IMEX channel: ComputeDomain daemon 已在运行 → claim 满足 ✓
RDMA NIC: DRANET 分配 4 个 CX-7 ✓
GPU: nvidia-device-plugin 分配 4 GPU ✓
t=~30s 训练从 checkpoint 恢复
完整 YAML 参考:yamls/k8s134-2domain-64plus8-computedomain-dranet.yaml(包含 PriorityClass + ComputeDomain + 每域各 1 个训练 Pod 和 2 个 Placeholder 示例)
GB200 A4X Worker 节点使用 TencentOS Server 4.0 ARM64 自定义镜像。由于 GCE 官方不提供 TencentOS 预构建镜像,需要在一台 ARM64 Builder VM 上通过 dnf installroot 跨盘构建系统,编译 NVIDIA Open Kernel 驱动,并封装为 GCE 自定义镜像。
libboost_regex.so.1.75.0 和 libicu*.so.67,而 TLinux 4 仅提供更高版本(boost 1.82、libicu 71/72)。由于 ABI 不兼容,直接软链接会导致 OS Login 崩溃、SSH via IAP 失败。本节包含完整的修复方案。
在 Builder VM 上声明以下变量,后续所有命令直接引用:
export PROJECT_ID="gpu-launchpad-playground"
export ZONE="us-central1-a"
export BUILDER_VM="tlinux-builder"
export DISK_DEVICE="/dev/nvme0n2" # Builder VM 附加的 50GB 数据盘
export MOUNT_DIR="/mnt/tlinux"
export IMAGE_NAME="tlinux-server-4-gb200-v2"
export SNAPSHOT_NAME="tlinux-v4-snap-v2"
export NVIDIA_VERSION="580.126.20" # R580 Tesla 驱动版本
在附加的 50GB 数据盘上建立 GPT 分区表:512MB UEFI ESP(FAT32)+ 剩余空间根分区(XFS)。
# 安装分区工具
sudo dnf install -y parted gdisk xfsprogs wget curl
# GPT 分区:EFI 512MB + 系统根分区
sudo parted -s "$DISK_DEVICE" mklabel gpt
sudo parted -s "$DISK_DEVICE" mkpart primary fat32 1MiB 513MiB
sudo parted -s "$DISK_DEVICE" set 1 esp on
sudo parted -s "$DISK_DEVICE" mkpart primary xfs 513MiB 100%
# 刷新分区表
sudo partprobe "$DISK_DEVICE"
sleep 3
# 格式化
sudo mkfs.vfat -F32 "${DISK_DEVICE}p1"
sudo mkfs.xfs -f "${DISK_DEVICE}p2"
# 挂载
sudo mkdir -p "$MOUNT_DIR"
sudo mount "${DISK_DEVICE}p2" "$MOUNT_DIR"
sudo mkdir -p "$MOUNT_DIR/boot/efi"
sudo mount "${DISK_DEVICE}p1" "$MOUNT_DIR/boot/efi"
配置腾讯镜像源,通过 dnf installroot 跨盘安装基础系统和编译依赖(含 RDMA 用户态库)。
# 创建临时镜像源配置
cat <<EOF | sudo tee /tmp/tlinux4.repo
[tlinux4-baseos]
name=TencentOS Server 4.0 BaseOS
baseurl=https://mirrors.tencent.com/tlinux/4.0/BaseOS/aarch64/os/
enabled=1
gpgcheck=0
retries=20
[tlinux4-appstream]
name=TencentOS Server 4.0 AppStream
baseurl=https://mirrors.tencent.com/tlinux/4.0/AppStream/aarch64/os/
enabled=1
gpgcheck=0
retries=20
EOF
# 跨盘安装 TencentOS 4.0 核心系统
sudo dnf -y --installroot="$MOUNT_DIR" --config=/tmp/tlinux4.repo --releasever=4.0 \
--disablerepo="*" --enablerepo="tlinux4-*" \
--setopt=max_parallel_downloads=1 \
install \
systemd kernel kernel-core kernel-modules kernel-devel kernel-headers \
grub2-efi-aa64 shim-aa64 efibootmgr dnf iproute passwd \
openssh-server openssh-clients NetworkManager tar gzip git make gcc \
elfutils-libelf-devel binutils kmod pciutils lshw wget curl lsof rsync tmux \
rdma-core rdma-core-devel librdmacm librdmacm-utils libibverbs ucx-ib mstflint
绑定挂载宿主机虚拟设备到目标系统,然后进入 chroot 配置 fstab 和 GRUB。
# 绑定挂载虚拟设备
sudo mount --bind /dev "$MOUNT_DIR/dev"
sudo mount --bind /dev/pts "$MOUNT_DIR/dev/pts"
sudo mount --bind /proc "$MOUNT_DIR/proc"
sudo mount --bind /sys "$MOUNT_DIR/sys"
sudo mount --bind /sys/firmware/efi/efivars "$MOUNT_DIR/sys/firmware/efi/efivars" || true
# 同步 DNS 和镜像源
sudo cp /etc/resolv.conf "$MOUNT_DIR/etc/resolv.conf"
sudo mkdir -p "$MOUNT_DIR/etc/yum.repos.d"
sudo cp /tmp/tlinux4.repo "$MOUNT_DIR/etc/yum.repos.d/tlinux4.repo"
进入 chroot 后,根据分区 UUID 构建启动挂载体系。需要修正从宿主机残留的 BLS Root UUID。
sudo chroot "$MOUNT_DIR" /bin/bash <<'EOF'
set -ex
# 读取分区 UUID
ROOT_UUID=$(blkid -o value -s UUID /dev/nvme0n2p2)
EFI_UUID=$(blkid -o value -s UUID /dev/nvme0n2p1)
# 写入 fstab
cat <<FSTAB_CFG > /etc/fstab
UUID=$ROOT_UUID / xfs defaults 0 0
UUID=$EFI_UUID /boot/efi vfat defaults,uid=0,gid=0,umask=0077,shortname=winnt 0 2
FSTAB_CFG
# GRUB 配置:GCE ARM64 PL011 串口 + 屏蔽 nouveau
cat <<'GRUB_CFG' > /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="TencentOS"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="console=ttyAMA0,115200 earlycon=pl011,mmio32,0x09000000 rd.driver.blacklist=nouveau modprobe.blacklist=nouveau nvme_core.io_timeout=255"
GRUB_DISABLE_RECOVERY="true"
GRUB_ENABLE_BLSCFG=true
GRUB_CFG
# 生成 UEFI 启动配置
grub2-mkconfig -o /boot/grub2/grub.cfg
grub2-mkconfig -o /boot/efi/EFI/tencentos/grub.cfg || true
EOF
google_oslogin_nss_cache 硬编码链接了 Rocky 9 的 libicu*.so.67,而 TLinux 4 仅有 libicu 71/72。由于大版本间 ABI 签名已变更,软链接欺骗(ln -s libicu.so.71 libicu.so.67)会导致运行时崩溃。必须从 Rocky 9 提取原生 libicu 67 库文件注入。
# 1. 注入 boost-regex 1.75.0(GCE Guest Agent 依赖)
wget -O /tmp/boost-regex-1.75.0-13.el9_7.aarch64.rpm \
https://dl.rockylinux.org/pub/rocky/9/BaseOS/aarch64/os/Packages/b/boost-regex-1.75.0-13.el9_7.aarch64.rpm
sudo rpm -ivh --nodeps --root="$MOUNT_DIR" /tmp/boost-regex-1.75.0-13.el9_7.aarch64.rpm
# 2. 提取 Rocky 9 原生 libicu 67 并注入
dnf download libicu --destdir=/tmp || yumdownloader libicu --destdir=/tmp
mkdir -p /tmp/icu67-extract
rpm2cpio /tmp/libicu-67.1-*.el9_*.aarch64.rpm | cpio -idmv -D /tmp/icu67-extract/
sudo cp -d /tmp/icu67-extract/usr/lib64/libicu*.so.67* "$MOUNT_DIR/usr/lib64/"
rm -rf /tmp/icu67-extract /tmp/libicu-67.1-*.el9_*.aarch64.rpm
# 3. 在 chroot 内安装 GCE Guest Agent
sudo chroot "$MOUNT_DIR" /bin/bash <<'EOF'
set -ex
# 验证 OS Login 动态库已补全
ldd /usr/bin/google_oslogin_nss_cache
# 配置 GCE Agent 软件源
cat <<'GCREPO' > /etc/yum.repos.d/google-cloud.repo
[google-compute-engine]
name=Google Compute Engine
baseurl=https://packages.cloud.google.com/yum/repos/google-compute-engine-el9-aarch64-stable
enabled=1
gpgcheck=0
[google-cloud-sdk]
name=Google Cloud SDK
baseurl=https://packages.cloud.google.com/yum/repos/cloud-sdk-el9-aarch64
enabled=1
gpgcheck=0
GCREPO
# 安装并启用 GCE 组件
dnf install -y google-compute-engine google-guest-agent google-osconfig-agent
systemctl enable NetworkManager sshd
systemctl enable google-guest-agent google-startup-scripts google-shutdown-scripts google-osconfig-agent
EOF
GB200 需要 Open Kernel Module(--kernel-module-type=open)。闭源驱动在 NVLink 跨节点高速互联和 TCPXo DMA-BUF 处理上存在兼容问题。
sudo chroot "$MOUNT_DIR" /bin/bash <<EOF
set -ex
# 下载 R580 Tesla 驱动
curl -L "https://us.download.nvidia.com/tesla/${NVIDIA_VERSION}/NVIDIA-Linux-aarch64-${NVIDIA_VERSION}.run" \
-o /tmp/nvidia.run
# 屏蔽 nouveau
cat <<'NOUVEAU' > /etc/modprobe.d/blacklist-nouveau.conf
blacklist nouveau
options nouveau modeset=0
NOUVEAU
# 查找内核版本
KERNEL_VER=\$(rpm -q --qf "%{VERSION}-%{RELEASE}.%{ARCH}\n" kernel | head -n 1)
# 编译安装 Open Kernel Module
sh /tmp/nvidia.run \
--silent \
--no-questions \
--accept-license \
--kernel-module-type=open \
--kernel-name="\${KERNEL_VER}" \
--kernel-source-path="/lib/modules/\${KERNEL_VER}/build" \
--utility-prefix="/usr" \
--opengl-prefix="/usr"
rm -f /tmp/nvidia.run
EOF
Fabric Manager 和 IMEX 的 RPM 版本必须与 GPU 驱动版本(${NVIDIA_VERSION})完全一致。将预先下载的 RPM 文件注入 chroot 安装。
# 将版本匹配的 RPM 复制到目标盘
sudo cp /tmp/nvidia-fabricmanager.rpm "$MOUNT_DIR/tmp/"
sudo cp /tmp/nvidia-imex.rpm "$MOUNT_DIR/tmp/"
sudo chroot "$MOUNT_DIR" /bin/bash <<'EOF'
set -ex
# 安装 FM 和 IMEX
dnf localinstall -y /tmp/nvidia-fabricmanager.rpm /tmp/nvidia-imex.rpm
# 配置 IMEX channel 参数(Blackwell 必需)
echo "options nvidia NVreg_CreateImexChannel0=1" > /etc/modprobe.d/nvidia-imex.conf
# 启用开机自启
systemctl enable nvidia-fabricmanager
systemctl enable nvidia-imex
rm -f /tmp/nvidia-fabricmanager.rpm /tmp/nvidia-imex.rpm
EOF
删除临时文件,按依赖顺序递归卸载,避免镜像中残留设备锁。
# 删除 chroot 临时文件
sudo rm -f "$MOUNT_DIR/etc/resolv.conf"
sudo rm -f "$MOUNT_DIR/etc/yum.repos.d/tlinux4.repo"
# 按依赖顺序卸载
sudo umount "$MOUNT_DIR/sys/firmware/efi/efivars" || true
sudo umount "$MOUNT_DIR/sys" || true
sudo umount "$MOUNT_DIR/proc" || true
sudo umount "$MOUNT_DIR/dev/pts" || true
sudo umount "$MOUNT_DIR/dev" || true
sudo umount "$MOUNT_DIR/boot/efi" || true
sudo umount "$MOUNT_DIR" || true
# 验证无残留挂载
mount | grep "$MOUNT_DIR" || echo "磁盘已完全卸载"
将构建完成的数据盘快照并生成支持 UEFI + GVNIC + IDPF 的 ARM64 自定义镜像。
# 创建磁盘快照
gcloud compute snapshots create "$SNAPSHOT_NAME" \
--project="$PROJECT_ID" \
--source-disk="tlinux-root-disk" \
--source-disk-zone="$ZONE" \
--storage-location="us"
# 从快照生成自定义镜像(含 UEFI、GVNIC、IDPF 网卡特性)
gcloud compute images create "$IMAGE_NAME" \
--project="$PROJECT_ID" \
--source-snapshot="$SNAPSHOT_NAME" \
--guest-os-features="UEFI_COMPATIBLE,GVNIC,IDPF" \
--architecture="ARM64" \
--storage-location="us"
完成后,镜像名称(如 tlinux-server-4-gb200-v2)即可用于第 2.2 节 Worker VM 创建中的 --image 参数。
用一台非 GPU 的 ARM64 VM 验证镜像的基本启动和 GCE Agent 功能。
# 创建测试 VM
gcloud compute instances create tlinux-test-boot \
--project="$PROJECT_ID" \
--zone="$ZONE" \
--machine-type="t2a-standard-4" \
--image="$IMAGE_NAME" \
--network-interface=nic-type=GVNIC,subnet=default \
--metadata=enable-oslogin=TRUE
| 检查项 | 命令 | 预期结果 |
|---|---|---|
| 内核版本 | uname -r |
TencentOS 6.6.x 内核 |
| GCE Guest Agent | systemctl status google-guest-agent |
active (running) |
| OS Login SSH | gcloud compute ssh tlinux-test-boot --tunnel-through-iap |
SSH 连接成功 |
| GPU 驱动(需 GPU VM) | nvidia-smi |
显示 R580 驱动版本和 GPU 信息 |
| Fabric Manager | systemctl is-enabled nvidia-fabricmanager nvidia-imex |
enabled |
| 串行日志 | gcloud compute instances get-serial-port-output tlinux-test-boot |
无 google-oslogin-cache 错误 |
文档生成于 2026-06-17 · GCP GPU Infrastructure Team
基于 gpu-launchpad-playground 项目 us-east1-d 区域 GB200 A4X 集群验证