AI学习者 · 2021年03月03日

AI框架中数据处理的挑战与解决思路

文章转载于:知乎
作者:金雪锋

本文是AI框架分析专栏的第五篇,总体目录参见:

AI框架的演进趋势和MindSpore的构想:

金雪锋:AI框架的演进趋势和MindSpore的构想

数据处理其实在AI的训练和推理中占了很大的比重,但是业界在这一块的分析比较少,本文期望通过MindSpore实践给大家一些参考。

AI框架中的数据处理

在构建深度学习模型时,数据处理是我们最先面临的挑战。任务开始之前,由于数据量受限,或者为了得到更好的结果,通常需要进行数据增强操作,来获得能使网络受益的数据输入。典型的训练数据处理流程如下图所示:

加载:指从各种异构存储中将训练数据加载到内存中,加载时涉及数据的访问、解析等处理;

Shuffle:训练一般是多个epoch,通过shuffle打乱数据集不同epoch的数据排序,防止训练过拟合。如果数据集支持随机访问,则只需按不同顺序随机选择数据就可以非常有效地进行混洗shuffle。如果数据集不支持随机访问(或仅部分随机访问像多个文件对象),那么一个子集的数据可以加载到一个特殊的混洗缓冲区shuffle buffer中。

map:完成训练数据的处理,包括图像类数据增强、Text类分词等处理。其中,数据增强是一种创造有着不同方向的“新”数据的方法,一是从有限数据中生成“更多数据”,二是防止过拟合。

batch:训练时一般都是使用mini-batch的方式,即一个批次训练少量数据;batch算子负责构造一个批次的数据发送给训练;

repeat:可以通过repeat的方式增加训练的总数据量;一次repeat就是加载一遍整个训练集。

模型在进行推理时,同样会涉及到数据处理,典型的过程如下图所示:

**

**

推理过程的数据处理,将一张图片进行解码、缩放、中心截图、归一化、通道变换等操作后,送入模型中进行推理并得到结果。与训练相比,推理时的数据转换基本一致,不同的是推理时一般加载单样本进行处理,而非数据集。

本文将重点分析AI框架在数据处理时面临的挑战以及MindSpore的解决思路。

难点与挑战

2.1 数据处理的高效性

当前各AI框架的数据处理主要利用CPU运算,训练则利用GPU/AI芯片,两者是并行的。理想情况下,应该在每轮迭代开始前,就准备好完成增强之后的数据,保持训练过程持续地进行。然而在实际的训练中,很多时候数据处理成为了阻碍性能提升的瓶颈:或是因为从存储中读取数据的速度不足(I/O bound),或是因为数据增强操作效率过低(CPU bound)。

根据黄氏定律,GPU/AI芯片的算力每一年会提升一倍,相比于即将失效的摩尔定律,AI芯片的算力提升速度会远大于CPU。模型迭代计算效率的不断提升,对数据处理也提出了更高的要求:数据处理过程必须足够高效,才能够避免GPU/AI芯片因为等待训练数据而空闲。

2.2 数据处理的灵活性

数据处理的灵活性挑战主要体现在以下两个方面:

  • 数据集种类繁多,难以统一

目前已知常用的开源数据集有几百种,每种由不同的组织/机构来产生,有各自的格式与组织方式。根据深度学习任务的不同,每种类型的输入也有自身的特点。以图像为例,其包含类型、长、宽、大小等属性信息,每张训练图像被标记成某一个类别,这些图像及其对应类别的列表数据被用来进行分类等训练;以音频为例,通过训练可以直接将语音转换为文字,进一步作为智能AI的输入,完成语义理解及指令性操作。

在数据集加载时,如何支持种类繁多的图像、文本、音频、视频格式,屏蔽IO差异并将其映射到内存结构中进行下一步处理,是AI框架重点需要解决的问题。

  • 数据增强算法非常灵活,需要框架提供足够易用的接口来支持用户定制数据处理过程

例如CV类场景,图像作为网络的输入,需要保证一定的一致性(如:大小、通道数、归一化等),也需要有一定的泛化能力(如:镜像、旋转、混合、颜色变换等),才能使训练得到的模型具有更好的精度。研究已经证明,采用不同的数据处理逻辑,训练得到的模型精度会有明显的不同。

以resnet50和ssd为例,数据处理过程对比如下:

其中既涉及到经典的数据处理逻辑,又包含了用户自定义的处理过程。AutoML和动态shape等场景,也对数据处理的灵活性提出了更高要求。所以,提供更多、更灵活的数据处理机制,也是框架需要重点考虑的。

2.3 端云统一

  • 训练导出的模型,在推理时如何高效进行数据处理:

网络训练生成的模型文件中,记录了训练时的计算图及权重信息,但是数据处理过程往往没有统一存储到模型中,这就导致AI工程师在使用模型进行推理时,需要重新编写数据处理代码,十分不便。

  • 端侧资源受限,需要提供更轻量化的数据处理方式:

在端侧场景下,CPU和内存资源往往比较少,在提供数据处理的能力时,要求API尽可能简单、轻便,以最少的资源占用获得最快的执行效率。同时,需要AI框架提供的库尽可能的小。所以,使用云化场景提供的数据处理机制(启动慢、资源占用大)就不是特别适用。如何在不同的场景下提供最合适的数据处理方法是AI框架面临的挑战。

典型的云侧场景与端侧场景资源对比如下:

资源

云侧

端侧

CPU

100+核

4核-8核

内存

250G-750G

3G-8G

Device

GPU/Ascend910

CPU/Ascend310

MindSpore设计思考

3.1 设计目标与思路

MindSpore的设计中,充分考虑了数据处理的高效性、灵活性以及在不同场景下的适配性。整个数据处理子系统分为以下模块:

API:数据处理过程在MindSpore中以图的形式表示,称为“数据图”。MindSpore对外提供Python API来定义数据图,内部实现图优化和图执行。

整个数据加载和预处理的流程实现为多步并行流水线(data processing pipeline),包括:

Adaptor:将上层语言(如Python)构建的数据图,转换为下层可执行的C++数据图(Execution tree)

Optimizer:数据图优化器

Runtime:运行优化后Execution tree的执行引擎

数据集算子(dataset operators):Execution tree中的某个节点,对应数据处理流水线中的一步具体操作,比如从文件夹加载训练数据的ImageFolder算子,做各种数据增强的Map算子,Repeat算子等。

数据增强算子(data augmentation operators):也可称为tensor算子,是对某个tensor变换的,比如Decode,Resize,Crop,Pad等,通常是被dataset operator中的Map算子调用。

数据增强后的结果,通过队列和前向反向计算系统相连。下面将介绍这套子系统如何达到极致的数据处理性能。

3.2 极致的处理性能

  • 多段pipeline流水线:

相比于业界其他框架,MindSpore采用了多段并行流水线(multi-stage parallel pipeline)的方式来构建数据处理pipeline。这种架构一方面可以更加细粒度地规划CPU等计算资源的使用,另一方面天然支持各段使用异构硬件进行流水处理,从而提高数据处理过程的吞吐量。如上图所示,每个数据集算子(inline除外)都包含一个输出Connector:由一组阻塞队列和计数器组成的保序缓冲队列。每当一个数据集算子完成一块缓存数据的处理,这个算子会将这块缓存推送到自身的输出Connector。下游的数据集算子会从上游的输出Connector里取出缓存进行后续处理。这种机制的优势包括:

  1. 数据集加载、map、batch等操作以任务调度机制来驱动,每个操作的任务互相独立,上下文通过Connector来实现Pipeline;
  2. 每个操作均可以实现细粒度的多线程/多进程并行加速。数据框架为用户提供调整算子线程数/多进程处理的接口,可以灵活控制各个节点的处理速度,进而实现整个数据处理Pipeline性能最优;
  3. Connector支持用户对其大小进行设置,在一定程度上可以有效的控制内存的使用率,适用于不同网络对于数据处理速度的要求。

在这种数据处理机制下,对输出数据进行保序处理是保证训练精度的关键。保序意味数据处理流水线运行时,同样顺序的原始数据输入,需要保证数据处理完成后,得到同样顺序的数据输出。MindSpore采用轮询算法来保证多个线程数据处理的有序性:

在上面的数据处理pipeline示例中,保序操作发生在下游map操作(4并发)的取出操作(单线程执行)中,其以轮询的方式取出上游队列中的数据。Connector内部有两个计数器expect\_consumer\_记录已经有多少个consumer从queues\_中取了数据,pop\_from\_记录了哪个内部阻塞队列将要进行下一次取出。expect\_consumer\_以consumer个数取余,而pop\_from\_以producer个数取余。在expect\_consumer\_再次为0时,说明所有的local\_queues\_已经都处理上一批任务,然后继续下一批任务分配及处理,进而实现了上游至下游map操作的多并发保序处理。

  • 数据处理与计算过程pipeline

数据pipeline会不断地进行数据处理,并把处理后的数据发送到device侧的缓存;当一个step执行结束后,会直接从device的缓存中读取下一个step的数据。

数据处理:负责将数据集处理成网络需要的输入,并传递给发送队列中,保证数据处理的高效取可;

发送队列Queue:维护数据列队,保证数据处理与网络计算过程互不影响,实现桥梁的作用;

网络计算:从发送队列中获取数据,用于迭代训练。

以上三者各司其职,相互独立,构筑整个训练过程中Pipeline。这样,只要数据队列不为空,模型训练就不会因为等待训练数据而产生瓶颈。

  • 缓存技术

当数据集的size较大时,无法全部加载到memory cache,此时训练中的部分数据需要从磁盘读取,可能会遇到I/O瓶颈,增大每个epoch的cache命中率就显得尤为关键。传统的缓存管理策略采用LRU策略,没有考虑深度学习数据的读取特点:在不同的epoch之间数据是重复读取的,而在同一个epoch中随机读取。每条数据的读取概率都是相同的,因此哪个数据被缓存并不是最重要的,已经cache的数据在被使用之前不被换出更加重要。针对这个特点,我们使用了一个简单高效的缓存算法:数据一旦被缓存,就不会从cache中被换出。

在数据图优化的过程中,会根据流水线结构自动生成缓存算子,既可以缓存原始数据集,也可以缓存数据增强处理后的结果。

3.3 灵活的定制能力

整个数据处理pipeline中,用户往往需要特定的处理逻辑,这些处理逻辑有其特殊性,无法固化到框架中。因此,框架需要具备开放的能力,让用户能够订制不同的数据处理逻辑。MindSpore提供了灵活的数据集加载方式、丰富的数据处理算子、自动数据增强、数据动态Shape、数据处理Callback等机制等供开发人员在各种场景使用。

  • 灵活的数据集加载方式

针对数据集种类繁多、格式与组织各异的难题,MindSpore提供了三种不同的数据集加载方式:

1)如果用户使用常用数据集,那么可以使用MindSpore内置的API接口直接进行加载。MindSpore实现了包括CelebADataset、Cifar10Dataset、CocoDataset、ImageFolderDataset、MnistDataset、VOCDataset等数据集的C++ IO Reader加载,在保证性能的同时,实现了数据集的开箱即用。

2)用户将数据集转换为MindSpore数据格式,即MindRecord,然后通过MindSpore的API进行加载。MindRecord可以将不同的数据集格式归一化,有聚合存储、高效读取、快速编解码、灵活控制分区大小等多种优势。

3)如果用户已经有自己数据集的Python读取类,那么可以使用MindSpore的GeneratorDataset API调用该类实现数据集加载。这种方式可以快速集成已有代码,改动最小,但因为是Python IO Reader,需要额外关注数据加载性能。

  • 通过Python层自定义、C层插件的方式支持更多算子

MindSpore内置了丰富的数据处理算子。这些算子可以分为C层以及Python层,C层算子能提供较高的执行性能;Python层算子可以很方便集成第三方包完成数据处理的功能,但是性能较低,好处是易开发易使用。MindSpore支持用户扩展自定义的数据处理算子,用户可以开发C层算子代码,编译后以插件的形式注册到MindSpore的数据处理中进行调用。

  • 支持自动数据增强策略

MindSpore提供了基于特定策略自动对图像进行数据增强处理的机制:通过基于概率或者回调参数的数据增强策略,可以实现算子自动选择执行,达到训练精度提升的目的。

例如对 ImageNet 数据集,自动数据增强最终搜索出的方案包含 25 个子策略组合,每个子策略包含两种变换,针对每幅图像都随机的挑选一个子策略组合,然后以一定的概率来决定是否执行子策略中的每种变换。

这些策略包括:

RandomSelectSubpolicy - 多批概率算子,随机选择其中一批执行

