最近在试着用LoRA微调7B的LLaMA模型,机器是两张3090(24G),按说应该能跑吧?但一加载模型就报CUDA out of memory。
我参考了几个GitHub仓库,有的说用bitsandbytes量化成4bit就行,但我试了还是炸。是不是我dataloader的batch size设太大了(设了4)?还是说需要把模型分到多卡?
另外,我看很多人直接用Hugging Face的Trainer,是不是里面有些默认参数会爆显存?有没有什么通用的显存优化trick,比如gradient checkpointing到底该咋开?
求各位大佬指点,真的有点怀疑人生了……
用PyTorch微调LLaMA,总是OOM,是我显存不够还是代码有问题?
全部回复
共 33 条我也在折腾类似的问题,3090 24G按理说跑7B的LoRA应该够,batch size 4不算大,但会不会是模型加载时默认用了fp32?试试在from_pretrained里加torch_dtype=torch.float16,然后bitsandbytes的4bit要确认下是不是用的bnb_4bit_compute_dtype=torch.float16。gradient checkpointing直接在Trainer里设gradient_checkpointing=True就行,但要注意它和某些量化方法有冲突。另外你用的tokenizer max length设了多少?太长也会吃显存。
看到你这帖子,我第一反应是“又一位被LLaMA微调折磨的同志”,欢迎来到大模型炼丹的深坑。先别急着怀疑人生,你遇到的这个问题,说实话,几乎是每个从CV或者小模型转过来的人都会撞的南墙——你以为两张3090 24G很能打,结果LLaMA 7B连加载都费劲,这很反直觉但确实正常。
先说结论:你的显存绝对不够,但代码也有优化的空间。不是说你硬件不行,而是7B模型在FP16精度下,光模型权重就要吃掉14GB左右(7B * 2 bytes)。注意,这只是权重,还没算优化器状态、梯度、激活值、以及你最关心的dataloader batch。当你用LoRA时,虽然可训练参数变少了,但模型的前向和反向传播依然需要把完整的FP16权重加载到显存里。3090单卡24G,单卡跑7B全量微调几乎是极限,你这还要加上LoRA的额外内存开销,两张卡如果没做分布式或者张量并行,那第二张卡基本是闲置的——因为PyTorch默认把模型塞进单卡,除非你显式调用nn.DataParallel或者更靠谱的Fully Sharded Data Parallel。
关于你提到的batch size=4炸显存,这其实是个典型的“幻觉”。很多人以为batch size小就万事大吉,但大模型的显存占用大头其实是激活值。以LLaMA 7B为例,输入序列长度如果设为512,单条样本的激活值就可能占用几百MB到1GB,这取决于你的模型层数和注意力头数。batch size=4,意味着激活值要乘以4,再加上梯度累积的中间变量,24G卡的显存很快就会被撑爆。我建议你先从batch size=1开始试,甚至sequence length先砍到256,确认模型能加载进去,再逐步往上加。你问gradient checkpointing怎么开,这个其实就是用时间换空间——在反向传播时重新计算前向的中间激活,而不是全部存下来。在Hugging Face的Trainer里,直接设置model.gradient_checkpointing_enable()就行,简单粗暴。代价是训练速度会慢15%-30%,但显存占用能降低30%-50%。对于你这种卡在边缘的情况,这招绝对是救命稻草。
再来说你提到的bitsandbytes 4bit量化。这个方案理论上应该是能跑起来的,但如果你还是炸,大概率是你量化后没有正确处理模型加载。很多人用from_pretrained(load_in_4bit=True),然后直接套LoRA,但忽略了一个关键点:量化后的模型参数是整数类型,当你给LoRA层挂上可训练的FP16参数时,PyTorch会在前向时自动把量化参数反量化回FP16再计算,这个过程会在显存里临时创建一个FP16副本。如果你的LoRA rank设得太大(比如64甚至128),这些额外参数也会吃掉不少显存。我建议你先用QLoRA的官方实现,把LoRA rank降到8或16,target modules只选query和value(别全选),然后配合4bit NF4量化。我在一张RTX 4090 24G上试过,序列长度512,batch size=2,加上gradient checkpointing,能稳定跑完一个epoch。你这双3090,理论上应该更稳,前提是你把模型真正分布到两张卡上。
关于多卡,这里有个很常见的误区:你提到的“把模型分到多卡”,很多人第一反应是用DataParallel,但这玩意儿对大模型是灾难——它会每张卡复制一份完整模型,结果显存占用直接翻倍。你需要的是模型并行,比如Hugging Face的accelerate库配合device_map="auto",或者用DeepSpeed的ZeRO Stage 2/3。ZeRO Stage 2会把优化器状态和梯度分到多卡,而模型权重每卡都有一份,适合你这种双卡场景。推荐你用accelerate launch加上--use_deepspeed --zero_stage 2,然后在trainer里把per_device_train_batch_size设为1,梯度累积步数设为4或8,这样实际batch size还是4,但显存压力被分散到两步之间。我踩过最惨的坑是,之前用ZeRO Stage 3,结果因为显存碎片化,明明总显存够但就是OOM,后来发现是PyTorch的缓存分配器在作祟——可以在启动脚本里加PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True,这能缓解碎片问题。
另外,你怀疑Hugging Face Trainer的默认参数,这个直觉很准。Trainer默认会启用--fp16,这本身没问题,但它还会默认启用--ddp_find_unused_parameters True,这在多卡场景下会额外增加显存开销。建议你显式设置--fp16_full_eval False(如果只是训练不需要推理),以及--dataloader_pin_memory False(虽然会慢一点,但能释放一部分CPU pin住的内存映射)。还有一个容易被忽略的地方:--gradient_accumulation_steps如果设得太大,每次更新前会累积多个step的梯度,这些梯度在显存里是保留的,虽然理论上梯度只占一个参数大小的内存(7B * 2 bytes ≈ 14GB?不,其实每个参数对应一个梯度,也是FP16,所以梯度本身也要14GB),但如果你用了ZeRO,这部分会被分摊。如果不用ZeRO,单卡24G要同时装下权重+梯度+优化器状态,即使4bit量化,优化器状态(Adam的动量和方差)是FP32的,每个参数要8 bytes,这样权重4bit(0.5 bytes) + 梯度2 bytes + 优化器8 bytes ≈ 10.5 bytes per parameter,7B参数就是约73.5GB,这显然远超24G。所以你必须用ZeRO或者量化+LoRA的组合拳来绕过这个瓶颈。
我建议你现在的排查路径是这样的:第一步,单卡测试,用4bit量化+LoRA+gradient checkpointing+batch size=1+seq len=256,确认能跑通一个step。第二步,如果单卡能跑,再用accelerate+ZeRO Stage 2跑双卡,这时每卡只负责一半的优化器状态和梯度,权重虽然每卡都有一份,但因为量化后的权重只有约3.5GB(7B * 0.5 bytes),加上LoRA参数(假设rank=8,约几十MB),再加上激活值和梯度分摊,24G卡应该能塞下batch size=2甚至4。如果还炸,检查一下你的dataloader是不是用了过多的num_workers,或者CPU内存不足导致数据交换变慢,这会让GPU等待而累积显存未释放。最后,别忘了监控显存碎片——用torch.cuda.memory_summary()打印详细分配情况,我见过有人因为某个自定义LayerNorm实现里创建了临时张量没释放,导致显存泄漏。
至于你怀疑人生这件事,真没必要。大模型微调的显存优化,本来就是一场和物理极限的博弈,7B模型在24G卡上跑,严格来说就是“非标用法”。很多网上教程说“轻松跑”,要么是用了极短的序列长度(比如128),要么是只跑推理不训练,要么是人家用A100 80G。你双3090的配置,只要把上面这些点全对齐,是完全可以跑起来的。我自己的血泪教训是:不要迷信别人的配置文件,每一个参数(precision、量化方式、LoRA rank、target modules、seq len、batch size、gradient checkpointing、ZeRO stage、优化器类型)都值得你手动调一遍。最后送你一句大模型炼丹真言:显存不够,梯度累积来凑;炸了,就砍序列长度。先从batch size=1、seq len=128跑通一个demo,再慢慢往上涨,每一步都确认显存占用在可控范围内。等你能稳定跑完一个epoch,你会发现之前那些OOM报错,其实都是学费。
7B模型单卡24G跑LoRA完全够,batch size设为1先试试,gradient checkpointing在Trainer里设gradient_checkpointing=True就行。
同款配置,我踩过一样的坑。batch size设4对7B模型确实大了,3090单卡24G跑4bit量化+LoRA,bs=1都容易爆,建议先降到1或2试试。gradient checkpointing在Trainer里传gradient_checkpointing=True就行,能省不少显存。另外模型加载用device_map="auto"配合bitsandbytes的4bit配置,大概率是量化参数没调对。
7B模型用两张3090其实完全够的,问题很可能出在加载时没做量化或者直接用了全精度。batch size=4对于7B确实偏大,尤其没开gradient checkpointing的话显存会瞬间爆炸,建议先调到1试试。Hugging Face Trainer默认会缓存中间激活值,显存开销很夸张,你可以在模型配置里加一句model.gradient_checkpointing_enable(),这招对长序列特别管用。另外bitsandbytes的4bit量化要确保加载时传了load_in_4bit=True和bnb_4bit_compute_dtype=torch.float16,很多人踩过这个坑。
3090单卡24G跑7B全参肯定不够,但LoRA + 4bit量化按理说batch size设为1应该能塞下,建议先排除dataloader的num_workers或者梯度累积步数有没有无意间拉高显存。gradient checkpointing直接在Trainer里设gradient_checkpointing_enabled=True就行,能省不少显存,但会慢一点。另外试试transformers的device_map="auto"配合bitsandbytes,它会自动把部分层分配到CPU或者多卡上,我这么跑过7B单卡勉强能稳。
7B模型用两张3090跑LoRA按理说是够的,但问题可能出在加载基础模型时没做量化或CPU offload。batch size设4对于7B确实偏大了,可以先试batch size=1并且开gradient accumulation。Hugging Face Trainer默认会加载float32,记得在加载模型时指定torch_dtype=torch.float16或直接用bitsandbytes的4bit,另外gradient checkpointing直接在TrainingArguments里设gradient_checkpointing_enabled=True就行。如果还炸,试试先把模型分到两张卡上,用device_map="auto"自动分配。
7B用两张3090跑batch size 4确实有点莽,试试gradient checkpointing加batch size调成1,再配合4bit量化应该稳了。
batch size 4不算大,但7B模型不量化直接加载确实会爆,试试gradient checkpointing加上4bit量化,两张卡用device_map="auto"分配。
两张3090跑7B的LoRA按理说是够的,你batch size设4确实偏大了,可以先降到1试试,我一开始也踩过这个坑。gradient checkpointing一定要开,在Trainer里设gradient_checkpointing_enabled=True就行,能省不少显存。另外Hugging Face的Trainer默认会加载全精度模型,记得配合bitsandbytes的4bit量化,还要在load_in_4bit=True时把device_map="auto"加上,这样模型会自动分到两张卡上。还有个小细节,dataloader的pin_memory可以关掉,有时候这玩意儿也会偷偷吃显存。
试试把batch size降到1,开gradient checkpointing,4bit量化如果还炸,检查下transformers版本是不是太旧了。
7B加LoRA按理24G够用,先看看是不是没开gradient checkpointing,那个能省不少显存。
讲真,这配置跑7B LoRA按理说是够的,你batch size设4其实不算大,但问题可能出在别的地方。我猜你可能是没开gradient checkpointing,这个在Trainer里加一句--gradient_checkpointing就行,能省不少显存,代价就是训练慢一点。另外,bitsandbytes的4bit量化确实能压显存,但前提是你得把模型整个转成4bit,而不是加载完再量化,而且LLaMA有些层对量化比较敏感,容易炸。你试试加载时直接传load_in_4bit=True,配合bnb_4bit_compute_dtype=torch.float16,我这么搞过,显存能压到15G左右。还有个坑是Hugging Face的Trainer默认会缓存中间变量,你检查一下dataloader_pin_memory是不是设成了False,有时候这个也会导致显存泄漏。如果还炸,建议用accelerate库把模型分到两张卡上,LoRA本身支持多卡,但需要你手动配一下device_map。你怀疑人生很正常,我刚玩LoRA时也折腾了一周,最后发现是tokenizer的padding策略没设对导致序列长度不一致,显存忽高忽低。
7B用两张3090跑LoRA按理说完全够的,你batch size设4确实偏大了,先降到1试试。gradient checkpointing可以在Trainer里加--gradient_checkpointing参数或者直接model.gradient_checkpointing_enable(),能省不少显存。还有那个4bit量化,得确保bitsandbytes和transformers版本匹配,不然容易踩坑。建议先把batch size调到1,开gradient checkpointing,再加个torch.cuda.empty_cache(),应该能跑起来。
7B用两张3090跑4bit加gradient checkpointing肯定没问题,batch size设4太大了,先调成1试试。
7B模型用LoRA的话,两张3090理论上完全够,batch size设4也不算大,问题可能出在模型加载时默认用了完整精度。试试在加载模型时直接加上load_in_4bit=True和bnb_4bit_compute_dtype=torch.float16,同时把device_map="auto"打开让显存自动分配。gradient checkpointing在Trainer里直接设args.gradient_checkpointing=True就行,能省不少显存。另外检查下是不是dataloader的num_workers开太多,有时候内存爆了也会报OOM的假象。
7B模型开LoRA加4bit量化,两张3090理论上完全够用,batch size为4其实不算大。问题大概率出在Hugging Face的Trainer默认会加载完整模型到单卡,你可以试试用device_map="auto"配合bitsandbytes,再手动开启gradient checkpointing,在TrainingArguments里设gradient_checkpointing=True就行。另外检查下dataloader的num_workers别设太高,有时候数据加载也会占显存。
24G单卡跑7B全参数肯定不够,LoRA+4bit量化按理说能塞下,但batch size设4确实偏大,先降到1试试。gradient checkpointing在Trainer里直接传gradient_checkpointing=True就行,能省不少显存。另外检查下是不是加载模型时没清缓存,或者用了torch.cuda.empty_cache()?还有Hugging Face的Trainer默认会计算eval的loss,记得关掉或者设evaluation_strategy="no"。
batch size 4对7B模型确实偏大,先降到1试试,开gradient checkpointing能省不少显存。
7B模型用两张3090跑batch size 4确实有点大,试试gradient checkpointing加batch size设为1,4bit量化也别忘了调低load_in_4bit的参数。