2. 张量并行基础
任务目标
实现线性层(Y = X @ W^T + b)的按行切分张量并行版本。将权重矩阵 W 按行切分为两块,分别放置在两个设备上,每个设备独立完成部分矩阵乘法后,通过 AllReduce 求和得到与单卡完全一致的输出。
核心原理
按行切分时,W 被切分为 [W1; W2](上下两块),输入 X 需完整复制到每个设备。每个设备计算 Y_i = X @ W_i^T,得到的是输出特征维度的部分和(各设备输出维度不同),因此需要 AllReduce 将各设备的输出在特征维度上拼接(此处为求和后拼接,等价于 AllReduce Sum)。
环境依赖
- Python 3.8+
- PyTorch 2.0+(含
torch.distributed) - 单机多卡(或 CPU 模拟多进程)
完整实现
import os
import torch
import torch.distributed as dist
import torch.nn.functional as F
def setup(rank, world_size):
os.environ['MASTER_ADDR'] = '127.0.0.1'
os.environ['MASTER_PORT'] = '29500'
dist.init_process_group('gloo', rank=rank, world_size=world_size)
def row_parallel_linear(rank, world_size, X, W, b):
"""按行切分线性层: Y = X @ W^T + b"""
# 按行切分权重 (输出维度切分)
d_out = W.size(0)
assert d_out % world_size == 0, "输出维度需能被卡数整除"
local_d_out = d_out // world_size
W_local = W[rank * local_d_out : (rank + 1) * local_d_out].clone()
b_local = b[rank * local_d_out : (rank + 1) * local_d_out].clone()
# 每卡持有完整输入 X,计算本地部分输出
Y_local = X @ W_local.T + b_local # [batch, local_d_out]
# AllReduce Sum: 各卡输出维度不同,需先拼接再求和
# 收集所有卡的部分输出
gathered = [torch.zeros_like(Y_local) for _ in range(world_size)]
dist.all_gather(gathered, Y_local)
Y_full = torch.cat(gathered, dim=1) # [batch, d_out]
return Y_full
def verify(rank, world_size):
setup(rank, world_size)
torch.manual_seed(42)
batch, d_in, d_out = 4, 8, 6
X = torch.randn(batch, d_in)
W = torch.randn(d_out, d_in)
b = torch.randn(d_out)
# 单卡参考结果
Y_ref = X @ W.T + b
# 张量并行结果
Y_parallel = row_parallel_linear(rank, world_size, X, W, b)
# 验证一致性
if rank == 0:
assert torch.allclose(Y_parallel, Y_ref, atol=1e-5), "并行结果与单卡不一致!"
print(f"验证通过: 最大误差 = {(Y_parallel - Y_ref).abs().max().item():.2e}")
dist.destroy_process_group()
if __name__ == "__main__":
world_size = 2
rank = int(os.environ.get('RANK', 0))
verify(rank, world_size)
运行方式
# 单机双卡(或 CPU 多进程模拟)
torchrun --nproc_per_node=2 row_parallel.py
验证方法与判定标准
- 程序正常退出,无报错。
- rank 0 输出
验证通过: 最大误差 = x.xxe-xx,且误差小于1e-5。 - 若将
world_size改为 1 运行,结果与单卡完全一致(自洽性检查)。
关键说明
- 按行切分要求
d_out能被world_size整除,否则需 padding 或改用其他切分策略。 - AllReduce 在此实现中通过
all_gather+cat完成,实际工程中可用AllGather集合通信原语直接实现。 - 输入
X需完整复制到每卡(本实现中由外部传入同一份数据,实际分布式场景需广播或每卡持有相同输入)。