Files
TheRock/rocm-systems/projects/hip-tests/catch/unit/memory/hipMemcpyBatchAsync.cc
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

950 lines
37 KiB
C++

/*
* Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
*
* SPDX-License-Identifier: MIT
*/
#include <algorithm>
#include <array>
#include <cstddef>
#include <utility>
#include <vector>
#include <hip_test_common.hh>
#include <hip_test_defgroups.hh>
#include <resource_guards.hh>
#include <utils.hh>
/**
* @addtogroup hipMemcpyBatchAsync hipMemcpyBatchAsync
* @{
* @ingroup MemoryTest
* `hipError_t hipMemcpyBatchAsync(void** dsts, void** srcs, size_t* sizes,
* size_t count, hipMemcpyAttributes* attrs, size_t* attrsIdxs, size_t numAttrs,
* size_t* failIdx, hipStream_t stream __dparm(0))`
*
* Perform a batch of 1D copies.
*/
namespace {
constexpr size_t kOneKiB = 1024;
constexpr size_t kSmallCopySize = 4 * kOneKiB;
constexpr size_t kMediumCopySize = 32 * kOneKiB;
constexpr size_t kLargeCopySize = 512 * kOneKiB;
constexpr int kPatternValue = 0x42;
struct BatchConfig {
size_t copy_count;
size_t copy_size;
};
enum class PointerPattern {
kBasePointers,
kOffsetPointers,
kUnalignedPointers,
kBroadcastSource,
};
std::vector<std::pair<int, int>> GetPeerAccessibleDevicePairs() {
if (HipTest::getDeviceCount() < 2) {
HIP_SKIP_TEST(HipTest::SkipReason::kFewerThanTwoGpus);
}
const int device_count = HipTest::getDeviceCount();
std::vector<std::pair<int, int>> peer_pairs;
for (int src_device = 0; src_device < device_count; ++src_device) {
for (int dst_device = 0; dst_device < device_count; ++dst_device) {
if (src_device == dst_device) {
continue;
}
int can_access_peer = 0;
HIP_CHECK(hipDeviceCanAccessPeer(&can_access_peer, src_device, dst_device));
if (can_access_peer != 0) {
peer_pairs.emplace_back(src_device, dst_device);
}
}
}
if (peer_pairs.empty()) {
HIP_SKIP_TEST(HipTest::SkipReason::kPeerAccessUnavailable);
}
return peer_pairs;
}
void EnablePeerAccess(const std::vector<std::pair<int, int>>& peer_pairs) {
for (const auto& [src_device, dst_device] : peer_pairs) {
HIP_CHECK(hipSetDevice(src_device));
hipError_t peer_status = hipDeviceEnablePeerAccess(dst_device, 0);
if (peer_status != hipSuccess && peer_status != hipErrorPeerAccessAlreadyEnabled) {
HIP_CHECK(peer_status);
}
}
}
void DisablePeerAccess(const std::vector<std::pair<int, int>>& peer_pairs) {
for (const auto& [src_device, dst_device] : peer_pairs) {
HIP_CHECK(hipSetDevice(src_device));
HIP_CHECK(hipDeviceDisablePeerAccess(dst_device));
}
}
std::vector<LinearAllocGuard<int>> AllocateBatchBuffers(LinearAllocs allocation_type,
const BatchConfig& config,
size_t extra_bytes = 0) {
std::vector<LinearAllocGuard<int>> allocations;
for (size_t i = 0; i < config.copy_count; ++i) {
allocations.emplace_back(allocation_type, config.copy_size + extra_bytes);
}
return allocations;
}
std::vector<void*> MakeBatchPtrs(std::vector<LinearAllocGuard<int>>& allocations,
size_t offset_bytes = 0) {
std::vector<void*> ptrs;
for (LinearAllocGuard<int>& allocation : allocations) {
ptrs.push_back(static_cast<void*>(reinterpret_cast<char*>(allocation.ptr()) + offset_bytes));
}
return ptrs;
}
void FillDeviceBuffers(const std::vector<void*>& ptrs, size_t copy_size, int value) {
const size_t copy_elements = copy_size / sizeof(int);
std::vector<int> source(copy_elements);
for (size_t i = 0; i < ptrs.size(); ++i) {
std::fill(source.begin(), source.end(), value + static_cast<int>(i));
HIP_CHECK(hipMemcpy(ptrs[i], source.data(), copy_size, hipMemcpyHostToDevice));
}
}
void FillHostBuffers(std::vector<LinearAllocGuard<int>>& buffers, size_t copy_size,
int value = kPatternValue) {
const size_t copy_elements = copy_size / sizeof(int);
for (size_t i = 0; i < buffers.size(); ++i) {
std::fill_n(buffers[i].host_ptr(), copy_elements, value + static_cast<int>(i));
}
}
void VerifyArrayFromBothEnds(const int* values, size_t copy_elements, int expected,
size_t copy_index) {
for (size_t offset = 0; offset < (copy_elements + 1) / 2; ++offset) {
const size_t front_index = offset;
const size_t back_index = copy_elements - 1 - offset;
INFO("Array failure at copy index " << copy_index << ", element " << front_index);
REQUIRE(values[front_index] == expected);
if (front_index == back_index) {
continue;
}
INFO("Array failure at copy index " << copy_index << ", element " << back_index);
REQUIRE(values[back_index] == expected);
}
}
void VerifyDeviceBuffers(const std::vector<void*>& ptrs, size_t copy_size,
int expected = kPatternValue, bool add_index = true) {
const size_t copy_elements = copy_size / sizeof(int);
std::vector<int> result(copy_elements);
for (size_t i = 0; i < ptrs.size(); ++i) {
HIP_CHECK(hipMemcpy(result.data(), ptrs[i], copy_size, hipMemcpyDeviceToHost));
const int value = expected + (add_index ? static_cast<int>(i) : 0);
VerifyArrayFromBothEnds(result.data(), copy_elements, value, i);
}
}
void VerifyHostBuffers(std::vector<LinearAllocGuard<int>>& buffers, size_t copy_size,
int expected = kPatternValue) {
const size_t copy_elements = copy_size / sizeof(int);
for (size_t i = 0; i < buffers.size(); ++i) {
VerifyArrayFromBothEnds(buffers[i].host_ptr(), copy_elements, expected + static_cast<int>(i),
i);
}
}
} // namespace
/**
* Test Description
* ------------------------
* - Verifies API-level negative validation for hipMemcpyBatchAsync.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_Negative) {
constexpr size_t kCount = 3;
StreamGuard stream_guard(Streams::created);
std::array<LinearAllocGuard<int>, kCount> src_allocs;
std::array<LinearAllocGuard<int>, kCount> dst_allocs;
std::array<void*, kCount> src_ptrs{};
std::array<void*, kCount> dst_ptrs{};
std::array<size_t, kCount> sizes{};
for (size_t i = 0; i < kCount; ++i) {
src_allocs[i] = LinearAllocGuard<int>(LinearAllocs::hipMalloc, kSmallCopySize);
dst_allocs[i] = LinearAllocGuard<int>(LinearAllocs::hipMalloc, kSmallCopySize);
src_ptrs[i] = src_allocs[i].ptr();
dst_ptrs[i] = dst_allocs[i].ptr();
sizes[i] = kSmallCopySize;
}
size_t attrs_idxs[1] = {0};
SECTION("Null destination array") {
HIP_CHECK_ERROR(hipMemcpyBatchAsync(nullptr, src_ptrs.data(), sizes.data(), kCount, nullptr,
attrs_idxs, 0, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("Null source array") {
HIP_CHECK_ERROR(hipMemcpyBatchAsync(dst_ptrs.data(), nullptr, sizes.data(), kCount, nullptr,
attrs_idxs, 0, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("Null sizes array") {
HIP_CHECK_ERROR(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), nullptr, kCount, nullptr,
attrs_idxs, 0, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("Zero count") {
HIP_CHECK_ERROR(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), 0, nullptr,
attrs_idxs, 0, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("Null destination element") {
dst_ptrs[1] = nullptr;
HIP_CHECK_ERROR(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount,
nullptr, attrs_idxs, 0, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("Null source element") {
src_ptrs[1] = nullptr;
HIP_CHECK_ERROR(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount,
nullptr, attrs_idxs, 0, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("Zero size first copy") {
sizes[0] = 0;
size_t fail_idx = 0;
HIP_CHECK_ERROR(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount,
nullptr, attrs_idxs, 0, &fail_idx, stream_guard.stream()),
hipErrorInvalidValue);
REQUIRE(fail_idx == 0);
}
SECTION("Zero size middle copy") {
sizes[1] = 0;
size_t fail_idx = 0;
HIP_CHECK_ERROR(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount,
nullptr, attrs_idxs, 0, &fail_idx, stream_guard.stream()),
hipErrorInvalidValue);
REQUIRE(fail_idx == 1);
}
SECTION("Zero size last copy") {
sizes[2] = 0;
size_t fail_idx = 0;
HIP_CHECK_ERROR(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount,
nullptr, attrs_idxs, 0, &fail_idx, stream_guard.stream()),
hipErrorInvalidValue);
REQUIRE(fail_idx == 2);
}
SECTION("Null fail index on zero size") {
sizes[1] = 0;
HIP_CHECK_ERROR(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount,
nullptr, attrs_idxs, 0, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("Source range exceeds allocation") {
src_ptrs[1] = static_cast<void*>(src_allocs[1].ptr() + 1);
sizes[1] = kSmallCopySize;
HIP_CHECK_ERROR(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount,
nullptr, attrs_idxs, 0, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("Destination range exceeds allocation") {
dst_ptrs[1] = static_cast<void*>(dst_allocs[1].ptr() + 1);
sizes[1] = kSmallCopySize;
HIP_CHECK_ERROR(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount,
nullptr, attrs_idxs, 0, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
}
/**
* Test Description
* ------------------------
* - Verifies attribute array validation for hipMemcpyBatchAsync.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_Attrs_Negative) {
constexpr size_t kCount = 2;
BatchConfig config{kCount, kSmallCopySize};
StreamGuard stream_guard(Streams::created);
std::vector<LinearAllocGuard<int>> src = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<LinearAllocGuard<int>> dst = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<void*> src_ptrs = MakeBatchPtrs(src);
std::vector<void*> dst_ptrs = MakeBatchPtrs(dst);
std::vector<size_t> sizes(config.copy_count, config.copy_size);
std::array<hipMemcpyAttributes, 3> attrs{
hipMemcpyAttributes{hipMemcpySrcAccessOrderStream, {}, {}, hipMemcpyFlagDefault},
hipMemcpyAttributes{hipMemcpySrcAccessOrderStream, {}, {}, hipMemcpyFlagDefault},
hipMemcpyAttributes{hipMemcpySrcAccessOrderStream, {}, {}, hipMemcpyFlagDefault},
};
std::array<size_t, 3> attrs_idxs{0, 1, 2};
SECTION("Null attrs with nonzero numAttrs") {
HIP_CHECK_ERROR(
hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount, nullptr,
attrs_idxs.data(), 1, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("Null attrsIdxs with nonzero numAttrs") {
HIP_CHECK_ERROR(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount,
attrs.data(), nullptr, 1, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("First attrsIdxs entry is not zero") {
attrs_idxs[0] = 1;
HIP_CHECK_ERROR(
hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount, attrs.data(),
attrs_idxs.data(), 1, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("numAttrs exceeds count") {
HIP_CHECK_ERROR(
hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount, attrs.data(),
attrs_idxs.data(), kCount + 1, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("attrsIdxs is not monotonic") {
attrs_idxs[1] = 0;
HIP_CHECK_ERROR(
hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount, attrs.data(),
attrs_idxs.data(), 2, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("attrsIdxs entry is out of range") {
attrs_idxs[1] = kCount;
HIP_CHECK_ERROR(
hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount, attrs.data(),
attrs_idxs.data(), 2, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
SECTION("Invalid source access order") {
attrs[0].srcAccessOrder = hipMemcpySrcAccessOrderInvalid;
HIP_CHECK_ERROR(
hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount, attrs.data(),
attrs_idxs.data(), 1, nullptr, stream_guard.stream()),
hipErrorInvalidValue);
}
}
#if HT_AMD
/**
* Test Description
* ------------------------
* - Verifies D2D batch copies with hipMemcpyFlagExtOpSwap.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_D2D_Swap) {
constexpr size_t copy_count = 3;
constexpr size_t copy_size = kSmallCopySize;
constexpr int kSwapSrcValue = 23;
constexpr int kSwapDstValue = 47;
BatchConfig config{copy_count, copy_size};
StreamGuard stream_guard(Streams::created);
std::vector<LinearAllocGuard<int>> src = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<LinearAllocGuard<int>> dst = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<void*> src_ptrs = MakeBatchPtrs(src);
std::vector<void*> dst_ptrs = MakeBatchPtrs(dst);
std::vector<size_t> sizes(src_ptrs.size(), copy_size);
hipMemcpyAttributes attr{hipMemcpySrcAccessOrderStream, {}, {}, hipMemcpyFlagExtOpSwap};
size_t attrs_idxs[1] = {0};
FillDeviceBuffers(src_ptrs, copy_size, kSwapSrcValue);
FillDeviceBuffers(dst_ptrs, copy_size, kSwapDstValue);
hipError_t status =
hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), src_ptrs.size(), &attr,
attrs_idxs, 1, nullptr, stream_guard.stream());
if (status == hipErrorNotSupported) {
SUCCEED("hipMemcpyFlagExtOpSwap is not supported on this device");
} else {
HIP_CHECK(status);
HIP_CHECK(hipStreamSynchronize(stream_guard.stream()));
VerifyDeviceBuffers(src_ptrs, copy_size, kSwapDstValue);
VerifyDeviceBuffers(dst_ptrs, copy_size, kSwapSrcValue);
}
}
#endif
/**
* Test Description
* ------------------------
* - Verifies D2D batch copies across generated copy sizes, counts, pointer
* patterns, and copy flags.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_D2D_Functional) {
const size_t copy_count = GENERATE(1, 8);
const size_t copy_size = GENERATE(kSmallCopySize, kMediumCopySize, kLargeCopySize);
const PointerPattern pointer_pattern =
GENERATE(PointerPattern::kBasePointers, PointerPattern::kOffsetPointers,
PointerPattern::kUnalignedPointers, PointerPattern::kBroadcastSource);
#if HT_AMD
const hipMemcpyFlags flag = GENERATE(hipMemcpyFlagDefault, hipMemcpyFlagExtPreferCE);
#else
const hipMemcpyFlags flag = hipMemcpyFlagDefault;
#endif
const size_t offset_bytes = pointer_pattern == PointerPattern::kOffsetPointers ? sizeof(int)
: pointer_pattern == PointerPattern::kUnalignedPointers ? 1
: 0;
BatchConfig config{copy_count, copy_size};
StreamGuard stream_guard(Streams::created);
std::vector<LinearAllocGuard<int>> src =
AllocateBatchBuffers(LinearAllocs::hipMalloc, config, offset_bytes);
std::vector<LinearAllocGuard<int>> dst =
AllocateBatchBuffers(LinearAllocs::hipMalloc, config, offset_bytes);
std::vector<void*> src_ptrs = MakeBatchPtrs(src, offset_bytes);
std::vector<void*> dst_ptrs = MakeBatchPtrs(dst, offset_bytes);
std::vector<size_t> sizes(config.copy_count, config.copy_size);
size_t attrs_idxs[1] = {0};
hipMemcpyAttributes attr{
hipMemcpySrcAccessOrderAny, {}, {}, static_cast<unsigned int>(flag)};
if (pointer_pattern == PointerPattern::kBroadcastSource) {
FillDeviceBuffers(src_ptrs, copy_size, kPatternValue);
void* broadcast_src = src_ptrs.front();
std::fill(src_ptrs.begin(), src_ptrs.end(), broadcast_src);
} else {
FillDeviceBuffers(src_ptrs, copy_size, kPatternValue);
}
HIP_CHECK(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), copy_count, &attr,
attrs_idxs, 1, nullptr, stream_guard.stream()));
HIP_CHECK(hipStreamSynchronize(stream_guard.stream()));
if (pointer_pattern == PointerPattern::kBroadcastSource) {
VerifyDeviceBuffers(dst_ptrs, copy_size, kPatternValue, false);
} else {
VerifyDeviceBuffers(dst_ptrs, copy_size);
}
}
/**
* Test Description
* ------------------------
* - Verifies H2D batch copies across generated host source allocation types,
* copy counts, and copy sizes.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_H2D_Functional) {
const LinearAllocs host_alloc_type = GENERATE(LinearAllocs::malloc, LinearAllocs::hipHostMalloc);
const size_t copy_count = GENERATE(1, 8);
const size_t copy_size = GENERATE(kSmallCopySize, kMediumCopySize, kLargeCopySize);
BatchConfig config{copy_count, copy_size};
StreamGuard stream_guard(Streams::created);
std::vector<LinearAllocGuard<int>> src = AllocateBatchBuffers(host_alloc_type, config);
std::vector<LinearAllocGuard<int>> dst = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<void*> src_ptrs = MakeBatchPtrs(src);
std::vector<void*> dst_ptrs = MakeBatchPtrs(dst);
std::vector<size_t> sizes(config.copy_count, config.copy_size);
FillHostBuffers(src, copy_size);
HIP_CHECK(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), copy_count, nullptr,
nullptr, 0, nullptr, stream_guard.stream()));
HIP_CHECK(hipStreamSynchronize(stream_guard.stream()));
VerifyDeviceBuffers(dst_ptrs, copy_size);
}
/**
* Test Description
* ------------------------
* - Verifies that pageable H2D source access is complete before
* hipMemcpyBatchAsync returns when srcAccessOrder is DuringApiCall.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_H2D_Pageable_DuringApiCall_SourceAccess) {
constexpr size_t copy_count = 8;
constexpr size_t copy_size = kLargeCopySize;
constexpr int kOriginalValue = 17;
constexpr int kAlteredValue = 23;
BatchConfig config{copy_count, copy_size};
StreamGuard stream_guard(Streams::created);
std::vector<LinearAllocGuard<int>> src = AllocateBatchBuffers(LinearAllocs::malloc, config);
std::vector<LinearAllocGuard<int>> dst = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<void*> src_ptrs = MakeBatchPtrs(src);
std::vector<void*> dst_ptrs = MakeBatchPtrs(dst);
std::vector<size_t> sizes(config.copy_count, config.copy_size);
size_t attrs_idxs[1] = {0};
hipMemcpyAttributes attr{hipMemcpySrcAccessOrderDuringApiCall, {}, {}, hipMemcpyFlagDefault};
FillHostBuffers(src, copy_size, kOriginalValue);
HIP_CHECK(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), copy_count, &attr,
attrs_idxs, 1, nullptr, stream_guard.stream()));
FillHostBuffers(src, copy_size, kAlteredValue);
HIP_CHECK(hipStreamSynchronize(stream_guard.stream()));
VerifyDeviceBuffers(dst_ptrs, copy_size, kOriginalValue);
VerifyHostBuffers(src, copy_size, kAlteredValue);
}
/**
* Test Description
* ------------------------
* - Verifies that pageable H2D source access observes previous same-stream
* writes to the source when srcAccessOrder is Stream.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_H2D_Pageable_Stream_SourceAccess) {
constexpr size_t copy_count = 1;
size_t copy_size = GENERATE(kSmallCopySize, kLargeCopySize);
constexpr int kStreamProducedValue = 47;
BatchConfig config{copy_count, copy_size};
StreamGuard stream_guard(Streams::created);
std::vector<LinearAllocGuard<int>> producer =
AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<LinearAllocGuard<int>> src = AllocateBatchBuffers(LinearAllocs::malloc, config);
std::vector<LinearAllocGuard<int>> dst = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<void*> src_ptrs = MakeBatchPtrs(src);
std::vector<void*> dst_ptrs = MakeBatchPtrs(dst);
std::vector<size_t> sizes(config.copy_count, config.copy_size);
size_t attrs_idxs[1] = {0};
hipMemcpyAttributes attr{hipMemcpySrcAccessOrderStream, {}, {}, hipMemcpyFlagDefault};
FillDeviceBuffers(MakeBatchPtrs(producer), copy_size, kStreamProducedValue);
for (size_t i = 0; i < copy_count; ++i) {
HIP_CHECK(hipMemcpyAsync(src_ptrs[i], producer[i].ptr(), copy_size, hipMemcpyDeviceToHost,
stream_guard.stream()));
}
HIP_CHECK(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), copy_count, &attr,
attrs_idxs, 1, nullptr, stream_guard.stream()));
HIP_CHECK(hipStreamSynchronize(stream_guard.stream()));
VerifyDeviceBuffers(dst_ptrs, copy_size, kStreamProducedValue);
}
/**
* Test Description
* ------------------------
* - Verifies D2H batch copies across generated host destination allocation
* types, copy counts, and copy sizes.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_D2H_Functional) {
const LinearAllocs host_alloc_type = GENERATE(LinearAllocs::malloc, LinearAllocs::hipHostMalloc);
const size_t copy_count = GENERATE(1, 8);
const size_t copy_size = GENERATE(kSmallCopySize, kMediumCopySize, kLargeCopySize);
BatchConfig config{copy_count, copy_size};
StreamGuard stream_guard(Streams::created);
std::vector<LinearAllocGuard<int>> src = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<LinearAllocGuard<int>> dst = AllocateBatchBuffers(host_alloc_type, config);
std::vector<void*> src_ptrs = MakeBatchPtrs(src);
std::vector<void*> dst_ptrs = MakeBatchPtrs(dst);
std::vector<size_t> sizes(config.copy_count, config.copy_size);
FillDeviceBuffers(src_ptrs, copy_size, kPatternValue);
HIP_CHECK(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), copy_count, nullptr,
nullptr, 0, nullptr, stream_guard.stream()));
HIP_CHECK(hipStreamSynchronize(stream_guard.stream()));
VerifyHostBuffers(dst, copy_size);
}
/**
* Test Description
* ------------------------
* - Verifies H2H batch copies across generated host allocation types, copy
* counts, and copy sizes.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_H2H_Functional) {
const LinearAllocs host_alloc_type = GENERATE(LinearAllocs::malloc, LinearAllocs::hipHostMalloc);
const size_t copy_count = GENERATE(1, 8);
const size_t copy_size = kSmallCopySize;
BatchConfig config{copy_count, copy_size};
StreamGuard stream_guard(Streams::created);
std::vector<LinearAllocGuard<int>> src = AllocateBatchBuffers(host_alloc_type, config);
std::vector<LinearAllocGuard<int>> dst = AllocateBatchBuffers(host_alloc_type, config);
std::vector<void*> src_ptrs = MakeBatchPtrs(src);
std::vector<void*> dst_ptrs = MakeBatchPtrs(dst);
std::vector<size_t> sizes(config.copy_count, config.copy_size);
FillHostBuffers(src, copy_size);
HIP_CHECK(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), copy_count, nullptr,
nullptr, 0, nullptr, stream_guard.stream()));
HIP_CHECK(hipStreamSynchronize(stream_guard.stream()));
VerifyHostBuffers(dst, copy_size);
}
/**
* Test Description
* ------------------------
* - Verifies one batch containing H2D, D2D, D2H, and H2H copies.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_Mixed_Functional) {
constexpr size_t copy_size = kMediumCopySize;
const size_t copy_elements = copy_size / sizeof(int);
if (HipTest::getDeviceCount() < 3) {
HIP_SKIP_TEST("Test requires at least three GPUs.");
}
HIP_CHECK(hipSetDevice(0));
StreamGuard stream_guard(Streams::created);
LinearAllocGuard<int> h2d_src(LinearAllocs::malloc, copy_size);
LinearAllocGuard<int> h2d_dst(LinearAllocs::hipMalloc, copy_size);
HIP_CHECK(hipSetDevice(1));
LinearAllocGuard<int> d2d_src(LinearAllocs::hipMalloc, copy_size);
LinearAllocGuard<int> d2d_dst(LinearAllocs::hipMalloc, copy_size);
HIP_CHECK(hipSetDevice(2));
LinearAllocGuard<int> d2h_src(LinearAllocs::hipMalloc, copy_size);
LinearAllocGuard<int> d2h_dst(LinearAllocs::malloc, copy_size);
LinearAllocGuard<int> h2h_src(LinearAllocs::malloc, copy_size);
LinearAllocGuard<int> h2h_dst(LinearAllocs::malloc, copy_size);
HIP_CHECK(hipSetDevice(0));
std::array<void*, 4> src_ptrs{h2d_src.ptr(), d2d_src.ptr(), d2h_src.ptr(), h2h_src.ptr()};
std::array<void*, 4> dst_ptrs{h2d_dst.ptr(), d2d_dst.ptr(), d2h_dst.ptr(), h2h_dst.ptr()};
std::array<size_t, 4> sizes{copy_size, copy_size, copy_size, copy_size};
std::fill_n(h2d_src.host_ptr(), copy_elements, kPatternValue);
std::fill_n(h2h_src.host_ptr(), copy_elements, kPatternValue + 3);
std::vector<int> source(copy_elements);
std::fill(source.begin(), source.end(), kPatternValue + 1);
HIP_CHECK(hipMemcpy(d2d_src.ptr(), source.data(), copy_size, hipMemcpyHostToDevice));
std::fill(source.begin(), source.end(), kPatternValue + 2);
HIP_CHECK(hipMemcpy(d2h_src.ptr(), source.data(), copy_size, hipMemcpyHostToDevice));
HIP_CHECK(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), sizes.size(),
nullptr, nullptr, 0, nullptr, stream_guard.stream()));
HIP_CHECK(hipStreamSynchronize(stream_guard.stream()));
std::vector<int> result(copy_elements);
HIP_CHECK(hipMemcpy(result.data(), h2d_dst.ptr(), copy_size, hipMemcpyDeviceToHost));
VerifyArrayFromBothEnds(result.data(), copy_elements, kPatternValue, 0);
HIP_CHECK(hipMemcpy(result.data(), d2d_dst.ptr(), copy_size, hipMemcpyDeviceToHost));
VerifyArrayFromBothEnds(result.data(), copy_elements, kPatternValue + 1, 1);
VerifyArrayFromBothEnds(d2h_dst.host_ptr(), copy_elements, kPatternValue + 2, 2);
VerifyArrayFromBothEnds(h2h_dst.host_ptr(), copy_elements, kPatternValue + 3, 3);
}
/**
* Test Description
* ------------------------
* - Verifies default stream behavior, same-stream ordering, and event
* dependency ordering for hipMemcpyBatchAsync.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_Stream) {
constexpr size_t kCount = 2;
constexpr size_t kCopyElements = kSmallCopySize / sizeof(int);
BatchConfig config{kCount, kSmallCopySize};
std::vector<size_t> sizes(config.copy_count, config.copy_size);
SECTION("Default stream") {
std::vector<LinearAllocGuard<int>> src = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<LinearAllocGuard<int>> dst = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<void*> src_ptrs = MakeBatchPtrs(src);
std::vector<void*> dst_ptrs = MakeBatchPtrs(dst);
FillDeviceBuffers(src_ptrs, kSmallCopySize, kPatternValue);
HIP_CHECK(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount, nullptr,
nullptr, 0, nullptr, nullptr));
HIP_CHECK(hipStreamSynchronize(nullptr));
VerifyDeviceBuffers(dst_ptrs, kSmallCopySize);
}
SECTION("Ordering after prior kernel on same stream") {
StreamGuard stream_guard(Streams::created);
std::vector<LinearAllocGuard<int>> src = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<LinearAllocGuard<int>> dst = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<void*> src_ptrs = MakeBatchPtrs(src);
std::vector<void*> dst_ptrs = MakeBatchPtrs(dst);
static_cast<void>(hipGetLastError());
for (size_t i = 0; i < kCount; ++i) {
VectorSet<<<1, 256, 0, stream_guard.stream()>>>(
static_cast<int*>(src_ptrs[i]), kPatternValue + static_cast<int>(i), kCopyElements);
}
HIP_CHECK(hipGetLastError());
HIP_CHECK(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount, nullptr,
nullptr, 0, nullptr, stream_guard.stream()));
HIP_CHECK(hipStreamSynchronize(stream_guard.stream()));
VerifyDeviceBuffers(dst_ptrs, kSmallCopySize);
}
SECTION("Ordering via event dependency") {
StreamGuard producer_stream(Streams::created);
StreamGuard copy_stream(Streams::created);
EventsGuard events(1);
std::vector<LinearAllocGuard<int>> src = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<LinearAllocGuard<int>> dst = AllocateBatchBuffers(LinearAllocs::hipMalloc, config);
std::vector<void*> src_ptrs = MakeBatchPtrs(src);
std::vector<void*> dst_ptrs = MakeBatchPtrs(dst);
static_cast<void>(hipGetLastError());
for (size_t i = 0; i < kCount; ++i) {
VectorSet<<<1, 256, 0, producer_stream.stream()>>>(
static_cast<int*>(src_ptrs[i]), kPatternValue + static_cast<int>(i), kCopyElements);
}
HIP_CHECK(hipGetLastError());
HIP_CHECK(hipEventRecord(events[0], producer_stream.stream()));
HIP_CHECK(hipStreamWaitEvent(copy_stream.stream(), events[0], 0));
HIP_CHECK(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), kCount, nullptr,
nullptr, 0, nullptr, copy_stream.stream()));
HIP_CHECK(hipStreamSynchronize(copy_stream.stream()));
VerifyDeviceBuffers(dst_ptrs, kSmallCopySize);
}
}
/**
* Test Description
* ------------------------
* - Verifies peer-to-peer batches across peer-accessible device pairs.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_P2P_Functional) {
const std::vector<std::pair<int, int>> peer_pairs = GetPeerAccessibleDevicePairs();
const int stream_device = peer_pairs.front().first;
#if HT_AMD
const hipMemcpyFlags flag = GENERATE(hipMemcpyFlagDefault, hipMemcpyFlagExtPreferCE);
#else
const hipMemcpyFlags flag = hipMemcpyFlagDefault;
#endif
constexpr size_t copy_size = kSmallCopySize;
constexpr size_t batch_count = 2;
const size_t total_copy_count = peer_pairs.size() * batch_count;
std::vector<LinearAllocGuard<int>> src_allocations;
std::vector<LinearAllocGuard<int>> dst_allocations;
std::vector<void*> src_ptrs;
std::vector<void*> dst_ptrs;
std::vector<size_t> sizes(total_copy_count, copy_size);
hipMemcpyAttributes attr{
hipMemcpySrcAccessOrderStream, {}, {}, static_cast<unsigned int>(flag)};
size_t attrs_idxs[1] = {0};
EnablePeerAccess(peer_pairs);
for (const auto& [src_device, dst_device] : peer_pairs) {
for (size_t i = 0; i < batch_count; ++i) {
HIP_CHECK(hipSetDevice(src_device));
src_allocations.emplace_back(LinearAllocs::hipMalloc, copy_size);
src_ptrs.push_back(src_allocations.back().ptr());
HIP_CHECK(hipSetDevice(dst_device));
dst_allocations.emplace_back(LinearAllocs::hipMalloc, copy_size);
dst_ptrs.push_back(dst_allocations.back().ptr());
}
}
HIP_CHECK(hipSetDevice(stream_device));
StreamGuard stream_guard(Streams::created);
FillDeviceBuffers(src_ptrs, copy_size, kPatternValue);
HIP_CHECK(hipSetDevice(stream_device));
HIP_CHECK(hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), sizes.size(), &attr,
attrs_idxs, 1, nullptr, stream_guard.stream()));
HIP_CHECK(hipStreamSynchronize(stream_guard.stream()));
VerifyDeviceBuffers(dst_ptrs, copy_size);
DisablePeerAccess(peer_pairs);
HIP_CHECK(hipSetDevice(stream_device));
}
#if HT_AMD
/**
* Test Description
* ------------------------
* - Verifies H2D hipMemcpyBatchAsync with hipMemcpyFlagExtOpIndirectSrc copies
* from the host buffer referenced by a pinned pointer slot.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_IndirectSrc) {
constexpr size_t copy_size = kSmallCopySize;
StreamGuard stream_guard(Streams::created);
LinearAllocGuard<int> src(LinearAllocs::hipHostMalloc, copy_size);
LinearAllocGuard<int> dst(LinearAllocs::hipMalloc, copy_size);
LinearAllocGuard<char> src_slot(LinearAllocs::hipHostMalloc, sizeof(void*));
const size_t copy_elements = copy_size / sizeof(int);
std::fill_n(src.host_ptr(), copy_elements, kPatternValue);
void* src_ptr = src.ptr();
std::memcpy(src_slot.ptr(), &src_ptr, sizeof(void*));
std::vector<void*> dst_ptrs{dst.ptr()};
std::vector<void*> src_ptrs{src_slot.ptr()};
std::vector<size_t> sizes{copy_size};
hipMemcpyAttributes attr{hipMemcpySrcAccessOrderStream, {}, {}, hipMemcpyFlagExtOpIndirectSrc};
size_t attrs_idx = 0;
hipError_t status = hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), 1, &attr,
&attrs_idx, 1, nullptr, stream_guard.stream());
if (status == hipErrorNotSupported) {
SUCCEED("hipMemcpyFlagExtOpIndirectSrc is not supported on this device");
} else {
HIP_CHECK(status);
HIP_CHECK(hipStreamSynchronize(stream_guard.stream()));
VerifyDeviceBuffers(dst_ptrs, copy_size);
}
}
/**
* Test Description
* ------------------------
* - Verifies D2H hipMemcpyBatchAsync with hipMemcpyFlagExtOpIndirectDst copies
* into the host buffer referenced by a pinned pointer slot.
* Test source
* ------------------------
* - catch/unit/memory/hipMemcpyBatchAsync.cc
* Test requirements
* ------------------------
* - HIP_VERSION >= 7.1
*/
HIP_TEST_CASE(Unit_hipMemcpyBatchAsync_IndirectDst) {
constexpr size_t copy_size = kSmallCopySize;
StreamGuard stream_guard(Streams::created);
LinearAllocGuard<int> src(LinearAllocs::hipMalloc, copy_size);
LinearAllocGuard<int> dst(LinearAllocs::hipHostMalloc, copy_size);
LinearAllocGuard<char> dst_slot(LinearAllocs::hipHostMalloc, sizeof(void*));
const size_t copy_elements = copy_size / sizeof(int);
std::vector<int> host_pattern(copy_elements, kPatternValue);
HIP_CHECK(hipMemcpy(src.ptr(), host_pattern.data(), copy_size, hipMemcpyHostToDevice));
std::fill_n(dst.host_ptr(), copy_elements, 0);
void* dst_ptr = dst.ptr();
std::memcpy(dst_slot.ptr(), &dst_ptr, sizeof(void*));
std::vector<void*> src_ptrs{src.ptr()};
std::vector<void*> dst_ptrs{dst_slot.ptr()};
std::vector<size_t> sizes{copy_size};
hipMemcpyAttributes attr{hipMemcpySrcAccessOrderStream, {}, {}, hipMemcpyFlagExtOpIndirectDst};
size_t attrs_idx = 0;
hipError_t status = hipMemcpyBatchAsync(dst_ptrs.data(), src_ptrs.data(), sizes.data(), 1, &attr,
&attrs_idx, 1, nullptr, stream_guard.stream());
if (status == hipErrorNotSupported) {
SUCCEED("hipMemcpyFlagExtOpIndirectDst is not supported on this device");
} else {
HIP_CHECK(status);
HIP_CHECK(hipStreamSynchronize(stream_guard.stream()));
VerifyArrayFromBothEnds(dst.host_ptr(), copy_elements, kPatternValue, 0);
}
}
#endif
/**
* End doxygen group MemoryTest.
* @}
*/