Skip to content

UCP/RMA: Make put/rndv and get/rndv a fallback of zcopy - #11940

Merged
tvegas1 merged 14 commits into
openucx:masterfrom
tvegas1:rndv_prio
Sep 22, 2026
Merged

tvegas1 merged 14 commits into
openucx:masterfrom
tvegas1:rndv_prio

Conversation

@tvegas1

@tvegas1 tvegas1 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

What?

Make get/zcopy and put/offload/zcopy override get/rndv and put/rndv, which become their fallback, and remove UCX_RMA_PPLN_ENABLE.

Why?

  • The RMA rendezvous protocols add an RTS/RTR round trip, and are only useful as a fallback where no direct zcopy path exists. This was approximated by a UCS_CPU_MODEL_NVIDIA_VERA gate, which made selection platform dependent.
  • cfg_priority cannot express it: it is inert unless cfg_thresh is configured.

How?

  • Add a fallback rule to protocol selection: a protocol declares the classes it belongs to in proto_class, and the classes it overrides in fallback_class. A protocol is dropped on message size ranges where another protocol declares its class as a fallback. Two new ucp_proto_t fields, both default 0, so other protocols are unaffected.
  • ucp_proto_select_disable_fallback() runs per message size range in ucp_proto_thresholds_next_range(), so the rendezvous protocols stay available wherever zcopy has no range.
  • It runs after the cfg_thresh and cfg_priority filters, so UCX_ZCOPY_THRESH is still honored: a zcopy protocol disabled below the configured threshold does not override its fallback.
  • Classes are matched within a single selection key, which has one operation, so the put and get protocols share the RMA_RNDV class without affecting each other.
  • Gtests with a synthetic class, and with a mock slow GET_ZCOPY device.

Protocols which implement the same operation by a different data path are
currently ranked only by their performance model, so preferring one over
another requires artificial cost penalties.

Add a protocol class to the protocol definition, and a bitmap of classes
which supersede it. A superseded protocol is not selected on message sizes
where a protocol of a superseding class is available.

No protocol declares a class yet, so protocol selection is unchanged.
The RMA rendezvous protocols were probed only on Vera Rubin, or when
UCX_RMA_PPLN_ENABLE was set, because on message sizes where a direct zcopy
protocol is available they must not be selected.

Declare the direct RMA zcopy protocols as UCP_PROTO_CLASS_RMA_ZCOPY, and
mark put/rndv and get/rndv as superseded by that class. Protocol selection
now drops them on the message sizes covered by a zcopy protocol, so the
platform check is no longer needed and the protocols become available
wherever no zcopy protocol can carry the operation.

The fallback overhead penalties are kept for now: they only affect message
sizes where no zcopy protocol is available, which the supersede rule does
not cover.
The configuration was only used to force-enable the RMA rendezvous put/get
protocols on non Vera Rubin platforms. Protocol selection now supersedes
them by the direct zcopy protocols, so they are always probed and the
configuration has no remaining user.

UCX_PROTOS can still be used to remove either the rendezvous or the zcopy
protocols from selection.
Add a mock test which supersedes the eager short protocol by the eager
copy-in protocol, and verifies that the short protocol is not selected
anymore, while the message sizes not covered by the superseding protocol
keep their selection.

put/rndv and get/rndv are probed only when one of the buffers is not in
host memory, so they cannot be used to test the rule on any device.
Add a mock device which reads from remote memory much slower than it
writes to it, so that get/rndv, which makes the remote side write the
data, is cheaper than get/zcopy on large messages. Check that get/zcopy
is still selected on all message sizes, because it supersedes get/rndv.

Move test_cuda_rma() to the common mock test class, to select an RMA
protocol on CUDA memory from both tests which need it.
@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/ucp/rma/rma_rndv.c Outdated
Comment thread test/gtest/ucp/test_ucp_proto_mock.cc
Comment thread src/ucp/proto/proto.h
Comment thread src/ucp/proto/proto_select.c
@svc-nvidia-pr-review

Copy link
Copy Markdown

Residual coverage gaps worth flagging: the new test_ucp_proto_mock_rcx_slow_get and test_ucp_proto_mock_supersede cases only run where CUDA memory and rc_x/posix,cma are available, and the supersede test mutates the global ucp_protocols[] entries (restored in cleanup()), so it depends on tests within a process running sequentially.

@svc-ucx

svc-ucx commented Sep 11, 2026

Copy link
Copy Markdown

🤖 CI Triage AgentUCX PR (Tests roce on worker 1) · commit 40c844b5

