基于跨虚拟机零下陷通信的加速器虚拟化框架∗

2020-01-02 03:45李鼎基糜泽羽吴保东赵永望丁佐华陈海波
软件学报 2020年10期
关键词:服务端调用加速器

李鼎基,糜泽羽,吴保东,陈 逊,赵永望,丁佐华,陈海波

1(上海交通大学 软件学院,上海 200240)

2(北京市商汤科技开发有限公司,北京 100080)

3(浙江大学 网络空间安全学院,浙江 杭州 310007)

4(浙江理工大学 信息学院,浙江 杭州 310018)

随着以深度学习[1]为代表的计算密集型应用的大量兴起,仅仅依靠CPU 提供的算力已经显得捉襟见肘,开发者们纷纷将目光转向了CPU 之外具有更强大计算能力的设备作为加速器.传统的通用加速器主要以GPGPU为主,而工业界也为了特定的目的开发出了以TPU(tensor processing unit)[2]为代表的专用加速器.数据中心是云计算(cloud computing)[3]时代的核心基础设施,其中的虚拟化平台是云服务能够高效运行的基础保证.近年来,越来越多的人工智能服务提供商倾向于将其应用部署在云系统中,使得各类加速器需要被整合进已有的虚拟化平台中,因此有关加速器虚拟化的需求也应运而生[4].虽然目前市面上已经出现了若干种加速器虚拟化的解决方案,但是这些方案在现实场景中的应用仍然存在着各类限制和挑战.

目前主流的加速器虚拟化方式是PCI 透传(passthrough)的方式,以Intel VT-d[5]为代表的I/O 设备虚拟化方案会将加速器设备直通到客户虚拟机中.这种虚拟化方式绕开虚拟机监视器(virtual machine monitor,简称VMM)的干预,把加速器全权交由客户虚拟机管理,虽然获得了与裸金属(bare-metal)环境几乎无异的性能,但是无法细粒度地虚拟化给多台客户虚拟机共享使用.因此,该类虚拟化方案使虚拟化平台失去了计算资源弹性分配的能力,较差的可扩展性让虚拟机监视器不能灵活地在多台虚拟机之间动态地调度计算资源.

一些加速器制造商也提供了以Nvidia Grid[6]和gVirt[7]为代表的虚拟化方案,通过让设备驱动程序与加速器配合以达到硬件资源的划分与时分复用.这些方案在虚拟机监视器协助下对于加速器的特定操作进行干预,其余操作则与PCI 透传方式类似,将加速器的运行时间公平地分配给各个客户虚拟机.然而,对于目前的加速器而言,时分复用方案的可用性并不高:一方面,现在成熟的时分复用方案不仅需要在软件层面配合特定的驱动程序,硬件层面上对加速器的型号也有着严格的限制[8],导致大部分普通加速器无法使用此虚拟化功能.另一方面,该类虚拟化方案可移植性差,在每个新型的加速器出现时都需要重新进行针对性的开发,而资源划分策略无法自由地进行调整也导致可扩展性差.

另外,也有以rCUDA[9]和GVirtuS[10]为代表的基于API 转发方式的虚拟化方案.该类方案均建立在分离式驱动模型[11]的基础上,在动态链接库层面进行虚拟vGPU 的抽象.该模型将设备驱动划分为前端驱动(frontend driver)和后端驱动(backend driver)两部分,其中,后端驱动程序扮演服务器的角色,并将从前端驱动程序接收到的请求转换为实际与底层硬件设备交互的驱动程序调用.由于需要进行前后端驱动程序间的通信,还涉及到数据的序列化、内存的额外拷贝等操作,相较于原生的非虚拟化方案,性能会有较大程度的损失,具体的损失程度主要取决于通信部分的性能以及所传输数据量的大小.

为了解决目前主流加速器虚拟化面临的问题,本文基于硬件虚拟化技术,提出了一种针对加速器的虚拟化框架,目的是:(1)针对加速器提供方便、可用的多租户虚拟化方案,提高硬件资源利用率;(2)保证用户间强隔离性与安全性;(3)尽可能地降低虚拟化带来的额外性能开销.为了满足目的1,本文的虚拟化框架放弃了PCI 直通的虚拟化方式,选择了基于API 转发方法的C/S 架构,参考了成熟的I/O 设备虚拟化方案,通过前后端的分离来支持多租户;为了满足目的2,本文将虚拟机作为基本保护域以保证多租户场景下的强隔离性[12],而不是直接在物理主机的操作系统上使用诸如Kubernetes[13]的容器编排系统[14];硬件虚拟化技术的VMFUNC 功能允许应用在非特权模式下切换扩展页表,为了满足目的3,本文借助该特性实现了CPU 控制流在虚拟机间零下陷地快速切换,重点优化了API 转发过程中虚拟机间通信流程的性能,尽可能地将性能损失降到最小.实践证明,本文实现的Wormhole 原型系统很好地解决了目前加速器虚拟化方案所面临的问题,在模型训练测试中相较于目前开源的类似方案取得了1.4~5 倍的性能提升.

本文做出了如下贡献:

(1)基于API 转发的方法,提出了一种针对加速器的、基于跨虚拟机代理执行的虚拟化框架.

(2)提出了一种基于硬件虚拟化技术的无下陷的虚拟机间通信加速机制.

(3)在KVM 上,将虚拟化框架应用于常见的Nvidia GPU,实现并支持了主流深度学习训练框架Caffe.

(4)扩展并优化了开源API 转发框架GVirtuS,使其同样支持深度学习框架,经过对比测试表明,本文提出的虚拟化框架相较于优化后的GVirtuS,在性能上有很大提升.

1 背景知识

1.1 虚拟机系统简介

基于虚拟机的虚拟化方案通过为客户操作系统提供一套完整的虚拟硬件资源来实现虚拟化.这种对于物理硬件进行虚拟化的方案具有很多优点,例如对于客户操作系统透明,并且允许运行未经修改的操作系统,具有很强的兼容性[13].由于每一个虚拟机都拥有其独立的操作系统、函数库、存储资源等,所以从安全性的角度来看,虚拟机的隔离性相当出色[15].

当前数据中心内主流的虚拟化方案是以KVM为代表的基于主机操作系统的虚拟化架构.如图1所示,对于CPU 的虚拟化,虚拟机监视器将每个物理CPU 划分为一个或多个虚拟vCPU 分配给不同的客户虚拟机使用,所有的vCPU 都会由虚拟机监视器统一管理和调度;在内存的虚拟化方面,现代虚拟机系统会为每个虚拟机分配一个扩展页表(extended page table,简称EPT),通过限制地址翻译来保证不同的虚拟机处于不同的地址空间中[16];I/O 的虚拟化通常会复用主机操作系统中的原生驱动程序,当虚拟机内发起I/O 请求后会经过虚拟机监视器转发给主机操作系统上的I/O 代理模块(如QEMU)进行处理.

Fig.1 Architecture of host-based virtualization图1 基于主机操作系统的虚拟化架构图

1.2 硬件虚拟化技术——VMFUNC

