Skip to content

Commit 2abd58c

Browse files
[Offload] Add framework for math conformance tests (llvm#149242)
This PR introduces the initial version of a C++ framework for the conformance testing of GPU math library functions, building upon the skeleton provided in llvm#146391. The main goal of this framework is to systematically measure the accuracy of math functions in the GPU libc, verifying correctness or at least conformance to standards like OpenCL via exhaustive or random accuracy tests.
1 parent 1598062 commit 2abd58c

32 files changed

+2435
-26
lines changed

offload/unittests/CMakeLists.txt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ function(add_offload_test_device_code test_filename test_name)
4141
COMMAND ${CMAKE_C_COMPILER}
4242
--target=nvptx64-nvidia-cuda -march=${nvptx_arch}
4343
-nogpulib --cuda-path=${CUDA_ROOT} -flto ${ARGN}
44-
-c ${SRC_PATH} -o ${output_file}
44+
${SRC_PATH} -o ${output_file}
4545
DEPENDS ${SRC_PATH}
4646
)
4747
add_custom_target(${test_name}.nvptx64 DEPENDS ${output_file})
@@ -64,7 +64,7 @@ function(add_offload_test_device_code test_filename test_name)
6464
OUTPUT ${output_file}
6565
COMMAND ${CMAKE_C_COMPILER}
6666
--target=amdgcn-amd-amdhsa -mcpu=${amdgpu_arch}
67-
-nogpulib -flto ${ARGN} -c ${SRC_PATH} -o ${output_file}
67+
-nogpulib -flto ${ARGN} ${SRC_PATH} -o ${output_file}
6868
DEPENDS ${SRC_PATH}
6969
)
7070
add_custom_target(${test_name}.amdgpu DEPENDS ${output_file})
@@ -106,16 +106,15 @@ function(add_conformance_test test_name)
106106
endif()
107107

108108
add_executable(${target_name} ${files})
109-
add_dependencies(${target_name} ${PLUGINS_TEST_COMMON} ${test_name}.bin)
110-
target_compile_definitions(${target_name} PRIVATE DEVICE_CODE_PATH="${CONFORMANCE_TEST_DEVICE_CODE_PATH}")
109+
add_dependencies(${target_name} conformance_device_binaries)
110+
target_compile_definitions(${target_name}
111+
PRIVATE DEVICE_BINARY_DIR="${OFFLOAD_CONFORMANCE_DEVICE_BINARY_DIR}")
111112
target_link_libraries(${target_name} PRIVATE ${PLUGINS_TEST_COMMON} libc)
112-
target_include_directories(${target_name} PRIVATE ${PLUGINS_TEST_INCLUDE})
113113
set_target_properties(${target_name} PROPERTIES EXCLUDE_FROM_ALL TRUE)
114114

115115
add_custom_target(offload.conformance.${test_name}
116116
COMMAND $<TARGET_FILE:${target_name}>
117-
DEPENDS ${target_name}
118-
COMMENT "Running conformance test ${test_name}")
117+
DEPENDS ${target_name})
119118
add_dependencies(offload.conformance offload.conformance.${test_name})
120119
endfunction()
121120

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
add_custom_target(offload.conformance)
22

