Maestro 0.3.1
Unified interface for quantum circuit simulation
Loading...
Searching...
No Matches
DistributedGpuState.h
Go to the documentation of this file.
1// Statevector bridge shared by the local and MPI distributed plugins.
2#pragma once
3#if defined(__linux__) && defined(INCLUDED_BY_FACTORY)
4#include "Configuration.h"
7#include <charconv>
8#include <limits>
9#include <numeric>
10#include <sstream>
11
12namespace Simulators::Private {
13class DistributedGpuState : public ISimulator {
14 public:
15 explicit DistributedGpuState(bool mpi = false) : mpi(mpi) {}
16 void Initialize() override { InitializeData(nrQubits, nullptr); }
17 void InitializeState(size_t n,
18 std::vector<std::complex<double>>& values) override {
19 InitializeVector(n, values);
20 }
21 void InitializeState(size_t n, Eigen::VectorXcd& values) override {
22 InitializeVector(n, values);
23 }
24#ifndef NO_QISKIT_AER
25 void InitializeState(size_t n,
26 AER::Vector<std::complex<double>>& values) override {
27 InitializeVector(n, values);
28 }
29#endif
30 void Configure(const char* key, const char* value) override {
31 if (!key || !value)
32 throw std::invalid_argument("Null distributed GPU configuration");
33 const std::string k(key), v(value);
34 if (configuration.WasApplied(k, v)) return;
35 static const std::unordered_set<std::string> distributionKeys{
36 "distributed_devices",
37 "distributed_global_qubits",
38 "distributed_flags",
39 "distributed_backend",
40 "distributed_max_queued_gates",
41 "distributed_transfer_workspace_bytes",
42 "distributed_snapshot_storage",
43 "mpi_communicator",
44 "mpi_p2p_bits"};
45 if ((k.compare(0, 12, "distributed_") == 0 ||
46 k.compare(0, 4, "mpi_") == 0) &&
47 !distributionKeys.count(k))
48 throw std::invalid_argument("Unknown distributed GPU option: " + k);
49 if (!mpi && k.compare(0, 4, "mpi_") == 0)
50 throw std::invalid_argument("MPI options require DistributedMpiGpu");
51 if (k == "method" && v != "statevector")
52 throw std::invalid_argument(
53 "Distributed GPU simulators currently support only statevector");
54 const bool allocationOption =
55 k == "gpu_device" || k == "precision" || k == "use_double_precision" ||
56 k.compare(0, 12, "distributed_") == 0 || k.compare(0, 4, "mpi_") == 0;
57 if (state && allocationOption)
58 throw std::logic_error(
59 "Clear the distributed GPU state before changing allocation "
60 "settings");
61 if (k == "gpu_device") Configuration::ParseGpuDevice(v);
62 if (k == "seed") {
63 auto seed = ParseUnsigned(v);
64 if (state) state->SetSeed(seed);
65 }
66 if (k == "precision" && v != "single" && v != "double")
67 throw std::invalid_argument("precision must be single or double");
68 if (k == "use_double_precision" && v != "0" && v != "1" && v != "false" &&
69 v != "true")
70 throw std::invalid_argument("use_double_precision must be a boolean");
71 if (k == "distributed_devices" || k == "distributed_global_qubits")
72 ParseList(v);
73 if (k == "distributed_flags" || k == "distributed_max_queued_gates" ||
74 k == "distributed_transfer_workspace_bytes" || k == "mpi_p2p_bits")
75 ParseUnsigned(v);
76 if (k == "mpi_communicator") ParseCommunicator(v);
77 if (k == "distributed_snapshot_storage" && v != "host" && v != "gpu")
78 throw std::invalid_argument(
79 "distributed_snapshot_storage must be host or gpu");
80 if (k == "distributed_flags" && ParseUnsigned(v) > 15)
81 throw std::invalid_argument("Unsupported distribution flags");
82 if (k == "distributed_max_queued_gates" &&
83 (ParseUnsigned(v) < 1 || ParseUnsigned(v) > 65536))
84 throw std::invalid_argument("Queue size must be 1..65536");
85 if (k == "mpi_p2p_bits" && ParseUnsigned(v) > 5)
86 throw std::invalid_argument("mpi_p2p_bits must be 0..5");
87 if (k == "distributed_backend" && v != "ex" && v != "conventional")
88 throw std::invalid_argument(
89 "distributed_backend must be ex or conventional");
90 if (mpi && k == "distributed_backend" && v != "ex")
91 throw std::invalid_argument("MPI requires the Ex backend");
92 configuration.SetConfiguration(k, v);
93 }
94 std::string GetConfiguration(const char* key) const override {
95 const std::string k(key);
96 if (k == "method") return "statevector";
97 if (state && (k == "distributed_shard_devices" ||
98 k == "distributed_configured_global_qubits" ||
99 k == "distributed_qubit_layout")) {
100 std::vector<int32_t> values(k == "distributed_qubit_layout" ? nrQubits
101 : 32);
102 int count = k == "distributed_shard_devices"
103 ? state->GetShardDevices(values.data(), values.size())
104 : k == "distributed_configured_global_qubits"
105 ? state->GetGlobalQubits(values.data(), values.size())
106 : state->GetQubitLayout(values.data(), values.size());
107 values.resize(count);
108 return Join(values);
109 }
110 return configuration.GetConfiguration(key);
111 }
112 const std::unordered_map<std::string, std::string>& GetConfigMap()
113 const override {
114 return configuration.GetConfigMap();
115 }
116 size_t AllocateQubits(size_t count) override {
117 if (state)
118 throw std::logic_error("Clear before allocating distributed GPU qubits");
119 if (count >= 63 || nrQubits >= 63 - count)
120 throw std::invalid_argument(
121 "Distributed GPU requires fewer than 63 qubits");
122 auto old = nrQubits;
123 nrQubits += count;
124 return old;
125 }
126 size_t GetNumberOfQubits() const override { return nrQubits; }
127 void Clear() override {
128 state.reset();
129 nrQubits = 0;
130 }
131 void Reset() override { Native().Reset(); }
132 size_t Measure(const Types::qubits_vector& qubits) override {
133 auto bits = MeasureMany(qubits);
134 size_t result = 0;
135 for (size_t i = 0; i < bits.size(); ++i)
136 if (bits[i]) result |= size_t{1} << i;
137 return result;
138 }
139 std::vector<bool> MeasureMany(const Types::qubits_vector& qubits) override {
140 auto qb = Qubits(qubits);
141 if (qb.empty()) return {};
142 std::vector<int> values(qb.size());
143 Native().MeasureQubitsCollapse(qb.data(), values.data(), values.size());
144 NotifyObservers(qubits);
145 return std::vector<bool>(values.begin(), values.end());
146 }
147 void ApplyReset(const Types::qubits_vector& qubits) override {
148 auto bits = MeasureMany(qubits);
149 for (size_t i = 0; i < qubits.size(); ++i)
150 if (bits[i]) ApplyX(qubits[i]);
151 }
152 double Probability(Types::qubit_t outcome) override {
153 return Native().BasisStateProbability(outcome);
154 }
155 std::complex<double> Amplitude(Types::qubit_t outcome) override {
156 double real, imag;
157 Native().Amplitude(outcome, &real, &imag);
158 return {real, imag};
159 }
160 std::complex<double> AmplitudeRaw(Types::qubit_t outcome) override {
161 return Amplitude(outcome);
162 }
163 std::complex<double> ProjectOnZero() override { return Amplitude(0); }
164 std::vector<double> AllProbabilities() override {
165 std::vector<double> values(size_t{1} << nrQubits);
166 Native().AllProbabilities(values.data());
167 return values;
168 }
169 std::vector<double> Probabilities(
170 const Types::qubits_vector& outcomes) override {
171 std::vector<double> values;
172 for (auto outcome : outcomes) values.push_back(Probability(outcome));
173 return values;
174 }
175 std::unordered_map<Types::qubit_t, Types::qubit_t> SampleCounts(
176 const Types::qubits_vector& qubits, size_t shots = 1000) override {
177 auto qb = Qubits(qubits);
178 std::unordered_map<Types::qubit_t, Types::qubit_t> result;
179 if (qb.empty() || !shots) return result;
180 if (shots > std::numeric_limits<unsigned>::max())
181 throw std::invalid_argument("Too many GPU samples");
182 std::vector<long> samples(shots);
183 Native().Sample(shots, samples.data(), qb.size(), qb.data());
184 for (auto sample : samples) ++result[static_cast<Types::qubit_t>(sample)];
185 return result;
186 }
187 std::unordered_map<std::vector<bool>, Types::qubit_t> SampleCountsMany(
188 const Types::qubits_vector& qubits, size_t shots = 1000) override {
189 std::unordered_map<std::vector<bool>, Types::qubit_t> result;
190 for (const auto& [outcome, count] : SampleCounts(qubits, shots)) {
191 std::vector<bool> bits(qubits.size());
192 for (size_t i = 0; i < bits.size(); ++i) bits[i] = (outcome >> i) & 1;
193 result[bits] += count;
194 }
195 return result;
196 }
197 double ExpectationValue(const std::string& pauli) override {
198 return Native().ExpectationValue(pauli.c_str(), pauli.size());
199 }
200 SimulatorType GetType() const override {
201 return mpi ? SimulatorType::kDistMpiGpuSim : SimulatorType::kDistGpuSim;
202 }
203 SimulationType GetSimulationType() const override {
204 return SimulationType::kStatevector;
205 }
206 int GetGpuDevice() const override {
207 return state ? state->GetStateVectorGpuId() : -1;
208 }
209 void Flush() override { Native().Synchronize(); }
210 void SaveStateToInternalDestructive() override {
211 Native().SaveStateDestructive();
212 }
214 Native().RestoreStateFreeSaved();
215 }
216 void SaveState() override {
217 if (configuration.GetConfiguration("distributed_snapshot_storage") ==
218 "host")
219 Native().SaveStateToHost();
220 else
221 Native().SaveState();
222 }
223 void RestoreState() override { Native().RestoreStateNoFreeSaved(); }
224 void SetMultithreading(bool = true) override {}
225 bool GetMultithreading() const override { return !mpi; }
226 bool IsQcsim() const override { return false; }
228 return Native().MeasureAllQubitsNoCollapse();
229 }
230 std::vector<bool> MeasureNoCollapseMany() override {
231 auto outcome = MeasureNoCollapse();
232 std::vector<bool> bits(nrQubits);
233 for (size_t i = 0; i < bits.size(); ++i) bits[i] = (outcome >> i) & 1;
234 return bits;
235 }
236
237 protected:
238 int QubitIndex(Types::qubit_t q) const {
239 if (q >= nrQubits)
240 throw std::out_of_range("Distributed GPU qubit is out of range");
241 return static_cast<int>(q);
242 }
243 DistributedGpuLibStateVectorSim& Native() const {
244 if (!state)
245 throw std::logic_error("Distributed GPU state is not initialized");
246 return *state;
247 }
248 static uint64_t ParseUnsigned(const std::string& v) {
249 uint64_t result = 0;
250 auto parsed = std::from_chars(v.data(), v.data() + v.size(), result);
251 if (parsed.ec != std::errc() || parsed.ptr != v.data() + v.size())
252 throw std::invalid_argument("Expected an unsigned integer: " + v);
253 return result;
254 }
255 static int64_t ParseCommunicator(const std::string& value) {
256 size_t used = 0;
257 const auto handle = std::stoll(value, &used);
258 if (used != value.size())
259 throw std::invalid_argument("Invalid MPI communicator handle");
260 return handle;
261 }
262 static int ParseInt(const std::string& v) {
263 int result = 0;
264 auto parsed = std::from_chars(v.data(), v.data() + v.size(), result);
265 if (parsed.ec != std::errc() || parsed.ptr != v.data() + v.size())
266 throw std::invalid_argument("Expected an integer: " + v);
267 return result;
268 }
269 static std::vector<int32_t> ParseList(const std::string& value) {
270 std::vector<int32_t> values;
271 if (value.empty()) return values;
272 size_t begin = 0;
273 do {
274 const auto end = value.find(',', begin);
275 int item = ParseInt(
276 value.substr(begin, end == std::string::npos ? end : end - begin));
277 if (item < 0)
278 throw std::invalid_argument("Distributed indices must be nonnegative");
279 values.push_back(item);
280 if (end == std::string::npos) break;
281 begin = end + 1;
282 } while (true);
283 return values;
284 }
285 static std::string Join(const std::vector<int32_t>& values) {
286 std::string result;
287 for (auto v : values) {
288 if (!result.empty()) result += ',';
289 result += std::to_string(v);
290 }
291 return result;
292 }
293 std::vector<int> Qubits(const Types::qubits_vector& qubits) const {
294 if (qubits.size() > nrQubits)
295 throw std::invalid_argument("Too many measured qubits");
296 std::vector<int> values;
297 std::unordered_set<Types::qubit_t> seen;
298 for (auto q : qubits) {
299 if (q >= nrQubits || !seen.insert(q).second)
300 throw std::invalid_argument("Invalid or duplicate qubit");
301 values.push_back(static_cast<int>(q));
302 }
303 return values;
304 }
305 uint64_t Option(const char* key, uint64_t fallback) const {
306 return configuration.IsSet(key)
307 ? ParseUnsigned(configuration.GetConfiguration(key))
308 : fallback;
309 }
310 template <class V>
311 void InitializeVector(size_t n, V& values) {
312 if (!n || n >= 63 || static_cast<size_t>(values.size()) != (size_t{1} << n))
313 throw std::invalid_argument("Statevector length must equal 2^num_qubits");
314 InitializeData(n, reinterpret_cast<const double*>(values.data()));
315 }
316 void InitializeData(size_t n, const double* values) {
317 if (state)
318 throw std::logic_error(
319 "Clear before initializing a distributed GPU state again");
320 if (!n || n >= 63)
321 throw std::invalid_argument("Distributed GPU requires 1..62 qubits");
322 int device = configuration.IsSet("gpu_device")
323 ? Configuration::ParseGpuDevice(
324 configuration.GetConfiguration("gpu_device"))
325 : 0;
326 auto devices =
327 ParseList(configuration.GetConfiguration("distributed_devices"));
328 auto globals =
329 ParseList(configuration.GetConfiguration("distributed_global_qubits"));
330 std::unique_ptr<DistributedGpuLibStateVectorSim> next;
331 if (mpi) {
332 auto lib = DistributedMpiGpuLibrary::GetInstance();
333 DistributedMpiGpuLibrary::Communicator descriptor{
334 sizeof(DistributedMpiGpuLibrary::Communicator), 0, 0};
335 const DistributedMpiGpuLibrary::Communicator* comm = nullptr;
336 if (configuration.IsSet("mpi_communicator")) {
337 descriptor.fortran_handle = ParseCommunicator(
338 configuration.GetConfiguration("mpi_communicator"));
339 comm = &descriptor;
340 }
341 const auto info = lib->GetRuntimeInfo(comm);
342 if (!devices.empty() && devices.size() != static_cast<size_t>(info.size))
343 throw std::invalid_argument(
344 "MPI distributed_devices must contain one ordinal per rank");
345 if (!configuration.IsSet("gpu_device"))
346 device = devices.empty() ? info.default_device : devices[info.rank];
347 if (devices.empty()) {
348 devices.resize(info.size);
349 lib->GatherDevices(comm, device, devices);
350 }
351 next = std::make_unique<DistributedMpiGpuLibStateVectorSim>(
352 lib, comm, device, Option("mpi_p2p_bits", 0));
353 } else {
354 auto lib = DistributedGpuLibrary::GetInstance();
355 if (devices.empty()) {
356 if (configuration.IsSet("gpu_device"))
357 devices.push_back(device);
358 else {
359 lib->RequireLoaded();
360 const int count = lib->GetGpuDeviceCount();
361 if (count <= 0) lib->Fail("GetGpuDeviceCount");
362 int shards = 1;
363 while (shards < 32 && shards * 2 <= count &&
364 static_cast<uint64_t>(shards * 2) <= (uint64_t{1} << (n - 1)))
365 shards *= 2;
366 for (int i = 0; i < shards; ++i) devices.push_back(i);
367 }
368 }
369 const int backend = configuration.GetConfiguration(
370 "distributed_backend") == "conventional"
371 ? 0
372 : 1;
373 next = std::make_unique<DistributedGpuLibStateVectorSim>(
374 lib, lib->CreateNative(device, backend));
375 }
376 if (devices.empty() || devices.size() > 32 ||
377 (devices.size() & (devices.size() - 1)))
378 throw std::invalid_argument(
379 "distributed_devices must contain a power of two shards (1..32)");
380 size_t globalBits = 0;
381 while ((size_t{1} << globalBits) < devices.size()) ++globalBits;
382 if (globalBits >= n)
383 throw std::invalid_argument(
384 "Distribution requires at least one local qubit");
385 if (!configuration.IsSet("distributed_global_qubits"))
386 for (size_t i = 0; i < globalBits; ++i) globals.push_back(i);
387 DistributedGpuApi::MgdDistributionConfig dist{
388 sizeof(dist),
389 static_cast<uint32_t>(Option("distributed_flags", 0)),
390 static_cast<uint32_t>(globals.size()),
391 globals.data(),
392 static_cast<uint32_t>(devices.size()),
393 devices.data()};
394 next->ConfigureDistribution(&dist);
395 const bool useDouble =
396 configuration.IsSet("precision")
397 ? configuration.GetConfiguration("precision") == "double"
398 : (configuration.GetConfiguration("use_double_precision") == "1" ||
399 configuration.GetConfiguration("use_double_precision") ==
400 "true");
401 next->SetDataType(useDouble ? 1 : 0);
402 if (configuration.IsSet("distributed_max_queued_gates") ||
403 configuration.IsSet("distributed_transfer_workspace_bytes")) {
404 DistributedGpuApi::MgdExExecutionConfig execution{
405 sizeof(execution),
406 static_cast<uint32_t>(Option("distributed_max_queued_gates", 1024)),
407 Option("distributed_transfer_workspace_bytes", 16ULL * 1024 * 1024)};
408 next->SetExExecutionConfig(&execution);
409 }
410 // Identical explicit/default seeds keep MPI rank streams aligned.
411 if (configuration.IsSet("seed") || mpi) next->SetSeed(Option("seed", 0));
412 if (values)
413 next->CreateWithState(n, values);
414 else
415 next->Create(n);
416 nrQubits = n;
417 state = std::move(next);
418 }
419 bool mpi;
420 size_t nrQubits = 0;
421 Configuration configuration;
422 std::unique_ptr<DistributedGpuLibStateVectorSim> state;
423};
424class DistributedMpiGpuState : public DistributedGpuState {
425 public:
426 DistributedMpiGpuState() : DistributedGpuState(true) {}
427};
428} // namespace Simulators::Private
429#endif
double Probability(void *sim, unsigned long long int outcome)
char * GetConfiguration(void *sim, const char *key)
int RestoreState(void *sim)
int ApplyReset(void *sim, const unsigned long int *qubits, unsigned long int nrQubits)
int ApplyX(void *sim, int qubit)
unsigned long int AllocateQubits(void *sim, unsigned long int nrQubits)
unsigned long int GetNumberOfQubits(void *sim)
double * AllProbabilities(void *sim)
unsigned long long int MeasureNoCollapse(void *sim)
int GetMultithreading(void *sim)
unsigned long long int Measure(void *sim, const unsigned long int *qubits, unsigned long int nrQubits)
double * Amplitude(void *sim, unsigned long long int outcome)
double * Probabilities(void *sim, const unsigned long long int *qubits, unsigned long int nrQubits)
int SetMultithreading(void *sim, int multithreading)
int SaveStateToInternalDestructive(void *sim)
int GetSimulationType(void *sim)
unsigned long long int * SampleCounts(void *sim, const unsigned long long int *qubits, unsigned long int nrQubits, unsigned long int shots)
int RestoreInternalDestructiveSavedState(void *sim)
int IsQcsim(void *sim)
int SaveState(void *sim)
SimulationType
The type of simulation.
Definition State.h:98
SimulatorType
The type of simulator.
Definition State.h:72
std::vector< qubit_t > qubits_vector
The type of a vector of qubits.
Definition Types.h:22
uint_fast64_t qubit_t
The type of a qubit.
Definition Types.h:21