1. 张量并行基础
任务目标
实现一个线性层(不带bias)的按列切分(Column Parallel)版本,将权重矩阵沿输出维度切分为两块,分别部署到两个GPU上。前向时两个GPU各自计算部分输出后拼接得到完整输出;反向时梯度按对应切片回传。最终通过数值对比验证前向与反向结果与单卡完整线性层一致。
环境与依赖
- Python 3.8+,PyTorch 2.0+(需支持
torch.distributed) - 两个GPU(或单机多卡),可通过
torchrun启动
核心实现
import torch
import torch.nn as nn
import torch.distributed as dist
class ColumnParallelLinear(nn.Module):
"""按列切分线性层:每个rank持有 weight[:, local_out_start:local_out_end]"""
def __init__(self, in_features, out_features, rank, world_size):
super().__init__()
self.in_features = in_features
self.out_features = out_features
self.rank = rank
self.world_size = world_size
# 每个rank的本地输出维度
assert out_features % world_size == 0, "out_features必须能被world_size整除"
self.local_out_features = out_features // world_size
# 本地权重切片,形状: [in_features, local_out_features]
self.weight = nn.Parameter(torch.randn(in_features, self.local_out_features) * 0.1)
def forward(self, x):
# x: [batch, in_features]
local_out = torch.matmul(x, self.weight) # [batch, local_out_features]
return local_out
def run_worker(rank, world_size):
dist.init_process_group("nccl", rank=rank, world_size=world_size)
torch.cuda.set_device(rank)
batch, in_f, out_f = 4, 8, 6
torch.manual_seed(42)
# 完整权重(仅用于验证,实际并行时不存储)
full_weight = torch.randn(in_f, out_f) * 0.1
# 按列切分
local_out_size = out_f // world_size
local_weight = full_weight[:, rank*local_out_size : (rank+1)*local_out_size].cuda()
# 构建并行层并同步本地权重
layer = ColumnParallelLinear(in_f, out_f, rank, world_size).cuda()
with torch.no_grad():
layer.weight.copy_(local_weight)
# 输入(所有rank相同)
x = torch.randn(batch, in_f).cuda()
# 前向
local_out = layer(x) # [batch, local_out_features]
# 收集所有rank的local_out并拼接
gathered = [torch.zeros_like(local_out) for _ in range(world_size)]
dist.all_gather(gathered, local_out)
if rank == 0:
full_out = torch.cat(gathered, dim=1) # [batch, out_f]
ref_out = torch.matmul(x.cpu(), full_weight) # 单卡参考
assert torch.allclose(full_out.cpu(), ref_out, atol=1e-5), "前向结果不一致"
print("前向验证通过")
# 反向:构造随机梯度并验证
grad_out = torch.randn(batch, out_f).cuda()
local_grad = grad_out[:, rank*local_out_size : (rank+1)*local_out_size]
# 并行层反向
layer.zero_grad()
local_out.backward(local_grad)
local_grad_w = layer.weight.grad # [in_f, local_out_features]
# 参考反向
ref_grad_w = torch.matmul(x.cpu().T, grad_out.cpu()) # [in_f, out_f]
ref_local_grad_w = ref_grad_w[:, rank*local_out_size : (rank+1)*local_out_size]
assert torch.allclose(local_grad_w.cpu(), ref_local_grad_w, atol=1e-5), "反向梯度不一致"
if rank == 0:
print("反向验证通过")
dist.destroy_process_group()
if __name__ == "__main__":
world_size = 2
torch.multiprocessing.spawn(run_worker, args=(world_size,), nprocs=world_size)
运行方式
torchrun --nproc_per_node=2 column_parallel_linear.py
验证方法与判定标准
- 前向验证:rank 0 将两个GPU的局部输出按列拼接后,与单卡完整权重计算结果对比,误差小于
1e-5即通过。 - 反向验证:每个rank将本地梯度与参考梯度对应切片对比,误差小于
1e-5即通过。 - 程序正常结束且打印“前向验证通过”和“反向验证通过”即视为测试通过。
关键说明
- 本实现假设
out_features能被world_size整除,且未包含bias以聚焦核心切分逻辑。 - 实际工程中需在
forward后调用AllGather获取完整输出,本案例为验证正确性在rank 0上执行拼接对比。 - 反向传播时,PyTorch自动计算本地权重的梯度,无需额外通信;若需对输入求梯度,则需在反向时执行
AllReduce,本案例未涉及。