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

Fix adjacent difference #6922

Draft
wants to merge 25 commits into
base: develop
Choose a base branch
from

Conversation

yasahi-hpc
Copy link
Contributor

@yasahi-hpc yasahi-hpc commented Apr 6, 2024

Resolves #6890

This PR aims at adding assertion in adjacent_difference to disallow the overlapping of source and destination.
expect_no_overlap is newly added which is enabled in debug mode only. In the debug mode, it checks

// KOKKOS_EXPECTS(first < s_first && s_last < last); // previous implementation
KOKKOS_EXPECTS(first > s_last1 || last < s_first1); // current implementation

It disallows the case 0 but still allows cases 1 and 2.

  1. Case 1 can be disabled if decltype(begin(const_a)) is constructible from decltype(begin(a)).
  2. In order to suppress Case 2, O (n) of comparisons may be needed.
// Case 0 No longer allowed
Kokkos::View<double*> a("A",N0);
auto res1 = KE::adjacent_difference("label", exespace(), a, a, args...);

// Case 1 still allowed
Kokkos::View<const double*> const_a(a);
auto res2 = KE::adjacent_difference("label", exespace(), const_a, a, args...);

// Case 2 still allowed (element b (1, 1) overlaps)
Kokkos::View<double**> b("B",N0, N1);
auto sub_b0 = Kokkos::subview(b, 0, Kokkos::pair<int, int>(1, 5) );
auto sub_b1 = Kokkos::subview(b, Kokkos::pair<int, int>(0, 4), 1 );
auto res3 = KE::adjacent_difference("label", exespace(), sub_b0, sub_b1, args...);

