Skip to content

Commit 372eedf

Browse files
committed
added Qureg
1 parent 6980f41 commit 372eedf

File tree

10 files changed

+408
-11
lines changed

10 files changed

+408
-11
lines changed

quest/include/qureg.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,56 @@
55
#ifndef QUREG_H
66
#define QUREG_H
77

8+
#include "quest/include/types.h"
9+
#include "quest/include/environment.h"
810

11+
// enable invocation by both C and C++ binaries
12+
#ifdef __cplusplus
13+
extern "C" {
14+
#endif
15+
16+
17+
18+
typedef struct Qureg
19+
{
20+
// deployment configuration
21+
int isGpuAccelerated;
22+
int isDistributed;
23+
int isMultithreaded;
24+
25+
// distributed configuration
26+
int rank;
27+
int numNodes;
28+
int logNumNodes;
29+
30+
// dimension
31+
int isDensityMatrix;
32+
int numQubits;
33+
qindex numAmps;
34+
qindex numAmpsPerNode;
35+
qindex logNumAmpsPerNode;
36+
37+
// amplitudes in CPU and GPU memory
38+
qcomp* cpuAmps;
39+
qcomp* gpuAmps;
40+
41+
// communication buffer in CPU and GPU memory
42+
qcomp* cpuCommBuffer;
43+
qcomp* gpuCommBuffer;
44+
45+
} Qureg;
46+
47+
48+
49+
Qureg createCustomQureg(int numQubits, int isDensMatr, int useDistrib, int useGpuAccel, int useMultithread, QuESTEnv env);
50+
51+
void destroyQureg(Qureg qureg);
52+
53+
54+
55+
// end de-mangler
56+
#ifdef __cplusplus
57+
}
58+
#endif
959

1060
#endif // QUREG_H

