GPU調(diào)度的binpack算法

概述

得益于不同尺寸的大模型的發(fā)展,我們業(yè)務(wù)最近部署的 GPU 服務(wù)的規(guī)格越來(lái)越多樣了,有1卡、2卡、4卡以及8卡的類型,這跟以往大部分都是1卡的情況相差很大,如果還是以 Kubernetes 默認(rèn)的調(diào)度方式,那么在不同規(guī)格的 GPU 服務(wù)的情況下,很容易產(chǎn)生資源碎片的問(wèn)題,尤其是在資源比較緊張,也就是 GPU 分配率本身就比較高的情況下,按照默認(rèn)的打散的方式的話,如果大部分節(jié)點(diǎn)都分配了5卡、6卡的情況下,那些4卡的服務(wù)就很難找到合適的節(jié)點(diǎn)調(diào)度上去了。這種情況下,GPU 服務(wù)調(diào)度的 binpack 算法可以緩解這樣的問(wèn)題。

img_2.png

binpack算法

該用 binpack 算法之后,可以看到,GPU 分配的時(shí)候會(huì)先填滿一個(gè)節(jié)點(diǎn),然后再考慮下一個(gè)節(jié)點(diǎn),于是4卡的容器就調(diào)度上去了。

img_1.png

從 Kubernetes v1.19 開始,調(diào)度框架(Scheduler Framework)被引入,提供了一個(gè)更靈活和可擴(kuò)展的插件機(jī)制,允許開發(fā)者通過(guò)插件的形式自定義調(diào)度邏輯。調(diào)度框架包括多個(gè)階段(例如Filter、Score、Reserve、Permit等)來(lái)替代原來(lái)的 Predicates 和 Priorities。

公司 Kubernetes 版本比較低(v1.20),關(guān)于調(diào)度器的配置在 v1.23 版本有過(guò)不大不小的變化,因此目前最新的 Kubernetes 版本的配置方法與 v1.20 是不太相同的,這點(diǎn)在測(cè)試和正式環(huán)境的時(shí)候需要注意,以免在 v1.20 的集群使用最新版本 Kubernetes 的配置。

  • Kubernetes v1.19: 調(diào)度框架引入,但Predicates和Priorities仍然存在,作為向后兼容的一部分
  • Kubernetes v1.20: 調(diào)度框架繼續(xù)改進(jìn),官方開始建議使用調(diào)度框架插件來(lái)替代Predicates和Priorities
  • Kubernetes v1.22: Predicates和Priorities被標(biāo)記為廢棄(Deprecated)
  • Kubernetes v1.23: Predicates和Priorities正式被移除,調(diào)度框架成為唯一的調(diào)度機(jī)制

在 Kubernetes 的調(diào)度器中,RequestedToCapacityRatioResourceAllocation 參數(shù)允許用戶指定資源以及每類資源的權(quán)重,以便根據(jù)請(qǐng)求數(shù)量與可用容量之比率為節(jié)點(diǎn)評(píng)分。這就使得用戶可以通過(guò)使用適當(dāng)?shù)膮?shù)來(lái)對(duì)擴(kuò)展資源執(zhí)行裝箱操作,從而提高了大型集群中稀缺資源的利用率。RequestedToCapacityRatioResourceAllocation 優(yōu)先級(jí)函數(shù)的行為可以通過(guò)名為 requestedToCapacityRatioArguments 的配置選項(xiàng)進(jìn)行控制。該標(biāo)志由兩個(gè)參數(shù) shape 和 resources 組成。shape 允許用戶根據(jù) utilization 和 score 值將函數(shù)調(diào)整為最少請(qǐng)求(least requested)或最多請(qǐng)求(most requested)計(jì)算。 resources 包含由 name 和 weight 組成,name 指定評(píng)分時(shí)要考慮的資源,weight 指定每種資源的權(quán)重。如果我們要啟用 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 來(lái)給調(diào)度器配置插件,可以用下面的配置,注意這是 configmap 的配置,如果使用 Profile 的方式,那么啟動(dòng)命令要改成使用 --config,詳細(xì)可以查看示例文件 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

關(guān)于 RequestedToCapacityRatioResourceAllocation 優(yōu)先級(jí)函數(shù)如何對(duì)節(jié)點(diǎn)評(píng)分,可以參考下面的流程,看完之后應(yīng)該會(huì)更清晰。

請(qǐng)求的資源

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

資源權(quán)重

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

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

節(jié)點(diǎn) Node1 配置

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

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

節(jié)點(diǎn)得分:

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

節(jié)點(diǎn) Node2 配置

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

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

節(jié)點(diǎn)得分:

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

因此按照以上的結(jié)算過(guò)程,Node2 是優(yōu)選的節(jié)點(diǎn)。            

Kubernetes配置

測(cè)試條件下,我們有如下配置的 Kubernetes 集群(沒(méi)有GPU節(jié)點(diǎn))。

# 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

雖然測(cè)試集群沒(méi)有可用的 GPU,但是可以通過(guò)下面的配置,可以讓給沒(méi)有 GPU 的節(jié)點(diǎn),虛假地上報(bào) GPU 資源,方便后面的測(cè)試。

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
# 模擬每個(gè)節(jié)點(diǎn)有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

查看部署的結(jié)果和上報(bào)的資源,可以看到 node1 和 node2 當(dāng)前都上報(bào)了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

自定義調(diào)度器

參考我個(gè)人整理的 yaml 文件 v-1-20-3-scheduler.yaml 可以在 kube-system 的命名空間下部署一個(gè)自定義的調(diào)度器,名叫 gpu-binpack-scheduler,并且作為測(cè)試,部署了一個(gè)10個(gè)副本,一共需要10卡的 deployment,看看調(diào)度的最終情況,這里需要注意的是,測(cè)試 deployment 的 schedulerName 必須指定為 gpu-binpack-scheduler。

測(cè)試結(jié)果

從測(cè)試結(jié)果看,會(huì)先將 node1 填滿了,再往 node2 分配,這個(gè)結(jié)果是符合我們的預(yù)期的。

# 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. 配置多個(gè)調(diào)度器
  2. 使用RequestedToCapacityRatioResourceAllocation啟用裝箱
  3. release-1.20
  4. kube-scheduler
  5. my-scheduler
  6. kube-scheduler-policy-config
  7. scheduler-config
  8. fake-gpu-operator
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容