马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
一、题目概述
1.题目分析
CUDA out of memory题目通常发生在深度学习训练过程中,当GPU的显存不足以容纳模子、输入数据以及中央盘算结果时就会触发。这个题目大概由几个因素引起:
- 模子和数据规模:深度学习模子尤其是大型模子,如Transformer或大型CNN,拥有大量的参数,这些参数在训练时必要被加载到GPU显存中。同时,假如批量巨细(batch size)设置得过大,一次性处置惩罚的数据量也会增长,进一步加大显存的负担。
- 内存管理:深度学习框架在处置惩罚数据和模子时,大概会因为不妥的内存管理而导致显存没有被实时开释,大概由于频仍的内存分配和开释造成内存碎片化,这会镌汰可用的一连内存块,纵然总显存利用量没有到达100%,也大概出现内存不足的情况。
- 数据加载和预处置惩罚:假如数据加载和预处置惩罚步调没有优化,比如加载了大量未被立纵然用的数据,大概数据没有被适当地压缩或降维,也大概导致显存利用量激增。
2.报错代码
3.示例代码
这里提供一个简朴的深度学习示例代码,利用PyTorch框架来实现一个简朴的多层感知器(MLP)模子,用于分类MNIST数据会合的手写数字。MNIST是一个包罗60,000个训练样本和10,000个测试样本的数据库,每个样本都是一个28x28的灰度图像,代表一个手写数字(0到9)。- import torch
- import torch.nn as nn
- import torch.nn.functional as F
- import torch.optim as optim
- from torchvision import datasets, transforms
- from torch.utils.data import DataLoader
- # 定义一个简单的多层感知器模型
- class SimpleMLP(nn.Module):
- def __init__(self):
- super(SimpleMLP, self).__init__()
- self.fc1 = nn.Linear(28 * 28, 512) # 输入层到隐藏层
- self.fc2 = nn.Linear(512, 256) # 隐藏层到隐藏层
- self.fc3 = nn.Linear(256, 10) # 隐藏层到输出层
- def forward(self, x):
- x = x.view(-1, 28 * 28) # 展平图像
- x = F.relu(self.fc1(x)) # 第一个隐藏层
- x = F.relu(self.fc2(x)) # 第二个隐藏层
- x = self.fc3(x) # 输出层
- return x
- # 设置数据加载器
- transform = transforms.Compose([
- transforms.ToTensor(), # 将图片转换为Tensor
- transforms.Normalize((0.5,), (0.5,)) # 归一化
- ])
- train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
- test_dataset = datasets.MNIST(root='./data', train=False, transform=transform)
- train_loader = DataLoader(dataset=train_dataset, batch_size=32, shuffle=True)
- test_loader = DataLoader(
复制代码 二、修改计谋
为了办理这些题目,我们可以接纳多种计谋。起首,调解批量巨细是一种简朴有用的方法。通过镌汰每次迭代中处置惩罚的数据量,可以减轻显存压力,但大概会影响模子训练的效率和终极性能。其次,梯度累积技术可以在不改变批量巨细时,通过累积多个小批量的梯度来模仿大批量的效果,如许可以在保持较大有用批量的同时镌汰单次迭代的显存需求。
模子优化也是一个紧张的方向。通过剪枝、量化或知识蒸馏等技术镌汰模子巨细,大概重新筹划模子布局以镌汰参数数量,可以有用地镌汰显存的利用。肴杂精度训练,即在训练过程中利用半精度浮点数(FP16)代替全精度浮点数(FP32),不但可以镌汰显存利用,还可以加速训练过程。在内存管理方面,我们可以在PyTorch中通过删除不再必要的变量并调用torch.cuda.empty_cache()来开释未利用的显存。别的,通过设置情况变量来调解CUDA的内存分配计谋,镌汰内存碎片化,也是一个有用的本事。
末了,分布式训练可以将模子和数据分布到多个GPU上,从而分散显存压力。这种方法固然必要更多的硬件资源,但在处置惩罚特殊大的模子或数据集时,可以显着进步训练的可行性和效率。
1. 减小批量巨细(Batch Size)
减小批量巨细是办理CUDA内存不足的简朴有用方法。以下是代码示例:- import torch
- import torchvision.models as models
- # 尝试较大的批量大小
- model = models.resnet50().cuda() # 将模型放到GPU上
- input = torch.randn(32, 3, 224, 224).cuda() # 大批量的输入数据
- try:
- output = model(input) # 尝试运行模型
- except RuntimeError as e:
- if 'out of memory' in str(e):
- print("CUDA内存不足,尝试减少批量大小...")
- torch.cuda.empty_cache() # 清理缓存
- input = torch.randn(16, 3, 224, 224).cuda() # 减小批量大小后重试
- output = model(input)
复制代码 2. 利用梯度累积(Gradient Accumulation)
通过累积梯度,可以在不增长显存压力的情况下训练更大批量的数据。以下是代码示例:- optimizer.zero_grad()
- for i in range(gradient_accumulation_steps):
- loss = model(input[i]).backward()
- optimizer.step()
复制代码 3. 清理显存
在不必要变量时手动删除,并调用 torch.cuda.empty_cache() 来开释GPU内存。- import torch, gc
- # 清理Python垃圾回收
- gc.collect()
- # 清理PyTorch缓存
- torch.cuda.empty_cache()
复制代码 4. 利用肴杂精度训练(Mixed Precision Training)
通过利用肴杂精度训练,可以镌汰内存的利用。以下是代码示例:- from torch.cuda.amp import GradScaler, autocast
- scaler = GradScaler()
- for data, target in train_loader:
- optimizer.zero_grad()
- with autocast():
- output = model(data)
- loss = criterion(output, target)
- scaler.scale(loss).backward()
- scaler.step(optimizer)
- scaler.update()
复制代码 5. 设置情况变量镌汰内存碎片化
通过设置 PYTORCH_CUDA_ALLOC_CONF 情况变量,调解内存分配计谋,镌汰内存碎片化。- import os
- os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'max_split_size_mb:128'
复制代码 这些代码示例提供了几种差异的方法来办理CUDA内存不足的题目,可以根据具体情况选择符合的计谋。
三、示例修改
这个代码将包罗一些改进,比如利用更小的批量巨细来镌汰显存利用,以及利用梯度累积来模仿更大的批量巨细。别的,我还会添加一些注释来资助明确代码的每个部门。- import torch
- import torch.nn as nn
- import torch.nn.functional as F
- import torch.optim as optim
- from torchvision import datasets, transforms
- from torch.utils.data import DataLoader
- # 定义一个简单的多层感知器模型
- class SimpleMLP(nn.Module):
- def __init__(self):
- super(SimpleMLP, self).__init__()
- self.fc1 = nn.Linear(28 * 28, 512) # 输入层到隐藏层
- self.fc2 = nn.Linear(512, 256) # 隐藏层到隐藏层
- self.fc3 = nn.Linear(256, 10) # 隐藏层到输出层
- def forward(self, x):
- x = x.view(-1, 28 * 28) # 展平图像
- x = F.relu(self.fc1(x)) # 第一个隐藏层
- x = F.relu(self.fc2(x)) # 第二个隐藏层
- x = self.fc3(x) # 输出层
- return x
- # 设置数据加载器
- transform = transforms.Compose([
- transforms.ToTensor(), # 将图片转换为Tensor
- transforms.Normalize((0.5,), (0.5,)) # 归一化
- ])
- train_dataset = datasets.MNIST(root='./data', train=True, transform=transform, download=True)
- test_dataset = datasets.MNIST(root='./data', train=False, transform=transform)
- # 使用更小的批量大小以减少显存使用
- batch_size = 16
- train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
- test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)
- # 初始化模型、损失函数和优化器
- model = SimpleMLP().cuda() # 将模型移到GPU上
- criterion = nn.CrossEntropyLoss()
- optimizer = optim.Adam(model.parameters(), lr=0.001)
- # 梯度累积步骤
- gradient_accumulation_steps = 4
- # 训练模型
- def train_model(model, criterion, optimizer, train_loader, test_loader, epochs=5):
- for epoch in range(epochs):
- model.train() # 设置模型为训练模式
- running_loss = 0.0
- for i, (images, labels) in enumerate(train_loader):
- images, labels = images.cuda(), labels.cuda() # 将数据移到GPU上
- optimizer.zero_grad() # 清空梯度
- # 梯度累积
- for _ in range(gradient_accumulation_steps):
- outputs = model(images) # 前向传播
- loss = criterion(outputs, labels) # 计算损失
- loss = loss / gradient_accumulation_steps # 将损失除以累积步数
- loss.backward() # 反向传播
- optimizer.step() # 更新权重
- running_loss += loss.item() * gradient_accumulation_steps
- print(f'Epoch {epoch+1}, Loss: {running_loss/len(train_loader)}')
- # 测试模型
- model.eval() # 设置模型为评估模式
- correct = 0
- total = 0
- with torch.no_grad(): # 在测试阶段不计算梯度
- for images, labels in test_loader:
- images, labels = images.cuda(), labels.cuda() # 将数据移到GPU上
- outputs = model(images)
- _, predicted = torch.max(outputs.data, 1)
- total += labels.size(0)
- correct += (predicted == labels).sum().item()
- print(f'Accuracy of the network on the test images: {100 * correct / total}%')
- # 运行训练
- train_model(model, criterion, optimizer, train_loader, test_loader, epochs=5)
复制代码 在这个修改后的代码中,做了以下几点改进:
- 减小批量巨细:将批量巨细从32镌汰到16,以镌汰每次迭代中GPU必要处置惩罚的数据量。
- 梯度累积:通过设置gradient_accumulation_steps为4,我们可以在不增长显存压力的情况下,通过累积梯度来模仿更大的批量巨细。
- 数据和模子移到GPU:在训练和测试阶段,将数据和模子移到GPU上,以利用GPU的盘算能力。
这些改进可以资助镌汰显存的利用,同时保持模子训练的效率和性能。
|