DDC 0.15.1
Loading...
Searching...
No Matches
splines_linear_problem_sparse.cpp
1// Copyright (C) The DDC development team, see COPYRIGHT.md file
2//
3// SPDX-License-Identifier: MIT
4
5#include <algorithm>
6#include <cassert>
7#include <cstddef>
8#include <memory>
9#include <optional>
10#include <stdexcept>
11#include <type_traits>
12
13#include <ginkgo/extensions/kokkos.hpp>
14#include <ginkgo/ginkgo.hpp>
15
16#include <Kokkos_Core.hpp>
17
20
21namespace ddc::detail {
22
23namespace {
24
25/**
26 * @brief Convert KokkosView to Ginkgo Dense matrix.
27 *
28 * @param[in] gko_exec A Ginkgo executor that has access to the Kokkos::View memory space
29 * @param[in] view A 2-D Kokkos::View with unit stride in the second dimension
30 * @return A Ginkgo Dense matrix view over the Kokkos::View data
31 */
32template <class KokkosViewType>
33auto to_gko_dense(std::shared_ptr<gko::Executor const> const& gko_exec, KokkosViewType const& view)
34{
35 static_assert(Kokkos::is_view_v<KokkosViewType> && KokkosViewType::rank == 2);
36 using value_type = KokkosViewType::traits::value_type;
37
38 if (view.stride(1) != 1) {
39 throw std::runtime_error("The view needs to be contiguous in the second dimension");
40 }
41
42 return gko::matrix::Dense<value_type>::
43 create(gko_exec,
44 gko::dim<2>(view.extent(0), view.extent(1)),
45 gko::array<value_type>::view(gko_exec, view.span(), view.data()),
46 view.stride(0));
47}
48
49/**
50 * @brief Return the default value of the parameter cols_per_chunk for a given Kokkos::ExecutionSpace.
51 *
52 * The values are hardware-specific (but they can be overridden in the constructor of SplinesLinearProblemSparse).
53 * They have been tuned on the basis of ddc/benchmarks/splines.cpp results on 4xIntel 6230 + Nvidia V100.
54 *
55 * @tparam ExecSpace The Kokkos::ExecutionSpace type.
56 * @return The default value for the parameter cols_per_chunk.
57 */
58template <class ExecSpace>
59std::size_t default_cols_per_chunk() noexcept
60{
61#if defined(KOKKOS_ENABLE_SERIAL)
62 if (std::is_same_v<ExecSpace, Kokkos::Serial>) {
63 return 8192;
64 }
65#endif
66#if defined(KOKKOS_ENABLE_OPENMP)
67 if (std::is_same_v<ExecSpace, Kokkos::OpenMP>) {
68 return 8192;
69 }
70#endif
71#if defined(KOKKOS_ENABLE_CUDA)
72 if (std::is_same_v<ExecSpace, Kokkos::Cuda>) {
73 return 65535;
74 }
75#endif
76#if defined(KOKKOS_ENABLE_HIP)
77 if (std::is_same_v<ExecSpace, Kokkos::HIP>) {
78 return 65535;
79 }
80#endif
81#if defined(KOKKOS_ENABLE_SYCL)
82 if (std::is_same_v<ExecSpace, Kokkos::SYCL>) {
83 return 65535;
84 }
85#endif
86 return 1;
87}
88
89/**
90 * @brief Return the default value of the parameter preconditioner_max_block_size for a given Kokkos::ExecutionSpace.
91 *
92 * The values are hardware-specific (but they can be overridden in the constructor of SplinesLinearProblemSparse).
93 * They have been tuned on the basis of ddc/benchmarks/splines.cpp results on 4xIntel 6230 + Nvidia V100.
94 *
95 * @tparam ExecSpace The Kokkos::ExecutionSpace type.
96 * @return The default value for the parameter preconditioner_max_block_size.
97 */
98template <class ExecSpace>
99unsigned int default_preconditioner_max_block_size() noexcept
100{
101#if defined(KOKKOS_ENABLE_SERIAL)
102 if (std::is_same_v<ExecSpace, Kokkos::Serial>) {
103 return 32U;
104 }
105#endif
106#if defined(KOKKOS_ENABLE_OPENMP)
107 if (std::is_same_v<ExecSpace, Kokkos::OpenMP>) {
108 return 1U;
109 }
110#endif
111#if defined(KOKKOS_ENABLE_CUDA)
112 if (std::is_same_v<ExecSpace, Kokkos::Cuda>) {
113 return 1U;
114 }
115#endif
116#if defined(KOKKOS_ENABLE_HIP)
117 if (std::is_same_v<ExecSpace, Kokkos::HIP>) {
118 return 1U;
119 }
120#endif
121#if defined(KOKKOS_ENABLE_SYCL)
122 if (std::is_same_v<ExecSpace, Kokkos::SYCL>) {
123 return 1U;
124 }
125#endif
126 return 1U;
127}
128
129} // namespace
130
131template <class ExecSpace>
132class SplinesLinearProblemSparse<ExecSpace>::Impl
133{
134public:
135 using MultiRHS = SplinesLinearProblem<ExecSpace>::MultiRHS;
136
137private:
138 using matrix_sparse_type = gko::matrix::Csr<double, gko::int32>;
139 using solver_type = gko::solver::Bicgstab<double>;
140
141private:
142 std::size_t m_mat_size;
143
144 std::unique_ptr<gko::matrix::Dense<double>> m_matrix_dense;
145
146 std::shared_ptr<matrix_sparse_type> m_matrix_sparse;
147
148 std::shared_ptr<solver_type> m_solver;
149 std::shared_ptr<gko::LinOp> m_solver_tr;
150
151 std::size_t m_cols_per_chunk; // Maximum number of columns of B to be passed to a Ginkgo solver
152
153 unsigned int m_preconditioner_max_block_size; // Maximum size of Jacobi-block preconditioner
154
155public:
156 explicit Impl(
157 std::size_t const mat_size,
158 std::optional<std::size_t> const cols_per_chunk = std::nullopt,
159 std::optional<unsigned int> const preconditioner_max_block_size = std::nullopt)
160 : m_mat_size(mat_size)
161 , m_cols_per_chunk(cols_per_chunk.value_or(default_cols_per_chunk<ExecSpace>()))
162 , m_preconditioner_max_block_size(preconditioner_max_block_size.value_or(
163 default_preconditioner_max_block_size<ExecSpace>()))
164 {
165 std::shared_ptr const gko_exec = gko::ext::kokkos::create_executor(ExecSpace());
166 m_matrix_dense = gko::matrix::Dense<
167 double>::create(gko_exec->get_master(), gko::dim<2>(mat_size, mat_size));
168 m_matrix_dense->fill(0);
169 m_matrix_sparse = matrix_sparse_type::create(gko_exec, gko::dim<2>(mat_size, mat_size));
170 }
171
172 double get_element(std::size_t const i, std::size_t const j) const
173 {
174 return m_matrix_dense->at(i, j);
175 }
176
177 void set_element(std::size_t const i, std::size_t const j, double const aij)
178 {
179 m_matrix_dense->at(i, j) = aij;
180 }
181
182 void setup_solver()
183 {
184 // Remove zeros
185 gko::matrix_data<double> matrix_data(gko::dim<2>(m_mat_size, m_mat_size));
186 m_matrix_dense->write(matrix_data);
187 m_matrix_dense.reset();
188 matrix_data.remove_zeros();
189 m_matrix_sparse->read(matrix_data);
190 std::shared_ptr const gko_exec = m_matrix_sparse->get_executor();
191
192 // Create the solver factory
193 std::shared_ptr const residual_criterion
194 = gko::stop::ResidualNorm<double>::build().with_reduction_factor(1e-15).on(
195 gko_exec);
196
197 std::shared_ptr const iterations_criterion
198 = gko::stop::Iteration::build().with_max_iters(1000U).on(gko_exec);
199
200 std::shared_ptr const preconditioner
201 = gko::preconditioner::Jacobi<double>::build()
202 .with_max_block_size(m_preconditioner_max_block_size)
203 .on(gko_exec);
204
205 std::unique_ptr const solver_factory
206 = solver_type::build()
207 .with_preconditioner(preconditioner)
208 .with_criteria(residual_criterion, iterations_criterion)
209 .on(gko_exec);
210
211 m_solver = solver_factory->generate(m_matrix_sparse);
212 m_solver_tr = m_solver->transpose();
213 gko_exec->synchronize();
214 }
215
216 /**
217 * @brief Solve the multiple right-hand sides linear problem Ax=b or its transposed version A^tx=b inplace.
218 *
219 * The solver method is currently BiCGSTAB.
220 *
221 * Multiple right-hand sides are sliced in chunks of size cols_per_chunk which are passed one-after-the-other to Ginkgo.
222 *
223 * @param[in, out] b A 2D Kokkos::View storing the multiple right-hand sides of the problem and receiving the corresponding solution.
224 * @param transpose Choose between the direct or transposed version of the linear problem.
225 */
226 void solve(MultiRHS const b, bool const transpose) const
227 {
228 assert(b.extent(0) == m_mat_size);
229
230 std::shared_ptr const gko_exec = m_solver->get_executor();
231 std::shared_ptr const convergence_logger = gko::log::Convergence<double>::create();
232
233 std::size_t const main_chunk_size = std::min(m_cols_per_chunk, b.extent(1));
234
235 MultiRHS const b_buffer("ddc_sparse_b_buffer", m_mat_size, main_chunk_size);
236 MultiRHS const x("ddc_sparse_x", m_mat_size, main_chunk_size);
237
238 std::size_t const iend = (b.extent(1) + main_chunk_size - 1) / main_chunk_size;
239 for (std::size_t i = 0; i < iend; ++i) {
240 std::size_t const subview_begin = i * main_chunk_size;
241 std::size_t const subview_end
242 = (i + 1 == iend) ? b.extent(1) : (subview_begin + main_chunk_size);
243
244 auto const b_chunk
245 = Kokkos::subview(b, Kokkos::ALL, Kokkos::pair(subview_begin, subview_end));
246 auto const b_buffer_chunk = Kokkos::
247 subview(b_buffer,
248 Kokkos::ALL,
249 Kokkos::pair(static_cast<std::size_t>(0), subview_end - subview_begin));
250 auto const x_chunk = Kokkos::
251 subview(x,
252 Kokkos::ALL,
253 Kokkos::pair(static_cast<std::size_t>(0), subview_end - subview_begin));
254
255 Kokkos::deep_copy(b_buffer_chunk, b_chunk);
256 Kokkos::deep_copy(x_chunk, b_chunk);
257
258 if (!transpose) {
259 m_solver->add_logger(convergence_logger);
260 m_solver
261 ->apply(to_gko_dense(gko_exec, b_buffer_chunk),
262 to_gko_dense(gko_exec, x_chunk));
263 m_solver->remove_logger(convergence_logger);
264 } else {
265 m_solver_tr->add_logger(convergence_logger);
266 m_solver_tr
267 ->apply(to_gko_dense(gko_exec, b_buffer_chunk),
268 to_gko_dense(gko_exec, x_chunk));
269 m_solver_tr->remove_logger(convergence_logger);
270 }
271
272 if (!convergence_logger->has_converged()) {
273 throw std::runtime_error(
274 "Ginkgo did not converged in ddc::detail::SplinesLinearProblemSparse");
275 }
276
277 Kokkos::deep_copy(b_chunk, x_chunk);
278 }
279 }
280};
281
282template <class ExecSpace>
283SplinesLinearProblemSparse<ExecSpace>::SplinesLinearProblemSparse(
284 std::size_t const mat_size,
285 std::optional<std::size_t> cols_per_chunk,
286 std::optional<unsigned int> preconditioner_max_block_size)
287 : SplinesLinearProblem<ExecSpace>(mat_size)
288 , m_impl(std::make_unique<Impl>(mat_size, cols_per_chunk, preconditioner_max_block_size))
289{
290}
291
292template <class ExecSpace>
293SplinesLinearProblemSparse<ExecSpace>::~SplinesLinearProblemSparse() = default;
294
295template <class ExecSpace>
296double SplinesLinearProblemSparse<ExecSpace>::get_element(std::size_t i, std::size_t j) const
297{
298 return m_impl->get_element(i, j);
299}
300
301template <class ExecSpace>
302void SplinesLinearProblemSparse<ExecSpace>::set_element(std::size_t i, std::size_t j, double aij)
303{
304 m_impl->set_element(i, j, aij);
305}
306
307template <class ExecSpace>
308void SplinesLinearProblemSparse<ExecSpace>::setup_solver()
309{
310 m_impl->setup_solver();
311}
312
313template <class ExecSpace>
314void SplinesLinearProblemSparse<ExecSpace>::solve(MultiRHS const b, bool const transpose) const
315{
316 m_impl->solve(b, transpose);
317}
318
319#if defined(KOKKOS_ENABLE_SERIAL)
320template class SplinesLinearProblemSparse<Kokkos::Serial>;
321#endif
322#if defined(KOKKOS_ENABLE_OPENMP)
323template class SplinesLinearProblemSparse<Kokkos::OpenMP>;
324#endif
325#if defined(KOKKOS_ENABLE_CUDA)
326template class SplinesLinearProblemSparse<Kokkos::Cuda>;
327#endif
328#if defined(KOKKOS_ENABLE_HIP)
329template class SplinesLinearProblemSparse<Kokkos::HIP>;
330#endif
331#if defined(KOKKOS_ENABLE_SYCL)
332template class SplinesLinearProblemSparse<Kokkos::SYCL>;
333#endif
334
335} // namespace ddc::detail
The top-level namespace of DDC.