Skip to content
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

RFC for "Passed Directly" Customization Point #1999

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
45248dd
initial draft for customization point
danhoeflinger Jan 13, 2025
5ff56d9
extra question
danhoeflinger Jan 13, 2025
643a2cb
spelling
danhoeflinger Jan 13, 2025
6c0fc33
brief comment on implementation details
danhoeflinger Jan 13, 2025
3bacd14
Adding a simpler example
danhoeflinger Jan 13, 2025
f24f8d2
spelling
danhoeflinger Jan 13, 2025
1145efa
name update and capitalization
danhoeflinger Jan 13, 2025
d1d0718
name adjustment
danhoeflinger Jan 14, 2025
81ad0a2
mend
danhoeflinger Jan 14, 2025
36b8ec5
rename directory
danhoeflinger Jan 14, 2025
b0dac4f
address feedback and minor adjustments
danhoeflinger Jan 14, 2025
4823c6e
improvements for clarity
danhoeflinger Jan 14, 2025
b79e7f5
adding the wrapper class alternative
danhoeflinger Jan 14, 2025
47b8be3
spacing
danhoeflinger Jan 14, 2025
92b1d33
updating with new strategy
danhoeflinger Jan 15, 2025
5c45e7d
improvements to language and strategy
danhoeflinger Jan 15, 2025
8bcc334
change header to type_traits
danhoeflinger Jan 16, 2025
27dc9ba
reverting accidental gitignore checkin
danhoeflinger Mar 10, 2025
60cd1a9
expand open questions
danhoeflinger Mar 11, 2025
2ddaf9d
revisions for clarity and specificity
danhoeflinger Mar 11, 2025
497b8c6
spelling grammar, minor edits
danhoeflinger Mar 11, 2025
7837319
typo
danhoeflinger Mar 11, 2025
79adf34
edits for clarity
danhoeflinger Mar 20, 2025
5d704ab
edits for clarity
danhoeflinger Mar 20, 2025
91b77a2
added proposed names
danhoeflinger Mar 20, 2025
0474d92
adding note about naming
danhoeflinger Mar 20, 2025
29a8374
separating trait name and ADL name, other feedback addressed
danhoeflinger Mar 21, 2025
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
170 changes: 170 additions & 0 deletions rfcs/proposed/iterators_pass_directly_customization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Passed Directly Customization Point for User Defined Types

## Introduction