为了提高虚拟化系统的性能,以Intel 为代表的各大CPU 制造商纷纷在其CPU 产品上添加了硬件虚拟化技术[17],其中,针对内存的硬件虚拟化已经基本替代了原本的影子页表(shadow page table,简称SPT)[18]方式,成为了默认的内存虚拟化方式.在现代虚拟机系统中,客户虚拟机想要访问物理内存,需要经过两级地址翻译:第1级是根据虚拟机内的页表(page table,简称PT)进行客户虚拟地址(guest virtual address,简称GVA)到客户物理地址(guest physical address,简称GPA)的翻译,第2 级是根据虚拟机监视器配置的扩展页表进行客户物理地址到主机物理地址(host physical address,简称HPA)的翻译.

从Intel 的第4 代CPU 开始,CPU 指令集中加入了VMFUNC[19]这一硬件指令,允许客户虚拟机能够在非特权模式(non-root mode)下执行一些特定的虚拟机相关操作而不会引发任何下陷到虚拟机监视器的行为.截至目前,VMFUNC 指令仅仅支持扩展页表指针(EPT pointer,简称EPTP)切换这一功能,该功能允许客户端虚拟机在非特权模式下由一条VMFUNC 指令执行EPTP 的切换.为了防止切换到错误的内存区域,需要预先在虚拟机监视器中为对应客户虚拟机配置EPTP 列表(EPTP list),应用程序只能从该列表中选择合法的EPTP,而一个EPTP列表中最多可以容纳512 个EPTP.

VMFUNC 指令由于其无下陷的特点相较于传统方法有着很大的性能优势,在该指令出现之前,如果一个客户虚拟机试图完成更改EPTP,需要由非特权模式切换到特权模式修改VMCS 中EPTP 域的值,然后再由特权模式返回到非特权模式,其中,仅仅非特权模式到特权模式的切换就需要花费不少于300 个cycle.相比之下,一条VMFUNC 指令可以完成与上述整个流程同样的功能,在开启了(virtual processor identifier,简称VPID)功能的情况下只需要花费134 个cycle.VMFUNC 也因此被很多已有工作[20,21]所采用.

2 现有工作分析

根据上文所述背景,本文选择API 转发的方法作为Wormhole 的基础方法以兼顾性能和可用性.为了能够有针对性地解决现有基于API 转发的虚拟化方案中存在的关键问题,本节调研了目前公开的相关工作并进行了深入的测试与分析.

2.1 客户端与服务端交互模式的问题

在API 转发方法下,服务端进程与客户端进程的交互模式有多种选择.

(1)以GVirtuS 为代表的方案选择了Host-VM 模式,即将服务端进程放置在主机操作系统中,客户端进程放置在虚拟机或容器中.该模式下,加速器的管理与主机操作系统耦合,主机操作系统的内核需要分饰两角,不仅要扮演客户虚拟机的管理者角色,还要负责运行加速器的驱动程序.这样既打破了单一功能原则(single responsibility principle),又只能同时支持一个版本的加速器驱动,给整个系统的运维带来了困难.例如,当操作系统不支持动态地升级驱动程序时,可能需要重启使新版本驱动生效,但是正在运行着的客户虚拟机显然是无法避免受到影响的,在云服务提供商看来这是无法接受的.

(2)以vCUDA[22]为代表的方案选择了VM-VM 模式,服务端进程与客户端进程分别放置在不同的客户虚拟机中,可以选择多种跨虚拟机的通信方式进行数据交换.传统的基于网络或共享内存的通信方式由于系统调度等因素造成了较大的性能损失,而近年来的一些工作[21]已经提出了虚拟化环境下的快速通信方式,但是对于具有较复杂操作系统(如主流的Linux 系统)的虚拟机却无能为力.此外,随着加速器种类以及配套软件的不断更新,这类方案也未能提出适合的配套进化措施.

现有的API 转发方案的主要问题表现在性能方面,而主要的性能损失来自于通信模块.从目前了解到的基于API 转发的各类系统来看,通信方式主要分为以下几种.

(1)TCP/IP 通信方式[23].由于需要经过多次的内存拷贝,带来了大量的额外开销:以主流的Linux 操作系统利用套接字(socket)进行单向TCP 传输为例,用户态进程在发送数据时需要先将待发送的数据拷贝到内核的缓冲区中,然后内核中的TCP 栈会利用本地网卡将数据发送给目标地址.从上述流程可以看出,为了在两个进程间拷贝一段数据,TCP/IP 通信方式引入了两次额外内存拷贝,如果考虑I/O 虚拟化,额外内存拷贝的次数可能会翻倍,以常见的Virtio 方式[24]为例,一次单向TCP 通信会增加两次虚拟机到主机内核缓冲区的内存拷贝.除此之外,在服务端进程等待客户端请求时,CPU 会陷入睡眠或调度,等到网卡收到数据后会发送中断唤醒CPU,这些异步操作也会带来不小的延迟.

(2)共享内存(shared memory)方式.该通信方法通过在服务端进程和客户端进程之间建立一段共享的内存映射,消除了额外内存拷贝开销.然而,单纯的共享内存并未提供在数据拷贝完成后的通知机制,目前常见的通知机制是将能否共享一个信号量(semaphore)作为能否修改共享内存的标志.由于双方需要主动地轮询信号量,这样会导致CPU 花费大量时间在没有实际作用的轮询和调度上,造成较高的延迟.

(3)远程直接内存访问(remote direct memory access,简称RDMA)方式.RDMA 允许一台服务器直接访问另一台服务器上的内存而无需任意一方操作系统参与[25],意味着可以支持零拷贝(zero-copy)从而降低延迟以及CPU 的性能损失,RDMA 的驱动会直接复用用户态进程的内存来进行传输且完全无需CPU 参与.基于RDMA的通信方式有着优秀的性能与较低的延迟,然而,由于需要购置专用的RDMA 网卡并且安装专用的驱动,在运维方面会产生一笔较大的成本,降低了该类方案的可用性.

本节以Caffe 中Neuron Layer 测试为例,对于API 转发模式的虚拟化方案进行了一系列测试分析,该测试用例在一次运行过程中调用了超过110 万次、共计40 种不同的CUDA API,表1 列出了调用频率前5 的API 明细.测试之前,本文首先对GVirtuS 进行了扩展和优化,将其以VM-VM 模式部署在两台处于同一物理服务器的虚拟机中,通信模块以共享内存作为参数拷贝的载体,TCP/IP 作为参数拷贝完成的通知机制.

Table 1 List of APIs frequently called during Neuron Layer testcase表1 Neuron Layer 测试中被高频调用的CUDA API 列表

接下来,本文对于Caffe 中Neuron Layer 测试用例的API 转发流程进行了耗时分析,将虚拟化时的一次CUDA API 调用流程划分为以下3 个部分:(1)额外内存拷贝耗时,包含数据与共享内存间序列化与反序列化的时间开销;(2)通知机制耗时,包含虚拟机之间通知对方共享内存可用的时间开销;(3)原生API 执行耗时,其中只有原生API 执行耗时是不可避免的有效工作时间,剩余的耗时均属于通信模块的额外性能开销.经过测试,各部分平均耗时占比如图2 所示,测试中API 调用的总时间为283 750 417 817个cycle,可以看到,额外性能开销占整个流程耗时的88%以上(250 528 017 067cycle),其中,额外内存拷贝耗时仅占不到1%(1 910 231 774cycle),通知机制的耗时占比高达87%(248 617 785 293cycle),因此,现有虚拟化系统中的通知机制是造成性能问题的最大瓶颈,也是本文有针对性的要进行优化的重点.