TL;DR: The roce on worker 1 gtest job failed on a single test, rcx/test_ucp_fault_tolerance.probe_gated_recovery/6 <rc_x/AM>; that test's only unique check (EXPECT_TRUE(probe_armed)) samples internal recovery state (ep->ext->recovery_arg->probe[lane].comp.func) from between progress loops, and UCP arms and clears the aux probe (and frees recovery_arg) inside a single progress round, so the check is inherently racy and unrelated to the rndv_prio changes. Fix by observing probe arming through a hook/mock or a sticky counter instead of polling the transient comp.func field.

Full analysis

Summary: Azure job roce on worker 1 (build 135835) failed with make: *** [Makefile:4713: test] Error 1 — 8725/8726 tests passed, only rcx/test_ucp_fault_tolerance.probe_gated_recovery/6, where GetParam() = rc_x/AM failed. No crash, no hang, no timeout (the suite reached global tear-down normally).

Root cause: The test does wait_for_cond(...) and, inside the predicate, samples arg->probe[lane].comp.func != NULL to set probe_armed, then asserts EXPECT_TRUE(probe_armed) ("RC p2p lane recovery completed without arming an aux probe"). In the product code, that field is transient:

  • ucp_ep_recovery_arm_probe() sets probe->comp.func (src/ucp/core/ucp_ep.c:2082) and, if uct_ep_check() does not return UCS_INPROGRESS, immediately completes it in the same call (ucp_ep.c:2088-2093);
  • ucp_ep_recovery_rebuild_p2p_lane() calls ucp_ep_recovery_reset_probe() (comp.func = NULL) as soon as connect_to_ep_v2 succeeds (ucp_ep.c:2182/2187);
  • ucp_ep_recovery_progress() frees recovery_arg (sets it to NULL) the moment all failed lanes are cleared (ucp_ep.c:2415).

So arming → probe OK → rebuild → probe reset → recovery_arg freed can all happen inside one short_progress_loop(), i.e. between two evaluations of the test predicate. The predicate then returns true (failed lanes == 0) with probe_armed still false, and the EXPECT fires. This is a test-side sampling race (flaky test), not a defect introduced by the rndv priority PR — the same 8725 other tests, including all other fault-tolerance variants, passed. (Caveat: only the tail of the job log is retrievable, so the exact gtest failure message for this test is not visible; the diagnosis is based on the failing test identity plus the recovery code paths above.)

Implicated commit: db208ee "UCP/FT: probe-gated lane recovery via aux uct_ep_check (#11563)", Evgeny Leksikov — added probe_gated_recovery and the probe arm/reset logic. (Recent UD/keepalive changes 183a66d / 8a38ae2 / 0ee470a by Ilia Yastrebov affect the aux UD ep_check path and can change how long a probe stays in flight, making the window smaller.)

File: test/gtest/ucp/test_ucp_fault_tolerance.cc:922-943 (probe_armed sampling and EXPECT_TRUE), with product-side transitions at src/ucp/core/ucp_ep.c:2082, 2187, 2415

Suggested fix:

  1. Stop inferring "probe armed" from the transient comp.func field. Instead intercept the probe itself, as the sibling tests in the same file already do: install ucs::mock + mock_recovery_probe(sender().worker(), mock, hook) where the hook increments a counter and forwards to the real uct_ep_check (or use wait_for_recovery_probe_in_flight() before waiting for recovery completion), then assert on the counter.
  2. Alternatively add a sticky indicator in the product code that survives the round — e.g. a probes_armed counter in ucp_ep_recovery_arg_t, or bump an EP/worker statistic in ucp_ep_recovery_arm_probe() — and assert on that, so the check no longer depends on catching a one-progress-iteration window.
  3. Short term: re-trigger the job to confirm the flake, and consider marking probe_gated_recovery as flaky/skipped until the observation race is fixed, so it doesn't block unrelated PRs like UCP/RMA: Make put/rndv and get/rndv a fallback of zcopy #11940.

Related: PR #11563 (probe-gated lane recovery, introduced this test), PR #11940 (the PR under test — no evidence it is the cause); UD keepalive/ep_check changes #11800, 183a66d, 8a38ae2

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/ucp/rma/rma_rndv.c Outdated
Comment thread src/ucp/rma/rma_rndv.c Outdated
Comment thread src/ucp/proto/proto_select.c Outdated
@svc-ucx

svc-ucx commented Sep 14, 2026

Copy link
Copy Markdown

🤖 CI Triage AgentUCX PR (AddressSanitizer roce on worker 0) · commit c5eddb01

