陳 培, 王 超, 段國棟, 王德奎, 王 斌, 王文瀟, 孫遼東, 荊榮訊,邢良占, 劉慧興, 姬貴陽
(浪潮電子信息產業股份有限公司, 濟南 250101)
近年來人工智能技術快速發展, 尤其是深度學習方面取得了諸多令人矚目的成就, 而Kubernetes作為下一代分布式系統的主流, 作為云原生的新生力量, 其發展也是十分迅速, Kubernetes有著完善的組件和工具生態系統, 能夠減輕應用程序在公有云或私有云中運行的負擔, 并且可以和任何場景結合, 另外Kubernetes的插件化、組件化開發方式能夠支持更多定制化的設計開發工作, 這些優點讓越來越多的開發者和互聯網企業將人工智能應用部署在Kubernetes集群上. 但由于Kubernetes并不是主要針對深度學習設計, 對深度學習這個特定領域需要做定制優化.
目前針對Kubernetes集群部署深度學習應用已有很多優化嘗試, 如國內騰訊的Gaia調度系統[1,2]能夠細粒度使用GPU資源, 但是并沒有針對數據集使用和分布式訓練進行優化. 對于GPU虛擬化使用, NVIDIA推出了Multi-Process Service[3]和virtual GPU (vGPU)[4]兩種方案, 但MPS具有故障傳遞限制[5]而且vGPU需要授權使用, 其他的很多開源方案在具體實踐應用上都有一定的局限性.
本文針對具有一定規模的Kubernetes集群上部署深度學習負載的場景, 設計和實現了一系列的優化方案, 并且已經在實際生產環境中實踐, 取得了良好的效果.本文從深度學習所要求的數據處理、graphics processing unit (GPU)計算、分布式訓練等幾個方面進行優化, 主要優化方面有以下幾點: 針對目前人工智能應用只能占用整數GPU卡資源, 難以實現GPU卡資源多任務復用的場景, 提出GPU多任務共享調度技術,能夠實現多種應用共享同一張GPU卡資源, 極大限度的挖掘GPU計算力, 提升GPU的使用效率; 隨著訓練數據集規模的快速增長, 提出訓練數據集預加載技術能快速提高數據集讀取速度進而提高單機和分布式的訓練速度; Kubernetes的原生調度系統和策略并不能很好的滿足目前人工智能場景, 因此提出了針對non uniform memory access (NUMA)特性[6]、數據集親和性的優化調度技術. 本文提出的優化方案覆蓋了數據處理、計算等方面, 以上技術極大簡化人工智能負載在規模化云原生平臺上的部署難度和提高運行效率,同時從實踐上來看也驗證以上技術對人工智能應用有著顯著的提升作用.
云原生上部署深度學習應用時, 會遇到對接不同底層存儲的情況, 諸多實驗表明, 不同存儲系統對深度學習訓練性能是有著不同程度的影響. 訓練開始時, 存儲系統的性能會極大的影響訓練數據的加載和后續訓練數據的持續讀取, 如network file system (NFS)[7]等傳統存儲系統對于海量數據和海量小文件數據的讀寫性能不是很高, 而在深度學習訓練場景(圖片、視頻、語音等)中的訓練數據大多是以文件形式而存在, NFS系統在很多實際場景都會使用, 這樣會很大程度上影響訓練數據的讀取速度進而影響整體訓練性能. 高性能存儲系統如BeeGFS[8]等, 如果在高并發、大數據量和網絡帶寬有限制情況下也會出現性能下降問題. 因此, 加速訓練過程的數據前期讀取和簡化在云原生上對不同存儲的對接亦是業界熱門問題, 本文提出在Kubernetes集群上利用本地高性能存儲設備進行數據緩存進而提高訓練過程前、中的數據讀取速率, 大大減少對存儲系統和網絡的依賴, 另外根據深度學習任務特性進行針對性優化, 與直接使用傳統存儲系統的表現相比這些改進提高了訓練性能.
Kubernetes集群上完成對數據集緩存需要對訓練數據進行很多的針對性處理和設計, 本文對Kubernetes集群上數據集本地和節點緩存兩種情形設計了一套緩存系統, 該緩存系統要求需要對數據集生命周期具有細粒度的管理特性. 數據集緩存系統共分為兩種方式, 即本地模式和節點緩存模式. 本地模式即直接使用從存儲系統中讀取和使用數據集, 不做任何緩存, 數據讀取速率則完全依賴于存儲系統性能或者網絡帶寬. 節點緩存模式即本文提到的數據緩存加速機制實現, 在運行訓練任務的節點上進行數據集緩存和管理, 這樣在訓練時存儲系統性能和網絡帶寬就不會是瓶頸, 如果底層存儲介質為高性能設備如SSD、NVMe則會有更大的增益.
本文針對訓練使用數據集的過程做了主要以下優化工作:
(1) 設計dataset-agent 實現在Kubernetes內數據集的本地和節點緩存兩種使用模式.
(2) 針對大數據集和海量小文件數據緩存過程進行了優化并提高效率.
(3) 簡化使用過程和能夠對接不同存儲, 使用者無需關注底層存儲系統.
兩種數據集使用方式的架構如圖1和圖2所示.