Fig.2 Analysis of the average time cost of Neuron Layer in GVirtuS图2 GVirtuS 中Neuron 测试用例平均耗时占比分析

2.2 CPU 利用率的问题

在原生物理服务器的环境下,调用加速器的过程中一个进程的CPU 有效运行时间T有效可以主要划分为两部分:(1)在用户态运行应用程序功能的时间;(2)在内核态运行驱动程序与加速器交互的时间,其余时间由于没有产生真正有用的结果均可以视作无效运行时间T无效.因此,在加速器虚拟化的情况下,衡量一个虚拟化框架性能和效率的重要指标就是虚拟化后CPU 的有效运行时间占总运行时间的比例为T有效/(T有效+T无效).

在理想情况下,虚拟化后的比例和原生物理环境下的比例相同,即虚拟化没有造成额外的性能开销.本节所述的基于API 转发的方案均使用的是主动式的交互方式,不论是通过网络还是共享内存通信,都需要服务端与客户端双方的CPU 协助完成,而实际有效运行时间只有客户端CPU 执行应用程序以及服务端CPU 执行驱动程序的时间.为降低调度导致的延迟,正常情况下服务端与客户端都会被绑定在不同的物理CPU 上,如图3 所示,虚拟化时一次转发调用所耗费的CPU 资源会是原生物理环境的2 倍以上.

本节使用了Linux 操作系统内置的time 命令,利用上一节中的测试用例在原生物理环境与GVirtuS 虚拟化环境下分别测量了CPU 利用率.如表2 所示,原生物理环境下只占用了1 个CPU,利用率高达97.28%,而GVirtuS环境下占用了2 个CPU 且利用率分别仅为46.61%和38.52%.

Fig.3 CPU effective time in a virtualized environment图3 虚拟化环境下的CPU 有效时间示意图

Table 2 CPU utilization during Neuron Layer testcase表2 Neuron Layer 测试过程中的CPU 利用率

3 Wormhole 设计

Wormhole 加速器虚拟化框架的目标是面向数据中心的现实场景,在保证用户间强隔离性与安全性的前提下,针对加速器提供可用性高、性能好、支持多租户的高效虚拟化方案.本文利用虚拟机作为前后端驱动程序的保护域,结合已被广泛应用的硬件虚拟化技术,改进现有虚拟化方案的不足,实现了一个灵活通用、易于维护、高性能的加速器虚拟化框架.图4 展示了Wormhole 的架构设计以及一次API 转发调用的流程,本节将围绕该图的设计与调用流程示例加以详细展开.

Fig.4 Architecture and invocation example of Wormhole图4 Wormhole 架构设计及调用流程示例

3.1 基于虚拟机的被动式服务端

Wormhole 的设计选择了一种基于虚拟机的被动式服务端(passive server VM)模式,相较于现有的交互模式,有着性能以及灵活性方面的优势.所谓被动式服务端,就是服务端虚拟机在没有待处理的用户请求时不会主动占用任何CPU 资源.

在本文的设计中,所有加速器会通过PCI 直通的方式透传给专门用于管理的服务端虚拟机,从而实现加速器管理与主机操作系统解耦.服务端虚拟机在初始化阶段与一个普通的客户虚拟机别无二致,也可以拥有自己的CPU,如图4(a)所示,此时的服务端虚拟机独占1 号CPU,正在准备所需的通信接收模块等运行环境.在所有预先配置任务完成之后,服务端虚拟机会主动陷入类似快照的冻结状态,如图4(b)所示,此时的服务端虚拟机已经不再拥有CPU 资源,其原有的1 号CPU 资源可以被释放给其他的客户端虚拟机使用,这样就消除了不断等待客户端虚拟机的请求造成的服务端CPU 资源浪费.

根据测试数据可知,现有加速器虚拟化方案的通信模块花费了大量时间在互相等待对方,例如服务端CPU在运行后端驱动程序等逻辑时,客户端CPU 在结果返回之前一直处于空闲状态.因此在Wormhole 中,完全可以利用发送完请求的客户端CPU 在服务端虚拟机中代理执行来消除CPU 资源的浪费,使得被动式的服务端虚拟机变得可行.本设计提高了虚拟化后的CPU 使用效率,节约下来的CPU 资源可以分配给更多的客户虚拟机使用,解决了第2.2 节中提到的问题.

在同一个物理服务器上可以同时存在多个被动式服务端虚拟机,每个服务端虚拟机可以被分配到不同数量的加速器中.同时,得益于虚拟机的隔离,不同的服务端虚拟机可以安装不同版本的加速器驱动程序以及配套的计算库等,以适配不同用户的需要,这也意味着,如果物理服务器上接入了多种异构加速器,不同的加速器也可以经由不同的服务端虚拟机进行隔离,不会出现互相干扰的情况.本设计使得虚拟化框架能够非常容易地接纳快速更迭的不同种类加速器,解决了第2.1 节中提到的问题.

3.2 基于控制流切换的主动式跨虚拟机通信

由于Wormhole 的被动式服务端释放了自己的CPU 资源,虚拟机间的通信必须由客户端采取主动式通信的方式配合服务端,因此本文提出了代理执行的方法,允许客户端执行流主动地进入服务端虚拟机继续执行.为了能够确保代理执行的正确性,要求CPU 在不同虚拟机中运行时处于正确的地址空间中并能够访问到正确的指令和数据.在此基础上,必须进一步解决第2.2 节中提到的性能问题才能达到Wormhole 的高性能目标.因为虚拟化额外开销与下陷的次数紧密相关[26],为了获得优异的跨虚拟机通信性能,本文通过预先配置实现了控制流切换过程中的零虚拟机下陷.

本小节将以图4 中一次代理执行的流程为例介绍本设计的核心思想,在图4(b)中服务端虚拟机在初始化完毕后向虚拟机监视器注册了自己的服务端信息,之后,客户端虚拟机被允许在该服务端虚拟机中代理执行,在图4(c)中客户端虚拟机首先进行了一系列控制流切换前的准备工作,准备完成后切换执行流进入服务端虚拟机,实现了如图4(d)所示的代理执行.得益于本文的设计,图4(c)和图4(d)中与控制流切换相关的操作均会在非特权模式中完成,从而不会触发任何虚拟机下陷.一次完整的代理执行流程的调用可以被划分为6 个步骤.

首先,客户端虚拟机发起与服务端虚拟机配对的请求,会下陷到虚拟机监视器中添加一些内存映射.下列①~③步属于一次性的预先配置,这一阶段虽然会主动触发虚拟机下陷,但是并不在跨虚拟机通信的关键路径上,后续的代理执行不需要重复这些操作,配置完成之后的控制流切换过程会保持零下陷.