TL;DR: The ASAN "roce" job was aborted by the gtest 900-second watchdog because dc_ud/test_ucp_sockaddr_destroy_ep_on_err.empty/8 <dc_x,ud_v,ud_x,mm/stream,sa_data_v1> hung with an endpoint-close/disconnect request that never completed; the code touched by this PR (RMA put/get rendezvous supersede logic) cannot be reached by that test, so this is a hang in the CM disconnect path rather than a regression from the PR diff.

Full analysis

Summary: Test dc_ud/test_ucp_sockaddr_destroy_ep_on_err.empty/8 hung for 15 minutes; the gtest watchdog raised SIGABRT and make test failed with "Aborted (core dumped)".

Root cause: Timeline from the log proves a hang, not slowness:

  • 12:20:44.235 [ RUN ] dc_ud/test_ucp_sockaddr_destroy_ep_on_err.empty/8 <dc_x,ud_v,ud_x,mm/stream,sa_data_v1>
  • 12:20:44.585 last application output: server listening on 1.1.1.1:45834
  • 11 min 17 s of complete silence (largest gap in the whole log; everything before it runs in milliseconds)
  • 12:32:01 ucp_test.cc:300 UCX ERROR request 0x62e00000a418 did not complete on time — the in-test deadline expired on a request that never completed
  • 12:35:44 (exactly 900 s = watchdog_timeout after the test started) test_helpers.cc:57: Failure / Connection timed out - abort testing, then Caught signal 6; the backtrace shows the watched thread inside __sched_yield in a gtest progress/spin loop, i.e. it was polling for a completion that never arrived.

The empty test is listen_and_communicate(false, 0) (test/gtest/ucp/test_ucp_sockaddr.cc:2042) — with send direction 0 there is no data transfer at all, so the only outstanding request is the connection-establishment/ucp_ep_close_nbx (disconnect) request. So the hang is in CM-based connect/close over dc_x,ud_v,ud_x,mm with sa_data_v1: the close/flush request stayed UCS_INPROGRESS forever.

This is not explained by the PR diff. The branch's commits (1bcbbc13 supersede rule, 4e6057ef put/rndv+get/rndv superseded by zcopy, c5eddb01 supersede diagnostics) touch only src/ucp/proto/proto_select.c, src/ucp/proto/proto.h and src/ucp/rma/rma_rndv.c. The new RMA rendezvous protocols are gated in ucp_proto_rma_rndv_probe_check() (src/ucp/rma/rma_rndv.c:31-48) on (a) a non-NULL rkey_config_key, (b) dst_version >= 22, and (c) at least one side being non-host memory; and UCP_PROTO_CLASS_RMA_ZCOPY is the only supersede class (src/ucp/proto/proto.h:93-96), so ucp_proto_select_disable_superseded() is a no-op for every other operation. The hanging variant uses TAG+STREAM features only (no RMA op ids), host memory, and sa_data_v1 (low dst_version) — none of the modified paths can be exercised.

Implicated commit: unknown — not attributable to the PR commits (c5eddb01, 4e6057ef, 1bcbbc13, Thomas Vegas); the hang is in the UCP CM connect/close path exercised by the test.

File: test/gtest/ucp/test_ucp_sockaddr.cc:2042 (hung test), test/gtest/ucp/ucp_test.cc:300 (request never completed), test/gtest/common/test_helpers.cc:31,57 (900 s watchdog that aborted the run)

Suggested fix: Do not raise the watchdog timeout — the process was hung, not slow. Concretely:

  1. Re-trigger the AddressSanitizer roce on worker 0 job to confirm this is the known intermittent CM-disconnect hang; if it passes, it is unrelated to PR UCP/RMA: Make put/rndv and get/rndv a fallback of zcopy #11940 and the PR should not be blocked on it.
  2. If it reproduces, analyze the core file that was dumped (swx-rain03, pid 26697) — inspect worker->flush_ops_count and the stuck request 0x62e00000a418 (req->flags, send.uct/pending state) plus the CM lane / wireup EP state, to find which flush or wireup completion is missing on the dc_x + sa_data_v1 path.
  3. To make future occurrences diagnosable, make the watchdog dump the hung thread's stack and the pending-request state before aborting (currently the SIGABRT backtrace only shows the spin loop, which hides the real culprit), and re-run this single variant with UCX_LOG_LEVEL=debug and -gtest_filter=dc_ud/test_ucp_sockaddr_destroy_ep_on_err.empty/8 in a loop.

Related: PR #11940 (the PR under test); possibly relevant nearby work on selection/rkey stability: #11676

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/ucp/rma/rma_rndv.c
Comment thread src/ucp/proto/proto_select.c Outdated
Comment thread src/ucp/proto/AGENTS.md Outdated
Comment thread test/gtest/ucp/test_ucp_proto_mock.cc
@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/ucp/proto/proto_select.c Outdated
Comment thread test/gtest/ucp/test_ucp_proto_mock.cc
@svc-nvidia-pr-review

