最近在微调一个7B的Llama模型,单卡A100 80G,尝试用DeepSpeed的ZeRO-3跑,结果一启动就显存爆炸,直接OOM。我查了文档,把offload参数也打开了,optimizer和param都offload到了CPU,batch size降到1,梯度累积也调了,但还是在第一步就崩。
我怀疑是不是我的模型加载方式有问题?或者ZeRO-3需要特殊的模型并行配置?看网上有人说ZeRO-2就够用,但我怕显存不够。有没有大佬遇到过类似情况?是不是我漏了什么关键参数?真诚求教,实在不想为了省钱白嫖半天还跑不起来……
用DeepSpeed跑Llama微调,ZeRO-3总是OOM,是我配置姿势不对吗?
全部回复
共 177 条说实话ZeRO-3在单卡上反而容易出幺蛾子,它的设计初衷是多卡分片,单卡时offload的通信开销和内存碎片可能比省下的还多。你试过直接把offload全关掉,只用ZeRO-2加梯度检查点吗?7B在80G上其实勉强够跑,batch=1加gradient checkpointing应该能稳住。另外确认下你是不是用了transformers的from_pretrained加载,那个默认会先创建完整模型再分片,得用deepspeed.initialize里的模型并行加载才行。我之前也被这个坑过,换成ZeRO-2瞬间就通了。
遇到过一模一样的坑,当时也是单卡A100 80G跑7B,ZeRO-3一开就OOM,后来发现问题出在模型加载上——你用from_pretrained的时候如果没指定device_map,模型会先完整载入GPU再被DeepSpeed接管,这一瞬间就爆了。建议直接用deepspeed.initialize前先把模型放到CPU上,或者用HF的device_map="auto"配合zero3的stage3_gather_16bit_weights_on_model_save参数,能省不少临时显存。另外你确认一下offload的优化器状态是否真的生效了,有时候需要显式指定offload_optimizer_device和offload_param_device,光开offload=true可能不够。还有个容易忽略的点,ZeRO-3默认会为每个参数创建 partitioned buffers,如果你的config里没设stage3_max_live_parameters和stage3_max_reuse_distance,默认值在某些版本下会疯狂预留显存,建议调小到1e8左右。说实话单卡跑7B用ZeRO-2就够了,offload到CPU后显存占用大概能压到40G以下,ZeRO-3反而因为通信和碎片化开销更容易炸,除非你要跑13B以上才值得折腾。最后检查下transformers和deepspeed的版本兼容性,我遇到过老版本deepspeed对llama的tied weights处理有bug导致显存翻倍,升级到0.10+基本能解决。
我当初也踩过这个坑,7B上ZeRO-3单卡纯属给自己找罪受,那玩意儿本来就是给多卡跨节点设计的。你想想,ZeRO-3把参数也分片了,每个step前都要all-gather完整权重,光这个通信开销和临时显存就够呛,A100 80G跑7B真没必要上三档。先把offload里那个pin_memory打开试试,然后确认下是不是把cpu_offload和nvme_offload同时开了,这俩叠一起反而容易爆。另外你加载模型用的是from_pretrained还是先把模型放到meta device上再offload?如果是前者,权重直接进显存再被ZeRO切分,那瞬间峰值肯定爆。正确姿势是先初始化空模型,再让DeepSpeed把分片后的权重load进去,顺序反了必炸。当然我也同意楼上的,单卡直接ZeRO-2加offload就够,7B的激活值用gradient checkpointing压一压,80G完全能塞下,别迷信ZeRO-3。最后检查下transformers版本,老版本和DeepSpeed的兼容性有bug,新版本修了不少显存泄漏问题,升级一下说不定就好了。
看到你说ZeRO-3一启动就爆,我怀疑是stage3的param partition在初始化时把全量权重都载入显存做分片导致的,可以试试先加载到CPU再转成ZeRO-3,或者用model.from_pretrained(..., device_map="cpu")。另外你offload了optimizer和param但没提activation,7B模型激活值也挺吃显存的,建议加个activation offload或者用gradient_checkpointing,我之前就是这么救回来的。实在不行换ZeRO-2加CPU offload,7B用80G训练其实够,主要看你的序列长度和batch,别硬刚ZeRO-3。
单卡A100 80G跑7B用ZeRO-3确实有点杀鸡用牛刀了,这个规模ZeRO-2基本就能扛住,offload反而会拖慢速度。你OOM大概率不是配置问题,而是模型加载时默认把权重全塞进显存了,试试用from_pretrained加个low_cpu_mem_usage=True,或者先加载到CPU再手动move到GPU。另外ZeRO-3对单卡场景其实没收益,它本来就是为多卡设计的,你可以直接关掉offload,只留ZeRO-2加个CPU offload optimizer,batch size设2跑跑看。我之前微调13B用ZeRO-2加offload都没炸,你这7B应该更轻松才对。
说实话看到你这个情况我第一反应是模型加载方式确实背大锅,用from_pretrained直接加载7B权重再交给ZeRO-3,这时候每张卡上其实已经有一份完整模型副本了,显存当然秒炸。我建议你先用model_parallel或者干脆用deepspeed的zero.Init上下文去包裹模型实例化,这样参数才能真正按层切分,而不是等训练时才去分。另外你把offload全开但忘了设cpu_offload的pin_memory和nvme_offload_path的话,CPU内存交换也会成为瓶颈,尤其是param offload会频繁触发张量搬运,反而拖慢第一步。我猜你batch size调到1还是OOM,大概率是activation checkpointing没开,ZeRO-3下激活值占用跟模型并行不一样,7B模型序列稍微长点就是几十G的激活,试试用modeling_llama里自带的gradient_checkpointing_enable()。还有个小坑是optimizer offload到CPU后,学习率调度器或混合精度里的动态loss scaling可能还在GPU上建了额外buffer,你要检查一下optimizer的state_dict里的exp_avg是不是也被意外拷回GPU了。最后实在不行就退回ZeRO-2加offload,7B单卡80G其实ZeRO-2就够跑,我试过70B用4卡A100也是ZeRO-2加offload稳的一批,别被“ZeRO-3更高级”忽悠了。
试试ZeRO-2加offload,7B单卡真没必要上ZeRO-3,配置麻烦还容易踩坑。
其实我之前也踩过这个坑,单卡A100跑7B用ZeRO-3确实容易直接炸,问题多半出在模型加载时用了默认的meta device初始化,得配合zero.Init上下文或者先加载权重再分片。另外你可以试试把offload里的pin_memory关掉,有时候能省不少显存。不过说实话单卡上ZeRO-3收益不大,ZeRO-2加offload基本够用,我之前跑13B都没爆过,要不你先换个思路试试?
试试把zero_optimization里的reduce_bucket_size调小点,我上次调完立省5G显存。
offload都开了还炸,八成是模型加载时没走from_pretrained的low_cpu_mem_usage=True吧。
单卡A100 80G跑7B其实ZeRO-3有点大材小用了,offload全开反而可能因为CPU-GPU传输瓶颈卡在第一步。我建议你试试ZeRO-2加offload,或者干脆用LoRA,显存占用能降一大截。另外检查下是不是transformers版本和DeepSpeed不兼容,之前我遇到过类似问题,升级下就正常了。
单卡A100 80G跑7B其实ZeRO-3不是最优解,反而容易因为offload引入额外通信开销导致显存峰值异常。你可以试试ZeRO-2配合stage3的pin_memory和cpu_offload,或者直接把model并行切成2份,效果可能更稳。另外check一下你是不是用了huggingface的from_pretrained加载,那个默认会先把权重塞进GPU,改成先load到CPU再转device会好很多。我上次就是这么救回来的,你可以先跑个dummy batch看显存曲线再调。
ZeRO-3会把模型参数也切分,单卡跑7B反而比ZeRO-2更吃显存,试试纯ZeRO-2加offload。
大概率是模型加载时每个rank都完整载了一份权重,ZeRO-3的partition只在forward/backward时才生效,你试试先from_pretrained加device_map="auto"或者用deepspeed.initialize之前先让模型在CPU上初始化。另外offload到CPU后通信量会暴增,A100单卡跑7B其实ZeRO-2加offload就够,ZeRO-3反而容易因为NVMe和CPU带宽瓶颈卡死。我之前踩过这个坑,把zero_optimization.stage3_gather_16bit_weights_on_model_save设成true,然后reduce_bucket_size调小点能缓解,但第一步就崩多半还是初始化问题。
单卡跑7B开ZeRO-3纯属自找麻烦,参数全offload到CPU后通信开销反而更大,试试ZeRO-2加cpu offload,大概率能跑起来。
单卡A100 80G跑7B其实ZeRO-2就够用了,ZeRO-3的通信开销和显存碎片反而容易在单卡上搞出OOM。你试试把offload里的pin_memory关掉,然后检查下HF的模型加载是不是用了device_map="auto",有时候这跟DeepSpeed的zero.Init会打架。我之前也卡在这,后来直接改成ZeRO-2加gradient_checkpointing,batch size照样能开到4。
试试把zero_optimization里的stage3_gather_16bit_weights_on_model_save和reduce_scatter都显式设一下,另外确认下你的CPU内存够不够,offload后反而爆内存也会报OOM。
单卡A100 80G跑7B用ZeRO-3确实有点大材小用,这配置本身就是为了多卡设计的,单卡上它反而会大幅增加通信和内存碎片开销。我之前试过类似情况,把offload全开之后第一步要初始化optimizer状态,CPU内存带宽直接成瓶颈,显存没爆但卡死过。你不如试试ZeRO-2加offload,7B的参数量在80G上其实很宽裕,我这么跑过13B都没问题,先把stage设为2,offload只开optimizer试试。另外检查下你是不是把模型直接load到GPU了,用from_pretrained时加个low_cpu_mem_usage=True能省不少临时显存。要是还不行,直接砍一半序列长度,先跑通一个step看显存曲线,比瞎调参数靠谱。
说实话ZeRO-3在单卡上跑7B本来就容易出这种问题,它的设计初衷是多卡场景下把参数分片到各卡,单卡反而会引入额外的通信和内存碎片开销。你把offload全开其实已经触发了另一个坑,就是CPU offload和NVMe offload同时开的话,反而会导致内存分配爆炸,尤其是optimizer状态反复搬运的时候。建议你把offload_param和offload_optimizer分开配置,先只offload optimizer,param留在GPU上试试,毕竟7B模型权重也就14GB左右,A100 80G按理说塞得下,问题大概率出在临时缓冲区和激活值上。另外你检查过zero_force_ds_cpu_optimizer这个选项没?有时候默认值会导致Adam状态在CPU和GPU间来回跳,非常吃内存。我自己的经验是,单卡微调7B直接用ZeRO-2加gradient checkpointing反而最稳,激活值省下来的量比你想的多得多,不如先把activation checkpointing打开,然后ZeRO-2的stage 2配合offload optimizer,batch size哪怕2到4都能跑起来。你现在的现象是第一轮就崩,更像是显存被某个大的中间张量瞬间占满,比如embedding的梯度或者attention的score矩阵,建议用torch.cuda.set_per_process_memory_fraction限制一下显存比例,看看到底是哪个阶段爆的,顺便把日志里的显存峰值打出来。说到底,DeepSpeed的配置文档写得很隐晦,很多参数是相互影响的,官方示例里那些配置跑7B都得调半天,你直接套用肯定不灵光。
说真的,你这个问题我之前也踩过一模一样的坑,7B单卡A100 80G跑ZeRO-3,理论上是够的,但一开offload就特别容易炸。我当时查了半天,最后发现是HuggingFace的from_pretrained加载模型时,默认会把权重先塞进GPU显存,这时候ZeRO-3还没开始切分,峰值直接就爆了。你试试先加载到CPU,用device_map="cpu"或者先构造meta设备上的模型,再让DeepSpeed去初始化,这样能绕开那个峰值。另外你offload了optimizer和param,但有没有把optimizer的state也明确offload?有时候默认只offload了param,state还在显存里,7B的Adam state可是好几个GB。还有个小技巧,ZeRO-3配gradient_checkpointing能省不少激活显存,但注意它和offload的交互,可能需要在配置里显式开启。我最后是干脆降级到ZeRO-2,配合CPU offload optimizer,batch size 1跑起来了,速度还更快,ZeRO-3在这种单卡场景下反而多了不少通信和切分开销。你要是实在想用ZeRO-3,把stage3_prefetch_bucket_size和stage3_param_persistence_threshold调小一点,有时候默认值太大,也会在启动阶段拉爆显存。
单卡A100跑7B用ZeRO-3确实有点杀鸡用牛刀,offload到CPU之后通信开销反而可能把显存卡在峰值上。你把zero_force_optimizer_steps设成false试试,还有stage3_gather_16bit_weights_on_model_save这个参数有时候会影响初始内存分配。我之前遇到过类似情况,最后发现是huggingface的from_pretrained默认加载float32权重,得先手动转成half再丢给DeepSpeed。另外你可以看一眼训练脚本里是不是把model.to('cuda')写在了deepspeed.initialize前面,这会导致整模型先进显存再被分区。