3#if defined(__linux__) && defined(INCLUDED_BY_FACTORY)
12namespace Simulators::Private {
13class DistributedGpuState :
public ISimulator {
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);
21 void InitializeState(
size_t n, Eigen::VectorXcd& values)
override {
22 InitializeVector(n, values);
25 void InitializeState(
size_t n,
26 AER::Vector<std::complex<double>>& values)
override {
27 InitializeVector(n, values);
30 void Configure(
const char* key,
const char* value)
override {
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",
39 "distributed_backend",
40 "distributed_max_queued_gates",
41 "distributed_transfer_workspace_bytes",
42 "distributed_snapshot_storage",
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 "
61 if (k ==
"gpu_device") Configuration::ParseGpuDevice(v);
63 auto seed = ParseUnsigned(v);
64 if (state) state->SetSeed(seed);
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" &&
70 throw std::invalid_argument(
"use_double_precision must be a boolean");
71 if (k ==
"distributed_devices" || k ==
"distributed_global_qubits")
73 if (k ==
"distributed_flags" || k ==
"distributed_max_queued_gates" ||
74 k ==
"distributed_transfer_workspace_bytes" || k ==
"mpi_p2p_bits")
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);
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
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);
110 return configuration.GetConfiguration(key);
112 const std::unordered_map<std::string, std::string>& GetConfigMap()
114 return configuration.GetConfigMap();
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");
127 void Clear()
override {
131 void Reset()
override { Native().Reset(); }
133 auto bits = MeasureMany(qubits);
135 for (
size_t i = 0; i < bits.size(); ++i)
136 if (bits[i]) result |=
size_t{1} << i;
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());
148 auto bits = MeasureMany(qubits);
149 for (
size_t i = 0; i < qubits.size(); ++i)
150 if (bits[i])
ApplyX(qubits[i]);
153 return Native().BasisStateProbability(outcome);
157 Native().Amplitude(outcome, &real, &imag);
160 std::complex<double> AmplitudeRaw(
Types::qubit_t outcome)
override {
163 std::complex<double> ProjectOnZero()
override {
return Amplitude(0); }
165 std::vector<double> values(
size_t{1} << nrQubits);
166 Native().AllProbabilities(values.data());
171 std::vector<double> values;
172 for (
auto outcome : outcomes) values.push_back(
Probability(outcome));
175 std::unordered_map<Types::qubit_t, Types::qubit_t>
SampleCounts(
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)];
187 std::unordered_map<std::vector<bool>,
Types::qubit_t> SampleCountsMany(
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;
197 double ExpectationValue(
const std::string& pauli)
override {
198 return Native().ExpectationValue(pauli.c_str(), pauli.size());
201 return mpi ? SimulatorType::kDistMpiGpuSim : SimulatorType::kDistGpuSim;
204 return SimulationType::kStatevector;
206 int GetGpuDevice()
const override {
207 return state ? state->GetStateVectorGpuId() : -1;
209 void Flush()
override { Native().Synchronize(); }
211 Native().SaveStateDestructive();
214 Native().RestoreStateFreeSaved();
217 if (configuration.GetConfiguration(
"distributed_snapshot_storage") ==
219 Native().SaveStateToHost();
221 Native().SaveState();
223 void RestoreState()
override { Native().RestoreStateNoFreeSaved(); }
226 bool IsQcsim()
const override {
return false; }
228 return Native().MeasureAllQubitsNoCollapse();
230 std::vector<bool> MeasureNoCollapseMany()
override {
232 std::vector<bool> bits(nrQubits);
233 for (
size_t i = 0; i < bits.size(); ++i) bits[i] = (outcome >> i) & 1;
240 throw std::out_of_range(
"Distributed GPU qubit is out of range");
241 return static_cast<int>(q);
243 DistributedGpuLibStateVectorSim& Native()
const {
245 throw std::logic_error(
"Distributed GPU state is not initialized");
248 static uint64_t ParseUnsigned(
const std::string& v) {
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);
255 static int64_t ParseCommunicator(
const std::string& value) {
257 const auto handle = std::stoll(value, &used);
258 if (used != value.size())
259 throw std::invalid_argument(
"Invalid MPI communicator handle");
262 static int ParseInt(
const std::string& v) {
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);
269 static std::vector<int32_t> ParseList(
const std::string& value) {
270 std::vector<int32_t> values;
271 if (value.empty())
return values;
274 const auto end = value.find(
',', begin);
276 value.substr(begin, end == std::string::npos ? end : end - begin));
278 throw std::invalid_argument(
"Distributed indices must be nonnegative");
279 values.push_back(item);
280 if (end == std::string::npos)
break;
285 static std::string Join(
const std::vector<int32_t>& values) {
287 for (
auto v : values) {
288 if (!result.empty()) result +=
',';
289 result += std::to_string(v);
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));
305 uint64_t Option(
const char* key, uint64_t fallback)
const {
306 return configuration.IsSet(key)
307 ? ParseUnsigned(configuration.GetConfiguration(key))
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()));
316 void InitializeData(
size_t n,
const double* values) {
318 throw std::logic_error(
319 "Clear before initializing a distributed GPU state again");
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"))
327 ParseList(configuration.GetConfiguration(
"distributed_devices"));
329 ParseList(configuration.GetConfiguration(
"distributed_global_qubits"));
330 std::unique_ptr<DistributedGpuLibStateVectorSim> next;
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"));
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);
351 next = std::make_unique<DistributedMpiGpuLibStateVectorSim>(
352 lib, comm, device, Option(
"mpi_p2p_bits", 0));
354 auto lib = DistributedGpuLibrary::GetInstance();
355 if (devices.empty()) {
356 if (configuration.IsSet(
"gpu_device"))
357 devices.push_back(device);
359 lib->RequireLoaded();
360 const int count = lib->GetGpuDeviceCount();
361 if (count <= 0) lib->Fail(
"GetGpuDeviceCount");
363 while (shards < 32 && shards * 2 <= count &&
364 static_cast<uint64_t
>(shards * 2) <= (uint64_t{1} << (n - 1)))
366 for (
int i = 0; i < shards; ++i) devices.push_back(i);
369 const int backend = configuration.GetConfiguration(
370 "distributed_backend") ==
"conventional"
373 next = std::make_unique<DistributedGpuLibStateVectorSim>(
374 lib, lib->CreateNative(device, backend));
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;
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{
389 static_cast<uint32_t
>(Option(
"distributed_flags", 0)),
390 static_cast<uint32_t
>(globals.size()),
392 static_cast<uint32_t
>(devices.size()),
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") ==
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{
406 static_cast<uint32_t
>(Option(
"distributed_max_queued_gates", 1024)),
407 Option(
"distributed_transfer_workspace_bytes", 16ULL * 1024 * 1024)};
408 next->SetExExecutionConfig(&execution);
411 if (configuration.IsSet(
"seed") || mpi) next->SetSeed(Option(
"seed", 0));
413 next->CreateWithState(n, values);
417 state = std::move(next);
421 Configuration configuration;
422 std::unique_ptr<DistributedGpuLibStateVectorSim> state;
424class DistributedMpiGpuState :
public DistributedGpuState {
426 DistributedMpiGpuState() : DistributedGpuState(true) {}
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)
SimulationType
The type of simulation.
SimulatorType
The type of simulator.
std::vector< qubit_t > qubits_vector
The type of a vector of qubits.
uint_fast64_t qubit_t
The type of a qubit.