①虚拟机监视器会在服务端虚拟机的扩展页表中,将客户端进程CR3 的值映射到服务端进程页表的HPA.本步骤的目的是为后续的VMFUNC 指令实现虚拟机地址空间的正确切换做准备.对于虚拟机的二级地址翻译机制而言,VMFUNC 指令的现有功能只有切换控制第2 级地址翻译的扩展页表,而第1 级地址翻译依靠客户端进程CR3 指向的页表,所以需要在VMFUNC 指令的前后对应正确的页表以完成地址空间的变更.在本步骤添加映射后,客户端进程CR3 的值可以在客户端虚拟机的扩展页表中被翻译为客户端进程的页表内容,而同样的值可以在服务端虚拟机的扩展页表中被翻译为服务端进程的页表内容,保证了VMFUNC 指令前后地址空间的正确翻译.同时,本步骤使得第⑤步在虚拟机间切换地址空间时,仅在虚拟机用户态执行一条VMFUNC 指令就可以达到CR3 替换的等价效果.

② 虚拟机监视器会在服务端虚拟机的页表中,将客户端虚拟机LSTAR MSR(model specific register)的GVA 值映射到服务端虚拟机系统调用入口代码页的GPA.本步骤的目的是,当执行流处于服务端虚拟机的地址空间中时,能够在用户态应用程序发起系统调用后正确地执行系统调用.现代操作系统大都使用SYSCALL 指令进行系统调用,在执行SYSCALL 指令时CPU 会根据LSTAR MSR 的值跳转到系统调用的入口.默认配置下在虚拟机中修改LSTAR MSR 会触发虚拟机下陷由虚拟机监视器完成操作,因此本步骤使用添加映射的方式避免了后续控制流切换过程中造成的虚拟机下陷问题.在添加了上述映射后,代理执行过程中发起系统调用时可以凭借客户端LSTAR MSR 的值访问到服务端系统调用的入口,从而正常地与内核进行交互.

③虚拟机监视器中预先存放了一份跳板代码,配对时会将两个虚拟机高地址空间的某个相同的GVA 映射到这份跳板代码页.本步骤的目的是保证虚拟机地址空间切换前后CPU 指令流能够正确过渡.CPU 的程序计数器(program counter)是根据虚拟地址获取当前指令的,当VMFUNC 指令执行完后程序计数器的值会增加对应的长度,下一条指令已经处于服务端虚拟机的地址空间中.本步骤在两个虚拟地址空间中的相同位置提供了相同的指令,因此在地址空间切换前后CPU 执行的仍然是连续的正确指令.

然后,客户端虚拟机返回到用户态,调用跳板代码页提供的接口,开始执行控制流切换相关的代码.下列④~⑥步需要在每次代理执行过程中重复,所以要求每个步骤都是零下陷以提高跨虚拟机通信的性能.

④ 在切换地址空间之前,客户端进程需要临时修改FS.base 和GS.base 两个MSR 的值为服务端虚拟机中对应的MSR 的值.本步骤的目的是保证在代理执行过程中,段寄存器的访问机制在服务端虚拟机中可以正常工作.现代操作系统中有大量数据需要经过段寄存器机制进行访问,它们的基地址存储在对应虚拟机的VMCS 中的某些域中,不会随着地址空间的变化而改变,如果不做对应的调整,在代理执行期间就会根据错误的地址访问到错误的数据.本步骤预先替换好了正确的MSR 的值,这样一来在地址空间切换前后段寄存器访问机制取得的地址均为合法地址.虽然本步骤采用了替换MSR 的值而非添加映射的方法,但是虚拟机监视器默认对于虚拟机内读写FS.base 和GS.base 两个MSR 的操作是不做拦截的,所以依然不会引发任何的虚拟机下陷.

接下来,客户端虚拟机的执行流真正地切换到了虚拟机地址空间中.

⑤ 继续执行跳板代码页中的VMFUNC 指令以切换地址空间,从下一条指令开始,CPU 上仍然是客户端虚拟机的VMCS,但其中的扩展页表指针已经指向服务端虚拟机的扩展页表.本步骤真正进入了服务端虚拟机的地址空间中,当前生效的数据和资源如图4 中红色边框的区域所示.此时,CPU 的程序计数器指向共享的跳板代码页中的下一条指令,可以正确地继续执行余下的代码.得益于第①步添加的映射,在切换至目标地址空间时,无需像传统方案那样显式地修改CR3 寄存器的值,从而规避了虚拟机调用特权指令而造成的下陷开销.

最后,客户端虚拟机进入服务端的应用程序中开始代理执行.

⑥ 获取跳板代码页的代理执行入口地址,跳转到服务端虚拟机中的后端处理程序中,通过原生的驱动程序与加速器进行交互.本步骤终于将原本运行在客户端虚拟机中的控制流顺利地切换到了服务端虚拟机中开始了代理执行,得益于上述5 步的准备工作,后续的系统调用、中断等与加速器交互必需的复杂操作均可以正常地进行.

当代理执行的任务完成后需要从服务端虚拟机返回到客户端虚拟机中,此时,倒序执行④~⑥步的逆向操作即可,在此不再赘述.按照本小节的设计,通过初始化阶段的一次性预先配置,后续频繁的跨虚拟机控制流切换操作并不会触发任何一次虚拟机下陷,为代理执行的快速、高效,提供了保障.

3.3 本设计的技术点

通过总结上一小节中的6 步操作,我们提出了以下技术点.

(1)跨虚拟机地址空间的执行流快速切换.

主动式的跨虚拟机通信需要在执行流切换前后满足两点:切换前后地址翻译机制的正确以及切换前后CPU 指令流的过渡.原本的一条VMFUNC 指令只负责切换GPA 到HPA 的映射,因此需要有另外的措施负责切换到对应的页表.以一次客户端切换到服务端的地址空间为例,一种符合直觉的想法是在VMFUNC 指令的前后修改CR3 寄存器的值达到切换页表的目的,但是细看之下就会发现无法达到目的:程序计数器是根据虚拟地址获取当前指令的,如果VMFUNC 前一条指令更换了页表,那么CPU 在执行下一条指令时由于GVA 到GPA 映射的变化,实际上硬件看到的已经是一份无效的页表,从而导致运行错误;反之,如果试图在VMFUNC 后一条指令更换页表,由于在VMFUNC 指令执行完成后GPA 到HPA 的映射发生变化,实际访问到的下一条指令内容也不再是对于CR3 的修改,同样会引发异常.

Wormhole 通过将第①步中CR3 映射的添加与第⑤步中硬件虚拟化技术相结合,在不下陷到虚拟机监视器的情况下,实现了如图5 所示的一条VMFUNC 指令可以同时完成页表和扩展页表的切换,保证了切换前后地址翻译机制的正确,一条指令完成多项操作也大幅降低了跨虚拟机通信的开销.同时,因为代理执行的特点,客户端虚拟机与服务端虚拟机均运行在同一个物理CPU 上,避免了跨核中断(inter pocessor interrupt,简称IPI)带来的高昂开销.

