Transparent GPU Sharing in Container Clouds for Deep Learning Workloads
摘要
容器广泛用于数据中心中的资源管理。
- 支持容器云中的深度学习(DL)训练的一个常见实践是静态地将 GPU 完全绑定到容器上。
- 由于生产中 DL 作业的资源需求多种多样,大量的 GPU 未得到充分利用。
- 因此,GPU 集群具有较低的 GPU 利用率,由于排队,导致作业完成时间较长。
我们提出 TGS (透明 GPU 共享) ,一个系统,提供透明的 GPU 共享到集装箱云中的 DL 训练。
- 与最近 GPU 共享的应用层解决方案形成鲜明对比的是,TGS 在容器下的 OS 层运行。
- 透明性允许用户使用任何软件来开发模型并在其容器中运行作业。
- TGS 利用自适应速率控制和透明统一内存,同时实现高 GPU 利用率和性能隔离。
- 它确保生产作业不会受到共享 GPU 上机会作业的严重影响。
- 我们已经建立了 TGS,并将他与docker和kubernetes整合。实验结果表明:
- TGS 对生产作业的吞吐量影响不大
- TGS 为机会作业提供了与最先进的应用层解决方案 AntMan 相似的吞吐量,并且与现有的 OS 层解决方案 MPS 相比提高了15倍的吞吐量。
引言
问题:
大型企业构建多租户 GPU 集群,许多团队共享这些集群来开发和训练 DL 模型。
在容器云中支持 DL 训练的一个常见实践是静态地将完整的 GPU 绑定到容器。
- 当一个 GPU 被分配给一个容器时,容器对 GPU 有独占访问权,这为生产作业提供了性能隔离。
- 但这意味着其他集装箱在同一台机器上不能使用 GPU 时,GPU 未得到充分利用,甚至完全空闲。
这种方法的主要限制是资源利用率低。
- 微软最近对一个生产型 GPU 集群的研究表明,GPU 的平均利用率只有52% [5]。
- 另一项针对阿里巴巴一个生产型图形处理器集群的测量显示,该集群的图形处理器利用率甚至更低ーー图形处理器利用率的中值不超过10% 。
- 然而,由于独占 GPU 分配,即使许多 GPU 没有得到充分利用,传入作业也必须在队列中等待调度。这会导致后续作业的作业完成时间较长。
解决方案:
- 这个问题可以通过 GPU 共享来解决,从而提高 GPU 的利用率。
- 在生产环境中,DNN 训练作业通常分为两类:
- 生产作业:其他作业不会对他造成太多的性能下降
- 机会作业:利用空余资源
- 在两类作业之间共享 GPU 以提高 GPU 利用率是很自然的。然而,对于生产环境来说,确保 GPU 共享对生产作业的影响最小化是至关重要的。
- GPU 共享解决方案可以在应用层或操作系统层实现。
- AntMan是最先进的应用程序层解决方案。虽然 AntMan 可以提供高 GPU 利用率和性能隔离,但它对 DL 框架进行了重大修改,并限制用户使用给定框架的特定版本。
- NVIDIA MPS是一个操作系统层的解决方案。MPS 需要应用程序知识来设置性能隔离的资源限制,并且不支持 GPU 内存超订情况下的 GPU 共享。它将多个流程合并到一个 CUDA 上下文中,从而导致作业之间的命运共享。
我们提出 TGS,一个系统,提供透明的 GPU 共享到集装箱云中的 DL 训练。
- 特点
- 与应用层解决方案不同,TGS 在操作系统层工作,并在操作系统层实现应用层解决方案的好处,而不受现有操作系统层解决方案的限制。
- 透明性允许用户选择任何 DL 框架的任何版本(TensorFlow、 PyTorch 或自定义框架)来开发模型并在容器中运行作业。
- 核心:TGS 的核心是容器和 GPU 之间的轻量级间接层。
- 它拦截从容器到 GPU 的系统调用,并调节并发作业的 GPU 资源使用。
- TGS 支持生产作业和机会作业之间的 GPU 共享,但是在很大程度上将生产作业与争用隔离开来。
- 在实现具有性能隔离的 OS 层 GPU 共享解决方案时,存在两个主要的技术挑战。
- 第一个挑战是在没有应用程序知识的情况下自适应地在容器之间共享 GPU 计算资源。
- 不正确地为每个容器设置资源限制会降低作业性能或使资源闲置。
- MPS 和 MIG 需要应用程序知识来手动设置资源限制。TGS 在没有应用知识的情况下应用自适应速率控制方法来解决这一挑战。
- 它在运行时监视生产作业的性能,并自适应地更新资源分配到机会作业。
- 控制回路自动收敛到这样一个点,即机会作业利用尽可能多的资源,而不会对生产作业产生太大影响。
- 第二个挑战是启用透明的 GPU 内存超订。
- GPU 有自己的内存来保持应用程序状态。当容器所需的 GPU 内存总量超过 GPU 内存大小时,MPS 失败。
- AntMan 使用 DL 框架中的自定义内存管理组件来管理应用层 GPU 内存和主机内存之间的内存交换。
- 设计了一种基于 CUDA 统一存储器的透明统一存储机制,实现了操作系统层的统一存储,避免了显式修改应用程序的需要。
- 这种机制在 GPU 内存超额预订时管理底层的内存交换。TGS 利用位置偏好来确保 GPU 内存优先用于生产作业,以保护其性能。
- 第一个挑战是在没有应用程序知识的情况下自适应地在容器之间共享 GPU 计算资源。
- 特点
总之,我们做出了以下贡献。
- 我们提出了 TGS 系统,为容器云中的 DL 训练提供透明的GPU共享。
- 我们设计自适应速率控制和透明的统一内存机制,以同时实现高 GPU 利用率和性能隔离。
- 我们实施 TGS,并与 Docker 和 Kubernetes 整合。实验表明:
- TGS 对生产作业的吞吐量影响不大
- TGS 为机会作业提供了与现有 OS 层解决方案 AntMan 相似的吞吐量,并且与现有 OS 层解决方案 MPS 相比提高了15倍的吞吐量。
背景和动机
容器云
容器被广泛用于管理资源和在数据中心部署工作负载,并提供可移植性和隔离性。
- 可移植性
- 容器是一个独立的软件包,包括运行应用程序所需的所有内容。
- 容器化的应用程序可以在各种环境中运行,而不需要进行任何修改。
- 这种可移植性使开发人员能够使用自己选择的工具和应用程序堆栈来开发和运行自己的应用程序,而无需担心部署环境。
- 隔离性
- 不同容器中的应用程序通过使用独立的命名空间进行隔离。
- 可移植性
与虚拟机相比,容器是轻量级的。
- 虚拟机使用guest操作系统,但容器使用主机操作系统核函数。
- 因此,应用程序可以在容器中运行时实现裸金属性能。
- 云运营商使用容器编排平台在数据中心的许多机器上提供、管理和更新容器。
DL训练负载
- 训练作业包含许多迭代。
- 每次迭代使用来自数据集的一批样本来训练 DNN 模型。
- 迭代包括向前传递和向后传递。
- 前向传递使用 DNN 模型来计算批中样品的标签。根据输出标签和实际标签使用损失函数计算损失。
- 反向传递将损失从 DNN 模型的最后一层传递到第一层,并计算每个权重的梯度。DNN 模型使用优化器基于梯度进行更新。
- DL 训练是计算密集型的,因此通常使用 GPU。然而,正如微软和阿里巴巴所报道的那样,广泛采用的独家图形处理器分配导致生产中的图形处理器利用率较低。
现存工作的局限性
提高 GPU 利用率的一种自然方法是 GPU 共享。
- 如果一个容器不能利用所有的 GPU 资源,那么可以通过多个容器共享一个 GPU 来提高 GPU 的利用率。
- 然而,共享 GPU 上的容器会争夺 GPU 的计算和内存资源,这种干扰会降低作业的速度。
GPU 共享可以在应用层或操作系统层完成。
- 应用层解决方案[6,12,13]
- 主要缺点是它们对用户不透明,也就是说,它们需要对 DL 框架进行重大修改。
- 用户只能使用给定框架的一组受支持的版本,如果某个特定 DL 框架的新版本出现,则必须等待集成。
- 这种方法失去了允许用户使用任何工具在容器中开发和运行应用程序的优势。
- OS 层解决方案
- NVIDIA MPS [9]是一个用于 GPU 共享的 OS 层解决方案。
- 它需要应用程序知识来正确设置每个进程的资源限制,以确保性能隔离。更重要的是,MPS 需要进程的总 GPU 内存来适应 GPU 内存容量,并依赖于应用程序来处理 GPU 内存和主机内存之间的内存交换。
- MPS 的另一个限制是它不提供故障隔离。MPS 将多个进程的 CUDA 上下文合并到一个 CUDA 上下文中以共享 GPU。当一个进程失败时,它会使 MPS 服务器和其他进程处于未定义的状态,并可能导致进程挂起、损坏或失败。
- NVIDIA 多实例 GPU (MIG)[14,15]是另一种 OS 层解决方案。
- MIG 需要 GPU 硬件支持,目前只能在三种高端 GPU 上使用
- NVIDIA A100
- NVIDIA A30
- NVIDIA H100。
- MIG 不能根据应用程序的需要任意地对 GPU 进行分区; 它只支持针对给定配置集的 GPU 分区。
- NVIDIA A100 GPU 可以划分为 GPU 实例,为不同的 DL 训练任务提供单独的计算和内存资源,MIG 只为每个 GPU 实例提供7种固定配置
- 每个 GPU 实例不能使用超过4/7的 GPU 计算资源或1/2的 GPU 内存资源。
- 此外,如果 GPU 上有正在运行的作业,即使容器的 GPU 使用发生了变化,它也不能动态更改 GPU 实例所拥有的 GPU 资源。MIG 的重构只能在 GPU 空闲时进行。
- MIG 不支持内存超订。
- MIG 需要 GPU 硬件支持,目前只能在三种高端 GPU 上使用
- NVIDIA MPS [9]是一个用于 GPU 共享的 OS 层解决方案。
- 应用层解决方案[6,12,13]
TGS 总览
- 目标
- 透明性:应该对应用程序透明,以便用户可以使用任何软件开发和训练集装箱 DNN 模型。
- 高利用率:应实现高 GPU 利用率的计算机和内存资源。
- 性能隔离:应该为 DL 作业提供性能隔离,生产作业不应该受到机会作业的显著影响。
- 错误隔离:一个容器中的应用程序的故障不应该导致其他容器中的应用程序崩溃。
- 架构
- TGS 是一种 OS 层解决方案:
- 透明:
- 它位于容器和 GPU 之间。容器和应用程序不知道 TGS。用户可以使用任何自定义框架来开发和训练 DNN 模型。
- 一个 GPU 作为一个普通的 GPU 暴露在容器中。容器中的进程将 GPU 核函数(即在 GPU 上执行的函数)发送给 GPU,就像它们对专用 GPU 所做的那样。
- 共享
- TGS 使用一个轻量级的间接层在几个容器的工作负载之间共享 GPU。间接层从容器中截取 GPU 核函数并调节这些 GPU 核函数以控制每个容器的资源使用。
- 透明:
- TGS 是一种 OS 层解决方案:
- 核心idea
- TGS 利用自适应速率控制机制和透明的统一内存机制解决了在操作系统层提供透明 GPU 共享的两个挑战。
- 第一个挑战是在没有应用程序知识的容器之间自适应地共享 GPU 计算资源。
- 为了应对这一挑战,TGS 的速率监控器监控每个容器的性能,并提供 CUDA 块(GPU 上的一个基本调度和执行单元)的数量作为控制回路的实时信号。
- TGS 的速率控制根据信号自适应地控制每个集装箱向 GPU 发送 GPU 核函数的速率。
- 控制回路自动收敛到机会作业利用尽可能多的剩余资源来实现高 GPU 利用率而不会严重影响生产作业的性能。
- 第二个挑战是启用透明的 GPU 内存超订。
- AntMan [6]修改了 DL 框架,以便在 GPU 内存超额预订时交换 GPU 内存。
- OS 层解决方案 MPS 不支持 GPU 内存超订,并且依赖于应用程序来处理内存交换。这些方法是不透明的。
- 为了解决这个问题,TGS 开发了 CUDA 统一存储器[1
- 它将 GPU 存储器和主机存储器统一到一个单独的存储器中的内存空间。
- TGS 拦截和重定向 GPU 内存分配调用从容器到 CUDA 统一内存空间。当 GPU 内存超额预订时,TGS 可以自动将一些机会作业的数据驱逐到主机内存中,并将相应的虚地址映射到主机内存中的新数据位置。
- 整个过程对应用程序是透明的。为了确保性能隔离,TGS 使用内存放置首选项来优先分配 GPU 内存用于生产作业,而不是机会作业。
- 第一个挑战是在没有应用程序知识的容器之间自适应地共享 GPU 计算资源。
- TGS 的设计还有两个好处。
- 首先,架构是轻量级的。TGS 开销小,符合容器原理。
- 其次,TGS 提供了与常规容器相同的故障隔离属性。
- TGS 中的容器使用单独的 GPU 上下文,而 MPS 将容器的 CUDA 上下文合并为一个。
- 因此,一个容器中的应用程序错误不会影响或终止其他容器。
- TGS 利用自适应速率控制机制和透明的统一内存机制解决了在操作系统层提供透明 GPU 共享的两个挑战。
TGS 设计
共享GPU计算资源
TGS的目标与挑战
- 确保
- 生产作业的表现不会受到机会作业的严重影响。
- 机会主义工作使用的资源不超过生产性工作留下的资源。
- 要做到这一点,我们需要解决两个问题。
- 首先,我们需要估计生产作业留下了多少资源。
- 其次,我们需要控制机会主义的工作,使用剩余的资源。
稻草人解决方案: 优先级调度。
- 过程
- 它从容器中截取 GPU 核函数,并根据作业的优先级将其放入生产队列和机会队列中。
- 只有当生产队列为空时,机会队列中的核函数才被调度到 GPU。
- 该解决方案通过检查生产队列是否为空来估计是否有剩余资源,通过对生产队列中的核函数进行优先排序来控制机会作业的资源使用。
- 这是一种性能隔离和高利用率的规范解决方案,已经在计算机系统中得到广泛应用。
- 但是,这种解决方案不适合 GPU 共享。
- GPU 作业的空生产队列并不意味着生产作业不使用 GPU。
- GPU 核函数是一个经过优化的 GPU 函数,可以运行一段时间。
- 过去调度的 GPU 核函数可能仍然在 GPU 上运行,而生产队列是空的。
- 空队列也不能告诉 GPU 上剩下多少资源。
- 因此,如果机会队列中的核函数被发送到 GPU,并且生产作业使用了大部分的 GPU 资源,那么来自两个作业的 GPU 核函数将会相互竞争,这将会给生产作业带来巨大的开销。
- 跟踪运行在 GPU 上的 GPU 核函数也是不可行的,因为 GPU 的状态是不完全可见的。
- GPU 作业的空生产队列并不意味着生产作业不使用 GPU。
- 在 GPU 设备驱动程序中实现一个优先级调度程序是有可能的
- 这样调度程序就可以完全看到资源的使用情况,并且可以执行细粒度控制。
- 这个解决方案并不通用。
- 它与低级别的 GPU 细节紧密相连,并且需要基于其架构和执行模型与每种类型的 GPU 进行深度集成。
- 有些 GPU 是黑盒,不向操作系统公开这种控制。
- 过程
我们的解决方案: 自适应速率控制。
TGS 使用自适应速率控制方法。
- 主要思想:根据核函数到达速率精确控制机会主义队列中核函数的出队速率,使得机会主义作业可以在不影响生产作业的情况下消耗剩余的计算资源
- 这是一种通用的 OSlayer 方法: 它与低级别的 GPU 细节分离,并且不需要访问 GPU 内部控制。
这种方法需要一个反馈信号来告诉控制回路是否可以增加机会队列的排队速率以使用更多的资源或应该减少以避免降低生产作业。
1)理想情况下,我们希望使用应用程序性能,
- 即 DL 训练工作负载的训练吞吐量作为反馈信号,因为这是我们最终关心的指标。
- 然而,我们不能直接获得训练的吞吐量,因为这需要应用知识,我们的目标是设计一个操作系统层的解决方案,是透明的应用程序。
2)信号的一种选择是 GPU 利用率,
- 也就是说,如果 GPU 利用率低于100% ,则提高利用率。
- 而这个选择看起来很自然,但有两个缺点。
- 首先,GPU 利用率的定义是特定于硬件的,并且常常是模糊的。
- 今天的图形处理器在单个芯片上包含不同类型的计算单元
- 例如,NVIDIA 图形处理器上不同数据类型的张量核心和 CUDA 核心。
- GPU 驱动程序报告的 GPU 利用率(如果支持的话)通常缺乏一个精确的定义。
- 即使是这样(例如,使用流处理器的百分比) ,对于具有多种计算单元类型的 GPU 来说,单个利用率值实际上意味着什么还不清楚。
- 今天的图形处理器在单个芯片上包含不同类型的计算单元
- 其次,GPU 利用率仅与应用程序性能松散耦合。
- 即使报告的 GPU 利用率低于100% ,这并不意味着我们可以在不减慢生产作业的情况下增加机会队列的排队速度。
- 例如
- 一个生产作业和一个机会作业可能会竞争已经被生产作业单独使用的同一类型的计算单元,尽管还有其他类型的计算单元处于闲置状态
- 两个作业也可能会竞争 GPU 利用率所捕获的资源之外的其他资源。
- 首先,GPU 利用率的定义是特定于硬件的,并且常常是模糊的。
3)在 TGS 中,我们使用生产作业的核函数到达率(即 TGS 从容器接收核函数的速率)作为反馈信号
- 计算图:
- 一个 DL 训练作业基于 DNN 模型为其训练过程构造一个计算图。
- 它使用计算机图形生成和发送核函数到 GPU 执行训练。
- 计算图捕获核函数之间的依赖关系。
- 核函数到达率直接对应于训练吞吐量。
- 如果训练速度变慢,则核函数完成速度变慢,依赖性得到满足的速度变慢,核函数到达率下降。
- 因此,TGS 采用速率监控模块来监控生产作业的核函数到达率,并以此作为反馈信号来控制机会作业的核函数出队率。
- 注意,生产作业和机会作业之间的任何争用都可以通过这个核函数到达率来捕获,包括
- GPU 缓存争用
- CPU 争用
- 网络争用
- 它们中的一些超出了 GPU 硬件设计可以控制的范围,TGS 使用速率控制作为旋钮来控制所有这些。
- 由于核到达率的方差很小,TGS 使用移动平均来平滑核到达率的估计。
- 对于来自生产作业的核函数
- TGS 只执行一个简单的计数操作来估计核函数到达率。
- 它不对核函数排队,而是直接将它们传递给 GPU,以最小化对生产作业性能的影响。
- 注意,生产作业和机会作业之间的任何争用都可以通过这个核函数到达率来捕获,包括
- 计算图:
速率自适应算法。
- 速率自适应算法控制机会队列的核出队速率,使生产作业的核到达率不受影响,使机会作业的核出队速率达到最大。
- 在形式上,让 αin 和 αout 分别表示生产作业的核到达和离开 TGS 的速率
- 而 βin 和 βout 表示机会作业的核到达和离开的速率
- TGS 只监视,但不限制生产作业的速度。所以 αin = αout。
- 设 GPU 不共享时生产作业的核函数到达率为 R。速率控制算法是将 β 输出最大化,使得 αin = R。
- 在公式中,βout 是由算法控制的变量,αin 依赖于 βout。
- 设 f 是捕捉 αin 和 βout 之间关系的函数,即 αin = f (βout)。
- 然后算法必须解决以下最佳化问题。
- F (βout)的确切形状是未知的,但我们知道它的粗略形状的性质的问题。
- 如图3(a)所示,f (βout)是平的,当 βout 小时等于 R,当 βout 大时单调递减。
- 直觉认为,当 β 输出较小时,图形处理单元没有得到充分利用,执行机会作业的核心不会影响生产作业的表现,从而导致生产作业的表现为平线;
- 当 β 引爆流行减小时,机会作业开始与生产作业竞争图形处理单元的资源,从而导致生产作业的表现下降。注意单调递减部分不一定是线性的
- 图3(a)说明了当 βout 增加时 αin 递减的一般趋势。这个算法的目标是找到 f (βout)开始减小的引爆流行 β * 。
- 图3(b)是 GPU 已经被生产作业充分利用的特殊情况
- 因此即使为机会作业执行少量核函数也会降低生产作业的性能。
- 在这种情况下,直线没有平面部分。
- 图3(c)是机会作业需求非常小的特殊情况,
- 因此即使排队速度不受限制,生产作业的性能也不受影响。
- 在这种情况下,直线没有单调递减部分。
- 如图3(a)所示,f (βout)是平的,当 βout 小时等于 R,当 βout 大时单调递减。
- 为了逼近最优的 β 输出,我们使用典型的加法增加乘法减少(AIMD)方法来控制比率 β 输出,如算法1所示。
- 具体来说,TGS 首先测量 GPU 上生产作业的 R 速率,
- 然后再向 GPU 添加一个用于共享的机会作业(第1-3行)。
- 加入机会作业后
- 如果 α 大于或等于 R (第24-行) ,则 TGS 加大 βout
- 如果 αin 低于 R (第29-30行), 则乘法减少 βout。
- AIMD 确保 β 输出可以近似收敛为临界点β*。
- 为了加速收敛,采用了慢启动阶段(第17-22行)。
- 实验结果表明,该算法收敛速度快。
- 当生产作业更改其资源使用模式时,TGS 检测到 R 的方差超过了阈值。
- 在这种情况下,速率控制模块暂停机会作业并测量新的 R (第26-28行)。
- 当 R 变得稳定时,速率控制模块使用 AIMD 来调整临界点。
- 为了保证自适应码率控制算法在大多数情况下的收敛性,我们给出了以下定理。
- 为了加速收敛,采用了慢启动阶段(第17-22行)。
- 具体来说,TGS 首先测量 GPU 上生产作业的 R 速率,
- 速率自适应算法控制机会队列的核出队速率,使生产作业的核到达率不受影响,使机会作业的核出队速率达到最大。
定理1假设 DL 作业在剖析阶段和收敛阶段是稳定的,自适应速率控制算法在 O (B log B)函数调用中收敛,其中 B 是 GPU 中作业的吞吐量限制。
该定理的证明见附录 A。证明是基于深度学习训练工作量的稳定性。对于熟悉计算机网络拥塞控制的读者来说,我们的问题类似于多个流争夺共享链接的带宽资源时的带宽分配问题。在带宽分配中,每个流都使用一个拥塞控制算法来控制自己的速率,在系统收敛之后,每个流都会获得相当份额的链路带宽。我们的问题与带宽分配的微妙区别在于,我们没有限制生产作业的速率,只是控制机会作业的速率,以确保生产作业的性能不会受到资源共享的很大影响。
共享GPU内存资源
稻草人解决方案: 直通式分配。
- 草人的解决方案是直接将 GPU 内存分配调用从容器传递给 GPU。
- 好处:只要容器有足够的需求,GPU 内存就能得到充分利用。
- 限制:
- 这个解决方案的主要局限性在于它对生产作业有很大的开销。
- 在这个解决方案中,当生产作业不使用所有的 GPU 内存时,机会作业可以获得剩余的内存。
- 稍后,如果生产作业想要分配更多的 GPU 内存,它们将无法因为剩余的内存已经分配给机会作业。
- 如果没有足够的 GPU 内存,生产作业可能会以较低的速度运行,甚至失败,这违反了故障隔离。
- 此解决方案的另一个限制是它没有考虑到 DL 框架的特征。
- 当启动一个作业时,一些 DL 框架(例如 TensorFlow)会占用所有可用的 GPU 内存,即使训练作业不需要那么多内存。
- 这些 DL 框架通常有一个缓存所有已分配内存的内存池,并根据需要将内存分配给训练作业
- 当某些内存没有被使用时,它们不会释放并将分配的内存返回给 GPU。
- 这是这些 DL 框架中的一种优化,以避免在作业期间频繁调用 GPU 内存来分配和释放的开销。
- 这种优化给共享 GPU 内存带来了挑战。
- 像 AntMan [6]这样的应用层解决方案可以直接修改 DL 框架来获取训练作业的内存使用情况,并禁用不必要的内存缓存来将未使用的 GPU 内存返回给 GPU。
- 但是,为了设计透明的 OS 层解决方案,不允许修改 DL 框架或应用程序。
- 当启动一个作业时,一些 DL 框架(例如 TensorFlow)会占用所有可用的 GPU 内存,即使训练作业不需要那么多内存。
- 这个解决方案的主要局限性在于它对生产作业有很大的开销。
- 草人的解决方案是直接将 GPU 内存分配调用从容器传递给 GPU。
我们的解决方案: 统一的 GPU 和主机内存。
- 现代 GPU 提供了一种称为统一存储器的功能,它将 GPU 存储器和主机存储器统一在一个地址空间中。
- 统一内存传统上被应用程序用来简化 GPU 内存管理。
- TGS 统一应用 CUDA为了实现 GPU 内存共享的透明性和性能隔离,采用 CUDA 统一内存分配作为 GPU 内存分配的间接方式。
- 具体来说,TGS 将 CUDA 统一内存作为伪 GPU 内存公开给容器。
- 当一个容器发出 GPU 内存分配调用时,无论该调用是针对普通 GPU 内存还是 CUDA 统一内存,TGS 都会拦截该调用并在 CUDA 统一内存空间中分配该调用所请求的内存。
- 当生产作业没有用完 GPU 内存时,机会作业可以获得剩余的 GPU 内存。
- 伪 GPU 内存是指所分配的内存对容器和应用程序来说似乎是普通的 GPU 内存
- 而实际上它可以来自 GPU 内存,也可以来自主机内存,具体取决于可用性。
- 注意,我们不更改虚拟内存系统。
- 伪内存仍然是虚拟内存,应用程序使用虚拟内存地址访问分配的伪内存。
- GPU/主机虚拟内存地址由 GPU/主机内存管理单元转换为 GPU/主机物理内存地址。
- 而实际上它可以来自 GPU 内存,也可以来自主机内存,具体取决于可用性。
- TGS 中的透明统一存储器与原来的 CUDA 统一存储器有两个不同之处,
- 一是性能隔离
- 为了提供性能隔离,
- TGS 使用 CUDA 统一存储器中的位置偏好来优先分配 GPU 存储器到生产作业。
- 当 GPU 内存不满时,来自任何作业的内存分配请求将获得 GPU 内存。
- 当 GPU 内存满时,TGS 尝试将生产作业块放置在 GPU 内存中,并在必要时将机会作业块驱逐到主机。
- 这对容器是透明的
- 因为容器仍然使用相同的虚拟内存地址来访问它们分配的内存空间。虚拟内存地址被转换为不同位置的物理内存地址。
- 这种机制也不会引入额外的内存不足(OOM)故障,因为从 DL 训练作业的角度来看,GPU 的内存容量与原始 GPU 的内存容量相同。
- TGS 使用 CUDA 统一存储器中的位置偏好来优先分配 GPU 存储器到生产作业。
- 为了提供性能隔离,
- 二是 GPU 存储器的透明超订
- 一是性能隔离
- TGS 中的透明统一内存还解决了在现有 DL 框架中过度占用 GPU 内存的问题,而不需要修改 DL 框架。
- 当 DL 框架要求所有可用的 GPU 内存时,TGS 从 CUDA 统一内存空间中分配请求的内存量。
- 实际使用的内存将触发 GPU 页面错误,并在首次使用时交换到 GPU 内存,然后将驻留在 GPU 内存中。
- 因此,只有训练作业积极使用的部分在 GPU 内存中; 其余部分在主机内存中。
- 这允许机会作业有效地共享 GPU 内存。
- 当 DL 框架要求所有可用的 GPU 内存时,TGS 从 CUDA 统一内存空间中分配请求的内存量。
- 现代 GPU 提供了一种称为统一存储器的功能,它将 GPU 存储器和主机存储器统一在一个地址空间中。
TGS 实现
我们已经用 C + + 和 Python 实现了 TGS 的3000行代码的系统原型,并将其与 Docker 和 Kubernetes 集成。
- 协调进程负责资源管理,并利用 TGS 的间接层实现容器之间的 GPU 共享。
- 具体来说,采用 TGS 提供的自适应速率控制和透明统一存储器实现 GPU 共享。
自适应速率控制。
- TGS 从容器中拦截与 CUDA 核函数启动相关的 CUDA 驱动程序 API 调用,用于速率监控和速率控制。
- 由于 CUDA 核函数启动可能由容器中的多个线程引发,TGS 使用一个全局计数器来记录在给定时间段内启动的 CUDA 块的数量。
- CUDA 块是一组必须在同一 SM (流式多处理器)中执行的线程,不同的 CUDA 块可以独立并行运行。
- 由于 CUDA 驱动程序 API 调用中指定了核函数包含的 CUDA 块的数量,因此悬而未决的 CUDA 块的数量可以作为一个实时信号来估计生产作业的性能。
- 对于生产容器,一个独立的线程充当速率监视器,它定期读取 TGS 的这个计数器,并将值发送到同一 GPU 上机会容器的速率控制组件。
- 对于机会容器,当 CUDA 驱动程序开始工作时,将创建一个速率控制线程。
- 速率控制线程根据接收到的统计信息调整机会容器的速率限制。
- 为了使机会容器的核函数启动率保持在一个合适的值,所有 CUDA 核函数启动 API 调用都会首先重定向到速率控制组件。
- 速率控制组件访问由速率监视器生成的统计信息,以检查速率限制是否得到满足,并且如果机会容器的速率超过速率限制,则推迟核函数启动。
统一内存管理。
- 为了实现透明的内存共享,TGS 拦截与 GPU 内存分配相关的 CUDA 驱动程序 API 调用,比如 cuMemAlloc,并使用 cuMemAllocManaged 将这些调用替换为统一的内存分配调用。
- 我们使用 cuMemAdvise 对生产容器的 GPU 内存分配进行优先级排序。
- 具体来说,我们使用 cuMemAdvise 将内存分配的首选位置设置为当前 GPU,以避免对生产容器进行清除。
- 当生产容器完成时,机会容器中的间接层将使用 CUDA 驱动程序 API cuMemPrefetchAsync 透明地预取位于主机内存中的内存。
实验
自适应比率控制
统一内存管理
Job流混合负载
系统开销
收敛性
支持不同的DL框架
与AntMan的比较
- 在这个实验中,我们将 TGS 与 AntMan [6]进行了比较
- AntMan [6]是一个用于 GPU 共享的最先进的应用层解决方案。
- AntMan 与 DL 框架紧密耦合,并使用应用层度量(迭代时间)来控制机会主义的工作。
- AntMan 的开源 GitHub 存储库功能不全。它不包括将资源动态分配给作业的逻辑。我们联系了 AntMan 的作者,并按照他们的说明添加必要的代码以运行 AntMan。
- 图11a 和11b 分别显示了低竞争(批量4的 ShuffleNet 和批量4的 MobileNet)和高竞争场景(批量8的 ResNet-50和批量4的 ShuffleNet)下的比较。
- 尽管 AntMan 使用应用层知识并控制应用层的作业,TGS 仍然实现了与 AntMan 类似的性能。
- TGS 模式下生产作业的吞吐量比 AntMan 模式下高104.1% ~ 104.3%
- 采用 TGS 模式的机会作业的吞吐量比 AntMan 模式下高103% ~ 122%
- 与 AntMan 相比,TGS 提供了 GPU 共享的同样好处,并且对 DL 框架是透明的。
- 尽管 AntMan 使用应用层知识并控制应用层的作业,TGS 仍然实现了与 AntMan 类似的性能。