GPU调度的binpack算法

概述

得益于不同尺寸的大模型的发展,我们业务最近部署的 GPU 服务的规格越来越多样了,有1卡、2卡、4卡以及8卡的类型,这跟以往大部分都是1卡的情况相差很大,如果还是以 Kubernetes 默认的调度方式,那么在不同规格的 GPU 服务的情况下,很容易产生资源碎片的问题,尤其是在资源比较紧张,也就是 GPU 分配率本身就比较高的情况下,按照默认的打散的方式的话,如果大部分节点都分配了5卡、6卡的情况下,那些4卡的服务就很难找到合适的节点调度上去了。这种情况下,GPU 服务调度的 binpack 算法可以缓解这样的问题。

img_2.png

binpack算法

该用 binpack 算法之后,可以看到,GPU 分配的时候会先填满一个节点,然后再考虑下一个节点,于是4卡的容器就调度上去了。

img_1.png

从 Kubernetes v1.19 开始,调度框架(Scheduler Framework)被引入,提供了一个更灵活和可扩展的插件机制,允许开发者通过插件的形式自定义调度逻辑。调度框架包括多个阶段(例如Filter、Score、Reserve、Permit等)来替代原来的 Predicates 和 Priorities。

公司 Kubernetes 版本比较低(v1.20),关于调度器的配置在 v1.23 版本有过不大不小的变化,因此目前最新的 Kubernetes 版本的配置方法与 v1.20 是不太相同的,这点在测试和正式环境的时候需要注意,以免在 v1.20 的集群使用最新版本 Kubernetes 的配置。

  • Kubernetes v1.19: 调度框架引入,但Predicates和Priorities仍然存在,作为向后兼容的一部分
  • Kubernetes v1.20: 调度框架继续改进,官方开始建议使用调度框架插件来替代Predicates和Priorities
  • Kubernetes v1.22: Predicates和Priorities被标记为废弃(Deprecated)
  • Kubernetes v1.23: Predicates和Priorities正式被移除,调度框架成为唯一的调度机制

在 Kubernetes 的调度器中,RequestedToCapacityRatioResourceAllocation 参数允许用户指定资源以及每类资源的权重,以便根据请求数量与可用容量之比率为节点评分。这就使得用户可以通过使用适当的参数来对扩展资源执行装箱操作,从而提高了大型集群中稀缺资源的利用率。RequestedToCapacityRatioResourceAllocation 优先级函数的行为可以通过名为 requestedToCapacityRatioArguments 的配置选项进行控制。该标志由两个参数 shape 和 resources 组成。shape 允许用户根据 utilization 和 score 值将函数调整为最少请求(least requested)或最多请求(most requested)计算。 resources 包含由 name 和 weight 组成,name 指定评分时要考虑的资源,weight 指定每种资源的权重。如果我们要启用 GPU 的装箱(binpack),那么可以参考下面的 Policy 配置。

{
  "kind": "Policy",
  "apiVersion": "v1",
  "predicates": [],
  "priorities": [
    {
      "name": "RequestedToCapacityRatioPriority",
      "weight": 2,
      "argument": {
        "requestedToCapacityRatioArguments": {
          "shape": [
            {
              "utilization": 0,
              "score": 0
            },
            {
              "utilization": 100,
              "score": 10
            }
          ],
          "resources": [
            {
              "name": "nvidia.com/gpu",
              "weight": 10
            }
          ]
        }
      }
    }
  ]
}

如果想用 Profile 来给调度器配置插件,可以用下面的配置,注意这是 configmap 的配置,如果使用 Profile 的方式,那么启动命令要改成使用 --config,详细可以查看示例文件 v-1-20-3-scheduler-with-profile.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: gpu-binpack-scheduler-config
  namespace: kube-system
