Arcane  v4.1.1.0
Documentation développeur
Chargement...
Recherche...
Aucune correspondance
GenericPartitioner.h
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2025 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
4// See the top-level COPYRIGHT file for details.
5// SPDX-License-Identifier: Apache-2.0
6//-----------------------------------------------------------------------------
7/*---------------------------------------------------------------------------*/
8/* GenericPartitioner.h (C) 2000-2025 */
9/* */
10/* Algorithme de partitionnement de liste. */
11/*---------------------------------------------------------------------------*/
12#ifndef ARCANE_ACCELERATOR_GENERICPARTITIONER_H
13#define ARCANE_ACCELERATOR_GENERICPARTITIONER_H
14/*---------------------------------------------------------------------------*/
15/*---------------------------------------------------------------------------*/
16
17#include "arcane/utils/ArrayView.h"
18#include "arcane/utils/FatalErrorException.h"
19#include "arcane/utils/NotImplementedException.h"
20#include "arcane/utils/NumArray.h"
21
22#include "arcane/accelerator/AcceleratorGlobal.h"
23#include "arcane/accelerator/core/RunQueue.h"
24#include "arcane/accelerator/CommonUtils.h"
25#include "arcane/accelerator/RunCommandLaunchInfo.h"
26#if defined(ARCANE_COMPILING_SYCL)
28#endif
29
30/*---------------------------------------------------------------------------*/
31/*---------------------------------------------------------------------------*/
32
33namespace Arcane::Accelerator::impl
34{
35
36/*---------------------------------------------------------------------------*/
37/*---------------------------------------------------------------------------*/
44class ARCANE_ACCELERATOR_EXPORT GenericPartitionerBase
45{
46 friend class GenericPartitionerIf;
47
48 public:
49
50 explicit GenericPartitionerBase(const RunQueue& queue);
51
52 protected:
53
54 Int32 _nbFirstPart() const;
55 SmallSpan<const Int32> _nbParts() const;
56 void _allocate();
57 void _resetNbPart();
58
59 protected:
60
61 RunQueue m_queue;
62 GenericDeviceStorage m_algo_storage;
63 DeviceStorage<int, 2> m_device_nb_list1_storage;
64 NumArray<Int32, MDDim1> m_host_nb_list1_storage;
65};
66
67/*---------------------------------------------------------------------------*/
68/*---------------------------------------------------------------------------*/
74{
75 public:
76
80 template <typename SelectLambda, typename InputIterator, typename OutputIterator>
81 void apply(GenericPartitionerBase& s, Int32 nb_item, InputIterator input_iter, OutputIterator output_iter,
82 const SelectLambda& select_lambda, const TraceInfo& trace_info = TraceInfo())
83 {
84 RunQueue queue = s.m_queue;
85 eExecutionPolicy exec_policy = queue.executionPolicy();
86 RunCommand command = makeCommand(queue);
87 command << trace_info;
88 Impl::RunCommandLaunchInfo launch_info(command, nb_item);
89 launch_info.beginExecute();
90 switch (exec_policy) {
91#if defined(ARCANE_COMPILING_CUDA)
93 size_t temp_storage_size = 0;
94 cudaStream_t stream = Impl::CudaUtils::toNativeStream(&queue);
95 // Premier appel pour connaitre la taille pour l'allocation
96 int* nb_list1_ptr = nullptr;
97 ARCANE_CHECK_CUDA(::cub::DevicePartition::If(nullptr, temp_storage_size,
98 input_iter, output_iter, nb_list1_ptr, nb_item,
99 select_lambda, stream));
100
101 s.m_algo_storage.allocate(temp_storage_size);
102 nb_list1_ptr = s.m_device_nb_list1_storage.allocate();
103 ARCANE_CHECK_CUDA(::cub::DevicePartition::If(s.m_algo_storage.address(), temp_storage_size,
104 input_iter, output_iter, nb_list1_ptr, nb_item,
105 select_lambda, stream));
106 s.m_device_nb_list1_storage.copyToAsync(s.m_host_nb_list1_storage, queue);
107 } break;
108#endif
109#if defined(ARCANE_COMPILING_HIP)
111 size_t temp_storage_size = 0;
112 // Premier appel pour connaitre la taille pour l'allocation
113 hipStream_t stream = Impl::HipUtils::toNativeStream(&queue);
114 int* nb_list1_ptr = nullptr;
115 ARCANE_CHECK_HIP(rocprim::partition(nullptr, temp_storage_size, input_iter, output_iter,
116 nb_list1_ptr, nb_item, select_lambda, stream));
117
118 s.m_algo_storage.allocate(temp_storage_size);
119 nb_list1_ptr = s.m_device_nb_list1_storage.allocate();
120
121 ARCANE_CHECK_HIP(rocprim::partition(s.m_algo_storage.address(), temp_storage_size, input_iter, output_iter,
122 nb_list1_ptr, nb_item, select_lambda, stream));
123 s.m_device_nb_list1_storage.copyToAsync(s.m_host_nb_list1_storage, queue);
124 } break;
125#endif
126#if defined(ARCANE_COMPILING_SYCL)
128#if defined(ARCANE_HAS_ONEDPL)
129 // Seulement implémenté pour DPC++.
130 // Actuellement (dpc++ 2025.0), il n'y a pas l'équivalent avec SYCL de
131 // la méthode de partition de cub ou rocprim.
132 // Utilise la fonction 'stable_partition'. Cependant, cette fonction
133 // ne supporte pas si la mémoire uniquement sur accélérateur. De plus,
134 // il faut que InputIterator et OutputIterator remplissent le concept
135 // std::random_access_iterator ce qui n'est pas le cas (notamment car il n'y a
136 // pas les opérateurs de copie ni de constructeur vide à cause de la lambda).
137 // Pour éviter tous ces problèmes, on alloue donc des tableaux temporaires
138 // pour l'appel à 'stable_sort' et on recopie les valeurs en sortie.
139 using InputDataType = typename InputIterator::value_type;
140 using DataType = typename OutputIterator::value_type;
141 NumArray<DataType, MDDim1> tmp_output_numarray(nb_item);
142 NumArray<bool, MDDim1> tmp_select_numarray(nb_item);
143 auto tmp_output = tmp_output_numarray.to1DSmallSpan();
144 auto tmp_select = tmp_select_numarray.to1DSmallSpan();
145 {
146 auto command = makeCommand(queue);
147 command << RUNCOMMAND_LOOP1(iter, nb_item)
148 {
149 auto [i] = iter();
150 tmp_output[i] = input_iter[i];
151 };
152 }
153 auto tmp_select_lambda = [=](Int32 i) { return tmp_select[i]; };
154 sycl::queue sycl_queue = Impl::SyclUtils::toNativeStream(queue);
155 auto policy = oneapi::dpl::execution::make_device_policy(sycl_queue);
156 auto output_after = oneapi::dpl::stable_partition(policy, tmp_output.begin(), tmp_output.end(), select_lambda);
157 queue.barrier();
158 Int32 nb_list1 = (output_after - tmp_output.begin());
159 Int32 nb_list2 = nb_item - nb_list1;
160 s.m_host_nb_list1_storage[0] = nb_list1;
161 //std::cerr << "NbList1=" << nb_list1 << " NbList2=" << nb_list2 << "\n";
162 {
163 // Recopie dans output les valeurs filtrées.
164 // Pour être cohérent avec 'cub' et 'rocprim', il faut inverser l'ordre des
165 // des valeurs de la liste pour les éléments ne remplissant pas la condition.
166 // Pour cela, on fait une boucle de taille (nb_list1 + nb_list/2) et chaque
167 // itération pour i>=nb_list1 gère deux éléments.
168 auto command = makeCommand(queue);
169 Int32 nb_iter2 = (nb_list2 / 2) + (nb_list2 % 2);
170 //std::cout << "NB_ITER2=" << nb_iter2 << "\n";
171 command << RUNCOMMAND_LOOP1(iter, (nb_list1 + nb_iter2))
172 {
173 auto [i] = iter();
174 if (i >= nb_list1) {
175 // Partie de la liste pour les valeurs ne remplissant par le critère.
176 Int32 j = i - nb_list1;
177 Int32 reverse_i = (nb_item - (j + 1));
178 auto x1 = tmp_output[i];
179 auto x2 = tmp_output[reverse_i];
180 output_iter[i] = tmp_output[reverse_i];
181 output_iter[reverse_i] = tmp_output[i];
182 }
183 else
184 output_iter[i] = tmp_output[i];
185 };
186 }
187 queue.barrier();
188#else // ARCANE_HAS_ONEDPL
189 ARCANE_THROW(NotImplementedException,"Partition is only implemented for SYCL back-end using oneDPL");
190#endif // ARCANE_HAS_ONEDPL
191 } break;
192#endif // ARCANE_COMPILING_SYCL
194 // Pas encore implémenté en multi-thread
195 [[fallthrough]];
197 auto saved_output_iter = output_iter;
198 auto output2_iter = output_iter + nb_item;
199 for (Int32 i = 0; i < nb_item; ++i) {
200 auto v = *input_iter;
201 if (select_lambda(v)) {
202 *output_iter = v;
203 ++output_iter;
204 }
205 else {
206 --output2_iter;
207 *output2_iter = v;
208 }
209 ++input_iter;
210 }
211 Int32 nb_list1 = static_cast<Int32>(output_iter - saved_output_iter);
212 s.m_host_nb_list1_storage[0] = nb_list1;
213 } break;
214 default:
215 ARCANE_FATAL(getBadPolicyMessage(exec_policy));
216 }
217 launch_info.endExecute();
218 }
219
223 template <typename Select1Lambda, typename Select2Lambda,
224 typename InputIterator, typename FirstOutputIterator,
225 typename SecondOutputIterator, typename UnselectedIterator>
227 InputIterator input_iter,
228 FirstOutputIterator first_output_iter,
229 SecondOutputIterator second_output_iter,
230 UnselectedIterator unselected_iter,
231 const Select1Lambda& select1_lambda,
232 const Select2Lambda& select2_lambda,
233 const TraceInfo& trace_info = TraceInfo())
234 {
235 RunQueue queue = s.m_queue;
236 eExecutionPolicy exec_policy = queue.executionPolicy();
237 RunCommand command = makeCommand(queue);
238 command << trace_info;
239 Impl::RunCommandLaunchInfo launch_info(command, nb_item);
240 launch_info.beginExecute();
241 switch (exec_policy) {
242#if defined(ARCANE_COMPILING_CUDA)
244 size_t temp_storage_size = 0;
245 cudaStream_t stream = Impl::CudaUtils::toNativeStream(&queue);
246 // Premier appel pour connaitre la taille pour l'allocation
247 int* nb_list1_ptr = nullptr;
248 ARCANE_CHECK_CUDA(::cub::DevicePartition::If(nullptr, temp_storage_size,
249 input_iter, first_output_iter, second_output_iter,
250 unselected_iter, nb_list1_ptr, nb_item,
251 select1_lambda, select2_lambda, stream));
252
253 s.m_algo_storage.allocate(temp_storage_size);
254 nb_list1_ptr = s.m_device_nb_list1_storage.allocate();
255 ARCANE_CHECK_CUDA(::cub::DevicePartition::If(s.m_algo_storage.address(), temp_storage_size,
256 input_iter, first_output_iter, second_output_iter,
257 unselected_iter, nb_list1_ptr, nb_item,
258 select1_lambda, select2_lambda, stream));
259 s.m_device_nb_list1_storage.copyToAsync(s.m_host_nb_list1_storage, queue);
260 } break;
261#endif
262#if defined(ARCANE_COMPILING_HIP)
264 size_t temp_storage_size = 0;
265 // Premier appel pour connaitre la taille pour l'allocation
266 hipStream_t stream = Impl::HipUtils::toNativeStream(&queue);
267 int* nb_list1_ptr = nullptr;
268 using namespace rocprim;
269 ARCANE_CHECK_HIP(::rocprim::partition_three_way(nullptr, temp_storage_size, input_iter, first_output_iter,
270 second_output_iter, unselected_iter,
271 nb_list1_ptr, nb_item, select1_lambda, select2_lambda, stream));
272
273 s.m_algo_storage.allocate(temp_storage_size);
274 nb_list1_ptr = s.m_device_nb_list1_storage.allocate();
275
276 ARCANE_CHECK_HIP(partition_three_way(s.m_algo_storage.address(), temp_storage_size, input_iter, first_output_iter,
277 second_output_iter, unselected_iter, nb_list1_ptr, nb_item,
278 select1_lambda, select2_lambda, stream));
279 s.m_device_nb_list1_storage.copyToAsync(s.m_host_nb_list1_storage, queue);
280 } break;
281#endif
282#if defined(ARCANE_COMPILING_SYCL)
284 ARCANE_THROW(NotImplementedException,"3-way partition is not implemented for SYCL back-end");
285 }
286 break;
287#endif
289 // Pas encore implémenté en multi-thread
290 [[fallthrough]];
292 Int32 nb_first = 0;
293 Int32 nb_second = 0;
294 for (Int32 i = 0; i < nb_item; ++i) {
295 auto v = *input_iter;
296 bool is_1 = select1_lambda(v);
297 bool is_2 = select2_lambda(v);
298 if (is_1) {
299 *first_output_iter = v;
300 ++first_output_iter;
301 ++nb_first;
302 }
303 else {
304 if (is_2) {
305 *second_output_iter = v;
306 ++second_output_iter;
307 ++nb_second;
308 }
309 else {
310 *unselected_iter = v;
311 ++unselected_iter;
312 }
313 }
314 // Incrémenter l'itérateur à la fin car il est utilisé pour le positionnement
315 ++input_iter;
316 }
317 s.m_host_nb_list1_storage[0] = nb_first;
318 s.m_host_nb_list1_storage[1] = nb_second;
319 } break;
320 default:
321 ARCANE_FATAL(getBadPolicyMessage(exec_policy));
322 }
323 launch_info.endExecute();
324 }
325};
326
327/*---------------------------------------------------------------------------*/
328/*---------------------------------------------------------------------------*/
329
330} // namespace Arcane::Accelerator::impl
331
332namespace Arcane::Accelerator
333{
334
335/*---------------------------------------------------------------------------*/
336/*---------------------------------------------------------------------------*/
343class GenericPartitioner
345{
346 public:
347
348 explicit GenericPartitioner(const RunQueue& queue)
350 {
351 _allocate();
352 }
353
354 public:
355
368 template <typename SelectLambda, typename SetterLambda>
369 void applyWithIndex(Int32 nb_value, const SetterLambda& setter_lambda,
370 const SelectLambda& select_lambda, const TraceInfo& trace_info = TraceInfo())
371 {
372 if (_checkEmpty(nb_value))
373 return;
374 _setCalled();
375 impl::GenericPartitionerBase* base_ptr = this;
377
378 impl::IndexIterator input_iter;
380 gf.apply(*base_ptr, nb_value, input_iter, out, select_lambda, trace_info);
381 }
382
399 template <typename InputIterator, typename OutputIterator, typename SelectLambda>
400 void applyIf(Int32 nb_value, InputIterator input_iter, OutputIterator output_iter,
401 const SelectLambda& select_lambda, const TraceInfo& trace_info = TraceInfo())
402 {
403 if (_checkEmpty(nb_value))
404 return;
405 _setCalled();
406 impl::GenericPartitionerBase* base_ptr = this;
408 gf.apply(*base_ptr, nb_value, input_iter, output_iter, select_lambda, trace_info);
409 }
410
428 template <typename Setter1Lambda, typename Setter2Lambda, typename UnselectedSetterLambda,
429 typename Select1Lambda, typename Select2Lambda>
430 void applyWithIndex(Int32 nb_value,
431 const Setter1Lambda setter1_lambda,
432 const Setter2Lambda setter2_lambda,
433 const UnselectedSetterLambda& unselected_setter_lambda,
434 const Select1Lambda& select1_lambda,
435 const Select2Lambda& select2_lambda,
436 const TraceInfo& trace_info = TraceInfo())
437 {
438 if (_checkEmpty(nb_value))
439 return;
440 _setCalled();
441 impl::GenericPartitionerBase* base_ptr = this;
443 impl::IndexIterator input_iter;
444 impl::SetterLambdaIterator<Setter1Lambda> setter1_wrapper(setter1_lambda);
445 impl::SetterLambdaIterator<Setter2Lambda> setter2_wrapper(setter2_lambda);
446 impl::SetterLambdaIterator<UnselectedSetterLambda> unselected_setter_wrapper(unselected_setter_lambda);
447 gf.apply3(*base_ptr, nb_value, input_iter, setter1_wrapper, setter2_wrapper,
448 unselected_setter_wrapper, select1_lambda, select2_lambda, trace_info);
449 }
450
468 template <typename InputIterator, typename FirstOutputIterator,
469 typename SecondOutputIterator, typename UnselectedIterator,
470 typename Select1Lambda, typename Select2Lambda>
471 void applyIf(Int32 nb_value, InputIterator input_iter,
472 FirstOutputIterator first_output_iter,
473 SecondOutputIterator second_output_iter,
474 UnselectedIterator unselected_iter,
475 const Select1Lambda& select1_lambda,
476 const Select2Lambda& select2_lambda,
477 const TraceInfo& trace_info = TraceInfo())
478 {
479 if (_checkEmpty(nb_value))
480 return;
481 _setCalled();
482 impl::GenericPartitionerBase* base_ptr = this;
484 gf.apply3(*base_ptr, nb_value, input_iter, first_output_iter, second_output_iter,
485 unselected_iter, select1_lambda, select2_lambda, trace_info);
486 }
487
492 {
493 m_is_already_called = false;
494 return _nbFirstPart();
495 }
496
508 {
509 m_is_already_called = false;
510 return _nbParts();
511 }
512
513 private:
514
515 bool m_is_already_called = false;
516
517 private:
518
519 void _setCalled()
520 {
521 if (m_is_already_called)
522 ARCANE_FATAL("apply() has already been called for this instance");
523 m_is_already_called = true;
524 }
525 bool _checkEmpty(Int32 nb_value)
526 {
527 if (nb_value == 0) {
528 _resetNbPart();
529 return true;
530 }
531 return false;
532 }
533};
534
535/*---------------------------------------------------------------------------*/
536/*---------------------------------------------------------------------------*/
537
538} // namespace Arcane::Accelerator
539
540/*---------------------------------------------------------------------------*/
541/*---------------------------------------------------------------------------*/
542
543#endif
544
545/*---------------------------------------------------------------------------*/
546/*---------------------------------------------------------------------------*/
#define ARCANE_THROW(exception_class,...)
Macro pour envoyer une exception avec formattage.
#define ARCANE_FATAL(...)
Macro envoyant une exception FatalErrorException.
Types et macros pour gérer les boucles sur les accélérateurs.
#define RUNCOMMAND_LOOP1(iter_name, x1,...)
Boucle 1D sur accélérateur avec arguments supplémentaires.
SmallSpan< const Int32 > nbParts()
Nombre d'éléments de la première et deuxième partie de la liste.
Int32 nbFirstPart()
Nombre d'éléments de la première partie de la liste.
void applyWithIndex(Int32 nb_value, const Setter1Lambda setter1_lambda, const Setter2Lambda setter2_lambda, const UnselectedSetterLambda &unselected_setter_lambda, const Select1Lambda &select1_lambda, const Select2Lambda &select2_lambda, const TraceInfo &trace_info=TraceInfo())
Effectue un partitionnement d'une liste en trois parties.
void applyIf(Int32 nb_value, InputIterator input_iter, OutputIterator output_iter, const SelectLambda &select_lambda, const TraceInfo &trace_info=TraceInfo())
Effectue un partitionnement d'une liste en deux parties.
void applyWithIndex(Int32 nb_value, const SetterLambda &setter_lambda, const SelectLambda &select_lambda, const TraceInfo &trace_info=TraceInfo())
Effectue un partitionnement d'une liste en deux parties.
void applyIf(Int32 nb_value, InputIterator input_iter, FirstOutputIterator first_output_iter, SecondOutputIterator second_output_iter, UnselectedIterator unselected_iter, const Select1Lambda &select1_lambda, const Select2Lambda &select2_lambda, const TraceInfo &trace_info=TraceInfo())
Effectue un partitionnement d'une liste en trois parties.
Object temporaire pour conserver les informations d'exécution d'une commande et regrouper les tests.
void beginExecute()
Indique qu'on commence l'exécution de la commande.
void endExecute()
Signale la fin de l'exécution.
void barrier() const
Bloque tant que toutes les commandes associées à la file ne sont pas terminées.
Definition RunQueue.cc:159
eExecutionPolicy executionPolicy() const
Politique d'exécution de la file.
Definition RunQueue.cc:169
Gère l'allocation interne sur le device pour un type donné.
void copyToAsync(SmallSpan< DataType > dest_ptr, const RunQueue &queue)
Copie l'instance dans dest_ptr.
Gère l'allocation interne sur le device.
Definition CommonUtils.h:98
Classe de base pour effectuer un filtrage.
Classe pour effectuer un partitionnement d'une liste.
void apply3(GenericPartitionerBase &s, Int32 nb_item, InputIterator input_iter, FirstOutputIterator first_output_iter, SecondOutputIterator second_output_iter, UnselectedIterator unselected_iter, const Select1Lambda &select1_lambda, const Select2Lambda &select2_lambda, const TraceInfo &trace_info=TraceInfo())
Effectue le partitionnement d'une liste en trois parties.
void apply(GenericPartitionerBase &s, Int32 nb_item, InputIterator input_iter, OutputIterator output_iter, const SelectLambda &select_lambda, const TraceInfo &trace_info=TraceInfo())
Effectue le partitionnement d'une liste en deux parties.
Itérateur sur une lambda pour positionner une valeur via un index.
Exception lorsqu'une fonction n'est pas implémentée.
Tableaux multi-dimensionnels pour les types numériques accessibles sur accélérateurs.
constexpr SmallSpan< DataType > to1DSmallSpan()
Vue 1D sur l'instance (uniquement si rank == 1)
Vue d'un tableau d'éléments de type T.
Definition Span.h:801
Espace de nom pour l'utilisation des accélérateurs.
RunCommand makeCommand(const RunQueue &run_queue)
Créé une commande associée à la file run_queue.
eExecutionPolicy
Politique d'exécution pour un Runner.
@ SYCL
Politique d'exécution utilisant l'environnement SYCL.
@ HIP
Politique d'exécution utilisant l'environnement HIP.
@ CUDA
Politique d'exécution utilisant l'environnement CUDA.
@ Sequential
Politique d'exécution séquentielle.
@ Thread
Politique d'exécution multi-thread.
std::int32_t Int32
Type entier signé sur 32 bits.