3#if defined(__linux__) && defined(INCLUDED_BY_FACTORY)
15class DistributedGpuState :
public ISimulator
18 explicit DistributedGpuState(
bool mpi =
false) : mpi(mpi)
22 void Initialize()
override
24 InitializeData(nrQubits,
nullptr);
27 void InitializeState(
size_t n, std::vector<std::complex<double>> &values)
override
29 InitializeVector(n, values);
32 void InitializeState(
size_t n, Eigen::VectorXcd &values)
override
34 InitializeVector(n, values);
37 void InitializeState(
size_t n, AER::Vector<std::complex<double>> &values)
override
39 InitializeVector(n, values);
42 void Configure(
const char *key,
const char *value)
override
45 throw std::invalid_argument(
"Null distributed GPU configuration");
46 const std::string k(key), v(value);
47 if (k ==
"precision" || k ==
"use_double_precision")
49 Configuration::ParsePrecision(k, v);
53 configuration.SetConfiguration(k, v);
56 if (k !=
"seed" && configuration.WasApplied(k, v))
58 static const std::unordered_set<std::string> distributionKeys{
"distributed_devices",
59 "distributed_global_qubits",
61 "distributed_backend",
62 "distributed_max_queued_gates",
63 "distributed_transfer_workspace_bytes",
64 "distributed_snapshot_storage",
67 if ((k.compare(0, 12,
"distributed_") == 0 || k.compare(0, 4,
"mpi_") == 0) && !distributionKeys.count(k))
68 throw std::invalid_argument(
"Unknown distributed GPU option: " + k);
69 if (!mpi && k.compare(0, 4,
"mpi_") == 0)
70 throw std::invalid_argument(
"MPI options require DistributedMpiGpu");
71 if (k ==
"method" && v !=
"statevector")
72 throw std::invalid_argument(
"Distributed GPU simulators currently support only statevector");
73 const bool allocationOption =
74 k ==
"gpu_device" || k ==
"precision" || k ==
"use_double_precision" || k.compare(0, 12,
"distributed_") == 0 || k.compare(0, 4,
"mpi_") == 0;
75 if (state && allocationOption)
76 throw std::logic_error(
"Clear the distributed GPU state before changing allocation "
78 if (k ==
"gpu_device")
79 Configuration::ParseGpuDevice(v);
82 auto seed = ParseUnsigned(v);
83 SeedAuxiliaryRng(seed);
87 if (k ==
"distributed_devices" || k ==
"distributed_global_qubits")
89 if (k ==
"distributed_flags" || k ==
"distributed_max_queued_gates" || k ==
"distributed_transfer_workspace_bytes" || k ==
"mpi_p2p_bits")
91 if (k ==
"mpi_communicator")
93 if (k ==
"distributed_snapshot_storage" && v !=
"host" && v !=
"gpu")
94 throw std::invalid_argument(
"distributed_snapshot_storage must be host or gpu");
95 if (k ==
"distributed_flags" && ParseUnsigned(v) > 31)
96 throw std::invalid_argument(
"Unsupported distribution flags");
97 if (k ==
"distributed_max_queued_gates" && (ParseUnsigned(v) < 1 || ParseUnsigned(v) > 65536))
98 throw std::invalid_argument(
"Queue size must be 1..65536");
99 if (k ==
"mpi_p2p_bits" && ParseUnsigned(v) > 5)
100 throw std::invalid_argument(
"mpi_p2p_bits must be 0..5");
101 if (k ==
"distributed_backend" && v !=
"ex" && v !=
"conventional")
102 throw std::invalid_argument(
"distributed_backend must be ex or conventional");
103 if (mpi && k ==
"distributed_backend" && v !=
"ex")
104 throw std::invalid_argument(
"MPI requires the Ex backend");
105 configuration.SetConfiguration(k, v);
110 const std::string k(key);
112 return "statevector";
113 if (k ==
"precision" || k ==
"use_double_precision")
115 const bool useDouble = state ? state->IsDoublePrecision() : configuration.GetConfiguration(
"precision") ==
"double";
116 return k ==
"precision" ? (useDouble ?
"double" :
"single") : (useDouble ?
"1" :
"0");
118 if (state && (k ==
"distributed_shard_devices" || k ==
"distributed_configured_global_qubits" || k ==
"distributed_qubit_layout"))
120 std::vector<int32_t> values(k ==
"distributed_qubit_layout" ? nrQubits : 32);
121 int count = k ==
"distributed_shard_devices" ? state->GetShardDevices(values.data(), values.size())
122 : k ==
"distributed_configured_global_qubits" ? state->GetGlobalQubits(values.data(), values.size())
123 : state->GetQubitLayout(values.data(), values.size());
124 values.resize(count);
127 return configuration.GetConfiguration(key);
130 const std::unordered_map<std::string, std::string> &GetConfigMap()
const override
132 return configuration.GetConfigMap();
138 throw std::logic_error(
"Clear before allocating distributed GPU qubits");
139 if (count >= 63 || nrQubits >= 63 - count)
140 throw std::invalid_argument(
"Distributed GPU requires fewer than 63 qubits");
151 void Clear()
override
157 void Reset()
override
164 auto bits = MeasureMany(qubits);
166 for (
size_t i = 0; i < bits.size(); ++i)
168 result |=
size_t{1} << i;
177 std::vector<int> values(qb.size());
178 Native().MeasureQubitsCollapse(qb.data(), values.data(), values.size());
179 NotifyObservers(qubits);
180 return std::vector<bool>(values.begin(), values.end());
185 auto bits = MeasureMany(qubits);
186 for (
size_t i = 0; i < qubits.size(); ++i)
193 return Native().BasisStateProbability(outcome);
199 Native().Amplitude(outcome, &real, &imag);
203 std::complex<double> AmplitudeRaw(
Types::qubit_t outcome)
override
208 std::complex<double> ProjectOnZero()
override
215 std::vector<double> values(
size_t{1} << nrQubits);
216 Native().AllProbabilities(values.data());
222 std::vector<double> values;
223 for (
auto outcome : outcomes)
231 std::unordered_map<Types::qubit_t, Types::qubit_t> result;
232 if (qb.empty() || !shots)
234 if (shots > std::numeric_limits<unsigned>::max())
235 throw std::invalid_argument(
"Too many GPU samples");
236 std::vector<long> samples(shots);
237 Native().Sample(shots, samples.data(), qb.size(), qb.data());
238 for (
auto sample : samples)
246 for (
const auto &[outcome, count] :
SampleCounts(qubits, shots))
248 std::vector<bool> bits(qubits.size());
249 for (
size_t i = 0; i < bits.size(); ++i)
250 bits[i] = (outcome >> i) & 1;
251 result[bits] += count;
256 double ExpectationValue(
const std::string &pauli)
override
258 return Native().ExpectationValue(pauli.c_str(), pauli.size());
263 return mpi ? SimulatorType::kDistMpiGpuSim : SimulatorType::kDistGpuSim;
268 return SimulationType::kStatevector;
271 int GetGpuDevice()
const override
273 return state ? state->GetStateVectorGpuId() : -1;
276 void Flush()
override
278 Native().Synchronize();
283 Native().SaveStateDestructive();
288 Native().RestoreStateFreeSaved();
293 if (configuration.GetConfiguration(
"distributed_snapshot_storage") ==
"host")
294 Native().SaveStateToHost();
296 Native().SaveState();
301 Native().RestoreStateNoFreeSaved();
320 return Native().MeasureAllQubitsNoCollapse();
323 std::vector<bool> MeasureNoCollapseMany()
override
326 std::vector<bool> bits(nrQubits);
327 for (
size_t i = 0; i < bits.size(); ++i)
328 bits[i] = (outcome >> i) & 1;
336 throw std::out_of_range(
"Distributed GPU qubit is out of range");
337 return static_cast<int>(q);
340 DistributedGpuLibStateVectorSim &Native()
const
343 throw std::logic_error(
"Distributed GPU state is not initialized");
347 static uint64_t ParseUnsigned(
const std::string &v)
350 auto parsed = std::from_chars(v.data(), v.data() + v.size(), result);
351 if (parsed.ec != std::errc() || parsed.ptr != v.data() + v.size())
352 throw std::invalid_argument(
"Expected an unsigned integer: " + v);
356 static int64_t ParseCommunicator(
const std::string &value)
359 const auto handle = std::stoll(value, &used);
360 if (used != value.size())
361 throw std::invalid_argument(
"Invalid MPI communicator handle");
365 static int ParseInt(
const std::string &v)
368 auto parsed = std::from_chars(v.data(), v.data() + v.size(), result);
369 if (parsed.ec != std::errc() || parsed.ptr != v.data() + v.size())
370 throw std::invalid_argument(
"Expected an integer: " + v);
374 static std::vector<int32_t> ParseList(
const std::string &value)
376 std::vector<int32_t> values;
382 const auto end = value.find(
',', begin);
383 int item = ParseInt(value.substr(begin, end == std::string::npos ? end : end - begin));
385 throw std::invalid_argument(
"Distributed indices must be nonnegative");
386 values.push_back(item);
387 if (end == std::string::npos)
394 static std::string Join(
const std::vector<int32_t> &values)
397 for (
auto v : values)
401 result += std::to_string(v);
408 if (qubits.size() > nrQubits)
409 throw std::invalid_argument(
"Too many measured qubits");
410 std::vector<int> values;
411 std::unordered_set<Types::qubit_t> seen;
412 for (
auto q : qubits)
414 if (q >= nrQubits || !seen.insert(q).second)
415 throw std::invalid_argument(
"Invalid or duplicate qubit");
416 values.push_back(
static_cast<int>(q));
421 uint64_t Option(
const char *key, uint64_t fallback)
const
423 return configuration.IsSet(key) ? ParseUnsigned(configuration.GetConfiguration(key)) : fallback;
426 template <
class V>
void InitializeVector(
size_t n, V &values)
428 if (!n || n >= 63 ||
static_cast<size_t>(values.size()) != (
size_t{1} << n))
429 throw std::invalid_argument(
"Statevector length must equal 2^num_qubits");
430 InitializeData(n,
reinterpret_cast<const double *
>(values.data()));
433 void InitializeData(
size_t n,
const double *values)
436 throw std::logic_error(
"Clear before initializing a distributed GPU state again");
438 throw std::invalid_argument(
"Distributed GPU requires 1..62 qubits");
439 int device = configuration.IsSet(
"gpu_device") ? Configuration::ParseGpuDevice(configuration.GetConfiguration(
"gpu_device")) : 0;
440 auto devices = ParseList(configuration.GetConfiguration(
"distributed_devices"));
441 auto globals = ParseList(configuration.GetConfiguration(
"distributed_global_qubits"));
442 std::unique_ptr<DistributedGpuLibStateVectorSim> next;
445 auto lib = DistributedMpiGpuLibrary::GetInstance();
446 DistributedMpiGpuLibrary::Communicator descriptor{
sizeof(DistributedMpiGpuLibrary::Communicator), 0, 0};
447 const DistributedMpiGpuLibrary::Communicator *comm =
nullptr;
448 if (configuration.IsSet(
"mpi_communicator"))
450 descriptor.fortran_handle = ParseCommunicator(configuration.GetConfiguration(
"mpi_communicator"));
453 const auto info = lib->GetRuntimeInfo(comm);
454 if (!devices.empty() && devices.size() !=
static_cast<size_t>(info.size))
455 throw std::invalid_argument(
"MPI distributed_devices must contain one ordinal per rank");
456 if (!configuration.IsSet(
"gpu_device"))
457 device = devices.empty() ? info.default_device : devices[info.rank];
460 devices.resize(info.size);
461 lib->GatherDevices(comm, device, devices);
463 next = std::make_unique<DistributedMpiGpuLibStateVectorSim>(lib, comm, device, Option(
"mpi_p2p_bits", 0));
467 auto lib = DistributedGpuLibrary::GetInstance();
470 if (configuration.IsSet(
"gpu_device"))
471 devices.push_back(device);
474 lib->RequireLoaded();
475 const int count = lib->GetGpuDeviceCount();
477 lib->Fail(
"GetGpuDeviceCount");
479 while (shards < 32 && shards * 2 <= count &&
static_cast<uint64_t
>(shards * 2) <= (uint64_t{1} << (n - 1)))
481 for (
int i = 0; i < shards; ++i)
482 devices.push_back(i);
485 const int backend = configuration.GetConfiguration(
"distributed_backend") ==
"conventional" ? 0 : 1;
486 next = std::make_unique<DistributedGpuLibStateVectorSim>(lib, lib->CreateNative(device, backend));
488 if (devices.empty() || devices.size() > 32 || (devices.size() & (devices.size() - 1)))
489 throw std::invalid_argument(
"distributed_devices must contain a power of two shards (1..32)");
490 size_t globalBits = 0;
491 while ((
size_t{1} << globalBits) < devices.size())
494 throw std::invalid_argument(
"Distribution requires at least one local qubit");
495 if (!configuration.IsSet(
"distributed_global_qubits"))
496 for (
size_t i = 0; i < globalBits; ++i)
497 globals.push_back(i);
498 DistributedGpuApi::MgdDistributionConfig dist{
sizeof(dist),
499 static_cast<uint32_t
>(Option(
"distributed_flags", 0)),
500 static_cast<uint32_t
>(globals.size()),
502 static_cast<uint32_t
>(devices.size()),
504 next->ConfigureDistribution(&dist);
505 const bool useDouble = configuration.IsSet(
"precision") ? configuration.GetConfiguration(
"precision") ==
"double"
506 : (configuration.GetConfiguration(
"use_double_precision") ==
"1" ||
507 configuration.GetConfiguration(
"use_double_precision") ==
"true");
508 next->SetDataType(useDouble ? 1 : 0);
509 if (configuration.IsSet(
"distributed_max_queued_gates") || configuration.IsSet(
"distributed_transfer_workspace_bytes"))
511 DistributedGpuApi::MgdExExecutionConfig execution{
sizeof(execution),
static_cast<uint32_t
>(Option(
"distributed_max_queued_gates", 1024)),
512 Option(
"distributed_transfer_workspace_bytes", 16ULL * 1024 * 1024)};
513 next->SetExExecutionConfig(&execution);
517 if (mpi && !configuration.IsSet(
"seed"))
518 SetSeed(
GenerateRandomSeed(SimulatorType::kDistMpiGpuSim, configuration.GetConfigMap()));
519 if (configuration.IsSet(
"seed"))
520 next->SetSeed(Option(
"seed", 0));
522 next->CreateWithState(n, values);
526 state = std::move(next);
531 Configuration configuration;
532 std::unique_ptr<DistributedGpuLibStateVectorSim> state;
535class DistributedMpiGpuState :
public DistributedGpuState
538 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)
std::vector< int > Qubits(const Types::qubits_vector &qubits, size_t width)
uint64_t GenerateRandomSeed(SimulatorType type, const std::unordered_map< std::string, std::string > &options={})
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.