|
|
|
@@ -105,12 +105,10 @@ ncclResult_t ncclGroupStart() {
|
|
|
|
|
return ncclSuccess;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ncclResult_t scheduleSend(struct ncclComm* comm, int peer, int chunk, size_t count, void* buff, uint64_t opCount, uint16_t connIndex) {
|
|
|
|
|
static ncclResult_t scheduleSend(struct ncclComm* comm, int peer, int channelId, size_t count, void* buff, uint64_t opCount, uint16_t connIndex) {
|
|
|
|
|
struct ncclInfo info = { ncclFuncSend, "Send",
|
|
|
|
|
NULL, buff, count, ncclInt8, ncclSum, peer, comm, comm->userStream, /* Args */
|
|
|
|
|
1, 1 };
|
|
|
|
|
int channelId;
|
|
|
|
|
NCCLCHECK(ncclChannelCompute(comm, peer, chunk%comm->p2pnChannelsPerPeer, ncclFuncSend, &channelId));
|
|
|
|
|
info.channelId = channelId;
|
|
|
|
|
info.opCount = opCount;
|
|
|
|
|
info.connIndex = connIndex;
|
|
|
|
@@ -118,12 +116,10 @@ static ncclResult_t scheduleSend(struct ncclComm* comm, int peer, int chunk, siz
|
|
|
|
|
return ncclSuccess;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ncclResult_t scheduleRecv(struct ncclComm* comm, int peer, int chunk, size_t count, void* buff, uint64_t opCount, uint16_t connIndex) {
|
|
|
|
|
static ncclResult_t scheduleRecv(struct ncclComm* comm, int peer, int channelId, size_t count, void* buff, uint64_t opCount, uint16_t connIndex) {
|
|
|
|
|
struct ncclInfo info = { ncclFuncRecv, "Recv",
|
|
|
|
|
NULL, buff, count, ncclInt8, ncclSum, peer, comm, comm->userStream, /* Args */
|
|
|
|
|
1, 1 };
|
|
|
|
|
int channelId;
|
|
|
|
|
NCCLCHECK(ncclChannelCompute(comm, peer, chunk%comm->p2pnChannelsPerPeer, ncclFuncRecv, &channelId));
|
|
|
|
|
info.channelId = channelId;
|
|
|
|
|
info.opCount = opCount;
|
|
|
|
|
info.connIndex = connIndex;
|
|
|
|
@@ -244,8 +240,9 @@ ncclResult_t ncclGroupEnd() {
|
|
|
|
|
struct ncclAsyncArgs* args = ncclGroupArgs+i;
|
|
|
|
|
if (args->funcType == ASYNC_FUNC_COLL) {
|
|
|
|
|
struct ncclComm* comm = args->coll.comm;
|
|
|
|
|
int rank = comm->rank;
|
|
|
|
|
int nRanks = comm->nRanks;
|
|
|
|
|
int node = comm->node;
|
|
|
|
|
int nNodes = comm->nNodes;
|
|
|
|
|
int localRank = comm->localRank;
|
|
|
|
|
|
|
|
|
|
// Compute how much to split operations
|
|
|
|
|
// Natural step size matching buffer steps.
|
|
|
|
@@ -254,71 +251,89 @@ ncclResult_t ncclGroupEnd() {
|
|
|
|
|
int nChannelsMax = comm->p2pnChannelsPerPeer;
|
|
|
|
|
int nChannelsMin = nChannelsMax;
|
|
|
|
|
// Try to use all channels, but one channel per operation.
|
|
|
|
|
while (nChannelsMin*comm->nRanks > std::max(comm->nChannels, comm->p2pnChannels) && nChannelsMin > 1) nChannelsMin /= 2;
|
|
|
|
|
//while (nChannelsMin*comm->nRanks > std::max(comm->nChannels, comm->p2pnChannels) && nChannelsMin > 1) nChannelsMin /= 2;
|
|
|
|
|
// Avoid overloading channels with 8+ operations as we loose the sync warp, hence a bit of bandwidth.
|
|
|
|
|
while (nChannelsMax*comm->nRanks > std::max(comm->nChannels, comm->p2pnChannels)*4 && nChannelsMax > 1) nChannelsMax /= 2;
|
|
|
|
|
//while (nChannelsMax*comm->nRanks > std::max(comm->nChannels, comm->p2pnChannels)*4 && nChannelsMax > 1) nChannelsMax /= 2;
|
|
|
|
|
|
|
|
|
|
while (comm->p2pSendCount > 0 || comm->p2pRecvCount > 0) {
|
|
|
|
|
// schedule delta 0, +1, -1, +2, -2, ...
|
|
|
|
|
// also make sure we don't do 0 twice, nor +n/2 and -n/2 if n is even.
|
|
|
|
|
for (int d=0; d<=nRanks/4; d++) {
|
|
|
|
|
int deltas[4] = { d, (nRanks-d)%nRanks, nRanks/2-d, (nRanks-(nRanks/2-d))%nRanks };
|
|
|
|
|
for (int d=0; d<=nNodes/4; d++) {
|
|
|
|
|
int deltas[4] = { d, (nNodes-d)%nNodes, nNodes/2-d, (nNodes-(nNodes/2-d))%nNodes };
|
|
|
|
|
int index = 0;
|
|
|
|
|
int delta = deltas[index];
|
|
|
|
|
sched_delta:
|
|
|
|
|
uint32_t recvPeer = (rank+nRanks-delta)%nRanks;
|
|
|
|
|
uint32_t sendPeer = (rank+delta)%nRanks;
|
|
|
|
|
struct ncclP2Pinfo* recv = comm->p2pRecvs[recvPeer] ? comm->p2pRecvs[recvPeer]->getNext() : NULL;
|
|
|
|
|
struct ncclP2Pinfo* send = comm->p2pSends[sendPeer] ? comm->p2pSends[sendPeer]->getNext() : NULL;
|
|
|
|
|
if (recv != NULL || send != NULL) {
|
|
|
|
|
ssize_t totRecvBytes = -1, totSendBytes = -1;
|
|
|
|
|
if (recv != NULL) totRecvBytes = recv->nbytes;
|
|
|
|
|
if (send != NULL) totSendBytes = send->nbytes;
|
|
|
|
|
if (recv) comm->p2pRecvCount--;
|
|
|
|
|
if (send) comm->p2pSendCount--;
|
|
|
|
|
if (recvPeer == comm->rank) { // Check self send/recv
|
|
|
|
|
if (sendPeer != comm->rank) { WARN("Sendrecv schedule not aligned for self"); ret = ncclInternalError; goto group_cleanup; }
|
|
|
|
|
if (send && recv == NULL) { WARN("Trying to send to self without a matching recv"); ret = ncclInvalidUsage; goto group_cleanup; }
|
|
|
|
|
if (send == NULL && recv) { WARN("Trying to recv to self without a matching send"); ret = ncclInvalidUsage; goto group_cleanup; }
|
|
|
|
|
}
|
|
|
|
|
void* recvBuff = recv ? recv->buff : NULL;
|
|
|
|
|
void* sendBuff = send ? send->buff : NULL;
|
|
|
|
|
// After we recycle p2pSend/Recv, we're no longer allowed to dereference send or recv, only use them as boolean NULL/not NULL.
|
|
|
|
|
if (recv && comm->p2pRecvs[recvPeer]->peakNext() == NULL) comm->p2pRecvs[recvPeer]->recycle();
|
|
|
|
|
if (send && comm->p2pSends[sendPeer]->peakNext() == NULL) comm->p2pSends[sendPeer]->recycle();
|
|
|
|
|
uint32_t recvNode = (node+nNodes-delta)%nNodes;
|
|
|
|
|
uint32_t sendNode = (node+delta)%nNodes;
|
|
|
|
|
int steps = comm->maxLocalRanks;
|
|
|
|
|
for (int s=0; s<steps; s++) {
|
|
|
|
|
int recvIndex = (localRank-s+steps)%steps;
|
|
|
|
|
int recvPeer = recvIndex<comm->nodeRanks[recvNode].localRanks ? comm->nodeRanks[recvNode].localRankToRank[recvIndex] : -1;
|
|
|
|
|
int sendIndex = (localRank+s)%steps;
|
|
|
|
|
int sendPeer = sendIndex<comm->nodeRanks[sendNode].localRanks ? comm->nodeRanks[sendNode].localRankToRank[sendIndex] : -1;
|
|
|
|
|
struct ncclP2Pinfo* recv = recvPeer != -1 && comm->p2pRecvs[recvPeer] ? comm->p2pRecvs[recvPeer]->getNext() : NULL;
|
|
|
|
|
struct ncclP2Pinfo* send = sendPeer != -1 && comm->p2pSends[sendPeer] ? comm->p2pSends[sendPeer]->getNext() : NULL;
|
|
|
|
|
if (recv != NULL || send != NULL) {
|
|
|
|
|
ssize_t totRecvBytes = -1, totSendBytes = -1;
|
|
|
|
|
if (recv != NULL) totRecvBytes = recv->nbytes;
|
|
|
|
|
if (send != NULL) totSendBytes = send->nbytes;
|
|
|
|
|
if (recv) comm->p2pRecvCount--;
|
|
|
|
|
if (send) comm->p2pSendCount--;
|
|
|
|
|
if (recvPeer == comm->rank) { // Check self send/recv
|
|
|
|
|
if (sendPeer != comm->rank) { WARN("Sendrecv schedule not aligned for self"); ret = ncclInternalError; goto group_cleanup; }
|
|
|
|
|
if (send && recv == NULL) { WARN("Trying to send to self without a matching recv"); ret = ncclInvalidUsage; goto group_cleanup; }
|
|
|
|
|
if (send == NULL && recv) { WARN("Trying to recv to self without a matching send"); ret = ncclInvalidUsage; goto group_cleanup; }
|
|
|
|
|
}
|
|
|
|
|
void* recvBuff = recv ? recv->buff : NULL;
|
|
|
|
|
void* sendBuff = send ? send->buff : NULL;
|
|
|
|
|
// After we recycle p2pSend/Recv, we're no longer allowed to dereference send or recv, only use them as boolean NULL/not NULL.
|
|
|
|
|
if (recv && comm->p2pRecvs[recvPeer]->peakNext() == NULL) comm->p2pRecvs[recvPeer]->recycle();
|
|
|
|
|
if (send && comm->p2pSends[sendPeer]->peakNext() == NULL) comm->p2pSends[sendPeer]->recycle();
|
|
|
|
|
|
|
|
|
|
ssize_t recvChunkSize = getP2pChunkSize(totRecvBytes, nChannelsMin, nChannelsMax, stepSize, SENDRECV_SLICEFACTOR*stepSize);
|
|
|
|
|
ssize_t sendChunkSize = getP2pChunkSize(totSendBytes, nChannelsMin, nChannelsMax, stepSize, SENDRECV_SLICEFACTOR*stepSize);
|
|
|
|
|
ssize_t recvChunkSize = getP2pChunkSize(totRecvBytes, nChannelsMin, nChannelsMax, stepSize, SENDRECV_SLICEFACTOR*stepSize);
|
|
|
|
|
ssize_t sendChunkSize = getP2pChunkSize(totSendBytes, nChannelsMin, nChannelsMax, stepSize, SENDRECV_SLICEFACTOR*stepSize);
|
|
|
|
|
|
|
|
|
|
uint16_t sendIdx = 1, recvIdx = 1;
|
|
|
|
|
if(comm->p2pNet && totSendBytes > rcclParamP2pNetThreshold())
|
|
|
|
|
sendIdx = NCCL_CONN_IDX_P2P_NET;
|
|
|
|
|
if(comm->p2pNet && totRecvBytes > rcclParamP2pNetThreshold())
|
|
|
|
|
recvIdx = NCCL_CONN_IDX_P2P_NET;
|
|
|
|
|
uint16_t sendIdx = 1, recvIdx = 1;
|
|
|
|
|
if(comm->p2pNet && totSendBytes > rcclParamP2pNetThreshold())
|
|
|
|
|
sendIdx = NCCL_CONN_IDX_P2P_NET;
|
|
|
|
|
if(comm->p2pNet && totRecvBytes > rcclParamP2pNetThreshold())
|
|
|
|
|
recvIdx = NCCL_CONN_IDX_P2P_NET;
|
|
|
|
|
|
|
|
|
|
ssize_t sendOffset = 0;
|
|
|
|
|
ssize_t recvOffset = 0;
|
|
|
|
|
int sendRemaining = 1, recvRemaining = 1;
|
|
|
|
|
int chunk = 0;
|
|
|
|
|
do {
|
|
|
|
|
ssize_t recvbytes = totRecvBytes-recvOffset;
|
|
|
|
|
ssize_t sendbytes = totSendBytes-sendOffset;
|
|
|
|
|
if (recvbytes > recvChunkSize) { recvbytes = recvChunkSize; } else { recvRemaining = 0; }
|
|
|
|
|
if (sendbytes > sendChunkSize) { sendbytes = sendChunkSize; } else { sendRemaining = 0; }
|
|
|
|
|
// 0-bytes send/recv are considered as syncs. Make sure we only add syncs when requested
|
|
|
|
|
// (total size == 0), otherwise set size to -1.
|
|
|
|
|
ssize_t sendOffset = 0;
|
|
|
|
|
ssize_t recvOffset = 0;
|
|
|
|
|
int sendRemaining = 1, recvRemaining = 1;
|
|
|
|
|
int chunk = 0;
|
|
|
|
|
do {
|
|
|
|
|
int channelId;
|
|
|
|
|
// Shuffle channels with s intra-node, and delta inter-node. Inter-node, make sure
|
|
|
|
|
// to use multiple channels to guarantee progress on all ranks from the same node.
|
|
|
|
|
ssize_t recvbytes = totRecvBytes-recvOffset;
|
|
|
|
|
ssize_t sendbytes = totSendBytes-sendOffset;
|
|
|
|
|
if (recvbytes > recvChunkSize) { recvbytes = recvChunkSize; } else { recvRemaining = 0; }
|
|
|
|
|
if (sendbytes > sendChunkSize) { sendbytes = sendChunkSize; } else { sendRemaining = 0; }
|
|
|
|
|
// 0-bytes send/recv are considered as syncs. Make sure we only add syncs when requested
|
|
|
|
|
// (total size == 0), otherwise set size to -1.
|
|
|
|
|
if (sendbytes < 0 || (sendbytes == 0 && totSendBytes != 0)) send = NULL;
|
|
|
|
|
if (recvbytes < 0 || (recvbytes == 0 && totRecvBytes != 0)) recv = NULL;
|
|
|
|
|
if (recv) {
|
|
|
|
|
NCCLCHECKGOTO(scheduleRecv(comm, recvPeer, chunk, recvbytes, ((char*)(recv->buff))+recvOffset, recv->opCount, recvIdx), ret, group_cleanup);
|
|
|
|
|
}
|
|
|
|
|
if (send) {
|
|
|
|
|
NCCLCHECKGOTO(scheduleSend(comm, sendPeer, chunk, sendbytes, ((char*)(send->buff))+sendOffset, send->opCount, sendIdx), ret, group_cleanup);
|
|
|
|
|
}
|
|
|
|
|
recvOffset += recvChunkSize;
|
|
|
|
|
sendOffset += sendChunkSize;
|
|
|
|
|
chunk++;
|
|
|
|
|
} while (sendRemaining || recvRemaining);
|
|
|
|
|
if (send || recv) {
|
|
|
|
|
if (recv) {
|
|
|
|
|
NCCLCHECK(ncclChannelCompute(comm, recvPeer, chunk%comm->p2pnChannelsPerPeer, ncclFuncRecv, &channelId));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
recvPeer = -1;
|
|
|
|
|
if (send) {
|
|
|
|
|
NCCLCHECK(ncclChannelCompute(comm, sendPeer, chunk%comm->p2pnChannelsPerPeer, ncclFuncSend, &channelId));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
sendPeer = -1;
|
|
|
|
|
NCCLCHECKGOTO(scheduleRecv(comm, recvPeer, channelId, recvbytes, recv ? ((char*)recvBuff)+recvOffset : NULL, recv ? recv->opCount : 0, recvIdx), ret, group_cleanup);
|
|
|
|
|
NCCLCHECKGOTO(scheduleSend(comm, sendPeer, channelId, sendbytes, send ? ((char*)sendBuff)+sendOffset : NULL, send ? send->opCount : 0, sendIdx), ret, group_cleanup);
|
|
|
|
|
}
|
|
|
|
|
recvOffset += recvChunkSize;
|
|
|
|
|
sendOffset += sendChunkSize;
|
|
|
|
|
chunk++;
|
|
|
|
|
} while (sendRemaining || recvRemaining);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
index++;
|
|
|
|
|
if (index == 1 && deltas[1] == deltas[0]) index++;
|
|
|
|
|