xref: /petsc/src/sys/objects/device/interface/device.cxx (revision 1b37a2a7cc4a4fb30c3e967db1c694c0a1013f51)
1 #include "petscdevice_interface_internal.hpp" /*I <petscdevice.h> I*/
2 #include <petsc/private/petscadvancedmacros.h>
3 
4 #include <petsc/private/cpp/register_finalize.hpp>
5 
6 #include "../impls/host/hostdevice.hpp"
7 #if PetscDefined(HAVE_CUPM)
8   #include "../impls/cupm/cupmdevice.hpp"
9 #endif
10 #if PetscDefined(HAVE_SYCL)
11   #include "../impls/sycl/sycldevice.hpp"
12 #endif
13 
14 #include <utility> // std::make_pair
15 
16 using namespace Petsc::device;
17 
18 namespace
19 {
20 
21 /*
22   note to anyone adding more classes, the name must be ALL_CAPS_SHORT_NAME + Device exactly to
23   be picked up by the switch-case macros below
24 */
25 host::Device HOSTDevice{PetscDeviceContextCreate_HOST};
26 #if PetscDefined(HAVE_CUDA)
27 cupm::Device<cupm::DeviceType::CUDA> CUDADevice{PetscDeviceContextCreate_CUDA};
28 #endif
29 #if PetscDefined(HAVE_HIP)
30 cupm::Device<cupm::DeviceType::HIP> HIPDevice{PetscDeviceContextCreate_HIP};
31 #endif
32 #if PetscDefined(HAVE_SYCL)
33 sycl::Device SYCLDevice{PetscDeviceContextCreate_SYCL};
34 #endif
35 
36 } // namespace
37 
38 #define PETSC_DEVICE_CASE(IMPLS, func, ...) \
39   case PetscConcat_(PETSC_DEVICE_, IMPLS): { \
40     PetscCall(PetscConcat_(IMPLS, Device).func(__VA_ARGS__)); \
41   } break
42 
43 #define PETSC_VOID_0(...) ((void)0)
44 
45 /*
46   Suppose you have:
47 
48   CUDADevice.myFunction(arg1,arg2)
49 
50   that you would like to conditionally define and call in a switch-case:
51 
52   switch(PetscDeviceType) {
53   #if PetscDefined(HAVE_CUDA)
54   case PETSC_DEVICE_CUDA: {
55     PetscCall(CUDADevice.myFunction(arg1,arg2));
56   } break;
57   #endif
58   }
59 
60   then calling this macro:
61 
62   PETSC_DEVICE_CASE_IF_PETSC_DEFINED(CUDA,myFunction,arg1,arg2)
63 
64   will expand to the following case statement:
65 
66   case PETSC_DEVICE_CUDA: {
67     PetscCall(CUDADevice.myFunction(arg1,arg2));
68   } break
69 
70   if PetscDefined(HAVE_CUDA) evaluates to 1, and expand to nothing otherwise
71 */
72 #define PETSC_DEVICE_CASE_IF_PETSC_DEFINED(IMPLS, func, ...) PetscIfPetscDefined(PetscConcat_(HAVE_, IMPLS), PETSC_DEVICE_CASE, PETSC_VOID_0)(IMPLS, func, __VA_ARGS__)
73 
74 /*@C
75   PetscDeviceCreate - Get a new handle for a particular device (often a GPU) type
76 
77   Not Collective
78 
79   Input Parameters:
80 + type  - The type of `PetscDevice`
81 - devid - The numeric ID# of the device (pass `PETSC_DECIDE` to assign automatically)
82 
83   Output Parameter:
84 . device - The `PetscDevice`
85 
86   Level: beginner
87 
88   Notes:
89   This routine may initialize `PetscDevice`. If this is the case, it may cause some sort of
90   device synchronization.
91 
92   `devid` is what you might pass to `cudaSetDevice()` for example.
93 
94 .seealso: `PetscDevice`, `PetscDeviceInitType`,
95 `PetscDeviceInitialize()`, `PetscDeviceInitialized()`, `PetscDeviceConfigure()`,
96 `PetscDeviceView()`, `PetscDeviceDestroy()`
97 @*/
98 PetscErrorCode PetscDeviceCreate(PetscDeviceType type, PetscInt devid, PetscDevice *device)
99 {
100   static PetscInt PetscDeviceCounter = 0;
101 
102   PetscFunctionBegin;
103   PetscValidDeviceType(type, 1);
104   PetscAssertPointer(device, 3);
105   PetscCall(PetscDeviceInitializePackage());
106   PetscCall(PetscNew(device));
107   (*device)->id     = PetscDeviceCounter++;
108   (*device)->type   = type;
109   (*device)->refcnt = 1;
110   /*
111     if you are adding a device, you also need to add its initialization in
112     PetscDeviceInitializeTypeFromOptions_Private() below
113   */
114   switch (type) {
115     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(HOST, getDevice, *device, devid);
116     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(CUDA, getDevice, *device, devid);
117     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(HIP, getDevice, *device, devid);
118     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(SYCL, getDevice, *device, devid);
119   default:
120     /* in case the above macros expand to nothing this silences any unused variable warnings */
121     (void)(devid);
122     SETERRQ(PETSC_COMM_SELF, PETSC_ERR_PLIB, "PETSc was seemingly configured for PetscDeviceType %s but we've fallen through all cases in a switch", PetscDeviceTypes[type]);
123   }
124   PetscFunctionReturn(PETSC_SUCCESS);
125 }
126 
127 /*@C
128   PetscDeviceDestroy - Free a `PetscDevice`
129 
130   Not Collective
131 
132   Input Parameter:
133 . device - The `PetscDevice`
134 
135   Level: beginner
136 
137 .seealso: `PetscDevice`, `PetscDeviceCreate()`, `PetscDeviceConfigure()`, `PetscDeviceView()`,
138 `PetscDeviceGetType()`, `PetscDeviceGetDeviceId()`
139 @*/
140 PetscErrorCode PetscDeviceDestroy(PetscDevice *device)
141 {
142   PetscFunctionBegin;
143   PetscAssertPointer(device, 1);
144   if (!*device) PetscFunctionReturn(PETSC_SUCCESS);
145   PetscValidDevice(*device, 1);
146   PetscCall(PetscDeviceDereference_Internal(*device));
147   if ((*device)->refcnt) {
148     *device = nullptr;
149     PetscFunctionReturn(PETSC_SUCCESS);
150   }
151   PetscCall(PetscFree((*device)->data));
152   PetscCall(PetscFree(*device));
153   PetscFunctionReturn(PETSC_SUCCESS);
154 }
155 
156 /*@C
157   PetscDeviceConfigure - Configure a particular `PetscDevice`
158 
159   Not Collective
160 
161   Input Parameter:
162 . device - The `PetscDevice` to configure
163 
164   Level: beginner
165 
166   Notes:
167   The user should not assume that this is a cheap operation.
168 
169 .seealso: `PetscDevice`, `PetscDeviceCreate()`, `PetscDeviceView()`, `PetscDeviceDestroy()`,
170 `PetscDeviceGetType()`, `PetscDeviceGetDeviceId()`
171 @*/
172 PetscErrorCode PetscDeviceConfigure(PetscDevice device)
173 {
174   PetscFunctionBegin;
175   PetscValidDevice(device, 1);
176   /*
177     if no available configuration is available, this cascades all the way down to default
178     and error
179   */
180   switch (const auto dtype = device->type) {
181   case PETSC_DEVICE_HOST:
182     if (PetscDefined(HAVE_HOST)) break; // always true
183   case PETSC_DEVICE_CUDA:
184     if (PetscDefined(HAVE_CUDA)) break;
185     goto error;
186   case PETSC_DEVICE_HIP:
187     if (PetscDefined(HAVE_HIP)) break;
188     goto error;
189   case PETSC_DEVICE_SYCL:
190     if (PetscDefined(HAVE_SYCL)) break;
191     goto error;
192   default:
193   error:
194     SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "PETSc was not configured for PetscDeviceType %s", PetscDeviceTypes[dtype]);
195   }
196   PetscUseTypeMethod(device, configure);
197   PetscFunctionReturn(PETSC_SUCCESS);
198 }
199 
200 /*@C
201   PetscDeviceView - View a `PetscDevice`
202 
203   Collective on viewer
204 
205   Input Parameters:
206 + device - The `PetscDevice` to view
207 - viewer - The `PetscViewer` to view the device with (`NULL` for `PETSC_VIEWER_STDOUT_WORLD`)
208 
209   Level: beginner
210 
211 .seealso: `PetscDevice`, `PetscDeviceCreate()`, `PetscDeviceConfigure()`,
212 `PetscDeviceDestroy()`, `PetscDeviceGetType()`, `PetscDeviceGetDeviceId()`
213 @*/
214 PetscErrorCode PetscDeviceView(PetscDevice device, PetscViewer viewer)
215 {
216   auto      sub = viewer;
217   PetscBool iascii;
218 
219   PetscFunctionBegin;
220   PetscValidDevice(device, 1);
221   if (viewer) {
222     PetscValidHeaderSpecific(viewer, PETSC_VIEWER_CLASSID, 2);
223     PetscCall(PetscObjectTypeCompare(PetscObjectCast(viewer), PETSCVIEWERASCII, &iascii));
224   } else {
225     PetscCall(PetscViewerASCIIGetStdout(PETSC_COMM_WORLD, &viewer));
226     iascii = PETSC_TRUE;
227   }
228 
229   if (iascii) {
230     auto        dtype = PETSC_DEVICE_HOST;
231     MPI_Comm    comm;
232     PetscMPIInt size;
233     PetscInt    id = 0;
234 
235     PetscCall(PetscObjectGetComm(PetscObjectCast(viewer), &comm));
236     PetscCallMPI(MPI_Comm_size(comm, &size));
237 
238     PetscCall(PetscDeviceGetDeviceId(device, &id));
239     PetscCall(PetscDeviceGetType(device, &dtype));
240     PetscCall(PetscViewerGetSubViewer(viewer, PETSC_COMM_SELF, &sub));
241     PetscCall(PetscViewerASCIIPrintf(sub, "PetscDevice Object: %d MPI %s\n", size, size == 1 ? "process" : "processes"));
242     PetscCall(PetscViewerASCIIPushTab(sub));
243     PetscCall(PetscViewerASCIIPrintf(sub, "type: %s\n", PetscDeviceTypes[dtype]));
244     PetscCall(PetscViewerASCIIPrintf(sub, "id: %" PetscInt_FMT "\n", id));
245   }
246 
247   // see if impls has extra viewer stuff
248   PetscTryTypeMethod(device, view, sub);
249 
250   if (iascii) {
251     // undo the ASCII specific stuff
252     PetscCall(PetscViewerASCIIPopTab(sub));
253     PetscCall(PetscViewerRestoreSubViewer(viewer, PETSC_COMM_SELF, &sub));
254   }
255   PetscFunctionReturn(PETSC_SUCCESS);
256 }
257 
258 /*@C
259   PetscDeviceGetType - Get the type of device
260 
261   Not Collective
262 
263   Input Parameter:
264 . device - The `PetscDevice`
265 
266   Output Parameter:
267 . type - The `PetscDeviceType`
268 
269   Level: beginner
270 
271 .seealso: `PetscDevice`, `PetscDeviceType`, `PetscDeviceSetDefaultDeviceType()`,
272 `PetscDeviceCreate()`, `PetscDeviceConfigure()`, `PetscDeviceDestroy()`,
273 `PetscDeviceGetDeviceId()`, `PETSC_DEVICE_DEFAULT()`
274 @*/
275 PetscErrorCode PetscDeviceGetType(PetscDevice device, PetscDeviceType *type)
276 {
277   PetscFunctionBegin;
278   PetscValidDevice(device, 1);
279   PetscAssertPointer(type, 2);
280   *type = device->type;
281   PetscFunctionReturn(PETSC_SUCCESS);
282 }
283 
284 /*@C
285   PetscDeviceGetDeviceId - Get the device ID for a `PetscDevice`
286 
287   Not Collective
288 
289   Input Parameter:
290 . device - The `PetscDevice`
291 
292   Output Parameter:
293 . id - The id
294 
295   Level: beginner
296 
297   Notes:
298   The returned ID may have been assigned by the underlying device backend. For example if the
299   backend is CUDA then `id` is exactly the value returned by `cudaGetDevice()` at the time when
300   this device was configured.
301 
302 .seealso: `PetscDevice`, `PetscDeviceCreate()`, `PetscDeviceGetType()`
303 @*/
304 PetscErrorCode PetscDeviceGetDeviceId(PetscDevice device, PetscInt *id)
305 {
306   PetscFunctionBegin;
307   PetscValidDevice(device, 1);
308   PetscAssertPointer(id, 2);
309   *id = device->deviceId;
310   PetscFunctionReturn(PETSC_SUCCESS);
311 }
312 
313 namespace
314 {
315 
316 struct DefaultDeviceType : public Petsc::RegisterFinalizeable<DefaultDeviceType> {
317   PetscDeviceType type = PETSC_DEVICE_HARDWARE_DEFAULT_TYPE;
318 
319   PetscErrorCode finalize_() noexcept
320   {
321     PetscFunctionBegin;
322     type = PETSC_DEVICE_HARDWARE_DEFAULT_TYPE;
323     PetscFunctionReturn(PETSC_SUCCESS);
324   }
325 };
326 
327 auto default_device_type = DefaultDeviceType();
328 
329 } // namespace
330 
331 /*@C
332   PETSC_DEVICE_DEFAULT - Retrieve the current default `PetscDeviceType`
333 
334   Not Collective
335 
336   Level: beginner
337 
338   Notes:
339   Unless selected by the user, the default device is selected in the following order\:
340   `PETSC_DEVICE_HIP`, `PETSC_DEVICE_CUDA`, `PETSC_DEVICE_SYCL`, `PETSC_DEVICE_HOST`.
341 
342 .seealso: `PetscDeviceType`, `PetscDeviceSetDefaultDeviceType()`, `PetscDeviceGetType()`
343 @*/
344 PetscDeviceType PETSC_DEVICE_DEFAULT(void)
345 {
346   return default_device_type.type;
347 }
348 
349 /*@C
350   PetscDeviceSetDefaultDeviceType - Set the default device type for `PetscDevice`
351 
352   Not Collective
353 
354   Input Parameter:
355 . type - the new default device type
356 
357   Level: beginner
358 
359   Notes:
360   This sets the `PetscDeviceType` returned by `PETSC_DEVICE_DEFAULT()`.
361 
362 .seealso: `PetscDeviceType`, `PetscDeviceGetType`,
363 @*/
364 PetscErrorCode PetscDeviceSetDefaultDeviceType(PetscDeviceType type)
365 {
366   PetscFunctionBegin;
367   PetscValidDeviceType(type, 1);
368   if (default_device_type.type != type) {
369     // no need to waster a PetscRegisterFinalize() slot if we don't change it
370     default_device_type.type = type;
371     PetscCall(default_device_type.register_finalize());
372   }
373   PetscFunctionReturn(PETSC_SUCCESS);
374 }
375 
376 namespace
377 {
378 
379 std::array<std::pair<PetscDevice, bool>, PETSC_DEVICE_MAX> defaultDevices = {};
380 
381 /*
382   Actual initialization function; any functions claiming to initialize PetscDevice or
383   PetscDeviceContext will have to run through this one
384 */
385 PetscErrorCode PetscDeviceInitializeDefaultDevice_Internal(PetscDeviceType type, PetscInt defaultDeviceId)
386 {
387   PetscFunctionBegin;
388   PetscValidDeviceType(type, 1);
389   if (PetscUnlikely(!PetscDeviceInitialized(type))) {
390     auto &dev  = defaultDevices[type].first;
391     auto &init = defaultDevices[type].second;
392 
393     PetscAssert(!dev, PETSC_COMM_SELF, PETSC_ERR_MEM, "Trying to overwrite existing default device of type %s", PetscDeviceTypes[type]);
394     PetscCall(PetscDeviceCreate(type, defaultDeviceId, &dev));
395     PetscCall(PetscDeviceConfigure(dev));
396     init = true;
397   }
398   PetscFunctionReturn(PETSC_SUCCESS);
399 }
400 
401 } // namespace
402 
403 /*@C
404   PetscDeviceInitialize - Initialize `PetscDevice`
405 
406   Not Collective
407 
408   Input Parameter:
409 . type - The `PetscDeviceType` to initialize
410 
411   Level: beginner
412 
413   Notes:
414   Eagerly initializes the corresponding `PetscDeviceType` if needed. If this is the case it may
415   result in device synchronization.
416 
417 .seealso: `PetscDevice`, `PetscDeviceInitType`, `PetscDeviceInitialized()`,
418 `PetscDeviceCreate()`, `PetscDeviceDestroy()`
419 @*/
420 PetscErrorCode PetscDeviceInitialize(PetscDeviceType type)
421 {
422   PetscFunctionBegin;
423   PetscValidDeviceType(type, 1);
424   PetscCall(PetscDeviceInitializeDefaultDevice_Internal(type, PETSC_DECIDE));
425   PetscFunctionReturn(PETSC_SUCCESS);
426 }
427 
428 /*@C
429   PetscDeviceInitialized - Determines whether `PetscDevice` is initialized for a particular
430   `PetscDeviceType`
431 
432   Not Collective
433 
434   Input Parameter:
435 . type - The `PetscDeviceType` to check
436 
437   Level: beginner
438 
439   Notes:
440   Returns `PETSC_TRUE` if `type` is initialized, `PETSC_FALSE` otherwise.
441 
442   If one has not configured PETSc for a particular `PetscDeviceType` then this routine will
443   return `PETSC_FALSE` for that `PetscDeviceType`.
444 
445 .seealso: `PetscDevice`, `PetscDeviceInitType`, `PetscDeviceInitialize()`,
446 `PetscDeviceCreate()`, `PetscDeviceDestroy()`
447 @*/
448 PetscBool PetscDeviceInitialized(PetscDeviceType type)
449 {
450   return static_cast<PetscBool>(PetscDeviceConfiguredFor_Internal(type) && defaultDevices[type].second);
451 }
452 
453 /* Get the default PetscDevice for a particular type and constructs them if lazily initialized. */
454 PetscErrorCode PetscDeviceGetDefaultForType_Internal(PetscDeviceType type, PetscDevice *device)
455 {
456   PetscFunctionBegin;
457   PetscAssertPointer(device, 2);
458   PetscCall(PetscDeviceInitialize(type));
459   *device = defaultDevices[type].first;
460   PetscFunctionReturn(PETSC_SUCCESS);
461 }
462 
463 /*@C
464   PetscDeviceGetAttribute - Query a particular attribute of a `PetscDevice`
465 
466   Not Collective
467 
468   Input Parameters:
469 + device - The `PetscDevice`
470 - attr   - The attribute
471 
472   Output Parameter:
473 . value - The value of the attribute
474 
475   Level: intermediate
476 
477   Notes:
478   Since different attributes are often different types `value` is a `void *` to accommodate
479   them all. The underlying type of the attribute is therefore included in the name of the
480   `PetscDeviceAttribute` responsible for querying it. For example,
481   `PETSC_DEVICE_ATTR_SIZE_T_SHARED_MEM_PER_BLOCK` is of type `size_t`.
482 
483 .seealso: `PetscDeviceAtrtibute`, `PetscDeviceConfigure()`, `PetscDevice`
484 @*/
485 PetscErrorCode PetscDeviceGetAttribute(PetscDevice device, PetscDeviceAttribute attr, void *value)
486 {
487   PetscFunctionBegin;
488   PetscValidDevice(device, 1);
489   PetscValidDeviceAttribute(attr, 2);
490   PetscAssertPointer(value, 3);
491   PetscUseTypeMethod(device, getattribute, attr, value);
492   PetscFunctionReturn(PETSC_SUCCESS);
493 }
494 
495 namespace
496 {
497 
498 PetscErrorCode PetscDeviceInitializeTypeFromOptions_Private(MPI_Comm comm, PetscDeviceType type, PetscInt defaultDeviceId, PetscBool defaultView, PetscDeviceInitType *defaultInitType)
499 {
500   PetscFunctionBegin;
501   if (!PetscDeviceConfiguredFor_Internal(type)) {
502     PetscCall(PetscInfo(nullptr, "PetscDeviceType %s not available\n", PetscDeviceTypes[type]));
503     defaultDevices[type].first = nullptr;
504     PetscFunctionReturn(PETSC_SUCCESS);
505   }
506   PetscCall(PetscInfo(nullptr, "PetscDeviceType %s available, initializing\n", PetscDeviceTypes[type]));
507   /* ugly switch needed to pick the right global variable... could maybe do this as a union? */
508   switch (type) {
509     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(HOST, initialize, comm, &defaultDeviceId, &defaultView, defaultInitType);
510     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(CUDA, initialize, comm, &defaultDeviceId, &defaultView, defaultInitType);
511     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(HIP, initialize, comm, &defaultDeviceId, &defaultView, defaultInitType);
512     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(SYCL, initialize, comm, &defaultDeviceId, &defaultView, defaultInitType);
513   default:
514     SETERRQ(comm, PETSC_ERR_PLIB, "PETSc was seemingly configured for PetscDeviceType %s but we've fallen through all cases in a switch", PetscDeviceTypes[type]);
515   }
516   PetscCall(PetscInfo(nullptr, "PetscDevice %s initialized, default device id %" PetscInt_FMT ", view %s, init type %s\n", PetscDeviceTypes[type], defaultDeviceId, PetscBools[defaultView], PetscDeviceInitTypes[Petsc::util::to_underlying(*defaultInitType)]));
517   /*
518     defaultInitType, defaultView  and defaultDeviceId now represent what the individual TYPES
519     have decided to initialize as
520   */
521   if ((*defaultInitType == PETSC_DEVICE_INIT_EAGER) || defaultView) {
522     PetscCall(PetscInfo(nullptr, "Eagerly initializing %s PetscDevice\n", PetscDeviceTypes[type]));
523     PetscCall(PetscDeviceInitializeDefaultDevice_Internal(type, defaultDeviceId));
524     if (defaultView) PetscCall(PetscDeviceView(defaultDevices[type].first, nullptr));
525   }
526   PetscFunctionReturn(PETSC_SUCCESS);
527 }
528 
529 PetscErrorCode PetscDeviceInitializeQueryOptions_Private(MPI_Comm comm, PetscDeviceType *deviceContextInitDevice, PetscDeviceInitType *defaultInitType, PetscInt *defaultDevice, PetscBool *defaultDeviceSet, PetscBool *defaultView)
530 {
531   PetscInt initIdx       = PETSC_DEVICE_INIT_LAZY;
532   auto     initDeviceIdx = static_cast<PetscInt>(*deviceContextInitDevice);
533   auto     flg           = PETSC_FALSE;
534 
535   PetscFunctionBegin;
536   PetscCall(PetscOptionsHasName(nullptr, nullptr, "-log_view_gpu_time", &flg));
537   if (flg) PetscCall(PetscLogGpuTime());
538 
539   PetscOptionsBegin(comm, nullptr, "PetscDevice Options", "Sys");
540   PetscCall(PetscOptionsEList("-device_enable", "How (or whether) to initialize PetscDevices", "PetscDeviceInitialize()", PetscDeviceInitTypes, 3, PetscDeviceInitTypes[initIdx], &initIdx, nullptr));
541   PetscCall(PetscOptionsEList("-default_device_type", "Set the PetscDeviceType returned by PETSC_DEVICE_DEFAULT()", "PetscDeviceSetDefaultDeviceType()", PetscDeviceTypes, PETSC_DEVICE_MAX, PetscDeviceTypes[initDeviceIdx], &initDeviceIdx, defaultDeviceSet));
542   PetscCall(PetscOptionsRangeInt("-device_select", "Which device to use. Pass " PetscStringize(PETSC_DECIDE) " to have PETSc decide or (given they exist) [0-" PetscStringize(PETSC_DEVICE_MAX_DEVICES) ") for a specific device", "PetscDeviceCreate()", *defaultDevice, defaultDevice, nullptr, PETSC_DECIDE, PETSC_DEVICE_MAX_DEVICES));
543   PetscCall(PetscOptionsBool("-device_view", "Display device information and assignments (forces eager initialization)", "PetscDeviceView()", *defaultView, defaultView, &flg));
544   PetscOptionsEnd();
545 
546   if (initIdx == PETSC_DEVICE_INIT_NONE) {
547     /* disabled all device initialization if devices are globally disabled */
548     PetscCheck(*defaultDevice == PETSC_DECIDE, comm, PETSC_ERR_USER_INPUT, "You have disabled devices but also specified a particular device to use, these options are mutually exclusive");
549     *defaultView  = PETSC_FALSE;
550     initDeviceIdx = PETSC_DEVICE_HOST;
551   } else {
552     *defaultView = static_cast<PetscBool>(*defaultView && flg);
553     if (*defaultView) initIdx = PETSC_DEVICE_INIT_EAGER;
554   }
555   *defaultInitType         = PetscDeviceInitTypeCast(initIdx);
556   *deviceContextInitDevice = PetscDeviceTypeCast(initDeviceIdx);
557   PetscFunctionReturn(PETSC_SUCCESS);
558 }
559 
560 /* called from PetscFinalize() do not call yourself! */
561 PetscErrorCode PetscDeviceFinalize_Private()
562 {
563   PetscFunctionBegin;
564   if (PetscDefined(USE_DEBUG)) {
565     /*
566       you might be thinking, why on earth are you registered yet another finalizer in a
567       function already called during PetscRegisterFinalizeAll()? If this seems stupid it's
568       because it is.
569 
570       The crux of the problem is that the initializer (and therefore the ~finalizer~) of
571       PetscDeviceContext is guaranteed to run after PetscDevice's. So if the global context had
572       a default PetscDevice attached, that PetscDevice will have a reference count >0 and hence
573       won't be destroyed yet. So we need to repeat the check that all devices have been
574       destroyed again ~after~ the global context is destroyed. In summary:
575 
576       1. This finalizer runs and destroys all devices, except it may not because the global
577          context may still hold a reference!
578       2. The global context finalizer runs and does the final reference count decrement
579          required, which actually destroys the held device.
580       3. Our newly added finalizer runs and checks that all is well.
581     */
582     PetscCall(PetscRegisterFinalize([] {
583       PetscFunctionBegin;
584       for (auto &&device : defaultDevices) {
585         const auto dev = device.first;
586 
587         PetscCheck(!dev, PETSC_COMM_WORLD, PETSC_ERR_COR, "Device of type '%s' had reference count %" PetscInt_FMT " and was not fully destroyed during PetscFinalize()", PetscDeviceTypes[dev->type], dev->refcnt);
588       }
589       PetscFunctionReturn(PETSC_SUCCESS);
590     }));
591   }
592   for (auto &&device : defaultDevices) {
593     PetscCall(PetscDeviceDestroy(&device.first));
594     device.second = false;
595   }
596   PetscFunctionReturn(PETSC_SUCCESS);
597 }
598 
599 } // namespace
600 
601 /*
602   Begins the init proceeedings for the entire PetscDevice stack. there are 3 stages of
603   initialization types:
604 
605   1. defaultInitType - how does PetscDevice as a whole expect to initialize?
606   2. subTypeDefaultInitType - how does each PetscDevice implementation expect to initialize?
607      e.g. you may want to blanket disable PetscDevice init (and disable say Kokkos init), but
608      have all CUDA devices still initialize.
609 
610   All told the following happens:
611 
612   0. defaultInitType -> LAZY
613   1. Check for log_view/log_summary, if yes defaultInitType -> EAGER
614   2. PetscDevice initializes each sub type with deviceDefaultInitType.
615   2.1 Each enabled PetscDevice sub-type then does the above disable or view check in addition
616       to checking for specific device init. if view or specific device init
617       subTypeDefaultInitType -> EAGER. disabled once again overrides all.
618 */
619 
620 PetscErrorCode PetscDeviceInitializeFromOptions_Internal(MPI_Comm comm)
621 {
622   auto defaultView                    = PETSC_FALSE;
623   auto initializeDeviceContextEagerly = PETSC_FALSE;
624   auto defaultDeviceSet               = PETSC_FALSE;
625   auto defaultDevice                  = PetscInt{PETSC_DECIDE};
626   auto deviceContextInitDevice        = PETSC_DEVICE_DEFAULT();
627   auto defaultInitType                = PETSC_DEVICE_INIT_LAZY;
628 
629   PetscFunctionBegin;
630   if (PetscDefined(USE_DEBUG)) {
631     int result;
632 
633     PetscCallMPI(MPI_Comm_compare(comm, PETSC_COMM_WORLD, &result));
634     /* in order to accurately assign ranks to gpus we need to get the MPI_Comm_rank of the
635      * global space */
636     if (PetscUnlikely(result != MPI_IDENT)) {
637       char name[MPI_MAX_OBJECT_NAME] = {};
638       int  len; /* unused */
639 
640       PetscCallMPI(MPI_Comm_get_name(comm, name, &len));
641       SETERRQ(comm, PETSC_ERR_MPI, "Default devices being initialized on MPI_Comm '%s' not PETSC_COMM_WORLD", name);
642     }
643   }
644   comm = PETSC_COMM_WORLD; /* from this point on we assume we're on PETSC_COMM_WORLD */
645   PetscCall(PetscRegisterFinalize(PetscDeviceFinalize_Private));
646 
647   PetscCall(PetscDeviceInitializeQueryOptions_Private(comm, &deviceContextInitDevice, &defaultInitType, &defaultDevice, &defaultDeviceSet, &defaultView));
648 
649   // the precise values don't matter here, so long as they are sequential
650   static_assert(Petsc::util::to_underlying(PETSC_DEVICE_HOST) == 0, "");
651   static_assert(Petsc::util::to_underlying(PETSC_DEVICE_CUDA) == 1, "");
652   static_assert(Petsc::util::to_underlying(PETSC_DEVICE_HIP) == 2, "");
653   static_assert(Petsc::util::to_underlying(PETSC_DEVICE_SYCL) == 3, "");
654   static_assert(Petsc::util::to_underlying(PETSC_DEVICE_MAX) == 4, "");
655   for (int i = PETSC_DEVICE_HOST; i < PETSC_DEVICE_MAX; ++i) {
656     const auto deviceType = PetscDeviceTypeCast(i);
657     auto       initType   = defaultInitType;
658 
659     PetscCall(PetscDeviceInitializeTypeFromOptions_Private(comm, deviceType, defaultDevice, defaultView, &initType));
660     if (PetscDeviceConfiguredFor_Internal(deviceType)) {
661       if (initType == PETSC_DEVICE_INIT_EAGER) {
662         initializeDeviceContextEagerly = PETSC_TRUE;
663         // only update the default device if the user hasn't set it previously
664         if (!defaultDeviceSet) {
665           deviceContextInitDevice = deviceType;
666           PetscCall(PetscInfo(nullptr, "PetscDevice %s set as default device type due to eager initialization\n", PetscDeviceTypes[deviceType]));
667         }
668       } else if (initType == PETSC_DEVICE_INIT_NONE) {
669         if (deviceType != PETSC_DEVICE_HOST) PetscCheck(!defaultDeviceSet || (deviceType != deviceContextInitDevice), comm, PETSC_ERR_USER_INPUT, "Cannot explicitly disable the device set as default device type (%s)", PetscDeviceTypes[deviceType]);
670       }
671     }
672   }
673 
674   PetscCall(PetscDeviceSetDefaultDeviceType(deviceContextInitDevice));
675   PetscCall(PetscDeviceContextSetRootDeviceType_Internal(PETSC_DEVICE_DEFAULT()));
676   /* ----------------------------------------------------------------------------------- */
677   /*                       PetscDevice is now fully initialized                          */
678   /* ----------------------------------------------------------------------------------- */
679   {
680     /*
681       query the options db to get the root settings from the user (if any).
682 
683       This section is a bit of a hack. We have to reach across to dcontext.cxx to all but call
684       PetscDeviceContextSetFromOptions() before we even have one, then set a few static
685       variables in that file with the results.
686     */
687     auto dtype = std::make_pair(PETSC_DEVICE_DEFAULT(), PETSC_FALSE);
688     auto stype = std::make_pair(PETSC_DEVICE_CONTEXT_DEFAULT_STREAM_TYPE, PETSC_FALSE);
689 
690     PetscOptionsBegin(comm, "root_", "Root PetscDeviceContext Options", "Sys");
691     PetscCall(PetscDeviceContextQueryOptions_Internal(PetscOptionsObject, dtype, stype));
692     PetscOptionsEnd();
693 
694     if (dtype.second) PetscCall(PetscDeviceContextSetRootDeviceType_Internal(dtype.first));
695     if (stype.second) PetscCall(PetscDeviceContextSetRootStreamType_Internal(stype.first));
696   }
697 
698   if (initializeDeviceContextEagerly) {
699     PetscDeviceContext dctx;
700 
701     PetscCall(PetscInfo(nullptr, "Eagerly initializing PetscDeviceContext with %s device\n", PetscDeviceTypes[deviceContextInitDevice]));
702     /* instantiates the device context */
703     PetscCall(PetscDeviceContextGetCurrentContext(&dctx));
704     PetscCall(PetscDeviceContextSetUp(dctx));
705   }
706   PetscFunctionReturn(PETSC_SUCCESS);
707 }
708