Copy link
Copy Markdown

A class cycle (A supersedes B, B supersedes A) is only documented, not enforced — the assert catches self-overlap only, and a cycle would disable every protocol in a range and fall into the "all disabled" path. Not covered by any test (noted as low priority while only one class exists).

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread test/gtest/ucp/test_ucp_proto_mock.cc Outdated
@svc-ucx

svc-ucx commented Sep 14, 2026

Copy link
Copy Markdown

🤖 CI Triage AgentUCX PR (Static_check Static checks) · commit 1361d3ae

TL;DR: The Static_check job failed because clang's static analyzer reported memset(NULL, ...) in ucs_bitmap_bits_memset() (bitmap.h:118) on a path reachable from the new supersede/fallback logic in proto_select.c — the disabled_proto_mask dynamic bitmap can still be empty (backing buffer NULL, length 0) when ucs_dynamic_bitmap_reset_all() is called. Fix by making the zero-length early-return in ucs_bitmap_bits_memset() a runtime check (not __builtin_constant_p), or by skipping the reset when the mask is empty.

Full analysis

Summary: Static checkscstools reports step failed: static checkers found errors: with 2 × CLANG_WARNING ... bitmap.h:118:5: Null pointer passed to 1st parameter expecting 'nonnull' [core.NonNullParamChecker] while analyzing src/ucp/proto/proto_select.c.

Root cause: Build itself succeeded; the failure is the clang-analyzer gate. ucs_bitmap_bits_memset() only short-circuits when the word count is a compile-time constant zero:

if (__builtin_constant_p(num_words) && (num_words == 0)) return;
memset(bits, value, num_words * sizeof(ucs_bitmap_word_t));

In ucp_proto_thresholds_next_range() the disabled_proto_mask is created with ucs_dynamic_bitmap_init() (line 124), so ucs_array_begin() is NULL and length is 0 until some bit is set. On the path where no protocol has a configured cfg_thresh (nothing ever calls ucs_dynamic_bitmap_set on it), the analyzer can reach the fallback ucs_dynamic_bitmap_reset_all(&disabled_proto_mask) at line 197 (and the analogous reset_all(proto_mask) at line 123) with bits == NULL, num_words == 0memset(NULL, 0, 0), which the checker flags. bitmap.h hasn't changed since 2024; the newly reachable path was introduced by this PR's supersede work, specifically the reordering in the head commit that moved ucp_proto_select_disable_superseded() after the "all protocols disabled" fallback block, so the fallback reset_all is now reachable with an untouched (NULL-backed) mask.

Implicated commit: 1361d3a ("UCP/PROTO: Apply supersede rule after fallback", Thomas Vegas); series also includes 1bcbbc1, c5eddb0, e6954e5 (same author)

File: src/ucs/datastruct/bitmap.h:111-119 (warning site), triggered from src/ucp/proto/proto_select.c:195-201 (and :123-124)

Suggested fix: Preferred, one-line and general:

static UCS_F_ALWAYS_INLINE void
ucs_bitmap_bits_memset(ucs_bitmap_word_t *bits, int value, size_t num_words)
{
    if (num_words == 0) {
        return;   /* bits may be NULL for an empty dynamic bitmap */
    }
    memset(bits, value, num_words * sizeof(ucs_bitmap_word_t));
}

(keeps behaviour identical, removes the __builtin_constant_p limitation that made the analyzer see a NULL memset). Alternatively/in addition, guard in proto_select.c:

if (!ucs_dynamic_bitmap_is_zero(&disabled_proto_mask) &&
    ucs_dynamic_bitmap_is_equal(proto_mask, &disabled_proto_mask)) {
    ...
    ucs_dynamic_bitmap_reset_all(&disabled_proto_mask);
}

or reserve the mask up front with ucs_dynamic_bitmap_reserve(&disabled_proto_mask, ucs_array_length(&proto_init->protocols)) right after ucs_dynamic_bitmap_init() so the buffer is never NULL.

Related: PR #11940 (branch rndv_prio); no existing issue found for this analyzer warning.

The supersede test fixture mutates the global ucp_protocols[] table, and
restored it only after the base class cleanup. The base cleanup tears down
the endpoints and may throw, in which case the mutation would leak into the
following tests in the same process and change their protocol selection.
@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread test/gtest/ucp/test_ucp_proto_mock.cc
@svc-ucx

svc-ucx commented Sep 15, 2026

