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