惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
MyScale Blog
MyScale Blog
Recent Announcements
Recent Announcements
N
Netflix TechBlog - Medium
GbyAI
GbyAI
Vercel News
Vercel News
The GitHub Blog
The GitHub Blog
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
Visual Studio Blog
Martin Fowler
Martin Fowler
腾讯CDC
大猫的无限游戏
大猫的无限游戏
aimingoo的专栏
aimingoo的专栏
云风的 BLOG
云风的 BLOG
J
Java Code Geeks
WordPress大学
WordPress大学
P
Proofpoint News Feed
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
有赞技术团队
有赞技术团队
人人都是产品经理
人人都是产品经理
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Y
Y Combinator Blog

博客园 - xiezhengcai

日志分层设计 Ai阅读代码的局限性 业务系统缓存加速的五种缓存设计 The CustomResourceDefinition "applications.apps.abc.com" is invalid: metadata.annotations: Too long: must have at most 262144 bytes vscode ssh开发无法读取go env配置 deepspeed ray集群 ray 分布式, 工作节点、reylet、 主节点、GCS ray分布式 nccl & mpi 跨节点通信 cudaMemsetParams 参数详细 cuda 中的内存拷贝 cuda 编程 cuda vllm 参数分类 张量计算流程图标注方法 为什么nn.Linear 的weight 是 (out_features, in_features) torch.view、unsqueeze、reshape、transpose 和 permute PyTorch交叉熵损失函数详解
ray + nccl + 张量并行 训练
xiezhengcai · 2025-05-23 · via 博客园 - xiezhengcai

数据并行

首先要有一个分布式的环境, 比如ray
有一个集合通讯的后端 比如nccl
将模型初始化为DDP数据并行模型,并指定全局rank
数据使分布式采样
进行模型训练
假如ray集群有两个节点,主节点ip=10.230.40.150 , ray集群集群启动时,每个节点分配一张显卡,

import os
os.environ["RAY_DEDUP_LOGS"] = "0"
import torch
import torch.distributed as dist
import torch.nn as nn
import torch.optim as optim
from torch.nn.parallel import DistributedDataParallel as DDP
from torch.utils.data import DataLoader, DistributedSampler, TensorDataset
import ray
import time

def init_distributed_mode(rank, world_size):
    print("init start")
    device = torch.device('cuda')
    dist.init_process_group(
        backend='nccl', 
        init_method='tcp://10.230.40.150:23456', 
        world_size=world_size, 
        rank=rank
    )
    print("init end")
    return device

class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.fc = nn.Linear(10, 1)

    def forward(self, x):
        return self.fc(x)

def setup_model(rank, device):
    model = SimpleModel().to(device)
    return DDP(model, device_ids=[device])

def setup_dataloader(rank, world_size, batch_size=32):
    dataset = TensorDataset(torch.randn(1000, 10), torch.randn(1000, 1))
    sampler = DistributedSampler(dataset, num_replicas=world_size, rank=rank)
    dataloader = DataLoader(dataset, batch_size=batch_size, sampler=sampler)
    return dataloader

@ray.remote(num_gpus=1) # 确保每个任务分配GPU
def train(rank, world_size):
    device = init_distributed_mode(rank, world_size)
    model = setup_model(rank, device)
    dataloader = setup_dataloader(rank, world_size)

    optimizer = optim.SGD(model.parameters(), lr=0.01)
    loss_fn = nn.MSELoss()

    for epoch in range(5):
        time.sleep(60)
        for inputs, targets in dataloader:
            inputs, targets = inputs.to(device), targets.to(device)
            optimizer.zero_grad()
            outputs = model(inputs)
            loss = loss_fn(outputs, targets)
            loss.backward()
            optimizer.step()
        print(f"Rank {rank}, Epoch {epoch + 1}, Loss: {loss.item()}")

    dist.destroy_process_group()

if __name__ == "__main__":
    world_size = 2
    ray.init(address="auto")  
    remote_funcs = [train.remote(rank, world_size) for rank in range(world_size)]
    ray.get(remote_funcs)
    ray.shutdown()

