5. PyTorch+NCCL源码编译

系列文章


目录


前言

从源码编译PyTorch和NCCL,可以实现对NCCL源码进行修改以适应特定需求,并应用于实际的分布式训练中,本文基于torch 2.2.1和nccl 2.19.3描述了一个大致过程,并验证了源码更改的有效性。


一、本地环境

  • Ubuntu 22.04.4 LTS (GNU/Linux 6.5.0-35-generic x86_64)
  • cuda 11.8+ cudnn 8
  • python 3.11
  • torch v2.2.1+ nccl v2.19.3
  • NVIDIA GeForce RTX 4090 *2

二、安装cudnn

下载cudnn包之后打开

bash 复制代码
cd cudnn-linux-x86_64-8.9.7.29_cuda11-archive
# 复制到指定目录
sudo cp ./include/cudnn*.h /usr/local/cuda/include
sudo cp ./lib/libcudnn* /usr/local/cuda/lib64

chmod a+r /usr/local/cuda/include/cudnn*.h
chmod a+r /usr/local/cuda/lib64/libcudnn*

确认已经安装cudnn,除了cudnn_version.h,务必检查同目录下也有cudnn_ops_infer.h文件

bash 复制代码
cat /usr/local/cuda/include/cudnn_version.h | grep CUDNN_MAJOR -A 2

可以看到对应cudnn版本为8.9.7

三、使用pytorch自带NCCL库进行编译安装

1. 源码编译

使用 python setup.py 命令进行源码编译,develop 命令通常在开发过程中使用,以在"开发模式"中安装包,其中对源代码的更改会立即生效而无需重新安装。develop更改为install 就是直接安装。

bash 复制代码
# 新建conda虚拟环境,取名为nccl2
conda create -n nccl2 python=3.11
conda activate nccl2

#下载v2.2.1 源码
git clone --branch v2.2.1 --recursive https://github.com/pytorch/pytorch
cd pytorch      # v2.2.1 

# 安装依赖包
pip install -r requirements.txt

#以开发模式安装torch,不使用系统nccl,而是torch自带的,位于third party目录下 
MAX_JOBS=32 USE_CUDA=1 USE_NCCL=1 USE_SYSTEM_NCCL=0 python setup.py develop
  • 如下图所示即为开始编译:
  • 中途报错如下:貌似是numpy相关的错误
  • pip show numpy | grep Version查看numpy 版本,为2.0.0
  • 估计是numpy版本太新,导致一些变量名更改,=> 对numpy降级,实测1.26.3 可行, 之后make clean ,再重新编译
  • 源码编译的过程可能比较久,编译成功后提示如下,说明已经成功安装torch

2. 查看版本和all_reduce测试

编译完毕,测试能否用torch,cuda,nccl以及识别出GPU。这里新建了一个version.py

python 复制代码
# version.py
import torch

print("torch version",torch.__version__)
print(torch.cuda.is_available(), torch.distributed.is_nccl_available())
print("nccl version:",torch.cuda.nccl.version())
print("cuda version:", torch.version.cuda)       
        
cudnn_version = torch.backends.cudnn.version()
print("cuDNN version:", cudnn_version)
print(torch.cuda.device_count(), torch.cuda.get_device_name(0))

结果如下,可以看到troch和nccl的版本,检测到双卡等。

执行以下代码,新建test.py, 使用 nccl 作为通信后端,在一个gpu上测试分布式训练中张量的 all_reduce 操作。

python 复制代码
#test.py

import os
import torch
import torch.distributed as dist

os.environ['MASTER_ADDR'] = 'localhost'
os.environ['MASTER_PORT'] = '29500'
dist.init_process_group("nccl", rank=0, world_size=1)
x = torch.ones(6)

if torch.cuda.is_available():
    y = x.cuda()
    dist.all_reduce(y)
    print(f"cuda allreduce result: {y}")   

结果如下:

四、 修改NCCL源代码并重新编译后测试,体现出源码更改

修改 pytorch-2.2.1/third_party/nccl/nccl/src/collectives.cc 文件后,重新编译,

原代码如下,文件内包含了all_gather,all_reduce等各个集合通信操作,29行开始是All_Reduce的相关定义

cpp 复制代码
/*************************************************************************
 * Copyright (c) 2015-2023, NVIDIA CORPORATION. All rights reserved.
 *
 * See LICENSE.txt for license information
 ************************************************************************/

#include "argcheck.h" // Need some checks here since we access comm
#include "collectives.h"
#include "enqueue.h"
#include "nccl.h"

NCCL_API(ncclResult_t, ncclAllGather, const void* sendbuff, void* recvbuff, size_t sendcount,
    ncclDataType_t datatype, ncclComm_t comm, cudaStream_t stream);
ncclResult_t ncclAllGather(const void* sendbuff, void* recvbuff, size_t sendcount,
    ncclDataType_t datatype, ncclComm_t comm, cudaStream_t stream) {
  // Just pass the size of one message and not the total bytes sent/received.
  constexpr nvtxPayloadSchemaEntry_t AllGatherSchema[] = {
    {0, NVTX_PAYLOAD_ENTRY_TYPE_SIZE, "Message size [bytes]"}
  };
  size_t msgsize = sendcount * ncclTypeSize(datatype);
  NVTX3_FUNC_WITH_PARAMS(AllGather, AllGatherSchema, msgsize)

  struct ncclInfo info = { ncclFuncAllGather, "AllGather",
    sendbuff, recvbuff, sendcount, datatype, ncclSum, 0, comm, stream, /* Args */
    ALLGATHER_CHUNKSTEPS, ALLGATHER_SLICESTEPS };
  return ncclEnqueueCheck(&info);
}

NCCL_API(ncclResult_t, ncclAllReduce, const void* sendbuff, void* recvbuff, size_t count,ncclDataType_t datatype, ncclRedOp_t op, ncclComm* comm, cudaStream_t stream);
ncclResult_t ncclAllReduce(const void* sendbuff, void* recvbuff, size_t count,ncclDataType_t datatype, ncclRedOp_t op, ncclComm* comm, cudaStream_t stream) 
{
  struct NvtxParamsAllReduce {
    size_t bytes;
    ncclRedOp_t op;
  };
  // Just pass the size of one message and not the total bytes sent/received.
  static constexpr nvtxPayloadSchemaEntry_t AllReduceSchema[] = {
    {0, NVTX_PAYLOAD_ENTRY_TYPE_SIZE, "Message size [bytes]"},
    {0, NVTX_PAYLOAD_ENTRY_NCCL_REDOP, "Reduction operation", nullptr, 0,
      offsetof(NvtxParamsAllReduce, op)}
  };
  NvtxParamsAllReduce payload{count * ncclTypeSize(datatype), op};
  NVTX3_FUNC_WITH_PARAMS(AllReduce, AllReduceSchema, payload)

  struct ncclInfo info = { ncclFuncAllReduce, "AllReduce",
    sendbuff, recvbuff, count, datatype, op, 0, comm, stream, /* Args */
    ALLREDUCE_CHUNKSTEPS, ALLREDUCE_SLICESTEPS };
  return ncclEnqueueCheck(&info);
}

修改ncclAllReduce函数, 将内部全部注释掉,加一句 return ncclSystemError;

cpp 复制代码
NCCL_API(ncclResult_t, ncclAllReduce, const void* sendbuff, void* recvbuff, size_t count,ncclDataType_t datatype, ncclRedOp_t op, ncclComm* comm, cudaStream_t stream);

ncclResult_t ncclAllReduce(const void* sendbuff, void* recvbuff, size_t count,ncclDataType_t datatype, ncclRedOp_t op, ncclComm* comm, cudaStream_t stream) 
{
  // struct NvtxParamsAllReduce {
  //   size_t bytes;
  //   ncclRedOp_t op;
  // };
  // // Just pass the size of one message and not the total bytes sent/received.
  // static constexpr nvtxPayloadSchemaEntry_t AllReduceSchema[] = {
  //   {0, NVTX_PAYLOAD_ENTRY_TYPE_SIZE, "Message size [bytes]"},
  //   {0, NVTX_PAYLOAD_ENTRY_NCCL_REDOP, "Reduction operation", nullptr, 0,
  //     offsetof(NvtxParamsAllReduce, op)}
  // };
  // NvtxParamsAllReduce payload{count * ncclTypeSize(datatype), op};
  // NVTX3_FUNC_WITH_PARAMS(AllReduce, AllReduceSchema, payload)

  // struct ncclInfo info = { ncclFuncAllReduce, "AllReduce",
  //   sendbuff, recvbuff, count, datatype, op, 0, comm, stream, /* Args */
  //   ALLREDUCE_CHUNKSTEPS, ALLREDUCE_SLICESTEPS };
  // return ncclEnqueueCheck(&info);
  return ncclSystemError;
}

每次修改pytorch中nccl源码,要使之生效需要进行重新编译,先删除原有编译文件再重新编译

bash 复制代码
#删除原有nccl相关的
rm -r ./build/nccl*

#重新编译
MAX_JOBS=32 USE_CUDA=1 USE_NCCL=1 USE_SYSTEM_NCCL=0  python setup.py develop

#运行测试文件,看看有没有报错
python test.py

如图:报错ncclSystemError,体现出了源码的更改。

以后就可以按照这种方法修改nccl源码,使之与pytorch集成,将修改后的nccl应用于实际的分布式训练中了。

关于nccl源码及大致的总体流程,推荐一个大佬的文章,写的比较详细,令我受益匪浅。
https://blog.csdn.net/kidgin7439/category_11998768.html

相关推荐
写代码的小阿帆1 小时前
pytorch实现深度神经网络DNN与卷积神经网络CNN
pytorch·cnn·dnn
yx9o3 小时前
Kafka 源码 KRaft 模式本地运行
分布式·kafka
Gemini19953 小时前
分布式和微服务的区别
分布式·微服务·架构
G丶AEOM3 小时前
分布式——BASE理论
java·分布式·八股
P.H. Infinity9 小时前
【RabbitMQ】03-交换机
分布式·rabbitmq
龙哥·三年风水11 小时前
群控系统服务端开发模式-应用开发-个人资料
分布式·php·群控系统
丕羽12 小时前
【Pytorch】基本语法
人工智能·pytorch·python
funnyZpC13 小时前
quartz集群增强版🎉
java·分布式·开源·集群·定时任务
明达技术14 小时前
工业4.0时代下的分布式IO模块
分布式
天冬忘忧16 小时前
Spark 程序开发与提交:本地与集群模式全解析
大数据·分布式·spark