?

面向深度學習應用的組件式開發框架的設計實現

2024-03-21 02:25魏宏原
計算機應用 2024年2期
關鍵詞:實例部署組件

劉 祥,華 蓓,林 飛,魏宏原

(中國科學技術大學 計算機科學與技術學院,合肥 230027)

0 引言

近年來,深度學習算法在圖像識別[1]、目標檢測[2]、目標追蹤[3]等領域正逐漸取代傳統算法[4]。隨著深度神經網絡(Deep Neural Network,DNN)模型和通用數據處理過程的日趨多樣化,以及各種加速硬件的不斷涌現,深度學習應用的軟件開發難度在不斷增大。一個深度學習應用的開發通常需要整合來自多個開源項目的代碼,并針對底層硬件平臺進行運行效率優化,開發工作量大、周期長,而且圖形處理器(Graphics Processing Unit,GPU)資源利用率通常不高。GPU是深度學習的主要算力來源,但目前GPU 運行模型推理任務的資源利用率很低。例如,實時車牌識別應用中分別將YOLO(You Only Look Once)用于車輛檢測、RetinaNet 用于車牌檢測、LPRNet 用于車牌識別這3 個神經網絡模型,在3 840×2 160 分辨率、30 FPS(Frames Per Second)幀率的視頻流輸入下,使用單塊NVIDIA RTX2080Ti 依次推理模型時,GPU 的平均利用率僅為30.7%。

為了讓開發者從繁重的代碼開發、移植和調優中解脫出來,近些年出現了一些針對特定平臺的組件式深度學習應用開發框架,如MediaPipe[5]、OpenVINO[6]等。MediaPipe 是Google 面向感知應用開發的流水線構建套件,允許用戶通過網頁前端或配置文件組合組件庫的算子,構造輸入和輸出管道快速設計應用原型。用戶可以自定義算子加入MediaPipe庫,在移動端或桌面端部署應用。OpenVINO 是Intel 開發的一款面向計算機視覺應用的開發和部署工具,除支持深度學習模型部署外,還包含圖片處理工具包OpenCV 和視頻處理工具包Media,用于圖像和視頻解碼和推理前后的處理等,在Intel x86 中央處理器(Central Processing Unit,CPU)上的推理速度達到了行業領先。然而,MediaPipe 在設計之初主要面向移動端,僅提供有限數量的TensorFlow 或TensorFlow Lite類的模型,無法為PyTorch 模型和桌面端GPU 提供支持。OpenVino 面向的異構加速設備主要是Intel 的核心顯卡或集成顯卡,并不支持目前服務器節點中廣泛使用的NVIDIA GPU。

針對現有深度學習應用開發框架存在的問題,本文設計和實現了一個針對通用GPU 服務器,兼具靈活性、易用性和高效率的組件式深度學習應用開發框架。一方面,對深度學習應用中常見的通用處理過程提取和管理,降低開發者的查找和移植成本,提高代碼的可復用性;另一方面,將神經網絡的推理過程融入組件式開發方案,允許用戶指定吞吐量和延遲的平衡策略,保證應用運行的高效性。本文使用真實交通視頻進行的車牌檢測識別實驗結果表明,單塊GPU 卡(NVIDIA RTX 2080Ti)在吞吐優先的場景下最多可以支持7路視頻輸入,GPU 利用率(執行YOLO、RetinaNet 和LPRNet 推理任務)達到82%;在延遲優先的場景下平均單幀端到端延遲0.73 s,延遲標準差0.57 s,可以滿足實時處理的要求。本文使用框架重新開發了MediaPipe 上的典型應用(單人姿態估計和多人姿態估計)并進行實驗對比。在擁有2 個CPU(40個CPU 核)以及4 個GPU(NVIDIA RTX 2080Ti)的服務器上,單人姿態估計在延遲優先模式下處理幀率提高了0.64 倍,吞吐優先模式下處理幀率提高了12.11 倍;多人姿態估計應用處理幀率分別提高了31.61倍和107.70倍。

1 框架的設計準則及主要設計問題

1.1 框架的設計目標

框架的設計目標如下。

1)易用性:組件豐富,且便于擴展組件庫和移植新組件,保證應用程序編程接口(Application Programming Interface,API)的簡潔。

2)高效性:利用框架開發的深度學習應用資源利用高效,并能根據平臺資源作橫向擴展。

3)靈活性:包括應用開發的靈活性(可通過改變組件或組件參數實現新的應用),以及應用部署的靈活性(可提供不同場景的部署方案,并根據應用偏好平衡吞吐量和延遲)。

1.2 框架的設計準則

框架的設計準則如下:

1)應用是由若干組件通過輸入和輸出相連構成的有向無環圖(Directed Acyclic Graph,DAG),一個組件可以有0個、1 個或多個輸入,也可以有0 個、1 個或多個輸出。

2)有清晰的組件管理規范,以及完整的組件開發和移植規范。

3)面向開發者提供開發過程中需要用到的調試工具、運行日志、校驗工具以及可視化工具。

4)提供C++編程API,以及可選的配置文件開發方式。

5)支持Python 類型的組件,支持高效的C++數據類型和Python 數據類型之間的轉換,以保證Python 類深度學習項目的移植性。

6)使用C++作為主要開發語言,減少對第三方庫的依賴,保證框架的低開銷。

7)針對模型數多于GPU 卡的情況[7],保證單個GPU 上能高效運行多個深度學習模型。

8)保證應用設計邏輯和框架解耦,應用執行的高效性由框架保證,框架中的繁瑣算法以及高性能保證的實現對開發者透明;

9)保證在大多數高負載情況下對CPU 邏輯核和GPU 的高利用率。

其中:準則1)為基本的組件式設計原則,準則2)~5)等細分小點旨在減輕用戶開發過程中的心智負擔,準則6)~9)旨在保證開發的應用能高效利用平臺資源,實現高吞吐或低延遲。

1.3 主要技術問題及解決方案

在滿足設計原則的基礎上,在框架設計實現中需要重點解決以下問題。

1.3.1 組件的劃分問題

基于組件庫構建軟件的最大特點之一是層次化程度高。層次化建模的基礎是系統的可分解性,即系統可分解為若干相互作用的子系統,子系統本身又可以進一步分解。對每個子系統建立模型,就形成了層次化、模塊化的系統模型[8]。

劃分組件的目的是想通過以下3 點提高應用開發效率:1)增加組件的可重用性,繼承以往項目中的開發成果;2)降低組件之間的耦合程度,每個組件后期可以單獨修改和調試;

3)增強功能的內聚性和軟件的可配置性和可移植性,使組件具有靈活的結構和清晰的接口。

組件庫的開發在Linux 下GUN C 語言編譯器(GNU C Compiler,GCC)環境完成,按照功能分類分級構造組件庫的軟件層次體系。如圖1 所示,將組件庫劃分為功能層和實現層兩個層次,其中功能層又按照其包含關系進行二級分類。

圖1 組件庫的分層組織Fig.1 Hierarchical organization of component library

模型推理是深度學習應用的最重要組成部分,通常按執行流程分為前處理、推理和后處理三個過程。模型推理是一個相對復雜的任務,包含CPU 處理過程(消耗CPU 資源)、GPU 處理過程(消耗GPU 資源)以及主存和顯存之間的數據傳輸過程(消耗外圍組件快速互聯(Peripheral Component Interconnect express,PCIe)帶寬)。GPU 作為一種批量計算設備還會涉及任務組批和分批處理,因此在對模型推理過程進行拆分時,采用更細粒度的拆分方案:不僅將前處理、后處理、分批和組批功能放置到組件庫中,還抽象出了兩個傳輸組 件(簡 稱 trans 組 件):transferToDeviceMemory 和transferToHostMemory,可以在主存和顯存之間傳遞張量(Tensor)或者批量張量(Batch_Tensor)。

如圖2 所示,一個YOLO 模型的推理過程被拆分為包含CPU 處理任務、GPU 處理任務和數據傳輸任務的7 個階段。這種拆分方式便于將推理任務的瓶頸轉移到GPU 上,從而實現較高的GPU 利用率。

圖2 YOLO模型推理過程的組件拆分Fig.2 Component splitting of YOLO model inference process

較細粒度的組件拆分會增加任務間通信次數,需要高效設計底層數據結構和數據通信方式。在進行數據結構設計時需要考慮以下幾點因素:

1)良好的數據結構可操縱性,以便節省操作數據時的時間復雜度;

2)較少的存儲空間占用,盡量減少冗余的存儲開銷;

3)統一的管理方案,方便擴展新的數據類型;

4)安全性,在意外情況發生時能根據持久化的信息進行重建。

框架在存儲高效性方面借助C++的預編譯指令(pack)和共用聯合體(union),數據結構的元信息控制在20 個字節以內:若數據本身較小,例如矩形(Rect)數據和字符串(String)數據,數據體直接相連描述數據基本信息的元數據;若數據本身較大,例如矩形(Mat)數據和張量(Tensor)數據,則將真實的數據體單獨存放,元數據只存儲一些基本信息和真實數據體的指針,方便數據的統一管理和擴展。

元數據和數據體的分離也可以帶來某些細分操作的高效性,例如某些方法可能只需要訪問元數據,而另一些方法可能只需要拷貝數據體。在安全性方面,本框架開放了保存日志的選項,如果開啟數據日志,則會以文件形式記錄數據流。用戶借助Google 的Tracing 組件能對數據的處理時間線做可視化展示,方便進行意外中斷分析以及應用快照重建。

組件間通信時僅傳遞數據的首地址(在64 位系統中占用8 個字節)。實際測量表明,兩個組件之間的數據通信時間(從發出數據到接收到數據)可以控制在20 μs 以內。

1.3.2 GPU共享問題