RandomChoice - 多个算子选择其中一个执行

RandomApply -基于某个概率执行这批算子

**

**

通过自动数据增强操作,在ImageNet数据集上可以提升1%左右的训练精度。

  • 支持动态shape

MindSpore通过 per\_batch\_map支持用户自定义控制输出不同Shape的训练数据,满足网络需要基于不同场景调整数据Shape的诉求。

  1. 用户自定义数据Shape生成逻辑udf,如:第n个step生成shape为(x,y,z,...)的数据;
  2. 通过batch(…, per\_batch\_map=udf)将第一步定义的逻辑生效,最终得到不同Shape训练数据。
  • Callback机制让数据处理更加灵活

通过callback机制实现根据训练结果动态调整数据增强的逻辑,为数据增强过程提供更灵活的操作。

MindSpore支持用户基于数据处理提供的DSCallback(包含epoch开始、step开始、step结束、epoch结束等)实现自己的数据增强逻辑UDF,并将其添加至map操作中,以实现更灵活的数据增强操作。

3.4 端云统一架构

  • 数据图与计算图的统一

MindIR是MindSpore基于图表示的函数式IR,其最核心的目的是服务于自动微分变换。自动微分采用的是基于函数式编程框架的变换方法,因此IR采用了接近于ANF函数式的语义。

推理数据图典型的场景为:数据集样本大小缩放、中间截图、归一化、通道变换。

我们将推理数据图以子图的方式保存至生成的模型文件(MindIR)中,那么在推理时,可以通过统一的接口加载模型中数据处理流程,自动进行数据处理得到模型需要的输入数据,简化用户的操作,提升易用性。

  • 轻量化的数据处理

云侧训练时,可以被使用的资源往往比较充裕,数据处理Pipeline在运行过程中是会占用比较多系统资源(CPU和内存),以ImageNet为例,训练过程中CPU占用20%,内存占用30-50G,这显然在端侧是不可被接收的,并且数据处理Pipeline在初始化时启动会慢些,这也不适用于端侧需要快速启动、多次训练/推理的前提条件。故:MindSpore基于现有数据处理算子,提供一套更轻量化、更适用于端侧的API,解决云化场景数据处理Pipeline不适用于端侧的问题。

MindSpore基于Pipeline调整架构,支持数据处理单算子独立使用(Eager Mode),支持各种场景推理,提供给AI开发人员更大的灵活性;同时,将Pipeline轻量化,实现基于Pull Base的轻量化pipeline,减少资源占用并且处理速度快。

通过上述两种方法,MindSpore保证了统一的数据处理架构支撑多种不同的应用场景。

未来计划

在未来,MindSpore将继续完善数据处理的易用性,提供更丰富的算子和内置数据集。与此同时,探索大规模数据处理的加速技术,包括:

  • 资源自适应分配

当前各数据增强算子使用的处理线程数目由用户手工配置,对用户的调优经验要求极高。通过自适应判断Pipeline瓶颈,由框架给各个数据增强算子合理分配CPU资源,可以在训练过程中动态优化数据处理性能,免去用户繁琐的调优过程。

  • 异构硬件加速

当前的数据处理Pipeline操作在CPU执行,一旦出现瓶颈,带来AI芯片/GPU等待空闲,用户无法充分利用所有硬件的计算能力。MindSpore期望构建用户无感知的异构硬件资源调度能力:通过监测硬件资源使用,完善Ascend/GPU上的数据处理算子,采用代价模型自适应地将数据处理任务调度至合适的资源,实现异构硬件的充分利用。

  • 分布式缓存

当前如GPT-3等网络的训练需要使用超大规模数据集,这些数据难以在本地存储,直接从OBS读取会受网络IO限制而影响性能。与此同时,在AutoML场景下,用户经常在集群中运行同一类模型的多个作业(只是超参设置不同),每个作业独立进行数据加载和处理效率极低。MindSpore期望构建分布式缓存能力,加速这些场景下的数据处理。

推荐阅读

更多嵌入式AI技术干货请关注嵌入式AI专栏。
推荐阅读
关注数
16740
内容数
1233
嵌入式端AI,包括AI算法在推理框架Tengine,MNN,NCNN,PaddlePaddle及相关芯片上的实现。欢迎加入微信交流群,微信号:aijishu20(备注:嵌入式)
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息