Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix performance regression from QuantumError.to_dict #1408

Merged
merged 5 commits into from
Dec 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions qiskit/providers/aer/noise/errors/quantum_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from qiskit.circuit.exceptions import CircuitError
from qiskit.circuit.library.generalized_gates import PauliGate
from qiskit.circuit.library.standard_gates import IGate
from qiskit.compiler import assemble
from qiskit.exceptions import QiskitError
from qiskit.extensions import UnitaryGate
from qiskit.quantum_info.operators.base_operator import BaseOperator
Expand Down Expand Up @@ -453,13 +452,12 @@ def error_term(self, position):

def to_dict(self):
"""Return the current error as a dictionary."""
qobj = assemble(self.circuits)
instructions = [exp.to_dict()['instructions'] for exp in qobj.experiments]
error = {
"type": "qerror",
"id": self.id,
"operations": [],
"instructions": instructions,
"instructions": [[op[0].assemble().to_dict() for op in circ.data]
for circ in self._circs],
"probabilities": list(self.probabilities)
}
return error
Expand Down
8 changes: 8 additions & 0 deletions releasenotes/notes/fix-qerror-assemble-9919a93b210ca776.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
fixes:
- |
Fix performance regression in noisy simulations due to large increase in
serialization overhead for loading noise models from Python into C++
resulting from unintended nested Python multiprocessing calls.
See `issue 1407 <https://github.com/Qiskit/qiskit-aer/issues/1407>`__
for details.
40 changes: 15 additions & 25 deletions src/controllers/aer_controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,6 @@ class Controller {
}
return max_memory_mb_;
}
// Truncate and remap input qubits
bool enable_truncation_ = true;