quest/src/api/qureg.cpp

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,137 @@
11
/** @file
22
* API definitions for creating and managing Quregs, and automatically
33
* choosing their deployment modes.
4-
*/
4+
*/
5+
6+
#include "quest/include/qureg.h"
7+
#include "quest/include/environment.h"
8+
9+
#include "quest/src/core/validation.hpp"
10+
#include "quest/src/core/bitwise.hpp"
11+
#include "quest/src/cpu/cpu_config.hpp"
12+
#include "quest/src/gpu/gpu_config.hpp"
13+
14+
15+
16+
/*
17+
* PRIVATE INNER FUNCTIONS (C++)
18+
*/
19+
20+
21+
Qureg validateAndCreateCustomQureg(int numQubits, int isDensMatr, int useDistrib, int useGpuAccel, int useMultithread, QuESTEnv env, const char* caller) {
22+
23+
// bind deployment configuration
24+
Qureg qureg;
25+
qureg.isGpuAccelerated = useGpuAccel;
26+
qureg.isDistributed = useDistrib;
27+
qureg.isMultithreaded = useMultithread;
28+
29+
// if distributed, inherit config from env
30+
if (useDistrib) {
31+
qureg.rank = env.rank;
32+
qureg.numNodes = env.numNodes;
33+
qureg.logNumNodes = logBase2(env.numNodes);
34+
35+
// otherwise set config to single node
36+
} else {
37+
qureg.numNodes = 1;
38+
qureg.logNumNodes = 0;
39+
40+
// but still retain the env's potentially unique rank
41+
// because non-distributed quregs are still duplicated
42+
// between every node, and have duplicate processes.
43+
qureg.rank = env.rank;
44+
}
45+
46+
// set dimension
47+
qureg.isDensityMatrix = isDensMatr;
48+
qureg.numQubits = numQubits;
49+
qureg.numAmps = (isDensMatr)?
50+
powerOf2(2*numQubits) :
51+
powerOf2( numQubits);
52+
53+
// set dimension per node (even if not distributed)
54+
qureg.logNumAmpsPerNode = (isDensMatr)?
55+
(2*numQubits - qureg.logNumNodes) :
56+
( numQubits - qureg.logNumNodes);
57+
qureg.numAmpsPerNode = powerOf2(qureg.logNumAmpsPerNode);
58+
59+
// pre-set all pointers to NULL so post-alloc validation can safely free or ignore
60+
qureg.cpuAmps = NULL;
61+
qureg.gpuAmps = NULL;
62+
qureg.cpuCommBuffer = NULL;
63+
qureg.gpuCommBuffer = NULL;
64+
65+
// always allocate CPU memory
66+
qureg.cpuAmps = cpu_allocAmps(qureg.numAmpsPerNode);
67+
68+
// conditionally allocate CPU communication buffer (even if numNodes == 1)
69+
if (useDistrib)
70+
qureg.cpuCommBuffer = cpu_allocAmps(qureg.numAmpsPerNode);
71+
72+
// conditionally allocate GPU memory
73+
if (useGpuAccel)
74+
qureg.gpuAmps = gpu_allocAmps(qureg.numAmpsPerNode);
75+
76+
// conditionally allocate GPU communication buffer
77+
if (useGpuAccel && useDistrib)
78+
qureg.gpuCommBuffer = gpu_allocAmps(qureg.numAmpsPerNode);
79+
80+
// check all allocations succeeded (if any failed, validation frees all non-failures before throwing error)
81+
bool isNewQureg = true;
82+
validate_quregAllocs(qureg, isNewQureg, caller);
83+
84+
return qureg;
85+
}
86+
87+
88+
89+
/*
90+
* PUBLIC FUNCTIONS
91+
*/
92+
93+
94+
// enable invocation by both C and C++ binaries
95+
#ifdef __cplusplus
96+
extern "C" {
97+
#endif
98+
99+
100+
Qureg createCustomQureg(int numQubits, int isDensMatr, int useDistrib, int useGpuAccel, int useMultithread, QuESTEnv env) {
101+
validate_existingEnv(env, __func__);
102+
103+
return validateAndCreateCustomQureg(numQubits, isDensMatr, useDistrib, useGpuAccel, useMultithread, env, __func__);
104+
}
105+
106+
107+
void destroyQureg(Qureg qureg) {
108+
109+
// check below arrays are correctly allocated (if not, we do NOT free anything).
110+
// note this cannot detect whether the Qureg was already destroyed; see final comment
111+
bool isNewQureg = false;
112+
validate_quregAllocs(qureg, isNewQureg, __func__);
113+
114+
// free CPU memory
115+
cpu_deallocAmps(qureg.cpuAmps);
116+
117+
// free CPU communication buffer
118+
if (qureg.isDistributed)
119+
cpu_deallocAmps(qureg.cpuCommBuffer);
120+
121+
// free GPU memory
122+
if (qureg.isGpuAccelerated)
123+
gpu_deallocAmps(qureg.gpuAmps);
124+
125+
// free GPU communication buffer
126+
if (qureg.isGpuAccelerated && qureg.isDistributed)
127+
gpu_deallocAmps(qureg.gpuCommBuffer);
128+
129+
// cannot set freed fields to NULL because qureg
130+
// wasn't passed-by-reference, and isn't returned.
131+
}
132+
133+
134+
// end de-mangler
135+
#ifdef __cplusplus
136+
}
137+
#endif

quest/src/core/errors.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,13 @@ void error_gpuQueriedButGpuNotCompiled() {
8888

8989
raiseInternalError("A function attempted to query GPU properties but QuEST was not compiled with GPU acceleration enabled.");
9090
}
91+
92+
void error_gpuAllocButGpuNotCompiled() {
93+
94+
raiseInternalError("A function (most likely Qureg creation) attempted to allocate GPU memory but QuEST was not compiled with GPU acceleration enabled.");
95+
}
96+
97+
void error_gpuDeallocButGpuNotCompiled() {
98+
99+
raiseInternalError("A function (most likely Qureg creation) attempted to deallocate GPU memory but QuEST was not compiled with GPU acceleration enabled.");
100+
}

quest/src/core/errors.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ void error_cpuThreadsQueriedButEnvNotMultithreaded();
4848

4949
void error_gpuQueriedButGpuNotCompiled();
5050

51+
void error_gpuAllocButGpuNotCompiled();
52+
53+
void error_gpuDeallocButGpuNotCompiled();
54+
5155

5256

5357
#endif // ERRORS_HPP

quest/src/core/validation.cpp

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
* functions are satisfied, and otherwise throws a user-readable error.
44
*/
55

6-
#include "quest/include/environment.h"
76
#include "quest/include/modes.h"
7+
#include "quest/include/environment.h"
8+
#include "quest/include/qureg.h"
89

