db3bfd118f
Network user buffer support for collectives
* Leverage user buffer registration to achieve zero-copy
inter-node communications for Ring, NVLS and Collnet
Add RAS subsystem
* Create a RAS thread keeping track of all NCCL communicators.
* Add a ncclras tool contacting the RAS thread and getting a
report.
Add fp8 support
* Add support for e5m2 and e4m3 8-bit floating point operations.
* Use Tree/PAT algorithms when possible for better numerical
stability.
Add NIC fusion
* Add a NET API to ask the network plugin to fuse a set of
interfaces together.
* Fuse multiple NICs under the same PCI switch as a single,
larger NIC.
Socket connection failure retry
* Retry in case of socket connection failure (unreachable host)
* Avoid "Software caused connection abort" errors on retries
QP connection failure retry
* Retry in case of IB QP connection failure during ibv_modify_qp.
NET API improvements
* Allow plugins to force a flush in case data and completion
ordering is not guaranteed.
* Indicate when completion is not needed (e.g. for the LL128
protocol), allowing plugins to skip generating a completion.
* Allow for full offload of allgather operations when using one
GPU per node.
NCCL_ALGO/NCCL_PROTO strict enforcement
* Extend NCCL_ALGO/NCCL_PROTO syntax to be able to specify
ALGO/PROTO filters for each collective operation.
* Strictly enforce the ALGO/PROTO filters, no longer fall back
on the ring algorithm when the filtering leaves no option and
error out instead.
Enable CUMEM host allocations
* Use cumem functions for host memory allocation by default.
Improved profiler plugin API
* Avoid dependencies with NCCL includes.
* Add information on whether the buffer is registered or not
Adjust PAT tuning
* Improve transition between PAT and ring at scale.
Fix hangs when running with different CPU architectures
* Detect when we use a mix of GPU architectures
* Ensure Algo/Proto decisions are made based on that unified
state.
Fix FD leak in UDS
* Fix a leak when mapping buffers intra-node with cumem IPCs.
Fix crash when mixing buffer registration and graph buffer registration.
* Separate local and graph registration to avoid crashes when we free
buffers.
Fix user buffer registration with dmabuf
* Make ncclSend/ncclRecv communication with buffer registration functional
on network plugins relying on dmabuf for buffer registration.
Fix crash in IB code caused by uninitialized fields.
Fix non-blocking ncclSend/ncclRecv
* Fix case where ncclSend/ncclRecv would return ncclSuccess in non-blocking
mode even though the operation was not enqueued onto the stream.
* Issue #1495
Various compiler tweaks and fixes
* PR #758
Fix typo in ncclTopoPrintGraph
* Issue #1468
[ROCm/rccl commit: 6aae379278]
174 строки
7.4 KiB
C++
174 строки
7.4 KiB
C++
/*************************************************************************
|
|
* Copyright (c) 2015-2022, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* See LICENSE.txt for license information
|
|
************************************************************************/
|
|
|
|
#include "device.h"
|
|
#include "collectives.h"
|
|
#include "primitives.h"
|
|
|
|
template<typename T, typename RedOp>
|
|
struct RunWorkBatch<ncclFuncSendRecv, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
|
static_assert(sizeof(T)==1, "SendRecv only works on single byte types T.");
|
|
|
|
template<typename Proto>
|
|
__device__ void runSend(int tid, int tn, int group, struct ncclDevWorkP2p* work) {
|
|
size_t bytes = work->sendBytes;
|
|
bool useLargeChunk = (work->sendIpcReg && ncclShmem.comm.isAllNvlink) || work->sendNetReg;
|
|
int chunkSize = useLargeChunk ? NCCL_MAX_NET_SIZE : u32fp8Decode(work->sendChunkSize_u32fp8);
|
|
int stepSize = useLargeChunk ? NCCL_MAX_NET_SIZE : ncclShmem.comm.p2pChunkSize;
|
|
Primitives<T, RedOp, FanAsymmetric<0, 1>, 1, Proto, 1>
|
|
prims(tid, tn, nullptr, &work->sendRank, work->sendAddr, nullptr,
|
|
/*redOpArg(ignored)=*/0, group, 1, 1, nullptr, work, stepSize);
|
|
size_t cursor = 0;
|
|
do {
|
|
int n = min(size_t(chunkSize), bytes-cursor);
|
|
prims.directSend(cursor, cursor, n);
|
|
cursor += n;
|
|
} while (cursor < bytes);
|
|
}
|
|
|
|
template<typename Proto>
|
|
__device__ void runRecv(int tid, int tn, int group, struct ncclDevWorkP2p* work) {
|
|
size_t bytes = work->recvBytes;
|
|
bool useLargeChunk = (work->recvIpcReg && ncclShmem.comm.isAllNvlink) || work->recvNetReg;
|
|
int chunkSize = useLargeChunk ? NCCL_MAX_NET_SIZE : u32fp8Decode(work->recvChunkSize_u32fp8);
|
|
int stepSize = useLargeChunk ? NCCL_MAX_NET_SIZE : ncclShmem.comm.p2pChunkSize;
|
|
Primitives<T, RedOp, FanAsymmetric<1, 0>, 1, Proto, 1>
|
|
prims(tid, tn, &work->recvRank, nullptr, nullptr, work->recvAddr,
|
|
/*redOpArg(ignored)=*/0, group, 1, 1, nullptr, work, stepSize);
|
|
size_t cursor = 0;
|
|
do {
|
|
int n = min(size_t(chunkSize), bytes-cursor);
|
|
prims.directRecv(cursor, cursor, n);
|
|
cursor += n;
|
|
} while (cursor < bytes);
|
|
}
|
|
|
|
__device__ __forceinline__ void run() {
|
|
const int tid = threadIdx.x;
|
|
const int tn = blockDim.x;
|
|
const int wid = tid/WARP_SIZE;
|
|
const int nWarps = tn/WARP_SIZE;
|
|
const int lane = tid%WARP_SIZE;
|
|
|
|
struct Shared {
|
|
uint32_t workSendMask; // bitmasks of which work indices have send/recv
|
|
uint32_t workRecvMask;
|
|
};
|
|
Shared* shared = (Shared*)ncclScratchForWarp(0);
|
|
|
|
struct ncclDevWorkP2p* works = (ncclDevWorkP2p*)ncclShmem.workStorage;
|
|
int nWorks = ncclShmem.nWorks;
|
|
|
|
if (wid == 0) {
|
|
// Modify the memory range of each work[] to reflect this channel's
|
|
// partition of the work. Since integer divides are very heavy it's
|
|
// best to do them all in one warp.
|
|
int workIx = lane%16;
|
|
int isSend = lane < 16 ? 0 : 1;
|
|
bool hasWork = false;
|
|
if (workIx < nWorks) {
|
|
struct ncclDevWorkP2p* work = &works[workIx];
|
|
size_t bytes = isSend ? work->sendBytes : work->recvBytes;
|
|
int nParts = isSend ? work->nSendChannels : work->nRecvChannels;
|
|
int part = ncclP2pChannelToPart(work->nP2pChannels, work->channelBase, ncclShmem.channelId);
|
|
hasWork = (part < nParts);
|
|
if (nParts != 0) {
|
|
size_t partBeg, partEnd;
|
|
ncclP2pPartBounds(nParts, part, bytes, &partBeg, &partEnd);
|
|
(isSend ? work->sendAddr : work->recvAddr) = (char*)(isSend ? work->sendAddr : work->recvAddr) + partBeg;
|
|
(isSend ? work->sendBytes : work->recvBytes) = partEnd - partBeg;
|
|
}
|
|
}
|
|
// Coverity reports a possible thread divergence due to not all threads participating in the collective.
|
|
// However, the code ensures that the participation is on a per-warp basis.
|
|
// coverity[device_thread_diverged:FALSE]
|
|
uint32_t mask = __ballot_sync(~0u, hasWork);
|
|
if (lane == 0) {
|
|
shared->workSendMask = mask>>16;
|
|
shared->workRecvMask = mask & 0xffff;
|
|
}
|
|
}
|
|
|
|
// The fastest way to compute a warp uniform division x/y in [0,32) is to
|
|
// use each lane to guess a solution and count the ones that don't exceed
|
|
// the numerator:
|
|
// __popc(__ballot_sync(~0u, y*(lane+1) <= x))
|
|
// That takes 1/3 the time of standard division and about 3/4 the time of
|
|
// approximate floating point division:
|
|
// __float2int_rd(__fdividef(float(x),float(y))).
|
|
|
|
// nWarpPerWork = nWarps/nWorks
|
|
int nWarpPerWork = __popc(__ballot_sync(~0u, nWorks*(lane+1) <= nWarps));
|
|
int nRecvWarpPerWork = nWarpPerWork<=4 ? nWarpPerWork/2 : (nWarpPerWork-1)/2;
|
|
int nSendWarpPerWork = nWarpPerWork<=4 ? nRecvWarpPerWork : nRecvWarpPerWork+1;
|
|
// This might reduce nWarpPerWork which is probably desirable. It is better
|
|
// to have a balanced number of reading and writing threads even if that
|
|
// leaves warps unused.
|
|
nWarpPerWork = nSendWarpPerWork + nRecvWarpPerWork;
|
|
// The work index this warp belongs to: workIx = wid/nWarpPerWork
|
|
int workIx = __popc(__ballot_sync(~0u, (lane+1)*nWarpPerWork <= wid));
|
|
|
|
__syncthreads(); // Wait for works[] and shared->* to be updated by warp=0
|
|
|
|
uint32_t workSendMask = shared->workSendMask;
|
|
uint32_t workRecvMask = shared->workRecvMask;
|
|
|
|
__syncthreads(); // release scratch space used by shared->*
|
|
if (nWorks <= workIx) return;
|
|
|
|
// Thread range for whole work (send & recv combined)
|
|
int subtid = tid - workIx*nWarpPerWork*WARP_SIZE;
|
|
int subtn = nWarpPerWork*WARP_SIZE;
|
|
|
|
// A send primtive of sufficient size requires 2 cuda barrier ids.
|
|
constexpr int nSendWarpsForExtraGroup = NCCL_SIMPLE_EXTRA_GROUP_IF_NTHREADS_GE/WARP_SIZE;
|
|
// Count up all group ids used below this workIx:
|
|
int group, extra;
|
|
// Each recv gets one group id:
|
|
group = __popc(workRecvMask & ((1<<workIx)-1));
|
|
// Sends accompanying recvs get one and maybe an extra:
|
|
extra = (nSendWarpPerWork >= nSendWarpsForExtraGroup) ? 1 : 0;
|
|
group += __popc((workSendMask & workRecvMask) & ((1<<workIx)-1))*(1+extra);
|
|
// Sends without recvs use more warps so compute extra accordingly:
|
|
extra = (nWarpPerWork >= nSendWarpsForExtraGroup) ? 1 : 0;
|
|
group += __popc((workSendMask & ~workRecvMask) & ((1<<workIx)-1))*(1+extra);
|
|
|
|
struct ncclDevWorkP2p* work = &works[workIx];
|
|
bool hasSend = 1 & (workSendMask>>workIx);
|
|
bool hasRecv = 1 & (workRecvMask>>workIx);
|
|
bool isCopy = work->sendRank == ncclShmem.comm.rank;
|
|
bool isSend = !hasRecv || (hasSend && subtid < nSendWarpPerWork*WARP_SIZE);
|
|
|
|
if (!isCopy && hasSend && hasRecv) {
|
|
// Translate thread ids to reflect just this send or recv as opposed to whole work.
|
|
if (isSend) {
|
|
subtn = nSendWarpPerWork*WARP_SIZE;
|
|
} else {
|
|
subtid -= nSendWarpPerWork*WARP_SIZE;
|
|
subtn = nRecvWarpPerWork*WARP_SIZE;
|
|
group += 1 + (nSendWarpPerWork >= nSendWarpsForExtraGroup ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
if (isCopy) {
|
|
reduceCopy<COLL_UNROLL, RedOp, T, 0,1,1, 0,1,1, /*PreOpSrcs=*/0>
|
|
(subtid, subtn, 0, nullptr, false, 1, &work->sendAddr, 1, &work->recvAddr, (ssize_t)work->sendBytes);
|
|
} else if (isSend) {
|
|
if (work->sendProtoLL) {
|
|
runSend<ProtoLL>(subtid, subtn, group, work);
|
|
} else {
|
|
runSend<ProtoSimple<1,1>>(subtid, subtn, group, work);
|
|
}
|
|
} else {
|
|
if (work->recvProtoLL) {
|
|
runRecv<ProtoLL>(subtid, subtn, group, work);
|
|
} else {
|
|
runRecv<ProtoSimple<1,1>>(subtid, subtn, group, work);
|
|
}
|
|
}
|
|
}
|
|
};
|