当前位置:首页 > 民俗文化

显存不足怎么解决 模型训练太慢?显存不够?这个方法让你的GPU联手CPU

随着深度学习模型的复杂性和数据集规模的增加,计算效率已经成为一个不可忽视的问题。GPU凭借其强大的并行计算能力,成为深度学习加速的标准。然而,由于服务器的视频内存有限,随着训练样本越来越大,视频内存甚至不能容纳一个样本的现象经常发生。除了升级硬件(烧钱)和使用分布式训练(费力)外,你还知道其他方法吗?即使视频内存足够,在GPU上执行所有操作是否效率最高?只要掌握以下小知识,模特培训的所有问题都可以迎刃而解,省时省力省钱,重在高效!

其实CPU和GPU是协同工作的。如果能合理利用各自的优势,可以节省视频内存资源(视频内存不够凑),甚至获得更好的训练性能。本文为你提供了device_guard接口,只用一行命令就可以实现GPU和CPU的混合训练。它不仅可以通过调整批量大小来解决训练模型时视频内存仍然超出的问题,还可以训练无法在单个服务器上执行的模型。同时,本文还给出了一种提高GPU和CPU混合训练效率的方法,充分利用服务器资源帮助你提高模型的性能!

模型训练的特点

深度学习任务通常使用GPU进行模型训练。这是因为GPU的算术逻辑单元(alu)比CPU多,可以利用并行计算的优势,特别适合计算密集型任务,可以更高效地完成深度学习模型的训练。GPU模式下的模型训练如图1所示,可分为四个步骤:

第一步:将输入数据从系统内存复制到视频内存。

第二步:CPU指令GPU处理数据。

第三步:GPU并行完成一系列计算。

第四步:将计算结果从视频内存复制到内存。

▲模型训练示意图

从图中可以看出,GPU虽然具有优秀的并行计算能力,但不能单独工作,必须由CPU控制和调用;而且视频内存和内存之间频繁的数据复制也可能带来更大的性能开销。CPU的计算能力虽然不如GPU,但可以独立工作,直接访问内存数据完成计算。因此,为了获得更好的训练性能,需要合理利用GPU和CPU的优势。

模特训练中的常见问题

问题1: GPU内存已满,资源不足

你建立的模型是好的,在这个简洁的任务中它可能会成为一个新的SOTA,但是每次你试图批量处理更多的样本时,你会得到一个CUDA RuntimeError:内存不足。

这是因为GPU卡的图形内存非常有限,一般比系统内存低很多。以V100为例,最高内存只有32G,甚至有的内存也只有12G左右。因此,当模型参数较大时,可能无法在GPU模式下训练模型。

为模型训练设置CPU模式可以避免视频内存不足的问题,但是训练速度往往太慢。

那么有没有办法在单机训练中充分利用GPU和CPU资源,让一部分层在CPU上执行,一部分层在GPU上执行?

问题二:数据复制频繁,训练效率低

视频内存足够的时候可以直接用GPU模式训练模型,但是让所有的网络层都运行在GPU上是不是最高效的?其实GPU只是针对特定任务比较快,而CPU擅长各种复杂的逻辑运算。在框架中,有些操作会默认在CPU上执行,或者有些操作的输出会存储在CPU上,因为这些输出往往需要在CPU上访问。这样会导致训练时CPU和GPU之间的数据复制。

图2是CPU和GPU之间数据传输的示意图。假设模型中间层有四个算子。其中操作符a和操作符b都在CPU中执行,所以b可以直接使用a的输出..运算符c和运算符d都是在GPU上执行的,所以运算符d也可以直接使用c的输出。但是,在执行操作符B之后,它的输出就在CPU上了。当执行操作符C时,B的输出会从CPU复制到GPU。

频繁的数据复制也会影响模型的整体性能。如果可以设置A、B两个操作符在GPU上执行,或者设置C、D两个操作符在CPU上执行,避免数据传输,可能会提高模型性能。那么应该如何更合理的给操作人员分配设备,让培训过程更高效呢?我们需要在充分发挥GPU和CPU各自计算优势的前提下,更全面的考虑,减少数据复制带来的时间消耗。

