2018-09-24 16:06:59 -07:00
/*************************************************************************
2022-01-07 06:39:55 -08:00
* Copyright (c) 2016-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.
2018-09-24 16:06:59 -07:00
*
* See LICENSE.txt for license information
************************************************************************/
#include "nccl.h"
#include "core.h"
#include "socket.h"
#include "net.h"
2019-11-19 14:57:39 -08:00
#include "graph.h"
2018-09-24 16:06:59 -07:00
#include "utils.h"
#include "param.h"
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sys/types.h>
#include <unistd.h>
2022-01-07 06:39:55 -08:00
#define ENABLE_TIMER 0
#include "timer.h"
2018-09-24 16:06:59 -07:00
#include "ibvwrap.h"
2022-07-08 09:52:27 -07:00
#include "graph/xml.h"
2018-09-24 16:06:59 -07:00
#define MAXNAMESIZE 64
2020-09-04 14:35:05 -07:00
static char ncclIbIfName [ MAX_IF_NAME_SIZE + 1 ];
2022-01-07 06:39:55 -08:00
static union ncclSocketAddress ncclIbIfAddr ;
struct ncclIbMr {
uintptr_t addr ;
int pages ;
int refs ;
ibv_mr * mr ;
};
struct ncclIbMrCache {
struct ncclIbMr * slots ;
int capacity , population ;
};
2020-01-16 16:02:42 -08:00
2018-09-24 16:06:59 -07:00
static int ncclNIbDevs = - 1 ;
2022-03-30 02:25:49 -07:00
struct alignas ( 64 ) ncclIbDev {
2022-01-07 06:39:55 -08:00
pthread_mutex_t lock ;
2018-09-24 16:06:59 -07:00
int device ;
2020-01-16 16:02:42 -08:00
uint64_t guid ;
2018-09-24 16:06:59 -07:00
uint8_t port ;
2018-12-13 15:56:12 -08:00
uint8_t link ;
2020-01-16 16:02:42 -08:00
int speed ;
2018-09-24 16:06:59 -07:00
ibv_context * context ;
2022-01-07 06:39:55 -08:00
int pdRefs ;
ibv_pd * pd ;
2018-09-24 16:06:59 -07:00
char devName [ MAXNAMESIZE ];
2020-01-16 16:02:42 -08:00
char * pciPath ;
int realPort ;
int maxQp ;
2022-01-07 06:39:55 -08:00
struct ncclIbMrCache mrCache ;
2022-11-29 04:27:46 -08:00
int ar ; // ADAPTIVE_ROUTING
2018-09-24 16:06:59 -07:00
};
#define MAX_IB_PORT 15
struct userIbDev {
char devName [ MAXNAMESIZE ];
uint16_t port_en ;
};
2024-01-12 16:54:19 +00:00
#define MAX_IB_DEVS 16
2018-09-24 16:06:59 -07:00
struct ncclIbDev ncclIbDevs [ MAX_IB_DEVS ];
struct userIbDev userIbDevs [ MAX_IB_DEVS ];
pthread_mutex_t ncclIbLock = PTHREAD_MUTEX_INITIALIZER ;
2022-01-07 06:39:55 -08:00
static int ncclIbRelaxedOrderingEnabled = 0 ;
2018-09-24 16:06:59 -07:00
NCCL_PARAM ( IbGidIndex , "IB_GID_INDEX" , 0 );
2022-08-18 02:53:17 -07:00
NCCL_PARAM ( IbTimeout , "IB_TIMEOUT" , 18 );
2018-09-24 16:06:59 -07:00
NCCL_PARAM ( IbRetryCnt , "IB_RETRY_CNT" , 7 );
2020-09-04 14:35:05 -07:00
NCCL_PARAM ( IbPkey , "IB_PKEY" , 0 );
NCCL_PARAM ( IbUseInline , "IB_USE_INLINE" , 0 );
2018-09-24 16:06:59 -07:00
NCCL_PARAM ( IbSl , "IB_SL" , 0 );
NCCL_PARAM ( IbTc , "IB_TC" , 0 );
2020-01-16 16:02:42 -08:00
NCCL_PARAM ( IbArThreshold , "IB_AR_THRESHOLD" , 8192 );
2022-01-07 06:39:55 -08:00
NCCL_PARAM ( IbPciRelaxedOrdering , "IB_PCI_RELAXED_ORDERING" , 2 );
2022-11-29 04:27:46 -08:00
NCCL_PARAM ( IbAdaptiveRouting , "IB_ADAPTIVE_ROUTING" , - 2 );
2018-09-24 16:06:59 -07:00
2022-07-08 07:15:52 +08:00
NCCL_PARAM ( IbSockClientPortReuse , "IB_SOCK_CLIENT_PORT_REUSE" , 0 );
NCCL_PARAM ( IbSockServerPortReuse , "IB_SOCK_SERVER_PORT_REUSE" , 0 );
static thread_local union ncclSocketAddress reusedAddr ;
static thread_local int reusedSockfd = - 1 ;
2018-09-24 16:06:59 -07:00
pthread_t ncclIbAsyncThread ;
static void * ncclIbAsyncThreadMain ( void * args ) {
struct ibv_context * context = ( struct ibv_context * ) args ;
while ( 1 ) {
struct ibv_async_event event ;
if ( ncclSuccess != wrap_ibv_get_async_event ( context , & event )) { break ; }
char * str ;
if ( ncclSuccess != wrap_ibv_event_type_str ( & str , event . event_type )) { break ; }
if ( event . event_type != IBV_EVENT_COMM_EST )
WARN ( "NET/IB : Got async event : %s" , str );
if ( ncclSuccess != wrap_ibv_ack_async_event ( & event )) { break ; }
}
return NULL ;
}
2018-11-13 10:37:20 -08:00
NCCL_PARAM ( IbDisable , "IB_DISABLE" , 0 );
2023-04-03 05:32:07 -07:00
NCCL_PARAM ( IbMergeVfs , "IB_MERGE_VFS" , 1 );
2018-11-13 10:37:20 -08:00
2020-01-16 16:02:42 -08:00
static ncclResult_t ncclIbGetPciPath ( char * devName , char ** path , int * realPort ) {
char devicePath [ PATH_MAX ];
snprintf ( devicePath , PATH_MAX , "/sys/class/infiniband/%s/device" , devName );
char * p = realpath ( devicePath , NULL );
if ( p == NULL ) {
2021-02-09 15:34:08 -08:00
WARN ( "Could not find real path of %s (%s)" , devName , devicePath );
2020-01-16 16:02:42 -08:00
} else {
// Merge multi-port NICs into the same PCI device
p [ strlen ( p ) - 1 ] = '0' ;
2021-09-08 13:56:25 -07:00
// Also merge virtual functions (VF) into the same device
2023-06-13 00:19:57 -07:00
if ( ncclParamIbMergeVfs ()) p [ strlen ( p ) - 3 ] = p [ strlen ( p ) - 4 ] = '0' ;
2020-01-16 16:02:42 -08:00
// And keep the real port aside (the ibv port is always 1 on recent cards)
* realPort = 0 ;
for ( int d = 0 ; d < ncclNIbDevs ; d ++ ) {
if ( strcmp ( p , ncclIbDevs [ d ]. pciPath ) == 0 ) ( * realPort ) ++ ;
}
}
* path = p ;
return ncclSuccess ;
}
2021-08-24 09:42:04 -07:00
static int ibvWidths [] = { 1 , 4 , 8 , 12 , 2 };
2023-02-02 12:52:47 -08:00
static int ibvSpeeds [] = {
2500 , /* SDR */
5000 , /* DDR */
10000 , /* QDR */
10000 , /* QDR */
14000 , /* FDR */
25000 , /* EDR */
50000 , /* HDR */
100000 /* NDR */ };
2020-01-16 16:02:42 -08:00
static int firstBitSet ( int val , int max ) {
int i = 0 ;
while ( i < max && (( val & ( 1 << i )) == 0 )) i ++ ;
return i ;
}
static int ncclIbWidth ( int width ) {
return ibvWidths [ firstBitSet ( width , sizeof ( ibvWidths ) / sizeof ( int ) - 1 )];
}
static int ncclIbSpeed ( int speed ) {
return ibvSpeeds [ firstBitSet ( speed , sizeof ( ibvSpeeds ) / sizeof ( int ) - 1 )];
}
2022-01-07 06:39:55 -08:00
// Determine whether RELAXED_ORDERING is enabled and possible
static int ncclIbRelaxedOrderingCapable ( void ) {
int roMode = ncclParamIbPciRelaxedOrdering ();
ncclResult_t r = ncclInternalError ;
if ( roMode == 1 || roMode == 2 ) {
// Query IBVERBS_1.8 API - needed for IBV_ACCESS_RELAXED_ORDERING support
r = wrap_ibv_reg_mr_iova2 ( NULL , NULL , NULL , 0 , 0 , 0 );
}
return r == ncclInternalError ? 0 : 1 ;
}
2018-11-13 10:37:20 -08:00
ncclResult_t ncclIbInit ( ncclDebugLogger_t logFunction ) {
2022-01-07 06:39:55 -08:00
if ( ncclParamIbDisable ()) return ncclInternalError ;
2020-05-12 14:40:18 -07:00
static int shownIbHcaEnv = 0 ;
2018-11-13 10:37:20 -08:00
if ( wrap_ibv_symbols () != ncclSuccess ) { return ncclInternalError ; }
2018-09-24 16:06:59 -07:00
if ( ncclNIbDevs == - 1 ) {
pthread_mutex_lock ( & ncclIbLock );
wrap_ibv_fork_init ();
if ( ncclNIbDevs == - 1 ) {
ncclNIbDevs = 0 ;
2022-01-07 06:39:55 -08:00
if ( ncclFindInterfaces ( ncclIbIfName , & ncclIbIfAddr , MAX_IF_NAME_SIZE , 1 ) != 1 ) {
2018-09-24 16:06:59 -07:00
WARN ( "NET/IB : No IP interface found." );
2018-11-13 10:37:20 -08:00
return ncclInternalError ;
2018-09-24 16:06:59 -07:00
}
// Detect IB cards
int nIbDevs ;
struct ibv_device ** devices ;
// Check if user defined which IB device:port to use
char * userIbEnv = getenv ( "NCCL_IB_HCA" );
2020-05-12 14:40:18 -07:00
if ( userIbEnv != NULL && shownIbHcaEnv ++ == 0 ) INFO ( NCCL_NET | NCCL_ENV , "NCCL_IB_HCA set to %s" , userIbEnv );
2018-09-24 16:06:59 -07:00
struct netIf userIfs [ MAX_IB_DEVS ];
bool searchNot = userIbEnv && userIbEnv [ 0 ] == '^' ;
2019-11-19 14:57:39 -08:00
if ( searchNot ) userIbEnv ++ ;
2019-07-10 06:45:41 +09:00
bool searchExact = userIbEnv && userIbEnv [ 0 ] == '=' ;
2019-11-19 14:57:39 -08:00
if ( searchExact ) userIbEnv ++ ;
2018-09-24 16:06:59 -07:00
int nUserIfs = parseStringList ( userIbEnv , userIfs , MAX_IB_DEVS );
2018-11-13 10:37:20 -08:00
if ( ncclSuccess != wrap_ibv_get_device_list ( & devices , & nIbDevs )) return ncclInternalError ;
2018-09-24 16:06:59 -07:00
2019-07-16 08:41:56 -07:00
for ( int d = 0 ; d < nIbDevs && ncclNIbDevs < MAX_IB_DEVS ; d ++ ) {
2018-09-24 16:06:59 -07:00
struct ibv_context * context ;
2018-12-13 15:56:12 -08:00
if ( ncclSuccess != wrap_ibv_open_device ( & context , devices [ d ]) || context == NULL ) {
2018-09-24 16:06:59 -07:00
WARN ( "NET/IB : Unable to open device %s" , devices [ d ] -> name );
continue ;
}
2019-07-16 08:41:56 -07:00
int nPorts = 0 ;
2018-12-13 15:56:12 -08:00
struct ibv_device_attr devAttr ;
2019-03-14 19:39:20 -07:00
memset ( & devAttr , 0 , sizeof ( devAttr ));
2018-12-13 15:56:12 -08:00
if ( ncclSuccess != wrap_ibv_query_device ( context , & devAttr )) {
WARN ( "NET/IB : Unable to query device %s" , devices [ d ] -> name );
if ( ncclSuccess != wrap_ibv_close_device ( context )) { return ncclInternalError ; }
continue ;
}
for ( int port = 1 ; port <= devAttr . phys_port_cnt ; port ++ ) {
struct ibv_port_attr portAttr ;
if ( ncclSuccess != wrap_ibv_query_port ( context , port , & portAttr )) {
WARN ( "NET/IB : Unable to query port %d" , port );
2018-09-24 16:06:59 -07:00
continue ;
}
2018-12-13 15:56:12 -08:00
if ( portAttr . state != IBV_PORT_ACTIVE ) continue ;
2021-08-24 09:42:04 -07:00
if ( portAttr . link_layer != IBV_LINK_LAYER_INFINIBAND
&& portAttr . link_layer != IBV_LINK_LAYER_ETHERNET ) continue ;
2018-09-24 16:06:59 -07:00
2018-12-13 15:56:12 -08:00
// check against user specified HCAs/ports
2019-07-10 06:45:41 +09:00
if ( ! ( matchIfList ( devices [ d ] -> name , port , userIfs , nUserIfs , searchExact ) ^ searchNot )) {
2018-12-13 15:56:12 -08:00
continue ;
}
TRACE ( NCCL_INIT | NCCL_NET , "NET/IB: [%d] %s:%d/%s " , d , devices [ d ] -> name , port ,
portAttr . link_layer == IBV_LINK_LAYER_INFINIBAND ? "IB" : "RoCE" );
2022-01-07 06:39:55 -08:00
pthread_mutex_init ( & ncclIbDevs [ ncclNIbDevs ]. lock , NULL );
2018-12-13 15:56:12 -08:00
ncclIbDevs [ ncclNIbDevs ]. device = d ;
2020-01-16 16:02:42 -08:00
ncclIbDevs [ ncclNIbDevs ]. guid = devAttr . sys_image_guid ;
2018-12-13 15:56:12 -08:00
ncclIbDevs [ ncclNIbDevs ]. port = port ;
ncclIbDevs [ ncclNIbDevs ]. link = portAttr . link_layer ;
2020-01-16 16:02:42 -08:00
ncclIbDevs [ ncclNIbDevs ]. speed = ncclIbSpeed ( portAttr . active_speed ) * ncclIbWidth ( portAttr . active_width );
2018-12-13 15:56:12 -08:00
ncclIbDevs [ ncclNIbDevs ]. context = context ;
2022-01-07 06:39:55 -08:00
ncclIbDevs [ ncclNIbDevs ]. pdRefs = 0 ;
ncclIbDevs [ ncclNIbDevs ]. pd = NULL ;
2018-12-13 15:56:12 -08:00
strncpy ( ncclIbDevs [ ncclNIbDevs ]. devName , devices [ d ] -> name , MAXNAMESIZE );
2020-01-16 16:02:42 -08:00
NCCLCHECK ( ncclIbGetPciPath ( ncclIbDevs [ ncclNIbDevs ]. devName , & ncclIbDevs [ ncclNIbDevs ]. pciPath , & ncclIbDevs [ ncclNIbDevs ]. realPort ));
ncclIbDevs [ ncclNIbDevs ]. maxQp = devAttr . max_qp ;
2022-01-07 06:39:55 -08:00
ncclIbDevs [ ncclNIbDevs ]. mrCache . capacity = 0 ;
ncclIbDevs [ ncclNIbDevs ]. mrCache . population = 0 ;
ncclIbDevs [ ncclNIbDevs ]. mrCache . slots = NULL ;
2022-11-29 04:27:46 -08:00
// Enable ADAPTIVE_ROUTING by default on IB networks
// But allow it to be overloaded by an env parameter
ncclIbDevs [ ncclNIbDevs ]. ar = ( portAttr . link_layer == IBV_LINK_LAYER_INFINIBAND ) ? 1 : 0 ;
if ( ncclParamIbAdaptiveRouting () != - 2 ) ncclIbDevs [ ncclNIbDevs ]. ar = ncclParamIbAdaptiveRouting ();
2022-01-07 06:39:55 -08:00
pthread_create ( & ncclIbAsyncThread , NULL , ncclIbAsyncThreadMain , context );
ncclSetThreadName ( ncclIbAsyncThread , "NCCL IbAsync %2d" , ncclNIbDevs );
2022-05-03 01:30:26 -07:00
pthread_detach ( ncclIbAsyncThread ); // will not be pthread_join()'d
2018-12-13 15:56:12 -08:00
ncclNIbDevs ++ ;
2019-07-16 08:41:56 -07:00
nPorts ++ ;
2021-02-26 16:29:55 -07:00
// [RCCL]
pthread_detach ( ncclIbAsyncThread );
// [/RCCL]
2018-09-24 16:06:59 -07:00
}
2019-07-16 08:41:56 -07:00
if ( nPorts == 0 && ncclSuccess != wrap_ibv_close_device ( context )) { return ncclInternalError ; }
2018-09-24 16:06:59 -07:00
}
2018-11-13 10:37:20 -08:00
if ( nIbDevs && ( ncclSuccess != wrap_ibv_free_device_list ( devices ))) { return ncclInternalError ; };
2018-09-24 16:06:59 -07:00
}
2018-12-13 15:56:12 -08:00
if ( ncclNIbDevs == 0 ) {
INFO ( NCCL_INIT | NCCL_NET , "NET/IB : No device found." );
} else {
char line [ 1024 ];
line [ 0 ] = '\0' ;
2022-01-07 06:39:55 -08:00
// Determine whether RELAXED_ORDERING is enabled and possible
ncclIbRelaxedOrderingEnabled = ncclIbRelaxedOrderingCapable ();
2018-12-13 15:56:12 -08:00
for ( int d = 0 ; d < ncclNIbDevs ; d ++ ) {
snprintf ( line + strlen ( line ), 1023 - strlen ( line ), " [%d]%s:%d/%s" , d , ncclIbDevs [ d ]. devName ,
ncclIbDevs [ d ]. port , ncclIbDevs [ d ]. link == IBV_LINK_LAYER_INFINIBAND ? "IB" : "RoCE" );
}
line [ 1023 ] = '\0' ;
2020-09-04 14:35:05 -07:00
char addrline [ SOCKET_NAME_MAXLEN + 1 ];
2022-01-07 06:39:55 -08:00
INFO ( NCCL_INIT | NCCL_NET , "NET/IB : Using%s %s; OOB %s:%s" , line , ncclIbRelaxedOrderingEnabled ? "[RO]" : "" ,
ncclIbIfName , ncclSocketToString ( & ncclIbIfAddr , addrline ));
2018-12-13 15:56:12 -08:00
}
2018-09-24 16:06:59 -07:00
pthread_mutex_unlock ( & ncclIbLock );
}
2018-11-13 10:37:20 -08:00
return ncclSuccess ;
2018-09-24 16:06:59 -07:00
}
2018-11-13 10:37:20 -08:00
ncclResult_t ncclIbDevices ( int * ndev ) {
2018-09-24 16:06:59 -07:00
* ndev = ncclNIbDevs ;
2018-11-13 10:37:20 -08:00
return ncclSuccess ;
}
2018-09-24 16:06:59 -07:00
// Detect whether GDR can work on a given NIC with the current CUDA device
// Returns :
// ncclSuccess : GDR works
// ncclSystemError : no module or module loaded but not supported by GPU
ncclResult_t ncclIbGdrSupport ( int ibDev ) {
static int moduleLoaded = - 1 ;
if ( moduleLoaded == - 1 ) {
2019-08-15 12:15:30 -04:00
#if defined(__HIP_PLATFORM_HCC__) || defined(__HCC__) || defined(__HIPCC__)
2019-07-05 15:43:00 -07:00
moduleLoaded = ( access ( "/sys/kernel/mm/memory_peers/amdkfd/version" , F_OK ) == - 1 ) ? 0 : 1 ;
2022-07-08 09:52:27 -07:00
char strValue [ MAX_STR_LEN ];
NCCLCHECK ( ncclTopoGetStrFromSys ( "/sys/devices/virtual/dmi/id" , "bios_version" , strValue ));
if ( strncmp ( "Hyper-V UEFI Release" , strValue , 20 ) == 0 ) {
int roMode = ncclParamIbPciRelaxedOrdering ();
NCCLCHECK ( ncclTopoGetStrFromSys ( "/proc/sys/kernel" , "numa_balancing" , strValue ));
if ( strcmp ( strValue , "1" ) == 0 && roMode == 0 )
moduleLoaded = 0 ;
}
2019-07-05 15:43:00 -07:00
#else
2021-04-26 14:24:50 -07:00
// Check for the nv_peer_mem module being loaded
moduleLoaded = (( access ( "/sys/kernel/mm/memory_peers/nv_mem/version" , F_OK ) == - 1 ) &&
// Also support the new nvidia-peermem module
( access ( "/sys/kernel/mm/memory_peers/nvidia-peermem/version" , F_OK ) == - 1 )) ? 0 : 1 ;
2019-07-05 15:43:00 -07:00
#endif
2018-09-24 16:06:59 -07:00
}
if ( moduleLoaded == 0 ) return ncclSystemError ;
2019-11-19 14:57:39 -08:00
return ncclSuccess ;
2018-09-24 16:06:59 -07:00
}
2022-05-24 02:02:31 -07:00
// Detect whether DMA-BUF support is present in the kernel
// Returns :
// ncclSuccess : DMA-BUF support is available
// ncclSystemError : DMA-BUF is not supported by the kernel
ncclResult_t ncclIbDmaBufSupport ( int dev ) {
static int dmaBufSupported = - 1 ;
if ( dmaBufSupported == - 1 ) {
ncclResult_t res ;
2023-10-03 03:17:48 -06:00
NCCLCHECKGOTO ( rocmLibraryInit (), res , failure );
2022-05-24 02:02:31 -07:00
struct ibv_pd * pd ;
struct ibv_context * ctx ;
ctx = ncclIbDevs [ dev ]. context ;
NCCLCHECKGOTO ( wrap_ibv_alloc_pd ( & pd , ctx ), res , failure );
// Test kernel DMA-BUF support with a dummy call (fd=-1)
( void ) wrap_direct_ibv_reg_dmabuf_mr ( pd , 0ULL /*offset*/ , 0ULL /*len*/ , 0ULL /*iova*/ , - 1 /*fd*/ , 0 /*flags*/ );
2022-08-18 02:53:17 -07:00
// ibv_reg_dmabuf_mr() will fail with EOPNOTSUPP/EPROTONOSUPPORT if not supported (EBADF otherwise)
dmaBufSupported = ( errno != EOPNOTSUPP && errno != EPROTONOSUPPORT ) ? 1 : 0 ;
2022-05-24 02:02:31 -07:00
NCCLCHECKGOTO ( wrap_ibv_dealloc_pd ( pd ), res , failure );
}
if ( dmaBufSupported == 0 ) return ncclSystemError ;
return ncclSuccess ;
failure :
dmaBufSupported = 0 ;
return ncclSystemError ;
}
2022-01-07 06:39:55 -08:00
#define NCCL_NET_IB_MAX_RECVS 8
2020-01-16 16:02:42 -08:00
ncclResult_t ncclIbGetProperties ( int dev , ncclNetProperties_t * props ) {
props -> name = ncclIbDevs [ dev ]. devName ;
props -> pciPath = ncclIbDevs [ dev ]. pciPath ;
props -> guid = ncclIbDevs [ dev ]. guid ;
props -> ptrSupport = NCCL_PTR_HOST ;
2022-05-24 02:02:31 -07:00
if ( ncclIbGdrSupport ( dev ) == ncclSuccess ) {
props -> ptrSupport |= NCCL_PTR_CUDA ; // GDR support via nv_peermem
}
if ( ncclIbDmaBufSupport ( dev ) == ncclSuccess ) {
props -> ptrSupport |= NCCL_PTR_DMABUF ; // GDR support via DMA-BUF
2018-09-24 16:06:59 -07:00
}
2020-01-16 16:02:42 -08:00
props -> speed = ncclIbDevs [ dev ]. speed ;
2022-01-07 06:39:55 -08:00
props -> latency = 0 ; // Not set
2020-01-16 16:02:42 -08:00
props -> port = ncclIbDevs [ dev ]. port + ncclIbDevs [ dev ]. realPort ;
props -> maxComms = ncclIbDevs [ dev ]. maxQp ;
2022-01-07 06:39:55 -08:00
props -> maxRecvs = NCCL_NET_IB_MAX_RECVS ;
2018-09-24 16:06:59 -07:00
return ncclSuccess ;
}
2022-01-07 06:39:55 -08:00
// We need to support NCCL_NET_MAX_REQUESTS for each concurrent receive
#define MAX_REQUESTS (NCCL_NET_MAX_REQUESTS*NCCL_NET_IB_MAX_RECVS)
static_assert ( MAX_REQUESTS <= 256 , "request id are encoded in wr_id and we need up to 8 requests ids per completion" );
2018-09-24 16:06:59 -07:00
2021-07-08 14:12:04 -07:00
#define NCCL_IB_MAX_QPS 128
2018-09-24 16:06:59 -07:00
struct ncclIbQpInfo {
uint32_t lid ;
uint8_t ib_port ;
2022-01-07 06:39:55 -08:00
uint8_t link_layer ;
2021-07-08 14:12:04 -07:00
uint32_t qpn [ NCCL_IB_MAX_QPS ];
2018-09-24 16:06:59 -07:00
// For RoCE
uint64_t spn ;
uint64_t iid ;
enum ibv_mtu mtu ;
// FIFO RDMA info
uint32_t fifoRkey ;
uint64_t fifoAddr ;
};
2022-01-07 06:39:55 -08:00
enum ncclIbCommState {
ncclIbCommStateStart = 0 ,
ncclIbCommStateConnect = 1 ,
ncclIbCommStateAccept = 3 ,
ncclIbCommStateSend = 4 ,
ncclIbCommStateRecv = 5 ,
2023-02-27 02:48:21 -08:00
ncclIbCommStateConnecting = 6 ,
ncclIbCommStateConnected = 7 ,
ncclIbCommStatePendingReady = 8 ,
2022-01-07 06:39:55 -08:00
};
struct ncclIbCommStage {
enum ncclIbCommState state ;
int offset ;
void * buffer ;
void * comm ;
};
2018-09-24 16:06:59 -07:00
struct ncclIbHandle {
2022-01-07 06:39:55 -08:00
union ncclSocketAddress connectAddr ; // Filled by the target
2022-11-29 04:27:46 -08:00
uint64_t magic ; // random number to help debugging
2022-01-07 06:39:55 -08:00
struct ncclIbCommStage stage ; // Used by the other side when connecting
2018-09-24 16:06:59 -07:00
};
2023-04-03 05:32:07 -07:00
// Retain local and remote RoCE addresses for error logging
struct ncclIbGidInfo {
uint8_t link_layer ;
union ibv_gid localGid ;
union ibv_gid remoteGid ;
};
2022-01-07 06:39:55 -08:00
#define NCCL_NET_IB_REQ_UNUSED 0
#define NCCL_NET_IB_REQ_SEND 1
#define NCCL_NET_IB_REQ_RECV 2
#define NCCL_NET_IB_REQ_FLUSH 3
2023-04-03 05:32:07 -07:00
const char * reqTypeStr [] = { "Unused" , "Send" , "Recv" , "Flush" };
2022-01-07 06:39:55 -08:00
2018-09-24 16:06:59 -07:00
struct ncclIbRequest {
struct ncclIbVerbs * verbs ;
2022-01-07 06:39:55 -08:00
int type ;
2020-09-04 14:35:05 -07:00
int events ;
2022-11-29 04:27:46 -08:00
struct ncclSocket * sock ;
2023-04-03 05:32:07 -07:00
struct ncclIbGidInfo * gidInfo ;
2022-01-07 06:39:55 -08:00
int nreqs ;
union {
struct {
int size ;
void * data ;
uint32_t lkey ;
int offset ;
} send ;
struct {
int sizes [ NCCL_NET_IB_MAX_RECVS ];
} recv ;
};
2020-09-04 14:35:05 -07:00
};
struct ncclIbVerbs {
2022-01-07 06:39:55 -08:00
int dev ;
struct ibv_pd * pd ; // duplicate of ncclIbDevs[dev].pd
2020-09-04 14:35:05 -07:00
struct ibv_cq * cq ;
2022-01-07 06:39:55 -08:00
uint64_t pad [ 1 ];
2020-09-04 14:35:05 -07:00
struct ncclIbRequest reqs [ MAX_REQUESTS ];
2018-09-24 16:06:59 -07:00
};
struct ncclIbListenComm {
int dev ;
2022-01-07 06:39:55 -08:00
struct ncclSocket sock ;
struct ncclIbCommStage stage ;
2018-09-24 16:06:59 -07:00
};
2020-06-22 13:06:25 -07:00
struct alignas ( 64 ) ncclIbSendFifo {
2018-09-24 16:06:59 -07:00
uint64_t addr ;
int size ;
uint32_t rkey ;
2022-01-07 06:39:55 -08:00
uint32_t nreqs ;
uint32_t tag ;
uint64_t idx ;
2018-09-24 16:06:59 -07:00
};
struct ncclIbSendComm {
2018-12-13 15:56:12 -08:00
struct ncclIbVerbs verbs ;
2022-01-07 06:39:55 -08:00
struct ncclIbSendFifo fifo [ MAX_REQUESTS ][ NCCL_NET_IB_MAX_RECVS ];
uint64_t fifoHead ;
struct ncclIbRequest * fifoReqs [ MAX_REQUESTS ][ NCCL_NET_IB_MAX_RECVS ];
struct ibv_send_wr wrs [ NCCL_NET_IB_MAX_RECVS + 1 ];
struct ibv_sge sges [ NCCL_NET_IB_MAX_RECVS ];
struct ncclSocket sock ;
2018-09-24 16:06:59 -07:00
int ready ;
2021-07-08 14:12:04 -07:00
struct ibv_qp * qps [ NCCL_IB_MAX_QPS ];
int nqps ;
2023-04-03 05:32:07 -07:00
int qpIndex ;
2018-09-24 16:06:59 -07:00
struct ibv_mr * fifoMr ;
2022-11-29 04:27:46 -08:00
int ar ;
2023-04-03 05:32:07 -07:00
struct ncclIbGidInfo gidInfo ;
2018-09-24 16:06:59 -07:00
};
2020-09-04 14:35:05 -07:00
// The SendFifo needs to be 32-byte aligned and each element needs
// to be a 32-byte multiple, so that an entry does not get split and
// written out of order when IB Relaxed Ordering is enabled
static_assert (( offsetof ( struct ncclIbSendComm , fifo ) % 32 ) == 0 , "ncclIbSendComm fifo must be 32-byte aligned" );
static_assert (( sizeof ( struct ncclIbSendFifo ) % 32 ) == 0 , "ncclIbSendFifo element size must be 32-byte multiples" );
2018-09-24 16:06:59 -07:00
struct ncclIbGpuFlush {
int enabled ;
int hostMem ;
struct ibv_mr * hostMr ;
struct ibv_sge sge ;
struct ibv_qp * qp ;
};
struct ncclIbRemFifo {
2022-01-07 06:39:55 -08:00
struct ncclIbSendFifo elems [ MAX_REQUESTS ][ NCCL_NET_IB_MAX_RECVS ];
uint64_t fifoTail ;
2018-09-24 16:06:59 -07:00
uint64_t addr ;
uint32_t rkey ;
uint32_t flags ;
struct ibv_mr * mr ;
struct ibv_sge sge ;
};
struct ncclIbRecvComm {
2018-12-13 15:56:12 -08:00
struct ncclIbVerbs verbs ;
2018-09-24 16:06:59 -07:00
struct ncclIbRemFifo remFifo ;
2022-01-07 06:39:55 -08:00
struct ncclSocket sock ;
2018-09-24 16:06:59 -07:00
int ready ;
2021-07-08 14:12:04 -07:00
struct ibv_qp * qps [ NCCL_IB_MAX_QPS ];
int nqps ;
2023-04-03 05:32:07 -07:00
int qpIndex ;
2018-09-24 16:06:59 -07:00
struct ncclIbGpuFlush gpuFlush ;
2023-04-03 05:32:07 -07:00
struct ncclIbGidInfo gidInfo ;
2018-09-24 16:06:59 -07:00
};
2020-09-04 14:35:05 -07:00
static_assert (( offsetof ( struct ncclIbRecvComm , remFifo ) % 32 ) == 0 , "ncclIbSendComm fifo must be 32-byte aligned" );
2018-09-24 16:06:59 -07:00
2021-07-08 14:12:04 -07:00
NCCL_PARAM ( IbQpsPerConn , "IB_QPS_PER_CONNECTION" , 1 );
2022-01-07 06:39:55 -08:00
ncclResult_t ncclIbInitVerbs ( int dev , struct ibv_context * ctx , struct ncclIbVerbs * verbs ) {
verbs -> dev = dev ;
pthread_mutex_lock ( & ncclIbDevs [ dev ]. lock );
if ( 0 == ncclIbDevs [ dev ]. pdRefs ++ ) {
ncclResult_t res ;
NCCLCHECKGOTO ( wrap_ibv_alloc_pd ( & ncclIbDevs [ dev ]. pd , ctx ), res , failure );
if ( 0 ) {
failure :
pthread_mutex_unlock ( & ncclIbDevs [ dev ]. lock );
return res ;
}
}
verbs -> pd = ncclIbDevs [ dev ]. pd ;
pthread_mutex_unlock ( & ncclIbDevs [ dev ]. lock );
2020-09-04 14:35:05 -07:00
// Recv requests can generate 2 completions (one for the post FIFO, one for the Recv).
2021-07-08 14:12:04 -07:00
NCCLCHECK ( wrap_ibv_create_cq ( & verbs -> cq , ctx , 2 * MAX_REQUESTS * ncclParamIbQpsPerConn (), NULL , NULL , 0 ));
2018-09-24 16:06:59 -07:00
return ncclSuccess ;
}
ncclResult_t ncclIbDestroyVerbs ( struct ncclIbVerbs * verbs ) {
2022-01-07 06:39:55 -08:00
ncclResult_t res ;
2018-09-24 16:06:59 -07:00
NCCLCHECK ( wrap_ibv_destroy_cq ( verbs -> cq ));
2022-01-07 06:39:55 -08:00
pthread_mutex_lock ( & ncclIbDevs [ verbs -> dev ]. lock );
if ( 0 == -- ncclIbDevs [ verbs -> dev ]. pdRefs ) {
NCCLCHECKGOTO ( wrap_ibv_dealloc_pd ( ncclIbDevs [ verbs -> dev ]. pd ), res , returning );
}
res = ncclSuccess ;
returning :
pthread_mutex_unlock ( & ncclIbDevs [ verbs -> dev ]. lock );
return res ;
2018-09-24 16:06:59 -07:00
}
ncclResult_t ncclIbCreateQp ( uint8_t ib_port , struct ncclIbVerbs * verbs , int access_flags , struct ibv_qp ** qp ) {
struct ibv_qp_init_attr qpInitAttr ;
memset ( & qpInitAttr , 0 , sizeof ( struct ibv_qp_init_attr ));
qpInitAttr . send_cq = verbs -> cq ;
qpInitAttr . recv_cq = verbs -> cq ;
qpInitAttr . qp_type = IBV_QPT_RC ;
2020-09-04 14:35:05 -07:00
// We might send 2 messages per send (RDMA and RDMA_WITH_IMM)
2020-01-16 16:02:42 -08:00
qpInitAttr . cap . max_send_wr = 2 * MAX_REQUESTS ;
2018-09-24 16:06:59 -07:00
qpInitAttr . cap . max_recv_wr = MAX_REQUESTS ;
qpInitAttr . cap . max_send_sge = 1 ;
qpInitAttr . cap . max_recv_sge = 1 ;
2020-09-04 14:35:05 -07:00
qpInitAttr . cap . max_inline_data = ncclParamIbUseInline () ? sizeof ( struct ncclIbSendFifo ) : 0 ;
2018-09-24 16:06:59 -07:00
NCCLCHECK ( wrap_ibv_create_qp ( qp , verbs -> pd , & qpInitAttr ));
struct ibv_qp_attr qpAttr ;
memset ( & qpAttr , 0 , sizeof ( struct ibv_qp_attr ));
qpAttr . qp_state = IBV_QPS_INIT ;
2020-09-04 14:35:05 -07:00
qpAttr . pkey_index = ncclParamIbPkey ();
2018-09-24 16:06:59 -07:00
qpAttr . port_num = ib_port ;
qpAttr . qp_access_flags = access_flags ;
NCCLCHECK ( wrap_ibv_modify_qp ( * qp , & qpAttr , IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS ));
return ncclSuccess ;
}
2022-01-07 06:39:55 -08:00
ncclResult_t ncclIbRtrQp ( struct ibv_qp * qp , uint32_t qpn , struct ncclIbQpInfo * info ) {
2018-09-24 16:06:59 -07:00
struct ibv_qp_attr qpAttr ;
memset ( & qpAttr , 0 , sizeof ( struct ibv_qp_attr ));
qpAttr . qp_state = IBV_QPS_RTR ;
qpAttr . path_mtu = info -> mtu ;
2021-07-08 14:12:04 -07:00
qpAttr . dest_qp_num = qpn ;
2018-09-24 16:06:59 -07:00
qpAttr . rq_psn = 0 ;
qpAttr . max_dest_rd_atomic = 1 ;
qpAttr . min_rnr_timer = 12 ;
2022-01-07 06:39:55 -08:00
if ( info -> link_layer == IBV_LINK_LAYER_ETHERNET ) {
2018-09-24 16:06:59 -07:00
qpAttr . ah_attr . is_global = 1 ;
qpAttr . ah_attr . grh . dgid . global . subnet_prefix = info -> spn ;
qpAttr . ah_attr . grh . dgid . global . interface_id = info -> iid ;
qpAttr . ah_attr . grh . flow_label = 0 ;
qpAttr . ah_attr . grh . sgid_index = ncclParamIbGidIndex ();
qpAttr . ah_attr . grh . hop_limit = 255 ;
qpAttr . ah_attr . grh . traffic_class = ncclParamIbTc ();
} else {
qpAttr . ah_attr . is_global = 0 ;
qpAttr . ah_attr . dlid = info -> lid ;
}
qpAttr . ah_attr . sl = ncclParamIbSl ();
qpAttr . ah_attr . src_path_bits = 0 ;
qpAttr . ah_attr . port_num = info -> ib_port ;
NCCLCHECK ( wrap_ibv_modify_qp ( qp , & qpAttr , IBV_QP_STATE | IBV_QP_AV | IBV_QP_PATH_MTU | IBV_QP_DEST_QPN | IBV_QP_RQ_PSN | IBV_QP_MAX_DEST_RD_ATOMIC | IBV_QP_MIN_RNR_TIMER ));
return ncclSuccess ;
}
2022-01-07 06:39:55 -08:00
ncclResult_t ncclIbRtsQp ( struct ibv_qp * qp ) {
2018-09-24 16:06:59 -07:00
struct ibv_qp_attr qpAttr ;
memset ( & qpAttr , 0 , sizeof ( struct ibv_qp_attr ));
qpAttr . qp_state = IBV_QPS_RTS ;
qpAttr . timeout = ncclParamIbTimeout ();
qpAttr . retry_cnt = ncclParamIbRetryCnt ();
qpAttr . rnr_retry = 7 ;
qpAttr . sq_psn = 0 ;
qpAttr . max_rd_atomic = 1 ;
NCCLCHECK ( wrap_ibv_modify_qp ( qp , & qpAttr , IBV_QP_STATE | IBV_QP_TIMEOUT | IBV_QP_RETRY_CNT | IBV_QP_RNR_RETRY | IBV_QP_SQ_PSN | IBV_QP_MAX_QP_RD_ATOMIC ));
return ncclSuccess ;
}
ncclResult_t ncclIbListen ( int dev , void * opaqueHandle , void ** listenComm ) {
struct ncclIbListenComm * comm ;
NCCLCHECK ( ncclCalloc ( & comm , 1 ));
struct ncclIbHandle * handle = ( struct ncclIbHandle * ) opaqueHandle ;
static_assert ( sizeof ( struct ncclIbHandle ) < NCCL_NET_HANDLE_MAXSIZE , "ncclIbHandle size too large" );
2022-01-07 06:39:55 -08:00
memset ( handle , 0 , sizeof ( struct ncclIbHandle ));
2018-09-24 16:06:59 -07:00
comm -> dev = dev ;
2022-11-29 04:27:46 -08:00
handle -> magic = NCCL_SOCKET_MAGIC ;
NCCLCHECK ( ncclSocketInit ( & comm -> sock , & ncclIbIfAddr , handle -> magic , ncclSocketTypeNetIb , NULL , 1 ));
2022-07-08 07:15:52 +08:00
if ( ncclParamIbSockServerPortReuse ()) {
// reuse the socket address and fd for listen system call
if ( reusedSockfd == - 1 ) {
NCCLCHECK ( ncclSocketListen ( & comm -> sock ));
memcpy ( & reusedAddr , & comm -> sock . addr , sizeof ( union ncclSocketAddress ));
reusedSockfd = comm -> sock . fd ;
} else {
memcpy ( & comm -> sock . addr , & reusedAddr , sizeof ( union ncclSocketAddress ));
comm -> sock . fd = reusedSockfd ;
}
} else {
NCCLCHECK ( ncclSocketListen ( & comm -> sock ));
}
2022-11-29 04:27:46 -08:00
NCCLCHECK ( ncclSocketGetAddr ( & comm -> sock , & handle -> connectAddr ));
2018-09-24 16:06:59 -07:00
* listenComm = comm ;
return ncclSuccess ;
}
ncclResult_t ncclIbConnect ( int dev , void * opaqueHandle , void ** sendComm ) {
struct ncclIbHandle * handle = ( struct ncclIbHandle * ) opaqueHandle ;
2022-01-07 06:39:55 -08:00
struct ncclIbCommStage * stage = & handle -> stage ;
struct ncclIbSendComm * comm = ( struct ncclIbSendComm * ) stage -> comm ;
2022-11-29 04:27:46 -08:00
int ready ;
2022-01-07 06:39:55 -08:00
* sendComm = NULL ;
2023-02-27 02:48:21 -08:00
if ( stage -> state == ncclIbCommStateConnect ) goto ib_connect_check ;
if ( stage -> state == ncclIbCommStateSend ) goto ib_send ;
if ( stage -> state == ncclIbCommStateConnecting ) goto ib_connect ;
if ( stage -> state == ncclIbCommStateConnected ) goto ib_send_ready ;
2022-01-07 06:39:55 -08:00
if ( stage -> state != ncclIbCommStateStart ) {
WARN ( "Error: trying to connect already connected sendComm" );
return ncclInternalError ;
}
2018-09-24 16:06:59 -07:00
2022-01-07 06:39:55 -08:00
NCCLCHECK ( ncclIbMalloc (( void ** ) & comm , sizeof ( struct ncclIbSendComm )));
2022-11-29 04:27:46 -08:00
NCCLCHECK ( ncclSocketInit ( & comm -> sock , & handle -> connectAddr , handle -> magic , ncclSocketTypeNetIb , NULL , 1 ));
2022-01-07 06:39:55 -08:00
stage -> comm = comm ;
stage -> state = ncclIbCommStateConnect ;
2022-07-08 07:15:52 +08:00
NCCLCHECK ( ncclSocketConnect ( & comm -> sock , ncclParamIbSockClientPortReuse ()));
2022-01-07 06:39:55 -08:00
ib_connect_check :
/* since ncclSocketConnect is async, we must check if connection is complete */
2022-11-29 04:27:46 -08:00
NCCLCHECK ( ncclSocketReady ( & comm -> sock , & ready ));
if ( ! ready ) return ncclSuccess ;
2021-07-08 14:12:04 -07:00
2018-09-24 16:06:59 -07:00
// IB Setup
2022-01-07 06:39:55 -08:00
struct ibv_context * ctx ;
ctx = ncclIbDevs [ dev ]. context ;
NCCLCHECK ( ncclIbInitVerbs ( dev , ctx , & comm -> verbs ));
uint8_t ib_port ;
ib_port = ncclIbDevs [ dev ]. port ;
2021-07-08 14:12:04 -07:00
comm -> nqps = ncclParamIbQpsPerConn ();
for ( int q = 0 ; q < comm -> nqps ; q ++ ) {
NCCLCHECK ( ncclIbCreateQp ( ib_port , & comm -> verbs , IBV_ACCESS_REMOTE_WRITE , comm -> qps + q ));
}
2022-11-29 04:27:46 -08:00
comm -> ar = ncclIbDevs [ dev ]. ar ; // ADAPTIVE_ROUTING
2018-09-24 16:06:59 -07:00
// Send my QP Info to receiver through the socket. Hope this won't block.
struct ibv_port_attr portAttr ;
NCCLCHECK ( wrap_ibv_query_port ( ctx , ib_port , & portAttr ));
struct ncclIbQpInfo qpInfo ;
qpInfo . ib_port = ib_port ;
2021-07-08 14:12:04 -07:00
for ( int q = 0 ; q < comm -> nqps ; q ++ ) qpInfo . qpn [ q ] = comm -> qps [ q ] -> qp_num ;
2018-09-24 16:06:59 -07:00
qpInfo . mtu = portAttr . active_mtu ;
// Prepare my fifo
2022-01-07 06:39:55 -08:00
NCCLCHECK ( wrap_ibv_reg_mr ( & comm -> fifoMr , comm -> verbs . pd , comm -> fifo , sizeof ( struct ncclIbSendFifo ) * MAX_REQUESTS * NCCL_NET_IB_MAX_RECVS , IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ ));
2018-09-24 16:06:59 -07:00
qpInfo . fifoRkey = comm -> fifoMr -> rkey ;
qpInfo . fifoAddr = ( uint64_t ) comm -> fifo ;
// RoCE support
qpInfo . lid = portAttr . lid ;
2023-04-03 05:32:07 -07:00
qpInfo . link_layer = comm -> gidInfo . link_layer = portAttr . link_layer ;
2022-01-07 06:39:55 -08:00
if ( qpInfo . link_layer == IBV_LINK_LAYER_INFINIBAND ) { // IB
2021-07-08 14:12:04 -07:00
for ( int q = 0 ; q < comm -> nqps ; q ++ )
INFO ( NCCL_NET , "NET/IB: Dev %d Port %d qpn %d mtu %d LID %d" , dev , ib_port , qpInfo . qpn [ q ], qpInfo . mtu , qpInfo . lid );
2018-09-24 16:06:59 -07:00
} else { // RoCE
2023-04-03 05:32:07 -07:00
NCCLCHECK ( wrap_ibv_query_gid ( ctx , ib_port , ncclParamIbGidIndex (), & comm -> gidInfo . localGid ));
qpInfo . spn = comm -> gidInfo . localGid . global . subnet_prefix ;
qpInfo . iid = comm -> gidInfo . localGid . global . interface_id ;
2021-07-08 14:12:04 -07:00
for ( int q = 0 ; q < comm -> nqps ; q ++ )
INFO ( NCCL_NET , "NET/IB: Dev %d Port %d qpn %d mtu %d GID %ld (%lX/%lX)" , dev , ib_port , qpInfo . qpn [ q ], qpInfo . mtu , ncclParamIbGidIndex (), qpInfo . spn , qpInfo . iid );
2018-09-24 16:06:59 -07:00
}
2022-01-07 06:39:55 -08:00
stage -> state = ncclIbCommStateSend ;
stage -> offset = 0 ;
NCCLCHECK ( ncclIbMalloc (( void ** ) & stage -> buffer , sizeof ( qpInfo )));
memcpy ( stage -> buffer , & qpInfo , sizeof ( qpInfo ));
ib_send :
NCCLCHECK ( ncclSocketProgress ( NCCL_SOCKET_SEND , & comm -> sock , stage -> buffer , sizeof ( qpInfo ), & stage -> offset ));
2023-02-27 02:48:21 -08:00
if ( stage -> offset != sizeof ( qpInfo )) return ncclSuccess ;
2022-01-07 06:39:55 -08:00
2023-02-27 02:48:21 -08:00
stage -> state = ncclIbCommStateConnecting ;
stage -> offset = 0 ;
// Clear the staging buffer for re-use
memset ( stage -> buffer , 0 , sizeof ( qpInfo ));
ib_connect :
struct ncclIbQpInfo remQpInfo ;
NCCLCHECK ( ncclSocketProgress ( NCCL_SOCKET_RECV , & comm -> sock , stage -> buffer , sizeof ( ncclIbQpInfo ), & stage -> offset ));
if ( stage -> offset != sizeof ( remQpInfo )) return ncclSuccess ;
memcpy ( & remQpInfo , stage -> buffer , sizeof ( ncclIbQpInfo ));
2023-04-03 05:32:07 -07:00
comm -> gidInfo . remoteGid . global . subnet_prefix = remQpInfo . spn ;
comm -> gidInfo . remoteGid . global . interface_id = remQpInfo . iid ;
2023-02-27 02:48:21 -08:00
for ( int q = 0 ; q < comm -> nqps ; q ++ ) {
struct ibv_qp * qp = comm -> qps [ q ];
NCCLCHECK ( ncclIbRtrQp ( qp , remQpInfo . qpn [ q ], & remQpInfo ));
NCCLCHECK ( ncclIbRtsQp ( qp ));
}
comm -> ready = 1 ;
2022-01-07 06:39:55 -08:00
stage -> state = ncclIbCommStateConnected ;
2023-02-27 02:48:21 -08:00
stage -> offset = 0 ;
ib_send_ready :
NCCLCHECK ( ncclSocketProgress ( NCCL_SOCKET_SEND , & comm -> sock , & comm -> ready , sizeof ( int ), & stage -> offset ));
if ( stage -> offset != sizeof ( int )) return ncclSuccess ;
free ( stage -> buffer );
stage -> state = ncclIbCommStateStart ;
2022-01-07 06:39:55 -08:00
* sendComm = comm ;
2018-09-24 16:06:59 -07:00
return ncclSuccess ;
}
NCCL_PARAM ( IbGdrFlushDisable , "GDR_FLUSH_DISABLE" , 0 );
ncclResult_t ncclIbAccept ( void * listenComm , void ** recvComm ) {
struct ncclIbListenComm * lComm = ( struct ncclIbListenComm * ) listenComm ;
2022-01-07 06:39:55 -08:00
struct ncclIbCommStage * stage = & lComm -> stage ;
struct ncclIbRecvComm * rComm = ( struct ncclIbRecvComm * ) stage -> comm ;
2022-11-29 04:27:46 -08:00
int ready ;
2022-01-07 06:39:55 -08:00
* recvComm = NULL ;
2022-11-29 04:27:46 -08:00
if ( stage -> state == ncclIbCommStateAccept ) goto ib_accept_check ;
2022-01-07 06:39:55 -08:00
if ( stage -> state == ncclIbCommStateRecv ) goto ib_recv ;
if ( stage -> state == ncclIbCommStateSend ) goto ib_send ;
2023-02-27 02:48:21 -08:00
if ( stage -> state == ncclIbCommStatePendingReady ) goto ib_recv_ready ;
2022-01-07 06:39:55 -08:00
if ( stage -> state != ncclIbCommStateStart ) {
2023-02-27 02:48:21 -08:00
WARN ( "Listencomm in unknown state %d" , stage -> state );
2022-01-07 06:39:55 -08:00
return ncclInternalError ;
}
2018-09-24 16:06:59 -07:00
NCCLCHECK ( ncclIbMalloc (( void ** ) & rComm , sizeof ( struct ncclIbRecvComm )));
2022-01-07 06:39:55 -08:00
stage -> comm = rComm ;
stage -> state = ncclIbCommStateAccept ;
2022-11-29 04:27:46 -08:00
NCCLCHECK ( ncclSocketInit ( & rComm -> sock ));
2022-01-07 06:39:55 -08:00
NCCLCHECK ( ncclSocketAccept ( & rComm -> sock , & lComm -> sock ));
2022-11-29 04:27:46 -08:00
ib_accept_check :
NCCLCHECK ( ncclSocketReady ( & rComm -> sock , & ready ));
if ( ! ready ) return ncclSuccess ;
2018-09-24 16:06:59 -07:00
struct ncclIbQpInfo remQpInfo ;
2022-01-07 06:39:55 -08:00
stage -> state = ncclIbCommStateRecv ;
stage -> offset = 0 ;
NCCLCHECK ( ncclIbMalloc (( void ** ) & stage -> buffer , sizeof ( remQpInfo )));
2023-02-27 02:48:21 -08:00
2022-01-07 06:39:55 -08:00
ib_recv :
NCCLCHECK ( ncclSocketProgress ( NCCL_SOCKET_RECV , & rComm -> sock , stage -> buffer , sizeof ( remQpInfo ), & stage -> offset ));
2023-02-27 02:48:21 -08:00
if ( stage -> offset != sizeof ( remQpInfo )) return ncclSuccess ;
2022-01-07 06:39:55 -08:00
/* copy back the received info */
memcpy ( & remQpInfo , stage -> buffer , sizeof ( struct ncclIbQpInfo ));
2018-09-24 16:06:59 -07:00
2023-04-03 05:32:07 -07:00
rComm -> gidInfo . remoteGid . global . subnet_prefix = remQpInfo . spn ;
rComm -> gidInfo . remoteGid . global . interface_id = remQpInfo . iid ;
2018-09-24 16:06:59 -07:00
// IB setup
2022-01-07 06:39:55 -08:00
struct ibv_context * ctx ;
uint8_t ib_port ;
ctx = ncclIbDevs [ lComm -> dev ]. context ;
ib_port = ncclIbDevs [ lComm -> dev ]. port ;
2018-09-24 16:06:59 -07:00
struct ibv_port_attr portAttr ;
NCCLCHECK ( wrap_ibv_query_port ( ctx , ib_port , & portAttr ));
2023-04-03 05:32:07 -07:00
NCCLCHECK ( wrap_ibv_query_gid ( ctx , ib_port , ncclParamIbGidIndex (), & rComm -> gidInfo . localGid ));
2018-09-24 16:06:59 -07:00
// QP Creation
2022-01-07 06:39:55 -08:00
NCCLCHECK ( ncclIbInitVerbs ( lComm -> dev , ctx , & rComm -> verbs ));
2021-07-08 14:12:04 -07:00
rComm -> nqps = ncclParamIbQpsPerConn ();
for ( int q = 0 ; q < rComm -> nqps ; q ++ ) {
NCCLCHECK ( ncclIbCreateQp ( ib_port , & rComm -> verbs , IBV_ACCESS_REMOTE_WRITE , rComm -> qps + q ));
}
2018-09-24 16:06:59 -07:00
// Adjust the MTU
remQpInfo . mtu = ( enum ibv_mtu ) std :: min ( remQpInfo . mtu , portAttr . active_mtu );
// Setup QP
2021-07-08 14:12:04 -07:00
for ( int q = 0 ; q < rComm -> nqps ; q ++ ) {
struct ibv_qp * qp = rComm -> qps [ q ];
NCCLCHECK ( ncclIbRtrQp ( qp , remQpInfo . qpn [ q ], & remQpInfo ));
NCCLCHECK ( ncclIbRtsQp ( qp ));
}
2018-09-24 16:06:59 -07:00
// Retain remote fifo info and prepare my RDMA ops
rComm -> remFifo . rkey = remQpInfo . fifoRkey ;
rComm -> remFifo . addr = remQpInfo . fifoAddr ;
2022-01-07 06:39:55 -08:00
NCCLCHECK ( wrap_ibv_reg_mr ( & rComm -> remFifo . mr , rComm -> verbs . pd , & rComm -> remFifo . elems , sizeof ( struct ncclIbSendFifo ) * MAX_REQUESTS * NCCL_NET_IB_MAX_RECVS , IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ ));
2018-09-24 16:06:59 -07:00
rComm -> remFifo . sge . lkey = rComm -> remFifo . mr -> lkey ;
2020-09-04 14:35:05 -07:00
if ( ncclParamIbUseInline ()) rComm -> remFifo . flags = IBV_SEND_INLINE ;
2018-09-24 16:06:59 -07:00
// Allocate Flush dummy buffer for GPU Direct RDMA
2023-06-13 00:19:57 -07:00
rComm -> gpuFlush . enabled = (( ncclIbGdrSupport ( lComm -> dev ) == ncclSuccess || ncclIbDmaBufSupport ( lComm -> dev ) == ncclSuccess )
&& ( ncclParamIbGdrFlushDisable () == 0 )) ? 1 : 0 ;
2018-09-24 16:06:59 -07:00
if ( rComm -> gpuFlush . enabled ) {
NCCLCHECK ( wrap_ibv_reg_mr ( & rComm -> gpuFlush . hostMr , rComm -> verbs . pd , & rComm -> gpuFlush . hostMem , sizeof ( int ), IBV_ACCESS_LOCAL_WRITE ));
rComm -> gpuFlush . sge . addr = ( uint64_t ) & rComm -> gpuFlush . hostMem ;
rComm -> gpuFlush . sge . length = 1 ;
rComm -> gpuFlush . sge . lkey = rComm -> gpuFlush . hostMr -> lkey ;
NCCLCHECK ( ncclIbCreateQp ( ib_port , & rComm -> verbs , IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ , & rComm -> gpuFlush . qp ));
2021-07-08 14:12:04 -07:00
struct ncclIbQpInfo localQpInfo ;
localQpInfo . lid = portAttr . lid ;
2022-01-07 06:39:55 -08:00
localQpInfo . link_layer = portAttr . link_layer ;
2021-07-08 14:12:04 -07:00
localQpInfo . ib_port = ib_port ;
2023-04-03 05:32:07 -07:00
localQpInfo . spn = rComm -> gidInfo . localGid . global . subnet_prefix ;
localQpInfo . iid = rComm -> gidInfo . localGid . global . interface_id ;
2021-07-08 14:12:04 -07:00
localQpInfo . mtu = portAttr . active_mtu ;
NCCLCHECK ( ncclIbRtrQp ( rComm -> gpuFlush . qp , rComm -> gpuFlush . qp -> qp_num , & localQpInfo ));
2018-09-24 16:06:59 -07:00
NCCLCHECK ( ncclIbRtsQp ( rComm -> gpuFlush . qp ));
}
// Fill Handle
2021-07-08 14:12:04 -07:00
struct ncclIbQpInfo qpInfo ;
qpInfo . lid = portAttr . lid ;
2023-04-03 05:32:07 -07:00
qpInfo . link_layer = rComm -> gidInfo . link_layer = portAttr . link_layer ;
2021-07-08 14:12:04 -07:00
qpInfo . ib_port = ib_port ;
for ( int q = 0 ; q < rComm -> nqps ; q ++ ) qpInfo . qpn [ q ] = rComm -> qps [ q ] -> qp_num ;
2023-04-03 05:32:07 -07:00
qpInfo . spn = rComm -> gidInfo . localGid . global . subnet_prefix ;
qpInfo . iid = rComm -> gidInfo . localGid . global . interface_id ;
2021-07-08 14:12:04 -07:00
qpInfo . mtu = remQpInfo . mtu ;
2022-01-07 06:39:55 -08:00
stage -> state = ncclIbCommStateSend ;
stage -> offset = 0 ;
if ( stage -> buffer ) free ( stage -> buffer );
NCCLCHECK ( ncclIbMalloc (( void ** ) & stage -> buffer , sizeof ( struct ncclIbQpInfo )));
memcpy ( stage -> buffer , & qpInfo , sizeof ( struct ncclIbQpInfo ));
2023-02-27 02:48:21 -08:00
2022-01-07 06:39:55 -08:00
ib_send :
NCCLCHECK ( ncclSocketProgress ( NCCL_SOCKET_SEND , & rComm -> sock , stage -> buffer , sizeof ( struct ncclIbQpInfo ), & stage -> offset ));
if ( stage -> offset < sizeof ( struct ncclIbQpInfo )) return ncclSuccess ;
2023-02-27 02:48:21 -08:00
stage -> offset = 0 ;
stage -> state = ncclIbCommStatePendingReady ;
ib_recv_ready :
NCCLCHECK ( ncclSocketProgress ( NCCL_SOCKET_RECV , & rComm -> sock , & rComm -> ready , sizeof ( int ), & stage -> offset ));
if ( stage -> offset != sizeof ( int )) return ncclSuccess ;
2022-01-07 06:39:55 -08:00
free ( stage -> buffer );
2018-09-24 16:06:59 -07:00
* recvComm = rComm ;
2022-01-07 06:39:55 -08:00
/* reset lComm stage */
stage -> state = ncclIbCommStateStart ;
stage -> offset = 0 ;
stage -> comm = NULL ;
stage -> buffer = NULL ;
2018-09-24 16:06:59 -07:00
return ncclSuccess ;
}
2020-09-04 14:35:05 -07:00
ncclResult_t ncclIbGetRequest ( struct ncclIbVerbs * verbs , struct ncclIbRequest ** req ) {
2018-09-24 16:06:59 -07:00
for ( int i = 0 ; i < MAX_REQUESTS ; i ++ ) {
2020-09-04 14:35:05 -07:00
struct ncclIbRequest * r = verbs -> reqs + i ;
2022-01-07 06:39:55 -08:00
if ( r -> type == NCCL_NET_IB_REQ_UNUSED ) {
2020-09-04 14:35:05 -07:00
r -> verbs = verbs ;
r -> events = 1 ;
2022-11-29 04:27:46 -08:00
r -> sock = NULL ;
2023-04-03 05:32:07 -07:00
r -> gidInfo = NULL ;
2018-09-24 16:06:59 -07:00
* req = r ;
return ncclSuccess ;
}
}
WARN ( "NET/IB : unable to allocate requests" );
* req = NULL ;
return ncclInternalError ;
}
2020-09-04 14:35:05 -07:00
ncclResult_t ncclIbFreeRequest ( struct ncclIbRequest * r ) {
2022-01-07 06:39:55 -08:00
r -> type = NCCL_NET_IB_REQ_UNUSED ;
2020-09-04 14:35:05 -07:00
return ncclSuccess ;
}
2018-09-24 16:06:59 -07:00
ncclResult_t ncclIbTest ( void * request , int * done , int * size );
2022-05-24 02:02:31 -07:00
/* DMA-BUF support */
ncclResult_t ncclIbRegMrDmaBuf ( void * comm , void * data , size_t size , int type , uint64_t offset , int fd , void ** mhandle ) {
2020-09-04 14:35:05 -07:00
static_assert ( offsetof ( struct ncclIbSendComm , verbs ) == offsetof ( struct ncclIbRecvComm , verbs ), "Send and recv comms must have verbs at the same offset" );
2018-09-24 16:06:59 -07:00
assert ( size > 0 );
2022-01-07 06:39:55 -08:00
static __thread uintptr_t pageSize = 0 ;
if ( pageSize == 0 ) pageSize = sysconf ( _SC_PAGESIZE );
struct ncclIbVerbs * verbs = ( struct ncclIbVerbs * ) comm ;
struct ncclIbMrCache * cache = & ncclIbDevs [ verbs -> dev ]. mrCache ;
uintptr_t addr = ( uintptr_t ) data & - pageSize ;
2022-05-24 02:02:31 -07:00
size_t pages = (( uintptr_t ) data + size - addr + pageSize - 1 ) / pageSize ;
2022-01-07 06:39:55 -08:00
ncclResult_t res ;
pthread_mutex_lock ( & ncclIbDevs [ verbs -> dev ]. lock );
for ( int slot = 0 ; /*true*/ ; slot ++ ) {
if ( slot == cache -> population ) { // didn't find in cache
if ( cache -> population == cache -> capacity ) { // must grow cache
cache -> capacity = cache -> capacity < 32 ? 32 : 2 * cache -> capacity ;
NCCLCHECKGOTO ( ncclRealloc ( & cache -> slots , cache -> population , cache -> capacity ), res , returning );
}
// Deregister / register
struct ibv_mr * mr ;
unsigned int flags = IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ ;
2022-05-24 02:02:31 -07:00
if ( ncclIbRelaxedOrderingEnabled ) flags |= IBV_ACCESS_RELAXED_ORDERING ;
if ( fd != - 1 ) {
/* DMA-BUF support */
NCCLCHECKGOTO ( wrap_ibv_reg_dmabuf_mr ( & mr , verbs -> pd , offset , pages * pageSize , addr , fd , flags ), res , returning );
} else {
if ( ncclIbRelaxedOrderingEnabled ) {
// Use IBVERBS_1.8 API - needed for IBV_ACCESS_RELAXED_ORDERING support
NCCLCHECKGOTO ( wrap_ibv_reg_mr_iova2 ( & mr , verbs -> pd , ( void * ) addr , pages * pageSize , addr , flags ), res , returning );
}
else {
NCCLCHECKGOTO ( wrap_ibv_reg_mr ( & mr , verbs -> pd , ( void * ) addr , pages * pageSize , flags ), res , returning );
}
2022-01-07 06:39:55 -08:00
}
2022-05-24 02:02:31 -07:00
TRACE ( NCCL_INIT , "regAddr %llx size %lld rkey %x fd %d" , ( unsigned long long ) addr , ( long long ) pages * pageSize , mr -> rkey , fd );
2022-01-07 06:39:55 -08:00
cache -> population += 1 ;
cache -> slots [ slot ]. addr = addr ;
cache -> slots [ slot ]. pages = pages ;
cache -> slots [ slot ]. refs = 1 ;
cache -> slots [ slot ]. mr = mr ;
* mhandle = ( void * ) mr ;
res = ncclSuccess ;
goto returning ;
}
else if ( cache -> slots [ slot ]. addr == addr && cache -> slots [ slot ]. pages == pages ) {
cache -> slots [ slot ]. refs += 1 ;
* mhandle = ( void * ) cache -> slots [ slot ]. mr ;
res = ncclSuccess ;
goto returning ;
}
}
returning :
pthread_mutex_unlock ( & ncclIbDevs [ verbs -> dev ]. lock );
return res ;
2018-09-24 16:06:59 -07:00
}
2022-05-24 02:02:31 -07:00
ncclResult_t ncclIbRegMr ( void * comm , void * data , int size , int type , void ** mhandle ) {
return ncclIbRegMrDmaBuf ( comm , data , ( size_t ) size , type , 0ULL , - 1 , mhandle );
}
2018-12-13 15:56:12 -08:00
ncclResult_t ncclIbDeregMr ( void * comm , void * mhandle ) {
2022-01-07 06:39:55 -08:00
struct ncclIbVerbs * verbs = ( struct ncclIbVerbs * ) comm ;
struct ncclIbMrCache * cache = & ncclIbDevs [ verbs -> dev ]. mrCache ;
ncclResult_t res ;
pthread_mutex_lock ( & ncclIbDevs [ verbs -> dev ]. lock );
for ( int i = 0 ; i < cache -> population ; i ++ ) {
if ( mhandle == cache -> slots [ i ]. mr ) {
if ( 0 == -- cache -> slots [ i ]. refs ) {
memmove ( & cache -> slots [ i ], & cache -> slots [ -- cache -> population ], sizeof ( struct ncclIbMr ));
if ( cache -> population == 0 ) {
free ( cache -> slots );
cache -> slots = NULL ;
cache -> capacity = 0 ;
}
NCCLCHECKGOTO ( wrap_ibv_dereg_mr (( struct ibv_mr * ) mhandle ), res , returning );
}
res = ncclSuccess ;
goto returning ;
}
}
WARN ( "NET/IB: could not find mr %p inside cache of %d entries" , mhandle , cache -> population );
res = ncclInternalError ;
returning :
pthread_mutex_unlock ( & ncclIbDevs [ verbs -> dev ]. lock );
return res ;
2018-12-13 15:56:12 -08:00
}
2023-04-03 05:32:07 -07:00
NCCL_PARAM ( IbSplitDataOnQps , "IB_SPLIT_DATA_ON_QPS" , 1 );
2022-01-07 06:39:55 -08:00
ncclResult_t ncclIbMultiSend ( struct ncclIbSendComm * comm , int slot ) {
struct ncclIbRequest ** reqs = comm -> fifoReqs [ slot ];
volatile struct ncclIbSendFifo * slots = comm -> fifo [ slot ];
int nreqs = slots [ 0 ]. nreqs ;
if ( nreqs > NCCL_NET_IB_MAX_RECVS ) return ncclInternalError ;
2018-11-19 17:43:50 -08:00
2022-01-07 06:39:55 -08:00
uint64_t wr_id = 0ULL ;
2018-12-13 15:56:12 -08:00
2022-01-07 06:39:55 -08:00
for ( int r = 0 ; r < nreqs ; r ++ ) {
struct ibv_send_wr * wr = comm -> wrs + r ;
memset ( wr , 0 , sizeof ( struct ibv_send_wr ));
2018-09-24 16:06:59 -07:00
2022-01-07 06:39:55 -08:00
struct ibv_sge * sge = comm -> sges + r ;
sge -> addr = ( uintptr_t ) reqs [ r ] -> send . data ;
sge -> lkey = reqs [ r ] -> send . lkey ;
2018-09-24 16:06:59 -07:00
2022-01-07 06:39:55 -08:00
wr -> opcode = IBV_WR_RDMA_WRITE ;
wr -> send_flags = 0 ;
wr -> wr . rdma . remote_addr = slots [ r ]. addr ;
wr -> wr . rdma . rkey = slots [ r ]. rkey ;
wr -> next = wr + 1 ;
wr_id += ( reqs [ r ] - comm -> verbs . reqs ) << ( r * 8 );
}
2018-09-24 16:06:59 -07:00
2022-01-07 06:39:55 -08:00
// Write size as immediate data. In the case of multi-send, only write
// 0 or 1 as size to indicate whether there was data sent or received.
2022-05-24 02:02:31 -07:00
uint32_t immData = 0 ;
2022-01-07 06:39:55 -08:00
if ( nreqs == 1 ) {
immData = reqs [ 0 ] -> send . size ;
} else {
2022-05-24 02:02:31 -07:00
if ( nreqs > 32 ) {
WARN ( "Cannot store sizes of %d requests in a 32-bits field" , nreqs );
return ncclInternalError ;
}
2022-01-07 06:39:55 -08:00
for ( int r = 0 ; r < nreqs ; r ++ ) {
2022-05-24 02:02:31 -07:00
immData |= ( reqs [ r ] -> send . size ? 1 : 0 ) << r ;
2022-01-07 06:39:55 -08:00
}
}
2021-07-08 14:12:04 -07:00
2022-01-07 06:39:55 -08:00
struct ibv_send_wr * lastWr = comm -> wrs + nreqs - 1 ;
2022-11-29 04:27:46 -08:00
if ( nreqs > 1 || ( comm -> ar && reqs [ 0 ] -> send . size > ncclParamIbArThreshold ())) {
// When using ADAPTIVE_ROUTING, send the bulk of the data first as an
2022-01-07 06:39:55 -08:00
// RDMA_WRITE, then a 0-byte RDMA_WRITE_WITH_IMM to trigger a remote
// completion.
lastWr ++ ;
memset ( lastWr , 0 , sizeof ( struct ibv_send_wr ));
2018-09-24 16:06:59 -07:00
}
2022-01-07 06:39:55 -08:00
lastWr -> wr_id = wr_id ;
lastWr -> opcode = IBV_WR_RDMA_WRITE_WITH_IMM ;
lastWr -> imm_data = immData ;
lastWr -> next = NULL ;
lastWr -> send_flags = IBV_SEND_SIGNALED ;
2022-03-30 02:25:49 -07:00
// Multi-QP: make sure IB writes are multiples of 128B so that LL and LL128 protocols still work
const int align = 128 ;
2023-04-03 05:32:07 -07:00
const int nqps = ncclParamIbSplitDataOnQps () ? comm -> nqps : 1 ;
for ( int q = 0 ; q < nqps ; q ++ ) {
2022-01-07 06:39:55 -08:00
for ( int r = 0 ; r < nreqs ; r ++ ) {
2023-04-03 05:32:07 -07:00
int chunkSize = DIVUP ( DIVUP ( reqs [ r ] -> send . size , nqps ), align ) * align ;
2022-01-07 06:39:55 -08:00
int length = std :: min ( reqs [ r ] -> send . size - reqs [ r ] -> send . offset , chunkSize );
if ( length <= 0 ) {
comm -> wrs [ r ]. sg_list = NULL ;
comm -> wrs [ r ]. num_sge = 0 ;
} else {
comm -> sges [ r ]. length = length ;
comm -> wrs [ r ]. sg_list = comm -> sges + r ;
comm -> wrs [ r ]. num_sge = 1 ;
}
}
struct ibv_send_wr * bad_wr ;
2023-04-03 05:32:07 -07:00
NCCLCHECK ( wrap_ibv_post_send ( comm -> qps [ comm -> qpIndex ], comm -> wrs , & bad_wr ));
comm -> qpIndex = ( comm -> qpIndex + 1 ) % comm -> nqps ;
2022-01-07 06:39:55 -08:00
for ( int r = 0 ; r < nreqs ; r ++ ) {
2023-04-03 05:32:07 -07:00
int chunkSize = DIVUP ( DIVUP ( reqs [ r ] -> send . size , nqps ), align ) * align ;
2022-01-07 06:39:55 -08:00
reqs [ r ] -> send . offset += chunkSize ;
comm -> sges [ r ]. addr += chunkSize ;
comm -> wrs [ r ]. wr . rdma . remote_addr += chunkSize ;
}
2020-01-16 16:02:42 -08:00
}
2021-04-12 16:00:11 -07:00
2018-12-13 15:56:12 -08:00
return ncclSuccess ;
}
2021-07-08 14:12:04 -07:00
2022-01-07 06:39:55 -08:00
ncclResult_t ncclIbIsend ( void * sendComm , void * data , int size , int tag , void * mhandle , void ** request ) {
2018-09-24 16:06:59 -07:00
struct ncclIbSendComm * comm = ( struct ncclIbSendComm * ) sendComm ;
2023-02-27 02:48:21 -08:00
if ( comm -> ready == 0 ) { WARN ( "NET/IB: ncclIbIsend() called when comm->ready == 0" ); return ncclInternalError ; }
2018-11-19 17:43:50 -08:00
if ( comm -> ready == 0 ) { * request = NULL ; return ncclSuccess ; }
2018-12-13 15:56:12 -08:00
struct ibv_mr * mr = ( struct ibv_mr * ) mhandle ;
2018-11-19 17:43:50 -08:00
// Wait for the receiver to have posted the corresponding receive
2022-01-07 06:39:55 -08:00
int nreqs = 0 ;
volatile struct ncclIbSendFifo * slots ;
int slot = ( comm -> fifoHead ) % MAX_REQUESTS ;
struct ncclIbRequest ** reqs = comm -> fifoReqs [ slot ];
slots = comm -> fifo [ slot ];
2023-06-22 13:42:33 +03:00
uint64_t idx = comm -> fifoHead + 1 ;
2022-01-07 06:39:55 -08:00
if ( slots [ 0 ]. idx != idx ) { * request = NULL ; return ncclSuccess ; }
nreqs = slots [ 0 ]. nreqs ;
// Wait until all data has arrived
for ( int r = 1 ; r < nreqs ; r ++ ) while ( slots [ r ]. idx != idx );
__sync_synchronize (); // order the nreqsPtr load against tag/rkey/addr loads below
for ( int r = 0 ; r < nreqs ; r ++ ) {
if ( reqs [ r ] != NULL || slots [ r ]. tag != tag ) continue ;
// Sanity checks to catch user collective call count/size mismatches
2022-03-30 02:25:49 -07:00
if ( size > slots [ r ]. size ) {
2022-11-29 04:27:46 -08:00
char line [ SOCKET_NAME_MAXLEN + 1 ];
union ncclSocketAddress addr ;
ncclSocketGetAddr ( & comm -> sock , & addr );
2022-03-30 02:25:49 -07:00
WARN ( "NET/IB : req %d/%d tag %x peer %s collective mismatch error, local size %d remote size %d" ,
2022-11-29 04:27:46 -08:00
r , nreqs , tag , ncclSocketToString ( & addr , line ), size , slots [ r ]. size );
2022-03-30 02:25:49 -07:00
return ncclInvalidUsage ;
} // plus any potential programming errors
else if ( slots [ r ]. size < 0 || slots [ r ]. addr == 0 || slots [ r ]. rkey == 0 ) {
2022-11-29 04:27:46 -08:00
char line [ SOCKET_NAME_MAXLEN + 1 ];
union ncclSocketAddress addr ;
ncclSocketGetAddr ( & comm -> sock , & addr );
WARN ( "NET/IB : req %d/%d tag %x peer %s posted incorrect receive info: size %d addr %lx rkey %x" ,
r , nreqs , tag , ncclSocketToString ( & addr , line ), slots [ r ]. size , slots [ r ]. addr , slots [ r ]. rkey );
2022-01-07 06:39:55 -08:00
return ncclInternalError ;
2021-07-08 14:12:04 -07:00
}
2022-01-07 06:39:55 -08:00
struct ncclIbRequest * req ;
NCCLCHECK ( ncclIbGetRequest ( & comm -> verbs , & req ));
req -> type = NCCL_NET_IB_REQ_SEND ;
2022-11-29 04:27:46 -08:00
req -> sock = & comm -> sock ;
2022-01-07 06:39:55 -08:00
req -> verbs = & comm -> verbs ;
req -> nreqs = nreqs ;
req -> send . size = size ;
req -> send . data = data ;
req -> send . lkey = mr -> lkey ;
req -> send . offset = 0 ;
2023-04-03 05:32:07 -07:00
req -> events = ncclParamIbSplitDataOnQps () ? comm -> nqps : 1 ;
if ( comm -> gidInfo . link_layer == IBV_LINK_LAYER_ETHERNET ) req -> gidInfo = & comm -> gidInfo ;
2022-01-07 06:39:55 -08:00
* request = reqs [ r ] = req ;
// If this is a multi-recv, send only when all requests have matched.
for ( int r = 0 ; r < nreqs ; r ++ ) {
if ( reqs [ r ] == NULL ) return ncclSuccess ;
}
2018-09-24 16:06:59 -07:00
2022-01-07 06:39:55 -08:00
TIME_START ( 0 );
NCCLCHECK ( ncclIbMultiSend ( comm , slot ));
2018-09-24 16:06:59 -07:00
2022-01-07 06:39:55 -08:00
// Clear slots[0]->nreqs, as well as other fields to help debugging and sanity checks
memset (( void * ) slots , 0 , sizeof ( struct ncclIbSendFifo ));
memset ( reqs , 0 , NCCL_NET_IB_MAX_RECVS * sizeof ( struct ncclIbRequest * ));
comm -> fifoHead ++ ;
TIME_STOP ( 0 );
return ncclSuccess ;
2021-07-08 14:12:04 -07:00
}
2021-04-12 16:00:11 -07:00
2022-01-07 06:39:55 -08:00
* request = NULL ;
2018-09-24 16:06:59 -07:00
return ncclSuccess ;
}
2022-01-07 06:39:55 -08:00
ncclResult_t ncclIbPostFifo ( struct ncclIbRecvComm * comm , int n , void ** data , int * sizes , int * tags , void ** mhandles , struct ncclIbRequest * req ) {
2018-09-24 16:06:59 -07:00
struct ibv_send_wr wr ;
memset ( & wr , 0 , sizeof ( wr ));
2022-01-07 06:39:55 -08:00
int slot = comm -> remFifo . fifoTail % MAX_REQUESTS ;
struct ncclIbSendFifo * localElem = comm -> remFifo . elems [ slot ];
for ( int i = 0 ; i < n ; i ++ ) {
localElem [ i ]. addr = ( uint64_t ) data [ i ];
struct ibv_mr * mr = ( struct ibv_mr * ) mhandles [ i ];
localElem [ i ]. rkey = mr -> rkey ;
localElem [ i ]. nreqs = n ;
localElem [ i ]. size = sizes [ i ]; // Sanity/Debugging
localElem [ i ]. tag = tags [ i ];
localElem [ i ]. idx = comm -> remFifo . fifoTail + 1 ;
}
wr . wr . rdma . remote_addr = comm -> remFifo . addr + slot * NCCL_NET_IB_MAX_RECVS * sizeof ( struct ncclIbSendFifo );
2018-09-24 16:06:59 -07:00
wr . wr . rdma . rkey = comm -> remFifo . rkey ;
comm -> remFifo . sge . addr = ( uint64_t ) localElem ;
2022-01-07 06:39:55 -08:00
comm -> remFifo . sge . length = n * sizeof ( struct ncclIbSendFifo );
2018-09-24 16:06:59 -07:00
wr . sg_list = & comm -> remFifo . sge ;
wr . num_sge = 1 ;
wr . opcode = IBV_WR_RDMA_WRITE ;
2020-09-04 14:35:05 -07:00
wr . send_flags = comm -> remFifo . flags ; // IBV_SEND_INLINE
// We need to occasionally post a request with the IBV_SEND_SIGNALED flag, otherwise
// the send queue will never empty.
//
// From https://www.rdmamojo.com/2014/06/30/working-unsignaled-completions/
// "How to use Unsignaled Completion?" / "Gotchas and Pitfalls"
// All posted Send Requested, Signaled and Unsignaled, are considered outstanding until
// a Work Completion that they, or Send Requests that were posted after them, was polled
// from the Completion Queue associated with the Send Queue. This means if one works with
// a Queue Pair that was configured to work with Unsignaled Completions, he must make
// sure that occasionally (before the Send Queue is full with outstanding Send Requests)
// a Send Request that generate Work Completion will be posted.
//
// Not following this rule may lead to a case that the Send Queue is full with Send
// Requests that won't generate Work Completion:
//
// - The Send Queue is full, so no new Send Requests can be posted to it
// - The Send Queue can't be emptied, since no Work Completion can be generated anymore
// (the reason is that no Work Completion, that can generate Work Completion that
// polling it will empty the Send Queue, can be posted)
// - The status of all posted Send Request is considered unknown
//
if ( slot == 0 ) {
wr . send_flags |= IBV_SEND_SIGNALED ;
2022-01-07 06:39:55 -08:00
wr . wr_id = req - comm -> verbs . reqs ;
2020-09-04 14:35:05 -07:00
req -> events ++ ;
}
2018-09-24 16:06:59 -07:00
struct ibv_send_wr * bad_wr ;
2021-07-08 14:12:04 -07:00
NCCLCHECK ( wrap_ibv_post_send ( comm -> qps [ 0 ], & wr , & bad_wr ));
2022-01-07 06:39:55 -08:00
comm -> remFifo . fifoTail ++ ;
2018-09-24 16:06:59 -07:00
return ncclSuccess ;
}
2022-01-07 06:39:55 -08:00
ncclResult_t ncclIbIrecv ( void * recvComm , int n , void ** data , int * sizes , int * tags , void ** mhandles , void ** request ) {
2018-09-24 16:06:59 -07:00
struct ncclIbRecvComm * comm = ( struct ncclIbRecvComm * ) recvComm ;
2023-02-27 02:48:21 -08:00
if ( comm -> ready == 0 ) { WARN ( "NET/IB: ncclIbIrecv() called when comm->ready == 0" ); return ncclInternalError ; }
2018-11-19 17:43:50 -08:00
if ( comm -> ready == 0 ) { * request = NULL ; return ncclSuccess ; }
2022-01-07 06:39:55 -08:00
if ( n > NCCL_NET_IB_MAX_RECVS ) return ncclInternalError ;
2018-12-13 15:56:12 -08:00
2018-09-24 16:06:59 -07:00
struct ncclIbRequest * req ;
2020-09-04 14:35:05 -07:00
NCCLCHECK ( ncclIbGetRequest ( & comm -> verbs , & req ));
2022-01-07 06:39:55 -08:00
req -> type = NCCL_NET_IB_REQ_RECV ;
2022-11-29 04:27:46 -08:00
req -> sock = & comm -> sock ;
2022-01-07 06:39:55 -08:00
req -> nreqs = n ;
2023-04-03 05:32:07 -07:00
if ( comm -> gidInfo . link_layer == IBV_LINK_LAYER_ETHERNET ) req -> gidInfo = & comm -> gidInfo ;
2022-01-07 06:39:55 -08:00
for ( int i = 0 ; i < n ; i ++ ) req -> recv . sizes [ i ] = 0 ;
2018-09-24 16:06:59 -07:00
struct ibv_recv_wr wr ;
memset ( & wr , 0 , sizeof ( wr ));
2022-01-07 06:39:55 -08:00
wr . wr_id = req - comm -> verbs . reqs ;
2018-09-24 16:06:59 -07:00
2021-07-08 14:12:04 -07:00
wr . sg_list = NULL ;
wr . num_sge = 0 ;
2022-01-07 06:39:55 -08:00
TIME_START ( 1 );
2023-04-03 05:32:07 -07:00
const int nqps = ncclParamIbSplitDataOnQps () ? comm -> nqps : 1 ;
for ( int q = 0 ; q < nqps ; q ++ ) {
struct ibv_qp * qp = comm -> qps [ comm -> qpIndex ];
2021-07-08 14:12:04 -07:00
struct ibv_recv_wr * bad_wr ;
NCCLCHECK ( wrap_ibv_post_recv ( qp , & wr , & bad_wr ));
2023-04-03 05:32:07 -07:00
comm -> qpIndex = ( comm -> qpIndex + 1 ) % comm -> nqps ;
2018-09-24 16:06:59 -07:00
}
2022-01-07 06:39:55 -08:00
TIME_STOP ( 1 );
2023-04-03 05:32:07 -07:00
req -> events = nqps ;
2018-09-24 16:06:59 -07:00
* request = req ;
// Post to FIFO to notify sender
2022-01-07 06:39:55 -08:00
TIME_START ( 2 );
NCCLCHECK ( ncclIbPostFifo ( comm , n , data , sizes , tags , mhandles , req ));
TIME_STOP ( 2 );
2018-09-24 16:06:59 -07:00
return ncclSuccess ;
}
2022-01-07 06:39:55 -08:00
ncclResult_t ncclIbIflush ( void * recvComm , int n , void ** data , int * sizes , void ** mhandles , void ** request ) {
2018-09-24 16:06:59 -07:00
struct ncclIbRecvComm * comm = ( struct ncclIbRecvComm * ) recvComm ;
2022-01-07 06:39:55 -08:00
int last = - 1 ;
for ( int i = 0 ; i < n ; i ++ ) if ( sizes [ i ]) last = i ;
if ( comm -> gpuFlush . enabled == 0 || last == - 1 ) return ncclSuccess ;
2018-09-24 16:06:59 -07:00
2022-01-07 06:39:55 -08:00
// Only flush once using the last non-zero receive
2018-09-24 16:06:59 -07:00
struct ncclIbRequest * req ;
2020-09-04 14:35:05 -07:00
NCCLCHECK ( ncclIbGetRequest ( & comm -> verbs , & req ));
2022-01-07 06:39:55 -08:00
req -> type = NCCL_NET_IB_REQ_FLUSH ;
2022-11-29 04:27:46 -08:00
req -> sock = & comm -> sock ;
2022-01-07 06:39:55 -08:00
struct ibv_mr * mr = ( struct ibv_mr * ) mhandles [ last ];
2018-09-24 16:06:59 -07:00
struct ibv_send_wr wr ;
memset ( & wr , 0 , sizeof ( wr ));
2022-01-07 06:39:55 -08:00
wr . wr_id = req - comm -> verbs . reqs ;
2018-09-24 16:06:59 -07:00
2022-01-07 06:39:55 -08:00
wr . wr . rdma . remote_addr = ( uint64_t ) data [ last ];
2018-12-13 15:56:12 -08:00
wr . wr . rdma . rkey = mr -> rkey ;
2018-09-24 16:06:59 -07:00
wr . sg_list = & comm -> gpuFlush . sge ;
wr . num_sge = 1 ;
wr . opcode = IBV_WR_RDMA_READ ;
wr . send_flags = IBV_SEND_SIGNALED ;
2022-01-07 06:39:55 -08:00
TIME_START ( 4 );
2018-09-24 16:06:59 -07:00
struct ibv_send_wr * bad_wr ;
NCCLCHECK ( wrap_ibv_post_send ( comm -> gpuFlush . qp , & wr , & bad_wr ));
2022-01-07 06:39:55 -08:00
TIME_STOP ( 4 );
2018-09-24 16:06:59 -07:00
2020-09-04 14:35:05 -07:00
* request = req ;
2018-09-24 16:06:59 -07:00
return ncclSuccess ;
}
2022-01-07 06:39:55 -08:00
ncclResult_t ncclIbTest ( void * request , int * done , int * sizes ) {
2018-09-24 16:06:59 -07:00
struct ncclIbRequest * r = ( struct ncclIbRequest * ) request ;
* done = 0 ;
while ( 1 ) {
2020-09-04 14:35:05 -07:00
if ( r -> events == 0 ) {
2018-09-24 16:06:59 -07:00
* done = 1 ;
2022-01-07 06:39:55 -08:00
if ( sizes && r -> type == NCCL_NET_IB_REQ_RECV ) {
for ( int i = 0 ; i < r -> nreqs ; i ++ ) sizes [ i ] = r -> recv . sizes [ i ];
}
2020-09-04 14:35:05 -07:00
NCCLCHECK ( ncclIbFreeRequest ( r ));
2018-09-24 16:06:59 -07:00
return ncclSuccess ;
}
int wrDone = 0 ;
2018-12-13 15:56:12 -08:00
struct ibv_wc wcs [ 4 ];
2022-01-07 06:39:55 -08:00
TIME_START ( 3 );
2018-12-13 15:56:12 -08:00
NCCLCHECK ( wrap_ibv_poll_cq ( r -> verbs -> cq , 4 , wcs , & wrDone ));
2022-01-07 06:39:55 -08:00
if ( wrDone == 0 ) { TIME_CANCEL ( 3 ); } else { TIME_STOP ( 3 ); }
2018-09-24 16:06:59 -07:00
if ( wrDone == 0 ) return ncclSuccess ;
2018-12-13 15:56:12 -08:00
for ( int w = 0 ; w < wrDone ; w ++ ) {
struct ibv_wc * wc = wcs + w ;
if ( wc -> status != IBV_WC_SUCCESS ) {
2021-07-08 14:12:04 -07:00
char line [ SOCKET_NAME_MAXLEN + 1 ];
2022-11-29 04:27:46 -08:00
union ncclSocketAddress addr ;
ncclSocketGetAddr ( r -> sock , & addr );
2023-04-03 05:32:07 -07:00
char localGidString [ INET6_ADDRSTRLEN ] = "" ;
char remoteGidString [ INET6_ADDRSTRLEN ] = "" ;
const char * localGidStr = NULL , * remoteGidStr = NULL ;
if ( r -> gidInfo ) {
localGidStr = inet_ntop ( AF_INET6 , & r -> gidInfo -> localGid , localGidString , sizeof ( localGidString ));
remoteGidStr = inet_ntop ( AF_INET6 , & r -> gidInfo -> remoteGid , remoteGidString , sizeof ( remoteGidString ));
}
WARN ( "NET/IB : Got completion from peer %s with error %d, opcode %d, len %d, vendor err %d (%s)%s%s%s%s" ,
ncclSocketToString ( & addr , line ), wc -> status , wc -> opcode , wc -> byte_len , wc -> vendor_err , reqTypeStr [ r -> type ],
localGidStr ? " localGid " : "" , localGidString , remoteGidStr ? " remoteGid " : "" , remoteGidString );
2022-05-24 02:02:31 -07:00
return ncclRemoteError ;
2018-12-13 15:56:12 -08:00
}
2018-09-24 16:06:59 -07:00
2022-01-07 06:39:55 -08:00
struct ncclIbRequest * req = r -> verbs -> reqs + ( wc -> wr_id & 0xff );
if ( req -> type == NCCL_NET_IB_REQ_SEND ) {
for ( int i = 0 ; i < req -> nreqs ; i ++ ) {
struct ncclIbRequest * sendReq = r -> verbs -> reqs + (( wc -> wr_id >> ( i * 8 )) & 0xff );
if (( sendReq -> events <= 0 )) return ncclInternalError ;
sendReq -> events -- ;
2018-12-13 15:56:12 -08:00
}
2022-01-07 06:39:55 -08:00
} else {
if ( req && wc -> opcode == IBV_WC_RECV_RDMA_WITH_IMM ) {
if ( req -> type != NCCL_NET_IB_REQ_RECV ) return ncclInternalError ;
if ( req -> nreqs > 1 ) {
// In the case of a multi recv, we only set sizes to 0 or 1.
for ( int i = 0 ; i < req -> nreqs ; i ++ ) {
2022-05-24 02:02:31 -07:00
req -> recv . sizes [ i ] = ( wc -> imm_data >> i ) & 0x1 ;
2022-01-07 06:39:55 -08:00
}
} else {
req -> recv . sizes [ 0 ] += wc -> imm_data ;
}
2018-12-13 15:56:12 -08:00
}
2022-01-07 06:39:55 -08:00
req -> events -- ;
2018-09-24 16:06:59 -07:00
}
}
}
}
ncclResult_t ncclIbCloseSend ( void * sendComm ) {
struct ncclIbSendComm * comm = ( struct ncclIbSendComm * ) sendComm ;
if ( comm ) {
2022-11-29 04:27:46 -08:00
NCCLCHECK ( ncclSocketClose ( & comm -> sock ));
2021-07-08 14:12:04 -07:00
for ( int q = 0 ; q < comm -> nqps ; q ++ )
if ( comm -> qps [ q ] != NULL ) NCCLCHECK ( wrap_ibv_destroy_qp ( comm -> qps [ q ]));
2018-09-24 16:06:59 -07:00
if ( comm -> fifoMr != NULL ) NCCLCHECK ( wrap_ibv_dereg_mr ( comm -> fifoMr ));
NCCLCHECK ( ncclIbDestroyVerbs ( & comm -> verbs ));
free ( comm );
}
2022-01-07 06:39:55 -08:00
TIME_PRINT ( "IB" );
2018-09-24 16:06:59 -07:00
return ncclSuccess ;
}
ncclResult_t ncclIbCloseRecv ( void * recvComm ) {
struct ncclIbRecvComm * comm = ( struct ncclIbRecvComm * ) recvComm ;
if ( comm ) {
2023-02-04 01:43:38 +00:00
if ( ! ncclParamIbSockServerPortReuse () || reusedSockfd != comm -> sock . fd ) NCCLCHECK ( ncclSocketClose ( & comm -> sock ));
2021-07-08 14:12:04 -07:00
for ( int q = 0 ; q < comm -> nqps ; q ++ )
if ( comm -> qps [ q ] != NULL ) NCCLCHECK ( wrap_ibv_destroy_qp ( comm -> qps [ q ]));
2018-09-24 16:06:59 -07:00
if ( comm -> gpuFlush . enabled ) {
if ( comm -> gpuFlush . qp != NULL ) NCCLCHECK ( wrap_ibv_destroy_qp ( comm -> gpuFlush . qp ));
if ( comm -> gpuFlush . hostMr != NULL ) NCCLCHECK ( wrap_ibv_dereg_mr ( comm -> gpuFlush . hostMr ));
}
if ( comm -> remFifo . mr != NULL ) NCCLCHECK ( wrap_ibv_dereg_mr ( comm -> remFifo . mr ));
NCCLCHECK ( ncclIbDestroyVerbs ( & comm -> verbs ));
free ( comm );
}
return ncclSuccess ;
}
ncclResult_t ncclIbCloseListen ( void * listenComm ) {
struct ncclIbListenComm * comm = ( struct ncclIbListenComm * ) listenComm ;
if ( comm ) {
2022-11-29 04:27:46 -08:00
NCCLCHECK ( ncclSocketClose ( & comm -> sock ));
2018-09-24 16:06:59 -07:00
free ( comm );
}
return ncclSuccess ;
}
ncclNet_t ncclNetIb = {
"IB" ,
2018-11-13 10:37:20 -08:00
ncclIbInit ,
2018-09-24 16:06:59 -07:00
ncclIbDevices ,
2020-01-16 16:02:42 -08:00
ncclIbGetProperties ,
2018-09-24 16:06:59 -07:00
ncclIbListen ,
ncclIbConnect ,
ncclIbAccept ,
2018-12-13 15:56:12 -08:00
ncclIbRegMr ,
2022-05-24 02:02:31 -07:00
ncclIbRegMrDmaBuf ,
2018-12-13 15:56:12 -08:00
ncclIbDeregMr ,
2018-09-24 16:06:59 -07:00
ncclIbIsend ,
ncclIbIrecv ,
2020-09-04 14:35:05 -07:00
ncclIbIflush ,
2018-09-24 16:06:59 -07:00
ncclIbTest ,
ncclIbCloseSend ,
ncclIbCloseRecv ,
ncclIbCloseListen
};