@yasahi-hpc yasahi-hpc marked this pull request as draft April 6, 2024 14:27
@yasahi-hpc yasahi-hpc changed the base branch from master to develop April 6, 2024 14:29
IteratorType2 s_last) {
if constexpr (std::is_constructible_v<IteratorType2, IteratorType1>) {
IteratorType1 s_first1(s_first), s_last1(s_last);
KOKKOS_EXPECTS(first < s_first1 && s_last1 < last);
Copy link
Member

Choose a reason for hiding this comment

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

don't you want to check instead:

( first > s_last1 ) || ( last < s_first1 )

?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is better. I fixed accordingly

IteratorType2 s_last) {
if constexpr (std::is_constructible_v<IteratorType2, IteratorType1>) {
IteratorType1 s_first1(s_first), s_last1(s_last);
KOKKOS_EXPECTS(first > s_last1 || last < s_last1);
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
KOKKOS_EXPECTS(first > s_last1 || last < s_last1);
KOKKOS_EXPECTS(first > s_last1 || last < s_first1);

Copy link
Contributor

Choose a reason for hiding this comment

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

Are these checks correct for strided Views?

Copy link
Member

Choose a reason for hiding this comment

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

If the iterators are convertible to each other, they should refer to the same stride, no?

Copy link
Contributor

Choose a reason for hiding this comment

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

I was imagining a scenario where you could have iterators of the same type with the same stride but different starting pointers so that they don't overlap. For example, the location in memory would be alternating between the first iterator and the second iterator.

Copy link
Member

Choose a reason for hiding this comment

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

True... How do incomparable iterators compare in C++?

Copy link
Member

Choose a reason for hiding this comment

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

I think you can check for all Kokkos::view::iterator in case the referenced view is the same, even with non unit stride. This would case the most common case, wouldn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to add a method to Kokkos::iterator to compare internal views?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, Kokkos::Experimental::Impl::RandomAccessIterator. Note that you don't have direct access to its stride but you can get it from

auto next_iterator = iterator;
auto stride  = &*(++next_iterator) - &*iterator;

(assuming the View access operator returns a reference). Otherwise, you would need to add a get_view member function to RandomAccessIterator to obtain the stride directly.

Copy link
Contributor Author

@yasahi-hpc yasahi-hpc Apr 9, 2024

Choose a reason for hiding this comment

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

That is nice. So the check would be something like this? (assuming the View access operator returns a reference)

auto next_first = first;
auto stride  = &*(++next_first) - &*first;
if constexpr (std::is_constructible_v<IteratorType2, IteratorType1>) {
  IteratorType1 s_first1(s_first), s_last1(s_last);
  auto first_diff = &*first - &*s_first1;
  bool is_no_overlap = first_diff % stride;
  KOKKOS_EXPECTS(first > s_last1 || last < s_first1 || is_no_overlap);
}

Copy link
Member

Choose a reason for hiding this comment

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

this does heavily rely on assumptions about the internal working of view iterators (for example the fact that you can not iterate on a 2D view like with a std::ranges::cartesian_product_view) if this was ever to be extended to support it, the hypothesis here would start failing. I feel that making this check function friend of the iterator class would at least make this dependency explicit.

@@ -215,6 +215,28 @@ KOKKOS_INLINE_FUNCTION void expect_valid_range(IteratorType first,
(void)last;
}

//
// Check if iterators are overlapped
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
// Check if iterators are overlapped
// Check if iterators are overlapping

KOKKOS_EXPECTS(first > s_last1 || last < s_last1);
} else if constexpr (std::is_constructible_v<IteratorType1, IteratorType2>) {
IteratorType2 first2(first), last2(last);
KOKKOS_EXPECTS(first2 > s_last || last2 < s_last);
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
KOKKOS_EXPECTS(first2 > s_last || last2 < s_last);
KOKKOS_EXPECTS(first2 > s_last || last2 < s_first);

Comment on lines 233 to 237
// avoid compiler complaining when KOKKOS_EXPECTS is no-op
(void)first;
(void)last;
(void)s_first;
(void)s_last;
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
// avoid compiler complaining when KOKKOS_EXPECTS is no-op
(void)first;
(void)last;
(void)s_first;
(void)s_last;

Comment on lines 222 to 225
KOKKOS_INLINE_FUNCTION void expect_no_overlap(IteratorType1 first,
IteratorType1 last,
IteratorType2 s_first,
IteratorType2 s_last) {
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
KOKKOS_INLINE_FUNCTION void expect_no_overlap(IteratorType1 first,
IteratorType1 last,
IteratorType2 s_first,
IteratorType2 s_last) {
KOKKOS_INLINE_FUNCTION void expect_no_overlap([[maybe_unused]] IteratorType1 first,
[[maybe_unused]] IteratorType1 last,
[[maybe_unused]] IteratorType2 s_first,
[[maybe_unused]] IteratorType2 s_last) {

@yasahi-hpc
Copy link
Contributor Author

Hi, @masterleinad and @jbigot

Thank you for your comments. I made a fix

@crtrott
Copy link
Member

crtrott commented Apr 8, 2024

Not sure what the cases are, but I guess the thing this would now "wrongly" complain about is two layout stride views with offset 1 between them and stride 2? Do we care?

@crtrott
Copy link
Member

crtrott commented Apr 8, 2024

otherwise looks good.

@yasahi-hpc
Copy link
Contributor Author

That is true. We can create some valid (non-overlapping) cases with layout stride views, which do not satisfy this check. If there is a way to check that in a reasonable cost (say O (1)), it is certainly preferable to add that test. Maybe we can do some integer problem?

@yasahi-hpc
Copy link
Contributor Author

So, it now suppresses "maybe overlapping" cases for safety.

@masterleinad
Copy link
Contributor

That is true. We can create some valid (non-overlapping) cases with layout stride views, which do not satisfy this check. If there is a way to check that in a reasonable cost (say O (1)), it is certainly preferable to add that test. Maybe we can do some integer problem?

See #6922 (comment). I would just test specific types.

@masterleinad
Copy link
Contributor

You'll have to rebase on top of develop.

@yasahi-hpc
Copy link
Contributor Author

@masterleinad Sorry about that. I thought I did that, but something went wrong.

@@ -82,9 +82,17 @@ OutputIteratorType adjacent_difference_exespace_impl(
return first_dest;
}

// run
// ranges shall not overlap
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
// ranges shall not overlap
// check for overlapping iterators

Comment on lines 90 to 93
::Kokkos::parallel_for(
"Check", RangePolicy<ExecutionSpace>(ex, 0, 1), KOKKOS_LAMBDA(int) {
Impl::expect_no_overlap(first_from, last_from, first_dest, last_dest);
});
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
::Kokkos::parallel_for(
"Check", RangePolicy<ExecutionSpace>(ex, 0, 1), KOKKOS_LAMBDA(int) {
Impl::expect_no_overlap(first_from, last_from, first_dest, last_dest);
});
#ifdef KOKKOS_ENABLE_DEBUG
::Kokkos::parallel_for(
"Check", RangePolicy<ExecutionSpace>(ex, 0, 1), KOKKOS_LAMBDA(int) {
Impl::expect_no_overlap(first_from, last_from, first_dest, last_dest);
});
#endif

so we don't have to always pay for the launch overhead for another kernel.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is true. I also add this guard to the team level, since this check performs some computations

if constexpr (std::is_constructible_v<IteratorType2, IteratorType1>) {
IteratorType2 first2(first), last2(last);
IteratorType2 next_first2 = first2;
ptrdiff_t stride = &*(++next_first2) - &*first2;
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead, we could avoid executing this in the correct space if we allow to retrieve the View from the iterator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

adjacent_difference should not allow running with source being the same as destination
4 participants