forked from QuEST-Kit/QuEST
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathenvironment.cpp
More file actions
513 lines (375 loc) · 16.6 KB
/
Copy pathenvironment.cpp
File metadata and controls
513 lines (375 loc) · 16.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
/** @file
* API definitions for managing QuESTEnv instances, which
* themselves control and query the deployment environment.
*
* @author Tyson Jones
*/
#include "quest/include/environment.h"
#include "quest/include/precision.h"
#include "quest/include/modes.h"
#include "quest/src/core/errors.hpp"
#include "quest/src/core/memory.hpp"
#include "quest/src/core/parser.hpp"
#include "quest/src/core/printer.hpp"
#include "quest/src/core/envvars.hpp"
#include "quest/src/core/autodeployer.hpp"
#include "quest/src/core/validation.hpp"
#include "quest/src/core/randomiser.hpp"
#include "quest/src/comm/comm_config.hpp"
#include "quest/src/cpu/cpu_config.hpp"
#include "quest/src/gpu/gpu_config.hpp"
#include <iostream>
#include <typeinfo>
#include <cstring>
#include <cstdio>
#include <string>
#include <thread>
#include <vector>
#include <tuple>
using std::string;
/*
* PRIVATE QUESTENV SINGLETON
*
* Global to this file, accessible to other files only through
* getQuESTEnv() which returns a copy, which also has const fields.
* The use of static ensures we never accidentally expose the "true"
* runtime single instance to other files. We allocate the env
* in heap memory (hence the pointer) so that we can defer
* initialisation of the const fields. The address being nullptr
* indicates the QuESTEnv is not currently initialised; perhaps never,
* or it was but has since been finalized.
*/
static QuESTEnv* globalEnvPtr = nullptr;
/*
* PRIVATE QUESTENV INITIALISATION HISTORY
*
* indicating whether QuEST has ever been finalized. This is important, since
* the QuEST environment can only ever be initialised once, and can never
* be re-initialised after finalisation, due to re-initialisation of MPI
* being undefined behaviour.
*/
static bool hasEnvBeenFinalized = false;
/*
* PRIVATE QUESTENV INITIALISATION INNER FUNCTIONS
*/
void validateAndInitCustomQuESTEnv(int useDistrib, int useGpuAccel, int useMultithread, const char* caller) {
// ensure that we are never re-initialising QuEST (even after finalize) because
// this leads to undefined behaviour in distributed mode, as per the MPI
validate_envNeverInit(globalEnvPtr != nullptr, hasEnvBeenFinalized, caller);
envvars_validateAndLoadEnvVars(caller);
validateconfig_setEpsilonToDefault();
// ensure the chosen deployment is compiled and supported by hardware.
// note that these error messages will be printed by every node because
// validation occurs before comm_init() below, so all processes spawned
// by mpirun believe they are each the main rank. This seems unavoidable.
validate_newEnvDeploymentMode(useDistrib, useGpuAccel, useMultithread, caller);
// overwrite deployments left as modeflag::USE_AUTO
autodep_chooseQuESTEnvDeployment(useDistrib, useGpuAccel, useMultithread);
// optionally initialise MPI; necessary before completing validation,
// and before any GPU initialisation and validation, since we will
// perform that specifically upon the MPI-process-bound GPU(s). Further,
// we can make sure validation errors are reported only by the root node.
if (useDistrib)
comm_init();
validate_newEnvDistributedBetweenPower2Nodes(caller);
/// @todo
/// consider immediately disabling MPI here if comm_numNodes() == 1
/// (also overwriting useDistrib = 0)
// bind MPI nodes to unique GPUs; even when not distributed,
// and before we have validated local GPUs are compatible
if (useGpuAccel)
gpu_bindLocalGPUsToNodes();
// consult environment variable to decide whether to allow GPU sharing
// (default = false) which informs whether below validation is triggered
bool permitGpuSharing = envvars_getWhetherGpuSharingIsPermitted();
// each MPI process should ordinarily use a unique GPU. This is
// critical when initializing cuQuantum so that we don't re-init
// cuStateVec on any paticular GPU (which can apparently cause a
// so-far-unwitnessed runtime error), but is otherwise essential
// for good performance. GPU sharing is useful for unit testing
// however permitting a single GPU to test CUDA+MPI deployment
if (useGpuAccel && useDistrib && ! permitGpuSharing)
validate_newEnvNodesEachHaveUniqueGpu(caller);
/// @todo
/// should we warn here if each machine contains
/// more GPUs than deployed MPI-processes (some GPUs idle)?
// cuQuantum is always used in GPU-accelerated envs when available
bool useCuQuantum = useGpuAccel && gpu_isCuQuantumCompiled();
if (useCuQuantum) {
validate_gpuIsCuQuantumCompatible(caller); // assesses above bound GPU
gpu_initCuQuantum();
}
// initialise RNG, used by measurements and random-state generation
rand_setSeedsToDefault();
// allocate space for the global QuESTEnv singleton (overwriting nullptr, unless malloc fails)
globalEnvPtr = (QuESTEnv*) malloc(sizeof(QuESTEnv));
// pedantically check that teeny tiny malloc just succeeded
if (globalEnvPtr == nullptr)
error_allocOfQuESTEnvFailed();
// bind deployment info to global instance
globalEnvPtr->isMultithreaded = useMultithread;
globalEnvPtr->isGpuAccelerated = useGpuAccel;
globalEnvPtr->isDistributed = useDistrib;
globalEnvPtr->isCuQuantumEnabled = useCuQuantum;
globalEnvPtr->isGpuSharingEnabled = permitGpuSharing;
// bind distributed info
globalEnvPtr->rank = (useDistrib)? comm_getRank() : 0;
globalEnvPtr->numNodes = (useDistrib)? comm_getNumNodes() : 1;
}
/*
* PRIVATE QUESTENV REPORTING INNER FUNCTIONS
*/
void printPrecisionInfo() {
/// @todo
/// - report MPI qcomp type?
/// - report CUDA qcomp type?
/// - report CUDA kernel qcomp type?
print_table(
"precision", {
{"qreal", printer_getQrealType() + " (" + printer_getMemoryWithUnitStr(sizeof(qreal)) + ")"},
/// @todo this is showing the backend C++ qcomp type, rather than that actually wieldable
/// by the user which could the C-type. No idea how to solve this however!
{"qcomp", printer_getQcompType() + " (" + printer_getMemoryWithUnitStr(sizeof(qcomp)) + ")"},
{"qindex", printer_getQindexType() + " (" + printer_getMemoryWithUnitStr(sizeof(qindex)) + ")"},
/// @todo this currently prints 0 when epsilon is inf (encoded by zero), i.e. disabled
{"validationEpsilon", printer_toStr(validateconfig_getEpsilon())},
});
}
void printCompilationInfo() {
print_table(
"compilation", {
{"isMpiCompiled", comm_isMpiCompiled()},
{"isGpuCompiled", gpu_isGpuCompiled()},
{"isOmpCompiled", cpu_isOpenmpCompiled()},
{"isCuQuantumCompiled", gpu_isCuQuantumCompiled()},
});
}
void printDeploymentInfo() {
print_table(
"deployment", {
{"isMpiEnabled", globalEnvPtr->isDistributed},
{"isGpuEnabled", globalEnvPtr->isGpuAccelerated},
{"isOmpEnabled", globalEnvPtr->isMultithreaded},
{"isCuQuantumEnabled", globalEnvPtr->isCuQuantumEnabled},
{"isGpuSharingEnabled", globalEnvPtr->isGpuSharingEnabled},
});
}
void printCpuInfo() {
using namespace printer_substrings;
// assume RAM is unknown unless it can be queried
string ram = un;
try {
ram = printer_getMemoryWithUnitStr(mem_tryGetLocalRamCapacityInBytes()) + pm;
} catch(mem::COULD_NOT_QUERY_RAM e){};
/// @todo
/// - CPU info e.g. speeds/caches?
print_table(
"cpu", {
{"numCpuCores", printer_toStr(std::thread::hardware_concurrency()) + pm},
{"numOmpProcs", (cpu_isOpenmpCompiled())? printer_toStr(cpu_getNumOpenmpProcessors()) + pm : na},
{"numOmpThrds", (cpu_isOpenmpCompiled())? printer_toStr(cpu_getAvailableNumThreads()) + pn : na},
{"cpuMemory", ram},
{"cpuMemoryFree", un},
});
}
void printGpuInfo() {
using namespace printer_substrings;
/// @todo below:
/// - GPU compute capability
/// - GPU #SVMs etc
// must not query any GPU facilities unless confirmed compiled and available
bool isComp = gpu_isGpuCompiled();
bool isGpu = isComp && gpu_isGpuAvailable();
print_table(
"gpu", {
{"numGpus", isComp? printer_toStr(gpu_getNumberOfLocalGpus()) : na},
{"gpuDirect", isGpu? printer_toStr(gpu_isDirectGpuCommPossible()) : na},
{"gpuMemPools", isGpu? printer_toStr(gpu_doesGpuSupportMemPools()) : na},
{"gpuMemory", isGpu? printer_getMemoryWithUnitStr(gpu_getTotalMemoryInBytes()) + pg : na},
{"gpuMemoryFree", isGpu? printer_getMemoryWithUnitStr(gpu_getCurrentAvailableMemoryInBytes()) + pg : na},
{"gpuCache", isGpu? printer_getMemoryWithUnitStr(gpu_getCacheMemoryInBytes()) + pg : na},
});
}
void printDistributionInfo() {
using namespace printer_substrings;
print_table(
"distribution", {
{"isMpiGpuAware", (comm_isMpiCompiled())? printer_toStr(comm_isMpiGpuAware()) : na},
{"numMpiNodes", printer_toStr(globalEnvPtr->numNodes)},
});
}
void printQuregSizeLimits(bool isDensMatr) {
using namespace printer_substrings;
// for brevity
int numNodes = globalEnvPtr->numNodes;
// by default, CPU limits are unknown (because memory query might fail)
string maxQbForCpu = un;
string maxQbForMpiCpu = un;
// max CPU registers are only determinable if RAM query succeeds
try {
qindex cpuMem = mem_tryGetLocalRamCapacityInBytes();
maxQbForCpu = printer_toStr(mem_getMaxNumQuregQubitsWhichCanFitInMemory(isDensMatr, 1, cpuMem));
// and the max MPI sizes are only relevant when env is distributed
if (globalEnvPtr->isDistributed)
maxQbForMpiCpu = printer_toStr(mem_getMaxNumQuregQubitsWhichCanFitInMemory(isDensMatr, numNodes, cpuMem));
// when MPI irrelevant, change their status from "unknown" to "N/A"
else
maxQbForMpiCpu = na;
// no problem if we can't query RAM; we simply don't report relevant limits
} catch(mem::COULD_NOT_QUERY_RAM e) {};
// GPU limits are default N/A because they're always determinable when relevant
string maxQbForGpu = na;
string maxQbForMpiGpu = na;
// max GPU registers only relevant if env is GPU-accelerated
if (globalEnvPtr->isGpuAccelerated) {
qindex gpuMem = gpu_getCurrentAvailableMemoryInBytes();
maxQbForGpu = printer_toStr(mem_getMaxNumQuregQubitsWhichCanFitInMemory(isDensMatr, 1, gpuMem));
// and the max MPI sizes are further only relevant when env is distributed
if (globalEnvPtr->isDistributed)
maxQbForMpiGpu = printer_toStr(mem_getMaxNumQuregQubitsWhichCanFitInMemory(isDensMatr, numNodes, gpuMem));
}
// tailor table title to type of Qureg
string prefix = (isDensMatr)? "density matrix" : "statevector";
string title = prefix + " limits";
print_table(
title, {
{"minQubitsForMpi", (numNodes>1)? printer_toStr(mem_getMinNumQubitsForDistribution(numNodes)) : na},
{"maxQubitsForCpu", maxQbForCpu},
{"maxQubitsForGpu", maxQbForGpu},
{"maxQubitsForMpiCpu", maxQbForMpiCpu},
{"maxQubitsForMpiGpu", maxQbForMpiGpu},
{"maxQubitsForMemOverflow", printer_toStr(mem_getMaxNumQuregQubitsBeforeGlobalMemSizeofOverflow(isDensMatr, numNodes))},
{"maxQubitsForIndOverflow", printer_toStr(mem_getMaxNumQuregQubitsBeforeIndexOverflow(isDensMatr))},
});
}
void printQuregAutoDeployments(bool isDensMatr) {
// build all table rows dynamically before print
std::vector<std::tuple<string, string>> rows;
// we will get auto-deployment for every possible number of qubits; silly but cheap and robust!
int useDistrib, useGpuAccel, useMulti;
int prevDistrib, prevGpuAccel, prevMulti;
// assume all deployments disabled for 1 qubit
prevDistrib = 0;
prevGpuAccel = 0;
prevMulti = 0;
// test to theoretically max #qubits, surpassing max that can fit in RAM and GPUs, because
// auto-deploy will still try to deploy there to (then subsequent validation will fail)
int maxQubits = mem_getMaxNumQuregQubitsBeforeGlobalMemSizeofOverflow(isDensMatr, globalEnvPtr->numNodes);
for (int numQubits=1; numQubits<maxQubits; numQubits++) {
// re-choose auto deployment
useDistrib = modeflag::USE_AUTO;
useGpuAccel = modeflag::USE_AUTO;
useMulti = modeflag::USE_AUTO;;
autodep_chooseQuregDeployment(numQubits, isDensMatr, useDistrib, useGpuAccel, useMulti, *globalEnvPtr);
// skip if deployments are unchanged
if (useDistrib == prevDistrib &&
useGpuAccel == prevGpuAccel &&
useMulti == prevMulti)
continue;
// else prepare string summarising the new deployments (trailing space is fine)
string value = "";
if (useMulti)
value += "[omp] "; // ordered by #qubits to attempt consistent printed columns
if (useGpuAccel)
value += "[gpu] ";
if (useDistrib)
value += "[mpi] ";
// log the #qubits of the deployment change
rows.push_back({printer_toStr(numQubits) + " qubits", value});
// skip subsequent qubits with the same deployments
prevDistrib = useDistrib;
prevGpuAccel = useGpuAccel;
prevMulti = useMulti;
}
// tailor table title to type of Qureg
string prefix = (isDensMatr)? "density matrix" : "statevector";
string title = prefix + " autodeployment";
rows.empty()?
print_table(title, "(no parallelisations available)"):
print_table(title, rows);
}
/*
* API FUNCTIONS
*/
// enable invocation by both C and C++ binaries
extern "C" {
void initCustomQuESTEnv(int useDistrib, int useGpuAccel, int useMultithread) {
validateAndInitCustomQuESTEnv(useDistrib, useGpuAccel, useMultithread, __func__);
}
void initQuESTEnv() {
validateAndInitCustomQuESTEnv(modeflag::USE_AUTO, modeflag::USE_AUTO, modeflag::USE_AUTO, __func__);
}
int isQuESTEnvInit() {
return (int) (globalEnvPtr != nullptr);
}
QuESTEnv getQuESTEnv() {
validate_envIsInit(__func__);
// returns a copy, so cheeky users calling memcpy() upon const struct still won't mutate
return *globalEnvPtr;
}
void finalizeQuESTEnv() {
validate_envIsInit(__func__);
// NOTE:
// calling this will not automatically
// free the memory of existing Quregs
if (globalEnvPtr->isGpuAccelerated)
gpu_clearCache(); // syncs first
if (globalEnvPtr->isGpuAccelerated && gpu_isCuQuantumCompiled())
gpu_finalizeCuQuantum();
if (globalEnvPtr->isDistributed) {
comm_sync();
comm_end();
}
// free global env's heap memory and flag it as unallocated
free(globalEnvPtr);
globalEnvPtr = nullptr;
// flag that the environment was finalised, to ensure it is never re-initialised
hasEnvBeenFinalized = true;
}
void syncQuESTEnv() {
validate_envIsInit(__func__);
if (globalEnvPtr->isGpuAccelerated)
gpu_sync();
if (globalEnvPtr->isDistributed)
comm_sync();
}
void reportQuESTEnv() {
validate_envIsInit(__func__);
validate_numReportedNewlinesAboveZero(__func__); // because trailing newline mandatory
/// @todo add function to write this output to file (useful for HPC debugging)
print_label("QuEST execution environment");
bool statevec = false;
bool densmatr = true;
// we attempt to report properties of available hardware facilities
// (e.g. number of CPU cores, number of GPUs) even if the environment is not
// making use of them, to inform the user how they might change deployment.
printPrecisionInfo();
printCompilationInfo();
printDeploymentInfo();
printCpuInfo();
printGpuInfo();
printDistributionInfo();
printQuregSizeLimits(statevec);
printQuregSizeLimits(densmatr);
printQuregAutoDeployments(statevec);
printQuregAutoDeployments(densmatr);
// exclude mandatory newline above
print_oneFewerNewlines();
}
void getEnvironmentString(char str[200]) {
validate_envIsInit(__func__);
QuESTEnv env = getQuESTEnv();
int numThreads = cpu_isOpenmpCompiled()? cpu_getAvailableNumThreads() : 1;
int cuQuantum = env.isGpuAccelerated && gpu_isCuQuantumCompiled();
int gpuDirect = env.isGpuAccelerated && gpu_isDirectGpuCommPossible();
snprintf(str, 200, "CUDA=%d OpenMP=%d MPI=%d threads=%d ranks=%d cuQuantum=%d gpuDirect=%d",
env.isGpuAccelerated,
env.isMultithreaded,
env.isDistributed,
numThreads,
env.numNodes,
cuQuantum,
gpuDirect);
}
// end de-mangler
}