Skip to content

fix(legacy): make IBGDA CQ polling concurrency-safe - #702

Open
youchengsong wants to merge 1 commit into
deepseek-ai:mainfrom
youchengsong:fix-legacy-cq-poll
Open

fix(legacy): make IBGDA CQ polling concurrency-safe#702
youchengsong wants to merge 1 commit into
deepseek-ai:mainfrom
youchengsong:fix-legacy-cq-poll

Conversation

@youchengsong

Copy link
Copy Markdown

Summary

Make the legacy IBGDA CQ poller safe when multiple callers observe the same
collapsed CQ, and make its 16-bit completion-counter handling wrap-safe.

The change mirrors the core protocol used by NVSHMEM:

  • wait until the full-width target index has been submitted before
    interpreting the 16-bit CQE counter;
  • re-read the shared consumer while polling so another caller can satisfy the
    wait;
  • reconstruct the full consumer index from the hardware counter;
  • advance the shared consumer monotonically with atomicMax;
  • enforce the queue-depth bound required by the modular comparison.

Why

The existing helper explicitly retained an exclusive-poller assumption.
Concurrent callers can otherwise continue spinning after another caller has
already advanced the shared consumer, or write an older target over a newer
consumer index. A target sufficiently far ahead can also make a stale
16-bit WQE counter ambiguous across wraparound.

This extends the initial early-return added by #371 with the producer,
concurrent-consumer, wrap reconstruction, and monotonic update checks from
the NVSHMEM implementation.

Scope

This PR only changes CQ polling in
csrc/kernels/legacy/ibgda_device.cuh. It intentionally does not change:

  • RC QP selection or NVSHMEM layout compatibility;
  • WQE submission or post-send ordering;
  • CQE error reporting;
  • public APIs or call sites.

Validation

  • full build of this commit for sm_103 with CUDA 13.0.88,
    NVSHMEM 3.7.2, NCCL 2.30.7, and PyTorch 2.11;
  • bash ./format.sh;
  • git diff --check upstream/main...HEAD;
  • 8,126,464 boundary cases in a 16-bit-to-64-bit wrap reconstruction model;
  • protocol comparison against NVSHMEM 3.4.5, 3.5.19, and 3.7.2;
  • the equivalent legacy backport completed 18,926 rounds on 4 B300 nodes /
    32 GPUs, with the final correctness check at round 18,921 and without the
    prior CQ/queue timeout.

The multi-node validation binary also carried the required NVSHMEM 3.7.2 RC
QP-layout compatibility backport, so that result is integration evidence
rather than a current-main single-variable comparison.

Integration note

This patch relies on cq->prod_idx remaining the monotonic posted frontier.
Any async post-send changes, including when stacking with #677, must preserve
that invariant; the producer guard is required to disambiguate 16-bit
counter wraparound.


// A 16-bit WQE counter is ambiguous if `idx` is more than one epoch ahead.
// Wait until the target has been submitted before interpreting the counter.
while (ld_na_relaxed(cq->prod_idx) < idx)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 suggestion: The producer guard while (ld_na_relaxed(cq->prod_idx) < idx) is an unbounded busy-wait whose correctness depends entirely on cq->prod_idx aliasing the monotonic posted frontier that ibgda_post_send/ibgda_submit_requests update (via mvars->tx_wq.prod_idx in the async path and ready_head otherwise). This wiring lives in the NVSHMEM headers, not in this repo, so the invariant is implicit. Since the PR's own integration note flags this as a hard dependency (especially when stacking with async post-send / #677), please add a brief comment here stating that cq->prod_idx must point at the WQ posted frontier, so a future async-post-send refactor does not silently break the wrap disambiguation. Also consider a debug-only assert that the requested idx is reachable, since if a caller ever passes a stale/over-large target this loop hangs silently.

🤖 v3

// Reconstruct the full software consumer index from the 16-bit hardware
// counter and keep the shared consumer monotonic across concurrent pollers.
++wqe_counter;
const uint64_t new_cons_idx = ((idx & ~0xffffULL) | wqe_counter) + ((static_cast<uint16_t>(idx) > wqe_counter) ? 0x10000ULL : 0);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 suggestion: The full-index reconstruction is correct but subtle and relies on the newly added NVSHMEMI_IBGDA_MAX_QP_DEPTH <= 32768 bound plus the producer guard to keep wqe_counter+1 within one 16-bit epoch of idx. Please expand the comment to explicitly tie these together (e.g. "safe because idx has been produced (prod_idx >= idx) and depth <= 32768, so the completed counter is within one epoch of idx's low 16 bits"). This documents why the 0x10000 carry correction cannot mis-select an epoch and protects the invariant against future edits to the depth assert or the guard.

🤖 v3

@ds-review-bot

Copy link
Copy Markdown
Collaborator

🤖 ds-review-bot Code Review

v6

The concurrency-safe polling logic matches NVSHMEM's established CQ polling algorithm, including wraparound handling and monotonic consumer updates. No actionable regressions were identified.

v4

⚠️ 未完成评审(no_result_file:模型未产出结果文件)

v3

The change makes ibgda_poll_cq in csrc/kernels/legacy/ibgda_device.cuh safe for concurrent pollers observing the same collapsed CQ, and makes the 16-bit CQE completion-counter handling wrap-safe. It (1) adds a producer guard that spins until cq-&gt;prod_idx &gt;= idx before interpreting the ambiguous 16-bit wqe_counter, (2) re-reads the shared cq-&gt;cons_idx inside the completion-wait loop so another caller can satisfy the wait (early return), (3) reconstructs the full 64-bit software consumer index from the 16-bit hardware counter with an epoch carry correction, (4) advances the shared consumer monotonically via atomicMax instead of a plain store, and (5) adds a NVSHMEMI_IBGDA_MAX_QP_DEPTH &lt;= 32768 static assert to bound the modular comparison. The implementation faithfully mirrors NVSHMEM's ibgda_poll_cq protocol and correctly matches the PR's stated scope (single file, no changes to QP selection, WQE submission, CQE error reporting, or public APIs). The logic is correct: with the producer guard ensuring idx &lt;= prod_idx and depth <= 32768, wqe_counter+1 stays within one epoch of idx's low 16 bits, so the ((uint16_t)idx &gt; wqe_counter) ? 0x10000 : 0 carry is unambiguous. Two implementation details verified as correct and requiring no change: the new NVSHMEMI_IBGDA_MAX_QP_DEPTH &lt;= 32768 static assert is the precondition that makes both the modular - 2 &lt; ncqes comparison and the epoch reconstruction unambiguous (it is now a load-bearing constraint that must not be relaxed without revisiting the reconstruction math); and the in-loop re-read of cq-&gt;cons_idx with early return is placed correctly (after reading wqe_counter, before the do-while condition) with fencing consistent with the function's existing discipline. Overall this is a solid, well-reasoned fix; the remaining comments are defensive suggestions about documenting the load-bearing invariants rather than correctness defects.

Files reviewed: 1
Issues found: 🔵 2 suggestion
Inline comments posted: 2

⚠️ Parse warning: [v4] no_result_file:模型未产出结果文件

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants