「深度解析」AI训练之数据缓存

深度学习或者AI的出现,改变了咱们以往的解决问题的编程方式,再也不是代码上直观的表达。算法

举一个简单的例子,咱们如何识别一个数字(图片)是数字9呢?很是直观的方法就是上面有一小圆圈,下面有一个竖线。可是若是写的倾斜了一些呢?若是上面的圈没有闭合呢?若是竖线弯曲了呢?感受咱们平常的程序判断(switch)没法收敛,咱们只能用一种可以自我演进的方式来认识这个“看起来像9”的数字,而这也正是咱们大脑的学习行为,咱们第一个看到这个数字的时候,被告知这是9,那么图片就有了一个标签;下次再看到相似的,仍是属于标签9,见多识广,最后见到一个也许写得更加不像的咱们也可以识别出是9,这个过程正是由咱们大脑中的上千亿的神经细胞长时间的学习结果。编程

人类大脑的真正运行方式,依旧是神秘所在,但从这个过程当中咱们发展出了神经网络算法,能够从已有的知识中进行学习。勤能补拙,既然算法不如人脑,就经过学习大量的资料来加快学习的进程。MNIST 数据有 60,000张手写数字图片,ImageNet 数据有接近1500万张图片,Youtube-8M的视频数据有数TB,Google的 Open Image dataset, 仅仅是在 Open Images Challenge中使用的数据集就达到了18TB。后端

AI中有三大核心:算法,算力,数据(存储)。 算法自有成熟的框架,由数学科学家去解决;计算能力由CPU甚至GPU去解决。面对如此大量的数据,一台机器的内存、硬盘去承载基本不太可能,而对于CPU/GPU计算能力强悍的组件,频繁的去远端获取数据等待IO又是资源的浪费。有没有既能知足数据距离计算近、又能承载大量数据的方案呢?缓存是银弹!后面的主要篇幅从论文解析的角度来逐步阐述,论文来自Fast20 Quiver: An Informed Storage Cache for Deep Learning缓存

后续的讨论中,有个比较重要的概念,就是mini-batch, 若是没有实战的经历过,不是很容易理解这个概念。安全

深度学习的优化算法,本质就是梯度降低。每次的参数更新有两种方式。服务器

第一种,遍历所有数据集计算一次损失函数,而后计算函数对各个参数的梯度,更新梯度。这种方法每更新一次参数都要把数据集里的全部的样本数据遍历一遍,计算量开销大,计算速度慢,不支持在线学习,这称为Batch gradient descent(BGD),批梯度降低。网络

另外一种,每训练一个数据就算一下损失函数,而后求梯度更新参数,这个称为随机梯度降低,stochastic gradient descent。这个方法速度比较快,可是收敛性能不太好,可能在最优势附近波动,达不到最优势。两次参数的更新也有可能互相抵消掉,形成目标函数震荡的比较剧烈。框架

为了克服两种方法的缺点,如今通常采用的是一种折中手段,mini-batch gradient decent,小批的梯度降低,这种方法把数据分为若干个批,按批来更新参数,这样,一个批中的一组数据共同决定了本次梯度的方向,降低起来就不容易跑偏,减小了随机性。分布式

用一个示意图表示以下:函数

蓝色:为 batch 梯度降低,即 mini batch size = m,紫色:为 stochastic 梯度降低,即 mini batch size = 1,绿色:为 mini batch 梯度降低,即 1 < mini batch size < m。

如下图为例,执行了3轮训练(epoch),每轮里面定义mini-bach size=5, 其中数据集为1-20个数字,咱们看到经过torch.DataLoader, 每次得到了5个数据(batch x)。

01 深度学习训练的基本知识

深度学习训练任务(Deep Learning Training DLT)会将训练数据做为输入,从千丝万缕的线索中经过学习并获得一个输出模型来表明训练数据。

为了实现训练,DLT会使用一个较小的随机样本(mini-batch,一般是32到512个),并利用SGD来慢慢的学习各类参数进而提升准确率。

训练数据:一般咱们能够认为是一个列表,列表中的每个元素都是一个二元组<input,label>, input多是一张图片或者一段语音,而label则表明着input的语义,而这也正是深度学习网络所须要学习的并可以正确区分input的目标。例如ImageNet的所有数据集大概有150万张图片,每张图片在200KB左右。

为了可以以随机方式访问训练数据,DLT框架会使用索引序列来遍历数据。假设训练数据有100万个文件,那么会维护一个包含每个文件索引的列表,并对它进行随机的排列,随后根据mini-batch的数据量向后端存储得到数据,当所有的数据都完整遍历训练一次,一个epoch完成。对于下一个epoch, 再次对索引进行随机排列,重复上面的过程。一个典型的DLT任务会运行不少轮训练,例如50-200。

数据转换:从存储得到原始数据会被DLT框架进行转换,例如彩色图片变成黑白图片,同时将图片转换为像素数矩阵等等。固然这部分工做一般由CPU来完成。

多任务:由于DLT任务是一个试错的过程,因此实际运行过程当中,用户老是会使用不一样的参数来同时运行不一样的任务,全部的这些任务都会访问相同的完整数据集,不一样的就是以不一样的随机顺序来进行访问。