▲图2 CPU和GPU数据传输示意图

Device_guard自定义GPU和CPU混合训练

上述两种情况都希望为模型中的某些层指定运行设备。飞桨提供液体。CUDAPlace和fluid。用于指定运行设备的CPUPlace,但是这两个接口要么是在指定设备时选择的,也就是说是在GPU模式或者CPU模式下训练的。过去,我们无法指定计算的某个部分应该在GPU还是CPU上执行。flying oar开源框架从1.8版开始就提供了device_guard接口。有了这个接口,可以将网络中的计算层指定为CPU或GPU,实现更加灵活的异构计算调度。

如何使用device_guard接口解决以上两种场景中提到的问题?接下来我们来看具体的例子。

好处1:充分利用CPU资源,避免超出视频内存

如果通过使用流体来指定全局运行设备。CUDAPlace,飞桨会自动分配支持GPU计算的OP在GPU上执行。但当模型参数过大,视频内存有限时,很有可能会超出视频内存。如下例代码所示,嵌入层的参数大小包含两个元素,第一个元素是vocab_size(词汇大小),第二个元素是emb_size(嵌入层维度)。实际情况下,词汇量可能会很大。在示例代码中,词汇的大小设置为10,000,000,由该层创建的权重矩阵的大小为(10,000,000,150)。只有这一层需要占用5.59G的视频内存。如果加入其他网络层,很有可能在这个大词汇量的场景中,内存会被超过。

输入桨。流体为流体

data = fluid . layers . fill _ constant(shape =[1],value= 128,dtype= 'int64 ')

label = fluid . layers . fill _ constant(shape =[1,150],value= 0.5,dtype= 'float32 ')

emb = fluid . embing(input = data,size=( 10000000,150),dtype= 'float32 ')

out = fluid . layers . L2 _ normalize(x = emb,轴= -1)

cost =流体. layers . square _ error _ cost(input = out,label=label)

avg _ cost = fluid . layers . mean(cost)

SGD _ optimizer = fluid . optimizer . SGD(learning _ rate = 0.001)

SGD _ optimizer . minimum(avg _ cost)

place =流体。CUDAPlace( 0)

exe =流体。执行者(地点)

exe . run(fluid . default _ startup _ program)

result = exe . run(fluid . default _ main _ program,fetch_list=[avg_cost])

嵌入是指根据输入中的id信息,从嵌入矩阵中查询对应的嵌入信息。不是运算密度很高的OP,所以CPU上的运算速度是可以接受的。如果嵌入式层设置在CPU上运行,可以充分利用CPU内存大的优势,避免超出视频内存。可以参考下面的代码,使用device_guard在CPU上设置嵌入层。然后,除嵌入层之外的所有层都将在GPU上运行。

输入桨。流体为流体

data = fluid . layers . fill _ constant(shape =[1],value= 128,dtype= 'int64 ')

label = fluid . layers . fill _ constant(shape =[1,150],value= 0.5,dtype= 'float32 ')

用液体。device _ guard(“CPU”):#一行命令,指定运行设备的网络层是CPU

emb = fluid . embing(input = data,size=( 10000000,150),dtype= 'float32 ')

out = fluid . layers . L2 _ normalize(x = emb,轴= -1)

cost =流体. layers . square _ error _ cost(input = out,label=label)

avg _ cost = fluid . layers . mean(cost)

SGD _ optimizer = fluid . optimizer . SGD(learning _ rate = 0.001)

SGD _ optimizer . minimum(avg _ cost)

place =流体。CUDAPlace( 0)

exe =流体。执行者(地点)

exe . run(fluid . default _ startup _ program)

result = exe . run(fluid . default _ main _ program,fetch_list=[avg_cost])

所以在视频内存有限的情况下,可以参考上面的例子,在CPU上设置一些计算密度低的网络层,避免超出视频内存。

好处二:合理设置操作设备,减少数据传输

如果在GPU模式下训练模型,想提高训练速度,可以看看模型中是否有一些不必要的数据传输。在文章的开头,我们提到在CPU和GPU之间复制数据是很耗时的,所以如果能够避免这种情况,就有可能提高模型的性能。