Copy link
Copy Markdown

🤖 CI Triage AgentUCX PR (Static_check Static checks) · commit 839e883b

TL;DR: The Static check (clang static analyzer via cslinker/csgrep) failed on two core.NonNullParamChecker warnings at src/ucs/datastruct/bitmap.h:118 (memset(bits, ...)) reached from the new supersede logic in src/ucp/proto/proto_select.c, where a freshly-initialized ucs_dynamic_bitmap_t still has a NULL backing buffer and zero length. Fix by making ucs_bitmap_bits_memset() skip the memset when num_words == 0 at runtime (not just via __builtin_constant_p), or pre-reserve disabled_proto_mask right after ucs_dynamic_bitmap_init().

Full analysis

Summary: Static_check job for PR #11940 failed at the "cstools reports" step: static checkers found errors — 2x CLANG_WARNING: Null pointer passed to 1st parameter expecting 'nonnull' [core.NonNullParamChecker] in bitmap.h:118, analyzed from src/ucp/proto/proto_select.c. The build itself compiled and linked fine (no compile error, no timeout — the step ended cleanly at 08:46:46 with continuous log activity).

Root cause: ucs_bitmap_bits_memset() (src/ucs/datastruct/bitmap.h:111-119) only short-circuits when num_words is a compile-time constant 0 (__builtin_constant_p). In ucp_proto_thresholds_next_range(), disabled_proto_mask is created with ucs_dynamic_bitmap_init() (line 124), which does ucs_array_init_dynamic() — buffer pointer NULL, length 0. On paths where no bit was ever set (so no ucs_array_resize happened), ucs_dynamic_bitmap_reset_all(&disabled_proto_mask) at line 197 and the not_inplace/and_inplace tail-fill helpers pass ucs_array_begin()==NULL with num_words==0 into memset(), which is UB-per-standard and flagged by the analyzer as a nonnull violation. The PR's new supersede code (ucp_proto_select_disable_superseded, proto_select.c:51-93, called at line 200 after the "all protocols disabled → reset_all" fallback) gave the analyzer new feasible paths through this function, surfacing the warning that master does not report.

The secondary error ##[error]Path does not exist: .../static_check in the PublishPipelineArtifact step is a consequence of the checker step aborting before the report directory was populated, not an independent failure.

Implicated commit: 1361d3ae "UCP/PROTO: Apply supersede rule after fallback" (Thomas Vegas), part of the rndv_prio series with 1bcbbc13 "UCP/PROTO: Add protocol class supersede rule to selection"; PR head [REDACTED:Hex High Entropy String]

File: src/ucs/datastruct/bitmap.h:111-119 (warning site: bitmap.h:118), triggered from src/ucp/proto/proto_select.c:124,197,200-207

Suggested fix: Make the empty-bitmap case null-safe rather than relying on constant folding:

static UCS_F_ALWAYS_INLINE void
ucs_bitmap_bits_memset(ucs_bitmap_word_t *bits, int value, size_t num_words)
{
    if (num_words == 0) {   /* also covers bits == NULL for empty dynamic bitmaps */
        return;
    }

    memset(bits, value, num_words * sizeof(ucs_bitmap_word_t));
}

This keeps the original compiler-warning workaround, is a no-op cost-wise, and removes the analyzer false positive for all dynamic-bitmap callers. Alternatively/additionally, in ucp_proto_thresholds_next_range() add ucs_dynamic_bitmap_reserve(&disabled_proto_mask, ucs_array_length(&proto_init->protocols)); immediately after ucs_dynamic_bitmap_init() (line 124) so the mask always has allocated storage — this also avoids repeated incremental resizes in ucs_dynamic_bitmap_set(). Re-run the Static_check job after the change; no other stage was failing.

Related: PR #11940 (the PR under test; no pre-existing issue found for this analyzer warning)

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id 3e7b57c3-d348-46fa-9ded-18a8aa6f5e9b in the triage console for the audit trail.

Skip the memset() of an empty bitmap at runtime, so that a freshly
initialized dynamic bitmap does not pass a NULL pointer to memset() on
the path reported by the clang static analyzer.

Restore the mocked protocol fields from a scoped object, so that the
global 'ucp_protocols[]' entries are restored even when init() fails
before cleanup() is reached.
@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

@svc-nvidia-pr-review

Copy link
Copy Markdown

src/ucp/rma/rma_rndv.c

With the supersede rule, put/rndv and get/rndv can never be selected where put/get zcopy exists, even in the ranges where the cost model says rndv is faster (as the new slow_get test documents). Previously the 1ms *_FALLBACK_PENALTY still let rndv win for large transfers on Vera — is dropping that intended, and is UCX_PROTOS=^get/zcopy (which disables zcopy for all sizes) now the only way for a user to prefer the rndv path?