圖1 本地模式架構圖
本地模式數據讀取為直接使用存儲系統, 不在本文中詳述, 整體架構如圖1所示, 實現邏輯為管理節點的Kubernetes API Server接收到用戶任務請求后下發訓練任務數據集名稱、存儲位置等信息給Kubernetes,然后Kubernetes的調度器調度訓練Pod到相應節點上進行訓練, 其中數據集的加載和管理則完全依靠集群內的存儲系統進行管理, 如上所述, 性能和使用邏輯則完全依靠存儲系統, 由于分布式和共享存儲的訓練數據讀取很大程度上會與網絡環境強相關, 因此對訓練性能會有一定程度的影響, 取決于網絡設備性能和并發量, 節點緩存模式則避免上述問題.
節點緩存模式的架構如圖2所示, 各個計算節點上都會部署dataset-agent服務即數據集代理服務, 主要管控數據集緩存的生命周期, 包括數據集緩存創建、刪除、更新等信息. 其實現機制為在實際訓練開始前提前將訓練用數據集緩存到被調度使用的節點, 這樣在實際訓練時, 訓練數據則為本地數據, 不再受存儲系統或者網絡性能的影響. 具體實現邏輯為Kubernetes管理節點接收到訓練任務請求后, 在承載訓練任務的Pod中通過init-container形式將訓練所需的信息如數據集名稱等進行封裝, Kubernetes調度Pod成功后, initcontainer首先會將數據集信息下發到部署到該節點上的dataset-agent, 隨后dataset-agent進行校驗(數據一致性)決定是否訪問存儲系統進行數據集拉取進行緩存.

圖2 節點緩存架構圖
如果該節點進行了節點數據緩存, 即在訓練節點上的存儲設備上就會有相應的訓練數據.數據緩存結束后, 隨即啟動訓練程序Pod同時將緩存的數據集進行掛載, 這樣就能直接利用本地存儲介質進行數據讀取和訓練, 消除了存儲系統和網絡的影響, 提高訓練速度, 經測試該系統能夠支持多種存儲系統包括NFS、LusterFS[9]、BeeGFS、HDFS[10]等.
節點緩存技術在部署到Kubernetes集群生產環境中時遇到很多實際問題, 為此針對如下一些主要情況做了優化處理.
(1) 實際過程中一個訓練任務會出現掛載多個數據集的情況, 所以將這樣一對多的組合作為一個請求任務進行管理, 同一訓練任務在多次中只會有一條任務數據, 避免了由于init-container重啟導致多次發起數據集緩存請求造成重復數據的情況.
(2) 數據集一致性比對, 主要是針對緩存數據集前的校驗工作, 針對數據集名稱相同, 通過比對原始數據集文件摘要和節點中數據集緩存的文件摘要來確認數據集和需要的數據集緩存是否相同, 這種比對規則的前提, 即假定節點中的數據集緩存不會變更, 也就是在緩存前生成的文件摘要是準確不變的, 詳細流程見圖3.