Fig.5 Switch virtual machine address space through VMFUNC图5 VMFUNC 指令切换虚拟机地址空间原理图

针对切换前后CPU 指令流的过渡,Wormhole 在第③步操作中提供了一份共享跳板代码页以及一个代理执行专用的栈,代码页包括了VMFUNC 指令以及一些上下文的保存逻辑,将它们映射到双方虚拟机的页表中相同的GVA.这样,客户端进程在需要进行代理执行时可以跳转到这份代码页,在地址空间切换成功后,由于在服务端进程在相同的地址共享这份代码页,程序计数器可以无缝地过渡到VMFUNC 的下一条指令继续执行,也不会污染双方进程原本的栈的内容.另外,还需要服务端进程提前向虚拟机监视器注册代理执行函数的入口,跳板代码在控制流切换完成后会跳转到被注册的函数入口,正式开始在服务端进行加速器的访问操作.

(2)支持在控制流切换后系统调用、中断等复杂操作的正确处理.

一些以SkyBridge[21]为代表的前序工作同样使用了类似的代理执行思想,但是这些工作只是针对一些基于微内核的较简单的操作系统,面向的场景也仅仅是同一个操作系统中进程间的代理执行.在微内核的设计理念中,内核部分的代码量很小,通常只会保留几个最基础的管理功能,大量传统宏内核中的功能(例如设备驱动程序)被移出了内核态,作为一个专门的用户态进程提供相应的功能.因此,在微内核场景下,大多数功能不会在内核中完成.例如,I/O 相关的功能会由用户态的驱动程序负责,而中断发生后也会转交给用户态的特定进程进行中断处理.

然而,目前在数据中心内部,主流的依然是基于宏内核的Linux 操作系统.出于性能上的考虑,宏内核中集成了包括设备驱动、中断处理和资源管理在内的各类复杂功能,因此,用户态应用程序在运行时会比较频繁地与内核进行交互.大量紧耦合的复杂功能带来了错误隔离方面的一些不便,宏内核中存在大量涉及段寄存器的非常规内存访问机制,如果发生一些数据访问的错误往往会造成例如系统崩溃等无法挽回的后果,所以在代理执行的过程中保证与内核交互的正确性是异常重要的.

现代操作系统大都使用SYSCALL 指令进行系统调用,CPU 会根据LSTAR MSR 的值跳转到系统调用处理函数的入口,所以Wormhole 在第②步操作中在服务端虚拟机的地址空间中建立从客户端虚拟机LSTAR MSR到服务端虚拟机系统调用入口代码页的映射,保证可以通过客户端LSTAR MSR 的值找到正确的系统调用入口.

出于权限分离与安全上的考虑,宏内核中的用户态与内核态使用的包括栈在内的一系列内存结构是不同的,所以在用户态进程下陷到内核时需要保存用户态的上下文并切换到内核专用栈或中断专用栈.例如,在Linux 内核中这些栈顶的地址存储在一些per-CPU(每个CPU 一个)的变量中.在AMD64 平台的Linux 内核中,这些per-CPU 的变量使用GS 段寄存器来进行访问,它们的基地址存储在专门的MSR 中,不会随着地址空间的变化而改变.Wormhole 通过第④步操作保证了控制流切换前后GS 段内存访问机制的正确性.同样地,第④步操作也保证了FS 段内存访问机制的正确性.例如,Linux 操作系统在AMD64 平台上会利用段寄存器机制通过FS:0x28 来访问一个特殊的“哨兵(sentinel)”值用于检查栈缓冲区溢出(stack buffer overflow)情况.

(3)控制流切换过程中零虚拟机下陷.

控制流切换在整个通信流程的关键路径上,而每次API 转发调用都会有两次虚拟机间的控制流来回切换,因此要想尽量降低虚拟化额外开销,则必须尽量缩短其所消耗的时间.虚拟化额外开销与虚拟机下陷的次数是强相关的,为了尽可能地消除特权模式和非特权模式间的切换耗时,Wormhole 的设计保证了控制流切换过程中不会主动触发任何虚拟机下陷,所有配置操作均在非特权模式下完成.

在以KVM 为代表的虚拟化平台上,非特权模式下对于CR3 以及LSTAR MSR 的修改操作会默认下陷到特权模式中,因此,Wormhole 在第①、②步中采用了添加映射的方式,虽然在地址空间切换的前后虚拟机看到的寄存器的值保持不变,但是借助地址翻译机制实际访问到了正确的物理内存区域.这样既实现了零下陷的特性,又达到了与执行特权操作同样的效果.非特权模式下对于FS.base 和GS.base 的MSR 修改默认不会造成下陷,并且段寄存器的访问机制涉及的内存页数量较多、范围较广,不太适合采用在服务端地址空间添加映射的方法,故在第④步中发起虚拟机系统调用来临时更换两个MSR 中的值.

(4)支持在控制流切换后扩展页表缺页的正确处理.

在扩展页表技术的支持下,现代虚拟化系统对于虚拟机的内存分配请求采用了惰性分配策略,即在客户虚拟机最初申请一块内存时只在其页表内添加GVA 到GPA 的映射,直到该内存区域第1 次被访问时触发扩展页表缺页错误才会由虚拟机监视器在扩展页表中补充GPA 到HPA 的映射.在代理执行期间如果发生了虚拟机下陷,从虚拟机监视器的视角来看,当前的下陷仍然来自客户端虚拟机,默认会根据VMCS 中的相关信息对于客户端虚拟机执行下陷处理函数,而实际上造成这次下陷的原因来自于服务端虚拟机中,所以必须将处理对象从客户端虚拟机变更为服务端虚拟机.

Wormhole 的设计是,当发生了扩展页表缺页导致的下陷后,让虚拟机监视器判断是否在代理执行过程中发生的缺页错误.如果是,则提取客户端虚拟机VMCS 中缺页错误有关的参数,对于服务端虚拟机执行缺页处理函数,将GPA 到HPA 的映射添加到服务端虚拟机的扩展页表中.

4 Wormhole 原型系统实现

为了验证本加速器虚拟化框架的设计,本文在Intel 的x86-64 平台上,基于主流的Linux 操作系统以QEMU-KVM 作为虚拟化平台,按照上文的设计对于常用的NVIDIA GPU 实现了一个加速器虚拟化的原型系统,支持了CUDA 9.0 版本.原型系统架构如图6 所示,具体实现细节如下.

Fig.6 Architecture of prototype system图6 原型系统架构图

4.1 支持CUDA 调用的API 转发基础框架

目前主流的深度学习框架与GPU 类加速器的交互主要通过调用NVIDIA 公司推出的CUDA[27]统一计算API.本文在收集了被使用到的CUDA API 后发现,主流的深度学习框架用到的科学计算库主要有cudart、cuBLAS、cuDNN 和cuRAND,应用程序通过动态链接的方式调用这些计算库中的CUDA API.

本文在实现时,将整个虚拟化系统的基础框架划分为前端模块、通信模块以及后端模块,其中,前端模块放置在客户端虚拟机中,后端模块放置在服务端虚拟机中,通信模块用于在两个虚拟机之间实现代理执行.