3-
set(PLUGINS_TEST_COMMON LLVMOffload LLVMSupport)
4-
set(PLUGINS_TEST_INCLUDE ${LIBOMPTARGET_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/common)
3+
set(PLUGINS_TEST_COMMON MathTest)
54

65
add_subdirectory(device_code)
7-
8-
add_conformance_test(sin sin.cpp)
6+
add_subdirectory(lib)
7+
add_subdirectory(tests)
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# FIXME: Currently missing dependencies to build GPU portion automatically.
2-
add_offload_test_device_code(sin.c sin)
1+
add_offload_test_device_code(LLVMLibm.c llvm-libm -stdlib -fno-builtin)
32

4-
set(OFFLOAD_TEST_DEVICE_CODE_PATH ${CMAKE_CURRENT_BINARY_DIR} PARENT_SCOPE)
3+
add_custom_target(conformance_device_binaries DEPENDS llvm-libm.bin)
4+
set(OFFLOAD_CONFORMANCE_DEVICE_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR} PARENT_SCOPE)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// \file
10+
/// This file contains the implementation of the device kernels that wrap the
11+
/// math functions from the llvm-libm provider.
12+
///
13+
//===----------------------------------------------------------------------===//
14+
15+
#include <gpuintrin.h>
16+
#include <math.h>
17+
#include <stddef.h>
18+
#include <stdint.h>
19+
20+
typedef _Float16 float16;
21+
22+
__gpu_kernel void hypotf16Kernel(const float16 *X, float16 *Y, float16 *Out,
23+
size_t NumElements) {
24+
uint32_t Index =
25+
__gpu_num_threads_x() * __gpu_block_id_x() + __gpu_thread_id_x();
26+
27+
if (Index < NumElements)
28+
Out[Index] = hypotf16(X[Index], Y[Index]);
29+
}
30+
31+
__gpu_kernel void logfKernel(const float *X, float *Out, size_t NumElements) {
32+
uint32_t Index =
33+
__gpu_num_threads_x() * __gpu_block_id_x() + __gpu_thread_id_x();
34+
35+
if (Index < NumElements)
36+
Out[Index] = logf(X[Index]);
37+
}

