From 2fba5de820c08d05ce28be841d6523cb3463486b Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Sat, 26 Sep 2020 14:14:35 -0400 Subject: [PATCH 01/14] Add profiling functions for OpenMP and fusion --- qiskit/providers/aer/profile.py | 184 ++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 qiskit/providers/aer/profile.py diff --git a/qiskit/providers/aer/profile.py b/qiskit/providers/aer/profile.py new file mode 100644 index 0000000000..b1e0d17e64 --- /dev/null +++ b/qiskit/providers/aer/profile.py @@ -0,0 +1,184 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Profile backend options for optimal performance +""" +from qiskit import transpile, assemble, execute +from .aererror import AerError + + +def optimize_backend_options(min_qubits=10, max_qubits=20, ntrials=10): + """Set optimal OpenMP and fusion options for backend.""" + # Profile + profile = {} + + # Profile OpenMP threshold + try: + parallel_threshold = profile_parallel_threshold( + min_qubits=min_qubits, max_qubits=max_qubits, ntrials=ntrials) + + profile['statevector_parallel_threshold'] = parallel_threshold + except AerError: + pass + + # Profile CPU fusion threshold + try: + fusion_threshold = profile_fusion_threshold( + min_qubits=min_qubits, max_qubits=max_qubits, ntrials=ntrials) + profile['fusion_threshold'] = fusion_threshold + except AerError: + pass + + # Profile GPU fusion threshold + try: + fusion_threshold_gpu = profile_fusion_threshold( + gpu=True, min_qubits=min_qubits, max_qubits=max_qubits, ntrials=ntrials) + profile['fusion_threshold_gpu'] = fusion_threshold_gpu + except AerError: + pass + + # TODO: Write profile to a local qiskitaerrc file so this doesn't + # need to be re-run on a system and the following can be loaded + # in the AerBackend class from the rc file if it is found + from qiskit.providers.aer.backends.aerbackend import AerBackend + + if 'statevector_parallel_threshold' in profile: + AerBackend._statevector_parallel_threshold = profile[ + 'statevector_parallel_threshold'] + if 'fusion_threshold' in profile: + AerBackend._fusion_threshold = profile['fusion_threshold'] + if 'fusion_threshold_gpu' in profile: + AerBackend._fusion_threshold_gpu = profile['fusion_threshold_gpu'] + + return profile + + +def profile_parallel_threshold(min_qubits=10, max_qubits=20, ntrials=10, + backend_options=None, + return_ratios=False): + """Evaluate optimal OMP parallel threshold for current system.""" + from qiskit.circuit.library import QuantumVolume + from qiskit.providers.aer.backends.qasm_simulator import QasmSimulator + + simulator = QasmSimulator() + opts = {'method': 'statevector', + 'max_parallel_experiments': 1, + 'max_parallel_shots': 1, + 'fusion_enabled': False} + if backend_options: + for key, val in backend_options.items(): + opts[key] = val + # Ensure method is statevector + opts['method'] = 'statevector' + + omp_ratios = [] + qubit_threshold = None + + for i in range(min_qubits, max_qubits + 1): + circ = transpile(QuantumVolume(i, 10), + basis_gates=['id', 'u1', 'u2', 'u3', 'cx', 'swap']) + + qobj = assemble(ntrials * [circ], shots=1) + times = [] + for val in [i - 1, i]: + opts['statevector_parallel_threshold'] = val + result = simulator.run(qobj, backend_options=opts).result() + t = 0.0 + for j in range(ntrials): + t += result.results[j].time_taken + times.append(t) + + # Compute ratio + ratio = times[1] / times[0] + omp_ratios.append((i, ratio)) + + if qubit_threshold is None: + if ratio > 1: + qubit_threshold = i + elif ratio < 1: + qubit_threshold = None + else: + break + + # Check if we found a threshold in the provided range + if qubit_threshold is None: + raise AerError('Unable to find threshold in range [{}, {}]'.format( + min_qubits, max_qubits)) + + if return_ratios: + return qubit_threshold, omp_ratios + return qubit_threshold + + +def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, + backend_options=None, gpu=False, + return_ratios=False): + """Evaluate optimal OMP parallel threshold for current system.""" + from qiskit.circuit.library import QuantumVolume + from qiskit.providers.aer.backends.qasm_simulator import QasmSimulator + + simulator = QasmSimulator() + opts = {'method': 'statevector', + 'max_parallel_experiments': 1, + 'max_parallel_shots': 1} + if backend_options: + for key, val in backend_options.items(): + opts[key] = val + + # Ensure fusion is enabled and method is statevector + if gpu: + opts['method'] = 'statevector_gpu' + # Check GPU is supported + result = execute(QuantumVolume(1), simulator, backend_options=opts).result() + if not result.success: + raise AerError('"statevector_gpu" backend is not supported on this system.') + else: + opts['method'] = 'statevector' + opts['fusion_enabled'] = True + + omp_ratios = [] + qubit_threshold = None + + for i in range(min_qubits, max_qubits + 1): + circ = transpile(QuantumVolume(i, 10), + basis_gates=['id', 'u1', 'u2', 'u3', 'cx', 'swap']) + + qobj = assemble(ntrials * [circ], shots=1) + times = [] + for val in [i, i + 1]: + opts['fusion_threshold'] = val + result = simulator.run(qobj, backend_options=opts).result() + t = 0.0 + for j in range(ntrials): + t += result.results[j].time_taken + times.append(t) + + # Compute ratio + ratio = times[1] / times[0] + omp_ratios.append((i, ratio)) + + if qubit_threshold is None: + if ratio > 1: + qubit_threshold = i + elif ratio < 1: + qubit_threshold = None + else: + break + + # Check if we found a threshold in the provided range + if qubit_threshold is None: + raise AerError('Unable to find stable threshold in range [{}, {}]'.format( + min_qubits, max_qubits)) + + if return_ratios: + return qubit_threshold, omp_ratios + return qubit_threshold From ec53496f7d0d6b05c9e37b73c6e8056d7ee67b73 Mon Sep 17 00:00:00 2001 From: Christopher Wood Date: Sat, 26 Sep 2020 15:42:32 -0400 Subject: [PATCH 02/14] Add AerProvider function for optimizing backend options --- qiskit/providers/aer/aerprovider.py | 7 +++++++ qiskit/providers/aer/backends/aerbackend.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/qiskit/providers/aer/aerprovider.py b/qiskit/providers/aer/aerprovider.py index 2fdba50080..bf73347ca0 100644 --- a/qiskit/providers/aer/aerprovider.py +++ b/qiskit/providers/aer/aerprovider.py @@ -48,3 +48,10 @@ def backends(self, name=None, filters=None, **kwargs): def __str__(self): return 'AerProvider' + + @staticmethod + def optimize_backend_options(min_qubits=10, max_qubits=20, ntrials=10): + """Set optimal OpenMP and fusion options for backend.""" + from .profile import optimize_backend_options + return optimize_backend_options( + min_qubits=min_qubits, max_qubits=max_qubits, ntrials=ntrials) diff --git a/qiskit/providers/aer/backends/aerbackend.py b/qiskit/providers/aer/backends/aerbackend.py index ae467f8bf1..c3abcc6069 100644 --- a/qiskit/providers/aer/backends/aerbackend.py +++ b/qiskit/providers/aer/backends/aerbackend.py @@ -139,9 +139,25 @@ def _format_qobj(self, qobj, backend_options, noise_model): output = qobj.to_dict() # Add new parameters to config from backend options config = output["config"] + if backend_options is not None: for key, val in backend_options.items(): config[key] = val if not hasattr(val, 'to_dict') else val.to_dict() + + # Add default OpenMP options + if 'statevector_parallel_threshold' not in config and hasattr( + self, '_statevector_parallel_threshold'): + config['statevector_parallel_threshold'] = self._statevector_parallel_threshold + + # Add default fusion options + if 'fusion_threshold' not in config: + if 'gpu' in config.get('method', '') and hasattr(self, '_fusion_threshold_gpu'): + # Set GPU fusion threshold + config['fusion_threshold'] = self._fusion_threshold_gpu + elif hasattr(self, '_fusion_threshold'): + # Set CPU fusion threshold + config['fusion_threshold'] = self._fusion_threshold + # Add noise model to config if noise_model is not None: config["noise_model"] = noise_model From 91450d1c64b2ab60d14d97e06404f4b00973948b Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Thu, 18 Mar 2021 09:44:08 +0900 Subject: [PATCH 03/14] add fusion cost tuning --- qiskit/providers/aer/aerprovider.py | 2 +- qiskit/providers/aer/backends/aerbackend.py | 11 ++++++++-- qiskit/providers/aer/profile.py | 23 ++++++++------------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/qiskit/providers/aer/aerprovider.py b/qiskit/providers/aer/aerprovider.py index 31f1f900b5..7846b897bc 100644 --- a/qiskit/providers/aer/aerprovider.py +++ b/qiskit/providers/aer/aerprovider.py @@ -21,6 +21,7 @@ from .backends.statevector_simulator import StatevectorSimulator from .backends.unitary_simulator import UnitarySimulator from .backends.pulse_simulator import PulseSimulator +from .profile import optimize_backend_options class AerProvider(BaseProvider): @@ -56,6 +57,5 @@ def __str__(self): @staticmethod def optimize_backend_options(min_qubits=10, max_qubits=20, ntrials=10): """Set optimal OpenMP and fusion options for backend.""" - from .profile import optimize_backend_options return optimize_backend_options( min_qubits=min_qubits, max_qubits=max_qubits, ntrials=ntrials) diff --git a/qiskit/providers/aer/backends/aerbackend.py b/qiskit/providers/aer/backends/aerbackend.py index b2198cc6a1..80fdec4e80 100644 --- a/qiskit/providers/aer/backends/aerbackend.py +++ b/qiskit/providers/aer/backends/aerbackend.py @@ -237,14 +237,21 @@ def _run_job(self, job_id, qobj, backend_options, noise_model, validate): # Add default OpenMP options if 'statevector_parallel_threshold' not in backend_options and hasattr( self, '_statevector_parallel_threshold'): - backend_options['statevector_parallel_threshold'] = self._statevector_parallel_threshold + backend_options['statevector_parallel_threshold'] = \ + getattr(self, '_statevector_parallel_threshold') # Add default fusion options attr_postfix = '_gpu' if 'gpu' in backend_options.get('method', '') else '' if 'fusion_threshold' not in backend_options and hasattr( - self, f'_fusion_threshold{attr_postfix}'): + self, f'_fusion_threshold{attr_postfix}'): # Set fusion threshold backend_options['fusion_threshold'] = getattr(self, f'_fusion_threshold{attr_postfix}') + for i in range(1, 6): + if f'fusion_cost.{i}' not in backend_options and \ + hasattr(self, f'_fusion_cost{attr_postfix}.{i}'): + # Set cost for each + backend_options[f'fusion_cost.{i}'] = getattr(self, + f'_fusion_cost{attr_postfix}.{i}') # The new function swaps positional args qobj and job id so we do a # type check to swap them back diff --git a/qiskit/providers/aer/profile.py b/qiskit/providers/aer/profile.py index b1e0d17e64..ecbc9b85fb 100644 --- a/qiskit/providers/aer/profile.py +++ b/qiskit/providers/aer/profile.py @@ -13,7 +13,10 @@ Profile backend options for optimal performance """ from qiskit import transpile, assemble, execute +from qiskit.circuit.library import QuantumVolume from .aererror import AerError +from .backends.aerbackend import AerBackend +from .backends.qasm_simulator import QasmSimulator def optimize_backend_options(min_qubits=10, max_qubits=20, ntrials=10): @@ -49,8 +52,6 @@ def optimize_backend_options(min_qubits=10, max_qubits=20, ntrials=10): # TODO: Write profile to a local qiskitaerrc file so this doesn't # need to be re-run on a system and the following can be loaded # in the AerBackend class from the rc file if it is found - from qiskit.providers.aer.backends.aerbackend import AerBackend - if 'statevector_parallel_threshold' in profile: AerBackend._statevector_parallel_threshold = profile[ 'statevector_parallel_threshold'] @@ -66,9 +67,6 @@ def profile_parallel_threshold(min_qubits=10, max_qubits=20, ntrials=10, backend_options=None, return_ratios=False): """Evaluate optimal OMP parallel threshold for current system.""" - from qiskit.circuit.library import QuantumVolume - from qiskit.providers.aer.backends.qasm_simulator import QasmSimulator - simulator = QasmSimulator() opts = {'method': 'statevector', 'max_parallel_experiments': 1, @@ -92,10 +90,10 @@ def profile_parallel_threshold(min_qubits=10, max_qubits=20, ntrials=10, for val in [i - 1, i]: opts['statevector_parallel_threshold'] = val result = simulator.run(qobj, backend_options=opts).result() - t = 0.0 + time_taken = 0.0 for j in range(ntrials): - t += result.results[j].time_taken - times.append(t) + time_taken += result.results[j].time_taken + times.append(time_taken) # Compute ratio ratio = times[1] / times[0] @@ -123,9 +121,6 @@ def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, backend_options=None, gpu=False, return_ratios=False): """Evaluate optimal OMP parallel threshold for current system.""" - from qiskit.circuit.library import QuantumVolume - from qiskit.providers.aer.backends.qasm_simulator import QasmSimulator - simulator = QasmSimulator() opts = {'method': 'statevector', 'max_parallel_experiments': 1, @@ -157,10 +152,10 @@ def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, for val in [i, i + 1]: opts['fusion_threshold'] = val result = simulator.run(qobj, backend_options=opts).result() - t = 0.0 + time_taken = 0.0 for j in range(ntrials): - t += result.results[j].time_taken - times.append(t) + time_taken += result.results[j].time_taken + times.append(time_taken) # Compute ratio ratio = times[1] / times[0] From 6c4eb1c82f0e2e01ace5281facb3fecc9409a7dc Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Mon, 22 Mar 2021 18:23:10 +0900 Subject: [PATCH 04/14] add profile tests and correct backend to assign optimized parameters --- qiskit/providers/aer/backends/aerbackend.py | 41 +-- qiskit/providers/aer/profile.py | 345 +++++++++++++------- test/terra/backends/test_profile.py | 91 ++++++ 3 files changed, 337 insertions(+), 140 deletions(-) create mode 100644 test/terra/backends/test_profile.py diff --git a/qiskit/providers/aer/backends/aerbackend.py b/qiskit/providers/aer/backends/aerbackend.py index 80fdec4e80..7296afec82 100644 --- a/qiskit/providers/aer/backends/aerbackend.py +++ b/qiskit/providers/aer/backends/aerbackend.py @@ -143,9 +143,29 @@ def run(self, DeprecationWarning, stacklevel=3) + profiled_options = {} + + def set_if_profiled(prop_name, profiled_attr_name): + # DEPRECATED + if backend_options is not None and prop_name in backend_options: + return + if prop_name in run_options or not hasattr(self, profiled_attr_name): + return + profiled_options[prop_name] = getattr(self, profiled_attr_name) + + # Add default OpenMP options + set_if_profiled('statevector_parallel_threshold', '_statevector_parallel_threshold') + # Add default fusion options + attr_postfix = '' + if backend_options is not None and 'gpu' in backend_options.get('method', ''): + attr_postfix = '_gpu' + set_if_profiled('fusion_threshold', f'_fusion_threshold{attr_postfix}') + for i in range(1, 6): + set_if_profiled(f'fusion_cost.{i}', f'_fusion_cost{attr_postfix}.{i}') + # Add backend options to the Job qobj qobj = self._format_qobj( - qobj, backend_options=backend_options, **run_options) + qobj, backend_options=backend_options, **run_options, **profiled_options) # Optional validation if validate: @@ -234,25 +254,6 @@ def _run_job(self, job_id, qobj, backend_options, noise_model, validate): 'validate=True in the `run` method instead.', DeprecationWarning) - # Add default OpenMP options - if 'statevector_parallel_threshold' not in backend_options and hasattr( - self, '_statevector_parallel_threshold'): - backend_options['statevector_parallel_threshold'] = \ - getattr(self, '_statevector_parallel_threshold') - - # Add default fusion options - attr_postfix = '_gpu' if 'gpu' in backend_options.get('method', '') else '' - if 'fusion_threshold' not in backend_options and hasattr( - self, f'_fusion_threshold{attr_postfix}'): - # Set fusion threshold - backend_options['fusion_threshold'] = getattr(self, f'_fusion_threshold{attr_postfix}') - for i in range(1, 6): - if f'fusion_cost.{i}' not in backend_options and \ - hasattr(self, f'_fusion_cost{attr_postfix}.{i}'): - # Set cost for each - backend_options[f'fusion_cost.{i}'] = getattr(self, - f'_fusion_cost{attr_postfix}.{i}') - # The new function swaps positional args qobj and job id so we do a # type check to swap them back if not isinstance(job_id, str) and isinstance(qobj, str): diff --git a/qiskit/providers/aer/profile.py b/qiskit/providers/aer/profile.py index ecbc9b85fb..2dd5170c1b 100644 --- a/qiskit/providers/aer/profile.py +++ b/qiskit/providers/aer/profile.py @@ -12,168 +12,273 @@ """ Profile backend options for optimal performance """ -from qiskit import transpile, assemble, execute +import numpy as np + +from qiskit import transpile, assemble, execute, QuantumRegister, QuantumCircuit from qiskit.circuit.library import QuantumVolume +from qiskit.quantum_info import random_unitary from .aererror import AerError from .backends.aerbackend import AerBackend from .backends.qasm_simulator import QasmSimulator -def optimize_backend_options(min_qubits=10, max_qubits=20, ntrials=10): +def optimize_backend_options(min_qubits=10, max_qubits=25, ntrials=5, circuit=None): """Set optimal OpenMP and fusion options for backend.""" # Profile profile = {} # Profile OpenMP threshold + parallel_threshold = None try: parallel_threshold = profile_parallel_threshold( - min_qubits=min_qubits, max_qubits=max_qubits, ntrials=ntrials) - + min_qubits=min_qubits, max_qubits=max_qubits, circuit=circuit, ntrials=ntrials) profile['statevector_parallel_threshold'] = parallel_threshold + _set_optimized_option('statevector_parallel_threshold', parallel_threshold) except AerError: pass - # Profile CPU fusion threshold - try: - fusion_threshold = profile_fusion_threshold( - min_qubits=min_qubits, max_qubits=max_qubits, ntrials=ntrials) - profile['fusion_threshold'] = fusion_threshold - except AerError: - pass + # Profile CPU and GPU fusion threshold + for gpu in (False, True): + postfix = '_gpu' if gpu else '' + try: + fusion_threshold = profile_fusion_threshold(gpu=gpu, + min_qubits=min_qubits, + max_qubits=max_qubits, + ntrials=ntrials, + circuit=circuit) + profile[f'fusion_threshold{postfix}'] = fusion_threshold + _set_optimized_option(f'fusion_threshold{postfix}', fusion_threshold) - # Profile GPU fusion threshold - try: - fusion_threshold_gpu = profile_fusion_threshold( - gpu=True, min_qubits=min_qubits, max_qubits=max_qubits, ntrials=ntrials) - profile['fusion_threshold_gpu'] = fusion_threshold_gpu - except AerError: - pass + default_qubit = 20 + num_qubits = min(max(default_qubit, fusion_threshold), max_qubits) + costs = profile_fusion_costs(num_qubits, + ntrials=ntrials, + gpu=gpu, + diagonal=False) + for i, _ in enumerate(costs): + profile[f'fusion_cost{postfix}.{i + 1}'] = costs[i] + _set_optimized_option(f'fusion_cost{postfix}.{i + 1}', costs[i]) - # TODO: Write profile to a local qiskitaerrc file so this doesn't - # need to be re-run on a system and the following can be loaded - # in the AerBackend class from the rc file if it is found - if 'statevector_parallel_threshold' in profile: - AerBackend._statevector_parallel_threshold = profile[ - 'statevector_parallel_threshold'] - if 'fusion_threshold' in profile: - AerBackend._fusion_threshold = profile['fusion_threshold'] - if 'fusion_threshold_gpu' in profile: - AerBackend._fusion_threshold_gpu = profile['fusion_threshold_gpu'] + except AerError: + pass return profile +def _set_optimized_option(option_name, value): + setattr(AerBackend, f'_{option_name}', value) + + +def clear_optimized_backend_options(): + """Set profiled options for backend.""" + _clear_optimized_option('statevector_parallel_threshold') + for gpu in (False, True): + postfix = '_gpu' if gpu else '' + _clear_optimized_option(f'fusion_threshold{postfix}') + for i in range(5): + _clear_optimized_option(f'fusion_cost{postfix}.{i + 1}') + + +def _clear_optimized_option(option_name): + if hasattr(AerBackend, f'_{option_name}'): + delattr(AerBackend, f'_{option_name}') + + +def _generate_profile_circuit(profile_qubit, base_circuit=None, basis_gates=None): + if profile_qubit < 3: + raise AerError(f'number of qubit is too small: {profile_qubit}') + + if basis_gates is None: + basis_gates = ['id', 'u', 'cx'] + + if base_circuit is None: + return transpile(QuantumVolume(profile_qubit, 10), basis_gates=basis_gates) + + profile_circuit = transpile(base_circuit.copy(), basis_gates=basis_gates) + + if profile_qubit < profile_circuit.num_qubits: + + def global_index(qubit): + ret = 0 + for qreg in profile_circuit.qregs: + if qreg is qubit.register: + return ret + qubit.index + else: + ret += qreg.size + raise ValueError(f'odd qubit: {qubit}') + + def global_qubit(index): + orig = index + for qreg in profile_circuit.qregs: + if index < qreg.size: + return qreg[index] + else: + index -= qreg.size + raise ValueError(f'odd index: {orig}') + + for i in range(len(profile_circuit.data)): + inst, qubits, cbits = profile_circuit.data[i] + new_qubit_idxs = [] + changed = False + for qubit in qubits: + gidx = global_index(qubit) % profile_qubit + while gidx in new_qubit_idxs: + gidx = np.random.randint(profile_qubit) + changed |= (gidx != global_index(qubit)) + new_qubit_idxs.append(gidx) + if changed: + profile_circuit.data[i] = (inst, + [global_qubit(idx) for idx in new_qubit_idxs], + cbits) + + elif profile_circuit.num_qubits < profile_qubit: + # add new qubits + new_register = QuantumRegister(profile_qubit - profile_circuit.num_qubits) + profile_circuit.add_register(new_register) + # add a gate for each new qubit to prevent truncation + for new_qubit in new_register: + profile_circuit.u(0, 1, 2, new_qubit) + + return profile_circuit + + +def _profile_run(simulator, ntrials, backend_options, + qubit, circuit, basis_gates=None): + + if basis_gates is None: + basis_gates = ['id', 'u', 'cx'] + + profile_circuit = _generate_profile_circuit(qubit, circuit, basis_gates) + + qobj = assemble(ntrials * [profile_circuit], shots=1) + + result = simulator.run(qobj, **backend_options).result() + + time_taken = 0.0 + for j in range(ntrials): + time_taken += result.results[j].time_taken + + return time_taken + + def profile_parallel_threshold(min_qubits=10, max_qubits=20, ntrials=10, - backend_options=None, - return_ratios=False): + circuit=None, backend_options=None, return_ratios=False): """Evaluate optimal OMP parallel threshold for current system.""" - simulator = QasmSimulator() - opts = {'method': 'statevector', - 'max_parallel_experiments': 1, - 'max_parallel_shots': 1, - 'fusion_enabled': False} - if backend_options: + + profile_opts = {'method': 'statevector', + 'max_parallel_experiments': 1, + 'max_parallel_shots': 1, + 'fusion_enabled': False} + + if backend_options is not None: for key, val in backend_options.items(): - opts[key] = val - # Ensure method is statevector - opts['method'] = 'statevector' - - omp_ratios = [] - qubit_threshold = None - - for i in range(min_qubits, max_qubits + 1): - circ = transpile(QuantumVolume(i, 10), - basis_gates=['id', 'u1', 'u2', 'u3', 'cx', 'swap']) - - qobj = assemble(ntrials * [circ], shots=1) - times = [] - for val in [i - 1, i]: - opts['statevector_parallel_threshold'] = val - result = simulator.run(qobj, backend_options=opts).result() - time_taken = 0.0 - for j in range(ntrials): - time_taken += result.results[j].time_taken - times.append(time_taken) - - # Compute ratio - ratio = times[1] / times[0] - omp_ratios.append((i, ratio)) - - if qubit_threshold is None: - if ratio > 1: - qubit_threshold = i - elif ratio < 1: - qubit_threshold = None - else: - break + profile_opts[key] = val + + simulator = QasmSimulator() - # Check if we found a threshold in the provided range - if qubit_threshold is None: - raise AerError('Unable to find threshold in range [{}, {}]'.format( - min_qubits, max_qubits)) + ratios = [] + for qubit in range(min_qubits, max_qubits + 1): + profile_opts['statevector_parallel_threshold'] = 64 + serial_time_taken = _profile_run(simulator, ntrials, profile_opts, qubit, circuit) + profile_opts['statevector_parallel_threshold'] = 1 + parallel_time_taken = _profile_run(simulator, ntrials, profile_opts, qubit, circuit) + if return_ratios: + ratios.append(serial_time_taken / parallel_time_taken) + elif serial_time_taken < parallel_time_taken: + return qubit if return_ratios: - return qubit_threshold, omp_ratios - return qubit_threshold + return ratios + + raise AerError(f'Unable to find threshold in range [{min_qubits}, {max_qubits}]') def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, - backend_options=None, gpu=False, + circuit=None, backend_options=None, gpu=False, return_ratios=False): """Evaluate optimal OMP parallel threshold for current system.""" + + profile_opts = {'method': 'statevector', + 'max_parallel_experiments': 1, + 'max_parallel_shots': 1, + 'fusion_threshold': 1} + + if backend_options is not None: + for key, val in backend_options.items(): + profile_opts[key] = val + simulator = QasmSimulator() - opts = {'method': 'statevector', - 'max_parallel_experiments': 1, - 'max_parallel_shots': 1} - if backend_options: + + # Ensure fusion is enabled and method is statevector + if gpu: + # Check GPU is supported + result = execute(QuantumVolume(1), simulator, method='statevector_gpu').result() + if not result.success: + raise AerError('"statevector_gpu" backend is not supported on this system.') + profile_opts['method'] = 'statevector_gpu' + + ratios = [] + for qubit in range(min_qubits, max_qubits + 1): + profile_opts['fusion_enabled'] = False + non_fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, qubit, circuit) + profile_opts['fusion_enabled'] = True + fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, qubit, circuit) + if return_ratios: + ratios.append(non_fusion_time_taken / fusion_time_taken) + elif non_fusion_time_taken < fusion_time_taken: + return qubit + + if return_ratios: + return ratios + + raise AerError(f'Unable to find threshold in range [{min_qubits}, {max_qubits}]') + + +def profile_fusion_costs(num_qubits, ntrials=10, backend_options=None, gpu=False, diagonal=False): + """Evaluate optimal costs in cost-based fusion for current system.""" + profile_opts = {'method': 'statevector', + 'max_parallel_experiments': 1, + 'max_parallel_shots': 1, + 'fusion_threshold': 1} + + if backend_options is not None: for key, val in backend_options.items(): - opts[key] = val + profile_opts[key] = val + + simulator = QasmSimulator() # Ensure fusion is enabled and method is statevector if gpu: - opts['method'] = 'statevector_gpu' # Check GPU is supported - result = execute(QuantumVolume(1), simulator, backend_options=opts).result() + result = execute(QuantumVolume(1), simulator, method='statevector_gpu').result() if not result.success: raise AerError('"statevector_gpu" backend is not supported on this system.') - else: - opts['method'] = 'statevector' - opts['fusion_enabled'] = True - - omp_ratios = [] - qubit_threshold = None - - for i in range(min_qubits, max_qubits + 1): - circ = transpile(QuantumVolume(i, 10), - basis_gates=['id', 'u1', 'u2', 'u3', 'cx', 'swap']) - - qobj = assemble(ntrials * [circ], shots=1) - times = [] - for val in [i, i + 1]: - opts['fusion_threshold'] = val - result = simulator.run(qobj, backend_options=opts).result() - time_taken = 0.0 - for j in range(ntrials): - time_taken += result.results[j].time_taken - times.append(time_taken) - - # Compute ratio - ratio = times[1] / times[0] - omp_ratios.append((i, ratio)) - - if qubit_threshold is None: - if ratio > 1: - qubit_threshold = i - elif ratio < 1: - qubit_threshold = None + profile_opts['method'] = 'statevector_gpu' + + basis_gates = ['id', 'u', 'cx', 'unitary', 'diagonal'] + + all_gate_time = [] + for target in range(0, 5): + # Generate a circuit that consists of only unitary/diagonal gates with target-qubit + profile_circuit = QuantumCircuit(num_qubits) + if diagonal: + for i in range(0, 100): + qubits = [q % num_qubits for q in range(i, i + target + 1)] + profile_circuit.diagonal([1, -1] * (2 ** target), qubits) else: - break + for i in range(0, 100): + qubits = [q % num_qubits for q in range(i, i + target + 1)] + profile_circuit.unitary(random_unitary(2 ** (target + 1)), qubits) - # Check if we found a threshold in the provided range - if qubit_threshold is None: - raise AerError('Unable to find stable threshold in range [{}, {}]'.format( - min_qubits, max_qubits)) + all_gate_time.append(_profile_run(simulator, + ntrials, + profile_opts, + num_qubits, + profile_circuit, + basis_gates)) - if return_ratios: - return qubit_threshold, omp_ratios - return qubit_threshold + costs = [] + for target in range(0, 5): + costs.append(all_gate_time[target] / all_gate_time[0]) + + return costs diff --git a/test/terra/backends/test_profile.py b/test/terra/backends/test_profile.py new file mode 100644 index 0000000000..6b295b27ec --- /dev/null +++ b/test/terra/backends/test_profile.py @@ -0,0 +1,91 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2018, 2019, 2020, 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +""" +Profiler Tests +""" + +import sys +import unittest + +import numpy as np + +from test.terra import common + +from qiskit import execute, transpile +from qiskit.compiler import assemble +from qiskit.circuit.library import QuantumVolume +from qiskit.providers.aer import QasmSimulator +from qiskit.providers.aer.profile import optimize_backend_options, clear_optimized_backend_options + +class TestProfileQasmSimulator(common.QiskitAerTestCase): + + + def test_profile(self): + profile = optimize_backend_options() + + simulator = QasmSimulator() + + if 'statevector_parallel_threshold' in profile: + circuit = QuantumVolume(profile['statevector_parallel_threshold'], 1) + job = execute(circuit, simulator, method='statevector') + self.assertTrue(hasattr(job.qobj().config, 'statevector_parallel_threshold')) + + if 'fusion_threshold' in profile: + circuit = QuantumVolume(profile['fusion_threshold'], 1) + job = execute(circuit, simulator, method='statevector') + self.assertTrue(hasattr(job.qobj().config, 'fusion_threshold')) + self.assertEqual(job.result().results[0].metadata.get('fusion', {})['threshold'], + job.qobj().config.fusion_threshold) + + if 'fusion_cost.1' in profile: + circuit = QuantumVolume(profile['fusion_threshold'], 1) + job = execute(circuit, simulator, method='statevector') + self.assertTrue(hasattr(job.qobj().config, 'fusion_cost.1')) + + clear_optimized_backend_options() + circuit = QuantumVolume(10, 1) + job = execute(circuit, simulator, method='statevector') + self.assertFalse(hasattr(job.qobj().config, 'statevector_parallel_threshold')) + self.assertFalse(hasattr(job.qobj().config, 'fusion_threshold')) + self.assertFalse(hasattr(job.qobj().config, 'fusion_cost.1')) + + def test_profile_with_custom_circuit(self): + + circuit = transpile(QuantumVolume(20, 5), basis_gates = ['id', 'u', 'cx']) + + profile = optimize_backend_options(min_qubits=10, max_qubits=25, ntrials=5, circuit=circuit) + + simulator = QasmSimulator() + + if 'statevector_parallel_threshold' in profile: + circuit = QuantumVolume(profile['statevector_parallel_threshold'], 1) + job = execute(circuit, simulator, method='statevector') + self.assertTrue(hasattr(job.qobj().config, 'statevector_parallel_threshold')) + + if 'fusion_threshold' in profile: + circuit = QuantumVolume(profile['fusion_threshold'], 1) + job = execute(circuit, simulator, method='statevector') + self.assertTrue(hasattr(job.qobj().config, 'fusion_threshold')) + self.assertEqual(job.result().results[0].metadata.get('fusion', {})['threshold'], + job.qobj().config.fusion_threshold) + + if 'fusion_cost.1' in profile: + circuit = QuantumVolume(profile['fusion_threshold'], 1) + job = execute(circuit, simulator, method='statevector') + self.assertTrue(hasattr(job.qobj().config, 'fusion_cost.1')) + + clear_optimized_backend_options() + circuit = QuantumVolume(10, 1) + job = execute(circuit, simulator, method='statevector') + self.assertFalse(hasattr(job.qobj().config, 'statevector_parallel_threshold')) + self.assertFalse(hasattr(job.qobj().config, 'fusion_threshold')) + self.assertFalse(hasattr(job.qobj().config, 'fusion_cost.1')) From ae0428802a360d367014521693c2043607f13799 Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Tue, 23 Mar 2021 21:38:04 +0900 Subject: [PATCH 05/14] correct cost profiling --- qiskit/providers/aer/profile.py | 42 ++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/qiskit/providers/aer/profile.py b/qiskit/providers/aer/profile.py index 2dd5170c1b..13e2ea77e1 100644 --- a/qiskit/providers/aer/profile.py +++ b/qiskit/providers/aer/profile.py @@ -88,13 +88,13 @@ def _generate_profile_circuit(profile_qubit, base_circuit=None, basis_gates=None if profile_qubit < 3: raise AerError(f'number of qubit is too small: {profile_qubit}') - if basis_gates is None: - basis_gates = ['id', 'u', 'cx'] - if base_circuit is None: - return transpile(QuantumVolume(profile_qubit, 10), basis_gates=basis_gates) + return transpile(QuantumVolume(profile_qubit, 10), basis_gates=['id', 'u', 'cx']) - profile_circuit = transpile(base_circuit.copy(), basis_gates=basis_gates) + if basis_gates is not None: + profile_circuit = transpile(base_circuit.copy(), basis_gates=basis_gates) + else: + profile_circuit = base_circuit.copy() if profile_qubit < profile_circuit.num_qubits: @@ -145,15 +145,15 @@ def global_qubit(index): def _profile_run(simulator, ntrials, backend_options, qubit, circuit, basis_gates=None): - if basis_gates is None: - basis_gates = ['id', 'u', 'cx'] - profile_circuit = _generate_profile_circuit(qubit, circuit, basis_gates) qobj = assemble(ntrials * [profile_circuit], shots=1) result = simulator.run(qobj, **backend_options).result() + if not result.success: + raise AerError('Failed to run a profile circuit') + time_taken = 0.0 for j in range(ntrials): time_taken += result.results[j].time_taken @@ -168,20 +168,23 @@ def profile_parallel_threshold(min_qubits=10, max_qubits=20, ntrials=10, profile_opts = {'method': 'statevector', 'max_parallel_experiments': 1, 'max_parallel_shots': 1, - 'fusion_enabled': False} + 'fusion_enable': False} if backend_options is not None: for key, val in backend_options.items(): profile_opts[key] = val simulator = QasmSimulator() + basis_gates = ['id', 'u', 'cx'] ratios = [] for qubit in range(min_qubits, max_qubits + 1): profile_opts['statevector_parallel_threshold'] = 64 - serial_time_taken = _profile_run(simulator, ntrials, profile_opts, qubit, circuit) + serial_time_taken = _profile_run(simulator, ntrials, profile_opts, + qubit, circuit, basis_gates) profile_opts['statevector_parallel_threshold'] = 1 - parallel_time_taken = _profile_run(simulator, ntrials, profile_opts, qubit, circuit) + parallel_time_taken = _profile_run(simulator, ntrials, profile_opts, + qubit, circuit, basis_gates) if return_ratios: ratios.append(serial_time_taken / parallel_time_taken) elif serial_time_taken < parallel_time_taken: @@ -208,6 +211,7 @@ def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, profile_opts[key] = val simulator = QasmSimulator() + basis_gates = ['id', 'u', 'cx'] # Ensure fusion is enabled and method is statevector if gpu: @@ -219,10 +223,12 @@ def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, ratios = [] for qubit in range(min_qubits, max_qubits + 1): - profile_opts['fusion_enabled'] = False - non_fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, qubit, circuit) - profile_opts['fusion_enabled'] = True - fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, qubit, circuit) + profile_opts['fusion_enable'] = False + non_fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, + qubit, circuit, basis_gates) + profile_opts['fusion_enable'] = True + fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, + qubit, circuit, basis_gates) if return_ratios: ratios.append(non_fusion_time_taken / fusion_time_taken) elif non_fusion_time_taken < fusion_time_taken: @@ -239,7 +245,7 @@ def profile_fusion_costs(num_qubits, ntrials=10, backend_options=None, gpu=False profile_opts = {'method': 'statevector', 'max_parallel_experiments': 1, 'max_parallel_shots': 1, - 'fusion_threshold': 1} + 'fusion_enable': False} if backend_options is not None: for key, val in backend_options.items(): @@ -255,8 +261,6 @@ def profile_fusion_costs(num_qubits, ntrials=10, backend_options=None, gpu=False raise AerError('"statevector_gpu" backend is not supported on this system.') profile_opts['method'] = 'statevector_gpu' - basis_gates = ['id', 'u', 'cx', 'unitary', 'diagonal'] - all_gate_time = [] for target in range(0, 5): # Generate a circuit that consists of only unitary/diagonal gates with target-qubit @@ -275,7 +279,7 @@ def profile_fusion_costs(num_qubits, ntrials=10, backend_options=None, gpu=False profile_opts, num_qubits, profile_circuit, - basis_gates)) + None)) costs = [] for target in range(0, 5): From 7d9991897fb061de7fb155645c6ed30db7627c1e Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Tue, 23 Mar 2021 22:42:25 +0900 Subject: [PATCH 06/14] remove test_profile for a workaround --- test/terra/backends/test_profile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/terra/backends/test_profile.py b/test/terra/backends/test_profile.py index 6b295b27ec..c5028d64cc 100644 --- a/test/terra/backends/test_profile.py +++ b/test/terra/backends/test_profile.py @@ -26,6 +26,7 @@ from qiskit.providers.aer import QasmSimulator from qiskit.providers.aer.profile import optimize_backend_options, clear_optimized_backend_options +@unittest.skip("Remove this for manual run") class TestProfileQasmSimulator(common.QiskitAerTestCase): From 6ea3ab8959e29dc49c5f4a24c5561120e70b4fc9 Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Sat, 27 Mar 2021 06:18:11 +0900 Subject: [PATCH 07/14] save profiled performance options --- qiskit/providers/aer/aerprovider.py | 4 +- qiskit/providers/aer/backends/aerbackend.py | 27 ++-- qiskit/providers/aer/profile.py | 145 ++++++++++++++++---- test/terra/backends/test_profile.py | 31 ++++- 4 files changed, 157 insertions(+), 50 deletions(-) diff --git a/qiskit/providers/aer/aerprovider.py b/qiskit/providers/aer/aerprovider.py index 7846b897bc..f1c71a27e4 100644 --- a/qiskit/providers/aer/aerprovider.py +++ b/qiskit/providers/aer/aerprovider.py @@ -21,7 +21,7 @@ from .backends.statevector_simulator import StatevectorSimulator from .backends.unitary_simulator import UnitarySimulator from .backends.pulse_simulator import PulseSimulator -from .profile import optimize_backend_options +from .profile import profile_performance_options class AerProvider(BaseProvider): @@ -57,5 +57,5 @@ def __str__(self): @staticmethod def optimize_backend_options(min_qubits=10, max_qubits=20, ntrials=10): """Set optimal OpenMP and fusion options for backend.""" - return optimize_backend_options( + return profile_performance_options( min_qubits=min_qubits, max_qubits=max_qubits, ntrials=ntrials) diff --git a/qiskit/providers/aer/backends/aerbackend.py b/qiskit/providers/aer/backends/aerbackend.py index 7296afec82..6f3b6e147f 100644 --- a/qiskit/providers/aer/backends/aerbackend.py +++ b/qiskit/providers/aer/backends/aerbackend.py @@ -27,6 +27,8 @@ from qiskit.providers.models import BackendStatus from qiskit.result import Result +from qiskit.providers.aer.profile import get_performance_options + from ..aerjob import AerJob from ..aererror import AerError @@ -143,29 +145,16 @@ def run(self, DeprecationWarning, stacklevel=3) - profiled_options = {} - - def set_if_profiled(prop_name, profiled_attr_name): - # DEPRECATED - if backend_options is not None and prop_name in backend_options: - return - if prop_name in run_options or not hasattr(self, profiled_attr_name): - return - profiled_options[prop_name] = getattr(self, profiled_attr_name) - # Add default OpenMP options - set_if_profiled('statevector_parallel_threshold', '_statevector_parallel_threshold') - # Add default fusion options - attr_postfix = '' - if backend_options is not None and 'gpu' in backend_options.get('method', ''): - attr_postfix = '_gpu' - set_if_profiled('fusion_threshold', f'_fusion_threshold{attr_postfix}') - for i in range(1, 6): - set_if_profiled(f'fusion_cost.{i}', f'_fusion_cost{attr_postfix}.{i}') + gpu = backend_options is not None and 'gpu' in backend_options.get('method', '') + profiled_options = get_performance_options(gpu) + for run_option in run_options: + if run_option in profiled_options: + del profiled_options[run_option] # Add backend options to the Job qobj qobj = self._format_qobj( - qobj, backend_options=backend_options, **run_options, **profiled_options) + qobj, backend_options=backend_options, **profiled_options, **run_options) # Optional validation if validate: diff --git a/qiskit/providers/aer/profile.py b/qiskit/providers/aer/profile.py index 13e2ea77e1..4fa323b678 100644 --- a/qiskit/providers/aer/profile.py +++ b/qiskit/providers/aer/profile.py @@ -12,17 +12,19 @@ """ Profile backend options for optimal performance """ +import configparser as cp +import os +from socket import gethostname import numpy as np -from qiskit import transpile, assemble, execute, QuantumRegister, QuantumCircuit +from qiskit import transpile, assemble, execute, user_config, QuantumRegister, QuantumCircuit from qiskit.circuit.library import QuantumVolume from qiskit.quantum_info import random_unitary from .aererror import AerError -from .backends.aerbackend import AerBackend -from .backends.qasm_simulator import QasmSimulator -def optimize_backend_options(min_qubits=10, max_qubits=25, ntrials=5, circuit=None): +def profile_performance_options(min_qubits=10, max_qubits=25, ntrials=5, + circuit=None, persist=True): """Set optimal OpenMP and fusion options for backend.""" # Profile profile = {} @@ -33,7 +35,8 @@ def optimize_backend_options(min_qubits=10, max_qubits=25, ntrials=5, circuit=No parallel_threshold = profile_parallel_threshold( min_qubits=min_qubits, max_qubits=max_qubits, circuit=circuit, ntrials=ntrials) profile['statevector_parallel_threshold'] = parallel_threshold - _set_optimized_option('statevector_parallel_threshold', parallel_threshold) + _PerformanceOptions._set_option('statevector_parallel_threshold', + parallel_threshold, persist) except AerError: pass @@ -47,7 +50,7 @@ def optimize_backend_options(min_qubits=10, max_qubits=25, ntrials=5, circuit=No ntrials=ntrials, circuit=circuit) profile[f'fusion_threshold{postfix}'] = fusion_threshold - _set_optimized_option(f'fusion_threshold{postfix}', fusion_threshold) + _PerformanceOptions._set_option(f'fusion_threshold{postfix}', fusion_threshold, persist) default_qubit = 20 num_qubits = min(max(default_qubit, fusion_threshold), max_qubits) @@ -57,7 +60,7 @@ def optimize_backend_options(min_qubits=10, max_qubits=25, ntrials=5, circuit=No diagonal=False) for i, _ in enumerate(costs): profile[f'fusion_cost{postfix}.{i + 1}'] = costs[i] - _set_optimized_option(f'fusion_cost{postfix}.{i + 1}', costs[i]) + _PerformanceOptions._set_option(f'fusion_cost{postfix}.{i + 1}', costs[i], persist) except AerError: pass @@ -65,23 +68,113 @@ def optimize_backend_options(min_qubits=10, max_qubits=25, ntrials=5, circuit=No return profile -def _set_optimized_option(option_name, value): - setattr(AerBackend, f'_{option_name}', value) - - -def clear_optimized_backend_options(): - """Set profiled options for backend.""" - _clear_optimized_option('statevector_parallel_threshold') - for gpu in (False, True): - postfix = '_gpu' if gpu else '' - _clear_optimized_option(f'fusion_threshold{postfix}') +def get_performance_options(gpu=False): + """Return optimal OpenMP and fusion options for backend.""" + return _PerformanceOptions._get_performance_options(gpu) + + +def clear_performance_options(persist=True): + """Clear profiled options for backend.""" + return _PerformanceOptions._clear_performance_options(persist) + + +class _PerformanceOptions: + + _PERFORMANCE_OPTIONS = { + 'statevector_parallel_threshold': None, + **{f'fusion_threshold{postfix}': None for postfix in ['', '_gpu']}, + **{f'fusion_cost{postfix}.{i + 1}': None for postfix in ['', '_gpu'] for i in range(5)} + } + + @staticmethod + def _get_section_name(): + return f'perf:{gethostname()}' + + @staticmethod + def _set_option(option_name, value, persist): + _PerformanceOptions._PERFORMANCE_OPTIONS[option_name] = value + if persist: + config = cp.ConfigParser() + section_name = _PerformanceOptions._get_section_name() + filename = os.getenv('QISKIT_SETTINGS', user_config.DEFAULT_FILENAME) + if os.path.isfile(filename): + config.read(filename) + if section_name not in config.sections(): + config.add_section(section_name) + config.set(section_name, option_name, str(value)) + with open(filename, "w+") as config_file: + config.write(config_file) + + @staticmethod + def _get_options_from_file(option2type): + ret = {} + filename = os.getenv('QISKIT_SETTINGS', user_config.DEFAULT_FILENAME) + if not os.path.isfile(filename): + return ret + + config = cp.ConfigParser() + config.read(filename) + + section_name = _PerformanceOptions._get_section_name() + if section_name not in config.sections(): + return ret + + for option_name in option2type: + if option_name in config[section_name]: + ret[option_name] = option2type[option_name](config[section_name][option_name]) + + return ret + + @staticmethod + def _get_performance_options(gpu=False): + """Return optimal OpenMP and fusion options for backend.""" + # Profile + option2type = { + 'statevector_parallel_threshold': int, + **{f'fusion_threshold{postfix}': int for postfix in ['', '_gpu']}, + **{f'fusion_cost{postfix}.{i + 1}': float for postfix in ['', '_gpu'] for i in range(5)} + } + + profile = _PerformanceOptions._get_options_from_file(option2type) + + for option_name in option2type: + if _PerformanceOptions._PERFORMANCE_OPTIONS[option_name] is not None: + profile[option_name] = _PerformanceOptions._PERFORMANCE_OPTIONS[option_name] + + if 'fusion_threshold' in profile and gpu: + del profile['fusion_threshold'] + if 'fusion_threshold_gpu' in profile: + if gpu: + profile['fusion_threshold'] = profile['fusion_threshold_gpu'] + del profile['fusion_threshold_gpu'] for i in range(5): - _clear_optimized_option(f'fusion_cost{postfix}.{i + 1}') - - -def _clear_optimized_option(option_name): - if hasattr(AerBackend, f'_{option_name}'): - delattr(AerBackend, f'_{option_name}') + if f'fusion_cost.{i + 1}' in profile and gpu: + del profile[f'fusion_cost.{i + 1}'] + if f'fusion_cost_gpu.{i + 1}' in profile: + if gpu: + profile[f'fusion_cost.{i + 1}'] = profile[f'fusion_cost_gpu.{i + 1}'] + del profile[f'fusion_cost_gpu.{i + 1}'] + + return profile + + @staticmethod + def _clear_performance_options(persist=True): + """clear profiled options for backend.""" + _PerformanceOptions._PERFORMANCE_OPTIONS = { + 'statevector_parallel_threshold': None, + **{f'fusion_threshold{postfix}': None for postfix in ['', '_gpu']}, + **{f'fusion_cost{postfix}.{i + 1}': None for postfix in ['', '_gpu'] for i in range(5)} + } + if persist: + config = cp.ConfigParser() + filename = os.getenv('QISKIT_SETTINGS', user_config.DEFAULT_FILENAME) + if os.path.isfile(filename): + config.read(filename) + section_name = _PerformanceOptions._get_section_name() + if section_name in config.sections(): + config.remove_section(section_name) + with open(filename, "w+") as config_file: + config.write(config_file) def _generate_profile_circuit(profile_qubit, base_circuit=None, basis_gates=None): @@ -174,6 +267,8 @@ def profile_parallel_threshold(min_qubits=10, max_qubits=20, ntrials=10, for key, val in backend_options.items(): profile_opts[key] = val + # pylint: disable=C0415 + from .backends.qasm_simulator import QasmSimulator simulator = QasmSimulator() basis_gates = ['id', 'u', 'cx'] @@ -210,6 +305,8 @@ def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, for key, val in backend_options.items(): profile_opts[key] = val + # pylint: disable=C0415 + from .backends.qasm_simulator import QasmSimulator simulator = QasmSimulator() basis_gates = ['id', 'u', 'cx'] @@ -251,6 +348,8 @@ def profile_fusion_costs(num_qubits, ntrials=10, backend_options=None, gpu=False for key, val in backend_options.items(): profile_opts[key] = val + # pylint: disable=C0415 + from .backends.qasm_simulator import QasmSimulator simulator = QasmSimulator() # Ensure fusion is enabled and method is statevector diff --git a/test/terra/backends/test_profile.py b/test/terra/backends/test_profile.py index c5028d64cc..10b644aea7 100644 --- a/test/terra/backends/test_profile.py +++ b/test/terra/backends/test_profile.py @@ -24,14 +24,14 @@ from qiskit.compiler import assemble from qiskit.circuit.library import QuantumVolume from qiskit.providers.aer import QasmSimulator -from qiskit.providers.aer.profile import optimize_backend_options, clear_optimized_backend_options +from qiskit.providers.aer.profile import profile_performance_options, clear_performance_options -@unittest.skip("Remove this for manual run") +#@unittest.skip("Remove this for manual run") class TestProfileQasmSimulator(common.QiskitAerTestCase): def test_profile(self): - profile = optimize_backend_options() + profile = profile_performance_options() simulator = QasmSimulator() @@ -52,18 +52,37 @@ def test_profile(self): job = execute(circuit, simulator, method='statevector') self.assertTrue(hasattr(job.qobj().config, 'fusion_cost.1')) - clear_optimized_backend_options() + clear_performance_options(True) circuit = QuantumVolume(10, 1) job = execute(circuit, simulator, method='statevector') self.assertFalse(hasattr(job.qobj().config, 'statevector_parallel_threshold')) self.assertFalse(hasattr(job.qobj().config, 'fusion_threshold')) self.assertFalse(hasattr(job.qobj().config, 'fusion_cost.1')) + def test_profile_save(self): + profile = profile_performance_options(persist=True) + + simulator = QasmSimulator() + + clear_performance_options(False) + + if 'statevector_parallel_threshold' in profile: + circuit = QuantumVolume(profile['statevector_parallel_threshold'], 1) + job = execute(circuit, simulator, method='statevector') + self.assertTrue(hasattr(job.qobj().config, 'statevector_parallel_threshold')) + + clear_performance_options(True) + + if 'statevector_parallel_threshold' in profile: + circuit = QuantumVolume(profile['statevector_parallel_threshold'], 1) + job = execute(circuit, simulator, method='statevector') + self.assertFalse(hasattr(job.qobj().config, 'statevector_parallel_threshold')) + def test_profile_with_custom_circuit(self): circuit = transpile(QuantumVolume(20, 5), basis_gates = ['id', 'u', 'cx']) - profile = optimize_backend_options(min_qubits=10, max_qubits=25, ntrials=5, circuit=circuit) + profile = profile_performance_options(min_qubits=10, max_qubits=25, ntrials=5, circuit=circuit) simulator = QasmSimulator() @@ -84,7 +103,7 @@ def test_profile_with_custom_circuit(self): job = execute(circuit, simulator, method='statevector') self.assertTrue(hasattr(job.qobj().config, 'fusion_cost.1')) - clear_optimized_backend_options() + clear_performance_options(True) circuit = QuantumVolume(10, 1) job = execute(circuit, simulator, method='statevector') self.assertFalse(hasattr(job.qobj().config, 'statevector_parallel_threshold')) From 12db16fd9cefcaaab89f8e13416e53b126a6c702 Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Tue, 30 Mar 2021 10:27:38 +0900 Subject: [PATCH 08/14] format import in aerbackend --- qiskit/providers/aer/backends/aerbackend.py | 3 +-- qiskit/providers/aer/profile.py | 18 +++++++++--------- test/terra/backends/test_profile.py | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/qiskit/providers/aer/backends/aerbackend.py b/qiskit/providers/aer/backends/aerbackend.py index ea573f504d..4ec539cbc6 100644 --- a/qiskit/providers/aer/backends/aerbackend.py +++ b/qiskit/providers/aer/backends/aerbackend.py @@ -30,10 +30,9 @@ from qiskit.qobj import QasmQobj, PulseQobj from qiskit.compiler import assemble -from qiskit.providers.aer.profile import get_performance_options - from ..aerjob import AerJob from ..aererror import AerError +from ..profile import get_performance_options # Logger logger = logging.getLogger(__name__) diff --git a/qiskit/providers/aer/profile.py b/qiskit/providers/aer/profile.py index 4fa323b678..2839062232 100644 --- a/qiskit/providers/aer/profile.py +++ b/qiskit/providers/aer/profile.py @@ -99,11 +99,11 @@ def _set_option(option_name, value, persist): filename = os.getenv('QISKIT_SETTINGS', user_config.DEFAULT_FILENAME) if os.path.isfile(filename): config.read(filename) - if section_name not in config.sections(): - config.add_section(section_name) - config.set(section_name, option_name, str(value)) - with open(filename, "w+") as config_file: - config.write(config_file) + if section_name not in config.sections(): + config.add_section(section_name) + config.set(section_name, option_name, str(value)) + with open(filename, "w+") as config_file: + config.write(config_file) @staticmethod def _get_options_from_file(option2type): @@ -337,7 +337,8 @@ def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, raise AerError(f'Unable to find threshold in range [{min_qubits}, {max_qubits}]') -def profile_fusion_costs(num_qubits, ntrials=10, backend_options=None, gpu=False, diagonal=False): +def profile_fusion_costs(num_qubits, ntrials=10, backend_options=None, gpu=False, diagonal=False, + return_ratio=True): """Evaluate optimal costs in cost-based fusion for current system.""" profile_opts = {'method': 'statevector', 'max_parallel_experiments': 1, @@ -380,8 +381,7 @@ def profile_fusion_costs(num_qubits, ntrials=10, backend_options=None, gpu=False profile_circuit, None)) - costs = [] - for target in range(0, 5): - costs.append(all_gate_time[target] / all_gate_time[0]) + base_line = all_gate_time[0] if return_ratio else 1.0 + costs = [gate_time / base_line for gate_time in all_gate_time] return costs diff --git a/test/terra/backends/test_profile.py b/test/terra/backends/test_profile.py index 10b644aea7..5689ae71b2 100644 --- a/test/terra/backends/test_profile.py +++ b/test/terra/backends/test_profile.py @@ -26,7 +26,7 @@ from qiskit.providers.aer import QasmSimulator from qiskit.providers.aer.profile import profile_performance_options, clear_performance_options -#@unittest.skip("Remove this for manual run") +@unittest.skip("Remove this for manual run") class TestProfileQasmSimulator(common.QiskitAerTestCase): From 1c036be1efbe79bf8ec521a44efdbf41ad2b8450 Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Thu, 8 Apr 2021 14:24:19 +0900 Subject: [PATCH 09/14] truncate unnecessary options and determine options more carefully --- qiskit/providers/aer/backends/aerbackend.py | 20 +- qiskit/providers/aer/profile.py | 267 ++++++++++++-------- test/terra/backends/test_profile.py | 69 +++-- 3 files changed, 226 insertions(+), 130 deletions(-) diff --git a/qiskit/providers/aer/backends/aerbackend.py b/qiskit/providers/aer/backends/aerbackend.py index 4ec539cbc6..388a6fea13 100644 --- a/qiskit/providers/aer/backends/aerbackend.py +++ b/qiskit/providers/aer/backends/aerbackend.py @@ -138,18 +138,31 @@ def run(self, and direct kwarg's should be used for options to pass them to ``run_options``. """ + profiled_options = {} + num_qubits = None if isinstance(circuits, (QasmQobj, PulseQobj)): warnings.warn('Using a qobj for run() is deprecated and will be ' 'removed in a future release.', PendingDeprecationWarning, stacklevel=2) qobj = circuits + if isinstance(qobj, QasmQobj): + num_qubits = qobj.config.n_qubits else: options_dict = {} for key, value in self.options.__dict__.items(): if value is not None: options_dict[key] = value qobj = assemble(circuits, self, **options_dict) + num_qubits = (circuits[0] if isinstance(circuits, list) else circuits).num_qubits + + # Add default OpenMP options + gpu = backend_options is not None and 'gpu' in backend_options.get('method', '') + profiled_options = get_performance_options(num_qubits, gpu) + for run_option in run_options: + if run_option in profiled_options: + del profiled_options[run_option] + # DEPRECATED if backend_options is not None: warnings.warn( @@ -160,13 +173,6 @@ def run(self, DeprecationWarning, stacklevel=3) - # Add default OpenMP options - gpu = backend_options is not None and 'gpu' in backend_options.get('method', '') - profiled_options = get_performance_options(gpu) - for run_option in run_options: - if run_option in profiled_options: - del profiled_options[run_option] - # Add backend options to the Job qobj qobj = self._format_qobj( qobj, backend_options=backend_options, **profiled_options, **run_options) diff --git a/qiskit/providers/aer/profile.py b/qiskit/providers/aer/profile.py index 2839062232..650e0281bc 100644 --- a/qiskit/providers/aer/profile.py +++ b/qiskit/providers/aer/profile.py @@ -15,6 +15,7 @@ import configparser as cp import os from socket import gethostname +import time import numpy as np from qiskit import transpile, assemble, execute, user_config, QuantumRegister, QuantumCircuit @@ -23,7 +24,7 @@ from .aererror import AerError -def profile_performance_options(min_qubits=10, max_qubits=25, ntrials=5, +def profile_performance_options(min_qubits=10, max_qubits=25, ntrials=10, circuit=None, persist=True): """Set optimal OpenMP and fusion options for backend.""" # Profile @@ -44,33 +45,35 @@ def profile_performance_options(min_qubits=10, max_qubits=25, ntrials=5, for gpu in (False, True): postfix = '_gpu' if gpu else '' try: + qubit_to_costs = profile_fusion_costs(min_qubits=min_qubits, + max_qubits=max_qubits, + ntrials=ntrials, + gpu=gpu, + diagonal=False) + for num_qubits in qubit_to_costs: + costs = qubit_to_costs[num_qubits] + for i, _ in enumerate(costs): + profile[f'fusion_cost{postfix}.{num_qubits}.{i + 1}'] = costs[i] + _PerformanceOptions._set_option(f'fusion_cost{postfix}.{num_qubits}.{i + 1}', + costs[i], persist) + fusion_threshold = profile_fusion_threshold(gpu=gpu, min_qubits=min_qubits, max_qubits=max_qubits, ntrials=ntrials, circuit=circuit) profile[f'fusion_threshold{postfix}'] = fusion_threshold - _PerformanceOptions._set_option(f'fusion_threshold{postfix}', fusion_threshold, persist) - - default_qubit = 20 - num_qubits = min(max(default_qubit, fusion_threshold), max_qubits) - costs = profile_fusion_costs(num_qubits, - ntrials=ntrials, - gpu=gpu, - diagonal=False) - for i, _ in enumerate(costs): - profile[f'fusion_cost{postfix}.{i + 1}'] = costs[i] - _PerformanceOptions._set_option(f'fusion_cost{postfix}.{i + 1}', costs[i], persist) - + _PerformanceOptions._set_option(f'fusion_threshold{postfix}', + fusion_threshold, persist) except AerError: pass return profile -def get_performance_options(gpu=False): +def get_performance_options(num_qubits, gpu=False): """Return optimal OpenMP and fusion options for backend.""" - return _PerformanceOptions._get_performance_options(gpu) + return _PerformanceOptions._get_performance_options(num_qubits, gpu) def clear_performance_options(persist=True): @@ -83,15 +86,24 @@ class _PerformanceOptions: _PERFORMANCE_OPTIONS = { 'statevector_parallel_threshold': None, **{f'fusion_threshold{postfix}': None for postfix in ['', '_gpu']}, - **{f'fusion_cost{postfix}.{i + 1}': None for postfix in ['', '_gpu'] for i in range(5)} + **{f'fusion_cost{postfix}.{num_qubits}.{i + 1}': None + for num_qubits in range(1, 64) + for postfix in ['', '_gpu'] + for i in range(5)} } + _DEFAULT_PARALLEL_THRESHOLD = 14 + _DEFAULT_FUSION_THRESHOLD = 14 + + _CACHED_PERFORMANCE_OPTIONS = {} + @staticmethod def _get_section_name(): return f'perf:{gethostname()}' @staticmethod def _set_option(option_name, value, persist): + _PerformanceOptions._CACHED_PERFORMANCE_OPTIONS.clear() _PerformanceOptions._PERFORMANCE_OPTIONS[option_name] = value if persist: config = cp.ConfigParser() @@ -106,56 +118,73 @@ def _set_option(option_name, value, persist): config.write(config_file) @staticmethod - def _get_options_from_file(option2type): - ret = {} + def _get_options_from_file(option_to_type): filename = os.getenv('QISKIT_SETTINGS', user_config.DEFAULT_FILENAME) if not os.path.isfile(filename): - return ret + return {} config = cp.ConfigParser() config.read(filename) section_name = _PerformanceOptions._get_section_name() if section_name not in config.sections(): - return ret + return {} - for option_name in option2type: - if option_name in config[section_name]: - ret[option_name] = option2type[option_name](config[section_name][option_name]) + ret = {} + for option_name in config[section_name]: + ret[option_name] = option_to_type[option_name](config[section_name][option_name]) return ret @staticmethod - def _get_performance_options(gpu=False): + def _get_performance_options(num_qubits, gpu=False): """Return optimal OpenMP and fusion options for backend.""" + + cache_key = (num_qubits, gpu) + if cache_key in _PerformanceOptions._CACHED_PERFORMANCE_OPTIONS: + return _PerformanceOptions._CACHED_PERFORMANCE_OPTIONS[cache_key] + # Profile - option2type = { + postfix = '_gpu' if gpu else '' + option_to_type = { 'statevector_parallel_threshold': int, - **{f'fusion_threshold{postfix}': int for postfix in ['', '_gpu']}, - **{f'fusion_cost{postfix}.{i + 1}': float for postfix in ['', '_gpu'] for i in range(5)} + f'fusion_threshold{postfix}': int, + **{f'fusion_cost{postfix}.{profiled_qubits}.{i + 1}': float + for i in range(5) + for profiled_qubits in range(64)} } + all_profile = _PerformanceOptions._get_options_from_file(option_to_type) - profile = _PerformanceOptions._get_options_from_file(option2type) + for option_name in option_to_type: + if option_name not in _PerformanceOptions._PERFORMANCE_OPTIONS: + continue + v = _PerformanceOptions._PERFORMANCE_OPTIONS[option_name] + if v is not None: + all_profile[option_name] = v - for option_name in option2type: - if _PerformanceOptions._PERFORMANCE_OPTIONS[option_name] is not None: - profile[option_name] = _PerformanceOptions._PERFORMANCE_OPTIONS[option_name] - - if 'fusion_threshold' in profile and gpu: - del profile['fusion_threshold'] - if 'fusion_threshold_gpu' in profile: - if gpu: - profile['fusion_threshold'] = profile['fusion_threshold_gpu'] - del profile['fusion_threshold_gpu'] - for i in range(5): - if f'fusion_cost.{i + 1}' in profile and gpu: - del profile[f'fusion_cost.{i + 1}'] - if f'fusion_cost_gpu.{i + 1}' in profile: - if gpu: - profile[f'fusion_cost.{i + 1}'] = profile[f'fusion_cost_gpu.{i + 1}'] - del profile[f'fusion_cost_gpu.{i + 1}'] - - return profile + ret = {} + disable_fusion = False + if 'statevector_parallel_threshold' in all_profile: + profiled_value = all_profile['statevector_parallel_threshold'] + if num_qubits in range(profiled_value + 1, + _PerformanceOptions._DEFAULT_PARALLEL_THRESHOLD + 1): + ret['statevector_parallel_threshold'] = profiled_value + if f'fusion_threshold{postfix}' in all_profile: + profiled_value = all_profile[f'fusion_threshold{postfix}'] + if num_qubits in range(profiled_value + 1, + _PerformanceOptions._DEFAULT_FUSION_THRESHOLD + 1): + ret['fusion_threshold'] = profiled_value + disable_fusion = num_qubits < profiled_value + + if not disable_fusion: + for profiled_qubits in range(1, num_qubits + 1): + for i in range(5): + profile_name = f'fusion_cost{postfix}.{profiled_qubits}.{i + 1}' + if profile_name in all_profile: + ret[f'fusion_cost.{i + 1}'] = all_profile[profile_name] + + _PerformanceOptions._CACHED_PERFORMANCE_OPTIONS[cache_key] = ret + return ret @staticmethod def _clear_performance_options(persist=True): @@ -163,8 +192,14 @@ def _clear_performance_options(persist=True): _PerformanceOptions._PERFORMANCE_OPTIONS = { 'statevector_parallel_threshold': None, **{f'fusion_threshold{postfix}': None for postfix in ['', '_gpu']}, - **{f'fusion_cost{postfix}.{i + 1}': None for postfix in ['', '_gpu'] for i in range(5)} + **{f'fusion_cost{postfix}.{num_qubits}.{i + 1}': None + for num_qubits in range(1, 64) + for postfix in ['', '_gpu'] + for i in range(5)} } + + _PerformanceOptions._CACHED_PERFORMANCE_OPTIONS.clear() + if persist: config = cp.ConfigParser() filename = os.getenv('QISKIT_SETTINGS', user_config.DEFAULT_FILENAME) @@ -182,7 +217,7 @@ def _generate_profile_circuit(profile_qubit, base_circuit=None, basis_gates=None raise AerError(f'number of qubit is too small: {profile_qubit}') if base_circuit is None: - return transpile(QuantumVolume(profile_qubit, 10), basis_gates=['id', 'u', 'cx']) + return transpile(QuantumVolume(profile_qubit), basis_gates=['id', 'u', 'cx']) if basis_gates is not None: profile_circuit = transpile(base_circuit.copy(), basis_gates=basis_gates) @@ -236,22 +271,29 @@ def global_qubit(index): def _profile_run(simulator, ntrials, backend_options, - qubit, circuit, basis_gates=None): + qubit, circuit, basis_gates=None, use_time_taken=False): profile_circuit = _generate_profile_circuit(qubit, circuit, basis_gates) - qobj = assemble(ntrials * [profile_circuit], shots=1) + qobj = assemble(profile_circuit, shots=1) - result = simulator.run(qobj, **backend_options).result() + total_time_taken = 0.0 + total_time_elapsed = 0.0 + for _ in range(ntrials): + start_ts = time.time() + result = simulator.run(qobj, **backend_options).result() + end_ts = time.time() - if not result.success: - raise AerError('Failed to run a profile circuit') + if not result.success: + raise AerError('Failed to run a profile circuit') - time_taken = 0.0 - for j in range(ntrials): - time_taken += result.results[j].time_taken + total_time_taken += result.results[0].time_taken + total_time_elapsed += (end_ts - start_ts) - return time_taken + if use_time_taken: + return total_time_taken + else: + return total_time_elapsed def profile_parallel_threshold(min_qubits=10, max_qubits=20, ntrials=10, @@ -274,16 +316,22 @@ def profile_parallel_threshold(min_qubits=10, max_qubits=20, ntrials=10, ratios = [] for qubit in range(min_qubits, max_qubits + 1): - profile_opts['statevector_parallel_threshold'] = 64 - serial_time_taken = _profile_run(simulator, ntrials, profile_opts, - qubit, circuit, basis_gates) - profile_opts['statevector_parallel_threshold'] = 1 - parallel_time_taken = _profile_run(simulator, ntrials, profile_opts, - qubit, circuit, basis_gates) - if return_ratios: - ratios.append(serial_time_taken / parallel_time_taken) - elif serial_time_taken < parallel_time_taken: - return qubit + always_better = True + for _ in range(10): + profile_opts['statevector_parallel_threshold'] = 64 + serial_time_taken = _profile_run(simulator, ntrials, profile_opts, + qubit, circuit, basis_gates) + profile_opts['statevector_parallel_threshold'] = 1 + parallel_time_taken = _profile_run(simulator, ntrials, profile_opts, + qubit, circuit, basis_gates) + if return_ratios: + ratios.append(serial_time_taken / parallel_time_taken) + break + if parallel_time_taken >= serial_time_taken: + always_better = False + break + if not return_ratios and always_better: + return qubit - 1 if return_ratios: return ratios @@ -320,16 +368,22 @@ def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, ratios = [] for qubit in range(min_qubits, max_qubits + 1): - profile_opts['fusion_enable'] = False - non_fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, + always_better = True + for _ in range(10): + profile_opts['fusion_enable'] = False + non_fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, + qubit, circuit, basis_gates) + profile_opts['fusion_enable'] = True + fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, qubit, circuit, basis_gates) - profile_opts['fusion_enable'] = True - fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, - qubit, circuit, basis_gates) - if return_ratios: - ratios.append(non_fusion_time_taken / fusion_time_taken) - elif non_fusion_time_taken < fusion_time_taken: - return qubit + if return_ratios: + ratios.append(non_fusion_time_taken / fusion_time_taken) + break + if fusion_time_taken >= non_fusion_time_taken: + always_better = False + break + if not return_ratios and always_better: + return qubit - 1 if return_ratios: return ratios @@ -337,8 +391,8 @@ def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, raise AerError(f'Unable to find threshold in range [{min_qubits}, {max_qubits}]') -def profile_fusion_costs(num_qubits, ntrials=10, backend_options=None, gpu=False, diagonal=False, - return_ratio=True): +def profile_fusion_costs(min_qubits=10, max_qubits=20, ntrials=10, backend_options=None, + gpu=False, diagonal=False, return_ratio=True): """Evaluate optimal costs in cost-based fusion for current system.""" profile_opts = {'method': 'statevector', 'max_parallel_experiments': 1, @@ -361,27 +415,34 @@ def profile_fusion_costs(num_qubits, ntrials=10, backend_options=None, gpu=False raise AerError('"statevector_gpu" backend is not supported on this system.') profile_opts['method'] = 'statevector_gpu' - all_gate_time = [] - for target in range(0, 5): - # Generate a circuit that consists of only unitary/diagonal gates with target-qubit - profile_circuit = QuantumCircuit(num_qubits) - if diagonal: - for i in range(0, 100): - qubits = [q % num_qubits for q in range(i, i + target + 1)] - profile_circuit.diagonal([1, -1] * (2 ** target), qubits) - else: - for i in range(0, 100): - qubits = [q % num_qubits for q in range(i, i + target + 1)] - profile_circuit.unitary(random_unitary(2 ** (target + 1)), qubits) - - all_gate_time.append(_profile_run(simulator, - ntrials, - profile_opts, - num_qubits, - profile_circuit, - None)) - - base_line = all_gate_time[0] if return_ratio else 1.0 - costs = [gate_time / base_line for gate_time in all_gate_time] - - return costs + qubit_to_costs = {} + for num_qubits in range(min_qubits, max_qubits + 1): + all_gate_time = [] + for target in range(5, 0, -1): + # Generate a circuit that consists of only unitary/diagonal gates with target-qubit + profile_circuit = QuantumCircuit(num_qubits) + loop = 32 + if num_qubits > 20: + loop = max(1, int(loop / (2 ** (num_qubits - 20)))) + if diagonal: + for i in range(0, loop): + qubits = [q % num_qubits for q in range(i, i + target)] + profile_circuit.diagonal([1, -1] * (2 ** (target + 1), qubits)) + else: + for i in range(0, loop): + qubits = [q % num_qubits for q in range(i, i + target)] + profile_circuit.unitary(random_unitary(2 ** target), qubits) + + all_gate_time.append(_profile_run(simulator, + ntrials, + profile_opts, + num_qubits, + profile_circuit, + basis_gates=None, + use_time_taken=True)) + all_gate_time.reverse() + base_line = all_gate_time[0] if return_ratio else 1.0 + costs = [gate_time / base_line for gate_time in all_gate_time] + qubit_to_costs[num_qubits] = costs + + return qubit_to_costs diff --git a/test/terra/backends/test_profile.py b/test/terra/backends/test_profile.py index 5689ae71b2..44d29baa0e 100644 --- a/test/terra/backends/test_profile.py +++ b/test/terra/backends/test_profile.py @@ -24,9 +24,11 @@ from qiskit.compiler import assemble from qiskit.circuit.library import QuantumVolume from qiskit.providers.aer import QasmSimulator -from qiskit.providers.aer.profile import profile_performance_options, clear_performance_options +from qiskit.providers.aer.profile import (profile_performance_options, + get_performance_options, + clear_performance_options) -@unittest.skip("Remove this for manual run") +#@unittest.skip("Remove this for manual run") class TestProfileQasmSimulator(common.QiskitAerTestCase): @@ -53,11 +55,11 @@ def test_profile(self): self.assertTrue(hasattr(job.qobj().config, 'fusion_cost.1')) clear_performance_options(True) - circuit = QuantumVolume(10, 1) - job = execute(circuit, simulator, method='statevector') - self.assertFalse(hasattr(job.qobj().config, 'statevector_parallel_threshold')) - self.assertFalse(hasattr(job.qobj().config, 'fusion_threshold')) - self.assertFalse(hasattr(job.qobj().config, 'fusion_cost.1')) + + profile = get_performance_options(10) + self.assertFalse('statevector_parallel_threshold' in profile) + self.assertFalse('fusion_threshold' in profile) + self.assertFalse('fusion_cost.1' in profile) def test_profile_save(self): profile = profile_performance_options(persist=True) @@ -66,17 +68,45 @@ def test_profile_save(self): clear_performance_options(False) - if 'statevector_parallel_threshold' in profile: - circuit = QuantumVolume(profile['statevector_parallel_threshold'], 1) - job = execute(circuit, simulator, method='statevector') - self.assertTrue(hasattr(job.qobj().config, 'statevector_parallel_threshold')) + statevector_parallel_threshold = None + fusion_cost = None + fusion_cost_qubit = None + + for qubit in range(10, 25): + profile = get_performance_options(qubit) + if 'statevector_parallel_threshold' in profile: + statevector_parallel_threshold = profile['statevector_parallel_threshold'] + if 'fusion_cost.1' in profile: + fusion_cost = profile['fusion_cost.1'] + fusion_cost_qubit = qubit + + self.assertTrue(statevector_parallel_threshold is not None) + self.assertTrue(fusion_cost is not None) + + circuit = QuantumVolume(fusion_cost_qubit + 1, 1) + job = execute(circuit, simulator, method='statevector') + self.assertTrue(hasattr(job.qobj().config, 'fusion_cost.1')) clear_performance_options(True) - if 'statevector_parallel_threshold' in profile: - circuit = QuantumVolume(profile['statevector_parallel_threshold'], 1) - job = execute(circuit, simulator, method='statevector') - self.assertFalse(hasattr(job.qobj().config, 'statevector_parallel_threshold')) + statevector_parallel_threshold = None + fusion_threshold = None + fusion_cost = None + fusion_cost_qubit = None + + for qubit in range(10, 25): + profile = get_performance_options(qubit) + if 'statevector_parallel_threshold' in profile: + statevector_parallel_threshold = profile['statevector_parallel_threshold'] + if 'fusion_threshold' in profile: + fusion_threshold = profile['fusion_threshold'] + if 'fusion_cost.1' in profile: + fusion_cost = profile['fusion_cost.1'] + fusion_cost_qubit = qubit + + self.assertFalse(statevector_parallel_threshold is not None) + self.assertFalse(fusion_threshold is not None) + self.assertFalse(fusion_cost is not None) def test_profile_with_custom_circuit(self): @@ -104,8 +134,7 @@ def test_profile_with_custom_circuit(self): self.assertTrue(hasattr(job.qobj().config, 'fusion_cost.1')) clear_performance_options(True) - circuit = QuantumVolume(10, 1) - job = execute(circuit, simulator, method='statevector') - self.assertFalse(hasattr(job.qobj().config, 'statevector_parallel_threshold')) - self.assertFalse(hasattr(job.qobj().config, 'fusion_threshold')) - self.assertFalse(hasattr(job.qobj().config, 'fusion_cost.1')) + profile = get_performance_options(10) + self.assertFalse('statevector_parallel_threshold' in profile) + self.assertFalse('fusion_threshold' in profile) + self.assertFalse('fusion_cost.1' in profile) From 6f1bf8543a5efe3fff4156b77d00d3fc11ebf2d4 Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Thu, 8 Apr 2021 14:50:15 +0900 Subject: [PATCH 10/14] skip profiling test --- test/terra/backends/test_profile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/terra/backends/test_profile.py b/test/terra/backends/test_profile.py index 44d29baa0e..7fbc15c3d2 100644 --- a/test/terra/backends/test_profile.py +++ b/test/terra/backends/test_profile.py @@ -28,7 +28,7 @@ get_performance_options, clear_performance_options) -#@unittest.skip("Remove this for manual run") +@unittest.skip("Remove this for manual run") class TestProfileQasmSimulator(common.QiskitAerTestCase): From 14b780321c4681d2b4ad96fb2bd6bf934116c3a2 Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Thu, 8 Apr 2021 15:16:39 +0900 Subject: [PATCH 11/14] stop use profiled options for pulse --- qiskit/providers/aer/backends/aerbackend.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/qiskit/providers/aer/backends/aerbackend.py b/qiskit/providers/aer/backends/aerbackend.py index a273db01b8..7fbd309934 100644 --- a/qiskit/providers/aer/backends/aerbackend.py +++ b/qiskit/providers/aer/backends/aerbackend.py @@ -161,12 +161,13 @@ def run(self, qobj = assemble(circuits, self) num_qubits = (circuits[0] if isinstance(circuits, list) else circuits).num_qubits - # Get profiled options for performance - gpu = backend_options is not None and 'gpu' in backend_options.get('method', '') - profiled_options = get_performance_options(num_qubits, gpu) - for run_option in run_options: - if run_option in profiled_options: - del profiled_options[run_option] + if num_qubits is not None: + # Get profiled options for performance + gpu = backend_options is not None and 'gpu' in backend_options.get('method', '') + profiled_options = get_performance_options(num_qubits, gpu) + for run_option in run_options: + if run_option in profiled_options: + del profiled_options[run_option] self._add_options_to_qobj( qobj, backend_options=backend_options, **profiled_options, **run_options) From 5a7d30d18fdddd6c58342e1e8c712743ef8d4c86 Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Thu, 8 Apr 2021 18:50:14 +0900 Subject: [PATCH 12/14] reduce overhead of profiling --- qiskit/providers/aer/profile.py | 84 +++++++++++++++-------------- test/terra/backends/test_profile.py | 11 ---- 2 files changed, 45 insertions(+), 50 deletions(-) diff --git a/qiskit/providers/aer/profile.py b/qiskit/providers/aer/profile.py index 650e0281bc..7cfedbbfc9 100644 --- a/qiskit/providers/aer/profile.py +++ b/qiskit/providers/aer/profile.py @@ -25,7 +25,7 @@ def profile_performance_options(min_qubits=10, max_qubits=25, ntrials=10, - circuit=None, persist=True): + circuit=None, persist=True, gpu=False): """Set optimal OpenMP and fusion options for backend.""" # Profile profile = {} @@ -42,13 +42,15 @@ def profile_performance_options(min_qubits=10, max_qubits=25, ntrials=10, pass # Profile CPU and GPU fusion threshold - for gpu in (False, True): - postfix = '_gpu' if gpu else '' + for with_gpu in (False, True): + postfix = '_gpu' if with_gpu else '' + if with_gpu and not gpu: + continue try: qubit_to_costs = profile_fusion_costs(min_qubits=min_qubits, max_qubits=max_qubits, ntrials=ntrials, - gpu=gpu, + gpu=with_gpu, diagonal=False) for num_qubits in qubit_to_costs: costs = qubit_to_costs[num_qubits] @@ -57,7 +59,7 @@ def profile_performance_options(min_qubits=10, max_qubits=25, ntrials=10, _PerformanceOptions._set_option(f'fusion_cost{postfix}.{num_qubits}.{i + 1}', costs[i], persist) - fusion_threshold = profile_fusion_threshold(gpu=gpu, + fusion_threshold = profile_fusion_threshold(gpu=with_gpu, min_qubits=min_qubits, max_qubits=max_qubits, ntrials=ntrials, @@ -270,15 +272,17 @@ def global_qubit(index): return profile_circuit -def _profile_run(simulator, ntrials, backend_options, - qubit, circuit, basis_gates=None, use_time_taken=False): +def _profile_run(simulator, ntrials, backend_options, qubit, circuit, + basis_gates=None, use_time_taken=False, return_all=False): profile_circuit = _generate_profile_circuit(qubit, circuit, basis_gates) qobj = assemble(profile_circuit, shots=1) total_time_taken = 0.0 + time_taken_list = [] total_time_elapsed = 0.0 + time_elapsed_list = [] for _ in range(ntrials): start_ts = time.time() result = simulator.run(qobj, **backend_options).result() @@ -288,12 +292,20 @@ def _profile_run(simulator, ntrials, backend_options, raise AerError('Failed to run a profile circuit') total_time_taken += result.results[0].time_taken + time_taken_list.append(result.results[0].time_taken) total_time_elapsed += (end_ts - start_ts) + time_elapsed_list.append(end_ts - start_ts) if use_time_taken: - return total_time_taken + if return_all: + return time_taken_list + else: + return total_time_taken else: - return total_time_elapsed + if return_all: + return time_elapsed_list + else: + return total_time_elapsed def profile_parallel_threshold(min_qubits=10, max_qubits=20, ntrials=10, @@ -316,21 +328,18 @@ def profile_parallel_threshold(min_qubits=10, max_qubits=20, ntrials=10, ratios = [] for qubit in range(min_qubits, max_qubits + 1): - always_better = True - for _ in range(10): - profile_opts['statevector_parallel_threshold'] = 64 - serial_time_taken = _profile_run(simulator, ntrials, profile_opts, - qubit, circuit, basis_gates) - profile_opts['statevector_parallel_threshold'] = 1 - parallel_time_taken = _profile_run(simulator, ntrials, profile_opts, - qubit, circuit, basis_gates) - if return_ratios: - ratios.append(serial_time_taken / parallel_time_taken) - break - if parallel_time_taken >= serial_time_taken: - always_better = False - break - if not return_ratios and always_better: + profile_opts['statevector_parallel_threshold'] = 64 + serial_time_taken = _profile_run(simulator, ntrials, profile_opts, + qubit, circuit, basis_gates, return_all=True) + profile_opts['statevector_parallel_threshold'] = 1 + parallel_time_taken = _profile_run(simulator, ntrials, profile_opts, + qubit, circuit, basis_gates, return_all=True) + if return_ratios: + ratios.append(sum(serial_time_taken) / sum(parallel_time_taken)) + break + if max(parallel_time_taken) >= min(serial_time_taken): + continue + if not return_ratios: return qubit - 1 if return_ratios: @@ -368,21 +377,18 @@ def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, ratios = [] for qubit in range(min_qubits, max_qubits + 1): - always_better = True - for _ in range(10): - profile_opts['fusion_enable'] = False - non_fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, - qubit, circuit, basis_gates) - profile_opts['fusion_enable'] = True - fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, - qubit, circuit, basis_gates) - if return_ratios: - ratios.append(non_fusion_time_taken / fusion_time_taken) - break - if fusion_time_taken >= non_fusion_time_taken: - always_better = False - break - if not return_ratios and always_better: + profile_opts['fusion_enable'] = False + non_fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, + qubit, circuit, basis_gates, return_all=True) + profile_opts['fusion_enable'] = True + fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, + qubit, circuit, basis_gates, return_all=True) + if return_ratios: + ratios.append(sum(non_fusion_time_taken) / sum(fusion_time_taken)) + break + if max(fusion_time_taken) >= min(non_fusion_time_taken): + continue + if not return_ratios: return qubit - 1 if return_ratios: diff --git a/test/terra/backends/test_profile.py b/test/terra/backends/test_profile.py index 7fbc15c3d2..e60945eff3 100644 --- a/test/terra/backends/test_profile.py +++ b/test/terra/backends/test_profile.py @@ -68,19 +68,15 @@ def test_profile_save(self): clear_performance_options(False) - statevector_parallel_threshold = None fusion_cost = None fusion_cost_qubit = None for qubit in range(10, 25): profile = get_performance_options(qubit) - if 'statevector_parallel_threshold' in profile: - statevector_parallel_threshold = profile['statevector_parallel_threshold'] if 'fusion_cost.1' in profile: fusion_cost = profile['fusion_cost.1'] fusion_cost_qubit = qubit - self.assertTrue(statevector_parallel_threshold is not None) self.assertTrue(fusion_cost is not None) circuit = QuantumVolume(fusion_cost_qubit + 1, 1) @@ -96,16 +92,9 @@ def test_profile_save(self): for qubit in range(10, 25): profile = get_performance_options(qubit) - if 'statevector_parallel_threshold' in profile: - statevector_parallel_threshold = profile['statevector_parallel_threshold'] - if 'fusion_threshold' in profile: - fusion_threshold = profile['fusion_threshold'] if 'fusion_cost.1' in profile: fusion_cost = profile['fusion_cost.1'] - fusion_cost_qubit = qubit - self.assertFalse(statevector_parallel_threshold is not None) - self.assertFalse(fusion_threshold is not None) self.assertFalse(fusion_cost is not None) def test_profile_with_custom_circuit(self): From daf96ba87e559cd3f74266bea58620fcf000a60d Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Sat, 10 Apr 2021 00:45:41 +0900 Subject: [PATCH 13/14] reduce redundant performance options from profile --- qiskit/providers/aer/profile.py | 123 +++++++++++++++++--------------- 1 file changed, 66 insertions(+), 57 deletions(-) diff --git a/qiskit/providers/aer/profile.py b/qiskit/providers/aer/profile.py index 7cfedbbfc9..0747aacba4 100644 --- a/qiskit/providers/aer/profile.py +++ b/qiskit/providers/aer/profile.py @@ -25,51 +25,59 @@ def profile_performance_options(min_qubits=10, max_qubits=25, ntrials=10, + profile_parallelism=True, profile_fusion=True, circuit=None, persist=True, gpu=False): """Set optimal OpenMP and fusion options for backend.""" # Profile profile = {} - # Profile OpenMP threshold - parallel_threshold = None - try: - parallel_threshold = profile_parallel_threshold( - min_qubits=min_qubits, max_qubits=max_qubits, circuit=circuit, ntrials=ntrials) - profile['statevector_parallel_threshold'] = parallel_threshold - _PerformanceOptions._set_option('statevector_parallel_threshold', - parallel_threshold, persist) - except AerError: - pass - - # Profile CPU and GPU fusion threshold - for with_gpu in (False, True): - postfix = '_gpu' if with_gpu else '' - if with_gpu and not gpu: - continue + if profile_parallelism: + # Profile OpenMP threshold + parallel_threshold = None try: - qubit_to_costs = profile_fusion_costs(min_qubits=min_qubits, - max_qubits=max_qubits, - ntrials=ntrials, - gpu=with_gpu, - diagonal=False) - for num_qubits in qubit_to_costs: - costs = qubit_to_costs[num_qubits] - for i, _ in enumerate(costs): - profile[f'fusion_cost{postfix}.{num_qubits}.{i + 1}'] = costs[i] - _PerformanceOptions._set_option(f'fusion_cost{postfix}.{num_qubits}.{i + 1}', - costs[i], persist) - - fusion_threshold = profile_fusion_threshold(gpu=with_gpu, - min_qubits=min_qubits, - max_qubits=max_qubits, - ntrials=ntrials, - circuit=circuit) - profile[f'fusion_threshold{postfix}'] = fusion_threshold - _PerformanceOptions._set_option(f'fusion_threshold{postfix}', - fusion_threshold, persist) + parallel_threshold = profile_parallel_threshold( + min_qubits=min_qubits, max_qubits=max_qubits, circuit=circuit, ntrials=ntrials) + profile['statevector_parallel_threshold'] = parallel_threshold + _PerformanceOptions._set_option('statevector_parallel_threshold', + parallel_threshold, persist) except AerError: pass + if profile_fusion or circuit is not None: + # Profile CPU and GPU fusion threshold + + if not profile_fusion and circuit is not None: + min_qubits = circuit.num_qubits + max_qubits = circuit.num_qubits + + for with_gpu in [False, True] if gpu else [False]: + postfix = '_gpu' if with_gpu else '' + try: + qubit_to_costs = profile_fusion_costs(min_qubits=min_qubits, + max_qubits=max_qubits, + ntrials=ntrials, + gpu=with_gpu, + diagonal=False) + for num_qubits in qubit_to_costs: + costs = qubit_to_costs[num_qubits] + for i, _ in enumerate(costs): + profile[f'fusion_cost{postfix}.{num_qubits}.{i + 1}'] = costs[i] + _PerformanceOptions._set_option( + f'fusion_cost{postfix}.{num_qubits}.{i + 1}', + costs[i], persist) + + fusion_threshold = profile_fusion_threshold(gpu=with_gpu, + min_qubits=min_qubits, + max_qubits=max_qubits, + ntrials=ntrials, + circuit=circuit) + profile[f'fusion_threshold{postfix}'] = fusion_threshold + _PerformanceOptions._set_option(f'fusion_threshold{postfix}', + fusion_threshold, persist) + + except AerError: + pass + return profile @@ -97,15 +105,12 @@ class _PerformanceOptions: _DEFAULT_PARALLEL_THRESHOLD = 14 _DEFAULT_FUSION_THRESHOLD = 14 - _CACHED_PERFORMANCE_OPTIONS = {} - @staticmethod def _get_section_name(): return f'perf:{gethostname()}' @staticmethod def _set_option(option_name, value, persist): - _PerformanceOptions._CACHED_PERFORMANCE_OPTIONS.clear() _PerformanceOptions._PERFORMANCE_OPTIONS[option_name] = value if persist: config = cp.ConfigParser() @@ -141,11 +146,6 @@ def _get_options_from_file(option_to_type): @staticmethod def _get_performance_options(num_qubits, gpu=False): """Return optimal OpenMP and fusion options for backend.""" - - cache_key = (num_qubits, gpu) - if cache_key in _PerformanceOptions._CACHED_PERFORMANCE_OPTIONS: - return _PerformanceOptions._CACHED_PERFORMANCE_OPTIONS[cache_key] - # Profile postfix = '_gpu' if gpu else '' option_to_type = { @@ -173,10 +173,14 @@ def _get_performance_options(num_qubits, gpu=False): ret['statevector_parallel_threshold'] = profiled_value if f'fusion_threshold{postfix}' in all_profile: profiled_value = all_profile[f'fusion_threshold{postfix}'] - if num_qubits in range(profiled_value + 1, - _PerformanceOptions._DEFAULT_FUSION_THRESHOLD + 1): + if num_qubits in range(min(profiled_value + 1, + _PerformanceOptions._DEFAULT_FUSION_THRESHOLD + 1), + max(profiled_value + 1, + _PerformanceOptions._DEFAULT_FUSION_THRESHOLD + 1)): ret['fusion_threshold'] = profiled_value - disable_fusion = num_qubits < profiled_value + disable_fusion = num_qubits <= profiled_value + elif num_qubits < _PerformanceOptions._DEFAULT_FUSION_THRESHOLD + 1: + disable_fusion = True if not disable_fusion: for profiled_qubits in range(1, num_qubits + 1): @@ -184,8 +188,6 @@ def _get_performance_options(num_qubits, gpu=False): profile_name = f'fusion_cost{postfix}.{profiled_qubits}.{i + 1}' if profile_name in all_profile: ret[f'fusion_cost.{i + 1}'] = all_profile[profile_name] - - _PerformanceOptions._CACHED_PERFORMANCE_OPTIONS[cache_key] = ret return ret @staticmethod @@ -200,8 +202,6 @@ def _clear_performance_options(persist=True): for i in range(5)} } - _PerformanceOptions._CACHED_PERFORMANCE_OPTIONS.clear() - if persist: config = cp.ConfigParser() filename = os.getenv('QISKIT_SETTINGS', user_config.DEFAULT_FILENAME) @@ -219,7 +219,7 @@ def _generate_profile_circuit(profile_qubit, base_circuit=None, basis_gates=None raise AerError(f'number of qubit is too small: {profile_qubit}') if base_circuit is None: - return transpile(QuantumVolume(profile_qubit), basis_gates=['id', 'u', 'cx']) + return transpile(QuantumVolume(profile_qubit), basis_gates=basis_gates) if basis_gates is not None: profile_circuit = transpile(base_circuit.copy(), basis_gates=basis_gates) @@ -272,7 +272,7 @@ def global_qubit(index): return profile_circuit -def _profile_run(simulator, ntrials, backend_options, qubit, circuit, +def _profile_run(simulator, ntrials, backend_options, qubit, circuit, basis_gates=None, use_time_taken=False, return_all=False): profile_circuit = _generate_profile_circuit(qubit, circuit, basis_gates) @@ -324,7 +324,7 @@ def profile_parallel_threshold(min_qubits=10, max_qubits=20, ntrials=10, # pylint: disable=C0415 from .backends.qasm_simulator import QasmSimulator simulator = QasmSimulator() - basis_gates = ['id', 'u', 'cx'] + basis_gates = simulator.configuration().basis_gates ratios = [] for qubit in range(min_qubits, max_qubits + 1): @@ -345,6 +345,9 @@ def profile_parallel_threshold(min_qubits=10, max_qubits=20, ntrials=10, if return_ratios: return ratios + if circuit is not None: + return qubit + raise AerError(f'Unable to find threshold in range [{min_qubits}, {max_qubits}]') @@ -365,7 +368,7 @@ def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, # pylint: disable=C0415 from .backends.qasm_simulator import QasmSimulator simulator = QasmSimulator() - basis_gates = ['id', 'u', 'cx'] + basis_gates = simulator.configuration().basis_gates # Ensure fusion is enabled and method is statevector if gpu: @@ -380,13 +383,16 @@ def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, profile_opts['fusion_enable'] = False non_fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, qubit, circuit, basis_gates, return_all=True) - profile_opts['fusion_enable'] = True + if qubit < _PerformanceOptions._DEFAULT_FUSION_THRESHOLD: + profile_opts['fusion_enable'] = True + else: + del profile_opts['fusion_enable'] fusion_time_taken = _profile_run(simulator, ntrials, profile_opts, qubit, circuit, basis_gates, return_all=True) if return_ratios: ratios.append(sum(non_fusion_time_taken) / sum(fusion_time_taken)) break - if max(fusion_time_taken) >= min(non_fusion_time_taken): + if max(fusion_time_taken) > min(non_fusion_time_taken): continue if not return_ratios: return qubit - 1 @@ -394,6 +400,9 @@ def profile_fusion_threshold(min_qubits=10, max_qubits=20, ntrials=10, if return_ratios: return ratios + if circuit is not None: + return qubit + raise AerError(f'Unable to find threshold in range [{min_qubits}, {max_qubits}]') From 6812e2f2498cc04cb75bef2fd7b013a33ec2cda7 Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Sat, 10 Apr 2021 06:49:37 +0900 Subject: [PATCH 14/14] change to get num_qubits to be profiled in aerbackend --- qiskit/providers/aer/backends/aerbackend.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/qiskit/providers/aer/backends/aerbackend.py b/qiskit/providers/aer/backends/aerbackend.py index 7fbd309934..9088a0721c 100644 --- a/qiskit/providers/aer/backends/aerbackend.py +++ b/qiskit/providers/aer/backends/aerbackend.py @@ -147,24 +147,26 @@ def run(self, DeprecationWarning, stacklevel=3) - profiled_options = {} - num_qubits = None if isinstance(circuits, (QasmQobj, PulseQobj)): warnings.warn('Using a qobj for run() is deprecated and will be ' 'removed in a future release.', PendingDeprecationWarning, stacklevel=2) qobj = circuits - if isinstance(qobj, QasmQobj): - num_qubits = qobj.config.n_qubits else: qobj = assemble(circuits, self) - num_qubits = (circuits[0] if isinstance(circuits, list) else circuits).num_qubits - if num_qubits is not None: + profile_num_qubits = None + if isinstance(circuits, QasmQobj): + profile_num_qubits = qobj.config.n_qubits + if isinstance(circuits, list) and len(circuits) > 0 and hasattr(circuits[0], 'num_qubits'): + profile_num_qubits = circuits[0].num_qubits + + profiled_options = {} + if profile_num_qubits is not None: # Get profiled options for performance gpu = backend_options is not None and 'gpu' in backend_options.get('method', '') - profiled_options = get_performance_options(num_qubits, gpu) + profiled_options = get_performance_options(profile_num_qubits, gpu) for run_option in run_options: if run_option in profiled_options: del profiled_options[run_option]