圖3 數據集一致性比對流程示意圖
(3) 數據集緩存和鏡像拉取并行處理, 在節點沒有即將加載運行的鏡像情況下, 被調度到該節點的Pod相當一段時間是在進行鏡像拉取工作, 由于該段時間資源(CPU、內存等)已經分配, 但是并沒有在實際使用這些資源, 因此可以有效利用Pod調度成功并且運行前這一時間段內Pod的空閑資源來提升拉取數據集的效率, 這樣相對較小的數據集在鏡像拉取完成的同時也緩存完成.
(4) 自動清理數據集緩存, 當計算節點空間不足時,清理緩存數據釋放空間就變得十分必要, 系統根據既定的規則自動清除, 清除策略發生在新訓練任務緩存數據集, 但緩存空間不足時, 系統亦會清除緩存空間中未在使用的數據集, 且長時間未使用的數據集也會作為清除對象被自動清除, 當清除的空間能夠滿足新數據集緩存時候, 即停止清除, 這里需要清除空間的大小是所需數據集大小的1.2倍.
(5) 支持多種存儲系統下的數據集操作, 即將相關配置信息放到Pod的yaml的環境變量中來解決.
(6) 多個數據集緩存進程并發, 一個節點上的datasetagent可以處理多個數據集緩存操作來提高效率, 同時每個dataset-agent也做了多線程處理, 提高處理速度.
(7) 海量小文件數據集緩存優化處理, 采用對待緩存數據進行壓縮后再進行傳輸完成節點緩存, 這樣極大提高帶寬的利用率, 實際測試中能夠有效減少因為網絡傳輸帶來的數據緩存延遲, 聚合傳輸測試出文件個數為5萬打一個包, 每個包的大小約為800 MB左右, 該設置下效率較高, 性能比命令傳輸提高6-8倍.聚合傳輸可根據實際業務場景調整聚合傳輸相關參數,解決機器資源占用、傳輸效率問題.
(8) 特別針對NFS系統, 通過使用NFS-RDMA技術[11](如圖4所示)在小文件傳輸方面性能提升2倍左右.通過NFS-RDMA V3協議在小文件傳輸上提高約1.5倍.

圖4 NFS-RDMA技術應用示意圖
GPU有著強大的處理并行計算任務能力, 其單一芯片上集成大量的計算核心的架構設計使得GPU對于計算敏感性任務尤其適用. 當前GPU已經廣泛應用于視覺、自然語言處理等領域來加速處理過程.
CUDA (compute unified device architecture)是NVIDIA針對GPU架構提供的一套針對GPU的通用API平臺, 用戶可以通過CUDA簡單和快速的使用GPU以達到加速效果. 無論在公有云或者私有云, GPU設備已經廣泛部署并使用, 起到了顯著的效果. 實際使用中GPU使用實例的類型也是多種多樣, 對底層計算資源的要求也不同. 作為底層提供計算資源支持的平臺對于用戶來說應該是透明的, 需要做到按需索取和使用. 隨著深度學習業務廣泛鋪開和GPU的架構快速迭代, 對于GPU計算資源的需求也變的多樣, 從單卡訓練到分布式訓練, 從獨占使用到多任務共享. 但Kubernetes、GPU (除Ampere架構[12])和CUDA原生并不支持GPU細粒度調度和使用. 基于容器的GPU虛擬化的技術在Kubernetes上容器化使用GPU也同時面臨如下一些具體問題: 需要指定GPU設備[13]; 只能獨享該整個GPU設備[14], 不能多任務共享; 單一GPU使用容器間只能共享主機內存[15].
本文提出一種能夠在互相隔離的容器間進行共享同一GPU設備內存的方法來提高GPU的利用率.該方法不需要更改用戶的鏡像或者訓練代碼即可達到GPU虛擬化的目的. 通過自定義修改的Kubernetes device plugin可以實現按顯存大小來分配GPU資源,即GPU可按顯存大小粒度進行調度使用, 不再局限于整卡級別的粒度, 同時進程間可以做到隔離, 保證用戶應用不會互相影響. 除此之外利用unified memory技術[16]實現顯存的超分使用, 即在實際訓練過程中可以保證超出GPU總顯存量進行訓練, 在適量超出GPU顯存容量后保障較大模型正常訓練.
由于docker出于安全對權限做了限制導致NVML接口[17]在容器內無法查詢正在GPU上運行的進程, 為此本文針對GPU上正在運行進程的查詢機制做了優化, 實現在容器內可以查詢正在GPU上運行的進程信息, 可以正確的顯示該容器內運行進程而不是主機上的進程以保證進程安全和訪問安全.
本文針對GPU虛擬化主要做了以下工作:
(1) 通過GPU sharing device plugin 實現在Kubernetes內細粒度調度GPU任務.
(2) 封裝CUDA driver API 實現GPU虛擬化使用.
(3) 添加顯存超分使用, 即超出GPU總顯存量可以繼續進行訓練.
(4) 優化NVML查詢GPU進程機制使得在容器內正確顯示GPU上運行的進程信息.
Kubernetes的device plugin插件的主要用途為將計算資源信息(如GPU, RDMA, FPGA[18]等)發布給集群并無需修改Kubernetes核心代碼, 圖5展示了基本的device plugin與kubelet通訊過程, 主要通過兩個步驟實現.

