Skip to content

[SYCL] Make SYCL_BE force selection of specified BE if there are multiple choices #1522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions sycl/include/CL/sycl/detail/pi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ enum class PiApiKind {
class plugin;
namespace pi {

// The SYCL_PI_TRACE sets what we will trace.
// This is a bit-mask of various things we'd want to trace.
enum TraceLevel {
PI_TRACE_BASIC = 0x1,
PI_TRACE_CALLS = 0x2,
PI_TRACE_ALL = -1
};

// Return true if we want to trace PI related activities.
bool trace(TraceLevel level);

#ifdef SYCL_RT_OS_WINDOWS
#define OPENCL_PLUGIN_NAME "pi_opencl.dll"
#define CUDA_PLUGIN_NAME "pi_cuda.dll"
Expand Down Expand Up @@ -115,8 +126,8 @@ void *getOsLibraryFuncAddress(void *Library, const std::string &FunctionName);
// environment variable.
enum Backend { SYCL_BE_PI_OPENCL, SYCL_BE_PI_CUDA, SYCL_BE_PI_OTHER };

// Check for manually selected BE at run-time.
bool useBackend(Backend Backend);
// Get the preferred BE (selected with SYCL_BE).
Backend getPreferredBE();

// Get a string representing a _pi_platform_info enum
std::string platformInfoToString(pi_platform_info info);
Expand Down
146 changes: 93 additions & 53 deletions sycl/source/detail/pi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
#include <cstring>
#include <iostream>
#include <map>
#include <sstream>
#include <stddef.h>
#include <string>
#include <sstream>

#ifdef XPTI_ENABLE_INSTRUMENTATION
// Include the headers necessary for emitting
Expand Down Expand Up @@ -141,39 +141,80 @@ std::string memFlagsToString(pi_mem_flags Flags) {
return Sstream.str();
}

// Check for manually selected BE at run-time.
static Backend getBackend() {
static const char *GetEnv = std::getenv("SYCL_BE");
// Current default backend as SYCL_BE_PI_OPENCL
// Valid values of GetEnv are "PI_OPENCL", "PI_CUDA" and "PI_OTHER"
std::string StringGetEnv = (GetEnv ? GetEnv : "PI_OPENCL");
static const Backend Use =
std::map<std::string, Backend>{
{ "PI_OPENCL", SYCL_BE_PI_OPENCL },
{ "PI_CUDA", SYCL_BE_PI_CUDA },
{ "PI_OTHER", SYCL_BE_PI_OTHER }
}[ GetEnv ? StringGetEnv : "PI_OPENCL"];
return Use;
// A singleton class to aid that PI configuration parameters
// are processed only once, like reading a string from environment
// and converting it into a typed object.
//
template <typename T, const char *E> class Config {
static Config *m_Instance;
T m_Data;
Config();

public:
static T get() {
if (!m_Instance) {
m_Instance = new Config();
}
return m_Instance->m_Data;
}
};

template <typename T, const char *E>
Config<T, E> *Config<T, E>::m_Instance = nullptr;

// Lists valid configuration environment variables.
static constexpr char SYCL_BE[] = "SYCL_BE";
static constexpr char SYCL_INTEROP_BE[] = "SYCL_INTEROP_BE";
static constexpr char SYCL_PI_TRACE[] = "SYCL_PI_TRACE";

// SYCL_PI_TRACE gives the mask of enabled tracing components (0 default)
template <> Config<int, SYCL_PI_TRACE>::Config() {
const char *Env = std::getenv(SYCL_PI_TRACE);
m_Data = (Env ? std::atoi(Env) : 0);
}

static Backend getBE(const char *EnvVar) {
const char *BE = std::getenv(EnvVar);
const std::map<std::string, Backend> SyclBeMap{
{"PI_OTHER", SYCL_BE_PI_OTHER},
{"PI_CUDA", SYCL_BE_PI_CUDA},
{"PI_OPENCL", SYCL_BE_PI_OPENCL}};
if (BE) {
auto It = SyclBeMap.find(BE);
if (It == SyclBeMap.end())
pi::die("Invalid backend. "
"Valid values are PI_OPENCL/PI_CUDA");
return It->second;
}
// Default backend
return SYCL_BE_PI_OPENCL;
}

// Check for manually selected BE at run-time.
bool useBackend(Backend TheBackend) {
return TheBackend == getBackend();
template <> Config<Backend, SYCL_BE>::Config() { m_Data = getBE(SYCL_BE); }

// SYCL_INTEROP_BE is a way to specify the interoperability plugin.
template <> Config<Backend, SYCL_INTEROP_BE>::Config() {
m_Data = getBE(SYCL_INTEROP_BE);
}

// Helper interface to not expose "pi::Config" outside of pi.cpp
Backend getPreferredBE() { return Config<Backend, SYCL_BE>::get(); }

// GlobalPlugin is a global Plugin used with Interoperability constructors that
// use OpenCL objects to construct SYCL class objects.
std::shared_ptr<plugin> GlobalPlugin;

// Find the plugin at the appropriate location and return the location.
// TODO: Change the function appropriately when there are multiple plugins.
bool findPlugins(vector_class<std::string> &PluginNames) {
bool findPlugins(vector_class<std::pair<std::string, Backend>> &PluginNames) {
// TODO: Based on final design discussions, change the location where the
// plugin must be searched; how to identify the plugins etc. Currently the
// search is done for libpi_opencl.so/pi_opencl.dll file in LD_LIBRARY_PATH
// env only.
PluginNames.push_back(OPENCL_PLUGIN_NAME);
PluginNames.push_back(CUDA_PLUGIN_NAME);
//
PluginNames.push_back(std::make_pair<std::string, Backend>(
OPENCL_PLUGIN_NAME, SYCL_BE_PI_OPENCL));
PluginNames.push_back(
std::make_pair<std::string, Backend>(CUDA_PLUGIN_NAME, SYCL_BE_PI_CUDA));
return true;
}

Expand Down Expand Up @@ -207,52 +248,51 @@ bool bindPlugin(void *Library, PiPlugin *PluginInformation) {
return true;
}

// Load the plugin based on SYCL_BE.
// TODO: Currently only accepting OpenCL and CUDA plugins. Edit it to identify
// and load other kinds of plugins, do the required changes in the
// findPlugins, loadPlugin and bindPlugin functions.
bool trace(TraceLevel Level) {
auto TraceLevelMask = Config<int, SYCL_PI_TRACE>::get();
return (TraceLevelMask & Level) == Level;
}

// Initializes all available Plugins.
vector_class<plugin> initialize() {
vector_class<plugin> Plugins;

if (!useBackend(SYCL_BE_PI_OPENCL) && !useBackend(SYCL_BE_PI_CUDA)) {
die("Unknown SYCL_BE");
}

bool EnableTrace = (std::getenv("SYCL_PI_TRACE") != nullptr);

vector_class<std::string> PluginNames;
vector_class<std::pair<std::string, Backend>> PluginNames;
findPlugins(PluginNames);

if (PluginNames.empty() && EnableTrace)
std::cerr << "No Plugins Found." << std::endl;
if (PluginNames.empty() && trace(PI_TRACE_ALL))
std::cerr << "SYCL_PI_TRACE[-1]: No Plugins Found." << std::endl;

PiPlugin PluginInformation; // TODO: include.
PiPlugin PluginInformation;
for (unsigned int I = 0; I < PluginNames.size(); I++) {
void *Library = loadPlugin(PluginNames[I]);
void *Library = loadPlugin(PluginNames[I].first);

if (!Library) {
if (EnableTrace) {
std::cerr << "Check if plugin is present. Failed to load plugin: "
<< PluginNames[I] << std::endl;
if (trace(PI_TRACE_ALL)) {
std::cerr << "SYCL_PI_TRACE[-1]: Check if plugin is present. "
<< "Failed to load plugin: " << PluginNames[I].first
<< std::endl;
}
continue;
}

if (!bindPlugin(Library, &PluginInformation) && EnableTrace) {
std::cerr << "Failed to bind PI APIs to the plugin: " << PluginNames[I]
<< std::endl;
}
if (useBackend(SYCL_BE_PI_OPENCL) &&
PluginNames[I].find("opencl") != std::string::npos) {
// Use the OpenCL plugin as the GlobalPlugin
GlobalPlugin = std::make_shared<plugin>(PluginInformation);
if (!bindPlugin(Library, &PluginInformation)) {
if (trace(PI_TRACE_ALL)) {
std::cerr << "SYCL_PI_TRACE[-1]: Failed to bind PI APIs to the plugin: "
<< PluginNames[I].first << std::endl;
}
continue;
}
if (useBackend(SYCL_BE_PI_CUDA) &&
PluginNames[I].find("cuda") != std::string::npos) {
// Use the CUDA plugin as the GlobalPlugin
GlobalPlugin = std::make_shared<plugin>(PluginInformation);
// Set the Global Plugin based on SYCL_INTEROP_BE.
// Rework this when it will be explicit in the code which BE is used in the
// interoperability methods.
if (Config<Backend, SYCL_INTEROP_BE>::get() == PluginNames[I].second) {
GlobalPlugin =
std::make_shared<plugin>(PluginInformation, PluginNames[I].second);
}
Plugins.push_back(plugin(PluginInformation));
Plugins.emplace_back(plugin(PluginInformation, PluginNames[I].second));
if (trace(TraceLevel::PI_TRACE_BASIC))
std::cerr << "SYCL_PI_TRACE[1]: Plugin found and successfully loaded: "
<< PluginNames[I].first << std::endl;
}

#ifdef XPTI_ENABLE_INSTRUMENTATION
Expand Down
14 changes: 7 additions & 7 deletions sycl/source/detail/plugin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ class plugin {
public:
plugin() = delete;

plugin(RT::PiPlugin Plugin) : MPlugin(Plugin) {
MPiEnableTrace = (std::getenv("SYCL_PI_TRACE") != nullptr);
}
plugin(RT::PiPlugin Plugin, RT::Backend UseBackend)
: MPlugin(Plugin), MBackend(UseBackend) {}

~plugin() = default;

Expand All @@ -52,13 +51,13 @@ class plugin {
template <PiApiKind PiApiOffset, typename... ArgsT>
RT::PiResult call_nocheck(ArgsT... Args) const {
RT::PiFuncInfo<PiApiOffset> PiCallInfo;
if (MPiEnableTrace) {
if (pi::trace(pi::TraceLevel::PI_TRACE_CALLS)) {
std::string FnName = PiCallInfo.getFuncName();
std::cout << "---> " << FnName << "(" << std::endl;
RT::printArgs(Args...);
}
RT::PiResult R = PiCallInfo.getFuncPtr(MPlugin)(Args...);
if (MPiEnableTrace) {
if (pi::trace(pi::TraceLevel::PI_TRACE_CALLS)) {
std::cout << ") ---> ";
RT::printArgs(R);
}
Expand All @@ -74,10 +73,11 @@ class plugin {
checkPiResult(Err);
}

RT::Backend getBackend(void) const { return MBackend; }

private:
RT::PiPlugin MPlugin;
bool MPiEnableTrace;

const RT::Backend MBackend;
}; // class plugin
} // namespace detail
} // namespace sycl
Expand Down
3 changes: 2 additions & 1 deletion sycl/source/detail/program_manager/program_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ static bool isDeviceBinaryTypeSupported(const context &C,
}

// OpenCL 2.1 and greater require clCreateProgramWithIL
if (pi::useBackend(pi::SYCL_BE_PI_OPENCL) &&
pi::Backend CBackend = (detail::getSyclObjImpl(C)->getPlugin()).getBackend();
if ((CBackend == pi::SYCL_BE_PI_OPENCL) &&
C.get_platform().get_info<info::platform::version>() >= "2.1")
return true;

Expand Down
2 changes: 1 addition & 1 deletion sycl/source/detail/scheduler/commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1673,7 +1673,7 @@ cl_int ExecCGCommand::enqueueImp() {
Requirement *Req = (Requirement *)(Arg.MPtr);
AllocaCommandBase *AllocaCmd = getAllocaForReq(Req);
RT::PiMem MemArg = (RT::PiMem)AllocaCmd->getMemAllocation();
if (RT::useBackend(pi::Backend::SYCL_BE_PI_OPENCL)) {
if (Plugin.getBackend() == (pi::Backend::SYCL_BE_PI_OPENCL)) {
Plugin.call<PiApiKind::piKernelSetArg>(Kernel, Arg.MIndex,
sizeof(RT::PiMem), &MemArg);
} else {
Expand Down
45 changes: 45 additions & 0 deletions sycl/source/device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,51 @@ vector_class<device> device::get_devices(info::device_type deviceType) {
}
}
}

// If SYCL_BE is set and there are multiple devices of the same type
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused with meaning of SYCL_BE var. In another PR you are making so that SYCL_BE makes some preference towards specified BE in the selectors, but this patch removes all devices that do not match specified BE.
The documentation says that this var controls which BE will be used when a device is chosen using default selector only.
I think we should have consistent behavior and update documentation. Probably we need to introduce new env var if we need two different behaviors(force and preference).

// supported by different BE, and one of the devices is from SYCL_BE
// then only add that (and remove all others). This allows to force
// selection of a specific BE for a target, while running on other
// targets, unsupported by the SYCL_BE, with other BEs.
//
if (std::getenv("SYCL_BE")) {
vector_class<device> filtered_devices;
auto SyclBE = detail::pi::getPreferredBE();

// On the first pass see which device types are supported with SYCL_BE
pi_uint64 TypesSupportedBySyclBE = 0; // bit-set of info::device_type
for (const auto &dev : devices) {
if (dev.is_host())
continue;
auto BE = detail::getSyclObjImpl(dev)->getPlugin().getBackend();
if (BE == SyclBE) {
TypesSupportedBySyclBE |=
(pi_uint64)dev.get_info<info::device::device_type>();
}
}
// On the second pass only add devices that are from SYCL_BE or not
// supported there.
//
for (const auto &dev : devices) {
if (dev.is_host()) {
// TODO: decide if we really want to add the host here.
// The cons of doing so is that if SYCL_BE is set but that BE
// is unavailable for whatever reason, the execution would silently
// proceed to the host while people may think it is running
// with the SYCL_BE as they wanted.
//
filtered_devices.push_back(dev);
continue;
}

auto BE = detail::getSyclObjImpl(dev)->getPlugin().getBackend();
auto Type = (pi_uint64)dev.get_info<info::device::device_type>();
if (BE == SyclBE || (TypesSupportedBySyclBE & Type) == 0) {
filtered_devices.push_back(dev);
}
}
return filtered_devices;
}
return devices;
}

Expand Down
Loading