助力深度学习!阿里开源可插拔 GPU 共享调度工具
根據 Gartner 對全球 CIO 的調查結果顯示,人工智能將成為 2019 年組織革命的顛覆性力量。對于人工智能來說,算力即正義,成本即能力,利用 Docker 和 Kubernetes 代表云原生技術為 AI 提供了一種新的工作模式,將 GPU 機器放到統一的資源池進行調度和管理,這避免了GPU 資源利用率低下和人工管理的成本。因此,全球主要的容器集群服務廠商 Kubernetes 都提供了 Nvidia GPU 容器集群調度能力,但是通常都是將一個 GPU 卡分配給一個容器。這雖然可以實現比較好的隔離性,確保使用 GPU 的應用不會被其他應用影響;對于深度學習模型訓練的場景也非常適合,但是,針對模型開發和模型預測的場景還是會顯得比較浪費。基于此,大家有了共享 GPU 的集群調度需求。
Kubernetes 共享 GPU 集群調度
共享 GPU 的集群調度就是能夠讓更多的模型開發和預測服務共享同一個 GPU 卡,進而提高集群中 Nvidia GPU 的利用率。而這就需要提供 GPU 資源的劃分,而這里 GPU 資源劃分的維度指的就是 GPU 顯存和 Cuda Kernel 線程的劃分。通常在集群級別談支持共享 GPU 是以下兩件事情:
1.調度
2.隔離,我們這里主要討論的是調度,隔離的方案目前需要用戶通過應用限制(比如使用 Tensorflow 的per_process_gpu_memory_fraction 來控制),未來會提供基于 Nvidia 的 MPS 的可選項, 也會考慮 GPU 的方案。
而對于細粒度的 GPU 卡調度,目前 Kubernetes 社區并沒有很好的方案,這是由于 Kubernetes 對于 GPU 這類擴展資源的定義僅僅支持整數粒度的加加減減,無法支持復雜資源的分配。比如用戶希望使用 Pod A 占用半張 GPU卡,這在目前 Kubernetes 的架構設計中無法實現資源分配的記錄和調用。這里挑戰是多卡 GPU 共享是實際矢量資源問題,而 Extened Resource 是標量資源的描述。
針對此問題,我們設計了一個 Out Of Tree 的共享 GPU 調度方案,該方案依賴于 Kubernetes 的現有的工作機制:
Extended Resource 定義
Scheduler Extender 機制
Device Plugin 機制
Kubectl 的擴展機制
這個 GPU 共享調度擴展的好處是:利用 Kubernetes 的擴展和插件機制實現,對于 API Server,Scheduler,Controller Manager 以及 Kubelet 等核心組件沒有侵入性。這就方便了使用者可以在不同 Kubernetes 版本上應用這個方案,無需 rebase 代碼和重新構建 Kubernetes 二進制包。
用戶場景
集群管理員:“我想提高集群的 GPU 使用率;在開發過程中,多個用戶共享模型開發環境。”
應用開發人員:“我希望能夠同時在 Volta GPU 上運行多個推理任務。”
目標
能夠讓使用者通過 API 描述對于一個可共享資源的申請, 并能實現該種資源的調度
非目標
不支持該共享資源的隔離
不支持超賣
設計原則
明確問題簡化設計,第一步只負責調度和部署,后續再實現運行時顯存管控。
有很多的客戶明確的訴求是首先可以支持多AI應用可以調度到同一個 GPU 上,他們可以接受從應用級別控制顯存的大小,利用類似gpu_options.per_process_gpu_memory_fraction控制應用的顯存使用量。那我們要解決的問題就先簡化到以顯存為調度標尺,并且把顯存使用的大小以參數的方式傳遞給容器內部。
不做侵入式修改
本設計中不會修改 Kubernetes 核心的 Extended Resource 的設計, Scheduler 的實現,Device Plugin 的機制以及 Kubelet 的相關設計。重用 Extended Resource 描述共享資源的申請 API。這樣的好處在于提供一個可以移植的方案,用戶可以在原生 Kubernetes 上使用這個方案。
按顯存和按卡調度的方式可以在集群內并存,但是同一個節點內是互斥的,不支持二者并存;要么是按卡數目,要么是按顯存分配。
詳細設計
前提:
依舊延用 Kubernetes Extended Resource 定義,但是衡量維度最小單位從 1 個 GPU 卡變為 GPU 顯存的 MiB。如果所節點使用的 GPU 為單卡 16GiB 顯存,它對應的資源就是 16276MiB;
由于用戶對于共享GPU的訴求在于模型開發和模型預測場景,在此場景下,用戶申請的GPU資源上限不會超過一張卡,也就是申請的資源上限為單卡。
而我們的工作首先是定義了兩個新的 Extended Resource: 第一個是 gpu-mem, 對應的是 GPU 顯存;第二個是 gpu-count,對應的是 GPU 卡數。 通過兩個標量資源描述矢量資源, 并且結合這一資源,提供支持共享 GPU 的工作機制。下面是基本的架構圖:
核心功能模塊:
GPU Share Scheduler Extender: 利用 Kubernetes 的調度器擴展機制,負責在全局調度器 Filter 和 Bind 的時候判斷節點上單個 GPU 卡是否能夠提供足夠的 GPU Mem,并且在 Bind 的時刻將 GPU 的分配結果通過 annotation 記錄到 Pod Spec 以供后續 Filter 檢查分配結果。
GPU Share Device Plugin: 利用 Device Plugin 機制,在節點上被 Kubelet 調用負責 GPU 卡的分配,依賴 scheduler Extender 分配結果執行。
具體流程:
資源上報
GPU Share Device Plugin 利用 nvml 庫查詢到 GPU 卡的數量和每張 GPU 卡的顯存, 通過ListAndWatch()將節點的 GPU 總顯存(數量 顯存)作為另外 Extended Resource 匯報給 Kubelet; Kubelet 進一步匯報給 Kubernetes API Server。 舉例說明,如果節點含有兩塊 GPU 卡,并且每塊卡包含 16276MiB,從用戶的角度來看:該節點的 GPU 資源為 16276 2 = 32552; 同時也會將節點上的 GPU 卡數量 2 作為另外一個 Extended Resource 上報。
GPU Share Scheduler Extender 可以在分配 gpu-mem 給 Pod 的同時將分配信息以 annotation 的形式保留在 Pod spec 中,并且在過濾時刻根據此信息判斷每張卡是否包含足夠可用的 gpu-mem 分配。
2.1 Kubernetes 默認調度器在進行完所有過濾(filter)行為后會通過 http 方式調用 GPU Share Scheduler Extender的filter 方法, 這是由于默認調度器計算 Extended Resource 時,只能判斷資源總量是否有滿足需求的空閑資源,無法具體判斷單張卡上是否滿足需求;所以就需要由 GPU Share Scheduler Extender 檢查單張卡上是否含有可用資源。
以下圖為例, 在由 3 個包含兩塊 GPU 卡的節點組成的 Kubernetes 集群中,當用戶申請gpu-mem=8138時,默認調度器會掃描所有節點,發現 N1 所剩的資源為 (16276 * 2 - 16276 -12207 = 4069 )不滿足資源需求,N1 節點被過濾掉。
而 N2 和 N3 節點所剩資源都為 8138MiB,從整體調度的角度看,都符合默認調度器的條件;此時默認調度器會委托 GPU Share Scheduler Extender 進行二次過濾,在二次過濾中,GPU Share Scheduler Extender 需要判斷單張卡是否滿足調度需求,在查看 N2 節點時發現該節點雖然有 8138MiB 可用資源,但是落到每張卡上看,GPU0 和分別 GPU1 只有 4069MiB 的可用資源,無法滿足單卡 8138MiB 的訴求。而 N3 節點雖然也是總共有 8138MiB 可用資源,但是這些可用資源都屬于 GPU0,滿足單卡可調度的需求。由此,通過 GPU Share Scheduler Extender 的篩選就可以實現精準的條件篩選。
2.2 當調度器找到滿足條件的節點,就會委托 GPU Share Scheduler Extender 的 bind 方法進行節點和 Pod 的綁定,這里 Extender 需要做的是兩件事情
以 binpack 的規則找到節點中最優選擇的 GPU 卡 id,此處的最優含義是對于同一個節點不同的 GPU 卡,以 binpack 的原則作為判斷條件,優先選擇空閑資源滿足條件但同時又是所剩資源最少的 GPU 卡,并且將其作為ALIYUN_COM_GPU_MEM_IDX保存到 Pod 的 annotation 中;同時也保存該 Pod 申請的 GPU Memory 作為ALIYUN_COM_GPU_MEM_POD和ALIYUN_COM_GPU_MEM_ASSUME_TIME保存至 Pod 的 annotation 中,并且在此時進行 Pod 和所選節點的綁定。
注意:這時還會保存ALIYUN_COM_GPU_MEM_ASSIGNED的 Pod annotation,它被初始化為“false”。它表示該 Pod 在調度時刻被指定到了某塊 GPU 卡,但是并沒有真正在節點上創建該 Pod。ALIYUN_COM_GPU_MEM_ASSUME_TIME代表了指定時間。
如果此時發現分配節點上沒有 GPU 資源符合條件,此時不進行綁定,直接不報錯退出,默認調度器會在 assume 超時后重新調度。
調用 Kubernetes API 執行節點和 Pod 的綁定
以下圖為例,當 GPU Share Scheduler Extender 要把 gpu-mem:8138 的 Pod 和經過篩選出來的節點 N1 綁定,首先會比較不同 GPU 的可用資源,分別為 GPU0(12207),GPU1(8138),GPU2(4069),GPU3(16276),其中 GPU2 所剩資源不滿足需求,被舍棄掉;而另外三個滿足條件的 GPU 中, GPU1 恰恰是符合空閑資源滿足條件但同時又是所剩資源最少的 GPU 卡,因此 GPU1 被選出。
當 Pod 和節點綁定的事件被 Kubelet 接收到后,Kubelet 就會在節點上創建真正的 Pod 實體,在這個過程中, Kubelet 會調用 GPU Share Device Plugin 的Allocate方法, Allocate方法的參數是 Pod 申請的 gpu-mem。而在Allocate方法中,會根據 GPU Share Scheduler Extender 的調度決策運行對應的 Pod
會列出該節點中所有狀態為 Pending 并且ALIYUN_COM_GPU_MEM_ASSIGNED為false的 GPU Share Pod
選擇出其中 Pod Annotation 的ALIYUN_COM_GPU_MEM_POD的數量與 Allocate 申請數量一致的 Pod。如果有多個符合這種條件的 Pod,就會選擇其中ALIYUN_COM_GPU_MEM_ASSUME_TIME最早的 Pod。
將該 Pod 的 annotation ALIYUN_COM_GPU_MEM_ASSIGNED設置為true,并且將 Pod annotation 中的 GPU 信息轉化為環境變量返回給 Kubelet 用以真正的創建 Pod。
4
相關項目
目前項目已經開源到 github.com 上
gpushare-scheduler-extender
gpushare-device-plugin
部署
請參照部署文檔
測試樣例
首先創建一個使用aliyun.com/gpu-mem的應用
apiVersion: apps/v1
kind: Deployment
metadata:
name: binpack-1
labels:
app: binpack-1
spec:
replicas: 1
selector: # define how the deployment finds the pods it manages
matchLabels:
app: binpack-1
template: # define the pods specifications
metadata:
labels:
app: binpack-1
spec:
containers:
- name: binpack-1
image: cheyang/gpu-player:v2
resources:
limits:MiB
aliyun.com/gpu-mem: 1024
使用
請參照使用文檔
構建
請參照如何構建
視頻 Demo
Demo 1: 部署多個 GPU Share 的 Pod,發現他們以 binpack 的方式被放置到同一個 GPU 卡上
5
Demo 2: 避免錯誤調度申請資源超過單個 GPU 可用資源的 Pod
6
Roadmap
在 Device Plugin 中提供 Nvidia MPS 的可選支持;
支持該方案可以在由 kubeadm 初始化的 Kubernetes 集群自動化部署;
提升 Scheduler Extener 的高可用性;
為 GPU, RDMA 和彈性網卡提供通用方案。
轉載于:https://blog.51cto.com/14031893/2359674
總結
以上是生活随笔為你收集整理的助力深度学习!阿里开源可插拔 GPU 共享调度工具的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【测试工程师面试】面试官热衷询问的N个问
- 下一篇: Linux rsync 命令参数详解