Skip to content

MPI_Get_hw_resource_info returns an empty info object (no resource keys populated) #14008

Description

@jsquyres

MPI_Get_hw_resource_info returns an empty info object (no resource keys populated)

Conformance status: PARTIAL | MPI-5.0 chapter: 9 - MPI Environmental Management | Parent: #13994 | Epic: #13987

Summary

MPI_Get_hw_resource_info is implemented as a stub: it validates its argument and returns a valid, freeable, but empty MPI_Info object — it never populates a single hardware-resource (key, value) pair. MPI-5.0 §9.1.2 requires the returned info object to describe the hardware resources the calling process is executing on, with URI-form keys (e.g. hwloc://NUMANode) and a "true"/"false" value per key indicating whether the process is restricted to a single instance of that resource type. Because Open MPI returns zero keys on every system (even though the hwloc topology is already loaded for binding and MPI_Comm_split_type), the standard's documented Example 9.1 discovery workflow finds nothing everywhere. The symbol is present and the success path is legal, so this is a present-but-substantively-incomplete feature (PARTIAL), not a hard violation.

What MPI-5.0 requires

MPI-5.0 §9.1.2 ("Environmental Inquiries"), subsection "Inquire Hardware Resource Information", printed p.441 (PDF p.493). MPI_Get_hw_resource_info is a standard-mandated procedure with C, Fortran 2008, and Fortran bindings. The normative description states:

MPI_GET_HW_RESOURCE_INFO is a local procedure that returns an info object containing information pertaining to the hardware platform on which the calling MPI process is executing at the moment of the call. This information is stored as (key,value) pairs where each key is the name of a hardware resource type and its value is set to "true" if the calling MPI process is restricted to a single instance of a hardware resource of that type and "false" otherwise. The order in which the keys are stored in hw_info is unspecified. This procedure will return different information for MPI processes that are restricted to different hardware resources. Otherwise, info objects with identical (key, value) pairs are returned. The user is responsible for freeing hw_info via MPI_INFO_FREE.

On the key format (same section, p.441):

The keys stored in the hw_info object have a Uniform Resource Identifier (URI) format. The first part of the URI indicates the key provider and the second part conforms to the format used by this key provider. The key provider "mpi://" is reserved for exclusive use by the MPI standard.

The intended consumer use is given in an Advice to users (p.442):

The keys stored in the info object returned by this procedure can be used in MPI_COMM_SPLIT_TYPE with the split_type value MPI_COMM_TYPE_HW_GUIDED or MPI_COMM_TYPE_RESOURCE_GUIDED as key values for the info key "mpi_hw_resource_type".

Standard Example 9.1 (p.442-443) demonstrates the full round-trip: call MPI_Get_hw_resource_info, iterate the returned keys with MPI_Info_get_nkeys / MPI_Info_get_nthkey, find the key "hwloc://NUMANode", inspect whether its value is "true", then reuse that key string as the value of "mpi_hw_resource_type" in an MPI_Comm_split_type(..., MPI_COMM_TYPE_RESOURCE_GUIDED, ...) call. The example cannot proceed past nb_keys == 0.

The C binding (p.441) is int MPI_Get_hw_resource_info(MPI_Info *hw_info). A Fortran 2008 binding exists (p.441): MPI_Get_hw_resource_info(hw_info, ierror) with TYPE(MPI_Info), INTENT(OUT) :: hw_info — so a real mpi_f08 reproducer is in scope.

Note that the standard places no numeric floor on the number of keys, and the Advice to users (p.441) notes the returned resources may be constrained "by access permissions or other constraints like environment variables and OS settings." An empty info is therefore a defensible degenerate result in principle. The gap is that Open MPI returns empty unconditionally, on every system, so the substantive feature described above is simply absent.

What Open MPI currently does

The C implementation allocates an empty info object and returns success without ever calling info_set.

ompi/mpi/c/get_hw_resource_info.c.in, function get_hw_resource_info (lines 44-65 as of 408ced4):

PROTOTYPE ERROR_CLASS get_hw_resource_info(INFO_OUT info)
{
    if (MPI_PARAM_CHECK) {
        if (NULL == info) {
            return OMPI_ERRHANDLER_NOHANDLE_INVOKE(MPI_ERR_INFO,
                                          FUNC_NAME);
        }
    }

    /*
     * Just allocate an info object.  No resources currently being
     * specified so just return empty info object.
     */

    *info = ompi_info_allocate ();
    if (NULL == (*info)) {
        return OMPI_ERRHANDLER_NOHANDLE_INVOKE(MPI_ERR_NO_MEM,
                                      FUNC_NAME);
    }

    return MPI_SUCCESS;
}

The developer comment ("No resources currently being specified so just return empty info object", lines 53-56) confirms this is an intentional placeholder. There are zero ompi_info_set calls, so MPI_Info_get_nkeys on the result always returns 0.

The Fortran mpi_f08 binding is a thin wrapper over the same C entry point, so it inherits the empty result. ompi/mpi/fortran/use-mpi-f08/get_hw_resource_info.c.in, function get_hw_resource_info (lines 23-34 as of 408ced4):

PROTOTYPE VOID get_hw_resource_info(INFO_OUT info)
{
    int c_ierr;
    MPI_Info c_info;

    c_ierr = @INNER_CALL@(&c_info);
    if (NULL != ierr) *ierr = OMPI_INT_2_FINT(c_ierr);

    if (MPI_SUCCESS == c_ierr) {
        *info = PMPI_Info_c2f(c_info);
    }
}

The consumer side already exists and works for bare-token values. ompi/communicator/comm.c defines the recognized resource-type vocabulary ompi_comm_split_type_hw_guided_support[] (lines 79-94 as of 408ced4) — cluster, nvlink, cu, host, mpi_shared_memory, board, numanode, socket, l3cache, l2cache, l1cache, core, hwthread — and ompi_comm_split_type matches the mpi_hw_resource_type value against that table (lines 1491-1499 as of 408ced4):

        flag = 0;
        for (int i = 0; NULL != ompi_comm_split_type_hw_guided_support[i].info_value; ++i) {
            if (0 == strncasecmp(value->string,
                                 ompi_comm_split_type_hw_guided_support[i].info_value,
                                 strlen(ompi_comm_split_type_hw_guided_support[i].info_value))) {
                split_type = ompi_comm_split_type_hw_guided_support[i].split_type;
                flag = 1;
                break;
            }
        }

Why this is a gap

The standard requires a non-trivial, topology-derived info object: one URI-form key per hardware resource type the provider can describe, each carrying a "true"/"false" value computed from the calling process's actual resource restriction. Open MPI returns an unconditionally empty object with zero keys. Every clause of the normative description — "each key is the name of a hardware resource type", value "set to 'true' ... and 'false' otherwise", "different information for MPI processes that are restricted to different hardware resources" — describes behavior that is never exercised because no key is ever emitted. The procedure therefore exists in name and signature only; the feature it is defined to provide is absent on every platform, and Example 9.1's documented discovery-then-split workflow is unusable. This is a present-but-incomplete implementation = PARTIAL.

Reproducers

C reproducer (mpicc)

Compile with mpicc -o hwinfo hwinfo.c and run with mpirun -np 1 ./hwinfo on any node with a normal hwloc topology (a multi-core machine; no special hardware or memory required). The program exits non-zero (FAIL) on today's stub because the returned info has zero keys; after the fix it must find at least one URI-form key whose value is exactly "true" or "false".

#include <mpi.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
    MPI_Init(&argc, &argv);

    MPI_Info hw_info;
    MPI_Get_hw_resource_info(&hw_info);

    int nkeys = 0;
    MPI_Info_get_nkeys(hw_info, &nkeys);
    printf("MPI_Get_hw_resource_info returned %d key(s)\n", nkeys);

    int uri_keys = 0;       /* keys in <provider>://... URI form              */
    int bool_values = 0;    /* keys whose value is exactly "true" or "false"  */
    for (int i = 0; i < nkeys; ++i) {
        char key[MPI_MAX_INFO_KEY];
        MPI_Info_get_nthkey(hw_info, i, key);

        int vlen = 0, flag = 0;
        MPI_Info_get_string(hw_info, key, &vlen, NULL, &flag);
        char value[256] = {0};
        if (flag && vlen > 0 && vlen < (int) sizeof(value)) {
            MPI_Info_get_string(hw_info, key, &vlen, value, &flag);
        }
        printf("  key[%d] = \"%s\" -> \"%s\"\n", i, key, value);

        if (strstr(key, "://") != NULL) {
            uri_keys++;
        }
        if (0 == strcmp(value, "true") || 0 == strcmp(value, "false")) {
            bool_values++;
        }
    }

    MPI_Info_free(&hw_info);

    /* MPI-5.0 Sec 9.1.2 p.441: keys are URI-form hardware-resource-type names;
     * each value is "true" if the calling process is restricted to a single
     * instance of that resource type, "false" otherwise. */
    int pass = (nkeys > 0) && (uri_keys == nkeys) && (bool_values == nkeys);
    if (pass) {
        printf("PASS: %d URI-form key(s), all with boolean value\n", nkeys);
    } else {
        printf("FAIL: expected >=1 URI-form key, each with a \"true\"/\"false\" "
               "value (got nkeys=%d uri=%d bool=%d)\n", nkeys, uri_keys, bool_values);
    }

    MPI_Finalize();
    return pass ? 0 : 1;
}