模型推理主要在GPU 上執行,由于通用服務器中GPU數一般遠小于模型數,多個模型推理任務需共用1 個GPU。NVIDIA 從Kepler 架構開始提供Hyper-Q[9]硬件特性,結合多進程服務(Multi-Process Service,MPS)可允許多個進程同時加載任務到一個GPU 上,并共享同一個統一的計算設備架構上下文(Compute Unified Device Architecture Context,CUDA Context)。然而文獻[10-14]研究發現,在GPU 上同時執行多個任務會在任務間產生干擾,導致任務計算速度減慢,大多數情況下甚至比串行執行還慢。在數據中心聯合調度訓練任務和推理任務的PipeSwitch[15]采用在GPU 上一次執行一個任務的方式保證計算速度,同時通過有效的顯存管理、提前綁定計算邏輯、主備worker、模型分塊以及邊加載模型邊計算等方法減少從訓練任務切換到推理任務的時間。但PipeSwitch 的任務切換方式存在以下不足:

1)邊加載模型邊計算的方式不能完全隱藏模型載入時間,且模型分塊策略僅針對卷積神經網絡設計,不能用于Transformer 模型[16];

2)經過組批后模型的輸入數據往往很大,例如YOLO 模型的單次輸入可達100 MB 以上,但PipeSwitch 并未試圖隱藏數據傳入傳出GPU 的時間;

3)主備worker 方式可重疊舊任務的結束處理和新任務的初始化過程,對于模型訓練任務效果很好,但對于模型推理任務過于笨重。

總之,目前尚缺少一個通用、簡潔、高效的多模型推理任務共享GPU 的方案。本文框架主要從應用層面解決GPU 共享問題,提高GPU 利用率,主要通過以下兩個策略實現:

1)增加數據批量,在吞吐量要求較高的場景下,輸入較大的批量進行推理;

2)任務打包,將多個模型推理任務打包到一個GPU 上,在滿足數據依賴和資源依賴的情況下串行執行。

其中,策略1)要提高單模型推理時的GPU 利用率,策略2)要使GPU 的空閑時間盡可能短,從而使GPU 利用率長時間保持在較高水平。以上策略在應用時會帶來一些問題:

1)批量處理會帶來較高的數據輸入、輸出時間;

2)對于實時應用而言,串行執行模型推理的切換時間不可忽略,包括模型換入/換出時間、模型啟動/結束時間、計算緩存的換入/換出時間等。

本文框架通過兩個trans 組件分離數據傳輸任務和推理任務,并在功能放置階段將它們分開放置,實現GPU 計算和數據傳輸的重疊,從而隱藏數據傳輸時間;進一步地,將預先加載模型權重文件、提前綁定模型計算邏輯、統一管理顯存等策略集成到框架實現和組件開發規范中,以減少任務切換開銷。

1.3.3 流水線部署問題

在采用流水線部署的應用中,輸入數據以一定的速率進入流水線,理想情形是每個階段的處理速度一致,且剛好最大限度利用了系統資源;然而真實情形通常如圖3 所示,流水線各階段的處理速度并不一致,有些階段的處理速度甚至低于數據到達速率,成為系統瓶頸。一旦形成瓶頸,處理負載就會堆積,導致端到端延遲不斷增大。

圖3 流水線瓶頸示意圖Fig.3 Schematic diagram of pipeline bottleneck

消除流水線瓶頸的方法是給瓶頸階段分配更多的資源。但流水線各階段的處理速度與各自階段的任務類型、平臺資源、輸入數據量等都有關系,難以精確刻畫。幸運的是,大多數深度學習應用的輸入數據來自通信頻率固定的傳感器,它的數據速率相對固定,例如智慧交通、智慧校園等場景下的應用[17-18]通常以固定幀率的視頻作為輸入數據。為此,框架對應用進行一段時間的評測,根據評測結果指導流水線分配各階段資源,具體分為任務評測和以任務實例為單位的資源分配兩個步驟。

任務評測包括測量任務對單項數據的平均處理時間Ti,以及在不同批量(batch_size)下相對于batch_size=1 時的加速比(僅針對GPU 上的任務)。圖4 為根據評測結果繪制的YOLO(yolo_v5m)、RetinaNet 和LPRNet 這3 個模型在不同的batch_size下,推理一張圖片的平均時間相對于batch_size=1時圖片推理時間的加速比。

圖4 不同批量大小推理加速比對比Fig.4 Comparison of inference speedup ratios under different batch_sizes

本文框架采用以任務實例為單位的資源分配方法。當一個處理過程不能在時間T內完成對數據項的處理時,增加該處理過程的運行實例數,通過任務并行提高該階段的處理能力。具體地,對于CPU 消耗型任務fi,若評測時得到它的單項數據處理時間為Ti,則為它分配的實例數由式(1)計算:

GPU 消耗型任務需要先確定批量大小,批量大小的選擇本質上是在GPU 吞吐量和應用延遲之間進行權衡??蚣軐⑦x擇權交給用戶,引入參數scale?[0,1]表示用戶對吞吐量或延遲的偏好。對于GPU 消耗型任務fi,其batch_size由式(2)計算:

當scale取0 時,batch_size為1,對應延遲優先的情況;當scale取1 時,batch_size最大,對應吞吐優先的情況;當scale取其他值時,表示對吞吐量和延遲的綜合考慮。在評測階段提前測量推理過程fi在某個batch_size下的用時Ti,根據式(3)確定分配的實例數量:

