Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The libnrt ↔ libnccom ABI

Two binaries, pinned together. libnrt.so.2.31.24.0 (aws-neuronx-runtime-lib 2.31.24.0-0b044f4ce, build-id 8bb57aba0fb2e0035f1d88e9fc4fb3e7387c102e, SONAME libnrt.so.1) and libnccom.so.2.31.24 (aws-neuronx-collectives 2.31.24.0-1a31ba186, build-id 9c00176c081788c9435d27d11bb40e92495463f0, SONAME libnccom.so.2). Both ELF64 x86-64 DYN, NOT stripped. A third file, libnccom-net.so (build-id 3415f096d479e7d7bef506bb68bc0fa7551a654a), exports the ncclNetPlugin_v4/v5/v6 structs and is catalogued here only at its boundary edge — its internals are owned by net-plugin. Every address on a FORWARD row is a libnccom.so.2.31.24 text VMA; every address on a REVERSE row is a libnrt.so.2.31.24.0 text VMA; .text/.rodata VMA == file offset in both. Definer-binary names appear demangled (nm -D --defined-only), user-binary references with their @version tag (nm -D --undefined-only --with-symbol-versions).

Evidence grade: Confirmed (byte-anchored, both sides re-derived) — the 37 / 16 / 4 counts were re-run with nm -D on each binary independently; every FORWARD row is anchored to its libnccom definer address AND its libnrt .bss slot + call *slot(%rip) site; every REVERSE row to its libnrt @@NRT_2.0.0 definer address AND its libnccom @NRT_2.0.0 UND import; readelf -d/-V confirm the asymmetric link (no DT_NEEDED libnccom in libnrt; DT_NEEDED libnrt.so.1 + VERNEED NRT_2.0.0 in libnccom). · Part XII — Multi-Node Collectives · back to index

Abstract

The runtime and the collectives library link to each other in two opposite directions by two different mechanisms, and this page is the symbol-by-symbol closure of that boundary. In the FORWARD direction (libnrtlibnccom) there is no ELF link at all: libnrt.so carries no DT_NEEDED on libnccom (its NEEDED list is nine toolchain libraries — libgcc_s, libutil, librt, libpthread, libdl, libstdc++, libm, libc, ld-linux), and it holds zero neuron* symbols in its dynamic table. Instead the embedded ncclInit (libnrt 0x1bff30) dlopens "libnccom.so" with RTLD_NOW at runtime and dlsyms 37 neuron* entry points into a contiguous .bss function-pointer table (libnrt 0xc967b8..0xc968d8, 8-byte stride). Thirty-eight thin nccl* trampolines call through those slots (call *0xc968d0(%rip) etc.). The 37 targets are plain unversioned T exports in libnccom; the only handshake is a numeric one — neuronGetVersionInfo must report compatibility id 89, checked at libnrt 0x1bff9b (cmpq $0x59,…) against libnccom's movq $0x59,… (0x464a8).

In the REVERSE direction (libnccomlibnrt) the link is hard and versioned: libnccom.so carries DT_NEEDED libnrt.so.1 and a .gnu.version_r entry requiring (libnrt.so.1, NRT_2.0.0) — and only NRT_2.0.0, never NRT_3.0.0. It imports 20 symbols, all tagged @NRT_2.0.0: 16 nec_* device/topology/proxy reverse-callbacks and 4 nrt_* runtime queries, every one resolving name-for-name against a libnrt @@NRT_2.0.0 definition. The page is organized as one catalogue with three groups — FORWARD (37 neuron* dlsym targets), REVERSE (16 nec_* + 4 nrt_* versioned imports), and the NET edge (ncclNetPlugin_v4/v5/v6, which belong to libnccom-net's ncclNet_vN ABI and are not part of the nec_/neuron boundary) — followed by the resolution-mechanism notes and the asymmetry quirk that a reimplementer must reproduce exactly.

For reimplementation, the contract of the boundary is:

  • The FORWARD table is a dlsym target list, not an export list. A reimplementation of libnccom must define all 37 neuron* symbols as plain global text exports (no version node) so libnrt's dlsym finds them; a reimplementation of libnrt must dlopen+dlsym them in order, NULL-check every result, and zero all 37 slots on the first failure (collectives degrade to all-or-nothing). There is no DT_NEEDED on this leg in either direction.
  • The REVERSE table is a versioned linker import. A reimplementation of libnccom must DT_NEEDED libnrt.so.1 and bind all 20 nec_*/nrt_* imports at @NRT_2.0.0; a reimplementation of libnrt must export those same 20 names at @@NRT_2.0.0. Dropping any one of the 20 from the NRT_2.0.0 node breaks libnccom at load time (before any dlopen).
  • The two skew axes are independent. The reverse leg is gated by the ELF symbol version (NRT_2.0.0); the forward leg is gated by the numeric compat id 89 returned by neuronGetVersionInfo. They are checked by different mechanisms at different times (link-time vs. first-call). Reproduce both, or one half of the boundary silently mis-negotiates.
  • The NET structs are a different ABI. ncclNetPlugin_v4/v5/v6 are exported by libnccom-net.so, follow the upstream ncclNet_vN C-struct-of-function-pointers shape, and reach libnrt only through the single nrt_get_libnccl_net@NRT_2.0.0 handoff. Do not conflate them with the neuron* dlsym set.

At a glance

Boundarylibnrt.so.1libnccom.so.2 (two packages, two build-ids)
FORWARD37 neuron*libnrt dlopen+dlsym, no version, no DT_NEEDED
REVERSE16 nec_* + 4 nrt_* = 20 — libnccom linker imports, all @NRT_2.0.0
NET (edge)ncclNetPlugin_v4/v5/v6 in libnccom-net.soncclNet_vN ABI, not the nec_/neuron set
FORWARD definerlibnccom.so.2.31.24 .text 0x46150..0x49ac0 (plain T, no @)
FORWARD userlibnrt .bss slot table 0xc967b8..0xc968d8 (8-byte stride, 37 slots)
REVERSE definerlibnrt.so.2.31.24.0 .text 0x1bfd40..0x1bff20 (nec_) + 0x83e10/0x84210/0x940b0/0x94400 (nrt_), all @@NRT_2.0.0
REVERSE userlibnccom UND imports, all @NRT_2.0.0
Forward gatenumeric compat id 89 (0x59): libnrt 0x1bff9b cmpqlibnccom 0x464a8 movq
Reverse gateELF version NRT_2.0.0 (libnccom VERNEED idx 3 → libnrt version_d node 2)
Forward entryncclInit @libnrt 0x1bff30 (dlopen "libnccom.so", RTLD_NOW)
Re-derived countsnm -D: FORWARD 37 · REVERSE 16 nec_ + 4 nrt_ (20 @NRT_2.0.0) · NET 3

Resolution Mechanisms

The two halves of the boundary do not just point in opposite directions — they are resolved by two unrelated dynamic-linker facilities, and conflating them is the classic reimplementation error.

FORWARD — runtime dlopen + dlsym, unversioned

libnrt never names libnccom in its ELF. readelf -d libnrt.so lists no NEEDED libnccom, and nm -D libnrt.so | grep neuron is empty — there is no neuron* symbol, defined or undefined, in the runtime's dynamic table. The link is forged at runtime: ncclInit (0x1bff30) calls dlopen("libnccom.so", RTLD_NOW), then dlsyms each neuron* name out of the returned handle and stores the resolved pointer into a fixed .bss slot. Because dlsym matches on name only, the 37 targets are exported from libnccom as plain T globals with no @version tag — and indeed nm -D --defined-only libnccom.so | grep neuron shows all 37 as bare T, none versioned. The nccl* trampolines that the rest of libnrt calls are one-instruction indirect jumps through the slot: e.g. ncclGetUniqueId @0x1c0a80 : call *0xad5e4a(%rip) # 0xc968d0 <neuronGetUniqueId> and ncclInitComm @0x1c0e57 : call *0xad5a63(%rip) # 0xc968c0 <neuronInitComm> (both re-read from objdump -d).

REVERSE — hard DT_NEEDED + versioned import

libnccom does name libnrt: readelf -d libnccom.so shows NEEDED libnrt.so.1, and readelf -V shows a .gnu.version_r entry File: libnrt.so.1 Cnt: 1 Name: NRT_2.0.0 (version index 3). Every nec_*/nrt_* import therefore arrives at the dynamic linker tagged @NRT_2.0.0 and is resolved by the standard symbol-versioning rule against libnrt's @@NRT_2.0.0 definitions — at load time, before ncclInit ever runs. There is exactly one required version node from libnrt (NRT_2.0.0); libnccom requires nothing from NRT_3.0.0 (the nrta_* async family), so a newer libnrt that keeps NRT_2.0.0 stays load-compatible.

QUIRK — the boundary is asymmetric by mechanism, not just by direction. FORWARD is a runtime dlsym keyed on bare name with no ELF version and a separate numeric compat gate (id 89); REVERSE is a load-time linker bind keyed on NRT_2.0.0 with no numeric gate. Exporting a nec_* callback unversioned breaks libnccom's load outright — its import is @NRT_2.0.0 and an unversioned definition does not satisfy a versioned need. Versioning a neuron* export, by contrast, changes nothing the forward leg observes: dlsym ignores the version and binds the default. The same mistake fails loudly on one leg and silently passes on the other.


The ABI Catalogue

One row per symbol. Direction is FORWARD (libnrtlibnccom, dlsym), REVERSE (libnccomlibnrt, versioned import), or NET (libnccom-net→host, ncclNet_vN). Definer is the binary that holds the T/D definition and its address; User is the binary that resolves/calls it. Signatures are from the definer-side DWARF; NRT_STATUS is the runtime's 28-value status enum. Every row is HIGH (byte-anchored on both sides) unless tagged.

FORWARD — 37 neuron* dlsym targets (definer libnccom, user libnrt)

The version probe (neuronGetVersionInfo) is dlsym'd first and gates the rest. The .bss slots descend from 0xc968d8 (probe) to 0xc967b8 (last) in 8-byte steps; each row gives the libnccom definer addr, the libnrt .bss slot, and the libnrt nccl* trampoline that calls through it.

SymbolDefiner addr (libnccom)libnrt .bss slotlibnrt trampolineRole / signatureConf
neuronGetVersionInfo0x461500xc968d8ncclGetVersionInfo 0x1c0a10version/compat probe (gate #0); void(nec_version_info_t*, uint32_t n=56) — writes compat=89 @off48HIGH
neuronGetUniqueId0x466600xc968d0ncclGetUniqueId 0x1c0a50NRT_STATUS(char id[128], int rank_n, const char *hint)HIGH
neuronInitGlobalComm0x46c400xc968c8ncclInitGlobalComm 0x1c0c30NRT_STATUS(int g_dev_id, int g_dev_count, const enc_neuron_device_info_t*, const char *root_id, void **comm, const char *hint)HIGH
neuronInitComm0x46d900xc968c0ncclInitComm 0x1c0e10NRT_STATUS(void **comm, int rank_n, char *id, int rank, const enc_neuron_device_info_t*, bool bypass_graph)HIGH
neuronBuildGraphComm0x46e900xc968b8ncclBuildGraphComm 0x1c0ff0NRT_STATUS(void *comm) — post-init graph buildHIGH
neuronGetCommInfo0x46ec00xc968b0ncclGetCommInfo 0x1c11b0NRT_STATUS(void *comm, nccl_comm_info_t *out) — fills 1376B contractHIGH
neuronFreeComm0x47f900xc968a8ncclFreeComm 0x1c1370NRT_STATUS(void *comm) — teardown (= ncclCommDestroy)HIGH
neuronExpandConnectivity0x47fe00xc968a0ncclExpandConnectivity 0x1c1530NRT_STATUS(void *comm, enc_net_connectivity_t)HIGH
neuronStartNetworkProxy0x483a00xc96898ncclStartNetworkProxy 0x1c16f0NRT_STATUS(void *comm, u32, u32, net_ops_info_t*, int, net_src_addr_t*, int, net_dest_addr_t*, int)HIGH
neuronStopNetworkProxy0x487d00xc96890ncclStopNetworkProxy 0x1c18e0NRT_STATUS(void *comm, uint32_t)HIGH
neuronNetworkProxyProgress0x489300xc96888ncclNetworkProxyProgress 0x1c1aa0NRT_STATUS(void *comm, u32, u32, bool*, bool*)HIGH
neuronBootstrapSend0x489a00xc96880ncclBootstrapSend 0x1c1af0NRT_STATUS(void *state, int peer, void *data, size_t, const char *hint)HIGH
neuronBootstrapRecv0x489d00xc96878ncclBootstrapRecv 0x1c1cc0NRT_STATUS(void *state, int peer, void *data, size_t, const char *hint)HIGH
neuronBootstrapAllGather0x48a000xc96870ncclBootstrapAllGather 0x1c1e90NRT_STATUS(void *state, void *allData, size_t, const char *hint)HIGH
neuronNetRegMr0x48a300xc96858ncclNetRegMr 0x1c2070NRT_STATUS(void*, enc_pattern_t, int, bool, ofi_comm_type_t, void*, size_t, nccl_ptr_type, void**)HIGH
neuronNetDeregMr0x48d500xc96850ncclNetDeregMr 0x1c2260NRT_STATUS(void*, enc_pattern_t, int, bool, ofi_comm_type_t, void*)HIGH
neuronGetPeerDeviceId0x48ff00xc96848ncclGetPeerDeviceId 0x1c2440NRT_STATUS(void *comm, int peer, int *out)HIGH
neuronSetAffinity0x490200xc96840ncclSetAffinity 0x1c2620NRT_STATUS(void *comm, cpu_set_t *override, cpu_set_t *save, int core)HIGH
neuronGetNetConnector0x490700xc96838ncclGetNetConnector 0x1c2800NRT_STATUS(void *comm, enc_pattern_t, int, bool, void**)HIGH
neuronPreferredNcRidNic0x468100xc96868ncclPreferredNcRidNic 0x1c29d0NRT_STATUS(u32, u32, int *out)HIGH
neuronPreferredNcRidNicBdf0x467100xc96860ncclPreferredNcRidNicBdf 0x1c2bb0NRT_STATUS(u32, u32, char *bdf, size_t)HIGH
neuronInitNet0x492200xc96830ncclInitNet 0x1c2d90NRT_STATUS(char *root_comm_id) — net-plugin lifecycle initHIGH
neuronFiniPlugin0x492400xc96828ncclFiniPlugin 0x1c2f50NRT_STATUS(void) — net-plugin teardownHIGH
neuronGetOFIHandle0x492600xc96820ncclGetOFIHandle 0x1c3110NRT_STATUS(void **out)HIGH
neuronNetListen0x492800xc96818ncclNetListen 0x1c32d0NRT_STATUS(int, void*, void**)HIGH
neuronNetConnect0x493200xc96810ncclNetConnect 0x1c34b0NRT_STATUS(int, void*, void**)HIGH
neuronNetAccept0x493c00xc96808ncclNetAccept 0x1c3690NRT_STATUS(void*, void**)HIGH
neuronNetRegMrOG0x494600xc96800ncclNetRegMrOG 0x1c3850NRT_STATUS(void*, void*, size_t, int, void**)HIGH
neuronNetGetMrKey0x495000xc967f0ncclNetGetMrKey 0x1c3be0NRT_STATUS(void*, uint64_t *key)HIGH
neuronNetDeregMrOG0x495a00xc967f8ncclNetDeregMrOG 0x1c3a20NRT_STATUS(void*, void*)HIGH
neuronNetIsend0x496400xc967e8ncclNetIsend 0x1c3da0NRT_STATUS(void*, void*, int, void*, void**)HIGH
neuronNetIrecv0x496f00xc967e0ncclNetIrecv 0x1c3f70NRT_STATUS(void*, void*, int, void*, void**)HIGH
neuronNetIflush0x497f00xc967d8ncclNetIflush 0x1c4140NRT_STATUS(void*, void*, int, void*, void**)HIGH
neuronNetTest0x498e00xc967d0ncclNetTest 0x1c4310NRT_STATUS(void*, int *done, int *size)HIGH
neuronNetCloseSend0x499800xc967c8ncclNetCloseSend 0x1c44f0NRT_STATUS(void*)HIGH
neuronNetCloseRecv0x49a200xc967c0ncclNetCloseRecv 0x1c46b0NRT_STATUS(void*)HIGH
neuronNetCloseListen0x49ac00xc967b8ncclNetCloseListen 0x1c4870NRT_STATUS(void*) — last slot (table base)HIGH

FORWARD count = 37, re-derived: nm -D --defined-only libnccom.so.2.31.24 | rg -c ' neuron'37; every addr above matches the nm dump, and libnrt's dynamic table holds no neuron* symbol (forward leg is pure dlopen+dlsym, confirmed).

NOTE — libnccom exports these 37 as bare T with no @version — there is no .gnu.version_d node for them at all (it consumes libnrt's NRT_2.0.0 node and defines none of its own). The version tag a reimplementer might expect on a "collectives ABI" simply does not exist on the forward leg; the only forward compatibility token is the numeric 89.

REVERSE — 4 nrt_* runtime queries (definer libnrt, user libnccom)

All four are libnrt @@NRT_2.0.0 exports; libnccom imports them @NRT_2.0.0. Addresses are libnrt text VMAs.

SymbolDefiner addr (libnrt)VersionRole / signatureConf
nrt_get_total_vnc_count0x83e10@@NRT_2.0.0bootstrap gate — NRT_STATUS(uint32_t *out); bootstrapInit/Flat/CreateRoot require ==0 to run bootstrapHIGH
nrt_get_instance_info0x84210@@NRT_2.0.0pervasive topology metadata — NRT_STATUS(nrt_instance_info_t *out, size_t n); family/size/arch_name/device_revisionHIGH
nrt_get_version0x940b0@@NRT_2.0.0NRT_STATUS(nrt_version_t *out, size_t n) — fills the 16-byte cross-process sanity hashHIGH
nrt_get_libnccl_net0x94400@@NRT_2.0.0net-plugin handoff — void *nrt_get_libnccl_net(int *errno_out, char *errmsg, size_t n); returns the libnccl_net_handle (libnrt .bss 0xc96628) that libnrt resolved by dlopening libnccom-net.soHIGH

REVERSE — 16 nec_* device/topology/proxy callbacks (definer libnrt, user libnccom)

All libnrt @@NRT_2.0.0 exports; thin thunks into libnrt's NDL/encd layer (target named where traced). libnccom imports them @NRT_2.0.0.

SymbolDefiner addr (libnrt)libnrt implRole / signatureConf
nec_inc_semaphore0x1bfd40inline mov %esi,(%rdi); retproxy completion signalling — void(volatile uint32_t *sem, uint32_t val)HIGH
nec_get_device_count0x1bfd50jmp ndl_available_devices 0xc2180int(int *out, uint32_t) — direction-proof thunkHIGH
nec_get_device_pci_bdf0x1bfd60call ndl_get_device_bdf_ext 0xc4b00int(int dev, u32 *dom, u32 *bus, u8 *slot, u8 *func)HIGH
nec_get_virtual_core_size0x1bfdd0jmp encd_get_virtual_core_size 0x242960NRT_STATUS(uint32_t *out) — most-used callback (topo/device sizing)HIGH
nec_build_port_and_rid_map0x1bfdf0jmp encd_arch_build_port_and_rid_map 0x256540NRT_STATUS(int, int*, int*, int)HIGH
nec_is_mla_available0x1bfe00jmp encd_arch_is_mla_available 0x256940bool(int, int)HIGH
nec_mla_idx_to_rid0x1bfe10jmp encd_arch_mla_idx_to_ridint(int, int)HIGH
nec_rid_to_mla_idx0x1bfe20jmp encd_arch_rid_to_mla_idxint(int, int)HIGH
nec_get_peer_mla_idx0x1bfe30re-enters nrt_get_instance_info (PLT 0x3d270)int(int, int, int) — bounded re-entry, no further nec_HIGH
nec_get_p2p_pod_peer_node0x1bfeb0call encd_get_p2p_pod_peer_node 0x256010int(u32, int, u32, int*)HIGH
nec_pod_node_can_access_peer_node0x1bfed0encd pod helperNRT_STATUS(nec_pod_type_t, u32, u32, u32, u32, int*)HIGH
nec_ndl_printk0x1bfee0jmp ndl_printk 0xc4ca0void(char *fmt, u32, u32) — routed into ncclDebugLog nccom-sideHIGH
nec_get_dynamic_send_size_bytes0x1bfef0jmp enc_get_send_count_size_bytes 0xfe980unsigned long(enc_host_mem_t*, size_t, int, int)HIGH
nec_get_dynamic_send_offset_bytes0x1bff00jmp enc_get_send_offset_bytes 0xfe960unsigned long(enc_host_mem_t*, size_t, int, int)HIGH
nec_get_dynamic_recv_offset_bytes0x1bff10jmp enc_get_recv_post_offset_bytes 0xfe9a0unsigned long(enc_host_mem_t*, size_t, int, int)HIGH
nec_set_recv_size_bytes0x1bff20jmp enc_set_recv_size_bytes 0xfe9e0void(enc_host_mem_t*, size_t, size_t, int, int)HIGH

REVERSE count = 16 nec_ + 4 nrt_ = 20, re-derived: nm -D --undefined-only libnccom.so.2.31.24 | rg -c ' nec_'16; … | rg -c ' nrt_'4; … --with-symbol-versions | rg -c '@NRT_2.0.0'20 (and … | rg 'NRT_3.0.0' → empty). Each name resolves against a libnrt @@NRT_2.0.0 definition at the address above (nm -D --defined-only --with-symbol-versions libnrt.so.2.31.24.0).

NOTE — the libnrt-local nec_get_version_info (0x1bfde0, jmp ncclGetVersionInfo 0x1c0a10) and the nec_vil_* helpers are not in the dynamic symbol table and are correctly absent from libnccom's import list — they are intra-libnrt calls, not boundary symbols. The catalogue is the dynsym boundary only.

NET (edge) — ncclNetPlugin_v4/v5/v6 (definer libnccom-net)

Catalogued here only to fix the distinction: these are not part of the nec_/neuron boundary. They are the upstream ncclNet_vN plugin ABI — flat C structs of function pointers in libnccom-net.so's .data, dlopened by libnrt (whose handle reaches libnccom via nrt_get_libnccl_net, the REVERSE row above). libnccom binds ncclNetPlugin_v6 only.

SymbolDefiner addr (libnccom-net)KindRoleConf
ncclNetPlugin_v40x43760D (16-slot struct)legacy ncclNet_v4 vtable; exported, no located host bindHIGH
ncclNetPlugin_v50x437e0D (24-slot struct)ncclNet_v5 vtable; exported, no located host bindHIGH
ncclNetPlugin_v60x438a0D (22-slot struct)ncclNet_v6 vtable — the bound struct (dlsym("ncclNetPlugin_v6"), init@+8, devices@+24)HIGH

NET count = 3, re-derived: nm -D --defined-only libnccom-net.so | rg -c ncclNetPlugin3. These three are data objects (D), not text — distinguishing them at a glance from the 37 neuron* T functions and the 20 nec_/nrt_ T callbacks.


Counts — re-derived from nm on both binaries

ClaimCommandResultVerdict
FORWARD 37nm -D --defined-only libnccom.so.2.31.24 | rg -c ' neuron'37matches
FORWARD has no versionnm -D --defined-only --with-symbol-versions libnccom… | rg ' neuron.*@'(empty)bare T, confirmed
libnrt holds no neuron*nm -D libnrt.so.2.31.24.0 | rg ' neuron'(empty)dlsym-only, confirmed
REVERSE 16 nec_nm -D --undefined-only libnccom… | rg -c ' nec_'16matches
REVERSE 4 nrt_nm -D --undefined-only libnccom… | rg -c ' nrt_'4matches
REVERSE all @NRT_2.0.0nm -D --undefined-only --with-symbol-versions libnccom… | rg -c '@NRT_2.0.0'2016+4 = 20, confirmed
REVERSE no NRT_3.0.0… | rg 'NRT_3.0.0'(empty)binds NRT_2.0.0 only
NET 3nm -D --defined-only libnccom-net.so | rg -c ncclNetPlugin3v4/v5/v6, confirmed
no DT_NEEDED libnccom in libnrtreadelf -d libnrt… | rg NEEDED9 toolchain libsno libnccom, confirmed
DT_NEEDED libnrt.so.1 in libnccomreadelf -d libnccom… | rg NEEDEDincludes libnrt.so.1confirmed
libnccom VERNEED NRT_2.0.0readelf -V libnccom… | rg -A1 libnrt.so.1Name: NRT_2.0.0 (idx 3)confirmed, sole NRT need

All three headline counts hold: 37 forward / 16 nec_ + 4 nrt_ reverse / 3 net. No CORRECTION to the source figures.

Cross-References