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