系列文章
- 第1章 多机多卡运行nccl-tests 和channel获取
- 第2章 多机多卡nccl-tests 对比分析
- 第3章 使用tcpdump抓取rdma数据包
- 第5章 PyTorch+NCCL源码编译
目录
- 系列文章
- 前言
- 一、本地环境
- 二、安装cudnn
- 三、使用pytorch自带NCCL库进行编译安装
- 1. 源码编译
- 2. 查看版本和all_reduce测试
- 四、 修改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包之后打开
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文件
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 就是直接安装。
# 新建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
# 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 操作。
#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的相关定义
/*************************************************************************
* 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
;
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源码,要使之生效需要进行重新编译,先删除原有编译文件再重新编译
#删除原有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