ContainerDays 2026 Singapore · 动手工作坊
基于 HAMi 的 Kubernetes GPU 调度动手实践
10 月 27 日 12:30 至 13:15
安装 HAMi。kind 节点把两块模拟 A100 作为 vGPU 容量上报。
共享一块 GPU。细粒度申请、完整拦截链路,以及一个永远调度不上的 Pod。
调度策略。卡维度上的 binpack 与 spread,以及拓扑感知。
超越默认调度器。KAI Scheduler、Volcano 与 DRA 路径。
两套指标体系,以及课后可以尝试什么。
所有实验都在笔记本上运行,GPU 是模拟的
git clone https://github.com/Project-HAMi/hami-workshop
cd hami-workshop/examples/containerdays-singapore
git clone https://github.com/fishman/hami-mock-monitor
docker pull kindest/node:v1.37.0
docker pull busybox:1.36
docker pull ghcr.io/llm-d/llm-d-inference-sim:v0.12.0
kind、kubectl、helm、jq、git这样做的代价
Kubernetes 以整卡为粒度分配 GPU:一块完整设备对应一个任务。

一块 GPU,多个任务
HAMi 为 GPU 调度带来的六件事
用一套工作流管理 GPU、NPU、MLU 等各类加速器。
运行时对显存与算力做硬隔离切分。
binpack、spread 与拓扑感知的放置策略。
Kubernetes 原生 API,支持 DRA 与 CDI。
显存与算力配额,保障共享的公平与稳定。
跨厂商一致的指标与可观测性。
跨厂商统一 API,业务零改造
# NVIDIA:单卡的 15 GB 显存 + 30% 算力
resources:
limits:
nvidia.com/gpu: 1
nvidia.com/gpumem: 15000
nvidia.com/gpucores: 30
# 昇腾 910C:8GB 显存 + 20% 算力
resources:
limits:
huawei.com/Ascend910C: "1"
huawei.com/Ascend910C-core: "20"
huawei.com/Ascend910C-memory: "8192"
hami-schedulerFilter 打分、Bind 阶段基于每张卡的状态决策Allocatelibvgpu.so,负责限额执行代码入口:cmd/scheduler、cmd/device-plugin、cmd/vGPUmonitor、pkg/device/<vendor>
把节点注解转换为 nvidia.com/gpumem 与 nvidia.com/gpucores 容量。真实 device plugin 从 NVML 写入同样的注解。
用负载源替代 GPU,产生 HAMi 的使用量指标(正常来自 vGPUmonitor 与 HAMi-core)。
没有真实 GPU 就少了一样东西:容器内的显存与算力隔离。那是 HAMi-core 的职责,收尾环节在真实显卡上现场演示。
./setup.sh
这个脚本做了什么:
hami-lab 并给节点打标 gpu=onnvidia.com/gpu=20:mock plugin 的健康门槛,2 卡 x 10 切分fake-gpus.json 的节点注解 hami.io/node-nvidia-register,对应真实 device plugin 读取 NVML 后写下的内容completeBinds=true(目录不存在会自动克隆)gpumem 与 gpucores,打印 allocatable,收尾提示 Ready for Lab 2结束时的预期输出:
{ "nvidia.com/gpu": "20", "nvidia.com/gpucores": "200",
"nvidia.com/gpumem": "81920", "nvidia.com/gpumem-percentage": "200" }
kubectl -n kube-system get pods | grep hami
kubectl -n kube-system get cm hami-scheduler-device -o yaml | less
kubectl get node hami-lab-control-plane \
-o jsonpath='{.metadata.annotations.hami\.io/node-nvidia-register}' | jq
[{"id":"GPU-mock-0","index":0,"count":10,"devmem":40960,"devcore":100,
"type":"NVIDIA-A100-SXM4-40GB","health":true,"numa":0,"mode":"hami-core"}, ...]
count:一张卡最多允许多少个 Pod 共享;devmem 单位 MB,devcore 单位百分比Filter 阶段读取kubectl apply -f share.yaml -f too-big.yaml
kubectl wait --for=condition=Ready pod/share-1 pod/share-2 pod/share-3 --timeout=120s
kubectl get pods -o wide
share-1..3 各申请 15000 MB 与单卡 30% 算力,都能运行;too-big 申请 45000 MB,超过任何一张 40960 MB 的卡,一直 Pending:
kubectl get events --field-selector involvedObject.name=too-big | tail -2
# 0/1 nodes are available: 1 2/2 CardInsufficientMemory.
45000 MB 没超过节点总量,但装不进任何一张 40960 MB 的卡。
for p in share-1 share-2 share-3; do
echo -n "$p "; kubectl get pod $p -o jsonpath='{.metadata.annotations.hami\.io/vgpu-devices-allocated}{"\n"}'
done
# share-1 GPU-mock-1,NVIDIA,15000,30:;
# share-2 GPU-mock-0,NVIDIA,15000,30:;
# share-3 GPU-mock-1,NVIDIA,15000,30:;
格式为每个设备 uuid,type,memory_mb,cores:,每个容器以 ; 分隔。device plugin 在 Allocate 阶段读取它。
四步,一个节点
schedulerName 改写为 hami-schedulerFilter 阶段读取节点注解 hami.io/node-nvidia-register(Lab 1 的显卡列表),逐卡检查显存与算力余量,选定物理卡 UUIDBind 阶段把 hami.io/vgpu-devices-allocated 写到 Pod 上,置 hami.io/bind-phase=allocating,并持有节点锁 hami.io/mutex.lockAllocate 挂载 /dev/nvidia* 与 vGPU 文件,目标来自 Pod 注解,随后标记 success 并释放锁completeBinds 存在的原因。没有它,下一个 Pod 要等锁最多 5 分钟。真实节点上,第 4 步还会通过 ld.so.preload 注入 libvgpu.so,HAMi-core 从这一刻起执行显存与算力限额。
| 轴 | 注解 | binpack | spread |
|---|---|---|---|
| 哪个节点 | hami.io/node-scheduler-policy | 更少节点 | 分散到更多节点 |
| 节点上的哪张卡 | hami.io/gpu-scheduler-policy | 同一张卡 | 分散到不同卡 |
kubectl delete -f share.yaml -f too-big.yaml
kubectl apply -f binpack.yaml
kubectl wait --for=condition=Ready pod/pack-1 pod/pack-2 --timeout=120s
for p in pack-1 pack-2; do
echo -n "$p "; kubectl get pod $p -o jsonpath='{.metadata.annotations.hami\.io/vgpu-devices-allocated}{"\n"}'
done
两个 Pod 报告同一个 GPU-mock-X:两个负载共享一张卡。为大盘任务腾出整卡。
kubectl delete -f binpack.yaml
kubectl apply -f spread.yaml
kubectl wait --for=condition=Ready pod/spread-1 pod/spread-2 --timeout=120s
for p in spread-1 spread-2; do
echo -n "$p "; kubectl get pod $p -o jsonpath='{.metadata.annotations.hami\.io/vgpu-devices-allocated}{"\n"}'
done
一卡一 Pod。同一集群、同样的申请、不同的策略、不同的放置。
metadata:
annotations:
hami.io/gpu-scheduler-policy: spread # 逐 Pod 指定 spread 或 binpack
fake-gpus.json 为每张卡携带 numa 字段,与真实 device plugin 从驱动记录的提示相同一个隔离层,多个调度大脑
HAMi 调度器扩展 + HAMi device plugin。
nvidia.com/gpu、gpumem、gpucores
Volcano deviceshare + volcano-vgpu-device-plugin。
volcano.sh/vgpu-number、vgpu-memory、vgpu-cores
KAI GPU 共享 + kai-resource-isolator。
gpu-memory 或 gpu-fraction 注解
三者都在容器内使用 HAMi-core 做隔离。一条规则:每个 GPU 节点只跑一条 device-plugin 路径。
NVIDIA 面向 AI 负载的调度器(CNCF Sandbox):gang 调度、层级公平队列、GPU 纯量共享、拓扑感知放置。
helm install kai-scheduler oci://ghcr.io/nvidia/kai-scheduler --version v0.17.0 \
--set global.gpuSharing=true --set binder.plugins.hamicore.enabled=true \
-n kai-scheduler --create-namespace
libvgpu.sogpu-fraction 或 gpu-memory 注解注入 CUDA_DEVICE_MEMORY_LIMITkind: Job # batch.volcano.sh/v1alpha1
spec:
schedulerName: volcano
minAvailable: 2 # 两个 worker 要么都来,要么都不来
queue: vgpu-queue
tasks:
- replicas: 2
template:
spec:
containers:
- resources:
limits:
volcano.sh/vgpu-number: 1
volcano.sh/vgpu-memory: 2000
volcano.sh/vgpu-cores: 30
deviceshare 插件加 volcano-vgpu-device-plugincapability 可按团队封顶 vGPU 显存,哪怕节点还有余量nvidia.com/gpu 申请改写为 DRA ResourceClaimgithub.com/Project-HAMi/HAMi-DRA
两套指标体系,以及课后可以尝试什么
30HAMi 暴露两套指标。把它们分开:一套是预留,一套是使用。
./metrics.sh scheduler # 分配量,端口 9395
./metrics.sh monitor # 使用量,端口 9394
gpu_memory_allocated_bytesgpu_core_allocated_rationode_gpu_memory_allocated_ratiogpu_shared_countvgpu_memory_allocated_bytesresource_quota_usedvgpu_memory_used_bytesvgpu_memory_limit_bytescontainer_device_utilization_ratiohost_gpu_utilization_ratiohost_gpu_memory_used_bytescontainer_last_kernel_elapsed_seconds所有指标以 hami_ 开头。标签相同:node、device_uuid、namespace、pod。可在 PromQL 中做连接。
kubectl apply -f sim.yaml # llm-d-inference-sim,申请量在 0 到 5 之间波动
watch -n 5 "./metrics.sh monitor | grep 'pod=\"sim\"'"
hami_container_device_utilization_ratio{pod="sim"} 26.4
hami_node_gpu_memory_allocated_ratio{device_uuid="GPU-mock-1"} 0.73
hami_host_gpu_utilization_ratio{device_uuid="GPU-mock-1"} 20.0
sim 是一个兼容 vLLM 的推理模拟器:利用率在 0 到 33 之间波动(负载 x 分配算力)提问:GPU-mock-1 显示已分配 73% 显存、只用了 20%。哪个数字进容量看板,哪个进成本报表?
nvidia.com/gpumem: 3000
Pod 里的 nvidia-smi 显示总显存 3000 MiB。
超额分配在 Pod 内部以 out of memory 失败,邻居负载继续运行。
metrics.sh 适合终端,HAMi WebUI 把同样的数字变成看板。它需要集群里有 Prometheus:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add hami-webui https://project-hami.github.io/HAMi-WebUI
helm upgrade --install prometheus prometheus-community/kube-prometheus-stack \
--version 75.15.1 -n monitoring --create-namespace --set grafana.enabled=false --wait
kubectl apply -f monitoring.yaml # 抓取 :9395 与 :9394
kubectl apply -f webui-rules.yaml # 为 WebUI 转换指标名
helm upgrade --install my-hami-webui hami-webui/hami-webui \
--set externalPrometheus.enabled=true --set dcgm-exporter.enabled=false \
-n kube-system --wait
kubectl -n kube-system port-forward svc/my-hami-webui 3000:3000
打开 http://localhost:3000。

hami_memory_size、hami_core_util、hami_container_vgpu_allocated 等)hami_gpu_memory_limit_bytes、hami_host_gpu_utilization_ratio 等)make test。从 Fit、PatchAnnotations(pkg/device/nvidia/device.go)或 Filter、Bind(pkg/scheduler/scheduler.go)入手。git commit -s。
把 nvidia.com/gpu 申请变成 DRA ResourceClaim 的 webhook。需要 Kubernetes 1.34+。
在 Allocate 中完成 bind(hami-mock-monitor/upstream/mock-device-plugin 里备好了补丁),或新增一个厂商。
更多厂商(Ascend、Hygon)、实验用 Grafana 看板、发布镜像。
找带 good first issue 标签的 issue。离开会场前提一个 draft PR。在 PR 中披露 AI 辅助。
kind delete cluster --name hami-lab
ContainerDays 2026 Singapore · 动手工作坊
基于 HAMi 的 Kubernetes GPU 调度动手实践
10 月 27 日 12:30 至 13:15
安装 HAMi。kind 节点把两块模拟 A100 作为 vGPU 容量上报。
共享一块 GPU。细粒度申请、完整拦截链路,以及一个永远调度不上的 Pod。
调度策略。卡维度上的 binpack 与 spread,以及拓扑感知。
超越默认调度器。KAI Scheduler、Volcano 与 DRA 路径。
两套指标体系,以及课后可以尝试什么。
所有实验都在笔记本上运行,GPU 是模拟的
git clone https://github.com/Project-HAMi/hami-workshop
cd hami-workshop/examples/containerdays-singapore
git clone https://github.com/fishman/hami-mock-monitor
docker pull kindest/node:v1.37.0
docker pull busybox:1.36
docker pull ghcr.io/llm-d/llm-d-inference-sim:v0.12.0
kind、kubectl、helm、jq、git这样做的代价
Kubernetes 以整卡为粒度分配 GPU:一块完整设备对应一个任务。

