From 66030b7afd244ce72f73a96d0b716d6eb8477bb4 Mon Sep 17 00:00:00 2001 From: "Kenneth Benzie (Benie)" Date: Mon, 1 Apr 2024 15:11:30 +0100 Subject: [PATCH] [Loader] Refactor ONEAPI_DEVICE_SELECTOR, add tests This patch resolves a number of issues with the extant implementation of the `ONEAPI_DEVICE_SELECTOR`: * Filtering only being applied to devices within a platform, resulting in an incorrect list of devices being returned in a multi-platform, multi-device context. * Device indices being counted only inside a platform, not globally, resulting in multiple devices with 0 index in the presence of multiple platforms. * Lack of testing for non-hardware dependent configurations, e.g. unable to emulate filtering against hypothetical sets of devices. * Parsing the ONEAPI_DEVICE_SELECTOR string each time `urDeviceGetSelected()` was called. This new implementation was written with testing of the individaul terms in the ONEAPI_DEVICE_SELECTOR BNF grammer as a design requirement. It decouples the enumeration of devices from the filtering logic. Both parsing and device enumeration are moved much earlier, into loader initialization, and performed only once. --- source/loader/CMakeLists.txt | 2 + source/loader/device_selector/CMakeLists.txt | 23 + source/loader/device_selector/backend.hpp | 89 +++ source/loader/device_selector/descriptor.hpp | 46 ++ source/loader/device_selector/device.hpp | 294 +++++++ source/loader/device_selector/filter.hpp | 73 ++ source/loader/device_selector/matcher.hpp | 121 +++ source/loader/device_selector/selector.cpp | 0 source/loader/ur_adapter_registry.hpp | 8 +- source/loader/ur_lib.cpp | 762 ++++--------------- source/loader/ur_lib.hpp | 13 + source/loader/ur_loader.hpp | 8 + test/loader/CMakeLists.txt | 1 + test/loader/device_selector/CMakeLists.txt | 16 + test/loader/device_selector/backend.cpp | 89 +++ test/loader/device_selector/common.hpp | 13 + test/loader/device_selector/device.cpp | 290 +++++++ test/loader/device_selector/filter.cpp | 116 +++ test/loader/device_selector/matcher.cpp | 193 +++++ test/loader/device_selector/parser.cpp | 37 + tools/urinfo/urinfo.cpp | 8 +- 21 files changed, 1604 insertions(+), 598 deletions(-) create mode 100644 source/loader/device_selector/CMakeLists.txt create mode 100644 source/loader/device_selector/backend.hpp create mode 100644 source/loader/device_selector/descriptor.hpp create mode 100644 source/loader/device_selector/device.hpp create mode 100644 source/loader/device_selector/filter.hpp create mode 100644 source/loader/device_selector/matcher.hpp create mode 100644 source/loader/device_selector/selector.cpp create mode 100644 test/loader/device_selector/CMakeLists.txt create mode 100644 test/loader/device_selector/backend.cpp create mode 100644 test/loader/device_selector/common.hpp create mode 100644 test/loader/device_selector/device.cpp create mode 100644 test/loader/device_selector/filter.cpp create mode 100644 test/loader/device_selector/matcher.cpp create mode 100644 test/loader/device_selector/parser.cpp diff --git a/source/loader/CMakeLists.txt b/source/loader/CMakeLists.txt index 474fa6c79b..22625d7969 100644 --- a/source/loader/CMakeLists.txt +++ b/source/loader/CMakeLists.txt @@ -3,6 +3,8 @@ # See LICENSE.TXT # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +add_subdirectory(device_selector) + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/UrLoaderVersion.rc.in ${CMAKE_CURRENT_BINARY_DIR}/UrLoaderVersion.rc diff --git a/source/loader/device_selector/CMakeLists.txt b/source/loader/device_selector/CMakeLists.txt new file mode 100644 index 0000000000..d6db01d164 --- /dev/null +++ b/source/loader/device_selector/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (C) 2022 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 + +add_ur_library(ur_device_selector STATIC + backend.hpp + device.hpp + filter.hpp + selector.cpp # TODO: Remove this and make it header only? + matcher.hpp +) + +add_library(${PROJECT_NAME}::device_selector ALIAS ur_device_selector) + +target_include_directories(ur_device_selector PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/.. +) + +target_link_libraries(ur_device_selector PUBLIC + ${PROJECT_NAME}::headers + ${PROJECT_NAME}::common +) diff --git a/source/loader/device_selector/backend.hpp b/source/loader/device_selector/backend.hpp new file mode 100644 index 0000000000..856e28dd69 --- /dev/null +++ b/source/loader/device_selector/backend.hpp @@ -0,0 +1,89 @@ +// 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 + +#pragma once + +#include "descriptor.hpp" +#include "ur_api.h" +#include +#include +#include +#include + +namespace ur::device_selector { + +struct BackendMatcher { + /// @brief Initilalize the backend matcher from a string in a case + /// insensitive way. + /// @param[in] backend Backend filter string. + /// @return Returns a diagnostic if an invalid backend is found. + std::optional init(std::string_view str) { + if (str.empty()) { + return Diagnostic("empty backend"); + } + std::string lower(str); + std::transform(lower.cbegin(), lower.cend(), lower.begin(), + [](char c) { return std::tolower(c); }); + if (lower == "*") { + matches.insert(matches.begin(), { + UR_PLATFORM_BACKEND_UNKNOWN, + UR_PLATFORM_BACKEND_LEVEL_ZERO, + UR_PLATFORM_BACKEND_OPENCL, + UR_PLATFORM_BACKEND_CUDA, + UR_PLATFORM_BACKEND_HIP, + UR_PLATFORM_BACKEND_OPENCL, + UR_PLATFORM_BACKEND_NATIVE_CPU, + }); + } else if (lower == "opencl") { + matches.push_back(UR_PLATFORM_BACKEND_OPENCL); + } else if (lower == "level_zero" || lower == "ext_oneapi_level_zero") { + matches.push_back(UR_PLATFORM_BACKEND_LEVEL_ZERO); + } else if (lower == "cuda" || lower == "ext_oneapi_cuda") { + matches.push_back(UR_PLATFORM_BACKEND_CUDA); + } else if (lower == "hip" || lower == "ext_oneapi_hip") { + matches.push_back(UR_PLATFORM_BACKEND_HIP); + } else if (lower == "native_cpu") { + matches.push_back(UR_PLATFORM_BACKEND_NATIVE_CPU); + } else { + std::string diagnostic = "invalid backend: '"; + diagnostic.append(str); + return Diagnostic(diagnostic + "'"); + } + return std::nullopt; + } + + friend bool operator==(const BackendMatcher &matcher, + const Descriptor &descriptor) { + return std::any_of( + matcher.matches.begin(), matcher.matches.end(), + [&descriptor](const ur_platform_backend_t &backendMatch) { + return backendMatch == descriptor.backend; + }); + } + + friend bool operator==(const Descriptor &backend, + const BackendMatcher &matcher) { + return matcher == backend; + } + + friend bool operator!=(const BackendMatcher &matcher, + const Descriptor &descriptor) { + return std::none_of( + matcher.matches.begin(), matcher.matches.end(), + [&descriptor](const ur_platform_backend_t &backendMatch) { + return backendMatch == descriptor.backend; + }); + } + + friend bool operator!=(const Descriptor &backend, + const BackendMatcher &matcher) { + return matcher != backend; + } + + private: + std::vector matches; +}; + +} // namespace ur::device_selector diff --git a/source/loader/device_selector/descriptor.hpp b/source/loader/device_selector/descriptor.hpp new file mode 100644 index 0000000000..a27f863806 --- /dev/null +++ b/source/loader/device_selector/descriptor.hpp @@ -0,0 +1,46 @@ +// 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 + +#pragma once + +#include "ur_api.h" +#include +#include + +namespace ur::device_selector { + +struct Diagnostic { + Diagnostic(std::string message) : message(message) {} + std::string message; +}; + +struct Descriptor { + Descriptor() = default; + + Descriptor(const Descriptor &other) + : backend(other.backend), type(other.type), index(other.index), + subIndex(other.subIndex), subSubIndex(other.subSubIndex) {} + + Descriptor(ur_platform_backend_t backend, ur_device_type_t type, + uint32_t index) + : backend(backend), type(type), index(index) {} + Descriptor(ur_platform_backend_t backend, ur_device_type_t type, + uint32_t index, uint32_t subDeviceIndex) + : backend(backend), type(type), index(index), subIndex(subDeviceIndex) { + } + Descriptor(ur_platform_backend_t backend, ur_device_type_t type, + uint32_t index, uint32_t subDeviceIndex, + uint32_t subSubDeviceIndex) + : backend(backend), type(type), index(index), subIndex(subDeviceIndex), + subSubIndex(subSubDeviceIndex) {} + + ur_platform_backend_t backend; + ur_device_type_t type; + uint32_t index = 0; + std::optional subIndex; + std::optional subSubIndex; +}; + +} // namespace ur::device_selector diff --git a/source/loader/device_selector/device.hpp b/source/loader/device_selector/device.hpp new file mode 100644 index 0000000000..28b242722d --- /dev/null +++ b/source/loader/device_selector/device.hpp @@ -0,0 +1,294 @@ +// 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 "descriptor.hpp" +#include "ur_api.h" +#include +#include +#include +#include + +namespace ur::device_selector { + +namespace detail { + +inline std::vector split(std::string_view str, char delim) { + std::vector parts; + size_t start = 0, end = 0; + while ((end = str.find(delim, start)) != std::string_view::npos) { + parts.push_back(str.substr(start, end - start)); + start = end + 1; + } + parts.push_back(str.substr(start)); + return parts; +} + +} // namespace detail + +struct DeviceMatcher { + std::optional init(std::string_view str) { + if (str.empty()) { + return Diagnostic("empty device"); + } + + // Make all characters lower case for case insensitive matching + std::string lower(str); + std::transform(lower.cbegin(), lower.cend(), lower.begin(), + [](char c) { return std::tolower(c); }); + + if (lower.front() == '*') { + if (lower.size() == 1) { + descriptor.index = ANY_INDEX; + kind = DeviceMatchKind::INDEX; + } else if (lower == "*.*") { + descriptor.index = ANY_INDEX; + descriptor.subIndex = ANY_INDEX; + kind = DeviceMatchKind::SUB_INDEX; + } else if (lower == "*.*.*") { + descriptor.index = ANY_INDEX; + descriptor.subIndex = ANY_INDEX; + descriptor.subSubIndex = ANY_INDEX; + kind = DeviceMatchKind::SUB_SUB_INDEX; + } else { + std::string message = "invalid device: '"; + message.append(str); + return Diagnostic(message + "'"); + } + + } else if (lower.front() >= '0' && lower.front() <= '9') { + auto parts = detail::split(lower, '.'); + + // We can assume parts is at least 1 element long due to empty + // string check above + uint32_t index = 0; + auto result = toUInt32(std::string(parts[0]), &index); + if (result.has_value()) { + return result.value(); + } + descriptor.index = index; + kind = DeviceMatchKind::INDEX; + + if (parts.size() >= 2) { + if (parts[1] == "*") { + descriptor.subIndex = ANY_INDEX; + } else { + uint32_t subIndex = 0; + auto result = toUInt32(std::string(parts[1]), &subIndex); + if (result.has_value()) { + auto message = result->message + " in '"; + message.append(str); + return message + "'"; + } + descriptor.subIndex = subIndex; + } + kind = DeviceMatchKind::SUB_INDEX; + } + + if (parts.size() == 3) { + if (parts[2] == "*") { + descriptor.subSubIndex = ANY_INDEX; + } else { + uint32_t subSubIndex = 0; + auto result = toUInt32(std::string(parts[2]), &subSubIndex); + if (result.has_value()) { + auto message = result->message + " in '"; + message.append(str); + return message + "'"; + } + descriptor.subSubIndex = subSubIndex; + } + kind = DeviceMatchKind::SUB_SUB_INDEX; + + } else if (parts.size() > 3) { + std::string message = "invalid device: '"; + message.append(str); + return Diagnostic(message + "'"); + } + + } else if (lower == "gpu") { + descriptor.type = UR_DEVICE_TYPE_GPU; + kind = DeviceMatchKind::TYPE; + } else if (lower == "cpu") { + descriptor.type = UR_DEVICE_TYPE_CPU; + kind = DeviceMatchKind::TYPE; + } else if (lower == "fpga") { + descriptor.type = UR_DEVICE_TYPE_FPGA; + kind = DeviceMatchKind::TYPE; + } else if (lower == "mca") { + descriptor.type = UR_DEVICE_TYPE_MCA; + kind = DeviceMatchKind::TYPE; + } else if (lower == "npu") { + descriptor.type = UR_DEVICE_TYPE_VPU; + kind = DeviceMatchKind::TYPE; + } else { + std::string message = "invalid device: '"; + message.append(str); + return Diagnostic(message + "'"); + } + + return std::nullopt; + } + + bool isSubDeviceMatcher() const { + return kind == DeviceMatchKind::SUB_INDEX || isSubSubDeviceMatcher(); + } + + bool isSubSubDeviceMatcher() const { + return kind == DeviceMatchKind::SUB_SUB_INDEX; + } + + friend bool operator==(const DeviceMatcher &matcher, + Descriptor descriptor) { + switch (matcher.kind) { + case DeviceMatchKind::TYPE: + return matcher.descriptor.type == descriptor.type; + + case DeviceMatchKind::INDEX: + return matcher.descriptor.index == ANY_INDEX + ? true + : matcher.descriptor.index == descriptor.index; + + case DeviceMatchKind::SUB_INDEX: + if (!descriptor.subIndex.has_value()) { + return false; + } + return (matcher.descriptor.index == ANY_INDEX + ? true + : matcher.descriptor.index == descriptor.index) && + (matcher.descriptor.subIndex.value() == ANY_INDEX + ? true + : matcher.descriptor.subIndex.value() == + descriptor.subIndex.value()); + + case DeviceMatchKind::SUB_SUB_INDEX: + if (!descriptor.subIndex.has_value() || + !descriptor.subSubIndex.has_value()) { + return false; + } + return (matcher.descriptor.index == ANY_INDEX + ? true + : matcher.descriptor.index == descriptor.index) && + (matcher.descriptor.subIndex.value() == ANY_INDEX + ? true + : matcher.descriptor.subIndex.value() == + descriptor.subIndex.value()) && + (matcher.descriptor.subSubIndex.value() == ANY_INDEX + ? true + : matcher.descriptor.subSubIndex.value() == + descriptor.subSubIndex.value()); + + default: + return false; + } + } + + friend bool operator==(Descriptor backend, const DeviceMatcher &matcher) { + return matcher == backend; + } + + friend bool operator!=(const DeviceMatcher &matcher, + Descriptor descriptor) { + return !(matcher == descriptor); + } + + friend bool operator!=(Descriptor backend, const DeviceMatcher &matcher) { + return matcher != backend; + } + + private: + static constexpr uint32_t ANY_INDEX = 0xFFFFFFFF; + + enum class DeviceMatchKind : uint8_t { + NONE = 0, + TYPE, + INDEX, + SUB_INDEX, + SUB_SUB_INDEX, + }; + + [[nodiscard]] std::optional toUInt32(const std::string &str, + uint32_t *value) { + try { + size_t pos; + uint32_t result = std::stoul(str, &pos, 10); + if (str.size() == pos) { + *value = result; + return std::nullopt; + } + } catch (const std::invalid_argument &) { + } catch (const std::out_of_range &) { + } + std::string message = "invalid number: '"; + message.append(str); + message += "'"; + return Diagnostic(message); + } + + Descriptor descriptor; + DeviceMatchKind kind; +}; + +struct DevicesMatcher { + std::optional init(std::string_view str) { + auto parts = detail::split(str, ','); + for (const auto &part : parts) { + DeviceMatcher matcher; + auto result = matcher.init(part); + if (result.has_value()) { + if (parts.size() > 1) { + auto message = result.value().message + " in: '"; + message.append(str); + return message + "'"; + } else { + return result.value(); + } + } + matches.push_back(matcher); + } + return std::nullopt; + } + + bool isSubDeviceMatcher() const { + return std::any_of( + matches.begin(), matches.end(), + [](const auto &matcher) { return matcher.isSubDeviceMatcher(); }); + } + + bool isSubSubDeviceMatcher() const { + return std::any_of(matches.begin(), matches.end(), + [](const auto &matcher) { + return matcher.isSubSubDeviceMatcher(); + }); + } + + friend bool operator==(const DevicesMatcher &matcher, + const Descriptor &descriptor) { + return std::any_of( + matcher.matches.begin(), matcher.matches.end(), + [&](const auto &matcher) { return matcher == descriptor; }); + } + + friend bool operator==(const Descriptor &descriptor, + const DevicesMatcher &matcher) { + return matcher == descriptor; + } + + friend bool operator!=(const DevicesMatcher &matcher, + const Descriptor &descriptor) { + return std::any_of( + matcher.matches.begin(), matcher.matches.end(), + [&](const auto &matcher) { return matcher != descriptor; }); + } + + friend bool operator!=(const Descriptor &descriptor, + const DevicesMatcher &matcher) { + return matcher != descriptor; + } + + private: + std::vector matches; +}; + +} // namespace ur::device_selector diff --git a/source/loader/device_selector/filter.hpp b/source/loader/device_selector/filter.hpp new file mode 100644 index 0000000000..48d6c2110f --- /dev/null +++ b/source/loader/device_selector/filter.hpp @@ -0,0 +1,73 @@ +// 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 + +#pragma once + +#include "device_selector/backend.hpp" +#include "device_selector/device.hpp" + +namespace ur::device_selector { + +struct FilterMatcher { + std::optional init(std::string_view filter) { + if (filter.empty()) { + return Diagnostic("empty filter"); + } + + auto pos = filter.find(':'); + if (pos == std::string_view::npos) { + std::string message = "invalid filter: '"; + message.append(filter); + message += "'"; + return Diagnostic(message); + } + + if (auto result = backendMatcher.init(filter.substr(0, pos))) { + return *result; + } + + if (auto result = devicesMatcher.init(filter.substr(pos + 1))) { + return *result; + } + + return std::nullopt; + } + + bool isSubDeviceMatcher() const { + return devicesMatcher.isSubDeviceMatcher(); + } + + bool isSubSubDeviceMatcher() const { + return devicesMatcher.isSubSubDeviceMatcher(); + } + + friend bool operator==(const FilterMatcher &matcher, + const Descriptor &descriptor) { + return matcher.backendMatcher == descriptor && + matcher.devicesMatcher == descriptor; + } + + friend bool operator==(const Descriptor &descriptor, + const FilterMatcher &matcher) { + return matcher == descriptor; + } + + friend bool operator!=(const FilterMatcher &matcher, + const Descriptor &descriptor) { + return matcher.backendMatcher != descriptor || + matcher.devicesMatcher != descriptor; + } + + friend bool operator!=(const Descriptor &descriptor, + const FilterMatcher &matcher) { + return matcher != descriptor; + } + + private: + BackendMatcher backendMatcher; + DevicesMatcher devicesMatcher; +}; + +} // namespace ur::device_selector diff --git a/source/loader/device_selector/matcher.hpp b/source/loader/device_selector/matcher.hpp new file mode 100644 index 0000000000..2d13bf73ed --- /dev/null +++ b/source/loader/device_selector/matcher.hpp @@ -0,0 +1,121 @@ +// 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 + +#pragma once + +#include "device_selector/descriptor.hpp" +#include "device_selector/filter.hpp" +#include +#include +#include + +namespace ur::device_selector { + +struct Matcher { + Matcher() = default; + + std::optional init(std::string_view selector) { + if (selector.empty()) { + return Diagnostic("empty selector"); + } + + auto parts = detail::split(selector, ';'); + for (const auto &part : parts) { + if (parts.empty()) { + std::string message = "empty filter: '"; + message.append(part); + return Diagnostic(message + "'"); + } + + if (part[0] == '!') { + FilterMatcher discardFilter; + if (auto error = discardFilter.init(part.substr(1, -1))) { + return error->message; + } + discardFilters.push_back(discardFilter); + } else { + if (!discardFilters.empty()) { + std::string message = + "accept filters must appear before discard filters: '"; + message.append(part); + return Diagnostic(message + "'"); + } + FilterMatcher acceptFilter; + if (auto error = acceptFilter.init(part)) { + return error->message; + } + acceptFilters.push_back(acceptFilter); + } + } + + if (acceptFilters.empty() && !discardFilters.empty()) { + // Add an implicit accept all filter if only discard filters were + // specified by the user + FilterMatcher matcher; + matcher.init("*:*"); + acceptFilters.push_back(matcher); + } + + return std::nullopt; + } + + bool isSubDeviceMatcher() const { + auto isSubDeviceMatcher = [](const auto &matcher) { + return matcher.isSubDeviceMatcher(); + }; + return std::any_of(acceptFilters.begin(), acceptFilters.end(), + isSubDeviceMatcher) || + std::any_of(discardFilters.begin(), discardFilters.end(), + isSubDeviceMatcher); + } + + bool isSubSubDeviceMatcher() const { + auto isSubSubDeviceMatcher = [](const auto &filter) { + return filter.isSubSubDeviceMatcher(); + }; + return std::any_of(acceptFilters.begin(), acceptFilters.end(), + isSubSubDeviceMatcher) || + std::any_of(discardFilters.begin(), discardFilters.end(), + isSubSubDeviceMatcher); + } + + friend bool operator==(const Matcher &matcher, + const Descriptor &descriptor) { + auto isEqual = [&descriptor](const auto &filter) { + return filter == descriptor; + }; + return std::any_of(matcher.acceptFilters.begin(), + matcher.acceptFilters.end(), isEqual) && + std::none_of(matcher.discardFilters.begin(), + matcher.discardFilters.end(), isEqual); + } + + friend bool operator==(const Descriptor &descriptor, + const Matcher &matcher) { + return matcher == descriptor; + } + + friend bool operator!=(const Matcher &matcher, + const Descriptor &descriptor) { + auto isEqual = [&descriptor](const auto &filter) { + return filter == descriptor; + }; + return std::any_of(matcher.discardFilters.begin(), + matcher.discardFilters.end(), isEqual) || + std::none_of(matcher.acceptFilters.begin(), + matcher.acceptFilters.end(), isEqual); + } + + friend bool operator!=(const Descriptor &descriptor, + const Matcher &matcher) { + return matcher != descriptor; + } + + private: + std::vector acceptFilters; + std::vector discardFilters; +}; + +} // namespace ur::device_selector diff --git a/source/loader/device_selector/selector.cpp b/source/loader/device_selector/selector.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/source/loader/ur_adapter_registry.hpp b/source/loader/ur_adapter_registry.hpp index 3cfac34647..060a5ae8a9 100644 --- a/source/loader/ur_adapter_registry.hpp +++ b/source/loader/ur_adapter_registry.hpp @@ -114,11 +114,13 @@ class AdapterRegistry { // to load the adapter. std::vector> adaptersLoadPaths; - static constexpr std::array knownAdapterNames{ + static constexpr std::array knownAdapterNames{ MAKE_LIBRARY_NAME("ur_adapter_level_zero", "0"), - MAKE_LIBRARY_NAME("ur_adapter_hip", "0"), MAKE_LIBRARY_NAME("ur_adapter_opencl", "0"), - MAKE_LIBRARY_NAME("ur_adapter_cuda", "0")}; + MAKE_LIBRARY_NAME("ur_adapter_cuda", "0"), + MAKE_LIBRARY_NAME("ur_adapter_hip", "0"), + MAKE_LIBRARY_NAME("ur_adapter_native_cpu", "0"), + }; std::optional> getEnvAdapterSearchPaths() { std::optional> pathStringsOpt; diff --git a/source/loader/ur_lib.cpp b/source/loader/ur_lib.cpp index d2ed4853a8..7cd2da5434 100644 --- a/source/loader/ur_lib.cpp +++ b/source/loader/ur_lib.cpp @@ -15,14 +15,14 @@ // (not quite sure why windows.h is being included here) #ifndef NOMINMAX #define NOMINMAX +#include "ur_util.hpp" #endif // !NOMINMAX -#include "ur_lib.hpp" #include "logger/ur_logger.hpp" +#include "ur_lib.hpp" #include "ur_loader.hpp" -#include // for std::memcpy -#include +#include namespace ur_lib { /////////////////////////////////////////////////////////////////////////////// @@ -91,6 +91,19 @@ __urdlllocal ur_result_t context_t::Init( result = urLoaderInit(); } + if (auto deviceSelector = ur_getenv("ONEAPI_DEVICE_SELECTOR")) { + deviceSelectorEnabled = true; + if (auto error = matcher.init(deviceSelector.value())) { + std::fprintf(stderr, "error: ONEAPI_DEVICE_SELECTOR: %s\n", + error->message.c_str()); + // TODO: Do something here, propogate the error up the stack so + // SYCL can throw an exception. + } + if (auto error = enumerateDevices()) { + return error; + } + } + if (hLoaderConfig) { codelocData = hLoaderConfig->codelocData; enabledLayerNames.merge(hLoaderConfig->getEnabledLayerNames()); @@ -103,6 +116,119 @@ __urdlllocal ur_result_t context_t::Init( return result; } +ur_result_t context_t::enumerateDevices() { + uint32_t numAdapters; + if (auto error = urAdapterGet(0, nullptr, &numAdapters)) { + return error; + } + std::vector adapters(numAdapters); + if (auto error = urAdapterGet(numAdapters, adapters.data(), nullptr)) { + return error; + } + logger::debug("{}: found {} adapters", __FUNCTION__, numAdapters); + + uint32_t numPlatforms; + if (auto error = urPlatformGet(adapters.data(), numAdapters, 0, nullptr, + &numPlatforms)) { + return error; + } + if (numPlatforms == 0) { + return UR_RESULT_SUCCESS; + } + std::vector platforms(numPlatforms); + if (auto error = urPlatformGet(adapters.data(), numAdapters, numPlatforms, + platforms.data(), nullptr)) { + return error; + } + logger::debug("{}: found {} platforms", __FUNCTION__, numPlatforms); + + // Keep track of device indicies for each platform backend type to ensure + // global ordering of devices can be maintained + std::unordered_map deviceIndexCounters; + + for (auto hPlatform : platforms) { + ur_platform_backend_t platformBackend; + if (auto error = urPlatformGetInfo(hPlatform, UR_PLATFORM_INFO_BACKEND, + sizeof(ur_platform_backend_t), + &platformBackend, nullptr)) { + return error; + } + logger::debug("{}: platform {} backend {}", __FUNCTION__, hPlatform, + platformBackend); + + if (deviceIndexCounters.find(platformBackend) == + deviceIndexCounters.end()) { + deviceIndexCounters[platformBackend] = 0; + } + + uint32_t numDevices; + if (auto error = urDeviceGet(hPlatform, UR_DEVICE_TYPE_ALL, 0, nullptr, + &numDevices)) { + return error; + } + std::vector devices(numDevices); + if (auto error = urDeviceGet(hPlatform, UR_DEVICE_TYPE_ALL, numDevices, + devices.data(), nullptr)) { + return error; + } + // FIXME: Level Zero devices don't enumerate here... + logger::debug("{}: found {} devices in platform {}", __FUNCTION__, + numDevices, hPlatform); + + for (auto hDevice : devices) { + ur_device_type_t deviceType; + if (auto error = urDeviceGetInfo(hDevice, UR_DEVICE_INFO_TYPE, + sizeof(ur_device_type_t), + &deviceType, nullptr)) { + return error; + } + logger::debug("{}: device {} type {}", __FUNCTION__, hDevice, + deviceType); + + ur::device_selector::Descriptor desc; + desc.backend = platformBackend; + desc.type = deviceType; + desc.index = deviceIndexCounters[platformBackend]; + + if (desc == matcher) { + auto &device = context->selectedDevices.emplace_back(); + device.hPlatform = hPlatform; + device.hDevice = hDevice; + device.desc = desc; + logger::debug( + "{}: matched device {} type {} in platform {} backend {}", + __FUNCTION__, hDevice, deviceType, hPlatform, + platformBackend); + } else { + logger::debug( + "{}: unmatched device {} type {} in platform {} backend {}", + __FUNCTION__, hDevice, deviceType, hPlatform, + platformBackend); + } + + if (matcher.isSubDeviceMatcher()) { + // TODO: Create sub-devices + return UR_RESULT_ERROR_UNSUPPORTED_FEATURE; + } + + if (matcher.isSubSubDeviceMatcher()) { + // TODO: Create sub-sub-devices + return UR_RESULT_ERROR_UNSUPPORTED_FEATURE; + } + + deviceIndexCounters[platformBackend]++; + } + } + + // TODO: Sort devices based on preferred type first (gpu before others) + // TODO: Filter unwanted platfomrs, e.g. NVIDIA/AMD OpenCL drivers + + logger::debug("{}: matched {} devices", __FUNCTION__, + selectedDevices.size()); + + return UR_RESULT_SUCCESS; +} + ur_result_t urLoaderConfigCreate(ur_loader_config_handle_t *phLoaderConfig) { if (!phLoaderConfig) { return UR_RESULT_ERROR_INVALID_NULL_POINTER; @@ -216,613 +342,63 @@ urLoaderConfigSetCodeLocationCallback(ur_loader_config_handle_t hLoaderConfig, } ur_result_t urDeviceGetSelected(ur_platform_handle_t hPlatform, - ur_device_type_t DeviceType, - uint32_t NumEntries, + ur_device_type_t deviceType, + uint32_t numEntries, ur_device_handle_t *phDevices, uint32_t *pNumDevices) { + if (!context->deviceSelectorEnabled) { + return urDeviceGet(hPlatform, deviceType, numEntries, phDevices, + pNumDevices); + } - if (!hPlatform) { + if (hPlatform == nullptr) { return UR_RESULT_ERROR_INVALID_NULL_HANDLE; } - if (NumEntries > 0 && !phDevices) { - return UR_RESULT_ERROR_INVALID_NULL_POINTER; - } - // pNumDevices is the actual number of device handles added to phDevices by this function - if (NumEntries == 0 && !pNumDevices) { - return UR_RESULT_ERROR_INVALID_SIZE; - } - switch (DeviceType) { - case UR_DEVICE_TYPE_ALL: - case UR_DEVICE_TYPE_GPU: - case UR_DEVICE_TYPE_DEFAULT: - case UR_DEVICE_TYPE_CPU: - case UR_DEVICE_TYPE_FPGA: - case UR_DEVICE_TYPE_MCA: - break; - default: - return UR_RESULT_ERROR_INVALID_ENUMERATION; - //urPrint("Unknown device type"); - break; + if (numEntries > 0 && phDevices == nullptr) { + return UR_RESULT_ERROR_INVALID_NULL_POINTER; } - // plan: - // 0. basic validation of argument values (see code above) - // 1. conversion of argument values into useful data items - // 2. retrieval and parsing of environment variable string - // 3. conversion of term map to accept and discard filters - // 4. inserting a default "*:*" accept filter, if required - // 5. symbolic consolidation of accept and discard filters - // 6. querying the platform handles for all 'root' devices - // 7. partioning via platform root devices into subdevices - // 8. partioning via platform subdevices into subsubdevices - // 9. short-listing devices to accept using accept filters - // A. de-listing devices to discard using discard filters - - // possible symbolic short-circuit special cases exist: - // * if there are no terms, select all root devices - // * if any discard is "*", select no root devices - // * if any discard is "*.*", select no sub-devices - // * if any discard is "*.*.*", select no sub-sub-devices - // * - // - // detail for step 5 of above plan: - // * combine all accept filters into a single accept list - // * combine all discard filters into single discard list - // then invert it to make the initial/default accept list - // (needs knowledge of the valid range from the platform) - // "!level_zero:1,2" -> "level_zero:0,3,...,max" - // * finally subtract the discard set from the accept set - - // accept "2,*" != "*,2" - // because "2,*" == "2,0,1,3" - // whereas "*,2" == "0,1,2,3" - // however - // discard "2,*" == "*,2" - - // The std::map is sorted by its key, so this method of parsing the ODS env var - // alters the ordering of the terms, which makes it impossible to check whether - // all discard terms appear after all accept terms and to preserve the ordering - // of backends as specified in the ODS string. - // However, for single-platform requests, we are only interested in exactly one - // backend, and we know that discard filter terms always override accept filter - // terms, so the ordering of terms can be safely ignored -- in the special case - // where the whole ODS string contains at most one accept term, and at most one - // discard term, for that backend. - // (If we wished to preserve the ordering of terms, we could replace `std::map` - // with `std::queue>` or something similar.) - auto maybeEnvVarMap = getenv_to_map("ONEAPI_DEVICE_SELECTOR", false); - logger::debug( - "getenv_to_map parsed env var and {} a map", - (maybeEnvVarMap.has_value() ? "produced" : "failed to produce")); - - // if the ODS env var is not set at all, then pretend it was set to the default - using EnvVarMap = std::map>; - EnvVarMap mapODS = maybeEnvVarMap.has_value() ? maybeEnvVarMap.value() - : EnvVarMap{{"*", {"*"}}}; - - // the full BNF grammar can be found here: - // https://github.com/intel/llvm/blob/sycl/sycl/doc/EnvironmentVariables.md#oneapi_device_selector - - // discardFilter = "!acceptFilter" - // acceptFilter = "backend:filterStrings" - // filterStrings = "filterString[,filterString[,...]]" - // filterString = "root[.sub[.subsub]]" - // root = "*|int|cpu|gpu|fpga" - // sub = "*|int" - // subsub = "*|int" - - // validation regex for filterString (not used in this code) - std::regex validation_pattern( - "^(" - "\\*" // C++ escape for \, regex escape for literal '*' - "|" - "cpu" // ensure case-insenitive, when using - "|" - "gpu" // ensure case-insenitive, when using - "|" - "fpga" // ensure case-insenitive, when using - "|" - "[[:digit:]]+" // '' - "|" - "[[:digit:]]+\\.[[:digit:]]+" // '.' - "|" - "[[:digit:]]+\\.\\*" // '.*.*' - "|" - "\\*\\.\\*" // C++ and regex escapes, literal '*.*' - "|" - "[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+" // '..' - "|" - "[[:digit:]]+\\.[[:digit:]]+\\.\\*" // '..*' - "|" - "[[:digit:]]+\\.\\*\\.\\*" // '.*.*' - "|" - "\\*\\.\\*\\.\\*" // C++ and regex escapes, literal '*.*.*' - ")$", - std::regex_constants::icase); - - ur_platform_backend_t platformBackend; - if (UR_RESULT_SUCCESS != - urPlatformGetInfo(hPlatform, UR_PLATFORM_INFO_BACKEND, - sizeof(ur_platform_backend_t), &platformBackend, 0)) { - return UR_RESULT_ERROR_INVALID_PLATFORM; - } - const std::string platformBackendName = // hPlatform->get_backend_name(); - [&platformBackend]() constexpr { - switch (platformBackend) { - case UR_PLATFORM_BACKEND_UNKNOWN: - return "*"; // the only ODS string that matches - break; - case UR_PLATFORM_BACKEND_LEVEL_ZERO: - return "level_zero"; - break; - case UR_PLATFORM_BACKEND_OPENCL: - return "opencl"; - break; - case UR_PLATFORM_BACKEND_CUDA: - return "cuda"; - break; - case UR_PLATFORM_BACKEND_HIP: - return "hip"; - break; - case UR_PLATFORM_BACKEND_NATIVE_CPU: - return "*"; // the only ODS string that matches - break; - case UR_PLATFORM_BACKEND_FORCE_UINT32: - return ""; // no ODS string matches this - break; - default: - return ""; // no ODS string matches this - break; - } - }(); - - using DeviceHardwareType = ur_device_type_t; - - enum class DevicePartLevel { ROOT, SUB, SUBSUB }; - - using DeviceIdType = unsigned long; - constexpr DeviceIdType DeviceIdTypeALL = - -1; // ULONG_MAX but without #include - - struct DeviceSpec { - DevicePartLevel level; - DeviceHardwareType hwType = ::UR_DEVICE_TYPE_ALL; - DeviceIdType rootId = DeviceIdTypeALL; - DeviceIdType subId = DeviceIdTypeALL; - DeviceIdType subsubId = DeviceIdTypeALL; - ur_device_handle_t urDeviceHandle; - }; - - auto getRootHardwareType = - [](const std::string &input) -> DeviceHardwareType { - std::string lowerInput(input); - std::transform(lowerInput.cbegin(), lowerInput.cend(), - lowerInput.begin(), ::tolower); - if (lowerInput == "cpu") { - return ::UR_DEVICE_TYPE_CPU; - } - if (lowerInput == "gpu") { - return ::UR_DEVICE_TYPE_GPU; - } - if (lowerInput == "fpga") { - return ::UR_DEVICE_TYPE_FPGA; - } - return ::UR_DEVICE_TYPE_ALL; - }; - auto getDeviceId = [&](const std::string &input) -> DeviceIdType { - if (input.find_first_not_of("0123456789") == std::string::npos) { - return std::stoul(input); - } - return DeviceIdTypeALL; - }; - - std::vector acceptDeviceList; - std::vector discardDeviceList; - - for (auto &termPair : mapODS) { - std::string backend = termPair.first; - // TODO: Figure out how to process all ODS errors rather than returning - // on the first error. - if (backend.empty()) { - // FIXME: never true because getenv_to_map rejects this case - // malformed term: missing backend -- output ERROR, then continue - logger::error("ERROR: missing backend, format of filter = " - "'[!]backend:filterStrings'"); - continue; - } - enum FilterType { - AcceptFilter, - DiscardFilter, - } termType = (backend.front() != '!') ? AcceptFilter : DiscardFilter; - logger::debug( - "termType is {}", - (termType != AcceptFilter ? "DiscardFilter" : "AcceptFilter")); - auto &deviceList = - (termType != AcceptFilter) ? discardDeviceList : acceptDeviceList; - if (termType != AcceptFilter) { - logger::debug("DEBUG: backend was '{}'", backend); - backend.erase(backend.cbegin()); - logger::debug("DEBUG: backend now '{}'", backend); - } - // Note the hPlatform -> platformBackend -> platformBackendName conversion above - // guarantees minimal sanity for the comparison with backend from the ODS string - if (backend.front() != '*' && - !std::equal(platformBackendName.cbegin(), - platformBackendName.cend(), backend.cbegin(), - backend.cend(), [](const auto &a, const auto &b) { - // case-insensitive comparison by converting both tolower - return std::tolower( - static_cast(a)) == - std::tolower(static_cast(b)); - })) { - // irrelevant term for current request: different backend -- silently ignore - logger::error("unrecognised backend '{}'", backend); - return UR_RESULT_ERROR_INVALID_VALUE; - } - if (termPair.second.size() == 0) { - // malformed term: missing filterStrings -- output ERROR - logger::error("missing filterStrings, format of filter = " - "'[!]backend:filterStrings'"); - return UR_RESULT_ERROR_INVALID_VALUE; - } - if (std::find_if(termPair.second.cbegin(), termPair.second.cend(), - [](const auto &s) { return s.empty(); }) != - termPair.second.cend()) { - // FIXME: never true because getenv_to_map rejects this case - // malformed term: missing filterString -- output warning, then continue - logger::warning( - "WARNING: empty filterString, format of filterStrings " - "= 'filterString[,filterString[,...]]'"); - continue; - } - if (std::find_if(termPair.second.cbegin(), termPair.second.cend(), - [](const auto &s) { - return std::count(s.cbegin(), s.cend(), '.') > 2; - }) != termPair.second.cend()) { - // malformed term: too many dots in filterString - logger::error("too many dots in filterString, format of " - "filterString = 'root[.sub[.subsub]]'"); - return UR_RESULT_ERROR_INVALID_VALUE; - } - if (std::find_if( - termPair.second.cbegin(), termPair.second.cend(), - [](const auto &s) { - // GOOD: "*.*", "1.*.*", "*.*.*" - // BAD: "*.1", "*.", "1.*.2", "*.gpu" - std::string prefix = "*."; // every "*." pattern ... - std::string whole = "*.*"; // ... must be start of "*.*" - std::string::size_type pos = 0; - while ((pos = s.find(prefix, pos)) != std::string::npos) { - if (s.substr(pos, whole.size()) != whole) { - return true; // found a BAD thing, either "\*\.$" or "\*\.[^*]" - } - pos += prefix.size(); - } - return false; // no BAD things, so must be okay - }) != termPair.second.cend()) { - // malformed term: star dot no-star in filterString - logger::error("invalid wildcard in filterString, '*.' => '*.*'"); - return UR_RESULT_ERROR_INVALID_VALUE; - } - - // TODO -- use regex validation_pattern to catch all other syntax errors in the ODS string - - for (auto &filterString : termPair.second) { - std::string::size_type locationDot1 = filterString.find('.'); - if (locationDot1 != std::string::npos) { - std::string firstPart = filterString.substr(0, locationDot1); - const auto hardwareType = getRootHardwareType(firstPart); - const auto firstDeviceId = getDeviceId(firstPart); - // first dot found, look for another - std::string::size_type locationDot2 = - filterString.find('.', locationDot1 + 1); - std::string secondPart = filterString.substr( - locationDot1 + 1, locationDot2 == std::string::npos - ? std::string::npos - : locationDot2 - locationDot1); - const auto secondDeviceId = getDeviceId(secondPart); - if (locationDot2 != std::string::npos) { - // second dot found, this is a subsubdevice - std::string thirdPart = - filterString.substr(locationDot2 + 1); - const auto thirdDeviceId = getDeviceId(thirdPart); - deviceList.push_back(DeviceSpec{ - DevicePartLevel::SUBSUB, hardwareType, firstDeviceId, - secondDeviceId, thirdDeviceId}); - } else { - // second dot not found, this is a subdevice - deviceList.push_back(DeviceSpec{DevicePartLevel::SUB, - hardwareType, firstDeviceId, - secondDeviceId}); - } - } else { - // first dot not found, this is a root device - const auto hardwareType = getRootHardwareType(filterString); - const auto firstDeviceId = getDeviceId(filterString); - deviceList.push_back(DeviceSpec{DevicePartLevel::ROOT, - hardwareType, firstDeviceId}); - } - } + if (numEntries == 0 && phDevices != nullptr) { + return UR_RESULT_ERROR_INVALID_SIZE; } - if (acceptDeviceList.size() == 0 && discardDeviceList.size() == 0) { - // nothing in env var was understood as a valid term - return UR_RESULT_SUCCESS; - } else if (acceptDeviceList.size() == 0) { - // no accept terms were understood, but at least one discard term was - // we are magnanimous to the user when there were bad/ignored accept terms - // by pretending there were no bad/ignored accept terms in the env var - // for example, we pretend that "garbage:0;!cuda:*" was just "!cuda:*" - // so we add an implicit accept-all term (equivalent to prepending "*:*;") - // as we would have done if the user had given us the corrected string - acceptDeviceList.push_back(DeviceSpec{ - DevicePartLevel::ROOT, ::UR_DEVICE_TYPE_ALL, DeviceIdTypeALL}); - } - - logger::debug("DEBUG: size of acceptDeviceList = {}", - acceptDeviceList.size()); - logger::debug("DEBUG: size of discardDeviceList = {}", - discardDeviceList.size()); - - std::vector rootDevices; - std::vector subDevices; - std::vector subSubDevices; - - // To support root device terms: - { - uint32_t platformNumRootDevicesAll = 0; - if (UR_RESULT_SUCCESS != urDeviceGet(hPlatform, UR_DEVICE_TYPE_ALL, 0, - nullptr, - &platformNumRootDevicesAll)) { - return UR_RESULT_ERROR_DEVICE_NOT_FOUND; - } - std::vector rootDeviceHandles( - platformNumRootDevicesAll); - auto pRootDevices = rootDeviceHandles.data(); - if (UR_RESULT_SUCCESS != urDeviceGet(hPlatform, UR_DEVICE_TYPE_ALL, - platformNumRootDevicesAll, - pRootDevices, 0)) { - return UR_RESULT_ERROR_DEVICE_NOT_FOUND; - } - - DeviceIdType deviceCount = 0; - std::transform( - rootDeviceHandles.cbegin(), rootDeviceHandles.cend(), - std::back_inserter(rootDevices), - [&](ur_device_handle_t urDeviceHandle) { - // obtain and record device type from platform (squash errors) - ur_device_type_t hardwareType = ::UR_DEVICE_TYPE_DEFAULT; - urDeviceGetInfo(urDeviceHandle, UR_DEVICE_INFO_TYPE, - sizeof(ur_device_type_t), &hardwareType, 0); - return DeviceSpec{DevicePartLevel::ROOT, hardwareType, - deviceCount++, DeviceIdTypeALL, - DeviceIdTypeALL, urDeviceHandle}; - }); - - // apply the function parameter: ur_device_type_t DeviceType - // remove_if(..., urDeviceHandle->deviceType == DeviceType) - rootDevices.erase( - std::remove_if( - rootDevices.begin(), rootDevices.end(), - [DeviceType](DeviceSpec &device) { - const bool keep = - (DeviceType == - DeviceHardwareType::UR_DEVICE_TYPE_ALL) || - (DeviceType == - DeviceHardwareType::UR_DEVICE_TYPE_DEFAULT) || - (DeviceType == device.hwType); - return !keep; - }), - rootDevices.end()); - } - - // To support sub-device terms: - std::for_each( - rootDevices.cbegin(), rootDevices.cend(), [&](DeviceSpec device) { - ur_device_partition_property_t propNextPart{ - UR_DEVICE_PARTITION_BY_AFFINITY_DOMAIN, - {UR_DEVICE_AFFINITY_DOMAIN_FLAG_NEXT_PARTITIONABLE}}; - ur_device_partition_properties_t partitionProperties{ - UR_STRUCTURE_TYPE_DEVICE_PARTITION_PROPERTIES, nullptr, - &propNextPart, 1}; - uint32_t numSubdevices = 0; - if (UR_RESULT_SUCCESS != - urDevicePartition(device.urDeviceHandle, &partitionProperties, - 0, nullptr, &numSubdevices)) { - return UR_RESULT_ERROR_DEVICE_PARTITION_FAILED; - } - std::vector subDeviceHandles(numSubdevices); - auto pSubDevices = subDeviceHandles.data(); - if (UR_RESULT_SUCCESS != - urDevicePartition(device.urDeviceHandle, &partitionProperties, - numSubdevices, pSubDevices, 0)) { - return UR_RESULT_ERROR_DEVICE_PARTITION_FAILED; + logger::debug("{}: platform {} device type {}", __FUNCTION__, hPlatform, + deviceType); + + logger::debug("{}: total num devices {}", __FUNCTION__, + context->selectedDevices.size()); + + std::vector devices; + for (auto selectedDevice : context->selectedDevices) { + logger::debug("{}: device {} type {} in platform {} backend {}", + __FUNCTION__, selectedDevice.hDevice, + selectedDevice.desc.type, selectedDevice.hPlatform, + selectedDevice.desc.backend); + + if (selectedDevice.hPlatform == hPlatform) { + if (deviceType == UR_DEVICE_TYPE_ALL || + selectedDevice.desc.type == deviceType) { + devices.push_back(selectedDevice.hDevice); + logger::debug( + "{}: return device {} type {} in platform {} backend {}", + __FUNCTION__, selectedDevice.hDevice, + selectedDevice.desc.type, selectedDevice.hPlatform, + selectedDevice.desc.backend); } - DeviceIdType subDeviceCount = 0; - std::transform(subDeviceHandles.cbegin(), subDeviceHandles.cend(), - std::back_inserter(subDevices), - [&](ur_device_handle_t urDeviceHandle) { - return DeviceSpec{ - DevicePartLevel::SUB, device.hwType, - device.rootId, subDeviceCount++, - DeviceIdTypeALL, urDeviceHandle}; - }); - return UR_RESULT_SUCCESS; - }); - - // To support sub-sub-device terms: - std::for_each( - subDevices.cbegin(), subDevices.cend(), [&](DeviceSpec device) { - ur_device_partition_property_t propNextPart{ - UR_DEVICE_PARTITION_BY_AFFINITY_DOMAIN, - {UR_DEVICE_AFFINITY_DOMAIN_FLAG_NEXT_PARTITIONABLE}}; - ur_device_partition_properties_t partitionProperties{ - UR_STRUCTURE_TYPE_DEVICE_PARTITION_PROPERTIES, nullptr, - &propNextPart, 1}; - uint32_t numSubSubdevices = 0; - if (UR_RESULT_SUCCESS != - urDevicePartition(device.urDeviceHandle, &partitionProperties, - 0, nullptr, &numSubSubdevices)) { - return UR_RESULT_ERROR_DEVICE_PARTITION_FAILED; - } - std::vector subSubDeviceHandles( - numSubSubdevices); - auto pSubSubDevices = subSubDeviceHandles.data(); - if (UR_RESULT_SUCCESS != - urDevicePartition(device.urDeviceHandle, &partitionProperties, - numSubSubdevices, pSubSubDevices, 0)) { - return UR_RESULT_ERROR_DEVICE_PARTITION_FAILED; - } - DeviceIdType subSubDeviceCount = 0; - std::transform( - subSubDeviceHandles.cbegin(), subSubDeviceHandles.cend(), - std::back_inserter(subSubDevices), - [&](ur_device_handle_t urDeviceHandle) { - return DeviceSpec{DevicePartLevel::SUBSUB, device.hwType, - device.rootId, device.subId, - subSubDeviceCount++, urDeviceHandle}; - }); - return UR_RESULT_SUCCESS; - }); - - auto ApplyFilter = [&](DeviceSpec &filter, DeviceSpec &device) -> bool { - bool matches = false; - if (filter.rootId == DeviceIdTypeALL) { - // if this is a root device filter, then it must be '*' or 'cpu' or 'gpu' or 'fpga' - // if this is a subdevice filter, then it must be '*.*' - // if this is a subsubdevice filter, then it must be '*.*.*' - matches = (filter.hwType == device.hwType) || - (filter.hwType == DeviceHardwareType::UR_DEVICE_TYPE_ALL); - logger::debug( - "DEBUG: In ApplyFilter, if block case 1, matches = {}", - matches); - } else if (filter.rootId != device.rootId) { - // root part in filter is a number but does not match the number in the root part of device - matches = false; - logger::debug("DEBUG: In ApplyFilter, if block case 2, matches = ", - matches); - } else if (filter.level == DevicePartLevel::ROOT) { - // this is a root device filter with a number that matches - matches = true; - logger::debug("DEBUG: In ApplyFilter, if block case 3, matches = ", - matches); - } else if (filter.subId == DeviceIdTypeALL) { - // sub type of star always matches (when root part matches, which we already know here) - // if this is a subdevice filter, then it must be 'matches.*' - // if this is a subsubdevice filter, then it must be 'matches.*.*' - matches = true; - logger::debug("DEBUG: In ApplyFilter, if block case 4, matches = ", - matches); - } else if (filter.subId != device.subId) { - // sub part in filter is a number but does not match the number in the sub part of device - matches = false; - logger::debug("DEBUG: In ApplyFilter, if block case 5, matches = ", - matches); - } else if (filter.level == DevicePartLevel::SUB) { - // this is a sub device number filter, numbers match in both parts - matches = true; - logger::debug("DEBUG: In ApplyFilter, if block case 6, matches = ", - matches); - } else if (filter.subsubId == DeviceIdTypeALL) { - // subsub type of star always matches (when other parts match, which we already know here) - // this is a subsub device filter, it must be 'matches.matches.*' - matches = true; - logger::debug("DEBUG: In ApplyFilter, if block case 7, matches = ", - matches); - } else { - // this is a subsub device filter, numbers in all three parts match - matches = (filter.subsubId == device.subsubId); - logger::debug("DEBUG: In ApplyFilter, if block case 8, matches = ", - matches); - } - return matches; - }; - - // apply each discard filter in turn by removing all matching elements - // from the appropriate device handle vector returned by the platform; - // no side-effect: the matching devices are just removed and discarded - for (auto &discard : discardDeviceList) { - auto ApplyDiscardFilter = [&](auto &device) -> bool { - return ApplyFilter(discard, device); - }; - if (discard.level == DevicePartLevel::ROOT) { - rootDevices.erase(std::remove_if(rootDevices.begin(), - rootDevices.end(), - ApplyDiscardFilter), - rootDevices.end()); - } - if (discard.level == DevicePartLevel::SUB) { - subDevices.erase(std::remove_if(subDevices.begin(), - subDevices.end(), - ApplyDiscardFilter), - subDevices.end()); - } - if (discard.level == DevicePartLevel::SUBSUB) { - subSubDevices.erase(std::remove_if(subSubDevices.begin(), - subSubDevices.end(), - ApplyDiscardFilter), - subSubDevices.end()); } } - std::vector selectedDevices; - - // apply each accept filter in turn by removing all matching elements - // from the appropriate device handle vector returned by the platform - // but using a predicate with a side-effect that takes a copy of each - // of the accepted device handles just before they are removed - // removing each item as it is selected prevents us taking duplicates - // without needing O(n^2) de-duplicatation or symbolic simplification - for (auto &accept : acceptDeviceList) { - auto ApplyAcceptFilter = [&](auto &device) -> bool { - const bool matches = ApplyFilter(accept, device); - if (matches) { - selectedDevices.push_back(device.urDeviceHandle); - } - return matches; - }; - auto numAlreadySelected = selectedDevices.size(); - if (accept.level == DevicePartLevel::ROOT) { - rootDevices.erase(std::remove_if(rootDevices.begin(), - rootDevices.end(), - ApplyAcceptFilter), - rootDevices.end()); - } - if (accept.level == DevicePartLevel::SUB) { - subDevices.erase(std::remove_if(subDevices.begin(), - subDevices.end(), - ApplyAcceptFilter), - subDevices.end()); - } - if (accept.level == DevicePartLevel::SUBSUB) { - subSubDevices.erase(std::remove_if(subSubDevices.begin(), - subSubDevices.end(), - ApplyAcceptFilter), - subSubDevices.end()); - } - if (numAlreadySelected == selectedDevices.size()) { - logger::warning("WARNING: an accept term was ignored because it " - "does not select any additional devices" - "selectedDevices.size() = {}", - selectedDevices.size()); - } + if (phDevices) { + auto end = + devices.begin() + std::min(size_t(numEntries), devices.size()); + std::copy(devices.begin(), end, phDevices); } - - // selectedDevices is now a vector containing all the right device handles - - // should we return the size of the vector or the content of the vector? - if (NumEntries == 0) { - *pNumDevices = static_cast(selectedDevices.size()); - } else if (NumEntries > 0) { - size_t numToCopy = std::min((size_t)NumEntries, selectedDevices.size()); - std::copy_n(selectedDevices.cbegin(), numToCopy, phDevices); - if (pNumDevices != nullptr) { - *pNumDevices = static_cast(numToCopy); - return UR_RESULT_ERROR_ADAPTER_SPECIFIC; - } + if (pNumDevices) { + *pNumDevices = uint32_t(devices.size()); } return UR_RESULT_SUCCESS; } + } // namespace ur_lib diff --git a/source/loader/ur_lib.hpp b/source/loader/ur_lib.hpp index 839c0041d9..6fd2d64ff9 100644 --- a/source/loader/ur_lib.hpp +++ b/source/loader/ur_lib.hpp @@ -19,6 +19,7 @@ #include "ur_proxy_layer.hpp" #include "ur_util.hpp" +#include "device_selector/matcher.hpp" #include "validation/ur_validation_layer.hpp" #if UR_ENABLE_TRACING #include "tracing/ur_tracing_layer.hpp" @@ -51,6 +52,12 @@ struct ur_loader_config_handle_t_ { }; namespace ur_lib { +struct device_desc_t { + ur_platform_handle_t hPlatform = nullptr; + ur_device_handle_t hDevice = nullptr; + ur::device_selector::Descriptor desc; +}; + /////////////////////////////////////////////////////////////////////////////// class __urdlllocal context_t { public: @@ -69,6 +76,12 @@ class __urdlllocal context_t { ur_result_t urLoaderInit(); ur_dditable_t urDdiTable = {}; + bool deviceSelectorEnabled = false; + ur::device_selector::Matcher matcher; + std::vector selectedDevices; + + ur_result_t enumerateDevices(); + const std::vector layers = { &ur_validation_layer::context, #if UR_ENABLE_TRACING diff --git a/source/loader/ur_loader.hpp b/source/loader/ur_loader.hpp index 8a0f4a8c23..0edc2f6ce8 100644 --- a/source/loader/ur_loader.hpp +++ b/source/loader/ur_loader.hpp @@ -44,6 +44,14 @@ class context_t { extern context_t *context; extern ur_event_factory_t ur_event_factory; +ur_result_t getSelectedDevices( + ur_platform_handle_t hPlatform, + ur_device_type_t DeviceType, + uint32_t NumEntries, + ur_device_handle_t *phDevices, + uint32_t *pNumDevices +); + } // namespace ur_loader #endif /* UR_LOADER_HPP */ diff --git a/test/loader/CMakeLists.txt b/test/loader/CMakeLists.txt index 5472da74bc..c84852d9e1 100644 --- a/test/loader/CMakeLists.txt +++ b/test/loader/CMakeLists.txt @@ -13,3 +13,4 @@ add_subdirectory(loader_config) add_subdirectory(loader_lifetime) add_subdirectory(platforms) add_subdirectory(handles) +add_subdirectory(device_selector) diff --git a/test/loader/device_selector/CMakeLists.txt b/test/loader/device_selector/CMakeLists.txt new file mode 100644 index 0000000000..49135eb183 --- /dev/null +++ b/test/loader/device_selector/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (C) 2022 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 + +add_ur_executable(test-device-selector + backend.cpp + device.cpp + filter.cpp + matcher.cpp +) + +target_link_libraries(test-device-selector PRIVATE + ${PROJECT_NAME}::device_selector + GTest::gtest_main +) diff --git a/test/loader/device_selector/backend.cpp b/test/loader/device_selector/backend.cpp new file mode 100644 index 0000000000..cf711b9e7d --- /dev/null +++ b/test/loader/device_selector/backend.cpp @@ -0,0 +1,89 @@ +// 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 "device_selector/backend.hpp" +#include "common.hpp" + +using namespace std::string_view_literals; + +TEST(BackendMatcher, Empty) { + ur::device_selector::BackendMatcher matcher; + auto result = matcher.init(""); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ("empty backend"sv, result->message); +} + +TEST(BackendMatcher, Invalid) { + ur::device_selector::BackendMatcher matcher; + auto result = matcher.init("NotABackendName"); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ("invalid backend: 'NotABackendName'"sv, result->message); +} + +TEST(BackendMatcher, Star) { + ur::device_selector::BackendMatcher matcher; + auto result = matcher.init("*"); + ASSERT_FALSE(result.has_value()); + ASSERT_EQ(matcher, DESCRIPTOR(LEVEL_ZERO, GPU, 0)); + ASSERT_EQ(matcher, DESCRIPTOR(OPENCL, GPU, 0)); + ASSERT_EQ(matcher, DESCRIPTOR(CUDA, GPU, 0)); + ASSERT_EQ(matcher, DESCRIPTOR(HIP, GPU, 0)); + ASSERT_EQ(matcher, DESCRIPTOR(NATIVE_CPU, CPU, 0)); +} + +TEST(BackendMatcher, OpenCL) { + ur::device_selector::BackendMatcher matcher; + auto result = matcher.init("OpenCL"); + ASSERT_FALSE(result.has_value()); + ASSERT_NE(matcher, DESCRIPTOR(LEVEL_ZERO, GPU, 0)); + ASSERT_EQ(matcher, DESCRIPTOR(OPENCL, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(CUDA, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(HIP, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(NATIVE_CPU, CPU, 0)); +} + +TEST(BackendMatcher, LevelZero) { + ur::device_selector::BackendMatcher matcher; + auto result = matcher.init("level_zero"); + ASSERT_FALSE(result.has_value()); + ASSERT_EQ(matcher, DESCRIPTOR(LEVEL_ZERO, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(OPENCL, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(CUDA, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(HIP, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(NATIVE_CPU, CPU, 0)); +} + +TEST(BackendMatcher, CUDA) { + ur::device_selector::BackendMatcher matcher; + auto result = matcher.init("CUDA"); + ASSERT_FALSE(result.has_value()); + ASSERT_NE(matcher, DESCRIPTOR(LEVEL_ZERO, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(OPENCL, GPU, 0)); + ASSERT_EQ(matcher, DESCRIPTOR(CUDA, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(HIP, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(NATIVE_CPU, CPU, 0)); +} + +TEST(BackendMatcher, HIP) { + ur::device_selector::BackendMatcher matcher; + auto result = matcher.init("Hip"); + ASSERT_FALSE(result.has_value()); + ASSERT_NE(matcher, DESCRIPTOR(LEVEL_ZERO, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(OPENCL, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(CUDA, GPU, 0)); + ASSERT_EQ(matcher, DESCRIPTOR(HIP, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(NATIVE_CPU, CPU, 0)); +} + +TEST(BackendMatcher, NativeCPU) { + ur::device_selector::BackendMatcher matcher; + auto result = matcher.init("Native_CPU"); + ASSERT_FALSE(result.has_value()); + ASSERT_NE(matcher, DESCRIPTOR(LEVEL_ZERO, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(OPENCL, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(CUDA, GPU, 0)); + ASSERT_NE(matcher, DESCRIPTOR(HIP, GPU, 0)); + ASSERT_EQ(matcher, DESCRIPTOR(NATIVE_CPU, CPU, 0)); +} diff --git a/test/loader/device_selector/common.hpp b/test/loader/device_selector/common.hpp new file mode 100644 index 0000000000..4e4aa5e559 --- /dev/null +++ b/test/loader/device_selector/common.hpp @@ -0,0 +1,13 @@ +// 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 + +#pragma once + +#include "device_selector/descriptor.hpp" +#include + +#define DESCRIPTOR(BACKEND, TYPE, ...) \ + ur::device_selector::Descriptor(UR_PLATFORM_BACKEND_##BACKEND, \ + UR_DEVICE_TYPE_##TYPE, __VA_ARGS__) diff --git a/test/loader/device_selector/device.cpp b/test/loader/device_selector/device.cpp new file mode 100644 index 0000000000..ffdf5e08ce --- /dev/null +++ b/test/loader/device_selector/device.cpp @@ -0,0 +1,290 @@ +// 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 "device_selector/device.hpp" +#include "common.hpp" + +using namespace std::string_view_literals; +using namespace ur::device_selector; + +TEST(DeviceMatcher, Empty) { + DeviceMatcher matcher; + auto result = matcher.init(""); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ("empty device"sv, result->message); +} + +TEST(DeviceMatcher, Invalid) { + DeviceMatcher matcher; + auto result = matcher.init("NotADeviceName"); + ASSERT_TRUE(result.has_value()); + ASSERT_STREQ("invalid device: 'NotADeviceName'", result->message.c_str()); +} + +TEST(DeviceMatcher, InvalidStar) { + DeviceMatcher matcher; + auto result = matcher.init("*garbage"); + ASSERT_TRUE(result.has_value()); + ASSERT_STREQ("invalid device: '*garbage'", result->message.c_str()); +} + +TEST(DeviceMatcher, Star) { + DeviceMatcher matcher; + auto result = matcher.init("*"); + ASSERT_FALSE(result.has_value()); + ASSERT_FALSE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + std::array indicies = {0, 0xFFFFFFFF}; + for (auto index : indicies) { + ASSERT_EQ(DESCRIPTOR(HIP, ALL, index), matcher); + ASSERT_EQ(DESCRIPTOR(HIP, GPU, index), matcher); + ASSERT_EQ(DESCRIPTOR(HIP, CPU, index), matcher); + ASSERT_EQ(DESCRIPTOR(HIP, FPGA, index), matcher); + ASSERT_EQ(DESCRIPTOR(HIP, MCA, index), matcher); + ASSERT_EQ(DESCRIPTOR(HIP, VPU, index), matcher); + } +} + +TEST(DeviceMatcher, GPU) { + DeviceMatcher matcher; + auto result = matcher.init("gpu"); + ASSERT_FALSE(result.has_value()); + ASSERT_FALSE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + ASSERT_EQ(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, CPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, FPGA, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, MCA, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, VPU, 0), matcher); +} + +TEST(DeviceMatcher, CPU) { + DeviceMatcher matcher; + auto result = matcher.init("cpu"); + ASSERT_FALSE(result.has_value()); + ASSERT_FALSE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(NATIVE_CPU, CPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, FPGA, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, MCA, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, VPU, 0), matcher); +} + +TEST(DeviceMatcher, FPGA) { + DeviceMatcher matcher; + auto result = matcher.init("fpga"); + ASSERT_FALSE(result.has_value()); + ASSERT_FALSE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, CPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, FPGA, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, MCA, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, VPU, 0), matcher); +} + +TEST(DeviceMatcher, MCA) { + DeviceMatcher matcher; + auto result = matcher.init("mca"); + ASSERT_FALSE(result.has_value()); + ASSERT_FALSE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, CPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, FPGA, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, MCA, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, VPU, 0), matcher); +} + +TEST(DeviceMatcher, NPU) { + DeviceMatcher matcher; + auto result = matcher.init("npu"); + ASSERT_FALSE(result.has_value()); + ASSERT_FALSE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, CPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, FPGA, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, MCA, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, VPU, 0), matcher); +} + +TEST(DeviceMatcher, StarDotStar) { + DeviceMatcher matcher; + auto result = matcher.init("*.*"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_TRUE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + std::array indicies = {0, 0xFFFFFFFF}; + for (auto index : indicies) { + for (auto subIndex : indicies) { + // Valid matches + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, ALL, index, subIndex), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, index, subIndex), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, CPU, index, subIndex), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, FPGA, index, subIndex), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, MCA, index, subIndex), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, VPU, index, subIndex), matcher); + // Invalid matches, the descriptor has no subIndex + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, ALL, index), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, index), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, CPU, index), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, FPGA, index), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, MCA, index), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, VPU, index), matcher); + } + } +} + +TEST(DeviceMatcher, InvalidNum) { + DeviceMatcher matcher; + auto result = matcher.init("0c"); + ASSERT_TRUE(result.has_value()); + ASSERT_STREQ("invalid number: '0c'", result->message.c_str()); +} + +TEST(DeviceMatcher, Num) { + DeviceMatcher matcher; + auto result = matcher.init("0"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_FALSE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + ASSERT_EQ(DESCRIPTOR(HIP, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 1), matcher); +} + +TEST(DeviceMatcher, NumDotInvalidNum) { + DeviceMatcher matcher; + { + auto result = matcher.init("1.crap"); + ASSERT_TRUE(result.has_value()); + ASSERT_STREQ("invalid number: 'crap' in '1.crap'", + result->message.c_str()); + } + { + auto result = matcher.init("1."); + ASSERT_TRUE(result.has_value()); + ASSERT_STREQ("invalid number: '' in '1.'", result->message.c_str()); + } +} + +TEST(DeviceMatcher, NumDotNum) { + DeviceMatcher matcher; + auto result = matcher.init("0.0"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_TRUE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 1), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 1, 0), matcher); +} + +TEST(DeviceMatcher, NumDotStar) { + DeviceMatcher matcher; + auto result = matcher.init("0.*"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_TRUE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 0xFFFFFFFF), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 1, 0), matcher); +} + +TEST(DeviceMatcher, NumDotNumDotInvalidNum) { + DeviceMatcher matcher; + auto result = matcher.init("1.0.garbage"); + ASSERT_TRUE(result.has_value()); + ASSERT_STREQ("invalid number: 'garbage' in '1.0.garbage'", + result->message.c_str()); +} + +TEST(DeviceMatcher, NumDotNumDotNum) { + DeviceMatcher matcher; + auto result = matcher.init("0.0.0"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_TRUE(matcher.isSubDeviceMatcher()); + ASSERT_TRUE(matcher.isSubSubDeviceMatcher()); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 0, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 0, 1), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 1, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 1, 0, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 1, 1), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 1, 0, 1), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 1, 1, 1), matcher); +} + +TEST(DeviceMatcher, NumDotNumDotStar) { + DeviceMatcher matcher; + auto result = matcher.init("0.0.*"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_TRUE(matcher.isSubDeviceMatcher()); + ASSERT_TRUE(matcher.isSubSubDeviceMatcher()); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 0, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 0, 0xFFFFFFFF), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 1, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 1, 0, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 1, 1), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 1, 0, 1), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 1, 1, 1), matcher); +} + +TEST(DeviceMatcher, StarDotStarDotStar) { + DeviceMatcher matcher; + auto result = matcher.init("*.*.*"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_TRUE(matcher.isSubDeviceMatcher()); + ASSERT_TRUE(matcher.isSubSubDeviceMatcher()); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 0, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 0, 0xFFFFFFFF), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 0xFFFFFFFF, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0xFFFFFFFF, 0, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 0xFFFFFFFF, 0xFFFFFFFF), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0xFFFFFFFF, 0, 0xFFFFFFFF), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF), + matcher); +} + +TEST(DevicesMatcher, InvalidBefore) { + DevicesMatcher matcher; + auto result = matcher.init(",gpu"); + ASSERT_TRUE(result.has_value()); + ASSERT_STREQ("empty device in: ',gpu'", result->message.c_str()); +} + +TEST(DevicesMatcher, InvalidAfter) { + DevicesMatcher matcher; + auto result = matcher.init("gpu,"); + ASSERT_TRUE(result.has_value()); + ASSERT_STREQ("empty device in: 'gpu,'", result->message.c_str()); +} + +TEST(DevicesMatcher, GPUCommaCPU) { + DevicesMatcher matcher; + auto result = matcher.init("gpu,cpu"); + ASSERT_FALSE(result.has_value()) << result.value().message; + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, CPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, FPGA, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, MCA, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, VPU, 0), matcher); +} + +TEST(DevicesMatcher, OneCommaTwo) { + DevicesMatcher matcher; + auto result = matcher.init("0,2"); + ASSERT_FALSE(result.has_value()) << result.value().message; + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 1), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 2), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 3), matcher); +} + +TEST(DevicesMatcher, StarCommaStarDotStar) { + DevicesMatcher matcher; + auto result = matcher.init("*,*.*"); + ASSERT_FALSE(result.has_value()) << result.value().message; + ASSERT_TRUE(matcher.isSubDeviceMatcher()); +} diff --git a/test/loader/device_selector/filter.cpp b/test/loader/device_selector/filter.cpp new file mode 100644 index 0000000000..35cdb98313 --- /dev/null +++ b/test/loader/device_selector/filter.cpp @@ -0,0 +1,116 @@ +// 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 "device_selector/filter.hpp" +#include "common.hpp" + +using namespace std::string_view_literals; +using namespace ur::device_selector; + +TEST(FilterMatcher, InvalidNoColon) { + FilterMatcher matcher; + auto result = matcher.init("level_zero0"); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ("invalid filter: 'level_zero0'"sv, result.value().message); +} + +TEST(FilterMatcher, InvalidBackend) { + FilterMatcher matcher; + auto result = matcher.init("InvalidBackend:0"); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ("invalid backend: 'InvalidBackend'"sv, result.value().message); +} + +TEST(FilterMatcher, InvalidDevice) { + FilterMatcher matcher; + auto result = matcher.init("level_zero:InvalidDevice"); + ASSERT_TRUE(result.has_value()); + ASSERT_EQ("invalid device: 'InvalidDevice'"sv, result.value().message); +} + +TEST(FilterMatcher, LevelZeroStar) { + FilterMatcher matcher; + auto result = matcher.init("level_zero:*"); + ASSERT_FALSE(result.has_value()); + ASSERT_FALSE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, CPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, FPGA, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, MCA, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, VPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, GPU, 0), matcher); +} + +TEST(FilterMatcher, OpenCLStar) { + FilterMatcher matcher; + auto result = matcher.init("opencl:*"); + ASSERT_FALSE(result.has_value()); + ASSERT_FALSE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, CPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, FPGA, 0), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, MCA, 0), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, VPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, GPU, 0), matcher); +} + +TEST(FilterMatcher, CUDAStar) { + FilterMatcher matcher; + auto result = matcher.init("cuda:*"); + ASSERT_FALSE(result.has_value()); + ASSERT_FALSE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + ASSERT_EQ(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(CUDA, CPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(CUDA, FPGA, 0), matcher); + ASSERT_EQ(DESCRIPTOR(CUDA, MCA, 0), matcher); + ASSERT_EQ(DESCRIPTOR(CUDA, VPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, GPU, 0), matcher); +} + +TEST(FilterMatcher, HIPStar) { + FilterMatcher matcher; + auto result = matcher.init("hip:*"); + ASSERT_FALSE(result.has_value()); + ASSERT_FALSE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + ASSERT_EQ(DESCRIPTOR(HIP, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(HIP, CPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(HIP, FPGA, 0), matcher); + ASSERT_EQ(DESCRIPTOR(HIP, MCA, 0), matcher); + ASSERT_EQ(DESCRIPTOR(HIP, VPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, GPU, 0), matcher); +} + +TEST(FilterMatcher, NativeCPUStar) { + FilterMatcher matcher; + auto result = matcher.init("native_cpu:*"); + ASSERT_FALSE(result.has_value()); + ASSERT_FALSE(matcher.isSubDeviceMatcher()); + ASSERT_FALSE(matcher.isSubSubDeviceMatcher()); + ASSERT_EQ(DESCRIPTOR(NATIVE_CPU, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(NATIVE_CPU, CPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(NATIVE_CPU, FPGA, 0), matcher); + ASSERT_EQ(DESCRIPTOR(NATIVE_CPU, MCA, 0), matcher); + ASSERT_EQ(DESCRIPTOR(NATIVE_CPU, VPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 0), matcher); +} diff --git a/test/loader/device_selector/matcher.cpp b/test/loader/device_selector/matcher.cpp new file mode 100644 index 0000000000..a7fec07451 --- /dev/null +++ b/test/loader/device_selector/matcher.cpp @@ -0,0 +1,193 @@ +// 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 "device_selector/matcher.hpp" +#include "common.hpp" +#include + +using namespace std::string_view_literals; + +// ONEAPI_DEVICE_SELECTOR=opencl:* +// Only the OpenCL devices are available +TEST(Matcher, Example_01) { + ur::device_selector::Matcher matcher; + auto result = matcher.init("opencl:*"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, CPU, 1), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, FPGA, 2), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, CPU, 0), matcher); +} + +// ONEAPI_DEVICE_SELECTOR=level_zero:gpu +// Only GPU devices on the Level Zero platform are available. +TEST(Matcher, Example_02) { + ur::device_selector::Matcher matcher; + auto result = matcher.init("level_zero:gpu"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, CPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, CPU, 0), matcher); +} + +// ONEAPI_DEVICE_SELECTOR="opencl:gpu;level_zero:gpu" +// GPU devices from both Level Zero and OpenCL are available. Note that +// escaping (like quotation marks) will likely be needed when using semi-colon +// separated entries. +TEST(Matcher, Example_03) { + ur::device_selector::Matcher matcher; + auto result = matcher.init("opencl:gpu;level_zero:gpu"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 1), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 1), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, CPU, 0), matcher); +} + +// ONEAPI_DEVICE_SELECTOR=opencl:gpu,cpu +// Only CPU and GPU devices on the OpenCL platform are available. +TEST(Matcher, Example_04) { + ur::device_selector::Matcher matcher; + auto result = matcher.init("opencl:gpu,cpu"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 1), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, CPU, 2), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, FPGA, 3), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, CPU, 0), matcher); +} + +// ONEAPI_DEVICE_SELECTOR=opencl:0 +// Only the device with index 0 on the OpenCL backend is available. +TEST(Matcher, Example_05) { + ur::device_selector::Matcher matcher; + auto result = matcher.init("opencl:0"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 1), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, CPU, 0), matcher); +} + +// ONEAPI_DEVICE_SELECTOR=hip:0,2 +// Only devices with indices of 0 and 2 from the HIP backend are available. +TEST(Matcher, Example_06) { + ur::device_selector::Matcher matcher; + auto result = matcher.init("hip:0,2"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_EQ(DESCRIPTOR(HIP, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 1), matcher); + ASSERT_EQ(DESCRIPTOR(HIP, GPU, 2), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 3), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, CPU, 0), matcher); +} + +// ONEAPI_DEVICE_SELECTOR=opencl:0.* +// All the sub-devices from the OpenCL device with index 0 are exposed as SYCL +// root devices. No other devices are available. +TEST(Matcher, Example_07) { + ur::device_selector::Matcher matcher; + auto result = matcher.init("opencl:0.*"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 0, 0), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 0, 1), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 0, 2), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 0, 3), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 0, 0), matcher); +} + +// ONEAPI_DEVICE_SELECTOR=opencl:0.2 +// The third sub-device (2 in zero-based counting) of the OpenCL device with +// index 0 will be the sole device available. +TEST(Matcher, Example_08) { + ur::device_selector::Matcher matcher; + auto result = matcher.init("opencl:0.2"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0, 1), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 0, 2), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0, 3), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 2), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0, 2), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 0, 2), matcher); +} + +// ONEAPI_DEVICE_SELECTOR=level_zero:*,*.* +// Exposes Level Zero devices to the application in two different ways. Each +// device (aka “card”) is exposed as a SYCL root device and each sub-device is +// also exposed as a SYCL root device. +TEST(Matcher, Example_09) { + ur::device_selector::Matcher matcher; + auto result = matcher.init("level_zero:*,*.*"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0, 1), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 1), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 1, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 1, 1), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 2), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 2, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 2, 1), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 3), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 3, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 3, 1), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0, 1), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 0), matcher); +} + +// ONEAPI_DEVICE_SELECTOR="opencl:*;!opencl:0" +// All OpenCL devices except for the device with index 0 are available. +TEST(Matcher, Example_10) { + ur::device_selector::Matcher matcher; + auto result = matcher.init("opencl:*;!opencl:0"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_NE(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 1), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 9), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(LEVEL_ZERO, GPU, 1), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(CUDA, GPU, 1), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(HIP, GPU, 1), matcher); +} + +// ONEAPI_DEVICE_SELECTOR="!*:cpu" +// All devices except for CPU devices are available. +TEST(Matcher, Example_11) { + ur::device_selector::Matcher matcher; + auto result = matcher.init("!*:cpu"); + ASSERT_FALSE(result.has_value()) << result->message; + ASSERT_EQ(DESCRIPTOR(OPENCL, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(OPENCL, FPGA, 0), matcher); + ASSERT_EQ(DESCRIPTOR(LEVEL_ZERO, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(CUDA, GPU, 0), matcher); + ASSERT_EQ(DESCRIPTOR(HIP, GPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(OPENCL, CPU, 0), matcher); + ASSERT_NE(DESCRIPTOR(NATIVE_CPU, CPU, 0), matcher); +} diff --git a/test/loader/device_selector/parser.cpp b/test/loader/device_selector/parser.cpp new file mode 100644 index 0000000000..691fb0a1a7 --- /dev/null +++ b/test/loader/device_selector/parser.cpp @@ -0,0 +1,37 @@ +// 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 "ur_api.h" +#include + +namespace device_selector { +struct Parser { + ur_platform_backend_t parseBackend(std::string_view str); + ur_device_type_t parseDevice(std::string_view str); +}; +} // namespace device_selector + +// ::= { * | cpu | gpu | fpga | | . | .* | *.* | .. | ..* | .*.* | *.*.* } // case insensitive +TEST(DeviceSelectorParser, Device) { + device_selector::Parser parser; + ASSERT_EQ(UR_DEVICE_TYPE_ALL, parser.parseDevice("*")); + ASSERT_EQ(UR_DEVICE_TYPE_CPU, parser.parseDevice("cpu")); + ASSERT_EQ(UR_DEVICE_TYPE_GPU, parser.parseDevice("gpu")); + ASSERT_EQ(UR_DEVICE_TYPE_FPGA, parser.parseDevice("fpga")); + // TODO: upper case variants +} + +// ::= { * | level_zero | opencl | cuda | hip | native_cpu } // case insensitive +TEST(DeviceSelectorParser, Backend) { + device_selector::Parser parser; + ASSERT_EQ(UR_PLATFORM_BACKEND_LEVEL_ZERO, parser.parseBackend("level_zero")); + ASSERT_EQ(UR_PLATFORM_BACKEND_LEVEL_ZERO, parser.parseBackend("opencl")); + ASSERT_EQ(UR_PLATFORM_BACKEND_LEVEL_ZERO, parser.parseBackend("cuda")); + ASSERT_EQ(UR_PLATFORM_BACKEND_LEVEL_ZERO, parser.parseBackend("hip")); + ASSERT_EQ(UR_PLATFORM_BACKEND_LEVEL_ZERO, parser.parseBackend("native_cpu")); + // TODO: upper case variants +} + + diff --git a/tools/urinfo/urinfo.cpp b/tools/urinfo/urinfo.cpp index bf7173234b..563959d234 100644 --- a/tools/urinfo/urinfo.cpp +++ b/tools/urinfo/urinfo.cpp @@ -16,6 +16,7 @@ struct app { bool verbose = false; bool linear_ids = true; bool ignore_device_selector = false; + bool suppress_number_printing = false; ur_loader_config_handle_t loaderConfig = nullptr; std::vector adapters; std::unordered_map> @@ -33,6 +34,7 @@ struct app { "To see all devices, use the " "--ignore-device-selector CLI option.\n\n", device_selector); + suppress_number_printing = true; } } UR_CHECK(urLoaderConfigCreate(&loaderConfig)); @@ -144,8 +146,10 @@ devices which are currently visible in the local execution environment. if (linear_ids) { std::cout << "[" << adapter_backend << ":" << device_type << "]"; - std::cout << "[" << adapter_backend << ":" - << adapter_device_id << "]"; + if (!suppress_number_printing) { + std::cout << "[" << adapter_backend << ":" + << adapter_device_id << "]"; + } } else { std::cout << "[adapter(" << adapterIndex << "," << adapter_backend << "):"