ryujin 2.1.1 revision b04fb4aba7bd97ddfa5994a6b4b6e8586eb15db2
Loading...
Searching...
No Matches
gpu.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2026 by the ryujin authors
4//
5
6#pragma once
7
8#include <compile_time_options.h>
10#include <simd.h>
11
12#include <deal.II/base/exceptions.h>
13#include <deal.II/base/memory_space.h>
14#include <deal.II/base/vectorization.h>
15
16#include <string>
17#include <type_traits>
18
19namespace ryujin
20{
29 inline constexpr bool have_separate_memory_spaces =
30 !std::is_same_v<dealii::MemorySpace::Host::kokkos_space,
31 dealii::MemorySpace::Default::kokkos_space>;
32
33
46 inline constexpr unsigned int warp_size =
48 : dealii::VectorizedArray<NUMBER>::size();
49
50
63 std::conditional_t<have_separate_memory_spaces,
64 dealii::MemorySpace::Default,
65 dealii::MemorySpace::Host>;
66
67
75 template <typename MemorySpace>
77 std::conditional_t<std::is_same_v<MemorySpace, dealii::MemorySpace::Host>,
78 dealii::MemorySpace::Default,
79 dealii::MemorySpace::Host>;
80
81
130
131
145
146
193 template <typename Derived>
195 {
196 public:
201
209 template <typename MemorySpace>
210 bool is_resident() const;
211
222 template <typename MemorySpace>
223 bool is_pinned() const;
224
233 template <typename MemorySpace>
235
247 template <typename MemorySpace>
249
254
263
264 protected:
266
270
271 MirroredStorage() = default;
272
282 template <typename MemorySpace>
284
296 template <typename MemorySpace>
298
308 void reset_residency(const bool host_resident, const bool default_resident);
309
311
312 private:
317
318 using HostSpace = dealii::MemorySpace::Host;
319 using DefaultSpace = dealii::MemorySpace::Default;
320
321 template <typename MemorySpace>
322 bool &residency_flag() const;
323
324 Derived &derived();
325 const Derived &derived() const;
326
327 mutable bool host_resident_ = false;
328 mutable bool default_resident_ = false;
329
331
333 };
334
335
458 template <typename T>
459 class Mirrored : public MirroredStorage<Mirrored<T>>
460 {
461 public:
467 static constexpr bool is_array = std::is_pointer_v<T>;
468
473 using value_type = std::remove_pointer_t<T>;
474
475 static_assert(std::is_trivially_copyable_v<value_type>,
476 "The stored type has to be trivially copyable so that we "
477 "can move it into device memory");
478
479 static_assert(!std::is_pointer_v<value_type>,
480 "Only a single level of indirection is supported: a "
481 "Mirrored<T *> object maintains a one dimensional array of "
482 "objects of type T");
483
484 static_assert(!std::is_array_v<T>,
485 "An array of objects is spelled Mirrored<T *>, and not "
486 "Mirrored<T[]>");
487
499 template <typename InitializedMemorySpace = dealii::MemorySpace::Host>
500 Mirrored(const std::string &label = "mirrored object",
503 InitializedMemorySpace = {})
504 requires(!is_array);
505
517 template <typename InitializedMemorySpace = dealii::MemorySpace::Host>
518 Mirrored(const std::string &label = "mirrored array",
519 const std::size_t size = 0,
522 InitializedMemorySpace = {})
523 requires(is_array);
524
530 template <typename InitializedMemorySpace = dealii::MemorySpace::Host>
531 void reinit(const std::size_t size,
534 InitializedMemorySpace = {})
535 requires(is_array);
536
540 std::size_t size() const
541 requires(is_array);
542
548 template <typename MemorySpace = dealii::MemorySpace::Host>
550
556 template <typename MemorySpace = dealii::MemorySpace::Host>
557 const value_type *view() const;
558
559 private:
564
565 using HostSpace = dealii::MemorySpace::Host;
566 using DefaultSpace = dealii::MemorySpace::Default;
567
568 template <typename MemorySpace>
569 using KokkosView = Kokkos::View<T, typename MemorySpace::kokkos_space>;
570
574 template <typename MemorySpace>
575 KokkosView<MemorySpace> &storage() const;
576
582 template <typename InitializedMemorySpace>
583 void initialize_storage();
584
585 /*
586 * Storage primitives required by the MirroredStorage base class:
587 */
588
589 template <typename MemorySpace>
590 void allocate_storage() const;
591
592 template <typename To, typename From>
593 void deep_copy_storage() const;
594
595 template <typename MemorySpace>
596 void deallocate_storage();
597
598 std::string label_;
599
600 /* The number of stored objects, only used in the array mode: */
601 std::size_t size_ = 0;
602
603 mutable KokkosView<HostSpace> host_;
604 mutable KokkosView<DefaultSpace> default_;
605
606 friend class MirroredStorage<Mirrored<T>>;
607
609 };
610
611
639 template <int dim, typename Number, typename MemorySpace, typename Object>
641 {
642 private:
643 /* Do not instantiate VectorizedArray for device code */
644 using SimdNumber = std::conditional_t<
645 std::is_same_v<MemorySpace, dealii::MemorySpace::Host>,
646 dealii::VectorizedArray<Number>,
647 Number>;
648
649 /*
650 * FIXME: a little dance around calling the templated view<...>()
651 * operator in the equation classes. We do this to support equations
652 * that have not yet been ported and do not support MemorySpace. For
653 * such equations we fall back to the host view if the host and
654 * default memory spaces are unified.
655 */
656 template <typename T>
657 static auto create_view(const Object &object)
658 {
659 if constexpr (std::is_same_v<MemorySpace, dealii::MemorySpace::Host>)
660 return object.template view<dim, T>();
661 else if constexpr (requires {
662 object.template view<dim, T, MemorySpace>();
663 })
664 return object.template view<dim, T, MemorySpace>();
665 else {
666 // Make the static_assert template dependent so that it gets
667 // evaluated at the right time...
668 static_assert(!have_separate_memory_spaces || sizeof(T) == 0,
669 "The equation does not support MemorySpace");
670 return object.template view<dim, T>();
671 }
672 }
673
674 using ScalarView =
675 decltype(create_view<Number>(std::declval<const Object &>()));
676 using SimdView =
677 decltype(create_view<SimdNumber>(std::declval<const Object &>()));
678
679 public:
680 SelectView(const Object &object)
681 : scalar_view_(create_view<Number>(object))
682 , simd_view_(create_view<SimdNumber>(object))
683 {
684 }
685
690 template <typename T>
691 DEAL_II_HOST_DEVICE_ALWAYS_INLINE const auto &view() const
692 {
693 if constexpr (std::is_same_v<T, typename get_value_type<T>::type>)
694 return scalar_view_;
695 else
696 return simd_view_;
697 }
698
699 private:
700 ScalarView scalar_view_;
701 SimdView simd_view_;
702 };
703
704
710 template <int dim, typename Number, typename MemorySpace, typename Object>
711 auto make_select_view(const Object &object)
712 {
714 }
715
716
717#ifndef DOXYGEN
718 /*
719 * -------------------------------------------------------------------------
720 * Inline function definitions
721 * -------------------------------------------------------------------------
722 */
723
724
725 template <typename Derived>
726 template <typename MemorySpace>
727 inline bool MirroredStorage<Derived>::is_resident() const
728 {
729 static_assert(std::is_same_v<MemorySpace, HostSpace> ||
730 std::is_same_v<MemorySpace, DefaultSpace>,
731 "Unexpected memory space");
732
733 if constexpr (!have_separate_memory_spaces) {
734 Assert(host_resident_ == default_resident_,
735 dealii::ExcMessage(
736 "Internal error: the host and default memory spaces coincide "
737 "but the two residency flags do not. There is only a single "
738 "allocation, so the object has to be resident on both memory "
739 "spaces, or on neither of them."));
740 return host_resident_ || default_resident_;
741 }
742
743 return residency_flag<MemorySpace>();
744 }
745
746
747 template <typename Derived>
748 template <typename MemorySpace>
749 inline bool MirroredStorage<Derived>::is_pinned() const
750 {
751 static_assert(std::is_same_v<MemorySpace, HostSpace> ||
752 std::is_same_v<MemorySpace, DefaultSpace>,
753 "Unexpected memory space");
754
755 if constexpr (!have_separate_memory_spaces) {
756 return false;
757 }
758
759 if constexpr (std::is_same_v<MemorySpace, HostSpace>) {
760 return transfer_policy_ ==
762
763 } else {
764 return transfer_policy_ ==
766 }
767 }
768
769
770 template <typename Derived>
771 template <typename MemorySpace>
773 {
774 static_assert(std::is_same_v<MemorySpace, HostSpace> ||
775 std::is_same_v<MemorySpace, DefaultSpace>,
776 "Unexpected memory space");
777
778 if constexpr (!have_separate_memory_spaces) {
779 /*
780 * If both memory spaces coincide there is only a single allocation
781 * that is resident on both of them: there is nothing to copy.
782 */
783 Assert(host_resident_ && default_resident_,
784 dealii::ExcMessage(
785 "Unable to copy to the chosen memory space: the host and "
786 "default memory spaces coincide but the object is not "
787 "resident on them. The object has not been properly "
788 "initialized."));
789 return;
790 }
791
792 Assert(!is_pinned<MemorySpace>() || is_resident<MemorySpace>(),
793 dealii::ExcMessage(
794 "Unable to copy to the chosen memory space: the selected "
795 "transfer policy pins the chosen memory space but the object "
796 "is not resident on it. The object has not been properly "
797 "initialized."));
798
799 if (is_resident<MemorySpace>())
800 return;
801
802 using OtherSpace = other_space_t<MemorySpace>;
803 Assert(is_resident<OtherSpace>(),
804 dealii::ExcMessage(
805 "Unable to copy to the chosen memory space: the object has "
806 "not been properly initialized."));
807
808 derived().template allocate_storage<MemorySpace>();
809 derived().template deep_copy_storage<MemorySpace, OtherSpace>();
810 residency_flag<MemorySpace>() = true;
811 }
812
813
814 template <typename Derived>
815 template <typename MemorySpace>
817 {
818 static_assert(std::is_same_v<MemorySpace, HostSpace> ||
819 std::is_same_v<MemorySpace, DefaultSpace>,
820 "Unexpected memory space");
821
822 if constexpr (!have_separate_memory_spaces) {
823 /*
824 * If both memory spaces coincide there is only a single allocation
825 * that no memory transfer can ever deallocate: there is nothing to
826 * move.
827 */
828 Assert(host_resident_ && default_resident_,
829 dealii::ExcMessage(
830 "Unable to move to the chosen memory space: the host and "
831 "default memory spaces coincide but the object is not "
832 "resident on them. The object has not been properly "
833 "initialized."));
834 return;
835 }
836
837 Assert(!is_pinned<MemorySpace>() || is_resident<MemorySpace>(),
838 dealii::ExcMessage(
839 "Unable to move to the chosen memory space: the selected "
840 "transfer policy pins the chosen memory space but the object "
841 "is not resident on it. The object has not been properly "
842 "initialized."));
843
844 using OtherSpace = other_space_t<MemorySpace>;
845
846 Assert(!is_pinned<OtherSpace>(),
847 dealii::ExcMessage(
848 "Unable to move to the chosen memory space: the selected "
849 "transfer policy requires the data to remain resident on "
850 "the other memory space."));
851
852 if (!is_resident<MemorySpace>()) {
853 Assert(is_resident<OtherSpace>(),
854 dealii::ExcMessage(
855 "Unable to move to the chosen memory space: the object has "
856 "not been properly initialized."));
857
858 derived().template allocate_storage<MemorySpace>();
859 derived().template deep_copy_storage<MemorySpace, OtherSpace>();
860 residency_flag<MemorySpace>() = true;
861 }
862
863 if (residency_flag<OtherSpace>()) {
864 derived().template deallocate_storage<OtherSpace>();
865 residency_flag<OtherSpace>() = false;
866 }
867 }
868
869
870 template <typename Derived>
872 {
873 return transfer_policy_;
874 }
875
876
877 template <typename Derived>
880 {
881 transfer_policy_ = transfer_policy;
882
883 Assert(!is_pinned<HostSpace>() || is_resident<HostSpace>(),
884 dealii::ExcMessage(
885 "Unable to select the given transfer policy: the policy pins "
886 "the host memory space but the object is not resident on it. "
887 "Transfer the data to the pinned memory space before selecting "
888 "the transfer policy."));
889
890 Assert(!is_pinned<DefaultSpace>() || is_resident<DefaultSpace>(),
891 dealii::ExcMessage(
892 "Unable to select the given transfer policy: the policy pins "
893 "the default memory space but the object is not resident on "
894 "it. Transfer the data to the pinned memory space before "
895 "selecting the transfer policy."));
896 }
897
898
899 template <typename Derived>
900 template <typename MemorySpace>
902 {
903 if (performs_implicit_transfers(transfer_policy_)) {
904 copy_to_memory_space<MemorySpace>();
905
906 } else {
907 Assert(is_resident<MemorySpace>(),
908 dealii::ExcMessage(
909 "The chosen memory space is not resident. Either call "
910 "copy_to_memory_space() / move_to_memory_space() prior to "
911 "requesting a view, or select one of the "
912 "TransferPolicy::implicit_transfers* policies."));
913 }
914 }
915
916
917 template <typename Derived>
918 template <typename MemorySpace>
920 {
921 if (performs_implicit_transfers(transfer_policy_)) {
922 Assert(!is_pinned<other_space_t<MemorySpace>>(),
923 dealii::ExcMessage(
924 "Unable to request a writable view on the chosen memory "
925 "space: the selected transfer policy requires the data to "
926 "remain resident on the other memory space."));
927
928 move_to_memory_space<MemorySpace>();
929
930 } else {
931 Assert(is_resident<MemorySpace>(),
932 dealii::ExcMessage(
933 "The chosen memory space is not resident. Either call "
934 "copy_to_memory_space() / move_to_memory_space() prior to "
935 "requesting a view, or select one of the "
936 "TransferPolicy::implicit_transfers* policies."));
937 }
938 }
939
940
941 template <typename Derived>
942 inline void
943 MirroredStorage<Derived>::reset_residency(const bool host_resident,
944 const bool default_resident)
945 {
946 if constexpr (!have_separate_memory_spaces) {
947 Assert(host_resident == default_resident,
948 dealii::ExcMessage(
949 "Unable to reset the residency flags: the host and default "
950 "memory spaces coincide, so there is only a single "
951 "allocation and both flags have to coincide as well."));
952 }
953
954 host_resident_ = host_resident;
955 default_resident_ = default_resident;
956
957 Assert(!is_pinned<HostSpace>() || is_resident<HostSpace>(),
958 dealii::ExcMessage(
959 "Unable to reset the residency flags: the selected transfer "
960 "policy pins the host memory space and requires the data to "
961 "remain resident on it."));
962
963 Assert(!is_pinned<DefaultSpace>() || is_resident<DefaultSpace>(),
964 dealii::ExcMessage(
965 "Unable to reset the residency flags: the selected transfer "
966 "policy pins the default memory space and requires the data "
967 "to remain resident on it."));
968 }
969
970
971 template <typename Derived>
972 template <typename MemorySpace>
973 inline bool &MirroredStorage<Derived>::residency_flag() const
974 {
975 if constexpr (std::is_same_v<MemorySpace, HostSpace>)
976 return host_resident_;
977 else
978 return default_resident_;
979 }
980
981
982 template <typename Derived>
983 inline Derived &MirroredStorage<Derived>::derived()
984 {
985 return static_cast<Derived &>(*this);
986 }
987
988
989 template <typename Derived>
990 inline const Derived &MirroredStorage<Derived>::derived() const
991 {
992 return static_cast<const Derived &>(*this);
993 }
994
995
996 template <typename T>
997 template <typename InitializedMemorySpace>
998 Mirrored<T>::Mirrored(const std::string &label,
1000 InitializedMemorySpace)
1001 requires(!is_array)
1002 : label_(label)
1003 {
1004 initialize_storage<InitializedMemorySpace>();
1005
1006 /* The transfer policy is selected last, see MirroredStorage: */
1007 this->set_transfer_policy(transfer_policy);
1008 }
1009
1010
1011 template <typename T>
1012 template <typename InitializedMemorySpace>
1013 Mirrored<T>::Mirrored(const std::string &label,
1014 const std::size_t size,
1016 InitializedMemorySpace)
1017 requires(is_array)
1018 : label_(label)
1019 {
1020 reinit(size, transfer_policy, InitializedMemorySpace{});
1021 }
1022
1023
1024 template <typename T>
1025 template <typename InitializedMemorySpace>
1026 void Mirrored<T>::reinit(const std::size_t size,
1028 InitializedMemorySpace)
1029 requires(is_array)
1030 {
1031 /*
1032 * Drop the transfer policy for the duration of the reinit so that a
1033 * pinned memory space of a previous policy does not interfere:
1034 */
1036
1037 size_ = size;
1038
1039 initialize_storage<InitializedMemorySpace>();
1040
1041 /* The transfer policy is selected last, see MirroredStorage: */
1042 this->set_transfer_policy(transfer_policy);
1043 }
1044
1045
1046 template <typename T>
1047 inline std::size_t Mirrored<T>::size() const
1048 requires(is_array)
1049 {
1050 return size_;
1051 }
1052
1053
1054 template <typename T>
1055 template <typename InitializedMemorySpace>
1056 void Mirrored<T>::initialize_storage()
1057 {
1058 static_assert(std::is_same_v<InitializedMemorySpace, HostSpace> ||
1059 std::is_same_v<InitializedMemorySpace, DefaultSpace>,
1060 "Unexpected memory space");
1061
1062 /*
1063 * Note: If the host and default memory spaces coincide then only the
1064 * host storage is ever allocated.
1065 */
1066 if constexpr (have_separate_memory_spaces &&
1067 !std::is_same_v<InitializedMemorySpace, HostSpace>) {
1068 /* Drop possibly stale storage from a previous reinit(): */
1069 deallocate_storage<HostSpace>();
1070 allocate_storage<DefaultSpace>();
1071
1072 this->reset_residency(/*host*/ false, /*default*/ true);
1073
1074 } else {
1075 if constexpr (have_separate_memory_spaces)
1076 deallocate_storage<DefaultSpace>();
1077 allocate_storage<HostSpace>();
1078
1079 /*
1080 * If both memory spaces coincide the single host allocation is
1081 * trivially resident on both of them:
1082 */
1083 this->reset_residency(/*host*/ true,
1084 /*default*/ !have_separate_memory_spaces);
1085 }
1086 }
1087
1088
1089 template <typename T>
1090 template <typename MemorySpace>
1091 inline auto Mirrored<T>::view() -> value_type *
1092 {
1093 this->template prepare_write_access<MemorySpace>();
1094
1095 return storage<MemorySpace>().data();
1096 }
1097
1098
1099 template <typename T>
1100 template <typename MemorySpace>
1101 inline auto Mirrored<T>::view() const -> const value_type *
1102 {
1103 this->template prepare_read_access<MemorySpace>();
1104
1105 return storage<MemorySpace>().data();
1106 }
1107
1108
1109 template <typename T>
1110 template <typename MemorySpace>
1111 inline auto Mirrored<T>::storage() const -> KokkosView<MemorySpace> &
1112 {
1113 static_assert(std::is_same_v<MemorySpace, HostSpace> ||
1114 std::is_same_v<MemorySpace, DefaultSpace>,
1115 "Unexpected memory space");
1116
1117 /*
1118 * Note: If the host and default memory spaces coincide then only the
1119 * host storage is ever allocated.
1120 */
1121 if constexpr (have_separate_memory_spaces &&
1122 !std::is_same_v<MemorySpace, HostSpace>)
1123 return default_;
1124 else
1125 return host_;
1126 }
1127
1128
1129 template <typename T>
1130 template <typename MemorySpace>
1131 inline void Mirrored<T>::allocate_storage() const
1132 {
1133 if constexpr (is_array)
1134 storage<MemorySpace>() = KokkosView<MemorySpace>(label_, size_);
1135 else
1136 storage<MemorySpace>() = KokkosView<MemorySpace>(label_);
1137 }
1138
1139
1140 template <typename T>
1141 template <typename To, typename From>
1142 inline void Mirrored<T>::deep_copy_storage() const
1143 {
1144 Kokkos::deep_copy(/*dst*/ storage<To>(), /*src*/ storage<From>());
1145 }
1146
1147
1148 template <typename T>
1149 template <typename MemorySpace>
1150 inline void Mirrored<T>::deallocate_storage()
1151 {
1152 storage<MemorySpace>() = KokkosView<MemorySpace>();
1153 }
1154
1155
1156#endif
1157} // namespace ryujin
void set_transfer_policy(const TransferPolicy transfer_policy)
void copy_to_memory_space() const
void reset_residency(const bool host_resident, const bool default_resident)
TransferPolicy transfer_policy() const
void prepare_read_access() const
bool is_resident() const
std::size_t size() const
static constexpr bool is_array
Definition gpu.h:467
std::remove_pointer_t< T > value_type
Definition gpu.h:473
value_type * view()
Mirrored(const std::string &label="mirrored array", const std::size_t size=0, const TransferPolicy transfer_policy=TransferPolicy::explicit_transfers, InitializedMemorySpace={})
Mirrored(const std::string &label="mirrored object", const TransferPolicy transfer_policy=TransferPolicy::explicit_transfers, InitializedMemorySpace={})
void reinit(const std::size_t size, const TransferPolicy transfer_policy=TransferPolicy::explicit_transfers, InitializedMemorySpace={})
DEAL_II_HOST_DEVICE_ALWAYS_INLINE const auto & view() const
Definition gpu.h:691
SelectView(const Object &object)
Definition gpu.h:680
auto make_select_view(const Object &object)
Definition gpu.h:711
std::conditional_t< std::is_same_v< MemorySpace, dealii::MemorySpace::Host >, dealii::MemorySpace::Default, dealii::MemorySpace::Host > other_space_t
Definition gpu.h:79
constexpr unsigned int warp_size
Definition gpu.h:46
std::conditional_t< have_separate_memory_spaces, dealii::MemorySpace::Default, dealii::MemorySpace::Host > selected_memory_space_t
Definition gpu.h:65
TransferPolicy
Definition gpu.h:88
constexpr bool performs_implicit_transfers(const TransferPolicy policy)
Definition gpu.h:139
constexpr bool have_separate_memory_spaces
Definition gpu.h:29