Skip to content

Commit 70e85f2

Browse files
authored
Merge pull request #2053 from nrspruit/ur_loader_prefilter
[LOADER] Added Prefilter using ONEAPI_DEVICE_SELECTOR to remove backends
2 parents 8c9dd7e + 2fcf3bf commit 70e85f2

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
@@ -163,10 +163,123 @@ class AdapterRegistry {
163163
return paths.empty() ? std::nullopt : std::optional(paths);
164164
}
165165

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

172285
// 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)