由于PCIe 傳輸過程和GPU 推理過程關聯,因此為PCIe傳輸任務分配的實例數與為對應的GPU 推理任務分配的實例數相等。

1.3.4 CPU利用率問題

以實例為單位的資源分配只考慮到消除流水線瓶頸,帶來的問題是CPU 核使用數較多,CPU 利用率很低。本文框架通過對任務實例進行組合放置解決CPU 利用率過低的問題。在對任務實例進行組合放置時,需要遵循以下3 條原則。首先,由于放入同一組合的任務實例將被串行執行,因此需要并行執行的任務實例必須放入不同的組合中(互斥原則),為此以下兩類任務實例必須放入不同的組合:①同一任務的不同實例;②模型推理任務及其對應的數據傳輸任務。其次,為使得CUDA Context 占用最少的顯存,GPU 上的進程數應盡可能少,為此放置在同一個GPU 上的推理任務所關聯的PCIe 任務應放入同一個組合中。最后,在滿足互斥原則的基礎上,最終得到的組合數越少越好(最小化原則)。

盡管本文將處理功能劃分成3 種資源類型,事實上所有功能都需要消耗CPU 時間,因為PCIe 傳輸和GPU 上的操作都需要CPU 調度控制。特別是,GPU 任務會占用與GPU 時間等長的CPU 時間,因此,需按照以下順序進行組合放置:①由于GPU 資源是瓶頸,應優先放置GPU 任務;②按照互斥原則放置與GPU 任務綁定的PCIe 任務;③按照互斥原則和最小化原則放置剩余的CPU 任務。

以上問題可以規約為一個分步裝箱問題。共有GPU 和CPU 兩種箱子,每個GPU 箱子代表一個GPU,每個CPU 箱子代表一個CPU 核。每個GPU 箱子必須唯一綁定一個CPU 箱子。GPU 箱子中只能放入GPU 任務,CPU 箱子按照可以放入的任務類型又分為CG、CT 和CC 三種箱子。CG 箱子允許放入GPU 任務和CPU 任務,CT 箱子允許放入PCIe 任務和CPU任務,CC 箱子只能放入CPU 任務,CG 和CT 箱子數相同。箱子的容量是數據流中數據項到達的時間間隔T,物品是任務實例f,物品的體積是任務實例的單項數據處理時間。

裝箱問題是組合優化問題中常見的NP Hard 問題,框架采用First Fit 貪心算法進行近似求解,即按照順序每次選擇剩余容量最大的箱子進行放置。

2 框架的設計與實現

如圖5 所示,框架設置了3 個層次:核心層、組件層和應用層。

圖5 本文框架的三層架構Fig.5 Three-layer architecture of proposed framework

核心層提供應用和處理過程的執行流抽象、高效的底層數據結構和C++/Python 數據類型轉換、共享內存和共享顯存管理、低開銷的組件間通信、評測系統和日志系統;組件層提供豐富的組件庫、模板庫以及用于構建組件的自動化腳本;應用層提供應用開發接口和調試工具用于應用開發、評測器和部署器用于應用部署。

2.1 核心層的設計實現

核心層由Function、Executor、App、Data、Queue、Memory和Profile 這7 個模塊組成。其中:Function 類提供處理過程對應代碼體的靜態抽象,它的子類PythonFunction 類提供C++/Python 數據類型轉換;Executor 類提供處理過程的執行流抽象,同時負責管理運行時日志;App 類提供應用的執行流抽象;Data 類提供高效的底層數據結構;Queue 類提供低開銷的組件間通信;Memory 模塊提供共享內存和共享顯存管理;Profile 類提供應用和處理過程的評測。7 個模塊相互協作,為組件層和應用層提供高效服務。

2.1.1 Function類

Function 類對應組件或任務體。Function 是所有組件的基類,向下派生出PythonFunction 和GpuFucntion 兩個類。PythonFunction 類中提供convertToPython 和convertToCpp 方法,提供常用C++和Python 類型的高效轉換。

Function 類是所有執行任務的代碼對應的靜態結構。一個執行過程開發為Function(組件)需要實現以下5 個函數。

1)void star(t):任務的初始化函數,在Function 初始化時被調用一次。

2)bool waitForResource():等待資源函數,在任務的process 方法被調用前執行,采用非阻塞式結構,不滿足依賴時返回false。

3)void process(&data_inputs,&data_outputs):處理函數,輸入參數為輸入數據集合和輸出數據集合的引用。

4)bool releaseResource():釋放資源函數,在process 方法執行后調用,釋放資源。

5)void finish():結束函數,在Function 完全執行完后調用。

Function 采用數據驅動的方式執行,當被當前Executor(CPU 核)調度時,僅當輸入數據依賴被滿足(指定batch_size的數據全部到達)時,它的process 函數才會被調用執行。

2.1.2 Executor類

Executor 類對應執行器,所有的應用邏輯、任務實例都要借助Executor 來執行。一個Executor 向上承載每個任務的執行邏輯,向下綁定平臺的硬件資源。每個Executor 類對應一個任務實例集合,同時綁定一個CPU 邏輯核,是連接硬件邏輯和軟件邏輯的核心中介。

