实验 10: 使用虚拟 GPU 进行拓扑感知调度
本实验将带你在单节点本地集群上使用 nvml-mock 和 HAMi 模拟一套非对称的 PCIe 拓扑。你将启用 HAMi 的拓扑感知调度器,注入自定义的连接性分数来孤立某一张 GPU,然后验证:多 GPU 请求会避开这张被孤立的 GPU,而单 GPU 请求则会选中它。无需物理 GPU —— 一切都在本地 Kubernetes 集群内完成。
你将得到什么
完成本实验后,你将获得:
- 一个使用 nvml-mock 模拟 8 张 A100 GPU 的本地集群(HAMi 将每张 GPU 切分为 10 份后共 80 个虚拟槽位)
- 从已验证的提交构建并安装 HAMi,启用拓扑感知调度(
gpuSchedulerPolicy=topology-aware) - 一个节点注解(
hami.io/node-nvidia-score),定义了一套自定义拓扑,其中 GPU7 与其余所有 GPU 的连接都很差 - 证明调度器在为单个 Pod 分配 2 张 GPU(多 GPU 请求)时会避开 GPU7,而在单 GPU 请求时会选中 GPU7 —— 这两种行为都来自同一个
topology-aware策略,无需额外开关 - 通过调度器自身日志洞察其拓扑决策过程
本实验中的虚拟 GPU 拓扑分数是人为构造的 —— nvml-mock 默认上报对称的连接性,我们手动覆盖节点注解来人为制造一张"连接最差"的 GPU。本实验验证的是调度器针对已知拓扑的处理逻辑,而不是真实的 PCIe/NVLink 测量结果。
分数方向很重要。 在 HAMi 实际的调度代码中(pkg/device/nvidia/device.go),成对分数越高代表连接性越好(类似 NVLink),分数越低代表越差。多 GPU 请求会选择总分最高的组合(通过 computeBestCombination);单 GPU 请求会选择分数最低的设备(通过 computeWorstSingleCard)—— 这两条路径都由 Fit() 中同一个 needTopology 判断门控,完全由 gpuSchedulerPolicy=topology-aware 驱动。单 GPU 评分没有单独的开关;策略一旦设置,它默认就是开启的。为了让 GPU7 成为连接最差的设备,我们把它的分数设在 50 基准线以下,而不是以上。
安装概览
整个实验共分 7 个步骤:
| 步骤 | 目的 | 解决的问题 |
|---|---|---|
| 搭建并验证环境 | 创建/验证集群,检查工具 | 确保有可用的 Kubernetes 集群 |
| 构建 nvml-mock | 模拟 8 张 A100 GPU | 为 device plugin 提供可读取的 NVML 拓扑 |
| 构建并安装 HAMi | 部署带 topology-aware 策略的调度器 | 使调度器在单 GPU 和多 GPU 请求中都能考虑连接性分数 |
| 孤立 GPU7 | 冻结 device plugin 并覆盖拓扑注解 | 制造一张已知连接性差的 GPU 用于测试 |
| 提高调度器日志级别 | 将调度器日志级别调到 -v=6 | 暴露 best device combination / worst device 拓扑日志行 |
| 验证调度行为 | 多 GPU Pod + 单 GPU Pod | 确认避开/选中行为与注入的拓扑一致 |
| 观察逐设备分数 | -v=6 级别的调度器日志 | 展示真实的 best device combination / worst device 日志行 |
前提条件
- macOS (OrbStack)
- Linux (Ubuntu + kind)
- macOS,Intel 或 Apple Silicon
- 已安装 OrbStack 并启用内置 Kubernetes
docker、git、python3- 可访问 GitHub、GHCR 和 HAMi Helm 仓库
- 至少 8 GB 空闲内存和 4 个 CPU 核心
OrbStack 自带内置 Kubernetes(基于 k3s),无需单独安装 kind 或 Docker Desktop。它占用资源更少、启动更快,是 macOS 上本地实验的首选。
检查 Helm:
helm version
如果未安装 Helm:
brew install helm
- Ubuntu 20.04 LTS 或更高版本,x86_64 或 ARM64
- Docker Engine、
kindv0.20+、kubectl、Helm 3.x git、python3- 可访问 GitHub、GHCR 和 HAMi Helm 仓库
- 至少 8 GB 空闲内存和 4 个 CPU 核心
kind(Kubernetes IN Docker)在 Docker 容器内运行完整的 Kubernetes 集群。它可以在任何安装了 Docker 的 Linux 发行版上运行,无需特殊的系统集成,是 Linux 上本地 Kubernetes 开发的标准工具。
如果需要安装以上前提条件,运行以下命令块:
# Docker Engine
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker
# git、python3
sudo apt-get update && sudo apt-get install -y git python3
# kind(自动适配架构:amd64 或 arm64)
KIND_VERSION=v0.23.0
ARCH=$(dpkg --print-architecture)
curl -Lo ./kind "https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-${ARCH}"
chmod +x ./kind && sudo mv ./kind /usr/local/bin/kind
# kubectl(自动适配架构:amd64 或 arm64)
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/${ARCH}/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl && rm kubectl
# Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
步骤 1:搭建并验证本地环境
为什么:我们需要一个可用的 Kubernetes 集群来部署 nvml-mock 和 HAMi。
- macOS
- Linux
在 OrbStack UI 中启用后,其内置的 Kubernetes 会自动启动。验证集群是否就绪:
kubectl version
示例输出:
Client Version: v1.33.9
Kustomize Version: v5.6.0
Server Version: v1.33.9+orb1
Server Version 中的 +orb1 后缀标识了 OrbStack 内置的 Kubernetes 发行版。
创建本地 Kubernetes 集群:
kind create cluster --name topo-lab --image kindest/node:v1.35.0
示例输出:
Creating cluster "topo-lab" ...
✓ Ensuring node image (kindest/node:v1.35.0) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-topo-lab"
--name topo-lab 参数为集群命名,生成的节点将被称为 topo-lab-control-plane。kind 会自动将 kubectl 上下文切换到新集群。
设置 NODE_NAME 变量
本实验后续步骤都使用 NODE_NAME 这个 shell 变量,避免在命令中硬编码节点名。每次打开新的终端会话时都要重新设置,因为关闭 shell 后该变量会丢失。
NODE_NAME=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
echo "NODE_NAME=${NODE_NAME}"
从这里开始,大多数命令在 macOS 和 Linux 上是一致的。仍有少数步骤存在差异 —— 步骤 2.3 和步骤 3.1 中分别有一条仅 Linux 需要执行的额外 kind load 命令 —— 其余部分完全相同,只是示例输出中的节点名不同。
步骤 2:构建并部署 nvml-mock(8 张模拟 A100 GPU)
为什么:nvml-mock 提供虚拟 GPU 设备和 NVML 拓扑数据,供 HAMi 发现并评分。
2.1 克隆 NVIDIA 测试基础设施仓库
git clone https://github.com/NVIDIA/k8s-test-infra.git
cd k8s-test-infra
2.2 构建 Mock Docker 镜像
docker build -t nvml-mock:local -f deployments/nvml-mock/Dockerfile .
2.3 将镜像加载到集群并安装
- macOS
- Linux
在 macOS 上使用 OrbStack 时,本地 Docker 镜像可直接被集群访问 —— 无需 kind load。
helm install nvml-mock oci://ghcr.io/nvidia/k8s-test-infra/chart/nvml-mock \
--set image.repository=nvml-mock \
--set image.tag=local \
--wait --timeout 120s
在 Linux 上使用 kind 时,必须先将镜像加载到集群,再安装 chart。如果跳过 kind load,GPU 标签会显示为 <none>。
kind load docker-image nvml-mock:local --name topo-lab
helm install nvml-mock oci://ghcr.io/nvidia/k8s-test-infra/chart/nvml-mock \
--set image.repository=nvml-mock \
--set image.tag=local \
--wait --timeout 120s
2.4 验证 GPU 标签已生效
kubectl get node ${NODE_NAME} -o custom-columns=NAME:.metadata.name,GPU:.metadata.labels.nvidia\\.com/gpu\\.present
NAME GPU
topo-lab-control-plane true
GPU列显示true,说明 nvml-mock 已经在节点上注册了模拟 GPU 设备。
步骤 3:从 main 分支构建 HAMi,并以拓扑感知调度模式安装
为什么:HAMi 的调度器会替代默认 Kubernetes 调度器处理 GPU Pod,启用 topology-aware 策略后可以做出拓扑感知的调度决策。
3.1 克隆 HAMi 并检出已验证的提交
本实验基于 2026-07-14 时的 HAMi main 分支验证通过。如果你当前的 main 分支表现不符合预期,请使用确切的提交 a1b418c:
cd ~
git clone https://github.com/Project-HAMi/HAMi.git
cd HAMi
git log --until=2026-07-14 --oneline -1
git checkout a1b418c
git submodule update --init --recursive
docker build -t hami:local -f docker/Dockerfile .
- macOS
- Linux
# 镜像已在本地可用,无需加载
kind load docker-image hami:local --name topo-lab
3.2 以拓扑感知策略安装 HAMi
直接从运行中的集群读取 Kubernetes 服务端版本,让调度器内置的 kube-scheduler 二进制文件与实际的 API server 版本保持一致。OrbStack 上报的版本类似 v1.33.9+orb1,而 kind 上报的是 v1.35.0,如果写死一个固定标签,只可能在其中一个平台上正确:
K8S_VERSION=$(kubectl version -o json | python3 -c "import json,sys; print(json.load(sys.stdin)['serverVersion']['gitVersion'].split('+')[0])")
echo "K8S_VERSION=${K8S_VERSION}"
helm install hami ./charts/hami \
-n kube-system \
--set devicePlugin.image.repository=hami \
--set devicePlugin.image.tag=local \
--set scheduler.image.repository=hami \
--set scheduler.image.tag=local \
--set devicePlugin.nvidiaDriverRoot=/var/lib/nvml-mock/driver \
--set scheduler.kubeScheduler.imageTag=${K8S_VERSION} \
--set scheduler.defaultSchedulerPolicy.gpuSchedulerPolicy=topology-aware
gpuSchedulerPolicy=topology-aware告诉调度器在选择 GPU 时使用节点注解中的成对连接性分数。这一个设置同时覆盖多 GPU 和单 GPU 两种请求 —— HAMi 的Fit()函数(pkg/device/nvidia/device.go)只检查一次该策略,然后在内部分支处理:多 GPU 请求走computeBestCombination(),单 GPU 请求走computeWorstSingleCard()。单 GPU 评分没有单独需要打开的开关。scheduler.kubeScheduler.imageTag是从实际运行的集群读取的,而不是写死的,因为 OrbStack 和 kind 提供的 Kubernetes 版本不同,这样可以让调度器内置的kube-scheduler二进制文件始终与实际运行的 API server 保持一致。