(1)在前端模块中,本系统为所有收集到的CUDA API 按照官方文档实现了相同函数原型的桩函数,分别封装在对应计算库同名的桩函数库中(如 libcudart.so,libcudnn.so 等).这些桩函数库将通过修改环境变量LD_LIBRARY_PATH 的方式代替客户端虚拟机中原生的计算库,实现对于应用程序CUDA 调用的拦截.

(2)在后端模块中,本系统首先使用dlopen 预载用到的CUDA 计算库,接着向虚拟机监视器注册后端处理函数的代理执行入口,然后会fork 出与CPU 核数等量的子进程以尽可能多地支持并发的前端请求,每个子进程可以对应一个前端模块.最后,后端模块会进入冻结状态,等待来自前端的代理执行.

(3)通信模块的具体实现将在本节余下部分详细地加以描述.

为了减少前后端通信的次数以降低通信开销,本系统对于常用的CUDA 核函数(kernel)的转发方法做了与GVirtuS、vCUDA 等前序工作类似的批处理(batching)优化.一次CUDA 核函数的调用实际上依次分别调用了3种CUDA API(1 次cudaConfigureCall➔1 次或多次cudaSetupArgument➔1 次cudaLaunch),因为只有最后的cudaLaunch 是真正与设备交互的显式同步点,所以每次拦截到前两种CUDA API 时不用立即转发到后端,可以与最后的cudaLaunch 一起批量地处理.

较新的CUDA 版本支持了统一虚拟内存(unified virtual addressing,简称UVA)的特性,例如cublasSdot 的指针参数既可能指向主机内存也可能指向设备内存,在原生环境下需要GPU 的驱动程序对于内存拷贝的方向(比如从设备拷贝到主机内存)进行判断,必须根据源地址段和目的地址段的内存类型(主机内存地址或设备内存地址)加以决定.在本虚拟化系统中,由于GPU 的驱动程序与应用程序处在不同的地址空间中,客户端虚拟机中的应用程序转发来的地址无法由服务端虚拟机中的驱动程序进行正确的检查和判断,因此本系统基于区间树(interval tree)实现了一套高效的GPU 设备地址的追踪模块,对于诸如cudaMalloc 等分配设备内存的API 返回的设备内存区间进行记录,在拦截到使用了UVA 特性的CUDA API 后,使用追踪模块查询传入的内存地址参数所属的内存类型,然后依据具体情况进行处理.

按照Wormhole 的设计,在服务器虚拟机启动前,需要主机操作系统将GPU 设备通过PCI 直通的方式实现透传,启动后需要配置好驱动程序和科学计算库等.双方虚拟机均需要下陷注册自己的相关信息,本系统修改了KVM 中CPUID 的下陷处理函数,在收到注册请求后会根据虚拟机的类型和请求完成相应的操作.

4.2 用于支持控制流切换的内存映射与用户态接口

包括跳板代码页在内的共享内存映射是主动式跨虚拟机通信的关键,本系统在KVM 的CPUID 处理函数中根据初始化时的虚拟机下陷指令,为服务端与客户端虚拟机按顺序预先配置了以下映射.

(1)客户端CR3、LSTAR 在服务端虚拟机中的映射.这两种映射的添加是后续跳板代码页执行过程中零下陷的基础.本系统首先在服务端虚拟机下陷时的VMCS 中读取到服务端CR3 的值后,利用软件模拟的方式遍历服务端虚拟机的扩展页表,翻译得到服务端进程页表在主机物理内存中的HPA.然后在客户端虚拟机下陷时的VMCS 中读取到客户端CR3 的值,再次遍历服务端虚拟机的扩展页表添加从客户端CR3 到服务端页表HPA 的映射.类似地,首先在服务端虚拟机下陷时读取到服务端LSTAR MSR 的值,利用服务端页表进行地址翻译获取对应的GPA,然后在客户端虚拟机下陷时获取客户端LSTAR MSR 的值作为新的GVA,接着在服务端页表中建立从该GVA 到服务端系统调用代码页GPA 的映射.

(2)共享内存.客户端虚拟机中前端模块拦截到的CUDA API 参数需要转发给服务端虚拟机中的后端模块,存在大量API 需要进行主机与设备间的内存拷贝.为了达到跨虚拟机传参的效果,本系统让KVM 分配一块足够大的内存作为传参用共享内存,在服务端进程与客户端进程的高地址空间预留了一段起始GVA,然后在KVM中添加双方应用程序页表与扩展页表的映射,使得CPU 在切换地址空间前后可以通过预留的GVA 访问到同一块物理内存.

(3)过渡用共享栈.在控制流从客户端进程切换到服务端进程的过程中有一段中间过渡期,为了不污染服务端进程与客户端进程原有的栈结构,本系统在KVM 中分配了16 个大小为4KB 的页内存,映射到了双方进程的高地址空间中的相同GVA,保证控制流切换前后栈结构的可用性.

(4)处理函数的指针数组在客户端虚拟机中的映射.控制流从跳板代码页跳转到服务端进程地址空间时需要明确目标函数的位置,本系统在KVM 中为每个服务端虚拟机中维护了一个函数指针数组,在服务端进程初始化时会下陷到KVM 将所有可用的处理函数虚拟地址存入该数组.类似地,为了过渡时能够在用户态访问处理函数的指针数组,本系统也将其映射到了客户端进程的高地址空间中.

(5)跳板代码页.在上述准备工作完成后,本系统在KVM 中存放了一份跳板代码页,其对用户态暴露了delegate_to_server 函数接口,参数包括服务端虚拟机的偏移量和后端处理函数的偏移量,使得客户端虚拟机中的通信模块通过调用delegate_to_server 切换到服务端虚拟机中对应的后端处理函数.跳板代码页被映射到了双方应用程序的高地址空间中的相同GVA,客户端进程将该地址强制性地转换为函数指针后即可按照函数调用的方式调用delegate_to_server 接口.跳板代码的逻辑是:(a)在被客户端应用程序调用时,先将当前所有的寄存器压栈来保存上下文,然后保存当前的栈指针并替换为过渡用的临时栈,最后发起arch_prctl 系统调用替换FS.base 和GS.base MSR;(b)调用VMFUNC 指令切换到服务端进程的地址空间,此时完成了一次跨虚拟机通信;(c)接着检查参数的合法性后从函数指针数组中读取后端处理函数的地址,将地址作为函数指针间接跳转进入后端处理函数执行;(d)待后端处理函数返回后,调用VMFUNC 指令切换回到客户端进程的地址空间;(e)发起arch_prctl 系统调用设置客户端进程的FS.base 和GS.base MSR,恢复为客户端进程原生的栈结构,然后从栈中恢复代理执行前的上下文.

4.3 服务端虚拟机的冻结

