迭代间隙的性能优化
该阶段反映的是每个迭代开始时等待训练数据的时间。
造成迭代间隙耗时长的原因可能有:数据处理慢、或计算资源分配不合理,造成进程在等待数据
所以首先可通过优化数据处理来优化迭代间隙;也可优化计算资源分配策略来优化迭代间隙
下面将介绍几种优化迭代间隙的方法。
优化数据来优化性能
借助自动数据加速工具来优化性能
MindSpore提供了一种自动数据调优的工具——Dataset AutoTune,用于在训练过程中根据环境资源的情况自动调整数据处理管道的并行度,最大化利用系统资源加速数据处理管道的处理速度。
启动自动数据加速方法、自动数据加速工具的约束及使用样例可参考:自动数据加速
优化batch操作
在数据处理的最后阶段,会使用batch操作将多条数据组织成一个batch,然后再传递给网络用于训练。
对于batch操作的性能优化可参考:数据batch性能优化
优化数据增强
数据增强性能优化建议如下:
优先使用MindSpore提供的数据增强操作,能获得更好的性能,如果性能仍无法满足需求,可采取如下方式进行优化:
1、多线程优化:增大map接口的参数num_parallel_workers(默认值:8)来取得更好的性能。
2、融合算子优化:在当前CPU占用率比较高时,使用融合操作来降低CPU占用会获得更好性能,可以通过配置环境变量 export OPTIMIZE=true 来使其生效。
3、Compose优化:在当前CPU占用率比较高时(如:单机多卡训练),通过一个map操作接收多个增强操作(会按照顺序应用这些操作)来降低CPU降低竞争以取得更好性能
具体可参考:数据增强性能优化
优化shuffle操作
shuffle操作主要是对有序的数据集或者进行过repeat的数据集进行混洗。MindSpore专门为用户提供了shuffle函数,它是基于内存缓存实现的,其中设定的buffer_size参数越大,混洗程度越大,但内存空间、时间消耗也会更大。
shuffle性能优化建议如下:
1、直接使用数据集加载接口中的shuffle=True参数进行数据的混洗;
2、如果使用的是shuffle函数,当混洗效果无法满足需求,可通过调大buffer_size参数的值来优化混洗效果;当机器内存占用率过高时,可通过调小buffer_size参数的值来降低内存占用率。
具体可参考:数据shuffle性能优化
合理分配计算资源来优化性能
数据处理主要在Host端进行,运行环境的配置也会对处理性能产生影响。所以合理的CPU计算资源分配能加快数据处理速度来优化性能。
- 计算资源分配
使用numactl --cpubind=0 python train.py,来实现为某个进程绑卡。
- CPU频率设置
使用cpupower frequency-set -g performance,将CPU运行模式设置为性能模式。
- 多线程竞争
数据处理使用cv2,numpy,numba三方库,且top指令查看出现sy占用高但us占用低时,需要解决线程竞争的问题。具体可参考:CPU计算资源之多线程竞争
- CPU、内存占用率高
当数据预处理出现CPU占用高或内存占用高时,需要降低CPU及内存占用率。详细可参考:解决CPU及内存占用率高问题
优化并行度来优化性能
并行度主要需要权衡num_parallel_workers和CPU资源关系。
在使用MindSpore进行单卡或多卡训练时,num_parallel_workers参数的设置应遵循以下原则:
-
各数据加载和处理操作所设置的num_parallel_workers参数之和应不大于CPU所支持的最大线程数,否则将造成各个操作间的资源竞争。
-
在设置num_parallel_workers参数之前,建议先使用MindSpore的Profiler(性能分析)工具分析训练中各个操作的性能情况,将更多的资源分配给性能较差的操作,即设置更大的num_parallel_workers,使得各个操作之间的吞吐达到平衡,避免不必要的等待。
-
在单卡训练场景中,提高num_parallel_workers参数往往能直接提高处理性能,但在多卡场景下,由于CPU竞争加剧,一味地提高num_parallel_workers可能会导致性能劣化,需要在实际训练中尝试使用折中数值。
具体设置原则可参考:并行度优化建议
多线程并发来优化性能
-
增大数据集接口的参数num_parallel_workers(默认值:1)来提升并发度;
-
将数据集接口的参数python_multiprocessing设置为True(默认值)/False来启动多进程模式/多线程模式,多进程模式适用于cpu计算密集型任务,多线程适用于IO密集型任务;
注意:如果配置 python_multiprocessing=True(默认值:True)和 num_parallel_workers>1(默认值:1)表示启动了多进程方式进行数据load加速,此时随着数据集迭代,子进程的内存占用会逐渐增加,主要是因为自定义数据集的子进程以 Copy-On-Write 的方式获取主进程中的成员变量。举例:如果自定义数据集 init 函数中包含大量成员变量数据(例如:在数据集构建时加载了一个非常大的文件名列表)并且使用了多进程方式,那这可能会导致产生OOM的问题(总内存的预估使用量是:(子进程数量 + 1) * 父进程的内存大小)。最简单的解决方法是成员变量用非引用数据类型 (如:Pandas、Numpy或PyArrow对象)替换Python对象(如:list / dict / int / float / string等),或者加载更少的元数据以减小成员变量,或者配置 python_multiprocessing=False 使用多线程方式。
- 如果有Using shared memory queue, but rowsize is larger than allocated memory …日志提示,那么将数据集接口的参数max_rowsize(默认值:6M)按日志提示进行增大来提升进程间数据传递的效率。
具体可参考:多线程并发方案
开启数据下沉来优化性能
每个训练迭代都需要从Host拷贝数据到Device,可通过开启数据下沉消除Host和Device间拷贝输入数据的开销。
数据下沉开启方式有以下两种:
- model.train实现数据下沉
在model.train函数中增加dataset_sink_mode=True
参数,如
model.train(epoch=10, train_dataset=ds_train, callbacks=cb, dataset_sink_mode=True, sink_size=10)
具体可参考:model.train实现数据下沉
- data_sink实现数据下沉
使用data_sink接口将模型的执行函数和数据集绑定,实现数据下沉。
sink_process = ms.data_sink(train_step, ds_train, sink_size=data_size, jit_config=jit)
具体可参考:data_sink实现数据下沉