// The maximum number of threads to use for various levels of parallelization
int max_parallel_threads_;
Expand Down Expand Up @@ -390,8 +388,6 @@ class Controller {
//-------------------------------------------------------------------------

void Controller::set_config(const json_t &config) {
// Load validation threshold
JSON::get_value(enable_truncation_, "enable_truncation", config);

// Load validation threshold
JSON::get_value(validation_threshold_, "validation_threshold", config);
Expand Down Expand Up @@ -846,37 +842,31 @@ Result Controller::execute(const inputdata_t &input_qobj) {
// Start QOBJ timer
auto timer_start = myclock_t::now();

Noise::NoiseModel noise_model;
json_t config;
// Initialize QOBJ
Qobj qobj(input_qobj);
auto qobj_time_taken =
std::chrono::duration<double>(myclock_t::now() - timer_start).count();

// Check for config
if (Parser<inputdata_t>::get_value(config, "config", input_qobj)) {
// Set config
set_config(config);
// Load noise model
Parser<json_t>::get_value(noise_model, "noise_model", config);
}
// Set config
set_config(qobj.config);

// Initialize qobj
Qobj qobj;
if (noise_model.has_nonlocal_quantum_errors()) {
// Non-local noise does not work with optimized initialization
qobj = Qobj(input_qobj, false);
} else {
qobj = Qobj(input_qobj, enable_truncation_);
}
// Run qobj circuits
auto result = execute(qobj.circuits, qobj.noise_model, qobj.config);

// Add QOBJ loading time
result.metadata.add(qobj_time_taken, "time_taken_load_qobj");
jakelishman marked this conversation as resolved.
Show resolved Hide resolved

auto result = execute(qobj.circuits, noise_model, config);
// Get QOBJ id and pass through header to result
result.qobj_id = qobj.id;
if (!qobj.header.empty()) {
result.header = qobj.header;
}

// Stop the timer and add total timing data including qobj parsing
auto timer_stop = myclock_t::now();
auto time_taken =
std::chrono::duration<double>(timer_stop - timer_start).count();
std::chrono::duration<double>(myclock_t::now() - timer_start).count();
result.metadata.add(time_taken, "time_taken");

jakelishman marked this conversation as resolved.
Show resolved Hide resolved
return result;
} catch (std::exception &e) {
// qobj was invalid, return valid output containing error message
Expand Down Expand Up @@ -1007,7 +997,7 @@ Result Controller::execute(std::vector<Circuit> &circuits,
auto timer_stop = myclock_t::now();
auto time_taken =
std::chrono::duration<double>(timer_stop - timer_start).count();
result.metadata.add(time_taken, "time_taken");
result.metadata.add(time_taken, "time_taken_execute");
}
jakelishman marked this conversation as resolved.
Show resolved Hide resolved
// If execution failed return valid output reporting error
catch (std::exception &e) {
Expand Down
32 changes: 27 additions & 5 deletions src/framework/qobj.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <vector>

#include "framework/circuit.hpp"
#include "noise/noise_model.hpp"

namespace AER {

Expand All @@ -40,7 +41,7 @@ class Qobj {

// Deserialization constructor
template <typename inputdata_t>
Qobj(const inputdata_t &input, bool truncation = false);
Qobj(const inputdata_t &input);

//----------------------------------------------------------------
// Data
Expand All @@ -50,6 +51,7 @@ class Qobj {
std::vector<Circuit> circuits; // List of circuits
json_t header; // (optional) passed through to result
json_t config; // (optional) qobj level config data
Noise::NoiseModel noise_model; // (optional) noise model
};

//============================================================================
Expand All @@ -60,7 +62,7 @@ class Qobj {
inline void from_json(const json_t &js, Qobj &qobj) { qobj = Qobj(js); }

template <typename inputdata_t>
Qobj::Qobj(const inputdata_t &input, bool truncation) {
Qobj::Qobj(const inputdata_t &input) {
// Check required fields
if (Parser<inputdata_t>::get_value(id, "qobj_id", input) == false) {
throw std::invalid_argument(R"(Invalid qobj: no "qobj_id" field)");
Expand All @@ -73,9 +75,29 @@ Qobj::Qobj(const inputdata_t &input, bool truncation) {
throw std::invalid_argument(R"(Invalid qobj: no "experiments" field.)");
}

// Get header and config;
Parser<inputdata_t>::get_value(config, "config", input);
Parser<inputdata_t>::get_value(header, "header", input);
// Apply qubit truncation
bool truncation = true;

// Parse config
if (Parser<inputdata_t>::get_value(config, "config", input)) {
// Check for truncation option
Parser<json_t>::get_value(truncation, "enable_truncation", config);

// Load noise model
if (Parser<json_t>::get_value(noise_model, "noise_model", config)) {
// If noise model has non-local errors disable trunction
if (noise_model.has_nonlocal_quantum_errors()) {
truncation = false;
}
}
} else {
config = json_t::object();
}

// Parse header
if (!Parser<inputdata_t>::get_value(header, "header", input)) {
header = json_t::object();
}

// Check for fixed simulator seed
// If simulator seed is set, each experiment will be set to a fixed (but different) seed
Expand Down
34 changes: 28 additions & 6 deletions test/terra/backends/aer_simulator/test_noise.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
from qiskit.providers.aer import noise

import qiskit.quantum_info as qi
from qiskit import transpile
from qiskit.circuit import QuantumCircuit, Reset
from qiskit.circuit.library import QFT
from qiskit.circuit.library.standard_gates import IGate, HGate
from qiskit.quantum_info.states.densitymatrix import DensityMatrix
from test.terra.backends.simulator_test_case import (
SimulatorTestCase, supported_methods)
from test.terra.reference import ref_kraus_noise
Expand Down Expand Up @@ -156,15 +158,35 @@ def test_kraus_gate_noise(self, method, device):
def test_kraus_gate_noise_on_QFT(self, method, device):
"""Test Kraus noise on a QFT circuit"""
shots = 10000
noise_model = ref_kraus_noise.kraus_gate_error_noise_models_full()

# Build noise model
error1 = noise.amplitude_damping_error(0.2)
error2 = error1.tensor(error1)
noise_model = noise.NoiseModel()
noise_model.add_all_qubit_quantum_error(error1, ['h'])
noise_model.add_all_qubit_quantum_error(error2, ['cp', 'swap'])

backend = self.backend(
method=method, device=device, noise_model=noise_model)
circuit = QFT(3).decompose()
circuit.measure_all()
ref_target = ref_kraus_noise.kraus_gate_error_counts_on_QFT(shots)
result = backend.run(circuit, shots=shots).result()
ideal_circuit = transpile(QFT(3), backend)

# manaully build noise circuit
noise_circuit = QuantumCircuit(3)
for inst, qargs, cargs in ideal_circuit.data:
noise_circuit.append(inst, qargs, cargs)
if inst.name == "h":
noise_circuit.append(error1, qargs)
elif inst.name in ["cp", "swap"]:
noise_circuit.append(error2, qargs)
# compute target counts
noise_state = DensityMatrix(noise_circuit)
ref_target = {i: shots * p for i, p in noise_state.probabilities_dict().items()}

# Run sim
ideal_circuit.measure_all()
result = backend.run(ideal_circuit, shots=shots).result()
self.assertSuccess(result)
self.compare_counts(result, [circuit], [ref_target], delta=0.1 * shots)
self.compare_counts(result, [ideal_circuit], [ref_target], hex_counts=False, delta=0.1 * shots)
jakelishman marked this conversation as resolved.
Show resolved Hide resolved

@supported_methods(ALL_METHODS)
def test_clifford_circuit_noise(self, method, device):
Expand Down
8 changes: 4 additions & 4 deletions test/terra/backends/aer_simulator/test_truncate.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def create_circuit_for_truncate(self):
circuit.measure(1, 1)
return circuit

def deveice_backend(self):
def device_backend(self):
return mock.FakeQuito()

def test_truncate_ideal_sparse_circuit(self):
Expand All @@ -65,7 +65,7 @@ def test_truncate_default(self):
[0, 1], [1, 2], [2, 3], [3, 4], [4, 5],
[5, 6], [6, 7], [7, 8], [8, 9], [9, 0]
]
noise_model = NoiseModel.from_backend(self.deveice_backend())
noise_model = NoiseModel.from_backend(self.device_backend())
backend = self.backend(noise_model=noise_model)
circuit = transpile(
self.create_circuit_for_truncate(),
Expand All @@ -78,7 +78,7 @@ def test_truncate_default(self):

def test_truncate_non_measured_qubits(self):
"""Test truncation of non-measured uncoupled qubits."""
noise_model = NoiseModel.from_backend(self.deveice_backend())
noise_model = NoiseModel.from_backend(self.device_backend())
backend = self.backend(noise_model=noise_model)
circuit = transpile(
self.create_circuit_for_truncate(),
Expand All @@ -95,7 +95,7 @@ def test_truncate_disable_noise(self):
[0, 1], [1, 2], [2, 3], [3, 4], [4, 5],
[5, 6], [6, 7], [7, 8], [8, 9], [9, 0]
]
noise_model = NoiseModel.from_backend(self.deveice_backend())
noise_model = NoiseModel.from_backend(self.device_backend())
backend = self.backend(noise_model=noise_model, enable_truncation=False)
circuit = transpile(
self.create_circuit_for_truncate(),
Expand Down
Loading