完成初始化后,服务端虚拟机的后端模块会从用户态进入冻结状态,此后服务端虚拟机的CPU 资源可以释放给其他客户虚拟机使用,内存与I/O 资源仍要保留供代理执行使用.要从用户态进入冻结状态的原因是在代理执行时客户端虚拟机的控制流也是从用户态切换而来,如果在内核态冻结则会导致内核的专用栈等数据结构被污染,代理执行过程中会造成内核的崩溃等严重错误.幸运的是,CPUID 指令在用户态与内核态都会无条件地触发虚拟机下陷,因此后端模块会在用户态调用CPUID 传递冻结指示参数下陷到KVM 中,KVM 在收到冻结请求后会在CPUID 处理函数中设置该虚拟机的冻结标志.在每个虚拟机的vCPU 试图执行VMRESUME 恢复运行之前,KVM 会检查该虚拟机的冻结标志,如果为真,则拦截其vCPU,并主动进入调度以释放CPU 资源.

4.4 代理执行时的扩展页表缺页处理

使用QEMU-KVM 虚拟化平台时,每个客户虚拟机从主机操作系统的角度来看,本质上都是一个可以利用KVM 内核模块进行加速的用户态QEMU 进程[28],所以每个虚拟机的内地址空间本质上都是对应QEMU 进程的地址空间.一个正常运行的虚拟机如果触发了扩展页表的缺页错误,CPU 会发生原因为EPT violation 的虚拟机下陷,KVM 会根据缺页错误的GPA 在当前QEMU 进程的地址空间中进行处理.处理过程中,Linux 内核会首先依据名为current 的per-CPU 变量来获取当前CPU 上运行着的进程描述符(task_struct 结构体),其中存放着与当前QEMU 进程绑定的内存描述符(mm_struct 结构体),然后利用Linux 内核的内存管理相关函数为该内存描述符分配实际的物理内存,最后给扩展页表补充GPA 到HPA 的映射.

代理执行时如果触发了扩展页表缺页错误,下陷后KVM 识别到的当前进程身份仍然是客户端虚拟机的QEMU 进程,因此KVM 会在客户端QEMU 进程的地址空间中分配新的内存,并向客户端虚拟机的扩展页表中添加映射.而实际上缺页错误发生在服务端虚拟机地址空间中,正确的操作应该是给服务端的QEMU 进程分配新的内存并添加扩展页表映射.因此,本系统修改了KVM 的EPT violation 处理函数,在发生缺页错误下陷后会首先判断当前是否正在代理执行.如果是,则会暂时将当前current 变量存储并替换为初始化时记录的服务端QEMU 进程的进程描述符,这样在内存分配和扩展页表映射时KVM 的操作对象均为服务端虚拟机,待完成后再将current 变量恢复.由于服务端虚拟机一直处于冻结状态,所以上述操作不会有数据冲突(data race)的风险.

4.5 亟待完善的部分CUDA 特性

对于CUDA 绑定内存特性(pinned memory)等由于驱动的闭源性未能支持,对于CUDA 多流(stream)操作等异步API 暂时会被转化为同步版的API 调用,因此对Host 和Device 间的内存拷贝性能会有一定的影响.不过,未完全实现的部分与本文的设计是正交的,并不会妨碍证明本加速器虚拟化框架设计带来的大幅度性能提升.

5 系统评测

为了测试原型系统的性能,本文使用了一台支持VMFUNC 硬件虚拟化特性的Intel Haswell-E 消费级服务器作为测试平台,该测试平台的主要软/硬件配置见表3.本节按照测试的粒度从小到大主要分为3 个部分,将PCI 直通的虚拟化方案作为最高性能的基准线(baseline).

Table 3 Testbed configuration表3 测试平台配置信息

为了对比目前公开可用的GPU 虚拟化解决方案,本文选取了具有代表性的开源方案GVirtuS 作为对照,由于最新的GVirtuS 支持的CUDA API 仍然非常有限,本文对GVirtuS 的源码进行了补充,使其支持与本原型系统同样多的CUDA API.此外,由于GVirtuS 的通信模块对于VM-VM 模式下的虚拟化仅支持TCP/IP 方式,在API转发时额外的内存拷贝开销较大.本文为GVirtuS 的通信模块添加了与原型系统相同的共享内存方式,消除了两个系统在内存拷贝开销上的差异,既提升了GVirtuS 系统的性能,又保证了性能测试的公平性.

过去的大部分原型系统仅仅支持了小部分的CUDA API,选用的测试用例与现实场景中的应用程序相差较大,无法全面地反映出系统真实的性能表现.本文选取了流行的Caffe[29]作为基准测试程序,Caffe 是一个用C++编写的深度学习框架,由于其清晰、高效的优点在深度学习领域[30]中被广泛使用.

5.1 微型基准测试(microbenchmark)

为了验证Wormhole 的设计对于第2.1 节提出的通信性能问题的提升,本节对原型系统中Neuron Layer 单元测试中所有CUDA API 的转发流程作了时间拆分(time breakdown)分析,调用过程中各部分的时间开销及占用总时间的百分比如图7 所示.测试中API 调用的总时间为23 778 309 322cycle,可以看到,额外性能开销占整个流程耗时的比例从GVirtuS 的88%降到了20%(4 660 728 884 个cycle),其中,额外内存拷贝耗时占3.65%(866 946 457 个cycle),系统调用修改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 原型系统中Neuron 测试用例平均耗时占比分析

从绝对时间开销来看,本次测试中GVirtuS 基于TCP/IP 的通知机制消耗了248 617 785 293 个cycle,而Wormhole 原型系统中的控制流切换机制仅消耗了4 660 728 884 个cycle.为了更细粒度地验证本设计在第3.3节中提出的“跨虚拟机地址空间的执行流切换”(下称“快速切换”)和“执行流切换过程中零虚拟机下陷”(下称“零下陷”)两个技术点的效果,本节增加了一次测试,在该测试中关闭了“零下陷”功能,即每次执行流切换前后主动下陷到虚拟机监视器更换MSR.结果表明,API 调用的总时间为29 638 851 151 个cycle,而通信模块占用了9 432 980 220 个cycle,意味着“快速切换”技术点将TCP/IP 通信开销降低了2 个数量级,而“零下陷”技术点进一步将执行流切换的开销降低了50%.综上所述,本设计大幅优化了虚拟化带来的额外耗时,从微观角度证明了Wormhole 的设计相较于以GVirtuS 为代表的现有设计大幅降低了虚拟化带来的额外开销.

从CPU 利用率来看,如表4 所示,Wormhole 只占用了1 个CPU 的总时间12.145s,其中用户态有效时间为9.474s、内核态有效时间为2.661s,利用率高达99.92%,远高于GVirtuS 环境下2 个CPU 的46.61%和38.52%,甚至要优于PCI 直通方案的97.28%,证明了Wormhole 的设计相较于现有方案,大幅度地提升了CPU 的利用率.

Table 4 The improvement of CPU utilization during Neuron Layer testcase表4 Neuron Layer 测试中的CPU 利用率提升效果

5.2 神经网络层单元测试

本节将使用Caffe 官方提供的多种神经网络层的单元测试用例,从宏观角度体现Wormhole 在不同神经网络层测试中获得的性能提升,采用测试用例自带的时间统计工具来衡量各个测试的用时,时间单位为ms,评价性能高低的标准是耗时越短,性能越好.

