Merge pull request #1559 from BertanDogancay/2.23
[SYNC] 2.23.4-1
[ROCm/rccl commit: 532f54c244]
This commit is contained in:
@@ -54,8 +54,11 @@ namespace {
|
||||
|
||||
T *inputBuf = (T*)work->sendbuff;
|
||||
T *outputBuf = (T*)work->recvbuff;
|
||||
// Coverity reports that the callee treats &ring->next as an array. However, due to the use of
|
||||
// FanSymmetric<1>, only the first element is ever accessed, so it's fine.
|
||||
// coverity[callee_ptr_arith:FALSE]
|
||||
Primitives<T, RedOp, FanSymmetric<1>, 0, Proto, 0> prims
|
||||
(tid, nthreads, &ring->prev, &ring->next, inputBuf, outputBuf, work->redOpArg, 0, work->connIndex, work->connIndex);
|
||||
(tid, nthreads, &ring->prev, &ring->next, inputBuf, outputBuf, work->redOpArg, 0, work->connIndex, work->connIndex, work);
|
||||
|
||||
#if defined(ENABLE_NPKIT)
|
||||
if (tid == 0) {
|
||||
@@ -106,7 +109,7 @@ namespace {
|
||||
rankDest = ringRanks[nranks-j];
|
||||
offset = dataOffset + rankDest * count;
|
||||
|
||||
prims.directRecvCopySend(offset, nelem);
|
||||
prims.directRecvCopyDirectSend(offset, nelem);
|
||||
}
|
||||
|
||||
#if defined(ENABLE_NPKIT) && defined(ENABLE_NPKIT_EVENT_ALL_GATHER_RING_RECV_COPY_SEND_EXIT)
|
||||
@@ -128,7 +131,7 @@ namespace {
|
||||
}
|
||||
#endif
|
||||
// Final wait/copy.
|
||||
prims.directRecv(offset, nelem);
|
||||
prims.directRecv(offset, offset, nelem);
|
||||
|
||||
#if defined(ENABLE_NPKIT) && defined(ENABLE_NPKIT_EVENT_ALL_GATHER_RING_DIRECT_RECV_EXIT)
|
||||
if (tid == 0) {
|
||||
@@ -171,6 +174,31 @@ struct RunWorkColl<ncclFuncAllGather, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_LL128
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkColl<ncclFuncAllGather, T, RedOp, NCCL_ALGO_PAT, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
using Proto = ProtoSimple<1, 1>;
|
||||
const int nranks = ncclShmem.comm.nRanks;
|
||||
const int rank = ncclShmem.comm.rank;
|
||||
size_t count, channelOffset, channelCount, chunkCount;
|
||||
ncclCollCbdPart(work, ncclShmem.channelId, Proto::Id, sizeof(T), &count, &channelOffset, &channelCount, &chunkCount);
|
||||
|
||||
T *inputBuf = (T*)work->sendbuff;
|
||||
T *outputBuf = (T*)work->recvbuff;
|
||||
Primitives<T, RedOp, FanSymmetric<1>, 0, Proto, 0> prims
|
||||
(tid, nthreads, NULL, NULL, inputBuf, outputBuf, work->redOpArg, 0*Proto::MaxGroupWidth, 0, 0, nullptr, false, false, 0, primsModePatAg);
|
||||
|
||||
PatAGAlgorithm<T> patAlgo(chunkCount*sizeof(T), NCCL_STEPS, channelOffset, channelOffset + channelCount, count, chunkCount, rank, nranks);
|
||||
int last = 0;
|
||||
while (!last) {
|
||||
int recvDim, sendDim, recvOffset, sendOffset, recvStepOffset, postRecv, postSend, nelem;
|
||||
size_t inpIx, outIx;
|
||||
patAlgo.getNextOp(recvDim, sendDim, inpIx, outIx, recvOffset, sendOffset, recvStepOffset, nelem, postRecv, postSend, last);
|
||||
prims.patCopy(recvDim, sendDim, inpIx, outIx, recvOffset, sendOffset, recvStepOffset, nelem, postRecv, postSend);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkColl<ncclFuncAllGather, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int/*nthreads*/, struct ncclDevWorkColl* work) {
|
||||
@@ -255,7 +283,7 @@ struct RunWorkColl<ncclFuncAllGather, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_P
|
||||
template<int SlicePerChunk, int MinSrcs, int MaxSrcs, int MinDsts, int MaxDsts>
|
||||
__device__ __forceinline__ void operator()(
|
||||
int tid, int tn, int slice, int maxSliceSize,
|
||||
int nSrcs, void** srcPtrs, int nDsts, void** dstPtrs, int32_t* dstSizes
|
||||
int nSrcs, void** srcPtrs, int nDsts, void** dstPtrs, int32_t* dstSizes, uint32_t sendDirectFlag, uint32_t recvDirectFlag
|
||||
) {
|
||||
static_assert(SlicePerChunk==1, "require: SlicePerChunk==1");
|
||||
static_assert(MaxDsts<=1 || MaxSrcs<=1, "require: MaxDsts<=1 || MaxSrcs<=1");
|
||||
@@ -293,19 +321,22 @@ struct RunWorkColl<ncclFuncAllGather, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_P
|
||||
int rank = ncclShmem.comm.collNetDenseToUserRank[node*nRails + rail];
|
||||
ssize_t userOneBeg = rank*sizePerRank + railOneOffset;
|
||||
int outIsDst = (inPlace && rank == ncclShmem.comm.rank) ? 0 : 1;
|
||||
reduceCopy<ncclCollUnroll(), RedOp, T,
|
||||
/*MultimemSrcs,MinSrcs,MaxSrcs=*/0,1,1,
|
||||
/*MultimemDsts=*/0, 0+MinDsts, 1+MaxDsts,
|
||||
/*PreOpSrcs=*/0>
|
||||
if (nSrcs != 0 && outIsDst+nDsts != 0) {
|
||||
reduceCopy<ncclCollUnroll(), RedOp, T,
|
||||
/*MultimemSrcs,MinSrcs,MaxSrcs=*/0,1,1,
|
||||
/*MultimemDsts=*/0, 0+MinDsts, 1+MaxDsts,
|
||||
/*PreOpSrcs=*/0>
|
||||
(tid, tn, 0, nullptr, false,
|
||||
/*nSrcs=*/1, [=]__device__(int s/*==0*/) -> void* {
|
||||
return (char*)srcPtrs[src] + railAllOffset;
|
||||
},
|
||||
/*nDsts=*/outIsDst+nDsts, [=]__device__(int d) -> void* {
|
||||
return d < outIsDst ? outbuf + userOneBeg
|
||||
: (char*)dstPtrs[d-outIsDst] + railAllOffset;
|
||||
},
|
||||
delta);
|
||||
/*nSrcs=*/1, [=]__device__(int s/*==0*/) -> void* {
|
||||
return work->regUsed && (recvDirectFlag & NCCL_DIRECT_READ) ? (char*)srcPtrs[src] + userOneBeg : (char*)srcPtrs[src] + railAllOffset;
|
||||
},
|
||||
/*nDsts=*/outIsDst+nDsts, [=]__device__(int d) -> void* {
|
||||
return d < outIsDst ? outbuf + userOneBeg
|
||||
: work->regUsed && (sendDirectFlag & NCCL_DIRECT_WRITE) ? (char*)dstPtrs[d-outIsDst] + userOneBeg
|
||||
: (char*)dstPtrs[d-outIsDst] + railAllOffset;
|
||||
},
|
||||
delta);
|
||||
}
|
||||
railAllOffset += delta;
|
||||
node += 1;
|
||||
}
|
||||
@@ -371,15 +402,15 @@ struct RunWorkColl<ncclFuncAllGather, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_P
|
||||
__syncwarp();
|
||||
} else {
|
||||
// Phase 2: Recv network -> deposit output + send to bcast
|
||||
Primitives<T, RedOp, FanAsymmetric<1, NCCL_MAX_DIRECT_ARITY>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, tn, &direct->out, direct->heads + 1, nullptr, nullptr,
|
||||
/*redOpArg=*/0, 1 * Proto::MaxGroupWidth, 0, 0);
|
||||
Primitives<T, RedOp, FanAsymmetric<1, NCCL_MAX_DIRECT_ARITY>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid, tn, &direct->out, direct->heads + 1, nullptr, work->recvbuff,
|
||||
/*redOpArg=*/0, 1 * Proto::MaxGroupWidth, 0, 0, work);
|
||||
for (ssize_t railGridOffset = 0; railGridOffset < nNodes * sizePerRank; railGridOffset += nChannels * chunkSize) {
|
||||
Scatterer</*BcastSendNotRecv=*/true> scat;
|
||||
scat.work = work;
|
||||
scat.chunkSize = chunkSize;
|
||||
scat.railGridOffset = railGridOffset;
|
||||
prims.template process</*Recv=*/1, /*Send=*/1>(scat);
|
||||
prims.template process</*Recv=*/1, /*Send=*/1>(scat, work->direct, 0);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -389,15 +420,15 @@ struct RunWorkColl<ncclFuncAllGather, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_P
|
||||
tn = nWarps3*WARP_SIZE;
|
||||
if (tid < tn) {
|
||||
// Phase 3: Recv bcast -> deposit output
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_DIRECT_ARITY, 0>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, tn, direct->heads+1, nullptr, nullptr, nullptr,
|
||||
/*redOpArg=*/0, 2*Proto::MaxGroupWidth, 0, 0);
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_DIRECT_ARITY, 0>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid, tn, direct->heads+1, nullptr, nullptr, work->recvbuff,
|
||||
/*redOpArg=*/0, 2*Proto::MaxGroupWidth, 0, 0, work);
|
||||
for (ssize_t railGridOffset=0; railGridOffset < nNodes*sizePerRank; railGridOffset += nChannels*chunkSize) {
|
||||
Scatterer</*BcastSendNotRecv=*/false> scat;
|
||||
scat.work = work;
|
||||
scat.chunkSize = chunkSize;
|
||||
scat.railGridOffset = railGridOffset;
|
||||
prims.template process</*Recv=*/1, /*Send=*/0>(scat);
|
||||
prims.template process</*Recv=*/1, /*Send=*/0>(scat, 0, work->direct);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,9 +58,11 @@ namespace {
|
||||
ncclShmem.comm.npKitEventCollectContexts + npKitCtxIdx);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Coverity reports that the callee treats &ring->next as an array. However, due to the use of
|
||||
// FanSymmetric<1>, only the first element is ever accessed, so it's fine.
|
||||
// coverity[callee_ptr_arith:FALSE]
|
||||
Primitives<T, RedOp, FanSymmetric<1>, 0, Proto, 0> prims
|
||||
(tid, nthreads, &ring->prev, &ring->next, work->sendbuff, work->recvbuff, work->redOpArg, 0, work->connIndex, work->connIndex);
|
||||
(tid, nthreads, &ring->prev, &ring->next, work->sendbuff, work->recvbuff, work->redOpArg, 0, work->connIndex, work->connIndex, work);
|
||||
|
||||
#if defined(ENABLE_NPKIT)
|
||||
if (tid == 0) {
|
||||
@@ -92,7 +94,7 @@ namespace {
|
||||
}
|
||||
#endif
|
||||
|
||||
prims.send(offset, nelem);
|
||||
prims.directSend(offset, offset, nelem);
|
||||
|
||||
#if defined(ENABLE_NPKIT) && defined(ENABLE_NPKIT_EVENT_ALL_REDUCE_RING_SEND_EXIT)
|
||||
if (tid == 0) {
|
||||
@@ -116,7 +118,7 @@ namespace {
|
||||
chunkOffset = chunk * chunkCount;
|
||||
offset = gridOffset + elemOffset + chunkOffset;
|
||||
nelem = (int)min(chunkCount, remCount - chunkOffset);
|
||||
prims.recvReduceSend(offset, nelem);
|
||||
prims.directRecvReduceDirectSend(offset, offset, nelem);
|
||||
}
|
||||
|
||||
#if defined(ENABLE_NPKIT) && defined(ENABLE_NPKIT_EVENT_ALL_REDUCE_RING_RECV_REDUCE_SEND_EXIT)
|
||||
@@ -141,7 +143,7 @@ namespace {
|
||||
}
|
||||
#endif
|
||||
|
||||
prims.directRecvReduceCopySend(offset, offset, nelem, /*postOp=*/true);
|
||||
prims.directRecvReduceCopyDirectSend(offset, offset, nelem, /*postOp=*/true);
|
||||
|
||||
#if defined(ENABLE_NPKIT) && defined(ENABLE_NPKIT_EVENT_ALL_REDUCE_RING_DIRECT_RECV_REDUCE_COPY_SEND_EXIT)
|
||||
if (tid == 0) {
|
||||
@@ -164,7 +166,7 @@ namespace {
|
||||
chunkOffset = chunk * chunkCount;
|
||||
offset = gridOffset + elemOffset + chunkOffset;
|
||||
nelem = (int)min(chunkCount, remCount - chunkOffset);
|
||||
prims.directRecvCopySend(offset, nelem);
|
||||
prims.directRecvCopyDirectSend(offset, nelem);
|
||||
}
|
||||
|
||||
#if defined(ENABLE_NPKIT) && defined(ENABLE_NPKIT_EVENT_ALL_REDUCE_RING_DIRECT_RECV_COPY_SEND_EXIT)
|
||||
@@ -188,7 +190,7 @@ namespace {
|
||||
offset = gridOffset + elemOffset + chunkOffset;
|
||||
nelem = (int)min(chunkCount, remCount - chunkOffset);
|
||||
|
||||
prims.directRecv(offset, nelem);
|
||||
prims.directRecv(offset, offset, nelem);
|
||||
|
||||
#if defined(ENABLE_NPKIT) && defined(ENABLE_NPKIT_EVENT_ALL_REDUCE_RING_DIRECT_RECV_EXIT)
|
||||
if (tid == 0) {
|
||||
@@ -251,7 +253,7 @@ namespace {
|
||||
|
||||
{ // Reduce : max number of recv is 3, max number of send is 1 (binary tree + local)
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_DEV_ARITY, 1>, /*Direct=*/0, Proto, 0> prims
|
||||
(tid, nthreads, tree->down, &tree->up, work->sendbuff, work->recvbuff, work->redOpArg);
|
||||
(tid, nthreads, tree->down, &tree->up, work->sendbuff, work->recvbuff, work->redOpArg, 0, 0, 0, work);
|
||||
|
||||
#if defined(ENABLE_NPKIT)
|
||||
if (tid == 0) {
|
||||
@@ -271,21 +273,21 @@ namespace {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
prims.recvReduceCopy(offset, offset, nelem, /*postOp=*/true);
|
||||
prims.directRecvReduceCopy(offset, offset, nelem, /*postOp=*/true);
|
||||
}
|
||||
}
|
||||
else if (tree->down[0] == -1) {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
prims.send(offset, nelem);
|
||||
prims.directSend(offset, offset, nelem);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
prims.recvReduceSend(offset, nelem);
|
||||
prims.directRecvReduceDirectSend(offset, offset, nelem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +302,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_DEV_ARITY>, /*Direct=*/0, Proto, 0> prims
|
||||
(tid, nthreads, &tree->up, tree->down, work->sendbuff, work->recvbuff, work->redOpArg);
|
||||
(tid, nthreads, &tree->up, tree->down, work->sendbuff, work->recvbuff, work->redOpArg, 0, 0, 0, work);
|
||||
|
||||
#if defined(ENABLE_NPKIT)
|
||||
if (tid == 0) {
|
||||
@@ -327,14 +329,14 @@ namespace {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
prims.directRecv(offset, nelem);
|
||||
prims.directRecv(offset, offset, nelem);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
prims.directRecvCopySend(offset, nelem);
|
||||
prims.directRecvCopyDirectSend(offset, nelem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,7 +419,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_DEV_ARITY>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, nthreads, tree->down, tree->down, work->sendbuff, work->recvbuff, work->redOpArg);
|
||||
prims(tid, nthreads, tree->down, tree->down, work->sendbuff, work->recvbuff, work->redOpArg, 0, 0, 0, work);
|
||||
|
||||
#if defined(ENABLE_NPKIT)
|
||||
if (isNpKitThread) {
|
||||
@@ -436,7 +438,7 @@ namespace {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
prims.directRecvReduceCopySend(offset, offset, nelem, /*doPost=*/true);
|
||||
prims.directRecvReduceCopyDirectSend(offset, offset, nelem, /*doPost=*/true);
|
||||
}
|
||||
|
||||
#if defined(ENABLE_NPKIT) && defined(ENABLE_NPKIT_EVENT_ALL_REDUCE_TREE_SPLIT_REDUCE_BROADCAST_EXIT)
|
||||
@@ -449,15 +451,18 @@ namespace {
|
||||
}
|
||||
else if (tid < nthreadsSplit) {
|
||||
/* Reduce up. Max number of recv is 3, max number of send is 1 (binary tree + local).
|
||||
* Why Direct=1????
|
||||
* Answer: Because despite not performing any direct operations, the ctor
|
||||
* must assume Direct so that it can exchange direct pointers with remote ctors
|
||||
* that are Direct, otherwise it hangs. A cleaner solution would be to seperate
|
||||
* into DirectRecv and DirectSend capabilities, this ctor would have both=0,
|
||||
* but the ctor above for tree roots would be DirectRecv=0 DirectSend=1.
|
||||
*/
|
||||
* Why Direct=1????
|
||||
* Answer: Because despite not performing any direct operations, the ctor
|
||||
* must assume Direct so that it can exchange direct pointers with remote ctors
|
||||
* that are Direct, otherwise it hangs. A cleaner solution would be to seperate
|
||||
* into DirectRecv and DirectSend capabilities, this ctor would have both=0,
|
||||
* but the ctor above for tree roots would be DirectRecv=0 DirectSend=1.
|
||||
*/
|
||||
// Coverity reports that the callee treats &tree->up as an array. However, due to the use of
|
||||
// FanAsymmetric<n, 1>, only the first element is ever accessed, so it's fine.
|
||||
// coverity[callee_ptr_arith:FALSE]
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_DEV_ARITY, 1>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, nthreadsSplit, tree->down, &tree->up, work->sendbuff, work->recvbuff, work->redOpArg, 0*Proto::MaxGroupWidth);
|
||||
prims(tid, nthreadsSplit, tree->down, &tree->up, work->sendbuff, work->recvbuff, work->redOpArg, 0*Proto::MaxGroupWidth, 0, 0, work);
|
||||
|
||||
#if defined(ENABLE_NPKIT)
|
||||
if (isNpKitThread) {
|
||||
@@ -477,14 +482,14 @@ namespace {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
prims.send(offset, nelem);
|
||||
prims.directSend(offset, offset, nelem);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
prims.recvReduceSend(offset, nelem);
|
||||
prims.directRecvReduceDirectSend(offset, offset, nelem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,9 +503,12 @@ namespace {
|
||||
}
|
||||
else {
|
||||
// Broadcast down. Max number of recv is 1, max number of send is 3 (binary tree + local)
|
||||
// Coverity reports that the callee treats &tree->up as an array. However, due to the use of
|
||||
// FanAsymmetric<1, n>, only the first element is ever accessed, so it's fine.
|
||||
// coverity[callee_ptr_arith:FALSE]
|
||||
Primitives<T, RedOp, FanAsymmetric<1, NCCL_MAX_DEV_ARITY>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid-nthreadsSplit, nthreads-nthreadsSplit, &tree->up, tree->down, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, 1*Proto::MaxGroupWidth);
|
||||
work->redOpArg, 1*Proto::MaxGroupWidth, 0, 0, work);
|
||||
|
||||
#if defined(ENABLE_NPKIT)
|
||||
if (isNpKitThread) {
|
||||
@@ -520,14 +528,14 @@ namespace {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
prims.directRecv(offset, nelem);
|
||||
prims.directRecv(offset, offset, nelem);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (size_t elemOffset = 0; elemOffset < channelCount; elemOffset += chunkCount) {
|
||||
offset = gridOffset + elemOffset;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
prims.directRecvCopySend(offset, nelem);
|
||||
prims.directRecvCopyDirectSend(offset, nelem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,7 +606,7 @@ struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_P
|
||||
// Scatter
|
||||
Primitives<T, RedOp, FanAsymmetric<0, NCCL_MAX_DIRECT_ARITY>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid-tidStartScatter, nThreadsScatter, NULL, direct->up, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, 2*Proto::MaxGroupWidth, 1, 1, work);
|
||||
work->redOpArg, 2*Proto::MaxGroupWidth, 1, 1);
|
||||
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);
|
||||
@@ -608,12 +616,15 @@ struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_P
|
||||
prims.scatter(offset, nelem, chunkSize, chunkSize, direct->headRank, direct->shift);
|
||||
}
|
||||
}
|
||||
// Coverity complains about a possible overrun inside the destructor of "prims", but that's actually
|
||||
// a false positive.
|
||||
// coverity[overrun-call:FALSE]
|
||||
} else if (tid >= tidStartReduce && direct->out != -1) {
|
||||
if (hasDn) {
|
||||
// Reduce, send to network
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_DIRECT_ARITY, 1>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid-tidStartReduce, nThreadsReduce, direct->down, &direct->out, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, 3*Proto::MaxGroupWidth, 1, 1, work);
|
||||
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);
|
||||
@@ -634,7 +645,7 @@ struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_P
|
||||
} else {
|
||||
Primitives<T, RedOp, FanAsymmetric<0, 1>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid-tidStartReduce, nThreadsReduce, nullptr, &direct->out, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, 3*Proto::MaxGroupWidth, 1, 1);
|
||||
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);
|
||||
@@ -646,7 +657,7 @@ struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_P
|
||||
// Gather
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_DIRECT_ARITY, 0>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid, nThreadsGather, direct->up, NULL, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, 0*Proto::MaxGroupWidth, 0, 0, work);
|
||||
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);
|
||||
@@ -655,9 +666,12 @@ struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NCCL_P
|
||||
} else if (tid >= tidStartBcast && tid < tidStartScatter && direct->out != -1) {
|
||||
if (hasDn) {
|
||||
// Recv from network, broadcast
|
||||
// Coverity complains about a possible overrun inside the class below, but that's actually
|
||||
// a false positive.
|
||||
// coverity[identity_transfer:FALSE]
|
||||
Primitives<T, RedOp, FanAsymmetric<1, NCCL_MAX_DIRECT_ARITY>, /*Direct=*/0, Proto, 0>
|
||||
prims(tid-tidStartBcast, nThreadsBcast, &direct->out, direct->down, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, 1*Proto::MaxGroupWidth, 0, 0, work);
|
||||
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);
|
||||
@@ -714,7 +728,7 @@ struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SIMPL
|
||||
ssize_t offset;
|
||||
int nelem;
|
||||
int remCount = channelCount%(nvls->nHeads*chunkSize);
|
||||
int lastChunkSize = alignUp(divUp(remCount, nvls->nHeads), 16/sizeof(T));
|
||||
int lastChunkSize = alignUp(divUp(remCount, nvls->nHeads), 16384/sizeof(T));
|
||||
|
||||
if (tid < tidEndScatter) {
|
||||
// Scatter
|
||||
@@ -788,6 +802,9 @@ struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SIMPL
|
||||
if (!hasOut) {
|
||||
// Reduce, broadcast through NVLS
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 1, 1>;
|
||||
// Coverity complains about a possible overrun inside the class below, but that's actually
|
||||
// a false positive.
|
||||
// coverity[identity_transfer:FALSE]
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndGather, nThreadsReduce, &nvls->down, &nvls->down, NULL, NULL,
|
||||
work->redOpArg, 2 * Proto::MaxGroupWidth, 0, 0, work);
|
||||
@@ -799,6 +816,9 @@ struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SIMPL
|
||||
} else {
|
||||
// Reduce, send to network
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 1, 0>;
|
||||
// Coverity complains about a possible overrun inside the class below, but that's actually
|
||||
// a false positive.
|
||||
// coverity[identity_transfer:FALSE]
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndGather, nThreadsReduce, &nvls->down, &nvls->out, NULL, NULL,
|
||||
work->redOpArg, 2 * Proto::MaxGroupWidth, 0, 1, work);
|
||||
@@ -811,6 +831,9 @@ struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SIMPL
|
||||
} else if (tid < tidEndBcast && nvls->headRank != -1) {
|
||||
// Recv from network, broadcast
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 0, 1>;
|
||||
// Coverity complains about a possible overrun inside the class below, but that's actually
|
||||
// a false positive.
|
||||
// coverity[identity_transfer:FALSE]
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndReduce, nThreadsBcast, &nvls->out, &nvls->down, NULL, NULL,
|
||||
work->redOpArg, 3 * Proto::MaxGroupWidth, 0, 0, work);
|
||||
@@ -896,6 +919,9 @@ struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS_TREE, NCCL_PROTO_
|
||||
} else {
|
||||
// Reduce, send to network
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 1, 0>;
|
||||
// Coverity reports that the callee treats &treeUp as an array. However, due to the use of
|
||||
// FanAsymmetric<3, 1>, only the first element is ever accessed, so it's fine.
|
||||
// coverity[callee_ptr_arith:FALSE]
|
||||
Primitives<T, RedOp, FanAsymmetric<3, 1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndGather, nThreadsReduce, treeDown, &treeUp, NULL, NULL,
|
||||
work->redOpArg, 2 * Proto::MaxGroupWidth, 0, 0, work);
|
||||
@@ -911,6 +937,9 @@ struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_NVLS_TREE, NCCL_PROTO_
|
||||
} else if (tid < tidEndBcast && nvls->headRank != -1) {
|
||||
// Recv from network, broadcast
|
||||
using Proto = ProtoSimple<1, 1, COLL_UNROLL, 0, 1>;
|
||||
// Coverity reports that the callee treats &treeUp as an array. However, due to the use of
|
||||
// FanAsymmetric<1, 3>, only the first element is ever accessed, so it's fine.
|
||||
// coverity[callee_ptr_arith:FALSE]
|
||||
Primitives<T, RedOp, FanAsymmetric<1, 3>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid - tidEndReduce, nThreadsBcast, &treeUp, treeDown, NULL, NULL,
|
||||
work->redOpArg, 3 * Proto::MaxGroupWidth, 0, 0, work);
|
||||
@@ -971,21 +1000,21 @@ struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_CHAIN, NCCL_PR
|
||||
} else {
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(groupTid, groupNthreads, &recv, &send, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex, work);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid * int(chunkSize);
|
||||
int nelem = min(chunkSize, size - offset);
|
||||
prims.send(offset, nelem);
|
||||
prims.directSend(offset, offset, nelem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(groupTid, groupNthreads, &recv, &send, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex, work);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid * int(chunkSize);
|
||||
int nelem = min(chunkSize, size - offset);
|
||||
prims.recvReduceSend(offset, nelem);
|
||||
prims.directRecvReduceDirectSend(offset, offset, nelem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1000,40 +1029,49 @@ struct RunWorkColl<ncclFuncAllReduce, T, RedOp, NCCL_ALGO_COLLNET_CHAIN, NCCL_PR
|
||||
}
|
||||
__syncwarp();
|
||||
} else {
|
||||
// Coverity reports that the callee treats &send as an array. However, due to the use of
|
||||
// FanSymmetric<1>, only the first element is ever accessed, so it's fine.
|
||||
// coverity[callee_ptr_arith:FALSE]
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(groupTid, groupNthreads, &recv, &send, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex, work);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid * int(chunkSize);
|
||||
int nelem = min(chunkSize, size - offset);
|
||||
prims.recv(offset, nelem, /*postOp*/true);
|
||||
prims.directRecv(offset, offset, nelem, /*postOp*/true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Coverity reports that the callee treats &send as an array. However, due to the use of
|
||||
// FanSymmetric<1>, only the first element is ever accessed, so it's fine.
|
||||
// coverity[callee_ptr_arith:FALSE]
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(groupTid, groupNthreads, &recv, &send, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex, work);
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid * int(chunkSize);
|
||||
int nelem = min(chunkSize, size - offset);
|
||||
prims.recvCopyDirectSend(offset, nelem, /*postOp*/true);
|
||||
prims.directRecvCopyDirectSend(offset, nelem, /*postOp*/true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Coverity reports that the callee treats &send as an array. However, due to the use of
|
||||
// FanSymmetric<1>, only the first element is ever accessed, so it's fine.
|
||||
// coverity[callee_ptr_arith:FALSE]
|
||||
Primitives<T, RedOp, FanSymmetric<1>, /*Direct=*/1, Proto, 0>
|
||||
prims(groupTid, groupNthreads, &recv, &send, work->sendbuff, work->recvbuff,
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex);
|
||||
work->redOpArg, group * Proto::MaxGroupWidth, connIndex, connIndex, work);
|
||||
if (send == -1) {
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid*int(chunkSize);
|
||||
int nelem = min(chunkSize, size-offset);
|
||||
prims.directRecv(offset, nelem);
|
||||
prims.directRecv(offset, offset, nelem);
|
||||
}
|
||||
} else {
|
||||
for (ssize_t gridOffset = 0; gridOffset < size; gridOffset += loopSize) {
|
||||
ssize_t offset = gridOffset + bid*int(chunkSize);
|
||||
int nelem = min(chunkSize, size-offset);
|
||||
prims.directRecvCopySend(offset, nelem);
|
||||
prims.directRecvCopyDirectSend(offset, nelem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace {
|
||||
}
|
||||
|
||||
// final step: recv
|
||||
prims.directRecv(recv_offset + prims_offset, prims_nelem);
|
||||
prims.directRecv(recv_offset + prims_offset, recv_offset + prims_offset, prims_nelem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,11 @@ namespace {
|
||||
|
||||
T *inputBuf = (T*)work->sendbuff;
|
||||
T *outputBuf = (T*)work->recvbuff;
|
||||
// Coverity reports that the callee treats &ring->next as an array. However, due to the use of
|
||||
// FanSymmetric<1>, only the first element is ever accessed, so it's fine.
|
||||
// coverity[callee_ptr_arith:FALSE]
|
||||
Primitives<T, RedOp, FanSymmetric<1>, 0, Proto, 0>
|
||||
prims(tid, nthreads, &ring->prev, &ring->next, inputBuf, outputBuf, work->redOpArg, 0, work->connIndex, work->connIndex);
|
||||
prims(tid, nthreads, &ring->prev, &ring->next, inputBuf, outputBuf, work->redOpArg, 0, work->connIndex, work->connIndex, work);
|
||||
|
||||
#if defined(ENABLE_NPKIT)
|
||||
if (tid == 0) {
|
||||
@@ -70,14 +73,14 @@ namespace {
|
||||
|
||||
if (rank == root) {
|
||||
if (inputBuf == outputBuf) {
|
||||
prims.send(offset, nelem);
|
||||
prims.directSend(offset, offset, nelem);
|
||||
} else {
|
||||
prims.copySend(offset, offset, nelem);
|
||||
prims.directCopySend(offset, offset, nelem);
|
||||
}
|
||||
} else if (nextRank == root) {
|
||||
prims.recv(offset, nelem);
|
||||
prims.directRecv(offset, offset, nelem);
|
||||
} else {
|
||||
prims.recvCopySend(offset, nelem);
|
||||
prims.directRecvCopyDirectSend(offset, nelem);
|
||||
}
|
||||
}
|
||||
#if defined(ENABLE_NPKIT) && defined(ENABLE_NPKIT_EVENT_BROADCAST_RING_EXIT)
|
||||
|
||||
@@ -214,7 +214,7 @@ __device__ inline void barrier_sync_aligned(int name, int nThreads) {
|
||||
|
||||
__device__ inline bool barrier_red_or(bool vote, int name) {
|
||||
int ans;
|
||||
asm("{ .reg .pred p;"
|
||||
asm volatile("{ .reg .pred p;"
|
||||
" setp.ne.s32 p, %1, 0;"
|
||||
" barrier.red.or.pred p, %2, p; "
|
||||
" selp.s32 %0, 1, 0, p; }"
|
||||
@@ -223,7 +223,7 @@ __device__ inline bool barrier_red_or(bool vote, int name) {
|
||||
}
|
||||
__device__ inline bool barrier_red_or(bool vote, int name, int nThreads) {
|
||||
int ans;
|
||||
asm("{ .reg .pred p;"
|
||||
asm volatile("{ .reg .pred p;"
|
||||
" setp.ne.s32 p, %1, 0;"
|
||||
" barrier.red.or.pred p, %2, %3, p; "
|
||||
" selp.s32 %0, 1, 0, p; }"
|
||||
@@ -232,7 +232,7 @@ __device__ inline bool barrier_red_or(bool vote, int name, int nThreads) {
|
||||
}
|
||||
__device__ inline bool barrier_red_or_aligned(bool vote, int name) {
|
||||
int ans;
|
||||
asm("{ .reg .pred p;"
|
||||
asm volatile("{ .reg .pred p;"
|
||||
" setp.ne.s32 p, %1, 0;"
|
||||
" barrier.red.or.pred.aligned p, %2, p; "
|
||||
" selp.s32 %0, 1, 0, p; }"
|
||||
@@ -441,6 +441,9 @@ struct RunWorkBatch {
|
||||
if (work->nWarps != workPrev->nWarps) __syncthreads();
|
||||
}
|
||||
int subtn = work->nWarps*WARP_SIZE;
|
||||
// Coverity reports a possible thread divergence due to not all threads participating in the collective.
|
||||
// However, the code ensures that the participation is on a per-warp basis.
|
||||
// coverity[device_thread_diverged:FALSE]
|
||||
if (tid < subtn) RunWorkColl<Fn, T, RedOp, Algo, Proto, COLL_UNROLL>().run(tid, subtn, work);
|
||||
}
|
||||
}
|
||||
@@ -466,25 +469,25 @@ __device__ __forceinline__ void ncclKernelMain(struct ncclDevKernelArgs const* a
|
||||
// do better when we know all threads are querying the same bitmask.
|
||||
switch (tid/WARP_SIZE) {
|
||||
case 0:
|
||||
//ncclShmem.channelId = blockIdx.x;
|
||||
//ncclShmem.channelId = blockIdx.x;
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (args->channelMask.masks[i] & (1ull<<x)) {
|
||||
y = __popcll(args->channelMask.masks[i] & ((1ull<<x)-1));
|
||||
y = total + y;
|
||||
if (blockIdx.x == y) {
|
||||
ncclShmem.channelId = x + total;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (WARP_SIZE < 64) {
|
||||
x = WARP_SIZE + tid;
|
||||
if (args->channelMask.masks[i] & (1ull<<x)) {
|
||||
y = __popcll(args->channelMask.masks[i] & ((1ull<<x)-1));
|
||||
y = y + total;
|
||||
y = __popcll(args->channelMask.masks[i] & ((1ull<<x)-1));
|
||||
y = y + total;
|
||||
if (blockIdx.x == y) {
|
||||
ncclShmem.channelId = x + total;
|
||||
break;
|
||||
}
|
||||
ncclShmem.channelId = x + total;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
total = total + __popcll(args->channelMask.masks[i]);
|
||||
@@ -529,6 +532,9 @@ __device__ __forceinline__ void ncclKernelMain(struct ncclDevKernelArgs const* a
|
||||
default:
|
||||
{ int subtid = tid - 2*WARP_SIZE;
|
||||
int subtn = tn - 2*WARP_SIZE;
|
||||
// Coverity reports a possible thread divergence due to not all threads participating in the collective.
|
||||
// However, the code ensures that the participation is on a per-warp basis.
|
||||
// coverity[device_thread_diverged:FALSE]
|
||||
loadWorkBatchToShmem(subtid, subtn, args, /*batchIx=*/blockIdx.x);
|
||||
} break;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ __device__ __forceinline__ void reduceCopyPacks(
|
||||
minSrcs[s] = cvta_to_global(srcPtrFn(s)) + threadBytesBehind;
|
||||
#pragma unroll
|
||||
for (int d=0; d < MinDsts; d++)
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
minDsts[d] = cvta_to_global(dstPtrFn(d)) + threadBytesBehind;
|
||||
|
||||
// We dictate loop termination condition according to whether partial hunks
|
||||
@@ -95,13 +97,17 @@ __device__ __forceinline__ void reduceCopyPacks(
|
||||
|
||||
#pragma unroll Unroll
|
||||
for (int s=1; s < MinSrcs; s++) {
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_begin]
|
||||
BytePack<BytePerPack> tmp[Unroll];
|
||||
// coverity[dead_error_line]
|
||||
RedFn preFn(s < PreOpSrcs ? preOpArgs[s] : 0);
|
||||
#pragma unroll Unroll
|
||||
for (int u=0; u < Unroll; u++) {
|
||||
if (s < MultimemSrcs) {
|
||||
// applyLoadMultimem uses relaxed semantics for same reason we use volatile below.
|
||||
acc[u] = applyLoadMultimem<RedFn, BytePerPack>(redFn, minSrcs[s]);
|
||||
// coverity[dead_error_line]
|
||||
tmp[u] = applyLoadMultimem<RedFn, BytePerPack>(redFn, minSrcs[s]);
|
||||
} else {
|
||||
// Use volatile loads in case credits are polled for with volatile (instead of acquire).
|
||||
tmp[u] = ld_volatile_global<BytePerPack>(minSrcs[s]);
|
||||
@@ -110,6 +116,7 @@ __device__ __forceinline__ void reduceCopyPacks(
|
||||
}
|
||||
#pragma unroll Unroll
|
||||
for (int u=0; u < Unroll; u++) {
|
||||
// coverity[dead_error_line]
|
||||
if (s < PreOpSrcs) tmp[u] = applyPreOp(preFn, tmp[u]);
|
||||
acc[u] = applyReduce(redFn, acc[u], tmp[u]);
|
||||
}
|
||||
@@ -118,6 +125,8 @@ __device__ __forceinline__ void reduceCopyPacks(
|
||||
for (int s=MinSrcs; (MinSrcs < MaxSrcs) && (s < MaxSrcs) && (s < nSrcs); s++) {
|
||||
uintptr_t src = cvta_to_global(srcPtrFn(s)) + threadBytesBehind;
|
||||
BytePack<BytePerPack> tmp[Unroll];
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
RedFn preFn(s < PreOpSrcs ? preOpArgs[s] : 0);
|
||||
#pragma unroll Unroll
|
||||
for (int u=0; u < Unroll; u++) {
|
||||
@@ -127,6 +136,8 @@ __device__ __forceinline__ void reduceCopyPacks(
|
||||
}
|
||||
#pragma unroll Unroll
|
||||
for (int u=0; u < Unroll; u++) {
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
if (s < PreOpSrcs) tmp[u] = applyPreOp(preFn, tmp[u]);
|
||||
acc[u] = applyReduce(redFn, acc[u], tmp[u]);
|
||||
}
|
||||
@@ -141,7 +152,10 @@ __device__ __forceinline__ void reduceCopyPacks(
|
||||
#pragma unroll Unroll
|
||||
for (int d=0; d < MinDsts; d++) {
|
||||
#pragma unroll Unroll
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_begin]
|
||||
for (int u=0; u < Unroll; u++) {
|
||||
// coverity[dead_error_condition]
|
||||
if (d < MultimemDsts) {
|
||||
multimem_st_global(minDsts[d], acc[u]);
|
||||
} else {
|
||||
@@ -163,6 +177,8 @@ __device__ __forceinline__ void reduceCopyPacks(
|
||||
#pragma unroll
|
||||
for (int s=0; s < MinSrcs; s++) minSrcs[s] += (nWarps-1)*BytePerHunk;
|
||||
#pragma unroll
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
for (int d=0; d < MinDsts; d++) minDsts[d] += (nWarps-1)*BytePerHunk;
|
||||
threadBytesBehind += nWarps*BytePerHunk;
|
||||
threadBytesAhead -= nWarps*BytePerHunk;
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
inline __device__ void load64gpu(const uint64_t* ptr, uint64_t &v) {
|
||||
#if __CUDA_ARCH__ >= 700
|
||||
asm volatile("ld.relaxed.gpu.u64 {%0}, [%1];"
|
||||
: "=l"(v) : "l"(ptr));
|
||||
: "=l"(v) : "l"(ptr) : "memory");
|
||||
#else
|
||||
// asm volatile("ld.volatile.global.u64 {%0}, [%1];"
|
||||
// : "=l"(v) : "l"(ptr));
|
||||
// : "=l"(v) : "l"(ptr) : "memory");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -226,6 +226,8 @@ inline __device__ void ncclNetDeviceUnpackInner(
|
||||
|
||||
int PPW = ppw(nbytes, nw);
|
||||
|
||||
// Coverity reports a potential overflow but in reality PPW is tiny so there's no need to store it in an uint64_t.
|
||||
// coverity[overflow_before_widen]
|
||||
for (uint64_t meta_s = w * PPW; meta_s < meta_cnt; meta_s += nw * PPW) {
|
||||
|
||||
uint64_t iter_meta_cnt = meta_cnt - meta_s;
|
||||
|
||||
@@ -147,6 +147,9 @@ struct BytePackOf<BytePack<0>> {
|
||||
template<typename T>
|
||||
__device__ __forceinline__ typename BytePackOf<T>::Pack toPack(T value) {
|
||||
union { typename BytePackOf<T>::Pack p; T v; };
|
||||
// Coverity recommends the use of std::move here but, given that T is a POD
|
||||
// scalar, a plain copy will be just as efficient.
|
||||
// coverity[copy_assignment_call]
|
||||
v = value;
|
||||
return p;
|
||||
}
|
||||
@@ -212,7 +215,7 @@ template<> __device__ __forceinline__ void st_global<0>(uintptr_t addr, BytePack
|
||||
// template<> \
|
||||
// __device__ __forceinline__ BytePack<bytes> ld_relaxed_gpu_global<bytes>(uintptr_t addr) { \
|
||||
// data_cxx_ty tmp; \
|
||||
// asm("ld." PTX_relaxed_gpu ".global." #data_ptx_ty " %0, [%1];" : "="#data_reg_ty(tmp) : "l"(addr)); \
|
||||
// asm volatile("ld." PTX_relaxed_gpu ".global." #data_ptx_ty " %0, [%1];" : "="#data_reg_ty(tmp) : "l"(addr) : "memory"); \
|
||||
// BytePack<bytes> ans; \
|
||||
// ans.native = tmp; \
|
||||
// return ans; \
|
||||
@@ -266,14 +269,14 @@ DEFINE_ld_st_16__space(global, uintptr_t, l)
|
||||
// template<>
|
||||
// __device__ __forceinline__ BytePack<16> ld_relaxed_gpu_global<16>(uintptr_t addr) {
|
||||
// BytePack<16> ans;
|
||||
// asm("ld." PTX_relaxed_gpu ".global.v2.b64 {%0,%1}, [%2];" : "=l"(ans.u64[0]), "=l"(ans.u64[1]) : "l"(addr));
|
||||
// asm volatile("ld." PTX_relaxed_gpu ".global.v2.b64 {%0,%1}, [%2];" : "=l"(ans.u64[0]), "=l"(ans.u64[1]) : "l"(addr) : "memory");
|
||||
// return ans;
|
||||
// }
|
||||
// template<>
|
||||
// __device__ __forceinline__ void st_relaxed_gpu_global<16>(uintptr_t addr, BytePack<16> value) {
|
||||
// asm volatile("st." PTX_relaxed_gpu ".global.v2.b64 [%0], {%1,%2};" :: "l"(addr), "l"(value.u64[0]), "l"(value.u64[1]) : "memory");
|
||||
// }
|
||||
//
|
||||
|
||||
// #undef PTX_relaxed_gpu
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -291,12 +294,12 @@ __device__ __forceinline__ uint64_t ld_relaxed_sys_global(uint64_t *ptr) {
|
||||
}
|
||||
|
||||
// __device__ __forceinline__ uint64_t ld_relaxed_gpu_global(uint64_t *ptr) {
|
||||
// uint64_t ans;
|
||||
// #if __CUDA_ARCH__ >= 700
|
||||
// asm("ld.relaxed.gpu.global.u64 %0, [%1];" : "=l"(ans) : "l"(cvta_to_global(ptr)));
|
||||
// #else
|
||||
// asm("ld.volatile.global.u64 %0, [%1];" : "=l"(ans) : "l"(cvta_to_global(ptr)));
|
||||
// #endif
|
||||
// uint64_t ans;
|
||||
// #if __CUDA_ARCH__ >= 700
|
||||
// asm volatile("ld.relaxed.gpu.global.u64 %0, [%1];" : "=l"(ans) : "l"(cvta_to_global(ptr)) : "memory");
|
||||
// #else
|
||||
// asm volatile("ld.volatile.global.u64 %0, [%1];" : "=l"(ans) : "l"(cvta_to_global(ptr)) : "memory");
|
||||
// #endif
|
||||
// return ans;
|
||||
// }
|
||||
|
||||
|
||||
@@ -154,19 +154,25 @@ struct PrimitivesWithoutDirect {
|
||||
__device__ void directSendFromOutput(intptr_t outIx, int eltN) {
|
||||
static_cast<RealPrimitives*>(this)->sendFromOutput(outIx, eltN);
|
||||
}
|
||||
__device__ void directRecv(intptr_t outIx, int eltN) {
|
||||
__device__ void directRecv(intptr_t inpIx, intptr_t outIx, int eltN) {
|
||||
static_cast<RealPrimitives*>(this)->recv(outIx, eltN, /*postOp=*/false);
|
||||
}
|
||||
__device__ void directCopySend(intptr_t inpIx, intptr_t outIx, int eltN, bool postOp=false) {
|
||||
static_cast<RealPrimitives*>(this)->copySend(inpIx, outIx, eltN, postOp);
|
||||
}
|
||||
__device__ void directRecvCopySend(intptr_t outIx, int eltN) {
|
||||
__device__ void directRecvCopyDirectSend(intptr_t outIx, int eltN, bool postOp=false) {
|
||||
static_cast<RealPrimitives*>(this)->recvCopySend(outIx, eltN, /*postOp=*/false);
|
||||
}
|
||||
__device__ void directRecvReduceCopySend(intptr_t inpIx, intptr_t outIx, int eltN, bool postOp=false) {
|
||||
__device__ void recvReduceCopyDirectSend(intptr_t inpIx, intptr_t outIx, int eltN, bool postOp=false) {
|
||||
// Direct is only for the send part
|
||||
static_cast<RealPrimitives*>(this)->recvReduceCopySend(inpIx, outIx, eltN, postOp);
|
||||
}
|
||||
__device__ __forceinline__ void directRecvReduceDirectSend(intptr_t inpIx, intptr_t outIx, ssize_t eltN, bool postOp=false) {
|
||||
static_cast<RealPrimitives*>(this)->recvReduceSend(inpIx, eltN);
|
||||
}
|
||||
__device__ __forceinline__ void directRecvReduceCopyDirectSend(intptr_t inpIx, intptr_t outIx, ssize_t eltN, bool postOp=false) {
|
||||
static_cast<RealPrimitives*>(this)->recvReduceCopySend(inpIx, outIx, eltN, postOp);
|
||||
}
|
||||
};
|
||||
|
||||
#include "prims_simple.h"
|
||||
|
||||
@@ -169,7 +169,7 @@ private:
|
||||
uint64_t val64 = (uint64_t)(i4.data1) + (((uint64_t)i4.data2) << 32);
|
||||
#else
|
||||
do {
|
||||
asm("ld.volatile.global.v4.u32 {%0,%1,%2,%3}, [%4];" : "=r"(data1), "=r"(flag1), "=r"(data2), "=r"(flag2) : "l"(&src->i4));
|
||||
asm volatile("ld.volatile.global.v4.u32 {%0,%1,%2,%3}, [%4];" : "=r"(data1), "=r"(flag1), "=r"(data2), "=r"(flag2) : "l"(&src->i4) : "memory");
|
||||
#if defined(ENABLE_NPKIT) && (defined(ENABLE_NPKIT_EVENT_PRIM_LL_DATA_PROCESS_ENTRY) && defined(ENABLE_NPKIT_EVENT_PRIM_LL_DATA_PROCESS_EXIT) || defined(ENABLE_NPKIT_PRIM_COLLECT_DATA_PROCESS_TIME))
|
||||
npkitWaitRecvSpins++;
|
||||
#endif
|
||||
@@ -192,6 +192,8 @@ private:
|
||||
__device__ void readLLBeginAll(int offset, ncclLLFifoLine(&line)[MaxRecv]) {
|
||||
#pragma unroll
|
||||
for (int i=BeginIx; i < MaxRecv; i++) {
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
if (i < fan.nrecv()) {
|
||||
union ncclLLFifoLine* src = recvPtr(i) + offset;
|
||||
#if defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__)
|
||||
@@ -203,7 +205,7 @@ private:
|
||||
line[i].v[1] = __builtin_nontemporal_load(src->v+1);
|
||||
#endif
|
||||
#else
|
||||
asm("ld.volatile.global.v4.u32 {%0,%1,%2,%3}, [%4];" : "=r"(line[i].data1), "=r"(line[i].flag1), "=r"(line[i].data2), "=r"(line[i].flag2) : "l"(&src->i4));
|
||||
asm volatile("ld.volatile.global.v4.u32 {%0,%1,%2,%3}, [%4];" : "=r"(line[i].data1), "=r"(line[i].flag1), "=r"(line[i].data2), "=r"(line[i].flag2) : "l"(&src->i4) : "memory");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -230,7 +232,7 @@ private:
|
||||
line[i].v[1] = __builtin_nontemporal_load(src->v+1);
|
||||
#endif
|
||||
#else
|
||||
asm("ld.volatile.global.v4.u32 {%0,%1,%2,%3}, [%4];" : "=r"(line[i].data1), "=r"(line[i].flag1), "=r"(line[i].data2), "=r"(line[i].flag2) : "l"(&src->i4));
|
||||
asm volatile("ld.volatile.global.v4.u32 {%0,%1,%2,%3}, [%4];" : "=r"(line[i].data1), "=r"(line[i].flag1), "=r"(line[i].data2), "=r"(line[i].flag2) : "l"(&src->i4) : "memory");
|
||||
#endif
|
||||
#if defined(ENABLE_NPKIT) && (defined(ENABLE_NPKIT_EVENT_PRIM_LL_DATA_PROCESS_ENTRY) && defined(ENABLE_NPKIT_EVENT_PRIM_LL_DATA_PROCESS_EXIT) || defined(ENABLE_NPKIT_PRIM_COLLECT_DATA_PROCESS_TIME))
|
||||
npkitWaitRecvSpins++;
|
||||
@@ -259,7 +261,7 @@ private:
|
||||
__builtin_nontemporal_store(i4.v[0], dst->v);
|
||||
__builtin_nontemporal_store(i4.v[1], dst->v+1);
|
||||
#else
|
||||
asm volatile("st.volatile.global.v4.u32 [%0], {%1,%2,%3,%4};" :: "l"(&dst->i4), "r"((uint32_t)val), "r"(flag), "r"((uint32_t)(val >> 32)), "r"(flag));
|
||||
asm volatile("st.volatile.global.v4.u32 [%0], {%1,%2,%3,%4};" :: "l"(&dst->i4), "r"((uint32_t)val), "r"(flag), "r"((uint32_t)(val >> 32)), "r"(flag) : "memory");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -301,13 +303,13 @@ private:
|
||||
#endif
|
||||
#else
|
||||
if(sizeof(U) == 1)
|
||||
asm("ld.volatile.global.b8 %0,[%1];" : "=r"(u4) : "l"(src));
|
||||
asm volatile("ld.volatile.global.b8 %0,[%1];" : "=r"(u4) : "l"(src) : "memory");
|
||||
else if(sizeof(U) == 2)
|
||||
asm("ld.volatile.global.b16 %0,[%1];" : "=h"(u2) : "l"(src));
|
||||
asm volatile("ld.volatile.global.b16 %0,[%1];" : "=h"(u2) : "l"(src) : "memory");
|
||||
else if(sizeof(U) == 4)
|
||||
asm("ld.volatile.global.b32 %0,[%1];" : "=r"(u4) : "l"(src));
|
||||
asm volatile("ld.volatile.global.b32 %0,[%1];" : "=r"(u4) : "l"(src) : "memory");
|
||||
else
|
||||
asm("ld.volatile.global.b64 %0,[%1];" : "=l"(u8) : "l"(src));
|
||||
asm volatile("ld.volatile.global.b64 %0,[%1];" : "=l"(u8) : "l"(src) : "memory");
|
||||
#endif
|
||||
return elt;
|
||||
}
|
||||
@@ -333,13 +335,13 @@ private:
|
||||
__builtin_nontemporal_store(u8, (uint64_t*)dst);
|
||||
#else
|
||||
if(sizeof(U) == 1)
|
||||
asm("st.volatile.global.b8 [%0],%1;" :: "l"(dst), "r"(u4));
|
||||
asm volatile("st.volatile.global.b8 [%0],%1;" :: "l"(dst), "r"(u4) : "memory");
|
||||
else if(sizeof(U) == 2)
|
||||
asm("st.volatile.global.b16 [%0],%1;" :: "l"(dst), "h"(u2));
|
||||
asm volatile("st.volatile.global.b16 [%0],%1;" :: "l"(dst), "h"(u2) : "memory");
|
||||
else if(sizeof(U) == 4)
|
||||
asm("st.volatile.global.b32 [%0],%1;" :: "l"(dst), "r"(u4));
|
||||
asm volatile("st.volatile.global.b32 [%0],%1;" :: "l"(dst), "r"(u4) : "memory");
|
||||
else
|
||||
asm("st.volatile.global.b64 [%0],%1;" :: "l"(dst), "l"(u8));
|
||||
asm volatile("st.volatile.global.b64 [%0],%1;" :: "l"(dst), "l"(u8) : "memory");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -363,6 +365,8 @@ private:
|
||||
else {
|
||||
#pragma unroll
|
||||
for(int i=0; i < EltPerLine; i++) {
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
if(i==0 || i < eltN)
|
||||
elt[i] = load(src + i);
|
||||
}
|
||||
@@ -387,6 +391,8 @@ private:
|
||||
u8 = val;
|
||||
#pragma unroll
|
||||
for(int i=0; i < EltPerLine; i++) {
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
if (i==0 || i < eltN)
|
||||
//store(dst+i, elt[i]);
|
||||
dst[i] = elt[i];
|
||||
@@ -460,6 +466,8 @@ private:
|
||||
if (RECV) {
|
||||
data = !SRC ? peerData : applyReduce(redOp, peerData, data);
|
||||
#pragma unroll MaxRecv
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
for (int i=1; i < MaxRecv && i < fan.nrecv(); i++) {
|
||||
peerData = readLLFinish(offset, line, i);
|
||||
data = applyReduce(redOp, peerData, data);
|
||||
@@ -470,6 +478,8 @@ private:
|
||||
|
||||
// Send : inter-node, then intra-node, then local
|
||||
if (SEND) {
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
for (int i=1; i < MaxSend && i < fan.nsend(); i++)
|
||||
storeLL(sendPtr(i)+offset, data, sendFlag(i));
|
||||
storeLL(sendPtr(0)+offset, data, sendFlag(0));
|
||||
@@ -502,6 +512,8 @@ private:
|
||||
postRecv();
|
||||
}
|
||||
if (SEND) {
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
for (int i=1; i < MaxSend && i < fan.nsend(); i++)
|
||||
incSend(i, offset);
|
||||
incSend(0, offset);
|
||||
@@ -610,12 +622,12 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
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,
|
||||
bool userBufReg=false, int stepSize_=0
|
||||
uint8_t connIndexRecv=0, uint8_t connIndexSend=0, struct ncclDevWorkColl* e = nullptr,
|
||||
bool ipcReg = false, bool netReg = false, int stepSize_ = 0
|
||||
):
|
||||
redOp(redOpArg),
|
||||
tid(tid), nthreads(nthreads), wid(tid%WARP_SIZE), group(group),
|
||||
@@ -625,16 +637,23 @@ private:
|
||||
// If we are going to support oneshot collNet + LL, then we would need to add connector index here
|
||||
int nrecv=0, nsend=0;
|
||||
// We compare with Fan::MaxRecv here because this->MaxRecv is always at least 1
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
while (nrecv < Fan::MaxRecv && recvPeers[nrecv] >= 0) {
|
||||
loadRecvConn(&channel->peers[recvPeers[nrecv]]->recv[connIndexRecv], nrecv);
|
||||
nrecv++;
|
||||
}
|
||||
// coverity[dead_error_line]
|
||||
while (nsend < MaxSend && sendPeers[nsend] >= 0) {
|
||||
loadSendConn(&channel->peers[sendPeers[nsend]]->send[connIndexSend], nsend);
|
||||
nsend++;
|
||||
}
|
||||
this->fan = Fan(nrecv, nsend);
|
||||
// Coverity reports recvConn and sendConn being possibly NULL at this point but that won't actually
|
||||
// happen given the two "while" loops just above.
|
||||
// coverity[var_deref_model:FALSE]
|
||||
loadRecvSync();
|
||||
// coverity[var_deref_model:FALSE]
|
||||
loadSendSync();
|
||||
setDataPtrs(inputBuf, outputBuf);
|
||||
}
|
||||
|
||||
@@ -264,6 +264,8 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
for (int i=1; i<MaxRecv && i<fan.nrecv(); i++) {
|
||||
uint64_t flag = recvFlag(i);
|
||||
uint64_t* ptr = recvPtr(i)+ll128Offset;
|
||||
@@ -305,6 +307,8 @@ private:
|
||||
#endif
|
||||
/************************ Send **************************/
|
||||
if (SEND) {
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
for (int i=1; i<MaxSend && i<fan.nsend(); i++) {
|
||||
uint64_t flag = sendFlag(i);
|
||||
uint64_t* ptr = sendPtr(i)+ll128Offset;
|
||||
@@ -489,7 +493,7 @@ public:
|
||||
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 ncclDevWorkColl* e = nullptr,
|
||||
bool userBufReg=false, int stepSize_=0
|
||||
bool ipcReg = false, bool netReg = false, int stepSize_ = 0
|
||||
):
|
||||
redOp(redOpArg),
|
||||
tid(tid), nthreads(nthreads), wid(tid%WARP_SIZE), warp(tid/WARP_SIZE),
|
||||
@@ -508,7 +512,11 @@ public:
|
||||
nsend++;
|
||||
}
|
||||
this->fan = Fan(nrecv, nsend);
|
||||
// Coverity reports recvConn and sendConn being possibly NULL at this point but that won't actually
|
||||
// happen given the two "while" loops just above.
|
||||
// coverity[var_deref_model:FALSE]
|
||||
loadRecvSync();
|
||||
// coverity[var_deref_model:FALSE]
|
||||
loadSendSync();
|
||||
setDataPtrs(inputBuf, outputBuf);
|
||||
}
|
||||
|
||||
@@ -14,28 +14,38 @@
|
||||
#include "network/unpack/unpack.h"
|
||||
#include <cassert>
|
||||
|
||||
enum primsMode {
|
||||
primsModeDefault = 0,
|
||||
primsModePatRs = 1,
|
||||
primsModePatAg = 2
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp, typename Fan, int Direct,
|
||||
int SlicePerChunk, int StepPerSlice, int Unroll, int P2p, int MultimemSrcs, int MultimemDsts>
|
||||
int SlicePerChunk, int StepPerSlice, int Unroll, int P2p, int MultimemSrcs, int MultimemDsts>
|
||||
class Primitives<
|
||||
T, RedOp, Fan, Direct, ProtoSimple<SlicePerChunk, StepPerSlice, Unroll, MultimemSrcs, MultimemDsts>, P2p
|
||||
> {
|
||||
static constexpr int MaxRecv = Fan::MaxRecv, MaxSend = Fan::MaxSend;
|
||||
static constexpr int Input=0, Output=1;
|
||||
static constexpr int RoleWaitRecv = 0x04, // 0x1 0x2 are free to use
|
||||
RoleWaitSend = 0x08,
|
||||
RolePostSend = 0x10,
|
||||
RolePostRecv = 0x20,
|
||||
Aborted = 0x40,
|
||||
UserBufferMode = 0x80,
|
||||
ConnFifoEnabled = 0x100,
|
||||
DirectWrite = 0x200,
|
||||
DirectRead = 0x400,
|
||||
// 0x800 is free to use
|
||||
NvlsMinPolling = 0x1000,
|
||||
NetDeviceUnpack = 0x2000,
|
||||
AnyNetDeviceUnpack = 0x4000,
|
||||
NvlsDirectRead = 0x8000,
|
||||
NvlsDirectWrite = 0x10000;
|
||||
static constexpr int RoleInput = 0x01,
|
||||
RoleOutput = 0x02,
|
||||
RoleWaitRecv = 0x04,
|
||||
RoleWaitSend = 0x08,
|
||||
RolePostSend = 0x10,
|
||||
RolePostRecv = 0x20,
|
||||
Aborted = 0x40,
|
||||
NetRegMode = 0x80,
|
||||
ConnFifoEnabled = 0x100,
|
||||
DirectWrite = 0x200,
|
||||
DirectRead = 0x400,
|
||||
PatMode = 0x800,
|
||||
NvlsMinPolling = 0x1000,
|
||||
NetDeviceUnpack = 0x2000,
|
||||
AnyNetDeviceUnpack = 0x4000,
|
||||
NvlsDirectRead = 0x8000,
|
||||
NvlsDirectWrite = 0x10000,
|
||||
IpcWrite = 0x20000,
|
||||
IpcRead = 0x40000;
|
||||
const int tid, tidInBlock;
|
||||
const int nthreads;
|
||||
int nworkers;
|
||||
@@ -45,13 +55,15 @@ class Primitives<
|
||||
int flags;
|
||||
const int group;
|
||||
uint64_t step;
|
||||
struct ncclConnInfo* conn = NULL;
|
||||
struct ncclConnFifo* connFifo = NULL;
|
||||
T* connEltsFifo;
|
||||
T* directBuff;
|
||||
T* directBuff = NULL;
|
||||
uint64_t *connStepPtr;
|
||||
uint64_t connStepCache; // Cache last seen value of (*connStepPtr)
|
||||
int connStepSize; // Connection step size
|
||||
void* netDeviceHandle;
|
||||
uint64_t accSize; // Accumulated size. Used by PAT operations
|
||||
uint32_t* next_hdp_reg;
|
||||
uint64_t* barriers;
|
||||
uint64_t barrier_next = 0;
|
||||
@@ -93,7 +105,7 @@ private:
|
||||
#if __CUDA_ARCH__ >= 900 && CUDART_VERSION >= 12010
|
||||
if (flags & NvlsMinPolling) {
|
||||
uint64_t ans;
|
||||
asm("multimem.ld_reduce.acquire.sys.global.min.u64 %0, [%1];" : "=l"(ans) : "l"(cvta_to_global(ptr)));
|
||||
asm volatile("multimem.ld_reduce.acquire.sys.global.min.u64 %0, [%1];" : "=l"(ans) : "l"(cvta_to_global(ptr)) : "memory");
|
||||
return ans;
|
||||
}
|
||||
#endif
|
||||
@@ -109,8 +121,10 @@ private:
|
||||
template <int DirectRecv, int DirectSend, int Recv, int Send, int Src, int Dst>
|
||||
__device__ __forceinline__ void waitPeer(intptr_t srcIx, intptr_t dstIx, int offset, int nelts) {
|
||||
const bool isSendNotRecv = (Send && Recv) ? (flags & RoleWaitSend) : Send;
|
||||
const bool noRecvWait = DirectRecv && Src && (flags & DirectRead); // no wait when directly reading from remote input
|
||||
const bool noRecvWait = DirectRecv && Src && (flags & (DirectRead | IpcRead)); // no wait when directly reading from remote input
|
||||
const bool noSendWait = DirectSend && (flags & (DirectRead|DirectWrite)); // no wait in empty send (e.g. directScatter) or direct remote write
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
if (((flags & (Recv*RoleWaitRecv)) && !noRecvWait) ||
|
||||
((flags & (Send*RoleWaitSend)) && !noSendWait)) {
|
||||
int spins = 0;
|
||||
@@ -134,28 +148,30 @@ private:
|
||||
|
||||
void **ptrs = isSendNotRecv ? (ncclShmem.groups[group].dsts + Dst)
|
||||
: (ncclShmem.groups[group].srcs + Src);
|
||||
if (flags & UserBufferMode) {
|
||||
// Do nothing
|
||||
if (flags & NetRegMode) {
|
||||
// Do nothing
|
||||
} else if ((flags & ConnFifoEnabled) && connFifo[step%NCCL_STEPS].mode == NCCL_MODE_OFFSET) {
|
||||
ptrs[index] = connEltsFifo + loadInt(&connFifo[step%NCCL_STEPS].offset)/sizeof(T);
|
||||
} else if (isSendNotRecv && DirectSend) {
|
||||
if (flags & (DirectWrite | NvlsDirectWrite)) {
|
||||
if (flags & (DirectWrite | NvlsDirectWrite | IpcWrite)) {
|
||||
ptrs[index] = directBuff + dstIx + offset;
|
||||
} else if (flags & DirectRead) { // empty send
|
||||
} else if ((flags & DirectRead) || (flags & IpcRead)) { // empty send
|
||||
ptrs[index] = nullptr;
|
||||
} else {
|
||||
ptrs[index] = connEltsFifo + (step%NCCL_STEPS)*connStepSize;
|
||||
}
|
||||
} else if (!isSendNotRecv && DirectRecv) {
|
||||
if (flags & (DirectRead | NvlsDirectRead)) {
|
||||
if (flags & (DirectRead | NvlsDirectRead | IpcRead)) {
|
||||
ptrs[index] = directBuff + srcIx + offset;
|
||||
} else if (flags & DirectWrite) {
|
||||
} else if ((flags & DirectWrite) || (flags & IpcWrite)) {
|
||||
ptrs[index] = directBuff + dstIx + offset; // send to next from my output buffer
|
||||
} else {
|
||||
ptrs[index] = connEltsFifo + (step%NCCL_STEPS)*connStepSize;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
ptrs[index] = connEltsFifo + (step%NCCL_STEPS)*connStepSize;
|
||||
}
|
||||
if (flags & NetDeviceUnpack) {
|
||||
@@ -198,7 +214,7 @@ private:
|
||||
int slice = 0;
|
||||
int offset = 0;
|
||||
|
||||
if (tid < nworkers && offset < nelem && ((flags & UserBufferMode) == 0)) {
|
||||
if (tid < nworkers && offset < nelem && ((flags & NetRegMode) == 0)) {
|
||||
// Worker-only loop for non-empty slices. Non-workers and empty slices are
|
||||
// processed in the loop following this if block. The benefit of splitting
|
||||
// the loop like this is we pull two branches out of the critical path.
|
||||
@@ -234,7 +250,7 @@ private:
|
||||
waitPeer<DirectRecv, DirectSend, Recv, Send, Src, Dst>(srcIx, dstIx, offset, sliceSize);
|
||||
subBarrier();
|
||||
/* if user abort the kernel, we don't need to actually perform copy/reduce; just set size
|
||||
* to 0 to avoid unnecessary workload. */
|
||||
* to 0 to avoid unnecessary workload. */
|
||||
int workSize = ncclShmem.aborted ? 0 : sliceSize;
|
||||
if (flags & AnyNetDeviceUnpack) {
|
||||
ncclNetDeviceUnpack<Recv>(tid, tidInBlock, nworkers, group, ncclShmem.groups[group].devicePlugin.unpack.unpackNetDeviceIndexMask, Src, workSize);
|
||||
@@ -244,8 +260,8 @@ private:
|
||||
|
||||
if (DirectRecv && ncclShmem.groups[group].srcs[0] == ncclShmem.groups[group].dsts[0]
|
||||
/* NVLS can have srcs[0] == dsts[0], but we cannot enter this "if branch",
|
||||
* so we need to check whether MultimemSrcs and MultimemDsts are 0. */
|
||||
&& MultimemSrcs == 0 && MultimemDsts == 0) {
|
||||
* so we need to check whether MultimemSrcs and MultimemDsts are 0. */
|
||||
&& MultimemSrcs == 0 && MultimemDsts == 0 && !Src) {
|
||||
// We can only have one direct receive. Since srcs[0] == dstPtr+offset, skip one copy
|
||||
if (Send) {
|
||||
|
||||
@@ -264,9 +280,9 @@ private:
|
||||
|
||||
reduceCopy<Unroll, RedOp, T, 0, 1, 1, 0, 1, MaxSend, /*PreOpSrcs*/0>
|
||||
(tid, nworkers, /*redArg*/0, /*preOpArgs*/nullptr, /*postOp*/false,
|
||||
1, ncclShmem.groups[group].srcs,
|
||||
fan.nsend(), ncclShmem.groups[group].dsts+1,
|
||||
workSize);
|
||||
1, ncclShmem.groups[group].srcs,
|
||||
fan.nsend(), ncclShmem.groups[group].dsts+1,
|
||||
workSize);
|
||||
|
||||
#if defined(ENABLE_NPKIT) && defined(ENABLE_NPKIT_PRIM_COLLECT_DATA_PROCESS_TIME)
|
||||
if (tid == 0) {
|
||||
@@ -300,9 +316,9 @@ private:
|
||||
|
||||
reduceCopy<Unroll, RedOp, T, 0, 1, 1, 0, 1, 1, /*PreOpSrcs*/0>
|
||||
(tid, nworkers, ncclShmem.redOpArgs[0], nullptr, postOp,
|
||||
Recv, ncclShmem.groups[group].srcs,
|
||||
Dst, ncclShmem.groups[group].dsts,
|
||||
workSize);
|
||||
Recv, ncclShmem.groups[group].srcs,
|
||||
Dst, ncclShmem.groups[group].dsts,
|
||||
workSize);
|
||||
|
||||
#if defined(ENABLE_NPKIT) && defined(ENABLE_NPKIT_PRIM_COLLECT_DATA_PROCESS_TIME)
|
||||
if (tid == 0) {
|
||||
@@ -318,7 +334,7 @@ private:
|
||||
}
|
||||
#endif
|
||||
|
||||
} else {
|
||||
} else if (ncclShmem.groups[group].srcs[0] && ncclShmem.groups[group].dsts[0]) {
|
||||
#if defined(ENABLE_NPKIT) && defined(ENABLE_NPKIT_EVENT_PRIM_SIMPLE_REDUCE_OR_COPY_MULTI_ENTRY)
|
||||
if (tid == 0) {
|
||||
NpKit::CollectGpuEvent(NPKIT_EVENT_PRIM_SIMPLE_REDUCE_OR_COPY_MULTI_ENTRY, sliceSize*sizeof(T), 0, NPKIT_GET_GPU_TIMESTAMP(),
|
||||
@@ -338,9 +354,9 @@ private:
|
||||
MultimemSrcs, Recv+Src, Recv*MaxRecv+Src,
|
||||
MultimemDsts, Send+Dst, Send*MaxSend+Dst, PreOpSrcs>
|
||||
(tid, nworkers, ncclShmem.redOpArgs[0], ncclShmem.redOpArgs, postOp,
|
||||
Recv*fan.nrecv()+Src, ncclShmem.groups[group].srcs,
|
||||
Send*fan.nsend()+Dst, ncclShmem.groups[group].dsts,
|
||||
workSize);
|
||||
Recv*fan.nrecv()+Src, ncclShmem.groups[group].srcs,
|
||||
Send*fan.nsend()+Dst, ncclShmem.groups[group].dsts,
|
||||
workSize);
|
||||
|
||||
#if defined(ENABLE_NPKIT) && defined(ENABLE_NPKIT_PRIM_COLLECT_DATA_PROCESS_TIME)
|
||||
if (tid == 0) {
|
||||
@@ -361,6 +377,8 @@ private:
|
||||
postPeer<Recv, Send>(0 < sliceSize);
|
||||
offset += sliceSize;
|
||||
slice += 1;
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
} while (slice < SlicePerChunk && offset < nelem);
|
||||
}
|
||||
|
||||
@@ -450,12 +468,13 @@ public:
|
||||
}
|
||||
|
||||
template<int Recv, int Send, typename Fn>
|
||||
__device__ __forceinline__ void process(Fn &&fn) {
|
||||
__device__ __forceinline__ void process(Fn &&fn, uint32_t sendDirectFlag, uint32_t recvDirectFlag) {
|
||||
#pragma unroll 1
|
||||
for (int slice=0; slice < SlicePerChunk; slice++) {
|
||||
if (tid < nworkers) {
|
||||
int nsend, nrecv;
|
||||
if (flags & (Recv*RoleWaitRecv | Send*RoleWaitSend)) {
|
||||
bool isSendNotRecv = (Send && Recv) ? (flags & RoleWaitSend) : Send;
|
||||
const bool isSendNotRecv = (Send && Recv) ? (flags & RoleWaitSend) : Send;
|
||||
int spins = 0;
|
||||
while (connStepCache + (isSendNotRecv ? NCCL_STEPS : 0) < step + StepPerSlice) {
|
||||
connStepCache = loadStepValue(connStepPtr);
|
||||
@@ -466,19 +485,53 @@ public:
|
||||
if ((flags & ConnFifoEnabled) && connFifo[step%NCCL_STEPS].mode == NCCL_MODE_OFFSET) {
|
||||
int offset = loadInt(&connFifo[step%NCCL_STEPS].offset);
|
||||
ptrs[index] = connEltsFifo + offset/sizeof(T);
|
||||
} else if (Direct && fn.work->regUsed) {
|
||||
if (isSendNotRecv) {
|
||||
if (flags & (DirectWrite | IpcWrite)) {
|
||||
ptrs[index] = directBuff;
|
||||
} else if (flags & (DirectRead | IpcRead)) { // empty send
|
||||
ptrs[index] = nullptr;
|
||||
} else {
|
||||
ptrs[index] = connEltsFifo + (step%NCCL_STEPS)*stepSize;
|
||||
}
|
||||
} else {
|
||||
if (flags & (DirectRead | IpcRead)) {
|
||||
ptrs[index] = directBuff;
|
||||
} else if (flags & (DirectWrite | IpcWrite)) {
|
||||
if (Send)
|
||||
ptrs[index] = directBuff; // send to next from my output buffer
|
||||
else
|
||||
ptrs[index] = nullptr;
|
||||
} else {
|
||||
ptrs[index] = connEltsFifo + (step%NCCL_STEPS)*stepSize;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ptrs[index] = connEltsFifo + (step%NCCL_STEPS)*stepSize;
|
||||
}
|
||||
}
|
||||
subBarrier();
|
||||
fn.template operator()<SlicePerChunk, 0, Recv*MaxRecv, 0, Send*MaxSend>
|
||||
(tid, nworkers, slice, stepSize*StepPerSlice,
|
||||
fan.nrecv(), ncclShmem.groups[group].srcs,
|
||||
fan.nsend(), ncclShmem.groups[group].dsts, ncclShmem.groups[group].dstSizes);
|
||||
if (Recv == 0 || ncclShmem.groups[group].srcs[0] == nullptr) {
|
||||
nrecv = 0;
|
||||
} else {
|
||||
nrecv = fan.nrecv();
|
||||
}
|
||||
|
||||
if (Send == 0 || ncclShmem.groups[group].dsts[0] == nullptr) {
|
||||
nsend = 0;
|
||||
} else {
|
||||
nsend = fan.nsend();
|
||||
}
|
||||
fn.template operator() < SlicePerChunk, 0, Recv*MaxRecv, 0, Send*MaxSend >
|
||||
(tid, nworkers, slice, stepSize * StepPerSlice,
|
||||
nrecv, ncclShmem.groups[group].srcs,
|
||||
nsend, ncclShmem.groups[group].dsts, ncclShmem.groups[group].dstSizes, sendDirectFlag, recvDirectFlag);
|
||||
}
|
||||
barrier();
|
||||
int32_t dstSize = 0;
|
||||
if (flags & Send*RolePostSend) {
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_begin]
|
||||
dstSize = ncclShmem.groups[group].dstSizes[index];
|
||||
ncclShmem.groups[group].dstSizes[index] = 0;
|
||||
if (flags & ConnFifoEnabled) connFifo[step%NCCL_STEPS].size = dstSize*sizeof(T);
|
||||
@@ -561,110 +614,109 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
__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) {
|
||||
// handle must be a device ptr
|
||||
netDeviceHandle = conn->netDeviceHandle.handle;
|
||||
// Cache the handle
|
||||
ncclNetDeviceUnpackSetup(netDeviceHandle, group, index);
|
||||
flags |= NetDeviceUnpack;
|
||||
}
|
||||
step = conn->step;
|
||||
step = roundUp(step, SlicePerChunk*StepPerSlice);
|
||||
if (flags & RolePostRecv) {
|
||||
connStepPtr = conn->head;
|
||||
STORE(connStepPtr, step); // Return credits in case we rounded up.
|
||||
}
|
||||
if (flags & RoleWaitRecv) {
|
||||
ncclShmem.groups[group].recvConns[index] = conn; // WaitRecv role saves since that's who needs it in setDataPtrs()
|
||||
flags |= (conn->flags & NCCL_NVLS_MIN_POLL) ? NvlsMinPolling : 0;
|
||||
connStepPtr = conn->tail;
|
||||
connStepCache = loadStepValue(connStepPtr);
|
||||
connStepSize = conn->stepSize/sizeof(T);
|
||||
connEltsFifo = (T*)conn->buffs[NCCL_PROTO_SIMPLE];
|
||||
if (conn->connFifo != nullptr) {
|
||||
flags |= ConnFifoEnabled;
|
||||
connFifo = conn->connFifo;
|
||||
} else if (Direct) {
|
||||
// User buffers have been registered
|
||||
if ((conn->flags & (NCCL_IPC_READ|NCCL_IPC_WRITE)) && e != nullptr && e->regUsed) {
|
||||
if (connIndex == 1 && P2p == 0) {
|
||||
flags |= DirectRead; // scatter-reduce use direct pull
|
||||
} else {
|
||||
flags |= (e->direct & NCCL_DIRECT_WRITE) ? DirectWrite :
|
||||
(e->direct & NCCL_DIRECT_READ) ? DirectRead : 0;
|
||||
}
|
||||
} else if (conn->flags & (NCCL_DIRECT_WRITE|NCCL_DIRECT_READ)) {
|
||||
if (connIndex == 1 && P2p == 0) {
|
||||
flags |= DirectRead; // scatter-reduce use direct pull
|
||||
} else {
|
||||
// direct read not allowed in non-register case
|
||||
// otherwise, in one-to-multi send, we could mix empty send and intermediate send
|
||||
flags |= (conn->flags & NCCL_DIRECT_WRITE) ? DirectWrite : 0;
|
||||
}
|
||||
} else if ((conn->flags & NCCL_NVLS_MIN_POLL) && e != nullptr && e->regUsed) {
|
||||
/* NVLS direct */
|
||||
flags |= NvlsDirectRead;
|
||||
__device__ __forceinline__ void loadRecvConn(ncclDevChannelPeer *peer, int connIndex, uint32_t direct, int regFlag) {
|
||||
conn = &peer->recv[connIndex];
|
||||
if (conn->netDeviceHandle.netDeviceType == NCCL_NET_DEVICE_UNPACK) {
|
||||
// handle must be a device ptr
|
||||
netDeviceHandle = conn->netDeviceHandle.handle;
|
||||
// Cache the handle
|
||||
ncclNetDeviceUnpackSetup(netDeviceHandle, group, index);
|
||||
flags |= NetDeviceUnpack;
|
||||
}
|
||||
step = conn->step;
|
||||
step = roundUp(step, SlicePerChunk*StepPerSlice);
|
||||
if (flags & RolePostRecv) {
|
||||
connStepPtr = conn->head;
|
||||
STORE(connStepPtr, step); // Return credits in case we rounded up.
|
||||
}
|
||||
if (flags & RoleWaitRecv) {
|
||||
if ((flags & PatMode) == 0) ncclShmem.groups[group].recvConns[index] = conn; // WaitRecv role saves since that's who needs it in setDataPtrs()
|
||||
flags |= (conn->flags & NCCL_NVLS_MIN_POLL) ? NvlsMinPolling : 0;
|
||||
connStepPtr = conn->tail;
|
||||
connStepCache = loadStepValue(connStepPtr);
|
||||
connStepSize = conn->stepSize/sizeof(T);
|
||||
connEltsFifo = (T*)conn->buffs[NCCL_PROTO_SIMPLE];
|
||||
if (conn->connFifo != nullptr) {
|
||||
flags |= ConnFifoEnabled;
|
||||
connFifo = conn->connFifo;
|
||||
} else if (Direct && regFlag) {
|
||||
// User buffers have been registered
|
||||
if (conn->flags & (NCCL_IPC_READ | NCCL_IPC_WRITE)) {
|
||||
if (P2p) {
|
||||
flags |= conn->flags & NCCL_IPC_WRITE ? IpcWrite : IpcRead;
|
||||
} else if (connIndex == 1 && direct) {
|
||||
flags |= IpcRead;
|
||||
} else {
|
||||
flags |= direct & NCCL_DIRECT_READ ? IpcRead : IpcWrite;
|
||||
}
|
||||
} else if (conn->flags & (NCCL_DIRECT_WRITE | NCCL_DIRECT_READ)) {
|
||||
if (P2p) {
|
||||
flags |= conn->flags & NCCL_DIRECT_WRITE ? DirectWrite : DirectRead;
|
||||
} else if (connIndex == 1 && direct) {
|
||||
flags |= DirectRead; // scatter-reduce use direct pull
|
||||
} else {
|
||||
flags |= direct & NCCL_DIRECT_READ ? DirectRead : DirectWrite;
|
||||
}
|
||||
} else if ((conn->flags & NCCL_NVLS_MIN_POLL)) {
|
||||
/* NVLS direct */
|
||||
flags |= NvlsDirectRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void loadSendConn(ncclDevChannelPeer *peer, int connIndex, struct ncclDevWorkColl* e) {
|
||||
if (flags & (RoleWaitSend|RolePostSend)) {
|
||||
auto *conn = &peer->send[connIndex];
|
||||
step = conn->step;
|
||||
step = roundUp(step, SlicePerChunk*StepPerSlice);
|
||||
__device__ __forceinline__ void loadSendConn(ncclDevChannelPeer *peer, int connIndex, uint32_t direct, int regFlag) {
|
||||
conn = &peer->send[connIndex];
|
||||
step = conn->step;
|
||||
step = roundUp(step, SlicePerChunk*StepPerSlice);
|
||||
|
||||
connFifo = conn->connFifo;
|
||||
if (connFifo != nullptr) flags |= ConnFifoEnabled;
|
||||
connFifo = conn->connFifo;
|
||||
if (connFifo != nullptr) flags |= ConnFifoEnabled;
|
||||
|
||||
if (flags & RolePostSend) {
|
||||
connStepPtr = conn->tail;
|
||||
next_hdp_reg = conn->next_hdp_reg;
|
||||
connEltsFifo = (T*)conn->buffs[NCCL_PROTO_SIMPLE];
|
||||
}
|
||||
if (flags & RoleWaitSend) {
|
||||
ncclShmem.groups[group].sendConns[index] = conn; // WaitSend role saves since that's who needs it in setDataPtrs()
|
||||
flags |= (conn->flags & NCCL_NVLS_MIN_POLL) ? NvlsMinPolling : 0;
|
||||
connStepPtr = conn->head;
|
||||
connStepCache = loadStepValue(connStepPtr);
|
||||
connStepSize = conn->stepSize/sizeof(T);
|
||||
connEltsFifo = (T*)conn->buffs[NCCL_PROTO_SIMPLE];
|
||||
if (connFifo == nullptr && Direct) {
|
||||
// User buffers have been registered
|
||||
if ((conn->flags & (NCCL_IPC_READ|NCCL_IPC_WRITE)) && e != nullptr && e->regUsed) {
|
||||
if (connIndex == 1 && P2p == 0) {
|
||||
flags |= DirectRead; // scatter-reduce use direct pull
|
||||
} else {
|
||||
flags |= (e->direct & NCCL_DIRECT_WRITE) ? DirectWrite :
|
||||
(e->direct & NCCL_DIRECT_READ) ? DirectRead : 0;
|
||||
}
|
||||
} else if (conn->flags & (NCCL_DIRECT_WRITE|NCCL_DIRECT_READ)) {
|
||||
if (connIndex == 1 && P2p == 0) {
|
||||
flags |= DirectRead; // scatter-reduce use direct pull
|
||||
} else {
|
||||
// direct read not allowed in non-register case
|
||||
// otherwise, in one-to-multi send, we could mix empty send and intermediate send
|
||||
flags |= (conn->flags & NCCL_DIRECT_WRITE) ? DirectWrite : 0;
|
||||
}
|
||||
} else if ((conn->flags & NCCL_NVLS_MIN_POLL) && e != nullptr && e->regUsed) {
|
||||
/* NVLS direct */
|
||||
flags |= NvlsDirectWrite;
|
||||
if (flags & RolePostSend) {
|
||||
connStepPtr = conn->tail;
|
||||
next_hdp_reg = conn->next_hdp_reg;
|
||||
connEltsFifo = (T*)conn->buffs[NCCL_PROTO_SIMPLE];
|
||||
}
|
||||
if (flags & RoleWaitSend) {
|
||||
if ((flags & PatMode) == 0) ncclShmem.groups[group].sendConns[index] = conn; // WaitSend role saves since that's who needs it in setDataPtrs()
|
||||
flags |= (conn->flags & NCCL_NVLS_MIN_POLL) ? NvlsMinPolling : 0;
|
||||
connStepPtr = conn->head;
|
||||
connStepCache = loadStepValue(connStepPtr);
|
||||
connStepSize = conn->stepSize/sizeof(T);
|
||||
connEltsFifo = (T*)conn->buffs[NCCL_PROTO_SIMPLE];
|
||||
if (connFifo == nullptr && Direct && regFlag) {
|
||||
// User buffers have been registered
|
||||
if (conn->flags & (NCCL_IPC_READ | NCCL_IPC_WRITE)) {
|
||||
if (P2p) {
|
||||
flags |= conn->flags & NCCL_IPC_WRITE ? IpcWrite : IpcRead;
|
||||
} else if (connIndex == 1 && direct) {
|
||||
flags |= IpcRead;
|
||||
} else {
|
||||
flags |= direct & NCCL_DIRECT_READ ? IpcRead : IpcWrite;
|
||||
}
|
||||
} else if (conn->flags & (NCCL_DIRECT_WRITE | NCCL_DIRECT_READ)) {
|
||||
if (P2p) {
|
||||
flags |= conn->flags & NCCL_DIRECT_WRITE ? DirectWrite : DirectRead;
|
||||
} else if (connIndex == 1 && direct) {
|
||||
flags |= DirectRead; // scatter-reduce use direct pull
|
||||
} else {
|
||||
flags |= direct & NCCL_DIRECT_READ ? DirectRead : DirectWrite;
|
||||
}
|
||||
} else if ((conn->flags & NCCL_NVLS_MIN_POLL)) {
|
||||
/* NVLS direct */
|
||||
flags |= NvlsDirectWrite;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
public:
|
||||
__forceinline__ __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 ncclDevWorkColl* e = nullptr,bool userBufReg=false, int stepSize_=0
|
||||
uint8_t connIndexRecv = 0, uint8_t connIndexSend = 0, struct ncclDevWorkColl* e = nullptr,
|
||||
bool ipcReg = false, bool netReg = false, int stepSize_ = 0, int mode = primsModeDefault
|
||||
):
|
||||
tid(tid), nthreads(nthreads), tidInBlock(threadIdx.x), group(group),
|
||||
stepSize(stepSize_ == 0 ? ncclShmem.comm.buffSizes[NCCL_PROTO_SIMPLE]/NCCL_STEPS/sizeof(T) : stepSize_) {
|
||||
@@ -673,33 +725,71 @@ private:
|
||||
barriers = &ncclShmem.groups[group].barrier;
|
||||
this->nworkers = nthreads;
|
||||
|
||||
int nrecv=0, nsend=0;
|
||||
while (nrecv < MaxRecv && recvPeers[nrecv] != -1) nrecv++;
|
||||
while (nsend < MaxSend && sendPeers[nsend] != -1) nsend++;
|
||||
this->fan = Fan(nrecv, nsend);
|
||||
|
||||
constexpr int ThreadPerSync =
|
||||
MaxSend >= 16 || MaxRecv >= 16 ? 32 : // NVLS may have an arity > 8. In that case increase the size of the groups
|
||||
MaxSend >= 8 || MaxRecv >= 8 ? 16 :
|
||||
8; // Allows for all roles (WaitRecv/WaitSend/PostRecv/PostSend) within a single warp
|
||||
static_assert(MaxSend <= ThreadPerSync && MaxRecv <= ThreadPerSync, "Not enough threads to cover all peers");
|
||||
|
||||
index = -1;
|
||||
int peer = -1;
|
||||
flags = 0;
|
||||
assert(2*(nrecv+nsend) <= nthreads); // Ensure no thread is assigned more than one role.
|
||||
if (tid < nrecv) { flags |= RoleWaitRecv; index = tid; }
|
||||
else if (tid < nrecv+nsend) { flags |= RoleWaitSend; index = tid-nrecv; }
|
||||
else if (nthreads-nsend <= tid) { flags |= RolePostSend; index = tid-(nthreads-nsend); }
|
||||
else if (nthreads-nrecv-nsend <= tid) { flags |= RolePostRecv; index = tid-(nthreads-nrecv-nsend); }
|
||||
index = -1;
|
||||
if (mode == primsModeDefault) { // Connect to ranks in sendPeers/recvPeers
|
||||
int nrecv=0, nsend=0;
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_line]
|
||||
while (nrecv < MaxRecv && recvPeers[nrecv] != -1) nrecv++;
|
||||
// coverity[dead_error_line]
|
||||
while (nsend < MaxSend && sendPeers[nsend] != -1) nsend++;
|
||||
this->fan = Fan(nrecv, nsend);
|
||||
|
||||
int peer = 0;
|
||||
if (flags & (RoleWaitRecv|RolePostRecv)) peer = recvPeers[index];
|
||||
if (flags & (RoleWaitSend|RolePostSend)) peer = sendPeers[index];
|
||||
constexpr int ThreadPerSync =
|
||||
MaxSend >= 16 || MaxRecv >= 16 ? 32 : // NVLS may have an arity > 8. In that case increase the size of the groups
|
||||
MaxSend >= 8 || MaxRecv >= 8 ? 16 :
|
||||
8; // Allows for all roles (WaitRecv/WaitSend/PostRecv/PostSend) within a single warp
|
||||
static_assert(MaxSend <= ThreadPerSync && MaxRecv <= ThreadPerSync, "Not enough threads to cover all peers");
|
||||
|
||||
loadRecvConn(ncclShmem.channel.peers[peer], connIndexRecv, e);
|
||||
loadSendConn(ncclShmem.channel.peers[peer], connIndexSend, e);
|
||||
assert(2*(nrecv+nsend) <= nthreads); // Ensure no thread is assigned more than one role.
|
||||
// Coverity assumes that index will equal tid based on the line below, but it doesn't consider the setting
|
||||
// of flags. This results in multiple false positive overruns being reported here and in all_reduce.h.
|
||||
// Unfortunately, we've been unsuccessful in trying to silence them with a single directive here so
|
||||
// instead it's being done at the callers.
|
||||
// coverity[assignment:FALSE]
|
||||
if (tid < nrecv) { flags |= RoleWaitRecv; index = tid; }
|
||||
// Yes, for some template arguments this code will be unreachable. That's fine.
|
||||
// coverity[dead_error_begin]
|
||||
else if (tid < nrecv+nsend) { flags |= RoleWaitSend; index = tid-nrecv; }
|
||||
else if (nthreads-nsend <= tid) { flags |= RolePostSend; index = tid-(nthreads-nsend); }
|
||||
else if (nthreads-nrecv-nsend <= tid) { flags |= RolePostRecv; index = tid-(nthreads-nrecv-nsend); }
|
||||
|
||||
if (userBufReg) flags |= UserBufferMode;
|
||||
if (flags & (RoleWaitRecv|RolePostRecv)) peer = recvPeers[index];
|
||||
if (flags & (RoleWaitSend|RolePostSend)) peer = sendPeers[index];
|
||||
} else if (mode == primsModePatRs || mode == primsModePatAg) { // Connect to all ranks +/- 2^n
|
||||
flags |= PatMode;
|
||||
accSize = 0;
|
||||
int nranks = ncclShmem.comm.nRanks;
|
||||
int rank = ncclShmem.comm.rank;
|
||||
// A thread is responsible for rank +/- 2 ^ (tid%32). That should be fine as long as rank is a 32-bits integer.
|
||||
index = tid % 32;
|
||||
uint32_t delta = 1 << index;
|
||||
const int roles[4] = { RoleWaitRecv, RoleWaitSend, RolePostSend, RolePostRecv};
|
||||
int block = tid / 32;
|
||||
if (block < 4 && delta < nranks) {
|
||||
int role = roles[block];
|
||||
if (mode == primsModePatRs) {
|
||||
if (role & (RoleWaitRecv|RolePostRecv)) peer = (rank - delta + nranks) % nranks;
|
||||
if (role & (RoleWaitSend|RolePostSend)) peer = (rank + delta) % nranks;
|
||||
} else if (mode == primsModePatAg) {
|
||||
if (role & (RoleWaitSend|RolePostSend)) peer = (rank - delta + nranks) % nranks;
|
||||
if (role & (RoleWaitRecv|RolePostRecv)) peer = (rank + delta) % nranks;
|
||||
}
|
||||
flags |= role;
|
||||
} else if (tid == 128) {
|
||||
flags |= RoleInput | RoleOutput; // Only one will be used depending on the operation
|
||||
}
|
||||
}
|
||||
|
||||
// Coverity thinks that index could be -1 here but that's not actually the case.
|
||||
// coverity[negative_returns:FALSE]
|
||||
if (flags & (RoleWaitRecv|RolePostRecv)) loadRecvConn(ncclShmem.channel.peers[peer], connIndexRecv, e ? e->direct : 0, e ? e->regUsed : ipcReg);
|
||||
// coverity[negative_returns:FALSE]
|
||||
if (flags & (RoleWaitSend|RolePostSend)) loadSendConn(ncclShmem.channel.peers[peer], connIndexSend, e ? e->direct : 0, e ? e->regUsed : ipcReg);
|
||||
|
||||
if (netReg) flags |= NetRegMode;
|
||||
|
||||
// if (barrierAny(flags & NetDeviceUnpack)) {
|
||||
// flags |= AnyNetDeviceUnpack;
|
||||
@@ -711,18 +801,14 @@ private:
|
||||
// }
|
||||
// }
|
||||
|
||||
setDataPtrs(inputBuf, outputBuf, redOpArg, (struct ncclDevWorkCollReg*)e);
|
||||
// coverity[negative_returns:FALSE]
|
||||
setDataPtrs(inputBuf, outputBuf, redOpArg, (struct ncclDevWorkCollReg*)e, (uint8_t)(e ? e->regUsed : ipcReg), peer);
|
||||
}
|
||||
|
||||
__forceinline__ __device__ ~Primitives() {
|
||||
// Ensure ncclShmem.groups[].send/recvConns are available
|
||||
barrier();
|
||||
// Save steps for the next operation
|
||||
if (flags & (RolePostSend|RolePostRecv)) {
|
||||
auto *conns = (flags & RolePostSend) ? ncclShmem.groups[group].sendConns : ncclShmem.groups[group].recvConns;
|
||||
conns[index]->step = step;
|
||||
}
|
||||
if ((flags & UserBufferMode) && (flags & RoleWaitSend)) {
|
||||
if (flags & (RolePostSend|RolePostRecv)) conn->step = step;
|
||||
if ((flags & NetRegMode) && (flags & RoleWaitSend)) {
|
||||
// Make sure we wait until the proxy has sent data before we return.
|
||||
// We don't want the next CUDA kernel to overwrite the send buffer which
|
||||
// was accessed directly.
|
||||
@@ -741,97 +827,111 @@ private:
|
||||
barrier();
|
||||
}
|
||||
|
||||
__device__ void setDataPtrs(void const *inputBuf, void *outputBuf, uint64_t redOpArg, struct ncclDevWorkCollReg* e) {
|
||||
__device__ void setDataPtrs(void const *inputBuf, void *outputBuf, uint64_t redOpArg, struct ncclDevWorkCollReg* work, uint8_t ipcReg, int peer) {
|
||||
if (tid==0) {
|
||||
ncclShmem.groups[group].userInput = (void*)inputBuf;
|
||||
ncclShmem.groups[group].userOutput = (void*)outputBuf;
|
||||
ncclShmem.redOpArgs[0] = redOpArg; // scaler for local input
|
||||
}
|
||||
bool recvProvider = flags == (flags|RoleWaitRecv|DirectWrite);
|
||||
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->coll.regUsed : 0;
|
||||
|
||||
if (Direct && recvProvider) {
|
||||
int spins = 0;
|
||||
void *volatile *slot = ncclShmem.groups[group].recvConns[index]->ptrExchange;
|
||||
// Wait for consumer to consume previous value before trampling it.
|
||||
if (slot) {
|
||||
while ((void *)atomicAdd((unsigned long long *) slot,0) != nullptr && !checkAbort(spins));
|
||||
directBuff = (T*)outputBuf;
|
||||
// Encode pointer by XOR'ing against some address they definitely wouldn't send
|
||||
// since we want to allow them sending us nullptr while not colliding with
|
||||
// the empty slot value.
|
||||
*slot = reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(directBuff) ^ reinterpret_cast<uintptr_t>(slot));
|
||||
}
|
||||
}
|
||||
if (Direct && sendAcceptor) {
|
||||
int spins = 0;
|
||||
void *volatile *slot = ncclShmem.groups[group].sendConns[index]->ptrExchange;
|
||||
void *ptr;
|
||||
while (slot) {
|
||||
ptr = (void *)atomicAdd((unsigned long long *) slot,0);
|
||||
if (ptr != nullptr || checkAbort(spins)) break;
|
||||
}
|
||||
|
||||
if (slot) {
|
||||
directBuff = regUsed ? (T*)(e->dnOutputs[index]) :
|
||||
reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(ptr) ^ reinterpret_cast<uintptr_t>(slot));
|
||||
*slot = nullptr;
|
||||
} else {
|
||||
/* slot is NULL, it must be regUsed == 1 */
|
||||
directBuff = (T*)e->dnOutputs[index];
|
||||
}
|
||||
}
|
||||
if (Direct && sendProvider) {
|
||||
int spins = 0;
|
||||
void *volatile *slot = ncclShmem.groups[group].sendConns[index]->ptrExchange;
|
||||
volatile uint64_t* argSlot0 = ncclShmem.groups[group].sendConns[index]->redOpArgExchange;
|
||||
volatile uint64_t* argSlot1 = ncclShmem.groups[group].sendConns[index]->redOpArgExchange+1;
|
||||
// Wait for consumer to consume previous value before trampling it.
|
||||
if (slot && argSlot0 && argSlot1) {
|
||||
while (((void *)atomicAdd((unsigned long long *) slot,0) != nullptr || *argSlot0 != 0 || *argSlot1 !=0) && !checkAbort(spins));
|
||||
// If there is no recv, then we are directly pulling from input buffer (e.g. directScatter)
|
||||
// Otherwise, we are pulling from output buffer (e.g. recvCopyDirectSend)
|
||||
directBuff = MaxRecv == 0 ? (T*)inputBuf : (T*)outputBuf;
|
||||
// Exchange pre-scalers for use in direct pull
|
||||
*argSlot0 = (uint64_t(1)<<32) | (uint32_t)redOpArg;
|
||||
*argSlot1 = (uint64_t(1)<<32) | (uint32_t)(redOpArg>>32);
|
||||
// Encode pointer by XOR'ing against some address they definitely wouldn't send
|
||||
// since we want to allow them sending us nullptr while not colliding with
|
||||
// the empty slot value.
|
||||
*slot = reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(directBuff) ^ reinterpret_cast<uintptr_t>(slot));
|
||||
}
|
||||
}
|
||||
if (Direct && recvAcceptor) {
|
||||
int spins = 0;
|
||||
void *volatile *slot = ncclShmem.groups[group].recvConns[index]->ptrExchange;
|
||||
volatile uint64_t* argSlot0 = ncclShmem.groups[group].recvConns[index]->redOpArgExchange;
|
||||
volatile uint64_t* argSlot1 = ncclShmem.groups[group].recvConns[index]->redOpArgExchange+1;
|
||||
void *ptr;
|
||||
while (slot) {
|
||||
ptr = (void *)atomicAdd((unsigned long long *) slot,0);
|
||||
if (ptr != nullptr || checkAbort(spins)) break;
|
||||
}
|
||||
|
||||
if (slot && argSlot0 && argSlot1) {
|
||||
directBuff = regUsed ? (T*)(MaxSend == 0 ? e->upOutputs[index] : e->dnInputs[index]) :
|
||||
reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(ptr) ^ reinterpret_cast<uintptr_t>(slot));
|
||||
if (MaxSend != 0) { // reduce group rather than gather group
|
||||
// Store scalers for remote inputs
|
||||
uint64_t arg0, arg1;
|
||||
while (true) {
|
||||
arg0 = *argSlot0;
|
||||
arg1 = *argSlot1;
|
||||
if ((arg0 != 0 && arg1 != 0) || checkAbort(spins)) break;
|
||||
if (Direct && ipcReg) {
|
||||
bool recvProvider = (flags & RoleWaitRecv) && (flags & DirectWrite || flags & IpcWrite);
|
||||
bool sendAcceptor = (flags & RoleWaitSend) && (flags & DirectWrite || flags & IpcWrite || flags & NvlsDirectWrite);
|
||||
bool sendProvider = (flags & RoleWaitSend) && (flags & DirectRead || flags & IpcRead); // sender provides direct buffer (to be fetched)
|
||||
bool recvAcceptor = (flags & RoleWaitRecv) && (flags & DirectRead || flags & IpcRead || flags & NvlsDirectRead); // receiver accepts direct buffer
|
||||
if (recvProvider) {
|
||||
int spins = 0;
|
||||
void* volatile* slot = ncclShmem.groups[group].recvConns[index]->ptrExchange;
|
||||
// Wait for consumer to consume previous value before trampling it.
|
||||
if (slot) {
|
||||
T* exchgPtr;
|
||||
directBuff = (T*)outputBuf;
|
||||
while ((void *)atomicAdd((unsigned long long *) slot,0) != nullptr && !checkAbort(spins));
|
||||
if (P2p) {
|
||||
exchgPtr = (T*)outputBuf;
|
||||
} else {
|
||||
int localPeer = ncclShmem.comm.rankToLocalRank[peer];
|
||||
exchgPtr = (T*)(work->coll.recvbuffOffset + work->coll.recvbuffRmtAddrs[localPeer]);
|
||||
}
|
||||
ncclShmem.redOpArgs[1 + index] = ((arg1 & 0xffffffff) << 32) | (arg0 & 0xffffffff);
|
||||
*slot = reinterpret_cast<void*>(exchgPtr);
|
||||
}
|
||||
}
|
||||
if (sendAcceptor) {
|
||||
int spins = 0;
|
||||
void* volatile* slot = ncclShmem.groups[group].sendConns[index]->ptrExchange;
|
||||
void* ptr;
|
||||
while (slot) {
|
||||
ptr = (void *)atomicAdd((unsigned long long *) slot,0);
|
||||
if (ptr != nullptr || checkAbort(spins)) break;
|
||||
}
|
||||
|
||||
if (slot) {
|
||||
directBuff = reinterpret_cast<T*>(ptr);
|
||||
*slot = nullptr;
|
||||
} else {
|
||||
directBuff = (T*)work->dnOutputs[index];
|
||||
}
|
||||
}
|
||||
if (sendProvider) {
|
||||
int spins = 0;
|
||||
void* volatile* slot = ncclShmem.groups[group].sendConns[index]->ptrExchange;
|
||||
volatile uint64_t* argSlot0 = ncclShmem.groups[group].sendConns[index]->redOpArgExchange;
|
||||
volatile uint64_t* argSlot1 = ncclShmem.groups[group].sendConns[index]->redOpArgExchange + 1;
|
||||
// Wait for consumer to consume previous value before trampling it.
|
||||
if (slot && argSlot0 && argSlot1) {
|
||||
T* exchgPtr;
|
||||
while (((void *)atomicAdd((unsigned long long *) slot,0) != nullptr || *argSlot0 != 0 || *argSlot1 != 0) && !checkAbort(spins));
|
||||
// If there is no recv, then we are directly pulling from input buffer (e.g. directScatter)
|
||||
// Otherwise, we are pulling from output buffer (e.g. recvCopyDirectSend)
|
||||
directBuff = MaxRecv == 0 ? (T*)inputBuf : (T*)outputBuf;
|
||||
if (P2p) {
|
||||
exchgPtr = MaxRecv == 0 ? (T*)inputBuf : (T*)outputBuf;
|
||||
} else {
|
||||
int localPeer = ncclShmem.comm.rankToLocalRank[peer];
|
||||
if (MaxRecv == 0)
|
||||
exchgPtr = (T*)(work->coll.sendbuffOffset + work->coll.sendbuffRmtAddrs[localPeer]);
|
||||
else
|
||||
exchgPtr = (T*)(work->coll.recvbuffOffset + work->coll.recvbuffRmtAddrs[localPeer]);
|
||||
}
|
||||
|
||||
// Exchange pre-scalers for use in direct pull
|
||||
*argSlot0 = (uint64_t(1) << 32) | (uint32_t)redOpArg;
|
||||
*argSlot1 = (uint64_t(1) << 32) | (uint32_t)(redOpArg >> 32);
|
||||
*slot = reinterpret_cast<T*>(exchgPtr);
|
||||
}
|
||||
}
|
||||
if (recvAcceptor) {
|
||||
int spins = 0;
|
||||
void* volatile* slot = ncclShmem.groups[group].recvConns[index]->ptrExchange;
|
||||
volatile uint64_t* argSlot0 = ncclShmem.groups[group].recvConns[index]->redOpArgExchange;
|
||||
volatile uint64_t* argSlot1 = ncclShmem.groups[group].recvConns[index]->redOpArgExchange + 1;
|
||||
void* ptr;
|
||||
while (slot) {
|
||||
ptr = (void *)atomicAdd((unsigned long long *) slot,0);
|
||||
if (ptr != nullptr || checkAbort(spins)) break;
|
||||
}
|
||||
|
||||
if (slot && argSlot0 && argSlot1) {
|
||||
directBuff = reinterpret_cast<T*>(ptr);
|
||||
if (MaxSend != 0) { // reduce group rather than gather group
|
||||
// Store scalers for remote inputs
|
||||
uint64_t arg0, arg1;
|
||||
while (true) {
|
||||
arg0 = *argSlot0;
|
||||
arg1 = *argSlot1;
|
||||
if ((arg0 != 0 && arg1 != 0) || checkAbort(spins)) break;
|
||||
}
|
||||
ncclShmem.redOpArgs[1 + index] = ((arg1 & 0xffffffff) << 32) | (arg0 & 0xffffffff);
|
||||
}
|
||||
*argSlot0 = 0; *argSlot1 = 0;
|
||||
*slot = nullptr;
|
||||
} else {
|
||||
// Coverity complains about work being possibly NULL below. However, slot
|
||||
// being NULL means that the NVLS buffer is registered (regUsed == 1)
|
||||
// so work can't be NULL in this code path.
|
||||
// coverity[var_deref_op]
|
||||
directBuff = (T*)work->dnInputs[index];
|
||||
}
|
||||
*argSlot0 = 0; *argSlot1 = 0;
|
||||
*slot = nullptr;
|
||||
} else {
|
||||
directBuff = (T*)e->dnInputs[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -867,8 +967,8 @@ private:
|
||||
__device__ __forceinline__ void recv(intptr_t outIx, int eltN, bool postOp=false) {
|
||||
genericOp<0, 0, 1, 0, -1, Output>(-1, outIx, eltN, postOp);
|
||||
}
|
||||
__device__ __forceinline__ void directRecv(intptr_t outIx, int eltN) {
|
||||
genericOp<1, 0, 1, 0, -1, Output>(-1, outIx, eltN, /*postOp=*/false);
|
||||
__device__ __forceinline__ void directRecv(intptr_t inpIx, intptr_t outIx, int eltN, bool postOp=false) {
|
||||
genericOp<1, 0, 1, 0, -1, Output>(inpIx, outIx, eltN, postOp);
|
||||
}
|
||||
__device__ __forceinline__ void directRecvCopy(intptr_t inpIx, intptr_t outIx, int eltN) {
|
||||
genericOp<1, 0, 1, 0, -1, Output>(inpIx, outIx, eltN, /*postOp=*/false);
|
||||
@@ -887,8 +987,8 @@ private:
|
||||
__device__ __forceinline__ void recvCopySend(intptr_t outIx, int eltN, bool postOp=false) {
|
||||
genericOp<0, 0, 1, 1, -1, Output>(-1, outIx, eltN, postOp);
|
||||
}
|
||||
__device__ __forceinline__ void directRecvCopySend(intptr_t outIx, int eltN) {
|
||||
genericOp<1, 1, 1, 1, -1, Output>(-1, outIx, eltN, false);
|
||||
__device__ __forceinline__ void directRecvCopyDirectSend(intptr_t outIx, int eltN, bool postOp=false) {
|
||||
genericOp<1, 1, 1, 1, -1, Output>(-1, outIx, eltN, postOp);
|
||||
}
|
||||
__device__ __forceinline__ void directRecvDirectSend(intptr_t inpIx, intptr_t outIx, int eltN) {
|
||||
genericOp<1, 1, 1, 1, -1, -1>(inpIx, outIx, eltN, false);
|
||||
@@ -900,6 +1000,9 @@ private:
|
||||
__device__ __forceinline__ void recvReduceCopy(intptr_t inpIx, intptr_t outIx, int eltN, bool postOp=false) {
|
||||
genericOp<0, 0, 1, 0, Input, Output>(inpIx, outIx, eltN, postOp);
|
||||
}
|
||||
__device__ __forceinline__ void directRecvReduceCopy(intptr_t inpIx, intptr_t outIx, int eltN, bool postOp=false) {
|
||||
genericOp<1, 0, 1, 0, Input, Output>(inpIx, outIx, eltN, postOp);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void recvReduceSend(intptr_t inpIx, int eltN, bool postOp=false) {
|
||||
genericOp<0, 0, 1, 1, Input, -1>(inpIx, -1, eltN, postOp);
|
||||
@@ -907,14 +1010,20 @@ private:
|
||||
__device__ __forceinline__ void directRecvReduceSend(intptr_t inpIx, int eltN, bool postOp=false) {
|
||||
genericOp<1, 0, 1, 1, Input, -1>(inpIx, -1, eltN, postOp);
|
||||
}
|
||||
__device__ __forceinline__ void directRecvReduceDirectSend(intptr_t inpIx, intptr_t outIx, ssize_t eltN, bool postOp=false) {
|
||||
genericOp<1, 1, 1, 1, Input, -1>(inpIx, outIx, eltN, postOp);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void recvReduceCopySend(intptr_t inpIx, intptr_t outIx, int eltN, bool postOp=false) {
|
||||
genericOp<0, 0, 1, 1, Input, Output>(inpIx, outIx, eltN, postOp);
|
||||
}
|
||||
__device__ __forceinline__ void directRecvReduceCopySend(intptr_t inpIx, intptr_t outIx, int eltN, bool postOp=false) {
|
||||
__device__ __forceinline__ void recvReduceCopyDirectSend(intptr_t inpIx, intptr_t outIx, int eltN, bool postOp=false) {
|
||||
// Direct is only for the send part
|
||||
genericOp<0, 1, 1, 1, Input, Output>(inpIx, outIx, eltN, postOp);
|
||||
}
|
||||
__device__ __forceinline__ void directRecvReduceCopyDirectSend(intptr_t inpIx, intptr_t outIx, ssize_t eltN, bool postOp=false) {
|
||||
genericOp<1, 1, 1, 1, Input, Output>(inpIx, outIx, eltN, postOp);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void
|
||||
scatter(intptr_t inpIx, ssize_t totalElem, int peerElem, ssize_t peerOffset, int skip, int shift) {
|
||||
@@ -934,6 +1043,127 @@ private:
|
||||
ScatterGatherOp<1, 0, 1, 0>(-1, outIx, totalElem, peerElem, peerOffset, skip, shift, /*postOp=*/false);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void patReduce(int recvPow2, int sendPow2, intptr_t inpIx, intptr_t outIx, int recvOffset, int sendOffset, int sendStepOffset, int nelem, int postRecv, int postSend) {
|
||||
nelem = nelem < 0 ? 0 : nelem;
|
||||
T* userInput = (T*)ncclShmem.groups[group].userInput;
|
||||
T* userOutput = (T*)ncclShmem.groups[group].userOutput;
|
||||
|
||||
if (recvPow2 >= 0 && recvPow2 == index && (flags & RoleWaitRecv)) {
|
||||
ncclShmem.groups[group].srcs[0] = (T*)(connEltsFifo + (step%NCCL_STEPS)*connStepSize) + recvOffset;
|
||||
int spins = 0;
|
||||
while (connStepCache < step + StepPerSlice) {
|
||||
connStepCache = loadStepValue(connStepPtr);
|
||||
if (checkAbort(spins)) break;
|
||||
}
|
||||
if (postRecv) step += StepPerSlice;
|
||||
}
|
||||
if (sendPow2 >= 0 && sendPow2 == index && (flags & RoleWaitSend)) {
|
||||
int spins = 0;
|
||||
while (connStepCache + NCCL_STEPS < step + sendStepOffset + StepPerSlice) {
|
||||
connStepCache = loadStepValue(connStepPtr);
|
||||
if (checkAbort(spins)) break;
|
||||
}
|
||||
ncclShmem.groups[group].dsts[0] = (T*)(connEltsFifo + ((step+sendStepOffset)%NCCL_STEPS)*connStepSize) + sendOffset;
|
||||
if (accSize < sendOffset + nelem + (step+sendStepOffset)*connStepSize) {
|
||||
// New data, add our own data to it.
|
||||
ncclShmem.groups[group].srcs[1] = userInput + inpIx;
|
||||
accSize = sendOffset + nelem + (step+sendStepOffset)*connStepSize;
|
||||
if (flags & ConnFifoEnabled)
|
||||
connFifo[(step+sendStepOffset)%NCCL_STEPS].size = (sendOffset + nelem)*sizeof(T);
|
||||
} else {
|
||||
// There is already data in there, accumulate instead of writing to it.
|
||||
ncclShmem.groups[group].srcs[1] = ncclShmem.groups[group].dsts[0];
|
||||
}
|
||||
if (postSend) step += StepPerSlice;
|
||||
}
|
||||
if (sendPow2 < 0 && (flags & RoleOutput)) { // Destination is our own local buffer
|
||||
ncclShmem.groups[group].dsts[0] = userOutput + outIx;
|
||||
if (accSize < outIx + nelem) {
|
||||
// New data, add our own data to it.
|
||||
ncclShmem.groups[group].srcs[1] = userInput + inpIx;
|
||||
accSize = outIx + nelem;
|
||||
} else {
|
||||
// There is already data in there, accumulate instead of writing to it.
|
||||
ncclShmem.groups[group].srcs[1] = ncclShmem.groups[group].dsts[0];
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
int nSrcs = 2;
|
||||
void** srcs = ncclShmem.groups[group].srcs;
|
||||
if (recvPow2 < 0) { srcs++; nSrcs--; } // No peer to receive from, remove one source
|
||||
|
||||
int workSize = ncclShmem.aborted ? 0 : nelem;
|
||||
|
||||
reduceCopy<Unroll, RedOp, T, 0, 1, 2, 0, 1, 1, /*PreOpSrcs*/0>
|
||||
(tid, nthreads, ncclShmem.redOpArgs[0], nullptr, /*postOp=*/false,
|
||||
nSrcs, srcs, 1, ncclShmem.groups[group].dsts, workSize);
|
||||
|
||||
barrier();
|
||||
if (postRecv && recvPow2 >= 0 && recvPow2 == index && (flags & RolePostRecv)) postPeer<1, 0>(0 < nelem);
|
||||
if (postSend && sendPow2 >= 0 && sendPow2 == index && (flags & RolePostSend)) postPeer<0, 1>(0 < nelem);
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void patCopy(int recvPow2, int sendPow2, intptr_t inpIx, intptr_t outIx, int recvOffset, int sendOffset, int recvStepOffset, int nelem, int postRecv, int postSend) {
|
||||
nelem = nelem < 0 ? 0 : nelem;
|
||||
T* userInput = (T*)ncclShmem.groups[group].userInput;
|
||||
T* userOutput = (T*)ncclShmem.groups[group].userOutput;
|
||||
|
||||
if (recvPow2 >= 0 && recvPow2 == index && (flags & RoleWaitRecv)) {
|
||||
ncclShmem.groups[group].srcs[0] = (T*)(connEltsFifo + ((step+recvStepOffset)%NCCL_STEPS)*connStepSize) + recvOffset;
|
||||
int spins = 0;
|
||||
while (connStepCache < step + recvStepOffset + StepPerSlice) {
|
||||
connStepCache = loadStepValue(connStepPtr);
|
||||
if (checkAbort(spins)) break;
|
||||
}
|
||||
if (accSize < recvOffset + nelem + (step+recvStepOffset)*connStepSize) {
|
||||
// New data, copy to our output buffer.
|
||||
ncclShmem.groups[group].dsts[1] = userOutput + outIx;
|
||||
accSize = recvOffset + nelem + (step+recvStepOffset)*connStepSize;
|
||||
} else {
|
||||
ncclShmem.groups[group].dsts[1] = ncclShmem.groups[group].srcs[0]; // Already done
|
||||
}
|
||||
if (postRecv) step += StepPerSlice;
|
||||
}
|
||||
if (sendPow2 >= 0 && sendPow2 == index && (flags & RoleWaitSend)) {
|
||||
int spins = 0;
|
||||
while (connStepCache + NCCL_STEPS < step + StepPerSlice) {
|
||||
connStepCache = loadStepValue(connStepPtr);
|
||||
if (checkAbort(spins)) break;
|
||||
}
|
||||
ncclShmem.groups[group].dsts[0] = (T*)(connEltsFifo + (step%NCCL_STEPS)*connStepSize) + sendOffset;
|
||||
if (postSend) {
|
||||
if (flags & ConnFifoEnabled)
|
||||
connFifo[step%NCCL_STEPS].size = (sendOffset + nelem)*sizeof(T);
|
||||
step += StepPerSlice;
|
||||
}
|
||||
}
|
||||
if (recvPow2 < 0 && (flags & RoleInput)) { // Source is our own local buffer
|
||||
ncclShmem.groups[group].srcs[0] = userInput + inpIx;
|
||||
if (accSize < inpIx + nelem) {
|
||||
// New data, copy to our output buffer.
|
||||
ncclShmem.groups[group].dsts[1] = userOutput + outIx;
|
||||
accSize = inpIx + nelem;
|
||||
} else {
|
||||
ncclShmem.groups[group].dsts[1] = ncclShmem.groups[group].srcs[0]; // Already done
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
int nDsts = 2;
|
||||
void** dsts = ncclShmem.groups[group].dsts;
|
||||
if (sendPow2 < 0) { dsts++; nDsts--; } // No peer to send to, remove one dest
|
||||
if (ncclShmem.groups[group].srcs[0] == ncclShmem.groups[group].dsts[1]) nDsts--; // In-place or already done.
|
||||
|
||||
int workSize = ncclShmem.aborted ? 0 : nelem;
|
||||
|
||||
reduceCopy<Unroll, RedOp, T, 0, 1, 1, 0, 1, 2, /*PreOpSrcs*/0>
|
||||
(tid, nthreads, ncclShmem.redOpArgs[0], nullptr, /*postOp=*/false,
|
||||
1, ncclShmem.groups[group].srcs, nDsts, dsts, workSize);
|
||||
|
||||
barrier();
|
||||
if (postRecv && recvPow2 >= 0 && recvPow2 == index && (flags & RolePostRecv)) postPeer<1, 0>(0 < nelem);
|
||||
if (postSend && sendPow2 >= 0 && sendPow2 == index && (flags & RolePostSend)) postPeer<0, 1>(0 < nelem);
|
||||
}
|
||||
|
||||
// MSCCL primitives
|
||||
__device__ __forceinline__ void sendWithBarrier(intptr_t inpIx, int eltN) {
|
||||
send(inpIx, eltN);
|
||||
@@ -941,4 +1171,4 @@ private:
|
||||
__device__ __forceinline__ void localCopy(T* srcs, T* dsts, int eltN) {
|
||||
return mscclGenericOp<0,1,0,0>(&srcs, 1, &dsts, 1, eltN);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -28,6 +28,9 @@ namespace {
|
||||
size_t offset;
|
||||
int nelem;
|
||||
|
||||
// Coverity reports that the callee treats &ring->next as an array. However, due to the use of
|
||||
// FanSymmetric<1>, only the first element is ever accessed, so it's fine.
|
||||
// coverity[callee_ptr_arith:FALSE]
|
||||
Primitives<T, RedOp, FanSymmetric<1>, 0, Proto, 0>
|
||||
prims(tid, nthreads, &ring->prev, &ring->next, work->sendbuff, work->recvbuff, work->redOpArg, 0, work->connIndex, work->connIndex);
|
||||
|
||||
|
||||
@@ -244,10 +244,10 @@ struct Apply_Reduce<FuncMinMax<uint8_t>, /*EltPerPack=*/4> {
|
||||
// uint32_t a = apack.native;
|
||||
// uint32_t b = bpack.native;
|
||||
// uint32_t ab0 = (a*b) & 0xffu;
|
||||
// asm("mad.lo.u32 %0, %1, %2, %0;" : "+r"(ab0) : "r"(a&0xff00u), "r"(b&0xff00u));
|
||||
// asm volatile("mad.lo.u32 %0, %1, %2, %0;" : "+r"(ab0) : "r"(a&0xff00u), "r"(b&0xff00u));
|
||||
// uint32_t ab1;
|
||||
// asm("mul.hi.u32 %0, %1, %2;" : "=r"(ab1) : "r"(a&0xff0000), "r"(b&0xff0000));
|
||||
// asm("mad.hi.u32 %0, %1, %2, %0;" : "+r"(ab1) : "r"(a&0xff000000u), "r"(b&0xff000000u));
|
||||
// asm volatile("mul.hi.u32 %0, %1, %2;" : "=r"(ab1) : "r"(a&0xff0000), "r"(b&0xff0000));
|
||||
// asm volatile("mad.hi.u32 %0, %1, %2, %0;" : "+r"(ab1) : "r"(a&0xff000000u), "r"(b&0xff000000u));
|
||||
// apack.native = __byte_perm(ab0, ab1, 0x6420);
|
||||
// return apack;
|
||||
// }
|
||||
@@ -273,10 +273,13 @@ SPECIALIZE_REDUCE(FuncMinMax, half, 1, half, fn.isMinNotMax ? __hmin(x, y) : __h
|
||||
#if defined(RCCL_BFLOAT16)
|
||||
#if __CUDA_ARCH__ >= 800
|
||||
SPECIALIZE_REDUCE(FuncSum, __nv_bfloat16, 1, __nv_bfloat16, __hadd(x, y))
|
||||
// coverity[copy_constructor_call]
|
||||
SPECIALIZE_REDUCE(FuncSum, __nv_bfloat16, 2, __nv_bfloat162, __hadd2(x, y))
|
||||
SPECIALIZE_REDUCE(FuncProd, __nv_bfloat16, 1, __nv_bfloat16, __hmul(x, y))
|
||||
// coverity[copy_constructor_call]
|
||||
SPECIALIZE_REDUCE(FuncProd, __nv_bfloat16, 2, __nv_bfloat162, __hmul2(x, y))
|
||||
SPECIALIZE_REDUCE(FuncMinMax, __nv_bfloat16, 1, __nv_bfloat16, fn.isMinNotMax ? __hmin(x, y) : __hmax(x, y))
|
||||
// coverity[copy_constructor_call]
|
||||
SPECIALIZE_REDUCE(FuncMinMax, __nv_bfloat16, 2, __nv_bfloat162, fn.isMinNotMax ? __hmin2(x, y) : __hmax2(x, y))
|
||||
#else
|
||||
SPECIALIZE_REDUCE(FuncSum, hip_bfloat16, 1, hip_bfloat16, (hip_bfloat16)((float)(x) + (float)(y)))
|
||||
@@ -406,6 +409,9 @@ struct FuncPreMulSum {
|
||||
};
|
||||
|
||||
template<>
|
||||
// Coverity recommends the users of this type to use std::move in certain cases but,
|
||||
// given that half is a scalar, a plain copy will be just as efficient.
|
||||
// coverity[moveable_type]
|
||||
struct FuncPreMulSum<half> {
|
||||
using EltType = half;
|
||||
half2 scalar;
|
||||
@@ -419,6 +425,9 @@ struct FuncPreMulSum<half> {
|
||||
|
||||
#if defined(RCCL_BFLOAT16)
|
||||
template<>
|
||||
// Coverity recommends the users of this type to use std::move in certain cases but,
|
||||
// given that __nv_bfloat16 is a scalar, a plain copy will be just as efficient.
|
||||
// coverity[moveable_type]
|
||||
struct FuncPreMulSum<hip_bfloat16> {
|
||||
using EltType = hip_bfloat16;
|
||||
#if __CUDA_ARCH__ >= 800
|
||||
@@ -631,9 +640,9 @@ struct Apply_PostOp<FuncSumPostDiv<T>, /*EltPerPack=*/1> {
|
||||
static constexpr int PackSize = SIZEOF_BytePack_field_##pack_field; \
|
||||
__device__ static BytePack<PackSize> load(FuncSum<T> fn, uintptr_t addr) { \
|
||||
BytePack<PackSize> ans; \
|
||||
asm("multimem.ld_reduce.relaxed.sys.global.add." #ptx_ty " %0, [%1];" \
|
||||
asm volatile("multimem.ld_reduce.relaxed.sys.global.add." #ptx_ty " %0, [%1];" \
|
||||
: "=" PTX_REG_BytePack_field_##pack_field(ans.pack_field) \
|
||||
: "l"(addr)); \
|
||||
: "l"(addr) : "memory"); \
|
||||
return ans; \
|
||||
} \
|
||||
};
|
||||
@@ -644,13 +653,13 @@ struct Apply_PostOp<FuncSumPostDiv<T>, /*EltPerPack=*/1> {
|
||||
__device__ static BytePack<PackSize> load(FuncMinMax<T> fn, uintptr_t addr) { \
|
||||
BytePack<PackSize> ans; \
|
||||
if (fn.isMinNotMax) { \
|
||||
asm("multimem.ld_reduce.relaxed.sys.global.min." #ptx_ty " %0, [%1];" \
|
||||
asm volatile("multimem.ld_reduce.relaxed.sys.global.min." #ptx_ty " %0, [%1];" \
|
||||
: "=" PTX_REG_BytePack_field_##pack_field(ans.pack_field) \
|
||||
: "l"(addr)); \
|
||||
: "l"(addr) : "memory"); \
|
||||
} else { \
|
||||
asm("multimem.ld_reduce.relaxed.sys.global.max." #ptx_ty " %0, [%1];" \
|
||||
asm volatile("multimem.ld_reduce.relaxed.sys.global.max." #ptx_ty " %0, [%1];" \
|
||||
: "=" PTX_REG_BytePack_field_##pack_field(ans.pack_field) \
|
||||
: "l"(addr)); \
|
||||
: "l"(addr) : "memory"); \
|
||||
} \
|
||||
return ans; \
|
||||
} \
|
||||
@@ -662,12 +671,12 @@ struct Apply_PostOp<FuncSumPostDiv<T>, /*EltPerPack=*/1> {
|
||||
static constexpr int PackSize = 4*(SIZEOF_BytePack_field_##pack_field); \
|
||||
__device__ static BytePack<PackSize> load(FuncSum<T> fn, uintptr_t addr) { \
|
||||
BytePack<PackSize> ans; \
|
||||
asm("multimem.ld_reduce.relaxed.sys.global.add.v4." #ptx_ty " {%0,%1,%2,%3}, [%4];" \
|
||||
asm volatile("multimem.ld_reduce.relaxed.sys.global.add.v4." #ptx_ty " {%0,%1,%2,%3}, [%4];" \
|
||||
: "=" PTX_REG_BytePack_field_##pack_field(ans.pack_field[0]), \
|
||||
"=" PTX_REG_BytePack_field_##pack_field(ans.pack_field[1]), \
|
||||
"=" PTX_REG_BytePack_field_##pack_field(ans.pack_field[2]), \
|
||||
"=" PTX_REG_BytePack_field_##pack_field(ans.pack_field[3]) \
|
||||
: "l"(addr)); \
|
||||
: "l"(addr) : "memory"); \
|
||||
return ans; \
|
||||
} \
|
||||
};
|
||||
@@ -678,19 +687,19 @@ struct Apply_PostOp<FuncSumPostDiv<T>, /*EltPerPack=*/1> {
|
||||
__device__ static BytePack<PackSize> load(FuncMinMax<T> fn, uintptr_t addr) { \
|
||||
BytePack<PackSize> ans; \
|
||||
if (fn.isMinNotMax) { \
|
||||
asm("multimem.ld_reduce.relaxed.sys.global.min.v4." #ptx_ty " {%0,%1,%2,%3}, [%4];" \
|
||||
asm volatile("multimem.ld_reduce.relaxed.sys.global.min.v4." #ptx_ty " {%0,%1,%2,%3}, [%4];" \
|
||||
: "=" PTX_REG_BytePack_field_##pack_field(ans.pack_field[0]), \
|
||||
"=" PTX_REG_BytePack_field_##pack_field(ans.pack_field[1]), \
|
||||
"=" PTX_REG_BytePack_field_##pack_field(ans.pack_field[2]), \
|
||||
"=" PTX_REG_BytePack_field_##pack_field(ans.pack_field[3]) \
|
||||
: "l"(addr)); \
|
||||
: "l"(addr) : "memory"); \
|
||||
} else { \
|
||||
asm("multimem.ld_reduce.relaxed.sys.global.max.v4." #ptx_ty " {%0,%1,%2,%3}, [%4];" \
|
||||
asm volatile("multimem.ld_reduce.relaxed.sys.global.max.v4." #ptx_ty " {%0,%1,%2,%3}, [%4];" \
|
||||
: "=" PTX_REG_BytePack_field_##pack_field(ans.pack_field[0]), \
|
||||
"=" PTX_REG_BytePack_field_##pack_field(ans.pack_field[1]), \
|
||||
"=" PTX_REG_BytePack_field_##pack_field(ans.pack_field[2]), \
|
||||
"=" PTX_REG_BytePack_field_##pack_field(ans.pack_field[3]) \
|
||||
: "l"(addr)); \
|
||||
: "l"(addr) : "memory"); \
|
||||
} \
|
||||
return ans; \
|
||||
} \
|
||||
@@ -702,9 +711,9 @@ struct Apply_PostOp<FuncSumPostDiv<T>, /*EltPerPack=*/1> {
|
||||
struct Apply_LoadMultimem<FuncSum<T>, sizeof(T)> { \
|
||||
__device__ static BytePack<sizeof(T)> load(FuncSum<T> fn, uintptr_t addr) { \
|
||||
BytePack<2*sizeof(T)> tmp; \
|
||||
asm("multimem.ld_reduce.relaxed.sys.global.add." #ptx_ty " %0, [%1];" \
|
||||
asm volatile("multimem.ld_reduce.relaxed.sys.global.add." #ptx_ty " %0, [%1];" \
|
||||
: "=" PTX_REG_BytePack_field_##pack_field(tmp.pack_field) \
|
||||
: "l"(addr & -uintptr_t(2*sizeof(T)))); \
|
||||
: "l"(addr & -uintptr_t(2*sizeof(T))) : "memory"); \
|
||||
return tmp.half[(addr/sizeof(T))%2]; \
|
||||
} \
|
||||
};
|
||||
@@ -715,13 +724,13 @@ struct Apply_PostOp<FuncSumPostDiv<T>, /*EltPerPack=*/1> {
|
||||
__device__ static BytePack<sizeof(T)> load(FuncMinMax<T> fn, uintptr_t addr) { \
|
||||
BytePack<2*sizeof(T)> tmp; \
|
||||
if (fn.isMinNotMax) { \
|
||||
asm("multimem.ld_reduce.relaxed.sys.global.min." #ptx_ty " %0, [%1];" \
|
||||
asm volatile("multimem.ld_reduce.relaxed.sys.global.min." #ptx_ty " %0, [%1];" \
|
||||
: "=" PTX_REG_BytePack_field_##pack_field(tmp.pack_field) \
|
||||
: "l"(addr & -uintptr_t(2*sizeof(T)))); \
|
||||
: "l"(addr & -uintptr_t(2*sizeof(T))) : "memory"); \
|
||||
} else { \
|
||||
asm("multimem.ld_reduce.relaxed.sys.global.max." #ptx_ty " %0, [%1];" \
|
||||
asm volatile("multimem.ld_reduce.relaxed.sys.global.max." #ptx_ty " %0, [%1];" \
|
||||
: "=" PTX_REG_BytePack_field_##pack_field(tmp.pack_field) \
|
||||
: "l"(addr & -uintptr_t(2*sizeof(T)))); \
|
||||
: "l"(addr & -uintptr_t(2*sizeof(T))) : "memory"); \
|
||||
} \
|
||||
return tmp.half[(addr/sizeof(T))%2]; \
|
||||
} \
|
||||
|
||||
@@ -53,7 +53,9 @@ namespace {
|
||||
ncclShmem.comm.npKitEventCollectContexts + npKitCtxIdx);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Coverity reports that the callee treats &ring->next as an array. However, due to the use of
|
||||
// FanSymmetric<1>, only the first element is ever accessed, so it's fine.
|
||||
// coverity[callee_ptr_arith:FALSE]
|
||||
Primitives<T, RedOp, FanSymmetric<1>, 0, Proto, 0>
|
||||
prims(tid, nthreads, &ring->prev, &ring->next, work->sendbuff, work->recvbuff, work->redOpArg, 0, work->connIndex, work->connIndex);
|
||||
|
||||
@@ -151,6 +153,32 @@ struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_L
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_PAT, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int nthreads, struct ncclDevWorkColl* work) {
|
||||
using Proto = ProtoSimple<1, 1>;
|
||||
const int nranks = ncclShmem.comm.nRanks;
|
||||
const int rank = ncclShmem.comm.rank;
|
||||
size_t count, channelOffset, channelCount, chunkCount;
|
||||
ncclCollCbdPart(work, ncclShmem.channelId, Proto::Id, sizeof(T), &count, &channelOffset, &channelCount, &chunkCount);
|
||||
|
||||
T *inputBuf = (T*)work->sendbuff;
|
||||
T *outputBuf = (T*)work->recvbuff;
|
||||
Primitives<T, RedOp, FanSymmetric<1>, 0, Proto, 0> prims
|
||||
(tid, nthreads, NULL, NULL, inputBuf, outputBuf, work->redOpArg, 0*Proto::MaxGroupWidth, 0, 0, nullptr, false, false, 0, primsModePatRs);
|
||||
|
||||
PatRSAlgorithm<T> patAlgo(chunkCount*sizeof(T), NCCL_STEPS, channelOffset, channelOffset + channelCount, count, chunkCount, rank, nranks);
|
||||
int last = 0;
|
||||
while (!last) {
|
||||
int recvDim, sendDim, recvOffset, sendOffset, sendStepOffset, postRecv, postSend, nelem;
|
||||
size_t inpIx, outIx;
|
||||
patAlgo.getNextOp(recvDim, sendDim, inpIx, outIx, recvOffset, sendOffset, sendStepOffset, nelem, postRecv, postSend, last);
|
||||
prims.patReduce(recvDim, sendDim, inpIx, outIx, recvOffset, sendOffset, sendStepOffset, nelem, postRecv, postSend);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename T, typename RedOp>
|
||||
struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_SIMPLE> {
|
||||
__device__ __forceinline__ void run(int tid, int/*nthreads*/, struct ncclDevWorkColl* work) {
|
||||
@@ -220,6 +248,9 @@ struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_NVLS, NCCL_PROTO_S
|
||||
size_t outOffset = gridOffset + elemOffset;
|
||||
size_t inpOffset = outOffset + rank * count;
|
||||
nelem = min(chunkCount, channelCount - elemOffset);
|
||||
// Coverity complains about a possible overrun inside the method invoked below, but that's actually
|
||||
// a false positive.
|
||||
// coverity[overrun-call:FALSE]
|
||||
prims.directRecvCopy(inpOffset, outOffset, nelem);
|
||||
}
|
||||
|
||||
@@ -241,7 +272,7 @@ struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NC
|
||||
template<int SlicePerChunk, int MinSrcs, int MaxSrcs, int MinDsts, int MaxDsts>
|
||||
__device__ __forceinline__ void operator()(
|
||||
int tid, int tn, int slice, int maxSliceSize,
|
||||
int nSrcs, void** srcPtrs, int nDsts, void** dstPtrs, int32_t* dstSizes
|
||||
int nSrcs, void** srcPtrs, int nDsts, void** dstPtrs, int32_t* dstSizes, uint32_t sendDirectFlag, uint32_t recvDirectFlag
|
||||
) {
|
||||
static_assert(SlicePerChunk==1, "require: SlicePerChunk==1");
|
||||
static_assert(MaxDsts<=1 || MaxSrcs<=1, "require: MaxDsts<=1 || MaxSrcs<=1");
|
||||
@@ -276,19 +307,23 @@ struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NC
|
||||
int delta = min(railAllEnd, railOneEnd) - (railAllBeg+railAllOffset);
|
||||
int rank = ncclShmem.comm.collNetDenseToUserRank[node*nRails + rail];
|
||||
ssize_t userOneBeg = rank*sizePerRank + railOneOffset;
|
||||
reduceCopy<ncclCollUnroll(), RedOp, T,
|
||||
if (nDsts != 0) {
|
||||
reduceCopy<ncclCollUnroll(), RedOp, T,
|
||||
/*MultimemSrcs=*/0, 1+MinSrcs, 1+MaxSrcs,
|
||||
/*MultimemDsts,MinDsts,MaxDsts=*/0,1,1,
|
||||
/*PreOpSrcs=*/1>
|
||||
(tid, tn, work->redOpArg, &work->redOpArg, false,
|
||||
/*nSrcs=*/1+nSrcs, [=]__device__(int s) {
|
||||
return s==0 ? (T*)inbuf + userOneBeg
|
||||
: work->regUsed && (recvDirectFlag & NCCL_DIRECT_READ)
|
||||
? (T*)srcPtrs[s-1] + userOneBeg
|
||||
: (T*)srcPtrs[s-1] + railAllOffset;
|
||||
},
|
||||
/*nDsts=*/1, [=]__device__(int d/*==0*/) {
|
||||
return (T*)dstPtrs[dst] + railAllOffset;
|
||||
},
|
||||
delta);
|
||||
}
|
||||
railAllOffset += delta;
|
||||
node += 1;
|
||||
}
|
||||
@@ -307,7 +342,7 @@ struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NC
|
||||
ssize_t chunkSize = int(work->collnet.chunkCount);
|
||||
ssize_t sizePerRank = work->collnet.count;
|
||||
|
||||
// if (direct->out == -1) __trap();
|
||||
if (direct->out == -1) __builtin_trap();
|
||||
bool isMultiRail = (direct->nHeads > 1);
|
||||
int nWarps1 = (isMultiRail ? 2 : 0);
|
||||
int nWarps2 = (isMultiRail ? 2 : 1);
|
||||
@@ -322,15 +357,15 @@ struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NC
|
||||
int tn = nWarps1*WARP_SIZE;
|
||||
if (tid < tn) {
|
||||
// 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,
|
||||
work->redOpArg, 0*Proto::MaxGroupWidth, 1, 1);
|
||||
Primitives<T, RedOp, FanAsymmetric<0, NCCL_MAX_DIRECT_ARITY>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid, tn, nullptr, direct->heads+1, work->sendbuff, nullptr,
|
||||
work->redOpArg, 0*Proto::MaxGroupWidth, 1, 1, work);
|
||||
for (ssize_t railGridOffset=0; railGridOffset < nNodes*sizePerRank; railGridOffset += nChannels*chunkSize) {
|
||||
Scatterer</*ReduceSendNotRecv=*/true> scat;
|
||||
scat.work = work;
|
||||
scat.chunkSize = chunkSize;
|
||||
scat.railGridOffset = railGridOffset;
|
||||
prims.template process</*Recv=*/0, /*Send=*/1>(scat);
|
||||
prims.template process</*Recv=*/0, /*Send=*/1>(scat, NCCL_DIRECT_READ, 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -346,15 +381,15 @@ struct RunWorkColl<ncclFuncReduceScatter, T, RedOp, NCCL_ALGO_COLLNET_DIRECT, NC
|
||||
__syncwarp();
|
||||
} else {
|
||||
// Phase 2: Reduce from peers + local input -> send to network
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_DIRECT_ARITY, 1>, /*Direct=*/0, Proto, 0>
|
||||
Primitives<T, RedOp, FanAsymmetric<NCCL_MAX_DIRECT_ARITY, 1>, /*Direct=*/1, Proto, 0>
|
||||
prims(tid, tn, direct->heads + 1, &direct->out, nullptr, nullptr,
|
||||
work->redOpArg, 1 * Proto::MaxGroupWidth, 1, 1);
|
||||
work->redOpArg, 1 * Proto::MaxGroupWidth, 1, 1, work);
|
||||
for (ssize_t railGridOffset = 0; railGridOffset < nNodes * sizePerRank; railGridOffset += nChannels * chunkSize) {
|
||||
Scatterer</*ReduceSendNotRecv=*/false> scat;
|
||||
scat.work = work;
|
||||
scat.chunkSize = chunkSize;
|
||||
scat.railGridOffset = railGridOffset;
|
||||
prims.template process</*Recv=*/1, /*Send=*/1>(scat);
|
||||
prims.template process</*Recv=*/1, /*Send=*/1>(scat, 0, NCCL_DIRECT_READ);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -19,8 +19,8 @@ struct RunWorkBatch<ncclFuncSendRecv, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPL
|
||||
template<typename Proto>
|
||||
__device__ void runSend(int tid, int tn, int group, struct ncclDevWorkP2p* work) {
|
||||
size_t bytes = work->sendBytes;
|
||||
int chunkSize = u32fp8Decode(work->sendChunkSize_u32fp8);
|
||||
|
||||
int chunkSize = work->sendIpcReg && ncclShmem.comm.isNvlink ? (1 << 30) : u32fp8Decode(work->sendChunkSize_u32fp8);
|
||||
|
||||
#if defined(ENABLE_NPKIT)
|
||||
bool isNpKitThread = (tid == 0);
|
||||
int npKitCtxIdx = blockIdx.x + group;
|
||||
@@ -43,7 +43,7 @@ struct RunWorkBatch<ncclFuncSendRecv, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPL
|
||||
Primitives<T, RedOp, FanAsymmetric<0, 1>, 0, Proto, 1>
|
||||
prims(tid, tn, nullptr, &work->sendRank, work->sendAddr, nullptr,
|
||||
/*redOpArg(ignored)=*/0, group, work->sendConnIndex, work->sendConnIndex, nullptr,
|
||||
/*userBufferMode=*/work->sendRegistered, ncclShmem.comm.p2pChunkSize);
|
||||
/*ipcReg=*/work->sendIpcReg, /*netReg=*/work->sendRegistered, ncclShmem.comm.p2pChunkSize);
|
||||
|
||||
#if defined(ENABLE_NPKIT)
|
||||
if (isNpKitThread) {
|
||||
@@ -77,7 +77,7 @@ struct RunWorkBatch<ncclFuncSendRecv, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPL
|
||||
template<typename Proto>
|
||||
__device__ void runRecv(int tid, int tn, int group, struct ncclDevWorkP2p* work) {
|
||||
size_t bytes = work->recvBytes;
|
||||
int chunkSize = u32fp8Decode(work->recvChunkSize_u32fp8);
|
||||
int chunkSize = work->recvIpcReg && ncclShmem.comm.isNvlink ? (1 << 30) : u32fp8Decode(work->recvChunkSize_u32fp8);
|
||||
|
||||
#if defined(ENABLE_NPKIT)
|
||||
bool isNpKitThread = (tid == 0);
|
||||
@@ -101,7 +101,7 @@ struct RunWorkBatch<ncclFuncSendRecv, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPL
|
||||
Primitives<T, RedOp, FanAsymmetric<1, 0>, 0, Proto, 1>
|
||||
prims(tid, tn, &work->recvRank, nullptr, nullptr, work->recvAddr,
|
||||
/*redOpArg(ignored)=*/0, group, work->recvConnIndex, work->recvConnIndex, nullptr,
|
||||
/*userBufferMode=*/work->recvRegistered, ncclShmem.comm.p2pChunkSize);
|
||||
/*ipcReg=*/work->recvIpcReg, /*netReg=*/work->recvRegistered, ncclShmem.comm.p2pChunkSize);
|
||||
|
||||
#if defined(ENABLE_NPKIT)
|
||||
if (isNpKitThread) {
|
||||
@@ -120,7 +120,7 @@ struct RunWorkBatch<ncclFuncSendRecv, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPL
|
||||
size_t cursor = 0;
|
||||
do {
|
||||
int n = min(size_t(chunkSize), bytes-cursor);
|
||||
prims.directRecv(cursor, n);
|
||||
prims.directRecv(cursor, cursor, n);
|
||||
cursor += n;
|
||||
} while (cursor < bytes && work->recvRegistered == 0);
|
||||
|
||||
@@ -172,6 +172,9 @@ struct RunWorkBatch<ncclFuncSendRecv, T, RedOp, NCCL_ALGO_RING, NCCL_PROTO_SIMPL
|
||||
(isSend ? work->sendBytes : work->recvBytes) = partEnd - partBeg;
|
||||
}
|
||||
}
|
||||
// Coverity reports a possible thread divergence due to not all threads participating in the collective.
|
||||
// However, the code ensures that the participation is on a per-warp basis.
|
||||
// coverity[device_thread_diverged:FALSE]
|
||||
uint32_t mask = __ballot(hasWork);
|
||||
if (lane == 0) {
|
||||
shared->workSendMask = mask>>16;
|
||||
|
||||
Reference in New Issue
Block a user