一、概念
模型并行是深度学习中用于训练大型神经网络的一种并行计算策略。它的目的是将一个大型模型的不同部分分布到多个GPU或计算设备上,从而实现模型的高效训练。与数据并行不同,模型并行关注的是模型的大小,特别是当模型太大而无法在单个GPU上容纳时。
模型并行的原理是人为地将模型的层或子网络分配到不同的GPU上,每个GPU负责模型的一部分计算,并通过通信接口(如PCIe或InfiniBand)交换中间结果。这样可以使得每个GPU只存储和处理模型的一部分参数,从而允许训练更大的模型。
二、基本流程
1、模型分割
将大型模型 分割成多个子模型或层,这些子模型或层可以并行处理。
2、设备分配
将分割后的模型部分分配到不同的GPU上。这通常基于模型的结构和计算需求。
3、前向传播
在前向传播过程中,数据在不同的GPU间传递,每个GPU计算其分配到的部分的输出。
4、梯度计算
在反向传播过程中,梯度需要在各个GPU间传递,以便每个部分的梯度可以被计算并用于更新模型参数。
5、梯度同步
为了确保模型参数的一致性,梯度需要在所有GPU上同步。
6、参数更新
根据聚合后的梯度更新模型参数。
三、python 实现
这里,我们构建一个非常简单的示例,用于帮助读者理解。我们首先构造一个6层的MLP,每一层隐藏层的参数量均为1024;接着,将这个MLP的前3层放入0号GPU,后三层放入1号GPU,并使用sklearn的wine数据集进行训练和验证。
1、导入必要的库
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
# 检查是否有可用的GPU
device0 = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
device1 = torch.device("cuda:1" if torch.cuda.device_count() > 1 else "cpu")2、数据集处理
加载wine数据集并进行训练集和测试集的划分,标准化数据之后转化为torch.tensor。
# 加载和预处理数据 wine = load_wine() X = wine.data y = wine.target # 标准化特征 scaler = StandardScaler() X = scaler.fit_transform(X) # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 转换为PyTorch张量 X_train = torch.tensor(X_train, dtype=torch.float32) y_train = torch.tensor(y_train, dtype=torch.long) X_test = torch.tensor(X_test, dtype=torch.float32) y_test = torch.tensor(y_test, dtype=torch.long)
3、神经网络构建
这里要注意一个细节,我们在前向传播函数forward中定义数据的动向,从而使得同一份数据可以在多个GPU之间共享。
# 定义神经网络
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(13, 1024)
self.fc2 = nn.Linear(1024, 1024)
self.fc3 = nn.Linear(1024, 1024)
self.fc4 = nn.Linear(1024, 1024)
self.fc5 = nn.Linear(1024, 1024)
self.fc6 = nn.Linear(1024, 3)
def forward(self, x):
# 首先将数据传入GPU0,在前三层网络上进行前向传播
x = x.to(device0)
x = torch.relu(self.fc1(x.to(device0)))
x = torch.relu(self.fc2(x))
x = torch.relu(self.fc3(x))
# 再将数据传入GPU1,完成后三层网络的前向传播
x = x.to(device1)
x = torch.relu(self.fc4(x))
x = torch.relu(self.fc5(x))
x = self.fc6(x)
return x下面实例化模型并将各层加载到对应的目标设备中:
# 创建模型实例 model = Net() # 将前3层放入GPU0,后3层放入GPU1 model.fc1.to(device0) model.fc2.to(device0) model.fc3.to(device0) model.fc4.to(device1) model.fc5.to(device1) model.fc6.to(device1)
4、模型训练
下面,我们训练模型。需要注意的是,真实标签y_train应当传入最后一层所在的GPU,这样才能正确计算loss。在调用loss.backward()函数之后,模型会将损失按照前向传播的路径反过来自动进行反向传播,不需要我们额外进行操作。
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 训练模型
num_epochs = 50
for epoch in range(num_epochs):
model.train()
optimizer.zero_grad()
outputs = model(X_train)
loss = criterion(outputs, y_train.to(device1))
loss.backward()
optimizer.step()
if (epoch+1) % 10 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')5、评估模型
完成训练之后,进行模型性能的评估,这个例子中所有指标的结果都是1,毕竟数据太简单,模型太复杂。
# 测试模型
model.eval()
with torch.no_grad():
outputs = model(X_test)
_, predicted = torch.max(outputs.data, 1)
accuracy = accuracy_score(y_test.cpu(), predicted.cpu())
precision = precision_score(y_test.cpu(), predicted.cpu(), average='weighted')
recall = recall_score(y_test.cpu(), predicted.cpu(), average='weighted')
f1 = f1_score(y_test.cpu(), predicted.cpu(), average='weighted')
print(f'Accuracy: {accuracy:.4f}')
print(f'Precision: {precision:.4f}')
print(f'Recall: {recall:.4f}')
print(f'F1 Score: {f1:.4f}')四、总结
通过上面的实践,相信大家对于模型并行已经有了深刻的了解了。即便真的需要微调超大模型,使用类似的途径也能够易如反掌。当然,上面的实现方式可拓展性不强,且实现过于简单,通过使用torch.nn.parallel.DistributedDataParallel等工具可以实现更为高效的模型并行,且大大提升GPU之间的通信效率,这些内容我们后面再深入探究。
版权声明:本文为CSDN博主「CM莫问」的原创文章,
遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ChaneMo/article/details/144357450





