1 /*
2 //@HEADER
3 // ************************************************************************
4 //
5 // Kokkos v. 3.0
6 // Copyright (2020) National Technology & Engineering
7 // Solutions of Sandia, LLC (NTESS).
8 //
9 // Under the terms of Contract DE-NA0003525 with NTESS,
10 // the U.S. Government retains certain rights in this software.
11 //
12 // Redistribution and use in source and binary forms, with or without
13 // modification, are permitted provided that the following conditions are
14 // met:
15 //
16 // 1. Redistributions of source code must retain the above copyright
17 // notice, this list of conditions and the following disclaimer.
18 //
19 // 2. Redistributions in binary form must reproduce the above copyright
20 // notice, this list of conditions and the following disclaimer in the
21 // documentation and/or other materials provided with the distribution.
22 //
23 // 3. Neither the name of the Corporation nor the names of the
24 // contributors may be used to endorse or promote products derived from
25 // this software without specific prior written permission.
26 //
27 // THIS SOFTWARE IS PROVIDED BY NTESS "AS IS" AND ANY
28 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NTESS OR THE
31 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
33 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
34 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
35 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
36 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
37 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 //
39 // Questions? Contact Christian R. Trott (crtrott@sandia.gov)
40 //
41 // ************************************************************************
42 //@HEADER
43 */
44
45 #ifndef KOKKOS_TEST_DUALVIEW_HPP
46 #define KOKKOS_TEST_DUALVIEW_HPP
47
48 #include <gtest/gtest.h>
49 #include <iostream>
50 #include <cstdlib>
51 #include <cstdio>
52 #include <impl/Kokkos_Timer.hpp>
53 #include <Kokkos_DualView.hpp>
54
55 namespace Test {
56
57 namespace Impl {
58 template <typename Scalar, class Device>
59 struct test_dualview_alloc {
60 using scalar_type = Scalar;
61 using execution_space = Device;
62
63 template <typename ViewType>
run_meTest::Impl::test_dualview_alloc64 bool run_me(unsigned int n, unsigned int m) {
65 if (n < 10) n = 10;
66 if (m < 3) m = 3;
67
68 {
69 ViewType b1;
70 if (b1.is_allocated() == true) return false;
71
72 b1 = ViewType("B1", n, m);
73 ViewType b2(b1);
74 ViewType b3("B3", n, m);
75
76 if (b1.is_allocated() == false) return false;
77 if (b2.is_allocated() == false) return false;
78 if (b3.is_allocated() == false) return false;
79 }
80 return true;
81 }
82
83 bool result = false;
84
test_dualview_allocTest::Impl::test_dualview_alloc85 test_dualview_alloc(unsigned int size) {
86 result = run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(
87 size, 3);
88 }
89 };
90
91 template <typename Scalar, class Device>
92 struct test_dualview_combinations {
93 using self_type = test_dualview_combinations<Scalar, Device>;
94
95 using scalar_type = Scalar;
96 using execution_space = Device;
97
98 Scalar reference;
99 Scalar result;
100
101 template <typename ViewType>
run_meTest::Impl::test_dualview_combinations102 Scalar run_me(unsigned int n, unsigned int m, bool with_init) {
103 if (n < 10) n = 10;
104 if (m < 3) m = 3;
105
106 ViewType a;
107
108 if (with_init) {
109 a = ViewType("A", n, m);
110 } else {
111 a = ViewType(Kokkos::view_alloc(Kokkos::WithoutInitializing, "A"), n, m);
112 }
113 Kokkos::deep_copy(a.d_view, 1);
114
115 a.template modify<typename ViewType::execution_space>();
116 a.template sync<typename ViewType::host_mirror_space>();
117 a.template sync<typename ViewType::host_mirror_space>(
118 Kokkos::DefaultExecutionSpace{});
119
120 a.h_view(5, 1) = 3;
121 a.h_view(6, 1) = 4;
122 a.h_view(7, 2) = 5;
123 a.template modify<typename ViewType::host_mirror_space>();
124 ViewType b = Kokkos::subview(a, std::pair<unsigned int, unsigned int>(6, 9),
125 std::pair<unsigned int, unsigned int>(0, 1));
126 a.template sync<typename ViewType::execution_space>();
127 a.template sync<typename ViewType::execution_space>(
128 Kokkos::DefaultExecutionSpace{});
129 b.template modify<typename ViewType::execution_space>();
130
131 Kokkos::deep_copy(b.d_view, 2);
132
133 a.template sync<typename ViewType::host_mirror_space>();
134 a.template sync<typename ViewType::host_mirror_space>(
135 Kokkos::DefaultExecutionSpace{});
136 Scalar count = 0;
137 for (unsigned int i = 0; i < a.d_view.extent(0); i++)
138 for (unsigned int j = 0; j < a.d_view.extent(1); j++)
139 count += a.h_view(i, j);
140 return count - a.d_view.extent(0) * a.d_view.extent(1) - 2 - 4 - 3 * 2;
141 }
142
test_dualview_combinationsTest::Impl::test_dualview_combinations143 test_dualview_combinations(unsigned int size, bool with_init) {
144 result = run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(
145 size, 3, with_init);
146 }
147 };
148
149 template <typename Scalar, class ViewType>
150 struct SumViewEntriesFunctor {
151 using value_type = Scalar;
152
153 ViewType fv;
154
SumViewEntriesFunctorTest::Impl::SumViewEntriesFunctor155 SumViewEntriesFunctor(const ViewType& fv_) : fv(fv_) {}
156
157 KOKKOS_INLINE_FUNCTION
operator ()Test::Impl::SumViewEntriesFunctor158 void operator()(const int i, value_type& total) const {
159 for (size_t j = 0; j < fv.extent(1); ++j) {
160 total += fv(i, j);
161 }
162 }
163 };
164
165 template <typename Scalar, class Device>
166 struct test_dual_view_deep_copy {
167 using scalar_type = Scalar;
168 using execution_space = Device;
169
170 template <typename ViewType>
run_meTest::Impl::test_dual_view_deep_copy171 void run_me(int n, const int m, const bool use_templ_sync) {
172 ViewType a, b;
173 if (n >= 0) {
174 a = ViewType("A", n, m);
175 b = ViewType("B", n, m);
176 } else {
177 n = 0;
178 }
179 const scalar_type sum_total = scalar_type(n * m);
180
181 Kokkos::deep_copy(a.d_view, 1);
182
183 if (use_templ_sync) {
184 a.template modify<typename ViewType::execution_space>();
185 a.template sync<typename ViewType::host_mirror_space>();
186 } else {
187 a.modify_device();
188 a.sync_host();
189 a.sync_host(Kokkos::DefaultExecutionSpace{});
190 }
191
192 // Check device view is initialized as expected
193 scalar_type a_d_sum = 0;
194 // Execute on the execution_space associated with t_dev's memory space
195 using t_dev_exec_space =
196 typename ViewType::t_dev::memory_space::execution_space;
197 Kokkos::parallel_reduce(
198 Kokkos::RangePolicy<t_dev_exec_space>(0, n),
199 SumViewEntriesFunctor<scalar_type, typename ViewType::t_dev>(a.d_view),
200 a_d_sum);
201 ASSERT_EQ(a_d_sum, sum_total);
202
203 // Check host view is synced as expected
204 scalar_type a_h_sum = 0;
205 for (size_t i = 0; i < a.h_view.extent(0); ++i)
206 for (size_t j = 0; j < a.h_view.extent(1); ++j) {
207 a_h_sum += a.h_view(i, j);
208 }
209
210 ASSERT_EQ(a_h_sum, sum_total);
211
212 // Test deep_copy
213 Kokkos::deep_copy(b, a);
214 if (use_templ_sync) {
215 b.template sync<typename ViewType::host_mirror_space>();
216 } else {
217 b.sync_host();
218 b.sync_host(Kokkos::DefaultExecutionSpace{});
219 }
220
221 // Perform same checks on b as done on a
222 // Check device view is initialized as expected
223 scalar_type b_d_sum = 0;
224 // Execute on the execution_space associated with t_dev's memory space
225 Kokkos::parallel_reduce(
226 Kokkos::RangePolicy<t_dev_exec_space>(0, n),
227 SumViewEntriesFunctor<scalar_type, typename ViewType::t_dev>(b.d_view),
228 b_d_sum);
229 ASSERT_EQ(b_d_sum, sum_total);
230
231 // Check host view is synced as expected
232 scalar_type b_h_sum = 0;
233 for (size_t i = 0; i < b.h_view.extent(0); ++i)
234 for (size_t j = 0; j < b.h_view.extent(1); ++j) {
235 b_h_sum += b.h_view(i, j);
236 }
237
238 ASSERT_EQ(b_h_sum, sum_total);
239
240 } // end run_me
241
test_dual_view_deep_copyTest::Impl::test_dual_view_deep_copy242 test_dual_view_deep_copy() {
243 run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(10, 5,
244 true);
245 run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(10, 5,
246 false);
247 // Test zero length but allocated (a.d_view.data!=nullptr but
248 // a.d_view.span()==0)
249 run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(0, 5, true);
250 run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(0, 5,
251 false);
252
253 // Test default constructed view
254 run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(-1, 5,
255 true);
256 run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >(-1, 5,
257 false);
258 }
259 };
260
261 template <typename Scalar, class Device>
262 struct test_dualview_resize {
263 using scalar_type = Scalar;
264 using execution_space = Device;
265
266 template <typename ViewType>
run_meTest::Impl::test_dualview_resize267 void run_me() {
268 const unsigned int n = 10;
269 const unsigned int m = 5;
270 const unsigned int factor = 2;
271
272 ViewType a("A", n, m);
273 Kokkos::deep_copy(a.d_view, 1);
274
275 /* Covers case "Resize on Device" */
276 a.modify_device();
277 Kokkos::resize(a, factor * n, factor * m);
278 ASSERT_EQ(a.extent(0), n * factor);
279 ASSERT_EQ(a.extent(1), m * factor);
280
281 Kokkos::deep_copy(a.d_view, 1);
282 a.sync_host();
283
284 // Check device view is initialized as expected
285 scalar_type a_d_sum = 0;
286 // Execute on the execution_space associated with t_dev's memory space
287 using t_dev_exec_space =
288 typename ViewType::t_dev::memory_space::execution_space;
289 Kokkos::parallel_reduce(
290 Kokkos::RangePolicy<t_dev_exec_space>(0, a.d_view.extent(0)),
291 SumViewEntriesFunctor<scalar_type, typename ViewType::t_dev>(a.d_view),
292 a_d_sum);
293
294 // Check host view is synced as expected
295 scalar_type a_h_sum = 0;
296 for (size_t i = 0; i < a.h_view.extent(0); ++i)
297 for (size_t j = 0; j < a.h_view.extent(1); ++j) {
298 a_h_sum += a.h_view(i, j);
299 }
300
301 // Check
302 ASSERT_EQ(a_h_sum, a_d_sum);
303 ASSERT_EQ(a_h_sum, a.extent(0) * a.extent(1));
304
305 /* Covers case "Resize on Host" */
306 a.modify_host();
307
308 Kokkos::resize(a, n / factor, m / factor);
309 ASSERT_EQ(a.extent(0), n / factor);
310 ASSERT_EQ(a.extent(1), m / factor);
311
312 a.sync_device();
313 a.sync_device(Kokkos::DefaultExecutionSpace{});
314
315 // Check device view is initialized as expected
316 a_d_sum = 0;
317 // Execute on the execution_space associated with t_dev's memory space
318 using t_dev_exec_space =
319 typename ViewType::t_dev::memory_space::execution_space;
320 Kokkos::parallel_reduce(
321 Kokkos::RangePolicy<t_dev_exec_space>(0, a.d_view.extent(0)),
322 SumViewEntriesFunctor<scalar_type, typename ViewType::t_dev>(a.d_view),
323 a_d_sum);
324
325 // Check host view is synced as expected
326 a_h_sum = 0;
327 for (size_t i = 0; i < a.h_view.extent(0); ++i)
328 for (size_t j = 0; j < a.h_view.extent(1); ++j) {
329 a_h_sum += a.h_view(i, j);
330 }
331
332 // Check
333 ASSERT_EQ(a_h_sum, a.extent(0) * a.extent(1));
334 ASSERT_EQ(a_h_sum, a_d_sum);
335
336 } // end run_me
337
test_dualview_resizeTest::Impl::test_dualview_resize338 test_dualview_resize() {
339 run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >();
340 }
341 };
342
343 template <typename Scalar, class Device>
344 struct test_dualview_realloc {
345 using scalar_type = Scalar;
346 using execution_space = Device;
347
348 template <typename ViewType>
run_meTest::Impl::test_dualview_realloc349 void run_me() {
350 const unsigned int n = 10;
351 const unsigned int m = 5;
352
353 ViewType a("A", n, m);
354 Kokkos::realloc(a, n, m);
355
356 Kokkos::deep_copy(a.d_view, 1);
357 a.modify_device();
358 a.sync_host();
359
360 // Check device view is initialized as expected
361 scalar_type a_d_sum = 0;
362 // Execute on the execution_space associated with t_dev's memory space
363 using t_dev_exec_space =
364 typename ViewType::t_dev::memory_space::execution_space;
365 Kokkos::parallel_reduce(
366 Kokkos::RangePolicy<t_dev_exec_space>(0, a.d_view.extent(0)),
367 SumViewEntriesFunctor<scalar_type, typename ViewType::t_dev>(a.d_view),
368 a_d_sum);
369
370 // Check host view is synced as expected
371 scalar_type a_h_sum = 0;
372 for (size_t i = 0; i < a.h_view.extent(0); ++i)
373 for (size_t j = 0; j < a.h_view.extent(1); ++j) {
374 a_h_sum += a.h_view(i, j);
375 }
376
377 // Check
378 ASSERT_EQ(a_h_sum, a.extent(0) * a.extent(1));
379 ASSERT_EQ(a_h_sum, a_d_sum);
380 } // end run_me
381
test_dualview_reallocTest::Impl::test_dualview_realloc382 test_dualview_realloc() {
383 run_me<Kokkos::DualView<Scalar**, Kokkos::LayoutLeft, Device> >();
384 }
385 };
386
387 } // namespace Impl
388
389 template <typename Scalar, typename Device>
test_dualview_combinations(unsigned int size,bool with_init)390 void test_dualview_combinations(unsigned int size, bool with_init) {
391 Impl::test_dualview_combinations<Scalar, Device> test(size, with_init);
392 ASSERT_EQ(test.result, 0);
393 }
394
395 template <typename Scalar, typename Device>
test_dualview_alloc(unsigned int size)396 void test_dualview_alloc(unsigned int size) {
397 Impl::test_dualview_alloc<Scalar, Device> test(size);
398 ASSERT_TRUE(test.result);
399 }
400
401 template <typename Scalar, typename Device>
test_dualview_deep_copy()402 void test_dualview_deep_copy() {
403 Impl::test_dual_view_deep_copy<Scalar, Device>();
404 }
405
406 template <typename Scalar, typename Device>
test_dualview_realloc()407 void test_dualview_realloc() {
408 Impl::test_dualview_realloc<Scalar, Device>();
409 }
410
411 template <typename Scalar, typename Device>
test_dualview_resize()412 void test_dualview_resize() {
413 Impl::test_dualview_resize<Scalar, Device>();
414 }
415
TEST(TEST_CATEGORY,dualview_combination)416 TEST(TEST_CATEGORY, dualview_combination) {
417 test_dualview_combinations<int, TEST_EXECSPACE>(10, true);
418 }
419
TEST(TEST_CATEGORY,dualview_alloc)420 TEST(TEST_CATEGORY, dualview_alloc) {
421 test_dualview_alloc<int, TEST_EXECSPACE>(10);
422 }
423
TEST(TEST_CATEGORY,dualview_combinations_without_init)424 TEST(TEST_CATEGORY, dualview_combinations_without_init) {
425 test_dualview_combinations<int, TEST_EXECSPACE>(10, false);
426 }
427
TEST(TEST_CATEGORY,dualview_deep_copy)428 TEST(TEST_CATEGORY, dualview_deep_copy) {
429 test_dualview_deep_copy<int, TEST_EXECSPACE>();
430 test_dualview_deep_copy<double, TEST_EXECSPACE>();
431 }
432
TEST(TEST_CATEGORY,dualview_realloc)433 TEST(TEST_CATEGORY, dualview_realloc) {
434 test_dualview_realloc<int, TEST_EXECSPACE>();
435 }
436
TEST(TEST_CATEGORY,dualview_resize)437 TEST(TEST_CATEGORY, dualview_resize) {
438 test_dualview_resize<int, TEST_EXECSPACE>();
439 }
440
441 namespace {
442 /**
443 *
444 * The following tests are a response to
445 * https://github.com/kokkos/kokkos/issues/3850
446 * and
447 * https://github.com/kokkos/kokkos/pull/3857
448 *
449 * DualViews were returning incorrect view types and taking
450 * inappropriate actions based on the templated view methods.
451 *
452 * Specifically, template view methods were always returning
453 * a device view if the memory space was UVM and a Kokkos::Device was passed.
454 * Sync/modify methods completely broke down So these tests exist to make sure
455 * that we keep the semantics of UVM DualViews intact.
456 */
457 // modify if we have other UVM enabled backends
458 #ifdef KOKKOS_ENABLE_CUDA // OR other UVM builds
459 #define UVM_ENABLED_BUILD
460 #endif
461
462 #ifdef UVM_ENABLED_BUILD
463 template <typename ExecSpace>
464 struct UVMSpaceFor;
465 #endif
466
467 #ifdef KOKKOS_ENABLE_CUDA // specific to CUDA
468 template <>
469 struct UVMSpaceFor<Kokkos::Cuda> {
470 using type = Kokkos::CudaUVMSpace;
471 };
472 #endif
473
474 #ifdef UVM_ENABLED_BUILD
475 template <>
476 struct UVMSpaceFor<Kokkos::DefaultHostExecutionSpace> {
477 using type = typename UVMSpaceFor<Kokkos::DefaultExecutionSpace>::type;
478 };
479 #else
480 template <typename ExecSpace>
481 struct UVMSpaceFor {
482 using type = typename ExecSpace::memory_space;
483 };
484 #endif
485
486 using ExecSpace = Kokkos::DefaultExecutionSpace;
487 using MemSpace = typename UVMSpaceFor<Kokkos::DefaultExecutionSpace>::type;
488 using DeviceType = Kokkos::Device<ExecSpace, MemSpace>;
489
490 using DualViewType = Kokkos::DualView<double*, Kokkos::LayoutLeft, DeviceType>;
491 using d_device = DeviceType;
492 using h_device = Kokkos::Device<
493 Kokkos::DefaultHostExecutionSpace,
494 typename UVMSpaceFor<Kokkos::DefaultHostExecutionSpace>::type>;
495
TEST(TEST_CATEGORY,dualview_device_correct_kokkos_device)496 TEST(TEST_CATEGORY, dualview_device_correct_kokkos_device) {
497 DualViewType dv("myView", 100);
498 dv.clear_sync_state();
499 auto v_d = dv.template view<d_device>();
500 using vdt = decltype(v_d);
501 using vdt_d = vdt::device_type;
502 using vdt_d_e = vdt_d::execution_space;
503 ASSERT_STREQ(vdt_d_e::name(), Kokkos::DefaultExecutionSpace::name());
504 }
TEST(TEST_CATEGORY,dualview_host_correct_kokkos_device)505 TEST(TEST_CATEGORY, dualview_host_correct_kokkos_device) {
506 DualViewType dv("myView", 100);
507 dv.clear_sync_state();
508 auto v_h = dv.template view<h_device>();
509 using vht = decltype(v_h);
510 using vht_d = vht::device_type;
511 using vht_d_e = vht_d::execution_space;
512 ASSERT_STREQ(vht_d_e::name(), Kokkos::DefaultHostExecutionSpace::name());
513 }
514
TEST(TEST_CATEGORY,dualview_host_modify_template_device_sync)515 TEST(TEST_CATEGORY, dualview_host_modify_template_device_sync) {
516 DualViewType dv("myView", 100);
517 dv.clear_sync_state();
518 dv.modify_host();
519 dv.template sync<d_device>();
520 EXPECT_TRUE(!dv.need_sync_device());
521 EXPECT_TRUE(!dv.need_sync_host());
522 dv.clear_sync_state();
523 }
524
TEST(TEST_CATEGORY,dualview_host_modify_template_device_execspace_sync)525 TEST(TEST_CATEGORY, dualview_host_modify_template_device_execspace_sync) {
526 DualViewType dv("myView", 100);
527 dv.clear_sync_state();
528 dv.modify_host();
529 dv.template sync<d_device::execution_space>();
530 EXPECT_TRUE(!dv.need_sync_device());
531 EXPECT_TRUE(!dv.need_sync_host());
532 dv.clear_sync_state();
533 }
534
TEST(TEST_CATEGORY,dualview_device_modify_template_host_sync)535 TEST(TEST_CATEGORY, dualview_device_modify_template_host_sync) {
536 DualViewType dv("myView", 100);
537 dv.clear_sync_state();
538 dv.modify_device();
539 dv.template sync<h_device>();
540 EXPECT_TRUE(!dv.need_sync_device());
541 EXPECT_TRUE(!dv.need_sync_host());
542 dv.clear_sync_state();
543 }
TEST(TEST_CATEGORY,dualview_device_modify_template_host_execspace_sync)544 TEST(TEST_CATEGORY, dualview_device_modify_template_host_execspace_sync) {
545 DualViewType dv("myView", 100);
546 dv.clear_sync_state();
547 dv.modify_device();
548 dv.template sync<h_device::execution_space>();
549 EXPECT_TRUE(!dv.need_sync_device());
550 EXPECT_TRUE(!dv.need_sync_host());
551 dv.clear_sync_state();
552 }
553
TEST(TEST_CATEGORY,dualview_template_views_return_correct_executionspace_views)554 TEST(TEST_CATEGORY,
555 dualview_template_views_return_correct_executionspace_views) {
556 DualViewType dv("myView", 100);
557 dv.clear_sync_state();
558 using hvt = decltype(dv.view<typename Kokkos::DefaultHostExecutionSpace>());
559 using dvt = decltype(dv.view<typename Kokkos::DefaultExecutionSpace>());
560 ASSERT_STREQ(Kokkos::DefaultExecutionSpace::name(),
561 dvt::device_type::execution_space::name());
562 ASSERT_STREQ(Kokkos::DefaultHostExecutionSpace::name(),
563 hvt::device_type::execution_space::name());
564 }
565
566 } // anonymous namespace
567 } // namespace Test
568
569 #endif // KOKKOS_TEST_DUALVIEW_HPP
570