This PR changes olLaunchKernel to accept an array of pointers to
arguments:
```
void *ArgPtrs[] = {&A, &B, &C};
size_t ArgSizes[] = {sizeof(A), sizeof(B), sizeof(C)};
olLaunchKernel(Queue, Device, Kernel, &LaunchArgs, std::size(ArgPtrs), ArgPtrs, ArgSizes);
```
The newly proposed interface is implementable by existing and
anticipated
backends, is familiar to CUDA programmers, eliminates the extraneous
construction of a contiguous arguments buffer, replacing it with
constructing
an array of pointers, sidesteps the alignment requirements, does not
require reading program image metadata where it's impractical, and
enables
a compliant SYCL implementation to be built on top of it.
The ArgSizes array is required to support OpenCL, which does not have
native support for launching a kernel with an argument pointer array, or
a reliable way of retrieving argument sizes for a kernel.
## Mapping the proposed API to backends
CUDA and Level-Zero both support accepting an array of pointers to
kernel arguments, through `cuLaunchKernel`
(https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__EXEC.html#group__CUDA__EXEC_1gb8f3dc3031b40da29d5f9a7139e52e15)
and `zeCommandListAppendLaunchKernelWithArguments`
(https://oneapi-src.github.io/level-zero-spec/level-zero/latest/core/api.html#zecommandlistappendlaunchkernelwitharguments)
respectively.
For OpenCL, which requires the kernel arguments to be set separately
from the kernel launch, a potential implementation can extract the
number of arguments from the OpenCL API, and then iterate over the
argument pointer and size arrays:
```
cl_uint num_args = 0;
cl_int err = clGetKernelInfo(kernel, CL_KERNEL_NUM_ARGS,
sizeof(num_args), &num_args, NULL);
for (cl_uint i = 0; i < num_args; ++i) {
clSetKernelArg(kernel, i, ArgSizes[i], ArgPtrs[i]);
}
```
The AMDGPU plugin needs to construct a contiguous buffer using the array
of argument pointers. To do this conversion, we need to have offsets
at which to place the arguments. Here, luckily, as mentioned before,
the AMD plugin already reads kernel metadata from the program image.
The implementation simply retrieves size and offset for each argument
from the kernel image, and then uses it to populate a buffer.
203 lines
6.7 KiB
C++
203 lines
6.7 KiB
C++
//===------- Offload API tests - olMemcpy --------------------------===//
|
|
//
|
|
// 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 olMemcpyTest = OffloadQueueTest;
|
|
OFFLOAD_TESTS_INSTANTIATE_DEVICE_FIXTURE(olMemcpyTest);
|
|
|
|
struct olMemcpyGlobalTest : OffloadGlobalTest {
|
|
void SetUp() override {
|
|
RETURN_ON_FATAL_FAILURE(OffloadGlobalTest::SetUp());
|
|
ASSERT_SUCCESS(
|
|
olGetSymbol(Program, "read", OL_SYMBOL_KIND_KERNEL, &ReadKernel));
|
|
ASSERT_SUCCESS(
|
|
olGetSymbol(Program, "write", OL_SYMBOL_KIND_KERNEL, &WriteKernel));
|
|
ASSERT_SUCCESS(olCreateQueue(Device, &Queue));
|
|
ASSERT_SUCCESS(olGetSymbolInfo(
|
|
Global, OL_SYMBOL_INFO_GLOBAL_VARIABLE_ADDRESS, sizeof(Addr), &Addr));
|
|
|
|
LaunchArgs.Dimensions = 1;
|
|
LaunchArgs.GroupSize = {64, 1, 1};
|
|
LaunchArgs.NumGroups = {1, 1, 1};
|
|
|
|
LaunchArgs.DynSharedMemory = 0;
|
|
}
|
|
|
|
ol_kernel_launch_size_args_t LaunchArgs{};
|
|
void *Addr;
|
|
ol_symbol_handle_t ReadKernel;
|
|
ol_symbol_handle_t WriteKernel;
|
|
ol_queue_handle_t Queue;
|
|
};
|
|
OFFLOAD_TESTS_INSTANTIATE_DEVICE_FIXTURE(olMemcpyGlobalTest);
|
|
|
|
TEST_P(olMemcpyTest, SuccessHtoD) {
|
|
constexpr size_t Size = 1024;
|
|
void *Alloc;
|
|
ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_DEVICE, Size, &Alloc));
|
|
std::vector<uint8_t> Input(Size, 42);
|
|
ASSERT_SUCCESS(olMemcpy(Queue, Alloc, Device, Input.data(), Host, Size));
|
|
olSyncQueue(Queue);
|
|
olMemFree(Alloc);
|
|
}
|
|
|
|
TEST_P(olMemcpyTest, SuccessDtoH) {
|
|
constexpr size_t Size = 1024;
|
|
void *Alloc;
|
|
std::vector<uint8_t> Input(Size, 42);
|
|
std::vector<uint8_t> Output(Size, 0);
|
|
|
|
ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_DEVICE, Size, &Alloc));
|
|
ASSERT_SUCCESS(olMemcpy(Queue, Alloc, Device, Input.data(), Host, Size));
|
|
ASSERT_SUCCESS(olMemcpy(Queue, Output.data(), Host, Alloc, Device, Size));
|
|
ASSERT_SUCCESS(olSyncQueue(Queue));
|
|
for (uint8_t Val : Output) {
|
|
ASSERT_EQ(Val, 42);
|
|
}
|
|
ASSERT_SUCCESS(olMemFree(Alloc));
|
|
}
|
|
|
|
TEST_P(olMemcpyTest, SuccessDtoD) {
|
|
constexpr size_t Size = 1024;
|
|
void *AllocA;
|
|
void *AllocB;
|
|
std::vector<uint8_t> Input(Size, 42);
|
|
std::vector<uint8_t> Output(Size, 0);
|
|
|
|
ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_DEVICE, Size, &AllocA));
|
|
ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_DEVICE, Size, &AllocB));
|
|
ASSERT_SUCCESS(olMemcpy(Queue, AllocA, Device, Input.data(), Host, Size));
|
|
ASSERT_SUCCESS(olMemcpy(Queue, AllocB, Device, AllocA, Device, Size));
|
|
ASSERT_SUCCESS(olMemcpy(Queue, Output.data(), Host, AllocB, Device, Size));
|
|
ASSERT_SUCCESS(olSyncQueue(Queue));
|
|
for (uint8_t Val : Output) {
|
|
ASSERT_EQ(Val, 42);
|
|
}
|
|
ASSERT_SUCCESS(olMemFree(AllocA));
|
|
ASSERT_SUCCESS(olMemFree(AllocB));
|
|
}
|
|
|
|
TEST_P(olMemcpyTest, SuccessHtoHSync) {
|
|
constexpr size_t Size = 1024;
|
|
std::vector<uint8_t> Input(Size, 42);
|
|
std::vector<uint8_t> Output(Size, 0);
|
|
|
|
ASSERT_SUCCESS(
|
|
olMemcpy(nullptr, Output.data(), Host, Input.data(), Host, Size));
|
|
|
|
for (uint8_t Val : Output) {
|
|
ASSERT_EQ(Val, 42);
|
|
}
|
|
}
|
|
|
|
TEST_P(olMemcpyTest, SuccessDtoHSync) {
|
|
constexpr size_t Size = 1024;
|
|
void *Alloc;
|
|
std::vector<uint8_t> Input(Size, 42);
|
|
std::vector<uint8_t> Output(Size, 0);
|
|
|
|
ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_DEVICE, Size, &Alloc));
|
|
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(olMemcpyTest, SuccessSizeZero) {
|
|
constexpr size_t Size = 1024;
|
|
std::vector<uint8_t> Input(Size, 42);
|
|
std::vector<uint8_t> Output(Size, 0);
|
|
|
|
// As with std::memcpy, size 0 is allowed. Keep all other arguments valid even
|
|
// if they aren't used.
|
|
ASSERT_SUCCESS(olMemcpy(nullptr, Output.data(), Host, Input.data(), Host, 0));
|
|
}
|
|
|
|
TEST_P(olMemcpyGlobalTest, SuccessRoundTrip) {
|
|
void *SourceMem;
|
|
ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_MANAGED,
|
|
64 * sizeof(uint32_t), &SourceMem));
|
|
uint32_t *SourceData = (uint32_t *)SourceMem;
|
|
for (auto I = 0; I < 64; I++)
|
|
SourceData[I] = I;
|
|
|
|
void *DestMem;
|
|
ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_MANAGED,
|
|
64 * sizeof(uint32_t), &DestMem));
|
|
|
|
ASSERT_SUCCESS(
|
|
olMemcpy(Queue, Addr, Device, SourceMem, Host, 64 * sizeof(uint32_t)));
|
|
ASSERT_SUCCESS(olSyncQueue(Queue));
|
|
ASSERT_SUCCESS(
|
|
olMemcpy(Queue, DestMem, Host, Addr, Device, 64 * sizeof(uint32_t)));
|
|
ASSERT_SUCCESS(olSyncQueue(Queue));
|
|
|
|
uint32_t *DestData = (uint32_t *)DestMem;
|
|
for (uint32_t I = 0; I < 64; I++)
|
|
ASSERT_EQ(DestData[I], I);
|
|
|
|
ASSERT_SUCCESS(olMemFree(DestMem));
|
|
ASSERT_SUCCESS(olMemFree(SourceMem));
|
|
}
|
|
|
|
TEST_P(olMemcpyGlobalTest, SuccessWrite) {
|
|
void *SourceMem;
|
|
ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_MANAGED,
|
|
LaunchArgs.GroupSize.x * sizeof(uint32_t),
|
|
&SourceMem));
|
|
uint32_t *SourceData = (uint32_t *)SourceMem;
|
|
for (auto I = 0; I < 64; I++)
|
|
SourceData[I] = I;
|
|
|
|
void *DestMem;
|
|
ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_MANAGED,
|
|
LaunchArgs.GroupSize.x * sizeof(uint32_t),
|
|
&DestMem));
|
|
void *ArgPtrs[] = {&DestMem};
|
|
size_t ArgSizes[] = {sizeof(DestMem)};
|
|
|
|
ASSERT_SUCCESS(
|
|
olMemcpy(Queue, Addr, Device, SourceMem, Host, 64 * sizeof(uint32_t)));
|
|
ASSERT_SUCCESS(olSyncQueue(Queue));
|
|
ASSERT_SUCCESS(olLaunchKernel(Queue, Device, ReadKernel, &LaunchArgs, nullptr,
|
|
1, ArgPtrs, ArgSizes));
|
|
ASSERT_SUCCESS(olSyncQueue(Queue));
|
|
|
|
uint32_t *DestData = (uint32_t *)DestMem;
|
|
for (uint32_t I = 0; I < 64; I++)
|
|
ASSERT_EQ(DestData[I], I);
|
|
|
|
ASSERT_SUCCESS(olMemFree(DestMem));
|
|
ASSERT_SUCCESS(olMemFree(SourceMem));
|
|
}
|
|
|
|
TEST_P(olMemcpyGlobalTest, SuccessRead) {
|
|
void *DestMem;
|
|
ASSERT_SUCCESS(olMemAlloc(Device, OL_ALLOC_TYPE_MANAGED,
|
|
LaunchArgs.GroupSize.x * sizeof(uint32_t),
|
|
&DestMem));
|
|
|
|
ASSERT_SUCCESS(olLaunchKernel(Queue, Device, WriteKernel, &LaunchArgs,
|
|
nullptr, 0, nullptr, nullptr));
|
|
ASSERT_SUCCESS(olSyncQueue(Queue));
|
|
ASSERT_SUCCESS(
|
|
olMemcpy(Queue, DestMem, Host, Addr, Device, 64 * sizeof(uint32_t)));
|
|
ASSERT_SUCCESS(olSyncQueue(Queue));
|
|
|
|
uint32_t *DestData = (uint32_t *)DestMem;
|
|
for (uint32_t I = 0; I < 64; I++)
|
|
ASSERT_EQ(DestData[I], I * 2);
|
|
|
|
ASSERT_SUCCESS(olMemFree(DestMem));
|
|
}
|