Skip to content

Commit c7cf0b7

Browse files
authored
Merge pull request open-mpi#14115 from yinliaws/btl-ofi-multi-rail
btl/ofi: enable multi-rail - one module per NIC with disjoint per-process NIC slices
2 parents bcf46b9 + 9339ec9 commit c7cf0b7

4 files changed

Lines changed: 229 additions & 86 deletions

File tree

opal/mca/btl/ofi/btl_ofi.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
#include <rdma/fi_rma.h>
5252

5353
BEGIN_C_DECLS
54-
#define MCA_BTL_OFI_MAX_MODULES 16
5554
#define MCA_BTL_OFI_NUM_CQE_READ 64
5655

5756
#define MCA_BTL_OFI_DEFAULT_RD_NUM 10
@@ -121,7 +120,10 @@ struct mca_btl_ofi_module_t {
121120
int num_contexts;
122121
mca_btl_ofi_context_t *contexts;
123122

124-
char *linux_device_name;
123+
char *domain_name;
124+
int module_index;
125+
void *ep_name;
126+
size_t ep_namelen;
125127

126128
/** whether the module has been fully initialized or not */
127129
bool initialized;
@@ -172,7 +174,8 @@ struct mca_btl_ofi_component_t {
172174
bool disable_hmem;
173175

174176
/** All BTL OFI modules (1 per tl) */
175-
mca_btl_ofi_module_t *modules[MCA_BTL_OFI_MAX_MODULES];
177+
mca_btl_ofi_module_t **modules;
178+
int modules_allocated;
176179
};
177180
typedef struct mca_btl_ofi_component_t mca_btl_ofi_component_t;
178181

opal/mca/btl/ofi/btl_ofi_component.c

Lines changed: 160 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ static char *ofi_progress_mode;
6060
static bool disable_sep;
6161
static int mca_btl_ofi_init_device(struct fi_info *info);
6262

63+
/* qsort comparator for an array of domain name strings */
64+
static int domain_name_compare(const void *a, const void *b)
65+
{
66+
return strcmp(*(const char *const *) a, *(const char *const *) b);
67+
}
68+
6369
/* validate information returned from fi_getinfo().
6470
* return OPAL_ERROR if we dont have what we need. */
6571
static int validate_info(struct fi_info *info, uint64_t required_caps, char **include_list,
@@ -244,6 +250,8 @@ static int mca_btl_ofi_component_close(void)
244250
{
245251
int ret;
246252
ret = opal_common_ofi_close();
253+
free(mca_btl_ofi_component.modules);
254+
mca_btl_ofi_component.modules = NULL;
247255
/* If we don't sleep, sockets provider freaks out. Ummm this is a scary comment */
248256
sleep(1);
249257
return ret;
@@ -285,7 +293,8 @@ static mca_btl_base_module_t **mca_btl_ofi_component_init(int *num_btl_modules,
285293
return NULL;
286294
}
287295

288-
struct fi_info *info, *info_list = NULL, *selected_info = NULL;
296+
struct fi_info *info, *info_list = NULL;
297+
const char **unique_domains = NULL;
289298
struct fi_info hints = {0};
290299
struct fi_ep_attr ep_attr = {0};
291300
struct fi_rx_attr rx_attr = {0};
@@ -447,40 +456,130 @@ static mca_btl_base_module_t **mca_btl_ofi_component_init(int *num_btl_modules,
447456

448457
info = info_list;
449458

450-
while (info) {
451-
rc = validate_info(info, required_caps, include_list, exclude_list);
452-
if (OPAL_SUCCESS == rc) {
453-
/* Device passed sanity check, let's make a module.
454-
*
455-
* The initial fi_getinfo() call will return a list of providers
456-
* available for this process. once a provider is selected from the
457-
* list, we will cycle through the remaining list to identify NICs
458-
* serviced by this provider, and try to pick one on the same NUMA
459-
* node as this process. If there are no NICs on the same NUMA node,
460-
* we pick one in a manner which allows all ranks to make balanced
461-
* use of available NICs on the system.
462-
*
463-
* Most providers give a separate fi_info object for each NIC,
464-
* however some may have multiple info objects with different
465-
* attributes for the same NIC. The initial provider attributes
466-
* are used to ensure that all NICs we return provide the same
467-
* capabilities as the initial one.
468-
*/
469-
selected_info = opal_common_ofi_select_provider(info, &opal_process_info);
470-
rc = mca_btl_ofi_init_device(selected_info);
471-
if (OPAL_SUCCESS == rc) {
472-
info = selected_info;
459+
/* Count unique NIC domains for the selected provider to determine how many
460+
* modules this process should create. This avoids creating resources that
461+
* will never be used. We match by both provider name and domain name to
462+
* avoid double-counting entries that share a physical NIC but differ in
463+
* provider variant (e.g., efa vs efa-direct) or attributes. */
464+
int num_nics = 0;
465+
const char *first_prov = NULL;
466+
if (resource_count > 0) {
467+
unique_domains = (const char **) calloc(resource_count, sizeof(const char *));
468+
}
469+
{
470+
struct fi_info *tmp = info_list;
471+
while (tmp && NULL != unique_domains) {
472+
if (OPAL_SUCCESS == validate_info(tmp, required_caps, include_list, exclude_list)) {
473+
const char *p = (NULL != tmp->fabric_attr) ? tmp->fabric_attr->prov_name : NULL;
474+
const char *d = (NULL != tmp->domain_attr) ? tmp->domain_attr->name : NULL;
475+
if (NULL == first_prov) first_prov = p;
476+
if (NULL != first_prov && NULL != p && 0 == strcmp(first_prov, p) && NULL != d) {
477+
/* Only count unique domain names */
478+
bool dup = false;
479+
for (int i = 0; i < num_nics; i++) {
480+
if (0 == strcmp(unique_domains[i], d)) { dup = true; break; }
481+
}
482+
if (!dup) {
483+
unique_domains[num_nics] = d;
484+
num_nics++;
485+
}
486+
}
487+
}
488+
tmp = tmp->next;
489+
}
490+
}
491+
/* Sort the unique domain names so the list order is deterministic and
492+
* identical on every process regardless of how the provider ordered
493+
* its fi_getinfo() results. */
494+
if (1 < num_nics) {
495+
qsort(unique_domains, num_nics, sizeof(unique_domains[0]),
496+
domain_name_compare);
497+
}
498+
499+
/* Distribute NICs evenly across local processes */
500+
int num_local_procs = (int)(opal_process_info.num_local_peers + 1);
501+
int modules_per_proc = (num_nics > num_local_procs) ? (num_nics / num_local_procs) : 1;
502+
503+
/* Each local process takes a disjoint, contiguous slice of the sorted
504+
* unique NIC (domain) list so that local processes do not collapse
505+
* onto the same NICs. The list order is identical on all processes on
506+
* a node, so slices are disjoint whenever there are at least as many
507+
* NICs as local processes. */
508+
int slice_start = 0;
509+
if (0 < num_nics) {
510+
slice_start = ((int) opal_process_info.my_local_rank * modules_per_proc) % num_nics;
511+
}
512+
513+
/* Allocate the modules array dynamically based on actual need */
514+
mca_btl_ofi_component.modules = (mca_btl_ofi_module_t **)
515+
calloc(modules_per_proc, sizeof(mca_btl_ofi_module_t *));
516+
if (NULL == mca_btl_ofi_component.modules) {
517+
goto out;
518+
}
519+
mca_btl_ofi_component.modules_allocated = modules_per_proc;
520+
521+
/* Create one module per NIC (domain) in this process' slice of the
522+
* unique-domain list. Pin to a single provider (the first valid one)
523+
* so all modules share one address format. */
524+
for (int mi = 0; mi < modules_per_proc && 0 < num_nics; mi++) {
525+
const char *want = unique_domains[(slice_start + mi) % num_nics];
526+
for (info = info_list; NULL != info; info = info->next) {
527+
if (OPAL_SUCCESS != validate_info(info, required_caps, include_list, exclude_list)) {
528+
continue;
529+
}
530+
const char *prov = (NULL != info->fabric_attr)
531+
? info->fabric_attr->prov_name : NULL;
532+
const char *d = (NULL != info->domain_attr) ? info->domain_attr->name : NULL;
533+
if (NULL != first_prov && NULL != prov && NULL != d
534+
&& 0 == strcmp(first_prov, prov) && 0 == strcmp(want, d)) {
535+
(void) mca_btl_ofi_init_device(info);
473536
break;
474537
}
475538
}
476-
info = info->next;
477539
}
478540

479-
if (NULL == info) {
541+
if (0 == mca_btl_ofi_component.module_count) {
480542
BTL_VERBOSE(("No provider is selected"));
481543
goto out;
482544
}
483545

546+
/* Publish all module endpoint names in a single modex blob so peers can
547+
* pair modules by index. Layout: uint32 nmodules, then per module:
548+
* uint32 namelen, namelen bytes. */
549+
{
550+
size_t total = sizeof(uint32_t);
551+
for (int mi = 0; mi < mca_btl_ofi_component.module_count; mi++) {
552+
total += sizeof(uint32_t) + mca_btl_ofi_component.modules[mi]->ep_namelen;
553+
}
554+
uint8_t *blob = (uint8_t *) malloc(total);
555+
if (NULL == blob) {
556+
BTL_ERROR(("failed to allocate modex blob"));
557+
goto out;
558+
}
559+
uint8_t *p = blob;
560+
uint32_t nm = (uint32_t) mca_btl_ofi_component.module_count;
561+
memcpy(p, &nm, sizeof(uint32_t));
562+
p += sizeof(uint32_t);
563+
for (int mi = 0; mi < mca_btl_ofi_component.module_count; mi++) {
564+
uint32_t nl = (uint32_t) mca_btl_ofi_component.modules[mi]->ep_namelen;
565+
memcpy(p, &nl, sizeof(uint32_t));
566+
p += sizeof(uint32_t);
567+
memcpy(p, mca_btl_ofi_component.modules[mi]->ep_name, nl);
568+
p += nl;
569+
}
570+
OPAL_MODEX_SEND(rc, PMIX_GLOBAL, &mca_btl_ofi_component.super.btl_version, blob, total);
571+
free(blob);
572+
if (OPAL_SUCCESS != rc) {
573+
BTL_ERROR(("modex send failed"));
574+
goto out;
575+
}
576+
/* ep_name copies no longer needed after packing into blob */
577+
for (int mi = 0; mi < mca_btl_ofi_component.module_count; mi++) {
578+
free(mca_btl_ofi_component.modules[mi]->ep_name);
579+
mca_btl_ofi_component.modules[mi]->ep_name = NULL;
580+
}
581+
}
582+
484583
/* pass module array back to caller */
485584
base_modules = calloc(mca_btl_ofi_component.module_count, sizeof(*base_modules));
486585
if (NULL == base_modules) {
@@ -496,6 +595,21 @@ static mca_btl_base_module_t **mca_btl_ofi_component_init(int *num_btl_modules,
496595
*num_btl_modules = mca_btl_ofi_component.module_count;
497596

498597
out:
598+
if (NULL == base_modules) {
599+
/* Initialization failed after some modules may already have been
600+
* created: finalize them so we do not leak libfabric resources. */
601+
for (int mi = 0; mi < mca_btl_ofi_component.module_count; mi++) {
602+
if (NULL != mca_btl_ofi_component.modules[mi]) {
603+
(void) mca_btl_ofi_finalize(
604+
(mca_btl_base_module_t *) mca_btl_ofi_component.modules[mi]);
605+
}
606+
}
607+
mca_btl_ofi_component.module_count = 0;
608+
free(mca_btl_ofi_component.modules);
609+
mca_btl_ofi_component.modules = NULL;
610+
mca_btl_ofi_component.modules_allocated = 0;
611+
}
612+
free(unique_domains);
499613
if (include_list) {
500614
opal_argv_free(include_list);
501615
}
@@ -516,7 +630,7 @@ static int mca_btl_ofi_init_device(struct fi_info *info)
516630
size_t namelen;
517631
size_t num_contexts_to_create;
518632

519-
char *linux_device_name;
633+
char *domain_name = NULL;
520634
void *ep_name = NULL;
521635

522636
struct fi_info *ofi_info;
@@ -564,21 +678,25 @@ static int mca_btl_ofi_init_device(struct fi_info *info)
564678
fi_strerror(-rc)));
565679
}
566680

567-
linux_device_name = info->domain_attr->name;
681+
domain_name = strdup(info->domain_attr->name);
682+
if (NULL == domain_name) {
683+
BTL_ERROR(("failed to duplicate domain name"));
684+
goto fail;
685+
}
568686
BTL_VERBOSE(
569-
("initializing dev:%s provider:%s", linux_device_name, info->fabric_attr->prov_name));
687+
("initializing dev:%s provider:%s", domain_name, info->fabric_attr->prov_name));
570688

571689
/* fabric */
572690
rc = opal_common_ofi_fi_fabric(ofi_info->fabric_attr, &fabric);
573691
if (0 != rc) {
574-
BTL_VERBOSE(("%s failed fi_fabric with err=%s", linux_device_name, fi_strerror(-rc)));
692+
BTL_VERBOSE(("%s failed fi_fabric with err=%s", domain_name, fi_strerror(-rc)));
575693
goto fail;
576694
}
577695

578696
/* domain */
579697
rc = opal_common_ofi_fi_domain(fabric, ofi_info, &domain);
580698
if (0 != rc) {
581-
BTL_VERBOSE(("%s failed fi_domain with err=%s", linux_device_name, fi_strerror(-rc)));
699+
BTL_VERBOSE(("%s failed fi_domain with err=%s", domain_name, fi_strerror(-rc)));
582700
goto fail;
583701
}
584702

@@ -591,7 +709,7 @@ static int mca_btl_ofi_init_device(struct fi_info *info)
591709
av_attr.type = FI_AV_MAP;
592710
rc = fi_av_open(domain, &av_attr, &av, NULL);
593711
if (0 != rc) {
594-
BTL_VERBOSE(("%s failed fi_av_open with err=%s", linux_device_name, fi_strerror(-rc)));
712+
BTL_VERBOSE(("%s failed fi_av_open with err=%s", domain_name, fi_strerror(-rc)));
595713
goto fail;
596714
}
597715

@@ -616,7 +734,7 @@ static int mca_btl_ofi_init_device(struct fi_info *info)
616734
rc = fi_scalable_ep(domain, ofi_info, &ep, NULL);
617735
if (0 != rc) {
618736
BTL_VERBOSE(
619-
("%s failed fi_scalable_ep with err=%s", linux_device_name, fi_strerror(-rc)));
737+
("%s failed fi_scalable_ep with err=%s", domain_name, fi_strerror(-rc)));
620738
goto fail;
621739
}
622740

@@ -639,7 +757,7 @@ static int mca_btl_ofi_init_device(struct fi_info *info)
639757

640758
rc = fi_endpoint(domain, ofi_info, &ep, NULL);
641759
if (0 != rc) {
642-
BTL_VERBOSE(("%s failed fi_endpoint with err=%s", linux_device_name, fi_strerror(-rc)));
760+
BTL_VERBOSE(("%s failed fi_endpoint with err=%s", domain_name, fi_strerror(-rc)));
643761
goto fail;
644762
}
645763

@@ -658,7 +776,7 @@ static int mca_btl_ofi_init_device(struct fi_info *info)
658776
/* enable the endpoint for using */
659777
rc = fi_enable(ep);
660778
if (0 != rc) {
661-
BTL_VERBOSE(("%s failed fi_enable with err=%s", linux_device_name, fi_strerror(-rc)));
779+
BTL_VERBOSE(("%s failed fi_enable with err=%s", domain_name, fi_strerror(-rc)));
662780
goto fail;
663781
}
664782

@@ -669,7 +787,7 @@ static int mca_btl_ofi_init_device(struct fi_info *info)
669787
module->domain = domain;
670788
module->av = av;
671789
module->ofi_endpoint = ep;
672-
module->linux_device_name = linux_device_name;
790+
module->domain_name = domain_name;
673791
module->outstanding_rdma = 0;
674792
module->use_virt_addr = false;
675793
module->use_fi_mr_bind = false;
@@ -704,7 +822,7 @@ static int mca_btl_ofi_init_device(struct fi_info *info)
704822
&ep_name,
705823
&namelen);
706824
if (OPAL_SUCCESS != rc) {
707-
BTL_VERBOSE(("%s failed opal_common_ofi_fi_getname with err=%d", linux_device_name, rc));
825+
BTL_VERBOSE(("%s failed opal_common_ofi_fi_getname with err=%d", domain_name, rc));
708826
goto fail;
709827
}
710828

@@ -720,12 +838,12 @@ static int mca_btl_ofi_init_device(struct fi_info *info)
720838
}
721839
}
722840

723-
/* post our endpoint name so peer can use it to connect to us */
724-
OPAL_MODEX_SEND(rc, PMIX_GLOBAL, &mca_btl_ofi_component.super.btl_version, ep_name, namelen);
725-
mca_btl_ofi_component.namelen = namelen;
726-
free(ep_name);
841+
/* Save endpoint name on the module; modex send happens after all modules are created */
842+
module->ep_name = ep_name;
843+
module->ep_namelen = namelen;
727844

728845
/* add this module to the list */
846+
module->module_index = *module_count;
729847
mca_btl_ofi_component.modules[(*module_count)++] = module;
730848

731849
return OPAL_SUCCESS;
@@ -765,6 +883,7 @@ static int mca_btl_ofi_init_device(struct fi_info *info)
765883
if (NULL != fabric) {
766884
opal_common_ofi_fabric_release(fabric);
767885
}
886+
free(domain_name);
768887
free(module);
769888

770889
if (NULL != ep_name) {

0 commit comments

Comments
 (0)