一个云计算平台主要解决三大类问题:计算、存储和网络。在计算方面,爱奇艺大量使用虚拟化计算和容器云计算。相比于虚拟化技术,容器更加简单便捷,不需要额外的硬件支持,近乎无损的提供GPU计算能力,可以快速的提供不同软件环境,启动时间短,成为首选的GPU计算承载方式。
训练任务场景
我们的第一次尝试从训练任务容器化开始。一个典型的训练任务和普通数据处理任务类似:训练程序设置参数,从数据源读取数据,使用框架提供的API进行计算,输出保存点和最终模型,同时也会输出日志和一些事件信息。
训练任务容器化demo很快就完成了,我们拿着demo去和算法同事沟通:
A:“首先需要把训练框架和代码做成一个Docker镜像,然后登录到我们容器云平台,启动容器……”
B:“什么是Docker/容器?可以直接跑训练任务吗?”算法同事问。
A:“我们两个星期后要上线,什么时候可以给训练任务服务?”
B:”有没有一个方案可以让算法用户在不了解容器的情况下使用训练服务?最好两个星期内做出来!“
PS:这个时候发现我们忽略了最终用户的基础情况:算法工程师专注与算法,关心平台的易用性和实际训练时间。通常没有兴趣了解容器、存储等基础服务,对计算性能几乎没有概念。
Runonce训练服务
两个星期后,我们开始测试了Runonce训练服务。Runonce的最初设计目的是,让用户像使用虚拟机一样使用容器。Runonce底层设施是基于mesos容器云环境。计算资源已容器方式的提供,所有容器使用统一镜像。为了保存用户数据,容器使用了Ceph RBD网络块存储设备作为RootFS。容器启动点是一个sshd服务,作为用户的登录入口。我们提供了特殊的文件注入工具帮助用户管理ssh key。
Runonce训练服务功能简单,开发量小,短时间完成上线。Runonce服务的用户体验没有发生根本新的变化,可以很快上手。但是,缺点也很明显。用户对软件环境完全控制,常常出现错误修改系统文件引发的问题。这些问题很难跟踪和调查,带来很高的运维开销。用户是在使用shell来交互操作资源,通常会进行大量代码修改和调试,实际使用GPU时间不多,最终体现是整体GPU利用率较低。由于块存储设备不能进行共享,RUNONCE不支持数据集共享,也不能支持并行多任务训练,更加不支持分布式训练。
因为Runonce服务存在无法修复的缺陷,我们决定开发一个新的训练平台。通过大量观察用户使用Runonce服务行为,我们总结出典型使用步骤:数据集上传,编写训练代码,执行训练任务,获取训练结果。这些步骤后面的核心是数据,而支持数据读取的存储方案是最关键的方案。
储存选择
如果需要进行数据共享,那么网络存储是最好的选择。在对网络存储选择过程中,我们关注下面这些设计和指标:健壮性,伸缩性,吞吐量,并发,延迟。由于多数训练框架都支持文件系统接口,所以提供文件接口是存储的必须功能。综合各个因素后,Jarvis使用的首选存储平台是Ceph对象存储(RWG)。Ceph对象存储有很好的并发性能,可以很容易的进行水平扩展,但对象储存提供的接口是基于HTTP的S3接口,需要进行文件API的封装。
Jarvis存储
Jarvis在S3上采用fuse文件系统,用于支持文件读写。S3 fuse对读操作很友好,但是在写功能上有很多限制,例如:不支持随机写,任何文件修改都需要重新上传整个文件。Jarvis使用本地文件作为缓存,然后定期同步到Ceph对象存储。因为同步会造成大量重复写操作,Jarvis对写操作做了划分,一类是写日志这样的小文件操作,一类是模型同步等的大文件操作。小文件使用较短的同步周期,大文件使用较长的同步周期。
Jarvis运行环境
在Jarvis上,训练任务容器使用的是定制的镜像,软件环境是精确定义。镜像启动脚本将训练数据通过S3 fuse接入到容器内部,然后拉取gitlab代码,执行用户训练任务,同时将输出数据同步到对象存储上。这里我们做一个小小的优化,软件环境划分为基础镜像和运行时脚本镜像。在基础镜像上,注入运行时脚本镜像内容。每次修改运行脚本,可以不必更新基础镜像,大大加快了开发效率。
但是Jarvis很大程度的改变了用户使用方法,用户普遍反应使用更不方便。我们在调试和状态查询上下了很大的功夫,提供了实时日志展现网页,同时还提供Tensorboard这样的图形化状态查看的功能。Tensorboard功能强大,但是内存消耗也比较大。于是,我们开发了轻量化指标收集器,将指标直接投递到数据后台,由数据后台界面来进行展现。
在Jarvis平台运行一段时间之后,我们没有观察到GPU利用率显著提升。这样结果让我们很惊讶。我们发现,GPU利用率低的主要原因是,大多数任务没有对优化数据读取。这个问题在网络存储环境下会更加突出。
存储优化
网络存储有以下这些特点:
· 高吞吐:读写速度很高
· 高并发:可以支持很高的并发读写
· 高延迟:发去请求到拿到第一个字节的时间相对较长
· 掉队现象:某些请求可能会过很长时间才返回
在网络存储环境下,我们需要增加处理数据读取和处理的并发数,尽量避免使用小文件,放宽读取数据顺序的限制。
如果训练数据文件都是比较小的图片,应该合并若干小文件为一个tfrecord的大文件,降低请求次数。目前,tensorflow在并发数据读取处理支持做好,建议阅读 Data Input Pipeline Performance。
申请容器时,用户应该注意CPU资源的数量。如果CPU申请较少,数据处理速度慢,GPU处于等待数据空闲状态。
同时,还应该做好数据的预处理。我们遇到一个案例,使用纯文本的输入数据,大量CPU时间花费在字符串到数字转换上。在将文本数据处理成二级制数据之后,训练任务几乎跑满了GPU计算能力。
深度学习技术发展迅速,大量变革正在发生。很多端到端的平台正在开发,例如Kubeflow,OpenPAI。这些平台都想把深度学习做成完整的闭环功能,让算法工程师专注于数据和算法。要实现这样的功能,我们还有很长的路要走。