diff --git a/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/labs/kueue-hami-vgpu.md b/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/labs/kueue-hami-vgpu.md new file mode 100644 index 00000000..25b311a9 --- /dev/null +++ b/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/labs/kueue-hami-vgpu.md @@ -0,0 +1,393 @@ +--- +title: "实验 9: 使用 Kueue 管理 HAMi vGPU 队列" +description: "在 Pod 进入调度器之前,按 vGPU 数量、显存和算力执行配额准入。" +sidebar_label: "实验 9: Kueue + HAMi vGPU" +lab: + level: Advanced + duration: 约 60 分钟 + environment: 单张 NVIDIA Tesla T4 的 Kubernetes 1.36 集群 + authors: + - lixd + verified: "2026-07-13" +tags: + - kueue + - DRA + - vgpu + - quota +toc_max_heading_level: 2 +--- + +本实验把 HAMi GPU 切分与 Kueue 准入控制接到一起。HAMi 为 Pod 分配显存和算力切片,Kueue 在 Job 准入前统计这些切片。你将提交三个相同规格的 Job:前两个正常运行,第三个因为 vGPU、显存和算力配额已经用完而保持挂起。 + +文中的输出来自一套实际环境:Kubernetes 1.36.1、containerd 2.2.4、Kueue 0.18.1、HAMi 2.9.0,GPU 为一张 15 GiB Tesla T4。 + +:::warning 版本相关的 API + +本实验使用 Kueue 0.18.1 提供的 `v1beta2` API 和 `ResourceTransformation` 配置。若使用其他 Kueue 版本,请先核对对应版本的发布说明。 + +::: + +## 你将学到什么 + +- 以 DRA 兼容模式安装 HAMi 2.9.0; +- 验证 `4 GiB / 50%` 扩展资源请求如何转换为 DRA `ResourceClaim`; +- 把 HAMi 按单个 vGPU 表达的显存和算力请求转换成 Kueue 总量配额; +- 在准入阶段挂起超额 Job,避免 Pod 进入调度阶段后才 Pending。 + +## 实验概览 + +```mermaid +%% title: Kueue 与 HAMi vGPU 实验流程 +flowchart LR + Step1["步骤 1
安装 HAMi DRA"] --> Step2["步骤 2
检查设备容量"] + Step2 --> Step3["步骤 3
验证一个 vGPU"] + Step3 --> Step4["步骤 4
配置资源转换"] + Step4 --> Step5["步骤 5
创建队列配额"] + Step5 --> Step6["步骤 6
准入两个 Job"] + Step6 --> Step7["步骤 7
阻塞第三个 Job"] +``` + +## 前提条件 + +- Kubernetes 1.36 集群,已安装 Kueue 0.18.1,并启用 `batch/job` 集成。 +- 一个 NVIDIA GPU 节点。本文验证环境使用 15 GiB Tesla T4。 +- GPU Operator 或等价组件已提供 NVIDIA 驱动和 GPU Feature Discovery 标签。 +- NVIDIA Device Plugin 已关闭。本实验由 HAMi 接管 GPU 设备路径。 +- 有权限安装集群级资源、编辑 Kueue manager 配置的 `kubectl` 和 Helm 环境。 +- 已安装 cert-manager,HAMi DRA webhook 需要用它签发 TLS 证书。 +- [`tutorials/labs/examples/09-kueue-hami-vgpu/`](https://github.com/Project-HAMi/website/tree/master/tutorials/labs/examples/09-kueue-hami-vgpu) 中的实验清单。 + +如果 GPU Operator 管理 GPU 节点,安装或升级时需关闭它的 device plugin: + +```text +--set devicePlugin.enabled=false +``` + +## 步骤 1: 以 DRA 兼容模式安装 HAMi + +创建实验命名空间。通过 GPU Feature Discovery 标签选择 T4 节点,不要照抄验证环境里的节点名,然后为 HAMi 添加标签: + +```bash +kubectl create namespace hami-kueue-demo +export GPU_NODE=$(kubectl get nodes -l nvidia.com/gpu.product=Tesla-T4 \ + -o jsonpath='{.items[0].metadata.name}') +test -n "${GPU_NODE}" && echo "GPU_NODE=${GPU_NODE}" +kubectl label node "${GPU_NODE}" gpu=on --overwrite +``` + +安装 HAMi 2.9.0,开启 DRA,关闭传统 device plugin: + +```bash +helm repo add hami-charts https://project-hami.github.io/HAMi/ +helm repo update + +helm install hami hami-charts/hami \ + --namespace hami-system \ + --create-namespace \ + --version 2.9.0 \ + --set dra.enabled=true \ + --set devicePlugin.enabled=false +``` + +:::important + +不要同时开启 HAMi DRA 和传统 device plugin 模式。如果 NVIDIA 驱动直接安装在主机上,而不是由 GPU Operator 管理,还需设置 `hami-dra.drivers.nvidia.containerDriver=false`。 + +::: + +等待三个 DRA 组件启动: + +```bash +kubectl get pods -n hami-system +``` + +```plaintext +NAME READY STATUS +hami-dra-driver-kubelet-plugin-fb4zm 1/1 Running +hami-hami-dra-monitor-7b8df84bd-jsjrd 1/1 Running +hami-hami-dra-webhook-7bb65cbcc5-g5742 1/1 Running +``` + +## 步骤 2: 检查 HAMi 发布的 GPU 容量 + +HAMi 会发布一个 `DeviceClass` 和一个节点级 `ResourceSlice`: + +```bash +kubectl get deviceclass,resourceslice +``` + +```plaintext +NAME AGE +deviceclass.resource.k8s.io/hami-core-gpu.project-hami.io 32m + +NAME NODE DRIVER +resourceslice.resource.k8s.io/lixd-test-gpu-hami-core-gpu.project-hami.io-2drs2 lixd-test-gpu hami-core-gpu.project-hami.io +``` + +查看设备容量: + +```bash +kubectl get resourceslice \ + -o jsonpath='{.items[0].spec.devices[0]}' | python3 -m json.tool +``` + +验证环境中的 T4 包含以下字段: + +```json +{ + "allowMultipleAllocations": true, + "capacity": { + "cores": { "value": "100" }, + "memory": { "value": "15Gi" } + }, + "name": "hami-gpu-0" +} +``` + +`allowMultipleAllocations: true` 表示同一张物理 GPU 可以分配给多个 Claim,直到显存或算力容量用完。 + +## 步骤 3: 验证一个 HAMi vGPU 切片 + +兼容模式允许现有业务继续使用 HAMi 扩展资源。下面的请求表示一个 vGPU、4096 MiB 显存和 50% 算力: + +```bash +kubectl apply -f tutorials/labs/examples/09-kueue-hami-vgpu/01-smoke-pod.yaml +kubectl wait -n hami-kueue-demo \ + --for=condition=Ready pod/hami-compatible-smoke --timeout=5m +``` + +HAMi webhook 会把请求转换成 DRA Claim: + +```bash +kubectl get resourceclaim -n hami-kueue-demo \ + hami-kueue-demo-hami-compatible-smoke-cuda \ + -o jsonpath='{.status.allocation.devices.results[0]}' | python3 -m json.tool +``` + +```json +{ + "consumedCapacity": { + "cores": "50", + "memory": "4Gi" + }, + "device": "hami-gpu-0", + "driver": "hami-core-gpu.project-hami.io" +} +``` + +容器中可以看到切分后的显存上限: + +```bash +kubectl exec -n hami-kueue-demo hami-compatible-smoke -- nvidia-smi +``` + +```plaintext +| 0 Tesla T4 ... | 0MiB / 4096MiB | 0% Default | +``` + +删除 smoke Pod,避免它影响后面的队列测试: + +```bash +kubectl delete pod -n hami-kueue-demo hami-compatible-smoke +``` + +## 步骤 4: 配置 Kueue 资源转换 + +HAMi 的 `gpumem` 和 `gpucores` 按单个 vGPU 表达,Kueue 需要统计总量。两个相同 Job 各申请一个 vGPU、4096 MiB 和 50% 算力时,总用量为: + +```text +vGPU 实例数:2 +显存总量: 2 x 4096 MiB = 8192 MiB +算力总量: 2 x 50 = 100 +``` + +编辑 Kueue manager 配置: + +```bash +kubectl edit configmap kueue-manager-config -n kueue-system +``` + +在 `controller_manager_config.yaml` 的现有 `Configuration` 文档中加入 `resources.transformations`: + +```yaml +apiVersion: config.kueue.x-k8s.io/v1beta2 +kind: Configuration +integrations: + frameworks: + - batch/job +resources: + transformations: + - input: nvidia.com/gpumem + strategy: Replace + outputs: + nvidia.com/total-gpumem: 1 + multiplyBy: nvidia.com/gpu + - input: nvidia.com/gpucores + strategy: Replace + outputs: + nvidia.com/total-gpucores: 1 + multiplyBy: nvidia.com/gpu +``` + +保留原有配置的其余部分。重启 Kueue,并等待 rollout 完成: + +```bash +kubectl rollout restart deployment/kueue-controller-manager -n kueue-system +kubectl rollout status deployment/kueue-controller-manager -n kueue-system +``` + +```plaintext +deployment "kueue-controller-manager" successfully rolled out +``` + +`Replace` 会从 Kueue 记账中移除按设备表达的输入资源;`multiplyBy: nvidia.com/gpu` 根据 vGPU 实例数计算显存和算力总量。 + +## 步骤 5: 创建 Kueue 配额 + +本实验的队列允许两个 vGPU 实例、8192 MiB 显存总量和 100 点算力总量: + +```bash +kubectl apply -f tutorials/labs/examples/09-kueue-hami-vgpu/02-queues.yaml +kubectl get resourceflavor,clusterqueue +kubectl get localqueue -n hami-kueue-demo +``` + +```plaintext +NAME AGE +resourceflavor.kueue.x-k8s.io/hami-t4 8s + +NAME COHORT PENDING WORKLOADS ADMITTED WORKLOADS +clusterqueue.kueue.x-k8s.io/hami-cq 0 0 + +NAME CLUSTERQUEUE PENDING WORKLOADS ADMITTED WORKLOADS +localqueue.kueue.x-k8s.io/hami-queue hami-cq 0 0 +``` + +显存配额以 MiB 为单位,与工作负载里的 `nvidia.com/gpumem: 4096` 一致。`ResourceFlavor` 的节点标签必须与 GPU 节点匹配;如果不是 T4,请修改清单中的 `Tesla-T4`。 + +## 步骤 6: 准入两个 vGPU Job + +创建三个相同规格的 Job: + +```bash +kubectl apply -f tutorials/labs/examples/09-kueue-hami-vgpu/03-jobs.yaml +kubectl get job,workload -n hami-kueue-demo +``` + +```plaintext +NAME STATUS +job.batch/hami-kueue-running-a Running +job.batch/hami-kueue-running-b Running +job.batch/hami-kueue-pending-c Suspended + +NAME QUEUE RESERVED IN ADMITTED +workload.kueue.x-k8s.io/job-hami-kueue-running-a-59997 hami-queue hami-cq True +workload.kueue.x-k8s.io/job-hami-kueue-running-b-9d737 hami-queue hami-cq True +workload.kueue.x-k8s.io/job-hami-kueue-pending-c-d854d hami-queue +``` + +查看任意一个已准入 Workload: + +```bash +kubectl get workload -n hami-kueue-demo \ + -l kueue.x-k8s.io/job-name=hami-kueue-running-a \ + -o jsonpath='{.items[0].status.admission.podSetAssignments[0].resourceUsage}' \ + | python3 -m json.tool +``` + +```json +{ + "nvidia.com/gpu": "1", + "nvidia.com/total-gpucores": "50", + "nvidia.com/total-gpumem": "4096" +} +``` + +HAMi 分配 DRA Claim、Pod 进入常规调度之前,Kueue 已经扣除了三项配额。 + +## 步骤 7: 验证第三个 Job 留在队列中 + +查看 ClusterQueue 用量: + +```bash +kubectl get clusterqueue hami-cq -o yaml +``` + +```yaml +status: + admittedWorkloads: 2 + flavorsUsage: + - name: hami-t4 + resources: + - name: nvidia.com/gpu + total: "2" + - name: nvidia.com/total-gpucores + total: "100" + - name: nvidia.com/total-gpumem + total: "8192" + pendingWorkloads: 1 +``` + +Pending Workload 同时记录了转换后的请求和未准入原因: + +```bash +kubectl get workload -n hami-kueue-demo \ + -l kueue.x-k8s.io/job-name=hami-kueue-pending-c -o yaml +``` + +```yaml +status: + conditions: + - reason: Pending + message: >- + couldn't assign flavors to pod set main: insufficient unused quota for nvidia.com/gpu in flavor hami-t4, 1 more needed, insufficient unused quota for nvidia.com/total-gpucores in flavor hami-t4, 50 more needed + + + resourceRequests: + - resources: + nvidia.com/gpu: "1" + nvidia.com/total-gpucores: "50" + nvidia.com/total-gpumem: "4096" +``` + +第三个 Job 保持 `Suspended`,不会创建 Pod 去竞争已经用完的 GPU 容量。 + +## 清理 + +删除工作负载和队列资源: + +```bash +kubectl delete -f tutorials/labs/examples/09-kueue-hami-vgpu/03-jobs.yaml +kubectl delete -f tutorials/labs/examples/09-kueue-hami-vgpu/02-queues.yaml +kubectl delete namespace hami-kueue-demo +``` + +从 `kueue-manager-config` 删除两项 `resources.transformations`,然后重启 Kueue: + +```bash +kubectl edit configmap kueue-manager-config -n kueue-system +kubectl rollout restart deployment/kueue-controller-manager -n kueue-system +kubectl rollout status deployment/kueue-controller-manager -n kueue-system +``` + +如果集群只用于本实验,可以卸载 HAMi: + +```bash +helm uninstall hami -n hami-system +kubectl delete namespace hami-system +``` + +## 本实验验证了什么 + +| 结论 | 证据 | +| --- | --- | +| HAMi 把扩展资源请求转换成 DRA 分配 | 生成的 `ResourceClaim` 从 `hami-gpu-0` 消耗 `4Gi` 显存和 `50` cores | +| vGPU 显存上限进入了容器 | `nvidia-smi` 显示 4096 MiB 上限 | +| Kueue 把单个 vGPU 资源折算成总量 | 每个已准入 Workload 使用一个 vGPU、4096 MiB 总显存和 50 点总算力 | +| 队列准入阻止了超额任务 | 两个 Job 运行,第三个因配额不足保持挂起 | + +## 后续练习 + +- 只调整显存配额,观察最先触发阻塞的配额维度。 +- 为不同团队创建独立 `ClusterQueue`,分配不同的 GPU 预算。 +- 对比 [实验 4](./hami-dra.md) 中直接使用 `ResourceClaim` 的原生 DRA 流程。 diff --git a/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/overview.md b/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/overview.md index ba78b71b..2aa8e883 100644 --- a/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/overview.md +++ b/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/overview.md @@ -19,4 +19,4 @@ import LabCardGridAuto from '@site/src/components/labs/LabCardGridAuto'; -每个实验都列出了各自的前提条件。实验 3 和 4 直接复用实验 1 搭建的集群,一次开机即可完成全部三个实验;实验 2 可在任意笔记本上运行,无需 GPU。实验 7 在租用的 GPU 虚拟机上自行搭建单节点 k3s 集群,不使用 GPU Operator。实验 8 需要已有的 Volcano GPU 集群,用于验证 Volcano vGPU、Gang 调度和队列级资源限制。 +每个实验都列出了各自的前提条件。实验 3 和 4 直接复用实验 1 搭建的集群,一次开机即可完成全部三个实验;实验 2 可在任意笔记本上运行,无需 GPU。实验 7 在租用的 GPU 虚拟机上自行搭建单节点 k3s 集群,不使用 GPU Operator。实验 8 需要已有的 Volcano GPU 集群,用于验证 Volcano vGPU、Gang 调度和队列级资源限制。实验 9 使用 Kueue 准入控制限制 HAMi vGPU 数量、显存和算力配额。 diff --git a/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/tags.yml b/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/tags.yml index 5ed0821e..f01bd7e1 100644 --- a/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/tags.yml +++ b/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/tags.yml @@ -19,6 +19,9 @@ "k3s": label: "k3s" permalink: "/k-3-s" +"kueue": + label: "Kueue" + permalink: "/kueue" "nvidia": label: "nvidia" permalink: "/nvidia" @@ -28,6 +31,9 @@ "queue": label: "queue" permalink: "/queue" +"quota": + label: "配额" + permalink: "/quota" "vgpu": label: "vgpu" permalink: "/vgpu" diff --git a/sidebars-tutorials.js b/sidebars-tutorials.js index 8b1fdfd0..efe8ddca 100644 --- a/sidebars-tutorials.js +++ b/sidebars-tutorials.js @@ -51,6 +51,11 @@ module.exports = { id: "labs/volcano-vgpu-gang-queue", customProps: { level: "Advanced", duration: "about 60 minutes" }, }, + { + type: "doc", + id: "labs/kueue-hami-vgpu", + customProps: { level: "Advanced", duration: "about 60 minutes" }, + }, ], }, ], diff --git a/tutorials/labs/examples/09-kueue-hami-vgpu/01-smoke-pod.yaml b/tutorials/labs/examples/09-kueue-hami-vgpu/01-smoke-pod.yaml new file mode 100644 index 00000000..a83d4b90 --- /dev/null +++ b/tutorials/labs/examples/09-kueue-hami-vgpu/01-smoke-pod.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + name: hami-compatible-smoke + namespace: hami-kueue-demo +spec: + restartPolicy: Never + containers: + - name: cuda + image: nvcr.io/nvidia/cuda:12.6.0-base-ubuntu24.04 + imagePullPolicy: IfNotPresent + command: ["bash", "-c", "nvidia-smi && sleep 3600"] + resources: + limits: + nvidia.com/gpu: 1 + nvidia.com/gpumem: 4096 + nvidia.com/gpucores: 50 diff --git a/tutorials/labs/examples/09-kueue-hami-vgpu/02-queues.yaml b/tutorials/labs/examples/09-kueue-hami-vgpu/02-queues.yaml new file mode 100644 index 00000000..08cb3638 --- /dev/null +++ b/tutorials/labs/examples/09-kueue-hami-vgpu/02-queues.yaml @@ -0,0 +1,36 @@ +apiVersion: kueue.x-k8s.io/v1beta2 +kind: ResourceFlavor +metadata: + name: hami-t4 +spec: + nodeLabels: + nvidia.com/gpu.product: Tesla-T4 +--- +apiVersion: kueue.x-k8s.io/v1beta2 +kind: ClusterQueue +metadata: + name: hami-cq +spec: + namespaceSelector: {} + resourceGroups: + - coveredResources: + - nvidia.com/gpu + - nvidia.com/total-gpumem + - nvidia.com/total-gpucores + flavors: + - name: hami-t4 + resources: + - name: nvidia.com/gpu + nominalQuota: 2 + - name: nvidia.com/total-gpumem + nominalQuota: 8192 + - name: nvidia.com/total-gpucores + nominalQuota: 100 +--- +apiVersion: kueue.x-k8s.io/v1beta2 +kind: LocalQueue +metadata: + name: hami-queue + namespace: hami-kueue-demo +spec: + clusterQueue: hami-cq diff --git a/tutorials/labs/examples/09-kueue-hami-vgpu/03-jobs.yaml b/tutorials/labs/examples/09-kueue-hami-vgpu/03-jobs.yaml new file mode 100644 index 00000000..02c8870b --- /dev/null +++ b/tutorials/labs/examples/09-kueue-hami-vgpu/03-jobs.yaml @@ -0,0 +1,74 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: hami-kueue-running-a + namespace: hami-kueue-demo + labels: + kueue.x-k8s.io/queue-name: hami-queue +spec: + suspend: true + parallelism: 1 + completions: 1 + template: + spec: + restartPolicy: Never + containers: + - name: cuda + image: nvcr.io/nvidia/cuda:12.6.0-base-ubuntu24.04 + imagePullPolicy: IfNotPresent + command: ["bash", "-c", "nvidia-smi && echo HAMI_KUEUE_RUNNING_A && sleep 3600"] + resources: + limits: + nvidia.com/gpu: 1 + nvidia.com/gpumem: 4096 + nvidia.com/gpucores: 50 +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: hami-kueue-running-b + namespace: hami-kueue-demo + labels: + kueue.x-k8s.io/queue-name: hami-queue +spec: + suspend: true + parallelism: 1 + completions: 1 + template: + spec: + restartPolicy: Never + containers: + - name: cuda + image: nvcr.io/nvidia/cuda:12.6.0-base-ubuntu24.04 + imagePullPolicy: IfNotPresent + command: ["bash", "-c", "nvidia-smi && echo HAMI_KUEUE_RUNNING_B && sleep 3600"] + resources: + limits: + nvidia.com/gpu: 1 + nvidia.com/gpumem: 4096 + nvidia.com/gpucores: 50 +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: hami-kueue-pending-c + namespace: hami-kueue-demo + labels: + kueue.x-k8s.io/queue-name: hami-queue +spec: + suspend: true + parallelism: 1 + completions: 1 + template: + spec: + restartPolicy: Never + containers: + - name: cuda + image: nvcr.io/nvidia/cuda:12.6.0-base-ubuntu24.04 + imagePullPolicy: IfNotPresent + command: ["bash", "-c", "nvidia-smi && echo HAMI_KUEUE_PENDING_C && sleep 3600"] + resources: + limits: + nvidia.com/gpu: 1 + nvidia.com/gpumem: 4096 + nvidia.com/gpucores: 50 diff --git a/tutorials/labs/kueue-hami-vgpu.md b/tutorials/labs/kueue-hami-vgpu.md new file mode 100644 index 00000000..5124e27f --- /dev/null +++ b/tutorials/labs/kueue-hami-vgpu.md @@ -0,0 +1,393 @@ +--- +title: "Lab 9: Queue HAMi vGPU Workloads with Kueue" +description: "Enforce vGPU count, memory, and compute quotas for HAMi workloads before Pods reach the scheduler." +sidebar_label: "Lab 9: Kueue + HAMi vGPU" +lab: + level: Advanced + duration: about 60 minutes + environment: Kubernetes 1.36 cluster with one NVIDIA Tesla T4 + authors: + - lixd + verified: "2026-07-13" +tags: + - kueue + - dra + - vgpu + - quota +toc_max_heading_level: 2 +--- + +This lab combines HAMi GPU slicing with Kueue admission control. HAMi gives each Pod a slice of GPU memory and compute capacity; Kueue accounts for those slices before admitting the Job. You will admit two identical Jobs and verify that a third remains suspended when the queue reaches its vGPU, memory, and compute quotas. + +The captured output comes from Kubernetes 1.36.1, containerd 2.2.4, Kueue 0.18.1, and HAMi 2.9.0 on a node with one 15 GiB Tesla T4. + +:::warning Version-specific APIs + +This lab uses Kueue `v1beta2` APIs and the `ResourceTransformation` configuration available in Kueue 0.18.1. Check the Kueue release notes before applying the configuration to a different version. + +::: + +## What You'll Learn + +- install HAMi 2.9.0 in DRA compatibility mode; +- verify that a `4 GiB / 50%` extended-resource request becomes a DRA `ResourceClaim`; +- transform per-vGPU HAMi memory and compute requests into total Kueue quota usage; and +- keep excess Jobs suspended at admission time instead of letting their Pods become scheduler-level Pending. + +## Lab Overview + +```mermaid +%% title: Kueue and HAMi vGPU Lab Flow +flowchart LR + Step1["Step 1
Install HAMi DRA"] --> Step2["Step 2
Verify device capacity"] + Step2 --> Step3["Step 3
Test one vGPU"] + Step3 --> Step4["Step 4
Configure transformations"] + Step4 --> Step5["Step 5
Create queue quotas"] + Step5 --> Step6["Step 6
Admit two Jobs"] + Step6 --> Step7["Step 7
Block the third Job"] +``` + +## Prerequisites + +- A Kubernetes 1.36 cluster with Kueue 0.18.1 installed and the `batch/job` integration enabled. +- One NVIDIA GPU node. The verified environment uses a 15 GiB Tesla T4. +- GPU Operator or an equivalent installation that provides the NVIDIA driver and GPU Feature Discovery labels. +- NVIDIA Device Plugin disabled because HAMi owns the GPU device path in this lab. +- `kubectl` and Helm access with permission to install cluster-scoped resources and edit the Kueue manager configuration. +- cert-manager installed; the HAMi DRA webhook uses it to provision TLS certificates. +- The manifests from [`tutorials/labs/examples/09-kueue-hami-vgpu/`](https://github.com/Project-HAMi/website/tree/master/tutorials/labs/examples/09-kueue-hami-vgpu). + +If GPU Operator manages the node, install or upgrade it with its device plugin disabled: + +```text +--set devicePlugin.enabled=false +``` + +## Step 1: Install HAMi in DRA Compatibility Mode + +Create the namespace used throughout the lab. Select the T4 node from its GPU Feature Discovery label instead of copying the verified environment's node name, then label it for HAMi: + +```bash +kubectl create namespace hami-kueue-demo +export GPU_NODE=$(kubectl get nodes -l nvidia.com/gpu.product=Tesla-T4 \ + -o jsonpath='{.items[0].metadata.name}') +test -n "${GPU_NODE}" && echo "GPU_NODE=${GPU_NODE}" +kubectl label node "${GPU_NODE}" gpu=on --overwrite +``` + +Install HAMi 2.9.0 with DRA enabled and the traditional device plugin disabled: + +```bash +helm repo add hami-charts https://project-hami.github.io/HAMi/ +helm repo update + +helm install hami hami-charts/hami \ + --namespace hami-system \ + --create-namespace \ + --version 2.9.0 \ + --set dra.enabled=true \ + --set devicePlugin.enabled=false +``` + +:::important + +Do not enable HAMi's DRA and traditional device-plugin modes at the same time. If the NVIDIA driver is installed directly on the host rather than by GPU Operator, also set `hami-dra.drivers.nvidia.containerDriver=false`. + +::: + +Wait for the three DRA components: + +```bash +kubectl get pods -n hami-system +``` + +```plaintext +NAME READY STATUS +hami-dra-driver-kubelet-plugin-fb4zm 1/1 Running +hami-hami-dra-monitor-7b8df84bd-jsjrd 1/1 Running +hami-hami-dra-webhook-7bb65cbcc5-g5742 1/1 Running +``` + +## Step 2: Inspect the Published GPU Capacity + +HAMi publishes a `DeviceClass` and a node-local `ResourceSlice`: + +```bash +kubectl get deviceclass,resourceslice +``` + +```plaintext +NAME AGE +deviceclass.resource.k8s.io/hami-core-gpu.project-hami.io 32m + +NAME NODE DRIVER +resourceslice.resource.k8s.io/lixd-test-gpu-hami-core-gpu.project-hami.io-2drs2 lixd-test-gpu hami-core-gpu.project-hami.io +``` + +Inspect the device capacity: + +```bash +kubectl get resourceslice \ + -o jsonpath='{.items[0].spec.devices[0]}' | python3 -m json.tool +``` + +The verified T4 reported these relevant fields: + +```json +{ + "allowMultipleAllocations": true, + "capacity": { + "cores": { "value": "100" }, + "memory": { "value": "15Gi" } + }, + "name": "hami-gpu-0" +} +``` + +`allowMultipleAllocations: true` lets multiple claims consume capacity from the same physical GPU until memory or compute capacity is exhausted. + +## Step 3: Verify One HAMi vGPU Slice + +Compatibility mode lets an existing workload keep using HAMi extended resources. The request below means one vGPU with 4096 MiB of memory and 50% compute capacity: + +```bash +kubectl apply -f tutorials/labs/examples/09-kueue-hami-vgpu/01-smoke-pod.yaml +kubectl wait -n hami-kueue-demo \ + --for=condition=Ready pod/hami-compatible-smoke --timeout=5m +``` + +HAMi's webhook converts that request into a DRA claim. Inspect the generated claim: + +```bash +kubectl get resourceclaim -n hami-kueue-demo \ + hami-kueue-demo-hami-compatible-smoke-cuda \ + -o jsonpath='{.status.allocation.devices.results[0]}' | python3 -m json.tool +``` + +```json +{ + "consumedCapacity": { + "cores": "50", + "memory": "4Gi" + }, + "device": "hami-gpu-0", + "driver": "hami-core-gpu.project-hami.io" +} +``` + +The container sees the sliced memory ceiling: + +```bash +kubectl exec -n hami-kueue-demo hami-compatible-smoke -- nvidia-smi +``` + +```plaintext +| 0 Tesla T4 ... | 0MiB / 4096MiB | 0% Default | +``` + +Remove the smoke Pod so it does not consume GPU capacity during the queue test: + +```bash +kubectl delete pod -n hami-kueue-demo hami-compatible-smoke +``` + +## Step 4: Configure Kueue Resource Transformations + +HAMi expresses `gpumem` and `gpucores` per vGPU. Kueue needs total usage. For example, two Jobs that each request one vGPU, 4096 MiB, and 50% compute consume: + +```text +vGPU instances: 2 +total memory: 2 x 4096 MiB = 8192 MiB +total compute: 2 x 50 = 100 +``` + +Edit the Kueue manager configuration: + +```bash +kubectl edit configmap kueue-manager-config -n kueue-system +``` + +Add `resources.transformations` to the existing `Configuration` document in `controller_manager_config.yaml`: + +```yaml +apiVersion: config.kueue.x-k8s.io/v1beta2 +kind: Configuration +integrations: + frameworks: + - batch/job +resources: + transformations: + - input: nvidia.com/gpumem + strategy: Replace + outputs: + nvidia.com/total-gpumem: 1 + multiplyBy: nvidia.com/gpu + - input: nvidia.com/gpucores + strategy: Replace + outputs: + nvidia.com/total-gpucores: 1 + multiplyBy: nvidia.com/gpu +``` + +Preserve the rest of the existing configuration. Restart Kueue and wait for it to become available: + +```bash +kubectl rollout restart deployment/kueue-controller-manager -n kueue-system +kubectl rollout status deployment/kueue-controller-manager -n kueue-system +``` + +```plaintext +deployment "kueue-controller-manager" successfully rolled out +``` + +The `Replace` strategy removes the per-device input from Kueue accounting. `multiplyBy: nvidia.com/gpu` creates total memory and compute requests based on the number of requested vGPU instances. + +## Step 5: Create the Kueue Quotas + +The queue allows two vGPU instances, 8192 MiB of total GPU memory, and 100 total compute points: + +```bash +kubectl apply -f tutorials/labs/examples/09-kueue-hami-vgpu/02-queues.yaml +kubectl get resourceflavor,clusterqueue +kubectl get localqueue -n hami-kueue-demo +``` + +```plaintext +NAME AGE +resourceflavor.kueue.x-k8s.io/hami-t4 8s + +NAME COHORT PENDING WORKLOADS ADMITTED WORKLOADS +clusterqueue.kueue.x-k8s.io/hami-cq 0 0 + +NAME CLUSTERQUEUE PENDING WORKLOADS ADMITTED WORKLOADS +localqueue.kueue.x-k8s.io/hami-queue hami-cq 0 0 +``` + +The memory quota uses MiB, matching `nvidia.com/gpumem: 4096` in the workload. The `ResourceFlavor` node label must match the label on your GPU node; adjust `Tesla-T4` if you use another model. + +## Step 6: Admit Two vGPU Jobs + +Create three identical Jobs. Applying one file keeps their specifications exactly the same apart from the names: + +```bash +kubectl apply -f tutorials/labs/examples/09-kueue-hami-vgpu/03-jobs.yaml +kubectl get job,workload -n hami-kueue-demo +``` + +```plaintext +NAME STATUS +job.batch/hami-kueue-running-a Running +job.batch/hami-kueue-running-b Running +job.batch/hami-kueue-pending-c Suspended + +NAME QUEUE RESERVED IN ADMITTED +workload.kueue.x-k8s.io/job-hami-kueue-running-a-59997 hami-queue hami-cq True +workload.kueue.x-k8s.io/job-hami-kueue-running-b-9d737 hami-queue hami-cq True +workload.kueue.x-k8s.io/job-hami-kueue-pending-c-d854d hami-queue +``` + +Inspect either admitted Workload: + +```bash +kubectl get workload -n hami-kueue-demo \ + -l kueue.x-k8s.io/job-name=hami-kueue-running-a \ + -o jsonpath='{.items[0].status.admission.podSetAssignments[0].resourceUsage}' \ + | python3 -m json.tool +``` + +```json +{ + "nvidia.com/gpu": "1", + "nvidia.com/total-gpucores": "50", + "nvidia.com/total-gpumem": "4096" +} +``` + +Kueue has charged all three quota dimensions before HAMi allocates the DRA claim and before the Pod reaches normal scheduling. + +## Step 7: Verify the Third Job Stays Queued + +Inspect the ClusterQueue usage: + +```bash +kubectl get clusterqueue hami-cq -o yaml +``` + +```yaml +status: + admittedWorkloads: 2 + flavorsUsage: + - name: hami-t4 + resources: + - name: nvidia.com/gpu + total: "2" + - name: nvidia.com/total-gpucores + total: "100" + - name: nvidia.com/total-gpumem + total: "8192" + pendingWorkloads: 1 +``` + +The pending Workload records both its transformed request and why it was not admitted: + +```bash +kubectl get workload -n hami-kueue-demo \ + -l kueue.x-k8s.io/job-name=hami-kueue-pending-c -o yaml +``` + +```yaml +status: + conditions: + - reason: Pending + message: >- + couldn't assign flavors to pod set main: insufficient unused quota for nvidia.com/gpu in flavor hami-t4, 1 more needed, insufficient unused quota for nvidia.com/total-gpucores in flavor hami-t4, 50 more needed + + + resourceRequests: + - resources: + nvidia.com/gpu: "1" + nvidia.com/total-gpucores: "50" + nvidia.com/total-gpumem: "4096" +``` + +The third Job remains `Suspended`; it does not create a Pod that competes for already exhausted GPU capacity. + +## Cleanup + +Delete the workloads and queue resources: + +```bash +kubectl delete -f tutorials/labs/examples/09-kueue-hami-vgpu/03-jobs.yaml +kubectl delete -f tutorials/labs/examples/09-kueue-hami-vgpu/02-queues.yaml +kubectl delete namespace hami-kueue-demo +``` + +Remove the two `resources.transformations` entries from `kueue-manager-config`, then restart Kueue: + +```bash +kubectl edit configmap kueue-manager-config -n kueue-system +kubectl rollout restart deployment/kueue-controller-manager -n kueue-system +kubectl rollout status deployment/kueue-controller-manager -n kueue-system +``` + +If this cluster is only for the lab, uninstall HAMi: + +```bash +helm uninstall hami -n hami-system +kubectl delete namespace hami-system +``` + +## What This Lab Proved + +| Claim | Evidence | +| --- | --- | +| HAMi converted an extended-resource request into a DRA allocation | The generated `ResourceClaim` consumed `4Gi` memory and `50` cores from `hami-gpu-0` | +| The vGPU memory limit reached the container | `nvidia-smi` reported a 4096 MiB ceiling | +| Kueue accounted for per-vGPU resources as totals | Each admitted Workload used one vGPU, 4096 total MiB, and 50 total compute points | +| Queue admission prevented oversubscription | Two Jobs ran while the third remained suspended with an insufficient-quota condition | + +## Next Steps + +- Change only the memory quota to observe which quota dimension blocks admission first. +- Add separate `ClusterQueue` objects for teams that need different GPU budgets. +- Compare this compatibility path with the native `ResourceClaim` workflow in [Lab 4](./hami-dra.md). diff --git a/tutorials/overview.md b/tutorials/overview.md index 42126285..4da882fa 100644 --- a/tutorials/overview.md +++ b/tutorials/overview.md @@ -17,4 +17,4 @@ Background knowledge that the labs build on. ## Labs - Each lab lists its own prerequisites. Labs 3 and 4 continue from the cluster Lab 1 builds, so a single session covers all three; Lab 2 runs on any laptop with no GPU required. Lab 7 brings up its own single-node k3s cluster on a rented GPU VM, without the GPU Operator. Lab 8 requires an existing Volcano GPU cluster and validates Volcano vGPU, Gang scheduling, and queue-level limits. + Each lab lists its own prerequisites. Labs 3 and 4 continue from the cluster Lab 1 builds, so a single session covers all three; Lab 2 runs on any laptop with no GPU required. Lab 7 brings up its own single-node k3s cluster on a rented GPU VM, without the GPU Operator. Lab 8 requires an existing Volcano GPU cluster and validates Volcano vGPU, Gang scheduling, and queue-level limits. Lab 9 uses Kueue admission control to enforce HAMi vGPU count, memory, and compute quotas.