2.22.3-1
Rework core for NVIDIA Trusted Computing * Compress work structs so that they are shared between channels * Utilize the full amount of kernel argument space permitted (4k) before resorting to work fifo. * Rework the task preprocessing phase. * Use a separate abortDevFlag which is kept in sync with abortFlag using cudaMemcpy operations. * Rename src/include/align.h to src/include/bitops.h Add lazy connection establishment for collective operations * Move buffer allocation and connection establishment to the first collective operation using that algorithm. * Accelerate init time and reduce memory usage. * Avoid allocating NVLS buffers if all calls are registered. * Compute algo/proto in ncclLaunchCollTasksInfo early on. * Connect peers in ncclCollPreconnectFunc if not connected already. * Also move shared buffer creation to the first send/recv call. Accelerate intra-node NVLink detection * Make each rank only detect NVLinks attached to its GPU. * Fuse XMLs to reconstruct the full NVLink topology Add init profiling to report time spend in different init phases. * Report timings of bootstrap, allgather, search, connect, etc. * Add new "PROFILE" category for NCCL_DEBUG_SUBSYS. Add support for PCI p2p on split PCI switches * Detect split PCI switches through a kernel module exposing switch information. * Update the topology XML and graph to add those inter-switch connections. Add cost estimation API * Add a new ncclGroupEndSimulate primitive to return the estimated time a group would take. Net/IB: Add separate traffic class for fifo messages * Add NCCL_IB_FIFO_TC to control the traffic class of fifo messages independently from NCCL_IB_TC. Merges PR #1194 Net/IB: Add support for IB router * Use flid instead of lid if subnets do not match * Warn if flid is 0 Optimizations and fixes for device network offload (unpack) * Double the default number of channels * Cache netDeviceType * Fix save/increment head logic to enable Tree support. Support ncclGroupStart/End for ncclCommAbort/Destroy * Allow Abort/Destroy to be called within a group when managing multiple GPUs with a single process. Improve Tuner API * Provide to the plugin the original cost table so that the plugin can leave unknown or disabled algo/proto combinations untouched. * Remove nvlsSupport and collnetSupport. Do not print version to stdout when using a debug file * Also print version from all processes with INFO debug level. Fixes issue #1271 Fix clang warnings in NVTX headers * Update NVTX headers to the latest version Fixes issue #1270 Disable port fusion in heterogeneous systems * Do not fuse ports if a mix of multi-port and single port are detected. Fix NVLS graphs search for dual NICs. * Fix NVLS graph search when we have more than one NIC per GPU. Fix crash with collnetDirect * Add separate graph search for collnetDirect, testing alltoall paths and working similarly to the NVLS search. Fix hang when nodes have different CPU types * Add the CPU type to the rank peer info. * Align all ranks on the CPU type after the first allgather. * Only use the aligned CPU type for all tuning operations. Fixes issue #1136 Fixes issue #1184 Fix performance of registered send/recv operations * Allow for single full size operations * Add INFO to confirm the registration of send/recv buffers. Move all sync ops to finalize stage * Ensure ncclCommDestroy is non-blocking if ncclCommFinalize has been called. Improve error reporting during SHM segment creation Improve support of various compilers Merges PR #1177 Merges PR #1228 Allow net and tuner plugins to be statically linked * Search for ncclNet or ncclTuner symbols in the main binary. Merges PR #979 Plugin examples includes cleanup * Harmonize err.h and common.h usage. * Add mixed plugin with both net and tuner.
This commit is contained in:
1 parent
529ee691c3
commit
178b6b7590
115 files changed
+8667
-4398
No files matched your search
+55
-62
@@ -10,30 +10,26 @@
|
||||
|
||||
namespace {
|
||||
template<typename T, typename RedOp, typename Proto>
|
||||
__device__ __forceinline__ void runRing(ncclWorkElem *args) {
|
||||
const int tid = threadIdx.x;
|
||||
const int nthreads = (int)args->nWarps * WARP_SIZE;
|
||||
__device__ __forceinline__ void runRing(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
ncclRing *ring = &ncclShmem.channel.ring;
|
||||
const int *ringRanks = ring->userRanks;
|
||||
const int nranks = ncclShmem.comm.nRanks;
|
||||
const size_t chunkCount = args->chunkCount;
|
||||
const size_t channelCount = args->workCount;
|
||||
const size_t gridOffset = args->workOffset;
|
||||
const size_t count = args->count;
|
||||
size_t count, partOffset, partCount, chunkCount;
|
||||
ncclCollCbdPart(work, ncclShmem.channelId, Proto::Id, sizeof(T), &count, &partOffset, &partCount, &chunkCount);
|
||||
size_t offset;
|
||||
size_t dataOffset;
|
||||
int nelem;
|
||||
int rankDest;
|
||||
|
||||
T *inputBuf = (T*)args->sendbuff;
|
||||
T *outputBuf = (T*)args->recvbuff;
|
||||
T *inputBuf = (T*)work->sendbuff;
|
||||
T *outputBuf = (T*)work->recvbuff;
|
||||
Primitives<T, RedOp, FanSymmetric<1>, 1, Proto, 0> prims
|
||||
(tid, nthreads, &ring->prev, &ring->next, inputBuf, outputBuf, args->redOpArg);
|
||||
(tid, nthreads, &ring->prev, &ring->next, inputBuf, outputBuf, work->redOpArg);
|
||||
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
for (size_t elemOffset = 0; elemOffset < partCount; elemOffset += chunkCount) {
|
||||
/////////////// begin AllGather steps ///////////////
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
dataOffset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, partCount - elemOffset);
|
||||
dataOffset = partOffset + elemOffset;
|
||||
|
||||
// step 0: push data to next GPU
|
||||
rankDest = ringRanks[0];
|
||||
@@ -64,52 +60,50 @@ namespace {
|
||||
}
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
struct RunWorkColl<ncclFuncAllGather, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
using Proto = ProtoSimple<ALLGATHER_CHUNKSTEPS/ALLGATHER_SLICESTEPS, ALLGATHER_SLICESTEPS>;
|
||||
runRing<T, RedOp, Proto>(args);
|
||||
runRing<T, RedOp, Proto>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
runRing<T, RedOp, ProtoLL>(args);
|
||||
struct RunWorkColl<ncclFuncAllGather, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
runRing<T, RedOp, ProtoLL>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL128> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
runRing<T, RedOp, ProtoLL128>(args);
|
||||
struct RunWorkColl<ncclFuncAllGather, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL128> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
runRing<T, RedOp, ProtoLL128>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
const int tid = threadIdx.x;
|
||||
struct RunWorkColl<ncclFuncAllGather, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int/*nthreads*/, struct ncclDevWorkColl* work) {
|
||||
struct ncclNvls* nvls = &ncclShmem.channel.nvls;
|
||||
const ssize_t count = args->count;
|
||||
const ssize_t rank = ncclShmem.comm.rank;
|
||||
const size_t chunkCount = args->chunkCount;
|
||||
size_t gridOffset = args->workOffset;
|
||||
size_t channelCount = args->workCount;
|
||||
size_t count, gridOffset, channelCount;
|
||||
size_t chunkCount;
|
||||
ncclCollCbdPart(work, ncclShmem.channelId, NCCL_PROTO_SIMPLE, sizeof(T), &count, &gridOffset, &channelCount, &chunkCount);
|
||||
size_t offset;
|
||||
int nelem;
|
||||
|
||||
const int nThreadsBcast = args->regUsed ? (NCCL_MAX_NTHREADS - WARP_SIZE) : 4 * WARP_SIZE;
|
||||
const int nThreadsGather = args->regUsed ? WARP_SIZE : NCCL_MAX_NTHREADS - nThreadsBcast;
|
||||
const int nThreadsBcast = work->regUsed ? (NCCL_MAX_NTHREADS - WARP_SIZE) : 4 * WARP_SIZE;
|
||||
const int nThreadsGather = work->regUsed ? WARP_SIZE : NCCL_MAX_NTHREADS - nThreadsBcast;
|
||||
const int tidEndGather = nThreadsGather;
|
||||
const int tidEndBcast = tidEndGather + nThreadsBcast;
|
||||
|
||||
if (!args->regUsed) {
|
||||
if (!work->regUsed) {
|
||||
if (tid < tidEndGather) {
|
||||
// Gather
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL>;
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_NVLS_ARITY, 0>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, nThreadsGather, nvls->up, NULL, NULL, args->recvbuff,
|
||||
args->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
prims(tid, nThreadsGather, nvls->up, NULL, NULL, work->recvbuff,
|
||||
work->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
@@ -119,8 +113,8 @@ struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SI
|
||||
// Bcast through NVLS
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 0, 1>;
|
||||
Primitives<T, RedOp, FanAsymmetric<0, 1>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid - tidEndGather, nThreadsBcast, NULL, &nvls->down, args->sendbuff, NULL,
|
||||
args->redOpArg, 3 * Proto::MaxGroupWidth, 0, 0);
|
||||
prims(tid - tidEndGather, nThreadsBcast, NULL, &nvls->down, work->sendbuff, NULL,
|
||||
work->redOpArg, 3 * Proto::MaxGroupWidth, 0, 0);
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
@@ -133,7 +127,7 @@ struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SI
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL>;
|
||||
Primitives<T, RedOp, FanSymmetric<NCCL_MAX_NVLS_ARITY>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, nThreadsGather, nvls->up, nvls->up, NULL, NULL,
|
||||
args->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
work->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
|
||||
/* used as sync */
|
||||
prims.scatter(0, 0, 0, 0, -1, 0);
|
||||
@@ -144,8 +138,8 @@ struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SI
|
||||
} else if (tid < tidEndBcast) {
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 0, 1>;
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndGather, nThreadsBcast, &nvls->down, &nvls->down, args->sendbuff, NULL,
|
||||
args->redOpArg, 1 * Proto::MaxGroupWidth, 0, 0, args);
|
||||
prims(tid - tidEndGather, nThreadsBcast, &nvls->down, &nvls->down, work->sendbuff, NULL,
|
||||
work->redOpArg, 1 * Proto::MaxGroupWidth, 0, 0, work);
|
||||
/* used as sync */
|
||||
prims.recv(0, 0);
|
||||
|
||||
@@ -161,10 +155,10 @@ struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SI
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_PROTO_SIMPLE> {
|
||||
struct RunWorkColl<ncclFuncAllGather, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_PROTO_SIMPLE> {
|
||||
template<bool BcastSendNotRecv>
|
||||
struct Scatterer {
|
||||
struct ncclWorkElem* args;
|
||||
struct ncclDevWorkColl* work;
|
||||
ssize_t chunkSize;
|
||||
ssize_t railGridOffset;
|
||||
|
||||
@@ -179,13 +173,13 @@ struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
struct ncclDirect* direct = &ncclShmem.channel.collnetDirect;
|
||||
int nNodes = ncclShmem.comm.nNodes;
|
||||
int nRails = direct->nHeads;
|
||||
int bid = args->bid;
|
||||
char* inbuf = (char*)args->sendbuff;
|
||||
char* outbuf = (char*)args->recvbuff;
|
||||
ssize_t sizePerRank = args->count*sizeof(T);
|
||||
int part = ncclShmem.channelId - work->channelLo;
|
||||
char* inbuf = (char*)work->sendbuff;
|
||||
char* outbuf = (char*)work->recvbuff;
|
||||
ssize_t sizePerRank = work->collnet.count*sizeof(T);
|
||||
bool inPlace = (inbuf == outbuf + ncclShmem.comm.rank*sizePerRank);
|
||||
|
||||
ssize_t railAllBeg = min(railGridOffset + bid*chunkSize, nNodes*sizePerRank);
|
||||
ssize_t railAllBeg = min(railGridOffset + part*chunkSize, nNodes*sizePerRank);
|
||||
ssize_t railAllEnd = min(railAllBeg + chunkSize, nNodes*sizePerRank);
|
||||
int railAllSize = railAllEnd - railAllBeg;
|
||||
if (tid < nDsts) dstSizes[tid] = railAllSize;
|
||||
@@ -232,28 +226,27 @@ struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
}
|
||||
};
|
||||
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
int tid = threadIdx.x;
|
||||
const int nChannels = args->nChannels;
|
||||
__device__ __forceinline__ void run(int tid, int/*nthreads*/, struct ncclDevWorkColl* work) {
|
||||
const int part = ncclShmem.channelId - work->channelLo;
|
||||
const int nChannels = work->channelHi - work->channelLo + 1;
|
||||
struct ncclDirect* direct = &ncclShmem.channel.collnetDirect;
|
||||
int const &nNodes = ncclShmem.comm.nNodes;
|
||||
ssize_t chunkSize = int(args->chunkCount);
|
||||
ssize_t const &sizePerRank = args->count;
|
||||
|
||||
ssize_t sizePerRank = work->collnet.count*sizeof(T);
|
||||
size_t chunkSize = work->collnet.chunkCount;
|
||||
bool isMultiRail = (direct->nHeads > 1);
|
||||
int nWarps1 = 1;
|
||||
int nWarps2 = (isMultiRail ? 2 : 1);
|
||||
int nWarps3 = (isMultiRail ? 2 : 0);
|
||||
float denom = float(args->nWarps)/float(nWarps1+nWarps2+nWarps3);
|
||||
float denom = float(work->nWarps)/float(nWarps1+nWarps2+nWarps3);
|
||||
nWarps3 = int(denom*nWarps3);
|
||||
nWarps2 = int(denom*nWarps2);
|
||||
nWarps1 = args->nWarps - (nWarps2+nWarps3);
|
||||
nWarps1 = work->nWarps - (nWarps2+nWarps3);
|
||||
|
||||
using Proto = ProtoSimple<1, 1>;
|
||||
|
||||
int tn = nWarps1*WARP_SIZE;
|
||||
if (tid < tn) {
|
||||
if (args->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (work->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (tid == 0) {
|
||||
int steps = (int)divUp(nNodes * sizePerRank * sizeof(T), NCCL_MAX_COLLNET_SIZE);
|
||||
Primitives<T, RedOp, FanAsymmetric<0, 1>, /*Direct=*/0, Proto, 0>::sendPeerNotify(direct->out, 1, steps);
|
||||
@@ -262,10 +255,10 @@ struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
} else {
|
||||
// Phase 1: send to network
|
||||
Primitives<T, RedOp, FanAsymmetric<0, 1>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, tn, nullptr, &direct->out, args->sendbuff, nullptr,
|
||||
prims(tid, tn, nullptr, &direct->out, work->sendbuff, nullptr,
|
||||
/*redOpArg=*/0, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
for (ssize_t railGridOffset = 0; railGridOffset < nNodes * sizePerRank; railGridOffset += nChannels * chunkSize) {
|
||||
ssize_t railAllBeg = railGridOffset + args->bid * chunkSize;
|
||||
ssize_t railAllBeg = railGridOffset + part * chunkSize;
|
||||
ssize_t railAllEnd = min(railAllBeg + chunkSize, nNodes * sizePerRank);
|
||||
ssize_t railOneBeg = ncclShmem.comm.node * sizePerRank;
|
||||
ssize_t railOneEnd = railOneBeg + sizePerRank;
|
||||
@@ -280,7 +273,7 @@ struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
|
||||
tn = nWarps2*WARP_SIZE;
|
||||
if (tid < tn) {
|
||||
if (args->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (work->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (tid == 0) {
|
||||
int steps = (int)divUp(nNodes * sizePerRank * sizeof(T), NCCL_MAX_COLLNET_SIZE);
|
||||
Primitives<T, RedOp, FanAsymmetric<1, NCCL_MAX_DIRECT_ARITY>, /*Direct=*/0, Proto, 0>::recvPeerNotify(direct->out, 0, steps);
|
||||
@@ -293,10 +286,10 @@ struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
/*redOpArg=*/0, 1 * Proto::MaxGroupWidth, 0, 0);
|
||||
for (ssize_t railGridOffset = 0; railGridOffset < nNodes * sizePerRank; railGridOffset += nChannels * chunkSize) {
|
||||
Scatterer</*BcastSendNotRecv=*/true> scat;
|
||||
scat.args = args;
|
||||
scat.work = work;
|
||||
scat.chunkSize = chunkSize;
|
||||
scat.railGridOffset = railGridOffset;
|
||||
prims.process</*Recv=*/1, /*Send=*/1>(scat);
|
||||
prims.template process</*Recv=*/1, /*Send=*/1>(scat);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -311,10 +304,10 @@ struct RunWorkElement<ncclFuncAllGather, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
/*redOpArg=*/0, 2*Proto::MaxGroupWidth, 0, 0);
|
||||
for (ssize_t railGridOffset=0; railGridOffset < nNodes*sizePerRank; railGridOffset += nChannels*chunkSize) {
|
||||
Scatterer</*BcastSendNotRecv=*/false> scat;
|
||||
scat.args = args;
|
||||
scat.work = work;
|
||||
scat.chunkSize = chunkSize;
|
||||
scat.railGridOffset = railGridOffset;
|
||||
prims.process</*Recv=*/1, /*Send=*/0>(scat);
|
||||
prims.template process</*Recv=*/1, /*Send=*/0>(scat);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
+143
-147
@@ -10,28 +10,27 @@
|
||||
|
||||
namespace {
|
||||
template<typename T, typename RedOp, typename Proto>
|
||||
__device__ __forceinline__ void runRing(ncclWorkElem *args) {
|
||||
const int tid = threadIdx.x;
|
||||
const int nthreads = (int)args->nWarps * WARP_SIZE;
|
||||
__device__ __forceinline__ void runRing(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
ncclRing *ring = &ncclShmem.channel.ring;
|
||||
int ringIx = ring->index;
|
||||
ssize_t chunkCount = args->chunkCount;
|
||||
const int nranks = ncclShmem.comm.nRanks;
|
||||
ssize_t gridOffset;
|
||||
ssize_t channelCount;
|
||||
ssize_t chunkCount;
|
||||
ncclCollCbdPart(work, ncclShmem.channelId, Proto::Id, sizeof(T), (ssize_t*)nullptr, &gridOffset, &channelCount, &chunkCount);
|
||||
const ssize_t loopCount = nranks * chunkCount;
|
||||
ssize_t offset;
|
||||
ssize_t gridOffset = args->workOffset;
|
||||
ssize_t channelCount = args->workCount;
|
||||
int nelem;
|
||||
int chunk;
|
||||
|
||||
Primitives<T, RedOp, FanSymmetric<1>, 1, Proto, 0> prims
|
||||
(tid, nthreads, &ring->prev, &ring->next, args->sendbuff, args->recvbuff, args->redOpArg);
|
||||
(tid, nthreads, &ring->prev, &ring->next, work->sendbuff, work->recvbuff, work->redOpArg);
|
||||
|
||||
for (ssize_t elemOffset = 0; elemOffset < channelCount; elemOffset += loopCount) {
|
||||
ssize_t remCount = channelCount - elemOffset;
|
||||
ssize_t chunkOffset;
|
||||
|
||||
if (remCount < loopCount) chunkCount = args->lastChunkCount;
|
||||
if (remCount < loopCount) chunkCount = alignUp(divUp(remCount, nranks), 16/sizeof(T));
|
||||
|
||||
auto modRanks = [&]__device__(int r)->int {
|
||||
return r - (r >= nranks ? nranks : 0);
|
||||
@@ -75,24 +74,24 @@ namespace {
|
||||
chunkOffset = chunk * chunkCount;
|
||||
offset = gridOffset + elemOffset + chunkOffset;
|
||||
nelem = (int)min(chunkCount, remCount - chunkOffset);
|
||||
|
||||
prims.directRecv(offset, nelem);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename RedOp, typename Proto>
|
||||
__device__ __forceinline__ void runTreeUpDown(ncclWorkElem *args) {
|
||||
const int tid = threadIdx.x;
|
||||
const int nthreads = (int)args->nWarps * WARP_SIZE;
|
||||
__device__ __forceinline__ void runTreeUpDown(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
ncclTree *tree = &ncclShmem.channel.tree;
|
||||
const size_t channelCount = args->workCount;
|
||||
const size_t gridOffset = args->workOffset;
|
||||
const size_t chunkCount = args->chunkCount;
|
||||
size_t gridOffset;
|
||||
size_t channelCount;
|
||||
size_t chunkCount;
|
||||
ncclCollCbdPart(work, ncclShmem.channelId, Proto::Id, sizeof(T), (size_t*)nullptr, &gridOffset, &channelCount, &chunkCount);
|
||||
size_t offset;
|
||||
int nelem;
|
||||
|
||||
{ // Reduce : max number of recv is 3, max number of send is 1 (binary tree + local)
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_TREE_ARITY, 1>, /*Direct=*/0, Proto, 0> prims
|
||||
(tid, nthreads, tree->down, &tree->up, args->sendbuff, args->recvbuff, args->redOpArg);
|
||||
(tid, nthreads, tree->down, &tree->up, work->sendbuff, work->recvbuff, work->redOpArg);
|
||||
if (tree->up == -1) {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
@@ -118,7 +117,7 @@ namespace {
|
||||
|
||||
{ // Broadcast : max number of recv is 1, max number of send is 3 (binary tree + local)
|
||||
Primitives<T, RedOp, FanAsymmetric<1, NCCL_MAX_TREE_ARITY>, /*Direct=*/1, Proto, 0> prims
|
||||
(tid, nthreads, &tree->up, tree->down, args->sendbuff, args->recvbuff, args->redOpArg);
|
||||
(tid, nthreads, &tree->up, tree->down, work->sendbuff, work->recvbuff, work->redOpArg);
|
||||
if (tree->up == -1) {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
@@ -144,16 +143,14 @@ namespace {
|
||||
}
|
||||
|
||||
template<typename T, typename RedOp, typename Proto>
|
||||
__device__ __forceinline__ void runTreeSplit(ncclWorkElem *args) {
|
||||
const int tid = threadIdx.x;
|
||||
const int nthreads = (int)args->nWarps * WARP_SIZE;
|
||||
__device__ __forceinline__ void runTreeSplit(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
ncclTree *tree = &ncclShmem.channel.tree;
|
||||
const size_t chunkCount = args->chunkCount;
|
||||
const size_t gridOffset = args->workOffset;
|
||||
const size_t channelCount = args->workCount;
|
||||
size_t gridOffset;
|
||||
size_t channelCount;
|
||||
size_t chunkCount;
|
||||
ncclCollCbdPart(work, ncclShmem.channelId, Proto::Id, sizeof(T), (size_t*)nullptr, &gridOffset, &channelCount, &chunkCount);
|
||||
size_t offset;
|
||||
int nelem;
|
||||
|
||||
int nthreadsSplit;
|
||||
if (Proto::Id == NCCL_PROTO_SIMPLE) {
|
||||
nthreadsSplit = nthreads/2;
|
||||
@@ -167,7 +164,7 @@ namespace {
|
||||
if (tree->up == -1) {
|
||||
// Reduce and broadcast. Max number of recv is 2, max number of send is 2
|
||||
Primitives<T, RedOp, FanSymmetric<NCCL_MAX_TREE_ARITY_TOP>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid, nthreads, tree->down, tree->down, args->sendbuff, args->recvbuff, args->redOpArg);
|
||||
prims(tid, nthreads, tree->down, tree->down, work->sendbuff, work->recvbuff, work->redOpArg);
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
@@ -184,7 +181,7 @@ namespace {
|
||||
* but the ctor above for tree roots would be DirectRecv=0 DirectSend=1.
|
||||
*/
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_TREE_ARITY, 1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid, nthreadsSplit, tree->down, &tree->up, args->sendbuff, args->recvbuff, args->redOpArg, 0*Proto::MaxGroupWidth);
|
||||
prims(tid, nthreadsSplit, tree->down, &tree->up, work->sendbuff, work->recvbuff, work->redOpArg, 0*Proto::MaxGroupWidth);
|
||||
if (tree->down[0] == -1) {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
@@ -203,8 +200,8 @@ namespace {
|
||||
else {
|
||||
// Broadcast down. Max number of recv is 1, max number of send is 3 (binary tree + local)
|
||||
Primitives<T, RedOp, FanAsymmetric<1, NCCL_MAX_TREE_ARITY>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid-nthreadsSplit, nthreads-nthreadsSplit, &tree->up, tree->down, args->sendbuff, args->recvbuff,
|
||||
args->redOpArg, 1*Proto::MaxGroupWidth);
|
||||
prims(tid-nthreadsSplit, nthreads-nthreadsSplit, &tree->up, tree->down, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, 1*Proto::MaxGroupWidth);
|
||||
if (tree->down[0] == -1) {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
@@ -224,34 +221,33 @@ namespace {
|
||||
}
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
using Proto = ProtoSimple<ALLREDUCE_CHUNKSTEPS/ALLREDUCE_SLICESTEPS, ALLREDUCE_SLICESTEPS>;
|
||||
runRing<T, RedOp, Proto>(args);
|
||||
runRing<T, RedOp, Proto>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_TREE, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_TREE, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
#if CUDART_VERSION >= 11020 && CUDART_VERSION < 11040 && __CUDA_ARCH__ >= 800
|
||||
runTreeUpDown<T, RedOp, ProtoSimple<1, 1>>(args);
|
||||
runTreeUpDown<T, RedOp, ProtoSimple<1, 1>>(tid, nthreads, work);
|
||||
#else
|
||||
runTreeSplit<T, RedOp, ProtoSimple<1, 1>>(args);
|
||||
runTreeSplit<T, RedOp, ProtoSimple<1, 1>>(tid, nthreads, work);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int/*nthreads*/, struct ncclDevWorkColl* work) {
|
||||
static constexpr int COLLNET_COPY_THREADS = 96;
|
||||
const int tid = threadIdx.x;
|
||||
const int bid = args->bid;
|
||||
const int nChannels = args->nChannels;
|
||||
const int bid = ncclShmem.channelId - work->channelLo;
|
||||
const int nChannels = work->channelHi - work->channelLo + 1;
|
||||
struct ncclDirect* direct = &ncclShmem.channel.collnetDirect;
|
||||
const ssize_t chunkSize = args->chunkCount;
|
||||
const ssize_t size = args->count;
|
||||
const ssize_t chunkSize = work->collnet.chunkCount;
|
||||
const ssize_t size = work->collnet.count;
|
||||
const ssize_t loopSize = nChannels*direct->nHeads*chunkSize;
|
||||
|
||||
const int hasUp = (direct->up[0] >= 0) ? 1 : 0;
|
||||
@@ -259,7 +255,7 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
const int nThreadsScatter = WARP_SIZE + ((hasUp && hasDn) ? COLLNET_COPY_THREADS : hasUp ? 3*COLLNET_COPY_THREADS : 0);
|
||||
const int nThreadsGather = ((hasUp && hasDn) ? COLLNET_COPY_THREADS : hasUp ? 2*COLLNET_COPY_THREADS : 0);
|
||||
const int nThreadsBcast = WARP_SIZE + ((hasUp && hasDn) ? COLLNET_COPY_THREADS : hasUp ? 0 : 2*COLLNET_COPY_THREADS);
|
||||
const int nThreadsReduce = args->nWarps*WARP_SIZE - nThreadsScatter - nThreadsGather - nThreadsBcast;
|
||||
const int nThreadsReduce = work->nWarps*WARP_SIZE - nThreadsScatter - nThreadsGather - nThreadsBcast;
|
||||
const int tidStartBcast = nThreadsGather;
|
||||
const int tidStartScatter = tidStartBcast + nThreadsBcast;
|
||||
const int tidStartReduce = tidStartScatter + nThreadsScatter;
|
||||
@@ -269,12 +265,12 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
if (tid >= tidStartScatter && tid < tidStartReduce && hasUp) {
|
||||
// Scatter
|
||||
Primitives<T, RedOp, FanAsymmetric<0, NCCL_MAX_DIRECT_ARITY>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid-tidStartScatter, nThreadsScatter, NULL, direct->up, args->sendbuff, args->recvbuff,
|
||||
args->redOpArg, 2*Proto::MaxGroupWidth, 1, 1, args);
|
||||
prims(tid-tidStartScatter, nThreadsScatter, NULL, direct->up, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, 2*Proto::MaxGroupWidth, 1, 1, work);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid*direct->nHeads*chunkSize;
|
||||
int nelem = min(direct->nHeads*chunkSize, size-offset);
|
||||
if (args->regUsed) {
|
||||
if (work->regUsed) {
|
||||
prims.directScatter(offset, nelem, chunkSize, chunkSize, direct->headRank, direct->shift);
|
||||
} else {
|
||||
prims.scatter(offset, nelem, chunkSize, chunkSize, direct->headRank, direct->shift);
|
||||
@@ -284,12 +280,12 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
if (hasDn) {
|
||||
// Reduce, send to network
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_DIRECT_ARITY, 1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid-tidStartReduce, nThreadsReduce, direct->down, &direct->out, args->sendbuff, args->recvbuff,
|
||||
args->redOpArg, 3*Proto::MaxGroupWidth, 1, 1, args);
|
||||
prims(tid-tidStartReduce, nThreadsReduce, direct->down, &direct->out, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, 3*Proto::MaxGroupWidth, 1, 1, work);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + (bid*direct->nHeads+direct->headRank)*chunkSize;
|
||||
int nelem = min(chunkSize, size-offset);
|
||||
if (args->regUsed) {
|
||||
if (work->regUsed) {
|
||||
prims.directRecvReduceSend(offset, nelem);
|
||||
} else {
|
||||
prims.recvReduceSend(offset, nelem);
|
||||
@@ -297,7 +293,7 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
}
|
||||
} else {
|
||||
// Directly send to network
|
||||
if (args->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (work->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (tid == tidStartReduce) {
|
||||
int steps = (int)divUp(size * sizeof(T), NCCL_MAX_COLLNET_SIZE);
|
||||
Primitives<T, RedOp, FanAsymmetric<0, 1>, /*Direct=*/0, Proto, 0>::sendPeerNotify(direct->out, 1, steps);
|
||||
@@ -305,8 +301,8 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
__syncwarp();
|
||||
} else {
|
||||
Primitives<T, RedOp, FanAsymmetric<0, 1>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid-tidStartReduce, nThreadsReduce, nullptr, &direct->out, args->sendbuff, args->recvbuff,
|
||||
args->redOpArg, 3*Proto::MaxGroupWidth, 1, 1);
|
||||
prims(tid-tidStartReduce, nThreadsReduce, nullptr, &direct->out, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, 3*Proto::MaxGroupWidth, 1, 1);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + (bid*direct->nHeads+direct->headRank)*chunkSize;
|
||||
int nelem = min(chunkSize, size-offset);
|
||||
@@ -317,8 +313,8 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
} else if (tid < tidStartBcast && hasUp) {
|
||||
// Gather
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_DIRECT_ARITY, 0>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid, nThreadsGather, direct->up, NULL, args->sendbuff, args->recvbuff,
|
||||
args->redOpArg, 0*Proto::MaxGroupWidth, 0, 0, args);
|
||||
prims(tid, nThreadsGather, direct->up, NULL, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, 0*Proto::MaxGroupWidth, 0, 0, work);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid*direct->nHeads*chunkSize;
|
||||
int nelem = min(direct->nHeads*chunkSize, size-offset);
|
||||
@@ -328,15 +324,15 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
if (hasDn) {
|
||||
// Recv from network, broadcast
|
||||
Primitives<T, RedOp, FanAsymmetric<1, NCCL_MAX_DIRECT_ARITY>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid-tidStartBcast, nThreadsBcast, &direct->out, direct->down, args->sendbuff, args->recvbuff,
|
||||
args->redOpArg, 1*Proto::MaxGroupWidth, 0, 0, args);
|
||||
prims(tid-tidStartBcast, nThreadsBcast, &direct->out, direct->down, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, 1*Proto::MaxGroupWidth, 0, 0, work);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + (bid*direct->nHeads+direct->headRank)*chunkSize;
|
||||
int nelem = min(chunkSize, size-offset);
|
||||
prims.recvCopyDirectSend(offset, nelem, /*postOp=*/true);
|
||||
}
|
||||
} else {
|
||||
if (args->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (work->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (tid == tidStartBcast) {
|
||||
int steps = (int)divUp(size * sizeof(T), NCCL_MAX_COLLNET_SIZE);
|
||||
Primitives<T, RedOp, FanAsymmetric<1, 0>, /*Direct=*/0, Proto, 0>::recvPeerNotify(direct->out, 0, steps);
|
||||
@@ -345,8 +341,8 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
} else {
|
||||
// Recv from network (no post thread needed)
|
||||
Primitives<T, RedOp, FanAsymmetric<1, 0>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid - tidStartBcast, nThreadsBcast, &direct->out, nullptr, args->sendbuff, args->recvbuff,
|
||||
args->redOpArg, 1 * Proto::MaxGroupWidth, 0, 0);
|
||||
prims(tid - tidStartBcast, nThreadsBcast, &direct->out, nullptr, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, 1 * Proto::MaxGroupWidth, 0, 0);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + (bid * direct->nHeads + direct->headRank) * chunkSize;
|
||||
int nelem = min(chunkSize, size - offset);
|
||||
@@ -359,18 +355,16 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCC
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
const int tid = threadIdx.x;
|
||||
struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int/*nthreads*/, struct ncclDevWorkColl* work) {
|
||||
struct ncclNvls* nvls = &ncclShmem.channel.nvls;
|
||||
ssize_t chunkSize = args->chunkCount;
|
||||
const bool hasOut = nvls->out != -1;
|
||||
const int nranks = ncclShmem.comm.nRanks;
|
||||
const int totalWarps = NCCL_MAX_NTHREADS/WARP_SIZE;
|
||||
const int bcastWarps = hasOut ? (args->regUsed ? ((totalWarps - 2) >> 1) - 1 : 2) : 0;
|
||||
const int reduceWarps = args->regUsed ? (totalWarps - bcastWarps - 2) : (hasOut ? 3 : nranks <= 6 ? 7 : 5);
|
||||
const int scatterWarps = args->regUsed ? 1 : (totalWarps - reduceWarps - bcastWarps + 1) >> 1;
|
||||
const int gatherWarps = args->regUsed ? 1 : (totalWarps - reduceWarps - bcastWarps) >> 1;
|
||||
const int bcastWarps = hasOut ? (work->regUsed ? ((totalWarps - 2) >> 1) - 1 : 2) : 0;
|
||||
const int reduceWarps = work->regUsed ? (totalWarps - bcastWarps - 2) : (hasOut ? 3 : nranks <= 6 ? 7 : 5);
|
||||
const int scatterWarps = work->regUsed ? 1 : (totalWarps - reduceWarps - bcastWarps + 1) >> 1;
|
||||
const int gatherWarps = work->regUsed ? 1 : (totalWarps - reduceWarps - bcastWarps) >> 1;
|
||||
|
||||
const int nThreadsScatter = scatterWarps*WARP_SIZE;
|
||||
const int nThreadsGather = gatherWarps*WARP_SIZE;
|
||||
@@ -381,35 +375,37 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SI
|
||||
const int tidEndReduce = tidEndGather + nThreadsReduce;
|
||||
const int tidEndBcast = tidEndReduce + nThreadsBcast;
|
||||
|
||||
if (args->oneNode) {
|
||||
if (work->oneNode) {
|
||||
ssize_t gridOffset, channelCount, chunkSize;
|
||||
ncclCollCbdPart(work, ncclShmem.channelId, NCCL_PROTO_SIMPLE, sizeof(T), (ssize_t*)nullptr, &gridOffset, &channelCount, &chunkSize);
|
||||
const ssize_t loopCount = nvls->nHeads * chunkSize;
|
||||
const ssize_t channelCount = args->workCount;
|
||||
const ssize_t gridOffset = args->workOffset;
|
||||
ssize_t offset;
|
||||
int nelem;
|
||||
int remCount = channelCount%(nvls->nHeads*chunkSize);
|
||||
int lastChunkSize = alignUp(divUp(remCount, nvls->nHeads), 16/sizeof(T));
|
||||
|
||||
if (tid < tidEndScatter) {
|
||||
// Scatter
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL>;
|
||||
Primitives<T, RedOp, FanAsymmetric<0, NCCL_MAX_NVLS_ARITY>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, nThreadsScatter, NULL, nvls->up, args->sendbuff, NULL,
|
||||
args->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
prims(tid, nThreadsScatter, NULL, nvls->up, work->sendbuff, NULL,
|
||||
work->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
for (ssize_t elemOffset = 0; elemOffset < channelCount; elemOffset += loopCount) {
|
||||
if (channelCount - elemOffset < loopCount) chunkSize = args->lastChunkCount;
|
||||
if (channelCount - elemOffset < loopCount) chunkSize = lastChunkSize;
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = args->regUsed ? 0 : min(loopCount, channelCount - elemOffset);
|
||||
nelem = work->regUsed ? 0 : min(loopCount, channelCount - elemOffset);
|
||||
prims.scatter(offset, nelem, chunkSize, chunkSize, -1, 0);
|
||||
}
|
||||
} else if (tid < tidEndGather) {
|
||||
// Gather
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL>;
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_NVLS_ARITY, 0>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid - tidEndScatter, nThreadsGather, nvls->up, NULL, NULL, args->recvbuff,
|
||||
args->redOpArg, 1 * Proto::MaxGroupWidth, 1, 1);
|
||||
prims(tid - tidEndScatter, nThreadsGather, nvls->up, NULL, NULL, work->recvbuff,
|
||||
work->redOpArg, 1 * Proto::MaxGroupWidth, 1, 1);
|
||||
for (ssize_t elemOffset = 0; elemOffset < channelCount; elemOffset += loopCount) {
|
||||
if (channelCount - elemOffset < loopCount) chunkSize = args->lastChunkCount;
|
||||
if (channelCount - elemOffset < loopCount) chunkSize = lastChunkSize;
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = args->regUsed ? 0 : min(loopCount, channelCount - elemOffset);
|
||||
nelem = work->regUsed ? 0 : min(loopCount, channelCount - elemOffset);
|
||||
prims.gather(offset, nelem, chunkSize, chunkSize, -1, 0);
|
||||
}
|
||||
} else if (tid < tidEndReduce) {
|
||||
@@ -417,10 +413,10 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SI
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 1, 1>;
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndGather, nThreadsReduce, &nvls->down, &nvls->down, NULL, NULL,
|
||||
args->redOpArg, 2 * Proto::MaxGroupWidth, 0, 0, args);
|
||||
work->redOpArg, 2 * Proto::MaxGroupWidth, 0, 0, work);
|
||||
for (ssize_t elemOffset = 0; elemOffset < channelCount; elemOffset += loopCount) {
|
||||
ssize_t chunkOffset;
|
||||
if (channelCount - elemOffset < loopCount) chunkSize = args->lastChunkCount;
|
||||
if (channelCount - elemOffset < loopCount) chunkSize = lastChunkSize;
|
||||
chunkOffset = elemOffset + nvls->headRank * chunkSize;
|
||||
offset = gridOffset + chunkOffset;
|
||||
nelem = min(chunkSize, channelCount - chunkOffset);
|
||||
@@ -428,30 +424,32 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SI
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const int bid = args->bid;
|
||||
const ssize_t loopSize = args->nChannels * nvls->nHeads * chunkSize;
|
||||
const ssize_t size = args->count;
|
||||
const int bid = ncclShmem.channelId - work->channelLo;
|
||||
const int nChannels = work->channelHi - work->channelLo + 1;
|
||||
const ssize_t chunkSize = work->collnet.chunkCount;
|
||||
const ssize_t loopSize = nChannels * nvls->nHeads * chunkSize;
|
||||
const ssize_t size = work->collnet.count;
|
||||
|
||||
if (tid < tidEndScatter) {
|
||||
// Scatter
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL>;
|
||||
Primitives<T, RedOp, FanAsymmetric<0, NCCL_MAX_NVLS_ARITY>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, nThreadsScatter, NULL, nvls->up, args->sendbuff, NULL,
|
||||
args->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
prims(tid, nThreadsScatter, NULL, nvls->up, work->sendbuff, NULL,
|
||||
work->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid * nvls->nHeads * chunkSize;
|
||||
int nelem = args->regUsed ? 0 : min(nvls->nHeads * chunkSize, size - offset);
|
||||
int nelem = work->regUsed ? 0 : min(nvls->nHeads * chunkSize, size - offset);
|
||||
prims.scatter(offset, nelem, chunkSize, chunkSize, -1, 0);
|
||||
}
|
||||
} else if (tid < tidEndGather) {
|
||||
// Gather
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL>;
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_NVLS_ARITY, 0>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid - tidEndScatter, nThreadsGather, nvls->up, NULL, NULL, args->recvbuff,
|
||||
args->redOpArg, 1 * Proto::MaxGroupWidth, 1, 1);
|
||||
prims(tid - tidEndScatter, nThreadsGather, nvls->up, NULL, NULL, work->recvbuff,
|
||||
work->redOpArg, 1 * Proto::MaxGroupWidth, 1, 1);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid * nvls->nHeads * chunkSize;
|
||||
int nelem = args->regUsed ? 0 :min(nvls->nHeads * chunkSize, size - offset);
|
||||
int nelem = work->regUsed ? 0 :min(nvls->nHeads * chunkSize, size - offset);
|
||||
prims.gather(offset, nelem, chunkSize, chunkSize, -1, 0);
|
||||
}
|
||||
} else if (tid < tidEndReduce && nvls->headRank != -1) {
|
||||
@@ -460,7 +458,7 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SI
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 1, 1>;
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndGather, nThreadsReduce, &nvls->down, &nvls->down, NULL, NULL,
|
||||
args->redOpArg, 2 * Proto::MaxGroupWidth, 0, 0, args);
|
||||
work->redOpArg, 2 * Proto::MaxGroupWidth, 0, 0, work);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + (bid * nvls->nHeads + nvls->headRank) * chunkSize;
|
||||
int nelem = min(chunkSize, size - offset);
|
||||
@@ -471,7 +469,7 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SI
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 1, 0>;
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndGather, nThreadsReduce, &nvls->down, &nvls->out, NULL, NULL,
|
||||
args->redOpArg, 2 * Proto::MaxGroupWidth, 0, 1, args);
|
||||
work->redOpArg, 2 * Proto::MaxGroupWidth, 0, 1, work);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + (bid * nvls->nHeads + nvls->headRank) * chunkSize;
|
||||
int nelem = min(chunkSize, size - offset);
|
||||
@@ -483,7 +481,7 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SI
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 0, 1>;
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndReduce, nThreadsBcast, &nvls->out, &nvls->down, NULL, NULL,
|
||||
args->redOpArg, 3 * Proto::MaxGroupWidth, 0, 0, args);
|
||||
work->redOpArg, 3 * Proto::MaxGroupWidth, 0, 0, work);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + (bid * nvls->nHeads + nvls->headRank) * chunkSize;
|
||||
int nelem = min(chunkSize, size - offset);
|
||||
@@ -495,25 +493,25 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SI
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS_TREE, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
const int tid = threadIdx.x;
|
||||
struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS_TREE, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int/*nthreads*/, struct ncclDevWorkColl* work) {
|
||||
struct ncclNvls* nvls = &ncclShmem.channel.nvls;
|
||||
const int treeUp = nvls->treeUp;
|
||||
const int* treeDown = nvls->treeDown;
|
||||
ssize_t chunkCount = args->chunkCount;
|
||||
ssize_t gridOffset, channelCount, chunkCount;
|
||||
ncclCollCbdPart(work, ncclShmem.channelId, NCCL_PROTO_SIMPLE, sizeof(T), (ssize_t*)nullptr, &gridOffset, &channelCount, &chunkCount);
|
||||
const ssize_t loopCount = nvls->nHeads * chunkCount;
|
||||
const ssize_t channelCount = args->workCount;
|
||||
const ssize_t gridOffset = args->workOffset;
|
||||
const int nranks = ncclShmem.comm.nRanks;
|
||||
const bool hasUp = treeUp != -1;
|
||||
const int totalWarps = NCCL_MAX_NTHREADS/WARP_SIZE;
|
||||
const int bcastWarps = hasUp ? (args->regUsed ? ((totalWarps - 2) >> 1) - 1 : 4) : 0;
|
||||
const int reduceWarps = args->regUsed ? (totalWarps - bcastWarps - 2) : (hasUp ? 5 : nranks <= 6 ? 7 : 5);
|
||||
const int scatterWarps = args->regUsed ? 1 : (totalWarps - reduceWarps - bcastWarps + 1) >> 1;
|
||||
const int gatherWarps = args->regUsed ? 1 : (totalWarps - reduceWarps - bcastWarps) >> 1;
|
||||
const int bcastWarps = hasUp ? (work->regUsed ? ((totalWarps - 2) >> 1) - 1 : 4) : 0;
|
||||
const int reduceWarps = work->regUsed ? (totalWarps - bcastWarps - 2) : (hasUp ? 5 : nranks <= 6 ? 7 : 5);
|
||||
const int scatterWarps = work->regUsed ? 1 : (totalWarps - reduceWarps - bcastWarps + 1) >> 1;
|
||||
const int gatherWarps = work->regUsed ? 1 : (totalWarps - reduceWarps - bcastWarps) >> 1;
|
||||
ssize_t offset;
|
||||
int nelem;
|
||||
int remCount = channelCount%(nvls->nHeads*chunkCount);
|
||||
int lastChunkCount = alignUp(divUp(remCount, nvls->nHeads), 16/sizeof(T));
|
||||
|
||||
const int nThreadsScatter = scatterWarps*WARP_SIZE;
|
||||
const int nThreadsGather = gatherWarps*WARP_SIZE;
|
||||
@@ -528,24 +526,24 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS_TREE, NCCL_PRO
|
||||
// Scatter
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL>;
|
||||
Primitives<T, RedOp, FanAsymmetric<0, NCCL_MAX_NVLS_ARITY>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, nThreadsScatter, NULL, nvls->up, args->sendbuff, NULL,
|
||||
args->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
prims(tid, nThreadsScatter, NULL, nvls->up, work->sendbuff, NULL,
|
||||
work->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
for (ssize_t elemOffset = 0; elemOffset < channelCount; elemOffset += loopCount) {
|
||||
if (channelCount - elemOffset < loopCount) chunkCount = args->lastChunkCount;
|
||||
if (channelCount - elemOffset < loopCount) chunkCount = lastChunkCount;
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = args->regUsed ? 0 : min(loopCount, channelCount - elemOffset);
|
||||
nelem = work->regUsed ? 0 : min(loopCount, channelCount - elemOffset);
|
||||
prims.scatter(offset, nelem, chunkCount, chunkCount, -1, 0);
|
||||
}
|
||||
} else if (tid < tidEndGather) {
|
||||
// Gather
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL>;
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_NVLS_ARITY, 0>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid - tidEndScatter, nThreadsGather, nvls->up, NULL, NULL, args->recvbuff,
|
||||
args->redOpArg, 1 * Proto::MaxGroupWidth, 1, 1);
|
||||
prims(tid - tidEndScatter, nThreadsGather, nvls->up, NULL, NULL, work->recvbuff,
|
||||
work->redOpArg, 1 * Proto::MaxGroupWidth, 1, 1);
|
||||
for (ssize_t elemOffset = 0; elemOffset < channelCount; elemOffset += loopCount) {
|
||||
if (channelCount - elemOffset < loopCount) chunkCount = args->lastChunkCount;
|
||||
if (channelCount - elemOffset < loopCount) chunkCount = lastChunkCount;
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = args->regUsed ? 0 : min(loopCount, channelCount - elemOffset);
|
||||
nelem = work->regUsed ? 0 : min(loopCount, channelCount - elemOffset);
|
||||
prims.gather(offset, nelem, chunkCount, chunkCount, -1, 0);
|
||||
}
|
||||
} else if (tid < tidEndReduce && nvls->headRank != -1) {
|
||||
@@ -554,10 +552,10 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS_TREE, NCCL_PRO
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 1, 1>;
|
||||
Primitives<T, RedOp, FanSymmetric<3>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndGather, nThreadsReduce, treeDown, treeDown, NULL, NULL,
|
||||
args->redOpArg, 2 * Proto::MaxGroupWidth, 0, 0, args);
|
||||
work->redOpArg, 2 * Proto::MaxGroupWidth, 0, 0, work);
|
||||
for (ssize_t elemOffset = 0; elemOffset < channelCount; elemOffset += loopCount) {
|
||||
ssize_t chunkOffset;
|
||||
if (channelCount - elemOffset < loopCount) chunkCount = args->lastChunkCount;
|
||||
if (channelCount - elemOffset < loopCount) chunkCount = lastChunkCount;
|
||||
chunkOffset = elemOffset + nvls->headRank * chunkCount;
|
||||
offset = gridOffset + chunkOffset;
|
||||
nelem = min(chunkCount, channelCount - chunkOffset);
|
||||
@@ -568,10 +566,10 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS_TREE, NCCL_PRO
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 1, 0>;
|
||||
Primitives<T, RedOp, FanAsymmetric<3, 1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndGather, nThreadsReduce, treeDown, &treeUp, NULL, NULL,
|
||||
args->redOpArg, 2 * Proto::MaxGroupWidth, 0, 0, args);
|
||||
work->redOpArg, 2 * Proto::MaxGroupWidth, 0, 0, work);
|
||||
for (ssize_t elemOffset = 0; elemOffset < channelCount; elemOffset += loopCount) {
|
||||
ssize_t chunkOffset;
|
||||
if (channelCount - elemOffset < loopCount) chunkCount = args->lastChunkCount;
|
||||
if (channelCount - elemOffset < loopCount) chunkCount = lastChunkCount;
|
||||
chunkOffset = elemOffset + nvls->headRank * chunkCount;
|
||||
offset = gridOffset + chunkOffset;
|
||||
nelem = min(chunkCount, channelCount - chunkOffset);
|
||||
@@ -583,10 +581,10 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS_TREE, NCCL_PRO
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 0, 1>;
|
||||
Primitives<T, RedOp, FanAsymmetric<1, 3>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndReduce, nThreadsBcast, &treeUp, treeDown, NULL, NULL,
|
||||
args->redOpArg, 3 * Proto::MaxGroupWidth, 0, 0, args);
|
||||
work->redOpArg, 3 * Proto::MaxGroupWidth, 0, 0, work);
|
||||
for (ssize_t elemOffset = 0; elemOffset < channelCount; elemOffset += loopCount) {
|
||||
ssize_t chunkOffset;
|
||||
if (channelCount - elemOffset < loopCount) chunkCount = args->lastChunkCount;
|
||||
if (channelCount - elemOffset < loopCount) chunkCount = lastChunkCount;
|
||||
chunkOffset = elemOffset + nvls->headRank * chunkCount;
|
||||
offset = gridOffset + chunkOffset;
|
||||
nelem = min(chunkCount, channelCount - chunkOffset);
|
||||
@@ -597,17 +595,15 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS_TREE, NCCL_PRO
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_CHAIN, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
const int tid = threadIdx.x;
|
||||
const int nthreads = args->nWarps*WARP_SIZE;
|
||||
const int bid = args->bid;
|
||||
const int nChannels = args->nChannels;
|
||||
struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_CHAIN, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
const int bid = ncclShmem.channelId - work->channelLo;
|
||||
const int nChannels = work->channelHi - work->channelLo + 1;
|
||||
ncclTree *tree = &ncclShmem.channel.collnetChain;
|
||||
ssize_t chunkSize = args->chunkCount;
|
||||
ssize_t chunkSize = work->collnet.chunkCount;
|
||||
const ssize_t loopSize = int(nChannels*chunkSize);
|
||||
const int nranks = ncclShmem.comm.nRanks;
|
||||
const ssize_t size = args->count;
|
||||
const ssize_t size = work->collnet.count;
|
||||
|
||||
int nthreadsSplit = nthreads/2;
|
||||
if (nthreadsSplit >= 256) nthreadsSplit += 64;
|
||||
@@ -634,7 +630,7 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_CHAIN, NCCL
|
||||
|
||||
if (tid < nthreadsSplit) {
|
||||
if (recv == -1) {
|
||||
if (args->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (work->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (groupTid == 0) {
|
||||
int steps = (int)divUp(size * sizeof(T), NCCL_MAX_COLLNET_SIZE);
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>::sendPeerNotify(send, connIndex, steps);
|
||||
@@ -642,8 +638,8 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_CHAIN, NCCL
|
||||
__syncwarp();
|
||||
} else {
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(groupTid, groupNthreads, &recv, &send, args->sendbuff, args->recvbuff,
|
||||
args->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
prims(groupTid, groupNthreads, &recv, &send, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid * int(chunkSize);
|
||||
int nelem = min(chunkSize, size - offset);
|
||||
@@ -652,8 +648,8 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_CHAIN, NCCL
|
||||
}
|
||||
} else {
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(groupTid, groupNthreads, &recv, &send, args->sendbuff, args->recvbuff,
|
||||
args->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
prims(groupTid, groupNthreads, &recv, &send, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid * int(chunkSize);
|
||||
int nelem = min(chunkSize, size - offset);
|
||||
@@ -665,7 +661,7 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_CHAIN, NCCL
|
||||
if (recv == nranks) {
|
||||
// I'm the first in the broadcast chain, I need to perform the division (postOp)
|
||||
if (send == -1) {
|
||||
if (args->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (work->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (groupTid == 0) {
|
||||
int steps = (int)divUp(size * sizeof(T), NCCL_MAX_COLLNET_SIZE);
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>::recvPeerNotify(recv, connIndex, steps);
|
||||
@@ -673,8 +669,8 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_CHAIN, NCCL
|
||||
__syncwarp();
|
||||
} else {
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(groupTid, groupNthreads, &recv, &send, args->sendbuff, args->recvbuff,
|
||||
args->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
prims(groupTid, groupNthreads, &recv, &send, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid * int(chunkSize);
|
||||
int nelem = min(chunkSize, size - offset);
|
||||
@@ -683,8 +679,8 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_CHAIN, NCCL
|
||||
}
|
||||
} else {
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(groupTid, groupNthreads, &recv, &send, args->sendbuff, args->recvbuff,
|
||||
args->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
prims(groupTid, groupNthreads, &recv, &send, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid * int(chunkSize);
|
||||
int nelem = min(chunkSize, size - offset);
|
||||
@@ -693,8 +689,8 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_CHAIN, NCCL
|
||||
}
|
||||
} else {
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(groupTid, groupNthreads, &recv, &send, args->sendbuff, args->recvbuff,
|
||||
args->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
prims(groupTid, groupNthreads, &recv, &send, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
if (send == -1) {
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid*int(chunkSize);
|
||||
@@ -714,29 +710,29 @@ struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_CHAIN, NCCL
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
runRing<T, RedOp, ProtoLL>(args);
|
||||
struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
runRing<T, RedOp, ProtoLL>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_TREE, NCCL_PROTO_LL> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
runTreeSplit<T, RedOp, ProtoLL>(args);
|
||||
struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_TREE, NCCL_PROTO_LL> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
runTreeSplit<T, RedOp, ProtoLL>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL128> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
runRing<T, RedOp, ProtoLL128>(args);
|
||||
struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL128> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
runRing<T, RedOp, ProtoLL128>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_TREE, NCCL_PROTO_LL128> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
runTreeSplit<T, RedOp, ProtoLL128>(args);
|
||||
struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_TREE, NCCL_PROTO_LL128> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
runTreeSplit<T, RedOp, ProtoLL128>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
+18
-19
@@ -10,23 +10,22 @@
|
||||
|
||||
namespace {
|
||||
template<typename T, typename RedOp, typename Proto>
|
||||
__device__ __forceinline__ void runRing(ncclWorkElem *args) {
|
||||
const int tid = threadIdx.x;
|
||||
const int nthreads = (int)args->nWarps * WARP_SIZE;
|
||||
__device__ __forceinline__ void runRing(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
ncclRing *ring = &ncclShmem.channel.ring;
|
||||
const int rank = ring->userRanks[0];
|
||||
const int nextRank = ring->userRanks[1];
|
||||
const int root = args->root;
|
||||
const size_t chunkCount = args->chunkCount;
|
||||
const size_t channelCount = args->workCount;
|
||||
const size_t gridOffset = args->workOffset;
|
||||
const int root = work->root;
|
||||
size_t chunkCount;
|
||||
size_t channelCount;
|
||||
size_t gridOffset;
|
||||
ncclCollCbdPart(work, ncclShmem.channelId, Proto::Id, sizeof(T), (size_t*)nullptr, &gridOffset, &channelCount, &chunkCount);
|
||||
size_t offset;
|
||||
int nelem;
|
||||
|
||||
T *inputBuf = (T*)args->sendbuff;
|
||||
T *outputBuf = (T*)args->recvbuff;
|
||||
T *inputBuf = (T*)work->sendbuff;
|
||||
T *outputBuf = (T*)work->recvbuff;
|
||||
Primitives<T, RedOp, FanSymmetric<1>, 0, Proto, 0>
|
||||
prims(tid, nthreads, &ring->prev, &ring->next, inputBuf, outputBuf, args->redOpArg);
|
||||
prims(tid, nthreads, &ring->prev, &ring->next, inputBuf, outputBuf, work->redOpArg);
|
||||
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
@@ -48,23 +47,23 @@ namespace {
|
||||
}
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncBroadcast, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
struct RunWorkColl<ncclFuncBroadcast, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
using Proto = ProtoSimple<BROADCAST_CHUNKSTEPS/BROADCAST_SLICESTEPS, BROADCAST_SLICESTEPS>;
|
||||
runRing<T, RedOp, Proto>(args);
|
||||
runRing<T, RedOp, Proto>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncBroadcast, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
runRing<T, RedOp, ProtoLL>(args);
|
||||
struct RunWorkColl<ncclFuncBroadcast, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
runRing<T, RedOp, ProtoLL>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncBroadcast, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL128> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
runRing<T, RedOp, ProtoLL128>(args);
|
||||
struct RunWorkColl<ncclFuncBroadcast, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL128> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
runRing<T, RedOp, ProtoLL128>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
@@ -14,11 +14,11 @@ __shared__ ncclShmemData ncclShmem;
|
||||
#endif
|
||||
|
||||
struct RunWorkNop {
|
||||
__device__ void run(ncclWork *w) {}
|
||||
__device__ void run() {}
|
||||
};
|
||||
|
||||
__global__ void ncclDevKernel_Generic(struct ncclDevComm* comm, uint64_t channelMask, struct ncclWork* workHead) {
|
||||
ncclKernelMain<-1, RunWorkNop>(comm, channelMask, workHead);
|
||||
__global__ void ncclDevKernel_Generic(ncclDevKernelArgs4K NCCL_GRID_CONSTANT const args4K) {
|
||||
ncclKernelMain<-1, RunWorkNop>(&args4K.args);
|
||||
}
|
||||
|
||||
__device__ void ncclDevFunc_Nop() {}
|
||||
+288
-118
@@ -10,10 +10,19 @@
|
||||
#include "collectives.h"
|
||||
#include "device.h"
|
||||
#include "op128.h"
|
||||
#include "reduce_kernel.h"
|
||||
#include "network/unpack/unpack_defs.h"
|
||||
|
||||
#define COLL_UNROLL (ncclCollUnroll())
|
||||
|
||||
#if __CUDA_ARCH__ >= 700
|
||||
// __grid_constant__ appears to break cuda-gdb
|
||||
//#define NCCL_GRID_CONSTANT __grid_constant__
|
||||
#define NCCL_GRID_CONSTANT
|
||||
#else
|
||||
#define NCCL_GRID_CONSTANT
|
||||
#endif
|
||||
|
||||
typedef void(*ncclDevFuncPtr_t)();
|
||||
extern __device__ ncclDevFuncPtr_t const ncclDevFuncTable[];
|
||||
|
||||
@@ -31,18 +40,28 @@ struct ncclShmemGroup {
|
||||
};
|
||||
|
||||
struct ncclShmemData {
|
||||
struct ncclShmemGroup groups[NCCL_MAX_GROUPS];
|
||||
uint64_t redOpArgs[NCCL_MAX_ARITY+1];
|
||||
struct ncclDevKernelArgs args;
|
||||
int channelId;
|
||||
int aborted;
|
||||
alignas(16) struct ncclDevComm comm;
|
||||
alignas(16) struct ncclDevChannel channel;
|
||||
alignas(16) struct ncclWork work;
|
||||
|
||||
int batchIx, nextBatchIx;
|
||||
enum ncclDevWorkType workType;
|
||||
uint8_t directMode;
|
||||
uint16_t funcId;
|
||||
int nWorks;
|
||||
int workSize;
|
||||
uint32_t workConsumed;
|
||||
struct ncclShmemGroup groups[NCCL_MAX_GROUPS];
|
||||
uint64_t redOpArgs[NCCL_MAX_NVLS_ARITY+1];
|
||||
|
||||
alignas(16) char workStorage[1024];
|
||||
|
||||
alignas(16) union {
|
||||
unpackShmem unpack;
|
||||
} devicePlugin;
|
||||
};
|
||||
static_assert(offsetof(struct ncclShmemData, work)%16 == 0, "shmem.work needs to be 16B aligned");
|
||||
|
||||
extern __shared__ ncclShmemData ncclShmem;
|
||||
#if __CUDA_ARCH__ >= 700
|
||||
@@ -55,14 +74,62 @@ __device__ inline void* ncclScratchForWarp(int warp) {
|
||||
return (char*)ncclShmemPerWarp + warp*ncclShmemScratchWarpSize();
|
||||
}
|
||||
|
||||
__device__ inline bool barrierReduceAny(int bit) {
|
||||
uint32_t popc;
|
||||
asm ("{"
|
||||
".reg .pred barr_pred;"
|
||||
"setp.eq.u32 barr_pred, %1, 1;"
|
||||
"bar.red.popc.u32 %0, 2, barr_pred;"
|
||||
"}" : "=r"(popc) : "r"(bit));
|
||||
return popc != 0;
|
||||
__device__ inline void barrier_sync(int name) {
|
||||
#if 0
|
||||
asm volatile("barrier.sync %0;" :: "r"(name) : "memory");
|
||||
#else
|
||||
asm volatile("barrier.sync.aligned %0;" :: "r"(name) : "memory");
|
||||
#endif
|
||||
}
|
||||
__device__ inline void barrier_sync(int name, int nThreads) {
|
||||
#if 0
|
||||
asm volatile("barrier.sync %0, %1;" :: "r"(name), "r"(nThreads) : "memory");
|
||||
#else
|
||||
asm volatile("barrier.sync.aligned %0, %1;" :: "r"(name), "r"(nThreads) : "memory");
|
||||
#endif
|
||||
}
|
||||
__device__ inline void barrier_sync_aligned(int name) {
|
||||
asm volatile("barrier.sync.aligned %0;" :: "r"(name) : "memory");
|
||||
}
|
||||
__device__ inline void barrier_sync_aligned(int name, int nThreads) {
|
||||
asm volatile("barrier.sync.aligned %0, %1;" :: "r"(name), "r"(nThreads) : "memory");
|
||||
}
|
||||
|
||||
__device__ inline bool barrier_red_or(bool vote, int name) {
|
||||
int ans;
|
||||
asm("{ .reg .pred p;"
|
||||
" setp.ne.s32 p, %1, 0;"
|
||||
" barrier.red.or.pred p, %2, p; "
|
||||
" selp.s32 %0, 1, 0, p; }"
|
||||
: "=r"(ans) : "r"((int)vote), "r"(name) : "memory");
|
||||
return bool(ans);
|
||||
}
|
||||
__device__ inline bool barrier_red_or(bool vote, int name, int nThreads) {
|
||||
int ans;
|
||||
asm("{ .reg .pred p;"
|
||||
" setp.ne.s32 p, %1, 0;"
|
||||
" barrier.red.or.pred p, %2, %3, p; "
|
||||
" selp.s32 %0, 1, 0, p; }"
|
||||
: "=r"(ans) : "r"((int)vote), "r"(name), "r"(nThreads) : "memory");
|
||||
return bool(ans);
|
||||
}
|
||||
__device__ inline bool barrier_red_or_aligned(bool vote, int name) {
|
||||
int ans;
|
||||
asm("{ .reg .pred p;"
|
||||
" setp.ne.s32 p, %1, 0;"
|
||||
" barrier.red.or.pred.aligned p, %2, p; "
|
||||
" selp.s32 %0, 1, 0, p; }"
|
||||
: "=r"(ans) : "r"((int)vote), "r"(name) : "memory");
|
||||
return bool(ans);
|
||||
}
|
||||
__device__ inline bool barrier_red_or_aligned(bool vote, int name, int nThreads) {
|
||||
int ans;
|
||||
asm("{ .reg .pred p;"
|
||||
" setp.ne.s32 p, %1, 0;"
|
||||
" barrier.red.or.pred.aligned p, %2, %3, p; "
|
||||
" selp.s32 %0, 1, 0, p; }"
|
||||
: "=r"(ans) : "r"((int)vote), "r"(name), "r"(nThreads) : "memory");
|
||||
return bool(ans);
|
||||
}
|
||||
|
||||
// Copy 16-byte aligned data. You must call with at least `(bytes+15)/16` threads.
|
||||
@@ -71,158 +138,261 @@ inline __device__ void copyToShmem16(int tid, void* dst, void const* src, int by
|
||||
if (offset < bytes) {
|
||||
uint64_t a=0, b=0;
|
||||
asm("ld.v2.u64 {%0,%1},[%2];" : "=l"(a),"=l"(b) : "l"((char const*)src + offset));
|
||||
asm volatile("st.v2.u64 [%0],{%1,%2};" :: "l"((char*)dst + offset), "l"(a), "l"(b));
|
||||
uint32_t udst = (uint32_t)__cvta_generic_to_shared(dst);
|
||||
asm volatile("st.shared.v2.u64 [%0],{%1,%2};" :: "r"(udst + offset), "l"(a), "l"(b));
|
||||
}
|
||||
}
|
||||
|
||||
// Must run with at least 64 threads
|
||||
__device__ __forceinline__ void loadWorkBatchToShmem(
|
||||
int tid, int tn, struct ncclDevKernelArgs const* args, int batchIx
|
||||
) {
|
||||
int lane = tid%WARP_SIZE;
|
||||
int workCursor = 0; // num works written in previous loop iterations.
|
||||
while (true) {
|
||||
struct ncclDevWorkBatch batch = ((struct ncclDevWorkBatch*)(args+1))[batchIx];
|
||||
|
||||
// fnsOfBitset[n] = index of n'th set bit in batch.offsetBitset.
|
||||
// PTX has instruction "fns" (find n-th set) but it expands to a lot of SASS,
|
||||
// since we know all lanes will be querying the same bitmask we can compute
|
||||
// much faster using shared memory.
|
||||
uint8_t* fnsOfBitset = (uint8_t*)ncclScratchForWarp(threadIdx.x/WARP_SIZE);
|
||||
__syncwarp();
|
||||
if (uint32_t(batch.offsetBitset) & (1u<<lane)) {
|
||||
int nWorksBelow = __popc(uint32_t(batch.offsetBitset) & ((1u<<lane)-1));
|
||||
fnsOfBitset[nWorksBelow] = lane;
|
||||
}
|
||||
int nWorksLow32 = __popc(uint32_t(batch.offsetBitset)); // just of low 32 bits
|
||||
if (uint32_t(batch.offsetBitset>>32) & (1u<<lane)) {
|
||||
int nWorksBelow = nWorksLow32;
|
||||
nWorksBelow += __popc(uint32_t(batch.offsetBitset>>32) & ((1u<<lane)-1));
|
||||
fnsOfBitset[nWorksBelow] = 32 + lane;
|
||||
}
|
||||
int nWorks = nWorksLow32 + __popc(uint32_t(batch.offsetBitset>>32)); // add high 32 bits
|
||||
__syncwarp();
|
||||
|
||||
int workSize;
|
||||
int nPacks; // total number of packs loaded, each pack is 16 bytes
|
||||
int packInWork; // my pack index within work struct
|
||||
int dstWork; // my work index in contiguous destination shmem
|
||||
switch (batch.workType) {
|
||||
case (int)ncclDevWorkTypeP2p:
|
||||
workSize = sizeof(struct ncclDevWorkP2p);
|
||||
nPacks = nWorks*(workSize/16);
|
||||
packInWork = tid%(workSize/16);
|
||||
dstWork = tid/(workSize/16);
|
||||
break;
|
||||
case (int)ncclDevWorkTypeColl:
|
||||
workSize = sizeof(struct ncclDevWorkColl);
|
||||
nPacks = nWorks*(workSize/16);
|
||||
packInWork = tid%(workSize/16);
|
||||
dstWork = tid/(workSize/16);
|
||||
break;
|
||||
case (int)ncclDevWorkTypeCollReg:
|
||||
default:
|
||||
workSize = sizeof(struct ncclDevWorkCollReg);
|
||||
nPacks = nWorks*(workSize/16);
|
||||
packInWork = tid%(workSize/16);
|
||||
dstWork = tid/(workSize/16);
|
||||
break;
|
||||
}
|
||||
if (tid == 0) {
|
||||
ncclShmem.workSize = workSize;
|
||||
ncclShmem.workConsumed = batch.offsetBase + (64-__clzll(batch.offsetBitset))*workSize;
|
||||
}
|
||||
// We deliberately replicate these div and mod calculations into the case
|
||||
// blocks above so that they get constant divisor optimizations by the compiler.
|
||||
// packInWork = tid%(workSize/16);
|
||||
// dstWork = tid/(workSize/16);
|
||||
|
||||
// We can only assume we have 64 threads, which means we can read at most 1024 bytes
|
||||
// here which is the per batch maximum.
|
||||
if (tid < nPacks) {
|
||||
int srcWork = fnsOfBitset[dstWork]; // find n'th set bit in batch.offsetBitset
|
||||
ulong2 tmp;
|
||||
// The loads done in these two cases must be kept separate since we are
|
||||
// relying on the compiler to use "ld.param" in the first one. The parameter
|
||||
// space is not generically addressable, so any attempt to load through
|
||||
// a pointer that *might* be parameter space backed will cause the
|
||||
// compiler to spill the parameter struct (4K!) to each thread's local space
|
||||
// before creating a pointer (to the spill) and decimate perf.
|
||||
//
|
||||
// An example of what not to do would be the following:
|
||||
//
|
||||
// if (condition) {
|
||||
// // The compiler could spill parameter_variable to local space and take
|
||||
// // the address of that, since when src is loaded below it could also
|
||||
// // be global space.
|
||||
// src = ¶meter_variable;
|
||||
// } else {
|
||||
// src = &global_variable;
|
||||
// }
|
||||
// memcpy(dst, src, n);
|
||||
if (ncclShmem.args.workStorageType == ncclDevWorkStorageTypeArgs) {
|
||||
char* src = (char*)args + (batch.offsetBase + srcWork*workSize + packInWork*16);
|
||||
tmp = *(ulong2*)src; // becomes ld.param.v2.u64
|
||||
} else {
|
||||
char* src = (char*)ncclShmem.args.workBuf + ((batch.offsetBase + srcWork*workSize + packInWork*16) & ncclShmem.args.workMask);
|
||||
tmp = *(ulong2*)src; // becomes ld.v2.u64
|
||||
}
|
||||
char* dst = ncclShmem.workStorage;
|
||||
dst += (workCursor + dstWork)*workSize + packInWork*16;
|
||||
*(ulong2*)dst = tmp;
|
||||
}
|
||||
workCursor += nWorks;
|
||||
|
||||
if (batch.nextExtends) {
|
||||
batchIx += batch.nextJump;
|
||||
tid -= 64; // Rotate threads so we use the next two warps for next batch struct.
|
||||
if (tid < 0) tid += tn;
|
||||
} else {
|
||||
if (tid == 0) {
|
||||
ncclShmem.batchIx = batchIx;
|
||||
ncclShmem.nextBatchIx = (batch.nextJump == 0) ? -1 : batchIx + batch.nextJump;
|
||||
ncclShmem.workType = (enum ncclDevWorkType)batch.workType;
|
||||
ncclShmem.nWorks = workCursor;
|
||||
ncclShmem.funcId = batch.funcId;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<ncclFunc_t Fn, typename T, typename RedOp, int Algo, int Proto>
|
||||
struct RunWorkElement {
|
||||
__device__ void run(ncclWorkElem*) {
|
||||
struct RunWorkColl {
|
||||
__device__ void run(int tid, int tn, struct ncclDevWorkColl* work) {
|
||||
// Put NOT IMPLEMENTED behavior here.
|
||||
}
|
||||
};
|
||||
|
||||
template<ncclFunc_t Fn, typename T, typename RedOp, int Algo, int Proto>
|
||||
struct RunWork {
|
||||
struct RunWorkBatch;
|
||||
|
||||
// Specialized for P2p in sendrecv.h
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkBatch<ncclFuncSendRecv, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE>;
|
||||
|
||||
// Specialized here for non-P2p (Coll and CollReg)
|
||||
template<ncclFunc_t Fn, typename T, typename RedOp, int Algo, int Proto>
|
||||
struct RunWorkBatch {
|
||||
// This __forceinline__ is necessary. The compiler was inserting a function call
|
||||
// here from the LL ncclKernel.
|
||||
__device__ __forceinline__ void run(ncclWork *w) {
|
||||
int wid = threadIdx.x / WARP_SIZE;
|
||||
ncclWorkElem* we = w->header.type == ncclWorkTypeRegColl ? &w->regElems[0].elem : &w->elems[0];
|
||||
int stride = w->header.type == ncclWorkTypeRegColl ? sizeof(ncclWorkElemReg) : sizeof(ncclWorkElem);
|
||||
#pragma unroll 1
|
||||
while ((char*)we + stride <= (char*)(w+1) && we->isUsed) {
|
||||
if (wid < we->nWarps) {
|
||||
RunWorkElement<Fn, T, RedOp, Algo, Proto>().run(we);
|
||||
__device__ __forceinline__ void run() {
|
||||
int tid = threadIdx.x;
|
||||
int tn = blockDim.x;
|
||||
|
||||
if (RedOpArg<RedOp>::ArgUsed) {
|
||||
int nWorks = ncclShmem.nWorks;
|
||||
for (int w=tid; w < nWorks; w += tn) {
|
||||
struct ncclDevWorkColl* work = (ncclDevWorkColl*)(ncclShmem.workStorage + w*ncclShmem.workSize);
|
||||
if (work->redOpArgIsPtr) {
|
||||
work->redOpArg = RedOpArg<RedOp>::loadArg(reinterpret_cast<void*>(work->redOpArg));
|
||||
}
|
||||
}
|
||||
we = (ncclWorkElem*)((char*)we + stride);
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
#pragma unroll 1
|
||||
for (int w=0; w < ncclShmem.nWorks; w++) {
|
||||
struct ncclDevWorkColl* work = (struct ncclDevWorkColl*)(ncclShmem.workStorage + w*ncclShmem.workSize);
|
||||
if (w != 0) {
|
||||
struct ncclDevWorkColl* workPrev = (struct ncclDevWorkColl*)(ncclShmem.workStorage + (w-1)*ncclShmem.workSize);
|
||||
if (work->nWarps != workPrev->nWarps) __syncthreads();
|
||||
}
|
||||
int subtn = work->nWarps*WARP_SIZE;
|
||||
if (tid < subtn) RunWorkColl<Fn, T, RedOp, Algo, Proto>().run(tid, subtn, work);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static __device__ void ncclRedopPtrDeref(struct ncclWorkElem* we) {
|
||||
if (we->isUsed && we->redOpArgIsPtr) {
|
||||
/* redOpArg is a pointer to the scalar value, so we'll dereference it
|
||||
* here so that redOpArg holds the bits of the scalar going forward.
|
||||
* The tricky thing is we don't know its type T since that's encoded in
|
||||
* the funcIndex. Because it would be difficult to get sizeof(T) from
|
||||
* funcIndex, we'll cheat and just dereference the largest possible size
|
||||
* given the alignment of the pointer. We might be reading in more bytes
|
||||
* than we need but that's harmless.
|
||||
*/
|
||||
if (we->redOpArg%2 != 0)
|
||||
we->redOpArg = *reinterpret_cast<uint8_t*>(we->redOpArg);
|
||||
else if (we->redOpArg%4 != 0)
|
||||
we->redOpArg = *reinterpret_cast<uint16_t*>(we->redOpArg);
|
||||
else if (we->redOpArg%8 != 0)
|
||||
we->redOpArg = *reinterpret_cast<uint32_t*>(we->redOpArg);
|
||||
else
|
||||
we->redOpArg = *reinterpret_cast<uint64_t*>(we->redOpArg);
|
||||
}
|
||||
}
|
||||
|
||||
template<int SpecializedFnId, typename SpecializedRunWork>
|
||||
__device__ void ncclKernelMain(struct ncclDevComm* comm, uint64_t channelMask, struct ncclWork* workHead) {
|
||||
template<int SpecializedFnId, typename SpecializedRunWorkBatch>
|
||||
__device__ __forceinline__ void ncclKernelMain(struct ncclDevKernelArgs const* args) {
|
||||
int tid = threadIdx.x;
|
||||
int tn = blockDim.x;
|
||||
|
||||
// Copy kernel args to shmem and then only read those. Otherwise the compiler
|
||||
// will end up putting the args into thread local stack which is very wasteful.
|
||||
if (tid < sizeof(ncclDevKernelArgs)/sizeof(uint32_t)) {
|
||||
((uint32_t*)&ncclShmem.args)[tid] = ((uint32_t*)args)[tid];
|
||||
}
|
||||
|
||||
// To map blockId to channelId, we need the n'th set bit of channelMask which
|
||||
// is the inverse of counting the number of set bits among the the first n.
|
||||
if (tid < WARP_SIZE) {
|
||||
int x = tid;
|
||||
if (channelMask & (1ull<<x)) {
|
||||
int y = __popcll(channelMask & ((1ull<<x)-1));
|
||||
if (blockIdx.x == y) ncclShmem.channelId = x;
|
||||
}
|
||||
if (32 < MAXCHANNELS) {
|
||||
x = 32 + tid;
|
||||
if (channelMask & (1ull<<x)) {
|
||||
int y = __popcll(channelMask & ((1ull<<x)-1));
|
||||
if (blockIdx.x == y) ncclShmem.channelId = x;
|
||||
}
|
||||
}
|
||||
// PTX has the fns instruction which does this but is extremely slow. We can
|
||||
// do better when we know all threads are querying the same bitmask.
|
||||
if (tid < MAXCHANNELS && (args->channelMask & (1ull<<tid))) {
|
||||
int n = __popcll(args->channelMask & ((1ull<<tid)-1));
|
||||
if (blockIdx.x == n) ncclShmem.channelId = tid;
|
||||
}
|
||||
__syncthreads(); // publish ncclShmem.channelId
|
||||
int channelId = ncclShmem.channelId;
|
||||
__syncthreads(); // publish ncclShmem.{args, channelId}
|
||||
/* set abort flag to 0 */
|
||||
if (tid == 0) ncclShmem.aborted = 0;
|
||||
|
||||
if (true) {
|
||||
void *dst, *src;
|
||||
int bytes;
|
||||
// Use first 3 warps to load comm, channel, and work into ncclShmem
|
||||
switch (tid/WARP_SIZE) {
|
||||
case 0:
|
||||
dst = &ncclShmem.comm;
|
||||
src = comm;
|
||||
bytes = sizeof(ncclDevComm);
|
||||
// Use first 2 warps to load comm and channel, and reamaining load work batch.
|
||||
switch (tid/WARP_SIZE) {
|
||||
case 0:
|
||||
{ void* dst = &ncclShmem.comm;
|
||||
void* src = ncclShmem.args.comm;
|
||||
int bytes = sizeof(ncclDevComm);
|
||||
static_assert(sizeof(ncclDevComm) <= 16*WARP_SIZE, "ncclDevComm cannot be loaded by a single warp in one insn.");
|
||||
break;
|
||||
case 1:
|
||||
// Get address of channel without incurring indirect load from ncclDevComm::channels
|
||||
dst = &ncclShmem.channel;
|
||||
src = &((ncclDevCommAndChannels*)comm)->channels[channelId];
|
||||
bytes = sizeof(ncclDevChannel);
|
||||
copyToShmem16(tid, dst, src, bytes);
|
||||
} break;
|
||||
case 1:
|
||||
{ // Get address of channel without incurring indirect load from ncclDevComm::channels
|
||||
void* dst = &ncclShmem.channel;
|
||||
void* src = &((ncclDevCommAndChannels*)ncclShmem.args.comm)->channels[ncclShmem.channelId];
|
||||
int bytes = sizeof(ncclDevChannel);
|
||||
static_assert(sizeof(ncclDevChannel) <= 16*WARP_SIZE, "ncclDevChannel cannot be loaded by a single warp in one insn.");
|
||||
break;
|
||||
case 2:
|
||||
dst = &ncclShmem.work;
|
||||
src = workHead + blockIdx.x;
|
||||
bytes = sizeof(ncclWork);
|
||||
static_assert(sizeof(ncclWork) <= 16*WARP_SIZE, "ncclWork cannot be loaded by a single warp in one insn.");
|
||||
break;
|
||||
default:
|
||||
bytes = 0;
|
||||
break;
|
||||
}
|
||||
if (bytes) copyToShmem16(tid%WARP_SIZE, dst, src, bytes);
|
||||
copyToShmem16(tid-WARP_SIZE, dst, src, bytes);
|
||||
} break;
|
||||
default:
|
||||
{ int subtid = tid - 2*WARP_SIZE;
|
||||
int subtn = tn - 2*WARP_SIZE;
|
||||
loadWorkBatchToShmem(subtid, subtn, args, /*batchIx=*/blockIdx.x);
|
||||
} break;
|
||||
}
|
||||
__syncthreads(); // publish ncclShmem
|
||||
|
||||
if (tid == 0 && ncclShmem.args.workStorageType == ncclDevWorkStorageTypeFifo) {
|
||||
// ncclShmem.workConsumed written by loadWorkBatchToShmem before __syncthreads()
|
||||
ncclShmem.comm.workConsumed[ncclShmem.channelId] = ncclShmem.workConsumed;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// Notify host that all fifo reads are complete.
|
||||
if (tid == 0 && ncclShmem.work.header.isLast && ncclShmem.work.header.inFifo) {
|
||||
*ncclShmem.channel.workFifoDone = ncclShmem.work.header.doneAcks;
|
||||
}
|
||||
|
||||
__syncwarp();
|
||||
if (ncclShmem.work.header.type == ncclWorkTypeColl) {
|
||||
if (tid < NCCL_MAX_WORK_ELEMENTS) ncclRedopPtrDeref(&ncclShmem.work.elems[tid]);
|
||||
} else if (ncclShmem.work.header.type == ncclWorkTypeRegColl) {
|
||||
if (tid < NCCL_MAX_WORK_ELEMENTS_REG) ncclRedopPtrDeref(&ncclShmem.work.regElems[tid].elem);
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
if (0 <= SpecializedFnId && ncclShmem.work.header.funcIndex == (unsigned)SpecializedFnId) {
|
||||
SpecializedRunWork().run(&ncclShmem.work);
|
||||
if (0 <= SpecializedFnId && ncclShmem.funcId == (unsigned)SpecializedFnId) {
|
||||
SpecializedRunWorkBatch().run();
|
||||
} else {
|
||||
ncclDevFuncTable[ncclShmem.work.header.funcIndex]();
|
||||
ncclDevFuncTable[ncclShmem.funcId]();
|
||||
}
|
||||
|
||||
int workIxNext = ncclShmem.work.header.workNext;
|
||||
if (ncclShmem.nextBatchIx == -1) break;
|
||||
int batchIx = ncclShmem.nextBatchIx;
|
||||
__syncthreads();
|
||||
if (ncclShmem.work.header.isLast) break;
|
||||
loadWorkBatchToShmem(tid, tn, args, batchIx);
|
||||
|
||||
copyToShmem16(tid, &ncclShmem.work, workHead + workIxNext, sizeof(ncclWork));
|
||||
|
||||
{ // Check whether the last operation was aborted and make sure all threads exit
|
||||
int aborted = tid == 0 ? *comm->abortFlag : 0;
|
||||
if (barrierReduceAny(aborted)) // publish ncclShmem.work
|
||||
break;
|
||||
// Check whether the last operation was aborted and make sure all threads exit
|
||||
bool aborted = false;
|
||||
if (tid == 0) aborted = *ncclShmem.comm.abortFlag;
|
||||
aborted = barrier_red_or_aligned(aborted, 0); // publish ncclShmem.work
|
||||
if (tid == 0 && ncclShmem.args.workStorageType == ncclDevWorkStorageTypeFifo) {
|
||||
// ncclShmem.workConsumed written by loadWorkBatchToShmem before barrier_red_or()
|
||||
ncclShmem.comm.workConsumed[ncclShmem.channelId] = ncclShmem.workConsumed;
|
||||
}
|
||||
if (aborted) break;
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void ncclDevKernel_Generic(struct ncclDevComm* comm, uint64_t channelMask, struct ncclWork* workHead);
|
||||
__global__ void ncclDevKernel_Generic(ncclDevKernelArgs4K NCCL_GRID_CONSTANT const args4K);
|
||||
__device__ void ncclDevFunc_Nop();
|
||||
|
||||
#define DEFINE_ncclDevKernel(suffix, coll, redop, ty, algo, proto, specializedFnId) \
|
||||
__global__ void ncclDevKernel_##suffix(struct ncclDevComm* comm, uint64_t channelMask, struct ncclWork* workHead) { \
|
||||
ncclKernelMain<specializedFnId, RunWork<coll, ty, redop<ty>, algo, proto>>(comm, channelMask, workHead); \
|
||||
__global__ void ncclDevKernel_##suffix(ncclDevKernelArgs4K NCCL_GRID_CONSTANT const args4K) { \
|
||||
ncclKernelMain<specializedFnId, RunWorkBatch<coll, ty, redop<ty>, algo, proto>>(&args4K.args); \
|
||||
}
|
||||
|
||||
#define DEFINE_ncclDevFunc(suffix, coll, redop, ty, algo, proto) \
|
||||
__device__ void ncclDevFunc_##suffix() { \
|
||||
RunWork<coll, ty, redop<ty>, algo, proto>().run(&ncclShmem.work); \
|
||||
RunWorkBatch<coll, ty, redop<ty>, algo, proto>().run(); \
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -233,6 +233,8 @@ with open(os.path.join(gensrc, "host_table.cc"), "w") as f:
|
||||
out('#include "device.h"\n')
|
||||
out("\n")
|
||||
|
||||
out("extern int const ncclDevFuncIdCount = %d;\n" % len(primary_funcs))
|
||||
|
||||
# The mapping from function rows to valid primary function ids.
|
||||
out("extern int const ncclDevFuncRowToId[] = {\n")
|
||||
index = 0
|
||||
@@ -251,7 +253,7 @@ with open(os.path.join(gensrc, "host_table.cc"), "w") as f:
|
||||
cudart, _ = required_cuda(*kfn)
|
||||
sym = paste("_", "ncclDevKernel", *kfn)
|
||||
if cudart != 0: out("#if CUDART_VERSION >= %d\n" % cudart)
|
||||
out("__global__ void %s(struct ncclDevComm*, uint64_t, struct ncclWork*);\n" % sym)
|
||||
out("__global__ void %s(ncclDevKernelArgs4K const);\n" % sym)
|
||||
if cudart != 0: out("#endif\n")
|
||||
out("\n")
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "unpack_defs.h"
|
||||
|
||||
#include "op128.h"
|
||||
#include "align.h"
|
||||
#include "bitops.h"
|
||||
#include "device.h"
|
||||
#include "common.h"
|
||||
|
||||
@@ -35,16 +35,16 @@ inline __device__ void ncclNetDeviceUnpackSetup(void* ohandle, const int group,
|
||||
struct unpackNetDeviceHandle* handle = (struct unpackNetDeviceHandle*) ohandle;
|
||||
ncclShmem.groups[group].devicePlugin.unpack.g_meta[index] = handle->meta;
|
||||
ncclShmem.devicePlugin.unpack.bounce_buf = handle->bounce_buf;
|
||||
ncclShmem.groups[group].devicePlugin.unpack.head = handle->head;
|
||||
ncclShmem.groups[group].devicePlugin.unpack.head[index] = handle->head;
|
||||
}
|
||||
|
||||
inline __device__ void ncclNetDeviceIncrementHead(const int group) {
|
||||
ncclShmem.groups[group].devicePlugin.unpack.head++;
|
||||
inline __device__ void ncclNetDeviceIncrementHead(const int group, const int index) {
|
||||
ncclShmem.groups[group].devicePlugin.unpack.head[index]++;
|
||||
}
|
||||
|
||||
inline __device__ void ncclNetDeviceSaveHead(void* ohandle, const int group) {
|
||||
inline __device__ void ncclNetDeviceSaveHead(void* ohandle, const int group, const int index) {
|
||||
struct unpackNetDeviceHandle* handle = (struct unpackNetDeviceHandle*) ohandle;
|
||||
handle->head = ncclShmem.groups[group].devicePlugin.unpack.head;
|
||||
handle->head = ncclShmem.groups[group].devicePlugin.unpack.head[index];
|
||||
}
|
||||
|
||||
template <uint8_t sz>
|
||||
@@ -183,7 +183,7 @@ inline __device__ void ncclNetDeviceUnpack</*Recv=*/1>(
|
||||
// Pack data from the internal iovec to the supplied flat srcs buffer using all the threads
|
||||
// + Src is necessary in the case of accessing the user buffer directly
|
||||
ncclNetDeviceUnpackInner(tid, tidInBlock, nworkers, group /* in case they need to use split warps shared memory partitioning*/,
|
||||
ix, ncclShmem.groups[group].srcs[ix + Src], workSize, ncclShmem.groups[group].devicePlugin.unpack.head);
|
||||
ix, ncclShmem.groups[group].srcs[ix + Src], workSize, ncclShmem.groups[group].devicePlugin.unpack.head[ix]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ struct unpackShmem {
|
||||
|
||||
struct unpackGroupShmem {
|
||||
int unpackNetDeviceIndexMask; // We store a single unpackNetDeviceIndex because only one peer can be network recv
|
||||
uint64_t head;
|
||||
uint64_t head[NET_UNPACK_MAX_NPEERS];
|
||||
struct netUnpackMeta* g_meta[NET_UNPACK_MAX_NPEERS]; // head of handle to index into meta for meta copy
|
||||
};
|
||||
|
||||
|
||||
@@ -44,10 +44,11 @@ class Primitives<T, RedOp, Fan, Direct, ProtoLL, P2p>:
|
||||
inline __device__ uint32_t sendFlag(int i) { return NCCL_LL_FLAG(sendStep[i]+1); }
|
||||
|
||||
inline __device__ void barrier() {
|
||||
if (nthreads == WARP_SIZE)
|
||||
if (nthreads == WARP_SIZE) {
|
||||
__syncwarp();
|
||||
else
|
||||
asm volatile ("bar.sync %1, %0;" :: "r"(nthreads), "r"(15-group));
|
||||
} else {
|
||||
barrier_sync(15-group, nthreads);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t abort = 0;
|
||||
@@ -323,7 +324,8 @@ class Primitives<T, RedOp, Fan, Direct, ProtoLL, P2p>:
|
||||
__device__ Primitives(
|
||||
const int tid, const int nthreads, int const *recvPeers, int const *sendPeers,
|
||||
void const *inputBuf, void *outputBuf, uint64_t redOpArg, uint8_t group=0,
|
||||
uint8_t connIndexRecv=0, uint8_t connIndexSend=0, struct ncclWorkElem* e = nullptr, struct ncclWorkElemP2p* p2p = nullptr, int stepSize_=0
|
||||
uint8_t connIndexRecv=0, uint8_t connIndexSend=0, struct ncclWorkElem* e = nullptr,
|
||||
bool userBufReg=false, int stepSize_=0
|
||||
):
|
||||
redOp(redOpArg),
|
||||
tid(tid), nthreads(nthreads), wid(tid%WARP_SIZE), group(group),
|
||||
|
||||
@@ -50,7 +50,7 @@ class Primitives<T, RedOp, Fan, Direct, ProtoLL128, P2p>:
|
||||
inline __device__ uint64_t sendFlag(int i) { return sendStep[i]+1; }
|
||||
|
||||
inline __device__ void barrier() {
|
||||
asm volatile ("bar.sync %1, %0;" :: "r"(nthreads), "r"(15-group));
|
||||
barrier_sync(15-group, nthreads);
|
||||
}
|
||||
|
||||
uint32_t abort = 0;
|
||||
@@ -364,7 +364,8 @@ public:
|
||||
__device__ Primitives(
|
||||
const int tid, const int nthreads, int const *recvPeers, int const *sendPeers,
|
||||
void const *inputBuf, void *outputBuf, uint64_t redOpArg, uint8_t group=0,
|
||||
uint8_t connIndexRecv=0, uint8_t connIndexSend=0, struct ncclWorkElem* e = nullptr, int stepSize_=0
|
||||
uint8_t connIndexRecv=0, uint8_t connIndexSend=0, struct ncclDevWorkColl* e = nullptr,
|
||||
bool userBufReg=false, int stepSize_=0
|
||||
):
|
||||
redOp(redOpArg),
|
||||
tid(tid), nthreads(nthreads), wid(tid%WARP_SIZE), warp(tid/WARP_SIZE),
|
||||
|
||||
+21
-37
@@ -23,7 +23,7 @@ class Primitives<
|
||||
ConnFifoEnabled = 0x100,
|
||||
DirectWrite = 0x200,
|
||||
DirectRead = 0x400,
|
||||
ThreadsSynced = 0x800,
|
||||
// 0x800 is free to use
|
||||
NvlsMinPolling = 0x1000,
|
||||
NetDeviceUnpack = 0x2000,
|
||||
AnyNetDeviceUnpack = 0x4000,
|
||||
@@ -44,53 +44,38 @@ class Primitives<
|
||||
uint64_t *connStepPtr;
|
||||
uint64_t connStepCache; // Cache last seen value of (*connStepPtr)
|
||||
int connStepSize; // Connection step size
|
||||
void* mhandle;
|
||||
void* netDeviceHandle;
|
||||
|
||||
// Don't use barrier 0 as it's used by the final sync
|
||||
__device__ void barrier() {
|
||||
flags |= ThreadsSynced;
|
||||
if (nthreads == WARP_SIZE) __syncwarp();
|
||||
else {
|
||||
int bar = 15-group;
|
||||
asm volatile("bar.sync %0, %1;" :: "r"(bar), "r"(nthreads) : "memory");
|
||||
barrier_sync(bar, nthreads);
|
||||
}
|
||||
}
|
||||
__device__ void subBarrier() {
|
||||
if (nworkers == WARP_SIZE) __syncwarp();
|
||||
else {
|
||||
int bar = (nworkers==nthreads ? 15 : 8) - group;
|
||||
asm volatile("bar.sync %0, %1;" :: "r"(bar), "r"(nworkers) : "memory");
|
||||
int bar = 15-group - (nworkers!=nthreads ? 1 : 0);
|
||||
barrier_sync(bar, nworkers);
|
||||
}
|
||||
}
|
||||
|
||||
__device__ bool barrierAny(int vote) {
|
||||
flags |= ThreadsSynced;
|
||||
if (nthreads == WARP_SIZE) {
|
||||
return __any_sync(~0u, vote);
|
||||
} else {
|
||||
int ans, bar = 15-group;
|
||||
asm volatile(
|
||||
"{ .reg .pred p;"
|
||||
" setp.ne.s32 p, %1, 0;"
|
||||
" bar.red.or.pred p, %2, %3, p; "
|
||||
" selp.s32 %0, 1, 0, p; }"
|
||||
: "=r"(ans) : "r"(vote), "r"(bar), "r"(nthreads) : "memory");
|
||||
return ans != 0;
|
||||
int name = 15-group;
|
||||
return barrier_red_or(vote, name, nthreads);
|
||||
}
|
||||
}
|
||||
__device__ bool subBarrierAny(int vote) {
|
||||
if (nworkers == WARP_SIZE) {
|
||||
return __any_sync(~0u, vote);
|
||||
} else {
|
||||
int ans, bar = (nworkers==nthreads ? 15 : 8) - group;
|
||||
asm volatile(
|
||||
"{ .reg .pred p;"
|
||||
" setp.ne.s32 p, %1, 0;"
|
||||
" bar.red.or.pred p, %2, %3, p; "
|
||||
" selp.s32 %0, 1, 0, p; }"
|
||||
: "=r"(ans) : "r"(vote), "r"(bar), "r"(nworkers) : "memory");
|
||||
return ans != 0;
|
||||
int name = 15-group - (nworkers!=nthreads ? 1 : 0);
|
||||
return barrier_red_or(vote, name, nworkers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,8 +149,8 @@ class Primitives<
|
||||
else {
|
||||
ptrs[index] = connEltsFifo + (step%NCCL_STEPS)*connStepSize;
|
||||
}
|
||||
if ((flags & (AnyNetDeviceUnpack)) && (flags & (Recv*RoleWaitRecv))) {
|
||||
ncclNetDeviceIncrementHead(group);
|
||||
if (flags & NetDeviceUnpack) {
|
||||
ncclNetDeviceIncrementHead(group, index);
|
||||
}
|
||||
step += StepPerSlice;
|
||||
}
|
||||
@@ -436,7 +421,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void loadRecvConn(ncclDevChannelPeer *peer, int connIndex, struct ncclWorkElem* e) {
|
||||
__device__ __forceinline__ void loadRecvConn(ncclDevChannelPeer *peer, int connIndex, struct ncclDevWorkColl* e) {
|
||||
if (flags & (RoleWaitRecv|RolePostRecv)) {
|
||||
auto *conn = &peer->recv[connIndex];
|
||||
if (conn->netDeviceHandle.netDeviceType == NCCL_NET_DEVICE_UNPACK) {
|
||||
@@ -488,7 +473,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void loadSendConn(ncclDevChannelPeer *peer, int connIndex, struct ncclWorkElem* e) {
|
||||
__device__ __forceinline__ void loadSendConn(ncclDevChannelPeer *peer, int connIndex, struct ncclDevWorkColl* e) {
|
||||
if (flags & (RoleWaitSend|RolePostSend)) {
|
||||
auto *conn = &peer->send[connIndex];
|
||||
step = conn->step;
|
||||
@@ -538,13 +523,13 @@ private:
|
||||
__device__ Primitives(
|
||||
int tid, int nthreads, int const *recvPeers, int const *sendPeers,
|
||||
void const *inputBuf, void *outputBuf, uint64_t redOpArg, uint8_t group=0,
|
||||
uint8_t connIndexRecv = 0, uint8_t connIndexSend = 0, struct ncclWorkElem* e = nullptr, struct ncclWorkElemP2p* p2p = nullptr, int stepSize_=0
|
||||
uint8_t connIndexRecv = 0, uint8_t connIndexSend = 0, struct ncclDevWorkColl* e = nullptr,bool userBufReg=false, int stepSize_=0
|
||||
):
|
||||
tid(tid), nthreads(nthreads), tidInBlock(threadIdx.x), group(group),
|
||||
stepSize(stepSize_ == 0 ? ncclShmem.comm.buffSizes[NCCL_PROTO_SIMPLE]/NCCL_STEPS/sizeof(T) : stepSize_) {
|
||||
|
||||
// For send operations, we need an extra warp to overlap the threadfence and the copy
|
||||
this->nworkers = nthreads - (MaxSend > 0 && nthreads-WARP_SIZE >= 64 ? WARP_SIZE : 0);
|
||||
this->nworkers = nthreads - (MaxSend > 0 && nthreads >= NCCL_SIMPLE_EXTRA_GROUP_IF_NTHREADS_GE ? WARP_SIZE : 0);
|
||||
|
||||
int nrecv=0, nsend=0;
|
||||
while (nrecv < MaxRecv && recvPeers[nrecv] != -1) nrecv++;
|
||||
@@ -572,7 +557,7 @@ private:
|
||||
loadRecvConn(ncclShmem.channel.peers[peer], connIndexRecv, e);
|
||||
loadSendConn(ncclShmem.channel.peers[peer], connIndexSend, e);
|
||||
|
||||
if (p2p && p2p->reg) flags |= UserBufferMode;
|
||||
if (userBufReg) flags |= UserBufferMode;
|
||||
|
||||
if (barrierAny(flags & NetDeviceUnpack)) {
|
||||
flags |= AnyNetDeviceUnpack;
|
||||
@@ -584,13 +569,12 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
setDataPtrs(inputBuf, outputBuf, redOpArg, (struct ncclWorkElemReg*)e);
|
||||
setDataPtrs(inputBuf, outputBuf, redOpArg, (struct ncclDevWorkCollReg*)e);
|
||||
}
|
||||
|
||||
__device__ ~Primitives() {
|
||||
// Ensure ncclShmem.groups[].send/recvConns are available
|
||||
if (!(flags & ThreadsSynced))
|
||||
barrier();
|
||||
barrier();
|
||||
// Save steps for the next operation
|
||||
if (flags & (RolePostSend|RolePostRecv)) {
|
||||
auto *conns = (flags & RolePostSend) ? ncclShmem.groups[group].sendConns : ncclShmem.groups[group].recvConns;
|
||||
@@ -606,8 +590,8 @@ private:
|
||||
while (*ptr != -1) if (checkAbort(spins)) break;
|
||||
}
|
||||
|
||||
if ((flags & (AnyNetDeviceUnpack)) && (flags & (RoleWaitRecv))) {
|
||||
ncclNetDeviceSaveHead(netDeviceHandle, group);
|
||||
if (flags & NetDeviceUnpack) {
|
||||
ncclNetDeviceSaveHead(netDeviceHandle, group, index);
|
||||
}
|
||||
|
||||
// Make sure all threads are done writing back conn->step and done using
|
||||
@@ -615,7 +599,7 @@ private:
|
||||
barrier();
|
||||
}
|
||||
|
||||
__device__ void setDataPtrs(void const *inputBuf, void *outputBuf, uint64_t redOpArg, struct ncclWorkElemReg* e) {
|
||||
__device__ void setDataPtrs(void const *inputBuf, void *outputBuf, uint64_t redOpArg, struct ncclDevWorkCollReg* e) {
|
||||
if (tid==0) {
|
||||
ncclShmem.groups[group].userInput = (void*)inputBuf;
|
||||
ncclShmem.groups[group].userOutput = (void*)outputBuf;
|
||||
@@ -625,7 +609,7 @@ private:
|
||||
bool sendAcceptor = (flags == (flags|RoleWaitSend|DirectWrite)) || (flags == (flags|RoleWaitSend|NvlsDirectWrite));
|
||||
bool sendProvider = flags == (flags|RoleWaitSend|DirectRead); // sender provides direct buffer (to be fetched)
|
||||
bool recvAcceptor = flags == (flags|RoleWaitRecv|DirectRead) || (flags == (flags|RoleWaitRecv|NvlsDirectRead)); // receiver accepts direct buffer
|
||||
int regUsed = e != nullptr ? e->elem.regUsed : 0;
|
||||
int regUsed = e != nullptr ? e->coll.regUsed : 0;
|
||||
|
||||
if (Direct && recvProvider) {
|
||||
int spins = 0;
|
||||
|
||||
+16
-17
@@ -10,22 +10,21 @@
|
||||
|
||||
namespace {
|
||||
template<typename T, typename RedOp, typename Proto>
|
||||
__device__ __forceinline__ void runRing(ncclWorkElem *args) {
|
||||
const int tid = threadIdx.x;
|
||||
const int nthreads = (int)args->nWarps * WARP_SIZE;
|
||||
__device__ __forceinline__ void runRing(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
ncclRing *ring = &ncclShmem.channel.ring;
|
||||
const int nranks = ncclShmem.comm.nRanks;
|
||||
const int rank = ncclShmem.comm.rank;
|
||||
const int prevRank = ring->userRanks[nranks-1];
|
||||
const int root = args->root;
|
||||
const size_t chunkCount = args->chunkCount;
|
||||
const size_t channelCount = args->workCount;
|
||||
const size_t gridOffset = args->workOffset;
|
||||
const int root = work->root;
|
||||
size_t chunkCount;
|
||||
size_t channelCount;
|
||||
size_t gridOffset;
|
||||
ncclCollCbdPart(work, ncclShmem.channelId, Proto::Id, sizeof(T), (size_t*)nullptr, &gridOffset, &channelCount, &chunkCount);
|
||||
size_t offset;
|
||||
int nelem;
|
||||
|
||||
Primitives<T, RedOp, FanSymmetric<1>, 0, Proto, 0>
|
||||
prims(tid, nthreads, &ring->prev, &ring->next, args->sendbuff, args->recvbuff, args->redOpArg);
|
||||
prims(tid, nthreads, &ring->prev, &ring->next, work->sendbuff, work->recvbuff, work->redOpArg);
|
||||
|
||||
if (prevRank == root) {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
@@ -52,23 +51,23 @@ namespace {
|
||||
}
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncReduce, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
struct RunWorkColl<ncclFuncReduce, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
using Proto = ProtoSimple<REDUCE_CHUNKSTEPS/REDUCE_SLICESTEPS, REDUCE_SLICESTEPS>;
|
||||
runRing<T, RedOp, Proto>(args);
|
||||
runRing<T, RedOp, Proto>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncReduce, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
runRing<T, RedOp, ProtoLL>(args);
|
||||
struct RunWorkColl<ncclFuncReduce, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
runRing<T, RedOp, ProtoLL>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncReduce, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL128> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
runRing<T, RedOp, ProtoLL128>(args);
|
||||
struct RunWorkColl<ncclFuncReduce, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL128> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
runRing<T, RedOp, ProtoLL128>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
@@ -37,6 +37,7 @@ template<typename T>
|
||||
struct FuncSum { using EltType = T; __device__ FuncSum(uint64_t opArg=0) {}; };
|
||||
template<typename T>
|
||||
struct FuncProd { using EltType = T; __device__ FuncProd(uint64_t opArg=0) {}; };
|
||||
|
||||
template<typename T>
|
||||
struct FuncMinMax {
|
||||
using EltType = T;
|
||||
@@ -47,9 +48,30 @@ struct FuncMinMax {
|
||||
isMinNotMax = (opArg&1)==0;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> struct FuncPreMulSum;
|
||||
template<typename T> struct FuncSumPostDiv;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Trait class for handling the reduction argument.
|
||||
|
||||
template<typename Fn>
|
||||
struct RedOpArg { // default case: no argument
|
||||
static constexpr bool ArgUsed = false;
|
||||
__device__ static uint64_t loadArg(void *ptr) { return 0; }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct RedOpArg<FuncMinMax<T>> {
|
||||
static constexpr bool ArgUsed = true;
|
||||
__device__ static uint64_t loadArg(void *ptr) {
|
||||
union { uint64_t u64; T val; };
|
||||
u64 = 0;
|
||||
val = *(T*)ptr;
|
||||
return u64;
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Trait classes for reduction functions. Given a function (FuncSum, etc.)
|
||||
// and a number of elements in a pack, will reduce, preOp, or postOp a pack
|
||||
@@ -356,6 +378,17 @@ struct Apply_PostOp<Fn, /*EltPerPack=*/0> {
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// FuncPreMulSum
|
||||
|
||||
template<typename T>
|
||||
struct RedOpArg<FuncPreMulSum<T>> {
|
||||
static constexpr bool ArgUsed = true;
|
||||
__device__ static uint64_t loadArg(void *ptr) {
|
||||
union { uint64_t u64; T val; };
|
||||
u64 = 0;
|
||||
val = *(T*)ptr;
|
||||
return u64;
|
||||
}
|
||||
};
|
||||
|
||||
// General definition for all integral types, float, and double.
|
||||
template<typename T>
|
||||
struct FuncPreMulSum {
|
||||
@@ -486,6 +519,14 @@ struct Apply_PreOp<FuncPreMulSum<half>, /*EltPerPack=*/1> {
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// FuncSumPostDiv
|
||||
|
||||
template<typename T>
|
||||
struct RedOpArg<FuncSumPostDiv<T>> {
|
||||
static constexpr bool ArgUsed = true;
|
||||
__device__ static uint64_t loadArg(void *ptr) {
|
||||
return *(uint64_t*)ptr;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, bool IsFloating=IsFloatingPoint<T>::value>
|
||||
struct FuncSumPostDiv_IntOnly;
|
||||
|
||||
@@ -658,7 +699,7 @@ struct Apply_LoadMultimem {
|
||||
static constexpr bool IsFloat = IsFloatingPoint<T>::value;
|
||||
static constexpr int BigPackSize =
|
||||
IsFloat && IsSum && sizeof(T) < 8 ? 16 :
|
||||
IsFloat && IsSum ? 8 :
|
||||
IsFloat && IsSum ? sizeof(T) :
|
||||
IsFloat && IsMinMax && sizeof(T)==2 ? 16 :
|
||||
!IsFloat && (IsSum||IsMinMax) && sizeof(T)>=4 ? sizeof(T) :
|
||||
/*multimem.ld_reduce not supported:*/ 0;
|
||||
|
||||
+58
-59
@@ -10,23 +10,22 @@
|
||||
|
||||
namespace {
|
||||
template<typename T, typename RedOp, typename Proto>
|
||||
__device__ __forceinline__ void runRing(ncclWorkElem *args) {
|
||||
const int tid = threadIdx.x;
|
||||
const uint32_t nthreads = (uint32_t)args->nWarps * WARP_SIZE;
|
||||
__device__ __forceinline__ void runRing(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
ncclRing *ring = &ncclShmem.channel.ring;
|
||||
int const *ringRanks = ring->userRanks;
|
||||
const size_t chunkCount = args->chunkCount;
|
||||
const int nranks = ncclShmem.comm.nRanks;
|
||||
size_t channelCount = args->workCount;
|
||||
size_t gridOffset = args->workOffset;
|
||||
size_t count;
|
||||
size_t gridOffset;
|
||||
size_t channelCount;
|
||||
size_t chunkCount;
|
||||
ncclCollCbdPart(work, ncclShmem.channelId, Proto::Id, sizeof(T), &count, &gridOffset, &channelCount, &chunkCount);
|
||||
size_t offset;
|
||||
size_t dataOffset;
|
||||
size_t count = args->count;
|
||||
uint32_t nelem;
|
||||
int rankDest;
|
||||
|
||||
Primitives<T, RedOp, FanSymmetric<1>, 0, Proto, 0>
|
||||
prims(tid, nthreads, &ring->prev, &ring->next, args->sendbuff, args->recvbuff, args->redOpArg);
|
||||
prims(tid, nthreads, &ring->prev, &ring->next, work->sendbuff, work->recvbuff, work->redOpArg);
|
||||
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
@@ -54,56 +53,56 @@ namespace {
|
||||
}
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
using Proto = ProtoSimple<REDUCESCATTER_CHUNKSTEPS/REDUCESCATTER_SLICESTEPS, REDUCESCATTER_SLICESTEPS>;
|
||||
runRing<T, RedOp, Proto>(args);
|
||||
runRing<T, RedOp, Proto>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
runRing<T, RedOp, ProtoLL>(args);
|
||||
struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
runRing<T, RedOp, ProtoLL>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL128> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
runRing<T, RedOp, ProtoLL128>(args);
|
||||
struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL128> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
runRing<T, RedOp, ProtoLL128>(tid, nthreads, work);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
const int tid = threadIdx.x;
|
||||
struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int/*nthreads*/, struct ncclDevWorkColl* work) {
|
||||
struct ncclNvls* nvls = &ncclShmem.channel.nvls;
|
||||
const size_t chunkCount = args->chunkCount;
|
||||
const size_t count = args->count;
|
||||
size_t count;
|
||||
size_t gridOffset;
|
||||
size_t channelCount;
|
||||
size_t chunkCount;
|
||||
ncclCollCbdPart(work, ncclShmem.channelId, NCCL_PROTO_SIMPLE, sizeof(T), &count, &gridOffset, &channelCount, &chunkCount);
|
||||
const int rank = ncclShmem.comm.rank;
|
||||
const int nranks = ncclShmem.comm.nRanks;
|
||||
size_t gridOffset = args->workOffset;
|
||||
size_t channelCount = args->workCount;
|
||||
size_t offset;
|
||||
int nelem;
|
||||
|
||||
/* if we are direct NVLS, we only need to allocate 1 warp to scatter for sync;
|
||||
* if not, based on #ranks, we allocate 7 or 5 warps to reduce to saturate bandwidth
|
||||
* and the rest are allocated to scatter. */
|
||||
const int nThreadsReduce = args->regUsed ? (NCCL_MAX_NTHREADS - WARP_SIZE) : (nranks <= 6 ? 7 * WARP_SIZE : 5 * WARP_SIZE);
|
||||
const int nThreadsScatter = args->regUsed ? WARP_SIZE : (NCCL_MAX_NTHREADS - nThreadsReduce);
|
||||
const int nThreadsReduce = work->regUsed ? (NCCL_MAX_NTHREADS - WARP_SIZE) : (nranks <= 6 ? 7 * WARP_SIZE : 5 * WARP_SIZE);
|
||||
const int nThreadsScatter = work->regUsed ? WARP_SIZE : (NCCL_MAX_NTHREADS - nThreadsReduce);
|
||||
const int tidEndScatter = nThreadsScatter;
|
||||
const int tidEndReduce = tidEndScatter + nThreadsReduce;
|
||||
|
||||
if (!args->regUsed) {
|
||||
if (!work->regUsed) {
|
||||
if (tid < tidEndScatter) {
|
||||
// Scatter
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL>;
|
||||
Primitives<T, RedOp, FanAsymmetric<0, NCCL_MAX_NVLS_ARITY>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, nThreadsScatter, NULL, nvls->up, args->sendbuff, NULL,
|
||||
args->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
prims(tid, nThreadsScatter, NULL, nvls->up, work->sendbuff, NULL,
|
||||
work->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
@@ -113,8 +112,8 @@ struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROT
|
||||
// Reduce through NVLS
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 1, 0>;
|
||||
Primitives<T, RedOp, FanAsymmetric<1, 0>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid - tidEndScatter, nThreadsReduce, &nvls->down, NULL, NULL, args->recvbuff,
|
||||
args->redOpArg, 3 * Proto::MaxGroupWidth, 0, 0);
|
||||
prims(tid - tidEndScatter, nThreadsReduce, &nvls->down, NULL, NULL, work->recvbuff,
|
||||
work->redOpArg, 3 * Proto::MaxGroupWidth, 0, 0);
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
@@ -127,7 +126,7 @@ struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROT
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL>;
|
||||
Primitives<T, RedOp, FanSymmetric<NCCL_MAX_NVLS_ARITY>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, nThreadsScatter, nvls->up, nvls->up, NULL, NULL,
|
||||
args->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
work->redOpArg, 0 * Proto::MaxGroupWidth, 1, 1);
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
prims.scatter(0, 0, 0, 0, -1, 0);
|
||||
}
|
||||
@@ -138,8 +137,8 @@ struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROT
|
||||
// Reduce through NVLS
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 1, 0>;
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndScatter, nThreadsReduce, &nvls->down, &nvls->down, NULL, args->recvbuff,
|
||||
args->redOpArg, 3 * Proto::MaxGroupWidth, 0, 0, args);
|
||||
prims(tid - tidEndScatter, nThreadsReduce, &nvls->down, &nvls->down, NULL, work->recvbuff,
|
||||
work->redOpArg, 3 * Proto::MaxGroupWidth, 0, 0, work);
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
size_t outOffset = gridOffset + elemOffset;
|
||||
size_t inpOffset = outOffset + rank * count;
|
||||
@@ -155,10 +154,10 @@ struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROT
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_PROTO_SIMPLE> {
|
||||
struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_PROTO_SIMPLE> {
|
||||
template<bool ReduceSendNotRecv>
|
||||
struct Scatterer {
|
||||
struct ncclWorkElem* args;
|
||||
struct ncclDevWorkColl* work;
|
||||
int chunkSize;
|
||||
ssize_t railGridOffset;
|
||||
|
||||
@@ -173,11 +172,11 @@ struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT,
|
||||
struct ncclDirect* direct = &ncclShmem.channel.collnetDirect;
|
||||
int nNodes = ncclShmem.comm.nNodes;
|
||||
int nRails = direct->nHeads;
|
||||
int bid = args->bid;
|
||||
void* inbuf = (void*)args->sendbuff;
|
||||
ssize_t sizePerRank = args->count;
|
||||
int part = ncclShmem.channelId - work->channelLo;
|
||||
void* inbuf = (void*)work->sendbuff;
|
||||
ssize_t sizePerRank = work->collnet.count;
|
||||
|
||||
ssize_t railAllBeg = min(railGridOffset + bid*chunkSize, nNodes*sizePerRank);
|
||||
ssize_t railAllBeg = min(railGridOffset + part*chunkSize, nNodes*sizePerRank);
|
||||
ssize_t railAllEnd = min(railAllBeg + chunkSize, nNodes*sizePerRank);
|
||||
int railAllSize = railAllEnd - railAllBeg;
|
||||
if (tid < nDsts) dstSizes[tid] = railAllSize;
|
||||
@@ -204,7 +203,7 @@ struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT,
|
||||
/*MultimemSrcs=*/0, 1+MinSrcs, 1+MaxSrcs,
|
||||
/*MultimemDsts,MinDsts,MaxDsts=*/0,1,1,
|
||||
/*PreOpSrcs=*/1>
|
||||
(tid, tn, args->redOpArg, &args->redOpArg, false,
|
||||
(tid, tn, work->redOpArg, &work->redOpArg, false,
|
||||
/*nSrcs=*/1+nSrcs, [=]__device__(int s) {
|
||||
return s==0 ? (T*)inbuf + userOneBeg
|
||||
: (T*)srcPtrs[s-1] + railAllOffset;
|
||||
@@ -223,23 +222,23 @@ struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT,
|
||||
}
|
||||
};
|
||||
|
||||
__device__ __forceinline__ void run(ncclWorkElem *args) {
|
||||
int tid = threadIdx.x;
|
||||
const int nChannels = args->nChannels;
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
const int part = ncclShmem.channelId - work->channelLo;
|
||||
const int nChannels = work->channelHi - work->channelLo + 1;
|
||||
struct ncclDirect* direct = &ncclShmem.channel.collnetDirect;
|
||||
int const &nNodes = ncclShmem.comm.nNodes;
|
||||
ssize_t chunkSize = int(args->chunkCount);
|
||||
ssize_t sizePerRank = args->count;
|
||||
ssize_t chunkSize = int(work->collnet.chunkCount);
|
||||
ssize_t sizePerRank = work->collnet.count;
|
||||
|
||||
if (direct->out == -1) __trap();
|
||||
bool isMultiRail = (direct->nHeads > 1);
|
||||
int nWarps1 = (isMultiRail ? 2 : 0);
|
||||
int nWarps2 = (isMultiRail ? 2 : 1);
|
||||
int nWarps3 = 1;
|
||||
float denom = float(args->nWarps)/float(nWarps1+nWarps2+nWarps3);
|
||||
float denom = float(work->nWarps)/float(nWarps1+nWarps2+nWarps3);
|
||||
nWarps3 = int(denom*nWarps3);
|
||||
nWarps2 = int(denom*nWarps2);
|
||||
nWarps1 = args->nWarps - (nWarps2+nWarps3);
|
||||
nWarps1 = work->nWarps - (nWarps2+nWarps3);
|
||||
|
||||
using Proto = ProtoSimple<1, 1>;
|
||||
|
||||
@@ -248,13 +247,13 @@ struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT,
|
||||
// Phase 1: Scatter inputs to peers
|
||||
Primitives<T, RedOp, FanAsymmetric<0, NCCL_MAX_DIRECT_ARITY>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, tn, nullptr, direct->heads+1, nullptr, nullptr,
|
||||
args->redOpArg, 0*Proto::MaxGroupWidth, 1, 1);
|
||||
work->redOpArg, 0*Proto::MaxGroupWidth, 1, 1);
|
||||
for (ssize_t railGridOffset=0; railGridOffset < nNodes*sizePerRank; railGridOffset += nChannels*chunkSize) {
|
||||
Scatterer</*ReduceSendNotRecv=*/true> scat;
|
||||
scat.args = args;
|
||||
scat.work = work;
|
||||
scat.chunkSize = chunkSize;
|
||||
scat.railGridOffset = railGridOffset;
|
||||
prims.process</*Recv=*/0, /*Send=*/1>(scat);
|
||||
prims.template process</*Recv=*/0, /*Send=*/1>(scat);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -262,7 +261,7 @@ struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT,
|
||||
|
||||
tn = nWarps2*WARP_SIZE;
|
||||
if (tid < tn) {
|
||||
if (args->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (work->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (tid == 0) {
|
||||
int steps = (int)divUp(nNodes * sizePerRank * sizeof(T), NCCL_MAX_COLLNET_SIZE);
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_DIRECT_ARITY, 1>, /*Direct=*/0, Proto, 0>::sendPeerNotify(direct->out, 1, steps);
|
||||
@@ -272,13 +271,13 @@ struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT,
|
||||
// Phase 2: Reduce from peers + local input -> send to network
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_DIRECT_ARITY, 1>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, tn, direct->heads + 1, &direct->out, nullptr, nullptr,
|
||||
args->redOpArg, 1 * Proto::MaxGroupWidth, 1, 1);
|
||||
work->redOpArg, 1 * Proto::MaxGroupWidth, 1, 1);
|
||||
for (ssize_t railGridOffset = 0; railGridOffset < nNodes * sizePerRank; railGridOffset += nChannels * chunkSize) {
|
||||
Scatterer</*ReduceSendNotRecv=*/false> scat;
|
||||
scat.args = args;
|
||||
scat.work = work;
|
||||
scat.chunkSize = chunkSize;
|
||||
scat.railGridOffset = railGridOffset;
|
||||
prims.process</*Recv=*/1, /*Send=*/1>(scat);
|
||||
prims.template process</*Recv=*/1, /*Send=*/1>(scat);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -287,7 +286,7 @@ struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT,
|
||||
|
||||
tn = nWarps3*WARP_SIZE;
|
||||
if (tid < tn) {
|
||||
if (args->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (work->regUsed == NCCL_COLLNET_REG_BUFFER) {
|
||||
if (tid == 0) {
|
||||
int steps = (int)divUp(nNodes * sizePerRank * sizeof(T), NCCL_MAX_COLLNET_SIZE);
|
||||
Primitives<T, RedOp, FanAsymmetric<1, 0>, /*Direct=*/0, Proto, 0>::recvPeerNotify(direct->out, 0, steps);
|
||||
@@ -296,10 +295,10 @@ struct RunWorkElement<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT,
|
||||
} else {
|
||||
// Phase 3: recv from network
|
||||
Primitives<T, RedOp, FanAsymmetric<1, 0>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, tn, &direct->out, nullptr, nullptr, args->recvbuff,
|
||||
args->redOpArg, 2 * Proto::MaxGroupWidth, 0, 0);
|
||||
prims(tid, tn, &direct->out, nullptr, nullptr, work->recvbuff,
|
||||
work->redOpArg, 2 * Proto::MaxGroupWidth, 0, 0);
|
||||
for (ssize_t railGridOffset = 0; railGridOffset < nNodes * sizePerRank; railGridOffset += nChannels * chunkSize) {
|
||||
ssize_t railAllBeg = railGridOffset + args->bid * chunkSize;
|
||||
ssize_t railAllBeg = railGridOffset + part * chunkSize;
|
||||
ssize_t railAllEnd = min(railAllBeg + chunkSize, nNodes * sizePerRank);
|
||||
ssize_t railOneBeg = ncclShmem.comm.node * sizePerRank;
|
||||
ssize_t railOneEnd = railOneBeg + sizePerRank;
|
||||
|
||||
+141
-65
@@ -9,83 +9,159 @@
|
||||
#include "primitives.h"
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWork<ncclFuncSendRecv, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPLE> {
|
||||
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(const int tid, const int nthreads, const uint8_t group, struct ncclWorkElemP2p* args) {
|
||||
void* buff = reinterpret_cast<void*>(uintptr_t(args->buffHi32)<<32 | args->buffLo32);
|
||||
ssize_t count = reinterpret_cast<size_t>(size_t(args->countHi32)<<32 | args->countLo32);
|
||||
if (args->peer == ncclShmem.comm.rank) {
|
||||
struct ncclWorkElemP2p* recvArgs = args-1;
|
||||
void* recvBuff = reinterpret_cast<void*>(uintptr_t(recvArgs->buffHi32)<<32 | recvArgs->buffLo32);
|
||||
if (buff != recvBuff) {
|
||||
reduceCopy<COLL_UNROLL, RedOp, T, 0,1,1, 0,1,1, /*PreOpSrcs=*/0>
|
||||
(tid, nthreads, 0, nullptr, false, 1, &buff, 1, &recvBuff, count);
|
||||
}
|
||||
} else {
|
||||
int chunkSize = args->chunkSize/sizeof(T);
|
||||
if (args->proto == NCCL_PROTO_LL) chunkSize /= 2;
|
||||
int const peer = args->peer;
|
||||
Primitives<T, RedOp, FanAsymmetric<0, 1>, 1, Proto, 1> prims
|
||||
(tid, nthreads, nullptr, &peer, buff, nullptr, /*redOpArg(ignored)=*/0, group, 1, 1, nullptr, args, ncclShmem.comm.p2pChunkSize/sizeof(T));
|
||||
size_t offset = 0;
|
||||
do {
|
||||
int nelem = min(size_t(chunkSize), count-offset);
|
||||
prims.directSend(offset, offset, nelem);
|
||||
offset += nelem;
|
||||
} while(offset < count && args->reg == 0);
|
||||
}
|
||||
__device__ void runSend(int tid, int tn, int group, struct ncclDevWorkP2p* work) {
|
||||
size_t bytes = work->sendBytes;
|
||||
int chunkSize = u32fp8Decode(work->sendChunkSize_u32fp8);
|
||||
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,
|
||||
/*userBufferMode=*/work->sendRegistered, ncclShmem.comm.p2pChunkSize);
|
||||
size_t cursor = 0;
|
||||
do {
|
||||
int n = min(size_t(chunkSize), bytes-cursor);
|
||||
prims.directSend(cursor, cursor, n);
|
||||
cursor += n;
|
||||
} while (cursor < bytes && work->sendRegistered == 0);
|
||||
}
|
||||
|
||||
template<typename Proto>
|
||||
__device__ void runRecv(const int tid, const int nthreads, const uint8_t group, struct ncclWorkElemP2p* args) {
|
||||
if (args->peer != ncclShmem.comm.rank) {
|
||||
void* buff = reinterpret_cast<void*>(uintptr_t(args->buffHi32)<<32 | args->buffLo32);
|
||||
ssize_t count = reinterpret_cast<size_t>(size_t(args->countHi32)<<32 | args->countLo32);
|
||||
int chunkSize = args->chunkSize/sizeof(T);
|
||||
if (args->proto == NCCL_PROTO_LL) chunkSize /= 2; // This is to account for chunkEffectiveSize
|
||||
int const peer = args->peer;
|
||||
Primitives<T, RedOp, FanAsymmetric<1, 0>, 1, Proto, 1> prims
|
||||
(tid, nthreads, &peer, nullptr, nullptr, buff, /*redOpArg(ignored)=*/0, group, 1, 1, nullptr, args, ncclShmem.comm.p2pChunkSize/sizeof(T));
|
||||
size_t offset = 0;
|
||||
do {
|
||||
int nelem = min(size_t(chunkSize), count-offset);
|
||||
prims.directRecv(offset, nelem);
|
||||
offset += nelem;
|
||||
} while(offset < count && args->reg == 0);
|
||||
}
|
||||
__device__ void runRecv(int tid, int tn, int group, struct ncclDevWorkP2p* work) {
|
||||
size_t bytes = work->recvBytes;
|
||||
int chunkSize = u32fp8Decode(work->recvChunkSize_u32fp8);
|
||||
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,
|
||||
/*userBufferMode=*/work->recvRegistered, ncclShmem.comm.p2pChunkSize);
|
||||
size_t cursor = 0;
|
||||
do {
|
||||
int n = min(size_t(chunkSize), bytes-cursor);
|
||||
prims.directRecv(cursor, n);
|
||||
cursor += n;
|
||||
} while (cursor < bytes && work->recvRegistered == 0);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void run(ncclWork *work) {
|
||||
struct ncclWorkElemP2p* args = work->p2pElems;
|
||||
int ngroups = args->ngroups;
|
||||
int tid = threadIdx.x;
|
||||
int wid = tid / WARP_SIZE;
|
||||
// This has to work even for groups of 2.5 warps (which is 8 groups, and means 3
|
||||
// warps for send, 2 warps for recv).
|
||||
// warpStarts were rounded thanks to int division, but for group number we need to round the other way around
|
||||
// So we mirror wid then mirror again the group.
|
||||
#define NWARPS (NCCL_MAX_NTHREADS/WARP_SIZE)
|
||||
uint8_t group = ngroups-1- (NWARPS-1-wid) * ngroups / NWARPS;
|
||||
args += group;
|
||||
tid -= args->warpStart * WARP_SIZE;
|
||||
int nthreads = args->nWarps * WARP_SIZE;
|
||||
__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;
|
||||
|
||||
if (args->p2pType == ncclWorkP2pTypeUnused) return;
|
||||
if (tid >= nthreads || args->peer == -1) return;
|
||||
struct Shared {
|
||||
uint32_t workSendMask; // bitmasks of which work indices have send/recv
|
||||
uint32_t workRecvMask;
|
||||
};
|
||||
Shared* shared = (Shared*)ncclScratchForWarp(0);
|
||||
|
||||
// Select Proto here
|
||||
// This is to allow the same kernel to run multiple primitives on different warps (thread groups)
|
||||
if ((group%2) == 0) {
|
||||
if (args->proto == NCCL_PROTO_LL) {
|
||||
runRecv<ProtoLL>(tid, nthreads, group, args);
|
||||
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;
|
||||
}
|
||||
}
|
||||
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 {
|
||||
runRecv<ProtoSimple<1,1>>(tid, nthreads, group, args);
|
||||
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 (args->proto == NCCL_PROTO_LL) {
|
||||
runSend<ProtoLL>(tid, nthreads, group, args);
|
||||
if (work->recvProtoLL) {
|
||||
runRecv<ProtoLL>(subtid, subtn, group, work);
|
||||
} else {
|
||||
runSend<ProtoSimple<1,1>>(tid, nthreads, group, args);
|
||||
runRecv<ProtoSimple<1,1>>(subtid, subtn, group, work);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user