BEFORE (stub): prints returned 0 key(s), then FAIL: ... got nkeys=0 ..., and exits 1. AFTER (fixed): prints one or more hwloc://...-style keys each with value true or false, then PASS, and exits 0.

This is a run-time check (the program compiles and runs cleanly today; it fails by returning a non-zero exit code, not by crashing or by failing to compile).

Fortran reproducer (use mpi_f08, mpifort)

The MPI-5.0 Fortran 2008 binding MPI_Get_hw_resource_info(hw_info, ierror) exists (§9.1.2, p.441), and Open MPI ships it (ompi/mpi/fortran/use-mpi-f08/get_hw_resource_info.c.in; interface in ompi/mpi/fortran/use-mpi-f08/mod/mpi-f08-interfaces-generated.h:3065-3073). Compile with mpifort -o hwinfo_f08 hwinfo.f90 and run mpirun -np 1 ./hwinfo_f08.

program hwinfo_f08
   use mpi_f08
   implicit none

   type(MPI_Info) :: hw_info
   integer :: nkeys, i, vlen, ierr
   logical :: flag
   integer :: uri_keys, bool_values
   character(len=MPI_MAX_INFO_KEY) :: key
   character(len=256)              :: value

   call MPI_Init()

   call MPI_Get_hw_resource_info(hw_info)

   call MPI_Info_get_nkeys(hw_info, nkeys)
   print '(A,I0,A)', "MPI_Get_hw_resource_info returned ", nkeys, " key(s)"

   uri_keys    = 0
   bool_values = 0
   do i = 0, nkeys - 1
      call MPI_Info_get_nthkey(hw_info, i, key)
      value = ""
      call MPI_Info_get_string(hw_info, trim(key), vlen, value, flag)
      print '(A,I0,A,A,A,A,A)', "  key[", i, "] = """, trim(key), &
                                """ -> """, trim(value), """"
      if (index(key, "://") > 0)                            uri_keys    = uri_keys    + 1
      if (trim(value) == "true" .or. trim(value) == "false") bool_values = bool_values + 1
   end do

   call MPI_Info_free(hw_info)

   ! MPI-5.0 Sec 9.1.2 p.441: URI-form keys, each value "true"/"false".
   if (nkeys > 0 .and. uri_keys == nkeys .and. bool_values == nkeys) then
      print '(A,I0,A)', "PASS: ", nkeys, " URI-form key(s), all with boolean value"
      call MPI_Finalize()
   else
      print '(A,I0,A,I0,A,I0,A)', "FAIL: expected >=1 URI-form key each with a " // &
            """true""/""false"" value (got nkeys=", nkeys, " uri=", uri_keys, &
            " bool=", bool_values, ")"
      call MPI_Finalize()
      call exit(1)
   end if
end program hwinfo_f08

BEFORE (stub): prints returned 0 key(s) and FAIL: ..., exits 1. AFTER (fixed): prints URI-form keys with true/false values, prints PASS, exits 0.

These reproducers are starting points. The implementor must add exhaustive singleton unit tests and/or mpirun/mpiexec-launched functional tests to fully prove the gap is closed.

Honesty notes: both reproducers are single-rank, run on any normal multi-core node, need no special memory or hardware, and signal failure via a non-zero exit code (they do not crash and do compile today). The "true"/"false" value is binding-dependent: an unbound rank on a node with one NUMA node should still see hwloc://NUMANode = "true", while a rank whose cpuset spans two cores should see hwloc://Core = "false". To exercise the false path deterministically, launch with an explicit binding that spans more than one of some resource type (e.g. mpirun --bind-to none or --map-by ... with a multi-object cpuset). Keys must populate regardless of binding, which is why nkeys > 0 is the robust before/after discriminator.

Success criteria

  • MPI_Get_hw_resource_info returns an info object with at least one key on a node with a known multi-level hwloc topology, instead of always zero keys (§9.1.2, p.441: "returns an info object containing information pertaining to the hardware platform"; Example 9.1).
  • Every key is in URI form <provider>://<name> (e.g. hwloc://NUMANode, hwloc://Package, hwloc://Core) (§9.1.2, p.441: "The keys stored in the hw_info object have a Uniform Resource Identifier (URI) format").
  • Each key's value is exactly the string "true" or "false", computed from whether the calling process is restricted to a single instance of that resource type, not a hard-coded constant (§9.1.2, p.441: value "set to 'true' if the calling MPI process is restricted to a single instance of a hardware resource of that type and 'false' otherwise").
  • Two ranks with different hardware restrictions can observe different (key, value) pairs; ranks with identical restrictions observe identical info (§9.1.2, p.441: "will return different information for MPI processes that are restricted to different hardware resources. Otherwise, info objects with identical (key, value) pairs are returned").
  • The C reproducer above exits 0; the mpi_f08 reproducer exits 0.
  • Behavior is identical (modulo binding-dependent true/false) through the C and mpi_f08 bindings (both bindings mandated by §9.1.2, p.441).
  • The empty-info fallback is retained for the case where no hwloc topology is available, so nothing that builds/runs today regresses.

Where to fix (code pointers)

  • ompi/mpi/c/get_hw_resource_info.c.in, function get_hw_resource_info (lines 44-65 as of 408ced4) — the stub to replace; add topology enumeration after the *info = ompi_info_allocate() allocation.
  • opal/mca/hwloc/hwloc-internal.h — exposes opal_hwloc_topology (hwloc_topology_t) and opal_hwloc_topology_inited, the loaded node topology to enumerate and the guard for its availability.
  • ompi/communicator/comm.c, ompi_comm_split_type_hw_guided_support[] (lines 79-94 as of 408ced4) — the canonical hardware-resource-type vocabulary; the producer's URI key names should map to these resource types so the two sides share one source of truth.
  • The mpi_f08 wrapper ompi/mpi/fortran/use-mpi-f08/get_hw_resource_info.c.in needs no change — it forwards to the C entry point and will reflect the populated info automatically.

Recommended approach

Populate the info object from the already-loaded opal_hwloc_topology instead of returning empty. The non-obvious part — and the place the prior internal sketch got it wrong — is that the value is not a resource-type token; it is "true"/"false" reflecting the calling process's binding, per the quoted standard sentence. Mere "this type exists on the node" is the wrong computation.

Suggested design:

  1. After *info = ompi_info_allocate(), guard on opal_hwloc_topology_inited. If hwloc has no topology, keep the current empty-info result (legal degenerate fallback) and return MPI_SUCCESS.
  2. Obtain the calling process's cpuset/binding from the topology (e.g. via hwloc_get_cpubind / the topology's allowed cpuset, mirroring how binding is already consulted elsewhere in OPAL).
  3. For each hwloc object type relevant to splitting — HWLOC_OBJ_NUMANODE, HWLOC_OBJ_PACKAGE, HWLOC_OBJ_L3CACHE, HWLOC_OBJ_L2CACHE, HWLOC_OBJ_L1CACHE, HWLOC_OBJ_CORE, HWLOC_OBJ_PU — that is present on the node (hwloc_get_nbobjs_by_type(...) > 0):
    • Build the URI key "hwloc://<HwlocCamelName>" (e.g. hwloc://NUMANode, hwloc://Package, hwloc://Core, hwloc://L3Cache, hwloc://PU).
    • Compute the value: "true" iff the process's cpuset is contained within (does not span more than) a single object of that type, else "false". With hwloc this is a containment/coverage test of the process cpuset against the per-type object cpusets.
    • ompi_info_set(*info, key, value).
  4. On any ompi_info_set failure, free and return MPI_ERR_NO_MEM, consistent with the existing allocation-failure path.

Keep the producer's resource-type set aligned with the consumer vocabulary in comm.c (ompi_comm_split_type_hw_guided_support[]) so a key the producer emits maps to a split type the consumer understands — ideally by deriving both from one shared table (hwloc obj type -> URI CamelName -> consumer token) rather than hard-coding a second list that can drift.

Patch-sketch shape (illustrative, not final) in get_hw_resource_info.c.in after the allocation:

    if (!opal_hwloc_topology_inited) {
        return MPI_SUCCESS;          /* legal empty fallback: no topology */
    }
    for (each relevant hwloc obj type T present on the node) {
        const char *key   = uri_key_for(T);        /* "hwloc://NUMANode", ... */
        const char *value = proc_restricted_to_single(T) ? "true" : "false";
        if (OMPI_SUCCESS != ompi_info_set(*info, key, value)) {
            return OMPI_ERRHANDLER_NOHANDLE_INVOKE(MPI_ERR_NO_MEM, FUNC_NAME);
        }
    }
    return MPI_SUCCESS;

Important scope note: emitting URI-form keys (hwloc://NUMANode) is mandatory and must not be weakened to bare tokens (numanode) just to satisfy the current consumer. The consumer at comm.c:1491-1499 today matches only bare lowercase tokens via strncasecmp(..., strlen("numanode")) and would reject hwloc://NUMANode; teaching the consumer to accept the URI form is the sibling mpi_hw_resource_type gap (ch07), not this issue. Producing standard-compliant URI keys here and fixing URI matching there together close Example 9.1's discover-then-split round-trip.

Effort: medium — reuses the already-loaded opal_hwloc_topology and existing consumer vocabulary, but adds per-type binding-containment computation. Risk: low-to-medium — the empty-info path remains the fallback when hwloc is unavailable, so nothing that works today regresses; the main hazard is getting the "true"/"false" containment semantics right.

Cross-references

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions