Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions projects/rccl/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Full documentation for RCCL is available at [https://rccl.readthedocs.io](https:
* `NCCL_TOPO_SCATTER_START_NET`, which selected how the first network device was scattered across GPUs. Rail and plane assignment replaces it.

### Resolved issues
* Fixed topology GPU indices that followed the HIP device order instead of the PCI order. This reordered rings and NIC selection, raising 16 KB to 1 MB `ncclGather` latency by up to 18% on multi-node MI355X systems, and made communicator initialization fail when each rank sees only its own GPU.
* Fixed profiler overhead when no profiler plugin is loaded or only kernel-channel events are enabled.
* Fixed profiler API events reporting rank 0 instead of the originating communicator rank.
* Fixed P2P IPC registration reuse producing out-of-bounds remote addresses when a registered allocation spans multiple cuMem segments.
Expand Down
6 changes: 3 additions & 3 deletions projects/rccl/src/graph/paths.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1191,12 +1191,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
14 changes: 7 additions & 7 deletions projects/rccl/src/graph/search.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1743,17 +1743,17 @@ 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,
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 @@ -1775,27 +1775,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
20 changes: 11 additions & 9 deletions projects/rccl/src/graph/xml.cc
Original file line number Diff line number Diff line change
Expand Up @@ -952,7 +952,7 @@ ncclResult_t ncclTopoGetXmlFromSys(struct ncclXmlNode* pciNode, struct ncclXml*
// at that BDF is missing or a different function. Link those HIP siblings with
// 1-hop XGMI even when SMI only enumerates the physical GPU.
static ncclResult_t ncclTopoXmlAddHipPartitionXgmi(struct ncclXml* xml, struct ncclXmlNode* gpuNode,
struct ncclXmlNode* pciNode, uint32_t xmlDev) {
struct ncclXmlNode* pciNode, uint32_t hipDev) {
const char* pciBusId = NULL;
NCCLCHECK(xmlGetAttr(pciNode, "busid", &pciBusId));
if (pciBusId == NULL) return ncclSuccess;
Expand All @@ -962,7 +962,7 @@ static ncclResult_t ncclTopoXmlAddHipPartitionXgmi(struct ncclXml* xml, struct n
int nHip = 0;
if (hipGetDeviceCount(&nHip) != hipSuccess) return ncclSuccess;
for (int h = 0; h < nHip; h++) {
if (h == (int)xmlDev) continue;
if (h == (int)hipDev) continue;
char peerBus[NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE];
if (hipDeviceGetPCIBusId(peerBus, sizeof(peerBus), h) != hipSuccess) continue;
int64_t peerId = 0;
Expand All @@ -985,18 +985,20 @@ static ncclResult_t ncclTopoXmlAddHipPartitionXgmi(struct ncclXml* xml, struct n
}
#endif

ncclResult_t ncclTopoGetXmlFromGpu(struct ncclXmlNode* pciNode, uint32_t xmlDev, uint32_t smiDev, struct ncclXml* xml,
ncclResult_t ncclTopoGetXmlFromGpu(struct ncclXmlNode* pciNode, uint32_t hipDev, uint32_t smiDev, struct ncclXml* xml,
struct ncclXmlNode** gpuNodeRet) {
struct ncclXmlNode* gpuNode = NULL;
NCCLCHECK(xmlGetSubKvInt(pciNode, "gpu", &gpuNode, "dev", (int)xmlDev));
// Processes learn about each other's GPUs only through XML fusion, so "dev" must mean the same in all
// of them. hipDev depends on each process's device visibility; the SMI index does not.
NCCLCHECK(xmlGetSubKvInt(pciNode, "gpu", &gpuNode, "dev", (int)smiDev));
if (gpuNode == NULL) NCCLCHECK(xmlAddNode(xml, pciNode, "gpu", &gpuNode));

int index = -1;

int dev = -1;
NCCLCHECK(xmlGetAttrIndex(gpuNode, "dev", &index));
if (index == -1) {
NCCLCHECK(xmlSetAttrInt(gpuNode, "dev", xmlDev));
NCCLCHECK(xmlSetAttrInt(gpuNode, "dev", smiDev));
Comment thread
amd-arozanov marked this conversation as resolved.
Resolved
}
NCCLCHECK(xmlGetAttrInt(gpuNode, "dev", &dev));
if (dev == -1) {
Expand All @@ -1007,7 +1009,7 @@ ncclResult_t ncclTopoGetXmlFromGpu(struct ncclXmlNode* pciNode, uint32_t xmlDev,
NCCLCHECK(xmlGetAttrIndex(gpuNode, "sm", &index));
if (index == -1) {
cudaDeviceProp devProp;
CUDACHECK(cudaGetDeviceProperties(&devProp, (int)xmlDev));
CUDACHECK(cudaGetDeviceProperties(&devProp, (int)hipDev));
NCCLCHECK(xmlSetAttrInt(gpuNode, "sm", devProp.multiProcessorCount));
}
int sm;
Expand All @@ -1018,7 +1020,7 @@ ncclResult_t ncclTopoGetXmlFromGpu(struct ncclXmlNode* pciNode, uint32_t xmlDev,
NCCLCHECK(xmlGetAttrIndex(gpuNode, "gcn", &index));
if (index == -1) {
hipDeviceProp_t devProp;
CUDACHECK(hipGetDeviceProperties(&devProp, (int)xmlDev));
CUDACHECK(hipGetDeviceProperties(&devProp, (int)hipDev));
// extract only the releveant info from the gcnArchName attribute
// e.g.: convert "gfx908:sramecc+:xnack-" to "gfx908"
char gcnArchNameSubstr[128];
Expand All @@ -1036,7 +1038,7 @@ ncclResult_t ncclTopoGetXmlFromGpu(struct ncclXmlNode* pciNode, uint32_t xmlDev,
NCCLCHECK(xmlGetAttrIndex(gpuNode, "arch", &index));
if (index == -1) {
hipDeviceProp_t devProp;
CUDACHECK(hipGetDeviceProperties(&devProp, (int)xmlDev));
CUDACHECK(hipGetDeviceProperties(&devProp, (int)hipDev));
memcpy(&arch.arch, &devProp.arch, sizeof(hipDeviceArch_t));
NCCLCHECK(xmlSetAttrInt(gpuNode, "arch", arch.value));
}
Expand Down Expand Up @@ -1240,7 +1242,7 @@ ncclResult_t ncclTopoGetXmlFromGpu(struct ncclXmlNode* pciNode, uint32_t xmlDev,
#endif
}
#if defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__)
NCCLCHECK(ncclTopoXmlAddHipPartitionXgmi(xml, gpuNode, pciNode, xmlDev));
NCCLCHECK(ncclTopoXmlAddHipPartitionXgmi(xml, gpuNode, pciNode, hipDev));
#endif
#if CUDART_VERSION >= 11080
struct ncclXmlNode* c2cNode = NULL;
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 @@ -86,7 +86,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,
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
Expand Down
2 changes: 1 addition & 1 deletion projects/rccl/src/transport.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ static ncclResult_t selectTransport(struct ncclComm* comm, struct ncclTopoGraph*
NCCLCHECK(ncclTopoGetIntraNetDev(comm->topo, peer, graph, channelId, (type == 1) ? 0 : 1, nullptr, &n2));
}
bool xgmi;
NCCLCHECK(ncclTopoGetLinkType(comm->topo, myInfo->cudaDev, peerInfo->cudaDev, &xgmi));
NCCLCHECK(ncclTopoGetLinkType(comm->topo, myInfo->nvmlDev, peerInfo->nvmlDev, &xgmi));
Comment thread
i-kosarev marked this conversation as resolved.
Resolved

for (int t = 0; t < NTRANSPORTS; t++) {
if (graph == NULL && connIndex == NCCL_CONN_IDX_P2P_NET && (t == TRANSPORT_SHM || (!xgmi && t == TRANSPORT_P2P)))
Expand Down
2 changes: 1 addition & 1 deletion projects/rccl/src/transport/p2p.cc
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ ncclResult_t p2pSendSetup(struct ncclComm* comm, struct ncclTopoGraph* graph, st

resources->next_hdp_reg = 0;
bool isXGMI;
if (ncclTopoGetLinkType(comm->topo, myInfo->cudaDev, peerInfo->cudaDev, &isXGMI) != ncclSuccess) {
if (ncclTopoGetLinkType(comm->topo, myInfo->nvmlDev, peerInfo->nvmlDev, &isXGMI) != ncclSuccess) {
INFO(NCCL_INIT | NCCL_P2P, "Ring %02d : %d -> %d failed to get link type and hop count", channelId, myInfo->rank,
peerInfo->rank);
return ncclInternalError;
Expand Down
1 change: 1 addition & 0 deletions projects/rccl/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,7 @@ if(BUILD_TESTS)
graph/XmlTests.cpp
graph/NetDevsPolicyP2pNetTests.cpp
graph/P2pPxnLevel1NetIdTests.cpp
graph/TopoGpuDevTests.cpp
graph/TopoTests.cpp
transport/NetIbCast/NetIbCastPlaneSubnetTests.cpp
transport/NetIbCast/NetIbCastMultiplaneTests.cpp
Expand Down
50 changes: 50 additions & 0 deletions projects/rccl/test/graph/TopoGpuDevTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*************************************************************************
* Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/

// Checks that the GPU nodes of a real topology carry the SMI index as `dev`, distinct per GPU.
// Needs at least two GPUs; on a host whose HIP order matches the SMI order it cannot fail.
Comment thread
i-kosarev marked this conversation as resolved.
Resolved

#include <gtest/gtest.h>
#include <rccl/rccl.h>

#include <set>
#include <vector>

#include "comm.h"
#include "graph/topo.h"
#include "../common/ProcessIsolatedTestRunner.hpp"

namespace RcclUnitTesting {

TEST(TopoGpuDev, OwnGpuDevIsNvmlDevAndUnique)
{
RUN_ISOLATED_TEST("TopoGpuDev_OwnGpuDevIsNvmlDevAndUnique", []() {
int numDevices = 0;
ASSERT_EQ(hipGetDeviceCount(&numDevices), hipSuccess);
if (numDevices < 2) GTEST_SKIP() << "Needs at least 2 GPUs";

std::vector<ncclComm_t> comms(numDevices, nullptr);
ASSERT_EQ(ncclCommInitAll(comms.data(), numDevices, nullptr), ncclSuccess);
Comment thread
i-kosarev marked this conversation as resolved.
Resolved
Comment thread
i-kosarev marked this conversation as resolved.
Resolved

for (ncclComm_t comm : comms) {
const struct ncclTopoNodeSet& gpus = comm->topo->nodes[GPU];
int ownDev = -1;
for (int gpuIndex = 0; gpuIndex < gpus.count; gpuIndex++) {
if (gpus.nodes[gpuIndex].gpu.rank == comm->rank) ownDev = gpus.nodes[gpuIndex].gpu.dev;
}
EXPECT_EQ(ownDev, comm->nvmlDev) << "rank " << comm->rank << " (HIP device " << comm->cudaDev << ")";
}

const struct ncclTopoNodeSet& gpus = comms[0]->topo->nodes[GPU];
std::set<int> devs;
for (int gpuIndex = 0; gpuIndex < gpus.count; gpuIndex++) devs.insert(gpus.nodes[gpuIndex].gpu.dev);
EXPECT_EQ(static_cast<int>(devs.size()), gpus.count) << "GPU nodes share a dev index";

for (ncclComm_t comm : comms) EXPECT_EQ(ncclCommDestroy(comm), ncclSuccess);
});
}

} // namespace RcclUnitTesting
Loading