如果world_size设置为4,

  • 只有 2 个任务能获得 GPU 并启动,另外 2 个会​​无限等待​​(因为 num_gpus=1 是硬性要求)。
  • 已启动的 2 个任务会因 NCCL 等待其他 rank(world_size=4)而​​卡在 dist.init_process_group()​​。
(train pid=63009) init start
(train pid=37071, ip=10.230.3.42) init start
(train pid=37071, ip=10.230.3.42) init end

然后直到 出现错误: torch.distributed.DistBackendError: [1] is setting up NCCL communicator and retrieving ncclUniqueId from [0] via c10d key-value store by key '0', but store->get('0') got error: Socket Timeout

张量并行推理

如果主节点 在tcp://10.230.20.229:23456上

import os
os.environ["RAY_DEDUP_LOGS"] = "0"
import ray
import torch
import torch.distributed as dist
import torch.nn as nn
import torch.nn.functional as F
import time


class ColumnParallelLinear(nn.Module):
    def __init__(self, input_size, output_size, rank, world_size, is_gather = False):
        super(ColumnParallelLinear, self).__init__()
        self.world_size = world_size if world_size is not None else dist.get_world_size()
        self.rank = rank
        self.input_size = input_size
        self.output_size = output_size
        self.local_output_size = output_size // self.world_size
        self.weight = nn.Parameter(torch.empty(self.local_output_size, input_size))
        self.bias = nn.Parameter(torch.empty(self.local_output_size))
        self.is_gather = is_gather

    def init_weight(self, offset):
        with torch.no_grad():
            weight_value = torch.arange(0, self.weight.numel() * self.world_size).reshape(self.output_size, self.input_size)[self.rank * self.local_output_size : (self.rank + 1) * self.local_output_size, :]
            self.weight.copy_(weight_value)
            bias_value = torch.arange(0, self.bias.numel() * self.world_size)[self.rank * self.local_output_size : (self.rank + 1) * self.local_output_size]
            self.bias.copy_(bias_value)
            self.weight += offset
            self.bias += offset

    def forward(self, x):
        output_ = F.linear(x, self.weight) + self.bias
        if self.is_gather:
            ouptut = [torch.zeros_like(output_) for _ in range(self.world_size)]
            dist.all_gather(ouptut, output_)
            ouptut = torch.cat(ouptut, dim = -1)
        else:
            output = output_
        return output



class RowParallelLinear(nn.Module):
    def __init__(self, input_size, output_size, rank, world_size, is_reduce = True):
        super(RowParallelLinear, self).__init__()
        self.world_size = world_size if world_size is not None else dist.get_world_size()
        self.rank = rank
        self.input_size = input_size
        self.output_size = output_size
        self.local_input_size = input_size // self.world_size
        self.weight = nn.Parameter(torch.empty(output_size, self.local_input_size))
        self.bias = nn.Parameter(torch.empty(output_size))
        self.is_reduce = is_reduce
    
    def init_weight(self, offset):
        with torch.no_grad():
            weight_values = torch.arange(0, self.weight.numel() * self.world_size).reshape(self.output_size, self.input_size)[:, self.rank * self.local_input_size : (self.rank+1) * self.local_input_size]
            self.weight.copy_(weight_values)
            bias_value = torch.arange(0, self.bias.numel()).float()
            self.bias.copy_(bias_value)
            self.weight += offset
            self.bias += offset
        
    def forward(self, x):
        output_ = F.linear(x, self.weight)
        if self.is_reduce:
            dist.all_reduce(output_, op = dist.ReduceOp.SUM)
        output = output_ + self.bias
        return output