Executor 類的主要功能如下:

1)管理任務實例的執行,提供非阻塞式、非搶占式的輪詢任務調度方案;

2)提供共享內存和共享顯存的操作接口,供組件層調用;

3)管理任務邏輯在執行器的額外工作,包括環境初始化,向共享內存管理器和共享顯存管理器下發命令,以及在運行期間采集信息和記錄日志等。

Executor 根據它綁定的任務實例集合,在初始化階段決定是否需要進行Python 環境或GPU 運行環境的初始化。在GPU 運行環境初始化階段會進行Torch 的初始化,并根據App 提供的信息引導顯存空間的申請,以及控制顯存空間句柄的發送和接收。

2.1.3 App類

App 類對應應用,所有應用都需要借助App 執行。一個App 向上承載指定的應用邏輯和部署邏輯,向下管理若干個Executor,是從應用邏輯到硬件執行邏輯所要經歷的第一關。

App 類的主要功能如下:

1)在App 運行前檢查上下游組件的輸出輸入數據類型是否匹配;

2)管理Executor 的生命周期,在應用開始執行時統籌所有Executor 的初始化,并啟動所有Executor 執行;

3)為Executor 提供一些全局信息,例如哪些Executor 被綁定到同一個GPU 上,或者該Executor 在某個GPU 上的序號。

App 通常在主進程中建立,與每個Executor(對應子進程)都有兩個傳遞命令的管道(分別用于接收命令和發送命令),并負責管理Executor 的生命周期。

2.1.4 Data類

Data 類對應不同任務間傳輸的數據,所有任務的輸入輸出都以Data 類的對象作為媒介。Data 下維護9 個子類型,分別為存儲圖片的Mat、存儲字符串的String、存儲矩形坐標的Rect、存儲網格信息的Mesh、在主存中存儲單個張量的Tensor 和存儲多個張量的Batch_Tensor、在顯存中存儲單個張量的Gpu_Tensor、存儲多個張量的Batch_Gpu_Tensor 以及僅用于數據初始化的Unknown 類型。

Data 數據類型還包含3 個id,分別為用于標識應用的app_id、標識流的flow_id 和標識請求的request_id。一些Data類型中只包含數據的元信息(String、Rect、Gpu_Tensor 和Batch_Gpu_Tensor),另有一些Data 類型(Mat、Mesh、Tensor 和Batch_Tensor)會額外申請數據體空間,用于存放相對較大的連續數據,元信息中附加存放數據體首地址。Data 類提供面向指定內存地址的構造方案,方便開發者在共享內存中創建Data 類型。事實上,由于不同Executor 執行流在不同的子進程中,所有的Data 類型都是在共享內存中被創建和使用。

Data 類可以擴展子類型,擴展時需要指定新的type 標識和3 個id 屬性,其余部分的存儲結構可以按照1.3.1 節的原則自行設計。

2.1.5 Queue類

Queue 使用C++泛型實現多生產者多消費者隊列,提供阻塞和非阻塞發送和接收API。底層基于共享內存的環形隊列實現,通過atomic 庫模擬自旋鎖的方式實現進程間互斥??蚣苁褂肣ueue 傳遞Data 類型的指針,實現組件之間的消息傳遞。

2.1.6 Memory模塊

Memory 模塊首先封裝了一個基本的共享內存池類SharedMemoryPool,該類可以提供共享內存中相同大小數據單元的連續存儲。在共享內存池類的基礎上,Memory 模塊構造了共享內存管理器smm,smm 包含了9 個固定的共享內存池(維持基本的框架功能)和1 個用于自定義的共享內存池容器。9 個固定內存池為Data、Mat4K、Mat1080P、Mat720P、Mat540P、Tensor10M、Tensor1M、Tensor100K 和Tensor10K 內存池,分別用于存儲不同類型或不同大小的數據,內存池的容量大小可以根據配置文件自行指定。一個自定義的共享內存池容器可以根據需求使用smm 自行創建和刪除共享內存池。

Memory 模塊還包含一個共享顯存管理器類gpu_smm,gpu_smm 接管整個GPU 顯存,將整個顯存劃分為數據存儲區、模型區和計算區。數據存儲區用于緩存輸入輸出數據,模型區用于存儲模型權重,計算區用于模型推理時存儲中間數據。數據存儲區劃分成多個數據存儲單元(block),每次數據傳輸時申請一個block,傳輸完后釋放??蚣軙赑yTorch 插件(針對PyTorch 的Cache 層編寫的插件,提供申請/釋放顯存空間、申請/釋放顯存塊等API)的基礎上提供操縱顯存的C++API,供App 類和Executor 類在操作顯存中使用。相關的Python API 跟隨PyTorch 插件編譯至PyTorch 庫,供深度學習推理組件和trans 組件調用。

2.1.7 Profile類

Profile 類主要評測應用和處理過程,匯總和處理每個Executor 運行過程產生的時間戳信息,產生應用運行日志并統計應用運行時的資源消耗,為應用層部署器的決策提供底層信息參考。