圖5 Kubernetes device plugin和各組件關系
(1) 資源發現: 首先每種擴展的資源類型都作為一個device plugin形式展現. Device plugin通過gRPC服務注冊到kubelet上. 注冊成功后, device plugin將其所管理的設備列表發送給kubelet. 最后kubelet負責將這些擴展資源發布給Kubernetes master;
(2) 資源分配: 當用戶申請資源時, 調度器會將相應的Pod調度到具有所申請擴展資源的節點上. 所在節點的kubelet會將設備使用請求發送給device plugin.然后device plugin將相應的擴展資源分配給Pod. 但針對GPU等設備, 直接使用開源device plugin并不能針對GPU內存進行細粒度的使用和分配.
本文中將現有的一些擴展設備資源(如GPU等)的device plugin進行優化, 實現了以下功能: 基于Kubernetes標準的device plugin機制, 支持接入多種AI計算資源; 多種可調度的資源在業務上統一建模, 以資源名稱、數量、類型等; 信息描述接入集群的異構實現, 實現統一的調度、運維管理; 實現多device plugin管理插件, 由一個device plugin實現多個異構資源的注冊、分配等, 且plugin的資源使用僅需要0.1 CPU/0.3 GB內存, 降低運維成本; 實現GB粒度的資源管理以及GPU復用場景下的資源管理.
整體vCUDA架構設計和流程如圖6所示, 主要由3部分組成: GPU sharing device plugin (以下簡稱GS device plugin), 調度器scheduler和vCUDA library.

圖6 vCUDA架構和流程示意圖
(1) GS device plugin
其中經過修改和優化的GS device plugin 在各個節點上運行負責建立虛擬GPU設備和與kubelet進行通訊. GS device plugin發現設備上報時將GPU顯存視為一種資源進行上報, 這樣GPU顯存也可以作為可調度的Kubernetes集群資源進行使用.
(2)調度器 scheduler
調度器為GS device plugin提供其所申請的調度服務, 調度成功后調度器會返回包含所分配GPU信息的響應.
(3) vCUDA library
在運行的Pod中vCUDA負責實際的內存控制.vCUDA庫通過掛載的方式與運行的Pod進行綁定. 當容器中應用開始運行時, vCUDA通過對訓練過程中內存相關的API進行劫持從而實現內存大小的控制和隔離, 主要由以下幾個部分組成:
(1) vCUDAManager: vCUDA library的總控制, 對于CUDA的操作均需要通過該類對象, 單例運行只初始化一次. 其中主要包括cudaManager、nvmlManager、gpuMemoryManager和dlsym的map管理.
(2) cudaManager: 管理所有CUDA API的劫持, 主要是cuMalloc類似的函數, 當分配顯存時調用此類的接口來控制OOM問題.
(3) nvmlManager: NVML API的劫持管理類, 主要是獲取NVIDIA GPU卡上各個進程運行的詳細信息,如顯存和進程PID.
(4) gpuMemoryManger: 記錄各個GPU卡的顯存利用信息, 當分配顯存時會調用此類API判斷是否OOM.
GPU虛擬過程主要通過GPU的顯存申請和分配來實現. 本文中以1 GB顯存作為基本粒度, 一個最終的內存分配單元作為一個虛擬GPU設備.當用戶申請一個規定大小(GB粒度)的虛擬GPU調度請求后, 調度器會將請求發布到給個節點上的kubelet, 由于GS device plugin已經將其所管理的設備列表和資源信息發送給kubelet, 因此GS device plugin 在收到所分配的Pod為虛擬GPU的請求后將Pod所要創建的allocateResponse返回給kubelet進行資源創建即可, 其中包括基本的Pod環境變量, Pod掛載卷配置 (例如NVIDIA驅動, CUDA庫, vCUDA庫)和相應設備.
另外通過dlsym劫持函數的map對象中, 針對NVML庫設置單獨劫持進行處理主要是為了防止其他應用通過dlsym來調用CUDA和NVML的API, 例如nvidia-smi命令, 在使用CUDA劫持庫時始終保持結果一致性. 具體使用到的CUDA和NVML API如表1和表2所示.