910
#include "quest/src/core/errors.hpp"
1011
#include "quest/src/core/bitwise.hpp"
@@ -57,6 +58,35 @@ namespace report {
5758
std::string CANNOT_DISTRIB_ENV_BETWEEN_NON_POW_2_NODES =
5859
"Cannot distribute QuEST between ${NUM_NODES} nodes; must use a power-of-2 number of nodes.";
5960

61+
62+
/*
63+
* QUREG CREATION
64+
*/
65+
66+
std::string FAILED_ALLOC_OF_CPU_AMPS =
67+
"Allocation of memory to store the CPU amplitudes failed.";
68+
69+
std::string FAILED_ALLOC_OF_GPU_AMPS =
70+
"Allocation of memory to store the GPU amplitudes failed.";
71+
72+
std::string FAILED_ALLOC_OF_CPU_COMM_BUFFER =
73+
"Allocation of memory for the distributed CPU communication buffer failed.";
74+
75+
std::string FAILED_ALLOC_OF_GPU_COMM_BUFFER =
76+
"Allocation of memory for the distributed GPU communication buffer failed.";
77+
78+
79+
std::string INVALID_ALLOC_OF_CPU_AMPS =
80+
"Invalid Qureg state. The CPU memory was seemingly unallocated.";
81+
82+
std::string INVALID_ALLOC_OF_CPU_COMM_BUFFER =
83+
"Invalid Qureg state. The distributed CPU communication buffer was seemingly unallocated.";
84+
85+
std::string INVALID_ALLOC_OF_GPU_AMPS =
86+
"Invalid Qureg state. The GPU memory was seemingly unallocated.";
87+
88+
std::string INVALID_ALLOC_OF_GPU_COMM_BUFFER =
89+
"Invalid Qureg state. The distributed GPU communication buffer was seemingly unallocated.";
6090
}
6191

6292

@@ -105,26 +135,35 @@ extern "C" {
105135
* UTILITIES
106136
*/
107137

108-
// list like {{"${X}", 1}, {"${Y}", -1}} with max-size signed int values to prevent overflows
109-
using tokenSubs = std::initializer_list<std::map<std::string, long long int>::value_type>;
138+
// map like "${X}" -> 5, with max-size signed int values to prevent overflows.
139+
// in C++11, these can be initialised with {{"${X}", 5}, ...}
140+
using tokenSubs = std::map<std::string, long long int>;
110141

111142
std::string getStringWithSubstitutedVars(std::string oldStr, tokenSubs varsAndVals) {
112143

113144
std::string newStr = oldStr;
114145

146+
// substitute every var,val pair into newStr
115147
for (auto varAndVal : varsAndVals) {
116148

149+
// unpack var -> val
117150
std::string var = std::get<0>(varAndVal);
118151
std::string val = std::to_string(std::get<1>(varAndVal));
119152

153+
// assert var is well-formed
120154
if (var[0] != '$' || var[1] != '{' || var.back() != '}' )
121155
error_validationMessageVarWasIllFormed(newStr, var);
122156

123-
size_t ind = newStr.find(var);
124-
if (ind == std::string::npos)
157+
// assert var appears at least once in string
158+
if (newStr.find(var) == std::string::npos)
125159
error_validationMessageVarNotSubstituted(newStr, var);
126160

127-
newStr = newStr.replace(ind, var.length(), val);
161+
// replace every occurrence of var with val
162+
size_t ind = newStr.find(var);
163+
while (ind != std::string::npos) {
164+
newStr = newStr.replace(ind, var.length(), val);
165+
ind = newStr.find(var, ind);
166+
}
128167
}
129168

130169
// assert there is no $ left in the strings
@@ -160,15 +199,16 @@ void validate_existingEnv(QuESTEnv env, const char* caller) {
160199

161200
void validate_envNotYetInit(const char* caller) {
162201

163-
// how will I do this?? Keep track of a singleton??
202+
// TODO:
203+
// consult a comm/ singleton?
164204
}
165205

166206
void validate_envDeploymentMode(int isDistrib, int isGpuAccel, int isMultithread, const char* caller) {
167207

168208
// deployment flags must be boolean or auto
169209
tokenSubs subs = {{"${AUTO_DEPLOYMENT_FLAG}", modeflag::USE_AUTO}};
170-
assertThat(isDistrib == 0 || isDistrib == 1 || isDistrib == modeflag::USE_AUTO, report::INVALID_OPTION_FOR_ENV_IS_DISTRIB, subs, caller);
171-
assertThat(isGpuAccel == 0 || isGpuAccel == 1 || isGpuAccel == modeflag::USE_AUTO, report::INVALID_OPTION_FOR_ENV_IS_GPU_ACCEL, subs, caller);
210+
assertThat(isDistrib == 0 || isDistrib == 1 || isDistrib == modeflag::USE_AUTO, report::INVALID_OPTION_FOR_ENV_IS_DISTRIB, subs, caller);
211+
assertThat(isGpuAccel == 0 || isGpuAccel == 1 || isGpuAccel == modeflag::USE_AUTO, report::INVALID_OPTION_FOR_ENV_IS_GPU_ACCEL, subs, caller);
172212
assertThat(isMultithread == 0 || isMultithread == 1 || isMultithread == modeflag::USE_AUTO, report::INVALID_OPTION_FOR_ENV_IS_MULTITHREAD, subs, caller);
173213

174214
// if a deployment is forced (i.e. not auto), assert that the backend binaries are compiled correctly
@@ -203,3 +243,64 @@ void validate_envDistributedBetweenPower2Nodes(int numNodes, const char* caller)
203243
assertThat(false, report::CANNOT_DISTRIB_ENV_BETWEEN_NON_POW_2_NODES, {{"${NUM_NODES}",numNodes}}, caller);
204244
}
205245

246+
247+
248+
/*
249+
* QUREG CREATION
250+
*/
251+
252+
void validate_quregAllocs(Qureg qureg, bool isNewQureg, const char* caller) {
253+
254+
// determine if all relevant arrays are correctly allocated (in order of report precedence)...
255+
bool isValid = true;
256+
std::string errMsg = "";
257+
258+
// CPU amps should always be allocated
259+
if (qureg.cpuAmps == NULL) {
260+
errMsg = (isNewQureg)?
261+
report::FAILED_ALLOC_OF_CPU_AMPS :
262+
report::INVALID_ALLOC_OF_CPU_AMPS;
263+
isValid = false;
264+
}
265+
266+
// CPU communication buffer is only allocated if distributed to >1 nodes
267+
else if (qureg.isDistributed && qureg.cpuCommBuffer == NULL) {
268+
errMsg = (isNewQureg)?
269+
report::FAILED_ALLOC_OF_CPU_COMM_BUFFER :
270+
report::INVALID_ALLOC_OF_CPU_COMM_BUFFER;
271+
isValid = false;
272+
}
273+
274+
// GPU amps are only allocated in GPU mode
275+
else if (qureg.isGpuAccelerated && qureg.gpuAmps == NULL) {
276+
errMsg = (isNewQureg)?
277+
report::FAILED_ALLOC_OF_GPU_AMPS :
278+
report::INVALID_ALLOC_OF_GPU_AMPS;
279+
isValid = false;
280+
}
281+
282+
// GPU communication buffer is only allocated in GPU mode, and when distributed to >1 nodes
283+
else if (qureg.isGpuAccelerated && qureg.isDistributed && qureg.gpuCommBuffer == NULL) {
284+
errMsg = (isNewQureg)?
285+
report::FAILED_ALLOC_OF_GPU_COMM_BUFFER :
286+
report::INVALID_ALLOC_OF_GPU_COMM_BUFFER;
287+
isValid = false;
288+
}
289+
290+
// if the qureg was only just (attemptedly) created, and one or more arrays were not correctly allocated...
291+
if (isNewQureg && !isValid) {
292+
293+
// then de-allocate all the successfully allocated arrays to avoid memory leak in subsequent error
294+
if (qureg.cpuAmps != NULL)
295+
cpu_deallocAmps(qureg.cpuAmps);
296+
if (qureg.cpuCommBuffer != NULL)
297+
cpu_deallocAmps(qureg.cpuCommBuffer);
298+
if (qureg.gpuAmps != NULL)
299+
gpu_deallocAmps(qureg.gpuAmps);
300+
if (qureg.gpuCommBuffer != NULL)
301+
gpu_deallocAmps(qureg.gpuCommBuffer);
302+
}
303+
304+
// throw error or continue
305+
assertThat(isValid, errMsg, caller);
306+
}

0 commit comments

Comments
 (0)