最近在尝试用DeepSpeed微调一个7B的LLM,显卡是两张3090(24G)。参考了几个开源项目的配置,把ZeRO Stage调到了2,offload_optimizer也开了,但跑起来大概十几个step之后就报CUDA OOM。
我看nvidia-smi,显存占用并没有满,但就是报错。后来试了把train_batch_size降到1,gradient_accumulation设成8,还是不行。
有点迷茫:是ZeRO的partition策略没生效,还是我的模型本身太大,两张卡真的带不动?
或者是不是我漏了offload_param?看网上有人说Stage 2要配合offload_param才能稳,但那样速度会不会太慢?
求有经验的老哥指点一下,这种配置到底该怎么调,或者说7B微调最低需要多少显存?谢谢!
用DeepSpeed微调7B模型总是OOM,是ZeRO配置问题还是我显存真不够?
全部回复
共 83 条3090的24G显存跑7B其实够呛,尤其你开了offload_optimizer之后,通信开销和显存碎片化会明显加剧,nvidia-smi看着没满但实际可用块已经碎了。我建议你把offload_param也打开试试,Stage 2本来就要配合它才能把参数和优化器状态都挪走,不然光offload优化器省不了多少。另外你检查下是不是用了flash attention,没开的话激活值会吃掉一大块,降到batch=1还OOM大概率是这块的问题。我之前用A6000跑7B,Stage 2全offload加flash attention,batch=1能稳,两张3090理论上应该行,只是要扣得更细。
offload_param不开的话,stage2优化器offload了但参数还是占满显存,试试点上这个。
3090的24G跑7B全参数微调确实紧,试试offload_param=true,把参数也卸到CPU。
开offload_optimizer时记得配下cpu_offload,不然优化器状态还是占显存。
3090虽然是24G,但7B全参微调本来就很极限,你开了offload_optimizer却不开offload_param,相当于把压力全堆在激活值和梯度上了,试试把offload_param也打开,配合stage 2应该能缓解。另外检查下你是不是忘了设zero_force_opt_optimization_step,有时候这个不配好,优化器状态还是会残留一部分在显存里。还有个小技巧,可以开activation checkpointing,能省不少显存,就是慢点。我之前用双卡跑6.7B也遇到过类似情况,把这两个offload都开了之后就没再OOM过。
说实话两张3090硬扛7B全参数微调确实极限,但你这个报错更像是显存碎片化而不是真爆了。建议先确认下是不是gradient checkpointing没开,这个能省将近一半激活显存。另外offload_param在stage2里确实不是必须的,但你可以试试把optimizer和param一起offload到CPU,代价是训练速度会明显变慢。还有个偏方,把模型加载成half精度,然后显存分配策略改成auto,有时候能救回来。如果还不行,大概率就是数据并行维度上没切干净,你检查下zero_force_ds_cpu_optimizer是不是被某些库覆盖了。
你这情况我上周刚踩过一模一样的坑,7B在24G卡上Stage 2不加offload_param就是会卡在某个step突然爆显存,因为优化器状态和梯度都还占着空间。建议先把offload_param也打开试试,同时把ZeRO的partition_buffers设成true,这俩对显存释放特别明显。另外你确认下是不是没开activation checkpointing?这个对7B来说省显存效果比调batch size立竿见影。如果还不行,把max_seq_len砍到1024试试,很多时候是序列长度在偷偷吃显存。
3090的24G跑7B全参微调本来就很悬,offload_param也得开,不然ZeRO2光offload优化器省不了多少。
你这配置跟我之前一模一样,后来把offload全开加CPU Adam才勉强跑起来,但速度慢得怀疑人生。
3090虽然是24G,但7B全参微调就算开ZeRO2+offload_optimizer,单卡算力瓶颈和通信开销也容易卡在中间态,你可以先看看是不是activation显存炸了,开个gradient_checkpointing能省不少。另外offload_param在stage2里其实不是必须的,但如果你把optimizer和param都offload到CPU,两卡24G应该勉强够跑,只是速度会慢到怀疑人生。我试过类似配置,最后是把序列长度砍到512才稳定跑完,你不如先检查下实际峰值显存是不是被临时tensor顶爆了,而不是单纯看nvidia-smi的占用率。
offload_param也得开,不然优化器状态还是占显存,我上次就是漏了这个直接爆。
试下stage 3加offload,两张24G跑7B理论上够,大概率是配置没吃透。
3090虽然24G,但7B全参微调本来就很极限,两张卡跑ZeRO-2理论够,但你看nvidia-smi没满可能是碎片或峰值问题,不一定准。我建议先查一下是不是activation memory爆了,开个gradient_checkpointing试试,省下的显存可能比你想的多。另外offload_param确实该开,Stage 2只offload优化器状态不够,参数和梯度也得考虑,不然权重本身就能吃满一张卡。最后实在不行就上LoRA吧,7B用QLoRA两张3090能跑得很舒服,效果也不差。
说实话你这个问题我太有共鸣了,之前用4张3090跑13B也遇到过一模一样的现象,显存看着没满但就是OOM,后来发现是碎片化加通信峰值的问题。ZeRO Stage 2本身只切了optimizer和gradient,模型参数和激活值还是全量复制到每张卡上的,7B光参数fp16就要14G,加上activation和临时buffer,两张24G其实非常极限,特别是你开offload_optimizer之后,CPU-GPU频繁搬运反而容易在某个step瞬间爆掉显存。你提到train_batch_size降到1还不行,我怀疑是gradient_accumulation在ZeRO下会放大通信量,每个micro-step都要做reduce,累积8次等于把峰值又抬高了。建议你试试把offload_param也打开,虽然会慢不少,但能腾出模型参数那十几G,另外可以查一下你的seq_len是不是很长,7B模型如果序列超过2k,激活值内存会指数级增长。还有一个偏方,就是给DeepSpeed配个stage3_gather_16bit_weights_on_model_save,然后关掉offload_optimizer只开offload_param,让模型权重边用边加载,虽然训练速度会掉到惨不忍睹,但至少能跑完。最后如果还不行,大概率是你某些中间变量没显式释放,建议开一下torch.cuda.empty_cache(),或者看看是不是tokenizer的padding策略搞出了巨大的batch内动态shape。
3090的24G跑7B全参微调确实紧,但OOM多半是activation峰值爆炸,先查下max_seq_len和batchsize的乘积。
说实话7B在两张24G上跑纯ZeRO-2不开offload_param确实很极限,我试过类似配置,光是参数+梯度+Adam状态就超40G了。你开了offload_optimizer但param还在显存里,等于只省了一半,建议试试把offload_param也打开,虽然慢点但能稳。另外你报错时nvidia-smi没满,有可能是碎片化或者通信buffer峰值涨上去了,可以留意一下step刚开始那瞬间的显存。还有个思路,既然gradient_accumulation都开到8了,不如试试把ZeRO-3配上offload,两张卡当一张用,7B应该能塞下。
说实话你这个现象我太熟了,3090两张跑7B按理说ZeRO-2是够用的,关键问题大概率不是显存总量不够,而是碎片化和峰值分配的问题。你看nvidia-smi没满但OOM,很多时候是某个瞬间激活值或者临时buffer冲高,尤其是在前十几个step之后,可能正好碰到序列长度变化或者loss计算那一下。offload_optimizer开了是对的,但如果你没同时开offload_param,那模型权重和梯度还是占着显存,ZeRO-2本来就不分担参数,所以两张卡各存一份完整权重,这本身就吃掉不少了。我建议你先把zero_optimization里那三个offload全开了试试,尤其是offload_param设为true,虽然速度会慢一点,但能腾出好几个G。另外你gradient_accumulation设成8,但实际有效batch还是等于1,这会让通信开销变大,反而容易在reduce_scatter阶段触发临时显存分配,你可以试试把accumulation降到2或者4,同时把微批大小调成2,看看峰值会不会降下来。还有个坑是如果用了flash attention或者某些自定义kernel,它们会有额外的workspace显存,那个在nvidia-smi里不体现,但会直接导致OOM。最后实在不行就开ZeRO-3加全offload,虽然慢,但7B两张24G肯定能跑,只是你得接受速度掉一半。
说实话你这个情况我太熟了,之前用双卡跑13B也踩过一模一样的坑。nvidia-smi看着没满但OOM,大概率不是显存真不够,而是碎片化或者峰值内存爆了——比如前向传播时activation峰值特别高,你观察到的占用是step结束后的状态,误导性很强。ZeRO Stage 2只切了optimizer和gradient,模型参数和activation还是每卡一份,7B模型光参数就要14G,两张24G卡其实已经很极限了。你试着把offload_param也开开,把参数也塞到CPU上,虽然慢一点但能腾出不少显存,反正你已经有offload_optimizer了,这俩搭配起来才是完整的Stage 2 offload方案。另外gradient_accumulation不影响峰值显存,它只是把梯度累加次数变多,而train_batch_size降到1其实已经是最低限度了,再降就失真了。我猜你还有个隐藏问题,就是没有设置zero_force_ds_cpu_optimizer或者没开pin_memory,导致CPU offload的时候传输效率极低,反而堆积了临时缓冲。最后建议你开一下DeepSpeed的monitor看实时内存曲线,找到真正爆掉的那个step,比瞎调配置靠谱得多。
说实话我觉得你这情况大概率不是显存物理容量不够,而是DeepSpeed的内存管理和显存分配之间出了点微妙的问题。7B模型在fp16下光权重就14G,两张3090各24G,理论上Stage 2加offload是能塞下的,但你说显存没满却OOM,这很可能是碎片化或者临时buffer炸了,比如activation checkpointing没开,前向传播的激活值在长序列下会突然暴涨。你可以先试下把offload_param也打开,虽然Stage 2理论上不强制,但实际跑起来param offload能显著降低峰值显存,代价只是慢一点。另外检查一下你是不是用了ZeRO Infinity或者配置里stage3的某些参数残留,有时候config文件里混着写会触发奇怪的分配逻辑。还有个小技巧,把zero_force_ds_cpu_optimizer设成false,有时候CPU optimizer会抢内存导致CUDA context异常。我之前遇到过类似情况,最后是靠把model并行切分得更细,同时把gradient_accumulation步数拉高到16,才稳定跑完的。你也可以试试看能不能用huggingface的accelerate来包一层,它会自动帮你检测显存碎片,比纯手写DeepSpeed配置省心不少。
说到OOM这事我太有共鸣了,之前用双卡跑13B也踩过一模一样的坑,nvidia-smi看着没满但就是炸。你这个问题我怀疑不是显存容量不够,而是碎片化或者通信峰值导致的,尤其开offload_optimizer之后,CPU和GPU之间来回搬运,某个瞬间的峰值可能远超你看到的空闲量。
ZeRO Stage 2确实不强制开offload_param,但如果你跑的是7B,只offload优化器状态其实省不了多少,模型权重和梯度还是得占着显存。我建议你直接上Stage 3,把参数也offload出去,两张3090跑7B应该能塞下,代价是慢不少。另外检查下你是不是用了flash attention,没开的话激活值也能吃掉好几G。
还有个小细节,你gradient_accumulation设成8,但train_batch_size=1,这意味着逻辑上batch是8,但物理上每次只算1个样本,显存占用其实没降多少。真正有效的是把seq_len缩短试试,比如从2048砍到1024,看看OOM是不是延后了。你那个“十几个step之后才报错”也很典型,说明是累积到某个阶段才爆,不是起步就超,这种时候看看是不是allreduce通信时临时多占了一块显存。
我遇到过类似的坑,3090虽然24G但7B全参微调确实吃紧,尤其你开了offload_optimizer后通信开销会暴涨,显存看着没满但碎片化严重也会OOM。建议查下transformers的generation_config是不是默认pad了,或者试试zero stage 3加pin_memory,我上次这么调完明显稳了。另外train_batch_size降到1还不行的话,重点检查下是否忘了关gradient_checkpointing,这个对显存影响比ZeRO配置还大。
说实话你这个问题我当初调7B的时候也撞过,最后发现大概率不是显存真不够,而是DeepSpeed的显存碎片化加通信缓冲在作祟。你nvidia-smi看着没满,但PyTorch缓存分配器可能已经预留了一大块连续显存,等某个step的激活值峰值一来就直接炸了,跟ZeRO Stage 2本身关系不大。
offload_param确实值得试,但更关键的是看你有没有设zero_force_ds_cpu_optimizer那个参数,很多时候offload_optimizer开了但优化器状态没真正挪到CPU。另外你可以先在单卡上把micro batch size压到1,然后开gradient_checkpointing,这个对激活显存削减特别明显,7B模型不开它基本硬跑就是等死。
还有个容易忽略的点是你在配置里设的train_batch_size指的是全局batch,但实际吃显存的是per_gpu_batch_size,如果没显式写清楚,DeepSpeed可能会按你数据加载器的默认值跑,导致你感觉改了但没生效。我当时的解法是直接开Stage 3,把参数和优化器全offload到CPU,虽然慢一半但至少不会OOM,两张3090跑7B是绰绰有余的。
你试试把zero_allow_untested_optimizer和zero_force_ds_cpu_optimizer同时打开,然后把offload_param也设成cpu,应该就能稳住了。如果还炸,那大概率是你某个中间层的activation特别大,建议检查一下模型的tie_word_embeddings是不是开着,那个有时候会偷偷复制一份embedding。
看到你说显存没满但OOM,我赌大概率是碎片化和峰值分配的问题,3090的24G跑7B全参数微调本来就悬,就算offload了optimizer,激活值在反向传播时照样能给你顶爆。你可以试试把zero stage提到3,然后把offload_param也打开,虽然慢点但能稳很多。另外检查下你是不是忘了设pin_memory和num_workers,这俩有时候会让CUDA context吃额外显存,我上次就栽在这上面。还有个小技巧,用torch.cuda.empty_cache()在每step后清一下,偶尔能多撑几十步,但治标不治本。