共享配置 — 用切片、MIG、配额分开
目标
通过配置和对象处理让多人共享一张 GPU 的三种方式。通过 node status 确认可用位置会按 slice 数增加;通过调度确认 MIG 节点的 resource 名称本身不同;最后通过 quota 固定分配给团队的份额。
为什么重要
GPU 环境中常出现一种怪现象:日均利用率只有个位数,等待队列却总是排满。原因是一个 Pod 会独占整张卡。于是产生“让多人共享一张卡”的需求,而答案有三种;由于名称相似,它们经常被混淆。
最重要的区别在于究竟切分什么。time-slicing 的 replicas 并不是把 GPU 切片,而是多次注册同一设备。可用位置增加了,但 VRAM 仍是未切分的一整块;一个 Pod 占用过多时,其余 Pod 会遇到内存不足。MPS 通过并发执行 kernel 提升吞吐量,但隔离仍然较弱。只有 MIG 在硬件层切分,因此能隔离内存与故障。此外,MIG 节点的 resource 名称会直接变为 profile 名称,旧 manifest 可能因此永远无法使用该节点。
环境
此 Pod 中没有 GPU 或 device plugin。取而代之的是 kwok 启动的真实 control plane:在 node status 中写入数字后,真实 scheduler 会据此判断;quota 也由真实 apiserver 验证。工作目录为 /root/gpushare,其下使用 k8s/、bin/、out/。
步骤
- 创建
gpu-share、team-anamespace,以及一个包含两套配置的 ConfigMap。 - 通过 node label 选择配置,并分别写入各自的宣告量。
- 将
lab-node-2设置为 MIG mixed 节点。 - 启动两个仅 resource 名称不同的 Pod,观察它们如何分流。
- 为
team-a设置 quota,并将超额 request 的拒绝信息保存到out/quota-denied.txt。 - 用
bin/check-request.sh在部署前拦截申请多个 slice 的 request。 - 在 ConfigMap 中加入
mps,并在out/isolation.txt中编写隔离表。 - 将三个 workload 分别放到合适的节点,并记录到
out/placement.txt。
参考
- 第 2 步的宣告量不要猜测,请乘以第 1 步配置的
replicas。评分器会读取 ConfigMap 并重复相同计算。 - 必须同时写入
capacity和allocatable。只写一处时,scheduler 不会得到可用容量。 - 第 3 步不要在 MIG 节点上同时写入
nvidia.com/gpu。mixed 策略的核心就是该节点只通过 profile 名称宣告资源。 - 第 8 步的节点名称请通过
kubectl get pod -o wide确认后填写。
为两套 device plugin 配置命名并存放
创建 gpu-share 和 team-a namespace,并通过 /root/gpushare/k8s/device-plugin-configs.yaml 在 gpu-share 中创建 ConfigMap device-plugin-configs。键为 default 和 shared;shared 中的 sharing.timeSlicing 下包含 failRequestsGreaterThanOne: true,且 resources[0] 为 nvidia.com/gpu、replicas: 4。default 中不要加入 sharing。
device plugin 可以把多套配置放进一个 ConfigMap,并让各节点分别选择。这样同一集群中,有的节点可以独占使用,有的节点可以共享。failRequestsGreaterThanOne 是拒绝申请两个以上 slice 的开关。即使拿到两个 slice,也只是把同一设备预留两次,性能和内存都不会翻倍。ConfigMap 的值是多行字符串,因此使用 |-。
通过 node label 选择使用哪套配置
为 lab-node-0 添加 nvidia.com/device-plugin.config=default,为 lab-node-1 添加 =shared。然后在 status 中写入两个节点宣告的 nvidia.com/gpu。假设两个节点都有 2 张物理 GPU:lab-node-0 写 2,lab-node-1 写 2 乘以 slice 数。
device plugin 根据节点的 nvidia.com/device-plugin.config label 选择 ConfigMap 中的键,因此同一集群的不同节点可采用不同共享策略。宣告量不要猜测,请直接乘以第 1 步的 replicas。capacity 和 allocatable 两处都要填写。这里增加的只有位置数量,VRAM 仍然是一整块。
MIG mixed 节点的 resource 名称本身不同
为 lab-node-2 添加 nvidia.com/mig.strategy=mixed 和 nvidia.com/gpu.product label,并在 status 中把 nvidia.com/mig-1g.10gb 宣告为 7。不要在该节点上写入 nvidia.com/gpu。
MIG 在硬件层切分 GPU。在 mixed 策略下,device plugin 会按 profile 使用不同名称宣告 resource,因此 Pod 请求的 resource 名称也不再是 nvidia.com/gpu,而会变成 nvidia.com/mig-1g.10gb 这样的 profile 名称。所以该节点完全不宣告 nvidia.com/gpu。将 A100 40GB 按 1g.10gb 切分后会得到七个实例。
resource 名称不同就无法占用彼此的位置
在 /root/gpushare/k8s/mig-job.yaml 中写入两个 Pod,并应用到 gpu-share。mig-job 请求 1 个 nvidia.com/mig-1g.10gb,plain-gpu 请求 1 个 nvidia.com/gpu。二者都不要设置 node condition。
即使没有设置条件,也会仅凭 resource 名称分流。mig-job 只能去宣告该名称的节点,而 plain-gpu 无法去 MIG 节点。即使使用同一物理卡,对 scheduler 而言也是完全不同的 resource。因此,引入 MIG 节点时若原样保留旧 manifest,Pod 可能永远无法使用该节点。
用 quota 固定分配给团队的份额
通过 /root/gpushare/k8s/gpu-quota.yaml 在 team-a 中创建 ResourceQuota gpu-quota,将 requests.nvidia.com/gpu 限制为 2。然后在 team-a 启动两个各使用一张 GPU 的 Pod 以占满 quota,再应用额外请求一张的 /root/gpushare/k8s/team-over.yaml,并将拒绝消息保存到 /root/gpushare/out/quota-denied.txt。
extended resource 也可以通过 namespace quota 限制,只是键名采用 requests.<자원이름> 形式。两个 Pod 可通过 nvidia.com/device-plugin.config: shared nodeSelector 放到 slice 节点,该处容量充足。quota 用满后的 request 会在admission 阶段而非 scheduler 阶段被阻止,因此 Pod 根本不会创建。消息写到标准错误,请用 2>&1 捕获。
部署前拦截申请多个 slice 的 request
创建 /root/gpushare/bin/check-request.sh <파드매니페스트>。若 container 请求超过 1 个 nvidia.com/gpu,逐行输出 OVER=<컨테이너이름>=<개수>,并以退出码 1 结束;否则输出 OK,并以 0 结束。完全不请求 GPU 的 Pod 也必须通过。
failRequestsGreaterThanOne 是 device plugin 在 runtime 执行的判定。在部署前实施同样检查,可以防止用户误解资源后据此制定计划。文件可能包含多个 document,因此使用 yaml.safe_load_all 读取,并同时查看 limits 与 requests。评分器会用三种 manifest 实际运行该脚本。
加入第三种方式并制作隔离表
在 ConfigMap device-plugin-configs 中添加 mps 键。sharing.mps.resources[0] 必须为 nvidia.com/gpu、replicas: 4,且不得同时包含 timeSlicing。然后在 /root/gpushare/out/isolation.txt 中用五行编写隔离表:TIMESLICING_MEMORY_ISOLATION、MPS_MEMORY_ISOLATION、MIG_MEMORY_ISOLATION、TIMESLICING_FAULT_ISOLATION、MIG_FAULT_ISOLATION。
MPS 将多个 process 的 kernel 汇入一个 context,真正实现并发执行。虽然可以为每个 process 设置内存上限,但更接近协作式限制;一个 process 崩溃时也可能影响其他 process。五行的值从 yes、no、partial 中选择:完全隔离写 yes;无隔离写 no;可设置上限但非强制隔离写 partial。
将三类 workload 放到各自合适的节点
使用 /root/gpushare/k8s/placement.yaml 在 gpu-share 中创建三个 Pod。prod-inference 在 MIG 节点使用 profile resource;notebook 在 slice 节点使用一个 nvidia.com/gpu;batch-train 在不共享的节点使用一个 nvidia.com/gpu。通过 label 选择节点。然后把实际去向写入 /root/gpushare/out/placement.txt,格式为三行 <파드이름>=<노드이름>。
基本准则是:需要隔离就用 MIG,以提高利用率为目的就用 time-slicing。生产 inference 不能受相邻 Pod 内存事故影响,因此应放到硬件 partition;实验 notebook 利用率低,适合 slice 节点;batch training 独占整张卡更快,适合不共享的节点。node condition 直接使用第 2、3 步添加的 label。不要猜测文件中的节点名称,请执行 kubectl get pod -o wide 确认后填写。