Skip to content

Commit 2fcf3bf

Browse files
committed
[LOADER] Added Prefilter using ONEAPI_DEVICE_SELECTOR to remove backends
- Added Prefilter which uses ONEAPI_DEVICE_SELECTOR to remove backends that the user has not requested. - this handles both the "Accept" or "Discard" filters that can be set in the ONEAPI_DEVICE_SELECTOR. - Added tests verify the Accept/Discard use cases on Linux. - Added UR_LOADER_PRELOAD_FILTER to toggle the prefilter on and off. Signed-off-by: Neil R. Spruit <neil.r.spruit@intel.com>
1 parent cd92304 commit 2fcf3bf

File tree

5 files changed

+309
-0
lines changed

5 files changed

+309
-0
lines changed

scripts/core/INTRO.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,14 @@ Specific environment variables can be set to control the behavior of unified run
396396

397397
See the Layers_ section for details of the layers currently included in the runtime.
398398

399+
.. envvar:: UR_LOADER_PRELOAD_FILTER
400+
401+
If set, the loader will read `ONEAPI_DEVICE_SELECTOR` before loading the UR Adapters to determine which backends should be loaded.
402+
403+
.. note::
404+
405+
This environment variable is default enabled on Linux, but default disabled on Windows.
406+
399407
Service identifiers
400408
---------------------
401409

source/loader/ur_adapter_registry.hpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,123 @@ class AdapterRegistry {
152152
return paths.empty() ? std::nullopt : std::optional(paths);
153153
}
154154