一块 GPU,多个任务
HAMi 为 GPU 调度带来的六件事
用一套工作流管理 GPU、NPU、MLU 等各类加速器。
运行时对显存与算力做硬隔离切分。
binpack、spread 与拓扑感知的放置策略。
Kubernetes 原生 API,支持 DRA 与 CDI。
显存与算力配额,保障共享的公平与稳定。
跨厂商一致的指标与可观测性。
跨厂商统一 API,业务零改造
# NVIDIA:单卡的 15 GB 显存 + 30% 算力
resources:
limits:
nvidia.com/gpu: 1
nvidia.com/gpumem: 15000
nvidia.com/gpucores: 30
# 昇腾 910C:8GB 显存 + 20% 算力
resources:
limits:
huawei.com/Ascend910C: "1"
huawei.com/Ascend910C-core: "20"
huawei.com/Ascend910C-memory: "8192"
hami-schedulerFilter 打分、Bind 阶段基于每张卡的状态决策Allocatelibvgpu.so,负责限额执行代码入口:cmd/scheduler、cmd/device-plugin、cmd/vGPUmonitor、pkg/device/<vendor>
把节点注解转换为 nvidia.com/gpumem 与 nvidia.com/gpucores 容量。真实 device plugin 从 NVML 写入同样的注解。
用负载源替代 GPU,产生 HAMi 的使用量指标(正常来自 vGPUmonitor 与 HAMi-core)。
没有真实 GPU 就少了一样东西:容器内的显存与算力隔离。那是 HAMi-core 的职责,收尾环节在真实显卡上现场演示。
./setup.sh
这个脚本做了什么:
hami-lab 并给节点打标 gpu=onnvidia.com/gpu=20:mock plugin 的健康门槛,2 卡 x 10 切分fake-gpus.json 的节点注解 hami.io/node-nvidia-register,对应真实 device plugin 读取 NVML 后写下的内容completeBinds=true(目录不存在会自动克隆)gpumem 与 gpucores,打印 allocatable,收尾提示 Ready for Lab 2结束时的预期输出:
{ "nvidia.com/gpu": "20", "nvidia.com/gpucores": "200",
"nvidia.com/gpumem": "81920", "nvidia.com/gpumem-percentage": "200" }
kubectl -n kube-system get pods | grep hami
kubectl -n kube-system get cm hami-scheduler-device -o yaml | less
kubectl get node hami-lab-control-plane \
-o jsonpath='{.metadata.annotations.hami\.io/node-nvidia-register}' | jq
[{"id":"GPU-mock-0","index":0,"count":10,"devmem":40960,"devcore":100,
"type":"NVIDIA-A100-SXM4-40GB","health":true,"numa":0,"mode":"hami-core"}, ...]
count:一张卡最多允许多少个 Pod 共享;devmem 单位 MB,devcore 单位百分比Filter 阶段读取kubectl apply -f share.yaml -f too-big.yaml
kubectl wait --for=condition=Ready pod/share-1 pod/share-2 pod/share-3 --timeout=120s
kubectl get pods -o wide
share-1..3 各申请 15000 MB 与单卡 30% 算力,都能运行;too-big 申请 45000 MB,超过任何一张 40960 MB 的卡,一直 Pending:
kubectl get events --field-selector involvedObject.name=too-big | tail -2
# 0/1 nodes are available: 1 2/2 CardInsufficientMemory.
45000 MB 没超过节点总量,但装不进任何一张 40960 MB 的卡。
for p in share-1 share-2 share-3; do
echo -n "$p "; kubectl get pod $p -o jsonpath='{.metadata.annotations.hami\.io/vgpu-devices-allocated}{"\n"}'
done
# share-1 GPU-mock-1,NVIDIA,15000,30:;
# share-2 GPU-mock-0,NVIDIA,15000,30:;
# share-3 GPU-mock-1,NVIDIA,15000,30:;
格式为每个设备 uuid,type,memory_mb,cores:,每个容器以 ; 分隔。device plugin 在 Allocate 阶段读取它。
四步,一个节点
schedulerName 改写为 hami-schedulerFilter 阶段读取节点注解 hami.io/node-nvidia-register(Lab 1 的显卡列表),逐卡检查显存与算力余量,选定物理卡 UUIDBind 阶段把 hami.io/vgpu-devices-allocated 写到 Pod 上,置 hami.io/bind-phase=allocating,并持有节点锁 hami.io/mutex.lockAllocate 挂载 /dev/nvidia* 与 vGPU 文件,目标来自 Pod 注解,随后标记 success 并释放锁completeBinds 存在的原因。没有它,下一个 Pod 要等锁最多 5 分钟。真实节点上,第 4 步还会通过 ld.so.preload 注入 libvgpu.so,HAMi-core 从这一刻起执行显存与算力限额。
| 轴 | 注解 | binpack | spread |
|---|---|---|---|
| 哪个节点 | hami.io/node-scheduler-policy | 更少节点 | 分散到更多节点 |
| 节点上的哪张卡 | hami.io/gpu-scheduler-policy | 同一张卡 | 分散到不同卡 |
kubectl delete -f share.yaml -f too-big.yaml
kubectl apply -f binpack.yaml
kubectl wait --for=condition=Ready pod/pack-1 pod/pack-2 --timeout=120s
for p in pack-1 pack-2; do
echo -n "$p "; kubectl get pod $p -o jsonpath='{.metadata.annotations.hami\.io/vgpu-devices-allocated}{"\n"}'
done
两个 Pod 报告同一个 GPU-mock-X:两个负载共享一张卡。为大盘任务腾出整卡。
kubectl delete -f binpack.yaml
kubectl apply -f spread.yaml
kubectl wait --for=condition=Ready pod/spread-1 pod/spread-2 --timeout=120s
for p in spread-1 spread-2; do
echo -n "$p "; kubectl get pod $p -o jsonpath='{.metadata.annotations.hami\.io/vgpu-devices-allocated}{"\n"}'
done
一卡一 Pod。同一集群、同样的申请、不同的策略、不同的放置。
metadata:
annotations:
hami.io/gpu-scheduler-policy: spread # 逐 Pod 指定 spread 或 binpack
fake-gpus.json 为每张卡携带 numa 字段,与真实 device plugin 从驱动记录的提示相同一个隔离层,多个调度大脑
HAMi 调度器扩展 + HAMi device plugin。
nvidia.com/gpu、gpumem、gpucores
Volcano deviceshare + volcano-vgpu-device-plugin。
volcano.sh/vgpu-number、vgpu-memory、vgpu-cores
KAI GPU 共享 + kai-resource-isolator。
gpu-memory 或 gpu-fraction 注解
三者都在容器内使用 HAMi-core 做隔离。一条规则:每个 GPU 节点只跑一条 device-plugin 路径。
NVIDIA 面向 AI 负载的调度器(CNCF Sandbox):gang 调度、层级公平队列、GPU 纯量共享、拓扑感知放置。
helm install kai-scheduler oci://ghcr.io/nvidia/kai-scheduler --version v0.17.0 \
--set global.gpuSharing=true --set binder.plugins.hamicore.enabled=true \
-n kai-scheduler --create-namespace
libvgpu.sogpu-fraction 或 gpu-memory 注解注入 CUDA_DEVICE_MEMORY_LIMITkind: Job # batch.volcano.sh/v1alpha1
spec:
schedulerName: volcano
minAvailable: 2 # 两个 worker 要么都来,要么都不来
queue: vgpu-queue
tasks:
- replicas: 2
template:
spec:
containers:
- resources:
limits:
volcano.sh/vgpu-number: 1
volcano.sh/vgpu-memory: 2000
volcano.sh/vgpu-cores: 30
deviceshare 插件加 volcano-vgpu-device-plugincapability 可按团队封顶 vGPU 显存,哪怕节点还有余量nvidia.com/gpu 申请改写为 DRA ResourceClaimgithub.com/Project-HAMi/HAMi-DRA
两套指标体系,以及课后可以尝试什么
HAMi 暴露两套指标。把它们分开:一套是预留,一套是使用。
./metrics.sh scheduler # 分配量,端口 9395
./metrics.sh monitor # 使用量,端口 9394
gpu_memory_allocated_bytesgpu_core_allocated_rationode_gpu_memory_allocated_ratiogpu_shared_countvgpu_memory_allocated_bytesresource_quota_usedvgpu_memory_used_bytesvgpu_memory_limit_bytescontainer_device_utilization_ratiohost_gpu_utilization_ratiohost_gpu_memory_used_bytescontainer_last_kernel_elapsed_seconds所有指标以 hami_ 开头。标签相同:node、device_uuid、namespace、pod。可在 PromQL 中做连接。
kubectl apply -f sim.yaml # llm-d-inference-sim,申请量在 0 到 5 之间波动
watch -n 5 "./metrics.sh monitor | grep 'pod=\"sim\"'"
hami_container_device_utilization_ratio{pod="sim"} 26.4
hami_node_gpu_memory_allocated_ratio{device_uuid="GPU-mock-1"} 0.73
hami_host_gpu_utilization_ratio{device_uuid="GPU-mock-1"} 20.0
sim 是一个兼容 vLLM 的推理模拟器:利用率在 0 到 33 之间波动(负载 x 分配算力)提问:GPU-mock-1 显示已分配 73% 显存、只用了 20%。哪个数字进容量看板,哪个进成本报表?
nvidia.com/gpumem: 3000
Pod 里的 nvidia-smi 显示总显存 3000 MiB。
超额分配在 Pod 内部以 out of memory 失败,邻居负载继续运行。
metrics.sh 适合终端,HAMi WebUI 把同样的数字变成看板。它需要集群里有 Prometheus:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add hami-webui https://project-hami.github.io/HAMi-WebUI
helm upgrade --install prometheus prometheus-community/kube-prometheus-stack \
--version 75.15.1 -n monitoring --create-namespace --set grafana.enabled=false --wait
kubectl apply -f monitoring.yaml # 抓取 :9395 与 :9394
kubectl apply -f webui-rules.yaml # 为 WebUI 转换指标名
helm upgrade --install my-hami-webui hami-webui/hami-webui \
--set externalPrometheus.enabled=true --set dcgm-exporter.enabled=false \
-n kube-system --wait
kubectl -n kube-system port-forward svc/my-hami-webui 3000:3000
打开 http://localhost:3000。