oneDPL handles some types of input data automatically as input to its device backend (SYCL-based) as described
[here](https://uxlfoundation.github.io/oneDPL/parallel_api/pass_data_algorithms.html). Unified Shared Memory (USM)
pointers refer to data that is inherently device accessible, so no processing is required to pass this type of input
data to SYCL kernels. We refer to this trait as "passed directly". oneDPL also defines some rules for its provided
[iterator types](https://uxlfoundation.github.io/oneDPL/parallel_api/iterators.html) to be passed directly to SYCL
under some circumstances (usually based on their base types).

Internally, these rules are defined with a trait `oneapi::dpl::__ranges::is_passed_directly<T>` which evaluates to
`std::true_type` or `std::false_type` to indicate whether the iterator type `T` should be passed directly to SYCL
kernels. There exists an unofficial legacy `is_passed_directly` trait which types can define like this:
`using is_passed_directly = std::true_type;` which is supported within oneDPL. This method is currently used for a
number of helper types within the SYCLomatic compatibility headers (`device_pointer`, `device_iterator`,
`tagged_pointer`, `constant_iterator`, `iterator_adaptor`). There is no official public API for users who want to
create their own iterator types that could be passed directly to SYCL kernels. This is a gap that should be filled
with an official public API.

Without something like this, users are forced to rely only upon our provided iterator types or reach into implementation
details that are not part of oneDPL's specified interface.

## Proposal
### High Level Proposal

We will create an Argument-Dependent Lookup (ADL) customization point which defines if iterators are "passed directly"
`is_passed_directly_in_onedpl_device_policies`. Users may define if their iterator types should be "passed directly" by
oneDPL by defining a constexpr function in the same namespace where the iterator is defined.
`is_passed_directly_in_onedpl_device_policies` must accept a const lvalue reference to the iterator type being
specialized and return `std::true_type{}` if the iterator type is "passed directly" and `std::false_type{}` otherwise.

Additionally, oneDPL will provide a public trait,
`inline constexpr bool oneapi::dpl::is_passed_directly_in_onedpl_device_policies_v<T>`, indicating if the iterator type
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. oneapi::dpl::is_passed_directly_in_onedpl_device_policies_v has double info about "oneDPL":
    I mean "oneapi::dpl" and substring "onedpl".
  2. A suffix "_device_policies" is too long.... I would vote to omit that.
    So, I would suggest name oneapi::dpl::is_passed_directly_v

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For now, I think the idea is to merge this RFC with the naming and location as an open question, to be decided in the discussion of the specification PR. All the same, thanks for your input here.

Also some context for the individual points:

  1. The idea behind this is that the public trait follows the naming convention of the ADL customization point function. The ADL customization point function needs to identify itself as onedpl because it user's customizations will be in their own namespaces. However, we could decide not to have the public trait share naming with the ADL function, and I would agree with removing this redundancy then.

  2. There was an earlier discussion in this PR RFC for "Passed Directly" Customization Point #1999 (comment) , I think its a difficult decision with many opinions. I don't really have strong opinions here and am happy to go with the group's decision.

Copy link
Contributor

Choose a reason for hiding this comment

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

Are we also going to introduce a struct as a trait? Like below

template <typename T>
struct oneapi::dpl::is_passed_directly_in_onedpl_device_policies {};

I would write an honest template variable

Suggested change
`inline constexpr bool oneapi::dpl::is_passed_directly_in_onedpl_device_policies_v<T>`, indicating if the iterator type
```cpp
template <typename T
inline constexpr bool oneapi::dpl::is_passed_directly_in_onedpl_device_policies_v;

indicating if the iterator type


instead of what we have because it look like partial specialization or like explicit instantiation from the C++ standpoint. Until of course we want to keep some implementation freedom, which I don't see the space for in this case

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are we also going to introduce a struct as a trait?

Its a good question, and probably needs to be handled in connection with the question of naming, and if the ADL function should be named the same as the trait.

Now I am thinking that it would be better to make their names distinct, and then to offer both the struct and value version of the trait
oneapi::dpl::is_passed_directly_in_onedpl_device_policies[_v] as you describe. This is the more normal convention, but requires divorcing the names between the ADL customization point and the trait.

`T` is "passed directly". This public trait is intended to be used to help define
`is_passed_directly_in_onedpl_device_policies` for wrapper iterator types which depend on the "passed directly" status
of their base iterator(s) using only the base iterator type(s) rather than a named instance. It may also be used to
confirm user iterator types "passed directly" traits are as intended to prevent unnecessary overhead in oneDPL calls.

### Additional Information

The default implementation of the customization point `is_passed_directly_in_onedpl_device_policies` will be used to
explicitly mark the following iterators as "passed directly":
* Pointers (assumes USM pointers)
* Iterators containing the legacy `using is_passed_directly = true` trait defined within the type definition
* Iterators to USM shared allocated `std::vectors` (when knowable)
* `std::reverse_iterator<Iter>` when `Iter` is also "passed directly"

oneDPL will define the "passed directly" definitions of its custom iterators as follows:
* `zip_iterator` is "passed directly" when all base iterators are "passed directly"
* `counting_iterator` and `discard_iterator` are always "passed directly"
* `transform_iterator` is "passed directly" if its source iterator is "passed directly"
* `permutation_iterator` is "passed directly" if both its source iterator and its index map are "passed directly"

### Implementation Details

When using device policies, oneDPL will run compile-time checks on argument iterator types by calling
`is_passed_directly_in_onedpl_device_policies`. If `std::true_type{}` is returned, oneDPL will pass the iterator
directly to SYCL kernels rather than copying the data into `sycl::buffers` and using those buffers to transfer data to
SYCL kernels.

The specification and implementation will be prepared once this RFC is accepted as "proposed". We do not intend to offer
this first as experimental. This RFC will target "Supported" once the specification and implementation are accepted.

### Examples

Below is a simple example of an iterator and ADL customization point definition which is always "passed directly".

```
namespace user
{

struct my_passed_directly_iterator
{
/* unspecified user definition */
};

constexpr
auto
is_passed_directly_in_onedpl_device_policies(const my_passed_directly_iterator&)
{
return std::true_type{};
}
Comment on lines +94 to +99
Copy link
Contributor

Choose a reason for hiding this comment

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

by decltype-ing (see below) function I mean that this customization might look like

std::true_type is_passed_directly_in_onedpl_device_policies(const my_passed_directly_iterator&);

without any body and it would work.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would consider this not a typical example. Typically there should be some logic, like in the next example.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thinking about the way we use it for our own internal iterator types, I think normally there will be some logic, but there are also some iterator types which have signatures like this. (counting_iterator, discard_iterator)

} //namespace user
```

Users can use any `constexpr` logic based on their iterator to determine if the iterator can be passed directly into a
Copy link
Contributor

Choose a reason for hiding this comment

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

Users can? or users shall? I feel it's a requirement to use at least constexpr function for us to be able to get the result at compile time.

Also are we going to call it or are we going to "decltype" it? I might affect whether it shall be constexpr or not

Copy link
Contributor

Choose a reason for hiding this comment

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

"Users can use any logic" it says.

It is a requirement that the function is constexpr, and it is stated earlier. Remember that this is not a formal specification but design description.

The proposal says "When using device policies, oneDPL will run compile-time checks on argument iterator types by calling is_passed_directly_in_onedpl_device_policies.". It might be not 100% accurate but the intent is clear (at least to me).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've improved the language a bit to make it clear that any logic may be used, as long as it is constexpr logic.

SYCL kernel without any processing. Below is an example of a type which contains a pair of iterators and should be
treated as passed directly if and only if both base iterators are also passed directly. Note that the use of the public
trait enables the use of the base iterator type alone without creating a named instance of the base iterator to pass
into the ADL.

```
namespace user
{
template <typename It1, typename It2>
struct iterator_pair
{
It1 first;
It2 second;
};

template <typename It1, typename It2>
constexpr
auto is_passed_directly_in_onedpl_device_policies(const iterator_pair<It1, It2>&)
{
if constexpr (oneapi::dpl::is_passed_directly_in_onedpl_device_policies_v<It1> &&
oneapi::dpl::is_passed_directly_in_onedpl_device_policies_v<It2>)
return std::true_type{};
else
return std::false_type{};
}
} //namespace user
Copy link
Contributor

Choose a reason for hiding this comment

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

this function may be rewritten simpler. See below:

template <typename It1, typename It2>
constexpr auto is_passed_directly_in_onedpl_device_policies(const iterator_pair<It1, It2>&)
{
    return std::bool_constant<(oneapi::dpl::is_passed_directly_in_onedpl_device_policies_v<It1> &&
                  oneapi::dpl::is_passed_directly_in_onedpl_device_policies_v<It2>)>{};
}

Furthermore, it be be rewritten without a body if we want to support decltype customization style

template <typename It1, typename It2>
auto is_passed_directly_in_onedpl_device_policies(const iterator_pair<It1, It2>&) ->
    std::bool_constant<(oneapi::dpl::is_passed_directly_in_onedpl_device_policies_v<It1> &&
                                     oneapi::dpl::is_passed_directly_in_onedpl_device_policies_v<It2>)>

It seems like nobody seriously considered functions without body design aspect

Copy link
Contributor

Choose a reason for hiding this comment

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

It is more concise but not really simpler. Brevity does not equal to clarity. I personally think that the "decltype" variant is harder to comprehend (and so to write properly).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

While I'm not sure that your alternative is "simpler", I've included it as another example for those who do.

I prefer the first version for readability, however I may be missing some advantage the second provides technically.

```

## Alternatives Considered
### Public Trait Struct Explicit Specialization
oneDPL could make public our internal structure `oneapi::dpl::__ranges::is_passed_directly` as
`oneapi::dpl::is_passed_directly` for users to specialize to define rules for their types. This would be a similar
mechanism to `sycl::is_device_copyable`. The implementation details of this option should avoid some complexity required
to properly implement the customization point.

However, as we have learned from experience within oneDPL, explicit specialization of a structure in another library's
namespace creates maintenance problems. It either requires lots of closing of nested namespaces, opening of the external
library's namespace for the specialization, or it requires separating these specializations to a separate location
removed from the types they are specializing for. oneDPL has chosen to use the latter, which can be seen in
`include/oneapi/dpl/pstl/hetero/dpcpp/sycl_traits.h`. This has led to several errors where changes to structures should
have included changes to sycl_traits but did not, and needed to be fixed later.

In an effort to avoid this same issue for our users, we propose a similar method but instead with a constexpr
customization point, allowing the user to override that customization point within their own namespace as a free
function.

### Require Specifically Named Typedef / Using in User's Type
oneDPL could make official our requirements for users' types to include a typedef or using statement to define if the
type is "passed directly" like `using is_passed_directly = std::true_type;`, where the absence of this would be
equivalent to a `std::false_type`.

However, this clutters the user type definitions with specifics of oneDPL. It also may not be as clear what this
signifies for maintenance of user code without appropriate comments describing the details of oneDPL and SYCL. Users
have expressed that this is undesirable.

### Wrapper Class
oneDPL could provide some wrapper iterator `direct_iterator` which wraps an arbitrary base iterator and marks it as
"passed directly". `direct_iterator` could utilize either of the above alternatives to accomplish this and signal that
the iterator should be "passed directly". It would need to pass through all operations to the wrapped base iterator and
make sure no overhead is added in its usage.

There is some complexity in adding such a wrapper iterator, and it would need to be considered carefully to make sure no
problems would be introduced. This wrapper class may obfuscate users' types and make them more unwieldy to use. It is
also less expressive than the other options in that it only has the ability to unilaterally mark a type as "passed
directly". There is no logic that can be used to express some iterator type which may be conditionally "passed
directly", other than to have logic to conditionally apply the wrapper in the first place. This option seems less clear
and has more opportunity to cause problems.

## Testing
We will need a detailed test checking both positive and negative responses to
`oneapi::dpl::is_passed_directly_in_onedpl_device_policies_v` to ensure they have the expected result. This should
include tests with custom types, combinations of iterators, USM pointers, etc.

## Open Questions

* Is there a better, more concise name than `oneapi::dpl::is_passed_directly_in_onedpl_device_policies[_v]` that
* properly conveys the meaning to the users?
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* Is there a better, more concise name than `oneapi::dpl::is_passed_directly_in_onedpl_device_policies[_v]` that
* properly conveys the meaning to the users?
* Is there a better, more concise name than `oneapi::dpl::is_passed_directly_in_onedpl_device_policies[_v]` that
properly conveys the meaning to the users?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks, fixed.

* Other names proposed:
Copy link
Contributor

Choose a reason for hiding this comment

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

oneapi::dpl::is_passed_directly_to_device ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added the two proposed names you raised and a note to the open questions section.

Copy link
Contributor

Choose a reason for hiding this comment

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

I also raised the naming question.

I like the one that proposed by @MikeDvorskiy. I also thought about is_passed_directly_for_hetero_policies if we think about future extensibility of this trait beyond SYCL (hypothetical scenario for now)

just is_passed_directly also good enough to me

Copy link
Contributor

Choose a reason for hiding this comment

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

We might also consider one name for a trait itself and a different name for the customization function

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've separated the names of the ADL customization point and the public trait. This allows us to provide both the struct and value version of the trait, which is the norm.

* `oneapi::dpl::onedpl_is_iterator_device_ready[_v]`
* `oneapi::dpl::is_passed_directly_to_sycl_backend[_v]`
* `oneapi::dpl::requires_explicit_data_transfer_onedpl_device_policies[_v]` (inverted)
* Where should this be located?
* Possible options include `oneapi/dpl/iterator`, `oneapi/dpl/type_traits`.