Fix legacy RC QP indexing for NVSHMEM 3.5.19+ - #696
Conversation
| #if NVSHMEM_VENDOR_MAJOR_VERSION > 3 || (NVSHMEM_VENDOR_MAJOR_VERSION == 3 && NVSHMEM_VENDOR_MINOR_VERSION > 5) || \ | ||
| (NVSHMEM_VENDOR_MAJOR_VERSION == 3 && NVSHMEM_VENDOR_MINOR_VERSION == 5 && NVSHMEM_VENDOR_PATCH_VERSION >= 19) |
There was a problem hiding this comment.
🔴 critical: Use the loaded NVSHMEM version to choose the QP layout: NVSHMEM_VENDOR_* describes the headers used at compile time, not the loaded host library. If an extension built with NVSHMEM 3.4.x loads a compatible 3.5.19+ libnvshmem_host.so.3 (or patch versions straddle this cutoff), the runtime creates QP-major storage while this code selects the PE-major branch, causing RDMA operations to use unrelated QPs. Record the loaded vendor version during initialization or reject incompatible compile/runtime combinations.
🤖 v6
There was a problem hiding this comment.
Addressed in 2c70ee1. DeepEP now calls nvshmemx_vendor_get_version_info before nvshmemx_init_attr and rejects compile/runtime pairs when their RC-QP layout classes differ across the 3.5.19 transition. The error reports both versions and asks the user to rebuild against the loaded library, so no QPs are initialized under an incompatible layout.
There was a problem hiding this comment.
Additional source confirmation: nvshmemx_vendor_get_version_info is a public host API and returns the vendor macros compiled into the loaded host library itself. It is declared and implemented in each checked supported release: 3.4.5 declaration / implementation, 3.5.19 declaration / implementation, and 3.7.2 declaration / implementation. The new check therefore observes the loaded library version rather than the compile-time headers.
| return &state->globalmem | ||
| .rcs[pe * num_rc_per_pe * state->num_devices_initialized + id % (num_rc_per_pe * state->num_devices_initialized)]; | ||
| const auto num_rcs = state->num_rc_per_pe * state->num_devices_initialized; | ||
| const auto qp = id % num_rcs; |
There was a problem hiding this comment.
🔵 suggestion: The version guard relies on NVSHMEM_VENDOR_{MAJOR,MINOR,PATCH}_VERSION macros. If any of these are undefined in a given NVSHMEM release's headers, the preprocessor evaluates them as 0 and silently selects the older PE-major layout, which would reintroduce the bug on 3.5.19+. Consider adding a compile-time check (e.g. #ifndef guard or static_assert) that these macros are defined to fail loudly rather than silently picking the wrong layout.
🤖 v3
There was a problem hiding this comment.
Addressed in 2c70ee1. Both touched translation units now fail compilation unless all three NVSHMEM_VENDOR_{MAJOR,MINOR,PATCH}_VERSION macros are defined.
| #if NVSHMEM_VENDOR_MAJOR_VERSION > 3 || (NVSHMEM_VENDOR_MAJOR_VERSION == 3 && NVSHMEM_VENDOR_MINOR_VERSION > 5) || \ | ||
| (NVSHMEM_VENDOR_MAJOR_VERSION == 3 && NVSHMEM_VENDOR_MINOR_VERSION == 5 && NVSHMEM_VENDOR_PATCH_VERSION >= 19) | ||
| // Since 3.5.19, NVSHMEM stores RC QPs in QP-major, PE-interleaved order. | ||
| return &state->globalmem.rcs[qp * nvshmemi_device_state_d.npes + pe]; |
There was a problem hiding this comment.
🔵 suggestion: The new layout uses nvshmemi_device_state_d.npes for the PE stride while the fallback branch and num_rcs use state/ibgda_get_state(). Both are valid, but using a single consistent source (e.g. state->npes in both branches) would improve readability. Not a correctness issue.
🤖 v3
There was a problem hiding this comment.
Adjusted in 2c70ee1 by naming the common-state value once as num_pes. The IBGDA transport state does not contain npes in the supported NVSHMEM headers, so the value still comes from nvshmemi_device_state_d while the RC pointer and queue counts come from the IBGDA state.
🤖 ds-review-bot Code Reviewv6The layout selection uses the compile-time NVSHMEM version even though the QP array is created by the loaded runtime library. Supported version mismatches across the layout transition therefore select invalid QP entries. v4I was unable to make the
v3The change fixes legacy RC QP indexing in csrc/kernels/legacy/ibgda_device.cuh to match NVSHMEM's RC QP allocation layout. Prior to NVSHMEM 3.5.19, RC QPs were stored in PE-major order ( Files reviewed: 1 |
| const auto num_rcs = state->num_rc_per_pe * state->num_devices_initialized; | ||
| const auto qp = id % num_rcs; | ||
| #if NVSHMEM_VENDOR_MAJOR_VERSION > 3 || (NVSHMEM_VENDOR_MAJOR_VERSION == 3 && NVSHMEM_VENDOR_MINOR_VERSION > 5) || \ | ||
| (NVSHMEM_VENDOR_MAJOR_VERSION == 3 && NVSHMEM_VENDOR_MINOR_VERSION == 5 && NVSHMEM_VENDOR_PATCH_VERSION >= 19) |
There was a problem hiding this comment.
🔴 critical: Use the loaded NVSHMEM version to choose the QP layout: NVSHMEM_VENDOR_* describes the headers used at compile time, not the loaded host library. If an extension built with NVSHMEM 3.4.x loads a compatible 3.5.19+ libnvshmem_host.so.3 (or patch versions straddle this cutoff), the runtime creates QP-major storage while this code selects the PE-major branch, causing RDMA operations to use unrelated QPs. Record the loaded vendor version during initialization or reject incompatible compile/runtime combinations.
RESOLUTION: WONT_FIX-as-requested. The LOADED host library's VENDOR release (the only thing that fixes the layout) cannot be obtained from public host/device NVSHMEM APIs (nvshmem_get_version returns the stable API/spec version; the ABI/vendor 3.x release is a compile-time header property). The kernel is compiled with a single NVSHMEM distribution and runs only with an identical host runtime. Compile-time selection from the matched vendor version is the correct/feasible choice. Still, the code/comments must state that matched distribution is mandatory and missing-vendor-macros must fall back explicitly to the old PE-major layout instead of imputing 0.
🤖 v4
| return &state->globalmem | ||
| .rcs[pe * num_rc_per_pe * state->num_devices_initialized + id % (num_rc_per_pe * state->num_devices_initialized)]; | ||
| const auto num_rcs = state->num_rc_per_pe * state->num_devices_initialized; | ||
| const auto qp = id % num_rcs; |
There was a problem hiding this comment.
🔵 suggestion: The version guard relies on NVSHMEM_VENDOR_{MAJOR,MINOR,PATCH}_VERSION macros. If any of these are undefined in a given NVSHMEM release's headers, the preprocessor evaluates them as 0 and silently selects the older PE-major layout, which would reintroduce the bug on 3.5.19+. Consider adding a compile-time check (e.g. #ifndef guard or static_assert) that these macros are defined to fail loudly rather than silently picking the wrong layout.
RESOLUTION: ACCEPT_PARTIALLY. The maintained/oldest supported releases (>= 3.3.9) and the matched-distribution rule mean the macros are present in every real build, so a loud #error would risk breaking older/unknown distributions (which are PE-major). The safe middle is to make the unknown/old fallback EXPLICIT in the preprocessor and in comments (which the draft in recommended_source does).
🤖 v4
| #if NVSHMEM_VENDOR_MAJOR_VERSION > 3 || (NVSHMEM_VENDOR_MAJOR_VERSION == 3 && NVSHMEM_VENDOR_MINOR_VERSION > 5) || \ | ||
| (NVSHMEM_VENDOR_MAJOR_VERSION == 3 && NVSHMEM_VENDOR_MINOR_VERSION == 5 && NVSHMEM_VENDOR_PATCH_VERSION >= 19) | ||
| // Since 3.5.19, NVSHMEM stores RC QPs in QP-major, PE-interleaved order. | ||
| return &state->globalmem.rcs[qp * nvshmemi_device_state_d.npes + pe]; |
There was a problem hiding this comment.
🔵 suggestion: The new layout uses nvshmemi_device_state_d.npes for the PE stride while the fallback branch and num_rcs use state/ibgda_get_state(). Both are valid, but using a single consistent source (e.g. state->npes in both branches) would improve readability. Not a correctness issue.
RESOLUTION: ACCEPT. Compute the queue once and use a single layout expression. Keep nvshmemi_device_state_d.npes (used consistently by the other rkey-indexing code in the same file) rather than a state member that is not used elsewhere.
🤖 v4
| .rcs[pe * num_rc_per_pe * state->num_devices_initialized + id % (num_rc_per_pe * state->num_devices_initialized)]; | ||
| const auto num_rcs = state->num_rc_per_pe * state->num_devices_initialized; | ||
| const auto qp = id % num_rcs; | ||
| #if NVSHMEM_VENDOR_MAJOR_VERSION > 3 || (NVSHMEM_VENDOR_MAJOR_VERSION == 3 && NVSHMEM_VENDOR_MINOR_VERSION > 5) || \ |
There was a problem hiding this comment.
🔵 suggestion: Optional hardening: the version guard relies on NVSHMEM_VENDOR_{MAJOR,MINOR,PATCH}_VERSION being defined. If any of these is undefined in a given NVSHMEM release's headers, the preprocessor treats it as 0 and silently selects the older PE-major branch, which would reintroduce the bug on 3.5.19+. Consider adding a loud compile-time guard, e.g. #if !defined(NVSHMEM_VENDOR_MAJOR_VERSION) || !defined(NVSHMEM_VENDOR_MINOR_VERSION) || !defined(NVSHMEM_VENDOR_PATCH_VERSION) / #error "...", so a missing macro fails the build instead of silently picking the wrong layout.
🤖 v3
🤖 ds-review-bot Code Reviewv6The version-gated indexing matches NVSHMEM's PE-major layout before 3.5.19 and QP-major layout from 3.5.19 onward. No actionable regressions were identified. v4The MR chooses the RC QP layout (PE-major vs QP-major/PE-interleaved) from the compiled NVSHMEM vendor version macros. The CORE arithmetic of the change is correct and does fix the reported 3.7.2 fault for a matched NVSHMEM distribution. But the implementation is fragile/opaque: it silently imputes 0 to missing vendor macros, comments the layout only weakly, and does not establish the matched-runtime/compile distribution on which the whole approach depends. v3The PR fixes the legacy IBGDA RC QP indexing so that NVSHMEM 3.5.19+ (QP-major, PE-interleaved layout) is addressed correctly while preserving the old PE-major layout for earlier releases. The change is small, well-scoped, and correct: it derives Regarding the prior review notes: (1) The 'critical' comment about NVSHMEM_VENDOR_* reflecting compile-time headers rather than the loaded libnvshmem_host.so.3 is technically valid but is an acceptable, explicitly-chosen design trade-off. The device-side globalmem.rcs layout is a compile-time struct/ABI contract (the extension is compiled and linked against nvshmem_common_ibgda.h), so a host/device version mismatch straddling this cutoff is already an unsupported configuration for many struct layouts, not just this one; branching at runtime for a single array-stride selection in a hot device inline is impractical. (2) The suggestion to replace nvshmemi_device_state_d.npes with state->npes is not applicable: Files reviewed: 1 |
|
same issue #707 |
| #include <cstring> | ||
| #include <optional> | ||
| #include <vector> | ||
| #include <nvshmem.h> |
There was a problem hiding this comment.
why import nvshmem.h before compiled.cuh, it will cause
/nvidia/nvshmem/include/non_abi/device/coll/reduce.
cuh(101): note #3328-D: built-in operator<=>(<nullptr>, <nullptr>) does not match because argument #1 does not match
parameter
return (op1 > op2) ? op1 : op2;
Background
The legacy IBGDA path currently indexes
globalmem.rcsas PE-major. That matches NVSHMEM 3.4.5 and earlier, but NVSHMEM changed RC QP allocation to QP-major, PE-interleaved order in NVIDIA/nvshmem@ce9d487. The change is included in NVSHMEM 3.5.19 and later.With NVSHMEM 3.7.2, the old expression can select another PE RC QP. All ranks may initialize successfully, then the first cross-node dispatch stops making progress and can eventually surface a CUDA fault.
Change
queue * npes + pefor NVSHMEM 3.5.19 and later.pe * queues_per_pe + queuefor older supported NVSHMEM releases.Validation
d268280) successfully for SM 10.3 with CUDA 13, NVSHMEM 3.7.2, and NCCL 2.30.7;cuobjdumpconfirmsarch = sm_103.nvshmemx_vendor_get_version_infoand all three vendor version macros are available.torch.OutOfMemoryErrorwhile materializing FP32 vocabulary logits. The failure was outside DeepEP/NVSHMEM; no transport or device-fault signature preceded it.