Conversation
`EfaQueuePair::create` requires a device that advertises both `EFADV_DEVICE_ATTR_CAPS_RDMA_READ` and `..._RDMA_WRITE`, and errors out when it does not. EFA generations differ here: p5 and p5en advertise both and can carry RDMA over SRD, while p4d advertises neither and offers only send/recv. `EfaDevice::is_instance` claimed any device `efadv_query_device` recognized, so on p4d the shortfall surfaced as a failed queue-pair creation on the first transfer — one error per operation, from a host that has a working TCP transport available and no way to reach it. Check the capability while deciding whether the device belongs to `EfaDevice`. An unclaimed device leaves `IbvBackend::<EfaDevice>::available()` false, so `RdmaBackends::spawn_available` never spawns the EFA backend and routes to the next registered one instead. Declining the device also hides it from `list_all_devices()`, hence the warning naming the device and the reason. `rdmaxcel_efa_supports_rdma` reads the EFA capability bits, and additionally requires `max_qp_rd_atom > 0` from `ibv_query_device` so a device whose driver advertises the capability while the verbs layer refuses RDMA work requests is reported as incapable here rather than failing later. Signed-off-by: Anton Alexander <dmvevents@gmail.com>
EFA does not deliver a same-node RDMA transfer. The queue pair connects and the work request completes locally, so the operation is reported as successful, but the payload never lands and the destination keeps whatever bytes it had. `ensure_qp_actor` already recognizes this topology — its `is_loopback` skips the `CreatePeerQueuePair` round trip and connects the queue pair to its own endpoint — but the data path is unchanged and still posts to the NIC, so `test_loopback_write` passes only on Mellanox, where the NIC does deliver it. Both ends of such a transfer are registered in this process, so satisfy it in `SubmitOps` with `std::ptr::copy`. Handling it there rather than in `QueuePairActor` avoids creating a queue pair for a transfer that will not use one, and keeps credit accounting and `wr_id` correlation clear of a path that posts no work requests. The size rules mirror `put` and `get`, down to the message, so an operation the queue pair would have rejected is rejected here the same way. Eligibility is host memory on both ends: device memory would need the CUDA driver and, for a peer GPU, a peer-to-peer mapping, while the device RDMA path already works. The remote end is judged by its `MemoryLocation`, which `IbvRemoteMemoryRegionView` now carries, and not by probing its `addr`. That address is an offset into the MR's zero-based address space, so a device region's is frequently 0 — probing it would report host memory and copy over whatever is mapped there. It also names an address space this side does not share, so only the owner can resolve it, which is why `new` takes the location from the `KeepaliveLocalMemory` that resolved it. The manager is generic over the device, and a host with both NICs spawns one of each, so the shortcut is scoped by `I::backend_name()`. A process-wide "is there an EFA device" test would answer yes inside the Mellanox manager too and divert loopback transfers that work. Signed-off-by: Anton Alexander <dmvevents@gmail.com>
A WR that fails puts the QP in error state, and every WR already posted behind it completes with IBV_WC_WR_FLUSH_ERR. The order a poll drains the two in is not fixed, so keeping whichever error arrived first made an op report the consequence and hide the cause: a three-chunk write whose first chunk failed could reply "flushed behind the failure" with nothing naming the failure. Track whether the error an op is holding came from a flushed WR, and let a root cause displace one. The first of either kind is still kept, so a single failure and an all-flush op both report what they did before. This gives `WorkRequestError::is_wr_flush_err` its first caller. Signed-off-by: Anton Alexander <dmvevents@gmail.com>
rdmaxcel_qp_post_op takes a `signaled` parameter and legacy.rs passes one, but the EFA branch dropped it: rdmaxcel_efa_post_op had no such parameter and rdmaxcel_efa_post_write/_post_read hardcoded IBV_SEND_SIGNALED. The RC branch in the same function honors the flag, so the two paths disagreed about what the argument means, and gcc flagged it as an unused parameter. Thread it through to the wr_flags assignment. The EFA QP is created with sq_sig_all = 0, so the flag is the only thing deciding whether a work request produces a completion, and silently forcing it on would make an unsignaled request quietly signaled. No caller changes: all four post_op call sites pass signaled = true, so every request is still signaled and every posted WR still yields exactly one CQE, which is what QueuePairActor's credit accounting and per-WR error attribution require. Signed-off-by: Anton Alexander <dmvevents@gmail.com>
EFA carries RDMA over SRD, and several things that hold on Mellanox hardware fail there — same-node loopback is dropped, writes are unordered, there are no atomics, and p4d cannot do RDMA at all. Most of these fail silently, so they surface as a timeout or stale data far from the cause. Collect what we learned running this crate on p5.48xlarge, with p4d as a capability-delta reference, next to the code it describes. Covers the SRD constraints and how each is handled here, the loopback shortcut and the cross-process gap it does not close, dmabuf registration constraints, the GPU L2 coherence gap and why this crate does not yet close it, completion-queue rules, measured throughput characteristics, and the setup mistakes (missing self-referencing egress rule, secondary VPC CIDR) that look like code bugs. Linked from the crate README, since a reader debugging an EFA setup starts there. Signed-off-by: Anton Alexander <dmvevents@gmail.com>
|
Thanks for submitting this. I want to push back on some things here:
|
|
@samlurye — you're right on point 3, and the PR description is wrong. Thanks for catching it. I wrote that the change "plumbs
So What the commit actually does, stated correctly: it makes a dead parameter live on the EFA path —
Your second half of that point stands too, and it is the more important part: selective signaling is not a sound optimisation for SRD at all, because SRD is unordered, so a CQE for a signaled WR says nothing about unsignaled WRs posted before it. That makes the plumbing I added of no use to EFA specifically. If you'd rather the parameter stay dead than carry a flag that can't be safely set on SRD, I'll drop commit 4 and send the On your point 4, I want to separate two claims, because I think we're disagreeing about different sentences.
I'll reword to the accurate version:
Points 1 and 2 I'm not going to argue before I've reproduced them. On 2 specifically, your reasoning — an error on one SRD WR does not transition the QP to error or flush in-flight WRs — is consistent with everything we've measured about SRD, and if that holds then replacing a flush error with a stored root cause is wrong on EFA for the reason you give, not just stylistically undesirable. I'll read your monarch-1#2 loopback implementation before responding on 1. Given #4734 is also |
|
Following up on my own comment above — I went to the provider source to check your points properly, and two things changed. One of them is that the replacement wording I proposed for the CQ claim is also wrong, so I'd rather correct it here than leave it standing. Point 3 — you understated it, and commit 4 is worse than "groundwork"I said the plumbing was harmless because every call site passes /* efa_post_send_validate(), rdma-core efa verbs */
if (unlikely(!(wr_flags & IBV_SEND_SIGNALED) && !qp->sq_sig_all)) {
verbs_err(..., "SQ[%u] Non signaled WRs not supported\n", ...);
return EINVAL;
}That check is reached on exactly the path I patched — So commit 4 is dead plumbing whose only purpose is unreachable on this provider. That's a stronger reason than the ordering argument you gave, and it's decisive. Dropping it. Point 4 — my replacement wording was also wrongIn my comment above I proposed narrowing "local-only" to "a successful send CQE confirms local WQE consumption, not remote delivery." Having checked, that's still wrong, and for the reason you gave: What actually survives isn't about delivery at all, it's about visibility: I'll rewrite the This wording had propagated further than the PR — it was in our internal EFA ground-truth preamble that gets injected into every model query we run on this subject. That's fixed on our side now, so thank you for pushing on it; it was costing us more than this PR. Point 2 — your premise is right, my doc comment is wrong, but I think the code degrades safely
On the code, though: Also: our PR body credits this to your #3391 review. Re-reading your comment there, you questioned the value of the selective-signaling optimisation — you didn't ask for a flush-attribution mechanism. That attribution is wrong and I'll fix it. Point 1 — conceding pending measurement, and our evidence doesn't holdI can't defend the loopback special-casing. Our claim traces to a single 2026-02-20 run that our own documented constraints disqualify twice: it was on a p4d (which this PR's own notes say can't do EFA RDMA) and on a secondary VPC CIDR ( What I can still stand behind is narrower: libfabric routes intra-node traffic to SHM, so same-node libfabric traffic isn't SRD. That says nothing about raw-verbs loopback. Your self-connected SRD QP test is the experiment we don't have, and your AH explanation — a missing responder-side AH giving I have P5-class capacity with primary-CIDR nodes. Offer: I'll run your loopback test there and post the counters either way. If it passes, commit 2 comes out. So, concretelyOf the five commits: commit 1 (the P4d RDMA-capability gate) is the part I'd still like reviewed — it's untouched by your comment, fixes a real panic, and neither Would you rather I force-push that reduced version here, or close this and open a single-commit PR for the p4d gate alone? Either is fine; I'd just rather not rebase five commits when three of them are coming out. |
Successor of #3391, re-ported onto the restructured ibverbs backend as announced in #3391 (comment). That branch predates the per-device module split (
efa_device/efa_domain/efa_queue_pair), CQ pooling (#4668/#4688), and #4039's test-scaffolding removal, and could no longer merge meaningfully. All review discussion there carries over; the two follow-ups requested in those threads are delivered here as their own commits.Five commits:
efa_queue_pair.rsassertsdevice_capsRDMA read+write at QP creation; this checks the same caps (mirroring libfabric'sefa_prov_info.ctest) during backend selection instead, so a P4d-class device falls back to TCP gracefully rather than panicking. P5/P5en are unaffected.memcpy/cudaMemcpyinstead of a WQE that never completes.is_wr_flush_err()(previously defined but never called) into the error path so aWR_FLUSH_ERRmessage points at the completion cache's originating error rather than the symptom. Requested in EFA RDMA: P4d compat, loopback fix, GPU Direct, polling optimization #3391 review (@samlurye).signaledthroughrdmaxcel_efa_post_op/rdmaxcel_efa_post_writeso EFA gets the same selective-signaling batching the RC path already had (previously EFA hardcodedIBV_SEND_SIGNALEDon every chunk). Also from EFA RDMA: P4d compat, loopback fix, GPU Direct, polling optimization #3391 review.max_qp_rd_atomcontradiction fixed.Verification (all real runs, on this branch vs a clean
mainworktree):cargo check -p monarch_rdmaandcargo fmt -- --checkclean;gcc -fsyntax-only -Wall -Wextraonrdmaxcel.czero warnings (on stock main the same run showsunused parameter 'signaled'— which is what commit 4 fixes).cargo test -p monarch_rdma --lib(linked with-l ibverbs -l efa -l mlx5): 143 passed on this branch vs 127 on stock main — the delta is exactly the 10 new tests plus test-order shuffle; the "failures" in both runs are hardware skips implemented as panics (SKIPPED: no RDMA devices available, CUDA-unavailable) and the failing-name sets diff clean of any regression (every differing test passes in isolation on this branch).is_device_ptrprobe would pass for device memory and memcpy to near-null; the port carriesMemoryLocationon the wire instead) and flush-vs-root-cause attribution in both orderings, mutation-tested.Two review promises resolved differently than announced in the #3391 plan comment, for cause:
signaledplumb-through turned out to be real plumbing, not a comment — the parameter already existed end-to-end and only the EFA branch dropped it (hardcodingIBV_SEND_SIGNALED); withsq_sig_all = 0that flag alone decides CQE generation. All call sites still passtrue, so behavior is preserved; flipping any tofalseis deliberately left as a separate change because the new send-queue credit accounting assumes one CQE per WR.PollSleepPolicy+RDMA_CQ_BUSY_POLL_WINDOWand is not re-ported.One #3391 hunk is dropped rather than ported: the
efa_nv_peermemcheck invalidate_execution_contextis unreachable on the EFA path (its only consumer sits after theis_efaearly-return that existed at merge-base too), and porting it process-wide would have made the Mellanox manager on a mixed host skip its own peer-mapping check.Open review threads on #3391 that remain live design questions (delivery-complete semantics,
max_rdma_size-driven chunking) apply unchanged to this branch.