最近在尝试用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跑7B按理说stage2是够的,但你这个报错更像是碎片化显存或者激活值峰值爆了,不是单纯参数放不下。建议你先开offload_param试试,同时把gradient_checkpointing打开,这俩配合能省一大截显存。另外nvidia-smi看的是实时占用,但CUDA OOM往往是瞬间峰值,你用nsys或torch profiler抓一下allocator的峰值会更准。最后提醒下,双卡时确保nccl通信没吃额外显存,有时候allreduce的buffer也会突然顶爆。
说真的,你这个问题我上个月刚踩过一模一样的坑,两张3090跑7B按理说不是完全没戏,但你这现象太典型了——nvidia-smi看着没满却OOM,多半是碎片化或者峰值显存爆了,不是显存真不够。ZeRO Stage2确实会partition optimizer state和gradient,但模型参数和中间激活值还是每张卡都存一份的,7B的fp16权重本身就要14G,加上激活和临时buffer,24G其实很紧。你说开了offload_optimizer,但offload_param没开,那模型参数还是留在显存里,等于省的那部分还不够塞激活值的。我建议你直接Stage3加offload_param,或者试试把序列长度砍到512,同时确认一下你是不是忘了开activation checkpointing,那个能省掉一大截激活内存。另外你降到batch size=1还OOM的话,很可能是某个算子临时分配了超大张量,比如attention的中间矩阵,这时候可以看看是不是用了flash attention,没换的话赶紧换上,能缓解不少。最后想说,报错不一定发生在显存峰值时刻,有可能是你梯度累积的step里某个特定位置触发的,建议你把zero_force_opt_offload打开,再配个NCCL的P2P关闭,有时候多卡通信也会搞出奇怪的内存占用。
offload_param也得开,不然优化器状态还是占着显存,3090跑7B stage2确实勉强。
offload_param基本是必须的,光开optimizer不够,7B参数+梯度本身就超24G了。
试试把offload_param也打开,7B全参微调两张24G确实勉强,混合卸载能省不少显存。
兄弟你这情况大概率是碎显存的问题,3090的24G跑7B全参数微调本来就紧巴巴,ZeRO2开offload_param试试,说不定立马就稳了。
看到你说显存没满但报OOM,我猜大概率是碎片化或者峰值分配的问题,ZeRO Stage2本身不省激活内存,7B模型光参数和梯度就快20G了,两张24G确实很极限。你试试把offload_param也开了,同时检查一下是否用了flash attention,这个能省不少激活显存。另外3090的NVLink带宽其实一般,offload到CPU后通信开销会拖慢速度,但至少能跑起来,先验证能不能稳定训练再说。
offload_param也要开,不然优化器状态省了但模型参数还是占满显存,试试stage3吧。
看到你说显存没满但报OOM,我第一反应是碎片化或者缓存没清干净,而不是单纯的容量不够。我之前用A6000跑13B也遇到过类似情况,后来发现是PyTorch的缓存分配器在DeepSpeed的通信后端下没及时释放,你可以试试在训练循环里加torch.cuda.empty_cache(),或者设PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb=128,有时候能救急。
不过你提到Stage 2开了offload_optimizer,这个其实已经把优化器状态挪到CPU了,理论上24G两张卡跑7B应该是够的,除非你序列长度特别长或者用了大batch的梯度累积。我怀疑问题可能出在activation上——ZeRO不管这个,7B的activation在长序列下能吃掉十几个G,你降batch_size但没降seq_len的话,峰值还是会被打满。
另外,你确认offload_param没开对吧?Stage 2默认不offload参数,但如果你用的配置是从Stage 3改过来的,可能残留了offload_param=True的设定,这样反而会在CPU和GPU之间频繁搬运张量,导致显存分配异常。可以去config里显式写上"offload_param": {"device": "none"}试试。
最后想问你一下,报错的时候是直接说"CUDA out of memory",还是带上了"RuntimeError: CUDA error"的堆栈?如果是后者,可能是通信缓冲区分配失败,跟显存剩余量无关,查一下NCCL_P2P_DISABLE这个环境变量有没有设置过。我上次就是被这个坑了一下午,关了P2P反而好了。
offload_param也得开,不然优化器状态还是占显存,试试stage3加cpu offload吧。
显存没满但报OOM大概率是碎片化或者峰值预留的问题,3090的24G跑7B全参数微调确实很极限。你可以试试开offload_param,同时把ZeRO Stage提到3,虽然慢点但能稳很多。另外检查下是不是模型加载时没走huggingface的low_cpu_mem_usage,这也会导致临时显存暴涨。我之前用类似配置跑13B,Stage 3加双offload才勉强不炸,7B的话Stage 2理论上能跑,但得把batch压到1且关掉任何形式的eval。
说实话你这个现象我太熟了,之前用单卡跑6.7B也遇到过,显存看着没满但就是报OOM,后来查了一圈发现是PyTorch的缓存分配器在搞鬼,说白了就是显存碎片化。你降到batch size 1都不行的话,我觉得大概率不是参数量的硬限制,而是你offload_optimizer之后CPU内存和GPU之间通信的瓶颈,反而让某些临时张量堆积在显存里没及时释放。另外你只开了offload_optimizer没开offload_param,这俩在Stage 2里其实是分开控制的,param不offload的话模型权重还是全量驻留在GPU上,24G两张卡算下来光权重加梯度就快20G了,再塞激活值肯定危险。我建议你试试把ZeRO改成Stage 3,同时把offload_param和offload_optimizer都开满,虽然速度会慢一截,但显存占用能降一个量级。还有个小技巧,可以在训练循环里手动清一下torch.cuda.empty_cache(),有时候能救急,但治标不治本。你要是方便的话,把zero_optimization那段的配置贴出来看看,说不定是partition_gradients之类的参数没对齐。
说实话你这个情况我太熟了,之前用4张3090跑13B也撞过一模一样的墙。nvidia-smi看着没满但OOM,八成不是显存物理容量的问题,而是CUDA上下文碎片化或者某个中间张量瞬间暴涨,尤其是attention score这种跟序列长度平方相关的东西,你检查下max_seq_len是不是设太大了。ZeRO Stage 2确实默认不offload参数,只offload梯度,如果你不开offload_param,那模型权重还是得完整塞进显存,7B光fp16权重就14G,两张卡每张分到7G,再算上激活值和临时缓冲区,24G其实挺紧的。我建议你把offload_param也打开,用cpu offload,虽然会慢点,但能腾出不少空间。另外gradient_accumulation设8但micro batch还是1的话,本质上每步计算量没变,只是更新频率降低了,对峰值显存帮助有限,你可以试试把micro batch提到2甚至4,配合gradient checkpointing,有时候反而更省。还有个小坑,DeepSpeed的zero_optimization里stage3_prefetch_bucket_size和stage3_param_persistence_threshold这种参数,默认值在7B上可能触发奇怪的行为,手动设小一点能缓解碎片化。要是还不行,干脆降到Stage 1试试,纯DP跑7B两张卡也不是不能凑合,只是bs得压到2左右。最后实在不行就上量化微调吧,QLoRA配NF4,7B单卡都能跑,效果损失其实没那么夸张。
3090互联带宽一般,offload_param也得开,不然权重同步照样炸显存。
说实话我之前也踩过这个坑,两张3090跑7B按理说ZeRO-2是够的,但你说显存没满却OOM,大概率不是容量问题,而是碎片化或者通信峰值导致的。你可以试着把offload_param也打开,虽然慢点但能稳很多,另外确认一下是不是用了FlattenParams或者activation checkpointing,这俩对显存影响特别大。还有个思路是直接看DeepSpeed的日志里各stage的峰值显存统计,能帮你定位是不是某个tensor瞬间爆炸。纯靠降batch size不一定能解决,我之前是把offload_optimizer改成offload_optimizer+offload_param一起开才跑通的,你试试看。
我之前也遇到过类似情况,显存看着没满但就是OOM,后来发现是碎片化问题,ZeRO Stage 2的bucket size调小点会有改善。另外你只开offload_optimizer的话,7B的模型参数本身还是占着显存的,建议把offload_param也打开试试,虽然慢点但能稳不少。还有个小坑,如果用了flash attention,记得看下是不是跟ZeRO的显存预留冲突了,我之前就是这儿卡了好久。你试试把stage 3加上,虽然慢但两张24G带7B应该能跑。
我前两天刚用两张3090跑过7B,你这配置按理说能跑,但问题可能出在activation memory上,ZeRO只管模型状态,中间激活值还是吃显存的大头,试试把seq_len砍到512或者开gradient_checkpointing,能省出好几个G。另外offload_param确实建议一起开,不过开了之后速度会慢不少,得平衡一下。你报错前有没有看下每个step的峰值显存?如果没满就报OOM,有可能是碎片化问题,加个PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True试试。
我之前也卡在过这个点上,排查很久发现是CPU offload和NVMe offload混用导致显存碎片化,你试试把offload_optimizer改成pin_memory=true,然后确认下ZeRO的stage2是否真的把optimizer状态切分到了两张卡上,有时候多卡配置里reduce_scatter没生效会白占显存。另外7B模型光weights就14G,activation再一冲确实吃紧,建议开gradient_checkpointing,能省一大截。你要是方便的话,可以贴下deepspeed_config里的zero_optimization段,我帮你看看是不是partition大小写之类的低级问题。
另一个思路是别死磕微调,直接试LoRA或者QLoRA,7B全参微调在24G*2上本来就极限,哪怕勉强跑起来速度也慢得离谱。我后来换了4-bit量化加LoRA,单卡就能搞定,效果差距不大但省心太多。你如果非要用全参,可以把序列长度砍到512,或者把hidden_size相关的中间激活清掉,别让它攒着。
offload_param也得开,两张24G跑7B全参微调本来就极限,先把seq_len砍到512试试。
7B模型在两张3090上其实空间是够的,问题可能出在activation和临时张量上,OOM不一定是显存到顶,有时候是显存碎片化导致的,你可以试试开gradient_checkpointing,这个对显存占用立竿见影。另外你开了offload_optimizer但没开offload_param,说实话Stage2下offload_param意义不大,但如果你把optimizer和param都offload到CPU,显存压力会小很多,代价是训练速度会慢不少。建议先看看是不是某些特殊token或者padding导致batch内长度不均,偶尔几个长序列把显存顶爆了,调一下max_length或者用flash attention试试。我上次跑13B也是类似情况,最后是靠sequence packing解决的。