Kubernetes集群上深度学习负载优化①

2022-09-20 04:10段国栋王德奎王文潇孙辽东荆荣讯邢良占刘慧兴姬贵阳
计算机系统应用 2022年9期
关键词:存储系统集群调度

陈 培, 王 超, 段国栋, 王德奎, 王 斌, 王文潇, 孙辽东, 荆荣讯,邢良占, 刘慧兴, 姬贵阳

(浪潮电子信息产业股份有限公司, 济南 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]、数据集亲和性的优化调度技术. 本文提出的优化方案覆盖了数据处理、计算等方面, 以上技术极大简化人工智能负载在规模化云原生平台上的部署难度和提高运行效率,同时从实践上来看也验证以上技术对人工智能应用有着显著的提升作用.

1 基于缓存机制的数据读取加速技术

云原生上部署深度学习应用时, 会遇到对接不同底层存储的情况, 诸多实验表明, 不同存储系统对深度学习训练性能是有着不同程度的影响. 训练开始时, 存储系统的性能会极大的影响训练数据的加载和后续训练数据的持续读取, 如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技术应用示意图

2 vCUDA-GPU虚拟化技术

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上运行的进程信息.

2.1 Device plugin

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复用场景下的资源管理.

2.2 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适用场景说明

3 基于AI训练特点深度优化的调度策略

3.1 节点内NUMA亲和性调度

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都分配资源.

3.2 模块交互

本文提出的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分配状态信息的生成、更新和删除接口.

3.3 针对AI训练的Kubernetes集群数据集亲和性调度

在集群上运行大规模数据集训练时, 会遇到如网络带宽的限制影响数据集读取速率、训练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所需要的全部数据集, 则得分为最高值. 如果出现多个节点的数据集亲和性得分相同且都为最高分, 调度器可参考其它优选策略进行优选.

4 实验与分析

文中提到的vCUDA、NUMA、数据集亲和性调度策略已经在具体的生产环境中得到验证, 以下测试将具体展示其性能效果.

4.1 vCUDA实验

分别采用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 单任务训练对比结果

4.2 NUMA亲和性实验

为验证同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使用.

4.3 数据集缓存和亲和性调度实验

针对数据集缓存和亲和性调度采用海量小文件的训练场景, 即使用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的测试结果

实际应用环境中配合使用数据集调度策略可以充分利用大规模集群中节点上已缓存的数据资源, 进而提高训练性能和集群计算资源的利用率.

5 结束语

本文针对在Kubernetes集群上部署深度学习应用所遇到的一些问题, 对数据、计算方面提出了一系列优化方案和设计, 并结合实际场景进行了测试, 整体上达到了预期效果. 其中数据集缓存和亲和性调度能够极大的减少由于存储系统和网络环境限制带来的数据读取速率慢问题; vCUDA技术能够解决部分场景下的GPU共享要求, 并且利用UM机制扩大显存使用;另外NUMA亲和性调度和针对海量小文件的优化技巧在实际测试和生产场景中均能够提供可观的整体性能提升.

猜你喜欢
存储系统集群调度
齐口裂腹鱼集群行为对流态的响应
水资源平衡调度在农田水利工程中的应用
智能四向穿梭车系统的应用与调度对策研究
10kV配网调度运行故障及控制对策
天河超算存储系统在美创佳绩
面向4K/8K的到来 存储该怎么办?
勤快又呆萌的集群机器人