Disclaimer: this is my first PR to LLVM. I'm trying to follow the contribution guide and the conventions i see in other PRs, but if i missed something -- please let me know. ## Summary Fixes #182807. When the SYCL runtime wrapper is loaded on a host without a Level-Zero backend, `getDefaultDevice()` throws an `std::runtime_error`, `catchAll` catches it, and calls `abort()`, which results in a "PLEASE submit a bug report" stack dump, which is not correct for this kind of crash. With this change `catchAll` now writes to stderr and terminates via `std::exit(EXIT_FAILURE)`, yielding a clean exit code 1 with no crash dump. The "getDefaultDevice failed" message is also replaced with a (hopefully) better one. ## Implementation notes The runners in `mlir/lib/ExecutionEngine/` don't have much consistency with regards as to how to output diagnostics. 2 files use `llvm::outs/errs`, 9 files use `std::cout/cerr`, 8 files use `fprintf`. I left `fprintf` in place, since it was used in that module before and it's also used by CUDA and ROCm runners (the most dominant ones in the folder). I'm open to changing it to something else if needed. ## Notes for reviewers 1. Why this fix strategy doesn't match the CUDA/ROCm wrappers' print-warning-and-continue style: The `print-and-continue` approach was tried and it produces worse behavior with SYCL wrapper. The entry points would return `nullptr` on init failure, but the MLIR JIT runner does not null-check those returns, so the `nullptr` is dereferenced inside a later SYCL call and produces another SIGSEGV and the same crash dump this patch is trying to avoid. Making that style work would require null-checks across the downstream wrappers. We can go that way if desired, but i opted to start with a simpler fix. 2. Why `L0_SAFE_CALL` (`SyclRuntimeWrappers.cpp`, line 40) is left untouched: it's a deliberate decision, as `L0_SAFE_CALL` is supposed to abort on Level-Zero errors (kernel launch, OOM, etc). I can reconsider if there are some arguments for updating error handling strategy in `L0_SAFE_CALL` too, I just don't see any yet. 3. There's no unit test with this change, as I wasn't able to find a test that would test this logic (i guess this kind of logic isn't usually tested with unit tests anyway). I did test it manually, though, for both positive and negative cases.
219 lines
7.2 KiB
C++
219 lines
7.2 KiB
C++
//===- SyclRuntimeWrappers.cpp - MLIR SYCL wrapper library ------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Implements wrappers around the sycl runtime library with C linkage
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <level_zero/ze_api.h>
|
|
#include <sycl/ext/oneapi/backend/level_zero.hpp>
|
|
#include <sycl/sycl.hpp>
|
|
|
|
#ifdef _WIN32
|
|
#define SYCL_RUNTIME_EXPORT __declspec(dllexport)
|
|
#else
|
|
#define SYCL_RUNTIME_EXPORT
|
|
#endif // _WIN32
|
|
|
|
namespace {
|
|
|
|
template <typename F>
|
|
auto catchAll(F &&func) {
|
|
try {
|
|
return func();
|
|
} catch (const std::exception &e) {
|
|
fprintf(stderr, "SYCL runtime error: %s\n", e.what());
|
|
fflush(stderr);
|
|
std::exit(EXIT_FAILURE);
|
|
} catch (...) {
|
|
fprintf(stderr, "SYCL runtime error: unknown exception was thrown\n");
|
|
fflush(stderr);
|
|
std::exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
#define L0_SAFE_CALL(call) \
|
|
{ \
|
|
ze_result_t status = (call); \
|
|
if (status != ZE_RESULT_SUCCESS) { \
|
|
fprintf(stderr, "L0 error %d\n", status); \
|
|
fflush(stderr); \
|
|
abort(); \
|
|
} \
|
|
}
|
|
|
|
} // namespace
|
|
|
|
static sycl::device getDefaultDevice() {
|
|
static sycl::device syclDevice;
|
|
static bool isDeviceInitialised = false;
|
|
if (!isDeviceInitialised) {
|
|
auto platformList = sycl::platform::get_platforms();
|
|
for (const auto &platform : platformList) {
|
|
auto platformName = platform.get_info<sycl::info::platform::name>();
|
|
bool isLevelZero = platformName.find("Level-Zero") != std::string::npos;
|
|
if (!isLevelZero)
|
|
continue;
|
|
|
|
syclDevice = platform.get_devices()[0];
|
|
isDeviceInitialised = true;
|
|
return syclDevice;
|
|
}
|
|
throw std::runtime_error(
|
|
"no Level-Zero SYCL platform found; the MLIR SYCL runtime wrapper "
|
|
"currently requires a Level-Zero backend");
|
|
} else {
|
|
return syclDevice;
|
|
}
|
|
}
|
|
|
|
static sycl::context getDefaultContext() {
|
|
static sycl::context syclContext{getDefaultDevice()};
|
|
return syclContext;
|
|
}
|
|
|
|
static void *allocDeviceMemory(sycl::queue *queue, size_t size, bool isShared) {
|
|
void *memPtr = nullptr;
|
|
if (isShared) {
|
|
memPtr = sycl::aligned_alloc_shared(64, size, getDefaultDevice(),
|
|
getDefaultContext());
|
|
} else {
|
|
memPtr = sycl::aligned_alloc_device(64, size, *queue);
|
|
}
|
|
if (memPtr == nullptr) {
|
|
throw std::runtime_error("mem allocation failed!");
|
|
}
|
|
return memPtr;
|
|
}
|
|
|
|
static void deallocDeviceMemory(sycl::queue *queue, void *ptr) {
|
|
sycl::free(ptr, *queue);
|
|
}
|
|
|
|
static ze_module_handle_t loadModule(const void *data, size_t dataSize) {
|
|
assert(data);
|
|
ze_module_handle_t zeModule;
|
|
ze_module_desc_t desc = {ZE_STRUCTURE_TYPE_MODULE_DESC,
|
|
nullptr,
|
|
ZE_MODULE_FORMAT_IL_SPIRV,
|
|
dataSize,
|
|
(const uint8_t *)data,
|
|
nullptr,
|
|
nullptr};
|
|
auto zeDevice = sycl::get_native<sycl::backend::ext_oneapi_level_zero>(
|
|
getDefaultDevice());
|
|
auto zeContext = sycl::get_native<sycl::backend::ext_oneapi_level_zero>(
|
|
getDefaultContext());
|
|
L0_SAFE_CALL(zeModuleCreate(zeContext, zeDevice, &desc, &zeModule, nullptr));
|
|
return zeModule;
|
|
}
|
|
|
|
static sycl::kernel *getKernel(ze_module_handle_t zeModule, const char *name) {
|
|
assert(zeModule);
|
|
assert(name);
|
|
ze_kernel_handle_t zeKernel;
|
|
ze_kernel_desc_t desc = {};
|
|
desc.pKernelName = name;
|
|
|
|
L0_SAFE_CALL(zeKernelCreate(zeModule, &desc, &zeKernel));
|
|
sycl::kernel_bundle<sycl::bundle_state::executable> kernelBundle =
|
|
sycl::make_kernel_bundle<sycl::backend::ext_oneapi_level_zero,
|
|
sycl::bundle_state::executable>(
|
|
{zeModule}, getDefaultContext());
|
|
|
|
auto kernel = sycl::make_kernel<sycl::backend::ext_oneapi_level_zero>(
|
|
{kernelBundle, zeKernel}, getDefaultContext());
|
|
return new sycl::kernel(kernel);
|
|
}
|
|
|
|
static void launchKernel(sycl::queue *queue, sycl::kernel *kernel, size_t gridX,
|
|
size_t gridY, size_t gridZ, size_t blockX,
|
|
size_t blockY, size_t blockZ, size_t sharedMemBytes,
|
|
void **params, size_t paramsCount) {
|
|
auto syclGlobalRange =
|
|
sycl::range<3>(blockZ * gridZ, blockY * gridY, blockX * gridX);
|
|
auto syclLocalRange = sycl::range<3>(blockZ, blockY, blockX);
|
|
sycl::nd_range<3> syclNdRange(syclGlobalRange, syclLocalRange);
|
|
|
|
queue->submit([&](sycl::handler &cgh) {
|
|
for (size_t i = 0; i < paramsCount; i++) {
|
|
cgh.set_arg(static_cast<uint32_t>(i), *(static_cast<void **>(params[i])));
|
|
}
|
|
cgh.parallel_for(syclNdRange, *kernel);
|
|
});
|
|
}
|
|
|
|
// Wrappers
|
|
|
|
extern "C" SYCL_RUNTIME_EXPORT sycl::queue *mgpuStreamCreate() {
|
|
|
|
return catchAll([&]() {
|
|
sycl::queue *queue =
|
|
new sycl::queue(getDefaultContext(), getDefaultDevice());
|
|
return queue;
|
|
});
|
|
}
|
|
|
|
extern "C" SYCL_RUNTIME_EXPORT void mgpuStreamDestroy(sycl::queue *queue) {
|
|
catchAll([&]() { delete queue; });
|
|
}
|
|
|
|
extern "C" SYCL_RUNTIME_EXPORT void *
|
|
mgpuMemAlloc(uint64_t size, sycl::queue *queue, bool isShared) {
|
|
return catchAll([&]() {
|
|
return allocDeviceMemory(queue, static_cast<size_t>(size), true);
|
|
});
|
|
}
|
|
|
|
extern "C" SYCL_RUNTIME_EXPORT void mgpuMemFree(void *ptr, sycl::queue *queue) {
|
|
catchAll([&]() {
|
|
if (ptr) {
|
|
deallocDeviceMemory(queue, ptr);
|
|
}
|
|
});
|
|
}
|
|
|
|
extern "C" SYCL_RUNTIME_EXPORT ze_module_handle_t
|
|
mgpuModuleLoad(const void *data, size_t gpuBlobSize) {
|
|
return catchAll([&]() { return loadModule(data, gpuBlobSize); });
|
|
}
|
|
|
|
extern "C" SYCL_RUNTIME_EXPORT sycl::kernel *
|
|
mgpuModuleGetFunction(ze_module_handle_t module, const char *name) {
|
|
return catchAll([&]() { return getKernel(module, name); });
|
|
}
|
|
|
|
extern "C" SYCL_RUNTIME_EXPORT void
|
|
mgpuLaunchKernel(sycl::kernel *kernel, size_t gridX, size_t gridY, size_t gridZ,
|
|
size_t blockX, size_t blockY, size_t blockZ,
|
|
size_t sharedMemBytes, sycl::queue *queue, void **params,
|
|
void ** /*extra*/, size_t paramsCount) {
|
|
return catchAll([&]() {
|
|
launchKernel(queue, kernel, gridX, gridY, gridZ, blockX, blockY, blockZ,
|
|
sharedMemBytes, params, paramsCount);
|
|
});
|
|
}
|
|
|
|
extern "C" SYCL_RUNTIME_EXPORT void mgpuStreamSynchronize(sycl::queue *queue) {
|
|
|
|
catchAll([&]() { queue->wait(); });
|
|
}
|
|
|
|
extern "C" SYCL_RUNTIME_EXPORT void
|
|
mgpuModuleUnload(ze_module_handle_t module) {
|
|
|
|
catchAll([&]() { L0_SAFE_CALL(zeModuleDestroy(module)); });
|
|
}
|
|
|
|
extern "C" SYCL_RUNTIME_EXPORT void
|
|
mgpuMemcpy(void *dst, void *src, size_t sizeBytes, sycl::queue *queue) {
|
|
catchAll([&]() { queue->memcpy(dst, src, sizeBytes).wait(); });
|
|
}
|