2.2 組件層的設計實現

組件庫目前共實現了Image、Model、Tool 這3 類共36 個基礎組件,涵蓋實時視頻流的推拉,圖像的變形、裁剪、仿射變換和標記,5 個常用深度學習模型的前處理、后處理和推理過程,2 個主存和顯存之間傳輸的trans 過程,與批處理相關的組批、分批過程,與流控相關的多流聚合和多流分離過程,為開發新組件也提供了豐富的模板和范例。

框架提供groupByRequestId 和SplitByFlowId 兩個與流控相關的組件,用于多流應用中面向相同類型數據的多流聚合和其后的多流分離,在聚合和分離的過程中主要使用了Data中攜帶的flow_id 信息。由于多流應用中多數處理過程與流無關,即對于處理任務,只是增加了輸入數據流的密度,因此可以在單流輸入數據負載較小時,通過擴展應用服務范圍對輸入數據源進行橫向擴展,實現對批量計算硬件的充分利用。

2.3 應用層的設計實現

應用層提供豐富的組件、簡潔的應用開發和部署API,以及相關開發、調試和部署工具。API 主要分為以下幾類:①構建應用DAG 的連接類API(connect、connectOneByOne、connectOneToMany、connectManyToOne、connectOneFanMany、connectManyCollectOne);②擴展多實例的expand;③用于打印App 信息的help。

圖6 中,f 表示處理過程(function),q 表示隊列(queue)。圖6 列舉了6 種不同連接關系的API,在滿足多樣化開發需求的基礎上,力求簡潔規范易用,減少開發者的心智負擔。在框架的examples 文件夾下實現了6 個經典應用,涵蓋了所有API 的使用,為開發者開發應用提供范例和參考。

圖6 應用開發API示意圖Fig.6 Schematic diagram of application development API

3 應用實例

本章通過一個車牌號識別應用的開發和部署實例說明框架在應用開發和部署過程中的簡潔性、易用性和靈活性表現。

3.1 應用描述

車牌檢測識別應用以交通路口的高清攝像頭視頻流作為輸入數據,提取進入檢測區域的所有車輛的車牌號,繪制在圖片的右上角。圖7 為該應用的一個效果,可用于車輛追蹤和車流密度檢測等。

圖7 車牌號識別應用效果Fig.7 Rendering of license plate number recognition

3.2 應用開發

實現3.1 節應用需要車輛檢測、車牌位置檢測、車牌號識別等功能。經過調研,最終用YOLO(yolo_v5m)模型作車輛檢測、RetinaNet模型作車牌位置檢測和端到端的LPRNet模型作車牌圖片到車牌號碼字符的轉換。在以上3 個模型的基礎上,再結合一些常規的圖片處理過程,就能夠構造出一個實時車牌號檢測應用。圖8為該應用構建的處理任務DAG。

圖8 構成車牌號檢測應用的DAGFig.8 DAG of license plate number recognition application

根據處理流程的DAG 及性能要求在組件庫中選取組件。在實際開發時,可以根據組件的輸入參數作檢測效果和檢測速度的權衡。圖9 為使用CPU 進行推理的低速版本,圖10 為在GPU 上進行批量推理的高速版本,批量推理時的批處理大小可以通過參數指定。

圖9 CPU推理版本的關鍵代碼Fig.9 Key code for inference version on CPU

圖10 GPU推理版本的關鍵代碼Fig.10 Key code for inference version on GPU

框架還提供了使用MarkDown 文檔構建應用的方式。圖11 展示了CPU 推理版本的MarkDown 文檔輸入。

圖11 使用MarkDown方式開發應用Fig.11 Application development using MarkDown

得益于豐富的層次化組件設計和多種構造函數重載,通過調整各處理過程的輸入參數可進行例如檢測范圍的改變、標識位置和顏色的更新、輸出視頻流的分辨率大小調整等。如圖12 所示,改變一些組件和參數,就能得到一個車輛統計應用。

框架提供標準化的流程和豐富的模板范例用于新組件的開發和移植。例如,移植一個YOLO 模型至組件庫遵循以下流程:

1)下載ultralytics/yolov5 項目至本地,并下載模型權重文件(本例使用yolo_v5m.pt)至本地文件夾;

2)在根目錄文件夾中新建yolo.py 文件,提取preprocess和postprocess 過程接口,封裝于yolo 類;

3)運行框架提供的create_Python_GPU_Function.py 文件,在命令行參數中指定yolo 項目的根目錄、yolo.py 文件路徑和yolo_v5m.pt 權重文件路徑;

4)框架根據模板生成yolo_preprocess.h/.cpp、yolo_inference.h/.cpp、yolo_postprocess.h/.cpp 和新的yolo.py文件,并編譯入基礎組件庫。

3.3 應用部署

在定義了應用的處理邏輯后,需要進行應用的部署。本框架提供3 種部署方案:純手動部署、半自動部署和全自動部署。不同部署方案適用的場景不同,對應著對開發者不同的開放自由度。

3.3.1 純手動部署

