-
Notifications
You must be signed in to change notification settings - Fork 762
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
[SYCL][Doc] Add kernel_function lambda wrapper #17633
base: sycl
Are you sure you want to change the base?
Conversation
We previously removed the ability to pass properties directly to functions like single_task, parallel_for, etc, in favor of requiring them to be attached to the kernel. By introducing a new kernel_function wrapper that attaches properties to a kernel function, we remove the need for (most) developers to write such wrappers themselves. Signed-off-by: John Pennycook <john.pennycook@intel.com>
While working on the kernel_function definition, I noticed that some text referred to "previous sections" that no longer exist. I tweaked the wording here to reflect that properties must always be attached to the kernel type. Signed-off-by: John Pennycook <john.pennycook@intel.com>
A few notes that may help with review, below. This was my prototype implementation: template <typename Function, typename Properties = syclx::empty_properties_t>
struct kernel_function {
kernel_function(Function f, Properties p = syclx::properties{}) : f(f) {}
template <typename... Args>
std::enable_if_t<std::is_invocable_r_v<void, Function, Args...>>
/*void*/ operator()(Args... args) const {
f(std::forward<Args>(args)...);
}
auto get(syclx::properties_tag) {
return Properties{};
}
private:
const Function f;
}; I had planned to use One templated call operator seemed cleaner to me (and simpler to implement) than defining separate overloads for |
auto get(syclx::properties_tag) { | ||
return Properties{}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, we should be storing properties object, in case it will contain runtime properties in future.
I think we also need to have two version of this - one static constexpr
if Properties
is std::empty_v
, similarly to how properties' get_property
is implemented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, we should be storing properties object, in case it will contain runtime properties in future.
The reason I didn't do this is that anything stored in the function object has to be transferred to the device as one of the kernel arguments. I think it would only make sense to allow this if the run-time properties were intended to be consumed within the kernel.
We'd also have to add an explicit specialization for the case where the property list is empty or contains only compile-time properties, to avoid transferring 1 byte unnecessarily. But perhaps this is a quality of implementation thing.
I think we also need to have two version of this - one
static constexpr
ifProperties
isstd::empty_v
, similarly to how properties'get_property
is implemented.
Can you say more about this? Why would we only want it to be static constexpr
in this case? Could we make it static constexpr
if it doesn't contain any run-time properties, instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we also need to have two version of this - one
static constexpr
ifProperties
isstd::empty_v
, similarly to how properties'get_property
is implemented.Can you say more about this? Why would we only want it to be
static constexpr
in this case? Could we make itstatic constexpr
if it doesn't contain any run-time properties, instead?
std::is_empty
is exactly the check for compile-time-only (no run-time), see
llvm/sycl/include/sycl/ext/oneapi/properties/properties.hpp
Lines 256 to 273 in 5c5954e
// Compile-time property. | |
template <typename property_key_t> | |
static constexpr auto | |
get_property() -> std::enable_if_t<std::is_empty_v<prop_t<property_key_t>>, | |
prop_t<property_key_t>> { | |
return prop_t<property_key_t>{}; | |
} | |
// Runtime property. | |
// Extra operand to make MSVC happy as it complains otherwise: | |
// https://godbolt.org/z/WGqdqrejj | |
template <typename property_key_t> | |
constexpr auto get_property(int = 0) const | |
-> std::enable_if_t<!std::is_empty_v<prop_t<property_key_t>>, | |
prop_t<property_key_t>> { | |
return get_property_impl(detail::property_key_tag<property_key_t>{}); | |
} | |
}; |
I'm not sure if we have dedicated traits in the properties extension though. Maybe we can workaround that by using plain English in constraints/requirements?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But perhaps this is a quality of implementation thing.
Maybe we could protected
inherit from the properties to do that, but I agree about QoI.
This does sound like an issue in the "base" part of the extension though. IMO, having a "getter" for the properties without one for the actual kernel is the root cause of this issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried to address this in be618af; I've removed the implementation details from the synopsis, so that the implementation is no longer normative, and tried to describe the constraints we want.
The synopsis no longer shows what members are stored inside the kernel_function
object, so implementations have the freedom to do whatever they want to meet the specified behavior.
template <typename Function, typename Properties = empty_properties_t> | ||
struct kernel_function { | ||
|
||
kernel_function(Function f, Properties p = syclx::properties{}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kernel_function(Function f, Properties p = syclx::properties{}); | |
kernel_function(Function &&f, Properties p = syclx::properties{}); |
And then std::move(f)
inside implementation. That somewhat limits the applicability, but I'd rather start with that and extend later if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in 3d580ff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, adding &&
to my prototype actually breaks it... Whether I use const &&
or &&
, I get an error about expecting an rvalue. I thought &&
would accept both, but it seems not to work.
We want to support both uses below. What syntax do we need?
auto lambda = [=]() {};
auto kernel = syclx::kernel_function(lambda); // lambda is an l-value
auto kernel = syclx::kernel_function([=]() {}); // lambda is an r-value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://godbolt.org/z/fK36njGse works, but I don't know if that's the correct/idiomatic way. Otherwise, two overloads work too.
|
||
// Available only if Function is invocable with Args | ||
template <typename... Args> | ||
void operator()(Args... args) const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
void operator()(Args... args) const { | |
void operator()(Args&& ...args) const { |
I'm not sure about const
too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in 3d580ff.
Replace this with a detailed description of what the functions do. This is intended to permit implementations to specialize when properties is empty, to avoid transferring properties objects unnecessarily. Signed-off-by: John Pennycook <john.pennycook@intel.com>
void operator()(Args... args) const { | ||
f(std::forward<Args>(args)...); | ||
} | ||
void operator()(Args... args) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
void operator()(Args... args) const; | |
void operator()(Args&& ...args) const; |
is still needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the same as the other suggestion? I'm a bit confused by how GitHub is rendering this.
Signed-off-by: John Pennycook <john.pennycook@intel.com>
syclx:: convention is only for examples. Signed-off-by: John Pennycook <john.pennycook@intel.com>
We previously removed the ability to pass properties directly to functions like
single_task
,parallel_for
, etc, in favor of requiring them to be attached to the kernel.By introducing a new
kernel_function
wrapper that attaches properties to a kernel function, we remove the need for (most) developers to write such wrappers themselves.