UCT/ROCM: Enable dmabuf by default with peermem fallback - #11934
Conversation
|
🤖 Starting review — findings will be posted here when done. |
|
🤖 CI Triage Agent — TL;DR: This is not a code/test bug — the "AUTHORS file update check" failed because PR #11934's commit author, David DeBonis ddebonis@amd.com, is a new contributor who is not listed in the Full analysisSummary: The Root cause: The pipeline step computes the PR commit range ( Since neither the email Implicated commit: [REDACTED:Hex High Entropy String] (David DeBonis ddebonis@amd.com) — the PR head commit, which introduces a new author without an File: Suggested fix: In PR #11934, add the author to # from the repo root, on the PR branch
./contrib/authors_update.sh $(git merge-base HEAD origin/develop)..HEAD
git add AUTHORS && git commit -m "AUTHORS: add David DeBonis"Or simply insert Related: PR #11934 (the failing PR). No pre-existing issue tracks this failure mode; recent
|
|
Residual coverage gap: the ROCm CI containers ( |
|
🤖 Starting review — findings will be posted here when done. |
|
🤖 CI Triage Agent — TL;DR: The "commit title" codestyle job failed because one commit in PR #11934 is titled Full analysisSummary: Azure Pipelines job Root cause: Not a product or infrastructure bug. The log shows:
Secondary (cosmetic) CI issue visible in the same log: the step runs with Implicated commit: [REDACTED:Hex High Entropy String] (PR #11934 head, merged as 63e9a2a for CI) — the offending commit is the one titled File: buildlib/tools/codestyle.sh:14 (the enforcing regex); buildlib/pr/codestyle.yml:17-30 (the failing step) Suggested fix:
Related: PR #11934; contributor guidance: https://github.com/openucx/ucx/wiki/Guidance-for-contributors#general-guidelines
|
|
🤖 CI Triage Agent — TL;DR: The "commit title" codestyle job failed because two commits in PR #11934 have titles that don't match UCX's required Full analysisSummary: Azure job Root cause: Not an infrastructure or product bug.
Both bad titles set Implicated commit: [REDACTED:Hex High Entropy String] (PR #11934 head, merged as 2f6f623 for testing) — the offending commits are authored by the PR submitter, not by a UCX maintainer change. File: buildlib/tools/codestyle.sh:14 (check logic) — enforced via buildlib/pr/codestyle.yml:24 Suggested fix: Rewrite the PR history so every commit title has the required form. Simplest is to squash the three commits into one: If the commits must stay separate, amend each title to e.g. Related: PR #11934; contributor guidance referenced by the job: https://github.com/openucx/ucx/wiki/Guidance-for-contributors#general-guidelines
|
|
Residual gaps: both |
|
🤖 Starting review — findings will be posted here when done. |
|
Residual coverage gap: the ROCm CI jobs are build-only, so neither the compile break nor the offset math would be caught by the PR pipeline; an AMD GPU run of |
|
🤖 Starting review — findings will be posted here when done. |
|
🤖 CI Triage Agent — TL;DR: This is not an infrastructure failure — the "commit title" codestyle job correctly rejected PR #11934 because two of its commits have titles that lack the required Full analysisSummary: Azure Pipelines job Root cause: The job runs
Two bad titles set Implicated commit: No product regression. The offending commits are the intermediate commits of PR #11934 (merge commit File: Suggested fix:
Related: PR #11934; guidelines referenced by the job: https://github.com/openucx/ucx/wiki/Guidance-for-contributors#general-guidelines
|
|
🤖 Starting review — findings will be posted here when done. |
|
Residual coverage gap: ROCm CI jobs are build-only, so neither Residual coverage gap: Residual coverage gap: nothing covers a failing |
|
🤖 Starting review — findings will be posted here when done. |
|
|
||
| #include <ucs/sys/string.h> | ||
| #include <ucs/sys/module.h> | ||
| #include <ucs/sys/ptr_arith.h> |
There was a problem hiding this comment.
minor: ptr_arith.h isn't needed here, UCS_PTR_BYTE_DIFF comes from ucs/sys/compiler_def.h (ze_copy_md.c uses it for the same dmabuf offset math without this include).
| UCT_MD_MEM_ATTR_V2_FIELD_DMABUF_OFFSET); | ||
| } | ||
|
|
||
| status = uct_rocm_base_mem_query(uct_md, addr, length, |
There was a problem hiding this comment.
Redundant state in the new wrapper: can we pass md->have_dmabuf down to uct_rocm_base_mem_query() instead of clearing and restoring the caller's field_mask? It has only this one caller, so that would drop both the mask juggling and the fd/offset fixup below.
|
Residual gap: |
|
🤖 Starting review — findings will be posted here when done. |
| (mem_attr_p->field_mask & UCT_MD_MEM_ATTR_V2_FIELD_DMABUF_OFFSET)) { | ||
| uct_rocm_base_dmabuf_export(export_addr, export_len, mem_type, | ||
| &dmabuf_fd, &dmabuf_offset); | ||
| if (mem_attr_p->field_mask & UCT_MD_MEM_ATTR_V2_FIELD_DMABUF_FD) { |
There was a problem hiding this comment.
blocker: duplicated if (DMABUF_FD) block — returns a closed fd and leaks the fd.
if (mem_attr_p->field_mask & ..._DMABUF_FD) {
mem_attr_p->dmabuf_fd = dmabuf_fd;
if (mem_attr_p->field_mask & ..._DMABUF_FD) {
mem_attr_p->dmabuf_fd = dmabuf_fd;
} else {
ucs_close_fd(&dmabuf_fd);
}
ucs_close_fd(&dmabuf_fd);
}Brace matching makes this compile but it is semantically wrong in both directions:
- When
DMABUF_FDis requested, the fd is stored intomem_attr_p->dmabuf_fdand then unconditionally closed by the trailingucs_close_fd(). The caller (ucp_memh_register_internal()→ibv_reg_dmabuf_mr(), anducp_memh_register_internal()'s ownucs_close_fd(®_params.dmabuf_fd)) receives a stale fd number: registration fails withEBADF, or worse, the number is reused by another thread and a wrong file gets registered / double-closed. - When only
DMABUF_OFFSETis requested, the whole block is skipped, so the exported fd is never closed — fd leak on every offset-only query (gtestcheck_fd_leaks()will also flag it).
| if (mem_attr_p->field_mask & UCT_MD_MEM_ATTR_V2_FIELD_DMABUF_FD) { | |
| if (mem_attr_p->field_mask & UCT_MD_MEM_ATTR_V2_FIELD_DMABUF_FD) { | |
| mem_attr_p->dmabuf_fd = dmabuf_fd; | |
| } else { | |
| ucs_close_fd(&dmabuf_fd); | |
| } |
| } | ||
| } | ||
|
|
||
| UCS_TEST_P(test_md_dmabuf, rocm_dmabuf_disable, "ROCM_COPY_DMABUF~=no") |
There was a problem hiding this comment.
minor (test placement/coverage), both non-blocking:
- The test asks for
UCT_MD_MEM_ATTR_FIELD_DMABUF_OFFSETbut never checks it;EXPECT_EQ(0, mem_attr.dmabuf_offset)would also cover the disabled path of the new offset math. - It lives in the generic
test_md_dmabuffixture and relies onROCM_COPY_DMABUF~=noto skip all other MDs. Existing precedent for a component-specific test is a dedicated fixture plus_UCT_MD_INSTANTIATE_TEST_CASE(test_rocm, rocm_cpy)(seetest_gdr_copy), which makes the intent explicit instead of depending on the config-skip trick.
|
Residual gaps: the two mangled hunks mean the current head almost certainly does not build with ROCm enabled; ROCm CI jobs are build-only and not part of the default pipeline, so neither the compile break nor the fd lifetime bug would be caught. An AMD GPU run of Residual gap: nothing exercises a failing |
|
🤖 Starting review — findings will be posted here when done. |
| UCS_CONFIG_TYPE_TABLE(ucs_config_rcache_table)}, | ||
|
|
||
| {"DMABUF", "no", | ||
| {"DMABUF", "try", |
There was a problem hiding this comment.
With try as default, md_attr.dmabuf_mem_types now advertises ROCm dmabuf based only on the kernel-config/kallsyms heuristic in uct_rocm_base_is_dmabuf_supported(). UCP then adds every dmabuf-capable MD to reg_md_map[ROCM] (ucp_context.c, "we can register memory of this type with any md that supports dmabuf"), so ROCm buffers are pushed to IB MDs even when hsa_amd_portable_export_dmabuf() fails at runtime and peermem is not loaded — that turns "not registered on IB" into a registration error. cuda_copy handles this by clearing UCS_MEM_FLAG_REGISTRABLE when the export fails (uct_cuda_copy_md_is_registrable()), while uct_rocm_base_mem_query() always reports UCS_MEM_FLAG_REGISTRABLE in ucs_memtype_cache_update(). Can we detect the export failure the same way, or is the peermem fallback guaranteed to cover it?
| ASSERT_UCS_OK(status); | ||
| EXPECT_EQ(alloc_mem_type, mem_attr.mem_type); | ||
| if ((alloc_mem_type == UCS_MEMORY_TYPE_CUDA) || | ||
| (alloc_mem_type == UCS_MEMORY_TYPE_ROCM) || |
There was a problem hiding this comment.
Minor: the outer condition now lists UCS_MEMORY_TYPE_ROCM and then re-tests it inside; pls give ROCm its own branch so the expectations read directly.
|
Residual coverage gap: ROCm CI is build-only, so neither the compile break nor the offset math is exercised by the PR pipeline; an AMD GPU run of |
|
🤖 Starting review — findings will be posted here when done. |
| length); | ||
| ucs_debug("failed to export dmabuf handle for addr %p / %zu: 0x%x", | ||
| addr, length, status); | ||
| ucs_debug("failed to export dmabuf handle for addr %p / %zu: 0x%x", |
There was a problem hiding this comment.
the log line is duplicated
| ucs_debug("failed to export dmabuf handle for addr %p / %zu: 0x%x", | |
| ucs_debug("failed to export dmabuf handle for addr %p / %zu: 0x%x", | |
| addr, length, status); |
| mem_attr_p->dmabuf_fd = dmabuf_fd; | ||
| is_exported = 1; | ||
| } | ||
| if (mem_attr_p->field_mask & UCT_MD_MEM_ATTR_V2_FIELD_DMABUF_FD) { |
There was a problem hiding this comment.
minor: ucs_close_fd() already ignores UCT_DMABUF_FD_INVALID, so the extra check can go
| if (mem_attr_p->field_mask & UCT_MD_MEM_ATTR_V2_FIELD_DMABUF_FD) { | |
| if (mem_attr_p->field_mask & UCT_MD_MEM_ATTR_V2_FIELD_DMABUF_FD) { | |
| mem_attr_p->dmabuf_fd = dmabuf_fd; | |
| } else { | |
| ucs_close_fd(&dmabuf_fd); | |
| } |
| return UCS_OK; | ||
| } | ||
|
|
||
| static ucs_status_t |
There was a problem hiding this comment.
can we pass md->have_dmabuf down to uct_rocm_base_mem_query() instead of clearing and restoring mem_attr_p->field_mask? it avoids temporarily modifying the caller's input struct, and keeps the dmabuf decision in one place like cuda_copy does.
| } | ||
| } | ||
|
|
||
| UCS_TEST_P(test_md_dmabuf, rocm_dmabuf_disable, "ROCM_COPY_DMABUF~=no") |
There was a problem hiding this comment.
minor: can we put this in a rocm-only fixture with _UCT_MD_INSTANTIATE_TEST_CASE(test_rocm, rocm_cpy) (like test_gdr_copy) instead of relying on the ~= modifier to skip it for every other MD?
| @@ -542,10 +542,16 @@ UCS_TEST_P(test_md, mem_type_detect_mds) { | |||
| ASSERT_UCS_OK(status); | |||
| EXPECT_EQ(alloc_mem_type, mem_attr.mem_type); | |||
| if ((alloc_mem_type == UCS_MEMORY_TYPE_CUDA) || | |||
There was a problem hiding this comment.
minor: ROCM is tested twice in nested conditions; a separate if (alloc_mem_type == UCS_MEMORY_TYPE_ROCM) branch before the CUDA/ZE one would read better.
|
Residual gap: ROCm CI jobs are build-only, so neither the offset math ( Residual gap: |
7e3dddc to
4a9f32a
Compare
|
🤖 Starting review — findings will be posted here when done. |
| uct_md_mem_attr_v2_t *mem_attr_p) | ||
| { | ||
| uct_rocm_copy_md_t *md = ucs_derived_of(uct_md, uct_rocm_copy_md_t); | ||
| uint64_t orig_mask = mem_attr_p->field_mask; |
There was a problem hiding this comment.
why not pass md->have_dmabuf into uct_rocm_base_mem_query() and gate uct_rocm_base_dmabuf_export() there? it has no other caller, so that drops the temporary mutation of the caller's mem_attr_p->field_mask and the manual re-init of the two fields.
Rationale: mem_attr_p is a caller-owned in/out struct; clearing bits in it and restoring them after the callee returns is fragile (it also makes the base run with an empty mask purely for its side effects, where an HSA failure is still propagated as a raw hsa_status_t). Passing the flag down keeps the base's behavior identical to before and removes ~25 lines.
| } | ||
| } | ||
|
|
||
| UCS_TEST_P(test_md_dmabuf, rocm_dmabuf_disable, "ROCM_COPY_DMABUF~=no") |
There was a problem hiding this comment.
minor: this test only applies to rocm_cpy, so a dedicated fixture with _UCT_MD_INSTANTIATE_TEST_CASE(test_rocm_copy, rocm_cpy) (as done for test_gdr_copy) is clearer than relying on ~= to skip the other 12 MD instances.
4a9f32a to
ba5cbb6
Compare
|
🤖 Starting review — findings will be posted here when done. |
| ucs_sys_device_t sys_dev = UCS_SYS_DEVICE_ID_UNKNOWN; | ||
| int dmabuf_fd; | ||
| int dmabuf_fd = UCT_DMABUF_FD_INVALID; | ||
| hsa_status_t status; |
There was a problem hiding this comment.
the early exit below returns status (an hsa_status_t) as ucs_status_t, so a uct_rocm_base_get_ptr_info() failure propagates e.g. 0x1002 up to ucp_memh_register_internal(). this path becomes reachable in gtest now that test_md_dmabuf runs for rocm_cpy and queries host memory too. can we return UCS_ERR_INVALID_ADDR instead?
| if (have_dmabuf) { | ||
| uct_rocm_base_dmabuf_export(base_addr, base_size, mem_type, | ||
| &dmabuf_fd, &dmabuf_offset); | ||
| } |
There was a problem hiding this comment.
minor: pls add a blank line after the block, per docs/CodeStyle.md ("Use blank line after if/else blocks").
| } | |
| } | |
ba5cbb6 to
cb780f7
Compare
|
🤖 Starting review — findings will be posted here when done. |
| @@ -447,7 +455,7 @@ ucs_status_t uct_rocm_base_mem_query(uct_md_h md, const void *addr, | |||
| &base_size, &hsa_mem_type, &agent, | |||
There was a problem hiding this comment.
minor: ucs_close_fd() already returns early for -1, so the extra check can go.
| &base_size, &hsa_mem_type, &agent, | |
| if (mem_attr_p->field_mask & UCT_MD_MEM_ATTR_V2_FIELD_DMABUF_FD) { | |
| mem_attr_p->dmabuf_fd = dmabuf_fd; | |
| } else { | |
| ucs_close_fd(&dmabuf_fd); | |
| } |
cb780f7 to
2e8426a
Compare
|
🤖 Starting review — findings will be posted here when done. |
| &dmabuf_offset); | ||
| if (mem_attr_p->field_mask & UCT_MD_MEM_ATTR_V2_FIELD_DMABUF_OFFSET) { | ||
| mem_attr_p->dmabuf_offset = dmabuf_offset + | ||
| UCS_PTR_BYTE_DIFF(base_addr, addr); |
There was a problem hiding this comment.
dmabuf_offset is reported even when no dmabuf was exported. When have_dmabuf == 0 (e.g. ROCM_COPY_DMABUF=no), or when hsa_amd_portable_export_dmabuf() fails, dmabuf_fd stays UCT_DMABUF_FD_INVALID but we still report dmabuf_offset = addr - base_addr. Per the dmabuf_offset doc in uct.h (src/uct/api/uct.h:1686-1690) the offset is only defined relative to the dmabuf region identified by dmabuf_fd, so a non-zero value with an invalid fd has no meaning — please leave it 0 when nothing was exported. That also removes the hidden dependency behind EXPECT_EQ(0ul, mem_attr.dmabuf_offset) in the new dmabuf_disable test, which currently only passes because HSA happens to report agentBaseAddress == hipMalloc() pointer for that allocation. Note the fix cannot simply test dmabuf_fd == UCT_DMABUF_FD_INVALID at that point, because ucs_close_fd() resets it to -1 on the offset-only path; a separate "exported" flag or reordering is needed.
| ## Current | ||
| ### Features: | ||
| #### ROCM | ||
| * Enabled dmabuf by default for GPU-direct transfers, falling back to peermem when unavailable |
There was a problem hiding this comment.
minor: user-facing doc not updated for the ROCm default flip. Can we also update the dmabuf section in docs/source/faq.md ("What is needed for dmabuf support?")? It currently lists only Cuda requirements, and since this PR makes dmabuf the default for ROCm, the ROCm prerequisites (kernel with CONFIG_DMABUF_MOVE_NOTIFY/CONFIG_PCI_P2PDMA, hsa_amd_portable_export_dmabuf availability) belong there too.
|
Residual coverage gap: ROCm CI containers are build-only, so neither Residual coverage gap: nothing exercises a failing |
2e8426a to
6b6bb7b
Compare
|
🤖 Starting review — findings will be posted here when done. |
| if (mem_attr_p->field_mask & UCT_MD_MEM_ATTR_V2_FIELD_DMABUF_FD) { | ||
| mem_attr_p->dmabuf_fd = dmabuf_fd; | ||
| } else if (dmabuf_fd != UCT_DMABUF_FD_INVALID) { | ||
| ucs_close_fd(&dmabuf_fd); |
There was a problem hiding this comment.
minor: ucs_close_fd() already returns early for an invalid fd, so the extra check and the second sentence of the comment above can be dropped.
| ucs_close_fd(&dmabuf_fd); | |
| } else { | |
| ucs_close_fd(&dmabuf_fd); | |
| } |
| EXPECT_EQ(buffer_size, mem_attr.alloc_length); | ||
| /* ROCm HSA may round sizeInBytes up to a page */ | ||
| if (alloc_mem_type == UCS_MEMORY_TYPE_ROCM) { | ||
| EXPECT_GE(mem_attr.alloc_length, buffer_size); |
There was a problem hiding this comment.
minor: can we test ROCm in its own branch instead of nesting a second alloc_mem_type == UCS_MEMORY_TYPE_ROCM check inside the branch that just matched it?
|
Residual coverage gap: The ROCm CI containers ( Residual coverage gap: Residual coverage gap: Nothing exercises a failing |
6b6bb7b to
29cb04c
Compare
|
🤖 Starting review — findings will be posted here when done. |
|
🤖 Starting review — findings will be posted here when done. |
| if (!is_exported) { | ||
| uct_rocm_base_dmabuf_export(addr, length, mem_type, &dmabuf_fd, | ||
| &dmabuf_offset); | ||
| /* Offset is defined only relative to a valid dmabuf_fd. Check the fd |
There was a problem hiding this comment.
minor: the second sentence is hard to parse ("Check the fd before close, which sets it to -1..."), which obscures the real reason the offset block must precede the fd/close block.
| /* Offset is defined only relative to a valid dmabuf_fd. Check the fd | |
| /* The offset is meaningful only with a valid dmabuf_fd, so evaluate it | |
| * before the fd is closed below on the offset-only query path. */ |
| (alloc_mem_type == UCS_MEMORY_TYPE_ZE_DEVICE) || | ||
| (alloc_mem_type == UCS_MEMORY_TYPE_ZE_MANAGED)) { | ||
| EXPECT_EQ(buffer_size, mem_attr.alloc_length); | ||
| /* ROCm HSA may round sizeInBytes up to a page */ |
There was a problem hiding this comment.
minor: the comment about ROCm page rounding sits above a check that covers CUDA/ZE too, so it reads as if it applies to all of them. Pls move the comment into the ROCm branch, it does not apply to the CUDA/ZE check.
| /* ROCm HSA may round sizeInBytes up to a page */ | |
| if (alloc_mem_type == UCS_MEMORY_TYPE_ROCM) { | |
| /* ROCm HSA may round the allocation size up to a page */ | |
| EXPECT_GE(mem_attr.alloc_length, buffer_size); | |
| } else { | |
| EXPECT_EQ(buffer_size, mem_attr.alloc_length); | |
| } |
|
🤖 CI Triage Agent — TL;DR: The "AddressSanitizer roce on worker 1" job was cancelled by Azure mid-step (not a test failure, no ASAN report) while still in the very first ASAN phase ( Full analysisSummary: Root cause: From the log, no test failed and no AddressSanitizer report was produced. The last work shown is Contributing structural issue: worker 1 had spent the whole job budget without even reaching the main suite ( Implicated commit: Not the PR commit. File: Suggested fix:
Related: PR #11934 ( |
dcda336 to
29cb04c
Compare
…for GPU-direct when the kernel supports it, and keep peermem as the fallback on older kernels. Also report allocation-base offsets for interior ROCm addresses.
29cb04c to
57c450e
Compare
|
🤖 Starting review — findings will be posted here when done. |
|
🤖 Review complete — no issues found. |
What?
Use dma-buf for GPU-direct when the kernel supports it, and keep peermem as the fallback on older kernels. Also report allocation-base offsets for interior ROCm addresses.
Why?
The rocm components in UCX have support for the dma-buf mechanism to enable direct data transfer between an RDMA NIC and a GPU. However, the user explicitly needs to request that support by setting UCX_ROCM_COPY_DMABUF=y, dma-buf support is not turned on by default.
How?
Starting from linux kernel > 6.0, the peermem mechanism that was used previously is not useable in the vast majority of deployments, necessitating using dma-buf instead. The goal of this story is to enable dma-buf by default in the rocm components of UCX, and ensure that if dma-buf support is not available (e.g. linux kernel 5.15 in most cases) we can fall back to the peermem mechanism if that is available.