Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ Swen Boehm <boehms@ornl.gov>
Thomas Vegas <tvegas@nvidia.com>
Tomer Gilad <tgilad@nvidia.com>
Tony Curtis <tonycurtis32@gmail.com>
Tristan Rice <rice@fn.lc>
Tushar Gohad <tushar.gohad@intel.com>
Tzafrir Cohen <tzafrirc@nvidia.com>
Valentin Petrov <valentinp@mellanox.com>
Expand Down
11 changes: 8 additions & 3 deletions src/uct/cuda/cuda_copy/cuda_copy_md.c
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,11 @@ static void uct_cuda_copy_md_sync_memops_get_address_range(
ucs_trace("query address 0x%llx: 0x%llx..0x%llx length %zu", address,
base_address, base_address + alloc_length, alloc_length);

/* Host-located VMM reports a zero base address */
if (base_address == 0) {
goto out_ctx_pop;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

what about combining this condition and condition below?

Suggested change
/* Host-located VMM reports a zero base address */
if (base_address == 0) {
goto out_ctx_pop;
}
/* For multi-handle VMM, the physical allocation containing the address
* may not cover the whole requested range */
if ((base_address == 0) || /* host-located VMM reports a zero base */
(is_vmm && ((base_address + alloc_length) < (address + length)))) {
goto out_ctx_pop;
}


if (md->config.alloc_whole_reg == UCS_CONFIG_AUTO) {
total_bytes = uct_cuda_copy_md_get_total_device_mem(cuda_device);
if (alloc_length > (total_bytes * md->config.max_reg_ratio)) {
Expand All @@ -610,9 +615,9 @@ static void uct_cuda_copy_md_sync_memops_get_address_range(
ucs_assert(md->config.alloc_whole_reg == UCS_CONFIG_ON);
}

/* For multi-handle VMM, the physical allocation may be smaller than the
* mapped virtual range; preserve the caller's requested extent. */
if (is_vmm && (alloc_length < length)) {
/* For multi-handle VMM, the physical allocation containing the address
Comment thread
d4l3k marked this conversation as resolved.
Resolved

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

minor: the zero-base case is unrelated to multi-handle VMM (and not VMM-specific), pls split it out so each condition carries its own comment:

Suggested change
/* For multi-handle VMM, the physical allocation containing the address
if (base_address == 0) {
/* Host-located VMM reports a zero base address */
goto out_ctx_pop;
}
/* For multi-handle VMM, the physical allocation containing the address
* may not cover the whole requested range */
if (is_vmm && ((base_address + alloc_length) < (address + length))) {
goto out_ctx_pop;
}

* may not cover the whole requested range */
if (is_vmm && ((base_address + alloc_length) < (address + length))) {
Comment thread
d4l3k marked this conversation as resolved.
Resolved
Comment thread
d4l3k marked this conversation as resolved.
Resolved

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why keep is_vmm here? The new check is just "the reported range must cover the requested range", which also holds for legacy allocations.

goto out_ctx_pop;
}

Expand Down
68 changes: 0 additions & 68 deletions test/gtest/uct/cuda/test_cuda_ipc_md.cc
Original file line number Diff line number Diff line change
Expand Up @@ -740,71 +740,3 @@ UCS_TEST_F(test_cuda_ipc_cache_lru, stale_destroy_while_in_use) {
EXPECT_EQ(0UL, m_cache->total_size);
EXPECT_TRUE(ucs_list_is_empty(&m_cache->lru_list));
}


class test_cuda_copy_md : public test_md {
};

UCS_TEST_P(test_cuda_copy_md, vmm_multi_handle_range) {
/* A single byte per chunk is rounded up to the allocation granularity, so
* the buffer is backed by 3 distinct physical handles */
cuda_vmm_mem_buffer buffer(1, UCS_MEMORY_TYPE_CUDA, 3);
uct_md_mem_attr_t mem_attr;

mem_attr.field_mask = UCT_MD_MEM_ATTR_FIELD_MEM_TYPE |
UCT_MD_MEM_ATTR_FIELD_ALLOC_LENGTH;

/* Query the full multi-handle range; the guard preserves the caller's
* requested extent instead of shrinking to the single chunk that
* contains the base pointer. */
ASSERT_UCS_OK(uct_md_mem_query(md(), buffer.ptr(), buffer.size(),
&mem_attr));

EXPECT_EQ(UCS_MEMORY_TYPE_CUDA, mem_attr.mem_type);
EXPECT_GE(mem_attr.alloc_length, buffer.size());
}

UCS_TEST_P(test_cuda_copy_md, vmm_locality_domain_mem_type) {
#if HAVE_DECL_CU_MEM_LOCATION_TYPE_DEVICE_LOCALITY_DOMAIN
ASSERT_EQ(cudaSuccess, cudaSetDevice(0));

auto check_domain = [this](unsigned char domain_id,
ucs_sys_device_t &sys_dev) {
cuda_vmm_mem_buffer buffer;
CUresult ret = buffer.alloc(64, 0,
CU_MEM_LOCATION_TYPE_DEVICE_LOCALITY_DOMAIN,
1, domain_id);
if (ret != CUDA_SUCCESS) {
return false;
}

uct_md_mem_attr_t mem_attr;
mem_attr.field_mask = UCT_MD_MEM_ATTR_FIELD_MEM_TYPE |
UCT_MD_MEM_ATTR_FIELD_SYS_DEV;

ASSERT_UCS_OK(uct_md_mem_query(md(), buffer.ptr(), buffer.size(),
&mem_attr));
EXPECT_EQ(UCS_MEMORY_TYPE_CUDA, mem_attr.mem_type);
EXPECT_NE(UCS_SYS_DEVICE_ID_UNKNOWN, mem_attr.sys_dev);
sys_dev = mem_attr.sys_dev;
return true;
};

ucs_sys_device_t sys_dev0, sys_dev1;
if (!check_domain(0, sys_dev0)) {
UCS_TEST_SKIP_R("failed to allocate locality-domain 0 VMM memory");
}

if (!check_domain(1, sys_dev1)) {
ADD_FAILURE() << "locality domain 1 allocation failed even though "
"domain 0 succeeded";
return;
}

EXPECT_EQ(sys_dev0, sys_dev1);
#else
UCS_TEST_SKIP_R("built without locality-domain support");
#endif
}

_UCT_MD_INSTANTIATE_TEST_CASE(test_cuda_copy_md, cuda_cpy);
97 changes: 88 additions & 9 deletions test/gtest/uct/cuda/test_switch_cuda_device.cc
Original file line number Diff line number Diff line change
Expand Up @@ -646,14 +646,83 @@ UCS_TEST_P(test_mem_alloc_device, no_current_context_vmm_mem_registrable,
query_registrable_no_current_context(buffer.ptr(), size);
}

UCS_TEST_P(test_mem_alloc_device, vmm_multi_handle_range,
"CUDA_COPY_REG_WHOLE_ALLOC=on")
{
cuda_vmm_mem_buffer buffer(4 * UCS_MBYTE, UCS_MEMORY_TYPE_CUDA, 3);

/* The handle containing the address covers only the first chunk, so the
* requested extent must be kept */
check_mem_query_exact_range(buffer.ptr(), buffer.size());
}

UCS_TEST_P(test_mem_alloc_device, vmm_locality_domain_mem_type)
{
#if HAVE_DECL_CU_MEM_LOCATION_TYPE_DEVICE_LOCALITY_DOMAIN
ASSERT_EQ(cudaSuccess, cudaSetDevice(0));

auto check_domain = [this](unsigned char domain_id,
ucs_sys_device_t &sys_dev) {
cuda_vmm_mem_buffer buffer;
CUresult ret = buffer.alloc(64, 0,
CU_MEM_LOCATION_TYPE_DEVICE_LOCALITY_DOMAIN,
1, domain_id);
if (ret != CUDA_SUCCESS) {
return false;
}

uct_md_mem_attr_t mem_attr;
mem_attr.field_mask = UCT_MD_MEM_ATTR_FIELD_MEM_TYPE |
UCT_MD_MEM_ATTR_FIELD_SYS_DEV;

ASSERT_UCS_OK(uct_md_mem_query(md(), buffer.ptr(), buffer.size(),
&mem_attr));
EXPECT_EQ(UCS_MEMORY_TYPE_CUDA, mem_attr.mem_type);
EXPECT_NE(UCS_SYS_DEVICE_ID_UNKNOWN, mem_attr.sys_dev);
sys_dev = mem_attr.sys_dev;
return true;
};

ucs_sys_device_t sys_dev0, sys_dev1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

pls rebase — master now probes domain 1 first, because domain 0 also succeeds on a single-locality-domain device and this ADD_FAILURE() then fires spuriously.

The vmm_locality_domain_mem_type test is moved here in its pre-rebase form: the base branch now contains a different version of this block (in test_cuda_ipc_md.cc) that probes domain 1 first, with the comment "Domain 1 is present only on a device with several locality domains, so its absence is what tells a single-domain device apart". The version being moved probes domain 0 first, so on a device that supports locality domains but has only one, domain 0 succeeds, domain 1 fails, and ADD_FAILURE() fires spuriously. Since this PR deletes that block from test_cuda_ipc_md.cc, a rebase will conflict there and the stale logic can silently land in the new location.

Suggested change
ucs_sys_device_t sys_dev0, sys_dev1;
ucs_sys_device_t sys_dev0, sys_dev1;
/* Domain 1 is present only on a device with several locality domains, so
* its absence is what tells a single-domain device apart */
if (!check_domain(1, sys_dev1)) {
UCS_TEST_SKIP_R("single locality-domain device");
}
if (!check_domain(0, sys_dev0)) {
ADD_FAILURE() << "locality domain 0 allocation failed even though "
"domain 1 succeeded";
return;
}

if (!check_domain(0, sys_dev0)) {
UCS_TEST_SKIP_R("failed to allocate locality-domain 0 VMM memory");
}

if (!check_domain(1, sys_dev1)) {
ADD_FAILURE() << "locality domain 1 allocation failed even though "
"domain 0 succeeded";
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The moved copy of vmm_locality_domain_mem_type is an older revision than the one it replaces (see test/gtest/uct/cuda/test_cuda_ipc_md.cc:790-805). The current version probes domain 1 first and skips when it's absent (since domain 1 is present only on devices with several locality domains), then only calls ADD_FAILURE() if domain 0 fails. This PR's version inverts the order: it skips only when domain 0 fails and calls ADD_FAILURE() when domain 1 fails. On a GPU with a single locality domain, domain 0 succeeds and domain 1 fails, so the moved test turns a legitimate skip into a hard test failure (gtest failure on any single-locality-domain GPU built with CU_MEM_LOCATION_TYPE_DEVICE_LOCALITY_DOMAIN available).

Suggested fix:

Suggested change
}
ucs_sys_device_t sys_dev0, sys_dev1;
/* Domain 1 is present only on a device with several locality domains, so
* its absence is what tells a single-domain device apart */
if (!check_domain(1, sys_dev1)) {
UCS_TEST_SKIP_R("single locality-domain device");
}
if (!check_domain(0, sys_dev0)) {
ADD_FAILURE() << "locality domain 0 allocation failed even though "
"domain 1 succeeded";
return;
}

Please move the current version of this test (test_cuda_ipc_md.cc:790) instead of the older one.


EXPECT_EQ(sys_dev0, sys_dev1);
#else
UCS_TEST_SKIP_R("built without locality-domain support");
#endif
}

UCS_TEST_P(test_mem_alloc_device, vmm_multi_handle_unaligned_range,
"CUDA_COPY_REG_WHOLE_ALLOC=on")
{
cuda_vmm_mem_buffer buffer(4 * UCS_MBYTE, UCS_MEMORY_TYPE_CUDA, 2);
size_t chunk_size = buffer.size() / 2;
void *address = UCS_PTR_BYTE_OFFSET(buffer.ptr(), chunk_size / 2);
uct_md_mem_attr_t mem_attr;

/* Contained in the first handle: extended to the whole handle */
mem_attr.field_mask = UCT_MD_MEM_ATTR_FIELD_BASE_ADDRESS |
UCT_MD_MEM_ATTR_FIELD_ALLOC_LENGTH;
ASSERT_UCS_OK(uct_md_mem_query(md(), address, chunk_size / 2, &mem_attr));
EXPECT_EQ(buffer.ptr(), mem_attr.base_address);
EXPECT_EQ(chunk_size, mem_attr.alloc_length);

/* Ends in the second handle, while being shorter than a single handle:
* the requested extent must be kept */
check_mem_query_exact_range(address, (chunk_size / 2) + 1);
}

#if CUDA_VERSION >= 12020
/* Host-located VMM is not dmabuf-exportable but is registrable by IB.
*
* TODO: REG_WHOLE_ALLOC=off is needed because of an existing issue with
* base_address being set to 0, causing cache pollution with this type of memory
*/
UCS_TEST_P(test_mem_alloc_device, host_vmm_mem_registrable,
"CUDA_COPY_REG_WHOLE_ALLOC=off")
/* Host-located VMM is not dmabuf-exportable but is registrable by IB */
UCS_TEST_P(test_mem_alloc_device, host_vmm_mem_registrable)
{
constexpr size_t size = 4 * UCS_MBYTE;
cuda_host_vmm_mem_buffer buffer(size, UCS_MEMORY_TYPE_CUDA);
Expand All @@ -663,14 +732,24 @@ UCS_TEST_P(test_mem_alloc_device, host_vmm_mem_registrable,

/* Host-located VMM is typed as CUDA memory, but cannot be allocated as
* GPUDirect RDMA capable */
UCS_TEST_P(test_mem_alloc_device, host_vmm_mem_not_gdr_capable,
"CUDA_COPY_REG_WHOLE_ALLOC=off")
UCS_TEST_P(test_mem_alloc_device, host_vmm_mem_not_gdr_capable)
Comment thread
d4l3k marked this conversation as resolved.
Resolved
{
constexpr size_t size = 4 * UCS_MBYTE;
cuda_host_vmm_mem_buffer buffer(size, UCS_MEMORY_TYPE_CUDA);

query_registrable_gdr_capable(buffer.ptr(), size, 1, 0);
}

/* cuMemGetAddressRange() reports a zero base address for host-located VMM, so
* the registration range must not be extended to it */
UCS_TEST_P(test_mem_alloc_device, host_vmm_mem_range,
Comment thread
tvegas1 marked this conversation as resolved.
Resolved
"CUDA_COPY_REG_WHOLE_ALLOC=on")
{
constexpr size_t size = 4 * UCS_MBYTE;
cuda_host_vmm_mem_buffer buffer(size, UCS_MEMORY_TYPE_CUDA);

check_mem_query_exact_range(buffer.ptr(), buffer.size());
}
#endif

UCS_TEST_P(test_mem_alloc_device, legacy_cuda_no_memtype_copy_inter_node)
Expand Down
12 changes: 12 additions & 0 deletions test/gtest/uct/test_md.cc
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,18 @@ void test_md::check_memory(void *address, void *expect, size_t size,
EXPECT_TRUE(mem_buffer::compare(expect, address, size, mem_type));
}

void test_md::check_mem_query_exact_range(void *address, size_t length)
{
uct_md_mem_attr_t mem_attr;

mem_attr.field_mask = UCT_MD_MEM_ATTR_FIELD_BASE_ADDRESS |
UCT_MD_MEM_ATTR_FIELD_ALLOC_LENGTH;
ASSERT_UCS_OK(uct_md_mem_query(md(), address, length, &mem_attr));

EXPECT_EQ(address, mem_attr.base_address);
EXPECT_EQ(length, mem_attr.alloc_length);
}

void test_md::free_memory(void *address, ucs_memory_type_t mem_type)
{
mem_buffer::release(address, mem_type);
Expand Down
1 change: 1 addition & 0 deletions test/gtest/uct/test_md.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class test_md : public testing::TestWithParam<test_md_param>,
ucs_memory_type_t mem_type);
void check_memory(void *address, void *expect, size_t size,
ucs_memory_type_t mem_type);
void check_mem_query_exact_range(void *address, size_t length);
void free_memory(void *address, ucs_memory_type_t mem_type);
static bool is_device_detected(ucs_memory_type_t mem_type);
static void* alloc_thread(void *arg);
Expand Down