李鼎基,糜澤羽,吳保東,陳 遜,趙永望,丁佐華,陳海波
1(上海交通大學 軟件學院,上海 200240)
2(北京市商湯科技開發(fā)有限公司,北京 100080)
3(浙江大學 網絡空間安全學院,浙江 杭州 310007)
4(浙江理工大學 信息學院,浙江 杭州 310018)
隨著以深度學習[1]為代表的計算密集型應用的大量興起,僅僅依靠CPU 提供的算力已經顯得捉襟見肘,開發(fā)者們紛紛將目光轉向了CPU 之外具有更強大計算能力的設備作為加速器.傳統(tǒng)的通用加速器主要以GPGPU為主,而工業(yè)界也為了特定的目的開發(fā)出了以TPU(tensor processing unit)[2]為代表的專用加速器.數(shù)據(jù)中心是云計算(cloud computing)[3]時代的核心基礎設施,其中的虛擬化平臺是云服務能夠高效運行的基礎保證.近年來,越來越多的人工智能服務提供商傾向于將其應用部署在云系統(tǒng)中,使得各類加速器需要被整合進已有的虛擬化平臺中,因此有關加速器虛擬化的需求也應運而生[4].雖然目前市面上已經出現(xiàn)了若干種加速器虛擬化的解決方案,但是這些方案在現(xiàn)實場景中的應用仍然存在著各類限制和挑戰(zhàn).
目前主流的加速器虛擬化方式是PCI 透傳(passthrough)的方式,以Intel VT-d[5]為代表的I/O 設備虛擬化方案會將加速器設備直通到客戶虛擬機中.這種虛擬化方式繞開虛擬機監(jiān)視器(virtual machine monitor,簡稱VMM)的干預,把加速器全權交由客戶虛擬機管理,雖然獲得了與裸金屬(bare-metal)環(huán)境幾乎無異的性能,但是無法細粒度地虛擬化給多臺客戶虛擬機共享使用.因此,該類虛擬化方案使虛擬化平臺失去了計算資源彈性分配的能力,較差的可擴展性讓虛擬機監(jiān)視器不能靈活地在多臺虛擬機之間動態(tài)地調度計算資源.
一些加速器制造商也提供了以Nvidia Grid[6]和gVirt[7]為代表的虛擬化方案,通過讓設備驅動程序與加速器配合以達到硬件資源的劃分與時分復用.這些方案在虛擬機監(jiān)視器協(xié)助下對于加速器的特定操作進行干預,其余操作則與PCI 透傳方式類似,將加速器的運行時間公平地分配給各個客戶虛擬機.然而,對于目前的加速器而言,時分復用方案的可用性并不高:一方面,現(xiàn)在成熟的時分復用方案不僅需要在軟件層面配合特定的驅動程序,硬件層面上對加速器的型號也有著嚴格的限制[8],導致大部分普通加速器無法使用此虛擬化功能.另一方面,該類虛擬化方案可移植性差,在每個新型的加速器出現(xiàn)時都需要重新進行針對性的開發(fā),而資源劃分策略無法自由地進行調整也導致可擴展性差.
另外,也有以rCUDA[9]和GVirtuS[10]為代表的基于API 轉發(fā)方式的虛擬化方案.該類方案均建立在分離式驅動模型[11]的基礎上,在動態(tài)鏈接庫層面進行虛擬vGPU 的抽象.該模型將設備驅動劃分為前端驅動(frontend driver)和后端驅動(backend driver)兩部分,其中,后端驅動程序扮演服務器的角色,并將從前端驅動程序接收到的請求轉換為實際與底層硬件設備交互的驅動程序調用.由于需要進行前后端驅動程序間的通信,還涉及到數(shù)據(jù)的序列化、內存的額外拷貝等操作,相較于原生的非虛擬化方案,性能會有較大程度的損失,具體的損失程度主要取決于通信部分的性能以及所傳輸數(shù)據(jù)量的大小.
為了解決目前主流加速器虛擬化面臨的問題,本文基于硬件虛擬化技術,提出了一種針對加速器的虛擬化框架,目的是:(1)針對加速器提供方便、可用的多租戶虛擬化方案,提高硬件資源利用率;(2)保證用戶間強隔離性與安全性;(3)盡可能地降低虛擬化帶來的額外性能開銷.為了滿足目的1,本文的虛擬化框架放棄了PCI 直通的虛擬化方式,選擇了基于API 轉發(fā)方法的C/S 架構,參考了成熟的I/O 設備虛擬化方案,通過前后端的分離來支持多租戶;為了滿足目的2,本文將虛擬機作為基本保護域以保證多租戶場景下的強隔離性[12],而不是直接在物理主機的操作系統(tǒng)上使用諸如Kubernetes[13]的容器編排系統(tǒng)[14];硬件虛擬化技術的VMFUNC 功能允許應用在非特權模式下切換擴展頁表,為了滿足目的3,本文借助該特性實現(xiàn)了CPU 控制流在虛擬機間零下陷地快速切換,重點優(yōu)化了API 轉發(fā)過程中虛擬機間通信流程的性能,盡可能地將性能損失降到最小.實踐證明,本文實現(xiàn)的Wormhole 原型系統(tǒng)很好地解決了目前加速器虛擬化方案所面臨的問題,在模型訓練測試中相較于目前開源的類似方案取得了1.4~5 倍的性能提升.
本文做出了如下貢獻:
(1)基于API 轉發(fā)的方法,提出了一種針對加速器的、基于跨虛擬機代理執(zhí)行的虛擬化框架.
(2)提出了一種基于硬件虛擬化技術的無下陷的虛擬機間通信加速機制.
(3)在KVM 上,將虛擬化框架應用于常見的Nvidia GPU,實現(xiàn)并支持了主流深度學習訓練框架Caffe.
(4)擴展并優(yōu)化了開源API 轉發(fā)框架GVirtuS,使其同樣支持深度學習框架,經過對比測試表明,本文提出的虛擬化框架相較于優(yōu)化后的GVirtuS,在性能上有很大提升.
基于虛擬機的虛擬化方案通過為客戶操作系統(tǒng)提供一套完整的虛擬硬件資源來實現(xiàn)虛擬化.這種對于物理硬件進行虛擬化的方案具有很多優(yōu)點,例如對于客戶操作系統(tǒng)透明,并且允許運行未經修改的操作系統(tǒng),具有很強的兼容性[13].由于每一個虛擬機都擁有其獨立的操作系統(tǒng)、函數(shù)庫、存儲資源等,所以從安全性的角度來看,虛擬機的隔離性相當出色[15].
當前數(shù)據(jù)中心內主流的虛擬化方案是以KVM為代表的基于主機操作系統(tǒng)的虛擬化架構.如圖1所示,對于CPU 的虛擬化,虛擬機監(jiān)視器將每個物理CPU 劃分為一個或多個虛擬vCPU 分配給不同的客戶虛擬機使用,所有的vCPU 都會由虛擬機監(jiān)視器統(tǒng)一管理和調度;在內存的虛擬化方面,現(xiàn)代虛擬機系統(tǒng)會為每個虛擬機分配一個擴展頁表(extended page table,簡稱EPT),通過限制地址翻譯來保證不同的虛擬機處于不同的地址空間中[16];I/O 的虛擬化通常會復用主機操作系統(tǒng)中的原生驅動程序,當虛擬機內發(fā)起I/O 請求后會經過虛擬機監(jiān)視器轉發(fā)給主機操作系統(tǒng)上的I/O 代理模塊(如QEMU)進行處理.
Fig.1 Architecture of host-based virtualization圖1 基于主機操作系統(tǒng)的虛擬化架構圖
為了提高虛擬化系統(tǒng)的性能,以Intel 為代表的各大CPU 制造商紛紛在其CPU 產品上添加了硬件虛擬化技術[17],其中,針對內存的硬件虛擬化已經基本替代了原本的影子頁表(shadow page table,簡稱SPT)[18]方式,成為了默認的內存虛擬化方式.在現(xiàn)代虛擬機系統(tǒng)中,客戶虛擬機想要訪問物理內存,需要經過兩級地址翻譯:第1級是根據(jù)虛擬機內的頁表(page table,簡稱PT)進行客戶虛擬地址(guest virtual address,簡稱GVA)到客戶物理地址(guest physical address,簡稱GPA)的翻譯,第2 級是根據(jù)虛擬機監(jiān)視器配置的擴展頁表進行客戶物理地址到主機物理地址(host physical address,簡稱HPA)的翻譯.
從Intel 的第4 代CPU 開始,CPU 指令集中加入了VMFUNC[19]這一硬件指令,允許客戶虛擬機能夠在非特權模式(non-root mode)下執(zhí)行一些特定的虛擬機相關操作而不會引發(fā)任何下陷到虛擬機監(jiān)視器的行為.截至目前,VMFUNC 指令僅僅支持擴展頁表指針(EPT pointer,簡稱EPTP)切換這一功能,該功能允許客戶端虛擬機在非特權模式下由一條VMFUNC 指令執(zhí)行EPTP 的切換.為了防止切換到錯誤的內存區(qū)域,需要預先在虛擬機監(jiān)視器中為對應客戶虛擬機配置EPTP 列表(EPTP list),應用程序只能從該列表中選擇合法的EPTP,而一個EPTP列表中最多可以容納512 個EPTP.
VMFUNC 指令由于其無下陷的特點相較于傳統(tǒng)方法有著很大的性能優(yōu)勢,在該指令出現(xiàn)之前,如果一個客戶虛擬機試圖完成更改EPTP,需要由非特權模式切換到特權模式修改VMCS 中EPTP 域的值,然后再由特權模式返回到非特權模式,其中,僅僅非特權模式到特權模式的切換就需要花費不少于300 個cycle.相比之下,一條VMFUNC 指令可以完成與上述整個流程同樣的功能,在開啟了(virtual processor identifier,簡稱VPID)功能的情況下只需要花費134 個cycle.VMFUNC 也因此被很多已有工作[20,21]所采用.
根據(jù)上文所述背景,本文選擇API 轉發(fā)的方法作為Wormhole 的基礎方法以兼顧性能和可用性.為了能夠有針對性地解決現(xiàn)有基于API 轉發(fā)的虛擬化方案中存在的關鍵問題,本節(jié)調研了目前公開的相關工作并進行了深入的測試與分析.
在API 轉發(fā)方法下,服務端進程與客戶端進程的交互模式有多種選擇.
(1)以GVirtuS 為代表的方案選擇了Host-VM 模式,即將服務端進程放置在主機操作系統(tǒng)中,客戶端進程放置在虛擬機或容器中.該模式下,加速器的管理與主機操作系統(tǒng)耦合,主機操作系統(tǒng)的內核需要分飾兩角,不僅要扮演客戶虛擬機的管理者角色,還要負責運行加速器的驅動程序.這樣既打破了單一功能原則(single responsibility principle),又只能同時支持一個版本的加速器驅動,給整個系統(tǒng)的運維帶來了困難.例如,當操作系統(tǒng)不支持動態(tài)地升級驅動程序時,可能需要重啟使新版本驅動生效,但是正在運行著的客戶虛擬機顯然是無法避免受到影響的,在云服務提供商看來這是無法接受的.
(2)以vCUDA[22]為代表的方案選擇了VM-VM 模式,服務端進程與客戶端進程分別放置在不同的客戶虛擬機中,可以選擇多種跨虛擬機的通信方式進行數(shù)據(jù)交換.傳統(tǒng)的基于網絡或共享內存的通信方式由于系統(tǒng)調度等因素造成了較大的性能損失,而近年來的一些工作[21]已經提出了虛擬化環(huán)境下的快速通信方式,但是對于具有較復雜操作系統(tǒng)(如主流的Linux 系統(tǒng))的虛擬機卻無能為力.此外,隨著加速器種類以及配套軟件的不斷更新,這類方案也未能提出適合的配套進化措施.
現(xiàn)有的API 轉發(fā)方案的主要問題表現(xiàn)在性能方面,而主要的性能損失來自于通信模塊.從目前了解到的基于API 轉發(fā)的各類系統(tǒng)來看,通信方式主要分為以下幾種.
(1)TCP/IP 通信方式[23].由于需要經過多次的內存拷貝,帶來了大量的額外開銷:以主流的Linux 操作系統(tǒng)利用套接字(socket)進行單向TCP 傳輸為例,用戶態(tài)進程在發(fā)送數(shù)據(jù)時需要先將待發(fā)送的數(shù)據(jù)拷貝到內核的緩沖區(qū)中,然后內核中的TCP 棧會利用本地網卡將數(shù)據(jù)發(fā)送給目標地址.從上述流程可以看出,為了在兩個進程間拷貝一段數(shù)據(jù),TCP/IP 通信方式引入了兩次額外內存拷貝,如果考慮I/O 虛擬化,額外內存拷貝的次數(shù)可能會翻倍,以常見的Virtio 方式[24]為例,一次單向TCP 通信會增加兩次虛擬機到主機內核緩沖區(qū)的內存拷貝.除此之外,在服務端進程等待客戶端請求時,CPU 會陷入睡眠或調度,等到網卡收到數(shù)據(jù)后會發(fā)送中斷喚醒CPU,這些異步操作也會帶來不小的延遲.
(2)共享內存(shared memory)方式.該通信方法通過在服務端進程和客戶端進程之間建立一段共享的內存映射,消除了額外內存拷貝開銷.然而,單純的共享內存并未提供在數(shù)據(jù)拷貝完成后的通知機制,目前常見的通知機制是將能否共享一個信號量(semaphore)作為能否修改共享內存的標志.由于雙方需要主動地輪詢信號量,這樣會導致CPU 花費大量時間在沒有實際作用的輪詢和調度上,造成較高的延遲.
(3)遠程直接內存訪問(remote direct memory access,簡稱RDMA)方式.RDMA 允許一臺服務器直接訪問另一臺服務器上的內存而無需任意一方操作系統(tǒng)參與[25],意味著可以支持零拷貝(zero-copy)從而降低延遲以及CPU 的性能損失,RDMA 的驅動會直接復用用戶態(tài)進程的內存來進行傳輸且完全無需CPU 參與.基于RDMA的通信方式有著優(yōu)秀的性能與較低的延遲,然而,由于需要購置專用的RDMA 網卡并且安裝專用的驅動,在運維方面會產生一筆較大的成本,降低了該類方案的可用性.
本節(jié)以Caffe 中Neuron Layer 測試為例,對于API 轉發(fā)模式的虛擬化方案進行了一系列測試分析,該測試用例在一次運行過程中調用了超過110 萬次、共計40 種不同的CUDA API,表1 列出了調用頻率前5 的API 明細.測試之前,本文首先對GVirtuS 進行了擴展和優(yōu)化,將其以VM-VM 模式部署在兩臺處于同一物理服務器的虛擬機中,通信模塊以共享內存作為參數(shù)拷貝的載體,TCP/IP 作為參數(shù)拷貝完成的通知機制.
Table 1 List of APIs frequently called during Neuron Layer testcase表1 Neuron Layer 測試中被高頻調用的CUDA API 列表
接下來,本文對于Caffe 中Neuron Layer 測試用例的API 轉發(fā)流程進行了耗時分析,將虛擬化時的一次CUDA API 調用流程劃分為以下3 個部分:(1)額外內存拷貝耗時,包含數(shù)據(jù)與共享內存間序列化與反序列化的時間開銷;(2)通知機制耗時,包含虛擬機之間通知對方共享內存可用的時間開銷;(3)原生API 執(zhí)行耗時,其中只有原生API 執(zhí)行耗時是不可避免的有效工作時間,剩余的耗時均屬于通信模塊的額外性能開銷.經過測試,各部分平均耗時占比如圖2 所示,測試中API 調用的總時間為283 750 417 817個cycle,可以看到,額外性能開銷占整個流程耗時的88%以上(250 528 017 067cycle),其中,額外內存拷貝耗時僅占不到1%(1 910 231 774cycle),通知機制的耗時占比高達87%(248 617 785 293cycle),因此,現(xiàn)有虛擬化系統(tǒng)中的通知機制是造成性能問題的最大瓶頸,也是本文有針對性的要進行優(yōu)化的重點.
Fig.2 Analysis of the average time cost of Neuron Layer in GVirtuS圖2 GVirtuS 中Neuron 測試用例平均耗時占比分析
在原生物理服務器的環(huán)境下,調用加速器的過程中一個進程的CPU 有效運行時間T有效可以主要劃分為兩部分:(1)在用戶態(tài)運行應用程序功能的時間;(2)在內核態(tài)運行驅動程序與加速器交互的時間,其余時間由于沒有產生真正有用的結果均可以視作無效運行時間T無效.因此,在加速器虛擬化的情況下,衡量一個虛擬化框架性能和效率的重要指標就是虛擬化后CPU 的有效運行時間占總運行時間的比例為T有效/(T有效+T無效).
在理想情況下,虛擬化后的比例和原生物理環(huán)境下的比例相同,即虛擬化沒有造成額外的性能開銷.本節(jié)所述的基于API 轉發(fā)的方案均使用的是主動式的交互方式,不論是通過網絡還是共享內存通信,都需要服務端與客戶端雙方的CPU 協(xié)助完成,而實際有效運行時間只有客戶端CPU 執(zhí)行應用程序以及服務端CPU 執(zhí)行驅動程序的時間.為降低調度導致的延遲,正常情況下服務端與客戶端都會被綁定在不同的物理CPU 上,如圖3 所示,虛擬化時一次轉發(fā)調用所耗費的CPU 資源會是原生物理環(huán)境的2 倍以上.
本節(jié)使用了Linux 操作系統(tǒng)內置的time 命令,利用上一節(jié)中的測試用例在原生物理環(huán)境與GVirtuS 虛擬化環(huán)境下分別測量了CPU 利用率.如表2 所示,原生物理環(huán)境下只占用了1 個CPU,利用率高達97.28%,而GVirtuS環(huán)境下占用了2 個CPU 且利用率分別僅為46.61%和38.52%.
Fig.3 CPU effective time in a virtualized environment圖3 虛擬化環(huán)境下的CPU 有效時間示意圖
Table 2 CPU utilization during Neuron Layer testcase表2 Neuron Layer 測試過程中的CPU 利用率
Wormhole 加速器虛擬化框架的目標是面向數(shù)據(jù)中心的現(xiàn)實場景,在保證用戶間強隔離性與安全性的前提下,針對加速器提供可用性高、性能好、支持多租戶的高效虛擬化方案.本文利用虛擬機作為前后端驅動程序的保護域,結合已被廣泛應用的硬件虛擬化技術,改進現(xiàn)有虛擬化方案的不足,實現(xiàn)了一個靈活通用、易于維護、高性能的加速器虛擬化框架.圖4 展示了Wormhole 的架構設計以及一次API 轉發(fā)調用的流程,本節(jié)將圍繞該圖的設計與調用流程示例加以詳細展開.
Fig.4 Architecture and invocation example of Wormhole圖4 Wormhole 架構設計及調用流程示例
Wormhole 的設計選擇了一種基于虛擬機的被動式服務端(passive server VM)模式,相較于現(xiàn)有的交互模式,有著性能以及靈活性方面的優(yōu)勢.所謂被動式服務端,就是服務端虛擬機在沒有待處理的用戶請求時不會主動占用任何CPU 資源.
在本文的設計中,所有加速器會通過PCI 直通的方式透傳給專門用于管理的服務端虛擬機,從而實現(xiàn)加速器管理與主機操作系統(tǒng)解耦.服務端虛擬機在初始化階段與一個普通的客戶虛擬機別無二致,也可以擁有自己的CPU,如圖4(a)所示,此時的服務端虛擬機獨占1 號CPU,正在準備所需的通信接收模塊等運行環(huán)境.在所有預先配置任務完成之后,服務端虛擬機會主動陷入類似快照的凍結狀態(tài),如圖4(b)所示,此時的服務端虛擬機已經不再擁有CPU 資源,其原有的1 號CPU 資源可以被釋放給其他的客戶端虛擬機使用,這樣就消除了不斷等待客戶端虛擬機的請求造成的服務端CPU 資源浪費.
根據(jù)測試數(shù)據(jù)可知,現(xiàn)有加速器虛擬化方案的通信模塊花費了大量時間在互相等待對方,例如服務端CPU在運行后端驅動程序等邏輯時,客戶端CPU 在結果返回之前一直處于空閑狀態(tài).因此在Wormhole 中,完全可以利用發(fā)送完請求的客戶端CPU 在服務端虛擬機中代理執(zhí)行來消除CPU 資源的浪費,使得被動式的服務端虛擬機變得可行.本設計提高了虛擬化后的CPU 使用效率,節(jié)約下來的CPU 資源可以分配給更多的客戶虛擬機使用,解決了第2.2 節(jié)中提到的問題.
在同一個物理服務器上可以同時存在多個被動式服務端虛擬機,每個服務端虛擬機可以被分配到不同數(shù)量的加速器中.同時,得益于虛擬機的隔離,不同的服務端虛擬機可以安裝不同版本的加速器驅動程序以及配套的計算庫等,以適配不同用戶的需要,這也意味著,如果物理服務器上接入了多種異構加速器,不同的加速器也可以經由不同的服務端虛擬機進行隔離,不會出現(xiàn)互相干擾的情況.本設計使得虛擬化框架能夠非常容易地接納快速更迭的不同種類加速器,解決了第2.1 節(jié)中提到的問題.
由于Wormhole 的被動式服務端釋放了自己的CPU 資源,虛擬機間的通信必須由客戶端采取主動式通信的方式配合服務端,因此本文提出了代理執(zhí)行的方法,允許客戶端執(zhí)行流主動地進入服務端虛擬機繼續(xù)執(zhí)行.為了能夠確保代理執(zhí)行的正確性,要求CPU 在不同虛擬機中運行時處于正確的地址空間中并能夠訪問到正確的指令和數(shù)據(jù).在此基礎上,必須進一步解決第2.2 節(jié)中提到的性能問題才能達到Wormhole 的高性能目標.因為虛擬化額外開銷與下陷的次數(shù)緊密相關[26],為了獲得優(yōu)異的跨虛擬機通信性能,本文通過預先配置實現(xiàn)了控制流切換過程中的零虛擬機下陷.
本小節(jié)將以圖4 中一次代理執(zhí)行的流程為例介紹本設計的核心思想,在圖4(b)中服務端虛擬機在初始化完畢后向虛擬機監(jiān)視器注冊了自己的服務端信息,之后,客戶端虛擬機被允許在該服務端虛擬機中代理執(zhí)行,在圖4(c)中客戶端虛擬機首先進行了一系列控制流切換前的準備工作,準備完成后切換執(zhí)行流進入服務端虛擬機,實現(xiàn)了如圖4(d)所示的代理執(zhí)行.得益于本文的設計,圖4(c)和圖4(d)中與控制流切換相關的操作均會在非特權模式中完成,從而不會觸發(fā)任何虛擬機下陷.一次完整的代理執(zhí)行流程的調用可以被劃分為6 個步驟.
首先,客戶端虛擬機發(fā)起與服務端虛擬機配對的請求,會下陷到虛擬機監(jiān)視器中添加一些內存映射.下列①~③步屬于一次性的預先配置,這一階段雖然會主動觸發(fā)虛擬機下陷,但是并不在跨虛擬機通信的關鍵路徑上,后續(xù)的代理執(zhí)行不需要重復這些操作,配置完成之后的控制流切換過程會保持零下陷.
①虛擬機監(jiān)視器會在服務端虛擬機的擴展頁表中,將客戶端進程CR3 的值映射到服務端進程頁表的HPA.本步驟的目的是為后續(xù)的VMFUNC 指令實現(xiàn)虛擬機地址空間的正確切換做準備.對于虛擬機的二級地址翻譯機制而言,VMFUNC 指令的現(xiàn)有功能只有切換控制第2 級地址翻譯的擴展頁表,而第1 級地址翻譯依靠客戶端進程CR3 指向的頁表,所以需要在VMFUNC 指令的前后對應正確的頁表以完成地址空間的變更.在本步驟添加映射后,客戶端進程CR3 的值可以在客戶端虛擬機的擴展頁表中被翻譯為客戶端進程的頁表內容,而同樣的值可以在服務端虛擬機的擴展頁表中被翻譯為服務端進程的頁表內容,保證了VMFUNC 指令前后地址空間的正確翻譯.同時,本步驟使得第⑤步在虛擬機間切換地址空間時,僅在虛擬機用戶態(tài)執(zhí)行一條VMFUNC 指令就可以達到CR3 替換的等價效果.
② 虛擬機監(jiān)視器會在服務端虛擬機的頁表中,將客戶端虛擬機LSTAR MSR(model specific register)的GVA 值映射到服務端虛擬機系統(tǒng)調用入口代碼頁的GPA.本步驟的目的是,當執(zhí)行流處于服務端虛擬機的地址空間中時,能夠在用戶態(tài)應用程序發(fā)起系統(tǒng)調用后正確地執(zhí)行系統(tǒng)調用.現(xiàn)代操作系統(tǒng)大都使用SYSCALL 指令進行系統(tǒng)調用,在執(zhí)行SYSCALL 指令時CPU 會根據(jù)LSTAR MSR 的值跳轉到系統(tǒng)調用的入口.默認配置下在虛擬機中修改LSTAR MSR 會觸發(fā)虛擬機下陷由虛擬機監(jiān)視器完成操作,因此本步驟使用添加映射的方式避免了后續(xù)控制流切換過程中造成的虛擬機下陷問題.在添加了上述映射后,代理執(zhí)行過程中發(fā)起系統(tǒng)調用時可以憑借客戶端LSTAR MSR 的值訪問到服務端系統(tǒng)調用的入口,從而正常地與內核進行交互.
③虛擬機監(jiān)視器中預先存放了一份跳板代碼,配對時會將兩個虛擬機高地址空間的某個相同的GVA 映射到這份跳板代碼頁.本步驟的目的是保證虛擬機地址空間切換前后CPU 指令流能夠正確過渡.CPU 的程序計數(shù)器(program counter)是根據(jù)虛擬地址獲取當前指令的,當VMFUNC 指令執(zhí)行完后程序計數(shù)器的值會增加對應的長度,下一條指令已經處于服務端虛擬機的地址空間中.本步驟在兩個虛擬地址空間中的相同位置提供了相同的指令,因此在地址空間切換前后CPU 執(zhí)行的仍然是連續(xù)的正確指令.
然后,客戶端虛擬機返回到用戶態(tài),調用跳板代碼頁提供的接口,開始執(zhí)行控制流切換相關的代碼.下列④~⑥步需要在每次代理執(zhí)行過程中重復,所以要求每個步驟都是零下陷以提高跨虛擬機通信的性能.
④ 在切換地址空間之前,客戶端進程需要臨時修改FS.base 和GS.base 兩個MSR 的值為服務端虛擬機中對應的MSR 的值.本步驟的目的是保證在代理執(zhí)行過程中,段寄存器的訪問機制在服務端虛擬機中可以正常工作.現(xiàn)代操作系統(tǒng)中有大量數(shù)據(jù)需要經過段寄存器機制進行訪問,它們的基地址存儲在對應虛擬機的VMCS 中的某些域中,不會隨著地址空間的變化而改變,如果不做對應的調整,在代理執(zhí)行期間就會根據(jù)錯誤的地址訪問到錯誤的數(shù)據(jù).本步驟預先替換好了正確的MSR 的值,這樣一來在地址空間切換前后段寄存器訪問機制取得的地址均為合法地址.雖然本步驟采用了替換MSR 的值而非添加映射的方法,但是虛擬機監(jiān)視器默認對于虛擬機內讀寫FS.base 和GS.base 兩個MSR 的操作是不做攔截的,所以依然不會引發(fā)任何的虛擬機下陷.
接下來,客戶端虛擬機的執(zhí)行流真正地切換到了虛擬機地址空間中.
⑤ 繼續(xù)執(zhí)行跳板代碼頁中的VMFUNC 指令以切換地址空間,從下一條指令開始,CPU 上仍然是客戶端虛擬機的VMCS,但其中的擴展頁表指針已經指向服務端虛擬機的擴展頁表.本步驟真正進入了服務端虛擬機的地址空間中,當前生效的數(shù)據(jù)和資源如圖4 中紅色邊框的區(qū)域所示.此時,CPU 的程序計數(shù)器指向共享的跳板代碼頁中的下一條指令,可以正確地繼續(xù)執(zhí)行余下的代碼.得益于第①步添加的映射,在切換至目標地址空間時,無需像傳統(tǒng)方案那樣顯式地修改CR3 寄存器的值,從而規(guī)避了虛擬機調用特權指令而造成的下陷開銷.
最后,客戶端虛擬機進入服務端的應用程序中開始代理執(zhí)行.
⑥ 獲取跳板代碼頁的代理執(zhí)行入口地址,跳轉到服務端虛擬機中的后端處理程序中,通過原生的驅動程序與加速器進行交互.本步驟終于將原本運行在客戶端虛擬機中的控制流順利地切換到了服務端虛擬機中開始了代理執(zhí)行,得益于上述5 步的準備工作,后續(xù)的系統(tǒng)調用、中斷等與加速器交互必需的復雜操作均可以正常地進行.
當代理執(zhí)行的任務完成后需要從服務端虛擬機返回到客戶端虛擬機中,此時,倒序執(zhí)行④~⑥步的逆向操作即可,在此不再贅述.按照本小節(jié)的設計,通過初始化階段的一次性預先配置,后續(xù)頻繁的跨虛擬機控制流切換操作并不會觸發(fā)任何一次虛擬機下陷,為代理執(zhí)行的快速、高效,提供了保障.
通過總結上一小節(jié)中的6 步操作,我們提出了以下技術點.
(1)跨虛擬機地址空間的執(zhí)行流快速切換.
主動式的跨虛擬機通信需要在執(zhí)行流切換前后滿足兩點:切換前后地址翻譯機制的正確以及切換前后CPU 指令流的過渡.原本的一條VMFUNC 指令只負責切換GPA 到HPA 的映射,因此需要有另外的措施負責切換到對應的頁表.以一次客戶端切換到服務端的地址空間為例,一種符合直覺的想法是在VMFUNC 指令的前后修改CR3 寄存器的值達到切換頁表的目的,但是細看之下就會發(fā)現(xiàn)無法達到目的:程序計數(shù)器是根據(jù)虛擬地址獲取當前指令的,如果VMFUNC 前一條指令更換了頁表,那么CPU 在執(zhí)行下一條指令時由于GVA 到GPA 映射的變化,實際上硬件看到的已經是一份無效的頁表,從而導致運行錯誤;反之,如果試圖在VMFUNC 后一條指令更換頁表,由于在VMFUNC 指令執(zhí)行完成后GPA 到HPA 的映射發(fā)生變化,實際訪問到的下一條指令內容也不再是對于CR3 的修改,同樣會引發(fā)異常.
Wormhole 通過將第①步中CR3 映射的添加與第⑤步中硬件虛擬化技術相結合,在不下陷到虛擬機監(jiān)視器的情況下,實現(xiàn)了如圖5 所示的一條VMFUNC 指令可以同時完成頁表和擴展頁表的切換,保證了切換前后地址翻譯機制的正確,一條指令完成多項操作也大幅降低了跨虛擬機通信的開銷.同時,因為代理執(zhí)行的特點,客戶端虛擬機與服務端虛擬機均運行在同一個物理CPU 上,避免了跨核中斷(inter pocessor interrupt,簡稱IPI)帶來的高昂開銷.
Fig.5 Switch virtual machine address space through VMFUNC圖5 VMFUNC 指令切換虛擬機地址空間原理圖
針對切換前后CPU 指令流的過渡,Wormhole 在第③步操作中提供了一份共享跳板代碼頁以及一個代理執(zhí)行專用的棧,代碼頁包括了VMFUNC 指令以及一些上下文的保存邏輯,將它們映射到雙方虛擬機的頁表中相同的GVA.這樣,客戶端進程在需要進行代理執(zhí)行時可以跳轉到這份代碼頁,在地址空間切換成功后,由于在服務端進程在相同的地址共享這份代碼頁,程序計數(shù)器可以無縫地過渡到VMFUNC 的下一條指令繼續(xù)執(zhí)行,也不會污染雙方進程原本的棧的內容.另外,還需要服務端進程提前向虛擬機監(jiān)視器注冊代理執(zhí)行函數(shù)的入口,跳板代碼在控制流切換完成后會跳轉到被注冊的函數(shù)入口,正式開始在服務端進行加速器的訪問操作.
(2)支持在控制流切換后系統(tǒng)調用、中斷等復雜操作的正確處理.
一些以SkyBridge[21]為代表的前序工作同樣使用了類似的代理執(zhí)行思想,但是這些工作只是針對一些基于微內核的較簡單的操作系統(tǒng),面向的場景也僅僅是同一個操作系統(tǒng)中進程間的代理執(zhí)行.在微內核的設計理念中,內核部分的代碼量很小,通常只會保留幾個最基礎的管理功能,大量傳統(tǒng)宏內核中的功能(例如設備驅動程序)被移出了內核態(tài),作為一個專門的用戶態(tài)進程提供相應的功能.因此,在微內核場景下,大多數(shù)功能不會在內核中完成.例如,I/O 相關的功能會由用戶態(tài)的驅動程序負責,而中斷發(fā)生后也會轉交給用戶態(tài)的特定進程進行中斷處理.
然而,目前在數(shù)據(jù)中心內部,主流的依然是基于宏內核的Linux 操作系統(tǒng).出于性能上的考慮,宏內核中集成了包括設備驅動、中斷處理和資源管理在內的各類復雜功能,因此,用戶態(tài)應用程序在運行時會比較頻繁地與內核進行交互.大量緊耦合的復雜功能帶來了錯誤隔離方面的一些不便,宏內核中存在大量涉及段寄存器的非常規(guī)內存訪問機制,如果發(fā)生一些數(shù)據(jù)訪問的錯誤往往會造成例如系統(tǒng)崩潰等無法挽回的后果,所以在代理執(zhí)行的過程中保證與內核交互的正確性是異常重要的.
現(xiàn)代操作系統(tǒng)大都使用SYSCALL 指令進行系統(tǒng)調用,CPU 會根據(jù)LSTAR MSR 的值跳轉到系統(tǒng)調用處理函數(shù)的入口,所以Wormhole 在第②步操作中在服務端虛擬機的地址空間中建立從客戶端虛擬機LSTAR MSR到服務端虛擬機系統(tǒng)調用入口代碼頁的映射,保證可以通過客戶端LSTAR MSR 的值找到正確的系統(tǒng)調用入口.
出于權限分離與安全上的考慮,宏內核中的用戶態(tài)與內核態(tài)使用的包括棧在內的一系列內存結構是不同的,所以在用戶態(tài)進程下陷到內核時需要保存用戶態(tài)的上下文并切換到內核專用?;蛑袛鄬S脳?例如,在Linux 內核中這些棧頂?shù)牡刂反鎯υ谝恍﹑er-CPU(每個CPU 一個)的變量中.在AMD64 平臺的Linux 內核中,這些per-CPU 的變量使用GS 段寄存器來進行訪問,它們的基地址存儲在專門的MSR 中,不會隨著地址空間的變化而改變.Wormhole 通過第④步操作保證了控制流切換前后GS 段內存訪問機制的正確性.同樣地,第④步操作也保證了FS 段內存訪問機制的正確性.例如,Linux 操作系統(tǒng)在AMD64 平臺上會利用段寄存器機制通過FS:0x28 來訪問一個特殊的“哨兵(sentinel)”值用于檢查棧緩沖區(qū)溢出(stack buffer overflow)情況.
(3)控制流切換過程中零虛擬機下陷.
控制流切換在整個通信流程的關鍵路徑上,而每次API 轉發(fā)調用都會有兩次虛擬機間的控制流來回切換,因此要想盡量降低虛擬化額外開銷,則必須盡量縮短其所消耗的時間.虛擬化額外開銷與虛擬機下陷的次數(shù)是強相關的,為了盡可能地消除特權模式和非特權模式間的切換耗時,Wormhole 的設計保證了控制流切換過程中不會主動觸發(fā)任何虛擬機下陷,所有配置操作均在非特權模式下完成.
在以KVM 為代表的虛擬化平臺上,非特權模式下對于CR3 以及LSTAR MSR 的修改操作會默認下陷到特權模式中,因此,Wormhole 在第①、②步中采用了添加映射的方式,雖然在地址空間切換的前后虛擬機看到的寄存器的值保持不變,但是借助地址翻譯機制實際訪問到了正確的物理內存區(qū)域.這樣既實現(xiàn)了零下陷的特性,又達到了與執(zhí)行特權操作同樣的效果.非特權模式下對于FS.base 和GS.base 的MSR 修改默認不會造成下陷,并且段寄存器的訪問機制涉及的內存頁數(shù)量較多、范圍較廣,不太適合采用在服務端地址空間添加映射的方法,故在第④步中發(fā)起虛擬機系統(tǒng)調用來臨時更換兩個MSR 中的值.
(4)支持在控制流切換后擴展頁表缺頁的正確處理.
在擴展頁表技術的支持下,現(xiàn)代虛擬化系統(tǒng)對于虛擬機的內存分配請求采用了惰性分配策略,即在客戶虛擬機最初申請一塊內存時只在其頁表內添加GVA 到GPA 的映射,直到該內存區(qū)域第1 次被訪問時觸發(fā)擴展頁表缺頁錯誤才會由虛擬機監(jiān)視器在擴展頁表中補充GPA 到HPA 的映射.在代理執(zhí)行期間如果發(fā)生了虛擬機下陷,從虛擬機監(jiān)視器的視角來看,當前的下陷仍然來自客戶端虛擬機,默認會根據(jù)VMCS 中的相關信息對于客戶端虛擬機執(zhí)行下陷處理函數(shù),而實際上造成這次下陷的原因來自于服務端虛擬機中,所以必須將處理對象從客戶端虛擬機變更為服務端虛擬機.
Wormhole 的設計是,當發(fā)生了擴展頁表缺頁導致的下陷后,讓虛擬機監(jiān)視器判斷是否在代理執(zhí)行過程中發(fā)生的缺頁錯誤.如果是,則提取客戶端虛擬機VMCS 中缺頁錯誤有關的參數(shù),對于服務端虛擬機執(zhí)行缺頁處理函數(shù),將GPA 到HPA 的映射添加到服務端虛擬機的擴展頁表中.
為了驗證本加速器虛擬化框架的設計,本文在Intel 的x86-64 平臺上,基于主流的Linux 操作系統(tǒng)以QEMU-KVM 作為虛擬化平臺,按照上文的設計對于常用的NVIDIA GPU 實現(xiàn)了一個加速器虛擬化的原型系統(tǒng),支持了CUDA 9.0 版本.原型系統(tǒng)架構如圖6 所示,具體實現(xiàn)細節(jié)如下.
Fig.6 Architecture of prototype system圖6 原型系統(tǒng)架構圖
目前主流的深度學習框架與GPU 類加速器的交互主要通過調用NVIDIA 公司推出的CUDA[27]統(tǒng)一計算API.本文在收集了被使用到的CUDA API 后發(fā)現(xiàn),主流的深度學習框架用到的科學計算庫主要有cudart、cuBLAS、cuDNN 和cuRAND,應用程序通過動態(tài)鏈接的方式調用這些計算庫中的CUDA API.
本文在實現(xiàn)時,將整個虛擬化系統(tǒng)的基礎框架劃分為前端模塊、通信模塊以及后端模塊,其中,前端模塊放置在客戶端虛擬機中,后端模塊放置在服務端虛擬機中,通信模塊用于在兩個虛擬機之間實現(xiàn)代理執(zhí)行.
(1)在前端模塊中,本系統(tǒng)為所有收集到的CUDA API 按照官方文檔實現(xiàn)了相同函數(shù)原型的樁函數(shù),分別封裝在對應計算庫同名的樁函數(shù)庫中(如 libcudart.so,libcudnn.so 等).這些樁函數(shù)庫將通過修改環(huán)境變量LD_LIBRARY_PATH 的方式代替客戶端虛擬機中原生的計算庫,實現(xiàn)對于應用程序CUDA 調用的攔截.
(2)在后端模塊中,本系統(tǒng)首先使用dlopen 預載用到的CUDA 計算庫,接著向虛擬機監(jiān)視器注冊后端處理函數(shù)的代理執(zhí)行入口,然后會fork 出與CPU 核數(shù)等量的子進程以盡可能多地支持并發(fā)的前端請求,每個子進程可以對應一個前端模塊.最后,后端模塊會進入凍結狀態(tài),等待來自前端的代理執(zhí)行.
(3)通信模塊的具體實現(xiàn)將在本節(jié)余下部分詳細地加以描述.
為了減少前后端通信的次數(shù)以降低通信開銷,本系統(tǒng)對于常用的CUDA 核函數(shù)(kernel)的轉發(fā)方法做了與GVirtuS、vCUDA 等前序工作類似的批處理(batching)優(yōu)化.一次CUDA 核函數(shù)的調用實際上依次分別調用了3種CUDA API(1 次cudaConfigureCall?1 次或多次cudaSetupArgument?1 次cudaLaunch),因為只有最后的cudaLaunch 是真正與設備交互的顯式同步點,所以每次攔截到前兩種CUDA API 時不用立即轉發(fā)到后端,可以與最后的cudaLaunch 一起批量地處理.
較新的CUDA 版本支持了統(tǒng)一虛擬內存(unified virtual addressing,簡稱UVA)的特性,例如cublasSdot 的指針參數(shù)既可能指向主機內存也可能指向設備內存,在原生環(huán)境下需要GPU 的驅動程序對于內存拷貝的方向(比如從設備拷貝到主機內存)進行判斷,必須根據(jù)源地址段和目的地址段的內存類型(主機內存地址或設備內存地址)加以決定.在本虛擬化系統(tǒng)中,由于GPU 的驅動程序與應用程序處在不同的地址空間中,客戶端虛擬機中的應用程序轉發(fā)來的地址無法由服務端虛擬機中的驅動程序進行正確的檢查和判斷,因此本系統(tǒng)基于區(qū)間樹(interval tree)實現(xiàn)了一套高效的GPU 設備地址的追蹤模塊,對于諸如cudaMalloc 等分配設備內存的API 返回的設備內存區(qū)間進行記錄,在攔截到使用了UVA 特性的CUDA API 后,使用追蹤模塊查詢傳入的內存地址參數(shù)所屬的內存類型,然后依據(jù)具體情況進行處理.
按照Wormhole 的設計,在服務器虛擬機啟動前,需要主機操作系統(tǒng)將GPU 設備通過PCI 直通的方式實現(xiàn)透傳,啟動后需要配置好驅動程序和科學計算庫等.雙方虛擬機均需要下陷注冊自己的相關信息,本系統(tǒng)修改了KVM 中CPUID 的下陷處理函數(shù),在收到注冊請求后會根據(jù)虛擬機的類型和請求完成相應的操作.
包括跳板代碼頁在內的共享內存映射是主動式跨虛擬機通信的關鍵,本系統(tǒng)在KVM 的CPUID 處理函數(shù)中根據(jù)初始化時的虛擬機下陷指令,為服務端與客戶端虛擬機按順序預先配置了以下映射.
(1)客戶端CR3、LSTAR 在服務端虛擬機中的映射.這兩種映射的添加是后續(xù)跳板代碼頁執(zhí)行過程中零下陷的基礎.本系統(tǒng)首先在服務端虛擬機下陷時的VMCS 中讀取到服務端CR3 的值后,利用軟件模擬的方式遍歷服務端虛擬機的擴展頁表,翻譯得到服務端進程頁表在主機物理內存中的HPA.然后在客戶端虛擬機下陷時的VMCS 中讀取到客戶端CR3 的值,再次遍歷服務端虛擬機的擴展頁表添加從客戶端CR3 到服務端頁表HPA 的映射.類似地,首先在服務端虛擬機下陷時讀取到服務端LSTAR MSR 的值,利用服務端頁表進行地址翻譯獲取對應的GPA,然后在客戶端虛擬機下陷時獲取客戶端LSTAR MSR 的值作為新的GVA,接著在服務端頁表中建立從該GVA 到服務端系統(tǒng)調用代碼頁GPA 的映射.
(2)共享內存.客戶端虛擬機中前端模塊攔截到的CUDA API 參數(shù)需要轉發(fā)給服務端虛擬機中的后端模塊,存在大量API 需要進行主機與設備間的內存拷貝.為了達到跨虛擬機傳參的效果,本系統(tǒng)讓KVM 分配一塊足夠大的內存作為傳參用共享內存,在服務端進程與客戶端進程的高地址空間預留了一段起始GVA,然后在KVM中添加雙方應用程序頁表與擴展頁表的映射,使得CPU 在切換地址空間前后可以通過預留的GVA 訪問到同一塊物理內存.
(3)過渡用共享棧.在控制流從客戶端進程切換到服務端進程的過程中有一段中間過渡期,為了不污染服務端進程與客戶端進程原有的棧結構,本系統(tǒng)在KVM 中分配了16 個大小為4KB 的頁內存,映射到了雙方進程的高地址空間中的相同GVA,保證控制流切換前后棧結構的可用性.
(4)處理函數(shù)的指針數(shù)組在客戶端虛擬機中的映射.控制流從跳板代碼頁跳轉到服務端進程地址空間時需要明確目標函數(shù)的位置,本系統(tǒng)在KVM 中為每個服務端虛擬機中維護了一個函數(shù)指針數(shù)組,在服務端進程初始化時會下陷到KVM 將所有可用的處理函數(shù)虛擬地址存入該數(shù)組.類似地,為了過渡時能夠在用戶態(tài)訪問處理函數(shù)的指針數(shù)組,本系統(tǒng)也將其映射到了客戶端進程的高地址空間中.
(5)跳板代碼頁.在上述準備工作完成后,本系統(tǒng)在KVM 中存放了一份跳板代碼頁,其對用戶態(tài)暴露了delegate_to_server 函數(shù)接口,參數(shù)包括服務端虛擬機的偏移量和后端處理函數(shù)的偏移量,使得客戶端虛擬機中的通信模塊通過調用delegate_to_server 切換到服務端虛擬機中對應的后端處理函數(shù).跳板代碼頁被映射到了雙方應用程序的高地址空間中的相同GVA,客戶端進程將該地址強制性地轉換為函數(shù)指針后即可按照函數(shù)調用的方式調用delegate_to_server 接口.跳板代碼的邏輯是:(a)在被客戶端應用程序調用時,先將當前所有的寄存器壓棧來保存上下文,然后保存當前的棧指針并替換為過渡用的臨時棧,最后發(fā)起arch_prctl 系統(tǒng)調用替換FS.base 和GS.base MSR;(b)調用VMFUNC 指令切換到服務端進程的地址空間,此時完成了一次跨虛擬機通信;(c)接著檢查參數(shù)的合法性后從函數(shù)指針數(shù)組中讀取后端處理函數(shù)的地址,將地址作為函數(shù)指針間接跳轉進入后端處理函數(shù)執(zhí)行;(d)待后端處理函數(shù)返回后,調用VMFUNC 指令切換回到客戶端進程的地址空間;(e)發(fā)起arch_prctl 系統(tǒng)調用設置客戶端進程的FS.base 和GS.base MSR,恢復為客戶端進程原生的棧結構,然后從棧中恢復代理執(zhí)行前的上下文.
完成初始化后,服務端虛擬機的后端模塊會從用戶態(tài)進入凍結狀態(tài),此后服務端虛擬機的CPU 資源可以釋放給其他客戶虛擬機使用,內存與I/O 資源仍要保留供代理執(zhí)行使用.要從用戶態(tài)進入凍結狀態(tài)的原因是在代理執(zhí)行時客戶端虛擬機的控制流也是從用戶態(tài)切換而來,如果在內核態(tài)凍結則會導致內核的專用棧等數(shù)據(jù)結構被污染,代理執(zhí)行過程中會造成內核的崩潰等嚴重錯誤.幸運的是,CPUID 指令在用戶態(tài)與內核態(tài)都會無條件地觸發(fā)虛擬機下陷,因此后端模塊會在用戶態(tài)調用CPUID 傳遞凍結指示參數(shù)下陷到KVM 中,KVM 在收到凍結請求后會在CPUID 處理函數(shù)中設置該虛擬機的凍結標志.在每個虛擬機的vCPU 試圖執(zhí)行VMRESUME 恢復運行之前,KVM 會檢查該虛擬機的凍結標志,如果為真,則攔截其vCPU,并主動進入調度以釋放CPU 資源.
使用QEMU-KVM 虛擬化平臺時,每個客戶虛擬機從主機操作系統(tǒng)的角度來看,本質上都是一個可以利用KVM 內核模塊進行加速的用戶態(tài)QEMU 進程[28],所以每個虛擬機的內地址空間本質上都是對應QEMU 進程的地址空間.一個正常運行的虛擬機如果觸發(fā)了擴展頁表的缺頁錯誤,CPU 會發(fā)生原因為EPT violation 的虛擬機下陷,KVM 會根據(jù)缺頁錯誤的GPA 在當前QEMU 進程的地址空間中進行處理.處理過程中,Linux 內核會首先依據(jù)名為current 的per-CPU 變量來獲取當前CPU 上運行著的進程描述符(task_struct 結構體),其中存放著與當前QEMU 進程綁定的內存描述符(mm_struct 結構體),然后利用Linux 內核的內存管理相關函數(shù)為該內存描述符分配實際的物理內存,最后給擴展頁表補充GPA 到HPA 的映射.
代理執(zhí)行時如果觸發(fā)了擴展頁表缺頁錯誤,下陷后KVM 識別到的當前進程身份仍然是客戶端虛擬機的QEMU 進程,因此KVM 會在客戶端QEMU 進程的地址空間中分配新的內存,并向客戶端虛擬機的擴展頁表中添加映射.而實際上缺頁錯誤發(fā)生在服務端虛擬機地址空間中,正確的操作應該是給服務端的QEMU 進程分配新的內存并添加擴展頁表映射.因此,本系統(tǒng)修改了KVM 的EPT violation 處理函數(shù),在發(fā)生缺頁錯誤下陷后會首先判斷當前是否正在代理執(zhí)行.如果是,則會暫時將當前current 變量存儲并替換為初始化時記錄的服務端QEMU 進程的進程描述符,這樣在內存分配和擴展頁表映射時KVM 的操作對象均為服務端虛擬機,待完成后再將current 變量恢復.由于服務端虛擬機一直處于凍結狀態(tài),所以上述操作不會有數(shù)據(jù)沖突(data race)的風險.
對于CUDA 綁定內存特性(pinned memory)等由于驅動的閉源性未能支持,對于CUDA 多流(stream)操作等異步API 暫時會被轉化為同步版的API 調用,因此對Host 和Device 間的內存拷貝性能會有一定的影響.不過,未完全實現(xiàn)的部分與本文的設計是正交的,并不會妨礙證明本加速器虛擬化框架設計帶來的大幅度性能提升.
為了測試原型系統(tǒng)的性能,本文使用了一臺支持VMFUNC 硬件虛擬化特性的Intel Haswell-E 消費級服務器作為測試平臺,該測試平臺的主要軟/硬件配置見表3.本節(jié)按照測試的粒度從小到大主要分為3 個部分,將PCI 直通的虛擬化方案作為最高性能的基準線(baseline).
Table 3 Testbed configuration表3 測試平臺配置信息
為了對比目前公開可用的GPU 虛擬化解決方案,本文選取了具有代表性的開源方案GVirtuS 作為對照,由于最新的GVirtuS 支持的CUDA API 仍然非常有限,本文對GVirtuS 的源碼進行了補充,使其支持與本原型系統(tǒng)同樣多的CUDA API.此外,由于GVirtuS 的通信模塊對于VM-VM 模式下的虛擬化僅支持TCP/IP 方式,在API轉發(fā)時額外的內存拷貝開銷較大.本文為GVirtuS 的通信模塊添加了與原型系統(tǒng)相同的共享內存方式,消除了兩個系統(tǒng)在內存拷貝開銷上的差異,既提升了GVirtuS 系統(tǒng)的性能,又保證了性能測試的公平性.
過去的大部分原型系統(tǒng)僅僅支持了小部分的CUDA API,選用的測試用例與現(xiàn)實場景中的應用程序相差較大,無法全面地反映出系統(tǒng)真實的性能表現(xiàn).本文選取了流行的Caffe[29]作為基準測試程序,Caffe 是一個用C++編寫的深度學習框架,由于其清晰、高效的優(yōu)點在深度學習領域[30]中被廣泛使用.
為了驗證Wormhole 的設計對于第2.1 節(jié)提出的通信性能問題的提升,本節(jié)對原型系統(tǒng)中Neuron Layer 單元測試中所有CUDA API 的轉發(fā)流程作了時間拆分(time breakdown)分析,調用過程中各部分的時間開銷及占用總時間的百分比如圖7 所示.測試中API 調用的總時間為23 778 309 322cycle,可以看到,額外性能開銷占整個流程耗時的比例從GVirtuS 的88%降到了20%(4 660 728 884 個cycle),其中,額外內存拷貝耗時占3.65%(866 946 457 個cycle),系統(tǒng)調用修改FS.base 和GS.base MSR 的耗時占比為10.64%(2 530 932 006 個cycle),余下的包括VMFUNC 在內的控制流量切換操作的耗時占比為5.31%(1 262 850 421 個cycle).
Fig.7 Analysis of the average time cost of Neuron testcase in the prototype system圖7 原型系統(tǒng)中Neuron 測試用例平均耗時占比分析
從絕對時間開銷來看,本次測試中GVirtuS 基于TCP/IP 的通知機制消耗了248 617 785 293 個cycle,而Wormhole 原型系統(tǒng)中的控制流切換機制僅消耗了4 660 728 884 個cycle.為了更細粒度地驗證本設計在第3.3節(jié)中提出的“跨虛擬機地址空間的執(zhí)行流切換”(下稱“快速切換”)和“執(zhí)行流切換過程中零虛擬機下陷”(下稱“零下陷”)兩個技術點的效果,本節(jié)增加了一次測試,在該測試中關閉了“零下陷”功能,即每次執(zhí)行流切換前后主動下陷到虛擬機監(jiān)視器更換MSR.結果表明,API 調用的總時間為29 638 851 151 個cycle,而通信模塊占用了9 432 980 220 個cycle,意味著“快速切換”技術點將TCP/IP 通信開銷降低了2 個數(shù)量級,而“零下陷”技術點進一步將執(zhí)行流切換的開銷降低了50%.綜上所述,本設計大幅優(yōu)化了虛擬化帶來的額外耗時,從微觀角度證明了Wormhole 的設計相較于以GVirtuS 為代表的現(xiàn)有設計大幅降低了虛擬化帶來的額外開銷.
從CPU 利用率來看,如表4 所示,Wormhole 只占用了1 個CPU 的總時間12.145s,其中用戶態(tài)有效時間為9.474s、內核態(tài)有效時間為2.661s,利用率高達99.92%,遠高于GVirtuS 環(huán)境下2 個CPU 的46.61%和38.52%,甚至要優(yōu)于PCI 直通方案的97.28%,證明了Wormhole 的設計相較于現(xiàn)有方案,大幅度地提升了CPU 的利用率.
Table 4 The improvement of CPU utilization during Neuron Layer testcase表4 Neuron Layer 測試中的CPU 利用率提升效果
本節(jié)將使用Caffe 官方提供的多種神經網絡層的單元測試用例,從宏觀角度體現(xiàn)Wormhole 在不同神經網絡層測試中獲得的性能提升,采用測試用例自帶的時間統(tǒng)計工具來衡量各個測試的用時,時間單位為ms,評價性能高低的標準是耗時越短,性能越好.
本節(jié)選取了現(xiàn)實場景中一些重要的神經網絡層作為單元測試用例,主要有以下幾類:計算機視覺[31]領域常用的圖像處理網絡層:(1)卷積層(下稱CONV)和逆卷積層(下稱DECONV);(2)自然語言處理[32]領域常用的循環(huán)網絡層:循環(huán)網絡層(下稱 RNN)和長短期記憶網絡層(下稱 LSTM);(3)深度神經網絡中常用的規(guī)范化(normalization)網絡層:批規(guī)范化網絡層(下稱BN);(4)深度神經網絡中常用的激活(activation)網絡層:各類激活函數(shù)網絡層,包含ReLU、Sigmoid、TanH 等常見激活函數(shù),統(tǒng)稱為神經元網絡層(下稱Neuron).
測試結果與對比結果如圖8~圖10 所示,圖中,Baseline 代表PCI 直通虛擬機方式下的理想性能,Wormhole為本原型系統(tǒng)的性能,GVS 為優(yōu)化后的GVirtuS 系統(tǒng)的性能.
在圖像處理網絡層方面,本原型系統(tǒng)相較于優(yōu)化后的GVirtuS 系統(tǒng),在CONV 測試中的性能提升達到了88.31%,在DECONV 測試中的性能提升達到了88.67%.
Fig.8 Performance comparison of image processing layer圖8 圖像處理層性能對比
Fig.9 Performance comparison of recurrent network layer圖9 循環(huán)網絡層性能對比
Fig.10 Performance comparison of normalization and activation layer圖10 規(guī)范化層、激活層性能對比
在循環(huán)網絡層方面,本原型系統(tǒng)相較于優(yōu)化后的GVirtuS 系統(tǒng),在RNN 測試中的性能提升達到了89.62%,在LSTM 測試中的性能提升達到了88.97%.
在規(guī)范化層和激活層方面,本原型系統(tǒng)相較于優(yōu)化后的GVirtuS 系統(tǒng),在BN 測試中的性能提升達到了89.45%,在Neuron 測試中的性能提升達到了87.89%.
本節(jié)選取了AlexNet[33]和LeNet[34]進行完整的深度學習模型訓練測試,用以評測Wormhole 以及本原型系統(tǒng)在完整的真實工作負載下的性能表現(xiàn),采用深度學習框架自帶的吞吐量統(tǒng)計工具來衡量訓練過程中的吞吐量,單位為迭代次數(shù)/秒(iter/s),評價性能高低的標準是吞吐量越大性能越好.同樣地,本小節(jié)以Baseline 代表PCI直通方式下的理想性能,Wormhole 為本原型系統(tǒng)的性能,GVS 為優(yōu)化后的GVirtuS 系統(tǒng)的性能.
LeNet 誕生于1994 年,是最早的卷積神經網絡之一,并且推動了深度學習領域的發(fā)展.本節(jié)使用MNIST[35]作為數(shù)據(jù)集,批處理大小(batch size)為100,基于Caffe 進行10 000 次迭代訓練.如圖11 所示,本原型系統(tǒng)相較于優(yōu)化后的GVirtuS 系統(tǒng)吞吐量提升了5 倍.
AlexNet 于2012 年被提出,首次在CNN 中成功地應用了ReLU、Dropout 和LRN 等,可以算是LeNet 的一個更深、更寬的版本,是現(xiàn)代深度CNN 的奠基之作.本小節(jié)為消除大規(guī)模存儲設備I/O 造成的影響以方便測試,選用了模擬數(shù)據(jù)(dummy data)作為數(shù)據(jù)集,批處理大小為64,基于Caffe 進行1 800 次迭代訓練.如圖12 所示,本原型系統(tǒng)相較于優(yōu)化后的GVirtuS 系統(tǒng)吞吐量提升了1.4 倍.
Fig.12 Performance comparison of AlexNet training圖12 AlexNet 訓練性能對比
從系統(tǒng)評測章節(jié)可以看出,雖然本加速器虛擬化框架的設計相較于現(xiàn)有的解決方案有很大的性能提升,但是對比PCI 直通方案的理想性能,仍有一定的差距.在時間拆分分析部分,不難看出原型系統(tǒng)中通信模塊的耗時仍然較多,其中,耗時最長的操作是對于FS.base 和GS.base 兩個MSR 的修改.這兩個操作存有優(yōu)化的余地,理論上可以通過在服務端虛擬機地址空間中添加內存映射來回避寫MSR 的操作.由于涉及到的內存頁較多,映射時必須收集到所有可能通過段寄存器訪問的內存區(qū)域,因此本文會將這種優(yōu)化作為未來工作繼續(xù)加以研究.
雖然虛擬機之間有著很強的隔離性,但是本文所提到的代理執(zhí)行設計允許應用程序在用戶態(tài)實現(xiàn)虛擬機地址空間的切換,所以可能造成潛在的安全隱患,本小節(jié)針對使用本加速器虛擬化框架后的安全性進行了分析.本文將虛擬機監(jiān)視器以及服務端虛擬機視為可信部分,假設惡意用戶只可能通過客戶端虛擬機發(fā)起攻擊,攻擊對象可以是服務端虛擬機,也可以是同一物理服務器上的其他客戶端虛擬機.本文分析了以下攻擊方式.
(1)VMFUNC 非法切換攻擊.一個惡意用戶可以在自己控制的客戶虛擬機內自定義包含VMFUNC 指令的應用程序,該程序可以不使用Wormhole 提供的跳板機制,從而自行指定參數(shù),試圖將控制流切換到其他非服務端的客戶虛擬機,造成敏感數(shù)據(jù)的泄露等.Wormhole 通過限定每個客戶端虛擬機的EPTP 列表,第0 項為當前擴展頁表,第1 項為目標服務端虛擬機的擴展頁表,其余510 項強制填充無效地址0,使客戶端虛擬機在執(zhí)行地址空間切換時只有兩種選項:切換到自己或綁定的服務端虛擬機.如果惡意應用程序傳給VMFUNC 指令參數(shù)大于1,則會發(fā)生下陷被虛擬機監(jiān)視器攔截,因此不會對其他虛擬機造成影響.
(2)非法后端函數(shù)跳轉攻擊.一個惡意用戶可能試圖篡改映射到客戶端虛擬地址空間的后端處理函數(shù)指針數(shù)組中的地址,例如將函數(shù)指針指向一些可能會泄露敏感數(shù)據(jù)的函數(shù)來劫持控制流.Wormhole 在映射后端處理函數(shù)指針數(shù)組時,在擴展頁表中將數(shù)組所在內存頁的讀寫權限設為了只讀(read-only),如果發(fā)生試圖修改后端處理函數(shù)指針數(shù)組的行為,將會觸發(fā)虛擬機下陷,虛擬機監(jiān)視器會捕捉并阻止后續(xù)行為.
本文面向時下流行的云端深度學習場景,針對目前缺乏可用性好、方便高效、易于維護的加速器虛擬化方案的現(xiàn)狀,提出了Wormhole——一套基于硬件虛擬化技術的加速器虛擬化框架,為各類云服務提供商開發(fā)可定制化、易于更新的加速器虛擬化系統(tǒng)提供了支持.Wormhole 加速器虛擬化框架以API 轉發(fā)為基礎,以虛擬機為隔離保護域,創(chuàng)新性地提出了被動式服務端虛擬機的抽象以及跨虛擬機快速代理執(zhí)行的各項技術,在保證了用戶間強隔離性的前提下,實現(xiàn)了高硬件資源利用率、低虛擬化開銷的加速器虛擬化,并在主流的QEMU/KVM 平臺上實現(xiàn)了針對NVIDIA GPU 的原型系統(tǒng).測試結果表明,Wormhole 可以方便地部署在消費級服務器上,對比擴展優(yōu)化后的GPU 虛擬化代表性方案GVirtuS,有大幅度的性能提升,驗證了本加速器虛擬化框架的有效性.