xref: /petsc/src/sys/objects/device/interface/device.cxx (revision a69119a591a03a9d906b29c0a4e9802e4d7c9795)
1 #include "cupmdevice.hpp" /* I "petscdevice.h" */
2 #include <petsc/private/petscadvancedmacros.h>
3 
4 using namespace Petsc::Device;
5 
6 /*
7   note to anyone adding more classes, the name must be ALL_CAPS_SHORT_NAME + Device exactly to
8   be picked up by the switch-case macros below
9 */
10 #if PetscDefined(HAVE_CUDA)
11 static CUPM::Device<CUPM::DeviceType::CUDA> CUDADevice(PetscDeviceContextCreate_CUDA);
12 #endif
13 #if PetscDefined(HAVE_HIP)
14 static CUPM::Device<CUPM::DeviceType::HIP> HIPDevice(PetscDeviceContextCreate_HIP);
15 #endif
16 #if PetscDefined(HAVE_SYCL)
17 #include "sycldevice.hpp"
18 static SYCL::Device SYCLDevice(PetscDeviceContextCreate_SYCL);
19 #endif
20 
21 static_assert(Petsc::util::integral_value(PETSC_DEVICE_INVALID) == 0, "");
22 static_assert(Petsc::util::integral_value(PETSC_DEVICE_CUDA) == 1, "");
23 static_assert(Petsc::util::integral_value(PETSC_DEVICE_HIP) == 2, "");
24 static_assert(Petsc::util::integral_value(PETSC_DEVICE_SYCL) == 3, "");
25 static_assert(Petsc::util::integral_value(PETSC_DEVICE_MAX) == 4, "");
26 const char *const PetscDeviceTypes[] = {"invalid", "cuda", "hip", "sycl", "max", "PetscDeviceType", "PETSC_DEVICE_", PETSC_NULLPTR};
27 
28 static_assert(Petsc::util::integral_value(PETSC_DEVICE_INIT_NONE) == 0, "");
29 static_assert(Petsc::util::integral_value(PETSC_DEVICE_INIT_LAZY) == 1, "");
30 static_assert(Petsc::util::integral_value(PETSC_DEVICE_INIT_EAGER) == 2, "");
31 const char *const PetscDeviceInitTypes[] = {"none", "lazy", "eager", "PetscDeviceInitType", "PETSC_DEVICE_INIT_", PETSC_NULLPTR};
32 static_assert(sizeof(PetscDeviceInitTypes) / sizeof(*PetscDeviceInitTypes) == 6, "Must change CUPMDevice<T>::initialize number of enum values in -device_enable_cupm to match!");
33 
34 const char *const PetscDeviceAttributes[] = {"shared_mem_per_block", "max", "PetscDeviceAttribute", "PETSC_DEVICE_ATTR_", nullptr};
35 
36 #define PETSC_DEVICE_CASE(IMPLS, func, ...) \
37   case PetscConcat_(PETSC_DEVICE_, IMPLS): { \
38     PetscCall(PetscConcat_(IMPLS, Device).func(__VA_ARGS__)); \
39   } break
40 
41 /*
42   Suppose you have:
43 
44   CUDADevice.myFunction(arg1,arg2)
45 
46   that you would like to conditionally define and call in a switch-case:
47 
48   switch(PetscDeviceType) {
49   #if PetscDefined(HAVE_CUDA)
50   case PETSC_DEVICE_CUDA: {
51     PetscCall(CUDADevice.myFunction(arg1,arg2));
52   } break;
53   #endif
54   }
55 
56   then calling this macro:
57 
58   PETSC_DEVICE_CASE_IF_PETSC_DEFINED(CUDA,myFunction,arg1,arg2)
59 
60   will expand to the following case statement:
61 
62   case PETSC_DEVICE_CUDA: {
63     PetscCall(CUDADevice.myFunction(arg1,arg2));
64   } break
65 
66   if PetscDefined(HAVE_CUDA) evaluates to 1, and expand to nothing otherwise
67 */
68 #define PETSC_DEVICE_CASE_IF_PETSC_DEFINED(IMPLS, func, ...) PetscIfPetscDefined(PetscConcat_(HAVE_, IMPLS), PETSC_DEVICE_CASE, PetscExpandToNothing)(IMPLS, func, __VA_ARGS__)
69 
70 /*@C
71   PetscDeviceCreate - Get a new handle for a particular device type
72 
73   Not Collective, Possibly Synchronous
74 
75   Input Parameters:
76 + type  - The type of PetscDevice
77 - devid - The numeric ID# of the device (pass PETSC_DECIDE to assign automatically)
78 
79   Output Parameter:
80 . device - The PetscDevice
81 
82   Notes:
83   This routine may initialize PetscDevice. If this is the case, this will most likely cause
84   some sort of device synchronization.
85 
86   devid is what you might pass to cudaSetDevice() for example.
87 
88   Level: beginner
89 
90 .seealso: `PetscDevice`, `PetscDeviceInitType`, `PetscDeviceInitialize()`,
91           `PetscDeviceInitialized()`, `PetscDeviceConfigure()`, `PetscDeviceView()`, `PetscDeviceDestroy()`
92 @*/
93 PetscErrorCode PetscDeviceCreate(PetscDeviceType type, PetscInt devid, PetscDevice *device) {
94   static PetscInt PetscDeviceCounter = 0;
95   PetscDevice     dev;
96 
97   PetscFunctionBegin;
98   PetscValidDeviceType(type, 1);
99   PetscValidPointer(device, 3);
100   PetscCall(PetscDeviceInitializePackage());
101   PetscCall(PetscNew(&dev));
102   dev->id     = PetscDeviceCounter++;
103   dev->type   = type;
104   dev->refcnt = 1;
105   /*
106     if you are adding a device, you also need to add it's initialization in
107     PetscDeviceInitializeTypeFromOptions_Private() below
108   */
109   switch (type) {
110     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(CUDA, getDevice, dev, devid);
111     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(HIP, getDevice, dev, devid);
112     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(SYCL, getDevice, dev, devid);
113   default:
114     /* in case the above macros expand to nothing this silences any unused variable warnings */
115     (void)(devid);
116     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]);
117   }
118   *device = dev;
119   PetscFunctionReturn(0);
120 }
121 
122 /*@C
123   PetscDeviceDestroy - Free a PetscDevice
124 
125   Not Collective, Asynchronous
126 
127   Input Parameter:
128 . device - The PetscDevice
129 
130   Level: beginner
131 
132 .seealso: `PetscDevice`, `PetscDeviceCreate()`, `PetscDeviceConfigure()`, `PetscDeviceView()`
133 @*/
134 PetscErrorCode PetscDeviceDestroy(PetscDevice *device) {
135   PetscFunctionBegin;
136   if (!*device) PetscFunctionReturn(0);
137   PetscValidDevice(*device, 1);
138   PetscCall(PetscDeviceDereference_Internal(*device));
139   if ((*device)->refcnt) {
140     *device = PETSC_NULLPTR;
141     PetscFunctionReturn(0);
142   }
143   PetscCall(PetscFree((*device)->data));
144   PetscCall(PetscFree(*device));
145   PetscFunctionReturn(0);
146 }
147 
148 /*@C
149   PetscDeviceConfigure - Configure a particular PetscDevice
150 
151   Not Collective, Asynchronous
152 
153   Input Parameter:
154 . device - The PetscDevice to configure
155 
156   Notes:
157   The user should not assume that this is a cheap operation
158 
159   Level: beginner
160 
161 .seealso: `PetscDevice`, `PetscDeviceCreate()`, `PetscDeviceView()`, `PetscDeviceDestroy()`
162 @*/
163 PetscErrorCode PetscDeviceConfigure(PetscDevice device) {
164   PetscFunctionBegin;
165   PetscValidDevice(device, 1);
166   if (PetscDefined(USE_DEBUG)) {
167     /*
168       if no available configuration is available, this cascades all the way down to default
169       and error
170     */
171     switch (device->type) {
172     case PETSC_DEVICE_CUDA:
173       if (PetscDefined(HAVE_CUDA)) break;
174     case PETSC_DEVICE_HIP:
175       if (PetscDefined(HAVE_HIP)) break;
176     case PETSC_DEVICE_SYCL:
177       if (PetscDefined(HAVE_SYCL)) break;
178     default: 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[device->type]);
179     }
180   }
181   PetscUseTypeMethod(device, configure);
182   PetscFunctionReturn(0);
183 }
184 
185 /*@C
186   PetscDeviceView - View a PetscDevice
187 
188   Collective on viewer, Asynchronous
189 
190   Input Parameters:
191 + device - The PetscDevice to view
192 - viewer - The PetscViewer to view the device with (NULL for PETSC_VIEWER_STDOUT_WORLD)
193 
194   Level: beginner
195 
196 .seealso: `PetscDevice`, `PetscDeviceCreate()`, `PetscDeviceConfigure()`, `PetscDeviceDestroy()`
197 @*/
198 PetscErrorCode PetscDeviceView(PetscDevice device, PetscViewer viewer) {
199   PetscFunctionBegin;
200   PetscValidDevice(device, 1);
201   if (!viewer) PetscCall(PetscViewerASCIIGetStdout(PETSC_COMM_WORLD, &viewer));
202   PetscValidHeaderSpecific(viewer, PETSC_VIEWER_CLASSID, 2);
203   PetscUseTypeMethod(device, view, viewer);
204   PetscFunctionReturn(0);
205 }
206 
207 /*@C
208   PetscDeviceGetDeviceId - Get the device id
209 
210   Not collective
211 
212   Input Parameter:
213 . device - The PetscDevice
214 
215   Output Parameter:
216 . id - The device id
217 
218   Level: beginner
219 
220 .seealso: `PetscDevice`, `PetscDeviceCreate()`, `PetscDeviceConfigure()`, `PetscDeviceDestroy()`
221 @*/
222 PetscErrorCode PetscDeviceGetDeviceId(PetscDevice device, PetscInt *id) {
223   PetscFunctionBegin;
224   PetscValidDevice(device, 1);
225   PetscValidIntPointer(id, 2);
226   *id = device->deviceId;
227   PetscFunctionReturn(0);
228 }
229 
230 static std::array<bool, PETSC_DEVICE_MAX>        initializedDevice = {};
231 static std::array<PetscDevice, PETSC_DEVICE_MAX> defaultDevices    = {};
232 static_assert(initializedDevice.size() == defaultDevices.size(), "");
233 
234 /*@C
235   PetscDeviceInitialize - Initialize PetscDevice
236 
237   Not Collective, Possibly Synchronous
238 
239   Input Parameter:
240 . type - The PetscDeviceType to initialize
241 
242   Notes:
243   Eagerly initializes the corresponding PetscDeviceType if needed.
244 
245   Level: beginner
246 
247 .seealso: `PetscDevice`, `PetscDeviceInitType`, `PetscDeviceInitialized()`, `PetscDeviceCreate()`, `PetscDeviceDestroy()`
248 @*/
249 PetscErrorCode PetscDeviceInitialize(PetscDeviceType type) {
250   PetscFunctionBegin;
251   PetscValidDeviceType(type, 1);
252   PetscCall(PetscDeviceInitializeDefaultDevice_Internal(type, PETSC_DECIDE));
253   PetscFunctionReturn(0);
254 }
255 
256 /*@C
257   PetscDeviceInitialized - Determines whether PetscDevice is initialized for a particular
258   PetscDeviceType
259 
260   Not Collective, Asynchronous
261 
262   Input Parameter:
263 . type - The PetscDeviceType to check
264 
265   Output Parameter:
266 . [return value] - PETSC_TRUE if type is initialized, PETSC_FALSE otherwise
267 
268   Notes:
269   If one has not configured PETSc for a particular PetscDeviceType then this routine will
270   return PETSC_FALSE for that PetscDeviceType.
271 
272   Level: beginner
273 
274 .seealso: `PetscDevice`, `PetscDeviceInitType`, `PetscDeviceInitialize()`, `PetscDeviceCreate()`, `PetscDeviceDestroy()`
275 @*/
276 PetscBool PetscDeviceInitialized(PetscDeviceType type) {
277   return static_cast<PetscBool>(PetscDeviceConfiguredFor_Internal(type) && initializedDevice[type]);
278 }
279 
280 /*@C
281   PetscDeviceGetAttribute - Query a particular attribute of a `PetscDevice`
282 
283   Not Collective, Asynchronous
284 
285   Input Parameters:
286 + device - The `PetscDevice`
287 - attr   - The attribute
288 
289   Output Parameter:
290 . value - The value of the attribute
291 
292   Notes:
293   Since different attributes are often different types `value` is a `void *` to accommodate
294   them all. The underlying type of the attribute is therefore included in the name of the
295   `PetscDeviceAttribute` reponsible for querying it. For example,
296   `PETSC_DEVICE_ATTR_SIZE_T_SHARED_MEM_PER_BLOCK` is of type `size_t`.
297 
298 .seealso: `PetscDeviceAtrtibute`, `PetscDeviceConfigure()`, `PetscDevice`
299 @*/
300 PetscErrorCode PetscDeviceGetAttribute(PetscDevice device, PetscDeviceAttribute attr, void *value) {
301   PetscFunctionBegin;
302   PetscValidDevice(device, 1);
303   PetscValidDeviceAttribute(attr, 2);
304   PetscValidPointer(value, 3);
305   PetscUseTypeMethod(device, getattribute, attr, value);
306   PetscFunctionReturn(0);
307 }
308 
309 /*
310   Actual intialization function; any functions claiming to initialize PetscDevice or
311   PetscDeviceContext will have to run through this one
312 */
313 PetscErrorCode PetscDeviceInitializeDefaultDevice_Internal(PetscDeviceType type, PetscInt defaultDeviceId) {
314   PetscFunctionBegin;
315   PetscValidDeviceType(type, 1);
316   if (PetscLikely(PetscDeviceInitialized(type))) PetscFunctionReturn(0);
317   PetscAssert(!defaultDevices[type], PETSC_COMM_SELF, PETSC_ERR_MEM, "Trying to overwrite existing default device of type %s", PetscDeviceTypes[type]);
318   PetscCall(PetscDeviceCreate(type, defaultDeviceId, &defaultDevices[type]));
319   PetscCall(PetscDeviceConfigure(defaultDevices[type]));
320   initializedDevice[type] = true;
321   PetscFunctionReturn(0);
322 }
323 
324 #if PetscDefined(USE_LOG)
325 PETSC_INTERN PetscErrorCode PetscLogInitialize(void);
326 #else
327 #define PetscLogInitialize() 0
328 #endif
329 
330 static PetscErrorCode PetscDeviceInitializeTypeFromOptions_Private(MPI_Comm comm, PetscDeviceType type, PetscInt defaultDeviceId, PetscBool defaultView, PetscDeviceInitType *defaultInitType) {
331   PetscFunctionBegin;
332   if (!PetscDeviceConfiguredFor_Internal(type)) {
333     PetscCall(PetscInfo(PETSC_NULLPTR, "PetscDeviceType %s not supported\n", PetscDeviceTypes[type]));
334     defaultDevices[type] = PETSC_NULLPTR;
335     PetscFunctionReturn(0);
336   }
337   PetscCall(PetscInfo(PETSC_NULLPTR, "PetscDeviceType %s supported, initializing\n", PetscDeviceTypes[type]));
338   /* ugly switch needed to pick the right global variable... could maybe do this as a union? */
339   switch (type) {
340     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(CUDA, initialize, comm, &defaultDeviceId, defaultInitType);
341     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(HIP, initialize, comm, &defaultDeviceId, defaultInitType);
342     PETSC_DEVICE_CASE_IF_PETSC_DEFINED(SYCL, initialize, comm, &defaultDeviceId, defaultInitType);
343   default: SETERRQ(comm, PETSC_ERR_PLIB, "PETSc was seemingly configured for PetscDeviceType %s but we've fallen through all cases in a switch", PetscDeviceTypes[type]);
344   }
345   /*
346     defaultInitType and defaultDeviceId now represent what the individual TYPES have decided to
347     initialize as
348   */
349   if (*defaultInitType == PETSC_DEVICE_INIT_EAGER) {
350     PetscCall(PetscLogInitialize());
351     PetscCall(PetscInfo(PETSC_NULLPTR, "Eagerly initializing %s PetscDevice\n", PetscDeviceTypes[type]));
352     PetscCall(PetscDeviceInitializeDefaultDevice_Internal(type, defaultDeviceId));
353     if (defaultView) {
354       PetscViewer vwr;
355 
356       PetscCall(PetscViewerASCIIGetStdout(comm, &vwr));
357       PetscCall(PetscDeviceView(defaultDevices[type], vwr));
358     }
359   }
360   PetscFunctionReturn(0);
361 }
362 
363 /* called from PetscFinalize() do not call yourself! */
364 static PetscErrorCode PetscDeviceFinalize_Private(void) {
365   PetscFunctionBegin;
366   if (PetscDefined(USE_DEBUG)) {
367     const auto PetscDeviceCheckAllDestroyedAfterFinalize = [] {
368       PetscFunctionBegin;
369       for (auto &&device : defaultDevices)
370         PetscCheck(!device, PETSC_COMM_WORLD, PETSC_ERR_COR, "Device of type '%s' had reference count %" PetscInt_FMT " and was not fully destroyed during PetscFinalize()", PetscDeviceTypes[device->type], device->refcnt);
371       PetscFunctionReturn(0);
372     };
373     /*
374       you might be thinking, why on earth are you registered yet another finalizer in a
375       function already called during PetscRegisterFinalizeAll()? If this seems stupid it's
376       because it is.
377 
378       The crux of the problem is that the initializer (and therefore the ~finalizer~) of
379       PetscDeviceContext is guaranteed to run after PetscDevice's. So if the global context had
380       a default PetscDevice attached, that PetscDevice will have a reference count >0 and hence
381       won't be destroyed yet. So we need to repeat the check that all devices have been
382       destroyed again ~after~ the global context is destroyed. In summary:
383 
384       1. This finalizer runs and destroys all devices, except it may not because the global
385          context may still hold a reference!
386       2. The global context finalizer runs and does the final reference count decrement
387          required, which actually destroys the held device.
388       3. Our newly added finalizer runs and checks that all is well.
389     */
390     PetscCall(PetscRegisterFinalize(PetscDeviceCheckAllDestroyedAfterFinalize));
391   }
392   for (auto &&device : defaultDevices) PetscCall(PetscDeviceDestroy(&device));
393   PetscCallCXX(initializedDevice.fill(false));
394   PetscFunctionReturn(0);
395 }
396 
397 /*
398   Begins the init proceeedings for the entire PetscDevice stack. there are 3 stages of
399   initialization types:
400 
401   1. defaultInitType - how does PetscDevice as a whole expect to initialize?
402   2. subTypeDefaultInitType - how does each PetscDevice implementation expect to initialize?
403      e.g. you may want to blanket disable PetscDevice init (and disable say Kokkos init), but
404      have all CUDA devices still initialize.
405 
406   All told the following happens:
407 
408   0. defaultInitType -> LAZY
409   1. Check for log_view/log_summary, if yes defaultInitType -> EAGER
410   2. PetscDevice initializes each sub type with deviceDefaultInitType.
411   2.1 Each enabled PetscDevice sub-type then does the above disable or view check in addition
412       to checking for specific device init. if view or specific device init
413       subTypeDefaultInitType -> EAGER. disabled once again overrides all.
414 */
415 PetscErrorCode PetscDeviceInitializeFromOptions_Internal(MPI_Comm comm) {
416   auto                defaultView                    = PETSC_FALSE;
417   auto                initializeDeviceContextEagerly = PETSC_FALSE;
418   auto                defaultDevice                  = PetscInt{PETSC_DECIDE};
419   auto                deviceContextInitDevice        = PETSC_DEVICE_DEFAULT;
420   PetscDeviceInitType defaultInitType;
421 
422   PetscFunctionBegin;
423   if (PetscDefined(USE_DEBUG)) {
424     int result;
425 
426     PetscCallMPI(MPI_Comm_compare(comm, PETSC_COMM_WORLD, &result));
427     /* in order to accurately assign ranks to gpus we need to get the MPI_Comm_rank of the
428      * global space */
429     if (PetscUnlikely(result != MPI_IDENT)) {
430       char name[MPI_MAX_OBJECT_NAME] = {};
431       int  len; /* unused */
432 
433       PetscCallMPI(MPI_Comm_get_name(comm, name, &len));
434       SETERRQ(comm, PETSC_ERR_MPI, "Default devices being initialized on MPI_Comm '%s' not PETSC_COMM_WORLD", name);
435     }
436   }
437   comm = PETSC_COMM_WORLD; /* from this point on we assume we're on PETSC_COMM_WORLD */
438   PetscCall(PetscRegisterFinalize(PetscDeviceFinalize_Private));
439 
440   {
441     PetscInt  initIdx = PETSC_DEVICE_INIT_LAZY;
442     PetscBool flg;
443 
444     PetscCall(PetscOptionsHasName(PETSC_NULLPTR, PETSC_NULLPTR, "-log_view_gpu_time", &flg));
445     if (flg) PetscCall(PetscLogGpuTime());
446 
447     /* ----------------------------------------------------------------------------------- */
448     /*                              Global PetscDevice Options                             */
449     /* ----------------------------------------------------------------------------------- */
450     PetscOptionsBegin(comm, PETSC_NULLPTR, "PetscDevice Options", "Sys");
451     PetscCall(PetscOptionsEList("-device_enable", "How (or whether) to initialize PetscDevices", "PetscDeviceInitializeFromOptions_Internal()", PetscDeviceInitTypes, 3, PetscDeviceInitTypes[initIdx], &initIdx, PETSC_NULLPTR));
452     PetscCall(PetscOptionsRangeInt("-device_select", "Which device to use. Pass " PetscStringize(PETSC_DECIDE) " to have PETSc decide or (given they exist) [0-NUM_DEVICE) for a specific device", "PetscDeviceCreate()", defaultDevice, &defaultDevice, PETSC_NULLPTR, PETSC_DECIDE, std::numeric_limits<int>::max()));
453     PetscCall(PetscOptionsBool("-device_view", "Display device information and assignments (forces eager initialization)", PETSC_NULLPTR, defaultView, &defaultView, &flg));
454     PetscOptionsEnd();
455 
456     if (initIdx == PETSC_DEVICE_INIT_NONE) {
457       /* disabled all device initialization if devices are globally disabled */
458       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 exlusive");
459       defaultView = PETSC_FALSE;
460     } else {
461       defaultView = static_cast<decltype(defaultView)>(defaultView && flg);
462       if (defaultView) initIdx = PETSC_DEVICE_INIT_EAGER;
463     }
464     defaultInitType = static_cast<decltype(defaultInitType)>(initIdx);
465   }
466   static_assert((PETSC_DEVICE_INVALID == 0) && (PETSC_DEVICE_MAX < std::numeric_limits<int>::max()), "");
467   for (int i = 1; i < PETSC_DEVICE_MAX; ++i) {
468     const auto deviceType = static_cast<PetscDeviceType>(i);
469     auto       initType   = defaultInitType;
470 
471     PetscCall(PetscDeviceInitializeTypeFromOptions_Private(comm, deviceType, defaultDevice, defaultView, &initType));
472     if (PetscDeviceConfiguredFor_Internal(deviceType) && (initType == PETSC_DEVICE_INIT_EAGER)) {
473       initializeDeviceContextEagerly = PETSC_TRUE;
474       deviceContextInitDevice        = deviceType;
475       PetscCall(PetscInfo(PETSC_NULLPTR, "PetscDevice %s set as default device type due to eager initialization\n", PetscDeviceTypes[deviceType]));
476     }
477   }
478   if (initializeDeviceContextEagerly) {
479     PetscDeviceContext dctx;
480 
481     /*
482       somewhat inefficient here as the device context is potentially fully set up twice (once
483       when retrieved then the second time if setfromoptions makes changes)
484     */
485     PetscCall(PetscInfo(PETSC_NULLPTR, "Eagerly initializing PetscDeviceContext with %s device\n", PetscDeviceTypes[deviceContextInitDevice]));
486     PetscCall(PetscDeviceContextSetRootDeviceType_Internal(deviceContextInitDevice));
487     PetscCall(PetscDeviceContextGetCurrentContext(&dctx));
488     PetscCall(PetscDeviceContextSetFromOptions(comm, "root_", dctx));
489     PetscCall(PetscDeviceContextSetUp(dctx));
490   }
491   PetscFunctionReturn(0);
492 }
493 
494 /* Get the default PetscDevice for a particular type and constructs them if lazily initialized. */
495 PetscErrorCode PetscDeviceGetDefaultForType_Internal(PetscDeviceType type, PetscDevice *device) {
496   PetscFunctionBegin;
497   PetscValidPointer(device, 2);
498   PetscCall(PetscDeviceInitialize(type));
499   *device = defaultDevices[type];
500   PetscFunctionReturn(0);
501 }
502