当你训练模型或者运行一个比较大的程序时,电脑可能会弹出一个让人头疼的错误:显存不足(Out of Memory, OOM),这就像你要搬一大堆行李上车,但后备箱只有一点点大,东西根本塞不进去,下面我们就来详细聊聊,遇到这个问题该怎么一步步排查,以及有哪些办法可以解决它。
别慌张,我们得先搞清楚问题到底出在哪里,第一步是检查你的任务本身,你是不是在运行一个特别复杂的模型,比如现在很火的大语言模型或者高分辨率的图像生成模型?这些模型本身就像“巨无霸”,需要消耗大量的显存,或者,你是不是把模型的批量大小设得太高了?批量大小就是指一次性处理多少数据,一次性处理的数据越多,需要的临时空间就越大,显存自然就吃不消了,这就好比你一次想搬十箱水果,但你的小车一次只能拉两箱,硬塞肯定不行。
要看看是不是有其他程序在和你抢显存,你可能同时开着很多程序,比如浏览器开了几十个标签页、挂着聊天软件,或者还有其他深度学习任务在后台偷偷运行,这些程序可能也在占用显存,你可以打开任务管理器(比如NVIDIA的nvidia-smi命令),看看当前显存都被哪些进程占用了,如果发现有“不速之客”,果断关掉它们,为你的主要任务腾出空间。
如果确认就是你的主任务需要太多显存,那么我们就需要想办法“瘦身”或者“扩容”了,以下是一些非常实用的优化方法:
减小模型规模或批量大小。 这是最直接有效的方法,如果模型太大,能不能用一个简化版的模型?或者,毫不犹豫地把批量大小调低,比如从64降到32、16,甚至更小,虽然这可能会让训练速度慢一点点,但总比完全跑不起来要好。
使用梯度累积技巧。 如果你不想减小批量大小对训练效果的影响,可以试试梯度累积,它的原理是:我们还是用小批量数据计算,比如原本一次处理64个数据,现在我们改成连续处理4次,每次16个数据,每次计算完的梯度先累积起来,不立刻更新模型,等4次都算完了,再用累积起来的梯度总和去更新一次模型,这样,在效果上相当于用了64的批量大小,但显存占用却只是16的大小。
降低数据精度。 现代GPU通常支持混合精度训练,简单说,就是不用那么“精细”的数字来表示模型,默认的精度是FP32(单精度浮点数),我们可以让模型的大部分计算使用FP16(半精度浮点数),这样每个数字占用的空间直接减半,显存压力会大大降低,而且很多新显卡对半精度计算有优化,速度还会更快,这需要代码层面做一些设置。
及时清理不用的变量。 在程序运行过程中,会产生很多中间结果,有些结果用完之后就不会再用了,但它们可能还占着显存不释放,在代码里,要有意识地及时清理这些临时变量,确保你的代码在计算损失并更新模型参数后,正确地清空梯度,如果梯度一直累积,也会白白浪费显存。
使用检查点功能。 有些模型层数非常深,在前向传播(计算预测结果)过程中会产生大量中间结果,这些是用于后续反向传播(计算梯度)的,检查点技术的思路是:我们不在整个过程中保存所有中间结果,而是只保存几个关键点的结果,当反向传播需要用到某个中间结果时,如果发现它没有被保存,就临时从最近的关键点重新计算一下,这是一种“用计算时间换显存空间”的策略,非常适合显存极度紧张的情况。
升级硬件或使用多卡训练。 如果以上软件方法都试过了,显存还是不够,那可能就得考虑硬件问题了,如果你的显卡显存确实太小(比如只有4GB或6GB),而你的任务又非常庞大,那么升级一块显存更大的显卡是根本的解决办法,另一个高级的方法是使用多块显卡共同训练一个模型,把模型的不同部分或者数据的不同批次分配到不同的卡上,这叫模型并行或数据并行,不过这需要更多的硬件支持和更复杂的代码配置。
利用云计算资源。 如果你只是偶尔需要运行大任务,不想投资昂贵的硬件,云计算平台是个非常好的选择,你可以按小时租用拥有大显存(比如40GB甚至80GB)的GPU服务器,用完了就关掉,非常灵活和经济。
养成一些好习惯也很重要,在开始一个大型任务之前,先估算一下模型和数据大概需要多少显存,在代码里加入显存监控的语句,随时观察显存的使用情况变化,这样一旦发现显存使用量增长异常,就能快速定位问题。
解决显存不足问题是一个系统工程,需要从任务分析、软件优化到硬件配置等多个角度综合考虑,通常不是单一方法就能彻底解决的,需要你根据实际情况,像搭积木一样组合使用多种策略,耐心地一步步尝试,总能找到适合你当前条件的解决方案。