@tvegas1

tvegas1 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

src/ucp/rma/rma_rndv.c

With the supersede rule, put/rndv and get/rndv can never be selected where put/get zcopy exists, even in the ranges where the cost model says rndv is faster (as the new slow_get test documents). Previously the 1ms *_FALLBACK_PENALTY still let rndv win for large transfers on Vera — is dropping that intended, and is UCX_PROTOS=^get/zcopy (which disables zcopy for all sizes) now the only way for a user to prefer the rndv path?

yes, intended.

@svc-ucx

svc-ucx commented Sep 15, 2026

Copy link
Copy Markdown

🤖 CI Triage AgentUCX PR (AddressSanitizer gpu on worker 2) · commit 04b67766

TL;DR: The gtest suite failed on a single new test, rcx_gpu/test_ucp_proto_mock_rcx_no_get_zcopy.get/0, which this PR (#11940) itself adds with hard-coded protocol-selection range boundaries (1..1928 / 1929..INF); on the ASAN GPU worker the computed get/rndv threshold doesn't match those literals, so the EXPECT_EQ on the range data fails. Fix by making the new mock test's expectations machine-independent (mock the perf/registration attributes or stop asserting an exact byte boundary).

Full analysis

Summary: make test in the "gpu on worker 2" AddressSanitizer job exited non-zero because 1 of 10180 gtests failed: rcx_gpu/test_ucp_proto_mock_rcx_no_get_zcopy.get/0, where GetParam() = rc_x,cuda,rocm (make: *** [Makefile:4713: test] Error 1).

Root cause: The failing test is a protocol-selection mock test introduced by this PR's series ("UCP/RMA: Supersede put/rndv and get/rndv by zcopy"). It asserts exact selection ranges for CUDA GET:
{1, 1928, "rndv using zero-copy", "rc_mlx5/mock"} and {1929, INF, "rndv using zero-copy fenced write to remote", "rc_mlx5/mock"}.
Only the mocked rc_mlx5 iface attributes are faked (cap.get.max_zcopy = 0); the perf estimation for the remote (put) protocol still uses the real IB MD/GPU registration cost and bandwidth of the node (swx-rdmz-ucx-gpu-01), so the crossover point between "rndv using zero-copy" and "rndv using zero-copy fenced write to remote" is host-dependent and does not land exactly on 1928/1929 there. check_proto_select_elem() compares range_end/desc/config with EXPECT_EQ plus an EXPECT_NE at range_start - 1, so any shift of the boundary fails the test. This is a known fragility class for this file — cf. commit a79e5a0 "GTEST/UCP: Fix tests on GB300", which existed purely to re-tune these hard-coded numbers for a specific GPU node. Note the exact assertion text (and the dump_select_info() dump the test prints on failure) is above the retained log tail, so the precise mismatching range is not visible in the fetched log; everything else in the job passed and there is no hang (log timestamps are continuous, no gap > 1s).

Implicated commit: [REDACTED:Hex High Entropy String] (Thomas Vegas, PR #11940 head); the test itself comes from 40c844b / 4e6057e ("UCP/RMA: Supersede put/rndv and get/rndv by zcopy") in the same series.

File: test/gtest/ucp/test_ucp_proto_mock.cc:1131-1142 (test_ucp_proto_mock_rcx_no_get_zcopy.get, expectations at lines 1138-1140); mock iface at lines 1119-1129.

Suggested fix:

  1. Make the new test deterministic instead of relying on real hardware perf: add a perf_cb to add_mock_iface("mock", ...) that pins bandwidth/path_bandwidth (and, if needed, the PUT_ZCOPY/GET_ZCOPY operation bandwidths) exactly as test_ucp_proto_mock_rcx_slow_get does — that sibling test passed because its perf mock removes the host dependency.
  2. Alternatively, drop the exact byte boundary from the assertion: assert a single range {1, INF, "rndv using zero-copy", "rc_mlx5/mock"} under a config that forces one scheme (e.g. add "RNDV_SCHEME=get_zcopy" / a fixed ZCOPY_THRESH), so the test verifies the supersede behaviour (get/rndv is selected when no get/zcopy exists) rather than the threshold arithmetic.
  3. Re-run the job with the full step log kept, or reproduce locally, to read the unexpected difference at range[N] message and the dump_select_info output before re-tuning any literal.

Related: PR #11940 (#11940); prior threshold-retuning precedent: commit a79e5a0 "GTEST/UCP: Fix tests on GB300 (#11616)".

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id 8beb2d2c-aa8e-4663-8838-15f74a9670ca in the triage console for the audit trail.

The expected ranges depended on the message size where the remote side
switches from am/zcopy to put/zcopy, which is derived from the device
attributes of the host.
@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/ucp/core/ucp_context.c
Comment thread src/ucp/proto/proto_select.c Outdated
}

proto = &ucs_array_elem(&proto_init->protocols, proto_idx);
superseded_by = ucp_proto_id_field(proto->proto_id, superseded_by) &

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you pls use fllback instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/ucp/proto/proto_select.c Outdated
Comment thread src/ucs/datastruct/bitmap.h
@svc-nvidia-pr-review

Copy link
Copy Markdown

Residual coverage gap: the new test_ucp_proto_mock_rcx_slow_get / test_ucp_proto_mock_rcx_no_get_zcopy cases only run where CUDA memory and rc_x are available, and test_ucp_proto_mock_fallback only with posix,cma; on other CI configurations the new selection rule is exercised only indirectly (e.g. test_ucp_proto_mock_cuda_ipc.put/get, which now also implicitly assert that put/rndv/get/rndv are dropped).

Residual coverage gap: the PUT direction of the rule has no dedicated mock test; it is covered only implicitly by the existing cuda_ipc.put expectation.

test_ucp_proto_mock_fallback still mutates the global ucp_protocols[] entries; scoping the rule to the test object (destructor rather than cleanup()) addresses the earlier lifetime concern, but it still depends on tests in a process running sequentially.

@tvegas1 tvegas1 changed the title UCP/RMA: Supersede put/rndv and get/rndv by zcopy UCP/RMA: Make put/rndv and get/rndv a fallback of zcopy Sep 18, 2026
@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/ucp/rma/rma_rndv.c
Comment thread test/gtest/ucp/test_ucp_proto_mock.cc
Comment thread test/gtest/ucp/test_ucp_proto_mock.cc
@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

Comment thread src/ucs/datastruct/bitmap.h
Declare the relation on the preferred protocol, which lists the protocol
classes it overrides in fallback_class, instead of on the superseded one.
Pair the existing selection check with the same mock configuration where
get/zcopy is excluded, to show that get/rndv is a candidate removed by the
fallback rule rather than one which was never probed.
@svc-nvidia-pr-review

Copy link
Copy Markdown

🤖 Starting review — findings will be posted here when done.

@svc-nvidia-pr-review

Copy link
Copy Markdown

src/ucp/proto/proto_select.c

In ucp_proto_select_disable_fallback(), on overridden_classes |= fallback_class;: a proto that gets dropped by the second loop still contributed its own fallback_class here, while config-disabled protos are skipped above - so with a chain A→B→C, C is dropped because of B even though B itself is dropped. Is that intended, or should overridden_classes be recomputed until it converges? (No impact today since UCP_PROTO_CLASS_RMA_RNDV is the only class and no proto both belongs to a class and declares a fallback, so this is a question about the intended rule, not a bug.)

Residual coverage gap: the first loop's continue path (preferred proto disabled by configuration, but not all protos disabled, so the fallback proto survives) is not covered by any new test; get_zcopy_thresh only covers the all-disabled-then-re-enabled path.

Residual coverage gap: the PUT direction of the new rule still has no dedicated mock test (covered only implicitly by test_ucp_proto_mock_cuda_ipc.put), and the get_no_zcopy_proto / no_get_zcopy expectations of a single 1..INF range depend on RNDV_SCHEME=put_zcopy fully pinning the remote-side selection for CUDA memory — worth watching in CI on GPU nodes.

@tvegas1

tvegas1 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

retested on setup, checked trace are not flooding. @brminich can we go ahead?

@svc-ucx

svc-ucx commented Sep 18, 2026

Copy link
Copy Markdown

🤖 CI Triage AgentUCX PR (Tests new on worker 2) · commit 79ef7ee0

TL;DR: The make test step failed because exactly one gtest failed — ib/test_ucp_sockaddr_protocols_diff_config.diff_num_paths_small_msg_server_less_lanes/0 (ib,cuda_copy,rocm_copy/all_devs) — an asymmetric-lane (client IB_NUM_PATHS=2 / server =1, MAX_EAGER_LANES=2) tag test that exercises exactly the protocol-selection path this PR (rndv_prio, "make put/rndv and get/rndv a fallback of zcopy") rewrites; the fix is to re-check the new fallback/supersede disabling logic for lane-asymmetric configs (and re-run that single test with logs retained, since the assertion text fell outside the fetchable log window).

Full analysis

Summary: Azure job "new on worker 2" (build 136702) failed at ./contrib/test_jenkins.shmake: *** [Makefile:4763: test] Error 1; 8613/8614 gtests passed, 1 failed: ib/test_ucp_sockaddr_protocols_diff_config.diff_num_paths_small_msg_server_less_lanes/0.

Root cause: Evidence from the log: the build's only failure is that single gtest (no timeout, no crash/SIGTERM, no hang — the run ended normally after 3,254 s with continuous output). The failing test (test/gtest/ucp/test_ucp_sockaddr.cc:2907) connects a client created with UCX_IB_NUM_PATHS=2 to a server created with UCX_IB_NUM_PATHS=1 under UCX_MAX_EAGER_LANES=2 and then does a 4 KB unexpected tag send/recv 10 times, with the error-wrapping log handler installed only on the first iteration (test_tag_send_recv, lines 2336-2416). So a failure here means either a data/length mismatch, a non-completing request, or an unexpected UCS error log during iterations 2-10 on an endpoint whose two sides have different lane counts. That is precisely the code this PR touches: the new protocol-selection "fallback/supersede" chain (ucp_proto_select_disable_fallback, src/ucp/proto/proto_select.c:75-108, added/modified in the Sept-2026 series 1bcbbc1c5eddb0e6954e51361d3a7d73107, all Thomas Vegas) which can mask protocols (e.g. bcopy/zcopy vs rndv) per message-size range; the sibling not_all_devs variant and all other 8613 tests passed, i.e. the failure is specific to the multi-path/asymmetric-lane configuration. Note: the exact gtest assertion line is not in the retrievable log window (the Azure API only returns the tail of this ~54-minute job), so the precise assertion text could not be confirmed.

Implicated commit: PR #11940 head [REDACTED:Hex High Entropy String] (branch rndv_prio, Thomas Vegas), building on 7d731073 / 1361d3ae / e6954e5b "UCP/PROTO: … fallback/supersede rule" (Thomas Vegas). Secondary suspect: 858ffeae "UCP/WIREUP: Improve p2p lane matching diagnostics (#11912)" (Thomas Vegas), which touched both src/ucp/wireup/wireup.c and this very test file and can emit new error-level diagnostics on lane mismatch (an unexpected ucs_error fails gtest outside the scoped log handler).

File: test/gtest/ucp/test_ucp_sockaddr.cc:2907 (failing test) → src/ucp/proto/proto_select.c:75-108 (ucp_proto_select_disable_fallback)

Suggested fix:

  1. Reproduce locally/in CI with just this case and full output kept, e.g. GTEST_FILTER='ib/test_ucp_sockaddr_protocols_diff_config.diff_num_paths_small_msg_server_less_lanes/0' UCX_LOG_LEVEL=info ./gtest — this yields the assertion text that the truncated Azure tail hides.
  2. In ucp_proto_select_disable_fallback(), verify a protocol is never disabled as a "fallback" when its superseding protocol is unusable for the remote configuration (here the server exposes fewer eager/rndv lanes than the client). Guard the overridden_classes accumulation so only protocols actually selectable on the given range/lane set can override others, and add an assertion that at least one protocol remains enabled per range.
  3. If step 1 shows an unexpected error log rather than a data mismatch, the p2p-lane-matching diagnostic from UCP/WIREUP: Improve p2p lane matching diagnostics #11912 is being printed at error level for a legitimate asymmetric-lane case — demote it to ucs_diag/ucs_debug or wrap the whole loop (not just iteration 0) in scoped_log_handler(wrap_errors_logger).

Related: PR #11940 (this PR, "UCP/RMA: Make put/rndv and get/rndv a fallback of zcopy"); PR #11912 (p2p lane matching diagnostics); PR #11868 also references this test name.

🛡️ This comment had 1 potential secret(s) redacted (Hex High Entropy String). See request_id c7ac392d-b573-4e3a-a6e6-d182934ca4ca in the triage console for the audit trail.

@tvegas1
tvegas1 enabled auto-merge (squash) September 20, 2026 05:33
Comment on lines +94 to +96
if (ucs_dynamic_bitmap_get(disabled_proto_mask, proto_idx)) {
continue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minor:
Should this compute the transitive fallback closure? For A → B → C, when B is disabled by configuration, this continue prevents collecting B → C, so C can still be selected against A. If fallback chains are intentionally unsupported, pls document.
(one example is zcopy->rndv->am_ sw_emulation)

@tvegas1
tvegas1 merged commit fe0ea85 into openucx:master Sep 22, 2026
162 checks passed
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.

4 participants