2019-11-19 14:57:39 -08:00
/*************************************************************************
2022-01-07 06:39:55 -08:00
* Copyright (c) 2018-2022, NVIDIA CORPORATION. All rights reserved.
2023-02-04 01:43:38 +00:00
* Modifications Copyright (c) 2019-2023 Advanced Micro Devices, Inc. All rights reserved.
2019-11-19 14:57:39 -08:00
*
* See LICENSE.txt for license information
************************************************************************/
#include "core.h"
#include "graph.h"
#include "topo.h"
#include "comm.h"
#include "net.h"
2020-05-12 14:40:18 -07:00
#include "channel.h"
2023-11-22 15:07:36 -08:00
#include "xml.h"
2019-11-19 14:57:39 -08:00
// Pre-compute GPU->NIC, GPU->GPU and NIC->GPU paths
struct ncclTopoNodeList {
struct ncclTopoNode * list [ NCCL_TOPO_MAX_NODES ];
int count ;
};
static ncclResult_t getPath ( struct ncclTopoSystem * system , struct ncclTopoNode * node , int t , int64_t id , struct ncclTopoLinkList ** path ) {
for ( int i = 0 ; i < system -> nodes [ t ]. count ; i ++ ) {
if ( system -> nodes [ t ]. nodes [ i ]. id == id ) {
* path = node -> paths [ t ] + i ;
return ncclSuccess ;
}
}
2021-02-09 15:34:08 -08:00
WARN ( "Could not find node of type %d id %lx" , t , id );
2019-11-19 14:57:39 -08:00
return ncclInternalError ;
}
2021-05-11 18:16:30 -07:00
NCCL_PARAM ( NvbDisable , "NVB_DISABLE" , 0 );
2019-11-19 14:57:39 -08:00
static ncclResult_t ncclTopoSetPaths ( struct ncclTopoNode * baseNode , struct ncclTopoSystem * system ) {
if ( baseNode -> paths [ baseNode -> type ] == NULL ) {
NCCLCHECK ( ncclCalloc ( baseNode -> paths + baseNode -> type , system -> nodes [ baseNode -> type ]. count ));
}
// breadth-first search to set all paths to that node in the system
struct ncclTopoNodeList nodeList ;
struct ncclTopoNodeList nextNodeList ;
nodeList . count = 1 ; nodeList . list [ 0 ] = baseNode ;
nextNodeList . count = 0 ;
struct ncclTopoLinkList * basePath ;
NCCLCHECK ( getPath ( system , baseNode , baseNode -> type , baseNode -> id , & basePath ));
basePath -> count = 0 ;
2022-08-18 02:53:17 -07:00
basePath -> bw = LOC_BW ;
2020-01-16 16:02:42 -08:00
basePath -> type = PATH_LOC ;
2019-11-19 14:57:39 -08:00
while ( nodeList . count ) {
nextNodeList . count = 0 ;
for ( int n = 0 ; n < nodeList . count ; n ++ ) {
struct ncclTopoNode * node = nodeList . list [ n ];
struct ncclTopoLinkList * path ;
NCCLCHECK ( getPath ( system , node , baseNode -> type , baseNode -> id , & path ));
for ( int l = 0 ; l < node -> nlinks ; l ++ ) {
struct ncclTopoLink * link = node -> links + l ;
struct ncclTopoNode * remNode = link -> remNode ;
if ( remNode -> paths [ baseNode -> type ] == NULL ) {
NCCLCHECK ( ncclCalloc ( remNode -> paths + baseNode -> type , system -> nodes [ baseNode -> type ]. count ));
2024-03-26 06:08:55 -07:00
for ( int i = 0 ; i < system -> nodes [ baseNode -> type ]. count ; i ++ ) remNode -> paths [ baseNode -> type ][ i ]. type = PATH_DIS ;
2019-11-19 14:57:39 -08:00
}
struct ncclTopoLinkList * remPath ;
NCCLCHECK ( getPath ( system , remNode , baseNode -> type , baseNode -> id , & remPath ));
2022-08-18 02:53:17 -07:00
float bw = std :: min ( path -> bw , link -> bw );
2020-09-04 14:35:05 -07:00
// allow routing through a GPU only as 1 hop
if ( node != baseNode && node -> type == GPU &&
2021-05-11 18:16:30 -07:00
( ncclParamNvbDisable () || link -> type != LINK_NVL || remNode -> type != GPU || path -> count > 1 )) continue ;
2020-09-04 14:35:05 -07:00
2022-08-18 02:53:17 -07:00
if (( remPath -> bw == 0 || remPath -> count > path -> count ) && remPath -> bw < bw ) {
2019-11-19 14:57:39 -08:00
// Find reverse link
for ( int l = 0 ; l < remNode -> nlinks ; l ++ ) {
2023-09-26 05:47:28 -07:00
if ( remNode -> links [ l ]. remNode == node && remNode -> links [ l ]. type == link -> type ) {
2019-11-19 14:57:39 -08:00
remPath -> list [ 0 ] = remNode -> links + l ;
break ;
}
}
if ( remPath -> list [ 0 ] == NULL ) {
2020-01-16 16:02:42 -08:00
WARN ( "Failed to find reverse path from remNode %d/%lx nlinks %d to node %d/%lx" ,
remNode -> type , remNode -> id , remNode -> nlinks , node -> type , node -> id );
2019-11-19 14:57:39 -08:00
return ncclInternalError ;
}
// Copy the rest of the path
for ( int i = 0 ; i < path -> count ; i ++ ) remPath -> list [ i + 1 ] = path -> list [ i ];
remPath -> count = path -> count + 1 ;
2022-08-18 02:53:17 -07:00
remPath -> bw = bw ;
2019-11-19 14:57:39 -08:00
2020-01-16 16:02:42 -08:00
// Start with path type = link type. PATH and LINK types are supposed to match.
// Don't consider LINK_NET as we only care about the NIC->GPU path.
2020-09-04 14:35:05 -07:00
int type = link -> type == LINK_NET ? LINK_LOC : link -> type ;
2020-01-16 16:02:42 -08:00
// Differentiate between one and multiple PCI switches
2020-09-04 14:35:05 -07:00
if ( node -> type == PCI && remNode -> type == PCI ) type = PATH_PXB ;
2020-01-16 16:02:42 -08:00
// Consider a path going through the CPU as PATH_PHB
if ( link -> type == LINK_PCI && ( node -> type == CPU || link -> remNode -> type == CPU )) type = PATH_PHB ;
2020-09-04 14:35:05 -07:00
// Set 1 hop NVLink as NVB
2020-12-08 17:35:07 -05:00
//if (node->type == GPU && path->type == PATH_NVL && type == PATH_NVL && remPath->count > 1) type = PATH_NVB;
2020-01-16 16:02:42 -08:00
2019-11-19 14:57:39 -08:00
remPath -> type = std :: max ( path -> type , type );
// Add to the list for the next iteration if not already in the list
2020-12-08 17:35:07 -05:00
// Disallow GPUs as intermediate steps for now
if ( remNode -> type != GPU ) {
int i ;
for ( i = 0 ; i < nextNodeList . count ; i ++ ) if ( nextNodeList . list [ i ] == remNode ) break ;
if ( i == nextNodeList . count ) nextNodeList . list [ nextNodeList . count ++ ] = remNode ;
}
2019-11-19 14:57:39 -08:00
}
}
}
memcpy ( & nodeList , & nextNodeList , sizeof ( nodeList ));
}
return ncclSuccess ;
}
static void printNodePaths ( struct ncclTopoSystem * system , struct ncclTopoNode * node ) {
2024-10-02 09:29:22 -05:00
const int linesize = 2048 ;
2024-03-26 06:08:55 -07:00
char line [ linesize ];
2019-11-19 14:57:39 -08:00
#ifdef ENABLE_TRACE
INFO ( NCCL_GRAPH , "Paths from %s/%lX :" , topoNodeTypeStr [ node -> type ], node -> id );
#else
2024-03-26 06:08:55 -07:00
snprintf ( line , linesize , "%s/%lX :" , topoNodeTypeStr [ node -> type ], node -> id );
2019-11-19 14:57:39 -08:00
int offset = strlen ( line );
#endif
for ( int t = 0 ; t < NCCL_TOPO_NODE_TYPES ; t ++ ) {
if ( node -> paths [ t ] == NULL ) continue ;
for ( int n = 0 ; n < system -> nodes [ t ]. count ; n ++ ) {
#ifdef ENABLE_TRACE
line [ 0 ] = 0 ;
int offset = 0 ;
for ( int i = 0 ; i < node -> paths [ t ][ n ]. count ; i ++ ) {
struct ncclTopoLink * link = node -> paths [ t ][ n ]. list [ i ];
struct ncclTopoNode * remNode = link -> remNode ;
2024-03-26 06:08:55 -07:00
snprintf ( line + offset , linesize - offset , "--%s(%g)->%s/%lx-%lx" , topoLinkTypeStr [ link -> type ], link -> bw , topoNodeTypeStr [ remNode -> type ], NCCL_TOPO_ID_SYSTEM_ID ( remNode -> id ), NCCL_TOPO_ID_LOCAL_ID ( remNode -> id ));
2019-11-19 14:57:39 -08:00
offset = strlen ( line );
}
2022-08-18 02:53:17 -07:00
INFO ( NCCL_GRAPH , "%s (%f)" , line , node -> paths [ t ][ n ]. bw );
2019-11-19 14:57:39 -08:00
#else
2024-03-26 06:08:55 -07:00
snprintf ( line + offset , linesize - offset , "%s/%lx-%lx (%d/%.1f/%s) " , topoNodeTypeStr [ t ], NCCL_TOPO_ID_SYSTEM_ID ( system -> nodes [ t ]. nodes [ n ]. id ), NCCL_TOPO_ID_LOCAL_ID ( system -> nodes [ t ]. nodes [ n ]. id ), node -> paths [ t ][ n ]. count , node -> paths [ t ][ n ]. bw , topoPathTypeStr [ node -> paths [ t ][ n ]. type ]);
2019-11-19 14:57:39 -08:00
offset = strlen ( line );
#endif
}
}
#ifndef ENABLE_TRACE
INFO ( NCCL_GRAPH , "%s" , line );
#endif
}
ncclResult_t ncclTopoPrintPaths ( struct ncclTopoSystem * system ) {
for ( int i = 0 ; i < system -> nodes [ GPU ]. count ; i ++ ) {
printNodePaths ( system , system -> nodes [ GPU ]. nodes + i );
}
for ( int i = 0 ; i < system -> nodes [ NET ]. count ; i ++ ) {
printNodePaths ( system , system -> nodes [ NET ]. nodes + i );
}
return ncclSuccess ;
}
static ncclResult_t getLocalCpu ( struct ncclTopoSystem * system , int gpu , int * retCpu ) {
// Find the closest CPU to a GPU
int minHops = 0 ;
int localCpu = - 1 ;
struct ncclTopoLinkList * paths = system -> nodes [ GPU ]. nodes [ gpu ]. paths [ CPU ];
for ( int c = 0 ; c < system -> nodes [ CPU ]. count ; c ++ ) {
int hops = paths [ c ]. count ;
if ( minHops == 0 || hops < minHops ) {
localCpu = c ;
minHops = hops ;
}
}
if ( localCpu == - 1 ) {
WARN ( "Error : could not find CPU close to GPU %d" , gpu );
return ncclInternalError ;
}
* retCpu = localCpu ;
return ncclSuccess ;
}
2022-01-07 06:39:55 -08:00
static ncclResult_t addInterStep ( struct ncclTopoSystem * system , int tx , int ix , int t1 , int i1 , int t2 , int i2 ) {
struct ncclTopoNode * cpuNode = system -> nodes [ tx ]. nodes + ix ;
2019-11-19 14:57:39 -08:00
struct ncclTopoNode * srcNode = system -> nodes [ t1 ]. nodes + i1 ;
int l = 0 ;
// Node 1 -> CPU
2022-01-07 06:39:55 -08:00
for ( int i = 0 ; i < srcNode -> paths [ tx ][ ix ]. count ; i ++ ) srcNode -> paths [ t2 ][ i2 ]. list [ l ++ ] = srcNode -> paths [ tx ][ ix ]. list [ i ];
2019-11-19 14:57:39 -08:00
// CPU -> Node 2
for ( int i = 0 ; i < cpuNode -> paths [ t2 ][ i2 ]. count ; i ++ ) srcNode -> paths [ t2 ][ i2 ]. list [ l ++ ] = cpuNode -> paths [ t2 ][ i2 ]. list [ i ];
// Update path characteristics
srcNode -> paths [ t2 ][ i2 ]. count = l ;
2022-01-07 06:39:55 -08:00
srcNode -> paths [ t2 ][ i2 ]. type = std :: max ( srcNode -> paths [ tx ][ ix ]. type , cpuNode -> paths [ t2 ][ i2 ]. type );
if ( tx == GPU ) srcNode -> paths [ t2 ][ i2 ]. type = PATH_PXN ;
2022-08-18 02:53:17 -07:00
srcNode -> paths [ t2 ][ i2 ]. bw = std :: min ( srcNode -> paths [ tx ][ ix ]. bw , cpuNode -> paths [ t2 ][ i2 ]. bw );
2019-11-19 14:57:39 -08:00
return ncclSuccess ;
}
// Remove/free paths for a given type
static void ncclTopoRemovePathType ( struct ncclTopoSystem * system , int nodeType ) {
for ( int t = 0 ; t < NCCL_TOPO_NODE_TYPES ; t ++ ) {
2020-01-07 13:29:57 -08:00
// Remove links _to_ the given type
2019-11-19 14:57:39 -08:00
for ( int n = 0 ; n < system -> nodes [ t ]. count ; n ++ ) {
struct ncclTopoNode * node = system -> nodes [ t ]. nodes + n ;
free ( node -> paths [ nodeType ]);
node -> paths [ nodeType ] = NULL ;
}
2020-01-07 13:29:57 -08:00
// Remove links _from_ the given type
for ( int n = 0 ; n < system -> nodes [ nodeType ]. count ; n ++ ) {
struct ncclTopoNode * node = system -> nodes [ nodeType ]. nodes + n ;
free ( node -> paths [ t ]);
node -> paths [ t ] = NULL ;
}
2019-11-19 14:57:39 -08:00
}
}
2020-01-16 16:02:42 -08:00
static const int levelsOldToNew [] = { PATH_LOC , PATH_PIX , PATH_PXB , PATH_PHB , PATH_SYS , PATH_SYS };
ncclResult_t ncclGetLevel ( int * level , const char * disableEnv , const char * levelEnv ) {
if ( * level == - 1 ) {
int l = - 1 ;
if ( disableEnv ) {
2023-09-26 05:47:28 -07:00
const char * str = ncclGetEnv ( disableEnv );
2020-01-16 16:02:42 -08:00
if ( str ) {
int disable = strtol ( str , NULL , 0 );
if ( disable == 1 ) l = 0 ;
}
}
if ( l == - 1 ) {
2023-09-26 05:47:28 -07:00
const char * str = ncclGetEnv ( levelEnv );
2020-01-16 16:02:42 -08:00
if ( str ) {
2020-09-04 14:35:05 -07:00
for ( int i = 0 ; i <= PATH_SYS ; i ++ ) {
2020-01-16 16:02:42 -08:00
if ( strcmp ( str , topoPathTypeStr [ i ]) == 0 ) {
l = i ;
break ;
}
}
// Old style numbering
2022-05-03 01:30:26 -07:00
// levelsOldToNew to is an array with each index corresponding to the
// "old level" int, and each value mapping to the correct value defined in topo.h
// maxOldLevel is a quick check to handle out of bounds (based on the length of levelsOldToNew)
2020-01-16 16:02:42 -08:00
if ( l == - 1 && str [ 0 ] >= '0' && str [ 0 ] <= '9' ) {
int oldLevel = strtol ( str , NULL , 0 );
const int maxOldLevel = sizeof ( levelsOldToNew ) / sizeof ( int ) - 1 ;
if ( oldLevel > maxOldLevel ) oldLevel = maxOldLevel ;
l = levelsOldToNew [ oldLevel ];
}
}
}
2020-05-12 14:40:18 -07:00
if ( l >= 0 ) INFO ( NCCL_ALL , "%s set by environment to %s" , levelEnv , topoPathTypeStr [ l ]);
2020-01-16 16:02:42 -08:00
* level = l >= 0 ? l : - 2 ;
}
return ncclSuccess ;
}
2022-01-07 06:39:55 -08:00
NCCL_PARAM ( IgnoreDisabledP2p , "IGNORE_DISABLED_P2P" , 0 );
2020-01-16 16:02:42 -08:00
int ncclTopoUserP2pLevel = - 1 ;
2020-09-04 14:35:05 -07:00
ncclResult_t ncclTopoCheckP2p ( struct ncclTopoSystem * system , int64_t id1 , int64_t id2 , int * p2p , int * read , int * intermediateRank ) {
2020-01-16 16:02:42 -08:00
* p2p = 0 ;
2020-09-04 14:35:05 -07:00
if ( read ) * read = 0 ;
if ( intermediateRank ) * intermediateRank = - 1 ;
2020-01-16 16:02:42 -08:00
// Get GPUs from topology
int g1 , g2 ;
NCCLCHECK ( ncclTopoIdToIndex ( system , GPU , id1 , & g1 ));
struct ncclTopoNode * gpu1 = system -> nodes [ GPU ]. nodes + g1 ;
if ( ncclTopoIdToIndex ( system , GPU , id2 , & g2 ) == ncclInternalError ) {
// GPU not found, we can't use p2p.
return ncclSuccess ;
}
2020-09-04 14:35:05 -07:00
2022-01-07 06:39:55 -08:00
int intermediateIndex = - 1 ;
2020-09-04 14:35:05 -07:00
// Set intermediate GPU rank, if routing through an intermediate GPU.
2020-01-16 16:02:42 -08:00
struct ncclTopoLinkList * path = gpu1 -> paths [ GPU ] + g2 ;
2020-09-04 14:35:05 -07:00
if ( path -> count == 2 ) {
struct ncclTopoNode * intermediateNode = path -> list [ 0 ] -> remNode ;
2022-01-07 06:39:55 -08:00
if ( intermediateNode -> type == GPU ) {
intermediateIndex = intermediateNode - system -> nodes [ GPU ]. nodes ;
if ( intermediateRank ) * intermediateRank = intermediateNode -> gpu . rank ;
2020-09-04 14:35:05 -07:00
}
}
2020-01-16 16:02:42 -08:00
// In general, use P2P whenever we can.
int p2pLevel = PATH_SYS ;
2020-05-12 14:40:18 -07:00
// User override
if ( ncclTopoUserP2pLevel == - 1 )
NCCLCHECK ( ncclGetLevel ( & ncclTopoUserP2pLevel , "NCCL_P2P_DISABLE" , "NCCL_P2P_LEVEL" ));
if ( ncclTopoUserP2pLevel != - 2 ) {
p2pLevel = ncclTopoUserP2pLevel ;
goto compare ;
}
2020-01-16 16:02:42 -08:00
// Don't use P2P through ARM CPUs
int arch , vendor , model ;
NCCLCHECK ( ncclTopoCpuType ( system , & arch , & vendor , & model ));
if ( arch == NCCL_TOPO_CPU_ARCH_ARM ) p2pLevel = PATH_PXB ;
2020-05-12 14:40:18 -07:00
if ( arch == NCCL_TOPO_CPU_ARCH_X86 && vendor == NCCL_TOPO_CPU_VENDOR_INTEL ) {
2021-04-12 16:00:11 -07:00
p2pLevel = PATH_PXB ;
2020-05-12 14:40:18 -07:00
}
2020-11-27 16:20:55 +08:00
if ( arch == NCCL_TOPO_CPU_ARCH_X86 && vendor == NCCL_TOPO_CPU_VENDOR_ZHAOXIN ) {
p2pLevel = PATH_PXB ;
}
2020-01-16 16:02:42 -08:00
2020-05-12 14:40:18 -07:00
compare :
2020-01-16 16:02:42 -08:00
// Compute the PCI distance and compare with the p2pLevel.
if ( path -> type <= p2pLevel ) * p2p = 1 ;
2024-08-14 15:04:13 -06:00
#if defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__)
2022-04-18 11:14:51 -07:00
#else
2022-01-07 06:39:55 -08:00
if ( * p2p == 1 ) {
// NCCL_IGNORE_DISABLED_P2P=2 is used by unit tests that don't want to
// validate against NVML at all since they are pretending to be on other hw.
if ( g1 != g2 && ncclParamIgnoreDisabledP2p () != 2 ) {
int indexes [ 3 ] = { - 1 , - 1 , - 1 };
int verticeN = 0 ;
NCCLCHECK ( ncclNvmlEnsureInitialized ());
indexes [ verticeN ++ ] = system -> nodes [ GPU ]. nodes [ g1 ]. gpu . dev ;
if ( intermediateIndex != - 1 ) indexes [ verticeN ++ ] = system -> nodes [ GPU ]. nodes [ intermediateIndex ]. gpu . dev ;
indexes [ verticeN ++ ] = system -> nodes [ GPU ]. nodes [ g2 ]. gpu . dev ;
for ( int i = 1 ; i < verticeN ; i ++ ) {
nvmlGpuP2PStatus_t status ;
status = ncclNvmlDevicePairs [ indexes [ i - 1 ]][ indexes [ i - 0 ]]. p2pStatusRead ;
bool good = status == NVML_P2P_STATUS_OK ;
status = ncclNvmlDevicePairs [ indexes [ i - 1 ]][ indexes [ i - 0 ]]. p2pStatusWrite ;
good &= status == NVML_P2P_STATUS_OK ;
if ( ! good ) {
2023-09-26 05:47:28 -07:00
if ( ! ncclParamIgnoreDisabledP2p ()) {
if ( path -> type <= PATH_NVB ) {
WARN ( "P2P is disabled between NVLINK connected GPUs %d and %d. This should not be the case given their connectivity, and is probably due to a hardware issue. If you still want to proceed, you can set NCCL_IGNORE_DISABLED_P2P=1." , indexes [ i - 1 ], indexes [ i - 0 ]);
return ncclUnhandledCudaError ;
} else if ( path -> type < PATH_SYS ) {
INFO ( NCCL_INIT , "P2P is disabled between connected GPUs %d and %d. You can repress this message with NCCL_IGNORE_DISABLED_P2P=1." , indexes [ i - 1 ], indexes [ i - 0 ]);
}
2022-01-07 06:39:55 -08:00
}
2023-09-26 05:47:28 -07:00
* p2p = 0 ;
2022-01-07 06:39:55 -08:00
}
}
}
}
2022-04-18 11:14:51 -07:00
#endif
2022-01-07 06:39:55 -08:00
2020-05-12 14:40:18 -07:00
if ( path -> type == PATH_NVL ) {
struct ncclTopoNode * gpu2 = system -> nodes [ GPU ]. nodes + g2 ;
// Enable P2P Read for Ampere/NVLink only
2020-09-04 14:35:05 -07:00
if ( read && ( gpu1 -> gpu . cudaCompCap == gpu2 -> gpu . cudaCompCap ) && ( gpu1 -> gpu . cudaCompCap == 80 )) * read = 1 ;
2020-05-12 14:40:18 -07:00
}
2020-01-16 16:02:42 -08:00
return ncclSuccess ;
}
2024-02-05 05:06:02 -08:00
// MNNVL: Check whether peers are in the same fabric cluster and clique
ncclResult_t ncclTopoCheckMNNVL ( struct ncclTopoSystem * system , struct ncclPeerInfo * info1 , struct ncclPeerInfo * info2 , int * ret ) {
* ret = 0 ;
nvmlGpuFabricInfoV_t * fabricInfo1 = & info1 -> fabricInfo ;
nvmlGpuFabricInfoV_t * fabricInfo2 = & info2 -> fabricInfo ;
// A zero UUID means we don't have MNNVL fabric info
if (((( long * ) & fabricInfo2 -> clusterUuid )[ 0 ] | (( long * ) fabricInfo2 -> clusterUuid )[ 1 ]) == 0 ) return ncclSuccess ;
if (( memcmp ( fabricInfo1 -> clusterUuid , fabricInfo2 -> clusterUuid , NVML_GPU_FABRIC_UUID_LEN ) == 0 ) &&
( fabricInfo1 -> cliqueId == fabricInfo2 -> cliqueId )) {
INFO ( NCCL_NET , "MNNVL matching peer 0x%lx UUID %lx.%lx cliqueId 0x%x" ,
info2 -> busId , (( long * ) fabricInfo2 -> clusterUuid )[ 0 ], (( long * ) fabricInfo2 -> clusterUuid )[ 1 ], fabricInfo2 -> cliqueId );
* ret = 1 ;
}
return ncclSuccess ;
}
2020-01-16 16:02:42 -08:00
NCCL_PARAM ( NetGdrRead , "NET_GDR_READ" , - 2 );
int ncclTopoUserGdrLevel = - 1 ;
2024-03-26 06:08:55 -07:00
ncclResult_t ncclTopoCheckGdr ( struct ncclTopoSystem * system , int64_t busId , int64_t netId , int read , int * useGdr ) {
2020-01-16 16:02:42 -08:00
* useGdr = 0 ;
// Get GPU and NET
int n , g ;
2024-03-26 06:08:55 -07:00
NCCLCHECK ( ncclTopoIdToIndex ( system , NET , netId , & n ));
2020-01-16 16:02:42 -08:00
struct ncclTopoNode * net = system -> nodes [ NET ]. nodes + n ;
NCCLCHECK ( ncclTopoIdToIndex ( system , GPU , busId , & g ));
struct ncclTopoNode * gpu = system -> nodes [ GPU ]. nodes + g ;
// Check that both the NIC and GPUs support it
if ( net -> net . gdrSupport == 0 ) return ncclSuccess ;
if ( gpu -> gpu . gdrSupport == 0 ) return ncclSuccess ;
if ( read ) { // For reads (sends) only enable under certain conditions
int gdrReadParam = ncclParamNetGdrRead ();
if ( gdrReadParam == 0 ) return ncclSuccess ;
2024-08-14 15:04:13 -06:00
#if defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__)
2024-05-03 17:57:16 -07:00
#else
2023-09-26 05:47:28 -07:00
// Disable GDR Reads pre-Ampere when we have other PCI flows
if ( gdrReadParam < 0 && gpu -> gpu . cudaCompCap < 80 ) {
2020-01-16 16:02:42 -08:00
int nvlink = 0 ;
// Since we don't know whether there are other communicators,
// it's better to keep things local if we have a single GPU.
if ( system -> nodes [ GPU ]. count == 1 ) nvlink = 1 ;
for ( int i = 0 ; i < system -> nodes [ GPU ]. count ; i ++ ) {
if ( i == g ) continue ;
if ( gpu -> paths [ GPU ][ i ]. type == PATH_NVL ) {
nvlink = 1 ;
break ;
}
}
if ( ! nvlink ) return ncclSuccess ;
}
2024-05-03 17:57:16 -07:00
#endif
2020-01-16 16:02:42 -08:00
}
// Check if we are close enough that it makes sense to enable GDR
2021-11-05 08:53:47 -07:00
int netGdrLevel = system -> netGdrLevel == - 2 ? PATH_PXB : system -> netGdrLevel ;
2020-01-16 16:02:42 -08:00
NCCLCHECK ( ncclGetLevel ( & ncclTopoUserGdrLevel , NULL , "NCCL_NET_GDR_LEVEL" ));
if ( ncclTopoUserGdrLevel != - 2 ) netGdrLevel = ncclTopoUserGdrLevel ;
2020-11-03 14:29:08 -08:00
else {
int arch , vendor , model ;
NCCLCHECK ( ncclTopoCpuType ( system , & arch , & vendor , & model ));
2021-02-22 16:46:45 -05:00
if ( arch == NCCL_TOPO_CPU_ARCH_X86 && vendor == NCCL_TOPO_CPU_VENDOR_AMD && model == NCCL_TOPO_CPU_TYPE_ROME ) {
int i , d1 = - 1 , d2 = - 1 ;
for ( i = 0 ; i < system -> nodes [ CPU ]. count ; i ++ )
if ( system -> nodes [ GPU ]. nodes [ g ]. paths [ CPU ][ i ]. count == 2 ) break ;
if ( i < system -> nodes [ CPU ]. count ) d1 = system -> nodes [ CPU ]. nodes [ i ]. id ;
for ( i = 0 ; i < system -> nodes [ CPU ]. count ; i ++ )
if ( system -> nodes [ NET ]. nodes [ n ]. paths [ CPU ][ i ]. count == 2 ) break ;
if ( i < system -> nodes [ CPU ]. count ) d2 = system -> nodes [ CPU ]. nodes [ i ]. id ;
if ( d1 != - 1 && d2 != - 1 && d1 == d2 &&
( system -> nodes [ GPU ]. nodes [ g ]. id & 0xf0000 ) == ( system -> nodes [ NET ]. nodes [ n ]. net . busId & 0xf0000 )) {
netGdrLevel = PATH_PHB ;
}
}
2020-11-03 14:29:08 -08:00
}
2020-01-16 16:02:42 -08:00
int distance = gpu -> paths [ NET ][ n ]. type ;
2022-01-07 06:39:55 -08:00
if ( distance == PATH_PXN ) {
// In case of PXN, use the intermediate GPU distance instead
int proxyRank , g ;
2024-03-26 06:08:55 -07:00
NCCLCHECK ( ncclTopoGetIntermediateRank ( system , gpu -> gpu . rank , netId , & proxyRank ));
2022-01-07 06:39:55 -08:00
NCCLCHECK ( ncclTopoRankToIndex ( system , proxyRank , & g ));
struct ncclTopoNode * proxyGpu = system -> nodes [ GPU ]. nodes + g ;
distance = proxyGpu -> paths [ NET ][ n ]. type ;
}
2020-01-16 16:02:42 -08:00
if ( distance > netGdrLevel ) {
2024-03-26 06:08:55 -07:00
INFO ( NCCL_NET , "GPU Direct RDMA Disabled for GPU %lx / HCA %lx (distance %d > %d)" , busId , netId , distance , netGdrLevel );
2020-01-16 16:02:42 -08:00
return ncclSuccess ;
}
* useGdr = 1 ;
2024-03-26 06:08:55 -07:00
INFO ( NCCL_NET , "GPU Direct RDMA Enabled for GPU %lx / HCA %lx (distance %d <= %d), read %d" , busId , netId , distance , netGdrLevel , read );
2020-01-16 16:02:42 -08:00
return ncclSuccess ;
}
2022-09-27 02:31:13 -07:00
// Set to 0 to disable the flush on Hopper when using GDR
2023-09-26 05:47:28 -07:00
NCCL_PARAM ( NetForceFlush , "NET_FORCE_FLUSH" , 0 );
2022-09-27 02:31:13 -07:00
// Determine whether we need to flush the GDR recv buffers
ncclResult_t ncclTopoNeedFlush ( struct ncclTopoSystem * system , int64_t busId , int * flush ) {
int g ;
NCCLCHECK ( ncclTopoIdToIndex ( system , GPU , busId , & g ));
struct ncclTopoNode * gpu = system -> nodes [ GPU ]. nodes + g ;
2024-08-14 15:04:13 -06:00
#if defined(__HIP_PLATFORM_AMD__) || defined(__HIPCC__)
2024-10-15 08:12:26 -07:00
* flush = 1 ;
2024-05-03 17:57:16 -07:00
#else
2022-09-27 02:31:13 -07:00
// Flush is required on Ampere and earlier
* flush = gpu -> gpu . cudaCompCap < 90 ? 1 : ncclParamNetForceFlush ();
2024-05-03 17:57:16 -07:00
#endif
2022-09-27 02:31:13 -07:00
return ncclSuccess ;
}
2022-10-20 15:40:03 +00:00
NCCL_PARAM ( NetDisableIntra , "NET_DISABLE_INTRA" , 1 );
2022-08-18 02:53:17 -07:00
// Check whether going through the network would be faster than going through P2P/SHM.
ncclResult_t ncclTopoCheckNet ( struct ncclTopoSystem * system , int64_t id1 , int64_t id2 , int * net ) {
if ( ncclParamNetDisableIntra () == 1 ) {
* net = 0 ;
return ncclSuccess ;
}
* net = 1 ;
// First check the current GPU-to-GPU speed.
int g1 , g2 ;
if ( ncclTopoIdToIndex ( system , GPU , id1 , & g1 ) != ncclSuccess ||
ncclTopoIdToIndex ( system , GPU , id2 , & g2 ) != ncclSuccess ) {
return ncclSuccess ;
}
struct ncclTopoNode * gpu1 = system -> nodes [ GPU ]. nodes + g1 ;
struct ncclTopoNode * gpu2 = system -> nodes [ GPU ]. nodes + g2 ;
float speed = gpu1 -> paths [ GPU ][ g2 ]. bw ;
// Now check the speed each GPU can access the network through PXB or better
float netSpeed1 = 0 , netSpeed2 = 0 ;
for ( int n = 0 ; n < system -> nodes [ NET ]. count ; n ++ ) {
struct ncclTopoLinkList * path = gpu1 -> paths [ NET ] + n ;
if ( path -> type <= PATH_PXB && path -> bw > netSpeed1 ) netSpeed1 = path -> bw ;
path = gpu2 -> paths [ NET ] + n ;
if ( path -> type <= PATH_PXB && path -> bw > netSpeed2 ) netSpeed2 = path -> bw ;
}
if ( netSpeed1 > speed && netSpeed2 > speed ) return ncclSuccess ;
* net = 0 ;
return ncclSuccess ;
}
2024-03-26 06:08:55 -07:00
ncclResult_t ncclTopoGetIntermediateRank ( struct ncclTopoSystem * system , int rank , int64_t netId , int * intermediateRank ) {
2022-01-07 06:39:55 -08:00
// Get GPU and NET
int n , g ;
2024-03-26 06:08:55 -07:00
NCCLCHECK ( ncclTopoIdToIndex ( system , NET , netId , & n ));
2022-01-07 06:39:55 -08:00
NCCLCHECK ( ncclTopoRankToIndex ( system , rank , & g ));
struct ncclTopoNode * gpu = system -> nodes [ GPU ]. nodes + g ;
struct ncclTopoLinkList * path = gpu -> paths [ NET ] + n ;
if ( path -> type == PATH_PXN ) {
struct ncclTopoNode * node ;
int type = NVS ;
for ( int i = 0 ; i < path -> count && type == NVS ; i ++ ) {
node = path -> list [ i ] -> remNode ;
type = node -> type ;
}
if ( type != GPU ) {
2024-03-26 06:08:55 -07:00
WARN ( "Could not find intermediate GPU between GPU rank %d and NIC %lx" , rank , netId );
2022-01-07 06:39:55 -08:00
return ncclInternalError ;
}
* intermediateRank = node -> gpu . rank ;
} else {
* intermediateRank = rank ;
}
return ncclSuccess ;
}
2022-04-18 11:14:51 -07:00
NCCL_PARAM ( PxnDisable , "PXN_DISABLE" , 1 );
2022-01-07 06:39:55 -08:00
// Net v4 plugins don't have non-blocking connect/accept. We can't therefore use
// remote proxies without risking deadlocks
2022-05-24 02:02:31 -07:00
int ncclPxnDisable ( struct ncclComm * comm ) {
2022-01-07 06:39:55 -08:00
static int pxnDisable = - 1 ;
if ( pxnDisable == - 1 ) {
2022-05-24 02:02:31 -07:00
if ( comm && ncclNetVersion ( comm ) == 4 ) {
2022-01-07 06:39:55 -08:00
INFO ( NCCL_INIT , "PXN Disabled as plugin is v4" );
pxnDisable = 1 ;
} else {
pxnDisable = ncclParamPxnDisable ();
}
}
return pxnDisable ;
}
ncclResult_t ncclTopoGetPxnRanks ( struct ncclComm * comm , int ** intermediateRanks , int * nranks ) {
struct ncclTopoSystem * system = comm -> topo ;
* nranks = 0 ;
* intermediateRanks = NULL ;
if ( system -> nodes [ NET ]. count == 0 ) return ncclSuccess ;
int nr = 0 ;
int * ranks = NULL ;
for ( int rank = 0 ; rank < comm -> nRanks ; rank ++ ) {
2024-03-26 06:08:55 -07:00
int64_t netId ;
int proxyRank ;
NCCLCHECK ( ncclTopoGetNetDev ( comm , comm -> rank , NULL , 0 , rank , & netId , NULL , & proxyRank ));
2022-01-07 06:39:55 -08:00
if ( proxyRank == comm -> rank ) continue ;
int useGdr ;
2024-03-26 06:08:55 -07:00
NCCLCHECK ( ncclTopoCheckGdr ( comm -> topo , comm -> busId , netId , 1 , & useGdr ));
2022-01-07 06:39:55 -08:00
if ( useGdr == 0 ) continue ;
int found = 0 ;
for ( int r = 0 ; r < nr ; r ++ ) {
if ( ranks [ r ] == proxyRank ) found = 1 ;
}
if ( ! found ) {
NCCLCHECK ( ncclRealloc ( & ranks , nr , nr + 1 ));
ranks [ nr ++ ] = proxyRank ;
}
}
* nranks = nr ;
* intermediateRanks = ranks ;
return ncclSuccess ;
}
2023-11-22 15:07:36 -08:00
static bool rcclPathOverride ( struct ncclTopoSystem * system , uint64_t distance ) {
int i , j ;
for ( i = 0 ; i < system -> nodes [ GPU ]. count ; i ++ ) {
for ( j = 0 ; j < system -> nodes [ NET ]. count ; j ++ ) {
2023-12-01 10:00:17 -06:00
if (( system -> nodes [ NET ]. nodes [ j ]. net . busId - system -> nodes [ GPU ]. nodes [ i ]. id == distance ) || ( system -> nodes [ GPU ]. nodes [ i ]. id - system -> nodes [ NET ]. nodes [ j ]. net . busId == distance ))
2023-11-22 15:07:36 -08:00
break ;
}
if ( j >= system -> nodes [ NET ]. count )
break ;
}
if ( i >= system -> nodes [ GPU ]. count ) {
for ( i = 0 ; i < system -> nodes [ GPU ]. count ; i ++ ) {
for ( j = 0 ; j < system -> nodes [ NET ]. count ; j ++ ) {
2023-12-01 10:00:17 -06:00
if (( system -> nodes [ NET ]. nodes [ j ]. net . busId - system -> nodes [ GPU ]. nodes [ i ]. id == distance ) || ( system -> nodes [ GPU ]. nodes [ i ]. id - system -> nodes [ NET ]. nodes [ j ]. net . busId == distance ))
2023-11-22 15:07:36 -08:00
system -> nodes [ GPU ]. nodes [ i ]. paths [ NET ][ j ]. type = PATH_PXB ;
}
}
return true ;
} else {
return false ;
}
}
2022-05-24 02:02:31 -07:00
ncclResult_t ncclTopoComputePaths ( struct ncclTopoSystem * system , struct ncclComm * comm ) {
2019-11-19 14:57:39 -08:00
// Precompute paths between GPUs/NICs.
// Remove everything in case we're re-computing
for ( int t = 0 ; t < NCCL_TOPO_NODE_TYPES ; t ++ ) ncclTopoRemovePathType ( system , t );
2022-08-18 02:53:17 -07:00
// Set direct paths to CPUs. We need them in many cases.
2019-11-19 14:57:39 -08:00
for ( int c = 0 ; c < system -> nodes [ CPU ]. count ; c ++ ) {
NCCLCHECK ( ncclTopoSetPaths ( system -> nodes [ CPU ]. nodes + c , system ));
}
2022-08-18 02:53:17 -07:00
// Set direct paths to GPUs.
2019-11-19 14:57:39 -08:00
for ( int g = 0 ; g < system -> nodes [ GPU ]. count ; g ++ ) {
NCCLCHECK ( ncclTopoSetPaths ( system -> nodes [ GPU ]. nodes + g , system ));
2022-08-18 02:53:17 -07:00
}
2019-11-19 14:57:39 -08:00
2022-08-18 02:53:17 -07:00
// Set direct paths to NICs.
for ( int n = 0 ; n < system -> nodes [ NET ]. count ; n ++ ) {
NCCLCHECK ( ncclTopoSetPaths ( system -> nodes [ NET ]. nodes + n , system ));
}
2023-04-03 05:32:07 -07:00
// Set direct paths to NVSwitches.
for ( int n = 0 ; n < system -> nodes [ NVS ]. count ; n ++ ) {
NCCLCHECK ( ncclTopoSetPaths ( system -> nodes [ NVS ]. nodes + n , system ));
}
2022-08-18 02:53:17 -07:00
// Update path for GPUs when we don't want to / can't use GPU Direct P2P
for ( int g = 0 ; g < system -> nodes [ GPU ]. count ; g ++ ) {
2019-11-19 14:57:39 -08:00
for ( int p = 0 ; p < system -> nodes [ GPU ]. count ; p ++ ) {
2020-09-04 14:35:05 -07:00
int p2p ;
NCCLCHECK ( ncclTopoCheckP2p ( system , system -> nodes [ GPU ]. nodes [ p ]. id , system -> nodes [ GPU ]. nodes [ g ]. id , & p2p , NULL , NULL ));
2019-11-19 14:57:39 -08:00
if ( p2p == 0 ) {
2020-01-16 16:02:42 -08:00
// Divert all traffic through the CPU
int cpu ;
NCCLCHECK ( getLocalCpu ( system , g , & cpu ));
2022-01-07 06:39:55 -08:00
NCCLCHECK ( addInterStep ( system , CPU , cpu , GPU , p , GPU , g ));
2020-01-16 16:02:42 -08:00
}
}
2022-05-24 02:02:31 -07:00
if ( comm == NULL ) continue ;
2022-08-18 02:53:17 -07:00
// Remove GPUs we can't (or don't want to) communicate with through P2P or SHM
2022-05-24 02:02:31 -07:00
struct ncclPeerInfo * dstInfo = comm -> peerInfo + system -> nodes [ GPU ]. nodes [ g ]. gpu . rank ;
2020-01-16 16:02:42 -08:00
for ( int p = 0 ; p < system -> nodes [ GPU ]. count ; p ++ ) {
if ( p == g ) continue ;
2022-05-24 02:02:31 -07:00
struct ncclPeerInfo * srcInfo = comm -> peerInfo + system -> nodes [ GPU ]. nodes [ p ]. gpu . rank ;
2021-07-08 14:12:04 -07:00
int p2p ;
2022-05-24 02:02:31 -07:00
NCCLCHECK ( ncclTransports [ TRANSPORT_P2P ] -> canConnect ( & p2p , system , NULL , srcInfo , dstInfo ));
2022-08-18 02:53:17 -07:00
if ( p2p == 0 ) {
int shm ;
NCCLCHECK ( ncclTransports [ TRANSPORT_SHM ] -> canConnect ( & shm , system , NULL , srcInfo , dstInfo ));
if ( shm == 0 ) {
// Mark this peer as inaccessible. We'll trim it later.
2023-04-03 05:32:07 -07:00
system -> nodes [ GPU ]. nodes [ p ]. paths [ GPU ][ g ]. type = PATH_NET ;
2022-08-18 02:53:17 -07:00
}
2019-11-19 14:57:39 -08:00
}
}
}
2023-11-22 15:07:36 -08:00
// Special handling of gfx94x
#if !defined(TOPO_EXPL)
char strValue [ 1024 ];
NCCLCHECK ( ncclTopoGetStrFromSys ( "/sys/devices/virtual/dmi/id" , "bios_version" , strValue ));
if ( strncmp ( "Hyper-V UEFI Release" , strValue , 20 ) == 0 ) {
#endif
int arch , vendor , model ;
NCCLCHECK ( ncclTopoCpuType ( system , & arch , & vendor , & model ));
if ( arch == NCCL_TOPO_CPU_ARCH_X86 && vendor == NCCL_TOPO_CPU_VENDOR_INTEL &&
IsArchMatch ( system -> nodes [ GPU ]. nodes [ 0 ]. gpu . gcn , "gfx94" ) &&
(( system -> nodes [ GPU ]. count == 8 && system -> nodes [ NET ]. count == 8 && system -> nodes [ GPU ]. count == system -> nRanks ) ||
( system -> nodes [ GPU ]. count != system -> nRanks ))) {
2024-01-10 08:01:37 -08:00
if ( ! rcclPathOverride ( system , 0x100000 ) && ! rcclPathOverride ( system , 0x1000 ))
rcclPathOverride ( system , 0xff00000 );
2023-11-22 15:07:36 -08:00
}
#if !defined(TOPO_EXPL)
}
#endif
2022-08-18 02:53:17 -07:00
// Update paths for NICs (no GPU Direct, PXN, ...)
2019-11-19 14:57:39 -08:00
for ( int n = 0 ; n < system -> nodes [ NET ]. count ; n ++ ) {
struct ncclTopoNode * netNode = system -> nodes [ NET ]. nodes + n ;
for ( int g = 0 ; g < system -> nodes [ GPU ]. count ; g ++ ) {
2022-01-07 06:39:55 -08:00
// Check whether we can access the NIC through another NVLink-connected GPU (PXN)
struct ncclTopoNode * gpu = system -> nodes [ GPU ]. nodes + g ;
2022-08-18 02:53:17 -07:00
if ( ncclPxnDisable ( comm ) != 1 ) {
2023-04-03 05:32:07 -07:00
int localGpuIndex ;
2024-03-26 06:08:55 -07:00
NCCLCHECK ( ncclTopoGetLocalGpu ( system , netNode -> id , & localGpuIndex ));
2023-04-03 05:32:07 -07:00
if ( localGpuIndex != g && localGpuIndex != - 1 ) {
2022-05-03 01:30:26 -07:00
// PXN = PCI + NVLink.
2023-04-03 05:32:07 -07:00
struct ncclTopoNode * peerNode = system -> nodes [ GPU ]. nodes + localGpuIndex ;
2022-08-18 02:53:17 -07:00
// Only use PXN for NIC n if remote GPU p ...
2023-04-03 05:32:07 -07:00
if ( peerNode -> paths [ NET ][ n ]. type <= PATH_PXB && // Is connected to the NIC through PCI
peerNode -> paths [ GPU ][ g ]. type <= PATH_NVL && // Is connected to us through NVLink
2024-03-26 06:08:55 -07:00
NCCL_TOPO_ID_SYSTEM_ID ( peerNode -> id ) == NCCL_TOPO_ID_SYSTEM_ID ( gpu -> id ) && // Is on the same node as us
2023-04-03 05:32:07 -07:00
( peerNode -> paths [ NET ][ n ]. bw > gpu -> paths [ NET ][ n ]. bw || // Has either higher BW to that NIC
gpu -> paths [ NET ][ n ]. type > PATH_PXB )) // or avoids going through a CPU
2022-01-07 06:39:55 -08:00
// We can use that GPU as relay to communicate with that NIC.
// Only enabling it in the GPU->NIC direction for now to favor
// receiving locally and sending remotely (consistent with net.cc)
2023-04-03 05:32:07 -07:00
NCCLCHECK ( addInterStep ( system , GPU , localGpuIndex , GPU , g , NET , n ));
2022-01-07 06:39:55 -08:00
}
}
2024-03-26 06:08:55 -07:00
if ( gpu -> paths [ NET ][ n ]. type < PATH_PHB ) {
// Update path when we dont want to / can't use GPU Direct RDMA.
int gdr ;
NCCLCHECK ( ncclTopoCheckGdr ( system , system -> nodes [ GPU ]. nodes [ g ]. id , netNode -> id , 0 , & gdr ));
if ( gdr == 0 ) {
// We cannot use GPU Direct RDMA, divert all traffic through the CPU local to the GPU
int localCpu ;
NCCLCHECK ( getLocalCpu ( system , g , & localCpu ));
NCCLCHECK ( addInterStep ( system , CPU , localCpu , NET , n , GPU , g ));
NCCLCHECK ( addInterStep ( system , CPU , localCpu , GPU , g , NET , n ));
}
2019-11-19 14:57:39 -08:00
}
}
}
return ncclSuccess ;
}
2023-11-22 15:07:36 -08:00
RCCL_PARAM ( EnableIntranet , "ENABLE_INTRANET" , - 2 );
2022-05-31 11:31:30 -07:00
2019-11-19 14:57:39 -08:00
ncclResult_t ncclTopoTrimSystem ( struct ncclTopoSystem * system , struct ncclComm * comm ) {
int * domains ;
int64_t * ids ;
NCCLCHECK ( ncclCalloc ( & domains , system -> nodes [ GPU ]. count ));
NCCLCHECK ( ncclCalloc ( & ids , system -> nodes [ GPU ]. count ));
int myDomain = 0 ;
for ( int g = 0 ; g < system -> nodes [ GPU ]. count ; g ++ ) {
struct ncclTopoNode * gpu = system -> nodes [ GPU ]. nodes + g ;
domains [ g ] = g ;
ids [ g ] = gpu -> id ;
for ( int p = 0 ; p < g ; p ++ ) {
2023-04-03 05:32:07 -07:00
if ( gpu -> paths [ GPU ][ p ]. type < PATH_NET ) {
2019-11-19 14:57:39 -08:00
domains [ g ] = std :: min ( domains [ g ], domains [ p ]);
}
}
2020-01-16 16:02:42 -08:00
if ( gpu -> gpu . rank == comm -> rank ) myDomain = domains [ g ];
2019-11-19 14:57:39 -08:00
}
int ngpus = system -> nodes [ GPU ]. count ;
for ( int i = 0 ; i < ngpus ; i ++ ) {
if ( domains [ i ] == myDomain ) continue ;
struct ncclTopoNode * gpu = NULL ;
int g ;
for ( g = 0 ; g < system -> nodes [ GPU ]. count /* This one varies over the loops */ ; g ++ ) {
gpu = system -> nodes [ GPU ]. nodes + g ;
if ( gpu -> id == ids [ i ]) break ; else gpu = NULL ;
}
if ( gpu == NULL ) {
WARN ( "Could not find id %lx" , ids [ i ]);
free ( domains );
free ( ids );
return ncclInternalError ;
}
2020-01-16 16:02:42 -08:00
NCCLCHECK ( ncclTopoRemoveNode ( system , GPU , g ));
2019-11-19 14:57:39 -08:00
}
2021-03-31 10:25:59 -07:00
// trim low speed port on same NIC
for ( int i = 0 ; i < system -> nodes [ NET ]. count ; i ++ ) {
for ( int j = 0 ; j < system -> nodes [ NET ]. count ; j ++ ) {
if ( i == j ) continue ;
if ( system -> nodes [ NET ]. nodes [ i ]. net . asic == system -> nodes [ NET ]. nodes [ j ]. net . asic ) {
2022-10-20 15:40:03 +00:00
if ( system -> nodes [ NET ]. nodes [ i ]. net . bw > system -> nodes [ NET ]. nodes [ j ]. net . bw )
system -> nodes [ NET ]. nodes [ j ]. net . bw = 0 ;
2021-03-31 10:25:59 -07:00
}
}
}
do {
int n ;
for ( n = 0 ; n < system -> nodes [ NET ]. count ; n ++ ) {
2022-10-20 15:40:03 +00:00
if ( system -> nodes [ NET ]. nodes [ n ]. net . bw == 0 ) break ;
2021-03-31 10:25:59 -07:00
}
if ( n < system -> nodes [ NET ]. count ) {
NCCLCHECK ( ncclTopoRemoveNode ( system , NET , n ));
}
else
break ;
} while ( system -> nodes [ NET ]. count );
2021-03-05 19:59:41 -08:00
int remove = 1 ;
2023-07-21 07:31:27 -07:00
int gdr = 1 ;
bool allXgmi = true ;
// 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 ;
for ( int j = 0 ; j < system -> nodes [ GPU ]. count && allXgmi ; j ++ ) {
if ( i == j ) continue ;
int cudaDev2 = system -> nodes [ GPU ]. nodes [ j ]. gpu . dev ;
bool isXGMI ;
NCCLCHECK ( ncclTopoGetLinkType ( comm -> topo , cudaDev1 , cudaDev2 , & isXGMI ));
allXgmi &= isXGMI ;
2021-03-05 19:59:41 -08:00
}
}
2023-07-21 07:31:27 -07:00
if ( allXgmi ) system -> type |= RCCL_TOPO_XGMI_ALL ;
for ( int g = 0 ; g < system -> nodes [ GPU ]. count ; g ++ ) {
2024-10-02 09:29:22 -05:00
int64_t netId ;
NCCLCHECK ( ncclTopoGetLocalNet ( system , system -> nodes [ GPU ]. nodes [ g ]. gpu . rank , 0 , & netId , nullptr ));
NCCLCHECK ( ncclTopoCheckGdr ( system , system -> nodes [ GPU ]. nodes [ g ]. id , netId , 1 , & gdr ));
2023-07-21 07:31:27 -07:00
if ( ! gdr ) break ;
}
if ( gdr && ! allXgmi ) {
remove = 0 ;
system -> type |= RCCL_TOPO_GDR_ALL ;
INFO ( NCCL_GRAPH , "GDR is available on all GPUs" );
}
2024-01-16 07:54:18 -08:00
if ( rcclParamEnableIntranet () == 1 ) {
remove = 0 ;
system -> type |= RCCL_TOPO_FORCE_INTRA ;
}
2023-06-21 20:54:24 -07:00
comm -> localRanks = system -> nodes [ GPU ]. count ;
2024-10-02 09:29:22 -05:00
if ( system -> nodes [ GPU ]. count == comm -> nRanks && remove ) {
2020-01-16 16:02:42 -08:00
for ( int n = system -> nodes [ NET ]. count - 1 ; n >= 0 ; n -- )
NCCLCHECK ( ncclTopoRemoveNode ( system , NET , n ));
2019-11-19 14:57:39 -08:00
}
2021-03-31 10:25:59 -07:00
2019-11-19 14:57:39 -08:00
free ( domains );
free ( ids );
return ncclSuccess ;
}
void ncclTopoFree ( struct ncclTopoSystem * system ) {
for ( int t = 0 ; t < NCCL_TOPO_NODE_TYPES ; t ++ ) ncclTopoRemovePathType ( system , t );
free ( system );
}
2020-05-12 14:40:18 -07:00
2024-02-05 05:06:02 -08:00
NCCL_PARAM ( NChannelsPerNetPeer , "NCHANNELS_PER_NET_PEER" , - 1 );
2023-06-06 08:41:38 -07:00
NCCL_PARAM ( NChannelsPerPeer , "NCHANNELS_PER_PEER" , - 2 );
2022-01-07 06:39:55 -08:00
2024-02-05 05:06:02 -08:00
static ncclResult_t ncclTopoGetNchannels ( struct ncclComm * comm , int g /*local gpu index*/ , int peerRank , int * nChannels ) {
2020-05-12 14:40:18 -07:00
int peer ;
2024-02-05 05:06:02 -08:00
struct ncclTopoSystem * system = comm -> topo ;
2020-05-12 14:40:18 -07:00
struct ncclTopoLinkList * path = NULL ;
if ( ncclTopoRankToIndex ( system , peerRank , & peer ) == ncclSuccess ) {
// Same rank
if ( g == peer ) {
* nChannels = - 1 ;
return ncclSuccess ;
}
// Local rank
path = system -> nodes [ GPU ]. nodes [ peer ]. paths [ GPU ] + g ;
if ( path -> type == PATH_NVL ) {
2023-11-03 16:06:49 -07:00
float nvlBw = ncclTopoXGMISpeed ( system -> nodes [ GPU ]. nodes [ g ]. gpu . gcn );
* nChannels = ( IsArchMatch ( system -> nodes [ GPU ]. nodes [ 0 ]. gpu . gcn , "gfx94" ) ? 4 : 2 ) * std :: max ( 1 , ( int )( path -> bw / nvlBw ));
2020-05-12 14:40:18 -07:00
} else {
* nChannels = 2 ;
}
} else {
// Remote rank, use network
2024-02-05 05:06:02 -08:00
int nNetChannels = ncclParamNChannelsPerNetPeer ();
if ( nNetChannels == - 1 ) {
//start from 2 channels per NIC and reduce with scale
nNetChannels = 2 ;
// check if we need to use more than one NIC, hence more than one channel
int netCountByBw = 1 , nChannelsMax = nNetChannels ;
NCCLCHECK ( getLocalNetCountByBw ( system , g , & netCountByBw ));
// Avoid overloading channels with 8+ operations as we loose the sync warp, hence a bit of bandwidth.
while ( nChannelsMax * comm -> nRanks > comm -> p2pnChannels * 4 && nChannelsMax > 1 ) nChannelsMax /= 2 ;
//allow upto channels requires to drive the NICs
nNetChannels = std :: max ( netCountByBw , nChannelsMax );
}
* nChannels = nNetChannels ;
2020-05-12 14:40:18 -07:00
}
return ncclSuccess ;
}
NCCL_PARAM ( MinP2pNChannels , "MIN_P2P_NCHANNELS" , 1 );
NCCL_PARAM ( MaxP2pNChannels , "MAX_P2P_NCHANNELS" , MAXCHANNELS );
static int nextPow2 ( int v ) {
int pow2 = 1 ;
while ( pow2 < v ) pow2 <<= 1 ;
return pow2 ;
}
ncclResult_t ncclTopoComputeP2pChannels ( struct ncclComm * comm ) {
2023-02-27 02:48:21 -08:00
/* here we already honor comm->max/minCTAs for p2pnChannels. */
2023-04-03 05:32:07 -07:00
if ( comm -> sharedRes -> owner != comm ) {
comm -> p2pnChannels = std :: min ( comm -> nChannels , ( int ) ncclParamMaxP2pNChannels ());
comm -> p2pnChannels = std :: min ( std :: max ( comm -> p2pnChannels , ( int ) ncclParamMinP2pNChannels ()), comm -> sharedRes -> tpP2pNChannels );
} else {
comm -> p2pnChannels = std :: min ( comm -> nChannels , ( int ) ncclParamMaxP2pNChannels ());
comm -> p2pnChannels = std :: max ( comm -> p2pnChannels , ( int ) ncclParamMinP2pNChannels ());
}
2020-05-12 14:40:18 -07:00
int minChannels = comm -> p2pnChannels ;
// We need to loop through all local GPUs to have a global picture
for ( int g = 0 ; g < comm -> topo -> nodes [ GPU ]. count ; g ++ ) {
for ( int r = 0 ; r < comm -> nRanks ; r ++ ) {
int nChannels ;
2024-02-05 05:06:02 -08:00
NCCLCHECK ( ncclTopoGetNchannels ( comm , g , r , & nChannels ));
2020-05-12 14:40:18 -07:00
if ( nChannels >= 0 ) minChannels = std :: min ( minChannels , nChannels );
}
}
2022-11-29 13:57:10 -08:00
int arch , vendor , model ;
NCCLCHECK ( ncclTopoCpuType ( comm -> topo , & arch , & vendor , & model ));
2023-07-21 07:31:27 -07:00
if ( arch == NCCL_TOPO_CPU_ARCH_X86 && vendor == NCCL_TOPO_CPU_VENDOR_INTEL && ! ( comm -> topo -> type & RCCL_TOPO_XGMI_ALL )) {
2022-11-29 13:57:10 -08:00
// Adjust P2P channels on Intel platform
comm -> p2pnChannelsPerPeer = 1 ;
comm -> p2pnChannels = 2 ;
2023-10-04 09:46:11 -06:00
} else if ( comm -> topo -> nodes [ GPU ]. count == comm -> topo -> nRanks && ( comm -> topo -> type & RCCL_TOPO_4P2H_ROME ) && ! ( comm -> topo -> type & RCCL_TOPO_GDR_ALL ) && ! ( comm -> topo -> type & RCCL_TOPO_XGMI_ALL )) {
2020-12-04 18:52:32 -05:00
// Adjust P2P channels on Rome
comm -> p2pnChannelsPerPeer = 2 ;
comm -> p2pnChannels = 2 ;
2023-11-03 16:06:49 -07:00
} else {
2020-12-04 18:52:32 -05:00
// Round to next pow2 nChannelsPerPeer and nChannels
2023-11-03 16:06:49 -07:00
comm -> p2pnChannelsPerPeer = ( ncclParamNChannelsPerPeer () == - 2 ? nextPow2 ( minChannels ) : ncclParamNChannelsPerPeer ());
2024-02-02 12:41:00 -08:00
// Doubling P2P channels per peer on single node
2024-02-07 14:05:57 -08:00
if ( comm -> topo -> nodes [ GPU ]. count == comm -> topo -> nRanks && IsArchMatch ( comm -> topo -> nodes [ GPU ]. nodes [ 0 ]. gpu . gcn , "gfx94" )) comm -> p2pnChannelsPerPeer *= 2 ;
2024-05-23 15:27:43 -05:00
comm -> p2pnChannels = std :: min ( nextPow2 ( comm -> p2pnChannels ), 4 * CHANNEL_LIMIT );
2020-12-04 18:52:32 -05:00
}
2020-05-12 14:40:18 -07:00
// Init channels that weren't used so far
2020-12-04 18:52:32 -05:00
for ( int c = comm -> nChannels ; c < std :: max ( comm -> nChannels , comm -> p2pnChannels ); c ++ ) NCCLCHECK ( initChannel ( comm , c ));
2020-05-12 14:40:18 -07:00
// We want to spread channels used when there aren't many and progressively
// fill the whole space of nChannels. To do so we mirror the bits in the
// nChannels space.
2022-05-24 02:02:31 -07:00
for ( int c = 0 ; c < comm -> p2pnChannels ; c ++ ) {
2024-05-23 15:27:43 -05:00
comm -> p2pChannels [ c ] = mirrorBits ( c , comm -> p2pnChannels );
2020-05-12 14:40:18 -07:00
}
return ncclSuccess ;
}
2021-05-11 18:16:30 -07:00
ncclResult_t ncclTopoGetNvbGpus ( struct ncclTopoSystem * system , int rank , int * nranks , int ** ranks ) {
int ngpus = system -> nodes [ GPU ]. count ;
NCCLCHECK ( ncclCalloc ( ranks , ngpus ));
int nvbGpus = 0 ;
for ( int g = 0 ; g < ngpus ; g ++ ) {
struct ncclTopoNode * gpu = system -> nodes [ GPU ]. nodes + g ;
if ( gpu -> gpu . rank != rank ) continue ;
for ( int p = 0 ; p < ngpus ; p ++ ) {
if ( gpu -> paths [ GPU ][ p ]. type == PATH_NVB ) {
( * ranks )[ nvbGpus ++ ] = system -> nodes [ GPU ]. nodes [ p ]. gpu . rank ;
}
}
}
* nranks = nvbGpus ;
return ncclSuccess ;
}
2022-11-29 04:27:46 -08:00
int ncclTopoPathAllNVLink ( struct ncclTopoSystem * system ) {
int minPath = PATH_DIS ;
for ( int i = 0 ; i < system -> nodes [ GPU ]. count ; i ++ ) {
struct ncclTopoLinkList * paths = system -> nodes [ GPU ]. nodes [ i ]. paths [ GPU ];
for ( int j = 0 ; j < system -> nodes [ GPU ]. count ; j ++ ) {
if ( i == j ) continue ;
minPath = std :: min ( minPath , paths [ j ]. type );
}
}
return minPath >= PATH_PIX ? 0 : 1 ;
2024-05-23 15:27:43 -05:00
}