02 深度学习的IO特色

咱们从DLT任务I/O访问的角度看来列举一下它的主要特色:

可共享性:在DLT训练任务中,不管是一个训练任务自身,仍是多个训练任务之间,都存在很大程度的I/O重叠性。在一个任务内,它会针对同一个数据集进行屡次的遍历(例如多个epoch),因此若是可以在第一个epoch的时候就对数据进行缓存,会大幅提高后续训练的效率。更重要的是,这种共享性甚至能够扩展到多任务之间,例如针对同一份训练数据集,配置不一样的参数,利用同一个训练模型运行多个不一样的任务。这些任务可能运行在不一样的机器上,可是访问的都是相同的底层数据集。

随机访问:因为数据的可共享性,这使得DLT具备很是的缓存友好性,但只有在所有数据可以被完整缓存的状况下才有效果,不然,DLT随机访问数据的方式又使得部分数据缓存很容易被穿透。例如只可以缓存20%的数据,那么这些数据立刻就会被后续的随机访问刷掉。

部分数据缓存对于DLT来讲很重要,由于训练数据一般已经足够大,而且会愈来愈多,例如前文提到过即便只是ImageNet这样的百万级规模的数据集,整体也已经达到了数TB的大小。

可替换性:从I/O的角度来讲,一个训练任务(epoch)主要关注如下两点便可:a)每个训练数据必须且仅被访问一次;b)而对于每次的mini-batch,必须是随机的序列。有趣的是,一组数据的精确顺序并不会对训练任务的准确或者精确性产生影响,这就意味着I/O是能够被替换的。对于特定若干文件的访问,DTL任务能够替换为一组其余的随机的且没有被访问过的数据。从缓存的角度来讲,这是一个独特的特性来提高缓存的命中率,即便缓存只能承载20%的数据,也能够在访问一个不存在于缓存中的数据,经过替换的方式返回一个存在的内容,同时并无破坏随机以及惟一性的训练要求。

可预测性:由于每个mini-batch的运行时间,能够事先得到,这样就能够用于评估一个训练任务对I/O性能的敏感性,进而能够进行策略调整以可以使那么I/O敏感的任务从缓存获益。

03 缓存的设计

总结起来深度学习的特色:

  1. 须要的数据量大
  2. 多台机器多个训练并行
  3. 每一个训练要运行屡次
  4. 在每次训练中,全部的数据须要遍历一遍
  5. 针对不一样的训练参数,以及在不一样的机器上运行的训练任务,数据集相对保持固定

针对以上的特色,当咱们考虑缓存的时候,不由会有以下的疑问:缓存毕竟容量有限,穿透如何处理?缓存的过时置换策略是如何的?当不一样的用户访问不一样的数据,安全性如何保证?等等。

Quiver、分布式缓存,经过与DLT框架深度整合,缓存客户端集成到训练任务的IO过程当中,进而为缓存服务端提供更多的策略信息。

系统结构

以公有云虚拟机环境举例,每个GPU VM带有一个本地SSD硬盘,每个DLT job会运行在本身的容器内,这样即便是多用户运行,也是在一个隔离的环境内。

每一个用户的数据存储在各自帐号的云存储内,这样保证了隐私以及访问权限。经过分布式缓存,即便训练任务因为调度等缘由在各个宿主之间切换,缓存数据依旧是可以提升训练效率。

数据安全

Quiver的设计是一个共享式的分布缓存,不管是不一样的任务,仍是不一样的用户之间,在共享的模式下如何保证数据的安全就是一个重要因素。Quiver保证了用户只能看到他有权限访问的数据,但这样彷佛又与缓存的重用产生了冲突。若是针对某一个数据集,例如ImageNet, 两个不一样用户分别在各自的存储帐号内各自保存了一份,那么逻辑上来说,缓存要分别为每一个用户各自缓存一份。这将致使缓存的效率下降,Quiver经过内容寻址(content-addressed)的方式来解决重用与隔离的问题。

内容寻址缓存

对于缓存,基本的行为就是经过一个<key, value>的映射关系,在咱们经过key查询时,可以快速的返回所对应的value。在Quiver中,缓存并非利用文件名以及偏移量在做为缓存的关键字,而是利用缓存内容的hashes。缓存内容的粒度是由具体的DLT任务决定的,多是一张图片,不管是对它的插入仍是寻址,都以它的hash(例如SHA1)来惟必定位。用hash来定位的好处是对于一个相同内容的文件,无论它来自于何处以及文件名是否相同,在缓存中都仅须要保留一份便可,这样也就可以达到即便在不一样的用户之间也可以共享目的。

同时为了保证数据的隔离性,Quiver利用摘要索引来访问训练数据,对于每一份数据,摘要索引将包含<content_hash: file_location>, 所以,在多用户拥有相同内容的数据集时,由于数据是存在在各自的存储系统内,每一个用户将拥有不一样的file_location,可是全部的content_hash是相同的。

缓存服务器

利用本地SSD做为介质的KV存储,经过一致性Hash的方式将key space分布在多个缓存服务器上。

