Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions projects/rccl/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Full documentation for RCCL is available at [https://rccl.readthedocs.io](https:

### Resolved Issues
* Fixed RCCL initialization failing (`Failed to find ROCm runtime library`) on runtime-only ROCm trees that ship no unversioned `libhsa-runtime64.so` developer symlink (e.g. TheRock multi-arch pip-wheel `/opt/rocm-less` deployments). RCCL no longer `dlopen`s the HSA runtime by name; instead it directly links `hsa-runtime64::hsa-runtime64` (already a hard transitive dependency via the HIP runtime) and binds `hsa_init`, `hsa_system_get_info`, `hsa_status_string`, and `hsa_amd_portable_export_dmabuf` to those symbols. The linker records `DT_NEEDED libhsa-runtime64.so.1` and resolves it through librccl's existing RPATH, removing the SONAME version-string fragility and load-scope (`RTLD_LOCAL`) issues. The `RCCL_ROCR_PATH` override is no longer needed and has been removed.
* Fixed topology GPU lookups that used the HIP device ordinal instead of the SMI index the topology stores. When HIP order differed from PCI order (for example with `HIP_VISIBLE_DEVICES`), XGMI link detection for transport selection, P2P setup and the clique check compared the wrong GPUs.
* Fixed a 15 to 30% `ncclScatter` bandwidth loss from 16 MB upward on multi-node AINIC jobs, caused by the move to two network channels per peer (for example on 4 nodes of 8 GPUs). Scatter now uses and connects the most channels per peer whose flows still fit the P2P channel pool. Point-to-point operations and the other collectives keep their channel count.

### Known issues
* Elastic-buffer support for GIN (multi-segment symmetric memory windows backed by a mix of device and CPU/`HOST_NUMA` memory, exposed through `NCCL_ELASTIC_BUFFER_REGISTER` and `NCCL_SYM_REUSE_SYSMEM_HANDLES`) was newly synced from upstream and compiles on ROCm, but is unverified on AMD hardware.
Expand Down
9 changes: 9 additions & 0 deletions projects/rccl/docs/api-reference/env-variables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ collected in the following table.
- | Protocol name string
| Used to override automatic protocol selection

* - | ``RCCL_DIRECT_ALLGATHER_DISABLE``
| Controls the direct AllGather algorithm. Because the algorithm builds a full
point-to-point mesh, its queue-pair footprint grows with the square
of the job size.
- | ``-1``: Automatic (default). Not selected on AINIC above 8 nodes.
| ``0``: Skips the automatic AINIC check. The size, architecture and
CTA-policy gates in ``rcclUseAllGatherDirect`` still apply.
| Any other value: Disabled.

Network and topology
====================

Expand Down
5 changes: 2 additions & 3 deletions projects/rccl/src/device/prims_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ class Primitives<T, RedOp, Fan, Direct, ProtoLL, P2p, isNetOffload, Metadata, Pi
return elt;
}

// User buffers only. Comm FIFO stores must go through storeLL, which keeps
// the gfx950 hipHostMallocUncached fence for ROCm < 7.0.2.
template<typename U>
__device__ static void store(U *dst, U val) {
union {
Expand Down Expand Up @@ -319,9 +321,6 @@ class Primitives<T, RedOp, Fan, Direct, ProtoLL, P2p, isNetOffload, Metadata, Pi
__builtin_nontemporal_store(u4, (uint32_t*)dst);
else
__builtin_nontemporal_store(u8, (uint64_t*)dst);
#if defined(__gfx950__) && ROCM_VERSION < 70002
__builtin_amdgcn_fence(__ATOMIC_RELEASE, ""); // flush cache on gfx950 if ROCr fix for hipHostMallocUncached is not available (ROCm version < 7.0.2)
#endif
#endif
#else
if(sizeof(U) == 1)
Expand Down
41 changes: 34 additions & 7 deletions projects/rccl/src/enqueue.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,19 @@ static int rcclEffectiveP2pBatchEnable(struct ncclComm* comm) {
return (isGfx950 && !rcclUseAinic()) ? 1 : 0;
}

// Number of the p2pnChannelsPerPeer parts a task of collAPI uses and connects. Multi-node AINIC scatter
// uses and connects only the most parts whose flows from the root still fit the pool; connecting more
// slows it down. A user-set nChannelsPerNetPeer is kept, as in ncclTopoComputeP2pChannels. Depend only
// on values both ends of a pair share, or they disagree on the parts.
int rcclP2pTaskParts(struct ncclComm* comm, ncclFunc_t collAPI) {
if (collAPI == ncclFuncScatter && comm->nNodes > 1 && comm->config.nChannelsPerNetPeer == NCCL_CONFIG_UNDEF_INT &&
rcclUseAinic()) {
int partsThatFit = comm->p2pnChannels / (comm->nRanks - 1);
return std::min(comm->p2pnChannelsPerPeer, partsThatFit >= 1 ? pow2Down(partsThatFit) : 1);
}
return comm->p2pnChannelsPerPeer;
}

// Put p2p op in plan assuming there is sizeof(ncclDevWorkBatch) in batch budget
// and sizeof(ncclDevWorkP2p) in work budget. "sendRank" and "recvRank" must
// match the corresponding values for this round of the p2p schedule (no -1's).
Expand All @@ -1113,6 +1126,11 @@ static ncclResult_t addP2pToPlan(
bool network[2] = {false, false};
bool proxySameProcess[2] = {true, true};
void** handles[2] = {NULL, NULL};
// Parts each direction's task uses; fewer than nChannelsMax also caps its channel count.
int taskParts[2];
for (int dir = 0; dir < 2; dir++)
taskParts[dir] =
p2pTasks[dir] ? std::min(nChannelsMax, rcclP2pTaskParts(comm, p2pTasks[dir]->collAPI)) : nChannelsMax;
auto batchP2PEnableEnv = rcclEffectiveP2pBatchEnable(comm);
auto p2pBatchThreshold = rcclParamP2pBatchThreshold();
bool belowThreshold = (recvBytes <= p2pBatchThreshold) && (sendBytes <= p2pBatchThreshold);
Expand All @@ -1137,6 +1155,7 @@ static ncclResult_t addP2pToPlan(
int channelId = ncclP2pChannelForPart(comm->p2pnChannels, base, part, nChannelsMax, comm->nNodes, comm->p2pChannelShiftSize);
struct ncclChannelPeer** channelPeers = comm->channels[channelId].peers;
for (int dir=0; dir <= 1; dir++) {
if (part >= taskParts[dir]) continue;
int peerRank = dir ? sendRank : recvRank;
struct ncclConnector* conn = dir ? &channelPeers[peerRank]->send[connIndex[dir]]
: &channelPeers[peerRank]->recv[connIndex[dir]];
Expand Down Expand Up @@ -1167,9 +1186,11 @@ static ncclResult_t addP2pToPlan(
else {
ssize_t minPartSize = comm->nNodes > 1 ? stepSize[dir]/2 : stepSize[dir]/8;
ssize_t maxPartSize = comm->nNodes > 1 ? stepSize[dir] : stepSize[dir]*32;
nChannels[dir] = std::min<int>(nChannelsMin, divUp(bytes[dir], minPartSize));
int nChMax = taskParts[dir];
int nChStart = taskParts[dir] < nChannelsMax ? nChMax : nChannelsMin;
nChannels[dir] = std::min<int>(nChStart, divUp(bytes[dir], minPartSize));
size_t partSize = std::max(minPartSize, divUp(bytes[dir], nChannels[dir]));
while (partSize > maxPartSize && nChannels[dir] <= nChannelsMax/2) {
while (partSize > maxPartSize && nChannels[dir] <= nChMax/2) {
nChannels[dir] *= 2;
partSize = divUp(bytes[dir], nChannels[dir]);
}
Expand Down Expand Up @@ -1205,7 +1226,7 @@ static ncclResult_t addP2pToPlan(
if (bytes[dir] > 0 && proxySameProcess[dir] && protocol[dir] == NCCL_PROTO_SIMPLE && (!pxnUsed)) {
int regFlag = 0;
NCCLCHECKGOTO(ncclCalloc(&handles[dir], nChannelsMax), ret, cleanup);
for (int part = 0; part < nChannelsMax; part++) {
for (int part = 0; part < taskParts[dir]; part++) {
int channelId = ncclP2pChannelForPart(comm->p2pnChannels, base, part, nChannelsMax, comm->nNodes, comm->p2pChannelShiftSize);
struct ncclChannelPeer** channelPeers = comm->channels[channelId].peers;
int peerRank = dir ? sendRank : recvRank;
Expand Down Expand Up @@ -1241,9 +1262,11 @@ static ncclResult_t addP2pToPlan(
else {
ssize_t minPartSize = comm->nNodes > 1 ? stepSize[dir]/2 : stepSize[dir]/8;
ssize_t maxPartSize = comm->nNodes > 1 ? stepSize[dir] : stepSize[dir]*32;
nChannels[dir] = std::min<int>(nChannelsMin, divUp(bytes[dir], minPartSize));
int nChMax = taskParts[dir];
int nChStart = taskParts[dir] < nChannelsMax ? nChMax : nChannelsMin;
nChannels[dir] = std::min<int>(nChStart, divUp(bytes[dir], minPartSize));
size_t partSize = std::max(minPartSize, divUp(bytes[dir], nChannels[dir]));
while (partSize > maxPartSize && nChannels[dir] <= nChannelsMax/2) {
while (partSize > maxPartSize && nChannels[dir] <= nChMax/2) {
nChannels[dir] *= 2;
partSize = divUp(bytes[dir], nChannels[dir]);
}
Expand Down Expand Up @@ -3131,8 +3154,12 @@ static ncclResult_t p2pTaskAppend(
// Mark channels that need pre-connect
if (comm->rank != peer) {
if (!(isSendNotRecv ? planner->peers[peer].sendSeen : planner->peers[peer].recvSeen)) {
int nParts = rcclP2pTaskParts(comm, collAPI);
// planner->peers[peer].send/recvSeen is private to each comm, so we need to set it anyway.
(isSendNotRecv ? planner->peers[peer].sendSeen : planner->peers[peer].recvSeen) = true;
// Leave it clear when only some parts are connected, so a later task to this peer that uses
// all of them still connects the rest.
if (nParts == comm->p2pnChannelsPerPeer)
(isSendNotRecv ? planner->peers[peer].sendSeen : planner->peers[peer].recvSeen) = true;
int round = 0;
while (peer != (isSendNotRecv ? comm->p2pSchedule[round].sendRank
: comm->p2pSchedule[round].recvRank)) {
Expand All @@ -3141,7 +3168,7 @@ static ncclResult_t p2pTaskAppend(
uint8_t base = ncclP2pChannelBaseForRound(comm, round, rcclEffectiveP2pBatchEnable(comm));


for (int c=0; c < comm->p2pnChannelsPerPeer; c++) {
for (int c=0; c < nParts; c++) {
int channelId = ncclP2pChannelForPart(comm->p2pnChannels, base, c, comm->p2pnChannelsPerPeer, comm->nNodes, comm->p2pChannelShiftSize);
if (isSendNotRecv) {
if (comm->channels[channelId].peers[peer]->send[1].hasSeen == 0) { // P2P uses only 1 connector
Expand Down
18 changes: 14 additions & 4 deletions projects/rccl/src/graph/paths.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1054,12 +1054,12 @@ ncclResult_t ncclTopoTrimSystem(struct ncclTopoSystem* system, struct ncclComm*

// detect if all GPUs are connected by XGMI
for (int i = 0; i < system->nodes[GPU].count && allXgmi; i++) {
int cudaDev1 = system->nodes[GPU].nodes[i].gpu.dev;
int dev1 = system->nodes[GPU].nodes[i].gpu.dev;
for (int j = 0; j < system->nodes[GPU].count && allXgmi; j++) {
if (i == j) continue;
int cudaDev2 = system->nodes[GPU].nodes[j].gpu.dev;
int dev2 = system->nodes[GPU].nodes[j].gpu.dev;
bool isXGMI;
NCCLCHECKGOTO(ncclTopoGetLinkType(comm->topo, cudaDev1, cudaDev2, &isXGMI), ret, fail);
NCCLCHECKGOTO(ncclTopoGetLinkType(comm->topo, dev1, dev2, &isXGMI), ret, fail);
allXgmi &= isXGMI;
}
}
Expand Down Expand Up @@ -1231,7 +1231,17 @@ ncclResult_t ncclTopoComputeP2pChannels(struct ncclComm* comm) {
if (comm->nNodes > 1 && comm->config.nChannelsPerNetPeer == NCCL_CONFIG_UNDEF_INT) {
// In the case of >1 NVLD (and the user didn't set nChannelsPerNetPeer), the network is the bottleneck.
// Reduce the number of channels per host to avoid going above p2pnChannels to fit all the peers within a single round.
while (comm->p2pnChannelsPerPeer * divUp(comm->nRanks, NCCL_MAX_DEV_WORK_P2P_PER_BATCH) >= comm->p2pnChannels && comm->p2pnChannelsPerPeer > 1) comm->p2pnChannelsPerPeer /= 2;
while (comm->p2pnChannelsPerPeer * divUp(comm->nRanks, NCCL_MAX_DEV_WORK_P2P_PER_BATCH) >= comm->p2pnChannels &&
comm->p2pnChannelsPerPeer > 1)
comm->p2pnChannelsPerPeer /= 2;
if (rcclUseAinic()) {
// A single AINIC NIC is only saturated by two net-p2p channels per peer.
// Restore the pre-2.29 count of max(netCountByBw, nChannelsMax) wherever the channel pool can hold it (was available up to 8 nodes).
bool atScale = comm->nNodes > 8 && 2 * comm->nRanks > comm->p2pnChannels;
int nChannelsMax = atScale ? 1 : 2;
comm->p2pnChannelsPerPeer = std::max(
comm->p2pnChannelsPerPeer, std::min(std::max(comm->minNetCount, nChannelsMax), comm->p2pnChannels));
}
} else {
comm->p2pnChannelsPerPeer = std::min(comm->p2pnChannelsPerPeer, comm->p2pnChannels);
}
Expand Down
14 changes: 7 additions & 7 deletions projects/rccl/src/graph/search.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1555,16 +1555,16 @@ ncclResult_t ncclTopoGetIntraNetDev(struct ncclTopoSystem* system, int rank, str
return ncclSuccess;
}

ncclResult_t ncclTopoGetLinkType(struct ncclTopoSystem* system, int cudaDev1, int cudaDev2, bool* isXGMI, int maxInter, int nInter, int *inter) {
ncclResult_t ncclTopoGetLinkType(struct ncclTopoSystem* system, int dev1, int dev2, bool* isXGMI, int maxInter, int nInter, int *inter) {
int interGpus[MAX_XGMI_INTER_GPUS+1];
int ngpus = system->nodes[GPU].count;
*isXGMI = false;
// check for direct XGMI connection
for (int i=0; i<ngpus; i++) {
if (system->nodes[GPU].nodes[i].gpu.dev == cudaDev1) {
if (system->nodes[GPU].nodes[i].gpu.dev == dev1) {
struct ncclTopoNode *node = system->nodes[GPU].nodes+i;
for (int k = 0; k<system->nodes[GPU].count; k++) {
if (system->nodes[GPU].nodes[k].gpu.dev == cudaDev2) {
if (system->nodes[GPU].nodes[k].gpu.dev == dev2) {
// NCCL 2.30 routes GPU->GPU through DEV nodes, so detect XGMI via path type (not a single hop).
if (node->paths[GPU][k].count > 0 && node->paths[GPU][k].type == PATH_NVL) {
*isXGMI = true;
Expand All @@ -1586,27 +1586,27 @@ ncclResult_t ncclTopoGetLinkType(struct ncclTopoSystem* system, int cudaDev1, in
}
if (j<nInter) return ncclSuccess;
if (nInter > 0 && inter != nullptr) {
ncclTopoGetLinkType(system, inter[nInter], cudaDev2, &res2, 0);
ncclTopoGetLinkType(system, inter[nInter], dev2, &res2, 0);
if (res2) {
*isXGMI = true;
return ncclSuccess;
}
memcpy(interGpus+1, inter+1, sizeof(int)*nInter);
}
interGpus[0] = cudaDev1;
interGpus[0] = dev1;
// add one more intermediate GPU recursively util reaching max depth
nInter++;
if (nInter+2 > ngpus || nInter > MAX_XGMI_INTER_GPUS || nInter > maxInter) return ncclSuccess;
for (int i=0; i<ngpus; i++) {
int dev = system->nodes[GPU].nodes[i].gpu.dev;
// skip duplicated GPU
if (dev == cudaDev2) continue;
if (dev == dev2) continue;
for (j=0; j<nInter; j++)
if (dev == interGpus[j]) break;
if (j<nInter) continue;
// check connectivity with intermediate GPUs
interGpus[nInter] = dev;
ncclTopoGetLinkType(system, cudaDev1, cudaDev2, &res3, maxInter, nInter, interGpus);
ncclTopoGetLinkType(system, dev1, dev2, &res3, maxInter, nInter, interGpus);
if (res3) {
*isXGMI = true;
return ncclSuccess;
Expand Down
15 changes: 15 additions & 0 deletions projects/rccl/src/graph/topo.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1850,6 +1850,21 @@ ncclResult_t getLocalNetCountByBw(struct ncclTopoSystem* system, int gpu, int *c
float gpuBw = system->nodes[GPU].nodes[gpu].paths[CPU][c].bw;
int rank = system->nodes[GPU].nodes[gpu].gpu.rank;

// RCCL: on configurations with no local network device (e.g. a single-node
// run using the socket transport with no IB devices), the topology has no
// GPU->NET path. Report zero net devices instead of failing, matching how
// callers (paths.cc) already handle a zero count.
{
int localNets[NCCL_TOPO_MAX_NODES];
int localNetCount = 0;
NCCLCHECK(ncclTopoGetLocal(system, GPU, gpu, NET, localNets, &localNetCount, NULL));
if (localNetCount == 0) {
*count = 0;
*bw = 0;
return ncclSuccess;
}
}

int netCountByBw = 0;
float totalNetBw = 0;
int64_t firstNetId = 0;
Expand Down
1 change: 1 addition & 0 deletions projects/rccl/src/include/channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "param.h"

bool rcclUseAinic();
int rcclP2pTaskParts(struct ncclComm* comm, ncclFunc_t collAPI);

RCCL_PARAM_DECLARE(PxnOptQpUsage); // RCCL_PXN_OPT_QP_USAGE: uses batch stride of comm->maxLocalRanks instead of 1 to reduce QP usage when p2p-batching is disabled

Expand Down
1 change: 1 addition & 0 deletions projects/rccl/src/include/comm.h
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,7 @@ struct ncclComm {

int node;
int nNodes;
int minNetCount; // Minimum number of network devices local to a rank
int rcclUseOneSlice; // RCCL: true if this comm is using one slice per primitive
int gfx9CheapFenceOff; // RCCL: true if gfx9 cheap fence is disabled
int localRank;
Expand Down
2 changes: 1 addition & 1 deletion projects/rccl/src/include/graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ ncclResult_t ncclGetUserP2pLevel(int* level);

#define MAX_XGMI_INTER_GPUS 4
ncclResult_t ncclTopoGetIntraNetDev(struct ncclTopoSystem* system, int rank, struct ncclTopoGraph* graph, int channelId, int type, int64_t* id, int* dev);
ncclResult_t ncclTopoGetLinkType(struct ncclTopoSystem* system, int cudaDev1, int cudaDev2, bool* isXGMI, int maxInter=MAX_XGMI_INTER_GPUS, int nInter=0, int *inter=nullptr);
ncclResult_t ncclTopoGetLinkType(struct ncclTopoSystem* system, int dev1, int dev2, bool* isXGMI, int maxInter=MAX_XGMI_INTER_GPUS, int nInter=0, int *inter=nullptr);

// Find CPU affinity
ncclResult_t ncclTopoGetCpuAffinity(struct ncclTopoSystem* system, int rank, ncclAffinity* affinity);
Expand Down
11 changes: 10 additions & 1 deletion projects/rccl/src/init.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,7 @@ static ncclResult_t initTransportsRank(struct ncclComm* comm, struct ncclComm* p
bool nicFused;
int localNetDeviceCount;
int localCollNetCount;
int localNetCountByBw;
bool isMultiRankGpu;
};

Expand All @@ -1376,6 +1377,7 @@ static ncclResult_t initTransportsRank(struct ncclComm* comm, struct ncclComm* p

int localNetDeviceCount = 0;
int localCollNetCount = 0;
int localNetCountByBw = 0;
int minLocalNetCount = INT_MAX;
int maxLocalNetCount = 0;
int minLocalCollNetCount = INT_MAX;
Expand Down Expand Up @@ -1643,7 +1645,7 @@ static ncclResult_t initTransportsRank(struct ncclComm* comm, struct ncclComm* p

bool isXGMI;
// Limit to single intermediate GPU for enabling clique
NCCLCHECK(ncclTopoGetLinkType(comm->topo, i, j, &isXGMI, 1));
NCCLCHECK(ncclTopoGetLinkType(comm->topo, comm->peerInfo[i].nvmlDev, comm->peerInfo[j].nvmlDev, &isXGMI, 1));
allXgmi &= isXGMI;
}
}
Expand Down Expand Up @@ -1756,7 +1758,11 @@ static ncclResult_t initTransportsRank(struct ncclComm* comm, struct ncclComm* p
allGather3Data[rank].ll128Enabled = comm->topo->ll128Enabled;

if (comm->ncclNet && comm->ncclNet->devices) {
int gpu;
float localNetBw = 0.0f;
NCCLCHECKGOTO(comm->ncclNet->devices(&localNetDeviceCount), ret, fail);
NCCLCHECKGOTO(ncclTopoRankToIndex(comm->topo, comm->rank, &gpu, false), ret, fail);
NCCLCHECKGOTO(getLocalNetCountByBw(comm->topo, gpu, &localNetCountByBw, &localNetBw), ret, fail);
}
if (collNetSupport(comm)) {
NCCLCHECKGOTO(collNetDevices(comm, &localCollNetCount), ret, fail);
Expand Down Expand Up @@ -1785,6 +1791,7 @@ static ncclResult_t initTransportsRank(struct ncclComm* comm, struct ncclComm* p
NCCLCHECKGOTO(ncclTopoCheckNicFused(comm, &allGather3Data[rank].nicFused), ret, fail);
allGather3Data[rank].localNetDeviceCount = localNetDeviceCount;
allGather3Data[rank].localCollNetCount = localCollNetCount;
allGather3Data[rank].localNetCountByBw = localNetCountByBw;
allGather3Data[rank].isMultiRankGpu = comm->isMultiRankGpu;
NCCLCHECKGOTO(ncclTopoGetMinNetBw(comm->topo, comm->rank, &allGather3Data[rank].minNetBw), ret, fail);

Expand All @@ -1811,6 +1818,7 @@ static ncclResult_t initTransportsRank(struct ncclComm* comm, struct ncclComm* p
NCCLCHECKGOTO(ncclCalloc(&nodesFirstRank, nranks), ret, fail);
NCCLCHECKGOTO(ncclCalloc(&nodesTreePatterns, nranks), ret, fail);
NCCLCHECKGOTO(ncclCalloc(&comm->rankToNode, comm->nRanks), ret, fail);
comm->minNetCount = INT_MAX;
for (int r=0; r<nranks; r++) {
int node;
int firstRank = allGather3Data[r].topoRanks.ringRecv[0];
Expand Down Expand Up @@ -1838,6 +1846,7 @@ static ncclResult_t initTransportsRank(struct ncclComm* comm, struct ncclComm* p
maxLocalNetCount = std::max(maxLocalNetCount, allGather3Data[r].localNetDeviceCount);
minLocalCollNetCount = std::min(minLocalCollNetCount, allGather3Data[r].localCollNetCount);
maxLocalCollNetCount = std::max(maxLocalCollNetCount, allGather3Data[r].localCollNetCount);
comm->minNetCount = std::min(comm->minNetCount, allGather3Data[r].localNetCountByBw);
if (allGather3Data[r].isMultiRankGpu) {
comm->isMultiRankGpu = true;
}
Expand Down
Loading
Loading