Repository navigation
Reject an unsupported ReduceOp before any message; remove the opt-in COMMUX_SPIN_US spin (#13, #12) - #14
Merged
Conversation
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The low-level UCX concurrency changes affect shared wait paths, while CUDA behavior remains unverified.
Review effort: Balanced
Findings: None
What changed in this PR
Prevents unsupported reductions from communicating and removes an unnecessary polling delay after request completion.
Changes:
- Validates reduction operations before collective communication.
- Rechecks completion before parking and improves busy-worker handling.
- Adds collective, unsupported-operation, latency, and CI coverage.
| File | Description |
|---|---|
src/process_group_ucx.cpp |
Implements validation and wait-loop fixes. |
tests/test_reduce_op.py |
Tests unsupported reductions across ranks. |
tests/test_commux.cpp |
Adds late-poll latency regressions. |
tests/test_collectives.py |
Expands operation, size, and payload coverage. |
.github/workflows/ci.yml |
Runs expanded CPU regression tests. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The spin was opt-in and no configuration sets it. It added a per-wait budget, a deadline through worker_wait_timed and extra branches in both wait loops, and each review of that path found another case to handle. worker_wait_timed, UCXWork::wait and wait_request are back to the default path they always took with COMMUX_SPIN_US unset. The spin-budget checks in test_commux go with it. The binomial-tree collectives and the build fixes stay.
A hosted runner is one node, so these are the transports the tests use there. A runner that exposes a verbs device without SRQ support fails ucp_worker_create before any test runs (run 36681745872, centralus).
UCzhangxi
force-pushed
the
xiz/reduce-op-check
branch
from
September 30, 2026 08:58
33d6480 to
d8fa479
Compare
A bare wait returns 0, so a failing background rank 1 passed the step. Also name the Python step's actual rank range (2-7).
check_reduce_op runs before the size_ > 1 guard, so an unsupported op must be rejected at world size 1 as well, where no message is sent.
zoeyzyhu
approved these changes
Sep 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Closes #13
Closes #12
Seven commits on main 0994e4e: three fix #13, one removes the opt-in
COMMUX_SPIN_USspin (which resolves #12), and three harden CI.#13: an unsupported ReduceOp must fail on every rank before any message
apply_reducerejected an op other than SUM, PRODUCT, MIN or MAX only on a rank that had already received data. In the binomial tree the leaves sent and returned, so some ranks raised, some returned and some hung, and the unmatched messages reached the next collective with the same tag.tests/test_reduce_op.pycalls reduce at every root and all_reduce with AVG and BAND. Every rank must raise, and a SUM afterwards must still be right. CI runs it at 1, 3 and 4 ranks undertimeout 120.check_reduce_op()at the top ofallreduce()andreduce(), before the lock and before any message. This also runs at world size 1, so an unsupported op now raises there too, where it used to return the tensor unchanged. No call in snapy main (3f7ad96) or in our downstream runner uses an op outside SUM, MIN or MAX.test_collectives.pyalso covers PRODUCT, MIN and MAX at every root, a 2 MiB all_reduce (rendezvous path) and a three-tensor all_reduce. CI runs it at 3 to 7 ranks.test_reduce_op.pyon the test commit alone (main's source) vs with the fix:#12: remove the opt-in spin
99a6347 removes
COMMUX_SPIN_US:spin_us(),SpinBudget, the deadline passed toworker_wait_timed, its sleep onUCS_ERR_BUSY, and the spin branches inUCXWork::waitandwait_request, together with the spin-only checks intests/test_commux.cpp. The spin was off by default, no configuration we know of sets it, and each review of the path found another case to handle. #12's extra poll only happened with the spin on, so removing the spin resolves #12 and returns the wait path to what it was before #11.Check:
git diff cf41b46 HEAD -- srccontains onlycheck_reduce_op(with its two calls) and the binomial-treereduce_lockedandbroadcast_lockedfrom #11.worker_wait_timed,UCXWork::waitandwait_requestare byte-identical to cf41b46. The build fixes from #11 (Threads fortest_commux, torch's pybind11 ABI tag) stay.The spin-path fixes that Sihe Chen worked out with the reviewers (reap before parking, parking a collective under a flood, a TCP-only park, multi-request and FIFO tests, relative flood bounds) are kept as a record at UCzhangxi/commux 33d6480, in case the spin is wanted again.
CI
d8fa479 sets
UCX_TLS: tcp,sm,selfon the two test steps. A hosted runner is one node, so these are the transports the tests use there. One runner (run 36681745872) exposed an RDMA verbs device without SRQ support, anducp_worker_createfailed before any test ran.eb85378 makes the C++ test step wait on rank 1's process id. A bare
waitreturns 0, so a failing background rank 1 used to pass the step. It also names the Python step's rank range.447cddc runs
test_reduce_op.pyat one rank too, where no message is sent. On main's source that run reports 4 failures (the op returns without an error); with the fix it passes.Checks at d8fa479 (eb85378 and 447cddc change only CI and a test docstring)
On a Linux CPU node, with the default transports and with
UCX_TLS=tcp,sm,self:test_torch_dist.pyat 2 ranks,test_reduce_op.pyat 3, 4 and 5,test_collectives.pyat 3 to 7: all pass, 0 failures.test_commuxat 2 ranks withCOMMUX_MT=1 COMMUX_DEADLOCK=1: passes, three runs each.With a CUDA build of this head:
test_collectives.pyat 6 ranks on CUDA and CPU tensors, andtest_torch_dist.pyat 2 ranks on CUDA: all pass, 0 failures.Not covered. More than one node.