data:
  gpu-binpack-scheduler-config.yaml: |
    apiVersion: kubescheduler.config.k8s.io/v1beta1
    kind: KubeSchedulerConfiguration
    leaderElection:
      leaderElect: true
      leaseDuration: 15s
      renewDeadline: 10s
      retryPeriod: 2s
      resourceLock: leases
      resourceNamespace: kube-system
      resourceName: gpu-binpack-scheduler
    profiles:
      - schedulerName: gpu-binpack-scheduler
        plugins:
          score:
            enabled:
              - name: NodeResourcesMostAllocated
        pluginConfig:
          - name: NodeResourcesMostAllocated
            args:
              resources:
                - name: nvidia.com/gpu
                  weight: 10
                - name: cpu
                  weight: 1
                - name: memory
                  weight: 1

关于 RequestedToCapacityRatioResourceAllocation 优先级函数如何对节点评分,可以参考下面的流程,看完之后应该会更清晰。

请求的资源

nvidia.com/gpu: 2
Memory: 256MB
CPU: 2

资源权重

nvidia.com/gpu: 5
Memory: 1
CPU: 3

FunctionShapePoint {{0, 0}, {100, 10}}

节点 Node1 配置

可用:
  nvidia.com/gpu: 4
  Memory: 1 GB
  CPU: 8

已用:
  nvidia.com/gpu: 1
  Memory: 256MB
  CPU: 1

节点得分:

