Repository navigation
Conversation
Nothing in uni20 enabled peer access, so every cross-device copy went through cudaMemcpyPeerAsync staged by the driver through host memory, and no kernel could read another device's buffer. On two NVLink-bridged Quadro GV100s a 256 MiB peer copy ran at 7.9 GB/s; with peer access it runs at 96.9 GB/s, and a kernel reading the peer device's memory reaches 85.2 GB/s. cuda::initialize() now grants direct access for every ordered pair of enrolled devices that cudaDeviceCanAccessPeer supports. CUDA needs two grants: cudaDeviceEnablePeerAccess covers cudaMalloc allocations, while the stream-ordered cudaMallocAsync allocations used by CudaBuffer are opened only by cudaMemPoolSetAccess on the owning device's pool. Each grant is recorded before state changes, and shutdown revokes exactly the access the runtime added once its allocations are drained. Runtime::peer_access(accessor, owner) reports a pair's state for later lowering, and RuntimeConfig::enable_peer_access (default on) allows disabling it to measure staged transfers. Tests check that access matches device capability and is fully revoked at shutdown, that the opt-out leaves access unchanged, and that a kernel on device 1 reads a pool-allocated CudaBuffer on device 0. With the pool grant disabled, that kernel faults with cudaErrorIllegalAddress. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review Summary: ❌ FailedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Code Review
This pull request implements automatic peer access management for CUDA devices within the Uni20 runtime. It introduces a mechanism to grant and revoke peer access between enrolled devices, covering both standard cudaMalloc allocations and stream-ordered memory pool allocations. The changes include updates to the Runtime class, the addition of a configuration option enable_peer_access, updated documentation, and new test cases to verify peer access functionality and proper cleanup at shutdown. I have no feedback to provide as the implementation appears robust and well-tested.
|
Review Bot: review of exact head Found 1 issue: the default combined test executable What the PR does. What I reviewed. Verified (polaron, 2× Quadro GV100 linked
Needs a human. Not checked.
Review tier: full (code or new pull request) · model: claude-opus-5-5 · effort: high |
| test_debug_cuda_scheduler.cpp test_cuda_storage_types.cpp | ||
| LIBS uni20_common uni20_async uni20_backend_cuda | ||
| ) | ||
| target_sources(uni20_cuda_runtime_tests PRIVATE test_cuda_peer_kernel.cu) |
There was a problem hiding this comment.
Review Bot: review of exact head 5f6bc1d822dadf5acdfedebd32b03f37b5905804
The new .cu helper is added only to uni20_cuda_runtime_tests, not to the combined uni20_tests executable. add_test_module (cmake/Uni20TestHelpers.cmake:29-31) collects only its SOURCES into UNI20_COMBINED_TEST_SRCS. So uni20_tests compiles test_cuda_runtime.cpp, which now calls uni20_test_launch_copy_doubles (test_cuda_runtime.cpp:292), but does not compile the file that defines it. UNI20_BUILD_COMBINED_TESTS defaults to ON for standalone builds (CMakeLists.txt:127, docs/development/testing.md:259), so a default CUDA build of all will fail to link. CI does not catch this because every job passes -DUNI20_BUILD_COMBINED_TESTS=OFF. The existing test_cuda_descriptor_compile.cu in tests/linalg avoids the problem only because nothing calls into it.
How I checked (polaron, GCC 13.3, CUDA 12.9, Debug, -DUNI20_ENABLE_CUDA=ON -DUNI20_BACKEND_CUSOLVER=ON -DUNI20_BUILD_COMBINED_TESTS=ON, other options as in the CI Release job):
-
ninja -t commands uni20_tests | tail -1: the link line has 228 objects, includingtests/CMakeFiles/uni20_tests.dir/backend/cuda/test_cuda_runtime.cpp.o, and no.cu.oat all. -
nm -C -uon that object listsU uni20_test_launch_copy_doubles(double*, double const*, unsigned long, CUstream_st*). Onlytests/backend/cuda/test_cuda_peer_kernel.cudefines it. -
Linking that object with the combined
uni20_tests_main.cpp.oand the same libraries fails with exactly one error:test_cuda_runtime.cpp:292: undefined reference to 'uni20_test_launch_copy_doubles(...)'. -
A full
cmake --build build-comb --target uni20_tests -- -k 0compiles every object. The only failure is the final link oftests/uni20_tests, with that same undefined reference. In this configurationuni20_common_testsalso builds without error.
A fix I tested in a scratch worktree of this head:
- Move
test_cuda_peer_kernel.cuinto theSOURCESofadd_test_module(cuda_runtime ...)and drop thetarget_sourcesline. - In
tests/CMakeLists.txt, afteruni20_testsis created, addif(CMAKE_CUDA_COMPILER) set_target_properties(uni20_tests PROPERTIES CUDA_STANDARD 20 CUDA_STANDARD_REQUIRED ON) endif().
Step 2 is needed: with step 1 alone, CMake configure fails with Target "uni20_tests" requires the language dialect "CUDA23". With both steps, uni20_tests and uni20_cuda_runtime_tests build and link, and uni20_tests --gtest_filter='*Peer*' passes 7/7.
Not checked. Clang 19 or Release builds of the combined target; a combined build with UNI20_ENABLE_CUDA=OFF (not affected, since tests/backend/cuda is CUDA-only); running all of uni20_tests.
There was a problem hiding this comment.
Confirmed, thanks. Fixed in a29f570 with your two-part change: test_cuda_peer_kernel.cu is now in the module's SOURCES, and uni20_tests gets CUDA_STANDARD 20 when a CUDA compiler is enabled. Checked in a fresh -DUNI20_BUILD_COMBINED_TESTS=ON CUDA tree on polaron: uni20_tests configures, builds and links, and its *Peer* tests pass (8/8 at the current head).
There was a problem hiding this comment.
Review Bot: recheck of exact head 66a304ff358bb4f95586822dd9a7c45bc6cfe0d8
Confirmed fixed. I configured a fresh tree at this head with -DUNI20_BUILD_COMBINED_TESTS=ON (GCC 13.3, CUDA 12.9, Debug, -DUNI20_ENABLE_CUDA=ON -DUNI20_BACKEND_CUSOLVER=ON -DCMAKE_CUDA_ARCHITECTURES=70, other options as in the CI Release job) and ran cmake --build build --target uni20_tests. All 278 steps finished, including the link of tests/uni20_tests. uni20_tests --gtest_filter='*Peer*' then passed 8/8 on the two GV100s.
Not checked. The combined target under Clang 19 or Release.
test_cuda_peer_kernel.cu was attached to uni20_cuda_runtime_tests with target_sources, so add_test_module never recorded it for the combined uni20_tests executable. That executable compiles test_cuda_runtime.cpp, which calls the helper, and failed to link in CUDA builds with UNI20_BUILD_COMBINED_TESTS on, the standalone default. List the helper in the module's SOURCES, and build uni20_tests with CUDA_STANDARD 20 when a CUDA compiler is enabled, as test modules may now contribute CUDA sources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Any unexpected failure from cudaDeviceEnablePeerAccess or the pool grant aborted cuda::initialize(), which previously succeeded with staged copies. Peer access is a performance optimization, and a grant can fail on valid hardware, for example with cudaErrorTooManyPeers beyond eight peers on systems without NVSwitch. Replace RuntimeConfig::enable_peer_access with peer_access_policy: - best_effort (default): grant every capable pair; a pair whose grant fails is rolled back, reported with display::warning, and left to staged copies. - required: a failed grant is an error, as before. - disabled: grant nothing. Policies apply only to pairs cudaDeviceCanAccessPeer reports as capable; other pairs are not failures. Each pair's grant now returns the failing CUDA status instead of raising, because cuda::check aborts by default rather than throwing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
On the Needs a human point: decided that a failed grant is not fatal by default. 66a304f replaces
Policies apply only to pairs that Tests: a new |
| | `best_effort` (default) | Grant every capable pair. A pair whose grant fails, for example with `cudaErrorTooManyPeers` beyond eight peers on systems without NVSwitch, is rolled back, reported with a warning, and left to staged copies. | | ||
| | `required` | Grant every capable pair; a failed grant is an error. Use it where staged peer copies would make a run unacceptably slow. | | ||
| | `disabled` | Grant nothing; use it to measure or diagnose staged transfers. | | ||
| Use `nvidia-smi topo -m` to see whether a pair is connected by |
There was a problem hiding this comment.
Review Bot: review of exact head 66a304ff358bb4f95586822dd9a7c45bc6cfe0d8
This line directly follows the policy table with no blank line between them. In GitHub-flavored Markdown a table continues until a blank line, so on GitHub this sentence and the next two lines (the NV# / PIX/PXB / SYS explanation) become three extra rows of the policy table, each a one-cell row with an empty "Behavior" column. The leading space does not stop the table.
How I checked: I took the ## Peer Access section of this file at this head and rendered it with cmarkgfm 2025.10.22 (github_flavored_markdown_to_html, the cmark-gfm library GitHub uses). After the disabled row the output has <tr><td>Use <code>nvidia-smi topo -m</code> to see whether a pair is connected by</td><td></td></tr> and two more rows like it. markdown-it-py 3.0 with tables enabled gives the same result. pandoc 3.1.3 -f gfm ends the table there instead, which is why some renderers will look fine. scripts/check-docs.py passes because it does not check table structure.
Fix: put a blank line before this line and drop its leading space.
Not checked. The Doxygen rendering of this page.
There was a problem hiding this comment.
Fixed in 700c363: blank line added and the leading space dropped.
|
Review Bot: review of exact head Found 1 new issue, a docs rendering problem (inline on What changed since
What I reviewed. In Verified (polaron, 2× Quadro GV100
Needs a human.
All 58 aborts are this same panic in this same test. The bug is therefore pre-existing, but the default grant makes it about five times more frequent on a two-GPU host. The task snapshot shows Not checked.
Review tier: full (code or new pull request) · model: claude-opus-5-5 · effort: high |
Without a blank line, GitHub-flavored Markdown continued the table into the following paragraph and rendered it as three extra rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
On the Needs a human deadlock in Measured on polaron (2× GV100), using your loop:
So #123 should merge before, or together with, this PR. |
Nothing in uni20 enabled CUDA peer access. Every cross-device copy therefore went through
cudaMemcpyPeerAsyncstaged by the driver through host memory, and no kernel could dereference another device's buffer.Measured on polaron (2x Quadro GV100, NVLink
NV4, CUDA 12.9), 256 MiB,cudaMallocAsyncpool memory asCudaBufferallocates it:cudaMemcpyPeerAsyncmain)cudaDeviceEnablePeerAccessonlycudaMemPoolSetAccessChange.
cuda::initialize()grants direct access for every ordered pair of enrolled devices thatcudaDeviceCanAccessPeersupports. CUDA needs two grants:cudaDeviceEnablePeerAccess(from the accessing device) coverscudaMallocallocations, andcudaMemPoolSetAccesson the owning device's default pool covers the stream-ordered allocations used byCudaBuffer. Each grant is recorded before state changes so a failure part-way can be rolled back, and runtime shutdown revokes exactly the access it added once its allocations are drained. Access that was already enabled is reported but left alone.Runtime::peer_access(accessor, owner)reports a pair's state, for the strided peer-copy lowering that follows.RuntimeConfig::enable_peer_access(defaulttrue) disables it to measure or diagnose staged transfers.DeviceResourcesare unaffected.Existing contiguous peer copies benefit with no API change. The runtime, buffer and getting-started docs describe the grants, the measurement, and how to read
nvidia-smi topo -m.Tests (new, in
uni20_cuda_runtime_tests; skipped with fewer than two devices):cudaDeviceCanAccessPeerfor both directions, at both the device and pool level, and is fully revoked at shutdown.enable_peer_access = falseleaves access unchanged.CudaBufferon device 0 (a small.cuhelper added to the test module). With only the pool grant disabled, this test aborts withcudaErrorIllegalAddress.Validation (polaron, GCC 13 Debug, CUDA + cuSOLVER):
uni20_cuda_runtime_tests89/89. Fullctest: 2738/2739; the one failure wasCudaTensorTest.StreamOrderedDescriptorAccessCanBeCoAwaitedAsMdspanLease, the flaky test fixed by #123. The full build also hits the pre-existinguni20_common_testsCUDA build failure, also fixed by #123.scripts/check-docs.pypasses; clang-format clean. Not run: CPU-only build (this change is CUDA-only), Clang, Release, CUDA 13, or hardware without peer support (the grant path skips such pairs).🤖 Generated with Claude Code