在接下来的内容中,我们将教你如何通过profile工具分析数据传输开销,以及如何使用device_guard避免不必要的数据传输,从而提高模型性能。一般过程如下:

首先使用 profile 工具对模型进行分析,查看是否存在 GpuMemcpySync 的调用耗时。若存在,则进一步分析发生数据传输的原因。通过 Profiling Report 找到发生 GpuMemcpySync 的 OP。如果需要,可以通过打印 log,找到 GpuMemcpySync 发生的具体位置。尝试使用 device_guard 设置部分 OP 的运行设备,来减少 GpuMemcpySync 的调用。最后比较修改前后模型的 Profiling Report,或者其他用来衡量性能的指标,确认修改后是否带来了性能提升。

步骤1使用配置文件工具确认数据传输是否发生

首先需要分析模型中CPU和GPU之间是否有数据传输。在OP执行过程中,如果输入张量所在的设备与OP执行的设备不同时,输入张量会自动从CPU复制到GPU,或者从GPU复制到CPU。这个过程是同步数据复制,通常很耗时。下面的14行示例代码是用profile设置的,我们可以使用profile工具看到模型的性能数据。

输入桨。流体为流体

importpaddle.fluid.compiler作为编译器

import paddle . fluid . profiler as profiler

data 1 = fluid . layers . fill _ constant(shape =[1,3,8,8],value= 0.5,dtype= 'float32 ')

data 2 = fluid . layers . fill _ constant(shape =[1,3,5,5],value= 0.5,dtype= 'float32 ')

shape = fluid.layers.shape(data2)

shape = fluid.layers.slice(shape,坐标轴=[ 0],起点=[ 0],终点=[ 4])

out = fluid . layers . crop _ tensor(data 1,shape=shape)

place =流体。CUDAPlace( 0)

exe =流体。执行者(地点)

exe . run(fluid . default _ startup _ program)

compiled_prog =编译器。compiled program(fluid . default _ main _ program)

withprofiler.profiler( 'All ',' total ')作为配置文件:

fori inrange( 10):

result = exe . run(Prog = compiled _ Prog,fetch_list=[out])

上述程序运行后,将自动打印以下分析报告。可以看到GpuMemCpy Summary给出了两次数据传输的调用时间。如果GpuMemcpySync存在于GpuMemCpy Summary中,则意味着您的模型中存在同步的数据副本。

进一步分析,我们可以看到在slice和crop_tensor执行中发生了GpuMemcpySync。看看网络的定义,我们可以发现,虽然我们在程序中设置了GPU模式运行,但是shape OP的输出结果存储在CPU上,导致在GPU上执行的切片使用这个结果时,数据从CPU复制到GPU。切片的输出结果存储在GPU上,而crop_tensor使用的参数默认是从CPU中取数据,因此发生了另一次数据复制。

->;分析报告& lt-

注意!该报告将所有线程信息合并为一个。

地点:所有

时间单位:毫秒

在同一线程中按排序顺序按总时间排序

总时间:26.6328

计算时间总计:13.3133比例:49.9884%

框架间接费用合计:13.3195比率:50.0116%

- GpuMemCpy摘要-

GpuMemcpy呼叫:30总计:1.47508比率:5.5386%

GpuMemcpyAsync呼叫:10总计:0.443514比率:1.66529%

GpuMemcpySync呼叫:20总计:1.03157比率:3.87331%

-事件摘要-

事件调用总CPU时间(比率)GPU时间(比率)最小值。最大值平均比率。

fasthreadedssagrafixecutorprepare 109.164939.152509(0.998645)0.012417(0.001355)0.0251928 . 2000000004

形状108.330578.330568(1.000000)0.000000(0.000000)0.0307117 . 9998490 . 9999999991

fill _ constant 204 . 060974 . 024522(0.991025)0.036449(0.008975)0.0750870 . 88889590 . 8888888886

切片101.780331.750439(0.983212)0.029888(0.016788)0.1485030 . 1485030 . 1485030301

GpuMemcpySync:CPU->;GPU 100.455240.446312(0.980388)0.008928(0.019612)0.0390890 . 0390890 . 0390899991