表1 CUDA driver API使用情況

API類別 NVML API 描述Device-related APIs nvmlDeviceGetComputeRunning-Processes Get information about processes with a compute context on a device.nvmlDeviceGetHandleByUUID Acquire the handle for a particular device, based on its globally unique immutable UUID associated with each device.nvmlDeviceGetIndex Retrieve the NVML index of this device.nvmlDeviceGetMemoryInfo Retrieve the amount of used, free and total memory available on the device, in bytes.nvmlDeviceGetComputeRunning-Processes Get information about processes with a compute context on a device.
基于不同場景, 本文中提到GPU虛擬化實現如下場景支持:
(1) 基于GPU顯存隔離的GPU虛擬化
資源調度模塊將多個GPU訓練任務調度到同一張GPU卡, 通過設置的顯存粒度大小, 對GPU顯存進行切片, 來限制每個任務對GPU顯存大小的使用.GPU任務通過設置的顯存粒度切片, 來達到不同任務所用顯存互相隔離的效果.
設置好需要使用的顯存粒度大小后, 具體使用時就把GPU卡整塊顯存按預置顯存粒度大小分割為多個粒度切片進行隔離. 如圖7上半部分場景所示, 4個GPU卡, 每個GPU卡的顯存大小為24 GB, 系統設置顯存粒度大小為8 GB, 這樣, 每個GPU卡就可以分割成3個 8 GB細粒度的GPU切片. 當作業運行時, 作業會按GPU切片請求GPU資源, 像圖中task1就是要求4個GPU切片, 每個切片大小為8 GB. 然后系統資源調度器會針對顯存隔離場景作業執行相應的調度策略, 給task1分配了GPU0、GPU4、GPU2、GPU3卡中各1個顯存切片. 再看單個GPU卡中運行的多個作業, 各作業所使用的顯存是互相隔離的, 如圖中GPU0卡運行的task1、task3、task4.
(2) 基于unified memory (UM)顯存隔離的GPU內存使用優化
NVIDIA的Pascal[19]之后架構均已經支持UM技術, 得益于啟用UM機制, 將主機部分內存分配給UM管理, UM可以用來統一管理分配作業請求的GPU顯存資源, 此時UM就擴展了系統中GPU可用顯存大小, 可以滿足運行更多的作業和對較大模型的支持. 圖7下半部分場景所示顯存粒度大小為8 GB時, 如圖所示按UM管理系統UM顯存容量為24 GB(其中8 GB為系統內存), 這樣在資源調度器可以執行UM場景下的調度策略, 即16 GB顯存大小的GPU卡就可以同時運行3個GPU作業(如圖7中task1、task2和task3), 每個作業使用8 GB顯存粒度. 此時, UM擴展了系統可用顯存容量, 支持運行更多的GPU作業.

圖7 vCUDA適用場景說明
NUMA (非統一的內存訪問)是一種在多CPU系統上可用的技術, 允許不同的CPU以不同的速度訪問不同部分的內存. 任何直接連接到CPU的內存都被認為是“本地的”, 并且可以快速地訪問. 沒有直接連接到CPU的任何內存都被認為是“非本地的”, 并且將有可變或較長的訪問時間, 這取決于必須通過多少個互連才能到達目標. 在現代系統中, “本地”和“非本地”內存的概念也可以擴展到外圍設備, 如NIC或GPU. 為了實現高性能, 應該在分配CPU和設備盡可能的使它們能夠訪問相同的本地內存.
NUMA拓撲管理就是用來處理實現高性能下的CPU與GPU等設備資源分配. 云原生上的很多深度學習負載除了依賴GPU計算能力之外, 對于某些特殊的應用同樣對CPU也有著較強的依賴, 如數據前處理、數據搬運等過程. 因此在調度任務時, 考慮到NUMA拓撲特性, 將最優化的NUNA組合分配給任務, 則能起到提升整體集群的運行效率和資源利用率的效果.
總體上, 節點內NUMA親和性調度遵循以下流程,如圖8所示: 當Pod創建的時候, 節點kubelet中的topology manager 根據配置的 policy 策略來調度Pod,其中有如下調度策略:

圖8 NUMA親和性示意圖
(1) none (默認): 無策略, 極容易產生所調度的Pod使用的CPU、內存和網卡分別位于不同的NUMA node上.
(2) best-effort: 根據當前Pod的資源請求, 盡量滿足Pod分配的資源, 不滿足的就隨意劃分.
(3) restricted: 嚴格保證Pod資源請求, 如果資源不滿足Pod的affinity需求, Pod就會進入terminated狀態.
(4) single-numa-node: 滿足policy的 Pod請求都會從一個單獨的NUMA node內進行分配, 如果不滿足,Pod會進入terminated狀態. 這個跟前兩個的區別是,前兩個可以請求從兩個NUMA node都分配資源.
本文提出的NUMA 拓撲管理通過topology-Manager和deviceManager、cpuManager、GPU設備之間交互來完成, 具體各模塊功能描述和交互過程如下和圖9所示.

圖9 Kubelet與device plugin 交互示意圖
(1) NUMA拓撲管理器topologyManager: 運行資源分配算法, 進行CPU和GPU資源的分配候選集的構造生成、合并優選和輸出最佳分配結果; 同時更新維護CPU和GPU已分配狀態信息.
(2) CPU管理器cpuManager: 提供獲取NUMA節點信息接口; 按分配算法執行具體的CPU分配操作;更新CPU分配狀態.
(3)設備管理器deviceManager: 提供GPU設備的NUMA信息; 按分配算法執行具體的GPU分配操作,選取包含必選設備中的最優設備組合, 具體說明如下:如當前節點kubelet傳遞可用設備是[GPU-0, GPU-1,GPU-2, GPU-3], 考慮NUMA親和性的必選集[GPU-2,GPU-3]作為候選, 而實際業務需求為3個GPU卡, 則GPU 插件會根據內置GPU通信打分規則(見表3)(該規則基于GPU與GPU間最優連接方式而確定, 由于NVLink具有GPU間最佳的傳輸效率, 因此該連接方式分數為100且隨鏈數依次累加, 其他連接方式依次遞減). 按照上述打分規則從[GPU-0, GPU-1]中選取最優的一個GPU (如GPU-0 (具有與GPU2或GPU3 NVLink連接))之后, 即組合[GPU-0, GPU-2, GPU-3]為最終分配組合; 其他功能如實現與GPU插件之間的業務接口和更新GPU分配狀態等.

表3 Device plugin 內置通信打分規則(GPU對)
(4)快照管理器checkpointManager: 提供進行CPU和GPU分配狀態信息的生成、更新和刪除接口.
在集群上運行大規模數據集訓練時, 會遇到如網絡帶寬的限制影響數據集讀取速率、訓練Pod所在計算節點沒有數據集緩存等情況導致需要重新進行緩存配置, 這些情況在大數據集和復雜(文件類型多樣、海量小數據等)情況下會極大延長訓練時間和占用網絡帶寬, 因此本文針對數據集的使用提出一種數據集親和性調度策略來提高本文第一部分提到的數據集緩存技術使用效率和間接減輕集群間由于數據集傳輸帶來的帶寬占用.
基本的數據集親和性準則為盡量選擇作業Pod所需數據集完全匹配命中節點緩存數據集的節點. 對于未命中或部分命中的節點則忽略數據集親和性策略,具體則由圖10舉例說明數據集親和性, 如集群中有Node1和Node2兩個節點, 其中Node1上已緩存有數據集A, Node2上已緩存有數據集B.如果作業需要數據集A, 則調度器需要在Node1滿足預選條件時, 并完全匹配作業所需數據集時才能將作業調度到Node1上. 如果作業需要數據集A, Node1不滿足預選條件,Node2滿足預選條件, 則調度器會將作業調度分配到Node2上, 即數據集親和性調度是一種優選算法策略.

圖10 數據集親和性場景
在進行數據親和性調度時會對待選節點進行打分,具體的得分計算方法如下.
節點緩存數據集得分公式:

