This patch is the first step towards introducing alignment support in memory allocations using liboffload, in order to enable SYCL implementation of aligned allocations. At the level of device allocators, it does not modify the Level Zero code except for forwarding the alignment parameter, since Level Zero already allows for specifying the alignment in its device allocator implementation. For AMD and CUDA, it checks whether the alignment passed by the caller is supported by the given backend; the reasoning behind this verification is described in the following paragraphs. At the API level, it adds a new function olMemAllocAligned, which is expected to work similarly to olMemAlloc, with the difference that the buffers returned by olMemAllocAligned should be aligned to the alignment passed by the user. At the level of the plugin interface internal abstractions, it adds a new argument Alignment to existing functions and delegates memory allocation between olMemAllocAligned and olMemAlloc implementations by using a common helper function. The goal of the anticipated series of patches is to implement handling of the alignment in the memory manager at the plugin interface level. At the first stage, presented in this patch, the information about the passed alignment is used mainly for checking whether the buffer returned by the device allocators meets the requirements. In the case of the requested memory size exceeding the thresholds of allocations handled by the memory manager, the request is forwarded directly to the device. Otherwise, the memory manager is responsible for allocating memory in full pages and pooling it according to the requested chunks. In the first scenario, the requested size, which is greater than the aforementioned threshold, is usually a multiple of the page size. Therefore, any alignment smaller than the page size would be correct. Neither CUDA nor HSA provides users with the ability to specify the alignment of the allocated memory. Their APIs include the alignment as one of the possible arguments only in functions that reserve virtual address space. CUDA enables users only to check the allocation granularity, which is usually synonymous with the page size. In the case of HSA, if the memory is allocated using a pool, the user is able to check not only the granularity, but also the alignment of the buffers allocated using the given pool. However, these values - granularity and alignment - are defined only if HSA_AMD_MEMORY_POOL_INFO_RUNTIME_ALLOC_ALLOWED is set to true, which should always be set since the current implementation of the memory pool for the AMD plugin in liboffload uses only hsa_amd_memory_pool_ts for allocations. Only Level Zero accepts the alignment parameter during the memory allocation process, but manages returned pointers internally. Since pooling is also implemented in the memory manager at the plugin level, such a design in the Level Zero device allocator duplicates pooling in the whole application. In the final version, the memory manager at the plugin interface level will handle memory pooling and the buffer alignment from different devices instead of delegating memory management to device allocator implementations, as this would simplify the Level Zero plugin design and provide AMD and CUDA plugins with support for the alignment in memory allocations, which is not natively included in their APIs.
212 lines
6.6 KiB
C++
212 lines
6.6 KiB
C++
//===--------------- Offload API tests - olMemAllocAligned ----------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "../common/Fixtures.hpp"
|
|
#include <OffloadAPI.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
using olMemAllocAlignedTest = OffloadDeviceTest;
|
|
|
|
OFFLOAD_TESTS_INSTANTIATE_DEVICE_FIXTURE(olMemAllocAlignedTest);
|
|
|
|
constexpr size_t DefaultAlignment = 16;
|
|
constexpr size_t TestAllocsNum = 1000;
|
|
|
|
TEST_P(olMemAllocAlignedTest, SuccessAllocMany) {
|
|
std::vector<void *> Allocs;
|
|
Allocs.reserve(1000);
|
|
|
|
constexpr ol_alloc_type_t TYPES[3] = {
|
|
OL_ALLOC_TYPE_DEVICE, OL_ALLOC_TYPE_MANAGED, OL_ALLOC_TYPE_HOST};
|
|
|
|
for (size_t I = 1; I < TestAllocsNum; I++) {
|
|
void *Alloc = nullptr;
|
|
ASSERT_SUCCESS(olMemAllocAligned(Device, TYPES[I % 3], 1024 * I,
|
|
DefaultAlignment, &Alloc));
|
|
ASSERT_NE(Alloc, nullptr);
|
|
|
|
Allocs.push_back(Alloc);
|
|
}
|
|
|
|
for (auto *A : Allocs) {
|
|
olMemFree(A);
|
|
}
|
|
}
|
|
|
|
TEST_P(olMemAllocAlignedTest, InvalidNullDevice) {
|
|
void *Alloc = nullptr;
|
|
ASSERT_ERROR(OL_ERRC_INVALID_NULL_HANDLE,
|
|
olMemAllocAligned(nullptr, OL_ALLOC_TYPE_DEVICE, 1024,
|
|
DefaultAlignment, &Alloc));
|
|
}
|
|
|
|
TEST_P(olMemAllocAlignedTest, InvalidNullOutPtr) {
|
|
ASSERT_ERROR(OL_ERRC_INVALID_NULL_POINTER,
|
|
olMemAllocAligned(Device, OL_ALLOC_TYPE_DEVICE, 1024,
|
|
DefaultAlignment, nullptr));
|
|
}
|
|
|
|
TEST_P(olMemAllocAlignedTest, InvalidAlignmentZero) {
|
|
void *Alloc = nullptr;
|
|
|
|
ASSERT_ERROR(
|
|
OL_ERRC_INVALID_ARGUMENT,
|
|
olMemAllocAligned(Device, OL_ALLOC_TYPE_DEVICE, 1024, 0, &Alloc));
|
|
}
|
|
|
|
TEST_P(olMemAllocAlignedTest, InvalidAlignmentNotAPowerOfTwo) {
|
|
void *Alloc = nullptr;
|
|
|
|
ASSERT_ERROR(
|
|
OL_ERRC_INVALID_ARGUMENT,
|
|
olMemAllocAligned(Device, OL_ALLOC_TYPE_DEVICE, 1024, 3, &Alloc));
|
|
}
|
|
|
|
TEST_P(olMemAllocAlignedTest, CudaExceedDefaultAlignment) {
|
|
if (getPlatformBackend() != OL_PLATFORM_BACKEND_CUDA) {
|
|
GTEST_SKIP() << "Test inteded for CUDA backend";
|
|
}
|
|
|
|
void *Alloc = nullptr;
|
|
// The default page size for cuda is 64 KB.
|
|
ASSERT_ERROR(OL_ERRC_UNSUPPORTED,
|
|
olMemAllocAligned(Device, OL_ALLOC_TYPE_DEVICE, 1024,
|
|
1024 * 64 * 64, &Alloc));
|
|
ASSERT_EQ(Alloc, nullptr);
|
|
}
|
|
|
|
TEST_P(olMemAllocAlignedTest, SuccessAllocManagedDifferentAlignments) {
|
|
void *Alloc = nullptr;
|
|
size_t NumAlignments = 6;
|
|
size_t Alignments[] = {8, 16, 32, 64, 128, 256};
|
|
size_t Alignment;
|
|
|
|
for (size_t i = 0; i < NumAlignments; i++) {
|
|
Alignment = Alignments[i];
|
|
SCOPED_TRACE("alignment: " + std::to_string(Alignment));
|
|
ASSERT_SUCCESS(olMemAllocAligned(Device, OL_ALLOC_TYPE_MANAGED, 1024,
|
|
Alignment, &Alloc));
|
|
ASSERT_NE(Alloc, nullptr);
|
|
olMemFree(Alloc);
|
|
}
|
|
}
|
|
|
|
TEST_P(olMemAllocAlignedTest, SuccessAllocHostDifferentAlignments) {
|
|
void *Alloc = nullptr;
|
|
size_t NumAlignments = 6;
|
|
size_t Alignments[] = {8, 16, 32, 64, 128, 256};
|
|
size_t Alignment;
|
|
|
|
for (size_t i = 0; i < NumAlignments; i++) {
|
|
Alignment = Alignments[i];
|
|
SCOPED_TRACE("alignment: " + std::to_string(Alignment));
|
|
ASSERT_SUCCESS(
|
|
olMemAllocAligned(Device, OL_ALLOC_TYPE_HOST, 1024, Alignment, &Alloc));
|
|
ASSERT_NE(Alloc, nullptr);
|
|
olMemFree(Alloc);
|
|
}
|
|
}
|
|
|
|
TEST_P(olMemAllocAlignedTest, SuccessAllocDeviceDifferentAlignments) {
|
|
void *Alloc = nullptr;
|
|
size_t NumAlignments = 6;
|
|
size_t Alignments[] = {8, 16, 32, 64, 128, 256};
|
|
size_t Alignment;
|
|
|
|
for (size_t i = 0; i < NumAlignments; i++) {
|
|
Alignment = Alignments[i];
|
|
SCOPED_TRACE("alignment: " + std::to_string(Alignment));
|
|
ASSERT_SUCCESS(olMemAllocAligned(Device, OL_ALLOC_TYPE_DEVICE, 1024,
|
|
Alignment, &Alloc));
|
|
ASSERT_NE(Alloc, nullptr);
|
|
|
|
olMemFree(Alloc);
|
|
}
|
|
}
|
|
|
|
TEST_P(olMemAllocAlignedTest, SuccessMemcpyManagedDiferentAlignments) {
|
|
constexpr size_t Size = 1024;
|
|
void *Alloc;
|
|
std::vector<uint8_t> Input(Size, 42);
|
|
std::vector<uint8_t> Output(Size, 0);
|
|
|
|
size_t NumAlignments = 6;
|
|
size_t Alignments[] = {8, 16, 32, 64, 128, 256};
|
|
size_t Alignment;
|
|
for (size_t i = 0; i < NumAlignments; i++) {
|
|
Alignment = Alignments[i];
|
|
SCOPED_TRACE("alignment: " + std::to_string(Alignment));
|
|
|
|
ASSERT_SUCCESS(olMemAllocAligned(Device, OL_ALLOC_TYPE_MANAGED, Size,
|
|
Alignment, &Alloc));
|
|
// memcpy is synchronous when queue is unspecified.
|
|
ASSERT_SUCCESS(olMemcpy(nullptr, Alloc, Device, Input.data(), Host, Size));
|
|
ASSERT_SUCCESS(olMemcpy(nullptr, Output.data(), Host, Alloc, Device, Size));
|
|
|
|
for (uint8_t Val : Output) {
|
|
ASSERT_EQ(Val, 42);
|
|
}
|
|
|
|
ASSERT_SUCCESS(olMemFree(Alloc));
|
|
}
|
|
}
|
|
|
|
TEST_P(olMemAllocAlignedTest, SuccessMemcpyDeviceDiferentAlignments) {
|
|
constexpr size_t Size = 1024;
|
|
void *Alloc;
|
|
std::vector<uint8_t> Input(Size, 42);
|
|
std::vector<uint8_t> Output(Size, 0);
|
|
|
|
size_t NumAlignments = 6;
|
|
size_t Alignments[] = {8, 16, 32, 64, 128, 256};
|
|
size_t Alignment;
|
|
for (size_t i = 0; i < NumAlignments; i++) {
|
|
Alignment = Alignments[i];
|
|
SCOPED_TRACE("alignment: " + std::to_string(Alignment));
|
|
|
|
ASSERT_SUCCESS(olMemAllocAligned(Device, OL_ALLOC_TYPE_DEVICE, Size,
|
|
Alignment, &Alloc));
|
|
// memcpy is synchronous when queue is unspecified.
|
|
ASSERT_SUCCESS(olMemcpy(nullptr, Alloc, Device, Input.data(), Host, Size));
|
|
ASSERT_SUCCESS(olMemcpy(nullptr, Output.data(), Host, Alloc, Device, Size));
|
|
|
|
for (uint8_t Val : Output) {
|
|
ASSERT_EQ(Val, 42);
|
|
}
|
|
|
|
ASSERT_SUCCESS(olMemFree(Alloc));
|
|
}
|
|
}
|
|
|
|
TEST_P(olMemAllocAlignedTest, SuccessMemcpyHostDiferentAlignments) {
|
|
constexpr size_t Size = 1024;
|
|
void *Alloc;
|
|
std::vector<uint8_t> Input(Size, 42);
|
|
std::vector<uint8_t> Output(Size, 0);
|
|
|
|
size_t NumAlignments = 6;
|
|
size_t Alignments[] = {8, 16, 32, 64, 128, 256};
|
|
size_t Alignment;
|
|
for (size_t i = 0; i < NumAlignments; i++) {
|
|
Alignment = Alignments[i];
|
|
SCOPED_TRACE("alignment: " + std::to_string(Alignment));
|
|
|
|
ASSERT_SUCCESS(
|
|
olMemAllocAligned(Device, OL_ALLOC_TYPE_HOST, Size, Alignment, &Alloc));
|
|
// memcpy is synchronous when queue is unspecified.
|
|
ASSERT_SUCCESS(olMemcpy(nullptr, Alloc, Device, Input.data(), Host, Size));
|
|
ASSERT_SUCCESS(olMemcpy(nullptr, Output.data(), Host, Alloc, Device, Size));
|
|
|
|
for (uint8_t Val : Output) {
|
|
ASSERT_EQ(Val, 42);
|
|
}
|
|
|
|
ASSERT_SUCCESS(olMemFree(Alloc));
|
|
}
|
|
}
|