本节选取了现实场景中一些重要的神经网络层作为单元测试用例,主要有以下几类:计算机视觉[31]领域常用的图像处理网络层:(1)卷积层(下称CONV)和逆卷积层(下称DECONV);(2)自然语言处理[32]领域常用的循环网络层:循环网络层(下称 RNN)和长短期记忆网络层(下称 LSTM);(3)深度神经网络中常用的规范化(normalization)网络层:批规范化网络层(下称BN);(4)深度神经网络中常用的激活(activation)网络层:各类激活函数网络层,包含ReLU、Sigmoid、TanH 等常见激活函数,统称为神经元网络层(下称Neuron).

测试结果与对比结果如图8~图10 所示,图中,Baseline 代表PCI 直通虚拟机方式下的理想性能,Wormhole为本原型系统的性能,GVS 为优化后的GVirtuS 系统的性能.

在图像处理网络层方面,本原型系统相较于优化后的GVirtuS 系统,在CONV 测试中的性能提升达到了88.31%,在DECONV 测试中的性能提升达到了88.67%.

Fig.8 Performance comparison of image processing layer图8 图像处理层性能对比

Fig.9 Performance comparison of recurrent network layer图9 循环网络层性能对比

Fig.10 Performance comparison of normalization and activation layer图10 规范化层、激活层性能对比

在循环网络层方面,本原型系统相较于优化后的GVirtuS 系统,在RNN 测试中的性能提升达到了89.62%,在LSTM 测试中的性能提升达到了88.97%.

在规范化层和激活层方面,本原型系统相较于优化后的GVirtuS 系统,在BN 测试中的性能提升达到了89.45%,在Neuron 测试中的性能提升达到了87.89%.

5.3 经典模型训练测试

本节选取了AlexNet[33]和LeNet[34]进行完整的深度学习模型训练测试,用以评测Wormhole 以及本原型系统在完整的真实工作负载下的性能表现,采用深度学习框架自带的吞吐量统计工具来衡量训练过程中的吞吐量,单位为迭代次数/秒(iter/s),评价性能高低的标准是吞吐量越大性能越好.同样地,本小节以Baseline 代表PCI直通方式下的理想性能,Wormhole 为本原型系统的性能,GVS 为优化后的GVirtuS 系统的性能.

LeNet 诞生于1994 年,是最早的卷积神经网络之一,并且推动了深度学习领域的发展.本节使用MNIST[35]作为数据集,批处理大小(batch size)为100,基于Caffe 进行10 000 次迭代训练.如图11 所示,本原型系统相较于优化后的GVirtuS 系统吞吐量提升了5 倍.

AlexNet 于2012 年被提出,首次在CNN 中成功地应用了ReLU、Dropout 和LRN 等,可以算是LeNet 的一个更深、更宽的版本,是现代深度CNN 的奠基之作.本小节为消除大规模存储设备I/O 造成的影响以方便测试,选用了模拟数据(dummy data)作为数据集,批处理大小为64,基于Caffe 进行1 800 次迭代训练.如图12 所示,本原型系统相较于优化后的GVirtuS 系统吞吐量提升了1.4 倍.

Fig.12 Performance comparison of AlexNet training图12 AlexNet 训练性能对比

6 讨论与展望

6.1 虚拟化额外开销

从系统评测章节可以看出,虽然本加速器虚拟化框架的设计相较于现有的解决方案有很大的性能提升,但是对比PCI 直通方案的理想性能,仍有一定的差距.在时间拆分分析部分,不难看出原型系统中通信模块的耗时仍然较多,其中,耗时最长的操作是对于FS.base 和GS.base 两个MSR 的修改.这两个操作存有优化的余地,理论上可以通过在服务端虚拟机地址空间中添加内存映射来回避写MSR 的操作.由于涉及到的内存页较多,映射时必须收集到所有可能通过段寄存器访问的内存区域,因此本文会将这种优化作为未来工作继续加以研究.

6.2 安全性分析

虽然虚拟机之间有着很强的隔离性,但是本文所提到的代理执行设计允许应用程序在用户态实现虚拟机地址空间的切换,所以可能造成潜在的安全隐患,本小节针对使用本加速器虚拟化框架后的安全性进行了分析.本文将虚拟机监视器以及服务端虚拟机视为可信部分,假设恶意用户只可能通过客户端虚拟机发起攻击,攻击对象可以是服务端虚拟机,也可以是同一物理服务器上的其他客户端虚拟机.本文分析了以下攻击方式.

(1)VMFUNC 非法切换攻击.一个恶意用户可以在自己控制的客户虚拟机内自定义包含VMFUNC 指令的应用程序,该程序可以不使用Wormhole 提供的跳板机制,从而自行指定参数,试图将控制流切换到其他非服务端的客户虚拟机,造成敏感数据的泄露等.Wormhole 通过限定每个客户端虚拟机的EPTP 列表,第0 项为当前扩展页表,第1 项为目标服务端虚拟机的扩展页表,其余510 项强制填充无效地址0,使客户端虚拟机在执行地址空间切换时只有两种选项:切换到自己或绑定的服务端虚拟机.如果恶意应用程序传给VMFUNC 指令参数大于1,则会发生下陷被虚拟机监视器拦截,因此不会对其他虚拟机造成影响.

(2)非法后端函数跳转攻击.一个恶意用户可能试图篡改映射到客户端虚拟地址空间的后端处理函数指针数组中的地址,例如将函数指针指向一些可能会泄露敏感数据的函数来劫持控制流.Wormhole 在映射后端处理函数指针数组时,在扩展页表中将数组所在内存页的读写权限设为了只读(read-only),如果发生试图修改后端处理函数指针数组的行为,将会触发虚拟机下陷,虚拟机监视器会捕捉并阻止后续行为.

7 总结

本文面向时下流行的云端深度学习场景,针对目前缺乏可用性好、方便高效、易于维护的加速器虚拟化方案的现状,提出了Wormhole——一套基于硬件虚拟化技术的加速器虚拟化框架,为各类云服务提供商开发可定制化、易于更新的加速器虚拟化系统提供了支持.Wormhole 加速器虚拟化框架以API 转发为基础,以虚拟机为隔离保护域,创新性地提出了被动式服务端虚拟机的抽象以及跨虚拟机快速代理执行的各项技术,在保证了用户间强隔离性的前提下,实现了高硬件资源利用率、低虚拟化开销的加速器虚拟化,并在主流的QEMU/KVM 平台上实现了针对NVIDIA GPU 的原型系统.测试结果表明,Wormhole 可以方便地部署在消费级服务器上,对比扩展优化后的GPU 虚拟化代表性方案GVirtuS,有大幅度的性能提升,验证了本加速器虚拟化框架的有效性.

猜你喜欢
服务端调用加速器
莫比斯加速器众创办公空间
知识快餐店 科学加速器
全民小康路上的“加速器”
系统虚拟化环境下客户机系统调用信息捕获与分析①
多人联机对战游戏的设计与实现
基于三层结构下机房管理系统的实现分析
基于三层结构下机房管理系统的实现分析
基于属性数据的系统调用过滤方法
利用RFC技术实现SAP系统接口通信
C++语言中函数参数传递方式剖析