crop _ tensor 101.676581.620542(0.966578)0.056034(0.033422)0.1439060 . 2587760 . 2587760606

GPumemcpysync:GPU->;中央处理器100.576330.552906(0.959357)0.023424(0.040643)0.0506570 . 0763220 . 0763222005

取100.9193610.895201(0.973721)0.024160(0.026279)0.0829350 . 1082932606

GPU:GPU->;中央处理器100.4435140.419354(0.945526)0.024160(0.054474)0.0406390 . 0406391

scopebuffered monitor::post _ local _ exec _ scopes _ process 100.3419990.341999(1.000000)0.000000(0.000000)0.0284360 . 000000001

急切_删除300.2872360.287236(1.000000)0.000000(0.000000)0.0054520.0226960.009574530

scopebuffered monitor::pre _ local _ exec _ scopes _ process 100.0478640.047864(1.000000)0.000000(0.000000)0.0036680.01000000004

InitLocalVars 10.0229810.022981(1.000000)0.000000(0.000000)0.0229810.0229810 . 0229810 . 0229812

通过日志检查数据传输的具体位置

有时候同一个OP在模型里用了很多次。例如,我们可以在网络的几个不同位置定义切片层。这时,如果想确认数据传输发生在哪里,需要查看更详细的调试信息,然后就可以打印出运行时日志了。还是以上面的程序为例,执行glog _ VM module = operator = 3 python test _ case . py会得到如下日志信息,可以看到这两个数据传输:

第3~7行 log 显示:shape 输出的结果在 CPU 上,在 slice 运行时,shape 的输出被拷贝到 GPU 上;第9~10行 log 显示:slice 执行完的结果在 GPU 上,当 crop_tensor 执行时,它会被拷贝到 CPU 上。

I 0406 14:56:23.28659217516 operator . cc:180]cuda place(0)Op(shape),输入:{ Input[fill _ constant _ 1 . tmp _ 0:float[1,3,5,5]({})]},输出:{ Out[shape _ 0 . tmp _ 0:int[4]({ })]}。

I 0406 14:56:23.28662817516 each _ delete _ op _ handle . cc:107]CudaPlace上的Erase变量fill _ constant _ 1 . tmp _ 0(0)

I 0406 14:56:23.28672517516 operator . cc:1210]Transform Variable shape _ 0 . tmp _ 0 from data _ type[int]:data _ LAYOUT[NCHW]:place[CPupLace]:library _ type[PLAIN]to data _ type[int]:data _ LAYOUT[ANY _ LAYOUT]:place[CudaPlace(0)]:library _ type[PLAIN]

I 0406 14:56:23.28676317516 scope . cc:169]Create variable shape _ 0 . tmp _ 0

I 0406 14:56:23.28678417516 data _ device _ transform . cc:21]device transform in,src _ place CPUPlace dst _ place:cuda place(0)

I 0406 14:56:23.28686717516 sensor _ util . Cu:129]TensorCopySync 4 fromcuplace到CUDAPlace( 0)

I 0406 14:56:23.28709917516 operator . cc:180]cuda place(0)Op(slice),输入:{EndsTensor[],EndsTensorList[],Input[shape _ 0 . tmp _ 0:int[4]({ })],StartsTensor[],StartsTensorList[]},输出:{ Out[slice _ 0 . tmp _ 0:int[4]({ })]}。

I 0406 14:56:23.28714017516 each _ deletion _ op _ handle . cc:107]在CUDAPlace( 0)上擦除变量shape_0.tmp_0

I 0406 14:56:23.28722017516传感器_ util . Cu:129]tensorcopy sync 4 FromCudaPlace(0)到CPUPlace

