PyTorch多GPU内存不足:如何将4块GPU等效为2块大内存GPU?
实现方案:将4块GPU模拟为2块大显存GPU
你想要的其实是把传统的数据并行(DataParallel)切换成「模型并行+数据并行」的混合模式——让单个样本的计算跨两块GPU分摊显存压力,同时用4块GPU承载2个样本的训练,刚好对应你说的「把4块GPU当2块大显存GPU用」的需求。下面给你几个可行的实现方案:
方案1:手动拆分模型+分组数据并行(灵活可控)
思路
把你的大模型拆成两个独立的部分(比如前半部分和后半部分,或者编码器/解码器),分别分配到两组GPU上(第一组用cuda:0+cuda:1,第二组用cuda:2+cuda:3)。每个样本的输入先在组内第一块GPU跑模型前半部分,再把中间结果迁移到组内第二块GPU跑后半部分;两个样本分别在两组GPU上同时计算,最终合并损失反向传播。
代码示例
import torch import torch.nn as nn from torch.utils.data import DataLoader # 定义可拆分的大模型 class BigModel(nn.Module): def __init__(self): super().__init__() # 模型前半部分:占显存较多的层可以放这里 self.part1 = nn.Sequential( nn.Linear(1024, 4096), nn.ReLU(), nn.Linear(4096, 8192) ) # 模型后半部分 self.part2 = nn.Sequential( nn.Linear(8192, 4096), nn.ReLU(), nn.Linear(4096, 10) ) # 初始化模型并分配到两组GPU model = BigModel() # 第一组GPU:cuda0跑前半,cuda1跑后半 model.part1.to('cuda:0') model.part2.to('cuda:1') # 第二组GPU:cuda2跑前半,cuda3跑后半 model_part1_group2 = model.part1.to('cuda:2') model_part2_group2 = model.part2.to('cuda:3') # 数据加载:batch size设为2,刚好对应两组GPU dataloader = DataLoader(your_dataset, batch_size=2, shuffle=True) # 优化器要包含所有GPU上的模型参数 optimizer = torch.optim.Adam( list(model.parameters()) + list(model_part1_group2.parameters()) + list(model_part2_group2.parameters()), lr=1e-4 ) # 训练循环 for inputs, labels in dataloader: optimizer.zero_grad() # 处理第一个样本(用cuda0+cuda1) input_sample0 = inputs[0:1].to('cuda:0') mid_output0 = model.part1(input_sample0) mid_output0 = mid_output0.to('cuda:1') # 迁移到第二块GPU final_output0 = model.part2(mid_output0) loss0 = nn.CrossEntropyLoss()(final_output0, labels[0:1].to('cuda:1')) # 处理第二个样本(用cuda2+cuda3) input_sample1 = inputs[1:2].to('cuda:2') mid_output1 = model_part1_group2(input_sample1) mid_output1 = mid_output1.to('cuda:3') final_output1 = model_part2_group2(mid_output1) loss1 = nn.CrossEntropyLoss()(final_output1, labels[1:2].to('cuda:3')) # 合并损失并反向传播 total_loss = loss0 + loss1 total_loss.backward() optimizer.step()
优缺点
- ✅ 优点:完全手动控制,灵活度高,不需要依赖额外库
- ❌ 缺点:需要手动拆分模型和管理张量设备迁移,模型结构复杂时代码会很繁琐
方案2:使用PyTorch流水线并行(适合分阶段的模型)
思路
如果你的模型有清晰的阶段划分(比如Transformer的多层编码器),可以用PyTorch的Pipe模块实现流水线并行:把模型拆成多个stage,每个stage分配到一块GPU,然后让batch里的样本以流水线的方式在不同GPU上依次计算,这样单个样本的计算会跨多块GPU,自然分摊显存。
代码示例
import torch import torch.nn as nn from torch.distributed.pipeline_sync import Pipe import torch.distributed as dist # 初始化分布式环境(需要用torchrun启动脚本) dist.init_process_group(backend='nccl') # 拆分模型为多个stage class Stage1(nn.Module): def __init__(self): super().__init__() self.layers = nn.Sequential(nn.Linear(1024, 4096), nn.ReLU()) class Stage2(nn.Module): def __init__(self): super().__init__() self.layers = nn.Sequential(nn.Linear(4096, 8192), nn.ReLU()) class Stage3(nn.Module): def __init__(self): super().__init__() self.layers = nn.Sequential(nn.Linear(8192, 4096), nn.ReLU()) class Stage4(nn.Module): def __init__(self): super().__init__() self.layers = nn.Sequential(nn.Linear(4096, 10)) # 构建流水线模型:4个stage分别分配到4块GPU,chunks=2表示把batch拆成2个微批量 model = nn.Sequential(Stage1(), Stage2(), Stage3(), Stage4()) model = Pipe(model, chunks=2, devices=[0,1,2,3]) # 数据加载:batch size设为2 dataloader = DataLoader(your_dataset, batch_size=2, shuffle=True) optimizer = torch.optim.Adam(model.parameters(), lr=1e-4) # 训练循环 for inputs, labels in dataloader: optimizer.zero_grad() inputs = inputs.to(model.devices[0]) labels = labels.to(model.devices[-1]) # Pipe会自动处理跨GPU的张量迁移和流水线调度 outputs = model(inputs).local_value() loss = nn.CrossEntropyLoss()(outputs, labels) loss.backward() optimizer.step()
注意事项
- 需要用
torchrun启动脚本(比如torchrun --nproc_per_node=4 your_script.py) - 适合Transformer、CNN等有明显阶段划分的模型
优缺点
- ✅ 优点:自动管理跨GPU调度,代码简洁,适合分阶段模型
- ❌ 缺点:需要初始化分布式环境,流水线调度会带来少量额外开销
方案3:使用DeepSpeed ZeRO(显存利用率最高)
思路
如果你不想手动拆分模型,追求最高的显存利用率,可以用DeepSpeed的ZeRO优化。ZeRO会把模型的参数、梯度、优化器状态自动分片到多个GPU上,让每个GPU只持有部分数据,相当于把多块GPU的显存合并成一块“虚拟大显存”用,完美匹配你的需求。
代码示例
第一步:创建DeepSpeed配置文件(ds_config.json)
{ "train_batch_size": 2, "gradient_accumulation_steps": 1, "optimizer": { "type": "Adam", "params": { "lr": 1e-4 } }, "zero_optimization": { "stage": 3, # ZeRO-3是最高粒度的分片,显存利用率最高 "allgather_partitions": true, "allgather_bucket_size": 1e8, "overlap_comm": true, "reduce_scatter": true, "reduce_bucket_size": 1e8, "contiguous_gradients": true }, "fp16": { "enabled": true # 可选:开启混合精度进一步节省显存 } }
第二步:训练代码
import torch import torch.nn as nn from torch.utils.data import DataLoader import deepspeed # 定义你的大模型(不需要手动拆分) class BigModel(nn.Module): def __init__(self): super().__init__() self.layers = nn.Sequential( nn.Linear(1024, 4096), nn.ReLU(), nn.Linear(4096, 8192), nn.ReLU(), nn.Linear(8192, 4096), nn.ReLU(), nn.Linear(4096, 10) ) model = BigModel() dataloader = DataLoader(your_dataset, batch_size=2, shuffle=True) # 初始化DeepSpeed model, optimizer, _, _ = deepspeed.initialize( model=model, config='ds_config.json', model_parameters=model.parameters() ) # 训练循环 for inputs, labels in dataloader: inputs = inputs.to(model.device) labels = labels.to(model.device) outputs = model(inputs) loss = nn.CrossEntropyLoss()(outputs, labels) # DeepSpeed自动处理反向传播和参数更新 model.backward(loss) optimizer.step()
启动方式
用DeepSpeed启动脚本:
deepspeed --num_gpus=4 your_script.py
优缺点
- ✅ 优点:几乎不需要修改模型代码,自动最大化显存利用率,支持超大模型
- ❌ 缺点:需要安装DeepSpeed库,配置项较多,适合有一定分布式训练基础的用户
内容的提问来源于stack exchange,提问作者yliats
相关产品推荐
相关产品推荐