框架提供expand API 用于擴展某個階段或多個連續階段的實例數,提供cpus_map 和gpus_map 兩個參數指示任務實例所放置的CPU 核或GPU。使用此種方式部署車牌識別應用的關鍵代碼如圖13 所示。

圖13 純手動部署關鍵代碼Fig.13 Key code for pure manual deployment application

純手動部署方案提供了直接面向硬件進行資源分配和功能放置的底層接口,是半自動部署和全自動部署方案的基礎,給部署人員提供了最高的自由度。

3.3.2 半自動部署

半自動部署是指部署人員可以結合框架提供的工具進行自動化的評測,評測器根據平臺硬件環境和運行環境給出流水線各階段的實例分配數及功能放置方案,部署人員可以直接使用此方案或在此方案的基礎上進行調整。圖14 為評測器統計的各階段消耗的CPU 時間。

圖14 各階段耗時統計Fig.14 Time consumption statistics of each stage

半自動化部署要求部署人員對軟件執行情況和服務器硬件資源有基本認知和了解,可用于專業人員的定制化部署。

3.3.3 全自動部署

全自動部署直接調用部署器的deploy 函數,部署器調用評測器,根據評測結果和自動化算法進行資源分配和實例放置,其函數原型為:

void deployer::deploy(float scale);

此種部署方式借助1.3.3 節和1.3.4 節中提及的資源分配和實例放置方式,只需指定scale值就能將部署方案編譯進應用的可執行文件中,不需要部署人員額外干預,是應用開發者的一鍵式部署方案。

3.4 擴展到多路輸入(多流)

上述應用可以通過使用 groupByRequestId 和splitByFlowId 組件改變應用的DAG 邏輯,將應用擴展到多路輸入。圖15 展示了一個四路視頻輸入的實時車牌號檢測應用的處理流程DAG。

圖15 四路視頻輸入的車牌號檢測應用DAGFig.15 DAG of license plate number recognition application with four-way video input

4 實驗驗證

4.1 實驗環境

實驗在一臺AMAX Super Server X11DPG-QT 上運行,包括2 片Intel Xeon Gold6230 CPU(每個CPU 包含20 個邏 輯核)、4 塊NVIDIA GeForce RTX 2080Ti GPU、376 GB 內存和PCI-E 3.0 X16。操作系統為Ubuntu16.04,編程語言為gcc-6.5.0,Python3.8.0,采用深度學習框架PyTorch1.7.0,軟件庫OpenCV4.5.4。

4.2 吞吐量和延遲

4.2.1 實驗目的

實驗旨在驗證框架在深度學習應用開發時的有效性。首先,通過一段時間的運行,驗證應用運行時的穩定性(不會產生數據項堆積);其次,通過對CPU 利用率和GPU 利用率等指標的測量驗證框架對平臺資源的利用效果。最后,通過觀察scale值對資源利用率以及應用延遲的影響,驗證scale值對應用吞吐/硬件延遲偏好的調節效果。

4.2.2 應用描述