缓存管理(Cache Manager)

因为Quiver是分布式缓存,那么针对全部的缓存服务器,缓存的插入、清理须要一个协调者Cache manager。

Cache manager同时会评估每个计算任务从缓存的受益状况,主要经过让缓存服务器针对训练任务所须要的若干mini-batch数据作cache misses, 而后与其余的缓存命中的训练人耗时机型对比,进而对缓存进行优先级调整。

缓存客户端

缓存客户端做为训练任务的一部分,经过干预DLT框架,例如PyTorch等的接口层来访问训练数据。在PyTorch中,DataSet会用来遍历全部的训练数据,而且内部维护一个随机的文件索引列表,其中Next的接口就能够用来得到下一个mini-batch数据。Quiver经过调整这个接口,利用一个摘要文件,当上层访问一组文件时,它会先对缓存进行数据的访问。

客户端会将训练任务的一些信息反馈给Cache Manager,例如每个mini-batch的训练时间,Cache Manager能够据此来优化缓存的策略。

替换命中率

在常规的缓存中,若是一个mini-batch包含了512个文件,那么Dataset会提供512个文件索引用来从后端存储得到文件内容,若是这其中只有部分缓存命中,那么将依然存在远程的I/O操做。在Quiver中,会从Cache中加载更多的(例如10倍的mini-batch数量)数据,而只要其中有512个数据可以被命中,那么就返回给上层训练任务,这样训练任务就不会被Cache miss阻塞。同时Quiver会标记Cache miss的数据为pending状态,周而复始,直到数据被遍历了一遍,这时将重头来过仅仅去关注以前pending的数据。

假设目前只有10%的数据在缓存中,为了简单起见,咱们能够认为就是连续的原始数据的10%, 由于DLT任务会随机的查找数据,因此每个长度为k的mini-batch序列,缓存的命中率应该为k/10, 所以若是咱们查找一个长度为10*k的序列,那么正好可以命中得到mini-batch所须要的数据。当下一轮查找pending数据的时候,另外的10%的数据可能已经在缓存中了,这也意味着可以1/9的命中率。须要注意的是,在多个任务的训练中,这依旧适用,所以多个训练任务尽管每一个都访问随机的训练数据,从总体来看,他们能够作到以全缓存命中的方式来运行。

训练准确性

因为上述的I/O可替换性,咱们有理由怀疑最终训练结果的准确性。这里借用原文的数据来讲明。

04 缓存的管理

在以前的描述中,当只有部分数据被缓存时,Quiver会在一个epoch的训练过程当中,再次遍历文件索引。为了能在这后续的遍历中得到更好的命中率,另外一部分数据必须被pre-fetch到缓存中。

Quiver经过缓存整个数据集的2个chunks来解决这个问题。首先数据集的摘要索引文件会被分红固定数据的chunks,例如每一个chunk包含10%的数据,同时每一个chunk表明着striped partition。例如咱们定义数据集中连续的10%为一个partition, 每一个partition被分红10个stripe units. 这样每一个chunk将包含全部的partition中的一个unit。这样当训练任务操做第一个chunk的过程当中,第二个chunk将被加载到缓存中,因此当部分训练任务完成第一次遍历开始第二次的时候,数据已经在缓存内,训练任务以递进的方式运行。

这里面潜在的一个问题就是何时将第一个chunk置换出去。若是置换太快,部分任务尚未完成将致使缓存失效,若是保留太长时间,那么第三个chunk将没法加载进来。在Quiver中,当第二个chunk被加载到缓存后,第一个chunk会被标记为能够清除,同时新的任务能够从第二个chunk中得到命中的数据。原有的任务依旧利用第一个chunk来运行,当全部的任务都已经遍历了第一个chunk数据,这部分数据才会真的从缓存中清除,同时第三部分数据将开始加载。

在上述的过程当中,若是某一个训练任务相比于其余的要慢不少,那么将致使前一个chunk迟迟不能释放,一般来讲,在同一个训练模型的多个任务中,每一个任务的训练时间基本是相同的,但没法避免在多个不一样的训练模型训练同一个数据集的场景。不过若是一个任务明显的耗时很长,那么将意味着每个mini-batch在GPU上的训练时间都很长,也就是它对I/O的性能没那么敏感,因此缓存的不命中并不会影响多少这个训练的效率,所以 Quiver会设定一个临界值来强制第一个chunk失效并清除。

05 缓存效果

论文做者经过以下的配置环境来进行效果的对比,从实际数据来看,训练性能确实有较大的提升。

Timeline of Mini-batches

吞吐提高

06 结论

深度学习场景中,更多的注意力放在了提升计算以及网络的性能上,而对于存储,则是利用现有的方案来解决,例如提早手动将数据加载到离GPU较近的SSD上。论文做者经过Quiver,提供了自动化的手段来消除存储的瓶颈。固然没法避免对训练框架的侵入性,但也正是由于如此,Quiver才能感知到训练I/O的特色,进而达到即便缓存只能承载部分数据也能够大幅调高缓存利用率的效果。

相关文章
相关标签/搜索