155+
ur_result_t readPreFilterODS(std::string platformBackendName) {
156+
// TODO: Refactor this to the common code such that both the prefilter and urDeviceGetSelected use the same functionality.
157+
bool acceptLibrary = true;
158+
std::optional<EnvVarMap> odsEnvMap;
159+
try {
160+
odsEnvMap = getenv_to_map("ONEAPI_DEVICE_SELECTOR", false);
161+
162+
} catch (...) {
163+
// If the selector is malformed, then we ignore selector and return success.
164+
logger::error("ERROR: missing backend, format of filter = "
165+
"'[!]backend:filterStrings'");
166+
return UR_RESULT_SUCCESS;
167+
}
168+
logger::debug(
169+
"getenv_to_map parsed env var and {} a map",
170+
(odsEnvMap.has_value() ? "produced" : "failed to produce"));
171+
172+
// if the ODS env var is not set at all, then pretend it was set to the default
173+
using EnvVarMap = std::map<std::string, std::vector<std::string>>;
174+
EnvVarMap mapODS =
175+
odsEnvMap.has_value() ? odsEnvMap.value() : EnvVarMap{{"*", {"*"}}};
176+
for (auto &termPair : mapODS) {
177+
std::string backend = termPair.first;
178+
// TODO: Figure out how to process all ODS errors rather than returning
179+
// on the first error.
180+
if (backend.empty()) {
181+
// FIXME: never true because getenv_to_map rejects this case
182+
// malformed term: missing backend -- output ERROR, then continue
183+
logger::error("ERROR: missing backend, format of filter = "
184+
"'[!]backend:filterStrings'");
185+
continue;
186+
}
187+
logger::debug("ONEAPI_DEVICE_SELECTOR Pre-Filter with backend '{}' "
188+
"and platform library name '{}'",
189+
backend, platformBackendName);
190+
enum FilterType {
191+
AcceptFilter,
192+
DiscardFilter,
193+
} termType =
194+
(backend.front() != '!') ? AcceptFilter : DiscardFilter;
195+
logger::debug(
196+
"termType is {}",
197+
(termType != AcceptFilter ? "DiscardFilter" : "AcceptFilter"));
198+
if (termType != AcceptFilter) {
199+
logger::debug("DEBUG: backend was '{}'", backend);
200+
backend.erase(backend.cbegin());
201+
logger::debug("DEBUG: backend now '{}'", backend);
202+
}
203+
204+
// Verify that the backend string is valid, otherwise ignore the backend.
205+
if ((strcmp(backend.c_str(), "*") != 0) &&
206+
(strcmp(backend.c_str(), "level_zero") != 0) &&
207+
(strcmp(backend.c_str(), "opencl") != 0) &&
208+
(strcmp(backend.c_str(), "cuda") != 0) &&
209+
(strcmp(backend.c_str(), "hip") != 0)) {
210+
logger::debug("ONEAPI_DEVICE_SELECTOR Pre-Filter with illegal "
211+
"backend '{}' ",
212+
backend);
213+
continue;
214+
}
215+
216+
// case-insensitive comparison by converting both tolower
217+
std::transform(platformBackendName.begin(),
218+
platformBackendName.end(),
219+
platformBackendName.begin(),
220+
[](unsigned char c) { return std::tolower(c); });
221+
std::transform(backend.begin(), backend.end(), backend.begin(),
222+
[](unsigned char c) { return std::tolower(c); });
223+
std::size_t nameFound = platformBackendName.find(backend);
224+
225+
bool backendFound = nameFound != std::string::npos;
226+
if (termType == AcceptFilter) {
227+
if (backend.front() != '*' && !backendFound) {
228+
logger::debug(
229+
"The ONEAPI_DEVICE_SELECTOR backend name '{}' was not "
230+
"found in the platform library name '{}'",
231+
backend, platformBackendName);
232+
acceptLibrary = false;
233+
continue;
234+
} else if (backend.front() == '*' || backendFound) {
235+
return UR_RESULT_SUCCESS;
236+
}
237+
} else {
238+
if (backendFound || backend.front() == '*') {
239+
acceptLibrary = false;
240+
logger::debug(
241+
"The ONEAPI_DEVICE_SELECTOR backend name for discard "
242+
"'{}' was found in the platform library name '{}'",
243+
backend, platformBackendName);
244+
continue;
245+
}
246+
}
247+
}
248+
if (acceptLibrary) {
249+
return UR_RESULT_SUCCESS;
250+
}
251+
return UR_RESULT_ERROR_INVALID_VALUE;
252+
}
253+
155254
void discoverKnownAdapters() {
156255
auto searchPathsEnvOpt = getEnvAdapterSearchPaths();
157256
auto loaderLibPathOpt = getLoaderLibPath();
257+
#if defined(_WIN32)
258+
bool loaderPreFilter = getenv_tobool("UR_LOADER_PRELOAD_FILTER", false);
259+
#else
260+
bool loaderPreFilter = getenv_tobool("UR_LOADER_PRELOAD_FILTER", true);
261+
#endif
158262
for (const auto &adapterName : knownAdapterNames) {
263+
264+
if (loaderPreFilter) {
265+
if (readPreFilterODS(adapterName) != UR_RESULT_SUCCESS) {
266+
logger::debug("The adapter '{}' was removed based on the "
267+
"pre-filter from ONEAPI_DEVICE_SELECTOR.",
268+
adapterName);
269+
continue;
270+
}
271+
}
159272
std::vector<fs::path> loadPaths;
160273

161274
// Adapter search order:

test/loader/adapter_registry/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,7 @@ add_adapter_reg_search_test(search-order
5151
SEARCH_PATH ${TEST_SEARCH_PATH}
5252
ENVS "TEST_ADAPTER_SEARCH_PATH=\"${TEST_SEARCH_PATH}\"" "TEST_CUR_SEARCH_PATH=\"${TEST_BIN_PATH}\""
5353
SOURCES search_order.cpp)
54+
55+
add_adapter_reg_search_test(prefilter
56+
SEARCH_PATH ""
57+
SOURCES prefilter.cpp)

test/loader/adapter_registry/fixtures.hpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,49 @@ struct adapterRegSearchTest : ::testing::Test {
7474
}
7575
}
7676
};
77+
#ifndef _WIN32
78+
struct adapterPreFilterTest : ::testing::Test {
79+
ur_loader::AdapterRegistry *registry;
80+
const fs::path levelzeroLibName =
81+
MAKE_LIBRARY_NAME("ur_adapter_level_zero", "0");
82+
std::function<bool(const fs::path &)> islevelzeroLibName =
83+
[this](const fs::path &path) { return path == levelzeroLibName; };
84+
85+
std::function<bool(const std::vector<fs::path> &)> haslevelzeroLibName =
86+
[this](const std::vector<fs::path> &paths) {
87+
return std::any_of(paths.cbegin(), paths.cend(),
88+
islevelzeroLibName);
89+
};
90+
91+
const fs::path openclLibName = MAKE_LIBRARY_NAME("ur_adapter_opencl", "0");
92+
std::function<bool(const fs::path &)> isOpenclLibName =
93+
[this](const fs::path &path) { return path == openclLibName; };
94+
95+
std::function<bool(const std::vector<fs::path> &)> hasOpenclLibName =
96+
[this](const std::vector<fs::path> &paths) {
97+
return std::any_of(paths.cbegin(), paths.cend(), isOpenclLibName);
98+
};
99+
100+
const fs::path cudaLibName = MAKE_LIBRARY_NAME("ur_adapter_cuda", "0");
101+
std::function<bool(const fs::path &)> isCudaLibName =
102+
[this](const fs::path &path) { return path == cudaLibName; };
103+
104+
std::function<bool(const std::vector<fs::path> &)> hasCudaLibName =
105+
[this](const std::vector<fs::path> &paths) {
106+
return std::any_of(paths.cbegin(), paths.cend(), isCudaLibName);
107+
};
108+
109+
void SetUp(std::string filter) {
110+
try {
111+
setenv("ONEAPI_DEVICE_SELECTOR", filter.c_str(), 1);
112+
registry = new ur_loader::AdapterRegistry;
113+
} catch (const std::invalid_argument &e) {
114+
FAIL() << e.what();
115+
}
116+
}
117+
void SetUp() override {}
118+
void TearDown() override { delete registry; }
119+
};
120+
#endif
77121