offload/unittests/Conformance/device_code/sin.c

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// \file
10+
/// This file contains the definition of custom command-line argument parsers
11+
/// using llvm::cl.
12+
///
13+
//===----------------------------------------------------------------------===//
14+
15+
#ifndef MATHTEST_COMMANDLINE_HPP
16+
#define MATHTEST_COMMANDLINE_HPP
17+
18+
#include "mathtest/TestConfig.hpp"
19+
20+
#include "llvm/ADT/STLExtras.h"
21+
#include "llvm/ADT/SmallVector.h"
22+
#include "llvm/ADT/StringRef.h"
23+
#include "llvm/Support/CommandLine.h"
24+
25+
#include <string>
26+
27+
namespace llvm {
28+
namespace cl {
29+
30+
struct TestConfigsArg {
31+
enum class Mode { Default, All, Explicit } Mode = Mode::Default;
32+
llvm::SmallVector<mathtest::TestConfig, 4> Explicit;
33+
};
34+
35+
template <> class parser<TestConfigsArg> : public basic_parser<TestConfigsArg> {
36+
public:
37+
parser(Option &O) : basic_parser<TestConfigsArg>(O) {}
38+
39+
static bool isAllowed(const mathtest::TestConfig &Config) {
40+
static const llvm::SmallVector<mathtest::TestConfig, 4> &AllTestConfigs =
41+
mathtest::getAllTestConfigs();
42+
43+
return llvm::is_contained(AllTestConfigs, Config);
44+
}
45+
46+
bool parse(Option &O, StringRef ArgName, StringRef ArgValue,
47+
TestConfigsArg &Val) {
48+
ArgValue = ArgValue.trim();
49+
if (ArgValue.empty())
50+
return O.error(
51+
"Expected '" + getValueName() +
52+
"', but got an empty string. Omit the flag to use defaults");
53+
54+
if (ArgValue.equals_insensitive("all")) {
55+
Val.Mode = TestConfigsArg::Mode::All;
56+
return false;
57+
}
58+
59+
llvm::SmallVector<StringRef, 8> Pairs;
60+
ArgValue.split(Pairs, ',', /*MaxSplit=*/-1, /*KeepEmpty=*/false);
61+
62+
Val.Mode = TestConfigsArg::Mode::Explicit;
63+
Val.Explicit.clear();
64+
65+
for (StringRef Pair : Pairs) {
66+
llvm::SmallVector<StringRef, 2> Parts;
67+
Pair.split(Parts, ':');
68+
69+
if (Parts.size() != 2)
70+
return O.error("Expected '<provider>:<platform>', got '" + Pair + "'");
71+
72+
StringRef Provider = Parts[0].trim();
73+
StringRef Platform = Parts[1].trim();
74+
75+
if (Provider.empty() || Platform.empty())
76+
return O.error("Provider and platform must not be empty in '" + Pair +
77+
"'");
78+
79+
mathtest::TestConfig Config = {Provider.str(), Platform.str()};
80+
if (!isAllowed(Config))
81+
return O.error("Invalid pair '" + Pair + "'");
82+
83+
Val.Explicit.push_back(Config);
84+
}
85+
86+
return false;
87+
}
88+
89+
StringRef getValueName() const override {
90+
return "all|provider:platform[,provider:platform...]";
91+
}
92+
93+
void printOptionDiff(const Option &O, const TestConfigsArg &V, OptVal Default,
94+
size_t GlobalWidth) const {
95+
printOptionNoValue(O, GlobalWidth);
96+
}
97+
};
98+
} // namespace cl
99+
} // namespace llvm
100+
101+
#endif // MATHTEST_COMMANDLINE_HPP
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// \file
10+
/// This file contains the declaration of the command-line options and the main
11+
/// interface for selecting test configurations.
12+
///
13+
//===----------------------------------------------------------------------===//
14+
15+
#ifndef MATHTEST_COMMANDLINEEXTRAS_HPP
16+
#define MATHTEST_COMMANDLINEEXTRAS_HPP
17+
18+
#include "mathtest/CommandLine.hpp"
19+
#include "mathtest/TestConfig.hpp"
20+
21+
#include "llvm/ADT/SmallVector.h"
22+
#include "llvm/Support/CommandLine.h"
23+
24+
namespace mathtest {
25+
namespace cl {
26+
27+
extern llvm::cl::opt<bool> IsVerbose;
28+
29+
namespace detail {
30+
31+
extern llvm::cl::opt<llvm::cl::TestConfigsArg> TestConfigsOpt;
32+
} // namespace detail
33+
34+
const llvm::SmallVector<TestConfig, 4> &getTestConfigs();
35+
} // namespace cl
36+
} // namespace mathtest
37+
38+
#endif // MATHTEST_COMMANDLINEEXTRAS_HPP
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// \file
10+
/// This file contains the definition of the DeviceContext class, which serves
11+
/// as the high-level interface to a particular device (GPU).
12+
///
13+
/// This class provides methods for allocating buffers, loading binaries, and
14+
/// getting and launching kernels on the device.
15+
///
16+
//===----------------------------------------------------------------------===//
17+
18+
#ifndef MATHTEST_DEVICECONTEXT_HPP
19+
#define MATHTEST_DEVICECONTEXT_HPP
20+
21+
#include "mathtest/DeviceResources.hpp"
22+
#include "mathtest/ErrorHandling.hpp"
23+
#include "mathtest/Support.hpp"
24+
25+
#include "llvm/ADT/SetVector.h"
26+
#include "llvm/ADT/StringRef.h"
27+
#include "llvm/Support/Error.h"
28+
29+
#include <cassert>
30+
#include <cstddef>
31+
#include <cstdint>
32+
#include <memory>
33+
#include <tuple>
34+
#include <type_traits>
35+
#include <utility>
36+
37+
namespace mathtest {
38+
39+
const llvm::SetVector<llvm::StringRef> &getPlatforms();
40+
41+
namespace detail {
42+
43+
void allocManagedMemory(ol_device_handle_t DeviceHandle, std::size_t Size,
44+
void **AllocationOut) noexcept;
45+
} // namespace detail
46+
47+
class DeviceContext {
48+
// For simplicity, the current design of this class doesn't have support for
49+
// asynchronous operations and all types of memory allocation.
50+
//
51+
// Other use cases could benefit from operations like enqueued kernel launch
52+
// and enqueued memcpy, as well as device and host memory allocation.
53+
54+
public:
55+
explicit DeviceContext(std::size_t GlobalDeviceId = 0);
56+
57+
explicit DeviceContext(llvm::StringRef Platform, std::size_t DeviceId = 0);
58+
59+
template <typename T>
60+
ManagedBuffer<T> createManagedBuffer(std::size_t Size) const noexcept {
61+
void *UntypedAddress = nullptr;
62+
63+
detail::allocManagedMemory(DeviceHandle, Size * sizeof(T), &UntypedAddress);
64+
T *TypedAddress = static_cast<T *>(UntypedAddress);
65+
66+
return ManagedBuffer<T>(TypedAddress, Size);
67+
}
68+
69+
[[nodiscard]] llvm::Expected<std::shared_ptr<DeviceImage>>
70+
loadBinary(llvm::StringRef Directory, llvm::StringRef BinaryName) const;
71+
72+
template <typename KernelSignature>
73+
[[nodiscard]] llvm::Expected<DeviceKernel<KernelSignature>>
74+
getKernel(const std::shared_ptr<DeviceImage> &Image,
75+
llvm::StringRef KernelName) const {
76+
assert(Image && "Image provided to getKernel is null");
77+
78+
if (Image->DeviceHandle != DeviceHandle)
79+
return llvm::createStringError(
80+
"Image provided to getKernel was created for a different device");
81+
82+
auto ExpectedHandle = getKernelHandle(Image->Handle, KernelName);
83+
84+
if (!ExpectedHandle)
85+
return ExpectedHandle.takeError();
86+
87+
return DeviceKernel<KernelSignature>(Image, *ExpectedHandle);
88+
}
89+
90+
template <typename KernelSignature, typename... ArgTypes>
91+
void launchKernel(DeviceKernel<KernelSignature> Kernel, uint32_t NumGroups,
92+
uint32_t GroupSize, ArgTypes &&...Args) const noexcept {
93+
using ExpectedTypes =
94+
typename FunctionTypeTraits<KernelSignature>::ArgTypesTuple;
95+
using ProvidedTypes = std::tuple<std::decay_t<ArgTypes>...>;
96+
97+
static_assert(std::is_same_v<ExpectedTypes, ProvidedTypes>,
98+
"Argument types provided to launchKernel do not match the "
99+
"kernel's signature");
100+
101+
if (Kernel.Image->DeviceHandle != DeviceHandle)
102+
FATAL_ERROR("Kernel provided to launchKernel was created for a different "
103+
"device");
104+
105+
if constexpr (sizeof...(Args) == 0) {
106+
launchKernelImpl(Kernel.Handle, NumGroups, GroupSize, nullptr, 0);
107+
} else {
108+
auto KernelArgs = makeKernelArgsPack(std::forward<ArgTypes>(Args)...);
109+
110+
static_assert(
111+
(std::is_trivially_copyable_v<std::decay_t<ArgTypes>> && ...),
112+
"Argument types provided to launchKernel must be trivially copyable");
113+
114+
launchKernelImpl(Kernel.Handle, NumGroups, GroupSize, &KernelArgs,
115+
sizeof(KernelArgs));
116+
}
117+
}
118+
119+
[[nodiscard]] llvm::StringRef getName() const noexcept;
120+
121+
[[nodiscard]] llvm::StringRef getPlatform() const noexcept;
122+
123+
private:
124+
[[nodiscard]] llvm::Expected<ol_symbol_handle_t>
125+
getKernelHandle(ol_program_handle_t ProgramHandle,
126+
llvm::StringRef KernelName) const noexcept;
127+
128+
void launchKernelImpl(ol_symbol_handle_t KernelHandle, uint32_t NumGroups,
129+
uint32_t GroupSize, const void *KernelArgs,
130+
std::size_t KernelArgsSize) const noexcept;
131+
132+
std::size_t GlobalDeviceId;
133+
ol_device_handle_t DeviceHandle;
134+
};
135+
} // namespace mathtest
136+
137+
#endif // MATHTEST_DEVICECONTEXT_HPP

0 commit comments

Comments
 (0)