hami_memory_size、hami_core_util、hami_container_vgpu_allocated 等)hami_gpu_memory_limit_bytes、hami_host_gpu_utilization_ratio 等)make test。从 Fit、PatchAnnotations(pkg/device/nvidia/device.go)或 Filter、Bind(pkg/scheduler/scheduler.go)入手。git commit -s。
把 nvidia.com/gpu 申请变成 DRA ResourceClaim 的 webhook。需要 Kubernetes 1.34+。
在 Allocate 中完成 bind(hami-mock-monitor/upstream/mock-device-plugin 里备好了补丁),或新增一个厂商。
更多厂商(Ascend、Hygon)、实验用 Grafana 看板、发布镜像。
找带 good first issue 标签的 issue。离开会场前提一个 draft PR。在 PR 中披露 AI 辅助。
kind delete cluster --name hami-lab
ContainerDays 2026 Singapore · 动手工作坊
基于 HAMi 的 Kubernetes GPU 调度动手实践
10 月 27 日 12:30 至 13:15
安装 HAMi。kind 节点把两块模拟 A100 作为 vGPU 容量上报。
共享一块 GPU。细粒度申请、完整拦截链路,以及一个永远调度不上的 Pod。
调度策略。卡维度上的 binpack 与 spread,以及拓扑感知。
超越默认调度器。KAI Scheduler、Volcano 与 DRA 路径。
两套指标体系,以及课后可以尝试什么。
所有实验都在笔记本上运行,GPU 是模拟的
git clone https://github.com/Project-HAMi/hami-workshop
cd hami-workshop/examples/containerdays-singapore
git clone https://github.com/fishman/hami-mock-monitor
docker pull kindest/node:v1.37.0
docker pull busybox:1.36
docker pull ghcr.io/llm-d/llm-d-inference-sim:v0.12.0
kind、kubectl、helm、jq、git这样做的代价
Kubernetes 以整卡为粒度分配 GPU:一块完整设备对应一个任务。

一块 GPU,多个任务
HAMi 为 GPU 调度带来的六件事
用一套工作流管理 GPU、NPU、MLU 等各类加速器。
运行时对显存与算力做硬隔离切分。
binpack、spread 与拓扑感知的放置策略。
Kubernetes 原生 API,支持 DRA 与 CDI。
显存与算力配额,保障共享的公平与稳定。
跨厂商一致的指标与可观测性。
跨厂商统一 API,业务零改造
# NVIDIA:单卡的 15 GB 显存 + 30% 算力
resources:
limits:
nvidia.com/gpu: 1
nvidia.com/gpumem: 15000
nvidia.com/gpucores: 30
# 昇腾 910C:8GB 显存 + 20% 算力
resources:
limits:
huawei.com/Ascend910C: "1"
huawei.com/Ascend910C-core: "20"
huawei.com/Ascend910C-memory: "8192"
hami-schedulerFilter 打分、Bind 阶段基于每张卡的状态决策Allocatelibvgpu.so,负责限额执行代码入口:cmd/scheduler、cmd/device-plugin、cmd/vGPUmonitor、pkg/device/<vendor>
把节点注解转换为 nvidia.com/gpumem 与 nvidia.com/gpucores 容量。真实 device plugin 从 NVML 写入同样的注解。
用负载源替代 GPU,产生 HAMi 的使用量指标(正常来自 vGPUmonitor 与 HAMi-core)。
没有真实 GPU 就少了一样东西:容器内的显存与算力隔离。那是 HAMi-core 的职责,收尾环节在真实显卡上现场演示。
./setup.sh
这个脚本做了什么:
hami-lab 并给节点打标 gpu=onnvidia.com/gpu=20:mock plugin 的健康门槛,2 卡 x 10 切分fake-gpus.json 的节点注解 hami.io/node-nvidia-register,对应真实 device plugin 读取 NVML 后写下的内容completeBinds=true(目录不存在会自动克隆)gpumem 与 gpucores,打印 allocatable,收尾提示 Ready for Lab 2结束时的预期输出:
{ "nvidia.com/gpu": "20", "nvidia.com/gpucores": "200",
"nvidia.com/gpumem": "81920", "nvidia.com/gpumem-percentage": "200" }
kubectl -n kube-system get pods | grep hami
kubectl -n kube-system get cm hami-scheduler-device -o yaml | less
kubectl get node hami-lab-control-plane \
-o jsonpath='{.metadata.annotations.hami\.io/node-nvidia-register}' | jq
[{"id":"GPU-mock-0","index":0,"count":10,"devmem":40960,"devcore":100,
"type":"NVIDIA-A100-SXM4-40GB","health":true,"numa":0,"mode":"hami-core"}, ...]
count:一张卡最多允许多少个 Pod 共享;devmem 单位 MB,devcore 单位百分比Filter 阶段读取kubectl apply -f share.yaml -f too-big.yaml
kubectl wait --for=condition=Ready pod/share-1 pod/share-2 pod/share-3 --timeout=120s
kubectl get pods -o wide
share-1..3 各申请 15000 MB 与单卡 30% 算力,都能运行;too-big 申请 45000 MB,超过任何一张 40960 MB 的卡,一直 Pending:
kubectl get events --field-selector involvedObject.name=too-big | tail -2
# 0/1 nodes are available: 1 2/2 CardInsufficientMemory.
45000 MB 没超过节点总量,但装不进任何一张 40960 MB 的卡。
for p in share-1 share-2 share-3; do
echo -n "$p "; kubectl get pod $p -o jsonpath='{.metadata.annotations.hami\.io/vgpu-devices-allocated}{"\n"}'
done
# share-1 GPU-mock-1,NVIDIA,15000,30:;
# share-2 GPU-mock-0,NVIDIA,15000,30:;
# share-3 GPU-mock-1,NVIDIA,15000,30:;
格式为每个设备 uuid,type,memory_mb,cores:,每个容器以 ; 分隔。device plugin 在 Allocate 阶段读取它。
四步,一个节点
schedulerName 改写为 hami-schedulerFilter 阶段读取节点注解 hami.io/node-nvidia-register(Lab 1 的显卡列表),逐卡检查显存与算力余量,选定物理卡 UUIDBind 阶段把 hami.io/vgpu-devices-allocated 写到 Pod 上,置 hami.io/bind-phase=allocating,并持有节点锁 hami.io/mutex.lockAllocate 挂载 /dev/nvidia* 与 vGPU 文件,目标来自 Pod 注解,随后标记 success 并释放锁completeBinds 存在的原因。没有它,下一个 Pod 要等锁最多 5 分钟。真实节点上,第 4 步还会通过 ld.so.preload 注入 libvgpu.so,HAMi-core 从这一刻起执行显存与算力限额。
| 轴 | 注解 | binpack | spread |
|---|---|---|---|
| 哪个节点 | hami.io/node-scheduler-policy | 更少节点 | 分散到更多节点 |
| 节点上的哪张卡 | hami.io/gpu-scheduler-policy | 同一张卡 | 分散到不同卡 |
kubectl delete -f share.yaml -f too-big.yaml
kubectl apply -f binpack.yaml
kubectl wait --for=condition=Ready pod/pack-1 pod/pack-2 --timeout=120s
for p in pack-1 pack-2; do
echo -n "$p "; kubectl get pod $p -o jsonpath='{.metadata.annotations.hami\.io/vgpu-devices-allocated}{"\n"}'
done
两个 Pod 报告同一个 GPU-mock-X:两个负载共享一张卡。为大盘任务腾出整卡。
kubectl delete -f binpack.yaml
kubectl apply -f spread.yaml
kubectl wait --for=condition=Ready pod/spread-1 pod/spread-2 --timeout=120s
for p in spread-1 spread-2; do
echo -n "$p "; kubectl get pod $p -o jsonpath='{.metadata.annotations.hami\.io/vgpu-devices-allocated}{"\n"}'
done
一卡一 Pod。同一集群、同样的申请、不同的策略、不同的放置。
metadata:
annotations:
hami.io/gpu-scheduler-policy: spread # 逐 Pod 指定 spread 或 binpack
fake-gpus.json 为每张卡携带 numa 字段,与真实 device plugin 从驱动记录的提示相同一个隔离层,多个调度大脑
HAMi 调度器扩展 + HAMi device plugin。
nvidia.com/gpu、gpumem、gpucores
Volcano deviceshare + volcano-vgpu-device-plugin。
volcano.sh/vgpu-number、vgpu-memory、vgpu-cores
KAI GPU 共享 + kai-resource-isolator。
gpu-memory 或 gpu-fraction 注解
三者都在容器内使用 HAMi-core 做隔离。一条规则:每个 GPU 节点只跑一条 device-plugin 路径。
NVIDIA 面向 AI 负载的调度器(CNCF Sandbox):gang 调度、层级公平队列、GPU 纯量共享、拓扑感知放置。
helm install kai-scheduler oci://ghcr.io/nvidia/kai-scheduler --version v0.17.0 \
--set global.gpuSharing=true --set binder.plugins.hamicore.enabled=true \
-n kai-scheduler --create-namespace
libvgpu.sogpu-fraction 或 gpu-memory 注解注入 CUDA_DEVICE_MEMORY_LIMITkind: Job # batch.volcano.sh/v1alpha1
spec:
schedulerName: volcano
minAvailable: 2 # 两个 worker 要么都来,要么都不来
queue: vgpu-queue
tasks:
- replicas: 2
template:
spec:
containers:
- resources:
limits:
volcano.sh/vgpu-number: 1
volcano.sh/vgpu-memory: 2000
volcano.sh/vgpu-cores: 30
deviceshare 插件加 volcano-vgpu-device-plugincapability 可按团队封顶 vGPU 显存,哪怕节点还有余量nvidia.com/gpu 申请改写为 DRA ResourceClaimgithub.com/Project-HAMi/HAMi-DRA
两套指标体系,以及课后可以尝试什么
HAMi 暴露两套指标。把它们分开:一套是预留,一套是使用。
./metrics.sh scheduler # 分配量,端口 9395
./metrics.sh monitor # 使用量,端口 9394
gpu_memory_allocated_bytesgpu_core_allocated_rationode_gpu_memory_allocated_ratiogpu_shared_countvgpu_memory_allocated_bytesresource_quota_usedvgpu_memory_used_bytesvgpu_memory_limit_bytescontainer_device_utilization_ratiohost_gpu_utilization_ratiohost_gpu_memory_used_bytescontainer_last_kernel_elapsed_seconds所有指标以 hami_ 开头。标签相同:node、device_uuid、namespace、pod。可在 PromQL 中做连接。
kubectl apply -f sim.yaml # llm-d-inference-sim,申请量在 0 到 5 之间波动
watch -n 5 "./metrics.sh monitor | grep 'pod=\"sim\"'"
hami_container_device_utilization_ratio{pod="sim"} 26.4
hami_node_gpu_memory_allocated_ratio{device_uuid="GPU-mock-1"} 0.73
hami_host_gpu_utilization_ratio{device_uuid="GPU-mock-1"} 20.0
sim 是一个兼容 vLLM 的推理模拟器:利用率在 0 到 33 之间波动(负载 x 分配算力)提问:GPU-mock-1 显示已分配 73% 显存、只用了 20%。哪个数字进容量看板,哪个进成本报表?
nvidia.com/gpumem: 3000
Pod 里的 nvidia-smi 显示总显存 3000 MiB。
超额分配在 Pod 内部以 out of memory 失败,邻居负载继续运行。
metrics.sh 适合终端,HAMi WebUI 把同样的数字变成看板。它需要集群里有 Prometheus:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add hami-webui https://project-hami.github.io/HAMi-WebUI
helm upgrade --install prometheus prometheus-community/kube-prometheus-stack \
--version 75.15.1 -n monitoring --create-namespace --set grafana.enabled=false --wait
kubectl apply -f monitoring.yaml # 抓取 :9395 与 :9394
kubectl apply -f webui-rules.yaml # 为 WebUI 转换指标名
helm upgrade --install my-hami-webui hami-webui/hami-webui \
--set externalPrometheus.enabled=true --set dcgm-exporter.enabled=false \
-n kube-system --wait
kubectl -n kube-system port-forward svc/my-hami-webui 3000:3000
打开 http://localhost:3000。

hami_memory_size、hami_core_util、hami_container_vgpu_allocated 等)hami_gpu_memory_limit_bytes、hami_host_gpu_utilization_ratio 等)make test。从 Fit、PatchAnnotations(pkg/device/nvidia/device.go)或 Filter、Bind(pkg/scheduler/scheduler.go)入手。git commit -s。
把 nvidia.com/gpu 申请变成 DRA ResourceClaim 的 webhook。需要 Kubernetes 1.34+。
在 Allocate 中完成 bind(hami-mock-monitor/upstream/mock-device-plugin 里备好了补丁),或新增一个厂商。
更多厂商(Ascend、Hygon)、实验用 Grafana 看板、发布镜像。
找带 good first issue 标签的 issue。离开会场前提一个 draft PR。在 PR 中披露 AI 辅助。
kind delete cluster --name hami-lab