Alien  1.3.0
Developer documentation
Loading...
Searching...
No Matches
SYCLKernelInternal.h
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2026 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#pragma once
9
10#include <alien/kernels/sycl/SYCLPrecomp.h>
11
12#ifdef USE_SYCL2020
13#include <sycl/sycl.hpp>
14#else
15#include <CL/sycl.hpp>
16#endif
17/*---------------------------------------------------------------------------*/
18
19namespace Alien::SYCLInternal
20{
21
22#ifndef USE_SYCL2020
23 using namespace cl ;
24#endif
25/*---------------------------------------------------------------------------*/
26namespace mi300 {
27
28static constexpr int WAVEFRONT = 64; // WF natif gfx942
29static constexpr int N_WARPS_BLOC = WG_SIZE / WAVEFRONT; // 4
30
31 template<typename ValueT, typename ReadAccessorT, typename ReadWriteAccessorT>
32 void dot_kernel_mi300(
33 sycl::nd_item<1> item,
34 ReadAccessorT x,
35 ReadAccessorT y,
36 ReadWriteAccessorT partials,
37 size_t N,
38 sycl::local_accessor<ValueT,1> lds)
39 {
40 const size_t gid = item.get_global_id(0);
41 const size_t stride = item.get_global_range(0);
42 const size_t lid = item.get_local_id(0);
43
44 // ── Phase 1a : accumulation avec unroll ───────────────────────────────
45 // Sur MI300, le backend HIP émet des GLOBAL_LOAD_DWORDX2 (64-bit loads).
46 // ITEMS_PER_WI=8 → 8 loads double indépendants = ILP pour masquer ~400 cy HBM3.
47 // La boucle strided garantit la coalescence : WI consécutifs (gid, gid+1, ...)
48 // accèdent à des adresses consécutives → 1 transaction mémoire pour 64 WI.
49 ValueT acc = 0;
50
51 const size_t stride_wi = stride * ITEMS_PER_WI;
52 size_t i = gid * ITEMS_PER_WI;
53
54 for (; i + ITEMS_PER_WI * stride <= N; i += stride_wi) {
55 // Unroll 8× — le compilateur AMDGCNx émet des v_fma_f64 indépendants
56 // → jusqu'à 2 FMA doubles/cycle sur les SIMD gfx942
57 #pragma unroll
58 for (size_t k = 0; k < ITEMS_PER_WI; ++k)
59 acc += x[i + k * stride] * y[i + k * stride];
60 }
61 // Epilogue
62 for (size_t j = gid; j < N; j += stride)
63 acc += x[j] * y[j];
64
65 // ── Phase 1b : réduction wavefront (sub_group) ────────────────────────
66 // Sur gfx942, reduce_over_group<plus<double>> se compile en :
67 // ds_swizzle_b32 / v_readlane_b32 / v_writelane_b32
68 // soit 6 niveaux de shuffle pour 64 threads — zéro accès LDS ici.
69 // IMPORTANT : sub_group_size(64) est annoté sur le kernel pour forcer
70 // l'émission de la séquence WF=64 (vs WF=32 qui nécessiterait 5 niveaux
71 // mais casserait la coalescence des loads précédents).
72 auto sg = item.get_sub_group();
73 acc = reduce_over_group(sg, acc, sycl::plus<ValueT>{});
74
75 // ── Phase 1c : réduction inter-wavefront via LDS ──────────────────────
76 // 4 wavefronts → 4 valeurs LDS = 32 bytes (tient dans 1 cache line LDS)
77 const size_t wf_id = lid / WAVEFRONT;
78 const size_t lane_id = lid % WAVEFRONT;
79
80 if (lane_id == 0)
81 lds[wf_id] = acc;
82
83 item.barrier(sycl::access::fence_space::local_space);
84
85 // Le premier wavefront charge les 4 partiels LDS et les réduit
86 if (wf_id == 0) {
87 // Lane 0..3 chargent les 4 partiels, lanes 4..63 chargent 0
88 ValueT val = (lid < N_WARPS_BLOC) ? lds[lid] : 0.0;
89 // Réduction sur le sous-groupe — sur les 64 lanes du WF0
90 // seuls les 4 premiers portent des données, le reste est neutre (0.0)
91 val = sycl::reduce_over_group(sg, val, sycl::plus<ValueT>{});
92 if (lane_id == 0)
93 partials[item.get_group(0)] = val;
94 }
95 }
96
97 // ---------------------------------------------------------------------------
98 // Variante avec double buffering explicite — masque davantage la latence HBM3
99 // Recommandée pour très grands N (> 500 M doubles) où la latence domine.
100 // ---------------------------------------------------------------------------
101 template<typename ValueT, typename ReadAccessorT, typename ReadWriteAccessorT>
102 void dot_kernel_mi300_doublebuf(
103 sycl::nd_item<1> item,
104 ReadAccessorT x,
105 ReadAccessorT y,
106 ReadWriteAccessorT partials,
107 size_t N,
108 sycl::local_accessor<ValueT,1> lds)
109 {
110 const size_t gid = item.get_global_id(0);
111 const size_t stride = item.get_global_range(0);
112 const size_t lid = item.get_local_id(0);
113
114 ValueT acc0 = 0, acc1 = 0; // Deux accumulateurs → 2 chaînes FMA indép.
115
116 // Double-stride : deux éléments par itération sur des adresses distantes
117 // → deux séquences de loads indépendantes dans le scoreboard
118 for (size_t i = gid; i + stride < N; i += 2 * stride) {
119 ValueT x0 = x[i], y0 = y[i];
120 ValueT x1 = x[i + stride], y1 = y[i + stride];
121 acc0 += x0 * y0;
122 acc1 += x1 * y1;
123 }
124 // Fusionner + epilogue
125 ValueT acc = acc0 + acc1;
126 for (size_t i = gid + ((N / (2*stride)) * 2 * stride); i < N; i += stride)
127 acc += x[i] * y[i];
128
129 auto sg = item.get_sub_group();
130 acc = sycl::reduce_over_group(sg, acc, sycl::plus<ValueT>{});
131
132 const size_t wf_id = lid / WAVEFRONT;
133 const size_t lane_id = lid % WAVEFRONT;
134 if (lane_id == 0) lds[wf_id] = acc;
135 item.barrier(sycl::access::fence_space::local_space);
136 if (wf_id == 0) {
137 ValueT val = (lid < N_WARPS_BLOC) ? lds[lid] : 0.0;
138 val = reduce_over_group(sg, val, sycl::plus<double>{});
139 if (lane_id == 0)
140 partials[item.get_group(0)] = val;
141 }
142 }
143}
144
145
146namespace h100 {
147
148template<typename ValueT, typename ReadAccessorT, typename ReadWriteAccessorT>
149void dot_kernel_h100(
150 sycl::nd_item<1> item,
151 ReadAccessorT x,
152 ReadAccessorT y,
153 ReadWriteAccessorT partials, // USM device — 1 double par bloc
154 std::size_t N,
155 sycl::local_accessor<ValueT,1> lds) // LDS : WG_SIZE/WARP_SIZE doubles
156{
157 using namespace std;
158 using namespace sycl;
159 const size_t gid = item.get_global_id(0);
160 const size_t stride = item.get_global_range(0);
161 const size_t lid = item.get_local_id(0);
162
163 // ── Phase 1a : accumulation locale avec unroll ─────────────────────────
164 // Chaque WI traite ITEMS_PER_WI éléments strided pour coalescence.
165 // Le compilateur NVPTX génère des LDG.128 (128-bit loads) si aligné.
166 double acc = 0.0;
167
168 // Boucle principale — stride de global_range pour couvrir tout N
169 size_t i = gid * ITEMS_PER_WI;
170 const size_t stride_wi = stride * ITEMS_PER_WI;
171
172 for (; i + ITEMS_PER_WI * stride <= N; i += stride_wi) {
173 // Unroll explicite — 8 FMA indépendants → ILP maximal
174 // Le compilateur H100 peut émettre 4 FMA doubles/cycle/SM
175 #pragma unroll
176 for (size_t k = 0; k < ITEMS_PER_WI; ++k)
177 acc += x[i + k * stride] * y[i + k * stride];
178 }
179 // Epilogue — éléments restants
180 for (size_t j = gid; j < N; j += stride)
181 acc += x[j] * y[j];
182
183 // ── Phase 1b : réduction warp-shuffle (sub_group) ─────────────────────
184 // sycl::reduce_over_group utilise les shuffles natifs PTX (__shfl_down)
185 // sur H100 — 5 niveaux pour 32 threads, zéro accès mémoire.
186 auto sg = item.get_sub_group();
187 acc = sycl::reduce_over_group(sg, acc, sycl::plus<ValueT>{});
188
189 // ── Phase 1c : réduction inter-warp via LDS ───────────────────────────
190 // Un seul thread leader par warp écrit en LDS.
191 // LDS = WG_SIZE/WARP_SIZE = 16 doubles = 128 bytes — tient dans 1 cache line
192 const size_t warp_id = lid / WARP_SIZE;
193 const size_t lane_id = lid % WARP_SIZE;
194 const size_t n_warps = WG_SIZE / WARP_SIZE; // 16
195
196 if (lane_id == 0)
197 lds[warp_id] = acc;
198
199 item.barrier(access::fence_space::local_space);
200
201 // Réduction finale des 16 valeurs LDS par le premier warp
202 if (warp_id == 0) {
203 ValueT val = (lid < n_warps) ? lds[lid] : 0.0;
204 val = reduce_over_group(sg, val, sycl::plus<ValueT>{});
205 if (lane_id == 0)
206 partials[item.get_group(0)] = val;
207 }
208}
209}
210
211template <typename T>
212class Future
213{
214 public:
215 using FunctionType = std::function<T(sycl::event&,sycl::buffer<T>&,std::size_t)>;
216 Future(T& value)
217 : m_value(value)
218 //, m_d_value{1}
219 , m_d_value{ SYCLEnv::instance()->maxNumGroups() * TARGET_WAVES }
220 {
221 }
222
223 T& operator()()
224 {
225 //alien_info([&] { cout()<<"SYCL FUTURE value="<<m_value; });
226 return m_value;
227 }
228
229 T operator()() const
230 {
231 return m_value;
232 }
233
234 T get()
235 {
236 if (m_parallel_mng)
237 {
238 Arccore::MessagePassing::mpWait(m_parallel_mng, m_request);
239 m_parallel_mng = nullptr;
240 }
241 else
242 {
243 if(m_wait_function)
244 {
245 m_value = m_wait_function(m_event,m_d_value,m_num_blocks) ;
246 m_wait_function = nullptr ;
247 }
248 }
249 return m_value;
250 }
251
252 sycl::buffer<T, 1>& deviceValue()
253 {
254 return m_d_value;
255 }
256
257 sycl::event& event() {
258 return m_event;
259 }
260
261
262 void addRequest(Arccore::MessagePassing::IMessagePassingMng* parallel_mng,
263 Arccore::MessagePassing::Request request)
264 {
265 m_parallel_mng = parallel_mng;
266 m_request = request;
267 }
268
269 void setWaitFunction(FunctionType wait_function)
270 {
271 m_wait_function = wait_function;
272 }
273
274 void setNumBlocks(std::size_t num_blocks)
275 {
276 m_num_blocks = num_blocks;
277 }
278
279 private:
280 T& m_value;
281 sycl::buffer<T, 1> m_d_value;
282 sycl::event m_event;
283 FunctionType m_wait_function = nullptr;
284 std::size_t m_num_blocks = SYCLEnv::instance()->maxNumGroups() * TARGET_WAVES ;
285
286 Arccore::MessagePassing::IMessagePassingMng* m_parallel_mng = nullptr;
287 Arccore::MessagePassing::Request m_request;
288};
289
290class KernelInternal
291{
292 private:
293 int m_dot_algo = 3;
294
295 public:
296 KernelInternal()
297 {
298 m_env = SYCLEnv::instance();
299
300 // clang-format off
301 m_max_num_groups = m_env->maxNumGroups() ;
302 m_max_work_group_size = m_env->maxWorkGroupSize() ;
303 m_total_threads = m_env->maxNumThreads() ;
304 // clang-format on
305 }
306
307 virtual ~KernelInternal() {}
308
309 void setDotAlgo(int dot_algo)
310 {
311 m_dot_algo = dot_algo;
312 }
313
314 template <typename T>
315 void assign(T const a,
316 sycl::buffer<T>& y)
317 {
318 m_env->internal()->queue().submit(
319 [&](sycl::handler& cgh)
320 {
321 auto acc = y.template get_access<sycl::access::mode::discard_write>(cgh);
322 cgh.fill(acc, a);
323 });
324 /*
325 sycl::range<1> work_items{ m_total_threads };
326 {
327 // clang-format off
328 m_env->internal()->queue().submit( [&](sycl::handler& cgh)
329 {
330 auto access_y = y.template get_access<sycl::access::mode::read_write>(cgh);
331 auto y_length = y.size() ;
332 cgh.parallel_for<class vector_assign>(sycl::range<1>{m_total_threads}, [=] (sycl::item<1> itemId)
333 {
334 auto id = itemId.get_id(0);
335 for (auto i = id; i < y_length; i += itemId.get_range()[0])
336 access_y[i] = a;
337 });
338 });
339 // clang-format on
340 }*/
341 }
342
343 template <typename T, typename Lambda>
344 void apply(Lambda const& lambda,
345 sycl::buffer<T>& y)
346 {
347 sycl::range<1> work_items{ m_total_threads };
348 {
349 // clang-format off
350 m_env->internal()->queue().submit( [&](sycl::handler& cgh)
351 {
352 auto access_y = y.template get_access<sycl::access::mode::read_write>(cgh);
353 auto y_length = y.size() ;
354 cgh.parallel_for<class vector_apply>(sycl::range<1>{m_total_threads}, [=] (sycl::item<1> itemId)
355 {
356 auto id = itemId.get_id(0);
357 for (auto i = id; i < y_length; i += itemId.get_range()[0])
358 access_y[i] = lambda(i);
359 });
360 });
361 // clang-format on
362 }
363 }
364
365#ifdef NAIVE
366 template <typename T>
367 void scal(T a,
368 sycl::buffer<T>& y)
369 {
370 sycl::range<1> work_items{ m_total_threads };
371 {
372 // clang-format off
373 m_env->internal()->queue().submit([&](sycl::handler& cgh)
374 {
375 auto access_y = y.template get_access<sycl::access::mode::read_write>(cgh);
376 auto y_length = y.size() ;
377 cgh.parallel_for<class vector_scal>(sycl::range<1>{m_total_threads}, [=] (sycl::item<1> itemId)
378 {
379 auto id = itemId.get_id(0);
380 for (auto i = id; i < y_length; i += itemId.get_range()[0])
381 access_y[i] = a*access_y[i];
382 });
383 });
384 // clang-format on
385 }
386 }
387
388 template <typename T>
389 void axpy(T const a,
390 sycl::buffer<T>& x,
391 sycl::buffer<T>& y)
392 {
393 sycl::range<1> work_items{ m_total_threads };
394 {
395 // clang-format off
396 m_env->internal()->queue().submit([&](sycl::handler& cgh)
397 {
398 auto access_x = x.template get_access<sycl::access::mode::read>(cgh);
399 auto access_y = y.template get_access<sycl::access::mode::read_write>(cgh);
400 auto y_length = y.size() ;
401 cgh.parallel_for<class vector_axpy>(sycl::range<1>{m_total_threads}, [=] (sycl::item<1> itemId)
402 {
403 auto id = itemId.get_id(0);
404 for (auto i = id; i < y_length; i += itemId.get_range()[0])
405 access_y[i] += a * access_x[i];
406 });
407 });
408 // clang-format on
409 }
410 }
411#endif
412 template <typename T>
413 void scal(T a,
414 sycl::buffer<T>& y)
415 {
416 using VecT = sycl::vec<T, 2>;
417
418 auto& queue = m_env->internal()->queue();
419 const size_t n = y.size();
420 const size_t n2 = n / 2; // éléments traités vectoriellement
421 const size_t tail = n % 2;
422
423 static constexpr size_t WG_SIZE = 256;
424 const size_t blocks = std::max((n2 + WG_SIZE - 1) / WG_SIZE,
425 m_max_num_groups * 4UL);
426 const size_t total = blocks * WG_SIZE;
427
428 // ── Reinterprétation des buffers en vec<T,2> ──────────────────────────
429 // Requiert que les buffers soient alignés 16 bytes (garantit par
430 // sycl::buffer qui aligne sur max_required_work_group_size).
431 sycl::buffer<VecT> y2{ y.template reinterpret<VecT>(sycl::range<1>{n2}) };
432
433 queue.submit([&](sycl::handler& cgh) {
434 auto ay = y2.template get_access<sycl::access::mode::read_write>(cgh);
435
436 cgh.parallel_for<class vector_xcal_vec>(
437 sycl::nd_range<1>{ {total}, {WG_SIZE} },
438 [=](sycl::nd_item<1> item) {
439 const size_t stride = item.get_global_range()[0];
440 for (size_t i = item.get_global_id(0); i < n2; i += stride) {
441 // Un seul load 128-bit pour x, un pour y
442 ay[i] = VecT{a, a} * ay[i];
443 }
444 });
445 });
446
447 // Epilogue scalaire si n est impair (rare en pratique)
448 if (tail) {
449 sycl::buffer<T> yt{ y.template reinterpret<T>(sycl::range<1>{n}) };
450 queue.submit([&](sycl::handler& cgh) {
451 auto ay = yt.template get_access<sycl::access::mode::read_write>(cgh);
452 cgh.single_task([=]{ ay[n-1] = a * ay[n-1]; });
453 });
454 }
455
456 }
457
458 template <typename T>
459 void axpy(T const a,
460 sycl::buffer<T>& x,
461 sycl::buffer<T>& y)
462 {
463 using VecT = sycl::vec<T, 2>;
464
465 auto& queue = m_env->internal()->queue();
466 const size_t n = y.size();
467 const size_t n2 = n / 2; // éléments traités vectoriellement
468 const size_t tail = n % 2;
469
470 static constexpr size_t WG_SIZE = 256;
471 const size_t blocks = std::max((n2 + WG_SIZE - 1) / WG_SIZE,
472 m_max_num_groups * 4UL);
473 const size_t total = blocks * WG_SIZE;
474
475 // ── Reinterprétation des buffers en vec<T,2> ──────────────────────────
476 // Requiert que les buffers soient alignés 16 bytes (garantit par
477 // sycl::buffer qui aligne sur max_required_work_group_size).
478 sycl::buffer<VecT> x2{ x.template reinterpret<VecT>(sycl::range<1>{n2}) };
479 sycl::buffer<VecT> y2{ y.template reinterpret<VecT>(sycl::range<1>{n2}) };
480
481 queue.submit([&](sycl::handler& cgh) {
482 auto ax = x2.template get_access<sycl::access::mode::read>(cgh);
483 auto ay = y2.template get_access<sycl::access::mode::read_write>(cgh);
484
485 cgh.parallel_for<class vector_axpy_vec>(
486 sycl::nd_range<1>{ {total}, {WG_SIZE} },
487 [=](sycl::nd_item<1> item) {
488 const size_t stride = item.get_global_range()[0];
489 for (size_t i = item.get_global_id(0); i < n2; i += stride) {
490 // Un seul load 128-bit pour x, un pour y
491 ay[i] = ay[i] + VecT{a, a} * ax[i]; // fused multiply-add
492 }
493 });
494 });
495
496 // Epilogue scalaire si n est impair (rare en pratique)
497 if (tail) {
498 sycl::buffer<T> xt{ x.template reinterpret<T>(sycl::range<1>{n}) };
499 sycl::buffer<T> yt{ y.template reinterpret<T>(sycl::range<1>{n}) };
500 queue.submit([&](sycl::handler& cgh) {
501 auto ax = xt.template get_access<sycl::access::mode::read>(cgh);
502 auto ay = yt.template get_access<sycl::access::mode::read_write>(cgh);
503 cgh.single_task([=]{ ay[n-1] += a * ax[n-1]; });
504 });
505 }
506 }
507
508 template <typename T>
509 void axpy(T const a,
510 sycl::buffer<T>& x,
511 Integer stride_x,
512 sycl::buffer<T>& y,
513 Integer stride_y)
514 {
515 sycl::range<1> work_items{ m_total_threads };
516 {
517 // clang-format off
518 m_env->internal()->queue().submit([&](sycl::handler& cgh)
519 {
520 auto access_x = x.template get_access<sycl::access::mode::read>(cgh);
521 auto access_y = y.template get_access<sycl::access::mode::read_write>(cgh);
522 auto x_length = x.size()/stride_x ;
523 cgh.parallel_for<class vector_axpy>(sycl::range<1>{m_total_threads}, [=] (sycl::item<1> itemId)
524 {
525 auto id = itemId.get_id(0);
526 for (auto i = id; i < x_length; i += itemId.get_range()[0])
527 access_y[i*stride_y] += a * access_x[i*stride_x];
528 });
529 });
530 // clang-format on
531 }
532 }
533 template <typename T>
534 void pointwiseMult(sycl::buffer<T>& x,
535 sycl::buffer<T>& y,
536 sycl::buffer<T>& z)
537 {
538#ifdef NAIVE
539 sycl::range<1> work_items{ m_total_threads };
540 {
541 // clang-format off
542 m_env->internal()->queue().submit([&](sycl::handler& cgh)
543 {
544 auto access_x = x.template get_access<sycl::access::mode::read>(cgh);
545 auto access_y = y.template get_access<sycl::access::mode::read>(cgh);
546 auto access_z = z.template get_access<sycl::access::mode::read_write>(cgh);
547 auto y_length = y.size() ;
548 cgh.parallel_for<class vector_pointwizemult>(sycl::range<1>{m_total_threads}, [=] (sycl::item<1> itemId)
549 {
550 auto id = itemId.get_id(0);
551 for (auto i = id; i < y_length; i += itemId.get_range()[0])
552 access_z[i] = access_x[i] * access_y[i];
553 });
554 });
555 // clang-format on
556 }
557#endif
558 using VecT = sycl::vec<T, 2>;
559
560 auto& queue = m_env->internal()->queue();
561 const size_t n = y.size();
562 const size_t n2 = n / 2; // éléments traités vectoriellement
563 const size_t tail = n % 2;
564
565 static constexpr size_t WG_SIZE = 256;
566 const size_t blocks = std::max((n2 + WG_SIZE - 1) / WG_SIZE,
567 m_max_num_groups * 4UL);
568 const size_t total = blocks * WG_SIZE;
569
570 // ── Reinterprétation des buffers en vec<T,2> ──────────────────────────
571 // Requiert que les buffers soient alignés 16 bytes (garantit par
572 // sycl::buffer qui aligne sur max_required_work_group_size).
573 sycl::buffer<VecT> x2{ x.template reinterpret<VecT>(sycl::range<1>{n2}) };
574 sycl::buffer<VecT> y2{ y.template reinterpret<VecT>(sycl::range<1>{n2}) };
575 sycl::buffer<VecT> z2{ z.template reinterpret<VecT>(sycl::range<1>{n2}) };
576
577 queue.submit([&](sycl::handler& cgh) {
578 auto ax = x2.template get_access<sycl::access::mode::read>(cgh);
579 auto ay = y2.template get_access<sycl::access::mode::read>(cgh);
580 auto az = z2.template get_access<sycl::access::mode::read_write>(cgh);
581
582 cgh.parallel_for<class vector_pointwizemult>(
583 sycl::nd_range<1>{ {total}, {WG_SIZE} },
584 [=](sycl::nd_item<1> item) {
585 const size_t stride = item.get_global_range()[0];
586 for (size_t i = item.get_global_id(0); i < n2; i += stride) {
587 // Un seul load 128-bit pour x, un pour y
588 az[i] = ax[i] * ay[i]; // fused multiply-add
589 }
590 });
591 });
592
593 // Epilogue scalaire si n est impair (rare en pratique)
594 if (tail) {
595 sycl::buffer<T> xt{ x.template reinterpret<T>(sycl::range<1>{n}) };
596 sycl::buffer<T> yt{ y.template reinterpret<T>(sycl::range<1>{n}) };
597 sycl::buffer<T> zt{ z.template reinterpret<T>(sycl::range<1>{n}) };
598 queue.submit([&](sycl::handler& cgh) {
599 auto ax = xt.template get_access<sycl::access::mode::read>(cgh);
600 auto ay = yt.template get_access<sycl::access::mode::read>(cgh);
601 auto az = zt.template get_access<sycl::access::mode::read_write>(cgh);
602 cgh.single_task([=]{ az[n-1] = ax[n-1] * ay[n-1]; });
603 });
604 }
605
606#ifdef PRINT_DEBUG_INFO
607 {
608 sycl::host_accessor<T, 1, sycl::access::mode::read> x_acc(x);
609 sycl::host_accessor<T, 1, sycl::access::mode::read> y_acc(y);
610 sycl::host_accessor<T, 1, sycl::access::mode::read> z_acc(z);
611 for(int il=0;il<x.size();++il)
612 {
613 std::cout<<"X Y Z ["<<il<<"] : "<<x_acc[il]<<"*"<<y_acc[il]<<"="<<z_acc[il]<<std::endl ;
614 }
615 }
616#endif
617 }
618
619 template <typename T>
620 void copy(sycl::buffer<T>& x,
621 sycl::buffer<T>& y)
622 {
623 //sycl::range<1> work_items{ m_total_threads };
624 {
625 // clang-format off
626 m_env->internal()->queue().submit(
627 [&](sycl::handler& cgh)
628 {
629 auto access_x = x.template get_access<sycl::access::mode::read>(cgh);
630 auto access_y = y.template get_access<sycl::access::mode::discard_write>(cgh);
631 cgh.copy(access_x,access_y) ;
632 /*
633 auto y_length = y.size() ;
634 cgh.parallel_for<class vector_copy>(sycl::range<1>{m_total_threads}, [=] (sycl::item<1> itemId)
635 {
636 auto id = itemId.get_id(0);
637 for (auto i = id; i < y_length; i += itemId.get_range()[0])
638 access_y[i] = access_x[i];
639 });
640 */
641 });
642 // clang-format on
643 }
644 }
645
646 template <typename T>
647 void copy(sycl::buffer<T>& x,
648 Integer stride_x,
649 sycl::buffer<T>& y,
650 Integer stride_y)
651 {
652 sycl::range<1> work_items{ m_total_threads };
653 {
654 // clang-format off
655 m_env->internal()->queue().submit( [&](sycl::handler& cgh)
656 {
657 auto access_x = x.template get_access<sycl::access::mode::read>(cgh);
658 auto access_y = y.template get_access<sycl::access::mode::read_write>(cgh);
659 auto x_length = x.size()/stride_x ;
660 cgh.parallel_for<class vector_copy>(sycl::range<1>{m_total_threads}, [=] (sycl::item<1> itemId)
661 {
662 auto id = itemId.get_id(0);
663 for (auto i = id; i < x_length; i += itemId.get_range()[0])
664 access_y[i*stride_y] = access_x[i*stride_x];
665 });
666 });
667 // clang-format on
668 }
669 }
670
671 template <typename T>
673 {
674 };
675
676 // to make global size multiple of local size
677 template <typename index_t>
678 inline index_t round_up(const index_t x, const index_t y)
679 {
680 return ((x + y - 1) / y) * y;
681 }
682
683 template <typename T>
685
686 template <typename T>
687 T reduce_sum(sycl::buffer<T>& x,
688 sycl::buffer<T>& y)
689 {
690
691 auto& w = getWorkBuffer<T>(x.size());
692
693 // clang-format off
694 m_env->internal()->queue().submit( [&](sycl::handler& cgh)
695 {
696 auto access_x = x.template get_access<sycl::access::mode::read>(cgh);
697 auto access_y = y.template get_access<sycl::access::mode::read>(cgh);
698 //auto access_w = w.template get_access<sycl::access::mode::write>(cgh);
699 auto access_w = sycl::accessor { w, cgh, sycl::write_only, sycl::property::no_init{}};
700 //auto access_w = w.get_access(cgh,sycl::write_only, sycl::property::no_init{});
701 auto y_length = y.size() ;
702 cgh.parallel_for<class vector_dot>(sycl::range<1>{m_total_threads}, [=] (sycl::item<1> itemId)
703 {
704 auto id = itemId.get_id(0);
705 for (auto i = id; i < y_length; i += itemId.get_range()[0])
706 access_w[i] = access_x[i]*access_y[i];
707 });
708 });
709 // clang-format on
710
711 std::size_t local = m_max_work_group_size;
712 std::size_t length = x.size();
713
714 int level = 0;
715 {
716 /* Each iteration of the do loop applies one level of reduction until
717 * the input is of length 1 (i.e. the reduction is complete). */
718 do {
719 auto round_length = round_up(length, local);
720 // clang-format off
721 auto f = [length, round_length, local, &w](sycl::handler& h) mutable
722 {
723 sycl::nd_range<1> range{sycl::range<1>{round_length},
724 sycl::range<1>{local}};
725 auto access_w = w.template get_access<sycl::access::mode::read_write>(h);
726
727 //sycl::accessor<T, 1, sycl::access::mode::read_write,sycl::access::target::local>
728 // scratch(sycl::range<1>(local), h);
729 sycl::local_accessor<T> scratch{sycl::range<1>(local), h};
730
731 /* The parallel_for invocation chosen is the variant with an nd_item
732 * parameter, since the code requires barriers for correctness. */
733 h.parallel_for<class sycl_reduction_sum_T>(range,
734 [access_w, scratch, local, length](sycl::nd_item<1> id)
735 {
736 std::size_t globalid = id.get_global_id(0);
737 std::size_t localid = id.get_local_id(0);
738
739 /* All threads collectively read from global memory into local.
740 * The barrier ensures all threads' IO is resolved before
741 * execution continues (strictly speaking, all threads within
742 * a single work-group - there is no co-ordination between
743 * work-groups, only work-items). */
744 scratch[localid] = (globalid < length)? access_w[globalid] : 0. ;
745 id.barrier(sycl::access::fence_space::local_space);
746
747 /* Apply the reduction operation between the current local
748 * id and the one on the other half of the vector. */
749 if (globalid < length)
750 {
751 //int min = (length < local) ? length : local;
752 std::size_t min = local ;
753 for (std::size_t offset = min / 2; offset > 0; offset /= 2)
754 //for (std::size_t offset = id.get_local_range(0) / 2; offset > 0; offset /= 2)
755 {
756 if (localid < offset)
757 {
758 scratch[localid] += scratch[localid + offset];
759 }
760 id.barrier(sycl::access::fence_space::local_space);
761 }
762 /* The final result will be stored in local id 0. */
763 if (localid == 0)
764 {
765 access_w[id.get_group(0)] = scratch[localid];
766 }
767 }
768 });
769 };
770 // clang-format on
771 m_env->internal()->queue().submit(f);
772 /* At this point, you could queue::wait_and_throw() to ensure that
773 * errors are caught quickly. However, this would likely impact
774 * performance negatively. */
775 length = (length + local - 1) / local;
776 ++level;
777 } while (length > 1);
778 }
779 //auto h_w = w.template get_access<sycl::access::mode::read>();
780 auto h_w = w.get_host_access();
781 T sum = h_w[0];
782 return sum;
783 }
784
785 template <typename T>
787
788 template <typename T>
790
791 template <typename T>
792 T map_reduce_sum(sycl::buffer<T>& x,
793 sycl::buffer<T>& y)
794 {
795 auto& w = getWorkBuffer<T>(x.size());
796
797 std::size_t local = m_max_work_group_size;
798 std::size_t length = x.size();
799
800 int level = 0;
801 {
802 /* Each iteration of the do loop applies one level of reduction until
803 * the input is of length 1 (i.e. the reduction is complete). */
804 //do
805 {
806 auto round_length = round_up(length, local);
807 // clang-format off
808 auto f0 = [length, round_length, local, &x,&y, &w](sycl::handler& h) mutable
809 {
810 sycl::nd_range<1> range{sycl::range<1>{round_length},
811 sycl::range<1>{local}};
812 auto access_x = x.template get_access<sycl::access::mode::read>(h);
813 auto access_y = y.template get_access<sycl::access::mode::read>(h);
814 //auto access_w = w.template get_access<sycl::access::mode::read_write>(h);
815 auto access_w = sycl::accessor { w, h, sycl::read_write, sycl::property::no_init{}};
816
817 //sycl::accessor<T, 1, sycl::access::mode::read_write,sycl::access::target::local>
818 // scratch(sycl::range<1>(local), h);
819 sycl::local_accessor<T> scratch{sycl::range<1>(local), h};
820
821 /* The parallel_for invocation chosen is the variant with an nd_item
822 * parameter, since the code requires barriers for correctness. */
823 h.parallel_for<class sycl_map_reduction_sum0_T>(range,
824 [access_x,access_y,access_w, scratch, local, length](sycl::nd_item<1> id)
825 {
826 std::size_t globalid = id.get_global_id(0);
827 std::size_t localid = id.get_local_id(0);
828
829 /* All threads collectively read from global memory into local.
830 * The barrier ensures all threads' IO is resolved before
831 * execution continues (strictly speaking, all threads within
832 * a single work-group - there is no co-ordination between
833 * work-groups, only work-items). */
834 scratch[localid] = (globalid < length)? access_x[globalid]*access_y[globalid] : 0. ;
835
836 id.barrier(sycl::access::fence_space::local_space);
837
838 /* Apply the reduction operation between the current local
839 * id and the one on the other half of the vector. */
840 if (globalid < length)
841 {
842 //int min = (length < local) ? length : local;
843 std::size_t min = local ;
844 for (std::size_t offset = min / 2; offset > 0; offset /= 2)
845 //for (std::size_t offset = id.get_local_range(0) / 2; offset > 0; offset /= 2)
846 {
847 if (localid < offset)
848 {
849 scratch[localid] += scratch[localid + offset];
850 }
851 id.barrier(sycl::access::fence_space::local_space);
852 }
853 /* The final result will be stored in local id 0. */
854 if (localid == 0)
855 {
856 access_w[id.get_group(0)] = scratch[localid];
857 }
858 }
859 });
860 };
861 // clang-format on
862
863 // clang-format off
864 auto f1 = [length, round_length, local, &w](sycl::handler& h) mutable
865 {
866 sycl::nd_range<1> range{sycl::range<1>{round_length},
867 sycl::range<1>{local}};
868 auto access_w = w.template get_access<sycl::access::mode::read_write>(h);
869
870 //sycl::accessor<T, 1, sycl::access::mode::read_write,sycl::access::target::local>
871 // scratch(sycl::range<1>(local), h);
872 sycl::local_accessor<T> scratch{sycl::range<1>(local), h};
873
874 /* The parallel_for invocation chosen is the variant with an nd_item
875 * parameter, since the code requires barriers for correctness. */
876 h.parallel_for<class sycl_map_reduction_sum_T>(range,
877 [access_w, scratch, local, length](sycl::nd_item<1> id)
878 {
879 std::size_t globalid = id.get_global_id(0);
880 std::size_t localid = id.get_local_id(0);
881
882 /* All threads collectively read from global memory into local.
883 * The barrier ensures all threads' IO is resolved before
884 * execution continues (strictly speaking, all threads within
885 * a single work-group - there is no co-ordination between
886 * work-groups, only work-items). */
887 scratch[localid] = (globalid < length)? access_w[globalid] : 0. ;
888 id.barrier(sycl::access::fence_space::local_space);
889
890 /* Apply the reduction operation between the current local
891 * id and the one on the other half of the vector. */
892 if (globalid < length)
893 {
894 //int min = (length < local) ? length : local;
895 std::size_t min = local ;
896 for (std::size_t offset = min / 2; offset > 0; offset /= 2)
897 //for (std::size_t offset = id.get_local_range(0) / 2; offset > 0; offset /= 2)
898 {
899 if (localid < offset)
900 {
901 scratch[localid] += scratch[localid + offset];
902 }
903 id.barrier(sycl::access::fence_space::local_space);
904 }
905 /* The final result will be stored in local id 0. */
906 if (localid == 0)
907 {
908 access_w[id.get_group(0)] = scratch[localid];
909 }
910 }
911 });
912 };
913 // clang-format on
914 if (level == 0)
915 m_env->internal()->queue().submit(f0);
916 else
917 m_env->internal()->queue().submit(f1);
918 /* At this point, you could queue::wait_and_throw() to ensure that
919 * errors are caught quickly. However, this would likely impact
920 * performance negatively. */
921 length = (length + local - 1) / local;
922 ++level;
923 } //while (length > 1);
924 }
925 auto h_x = w.get_host_access();
926 //T sum = h_x[0] ;
927 T sum = 0;
928 for (std::size_t i = 0; i < length; ++i)
929 sum += h_x[i];
930 return sum;
931 }
932
933 template <typename T>
935
936 template <typename T>
937 T map2_reduce_sum(sycl::buffer<T>& x,
938 sycl::buffer<T>& y)
939 {
940 std::size_t local = m_max_work_group_size;
941 std::size_t total_threads = m_total_threads;
942 std::size_t length = x.size();
943
944 T sum_init = 0;
945 sycl::buffer<T> sum{ &sum_init, 1 };
946
947 {
948 /* Each iteration of the do loop applies one level of reduction until
949 * the input is of length 1 (i.e. the reduction is complete). */
950 {
951 auto round_length = round_up(length, local);
952 // clang-format off
953 auto f0 = [length, round_length,total_threads, local, &x, &y, &sum](sycl::handler& h) mutable
954 {
955 sycl::nd_range<1> range{sycl::range<1>{std::min(total_threads,round_length)},
956 sycl::range<1>{local}};
957 auto access_x = x.template get_access<sycl::access::mode::read>(h);
958 auto access_y = y.template get_access<sycl::access::mode::read>(h);
959
960 sycl::accessor access_sum {sum, h};
961#ifdef USE_HIPSYCL
962 auto sumReduction = sycl::reduction(access_sum, sycl::plus<T>());
963#endif
964#ifdef USE_ONEAPI
965 auto sumReduction = sycl::reduction(sum, h, sycl::plus<T>());
966#endif
967#ifdef USE_ACPPSYCL
968 auto sumReduction = sycl::reduction(sum, h, sycl::plus<T>());
969#endif
970
971 //sycl::accessor<T, 1, sycl::access::mode::read_write,sycl::access::target::local>
972 // scratch(sycl::range<1>(local), h);
973 sycl::local_accessor<T> scratch{sycl::range<1>(local), h};
974
975 /* The parallel_for invocation chosen is the variant with an nd_item
976 * parameter, since the code requires barriers for correctness. */
977 h.parallel_for<class sycl_map2_reduction_sum0_T>(range,
978 sumReduction,
979 [access_x,access_y, scratch, local,total_threads,length](sycl::nd_item<1> id, auto &sum)
980 {
981 std::size_t globalid = id.get_global_id(0);
982 std::size_t localid = id.get_local_id(0);
983
984 /* All threads collectively read from global memory into local.
985 * The barrier ensures all threads' IO is resolved before
986 * execution continues (strictly speaking, all threads within
987 * a single work-group - there is no co-ordination between
988 * work-groups, only work-items). */
989 scratch[localid] = (globalid < length)? access_x[globalid]*access_y[globalid] : 0. ;
990 for (auto i = globalid+total_threads; i < length; i += total_threads)
991 scratch[localid] += access_x[i]*access_y[i];
992
993 id.barrier(sycl::access::fence_space::local_space);
994
995 /* Apply the reduction operation between the current local
996 * id and the one on the other half of the vector. */
997 //if (globalid < length)
998 {
999 //int min = (length < local) ? length : local;
1000 std::size_t min = local ;
1001 for (std::size_t offset = min / 2; offset > 0; offset /= 2)
1002 //for (std::size_t offset = id.get_local_range(0) / 2; offset > 0; offset /= 2)
1003 {
1004 if (localid < offset)
1005 {
1006 scratch[localid] += scratch[localid + offset];
1007 }
1008 id.barrier(sycl::access::fence_space::local_space);
1009 }
1010 /* The final result will be stored in local id 0. */
1011 if (localid == 0)
1012 {
1013 //access_w[id.get_group(0)] = scratch[localid];
1014 sum += scratch[localid];
1015 }
1016 }
1017 });
1018 };
1019 // clang-format on
1020 m_env->internal()->queue().submit(f0);
1021 }
1022 }
1023 auto h_sum = sum.get_host_access();
1024 return h_sum[0];
1025 }
1026
1027 template <typename T>
1029
1030 template <typename T>
1031 T map3_reduce_sum(sycl::buffer<T>& x,
1032 sycl::buffer<T>& y)
1033 {
1034 std::size_t local = m_max_work_group_size;
1035 std::size_t total_threads = m_total_threads;
1036 std::size_t length = x.size();
1037
1038 //std::vector<T> group_sum(m_max_num_groups) ;
1039 //sycl::buffer<T> group_sum{ m_max_num_groups };
1040 auto& group_sum = getWorkBuffer<T>(m_max_num_groups);
1041
1042 //T sum_init = 0;
1043 //sycl::buffer<T> sum{ &sum_init, 1 };
1044
1045 auto round_length = round_up(length, local);
1046 {
1047 /* Each iteration of the do loop applies one level of reduction until
1048 * the input is of length 1 (i.e. the reduction is complete). */
1049 //do
1050 {
1051 // clang-format off
1052 auto f0 = [total_threads,round_length, length, local, &x, &y,&group_sum](sycl::handler& h) mutable
1053 {
1054 sycl::nd_range<1> range{sycl::range<1>{std::min(total_threads,round_length)},
1055 sycl::range<1>{local}};
1056 auto access_x = x.template get_access<sycl::access::mode::read>(h);
1057 auto access_y = y.template get_access<sycl::access::mode::read>(h);
1058
1059 auto access_sum = sycl::accessor { group_sum, h, sycl::read_write, sycl::property::no_init{}};
1060
1061 //sycl::accessor access_sum {sum, h};
1062 //auto sumReduction = sycl::reduction(access_sum, sycl::plus<T>());
1063
1064 //sycl::accessor<T, 1, sycl::access::mode::read_write,sycl::access::target::local>
1065 // scratch(sycl::range<1>(local), h);
1066 sycl::local_accessor<T> scratch{sycl::range<1>(local), h};
1067
1068 /* The parallel_for invocation chosen is the variant with an nd_item
1069 * parameter, since the code requires barriers for correctness. */
1070
1071 h.parallel_for<class sycl_map3_reduction_sum0_T>(range,
1072 //sumReduction,
1073 //[access_x,access_y,scratch,local,length,total_threads] (sycl::nd_item<1> id, auto& sum)
1074 [access_x,access_y,access_sum,scratch,local,length,total_threads] (sycl::nd_item<1> id)
1075 {
1076 std::size_t globalid = id.get_global_id(0);
1077 std::size_t localid = id.get_local_id(0);
1078
1079 scratch[localid] = (globalid < length)? access_x[globalid]*access_y[globalid] : 0. ;
1080
1081 for (auto i = globalid+total_threads; i < length; i += total_threads)
1082 scratch[localid] += access_x[i]*access_y[i];
1083
1084 id.barrier(sycl::access::fence_space::local_space);
1085
1086 /* Apply the reduction operation between the current local
1087 * id and the one on the other half of the vector. */
1088 {
1089 //int min = (length < local) ? length : local;
1090 std::size_t min = local ;
1091 for (std::size_t offset = min / 2; offset > 0; offset /= 2)
1092 {
1093 if (localid < offset)
1094 {
1095 scratch[localid] += scratch[localid + offset];
1096 }
1097 id.barrier(sycl::access::fence_space::local_space);
1098 }
1099 }
1100 /* The final result will be stored in local id 0. */
1101 if (localid == 0)
1102 {
1103 access_sum[id.get_group(0)] = scratch[localid];
1104 //sum += scratch[localid] ;
1105 }
1106 });
1107
1108 };
1109 // clang-format on
1110 m_env->internal()->queue().submit(f0);
1111
1112 } //while (length > 1);
1113 }
1114
1115 //return sum.get_host_access()[0];
1116 auto h_sum = group_sum.get_host_access();
1117 T sum = 0;
1118 for (std::size_t i = 0; i < std::min(total_threads, round_length) / local; ++i)
1119 sum += h_sum[i];
1120 return sum;
1121 }
1122
1123 template <typename T>
1125
1126 template <typename T>
1127 void asynch_map4_reduce_sum(sycl::buffer<T>& x,
1128 sycl::buffer<T>& y,
1129 sycl::buffer<T>& res,
1130 sycl::event& event)
1131 {
1132 std::size_t local = m_max_work_group_size;
1133 std::size_t total_threads = m_total_threads;
1134 std::size_t length = x.size();
1135
1136 //T sum_init = 0 ;
1137 //sycl::buffer<T> sum{&sum_init,1};
1138 {
1139 /* Each iteration of the do loop applies one level of reduction until
1140 * the input is of length 1 (i.e. the reduction is complete). */
1141 //do
1142 {
1143 auto round_length = round_up(length, local);
1144 // clang-format off
1145 auto f0 = [total_threads, round_length, length, local, &x, &y,&res](sycl::handler& h) mutable
1146 {
1147 sycl::nd_range<1> range{sycl::range<1>{std::min(total_threads,round_length)},
1148 sycl::range<1>{local}};
1149 auto access_x = x.template get_access<sycl::access::mode::read>(h);
1150 auto access_y = y.template get_access<sycl::access::mode::read>(h);
1151#ifdef USE_HIPSYCL
1152 sycl::accessor access_sum {res, h};
1153 auto sumReduction = sycl::reduction(access_sum, sycl::plus<T>());
1154#endif
1155#ifdef USE_ONEAPI
1156 auto sumReduction = sycl::reduction(res, h, sycl::plus<T>());
1157#endif
1158#ifdef USE_ACPPSYCL
1159 auto sumReduction = sycl::reduction(res, h, sycl::plus<T>());
1160#endif
1161 //auto access_sum = sycl::accessor<T,0,access::mode::write,access::target::global_buffer>(res, h);
1162 //auto access_sum = sycl::accessor { res, h, sycl::read_write, sycl::property::no_init{}};
1163
1164 //auto sumReduction = sycl::reduction(access_sum,sycl::plus<T>(), sycl::property::reduction::initialize_to_identity);
1165 //auto sumReduction = sycl::reduction(access_sum,sycl::plus<T>());
1166
1167 //sycl::accessor<T, 1, sycl::access::mode::read_write,sycl::access::target::local>
1168 // scratch(sycl::range<1>(local), h);
1169 sycl::local_accessor<T> scratch{sycl::range<1>(local), h};
1170
1171 /* The parallel_for invocation chosen is the variant with an nd_item
1172 * parameter, since the code requires barriers for correctness. */
1173
1174 h.parallel_for<class map4_reduction_sum_T>(range,
1175 sumReduction,
1176 [access_x,access_y,scratch,local,length,total_threads] (sycl::nd_item<1> id, auto& sum)
1177 {
1178 std::size_t globalid = id.get_global_id(0);
1179 std::size_t localid = id.get_local_id(0);
1180
1181 scratch[localid] = (globalid < length)? access_x[globalid]*access_y[globalid] : 0. ;
1182
1183 for (auto i = globalid+total_threads; i < length; i += total_threads)
1184 scratch[localid] += access_x[i]*access_y[i];
1185
1186 id.barrier(sycl::access::fence_space::local_space);
1187
1188 /* Apply the reduction operation between the current local
1189 * id and the one on the other half of the vector. */
1190 //if (globalid < length)
1191 {
1192 //int min = (length < local) ? length : local;
1193 std::size_t min = local ;
1194 for (std::size_t offset = min / 2; offset > 0; offset /= 2)
1195 //for (std::size_t offset = id.get_local_range(0) / 2; offset > 0; offset /= 2)
1196 {
1197 if (localid < offset)
1198 {
1199 scratch[localid] += scratch[localid + offset];
1200 }
1201 id.barrier(sycl::access::fence_space::local_space);
1202 }
1203 }
1204 /* The final result will be stored in local id 0. */
1205 if (localid == 0)
1206 {
1207 //access_sum[id.get_group(0)] = scratch[localid];
1208 sum += scratch[localid] ;
1209 }
1210 });
1211
1212 };
1213 // clang-format on
1214 event = m_env->internal()->queue().submit(f0);
1215 }
1216 }
1217 }
1218
1219 template <typename T>
1220 T end_map4_reduce_sum(sycl::event& event,
1221 sycl::buffer<T>& res,
1222 [[maybe_unused]] std::size_t num_blocks)
1223 {
1224 event.wait() ;
1225 auto h_access = res.get_host_access();
1226 return h_access[0];
1227 }
1228
1229 template <typename T>
1231
1232 template <typename T>
1234
1235 template <typename T>
1236 void asynch_map5_reduce_sum(sycl::buffer<T>& x,
1237 sycl::buffer<T>& y,
1238 sycl::buffer<T>& res,
1239 sycl::event& event)
1240 {
1241 std::size_t local = m_max_work_group_size;
1242 std::size_t total_threads = m_total_threads;
1243 std::size_t length = x.size();
1244
1245 int level = 0;
1246 {
1247 /* Each iteration of the do loop applies one level of reduction until
1248 * the input is of length 1 (i.e. the reduction is complete). */
1249 do {
1250 // clang-format off
1251 auto f0 = [total_threads, length, local, &x, &y,&res](sycl::handler& h) mutable
1252 {
1253 sycl::nd_range<1> range{sycl::range<1>{total_threads},
1254 sycl::range<1>{local}};
1255 auto access_x = x.template get_access<sycl::access::mode::read>(h);
1256 auto access_y = y.template get_access<sycl::access::mode::read>(h);
1257 auto access_sum = sycl::accessor { res, h, sycl::read_write, sycl::property::no_init{}};
1258
1259 //sycl::accessor<T, 1, sycl::access::mode::read_write,sycl::access::target::local>
1260 // scratch(sycl::range<1>(local), h);
1261 sycl::local_accessor<T> scratch{sycl::range<1>(local), h};
1262
1263 /* The parallel_for invocation chosen is the variant with an nd_item
1264 * parameter, since the code requires barriers for correctness. */
1265
1266 h.parallel_for<class map5_reduction_sum_T>(range,
1267 [access_x,access_y,access_sum,scratch,local,length,total_threads] (sycl::nd_item<1> id)
1268 {
1269 std::size_t globalid = id.get_global_id(0);
1270 std::size_t localid = id.get_local_id(0);
1271
1272 scratch[localid] = (globalid < length)? access_x[globalid]*access_y[globalid] : 0. ;
1273
1274 for (auto i = globalid+total_threads; i < length; i += total_threads)
1275 scratch[localid] += access_x[i]*access_y[i];
1276
1277 id.barrier(sycl::access::fence_space::local_space);
1278
1279 /* Apply the reduction operation between the current local
1280 * id and the one on the other half of the vector. */
1281 if (globalid < length)
1282 {
1283 //int min = (length < local) ? length : local;
1284 for (std::size_t offset = local / 2; offset > 0; offset /= 2)
1285 //for (std::size_t offset = id.get_local_range(0) / 2; offset > 0; offset /= 2)
1286 {
1287 if (localid < offset)
1288 {
1289 scratch[localid] += scratch[localid + offset];
1290 }
1291 id.barrier(sycl::access::fence_space::local_space);
1292 }
1293 }
1294 /* The final result will be stored in local id 0. */
1295 if (localid == 0)
1296 {
1297 access_sum[id.get_group(0)] = scratch[localid];
1298 }
1299
1300 });
1301
1302 };
1303 // clang-format on
1304 auto f1 = [length, local, &res](sycl::handler& h) mutable {
1305 sycl::nd_range<1> range{ sycl::range<1>{ local },
1306 sycl::range<1>{ local } };
1307 auto access_sum = res.template get_access<sycl::access::mode::read_write>(h);
1308
1309 //sycl::accessor<T, 1, sycl::access::mode::read_write, sycl::access::target::local>
1310 //scratch(sycl::range<1>(local), h);
1311 sycl::local_accessor<T> scratch{sycl::range<1>(local), h};
1312
1313 /* The parallel_for invocation chosen is the variant with an nd_item
1314 * parameter, since the code requires barriers for correctness. */
1315 h.parallel_for<class map5_reduction_sum1_T>(range,
1316 [access_sum, scratch, local, length](sycl::nd_item<1> id) {
1317 //auto localid = id.get_id(0);
1318 std::size_t globalid = id.get_global_id(0);
1319 std::size_t localid = id.get_local_id(0);
1320
1321 /* All threads collectively read from global memory into local.
1322 * The barrier ensures all threads' IO is resolved before
1323 * execution continues (strictly speaking, all threads within
1324 * a single work-group - there is no co-ordination between
1325 * work-groups, only work-items). */
1326 scratch[localid] = (localid < length) ? access_sum[localid] : 0.;
1327 id.barrier(sycl::access::fence_space::local_space);
1328
1329 /* Apply the reduction operation between the current local
1330 * id and the one on the other half of the vector. */
1331 if (localid < length) {
1332 //int min = (length < local) ? length : local;
1333 std::size_t min = local;
1334 for (std::size_t offset = min / 2; offset > 0; offset /= 2)
1335 //for (std::size_t offset = id.get_local_range(0) / 2; offset > 0; offset /= 2)
1336 {
1337 if (localid < offset) {
1338 scratch[localid] += scratch[localid + offset];
1339 }
1340 id.barrier(sycl::access::fence_space::local_space);
1341 }
1342 /* The final result will be stored in local id 0. */
1343 if (localid == 0) {
1344 access_sum[0] = scratch[localid];
1345 }
1346 }
1347 });
1348 };
1349 // clang-format on
1350 if (level == 0)
1351 event = m_env->internal()->queue().submit(f0);
1352 else
1353 event = m_env->internal()->queue().submit(f1);
1354
1355 /* At this point, you could queue::wait_and_throw() to ensure that
1356 * errors are caught quickly. However, this would likely impact
1357 * performance negatively. */
1358 //length = (length + local - 1) / local;
1359 length = (std::min(total_threads, length) + local - 1) / local;
1360 ++level;
1361 } while (length > 1);
1362 }
1363 //auto h_x = res.template get_access<sycl::access::mode::read>();
1364 //T sum = h_x[0] ;
1365 //T sum = 0;
1366 //for (int i = 0; i < length; ++i)
1367 // sum += h_x[i];
1368 //return sum;
1369 }
1370
1371
1372 template <typename T>
1373 T end_map5_reduce_sum(sycl::event& event,
1374 sycl::buffer<T>& res,
1375 [[maybe_unused]] std::size_t num_blocks)
1376 {
1377 event.wait() ;
1378 auto h_access = res.get_host_access();
1379 return h_access[0];
1380 }
1381
1382 template <typename T>
1383 T reduce_sum2(const std::vector<T>& x)
1384 {
1385 T value = 0;
1386 //for( auto v : x)
1387 // value += v ;
1388 //return value ;
1389
1390 {
1391 auto num_groups = m_env->internal()->queue().get_device().get_info<sycl::info::device::max_compute_units>();
1392 // getting the maximum work group size per thread
1393 auto work_group_size = m_env->internal()->queue().get_device().get_info<sycl::info::device::max_work_group_size>();
1394 // building the best number of global thread
1395 auto total_threads = num_groups * work_group_size;
1396
1397 //std::cout << "LOCAL =" << work_group_size << std::endl;
1398 //std::cout << "NB GROUPS =" << num_groups << std::endl;
1399 //std::cout << "NB THREADS=" << total_threads << std::endl;
1400
1401 /* The buffer is used to initialise the data on the device, but we don't
1402 * want to copy back and trash it. buffer::set_final_data() tells the
1403 * SYCL runtime where to put the data when the buffer is destroyed; nullptr
1404 * indicates not to copy back. The vector's length is used as the global
1405 * work size (unless that is too large). */
1406 auto device = m_env->internal()->queue().get_device();
1407 //std::size_t local = std::min(x.size(),device.get_info<sycl::info::device::max_work_group_size>());
1408 std::size_t local = device.get_info<sycl::info::device::max_work_group_size>();
1409
1410 std::size_t length = x.size();
1411
1412 sycl::buffer<T, 1> xbuf(x.data(), sycl::range<1>(x.size()));
1413 xbuf.set_final_data(nullptr);
1414
1415 int level = 0;
1416 {
1417 /* Each iteration of the do loop applies one level of reduction until
1418 * the input is of length 1 (i.e. the reduction is complete). */
1419 do {
1420
1421 auto round_length = round_up(length, local);
1422 std::cout << "LENGTH :" << level << " " << length << " " << round_length << " " << local << std::endl;
1423 // clang-format off
1424 auto f = [length,round_length,local, &xbuf](sycl::handler& h) mutable
1425 {
1426 //sycl::nd_range<1> r{sycl::range<1>{std::max(length, local)},
1427 // sycl::range<1>{std::min(length, local)}};
1428 sycl::nd_range<1> r{sycl::range<1>{round_length},
1429 sycl::range<1>{local}};
1430 /* Two accessors are used: one to the buffer that is being reduced,
1431 * and a second to local memory, used to store intermediate data. */
1432 auto x_access = xbuf.template get_access<sycl::access::mode::read_write>(h);
1433
1434 //sycl::accessor<T, 1, sycl::access::mode::read_write,sycl::access::target::local>
1435 // scratch(sycl::range<1>(local), h);
1436 sycl::local_accessor<T> scratch{sycl::range<1>(local), h};
1437
1438 /* The parallel_for invocation chosen is the variant with an nd_item
1439 * parameter, since the code requires barriers for correctness. */
1440 h.parallel_for<class reduce_sum2>(r, [x_access, scratch, local, length](sycl::nd_item<1> id)
1441 {
1442 std::size_t globalid = id.get_global_id(0);
1443 std::size_t localid = id.get_local_id(0);
1444
1445 /* All threads collectively read from global memory into local.
1446 * The barrier ensures all threads' IO is resolved before
1447 * execution continues (strictly speaking, all threads within
1448 * a single work-group - there is no co-ordination between
1449 * work-groups, only work-items). */
1450 scratch[localid] = (globalid < length)? x_access[globalid] : 0. ;
1451 id.barrier(sycl::access::fence_space::local_space);
1452
1453 /* Apply the reduction operation between the current local
1454 * id and the one on the other half of the vector. */
1455 //#ifdef DEBUG
1456 if (globalid < length)
1457 {
1458 //int min = (length < local) ? length : local;
1459 int min = local ;
1460 for (std::size_t offset = min / 2; offset > 0; offset /= 2)
1461 //for (std::size_t offset = id.get_local_range(0) / 2; offset > 0; offset /= 2)
1462 {
1463 if (localid < offset)
1464 {
1465 scratch[localid] += scratch[localid + offset];
1466 }
1467 id.barrier(sycl::access::fence_space::local_space);
1468 }
1469 /* The final result will be stored in local id 0. */
1470 if (localid == 0)
1471 {
1472 x_access[id.get_group(0)] = scratch[localid];
1473 }
1474 }
1475 //#endif
1476 });
1477 };
1478 // clang-format on
1479 m_env->internal()->queue().submit(f);
1480 /* At this point, you could queue::wait_and_throw() to ensure that
1481 * errors are caught quickly. However, this would likely impact
1482 * performance negatively. */
1483 length = (length + local - 1) / local;
1484 std::cout << "AFTER LENGTH :" << level << " new length" << length << " " << local << std::endl;
1485 ++level;
1486 } while (length > 1);
1487 }
1488 /* It is always sensible to wrap host accessors in their own scope as
1489 * kernels using the buffers they access are blocked for the length
1490 * of the accessor's lifetime. */
1491 auto hI = xbuf.get_host_acces();
1492 value = hI[0];
1493 }
1494 //value = x[0] ;
1495 return value;
1496 }
1497
1498 template <typename T>
1499 T sycl_reduce_sum(sycl::buffer<T>& x,
1500 sycl::buffer<T>& y)
1501 {
1502 T sum_init = 0;
1503 sycl::buffer<T> sum_buff{ &sum_init, 1 };
1504
1505 // clang-format off
1506 m_env->internal()->queue().submit([&](sycl::handler &cgh)
1507 {
1508 auto access_x = x.template get_access<sycl::access::mode::read>(cgh);
1509 auto access_y = y.template get_access<sycl::access::mode::read>(cgh);
1510#ifdef USE_HIPSYCL
1511 sycl::accessor sum_acc {sum_buff, cgh};
1512 auto sumReduction = sycl::reduction(sum_acc, sycl::plus<T>());
1513#endif
1514#ifdef USE_ONEAPI
1515 auto sumReduction = sycl::reduction(sum_buff, cgh, sycl::plus<T>());
1516#endif
1517#ifdef USE_ACPPSYCL
1518 auto sumReduction = sycl::reduction(sum_buff, cgh, sycl::plus<T>());
1519#endif
1520 cgh.parallel_for(sycl::range<1>{x.size()},
1521 sumReduction,
1522 [=](sycl::id<1> idx, auto &sum)
1523 {
1524 sum += access_x[idx]*access_y[idx];
1525 });
1526 });
1527 // clang-format on
1528
1529 return sum_buff.get_host_access()[0];
1530 }
1531
1532 /*
1533 namespace mi300 {
1534
1535 // Constantes tuned pour MI300 (gfx942)
1536 // - CU count : 228 CUs
1537 // - Wavefront : 64 threads (RDNA/CDNA)
1538 // - LDS/CU : 64 KB → 1024 threads × 8 B (double) = 8 KB/workgroup, safe
1539 inline constexpr std::size_t WG_SIZE = 1024; // 16 wavefronts/WG
1540 inline constexpr std::size_t ITEMS_PER_WI = 8; // unroll : chaque WI traite 8 éléments
1541
1542 // ---------------------------------------------------------------------------
1543 // dot_product_device
1544 // Calcule <x, y> = sum_i x[i]*y[i] sur GPU MI300.
1545 // x_buf, y_buf : buffers SYCL en lecture (taille >= n)
1546 // Retourne le scalaire double via un buffer résultat temporaire.
1547 // ---------------------------------------------------------------------------
1548 inline double dot_product(
1549 sycl::queue& q,
1550 sycl::buffer<double, 1>& x_buf,
1551 sycl::buffer<double, 1>& y_buf,
1552 std::size_t n)
1553 {
1554 if (n == 0) return 0.0;
1555
1556 // Nombre de work-groups : on arrondit au multiple de WG_SIZE*ITEMS_PER_WI
1557 const std::size_t stride = WG_SIZE * ITEMS_PER_WI;
1558 const std::size_t n_wg = (n + stride - 1) / stride;
1559 const std::size_t n_padded = n_wg * stride; // domaine de dispatch
1560
1561 // Buffer de réductions partielles (une valeur par WG)
1562 sycl::buffer<double, 1> partial_buf(n_wg);
1563
1564 // -------------------------------------------------------------------
1565 // Kernel 1 — réduction locale par work-group
1566 // Chaque WI charge ITEMS_PER_WI paires (x[i], y[i]) et accumule.
1567 // La réduction finale dans le WG utilise la LDS (sycl::local_accessor).
1568 // -------------------------------------------------------------------
1569 q.submit([&](sycl::handler& cgh) {
1570 auto x = x_buf.template get_access<sycl::access::mode::read>(cgh);
1571 auto y = y_buf.template get_access<sycl::access::mode::read>(cgh);
1572 auto out = partial_buf.template get_access<sycl::access::mode::discard_write>(cgh);
1573
1574 // LDS : WG_SIZE doubles par work-group
1575 sycl::local_accessor<double, 1> local_sum(WG_SIZE, cgh);
1576
1577 sycl::nd_range<1> nd_range{n_padded, WG_SIZE};
1578
1579 cgh.parallel_for(nd_range, [=](sycl::nd_item<1> item) {
1580 const std::size_t gid = item.get_global_id(0);
1581 const std::size_t lid = item.get_local_id(0);
1582 const std::size_t wg_id = item.get_group(0);
1583 const std::size_t base = wg_id * stride;
1584
1585 // Accumulation privée — ITEMS_PER_WI éléments par WI
1586 double acc = 0.0;
1587 #pragma unroll
1588 for (std::size_t k = 0; k < ITEMS_PER_WI; ++k) {
1589 const std::size_t idx = base + lid + k * WG_SIZE;
1590 if (idx < n) {
1591 acc += x[idx] * y[idx];
1592 }
1593 }
1594
1595 // Dépôt en LDS
1596 local_sum[lid] = acc;
1597 sycl::group_barrier(item.get_group());
1598
1599 // Réduction en arbre dans la LDS (log2(WG_SIZE) = 10 passes)
1600 for (std::size_t offset = WG_SIZE / 2; offset > 0; offset >>= 1) {
1601 if (lid < offset) {
1602 local_sum[lid] += local_sum[lid + offset];
1603 }
1604 sycl::group_barrier(item.get_group());
1605 }
1606
1607 // WI 0 écrit la somme partielle du WG
1608 if (lid == 0) {
1609 out[wg_id] = local_sum[0];
1610 }
1611 });
1612 });
1613
1614 // -------------------------------------------------------------------
1615 // Kernel 2 — réduction finale des n_wg partielles
1616 // Si n_wg <= WG_SIZE on fait tout en un seul WG.
1617 // Sinon on rappelle dot_product récursivement (rare pour n < 2^30).
1618 // -------------------------------------------------------------------
1619 double result = 0.0;
1620
1621 if (n_wg <= WG_SIZE) {
1622 sycl::buffer<double, 1> result_buf(&result, 1);
1623
1624 q.submit([&](sycl::handler& cgh) {
1625 auto src = partial_buf.template get_access<sycl::access::mode::read>(cgh);
1626 auto dst = result_buf.template get_access<sycl::access::mode::discard_write>(cgh);
1627 sycl::local_accessor<double, 1> lmem(WG_SIZE, cgh);
1628
1629 // On dispatch exactement WG_SIZE threads, padding avec 0
1630 cgh.parallel_for(sycl::nd_range<1>{WG_SIZE, WG_SIZE},
1631 [=](sycl::nd_item<1> item) {
1632 const std::size_t lid = item.get_local_id(0);
1633 lmem[lid] = (lid < n_wg) ? src[lid] : 0.0;
1634 sycl::group_barrier(item.get_group());
1635
1636 for (std::size_t offset = WG_SIZE / 2; offset > 0; offset >>= 1) {
1637 if (lid < offset)
1638 lmem[lid] += lmem[lid + offset];
1639 sycl::group_barrier(item.get_group());
1640 }
1641 if (lid == 0) dst[0] = lmem[0];
1642 });
1643 });
1644 // L'accès au buffer result_buf en sortie de scope force la synchronisation
1645 } else {
1646 // Chemin récursif (n > WG_SIZE² ~ 1M WGs, soit n > ~8G éléments)
1647 // Rare en pratique — on réduit les partielles via un second appel
1648 result = dot_product(q, partial_buf, partial_buf, n_wg);
1649 }
1650
1651 return result;
1652 }
1653
1654 // ---------------------------------------------------------------------------
1655 // Variante asynchrone : retourne un sycl::event + résultat via buffer externe
1656 // Utile pour masquer la latence dans une boucle itérative (ex: CG)
1657 // ---------------------------------------------------------------------------
1658 inline sycl::event dot_product_async(
1659 sycl::queue& q,
1660 sycl::buffer<double, 1>& x_buf,
1661 sycl::buffer<double, 1>& y_buf,
1662 sycl::buffer<double, 1>& result_buf, // taille >= 1
1663 std::size_t n)
1664 {
1665 if (n == 0) {
1666 return q.submit([&](sycl::handler& cgh) {
1667 auto r = result_buf.template get_access<sycl::access::mode::discard_write>(cgh);
1668 cgh.single_task([=]{ r[0] = 0.0; });
1669 });
1670 }
1671
1672 const std::size_t stride = WG_SIZE * ITEMS_PER_WI;
1673 const std::size_t n_wg = (n + stride - 1) / stride;
1674 const std::size_t n_padded = n_wg * stride;
1675
1676 // Shared ptr pour que le buffer partiel survive au scope du lambda
1677 auto partial = std::make_shared<sycl::buffer<double,1>>(n_wg);
1678
1679 q.submit([&, partial](sycl::handler& cgh) {
1680 auto x = x_buf.template get_access<sycl::access::mode::read>(cgh);
1681 auto y = y_buf.template get_access<sycl::access::mode::read>(cgh);
1682 auto out = partial->template get_access<sycl::access::mode::discard_write>(cgh);
1683 sycl::local_accessor<double, 1> local_sum(WG_SIZE, cgh);
1684
1685 cgh.parallel_for(sycl::nd_range<1>{n_padded, WG_SIZE},
1686 [=](sycl::nd_item<1> item) {
1687 const std::size_t lid = item.get_local_id(0);
1688 const std::size_t base = item.get_group(0) * stride;
1689 double acc = 0.0;
1690 #pragma unroll
1691 for (std::size_t k = 0; k < ITEMS_PER_WI; ++k) {
1692 const std::size_t idx = base + lid + k * WG_SIZE;
1693 if (idx < n) acc += x[idx] * y[idx];
1694 }
1695 local_sum[lid] = acc;
1696 sycl::group_barrier(item.get_group());
1697 for (std::size_t offset = WG_SIZE / 2; offset > 0; offset >>= 1) {
1698 if (lid < offset) local_sum[lid] += local_sum[lid + offset];
1699 sycl::group_barrier(item.get_group());
1700 }
1701 if (lid == 0) out[item.get_group(0)] = local_sum[0];
1702 });
1703 });
1704
1705 return q.submit([partial](sycl::handler& cgh) {
1706 auto src = partial->template get_access<sycl::access::mode::read>(cgh);
1707 // reduction via sycl::reduction (SYCL 2020)
1708 // Note : result_buf doit être capturé par référence externe
1709 // → on utilise un single_task pour la somme finale des partielles (n_wg petit)
1710 cgh.single_task([=](){
1711 // accessible depuis le lambda — n_wg connu via closure
1712 });
1713 });
1714 // NOTE : pour la variante async complète, préférez sycl::reduction (voir ci-dessous)
1715 }
1716 } */
1717
1718
1719 template <typename T>
1720 inline T dot_product_h100(sycl::buffer<T>& buf_x,
1721 sycl::buffer<T>& buf_y)
1722 {
1723 using namespace sycl;
1724 auto& q = m_env->internal()->queue();
1725 const size_t N = buf_x.size();
1726 assert(buf_y.size() == N);
1727
1728 auto dev = q.get_device();
1729 //const size_t num_sm = dev.get_info<info::device::max_compute_units>(); // 132
1730
1731 // Grid : TARGET_WAVES * num_sm blocs, chacun de WG_SIZE threads,
1732 // chaque thread traite ITEMS_PER_WI éléments → couverture totale
1733 const size_t blocks_needed = (N + WG_SIZE * ITEMS_PER_WI - 1)
1734 / (WG_SIZE * ITEMS_PER_WI);
1735 const size_t num_blocks = std::max(blocks_needed,
1736 m_max_num_groups * TARGET_WAVES);
1737 const size_t total_threads = num_blocks * WG_SIZE;
1738
1739 // Allocation USM pour les partiels (un T par bloc)
1740 //T* partials = malloc_device<T>(num_blocks, q);
1741 sycl::buffer<T> partials{num_blocks};
1742
1743 // ── Phase 1 : kernel de réduction par blocs ───────────────────────────
1744 q.submit(
1745 [&](handler& cgh)
1746 {
1747 auto ax = buf_x.template get_access<access::mode::read>(cgh);
1748 auto ay = buf_y.template get_access<access::mode::read>(cgh);
1749 auto ap = partials.template get_access<access::mode::read_write>(cgh);
1750 // LDS : 1 T par warp dans le bloc
1751 local_accessor<T,1> lds{WG_SIZE / WARP_SIZE, cgh};
1752
1753 //const T* px = ax.get_pointer(); // accès USM-like depuis accessor
1754 //const T* py = ay.get_pointer();
1755 //T* pp = partials;
1756 const size_t n = N;
1757
1758 cgh.parallel_for<class dot_h100_phase1>(
1759 nd_range<1>{{total_threads}, {WG_SIZE}},
1760 [=](nd_item<1> item)
1761 [[intel::reqd_sub_group_size(WARP_SIZE)]]
1762 {
1763 h100::dot_kernel_h100(item, ax, ay, ap, n, lds);
1764 });
1765 });
1766
1767 // ── Phase 2 : réduction finale des partiels sur le CPU ────────────────
1768 // Pour N = 100M Ts → num_blocks ≤ 2048 → 16 KB de partiels.
1769 // Un memcpy D2H de 16 KB est négligeable vs le kernel principal.
1770 // Alternative : second kernel de réduction si num_blocks > 10 000.
1771 //std::vector<T> h_partials(num_blocks);
1772 //q.memcpy(h_partials.data(), partials, num_blocks * sizeof(T)).wait();
1773 //free(partials, q);
1774 auto h_partials = partials.get_host_access();
1775
1776 // Kahan summation pour précision numérique sur la réduction finale
1777 T sum = 0.0, c = 0.0;
1778 for (size_t b = 0; b < num_blocks; ++b) {
1779 T z = h_partials[b] - c;
1780 T t = sum + z;
1781 c = (t - sum) - z;
1782 sum = t;
1783 }
1784 return sum;
1785 }
1786
1787 template <typename T>
1788 inline std::size_t asynch_dot_product_h100(sycl::buffer<T>& buf_x,
1789 sycl::buffer<T>& buf_y,
1790 sycl::buffer<T>& res,
1791 sycl::event& event)
1792 {
1793 using namespace sycl;
1794 auto& q = m_env->internal()->queue();
1795 const size_t N = buf_x.size();
1796 assert(buf_y.size() == N);
1797
1798 //auto dev = q.get_device();
1799 //const size_t num_sm = dev.get_info<info::device::max_compute_units>(); // 132
1800
1801 // Grid : TARGET_WAVES * num_sm blocs, chacun de WG_SIZE threads,
1802 // chaque thread traite ITEMS_PER_WI éléments → couverture totale
1803 const size_t blocks_needed = (N + WG_SIZE * ITEMS_PER_WI - 1)
1804 / (WG_SIZE * ITEMS_PER_WI);
1805 const size_t num_blocks = std::max(blocks_needed,m_max_num_groups * TARGET_WAVES);
1806 const size_t total_threads = num_blocks * WG_SIZE;
1807
1808 // Allocation USM pour les partiels (un T par bloc)
1809 //partials = malloc_device<T>(num_blocks, q);
1810
1811 // ── Phase 1 : kernel de réduction par blocs ───────────────────────────
1812 event = q.submit(
1813 [&](handler& cgh)
1814 {
1815 auto ax = buf_x.template get_access<access::mode::read>(cgh);
1816 auto ay = buf_y.template get_access<access::mode::read>(cgh);
1817 auto ap = res.template get_access<sycl::access::mode::read_write>(cgh);
1818 // LDS : 1 T par warp dans le bloc
1819 local_accessor<T,1> lds{WG_SIZE / WARP_SIZE, cgh};
1820
1821 //const T* px = ax.get_pointer(); // accès USM-like depuis accessor
1822 //const T* py = ay.get_pointer();
1823 //T* pp = ap.get_pointer();
1824 const size_t n = N;
1825
1826 cgh.parallel_for<class dot_h100_phase1>(
1827 nd_range<1>{{total_threads}, {WG_SIZE}},
1828 [=](nd_item<1> item)
1829 [[intel::reqd_sub_group_size(WARP_SIZE)]]
1830 {
1831 h100::dot_kernel_h100(item, ax, ay, ap, n, lds);
1832 });
1833 });
1834 return num_blocks;
1835 }
1836
1837
1838 template <typename T>
1839 inline T end_dot_product_h100(sycl::event& event,
1840 sycl::buffer<T>& res,
1841 std::size_t num_blocks)
1842 {
1843 event.wait() ;
1844 // ── Phase 2 : réduction finale des partiels sur le CPU ────────────────
1845 // Pour N = 100M Ts → num_blocks ≤ 2048 → 16 KB de partiels.
1846 // Un memcpy D2H de 16 KB est négligeable vs le kernel principal.
1847 // Alternative : second kernel de réduction si num_blocks > 10 000.
1848 //std::vector<T> h_partials(num_blocks);
1849 //m_env->internal()->queue().memcpy(h_partials.data(), partials, num_blocks * sizeof(T)).wait();
1850 //free(partials, q);
1851 auto h_partials = res.get_host_access();
1852
1853 // Kahan summation pour précision numérique sur la réduction finale
1854 T sum = 0.0, c = 0.0;
1855 for (size_t b = 0; b < num_blocks; ++b) {
1856 T z = h_partials[b] - c;
1857 T t = sum + z;
1858 c = (t - sum) - z;
1859 sum = t;
1860 }
1861 return sum;
1862 }
1863
1864 // ---------------------------------------------------------------------------
1865 // Variante moderne SYCL 2020 : sycl::reduction (plus concis, potentiellement
1866 // optimisé par le runtime AdaptiveCPP selon la cible)
1867 // ---------------------------------------------------------------------------
1868
1869 template<typename T>
1870 inline T dot_product_mi300(sycl::buffer<T, 1>& x_buf,
1871 sycl::buffer<T, 1>& y_buf)
1872 {
1873 using namespace mi300 ;
1874 std::cout<<" DOT PROD MI300 : "<<WG_SIZE<<" "<<ITEMS_PER_WI<<std::endl ;
1875
1876 bool use_doublebuf = false;
1877 auto& q = m_env->internal()->queue();
1878 /*
1879
1880 std::size_t n = x_buf.size() ;
1881 sycl::buffer<T, 1> result_buf(&result, 1);
1882
1883 const std::size_t stride = WG_SIZE * ITEMS_PER_WI;
1884 const std::size_t n_padded = ((n + stride - 1) / stride) * stride;
1885
1886 q.submit([&x_buf, &y_buf, &result_buf, n, n_padded, stride](sycl::handler& cgh) {
1887 auto x = x_buf.template get_access<sycl::access::mode::read>(cgh);
1888 auto y = y_buf.template get_access<sycl::access::mode::read>(cgh);
1889 auto r = result_buf.template get_access<sycl::access::mode::read_write>(cgh);
1890 //sycl::accessor r {result_buf, cgh};
1891 auto red = sycl::reduction(r, sycl::plus<T>{});
1892 cgh.parallel_for(
1893 sycl::nd_range<1>{n_padded, WG_SIZE}, red,
1894 [=](sycl::nd_item<1> item, auto& sum) {
1895 const std::size_t lid = item.get_local_id(0);
1896 const std::size_t base = item.get_group(0) * stride;
1897 T acc = 0.0;
1898 #pragma unroll
1899 for (std::size_t k = 0; k < ITEMS_PER_WI; ++k) {
1900 const std::size_t idx = base + lid + k * WG_SIZE;
1901 if (idx < n) acc += x[idx] * y[idx];
1902 }
1903 sum.combine(acc);
1904 });
1905 });
1906 */
1907
1908 std::size_t N = x_buf.size() ;
1909 // Grid : couvrir N en ITEMS_PER_WI éléments/WI
1910 const size_t blocks_needed = (N + WG_SIZE * ITEMS_PER_WI - 1)
1911 / (WG_SIZE * ITEMS_PER_WI);
1912 // Minimum : num_cu × TARGET_WAVES × (WG_SIZE/WAVEFRONT) blocs
1913 // = 228 × 4 × 4 = 3 648 — cohérent avec les profils rocprof
1914 const size_t num_blocks = std::max(blocks_needed,
1915 m_max_num_groups * TARGET_WAVES
1916 * (WG_SIZE / WAVEFRONT));
1917 const size_t total_threads = num_blocks * WG_SIZE;
1918 //std::cout<<" N = "<<N<<" "<<" num_blocks="<<num_blocks<<" nthreads="<<total_threads<<std::endl ;
1919 //double* partials = malloc_device<double>(num_blocks, q);
1920 sycl::buffer<T, 1> partials(num_blocks);
1921
1922 q.submit([&](sycl::handler& cgh)
1923 {
1924 auto ax = x_buf.template get_access<sycl::access::mode::read>(cgh);
1925 auto ay = y_buf.template get_access<sycl::access::mode::read>(cgh);
1926 auto ap = partials.template get_access<sycl::access::mode::read_write>(cgh);
1927 sycl::local_accessor<double,1> lds{N_WARPS_BLOC, cgh}; // 4 doubles = 32 B
1928
1929 //const double* px = ax.get_pointer();
1930 //const double* py = ay.get_pointer();
1931 //double* pp = partials;
1932 const size_t n = N;
1933 const bool db = use_doublebuf;
1934
1935 cgh.parallel_for<class dot_mi300_phase1>(
1936 sycl::nd_range<1>{{total_threads}, {WG_SIZE}},
1937 // ★ sub_group_size(64) — force WF=64 sur gfx942
1938 // Sans cette annotation, ACPP peut émettre WF=32
1939 // et la réduction sub_group ne couvrirait que 32 lanes
1940 [=](sycl::nd_item<1> item)
1941 [[intel::reqd_sub_group_size(WAVEFRONT)]]
1942 {
1943 if (db)
1944 mi300::dot_kernel_mi300_doublebuf(item, ax, ay, ap, n, lds);
1945 else
1946 mi300::dot_kernel_mi300(item, ax, ay, ap, n, lds);
1947 });
1948 }).wait();
1949
1950 // Phase 2 : réduction CPU des partiels
1951 //std::vector<ValueT> h_partials(num_blocks);
1952 //q.memcpy(h_partials.data(), partials, num_blocks * sizeof(double)).wait();
1953 //free(partials, q);
1954 auto h_partials = partials.get_host_access();
1955
1956 // Kahan summation — précision sur la somme des ~3 648 partiels
1957 T sum = 0, c = 0;
1958 for (std::size_t b = 0; b < num_blocks; ++b)
1959 {
1960 T z = h_partials[b] - c;
1961 double t = sum + z;
1962 c = (t - sum) - z;
1963 sum = t;
1964 }
1965 return sum;
1966 }
1967
1968 template <typename T>
1969 inline void asynch_dot_product_mi300(sycl::buffer<T, 1>& x_buf,
1970 sycl::buffer<T, 1>& y_buf,
1971 sycl::buffer<T, 1>& result_buf,
1972 sycl::event& event)
1973 {
1974 using namespace mi300 ;
1975
1976 std::size_t n = x_buf.size() ;
1977 auto& q = m_env->internal()->queue();
1978
1979 const std::size_t stride = WG_SIZE * ITEMS_PER_WI;
1980 const std::size_t n_padded = ((n + stride - 1) / stride) * stride;
1981
1982 event = q.submit([&](sycl::handler& cgh) {
1983 auto x = x_buf.template get_access<sycl::access::mode::read>(cgh);
1984 auto y = y_buf.template get_access<sycl::access::mode::read>(cgh);
1985 auto red = sycl::reduction(result_buf, cgh, sycl::plus<T>{});
1986
1987 cgh.parallel_for(
1988 sycl::nd_range<1>{n_padded, WG_SIZE}, red,
1989 [=](sycl::nd_item<1> item, auto& sum) {
1990 const std::size_t lid = item.get_local_id(0);
1991 const std::size_t base = item.get_group(0) * stride;
1992 T acc = 0.0;
1993 #pragma unroll
1994 for (std::size_t k = 0; k < ITEMS_PER_WI; ++k) {
1995 const std::size_t idx = base + lid + k * WG_SIZE;
1996 if (idx < n) acc += x[idx] * y[idx];
1997 }
1998 sum.combine(acc);
1999 });
2000 });
2001 }
2002
2003
2004
2005 template <typename T>
2006 T end_dot_product_mi300(sycl::event& event,
2007 sycl::buffer<T>& res,
2008 [[maybe_unused]] std::size_t num_blocks)
2009 {
2010 event.wait() ;
2011 auto h_access = res.get_host_access();
2012 return h_access[0];
2013 }
2014
2015 template <typename T>
2016 T dot(sycl::buffer<T>& x,
2017 sycl::buffer<T>& y)
2018 {
2019 switch (m_dot_algo) {
2020 case 0:
2021 return reduce_sum(x, y);
2022 case 1:
2023 return map_reduce_sum(x, y);
2024 case 2:
2025 return map2_reduce_sum(x, y); // with sycl_reduction
2026 case 3:
2027 return map3_reduce_sum(x, y);
2028 case 4: //H100
2029 return dot_product_h100(x, y);
2030 case 5: //MI300
2031 return dot_product_mi300(x, y);
2032 default:
2033 return sycl_reduce_sum(x, y);
2034 }
2035 }
2036
2037 template <typename T>
2038 void dot(sycl::buffer<T>& x,
2039 sycl::buffer<T>& y,
2040 Future<Real>& res)
2041{
2042 switch (m_dot_algo)
2043 {
2044 case 2:
2045 asynch_map4_reduce_sum(x, y, res.deviceValue(), res.event());
2046 res.setWaitFunction([=](sycl::event& event, sycl::buffer<double>& res, std::size_t num_blocks)
2047 {
2048 return this->end_map4_reduce_sum(event,res, num_blocks) ;
2049 }) ;
2050 break;
2051 case 4:
2052 {
2053 std::size_t num_blocks = asynch_dot_product_h100(x, y, res.deviceValue(), res.event());
2054 res.setWaitFunction([=](sycl::event& event, sycl::buffer<double>& res, std::size_t num_blocks)
2055 {
2056 return this->end_dot_product_h100(event, res, num_blocks) ;
2057 }) ;
2058 res.setNumBlocks(num_blocks) ;
2059 }
2060 break;
2061 case 5:
2062 asynch_dot_product_mi300(x, y, res.deviceValue(), res.event());
2063 res.setWaitFunction([=](sycl::event& event, sycl::buffer<T>& res, std::size_t num_blocks)
2064 {
2065 return this->end_dot_product_mi300(event,res, num_blocks) ;
2066 }) ;
2067 break;
2068 default:
2069 asynch_map5_reduce_sum(x, y, res.deviceValue(), res.event());
2070 res.setWaitFunction([=](sycl::event& event, sycl::buffer<T>& res, std::size_t num_blocks)
2071 {
2072 return this->end_map5_reduce_sum(event,res, num_blocks) ;
2073 }) ;
2074 break;
2075 }
2076 }
2077
2078 private:
2079 // clang-format off
2080 SYCLEnv* m_env = nullptr ;
2081 std::size_t m_max_num_groups = 0 ;
2082 std::size_t m_max_work_group_size = 0 ;
2083 std::size_t m_total_threads = 0 ;
2084 // clang-format on
2085
2086 template <typename T>
2087 sycl::buffer<T>& getWorkBuffer(std::size_t size);
2088
2089 mutable sycl::buffer<double>* m_double_work = nullptr;
2090};
2091
2092
2093/*---------------------------------------------------------------------------*/
2094
2095} // namespace Alien::SYCLInternal
2096
2097/*---------------------------------------------------------------------------*/