Kokkos Core Kernels Package  Version of the Day
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
Kokkos_GraphNode.hpp
1 //@HEADER
2 // ************************************************************************
3 //
4 // Kokkos v. 4.0
5 // Copyright (2022) National Technology & Engineering
6 // Solutions of Sandia, LLC (NTESS).
7 //
8 // Under the terms of Contract DE-NA0003525 with NTESS,
9 // the U.S. Government retains certain rights in this software.
10 //
11 // Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions.
12 // See https://kokkos.org/LICENSE for license information.
13 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
14 //
15 //@HEADER
16 
17 #ifndef KOKKOS_IMPL_PUBLIC_INCLUDE
18 #include <Kokkos_Macros.hpp>
19 static_assert(false,
20  "Including non-public Kokkos header files is not allowed.");
21 #endif
22 #ifndef KOKKOS_KOKKOS_GRAPHNODE_HPP
23 #define KOKKOS_KOKKOS_GRAPHNODE_HPP
24 
25 #include <Kokkos_Macros.hpp>
26 
27 #include <impl/Kokkos_Error.hpp> // contract macros
28 
29 #include <Kokkos_Core_fwd.hpp>
30 #include <Kokkos_Graph_fwd.hpp>
31 #include <impl/Kokkos_GraphImpl_fwd.hpp>
32 #include <Kokkos_Parallel_Reduce.hpp>
33 #include <impl/Kokkos_GraphImpl_Utilities.hpp>
34 #include <impl/Kokkos_GraphImpl.hpp> // GraphAccess
35 
36 #include <memory> // std::shared_ptr
37 
38 namespace Kokkos {
39 namespace Experimental {
40 
41 template <class ExecutionSpace, class Kernel /*= TypeErasedTag*/,
42  class Predecessor /*= TypeErasedTag*/>
43 class GraphNodeRef {
44  //----------------------------------------------------------------------------
45  // <editor-fold desc="template parameter constraints"> {{{2
46 
47  // Note: because of these assertions, instantiating this class template is not
48  // intended to be SFINAE-safe, so do validation before you instantiate.
49 
50  static_assert(
51  std::is_same_v<Predecessor, TypeErasedTag> ||
52  Kokkos::Impl::is_specialization_of<Predecessor, GraphNodeRef>::value,
53  "Invalid predecessor template parameter given to GraphNodeRef");
54 
55  static_assert(
56  Kokkos::is_execution_space<ExecutionSpace>::value,
57  "Invalid execution space template parameter given to GraphNodeRef");
58 
59  static_assert(std::is_same_v<Predecessor, TypeErasedTag> ||
60  Kokkos::Impl::is_graph_kernel<Kernel>::value,
61  "Invalid kernel template parameter given to GraphNodeRef");
62 
63  static_assert(!Kokkos::Impl::is_more_type_erased<Kernel, Predecessor>::value,
64  "The kernel of a graph node can't be more type-erased than the "
65  "predecessor");
66 
67  // </editor-fold> end template parameter constraints }}}2
68  //----------------------------------------------------------------------------
69 
70  public:
71  //----------------------------------------------------------------------------
72  // <editor-fold desc="public member types"> {{{2
73 
74  using execution_space = ExecutionSpace;
75  using graph_kernel = Kernel;
76  using graph_predecessor = Predecessor;
77 
78  // </editor-fold> end public member types }}}2
79  //----------------------------------------------------------------------------
80 
81  private:
82  //----------------------------------------------------------------------------
83  // <editor-fold desc="Friends"> {{{2
84 
85  template <class, class, class>
86  friend class GraphNodeRef;
87  friend struct Kokkos::Impl::GraphAccess;
88 
89  // </editor-fold> end Friends }}}2
90  //----------------------------------------------------------------------------
91 
92  //----------------------------------------------------------------------------
93  // <editor-fold desc="Private Data Members"> {{{2
94 
95  using graph_impl_t = Kokkos::Impl::GraphImpl<ExecutionSpace>;
96  std::weak_ptr<graph_impl_t> m_graph_impl;
97 
98  // TODO @graphs figure out if we can get away with a weak reference here?
99  // GraphNodeRef instances shouldn't be stored by users outside
100  // of the create_graph closure, and so if we restructure things
101  // slightly, we could make it so that the graph owns the
102  // node_impl_t instance and this only holds a std::weak_ptr to
103  // it.
104  using node_impl_t =
105  Kokkos::Impl::GraphNodeImpl<ExecutionSpace, Kernel, Predecessor>;
106  std::shared_ptr<node_impl_t> m_node_impl;
107 
108  // </editor-fold> end Private Data Members }}}2
109  //----------------------------------------------------------------------------
110 
111  //----------------------------------------------------------------------------
112  // <editor-fold desc="Implementation detail accessors"> {{{2
113 
114  // Internally, use shallow constness
115  node_impl_t& get_node_impl() const { return *m_node_impl.get(); }
116  std::shared_ptr<node_impl_t> const& get_node_ptr() const& {
117  return m_node_impl;
118  }
119  std::shared_ptr<node_impl_t> get_node_ptr() && {
120  return std::move(m_node_impl);
121  }
122  std::weak_ptr<graph_impl_t> get_graph_weak_ptr() const {
123  return m_graph_impl;
124  }
125 
126  // </editor-fold> end Implementation detail accessors }}}2
127  //----------------------------------------------------------------------------
128 
129  // TODO kernel name propagation and exposure
130 
131  template <class NextKernelDeduced>
132  auto _then_kernel(NextKernelDeduced&& arg_kernel) const {
133  // readability note:
134  // std::remove_cvref_t<NextKernelDeduced> is a specialization of
135  // Kokkos::Impl::GraphNodeKernelImpl:
136  static_assert(Kokkos::Impl::is_specialization_of<
137  Kokkos::Impl::remove_cvref_t<NextKernelDeduced>,
138  Kokkos::Impl::GraphNodeKernelImpl>::value,
139  "Kokkos internal error");
140 
141  auto graph_ptr = m_graph_impl.lock();
142  KOKKOS_EXPECTS(bool(graph_ptr))
143 
144  using next_kernel_t = Kokkos::Impl::remove_cvref_t<NextKernelDeduced>;
145 
146  using return_t = GraphNodeRef<ExecutionSpace, next_kernel_t, GraphNodeRef>;
147 
148  auto rv = Kokkos::Impl::GraphAccess::make_graph_node_ref(
149  m_graph_impl,
150  Kokkos::Impl::GraphAccess::make_node_shared_ptr<
151  typename return_t::node_impl_t>(
152  m_node_impl->execution_space_instance(),
153  Kokkos::Impl::_graph_node_kernel_ctor_tag{},
154  (NextKernelDeduced&&)arg_kernel,
155  // *this is the predecessor
156  Kokkos::Impl::_graph_node_predecessor_ctor_tag{}, *this));
157 
158  // Add the node itself to the backend's graph data structure, now that
159  // everything is set up.
160  graph_ptr->add_node(rv.m_node_impl);
161  // Add the predecessaor we stored in the constructor above in the backend's
162  // data structure, now that everything is set up.
163  graph_ptr->add_predecessor(rv.m_node_impl, *this);
164  KOKKOS_ENSURES(bool(rv.m_node_impl))
165  return rv;
166  }
167 
168  //----------------------------------------------------------------------------
169  // <editor-fold desc="Private constructors"> {{{2
170 
171  GraphNodeRef(std::weak_ptr<graph_impl_t> arg_graph_impl,
172  std::shared_ptr<node_impl_t> arg_node_impl)
173  : m_graph_impl(std::move(arg_graph_impl)),
174  m_node_impl(std::move(arg_node_impl)) {}
175 
176  // </editor-fold> end Private constructors }}}2
177  //----------------------------------------------------------------------------
178 
179  public:
180  //----------------------------------------------------------------------------
181  // <editor-fold desc="Constructors, destructors, and assignment"> {{{2
182 
183  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
184  // <editor-fold desc="rule of 6 ctors"> {{{3
185 
186  // Copyable and movable (basically just shared_ptr semantics
187  GraphNodeRef() noexcept = default;
188  GraphNodeRef(GraphNodeRef const&) = default;
189  GraphNodeRef(GraphNodeRef&&) noexcept = default;
190  GraphNodeRef& operator=(GraphNodeRef const&) = default;
191  GraphNodeRef& operator=(GraphNodeRef&&) noexcept = default;
192  ~GraphNodeRef() = default;
193 
194  // </editor-fold> end rule of 6 ctors }}}3
195  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
196 
197  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
198  // <editor-fold desc="Type-erasing converting ctor and assignment"> {{{3
199 
200  template <class OtherKernel, class OtherPredecessor,
201  std::enable_if_t<
202  // Not a copy/move constructor
203  !std::is_same_v<GraphNodeRef,
204  GraphNodeRef<execution_space, OtherKernel,
205  OtherPredecessor>> &&
206  // must be an allowed type erasure of the kernel
207  Kokkos::Impl::is_compatible_type_erasure<
208  OtherKernel, graph_kernel>::value &&
209  // must be an allowed type erasure of the predecessor
210  Kokkos::Impl::is_compatible_type_erasure<
211  OtherPredecessor, graph_predecessor>::value,
212  int> = 0>
213  /* implicit */
214  GraphNodeRef(
215  GraphNodeRef<execution_space, OtherKernel, OtherPredecessor> const& other)
216  : m_graph_impl(other.m_graph_impl), m_node_impl(other.m_node_impl) {}
217 
218  // Note: because this is an implicit conversion (as is supposed to be the
219  // case with most type-erasing wrappers like this), we don't also need
220  // a converting assignment operator.
221 
222  // </editor-fold> end Type-erasing converting ctor and assignment }}}3
223  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
224 
225  // </editor-fold> end Constructors, destructors, and assignment }}}2
226  //----------------------------------------------------------------------------
227 
228  //----------------------------------------------------------------------------
229  // <editor-fold desc="then_parallel_for"> {{{2
230 
231  template <
232  class Policy, class Functor,
233  std::enable_if_t<
234  // equivalent to:
235  // requires Kokkos::ExecutionPolicy<remove_cvref_t<Policy>>
236  is_execution_policy<Kokkos::Impl::remove_cvref_t<Policy>>::value,
237  // --------------------
238  int> = 0>
239  auto then_parallel_for(std::string arg_name, Policy&& arg_policy,
240  Functor&& functor) const {
241  //----------------------------------------
242  KOKKOS_EXPECTS(!m_graph_impl.expired())
243  KOKKOS_EXPECTS(bool(m_node_impl))
244  // TODO @graph restore this expectation once we add comparability to space
245  // instances
246  // KOKKOS_EXPECTS(
247  // arg_policy.space() == m_graph_impl->get_execution_space());
248 
249  // needs to static assert constraint: DataParallelFunctor<Functor>
250 
251  using policy_t = Kokkos::Impl::remove_cvref_t<Policy>;
252  // constraint check: same execution space type (or defaulted, maybe?)
253  static_assert(
254  std::is_same<typename policy_t::execution_space,
255  execution_space>::value,
256  // TODO @graph make defaulted execution space work
257  //|| policy_t::execution_space_is_defaulted,
258  "Execution Space mismatch between execution policy and graph");
259 
260  auto policy = Experimental::require((Policy&&)arg_policy,
261  Kokkos::Impl::KernelInGraphProperty{});
262 
263  using next_policy_t = decltype(policy);
264  using next_kernel_t =
265  Kokkos::Impl::GraphNodeKernelImpl<ExecutionSpace, next_policy_t,
266  std::decay_t<Functor>,
267  Kokkos::ParallelForTag>;
268  return this->_then_kernel(next_kernel_t{std::move(arg_name), policy.space(),
269  (Functor&&)functor,
270  (Policy&&)policy});
271  }
272 
273  template <
274  class Policy, class Functor,
275  std::enable_if_t<
276  // equivalent to:
277  // requires Kokkos::ExecutionPolicy<remove_cvref_t<Policy>>
278  is_execution_policy<Kokkos::Impl::remove_cvref_t<Policy>>::value,
279  // --------------------
280  int> = 0>
281  auto then_parallel_for(Policy&& policy, Functor&& functor) const {
282  // needs to static assert constraint: DataParallelFunctor<Functor>
283  return this->then_parallel_for("", (Policy&&)policy, (Functor&&)functor);
284  }
285 
286  template <class Functor>
287  auto then_parallel_for(std::string name, std::size_t n,
288  Functor&& functor) const {
289  // needs to static assert constraint: DataParallelFunctor<Functor>
290  return this->then_parallel_for(std::move(name),
292  (Functor&&)functor);
293  }
294 
295  template <class Functor>
296  auto then_parallel_for(std::size_t n, Functor&& functor) const {
297  // needs to static assert constraint: DataParallelFunctor<Functor>
298  return this->then_parallel_for("", n, (Functor&&)functor);
299  }
300 
301  // </editor-fold> end then_parallel_for }}}2
302  //----------------------------------------------------------------------------
303 
304  //----------------------------------------------------------------------------
305  // <editor-fold desc="then_parallel_reduce"> {{{2
306 
307  template <
308  class Policy, class Functor, class ReturnType,
309  std::enable_if_t<
310  // equivalent to:
311  // requires Kokkos::ExecutionPolicy<remove_cvref_t<Policy>>
312  is_execution_policy<Kokkos::Impl::remove_cvref_t<Policy>>::value,
313  // --------------------
314  int> = 0>
315  auto then_parallel_reduce(std::string arg_name, Policy&& arg_policy,
316  Functor&& functor,
317  ReturnType&& return_value) const {
318  auto graph_impl_ptr = m_graph_impl.lock();
319  KOKKOS_EXPECTS(bool(graph_impl_ptr))
320  KOKKOS_EXPECTS(bool(m_node_impl))
321  // TODO @graph restore this expectation once we add comparability to space
322  // instances
323  // KOKKOS_EXPECTS(
324  // arg_policy.space() == m_graph_impl->get_execution_space());
325 
326  // needs static assertion of constraint:
327  // DataParallelReductionFunctor<Functor, ReturnType>
328 
329  using policy_t = std::remove_cv_t<std::remove_reference_t<Policy>>;
330  static_assert(
331  std::is_same<typename policy_t::execution_space,
332  execution_space>::value,
333  // TODO @graph make defaulted execution space work
334  // || policy_t::execution_space_is_defaulted,
335  "Execution Space mismatch between execution policy and graph");
336 
337  // This is also just an expectation, but it's one that we expect the user
338  // to interact with (even in release mode), so we should throw an exception
339  // with an explanation rather than just doing a contract assertion.
340  // We can't static_assert this because of the way that Reducers store
341  // whether or not they point to a View as a runtime boolean rather than part
342  // of the type.
343  if (Kokkos::Impl::parallel_reduce_needs_fence(
344  graph_impl_ptr->get_execution_space(), return_value)) {
345  Kokkos::Impl::throw_runtime_exception(
346  "Parallel reductions in graphs can't operate on Reducers that "
347  "reference a scalar because they can't complete synchronously. Use a "
348  "Kokkos::View instead and keep in mind the result will only be "
349  "available once the graph is submitted (or in tasks that depend on "
350  "this one).");
351  }
352 
353  //----------------------------------------
354  // This is a disaster, but I guess it's not a my disaster to fix right now
355  using return_type_remove_cvref =
356  std::remove_cv_t<std::remove_reference_t<ReturnType>>;
357  static_assert(Kokkos::is_view<return_type_remove_cvref>::value ||
358  Kokkos::is_reducer<return_type_remove_cvref>::value,
359  "Output argument to parallel reduce in a graph must be a "
360  "View or a Reducer");
361 
362  if constexpr (Kokkos::is_reducer_v<return_type_remove_cvref>) {
363  static_assert(
365  ExecutionSpace, typename return_type_remove_cvref::
366  result_view_type::memory_space>::accessible,
367  "The reduction target must be accessible by the graph execution "
368  "space.");
369  } else {
370  static_assert(
372  ExecutionSpace,
373  typename return_type_remove_cvref::memory_space>::accessible,
374  "The reduction target must be accessible by the graph execution "
375  "space.");
376  }
377 
378  using return_type =
379  // Yes, you do really have to do this...
380  std::conditional_t<Kokkos::is_reducer<return_type_remove_cvref>::value,
381  return_type_remove_cvref,
382  const return_type_remove_cvref>;
383  using functor_type = Kokkos::Impl::remove_cvref_t<Functor>;
384  // see Kokkos_Parallel_Reduce.hpp for how these details are used there;
385  // we're just doing the same thing here
386  using return_value_adapter =
387  Kokkos::Impl::ParallelReduceReturnValue<void, return_type,
388  functor_type>;
389  // End of Kokkos reducer disaster
390  //----------------------------------------
391 
392  auto policy = Experimental::require((Policy&&)arg_policy,
393  Kokkos::Impl::KernelInGraphProperty{});
394 
395  using passed_reducer_type = typename return_value_adapter::reducer_type;
396 
397  using reducer_selector = Kokkos::Impl::if_c<
398  std::is_same<InvalidType, passed_reducer_type>::value, functor_type,
399  passed_reducer_type>;
400  using analysis = Kokkos::Impl::FunctorAnalysis<
401  Kokkos::Impl::FunctorPatternInterface::REDUCE, Policy,
402  typename reducer_selector::type,
403  typename return_value_adapter::value_type>;
404  typename analysis::Reducer final_reducer(
405  reducer_selector::select(functor, return_value));
406  Kokkos::Impl::CombinedFunctorReducer<functor_type,
407  typename analysis::Reducer>
408  functor_reducer(functor, final_reducer);
409 
410  using next_policy_t = decltype(policy);
411  using next_kernel_t =
412  Kokkos::Impl::GraphNodeKernelImpl<ExecutionSpace, next_policy_t,
413  decltype(functor_reducer),
414  Kokkos::ParallelReduceTag>;
415 
416  return this->_then_kernel(next_kernel_t{
417  std::move(arg_name), graph_impl_ptr->get_execution_space(),
418  functor_reducer, (Policy&&)policy,
419  return_value_adapter::return_value(return_value, functor)});
420  }
421 
422  template <
423  class Policy, class Functor, class ReturnType,
424  std::enable_if_t<
425  // equivalent to:
426  // requires Kokkos::ExecutionPolicy<remove_cvref_t<Policy>>
427  is_execution_policy<Kokkos::Impl::remove_cvref_t<Policy>>::value,
428  // --------------------
429  int> = 0>
430  auto then_parallel_reduce(Policy&& arg_policy, Functor&& functor,
431  ReturnType&& return_value) const {
432  return this->then_parallel_reduce("", (Policy&&)arg_policy,
433  (Functor&&)functor,
434  (ReturnType&&)return_value);
435  }
436 
437  template <class Functor, class ReturnType>
438  auto then_parallel_reduce(std::string label,
439  typename execution_space::size_type idx_end,
440  Functor&& functor,
441  ReturnType&& return_value) const {
442  return this->then_parallel_reduce(
443  std::move(label), Kokkos::RangePolicy<execution_space>{0, idx_end},
444  (Functor&&)functor, (ReturnType&&)return_value);
445  }
446 
447  template <class Functor, class ReturnType>
448  auto then_parallel_reduce(typename execution_space::size_type idx_end,
449  Functor&& functor,
450  ReturnType&& return_value) const {
451  return this->then_parallel_reduce("", idx_end, (Functor&&)functor,
452  (ReturnType&&)return_value);
453  }
454 
455  // </editor-fold> end then_parallel_reduce }}}2
456  //----------------------------------------------------------------------------
457 
458  // TODO @graph parallel scan, deep copy, etc.
459 };
460 
461 } // end namespace Experimental
462 } // end namespace Kokkos
463 
464 #endif // KOKKOS_KOKKOS_GRAPHNODE_HPP
Can AccessSpace access MemorySpace ?
ReturnType
Execution policy for work over a range of an integral type.