Skip to content

Commit 88fb72a

Browse files
committed
Add multi-device conformance tests for urProgramCreateWithBinary and fix bugs
1 parent f3fa464 commit 88fb72a

File tree

5 files changed

+308
-5
lines changed

5 files changed

+308
-5
lines changed

source/adapters/level_zero/program.cpp

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ ur_result_t urProgramCreateWithBinary(
110110
// we could change the PI interface and have the caller pass additional
111111
// information to distinguish the cases.
112112
try {
113+
for (uint32_t i = 0; i < numDevices; i++) {
114+
UR_ASSERT(ppBinaries[i] || !pLengths[0], UR_RESULT_ERROR_INVALID_VALUE);
115+
UR_ASSERT(hContext->isValidDevice(phDevices[i]),
116+
UR_RESULT_ERROR_INVALID_DEVICE);
117+
}
113118
ur_program_handle_t_ *UrProgram = new ur_program_handle_t_(
114119
ur_program_handle_t_::Native, hContext, numDevices, phDevices,
115120
pProperties, ppBinaries, pLengths);
@@ -659,12 +664,15 @@ ur_result_t urProgramGetInfo(
659664
std::vector<size_t> binarySizes;
660665
for (auto Device : Program->AssociatedDevices) {
661666
auto State = Program->getState(Device->ZeDevice);
667+
if (State == ur_program_handle_t_::Native) {
668+
binarySizes.push_back(Program->getCodeSize(Device->ZeDevice));
669+
continue;
670+
}
662671
auto ZeModule = Program->getZeModuleHandle(Device->ZeDevice);
663672
if (!ZeModule)
664673
return UR_RESULT_ERROR_INVALID_PROGRAM;
665674

666675
if (State == ur_program_handle_t_::IL ||
667-
State == ur_program_handle_t_::Native ||
668676
State == ur_program_handle_t_::Object) {
669677
// We don't have a binary for this device, so return size of the spirv
670678
// code. This is an array of 1 element, initialized as if it were
@@ -701,11 +709,20 @@ ur_result_t urProgramGetInfo(
701709
deviceIndex < Program->AssociatedDevices.size(); deviceIndex++) {
702710
auto ZeDevice = Program->AssociatedDevices[deviceIndex]->ZeDevice;
703711
auto State = Program->getState(ZeDevice);
712+
if (State == ur_program_handle_t_::Native) {
713+
// If Program was created from Native code then return that code.
714+
if (PBinary) {
715+
std::memcpy(PBinary[deviceIndex], Program->getCode(ZeDevice),
716+
Program->getCodeSize(ZeDevice));
717+
}
718+
SzBinary += Program->getCodeSize(ZeDevice);
719+
continue;
720+
}
704721
auto ZeModule = Program->getZeModuleHandle(ZeDevice);
705722
if (!ZeModule) {
706723
return UR_RESULT_ERROR_INVALID_PROGRAM;
707724
}
708-
// If the caller is using a Program which is IL, Native or an object, then
725+
// If the caller is using a Program which is IL or an object, then
709726
// the program has not been built for multiple devices so a single IL is
710727
// returned.
711728
// TODO: currently if program is not compiled for any of the associated
@@ -714,7 +731,6 @@ ur_result_t urProgramGetInfo(
714731
// that program is compiled for subset of associated devices, so that case
715732
// probably should be explicitely specified and handled better.
716733
if (State == ur_program_handle_t_::IL ||
717-
State == ur_program_handle_t_::Native ||
718734
State == ur_program_handle_t_::Object) {
719735
if (PropSizeRet)
720736
*PropSizeRet = Program->getCodeSize();
@@ -725,7 +741,7 @@ ur_result_t urProgramGetInfo(
725741
} else if (State == ur_program_handle_t_::Exe) {
726742
size_t binarySize = 0;
727743
if (PBinary) {
728-
NativeBinaryPtr = PBinary[deviceIndex++];
744+
NativeBinaryPtr = PBinary[deviceIndex];
729745
}
730746
// If the caller is using a Program which is a built binary, then
731747
// the program returned will either be a single module if this is a

source/adapters/level_zero/program.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ struct ur_program_handle_t_ : _ur_object {
234234
// In IL and Object states, this contains the SPIR-V representation of the
235235
// module.
236236
std::unique_ptr<uint8_t[]> SpirvCode; // Array containing raw IL code.
237-
size_t SpirvCodeLength; // Size (bytes) of the array.
237+
size_t SpirvCodeLength = 0; // Size (bytes) of the array.
238238

239239
// The Level Zero module handle for interoperability.
240240
// This module handle is either initialized with the handle provided to

test/conformance/program/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ add_conformance_test_with_kernels_environment(program
77
urProgramBuild.cpp
88
urProgramCompile.cpp
99
urProgramCreateWithBinary.cpp
10+
urMultiDeviceProgramCreateWithBinary.cpp
1011
urProgramCreateWithIL.cpp
1112
urProgramCreateWithNativeHandle.cpp
1213
urProgramGetBuildInfo.cpp
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
2+
// Copyright (C) 2024 Intel Corporation
3+
// Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See LICENSE.TXT
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
7+
#include <uur/fixtures.h>
8+
#include <uur/raii.h>
9+
10+
struct urMultiDeviceProgramCreateWithBinaryTest
11+
: uur::urMultiDeviceProgramTest {
12+
void SetUp() override {
13+
UUR_RETURN_ON_FATAL_FAILURE(urMultiDeviceProgramTest::SetUp());
14+
15+
// First obtain binaries for all devices from the compiler SPIRV program.
16+
devices = uur::DevicesEnvironment::instance->devices;
17+
if (devices.size() < 2) {
18+
GTEST_SKIP();
19+
}
20+
ASSERT_SUCCESS(urProgramBuild(context, program, nullptr));
21+
size_t binary_sizes_len = 0;
22+
ASSERT_SUCCESS(urProgramGetInfo(program, UR_PROGRAM_INFO_BINARY_SIZES,
23+
0, nullptr, &binary_sizes_len));
24+
// We're expecting number of binaries equal to number of devices.
25+
ASSERT_EQ(binary_sizes_len / sizeof(size_t), devices.size());
26+
binary_sizes.resize(devices.size());
27+
binaries.resize(devices.size());
28+
ASSERT_SUCCESS(urProgramGetInfo(program, UR_PROGRAM_INFO_BINARY_SIZES,
29+
binary_sizes.size() * sizeof(size_t),
30+
binary_sizes.data(), nullptr));
31+
for (size_t i = 0; i < devices.size(); i++) {
32+
size_t binary_size = binary_sizes[i];
33+
binaries[i].resize(binary_size);
34+
pointers.push_back(binaries[i].data());
35+
}
36+
ASSERT_SUCCESS(urProgramGetInfo(program, UR_PROGRAM_INFO_BINARIES,
37+
sizeof(uint8_t *) * pointers.size(),
38+
pointers.data(), nullptr));
39+
40+
// Now create a program with multiple device binaries.
41+
ASSERT_SUCCESS(urProgramCreateWithBinary(
42+
context, devices.size(), devices.data(), binary_sizes.data(),
43+
pointers.data(), nullptr, &binary_program));
44+
}
45+
46+
void TearDown() override {
47+
if (binary_program) {
48+
EXPECT_SUCCESS(urProgramRelease(binary_program));
49+
}
50+
UUR_RETURN_ON_FATAL_FAILURE(urMultiDeviceProgramTest::TearDown());
51+
}
52+
53+
std::vector<std::vector<uint8_t>> binaries;
54+
std::vector<ur_device_handle_t> devices;
55+
std::vector<const uint8_t *> pointers;
56+
std::vector<size_t> binary_sizes;
57+
ur_program_handle_t binary_program = nullptr;
58+
};
59+
60+
// Create the kernel using the program created with multiple binaries and run it on all devices.
61+
TEST_F(urMultiDeviceProgramCreateWithBinaryTest,
62+
CreateAndRunKernelOnAllDevices) {
63+
constexpr size_t global_offset = 0;
64+
constexpr size_t n_dimensions = 1;
65+
constexpr size_t global_size = 100;
66+
constexpr size_t local_size = 100;
67+
68+
auto kernelName =
69+
uur::KernelsEnvironment::instance->GetEntryPointNames("foo")[0];
70+
71+
for (size_t i = 1; i < devices.size(); i++) {
72+
uur::raii::Kernel kernel;
73+
ASSERT_SUCCESS(urProgramBuild(context, binary_program, nullptr));
74+
ASSERT_SUCCESS(
75+
urKernelCreate(binary_program, kernelName.data(), kernel.ptr()));
76+
77+
ASSERT_SUCCESS(urEnqueueKernelLaunch(
78+
queues[i], kernel.get(), n_dimensions, &global_offset, &local_size,
79+
&global_size, 0, nullptr, nullptr));
80+
81+
ASSERT_SUCCESS(urQueueFinish(queues[i]));
82+
}
83+
}
84+
85+
TEST_F(urMultiDeviceProgramCreateWithBinaryTest, CheckCompileAndLink) {
86+
// TODO: Current behaviour is that we allow to compile only IL programs for Level Zero and link only programs in Object state.
87+
// OpenCL allows to compile and link programs created from native binaries, so probably we should align those two.
88+
ur_platform_backend_t backend;
89+
ASSERT_SUCCESS(urPlatformGetInfo(platform, UR_PLATFORM_INFO_BACKEND,
90+
sizeof(backend), &backend, nullptr));
91+
if (backend == UR_PLATFORM_BACKEND_LEVEL_ZERO) {
92+
ASSERT_EQ(urProgramCompile(context, binary_program, nullptr),
93+
UR_RESULT_ERROR_INVALID_OPERATION);
94+
uur::raii::Program linked_program;
95+
ASSERT_EQ(urProgramLink(context, 1, &binary_program, nullptr,
96+
linked_program.ptr()),
97+
UR_RESULT_ERROR_INVALID_OPERATION);
98+
} else if (backend == UR_PLATFORM_BACKEND_OPENCL) {
99+
ASSERT_SUCCESS(urProgramCompile(context, binary_program, nullptr));
100+
uur::raii::Program linked_program;
101+
ASSERT_SUCCESS(urProgramLink(context, 1, &binary_program, nullptr,
102+
linked_program.ptr()));
103+
} else {
104+
GTEST_SKIP();
105+
}
106+
}
107+
108+
TEST_F(urMultiDeviceProgramCreateWithBinaryTest,
109+
InvalidProgramBinaryForOneOfTheDevices) {
110+
std::vector<const uint8_t *> pointers_with_invalid_binary;
111+
for (size_t i = 1; i < devices.size(); i++) {
112+
pointers_with_invalid_binary.push_back(nullptr);
113+
}
114+
uur::raii::Program invalid_bin_program;
115+
ASSERT_EQ(urProgramCreateWithBinary(context, devices.size(), devices.data(),
116+
binary_sizes.data(),
117+
pointers_with_invalid_binary.data(),
118+
nullptr, invalid_bin_program.ptr()),
119+
UR_RESULT_ERROR_INVALID_VALUE);
120+
}
121+
122+
// Test the case when program is built multiple times for different devices from context.
123+
TEST_F(urMultiDeviceProgramCreateWithBinaryTest, MultipleBuildCalls) {
124+
// Run test only for level zero backend which supports urProgramBuildExp.
125+
ur_platform_backend_t backend;
126+
ASSERT_SUCCESS(urPlatformGetInfo(platform, UR_PLATFORM_INFO_BACKEND,
127+
sizeof(backend), &backend, nullptr));
128+
if (backend != UR_PLATFORM_BACKEND_LEVEL_ZERO) {
129+
GTEST_SKIP();
130+
}
131+
auto first_subset = std::vector<ur_device_handle_t>(
132+
devices.begin(), devices.begin() + devices.size() / 2);
133+
auto second_subset = std::vector<ur_device_handle_t>(
134+
devices.begin() + devices.size() / 2, devices.end());
135+
ASSERT_SUCCESS(urProgramBuildExp(binary_program, first_subset.size(),
136+
first_subset.data(), nullptr));
137+
auto kernelName =
138+
uur::KernelsEnvironment::instance->GetEntryPointNames("foo")[0];
139+
uur::raii::Kernel kernel;
140+
ASSERT_SUCCESS(
141+
urKernelCreate(binary_program, kernelName.data(), kernel.ptr()));
142+
ASSERT_SUCCESS(urProgramBuildExp(binary_program, second_subset.size(),
143+
second_subset.data(), nullptr));
144+
ASSERT_SUCCESS(
145+
urKernelCreate(binary_program, kernelName.data(), kernel.ptr()));
146+
147+
// Building for the same subset of devices should not fail.
148+
ASSERT_SUCCESS(urProgramBuildExp(binary_program, first_subset.size(),
149+
first_subset.data(), nullptr));
150+
}
151+
152+
// Test the case we get native binaries from program created with multiple binaries which wasn't built (i.e. in Native state).
153+
TEST_F(urMultiDeviceProgramCreateWithBinaryTest,
154+
GetBinariesAndSizesFromProgramInNativeState) {
155+
size_t exp_binary_sizes_len = 0;
156+
std::vector<size_t> exp_binary_sizes;
157+
std::vector<std::vector<uint8_t>> exp_binaries;
158+
std::vector<const uint8_t *> exp_pointer;
159+
ASSERT_SUCCESS(urProgramGetInfo(binary_program,
160+
UR_PROGRAM_INFO_BINARY_SIZES, 0, nullptr,
161+
&exp_binary_sizes_len));
162+
auto num = exp_binary_sizes_len / sizeof(size_t);
163+
exp_binary_sizes.resize(num);
164+
exp_binaries.resize(num);
165+
exp_pointer.resize(num);
166+
ASSERT_SUCCESS(urProgramGetInfo(binary_program,
167+
UR_PROGRAM_INFO_BINARY_SIZES,
168+
exp_binary_sizes.size() * sizeof(size_t),
169+
exp_binary_sizes.data(), nullptr));
170+
for (size_t i = 0; i < devices.size(); i++) {
171+
size_t binary_size = exp_binary_sizes[i];
172+
exp_binaries[i].resize(binary_size);
173+
exp_pointer[i] = exp_binaries[i].data();
174+
}
175+
ASSERT_SUCCESS(urProgramGetInfo(program, UR_PROGRAM_INFO_BINARIES,
176+
sizeof(uint8_t *) * exp_pointer.size(),
177+
exp_pointer.data(), nullptr));
178+
179+
// Verify that we get exactly what was provided at the creation step.
180+
ASSERT_EQ(exp_binaries, binaries);
181+
ASSERT_EQ(exp_binary_sizes, binary_sizes);
182+
}
183+
184+
TEST_F(urMultiDeviceProgramCreateWithBinaryTest, GetIL) {
185+
size_t il_length = 0;
186+
ASSERT_SUCCESS(urProgramGetInfo(binary_program, UR_PROGRAM_INFO_IL, 0,
187+
nullptr, &il_length));
188+
ASSERT_EQ(il_length, 0);
189+
std::vector<uint8_t> il(il_length);
190+
ASSERT_EQ(urProgramGetInfo(binary_program, UR_PROGRAM_INFO_IL, il.size(),
191+
il.data(), nullptr),
192+
UR_RESULT_ERROR_INVALID_NULL_POINTER);
193+
}
194+
195+
TEST_F(urMultiDeviceProgramCreateWithBinaryTest, CheckProgramGetInfo) {
196+
std::vector<char> property_value;
197+
size_t property_size = 0;
198+
199+
// Program is not in exe state, so error is expected.
200+
for (auto prop :
201+
{UR_PROGRAM_INFO_NUM_KERNELS, UR_PROGRAM_INFO_KERNEL_NAMES}) {
202+
auto result =
203+
urProgramGetInfo(binary_program, prop, 0, nullptr, &property_size);
204+
// TODO: OpenCL and Level Zero return diffent error code, it needs to be fixed.
205+
ASSERT_TRUE(result == UR_RESULT_ERROR_INVALID_PROGRAM_EXECUTABLE ||
206+
result == UR_RESULT_ERROR_INVALID_PROGRAM);
207+
}
208+
209+
// Now build the program and check that we can get the info.
210+
ASSERT_SUCCESS(urProgramBuild(context, binary_program, nullptr));
211+
212+
size_t logSize;
213+
std::string log;
214+
215+
for (auto dev : devices) {
216+
ASSERT_SUCCESS(urProgramGetBuildInfo(
217+
program, dev, UR_PROGRAM_BUILD_INFO_LOG, 0, nullptr, &logSize));
218+
// The size should always include the null terminator.
219+
ASSERT_GT(logSize, 0);
220+
log.resize(logSize);
221+
ASSERT_SUCCESS(urProgramGetBuildInfo(program, dev,
222+
UR_PROGRAM_BUILD_INFO_LOG, logSize,
223+
log.data(), nullptr));
224+
ASSERT_EQ(log[logSize - 1], '\0');
225+
}
226+
227+
ASSERT_SUCCESS(urProgramGetInfo(binary_program, UR_PROGRAM_INFO_NUM_KERNELS,
228+
0, nullptr, &property_size));
229+
property_value.resize(property_size);
230+
ASSERT_SUCCESS(urProgramGetInfo(binary_program, UR_PROGRAM_INFO_NUM_KERNELS,
231+
property_size, property_value.data(),
232+
nullptr));
233+
234+
auto returned_num_of_kernels =
235+
reinterpret_cast<uint32_t *>(property_value.data());
236+
ASSERT_GT(*returned_num_of_kernels, 0U);
237+
ASSERT_SUCCESS(urProgramGetInfo(binary_program,
238+
UR_PROGRAM_INFO_KERNEL_NAMES, 0, nullptr,
239+
&property_size));
240+
property_value.resize(property_size);
241+
ASSERT_SUCCESS(urProgramGetInfo(binary_program,
242+
UR_PROGRAM_INFO_KERNEL_NAMES, property_size,
243+
property_value.data(), nullptr));
244+
auto returned_kernel_names =
245+
reinterpret_cast<char *>(property_value.data());
246+
ASSERT_STRNE(returned_kernel_names, "");
247+
}

test/conformance/testing/include/uur/fixtures.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,45 @@ struct urMultiDeviceQueueTest : urMultiDeviceContextTest {
15601560
std::vector<ur_queue_handle_t> queues;
15611561
};
15621562

1563+
struct urMultiDeviceProgramTest : urMultiDeviceQueueTest {
1564+
void SetUp() override {
1565+
UUR_RETURN_ON_FATAL_FAILURE(urMultiDeviceQueueTest::SetUp());
1566+
1567+
ur_platform_backend_t backend;
1568+
ASSERT_SUCCESS(urPlatformGetInfo(platform, UR_PLATFORM_INFO_BACKEND,
1569+
sizeof(backend), &backend, nullptr));
1570+
// Multi-device programs are not supported for AMD and CUDA
1571+
if (backend == UR_PLATFORM_BACKEND_HIP &&
1572+
backend == UR_PLATFORM_BACKEND_CUDA) {
1573+
GTEST_SKIP();
1574+
}
1575+
UUR_RETURN_ON_FATAL_FAILURE(
1576+
uur::KernelsEnvironment::instance->LoadSource(program_name,
1577+
il_binary));
1578+
1579+
const ur_program_properties_t properties = {
1580+
UR_STRUCTURE_TYPE_PROGRAM_PROPERTIES, nullptr,
1581+
static_cast<uint32_t>(metadatas.size()),
1582+
metadatas.empty() ? nullptr : metadatas.data()};
1583+
1584+
ASSERT_SUCCESS(urProgramCreateWithIL(context, (*il_binary).data(),
1585+
(*il_binary).size(), &properties,
1586+
&program));
1587+
}
1588+
1589+
void TearDown() override {
1590+
if (program) {
1591+
EXPECT_SUCCESS(urProgramRelease(program));
1592+
}
1593+
UUR_RETURN_ON_FATAL_FAILURE(urMultiDeviceQueueTest::TearDown());
1594+
}
1595+
1596+
std::shared_ptr<std::vector<char>> il_binary;
1597+
std::string program_name = "foo";
1598+
ur_program_handle_t program = nullptr;
1599+
std::vector<ur_program_metadata_t> metadatas{};
1600+
};
1601+
15631602
} // namespace uur
15641603

15651604
#endif // UR_CONFORMANCE_INCLUDE_FIXTURES_H_INCLUDED

0 commit comments

Comments
 (0)