重慶分公司,新征程啟航
為企業提供網站建設、域名注冊、服務器等服務
為企業提供網站建設、域名注冊、服務器等服務
這篇文章將為大家詳細講解有關基于Kubernetes的GPU類型調度實現是怎樣的,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
十載專注成都網站制作,成都企業網站定制,個人網站制作服務,為大家分享網站制作知識、方案,網站設計流程、步驟,成功服務上千家企業。為您提供網站建設,網站制作,網頁設計及定制高端網站建設服務,專注于成都企業網站定制,高端網頁制作,對成都辦公空間設計等多個行業,擁有豐富的網站制作經驗。
現如今,隨著企業紛紛在機器學習和深度學習上加大投入,他們開始發現從頭構建一個 AI 系統并非易事。
以深度學習為例。對于深度學習來說,算力是一切的根本。為了用海量數據訓練性能更好的模型、加速整個流程,企業的 IT 系統需要具備快速、高效調用管理大規模 GPU 資源的能力。同時,由于算力資源十分昂貴,出于成本控制,企業也需要通過分布式訓練等方式最大化 GPU 資源利用率。
面對這類新要求,基于 Kubernetes 的云原生技術為人工智能提供了一種新的工作模式。憑借其特性,Kubernetes 可以無縫將模型訓練、inference 和部署擴展到多云 GPU 集群,允許數據科學家跨集群節點自動化多個 GPU 加速應用程序容器的部署、維護、調度和操作。
在 1.6 版本和 1.9 版本中,Kubernetes 先后提供了對 NVIDIA GPU、AMD GPU 容器集群管理調度的支持,進一步提高了對 GPU 等擴展資源進行統一管理和調度的能力。
但是,Kubernetes 作為新一代 AI 開發基礎也存在缺陷。為訓練任務分配算力資源時,它通常是隨機分配容器所在節點的 GPU,而不能指定使用某類 GPU 類型。
雖然這對大部分深度學習模型訓練場景來說已經足夠了,但如果數據科學家希望能更靈活地使用更高性能的或某一類型的 GPU,Kubernetes 的能力就有些捉襟見肘了。
如何基于 Kubernetes 靈活實現 GPU 類型的調度。
問題:原生 Kubernetes 如何讓 Pod 使用指定類型的 GPU?
假設集群中有兩個節點有 GPU:節點 A 上有兩個 Tesla K80,節點 B 上有兩個 Tesla P100。Kubernetes 可以通過 Node Label 和 Node Selector,把 Pod 調度到合適的節點上,具體如下。
先給 Node 打上特定的 Label:
# Label your nodes with the accelerator type they have. $ kubectl label nodes node-a accelerator=nvidia-tesla-k80 $ kubectl label nodes node-b accelerator=nvidia-tesla-p100
此時節點 A 如下:
$ kubectl describe node node-a Name: node-a Roles:Labels: ... beta.kubernetes.io/arch=amd64 beta.kubernetes.io/os=linux kubernetes.io/hostname=node-a accelerator=nvidia-tesla-k80 Annotations: kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock ......
當 Pod 想使用 NVIDIA Tesla K80 GPU 時,可以通過下面的方式:
apiVersion: v1 kind: Pod metadata: name: cuda-vector-add spec: containers: - name: cuda-vector-add image: "k8s.gcr.io/cuda-vector-add:v0.1" resources: limits: nvidia.com/gpu: 1 nodeSelector: accelerator: nvidia-tesla-k80
上述做法貌似解決了問題,但它其實治標不治本。
試想一下,如果用戶集群在同一個節點上掛載了多種 GPU,我們該如何實現篩選?如果用戶在同一個節點掛載了多個顯存不同的 NVIDIA Tesla K80,而且想使用大于 10GiB 顯存的 GPU,我們又該怎么辦?
Kubernetes 的 Node Label 和 Node Selector 是沒法解決這些問題的。
在上游社區,很多開發者也經常圍繞此類問題展開討論,但一直沒有實際可用的方案落地。盡管如此,社區還是提供了不少精彩見解,比如下面就是社區中討論最多的一個方案,我們的方案也借鑒了其中的部分設計。
新增 ResourceClass API,用來匹配集群中的擴展資源,具體用法見下文介紹;
修改 Node API,在 NodeStatus 中增加字段描述擴展資源:
type NodeStatus struct { … ComputeResources []ComputeResource } type ComputeResource struct { // unique and deterministically generated. “resourceName-propertyHash” naming convention, // where propertyHash is generated by calculating a hash over all resource properties Name string // raw resource name. E.g.: nvidia.com/nvidia-gpu ResourceName string // resource metadata received from device plugin. // e.g., gpuType: k80, zone: us-west1-b Properties map[string]string // list of deviceIds received from device plugin. // e.g., ["nvidia0", "nvidia1"] Devices []string // similar to the above but only contains allocatable devices. AllocatableDevices []string }
擴展資源通過 Device Plugin API 向 Kubelet 組件注冊其信息,隨后 Kubelet 組件可以通過接收到的擴展資源信息更新節點狀態,即上一步中的 ComputeResources 字段;
調度器根據 ResourceClass 的定義過濾選擇合適的節點。調度器監聽 NodeStatus.ComputeResources 的變化并緩存節點上 ComputeResource 的分配信息,以便 ResourceClass 匹配合適的節點。
相比 Node Label 和 Node Selector,社區的方案更成熟。但不難看出,這個方案雖然可以修改 Kubernetes 核心代碼和核心 API,但作為一個倍受關注的技術問題的解決方案,它的進度非常緩慢,一直沒有得出更進一步的結論。
為了盡快實現在 Pod 使用指定類型的 GPU,并把它集成到 Caicloud Compass 中,我們在上游社區方案的基礎上提出了一種全新方案。
它充分利用了 Kubernetes 的擴展性和插件機制,并遵循最小侵入和方便移植的設計原則。但是,出于簡化用戶使用和降低開發維護難度等原因,它還是修改了 Kubelet 和 Scheduler 組件。
同時,由于我們采用了多調度器的實現方式,所以方案中對于 Scheduler 組件的修改不影響現有集群和之后的版本升級,而 Kubelet 組件采用了向后兼容式修改,不影響已經在集群中運行的應用。
該方案不僅支持 GPU 資源,還支持包括 Infiniband、FPGAs 等擴展資源,它依賴以下現有 Kubernetes 工作機制:
Scheduler Extender 機制
Device Plugin 機制
API Server 擴展機制(CRD)
Admission 擴展機制(ResourceQuota)
在 1.6 版本中,Kubernetes 可以通過 ThirdPartyResource(TPR) 創建自定義資源,但在 1.7 版本中,它推出了 TPR 的替代方法: CustomResourceDefinition(CRD)。
CRD 允許自定義一個資源類型,因此開發人員不再需要修改 Kubernetes 核心 API 或通過 API server aggregation 增加新資源,開發和維護難度大大降低。
在我們的方案中,我們通過 CRD 定義了兩種資源:ExtendedResource 和 ResourceClass。ExtendedResource 描述了一種擴展資源,比如 NVIDIA GPU;ResourceClass 則定義了容器選擇哪種擴展資源,它的使用方式和 Kubernetes 中的 Extended Resource(詳見參考文獻)類似,用戶可以直接在容器中指定,就像使用 CPU 和 Memory 一樣。
下面是才云方案的基本架構圖:
核心模塊一:Scheduler Extender。Scheduler Extender 利用 Scheduler 組件的擴展性,負責調度容器中使用了 ResourceClass 資源對象的 Pod。它通過查詢 ResourceClass 對象的定義過濾選擇節點上的 ExtendedResource 資源,從而找到合適的節點并綁定,并將合適的 ExtendedResource 寫到 Pod Annotation 中,供 Kubelet 組件使用。由于 Scheduler Extender 的擴展機制是通過 HTTP 的方式實現的,為了不影響集群的默認調度器性能,通過多調度器的方式為僅需要使用擴展資源的 Pod 提供調度,并且這種方式具有可移植性。
核心模塊二:Nvidia Device Plugin。此組件僅針對 NVIDIA GPU 擴展資源,除了負責與 Kubelet 組件通信,它還負責創建和維護 ExtendedResource 資源對象。
那么,當同一節點上有多種不同類型的 GPU 時,這個方案是如何解決類型指定的呢?
我們假設有節點 A 上有兩張 GPU,一張是 NVIDIA Tesla K80,另一張是 NVIDIA Tesla P100。那么這個節點上的 NVIDIA Device Plugin 會創建兩個 ExtendedResource 資源對象,分別描述這兩張卡的基本屬性,如型號、顯存、頻率等。同時,它也會向 Kubelet 注冊,把 A 節點上有兩張 GPU 告知節點上的 Kubelet。
這時,如果用戶想創建一個使用 K80 這張 GPU 的應用,他只需要創建一個 ResourceClass 資源,在 ResourceClass 中聲明使用型號為 NVIDIA Tesla K80 的 GPU(比如通過 Selector 的方式聲明),然后在容器中使用這個 ResourceClass 資源。
kind: ResourceClass metadata: name: nvidia.tesla.k80 spec: selector: matchLabels: model: "NVIDIA Tesla K80" kind: Pod metadata: name: example-pod spec: containers: - name: example-container resources: limits: nvidia.tesla.k80: 1
Kubernetes 默認調度器在經過一系列篩選過濾后,會調用 Scheduler Extender 的 Filter 方法,并將需要調度的 Pod 和過濾后的 NodeList 傳遞給 Filter,實現 ResourceClass 查找滿足需求的 ExtendedResource,從而找到合適的節點;
當調度器找到合適的節點后,調用 Scheduler Extender 的 Bind 方法,將 Pod 和 Node 綁定,并將合適的 ExtendedResource 資源對象寫到 Pod Annotation 中,供 Kubelet 組件使用。
當 Pod 和 Node 綁定后,節點上的 Kubelet 組件則開始創建容器,并通過 Pod Annotation 獲取容器需要使用哪塊 GPU 的信息,然后通過 Device Plugin API 調用 NVIDIA Device Plugin 的 Allocate 方法。
Allocate 方法參數是容器使用的 GPU DeviceID,它通過 DeviceID 查詢 GPU 的信息作為環境變量,返回給 Kubelet 用以真正創建 Pod。
從上述流程中可以看出,當我們想使用特定類型的 GPU 或者某一類 GPU 時,我們只需聲明該類型的 ResourceClass 資源對象,比如:
kind: ResourceClass metadata: name: nvidia.high.mem spec: selector: - matchExpressions: - key: "memory" operator: "Gt" values: - "10GiB"
更進一步,我們可以通過實現一個 Controller 監聽集群中的 ExtendedResource 資源,自動為一種類型的 ExtendedResource 創建一個 ResourceClass 對象,為用戶提供一些默認規則的 ResourceClass 資源對象。
在實際生產集群環境中,我們不僅需要滿足不同應用對資源的使用,更是要做到不同應用對資源使用的限制,以及對不同的 namespace 分配不同的資源。而在 Kubernetes 中,我們一般會通過 ResourceQuota 資源對象來限制不同 namespace 的資源,例如:
kind: ResourceQuota metadata: name: example-quota namespace: system spec: hard: cpu: "10" memory: 20Gi nvidia.com/gpu: "5"
從上面的 ResourceQuota 定義里,我們可以看到 default 命名空間可以使用 5 塊 NVIDIA GPU,但它并不限制具體該使用哪種類型的 GPU。
那么,我們該如何實現對 GPU 類型的限制呢?
首先,GPU 這類擴展資源使用是標量,所以我們對標量資源的限制只能做到整數個數的限制。
其次,從上述方案中,我們知道一種 ResourceClass 代表了一種類型的擴展資源,因此對擴展資源的限制其實就是對 ResourceClass 的限制。
這樣理解之后,問題就很簡單明了了。下面直接給出相應的 ResourceQuota:
kind: ResourceQuota metadata: name: example-quota namespace: system spec: hard: cpu: "10" memory: 20Gi nvidia.tesla.k80: "5"
除了 GPU 類型調度,這個方案其實也可以解決 GPU 共享問題。這同樣是上游社區的一個熱門討論話題。
ExtendedResource 資源中包含著 GPU 的頻率、顯存等信息,當多個容器想使用同一塊 GPU 時,我們可以定義一個 ResourceClass 資源對象,在 ResourceClass 中聲明使用多少顯存(這里共享的是顯存)。這樣,應用部署時,我們只要在容器中聲明使用該 ResourceClass 資源即可,之后 Scheduler Extender 會過濾符合條件的 ExtendedResource 對象,綁定到合適的節點上。
如果要實現資源共享,我們可能需要在 ExtendedResource 中記錄顯存的用量情況,供調度參考。當然,這里沒有考慮到資源的隔離和限制的問題,這需要單獨實現和更進一步的討論。
關于基于Kubernetes的GPU類型調度實現是怎樣的就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。