其中, node為集群節點選擇器, 根據節點名稱參數nodename選擇節點; clusterNodeList為集群節點列表;NodeMatchDataSet為當前節點匹配作業要求數據集;dsname為數據集名; podRqDS為Pod所需的數據集;UpdateDatasetInUse表示使用中的數據集需要更新;nodeCacheDataSet為該節點上已經緩存的數據集.
公式說明:
NodeMatchDataSet為當前節點匹配作業要求數據集, 當Pod請求的數據集和當前節點緩存數據集的交集如果匹配則累計該數據集, 否則過濾;
如果節點已緩存數據集并未完全匹配作業數據集,則節點得分為0;
如果作業要更新節點上正在使用的數據集, 則得分為0;
如果作業所需數據集全部命中節點緩存數據集,且數據集可用, 則節點數據集得分為完全匹配數據集個數, 即作業所需數據集個數;
如果解析作業所需數據集和節點數據集參數異常,則節點得分為0.
此分值為節點數據集原始得分, 后面還需經歸一化處理后, 才能跟其它優選策略共同作用調度結果. 該得分值越高, 優先級越高, 得分越低, 優先級越低. 如果節點沒有或者只有部分作業Pod所需的數據集, 則其得分為0; 如果節點上有Pod所需要的全部數據集, 則得分為最高值. 如果出現多個節點的數據集親和性得分相同且都為最高分, 調度器可參考其它優選策略進行優選.
文中提到的vCUDA、NUMA、數據集親和性調度策略已經在具體的生產環境中得到驗證, 以下測試將具體展示其性能效果.
分別采用YOLOv3[21]、ResNet50[22]、BERT[23]在視覺、自然語言處理領域的典型模型進行測試vCUDA性能效果.
實驗環境如下:
模型: 采用YOLOv3, ResNet50和BERT模型, 網絡模型包含了主要的圖像處理、自然語言處理且具有相對復雜的網絡結構能夠充分利用GPU計算能力.
訓練數據: 采用COCO[24], ImageNet large scale visual recognition challenge (ILSVRC 2012)[25]訓練數據集(約130 G, 120萬張訓練圖片), SQuAD1.1[26].
訓練框架: TensorFlow[27], Darknet[28].
GPU: Tesla V100S 32 GB.
實驗內容如下:
在單GPU上進行單個任務和兩個任務測試, 采用直接在單GPU上調度訓練任務(即不限制內存使用和隔離)和使用vCUDA進行內存限制和隔離進行對比.
實驗結果和分析如下:
如圖11所示單任務訓練對比結果顯示在GPU上只有一個訓練任務時, 與benchmark結果比較, 采用vCUDA library對訓練沒有影響, 即CUDA的劫持方案并沒有造成訓練性能上的損失; 在同一個GPU上同時運行兩個訓練任務時, 采用vCUDA方案的訓練效果和使用原生CUDA方案沒有較大差異, 以上結果顯示采用vCUDA方案可以進行細粒度顯存使用和隔離的同時并保證同一塊GPU上進行多任務的提交和訓練, 以及性能保證.

圖11 單任務訓練對比結果
為驗證同NUMA內計算資源對訓練任務帶來相應的影響, 設計如下實驗.
實驗環境如下:
GPU: A100 40 GB;
CPU: Xeon Platinum 8 268 @ 2.9 GHz, 2 Sockets,Cores per socket: 24;
存儲系統: NFS.
實驗內容如下:
(1) 采用TensorFlow框架和ResNet50使用ILSVRC 2012數據集進行測試, batch_size=418, steps=500,GPU與同NUMA node和不同NUMA node的CPU綁定進行測試.
(2) 采用PyTorch[29]和Transformer[30]使用wmt14_en_de數據集[31]進行測試, GPU與同NUMA node和不同NUMA node的CPU綁定進行測試.
采用TensorFlow和ResNet50使用ILSVRC 2012數據集在GPU與同NUMA node和不同NUMA node的CPU綁定條件下的測試結果如表4, 采用PyTorch和Transformer使用wmt14_en_de數據集在GPU與同NUMA node和不同NUMA node的CPU綁定條件下測試結果如表5所示.

表4 在ILSVRC 2012數據集上采用TensorFlow和ResNet50的Throughput測試結果(image/s)

