Arcane  v4.1.0.0
Documentation développeur
Chargement...
Recherche...
Aucune correspondance
SyclAcceleratorRuntime.cc
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/* SyclAcceleratorRuntime.cc (C) 2000-2025 */
9/* */
10/* Runtime pour 'SYCL'. */
11/*---------------------------------------------------------------------------*/
12/*---------------------------------------------------------------------------*/
13
14#include "arcane/accelerator/sycl/SyclAccelerator.h"
15#include "arcane/accelerator/sycl/internal/SyclAcceleratorInternal.h"
16
17#include "arccore/base/PlatformUtils.h"
18#include "arccore/base/NotSupportedException.h"
19#include "arccore/base/FatalErrorException.h"
20#include "arccore/base/NotImplementedException.h"
21
22#include "arccore/common/IMemoryResourceMng.h"
23#include "arccore/common/internal/IMemoryResourceMngInternal.h"
24
25#include "arcane/utils/internal/MemoryUtilsInternal.h"
26
27#include "arcane/accelerator/core/RunQueueBuildInfo.h"
28#include "arcane/accelerator/core/Memory.h"
29#include "arcane/accelerator/core/DeviceInfoList.h"
30#include "arcane/accelerator/core/RunQueue.h"
31#include "arcane/accelerator/core/DeviceMemoryInfo.h"
32#include "arcane/accelerator/core/NativeStream.h"
33#include "arcane/accelerator/core/internal/IRunnerRuntime.h"
34#include "arcane/accelerator/core/internal/RegisterRuntimeInfo.h"
35#include "arcane/accelerator/core/internal/IRunQueueStream.h"
36#include "arcane/accelerator/core/internal/IRunQueueEventImpl.h"
37
38#include <iostream>
39
40namespace Arcane::Accelerator::Sycl
41{
42
43using namespace Arccore;
44
45#define ARCANE_SYCL_FUNC_NOT_HANDLED \
46 std::cout << "WARNING: SYCL: function not handled " << A_FUNCINFO << "\n"
47
49
50/*---------------------------------------------------------------------------*/
51/*---------------------------------------------------------------------------*/
52
53class SyclRunQueueStream
55{
56 public:
57
58 SyclRunQueueStream(SyclRunnerRuntime* runtime, const RunQueueBuildInfo& bi);
59 ~SyclRunQueueStream() override
60 {
61 }
62
63 public:
64
65 void notifyBeginLaunchKernel([[maybe_unused]] impl::RunCommandImpl& c) override
66 {
67 return m_runtime->notifyBeginLaunchKernel();
68 }
70 {
71 return m_runtime->notifyEndLaunchKernel();
72 }
73 void barrier() override
74 {
75 m_sycl_stream->wait_and_throw();
76 }
77 bool _barrierNoException() override
78 {
79 m_sycl_stream->wait();
80 return false;
81 }
82 void copyMemory(const MemoryCopyArgs& args) override
83 {
84 auto source_bytes = args.source().bytes();
85 m_sycl_stream->memcpy(args.destination().data(), source_bytes.data(),
86 source_bytes.size());
87 if (!args.isAsync())
88 this->barrier();
89 }
90 void prefetchMemory([[maybe_unused]] const MemoryPrefetchArgs& args) override
91 {
92 auto source_bytes = args.source().bytes();
93 Int64 nb_byte = source_bytes.size();
94 if (nb_byte == 0)
95 return;
96 m_sycl_stream->prefetch(source_bytes.data(), nb_byte);
97 if (!args.isAsync())
98 this->barrier();
99 }
101 {
102 return impl::NativeStream(m_sycl_stream.get());
103 }
104
105 void _setSyclLastCommandEvent([[maybe_unused]] void* sycl_event_ptr) override
106 {
107 sycl::event last_event;
108 if (sycl_event_ptr)
109 last_event = *(reinterpret_cast<sycl::event*>(sycl_event_ptr));
110 m_last_command_event = last_event;
111 }
112
113 public:
114
115 static sycl::async_handler _getAsyncHandler()
116 {
117 auto err_handler = [](const sycl::exception_list& exceptions) {
118 std::ostringstream ostr;
119 ostr << "Error in SYCL runtime\n";
120 for (const std::exception_ptr& e : exceptions) {
121 try {
122 std::rethrow_exception(e);
123 }
124 catch (const sycl::exception& e) {
125 ostr << "SYCL exception: " << e.what() << "\n";
126 }
127 }
128 ARCANE_FATAL(ostr.str());
129 };
130 return err_handler;
131 }
132
134 sycl::event lastCommandEvent() { return m_last_command_event; }
135
136 public:
137
138 sycl::queue& trueStream() const
139 {
140 return *m_sycl_stream;
141 }
142
143 private:
144
145 impl::IRunnerRuntime* m_runtime;
146 std::unique_ptr<sycl::queue> m_sycl_stream;
147 sycl::event m_last_command_event;
148};
149
150/*---------------------------------------------------------------------------*/
151/*---------------------------------------------------------------------------*/
152
153class SyclRunQueueEvent
155{
156 public:
157
158 explicit SyclRunQueueEvent([[maybe_unused]] bool has_timer)
159 {
160 }
161 ~SyclRunQueueEvent() override
162 {
163 }
164
165 public:
166
167 // Enregistre l'événement au sein d'une RunQueue
168 void recordQueue([[maybe_unused]] impl::IRunQueueStream* stream) final
169 {
170 ARCANE_CHECK_POINTER(stream);
171 auto* rq = static_cast<SyclRunQueueStream*>(stream);
172 m_sycl_event = rq->lastCommandEvent();
173#if defined(__ADAPTIVECPP__)
174 m_recorded_stream = stream;
175 // TODO: Vérifier s'il faut faire quelque chose
176#elif defined(__INTEL_LLVM_COMPILER)
177 //m_sycl_event = rq->trueStream().ext_oneapi_submit_barrier();
178#else
179 ARCANE_THROW(NotSupportedException, "Only supported for AdaptiveCpp and Intel DPC++ implementation");
180#endif
181 }
182
183 void wait() final
184 {
185 //ARCANE_SYCL_FUNC_NOT_HANDLED;
186 // TODO: Vérifier ce que cela signifie exactement
187 m_sycl_event.wait();
188 }
189
190 void waitForEvent([[maybe_unused]] impl::IRunQueueStream* stream) final
191 {
192#if defined(__ADAPTIVECPP__)
193 auto* rq = static_cast<SyclRunQueueStream*>(stream);
194 m_sycl_event.wait(rq->trueStream().get_wait_list());
195#elif defined(__INTEL_LLVM_COMPILER)
196 std::vector<sycl::event> events;
197 events.push_back(m_sycl_event);
198 auto* rq = static_cast<SyclRunQueueStream*>(stream);
199 rq->trueStream().ext_oneapi_submit_barrier(events);
200#else
201 ARCANE_THROW(NotSupportedException, "Only supported for AdaptiveCpp and Intel DPC++ implementation");
202#endif
203 }
204
205 Int64 elapsedTime([[maybe_unused]] IRunQueueEventImpl* start_event) final
206 {
207 ARCANE_CHECK_POINTER(start_event);
208 // Il faut prendre l'évènement de début car on est certain qu'il contient
209 // la bonne valeur de 'sycl::event'.
210 sycl::event event = (static_cast<SyclRunQueueEvent*>(start_event))->m_sycl_event;
211 // Si pas d'évènement associé, on ne fait rien pour éviter une exception
212 if (event==sycl::event())
213 return 0;
214
215 bool is_submitted = event.get_info<sycl::info::event::command_execution_status>() == sycl::info::event_command_status::complete;
216 if (!is_submitted)
217 return 0;
218 Int64 start = event.get_profiling_info<sycl::info::event_profiling::command_start>();
219 Int64 end = event.get_profiling_info<sycl::info::event_profiling::command_end>();
220 return (end - start);
221 }
222
223 bool hasPendingWork() final
224 {
225 ARCANE_THROW(NotImplementedException,"hasPendingWork()");
226 }
227
228 private:
229
230 sycl::event m_sycl_event;
231 impl::IRunQueueStream* m_recorded_stream = nullptr;
232};
233
234/*---------------------------------------------------------------------------*/
235/*---------------------------------------------------------------------------*/
236
239{
240 friend class SyclRunQueueStream;
241
242 public:
243
244 void notifyBeginLaunchKernel() override
245 {
246 }
247 void notifyEndLaunchKernel() override
248 {
249 }
250 void barrier() override
251 {
252 // TODO Faire le wait sur la file par défaut n'est pas strictement équivalent
253 // à la barrière en CUDA qui synchronize tout le device.
254 m_default_queue->wait();
255 }
256 eExecutionPolicy executionPolicy() const override
257 {
259 }
260 impl::IRunQueueStream* createStream(const RunQueueBuildInfo& bi) override
261 {
262 return new SyclRunQueueStream(this, bi);
263 }
264 impl::IRunQueueEventImpl* createEventImpl() override
265 {
266 return new SyclRunQueueEvent(false);
267 }
268 impl::IRunQueueEventImpl* createEventImplWithTimer() override
269 {
270 return new SyclRunQueueEvent(true);
271 }
272 void setMemoryAdvice([[maybe_unused]] ConstMemoryView buffer, [[maybe_unused]] eMemoryAdvice advice,
273 [[maybe_unused]] DeviceId device_id) override
274 {
275 }
276 void unsetMemoryAdvice([[maybe_unused]] ConstMemoryView buffer,
277 [[maybe_unused]] eMemoryAdvice advice, [[maybe_unused]] DeviceId device_id) override
278 {
279 }
280
281 void setCurrentDevice([[maybe_unused]] DeviceId device_id) final
282 {
283 ARCANE_SYCL_FUNC_NOT_HANDLED;
284 }
285 const IDeviceInfoList* deviceInfoList() override { return &m_device_info_list; }
286
287 void getPointerAttribute(PointerAttribute& attribute, const void* ptr) override
288 {
289 sycl::usm::alloc sycl_mem_type = sycl::get_pointer_type(ptr, *m_default_context);
290 ePointerMemoryType mem_type = ePointerMemoryType::Unregistered;
291 const void* host_ptr = nullptr;
292 const void* device_ptr = nullptr;
293 if (sycl_mem_type == sycl::usm::alloc::host) {
294 // HostPinned. Doit être accessible depuis le device mais
295 //
296 mem_type = ePointerMemoryType::Host;
297 host_ptr = ptr;
298 // TODO: Regarder comment récupérer la valeur
299 device_ptr = ptr;
300 }
301 else if (sycl_mem_type == sycl::usm::alloc::device) {
302 mem_type = ePointerMemoryType::Device;
303 device_ptr = ptr;
304 }
305 else if (sycl_mem_type == sycl::usm::alloc::shared) {
306 mem_type = ePointerMemoryType::Managed;
307 // TODO: pour l'instant on remplit avec le pointeur car on ne sait
308 // pas comment récupérer l'info.
309 host_ptr = ptr;
310 device_ptr = ptr;
311 }
312 // TODO: à corriger
313 Int32 device_id = 0;
314 _fillPointerAttribute(attribute, mem_type, device_id, ptr, device_ptr, host_ptr);
315 }
316
317 DeviceMemoryInfo getDeviceMemoryInfo([[maybe_unused]] DeviceId device_id) override
318 {
319 return {};
320 }
321
322 void fillDevicesAndSetDefaultQueue(bool is_verbose);
323 sycl::queue& defaultQueue() const { return *m_default_queue; }
324 sycl::device& defaultDevice() const { return *m_default_device; }
325
326 private:
327
328 impl::DeviceInfoList m_device_info_list;
329 std::unique_ptr<sycl::device> m_default_device;
330 std::unique_ptr<sycl::context> m_default_context;
331 std::unique_ptr<sycl::queue> m_default_queue;
332
333 private:
334
335 void _init(sycl::device& device)
336 {
337 m_default_device = std::make_unique<sycl::device>(device);
338 m_default_queue = std::make_unique<sycl::queue>(device);
339 m_default_context = std::make_unique<sycl::context>(device);
340 }
341};
342
343/*---------------------------------------------------------------------------*/
344/*---------------------------------------------------------------------------*/
345
346SyclRunQueueStream::
347SyclRunQueueStream(SyclRunnerRuntime* runtime, const RunQueueBuildInfo& bi)
348: m_runtime(runtime)
349{
350 sycl::device& d = runtime->defaultDevice();
351 // Indique que les commandes lancées sont implicitement exécutées les
352 // unes derrière les autres.
353 auto queue_property = sycl::property::queue::in_order();
354 // Pour le profiling
355 auto profiling_property = sycl::property::queue::enable_profiling();
356 sycl::property_list queue_properties(queue_property, profiling_property);
357
358 // Gestionnaire d'erreur.
359 sycl::async_handler err_handler;
360 err_handler = _getAsyncHandler();
361 if (bi.isDefault())
362 m_sycl_stream = std::make_unique<sycl::queue>(d, err_handler, queue_properties);
363 else {
364 ARCANE_SYCL_FUNC_NOT_HANDLED;
365 m_sycl_stream = std::make_unique<sycl::queue>(d, err_handler, queue_properties);
366 }
367}
368
369/*---------------------------------------------------------------------------*/
370/*---------------------------------------------------------------------------*/
371
372void SyclRunnerRuntime::
373fillDevicesAndSetDefaultQueue(bool is_verbose)
374{
375 if (is_verbose){
376 for (auto platform : sycl::platform::get_platforms()) {
377 std::cout << "Platform: "
378 << platform.get_info<sycl::info::platform::name>()
379 << std::endl;
380 }
381 }
382
383 sycl::device device{ sycl::gpu_selector_v };
384 if (is_verbose)
385 std::cout << "\nDevice: " << device.get_info<sycl::info::device::name>()
386 << "\nVersion=" << device.get_info<sycl::info::device::version>()
387 << std::endl;
388 // Pour l'instant, on prend comme file par défaut la première trouvée
389 // et on ne considère qu'un seul device accessible.
390 _init(device);
391
392 DeviceInfo device_info;
393 device_info.setDescription("No description info");
394 device_info.setDeviceId(DeviceId(0));
395 device_info.setName(device.get_info<sycl::info::device::name>());
396 m_device_info_list.addDevice(device_info);
397}
398
399/*---------------------------------------------------------------------------*/
400/*---------------------------------------------------------------------------*/
401
403: public IMemoryCopier
404{
405 void copy(ConstMemoryView from, eMemoryRessource from_mem,
407 const RunQueue* queue) override;
408};
409
410/*---------------------------------------------------------------------------*/
411/*---------------------------------------------------------------------------*/
412
413} // namespace Arcane::Accelerator::Sycl
414
415namespace
416{
418Arcane::Accelerator::Sycl::SyclMemoryCopier global_sycl_memory_copier;
419} // namespace
420
421/*---------------------------------------------------------------------------*/
422/*---------------------------------------------------------------------------*/
423
424namespace Arcane::Accelerator::Sycl
425{
426
427/*---------------------------------------------------------------------------*/
428/*---------------------------------------------------------------------------*/
429
430void SyclMemoryCopier::
431copy(ConstMemoryView from, [[maybe_unused]] eMemoryRessource from_mem,
432 MutableMemoryView to, [[maybe_unused]] eMemoryRessource to_mem,
433 const RunQueue* queue)
434{
435 if (queue) {
436 queue->copyMemory(MemoryCopyArgs(to.bytes(), from.bytes()).addAsync(queue->isAsync()));
437 return;
438 }
439 sycl::queue& q = global_sycl_runtime.defaultQueue();
440 q.memcpy(to.data(), from.data(), from.bytes().size()).wait();
441}
442
443} // namespace Arcane::Accelerator::Sycl
444
445/*---------------------------------------------------------------------------*/
446/*---------------------------------------------------------------------------*/
447
448// Cette fonction est le point d'entrée utilisé lors du chargement
449// dynamique de cette bibliothèque
450extern "C" ARCANE_EXPORT void
451arcaneRegisterAcceleratorRuntimesycl(Arcane::Accelerator::RegisterRuntimeInfo& init_info)
452{
453 using namespace Arcane;
454 using namespace Arcane::Accelerator::Sycl;
455 Arcane::Accelerator::impl::setUsingSYCLRuntime(true);
456 Arcane::Accelerator::impl::setSYCLRunQueueRuntime(&global_sycl_runtime);
457 MemoryUtils::setAcceleratorHostMemoryAllocator(getSyclMemoryAllocator());
460 mrm->setIsAccelerator(true);
461 mrm->setAllocator(eMemoryRessource::UnifiedMemory, getSyclUnifiedMemoryAllocator());
462 mrm->setAllocator(eMemoryRessource::HostPinned, getSyclHostPinnedMemoryAllocator());
463 mrm->setAllocator(eMemoryRessource::Device, getSyclDeviceMemoryAllocator());
464 mrm->setCopier(&global_sycl_memory_copier);
465 global_sycl_runtime.fillDevicesAndSetDefaultQueue(init_info.isVerbose());
466 setSyclMemoryQueue(global_sycl_runtime.defaultQueue());
467}
468
469/*---------------------------------------------------------------------------*/
470/*---------------------------------------------------------------------------*/
#define ARCANE_CHECK_POINTER(ptr)
Macro retournant le pointeur ptr s'il est non nul ou lancant une exception s'il est nul.
#define ARCANE_THROW(exception_class,...)
Macro pour envoyer une exception avec formattage.
#define ARCANE_FATAL(...)
Macro envoyant une exception FatalErrorException.
Identifiant d'un composant du système.
Definition DeviceId.h:33
Information mémoire d'un accélérateur.
Interface d'une liste de devices.
Arguments pour la copie mémoire.
Definition Memory.h:61
Arguments pour le préfetching mémoire.
Definition Memory.h:123
Informations sur une adresse mémoire.
Informations pour initialiser le runtime accélérateur.
Informations pour créer une RunQueue.
bool isDefault() const
Indique si l'instance a uniquement les valeurs par défaut.
File d'exécution pour un accélérateur.
bool isAsync() const
Indique si la file d'exécution est asynchrone.
Definition RunQueue.cc:320
void copyMemory(const MemoryCopyArgs &args) const
Copie des informations entre deux zones mémoires.
Definition RunQueue.cc:237
void copy(ConstMemoryView from, eMemoryRessource from_mem, MutableMemoryView to, eMemoryRessource to_mem, const RunQueue *queue) override
Copie les données de from vers to avec la queue queue.
impl::NativeStream nativeStream() override
Pointeur sur la structure interne dépendante de l'implémentation.
void barrier() override
Bloque jusqu'à ce que toutes les actions associées à cette file soient terminées.
sycl::event lastCommandEvent()
Évènement correspondant à la dernière commande.
void prefetchMemory(const MemoryPrefetchArgs &args) override
Effectue un pré-chargement d'une zone mémoire.
void copyMemory(const MemoryCopyArgs &args) override
Effectue une copie entre deux zones mémoire.
bool _barrierNoException() override
Barrière sans exception. Retourne true en cas d'erreur.
void _setSyclLastCommandEvent(void *sycl_event_ptr) override
Pour SYCL, positionne l'évènement associé à la dernière commande exécutée.
void notifyEndLaunchKernel(impl::RunCommandImpl &) override
Notification de fin de lancement de la commande.
void notifyBeginLaunchKernel(impl::RunCommandImpl &c) override
Notification avant le lancement de la commande.
Interface d'une liste de devices.
Interface de l'implémentation d'un évènement.
Interface d'un flux d'exécution pour une RunQueue.
Interface du runtime associé à un accélérateur.
Type opaque pour encapsuler une 'stream' native.
Implémentation d'une commande pour accélérateur.
Vue constante sur une zone mémoire contigue contenant des éléments de taille fixe.
constexpr SpanType bytes() const
Vue sous forme d'octets.
constexpr const std::byte * data() const
Pointeur sur la zone mémoire.
Interface pour les copies mémoire avec support des accélérateurs.
Partie interne à Arcane de 'IMemoryRessourceMng'.
virtual void setAllocator(eMemoryResource r, IMemoryAllocator *allocator)=0
Positionne l'allocateur pour la ressource r.
virtual void setIsAccelerator(bool v)=0
Indique si un accélérateur est disponible.
virtual void setCopier(IMemoryCopier *copier)=0
Positionne l'instance gérant les copies.
virtual IMemoryResourceMngInternal * _internal()=0
Interface interne.
Vue modifiable sur une zone mémoire contigue contenant des éléments de taille fixe.
constexpr std::byte * data() const
Pointeur sur la zone mémoire.
constexpr SpanType bytes() const
Vue sous forme d'octets.
Exception lorsqu'une fonction n'est pas implémentée.
Exception lorsqu'une opération n'est pas supportée.
constexpr __host__ __device__ SizeType size() const noexcept
Retourne la taille du tableau.
Definition Span.h:304
eMemoryAdvice
Conseils pour la gestion mémoire.
Definition Memory.h:34
ePointerMemoryType
Type de mémoire pour un pointeur.
eExecutionPolicy
Politique d'exécution pour un Runner.
@ SYCL
Politique d'exécution utilisant l'environnement SYCL.
IMemoryRessourceMng * getDataMemoryResourceMng()
Gestionnaire de ressource mémoire pour les données.
IMemoryAllocator * setAcceleratorHostMemoryAllocator(IMemoryAllocator *a)
Positionne l'allocateur spécifique pour les accélérateurs.
void setDefaultDataMemoryResource(eMemoryResource mem_resource)
Positionne la ressource mémoire utilisée pour l'allocateur mémoire des données.
Espace de nom pour les fonctions dépendant de la plateforme.
-*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
@ HostPinned
Alloue sur l'hôte.
@ UnifiedMemory
Alloue en utilisant la mémoire unifiée.
@ Device
Alloue sur le device.
Arcane::eMemoryResource eMemoryRessource
Typedef pour la version Arcane historique (avec 2's')