本實驗選用3.1 節中的車牌識別應用進行框架開發有效性的驗證。車牌號識別應用使用支持多流的GPU 推理版本,采用全自動部署方式,scale值設置為0、0.5 和1。應用的輸入為四路分辨率為4K(3 840×2 160)、幀率為30 fps 的視頻流,采用實時流傳輸協議(Real Time Streaming Protocol,RTSP)格式從流服務器拉??;輸出為四路1080P(1 920×1 080)、幀率為30 fps 的視頻流,實時推流到流媒體服務器。數據源來自手機拍攝的交通路口視頻(https://gitee.com/blazarx/ traffic_video_dataset)。

4.2.3 實驗結果

在應用運行過程中統計所支持的最大路數、資源使用量、資源利用率、平均單幀延遲和延遲標準差,實驗結果如表1 所示。

表1 不同scale下的吞吐量和延遲Tab.1 Throughput and latency under different scale

在吞吐優先模式(scale=1)下,單塊GPU 卡最多可支持七路視頻;在延遲優先模式(scale=0)下,可得到0.73 s 的應用延遲,能夠滿足此應用的實時性要求;在吞吐和延遲的均衡模式(scale=0.5)下,單塊GPU 卡最多支持四路視頻,得到1.15 s 的應用延遲,是吞吐量和延遲綜合考慮的結果。CPU單核利用率在三種模式下平均為68.8%,GPU 利用率在吞吐優先模式下可達到82%。

實驗結果表明,車牌識別應用能在快捷開發和全自動部署模式下正確平穩運行,能根據用戶意愿在不增加開發負擔的前提下,對硬件的使用作橫向擴展(CPU 核和GPU),在吞吐優先模式下能獲得較高的GPU 利用率,在延遲優先模式下能獲得較低的應用延遲,并且在三種模式下都能得到較高的CPU 利用率。

4.3 與MediaPipe的開發和性能對比測試

4.3.1 實驗目的

本實驗旨在通過與MediaPipe(針對移動端需求設計且被廣泛使用的深度學習應用構建框架)在開發成本和運行效率上的對比,驗證框架針對CPU-GPU 異構服務器進行深度學習應用開發時的獨特優勢。其中,開發成本可用代碼行數來衡量,實時視頻流應用的運行效率使用運行時能夠達到的最大幀率衡量。

4.3.2 框架和應用描述

本文選用MediaPipe 支持的經典應用,基于BlazePose[19]的單人姿態估計以及與YOLO(yolo_v5m)相結合的多人姿態估計。應用在Python 環境下采用MediaPipe 提供的Python API 開發,其中,mediapipe.solutions.pose.Pose 函數參數MODEL_COMPLEXITY=1(中等復雜度模型)。作為對比方案,選取 BlazePose 的 PyTorch 版(https://github.com/WangChyanhassth-2say/BlazePose_torch)的開源項目作為原項目,進行組件開發和移植,其余組件選用組件庫中已有方案,選用最細粒度的組件劃分方式。

人體姿態估計應用將每幀視頻中人體和手部共33 個關鍵點標識出來并連成骨架,可用于人員數量統計、人體動作捕捉、手勢識別等諸多應用。圖16 是多人姿態估計應用的其中一個輸出幀。

圖16 多人姿態估計應用效果Fig.16 Effect of multi-person pose estimation

應用的輸入和輸出為分辨率為720P(1 280×720)、幀率為23 fps 的視頻流。數據源來自網絡上真實舞蹈大賽視頻(https://www.youtube.com/watch?v=pObHe2A8ID4&ab_channel=TranslationSIT)。

4.3.3 實驗結果

實驗比較應用的開發成本(代碼行數)和執行效率(最高支持的處理幀率),實驗結果如表2 所示。

表2 與MediaPipe的開發成本和運行效率比較Tab.2 Comparison with MediaPipe on development cost and operating efficiency

在開發成本方面,使用本框架開發的應用代碼行數(C++API)與使用MediaPipe 開發的應用代碼行數(Python API)相近。在運行效率方面,單人姿態估計應用在延遲優先模式下,相較于MediaPipe 獲得0.64 倍的幀率提升,吞吐優先模式下獲得12.11 倍的幀率提升;多人姿態估計應用中分別獲得31.61 倍和107.70 倍的幀率提升。

4.4 實驗總結

在4.2 節中,通過對車牌號檢測應用在不同scale值下的應用延遲、CPU-GPU 資源占用量及資源利用率等指標的評測,驗證了框架開發深度學習應用的有效性,展示了框架在各個關鍵問題,如流水線部署問題、GPU 共享問題和資源利用率問題上的解決表現。4.3 節將框架和MediaPipe 在開發成本和運行性能進行對比,說明了框架在CPU-GPU 異構的服務器平臺下應用運行性能優勢。綜合兩個實驗結果可得,該框架能夠作為CPU-GPU 異構服務器上面向深度學習應用開發部署的有效解決方案。

5 結語

本文針對深度學習應用缺少開發與部署工具的問題,提出一個組件式的深度學習應用開發框架。就框架設計過程中的組件劃分問題、GPU 共享問題、流水線部署問題以及由此帶來的CPU 利用低效的問題做了詳細的考量,并給出了解決方案。在考慮深度學習應用的特點、組件間的通信開銷、應用的資源利用方式和應用的部署方式之后,設計并實現了應用開發和部署一體化的三層軟件框架。

通過應用開發和部署實例以及性能對比實驗表明,本工作基本上實現了框架初始設計時提出的易用性、高效性、靈活性等目標,說明了框架在實際的應用開發和部署場景下的實用性和有效性??蚣苣芨鶕掏潞蜁r延偏好作出對硬件方面的自適應擴展,為面向流式數據的深度學習應用提供了有效的開發部署方案。

本文框架尚存在許多不足,還有很多進一步的工作可以完成,現結合需求以及技術的發展趨勢,總結3 點如下:

1)框架目前僅對視頻流應用提供支持,無法完成其他模態的應用開發。應用開發和框架完善是一個互相迭代的過程。在之后的工作中,可以對更多類型的應用進行考量,進一步增加框架組件庫的豐富度。

2)當前深度學習領域前端框架和后端平臺類型十分豐富。本文框架目前只對PyTorch 類型的前端框架和CPU、GPU 等部署后端提供支持。后續可以面向更多硬件的存儲特點及編程范式進行分析、考量和設計,對更多種類的硬件平臺提供支持。

3)當前面向特定硬件的深度學習部署加速工具在不斷發展進程之中。為了得到在相應硬件上更高的模型推理效率,框架需要面向主流推理引擎做兼容性設計。

猜你喜歡
實例部署組件
無人機智能巡檢在光伏電站組件診斷中的應用
一種基于Kubernetes的Web應用部署與配置系統
晉城:安排部署 統防統治
新型碎邊剪刀盤組件
部署
U盾外殼組件注塑模具設計
部署“薩德”意欲何為?
風起新一代光伏組件膜層:SSG納米自清潔膜層
完形填空Ⅱ
完形填空Ⅰ
91香蕉高清国产线观看免费-97夜夜澡人人爽人人喊a-99久久久无码国产精品9-国产亚洲日韩欧美综合