Skip to content

Commit

Permalink
Add multi-device conformance tests for urProgramCreateWithBinary and …
Browse files Browse the repository at this point in the history
…fix bugs
  • Loading branch information
againull committed Oct 17, 2024
1 parent f3fa464 commit 88fb72a
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 5 deletions.
24 changes: 20 additions & 4 deletions source/adapters/level_zero/program.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ ur_result_t urProgramCreateWithBinary(
// we could change the PI interface and have the caller pass additional
// information to distinguish the cases.
try {
for (uint32_t i = 0; i < numDevices; i++) {
UR_ASSERT(ppBinaries[i] || !pLengths[0], UR_RESULT_ERROR_INVALID_VALUE);
UR_ASSERT(hContext->isValidDevice(phDevices[i]),
UR_RESULT_ERROR_INVALID_DEVICE);
}
ur_program_handle_t_ *UrProgram = new ur_program_handle_t_(
ur_program_handle_t_::Native, hContext, numDevices, phDevices,
pProperties, ppBinaries, pLengths);
Expand Down Expand Up @@ -659,12 +664,15 @@ ur_result_t urProgramGetInfo(
std::vector<size_t> binarySizes;
for (auto Device : Program->AssociatedDevices) {
auto State = Program->getState(Device->ZeDevice);
if (State == ur_program_handle_t_::Native) {
binarySizes.push_back(Program->getCodeSize(Device->ZeDevice));
continue;
}
auto ZeModule = Program->getZeModuleHandle(Device->ZeDevice);
if (!ZeModule)
return UR_RESULT_ERROR_INVALID_PROGRAM;

if (State == ur_program_handle_t_::IL ||
State == ur_program_handle_t_::Native ||
State == ur_program_handle_t_::Object) {
// We don't have a binary for this device, so return size of the spirv
// code. This is an array of 1 element, initialized as if it were
Expand Down Expand Up @@ -701,11 +709,20 @@ ur_result_t urProgramGetInfo(
deviceIndex < Program->AssociatedDevices.size(); deviceIndex++) {
auto ZeDevice = Program->AssociatedDevices[deviceIndex]->ZeDevice;
auto State = Program->getState(ZeDevice);
if (State == ur_program_handle_t_::Native) {
// If Program was created from Native code then return that code.
if (PBinary) {
std::memcpy(PBinary[deviceIndex], Program->getCode(ZeDevice),
Program->getCodeSize(ZeDevice));
}
SzBinary += Program->getCodeSize(ZeDevice);
continue;
}
auto ZeModule = Program->getZeModuleHandle(ZeDevice);
if (!ZeModule) {
return UR_RESULT_ERROR_INVALID_PROGRAM;
}
// If the caller is using a Program which is IL, Native or an object, then
// If the caller is using a Program which is IL or an object, then
// the program has not been built for multiple devices so a single IL is
// returned.
// TODO: currently if program is not compiled for any of the associated
Expand All @@ -714,7 +731,6 @@ ur_result_t urProgramGetInfo(
// that program is compiled for subset of associated devices, so that case
// probably should be explicitely specified and handled better.
if (State == ur_program_handle_t_::IL ||
State == ur_program_handle_t_::Native ||
State == ur_program_handle_t_::Object) {
if (PropSizeRet)
*PropSizeRet = Program->getCodeSize();
Expand All @@ -725,7 +741,7 @@ ur_result_t urProgramGetInfo(
} else if (State == ur_program_handle_t_::Exe) {
size_t binarySize = 0;
if (PBinary) {
NativeBinaryPtr = PBinary[deviceIndex++];
NativeBinaryPtr = PBinary[deviceIndex];
}
// If the caller is using a Program which is a built binary, then
// the program returned will either be a single module if this is a
Expand Down
2 changes: 1 addition & 1 deletion source/adapters/level_zero/program.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ struct ur_program_handle_t_ : _ur_object {
// In IL and Object states, this contains the SPIR-V representation of the
// module.
std::unique_ptr<uint8_t[]> SpirvCode; // Array containing raw IL code.
size_t SpirvCodeLength; // Size (bytes) of the array.
size_t SpirvCodeLength = 0; // Size (bytes) of the array.

// The Level Zero module handle for interoperability.
// This module handle is either initialized with the handle provided to
Expand Down
1 change: 1 addition & 0 deletions test/conformance/program/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ add_conformance_test_with_kernels_environment(program
urProgramBuild.cpp
urProgramCompile.cpp
urProgramCreateWithBinary.cpp
urMultiDeviceProgramCreateWithBinary.cpp
urProgramCreateWithIL.cpp
urProgramCreateWithNativeHandle.cpp
urProgramGetBuildInfo.cpp
Expand Down
247 changes: 247 additions & 0 deletions test/conformance/program/urMultiDeviceProgramCreateWithBinary.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@

// Copyright (C) 2024 Intel Corporation
// Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions.
// See LICENSE.TXT
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <uur/fixtures.h>
#include <uur/raii.h>

struct urMultiDeviceProgramCreateWithBinaryTest
: uur::urMultiDeviceProgramTest {
void SetUp() override {
UUR_RETURN_ON_FATAL_FAILURE(urMultiDeviceProgramTest::SetUp());

// First obtain binaries for all devices from the compiler SPIRV program.
devices = uur::DevicesEnvironment::instance->devices;
if (devices.size() < 2) {
GTEST_SKIP();
}
ASSERT_SUCCESS(urProgramBuild(context, program, nullptr));
size_t binary_sizes_len = 0;
ASSERT_SUCCESS(urProgramGetInfo(program, UR_PROGRAM_INFO_BINARY_SIZES,
0, nullptr, &binary_sizes_len));
// We're expecting number of binaries equal to number of devices.
ASSERT_EQ(binary_sizes_len / sizeof(size_t), devices.size());
binary_sizes.resize(devices.size());
binaries.resize(devices.size());
ASSERT_SUCCESS(urProgramGetInfo(program, UR_PROGRAM_INFO_BINARY_SIZES,
binary_sizes.size() * sizeof(size_t),
binary_sizes.data(), nullptr));
for (size_t i = 0; i < devices.size(); i++) {
size_t binary_size = binary_sizes[i];
binaries[i].resize(binary_size);
pointers.push_back(binaries[i].data());
}
ASSERT_SUCCESS(urProgramGetInfo(program, UR_PROGRAM_INFO_BINARIES,
sizeof(uint8_t *) * pointers.size(),
pointers.data(), nullptr));

// Now create a program with multiple device binaries.
ASSERT_SUCCESS(urProgramCreateWithBinary(
context, devices.size(), devices.data(), binary_sizes.data(),
pointers.data(), nullptr, &binary_program));
}

void TearDown() override {
if (binary_program) {
EXPECT_SUCCESS(urProgramRelease(binary_program));
}
UUR_RETURN_ON_FATAL_FAILURE(urMultiDeviceProgramTest::TearDown());
}

std::vector<std::vector<uint8_t>> binaries;
std::vector<ur_device_handle_t> devices;
std::vector<const uint8_t *> pointers;
std::vector<size_t> binary_sizes;
ur_program_handle_t binary_program = nullptr;
};

// Create the kernel using the program created with multiple binaries and run it on all devices.
TEST_F(urMultiDeviceProgramCreateWithBinaryTest,
CreateAndRunKernelOnAllDevices) {
constexpr size_t global_offset = 0;
constexpr size_t n_dimensions = 1;
constexpr size_t global_size = 100;
constexpr size_t local_size = 100;

auto kernelName =
uur::KernelsEnvironment::instance->GetEntryPointNames("foo")[0];

for (size_t i = 1; i < devices.size(); i++) {
uur::raii::Kernel kernel;
ASSERT_SUCCESS(urProgramBuild(context, binary_program, nullptr));
ASSERT_SUCCESS(
urKernelCreate(binary_program, kernelName.data(), kernel.ptr()));

ASSERT_SUCCESS(urEnqueueKernelLaunch(
queues[i], kernel.get(), n_dimensions, &global_offset, &local_size,
&global_size, 0, nullptr, nullptr));

ASSERT_SUCCESS(urQueueFinish(queues[i]));
}
}

TEST_F(urMultiDeviceProgramCreateWithBinaryTest, CheckCompileAndLink) {
// TODO: Current behaviour is that we allow to compile only IL programs for Level Zero and link only programs in Object state.
// OpenCL allows to compile and link programs created from native binaries, so probably we should align those two.
ur_platform_backend_t backend;
ASSERT_SUCCESS(urPlatformGetInfo(platform, UR_PLATFORM_INFO_BACKEND,
sizeof(backend), &backend, nullptr));
if (backend == UR_PLATFORM_BACKEND_LEVEL_ZERO) {
ASSERT_EQ(urProgramCompile(context, binary_program, nullptr),
UR_RESULT_ERROR_INVALID_OPERATION);
uur::raii::Program linked_program;
ASSERT_EQ(urProgramLink(context, 1, &binary_program, nullptr,
linked_program.ptr()),
UR_RESULT_ERROR_INVALID_OPERATION);
} else if (backend == UR_PLATFORM_BACKEND_OPENCL) {
ASSERT_SUCCESS(urProgramCompile(context, binary_program, nullptr));
uur::raii::Program linked_program;
ASSERT_SUCCESS(urProgramLink(context, 1, &binary_program, nullptr,
linked_program.ptr()));
} else {
GTEST_SKIP();
}
}

TEST_F(urMultiDeviceProgramCreateWithBinaryTest,
InvalidProgramBinaryForOneOfTheDevices) {
std::vector<const uint8_t *> pointers_with_invalid_binary;
for (size_t i = 1; i < devices.size(); i++) {
pointers_with_invalid_binary.push_back(nullptr);
}
uur::raii::Program invalid_bin_program;
ASSERT_EQ(urProgramCreateWithBinary(context, devices.size(), devices.data(),
binary_sizes.data(),
pointers_with_invalid_binary.data(),
nullptr, invalid_bin_program.ptr()),
UR_RESULT_ERROR_INVALID_VALUE);
}

// Test the case when program is built multiple times for different devices from context.
TEST_F(urMultiDeviceProgramCreateWithBinaryTest, MultipleBuildCalls) {
// Run test only for level zero backend which supports urProgramBuildExp.
ur_platform_backend_t backend;
ASSERT_SUCCESS(urPlatformGetInfo(platform, UR_PLATFORM_INFO_BACKEND,
sizeof(backend), &backend, nullptr));
if (backend != UR_PLATFORM_BACKEND_LEVEL_ZERO) {
GTEST_SKIP();
}
auto first_subset = std::vector<ur_device_handle_t>(
devices.begin(), devices.begin() + devices.size() / 2);
auto second_subset = std::vector<ur_device_handle_t>(
devices.begin() + devices.size() / 2, devices.end());
ASSERT_SUCCESS(urProgramBuildExp(binary_program, first_subset.size(),
first_subset.data(), nullptr));
auto kernelName =
uur::KernelsEnvironment::instance->GetEntryPointNames("foo")[0];
uur::raii::Kernel kernel;
ASSERT_SUCCESS(
urKernelCreate(binary_program, kernelName.data(), kernel.ptr()));
ASSERT_SUCCESS(urProgramBuildExp(binary_program, second_subset.size(),
second_subset.data(), nullptr));
ASSERT_SUCCESS(
urKernelCreate(binary_program, kernelName.data(), kernel.ptr()));

// Building for the same subset of devices should not fail.
ASSERT_SUCCESS(urProgramBuildExp(binary_program, first_subset.size(),
first_subset.data(), nullptr));
}

// Test the case we get native binaries from program created with multiple binaries which wasn't built (i.e. in Native state).
TEST_F(urMultiDeviceProgramCreateWithBinaryTest,
GetBinariesAndSizesFromProgramInNativeState) {
size_t exp_binary_sizes_len = 0;
std::vector<size_t> exp_binary_sizes;
std::vector<std::vector<uint8_t>> exp_binaries;
std::vector<const uint8_t *> exp_pointer;
ASSERT_SUCCESS(urProgramGetInfo(binary_program,
UR_PROGRAM_INFO_BINARY_SIZES, 0, nullptr,
&exp_binary_sizes_len));
auto num = exp_binary_sizes_len / sizeof(size_t);
exp_binary_sizes.resize(num);
exp_binaries.resize(num);
exp_pointer.resize(num);
ASSERT_SUCCESS(urProgramGetInfo(binary_program,
UR_PROGRAM_INFO_BINARY_SIZES,
exp_binary_sizes.size() * sizeof(size_t),
exp_binary_sizes.data(), nullptr));
for (size_t i = 0; i < devices.size(); i++) {
size_t binary_size = exp_binary_sizes[i];
exp_binaries[i].resize(binary_size);
exp_pointer[i] = exp_binaries[i].data();
}
ASSERT_SUCCESS(urProgramGetInfo(program, UR_PROGRAM_INFO_BINARIES,
sizeof(uint8_t *) * exp_pointer.size(),
exp_pointer.data(), nullptr));

// Verify that we get exactly what was provided at the creation step.
ASSERT_EQ(exp_binaries, binaries);
ASSERT_EQ(exp_binary_sizes, binary_sizes);
}

TEST_F(urMultiDeviceProgramCreateWithBinaryTest, GetIL) {
size_t il_length = 0;
ASSERT_SUCCESS(urProgramGetInfo(binary_program, UR_PROGRAM_INFO_IL, 0,
nullptr, &il_length));
ASSERT_EQ(il_length, 0);
std::vector<uint8_t> il(il_length);
ASSERT_EQ(urProgramGetInfo(binary_program, UR_PROGRAM_INFO_IL, il.size(),
il.data(), nullptr),
UR_RESULT_ERROR_INVALID_NULL_POINTER);
}

TEST_F(urMultiDeviceProgramCreateWithBinaryTest, CheckProgramGetInfo) {
std::vector<char> property_value;
size_t property_size = 0;

// Program is not in exe state, so error is expected.
for (auto prop :
{UR_PROGRAM_INFO_NUM_KERNELS, UR_PROGRAM_INFO_KERNEL_NAMES}) {
auto result =
urProgramGetInfo(binary_program, prop, 0, nullptr, &property_size);
// TODO: OpenCL and Level Zero return diffent error code, it needs to be fixed.
ASSERT_TRUE(result == UR_RESULT_ERROR_INVALID_PROGRAM_EXECUTABLE ||
result == UR_RESULT_ERROR_INVALID_PROGRAM);
}

// Now build the program and check that we can get the info.
ASSERT_SUCCESS(urProgramBuild(context, binary_program, nullptr));

size_t logSize;
std::string log;

for (auto dev : devices) {
ASSERT_SUCCESS(urProgramGetBuildInfo(
program, dev, UR_PROGRAM_BUILD_INFO_LOG, 0, nullptr, &logSize));
// The size should always include the null terminator.
ASSERT_GT(logSize, 0);
log.resize(logSize);
ASSERT_SUCCESS(urProgramGetBuildInfo(program, dev,
UR_PROGRAM_BUILD_INFO_LOG, logSize,
log.data(), nullptr));
ASSERT_EQ(log[logSize - 1], '\0');
}

ASSERT_SUCCESS(urProgramGetInfo(binary_program, UR_PROGRAM_INFO_NUM_KERNELS,
0, nullptr, &property_size));
property_value.resize(property_size);
ASSERT_SUCCESS(urProgramGetInfo(binary_program, UR_PROGRAM_INFO_NUM_KERNELS,
property_size, property_value.data(),
nullptr));

auto returned_num_of_kernels =
reinterpret_cast<uint32_t *>(property_value.data());
ASSERT_GT(*returned_num_of_kernels, 0U);
ASSERT_SUCCESS(urProgramGetInfo(binary_program,
UR_PROGRAM_INFO_KERNEL_NAMES, 0, nullptr,
&property_size));
property_value.resize(property_size);
ASSERT_SUCCESS(urProgramGetInfo(binary_program,
UR_PROGRAM_INFO_KERNEL_NAMES, property_size,
property_value.data(), nullptr));
auto returned_kernel_names =
reinterpret_cast<char *>(property_value.data());
ASSERT_STRNE(returned_kernel_names, "");
}
39 changes: 39 additions & 0 deletions test/conformance/testing/include/uur/fixtures.h
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,45 @@ struct urMultiDeviceQueueTest : urMultiDeviceContextTest {
std::vector<ur_queue_handle_t> queues;
};

struct urMultiDeviceProgramTest : urMultiDeviceQueueTest {
void SetUp() override {
UUR_RETURN_ON_FATAL_FAILURE(urMultiDeviceQueueTest::SetUp());

ur_platform_backend_t backend;
ASSERT_SUCCESS(urPlatformGetInfo(platform, UR_PLATFORM_INFO_BACKEND,
sizeof(backend), &backend, nullptr));
// Multi-device programs are not supported for AMD and CUDA
if (backend == UR_PLATFORM_BACKEND_HIP &&
backend == UR_PLATFORM_BACKEND_CUDA) {
GTEST_SKIP();
}
UUR_RETURN_ON_FATAL_FAILURE(
uur::KernelsEnvironment::instance->LoadSource(program_name,
il_binary));

const ur_program_properties_t properties = {
UR_STRUCTURE_TYPE_PROGRAM_PROPERTIES, nullptr,
static_cast<uint32_t>(metadatas.size()),
metadatas.empty() ? nullptr : metadatas.data()};

ASSERT_SUCCESS(urProgramCreateWithIL(context, (*il_binary).data(),
(*il_binary).size(), &properties,
&program));
}

void TearDown() override {
if (program) {
EXPECT_SUCCESS(urProgramRelease(program));
}
UUR_RETURN_ON_FATAL_FAILURE(urMultiDeviceQueueTest::TearDown());
}

std::shared_ptr<std::vector<char>> il_binary;
std::string program_name = "foo";
ur_program_handle_t program = nullptr;
std::vector<ur_program_metadata_t> metadatas{};
};

} // namespace uur

#endif // UR_CONFORMANCE_INCLUDE_FIXTURES_H_INCLUDED

0 comments on commit 88fb72a

Please sign in to comment.