78122
#endif // UR_ADAPTER_REG_TEST_HELPERS_H
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright (C) 2024 Intel Corporation
2+
// Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions.
3+
// See LICENSE.TXT
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
6+
#include "fixtures.hpp"
7+
8+
#ifndef _WIN32
9+
10+
TEST_F(adapterPreFilterTest, testPrefilterAcceptFilterSingleBackend) {
11+
SetUp("level_zero:*");
12+
auto levelZeroExists =
13+
std::any_of(registry->cbegin(), registry->cend(), haslevelzeroLibName);
14+
EXPECT_TRUE(levelZeroExists);
15+
auto openclExists =
16+
std::any_of(registry->cbegin(), registry->cend(), hasOpenclLibName);
17+
EXPECT_FALSE(openclExists);
18+
auto cudaExists =
19+
std::any_of(registry->cbegin(), registry->cend(), hasCudaLibName);
20+
EXPECT_FALSE(cudaExists);
21+
}
22+
23+
TEST_F(adapterPreFilterTest, testPrefilterAcceptFilterMultipleBackends) {
24+
SetUp("level_zero:*;opencl:*");
25+
auto levelZeroExists =
26+
std::any_of(registry->cbegin(), registry->cend(), haslevelzeroLibName);
27+
EXPECT_TRUE(levelZeroExists);
28+
auto openclExists =
29+
std::any_of(registry->cbegin(), registry->cend(), hasOpenclLibName);
30+
EXPECT_TRUE(openclExists);
31+
auto cudaExists =
32+
std::any_of(registry->cbegin(), registry->cend(), hasCudaLibName);
33+
EXPECT_FALSE(cudaExists);
34+
}
35+
36+
TEST_F(adapterPreFilterTest, testPrefilterDiscardFilterSingleBackend) {
37+
SetUp("!level_zero:*");
38+
auto levelZeroExists =
39+
std::any_of(registry->cbegin(), registry->cend(), haslevelzeroLibName);
40+
EXPECT_FALSE(levelZeroExists);
41+
auto openclExists =
42+
std::any_of(registry->cbegin(), registry->cend(), hasOpenclLibName);
43+
EXPECT_TRUE(openclExists);
44+
auto cudaExists =
45+
std::any_of(registry->cbegin(), registry->cend(), hasCudaLibName);
46+
EXPECT_TRUE(cudaExists);
47+
}
48+
49+
TEST_F(adapterPreFilterTest, testPrefilterDiscardFilterMultipleBackends) {
50+
SetUp("!level_zero:*;!cuda:*");
51+
auto levelZeroExists =
52+
std::any_of(registry->cbegin(), registry->cend(), haslevelzeroLibName);
53+
EXPECT_FALSE(levelZeroExists);
54+
auto openclExists =
55+
std::any_of(registry->cbegin(), registry->cend(), hasOpenclLibName);
56+
EXPECT_TRUE(openclExists);
57+
auto cudaExists =
58+
std::any_of(registry->cbegin(), registry->cend(), hasCudaLibName);
59+
EXPECT_FALSE(cudaExists);
60+
}
61+
62+
TEST_F(adapterPreFilterTest, testPrefilterAcceptAndDiscardFilter) {
63+
SetUp("!cuda:*;level_zero:*");
64+
auto levelZeroExists =
65+
std::any_of(registry->cbegin(), registry->cend(), haslevelzeroLibName);
66+
EXPECT_TRUE(levelZeroExists);
67+
auto openclExists =
68+
std::any_of(registry->cbegin(), registry->cend(), hasOpenclLibName);
69+
EXPECT_FALSE(openclExists);
70+
auto cudaExists =
71+
std::any_of(registry->cbegin(), registry->cend(), hasCudaLibName);
72+
EXPECT_FALSE(cudaExists);
73+
}
74+
75+
TEST_F(adapterPreFilterTest, testPrefilterDiscardFilterAll) {
76+
SetUp("*");
77+
auto levelZeroExists =
78+
std::any_of(registry->cbegin(), registry->cend(), haslevelzeroLibName);
79+
EXPECT_TRUE(levelZeroExists);
80+
auto openclExists =
81+
std::any_of(registry->cbegin(), registry->cend(), hasOpenclLibName);
82+
EXPECT_TRUE(openclExists);
83+
auto cudaExists =
84+
std::any_of(registry->cbegin(), registry->cend(), hasCudaLibName);
85+
EXPECT_TRUE(cudaExists);
86+
}
87+
88+
TEST_F(adapterPreFilterTest, testPrefilterWithInvalidMissingBackend) {
89+
SetUp(":garbage");
90+
auto levelZeroExists =
91+
std::any_of(registry->cbegin(), registry->cend(), haslevelzeroLibName);
92+
EXPECT_TRUE(levelZeroExists);
93+
auto openclExists =
94+
std::any_of(registry->cbegin(), registry->cend(), hasOpenclLibName);
95+
EXPECT_TRUE(openclExists);
96+
auto cudaExists =
97+
std::any_of(registry->cbegin(), registry->cend(), hasCudaLibName);
98+
EXPECT_TRUE(cudaExists);
99+
}
100+
101+
TEST_F(adapterPreFilterTest, testPrefilterWithInvalidBackend) {
102+
SetUp("garbage:0");
103+
auto levelZeroExists =
104+
std::any_of(registry->cbegin(), registry->cend(), haslevelzeroLibName);
105+
EXPECT_TRUE(levelZeroExists);
106+
auto openclExists =
107+
std::any_of(registry->cbegin(), registry->cend(), hasOpenclLibName);
108+
EXPECT_TRUE(openclExists);
109+
auto cudaExists =
110+
std::any_of(registry->cbegin(), registry->cend(), hasCudaLibName);
111+
EXPECT_TRUE(cudaExists);
112+
}
113+
114+
TEST_F(adapterPreFilterTest, testPrefilterWithNotAllAndAcceptFilter) {
115+
SetUp("!*;level_zero");
116+
auto levelZeroExists =
117+
std::any_of(registry->cbegin(), registry->cend(), haslevelzeroLibName);
118+
EXPECT_TRUE(levelZeroExists);
119+
auto openclExists =
120+
std::any_of(registry->cbegin(), registry->cend(), hasOpenclLibName);
121+
EXPECT_FALSE(openclExists);
122+
auto cudaExists =
123+
std::any_of(registry->cbegin(), registry->cend(), hasCudaLibName);
124+
EXPECT_FALSE(cudaExists);
125+
}
126+
127+
TEST_F(adapterPreFilterTest, testPrefilterWithNotAllFilter) {
128+
SetUp("!*");
129+
auto levelZeroExists =
130+
std::any_of(registry->cbegin(), registry->cend(), haslevelzeroLibName);
131+
EXPECT_FALSE(levelZeroExists);
132+
auto openclExists =
133+
std::any_of(registry->cbegin(), registry->cend(), hasOpenclLibName);
134+
EXPECT_FALSE(openclExists);
135+
auto cudaExists =
136+
std::any_of(registry->cbegin(), registry->cend(), hasCudaLibName);
137+
EXPECT_FALSE(cudaExists);
138+
}
139+
140+
#endif

0 commit comments

Comments
 (0)