-
Notifications
You must be signed in to change notification settings - Fork 790
[SYCL][Doc] Add kernel_function lambda wrapper #17633
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
base: sycl
Are you sure you want to change the base?
Changes from 2 commits
f6581d6
82de663
be618af
3d580ff
160fff7
1743542
bf7b8f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -287,16 +287,17 @@ by the property, the implementation must throw a synchronous exception with the | |||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
|=== | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
== Embedding Properties into a Kernel | ||||||||||||||||||||||||||||||||||||||||||||||
=== Embedding Properties into a Kernel | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
In other situations it may be useful to embed a kernel's properties directly | ||||||||||||||||||||||||||||||||||||||||||||||
into its type, to ensure that a kernel cannot be launched without a property | ||||||||||||||||||||||||||||||||||||||||||||||
that it depends upon for correctness. | ||||||||||||||||||||||||||||||||||||||||||||||
A kernel's properties are embedded directly into its type, to ensure that a | ||||||||||||||||||||||||||||||||||||||||||||||
kernel cannot be launched without a property that it depends upon for | ||||||||||||||||||||||||||||||||||||||||||||||
correctness. | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
To enable this use-case, this extension adds a mechanism for implementations to | ||||||||||||||||||||||||||||||||||||||||||||||
extract a property list from a kernel functor, if a kernel functor declares | ||||||||||||||||||||||||||||||||||||||||||||||
a member function named `get` accepting a `sycl::ext::oneapi::experimental::properties_tag` | ||||||||||||||||||||||||||||||||||||||||||||||
tag type and returning an instance of `sycl::ext::oneapi::experimental::properties`. | ||||||||||||||||||||||||||||||||||||||||||||||
To enable this, this extension adds a mechanism for implementations to extract | ||||||||||||||||||||||||||||||||||||||||||||||
a property list from a kernel functor, if a kernel functor declares a member | ||||||||||||||||||||||||||||||||||||||||||||||
function named `get` accepting a | ||||||||||||||||||||||||||||||||||||||||||||||
`sycl::ext::oneapi::experimental::properties_tag` tag type and returning an | ||||||||||||||||||||||||||||||||||||||||||||||
instance of `sycl::ext::oneapi::experimental::properties`. | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
```c++ | ||||||||||||||||||||||||||||||||||||||||||||||
namespace sycl { | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -323,8 +324,8 @@ attributes to be applied to different call operators within the same | |||||||||||||||||||||||||||||||||||||||||||||
functor. An embedded property list applies to all call operators in | ||||||||||||||||||||||||||||||||||||||||||||||
the functor. | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
The example below shows how the kernel from the previous section could be | ||||||||||||||||||||||||||||||||||||||||||||||
rewritten to leverage an embedded property list: | ||||||||||||||||||||||||||||||||||||||||||||||
The example below shows how a simple vector addition kernel could be | ||||||||||||||||||||||||||||||||||||||||||||||
written to leverage an embedded property list: | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
```c++ | ||||||||||||||||||||||||||||||||||||||||||||||
struct KernelFunctor { | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -363,6 +364,62 @@ diagnostic; invalid combinations that can only be detected at run-time should | |||||||||||||||||||||||||||||||||||||||||||||
result in an implementation throwing an `exception` with the `errc::invalid` | ||||||||||||||||||||||||||||||||||||||||||||||
error code. | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
=== Using Properties with Lambda Expressions | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
When a SYCL kernel is defined via a lambda expression, there is no way to | ||||||||||||||||||||||||||||||||||||||||||||||
define a `get` member function and subsequently no way to embed kernel | ||||||||||||||||||||||||||||||||||||||||||||||
properties. Instead, developers must wrap the lambda expression in an object. | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
To simplify this usage pattern, this extension defines a `kernel_function` | ||||||||||||||||||||||||||||||||||||||||||||||
that encapsulates a kernel function (which may be a lambda expression) and a | ||||||||||||||||||||||||||||||||||||||||||||||
property list. | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
NOTE: Developers are free to extend `kernel_function` or define their own | ||||||||||||||||||||||||||||||||||||||||||||||
wrapper classes (e.g., to attach commonly used property lists). | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
```c++ | ||||||||||||||||||||||||||||||||||||||||||||||
namespace sycl::ext::oneapi::experimental { | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
template <typename Function, typename Properties = empty_properties_t> | ||||||||||||||||||||||||||||||||||||||||||||||
struct kernel_function { | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
kernel_function(Function f, Properties p = syclx::properties{}); | ||||||||||||||||||||||||||||||||||||||||||||||
|
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.
Outdated
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.
Outdated
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.
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.
Why are we making accommodations for runtime kernel properties? Aren't kernel properties always compile-time by their very nature? If you think you want a runtime kernel property, then you must really want a launch property (whose value can change each time a kernel is launched). We already have a way to specify launch properties whose values are defined at runtime.
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 can make either work. I thought compile-time only properties made sense, but several people have asked about run-time properties now.
If we restrict this to compile-time properties only, then we're saying that the only properties that can be attached to a kernel function are properties that affect the actual compilation of the kernel. There'd be no room for properties that were "properties of the kernel" rather than "properties of how the kernel should be launched", and we couldn't change this later.
I'm happy to stick with compile-time only if everybody is confident that we'll never need run-time properties, or that we'll always be able to express run-time properties as a property of the launch in a way that makes sense semantically.
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.
We added kernel properties as a replacement for the C++ attribute decorations we have in SYCL 2020. C++ attributes are always a compile-time thing, so we do not need runtime properties to achieve our original goal.
I'm having a hard time imagining a runtime kernel property. It seems like you could either use a launch property (if it affected the way the kernel was launched) or just pass a kernel argument (if it changed the way the kernel runs). Do you have an example of a runtime kernel property that wouldn't fit into either of these categories?
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 don't have any run-time kernel properties in mind, no. @aelovikov-intel - Do you have any in mind?
If nobody has any suggestions for run-time kernel properties... Since the current plan is to do this via two different overloads, could we just remove the non-static constexpr
one, and revisit this later? We could always add the other overload later.
It would still make sense to have the Constraint that the static constexpr
overload is only available with compile-time property lists, because we wouldn't be exposing run-time properties. As long as we have that constraint, there'd be no problem with introducing another overload later.
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.
Trunk version has
llvm/sycl/doc/extensions/experimental/sycl_ext_oneapi_kernel_properties.asciidoc
Lines 341 to 344 in a123a74
auto get(sycl::ext::oneapi::experimental::properties_tag) { | |
return sycl::ext::oneapi::experimental::properties{sycl::ext::oneapi::experimental::work_group_size<8, 8>, | |
sycl::ext::oneapi::experimental::sub_group_size<8>}; | |
} |
If only compile-time properties are accepted, the extension needs to be consistent throughout. Also, if compile-time only, why
static
getter? Can't we just have an optional type alias? That will always be compile-time only...
Uh oh!
There was an error while loading. Please reload this page.