I 0406 14:56:23.28747317516 operator . cc:180]cuda place(0)Op(crop _ tensor),输入:{Offsets[],OffsetsTensor[],Shape[slice _ 0 . tmp _ 0:int[4]({ })],ShapeTensor,X[fill _ constant _ 0 . tmp _ 0:float[1,3,8,8]({ })},输出:{Out[crop_tensor_0.tmp_0:float

使用device_guard避免不必要的数据传输

在上面的例子中,形状的输出是一维张量,所以当执行切片时,计算成本可能小于数据传输成本。如果切片设置为在CPU上运行,可以避免两次数据传输。有没有可能加速模型?我们试图修改程序并将切片层设置为在CPU上执行:

输入桨。流体为流体

importpaddle.fluid.compiler作为编译器

import paddle . fluid . profiler as profiler

data 1 = fluid . layers . fill _ constant(shape =[1,3,8,8],value= 0.5,dtype= 'float32 ')

data 2 = fluid . layers . fill _ constant(shape =[1,3,5,5],value= 0.5,dtype= 'float32 ')

shape = fluid.layers.shape(data2)

用液体。device _ guard(“CPU”):#一行命令,指定运行设备的网络层是CPU

shape = fluid.layers.slice(shape,坐标轴=[ 0],起点=[ 0],终点=[ 4])

out = fluid . layers . crop _ tensor(data 1,shape=shape)

place =流体。CUDAPlace( 0)

exe =流体。执行者(地点)

exe . run(fluid . default _ startup _ program)

compiled_prog =编译器。compiled program(fluid . default _ main _ program)

withprofiler.profiler( 'All ',' total ')作为配置文件:

fori inrange( 10):

result = exe . run(Prog = compiled _ Prog,fetch_list=[out])

第四步对比修改前后的模型,确认是否会带来性能提升

再看看Profiling Report中的GpuMemCpy Summary,可以看到GpuMemCpySync已经被淘汰了。同时注意下面的Total时间是14.5345ms,但是修改前是26.6328ms,是速度的两倍!实验表明,使用device_guard来避免数据传输显著提高了样本模型的性能。

在实际模型中,如果GpuMemCpySync调用占用大量时间,并且可以通过设置device_guard来避免,则可以带来一定的性能提升。

->;分析报告& lt-

注意!该报告将所有线程信息合并为一个。

地点:所有

时间单位:毫秒

在同一线程中按排序顺序按总时间排序

总时间:14.5345

计算时间总计:4.47587比率:30.7948%

框架开销合计:10.0586比率:69.2052%

- GpuMemCpy摘要-

GpuMemcpy呼叫:10总计:0.457033比率:3.14447%

GpuMemcpyAsync呼叫:10总计:0.457033比率:3.14447%

-事件摘要-

事件调用总CPU时间(比率)GPU时间(比率)最小值。最大值平均比率。

fasthreadedssagrafixecutorprepare 107.701137.689066(0.998433)0.012064(0.001567)0.0326577 . 5032676

fill _ constant 202 . 622992 . 587022(0.986287)0.035968(0.013713)0.0710970 . 3420820 . 1420820866

形状101.935041.935040(1.000000)0.000000(0.000000)0.0267741 . 60160 . 60267741

取100.8804960.858512(0.975032)0.021984(0.024968)0.073920 . 1073960 . 1073964966

GPU:GPU->;CPU 100.4570330.435049(0.951898)0.021984(0.048102)0.0378360.0714240 . 0378361

crop _ tensor 100.7054260.671506(0.951916)0.033920(0.048084)0.058410 . 058410 . 058410 . 0584100006

切片100.3242410.324241(1.000000)0.000000(0.000000)0.0242990 . 072130 . 07242424244

急切_删除300.2505240.250524(1.000000)0.000000(0.000000)0.0041710.0162350.0083508505

scopebuffered monitor::post _ local _ exec _ scopes _ process 100.0477940.047794(1.000000)0.000000(0.000000)0.0033440.01000000004

InitLocalVars 10.0346290.034629(1.000000)0.000000(0.000000)0.0346290.0346290 . 0346291

scopebuffered monitor::pre _ local _ exec _ scopes _ process 100.0322310.032231(1.000000)0.000000(0.000000)0.0029520.00000000001

总结

通过以上实验对比,可以发现device_guard接口可以一个命令合理设置模型网络层的运行设备,更灵活的调度模型的GPU和CPU计算,最大限度的利用服务器的资源,解决了模型因视频内存容量不足而无法训练的问题。嗯,这个功能挺实用的!心不如心。请参考本文的方法,把自己的模型训练到自己心仪的内容。

使用中如有问题,可加入飞桨官方QQ群交换:1108045677

官网地址:

https://www.paddlepaddle.org.cn

Flying oar开源框架项目地址:

GitHub:

https://github.com/PaddlePaddle/Paddle

Gitee:

https://gitee.com/paddlepaddle/Paddle

1.《显存不足怎么解决 模型训练太慢?显存不够?这个方法让你的GPU联手CPU》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。

2.《显存不足怎么解决 模型训练太慢?显存不够?这个方法让你的GPU联手CPU》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。

3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/guoji/1114135.html

上一篇

女子隆胸时突然死亡 丈夫恢复监控看到愤怒一幕

下一篇

近5成空调或遭淘汰 淘汰的标准是什么

大众电脑 国产操作系统内存仅三百多兆,功能全的可怕!千万别小看

  • 大众电脑 国产操作系统内存仅三百多兆,功能全的可怕!千万别小看
  • 大众电脑 国产操作系统内存仅三百多兆,功能全的可怕!千万别小看
  • 大众电脑 国产操作系统内存仅三百多兆,功能全的可怕!千万别小看

王伟墓前有人送来歼20模型 19年来鲜花不断!我们从未忘却英雄!

有人把歼20模型送到王维墓前。19年来鲜花不断!我们永远不会忘记英雄!去年4月1日是海洋守护者王维牺牲19周年。不久前,他的父母专门捐了一万元抗击疫情。但两位老人都有遗憾:由于清明节在即,考虑到疫情防控和年老不便,今年取消...

阴暗河底捞出坠毁无人机 内存卡中的惊人画面曝光让人腿软

  • 阴暗河底捞出坠毁无人机 内存卡中的惊人画面曝光让人腿软
  • 阴暗河底捞出坠毁无人机 内存卡中的惊人画面曝光让人腿软
  • 阴暗河底捞出坠毁无人机 内存卡中的惊人画面曝光让人腿软

一出考场就收到妈妈送的高达模型 实名羡慕这样的家庭

  • 一出考场就收到妈妈送的高达模型 实名羡慕这样的家庭
  • 一出考场就收到妈妈送的高达模型 实名羡慕这样的家庭
  • 一出考场就收到妈妈送的高达模型 实名羡慕这样的家庭
书报架模型 中国报刊架行业市场前景分析预测报告

书报架模型 中国报刊架行业市场前景分析预测报告

[报告类型]多用户、行业报告/市场前景预测报告 【发布时间】即时更新(交付时间约6-10个工作日) 【发布者】晶晶视觉 【报告格式】PDF 版++WORD版+纸质版(限一份) [官网]http://www.cevsn.com/research/report/1/39989.html 核心内容...

超幸福!一出考场就收到妈妈送的高达模型

超幸福!一出考场就收到妈妈送的高达模型

来自广州的魏一走出考场,就收到了妈妈送的一个模型。魏妈妈说儿子说高考前不送花,高考后给他买了最大的。儿子是寄宿学生,考试前一天回学校。这两天她很紧张,就是想给儿子一个惊喜。延伸阅读考生冲出考场,高兴得空两个字都亮了!在长沙...

流体力学家童秉纲院士逝世 享年93岁

流体力学家童秉纲院士逝世 享年93岁

再会!流体力学家童炳刚院士逝世中共党员、中国科学院院士、中国科学院教授童炳刚同志于2020年7月9日在北京逝世,享年93岁。童炳刚,1927年9月28日出生于江苏张家港,1946年至1950年就读于国立中央大学(现南京大学...

amd5000 AMD Ryzen 5000系列更换AM5接口,升级DDR5内存

amd5000 AMD Ryzen 5000系列更换AM5接口,升级DDR5内存

Notebookcheck的报告显示,已经“设计”出7nm+技术的AMD Zen 3,AMD有望在2020年CES上披露开发信息。另外,据悉AMD将于2020年末正式推出Ryzen 4000处理器和X670芯片组。  消息称,X670主板还是AM4接口,是香硕设计的,而不是AMD。目前X67...