class MultiHeadAttentionTP(nn.Module):
    def __init__(self, embed_dim, num_heads, bias, rank, world_size): 
        super().__init__()
        self.embed_dim = embed_dim
        self.total_num_heads = num_heads
        self.node_num_heads = self.total_num_heads // world_size
        self.node_embed_dim = self.embed_dim // world_size
        self.head_dim = self.embed_dim // self.total_num_heads
        self.scalling = self.head_dim ** -0.5
        self.q_proj = ColumnParallelLinear(self.embed_dim, self.embed_dim, rank, world_size, False)
        self.k_proj = ColumnParallelLinear(self.embed_dim, self.embed_dim, rank, world_size, False)
        self.v_proj = ColumnParallelLinear(self.embed_dim, self.embed_dim, rank, world_size, False)
        self.out_proj = RowParallelLinear(embed_dim, embed_dim, rank, world_size, True)
        self.init_weight()

    def init_weight(self):
        self.q_proj.init_weight(0.1)
        self.k_proj.init_weight(-0.2)
        self.v_proj.init_weight(0.3)
        self.out_proj.init_weight(-0.4)



    # q, k, v: [num_seq,  node_num_heads, seq_len, head_dim]def attention(self, q, k, v):
    def attention(self,q, k, v):
        scores = torch.matmul(q, k.transpose(-2, -1)) * self.scalling # [num_seq,  node_num_heads, seq_len, seq_len]
        attn_weights = torch.nn.functional.softmax(scores, dim = -1) # [num_seq,  node_num_heads, seq_len, seq_len]
        attn_output = torch.matmul(attn_weights, v) # [num_seq,  node_num_heads, seq_len, head_dim]return attn_output
        return attn_output

    
    def forward(self, hidden_state):
        num_seq, seq_len, embed_dim = hidden_state.size() # hidden_state : [num_seq, seq_len, embed_dim]
        q = self.q_proj(hidden_state) # [num_seq, seq_len, node_embed_dim], embed_dim // world_size = node_num_heads * head_dim = node_embed_dim
        k = self.k_proj(hidden_state) # [num_seq, seq_len, node_embed_dim], embed_dim // world_size = node_num_heads * head_dim = node_embed_dim
        v = self.v_proj(hidden_state) # [num_seq, seq_len, node_embed_dim], embed_dim // world_size = node_num_heads * head_dim = node_embed_dim

        q = q.view(num_seq, seq_len, self.node_num_heads, self.head_dim).transpose(1,2) # [num_seq, node_num_heads, seq_len,  head_dim]
        k = k.view(num_seq, seq_len, self.node_num_heads, self.head_dim).transpose(1,2) # [num_seq, node_num_heads, seq_len,  head_dim]
        v = v.view(num_seq, seq_len, self.node_num_heads, self.head_dim).transpose(1,2)  # [num_seq, node_num_heads, seq_len,  head_dim]

        attn_output = self.attention(q, k, v) # [num_seq,  node_num_heads, seq_len, head_dim]

        attn_output = attn_output.transpose(1,2).contiguous().view(num_seq, seq_len, self.node_embed_dim)
        output = self.out_proj(attn_output)
        return output
    
world_size = 2
def init_distributed_mode(rank):
    print("init start")
    device = torch.device("cuda")
    dist.init_process_group(
        backend='nccl', 
        init_method='tcp://10.230.20.229:23456', 
        world_size=world_size, 
        rank=rank
    )
    print("init end")
    return device

class SimpleModel(nn.Module):
    def __init__(self, rank, world_size):
        super(SimpleModel, self).__init__()
        self.attn = MultiHeadAttentionTP(512 , 8 , True, rank, world_size)
    
    def forward(self, x):
        out = self.attn(x)
        return out
    
@ray.remote(num_gpus=1)
def example(rank):
    time.sleep(5*60)
    device = init_distributed_mode(rank)
    with torch.no_grad():
        model = SimpleModel(rank, world_size).to(device).eval()
        if rank == 0:
            torch.manual_seed(42)
            x = torch.randn(128, 8, 512 ).to(device)
        else:
            x = torch.empty(128, 8, 512 ).to(device)
        dist.broadcast(x, src = 0)
        out = model(x)
        print(out.mean())
    dist.destroy_process_group()

if __name__ == "__main__":
    ray.init(address = "auto")
    remotes = [example.remote(rank) for rank in range(world_size)]
    ray.get(remotes)
    # 清理
    ray.shutdown()