nvidia.com/gpu = resourceScoringFunction((2+1),4)
               = (100 - ((4-3)*100/4)
               = (100 - 25)
               = 75
               = rawScoringFunction(75)
               = 7

Memory         = resourceScoringFunction((256+256),1024)
               = (100 -((1024-512)*100/1024))
               = 50
               = rawScoringFunction(50)
               = 5

CPU            = resourceScoringFunction((2+1),8)
               = (100 -((8-3)*100/8))
               = 37.5
               = rawScoringFunction(37.5)
               = 3

NodeScore   =  (7 * 5) + (5 * 1) + (3 * 3) / (5 + 1 + 3)
            =  5

节点 Node2 配置

可用:
  nvidia.com/gpu: 8
  Memory: 1GB
  CPU: 8

已用:
  nvidia.com/gpu: 2
  Memory: 512MB
  CPU: 6

节点得分:

nvidia.com/gpu = resourceScoringFunction((2+2),8)
               = (100 - ((8-4)*100/8)
               = (100 - 50)
               = 50
               = rawScoringFunction(50)
               = 5

Memory         = resourceScoringFunction((256+512),1024)
               = (100 -((1024-768)*100/1024))
               = 75
               = rawScoringFunction(75)
               = 7

CPU            = resourceScoringFunction((2+6),8)
               = (100 -((8-8)*100/8))
               = 100
               = rawScoringFunction(100)
               = 10

NodeScore   =  (5 * 5) + (7 * 1) + (10 * 3) / (5 + 1 + 3)
            =  7

因此按照以上的结算过程,Node2 是优选的节点。            

Kubernetes配置

测试条件下,我们有如下配置的 Kubernetes 集群(没有GPU节点)。

# k get no -o wide
NAME     STATUS   ROLES                  VERSION   INTERNAL-IP      OS-IMAGE              KERNEL-VERSION                      CONTAINER-RUNTIME
master   Ready    control-plane,master   v1.20.3   192.168.1.200    openEuler 22.03 LTS   5.10.0-60.125.0.152.oe2203.x86_64   docker://19.3.13
node1    Ready    <none>                 v1.20.3   192.168.1.201    openEuler 22.03 LTS   5.10.0-60.139.0.166.oe2203.x86_64   docker://19.3.13
node2    Ready    <none>                 v1.20.3   192.168.1.202    openEuler 22.03 LTS   5.10.0-60.139.0.166.oe2203.x86_64   docker://19.3.13

fake-gpu

虽然测试集群没有可用的 GPU,但是可以通过下面的配置,可以让给没有 GPU 的节点,虚假地上报 GPU 资源,方便后面的测试。

kubectl label node node1 nvidia.com/gpu.deploy.device-plugin=true nvidia.com/gpu.deploy.dcgm-exporter=true --overwrite
kubectl label node node2 nvidia.com/gpu.deploy.device-plugin=true nvidia.com/gpu.deploy.dcgm-exporter=true --overwrite
kubectl label node 10.189.212.124 nvidia.com/gpu.deploy.device-plugin=true nvidia.com/gpu.deploy.dcgm-exporter=true --overwrite
kubectl label node 10.189.212.125 nvidia.com/gpu.deploy.device-plugin=true nvidia.com/gpu.deploy.dcgm-exporter=true --overwrite
helm repo add fake-gpu-operator https://fake-gpu-operator.storage.googleapis.com
# 模拟每个节点有8卡
helm upgrade -i gpu-operator fake-gpu-operator/fake-gpu-operator --namespace gpu-operator --create-namespace --set initialTopology.config.node-autofill.gpu-count=8

查看部署的结果和上报的资源,可以看到 node1 和 node2 当前都上报了8卡的 GPU。

# k get pods -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP            NODE 
device-plugin-ljwrv                1/1     Running   0          36m   10.244.1.54   node2
device-plugin-vncbc                1/1     Running   0          36m   10.244.2.77   node1
nvidia-dcgm-exporter-2t54q         1/1     Running   0          9h    10.244.2.10   node1
nvidia-dcgm-exporter-v8p5z         1/1     Running   0          9h    10.244.1.6    node2
status-updater-7dd49b7b5c-2ppq4    1/1     Running   0          17h   10.244.1.3    node2
topology-server-6845cbc768-wpbtm   1/1     Running   0          17h   10.244.2.8    node1

# kubectl-view-allocations -g node
 Resource               Requested           Limit  Allocatable    Free
  nvidia.com/gpu       (62%) 10.0      (62%) 10.0         16.0     6.0
  ├─ node1             (100%) 8.0      (100%) 8.0          8.0     0.0
  └─ node2              (25%) 2.0       (25%) 2.0          8.0     6.0

自定义调度器

参考我个人整理的 yaml 文件 v-1-20-3-scheduler.yaml 可以在 kube-system 的命名空间下部署一个自定义的调度器,名叫 gpu-binpack-scheduler,并且作为测试,部署了一个10个副本,一共需要10卡的 deployment,看看调度的最终情况,这里需要注意的是,测试 deployment 的 schedulerName 必须指定为 gpu-binpack-scheduler。

测试结果

从测试结果看,会先将 node1 填满了,再往 node2 分配,这个结果是符合我们的预期的。

# k get pods -o wide
NAME                                     READY   STATUS    RESTARTS   AGE     IP              NODE 
sleepy-deployment-d557d79ff-5mtlp        1/1     Running   0          7h16m   10.244.2.73     node1
sleepy-deployment-d557d79ff-5p2k5        1/1     Running   0          7h16m   10.244.2.69     node1
sleepy-deployment-d557d79ff-6rbmj        1/1     Running   0          7h16m   10.244.1.52     node2
sleepy-deployment-d557d79ff-f8js8        1/1     Running   0          7h16m   10.244.2.75     node1
sleepy-deployment-d557d79ff-j2hg4        1/1     Running   0          7h16m   10.244.2.70     node1
sleepy-deployment-d557d79ff-jhx27        1/1     Running   0          7h16m   10.244.2.71     node1
sleepy-deployment-d557d79ff-lxj6v        1/1     Running   0          7h16m   10.244.2.72     node1
sleepy-deployment-d557d79ff-mzklc        1/1     Running   0          7h16m   10.244.2.76     node1
sleepy-deployment-d557d79ff-tpclt        1/1     Running   0          7h16m   10.244.2.74     node1
sleepy-deployment-d557d79ff-zaaa5        1/1     Running   0          7h16m   10.244.1.53     node2

参考文章

  1. 配置多个调度器
  2. 使用RequestedToCapacityRatioResourceAllocation启用装箱
  3. release-1.20
  4. kube-scheduler
  5. my-scheduler
  6. kube-scheduler-policy-config
  7. scheduler-config
  8. fake-gpu-operator
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容