表5 在wmt14_en_de數據集上采用PyTorch和Transformer的Throughput測試結果(image/s)
從表4和表5可以看出采用和CPU相同的NUMA的GPU0和GPU1進行測試時, NUMA 綁定在使用GPU數量不多的情況下訓練方面提升有限, 多GPU訓練時綁定會有一定提升, 且同NUMA內 GPU、CPU、內存配合使用效果最好, 同時CPU和內存也不要跨NUMA node使用.
針對數據集緩存和親和性調度采用海量小文件的訓練場景, 即使用ILSVRC 2012數據集的原生JPEG格式數據和ResNet50模型進行訓練性能測試, 具體實驗信息如下:
實驗環境:
GPU: A100 40 GB;
訓練框架、模型: PyTorch, ResNet50;
數據集: ILSVRC 2012數據集(JPEG格式);
存儲系統: Lustre;
網絡: 100 Gb InfiniBand網絡.
實驗內容:
(1) 采用Horovod分布式訓練框架[32], PyTorch作為后端進行訓練, ResNet50使用ILSVRC 2012數據集進行測試, batch_size=256, 訓練數據集讀取方式采用網絡讀取存儲系統中的文件方式和節點緩存模式進行對比.
(2) 采用PyTorch框架的DistributedDataParallel(DDP)[33]方式進行訓練, ResNet50使用ILSVRC 2012數據集進行測試, batch_size=256, 訓練數據集讀取方式采用網絡讀取存儲系統中的文件方式和節點緩存模式進行對比.
采用Horovod和ResNet50使用ILSVRC 2012數據集在數據集讀取方式采用網絡讀取存儲系統方式和節點緩存模式進行訓練的對比結果如表6所示, 采用PyTorch的DDP和ResNet50使用ILSVRC 2012數據集在訓練數據集讀取方式采用網絡讀取存儲系統方式和節點緩存模式進行訓練的對比結果如表7所示.
從表6和表7數據可以看出在節點緩存模式下無論是使用Horovod還是PyTorch-DDP方式進行訓練,其訓練效果均要比直接使用網絡讀取存儲系統中的訓練數據集好, 并且在多GPU任務下的表現更加明顯. 直接使用存儲系統中文件由于在網絡、CPU和GPU之前的數據拷貝操作會使GPU計算資源處于閑置狀態,因此會影響訓練效果, 降低訓練速度, 圖12和圖13展示了在以上兩種測試中GPU使用情況, 其中圖12(a)為使用2個GPU訓練的情況, 圖12(b)和圖13為使用8個GPU訓練的情況. 可以看出節點緩存模式下訓練框架較充分使用了GPU計算資源, 使得GPU一直處于比較平均且正常的使用率, 在PyTorch-DDP方式下尤為明顯, 而通過網絡使用存儲系統的情況下可以看到GPU使用有相當的空置狀態, 此種情況會在高并發讀取數據情況下變得尤為突出, 如分布式訓練等.由此可見, 采用節點緩存方式能夠有效減小網絡上的傳輸壓力并同時提高訓練效果.

圖12 在ILSVRC 2012數據集上采用Horovod-PyTorch和ResNet50的GPU利用率(縱軸為GPU利用率(%), 橫軸為時間(s))左: 直接使用存儲系統; 右: 節點緩存模式

圖13 在ILSVRC 2012數據集上使用PyTorch-DDP和ResNet50的GPU利用率(縱軸為GPU利用率(%), 橫軸為時間(s))

表6 在ILSVRC 2012數據集上采用Horovod和ResNet50的測試結果

表7 在ILSVRC 2012數據集上采用PyTorch的DDP和ResNet50的測試結果
實際應用環境中配合使用數據集調度策略可以充分利用大規模集群中節點上已緩存的數據資源, 進而提高訓練性能和集群計算資源的利用率.
本文針對在Kubernetes集群上部署深度學習應用所遇到的一些問題, 對數據、計算方面提出了一系列優化方案和設計, 并結合實際場景進行了測試, 整體上達到了預期效果. 其中數據集緩存和親和性調度能夠極大的減少由于存儲系統和網絡環境限制帶來的數據讀取速率慢問題; vCUDA技術能夠解決部分場景下的GPU共享要求, 并且利用UM機制擴大顯存使用;另外NUMA親和性調度和針對海量小文件的優化技巧在實際測試和生產場景中均能夠提供可觀的整體性能提升.