Qiskit / qiskit

Qiskit is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives.
https://www.ibm.com/quantum/qiskit
Apache License 2.0
5.28k stars 2.37k forks source link

Redefine backend usecase #6825

Closed nkanazawa1989 closed 3 years ago

nkanazawa1989 commented 3 years ago

What is the expected enhancement?

This proposes a change of the policy for the backend handling, namely, allowing directly overriding the backend defaults. Note that technically it is already possible since backend is mutable, but the transpiler is not designed to do that.

This doesn't request to change any public API. Just requesting to approve the use case of overriding a backend with qiskit-experiment.

Background

So far the backend has been considered to be a non-rewritable object, since only provider can change the hardware configuration. This is why the transpiler has the API

circ = transpile(circ, backend, **options_to_override)

so that options default to the backend values, but they can be overridden while protecting the backend object.

With introduction of qiskit-experiment, user will get extended capability of experiment. This will provide a library of experiments to calibrate a quantum gate and one will be able to use their custom instruction without hack. This means Qiskit can also override a part of backend definitions and the backend is no longer non-rewritable object.

Example

Run single qubit gate calibration to create my_gate for Q0. Given this experiment consists of [amp_cal, freq_cal] for simplicity. The parameter is managed by Calibration class. Then, use my_gate for an optimization experiment provided by SomeOptimization class that may be provided by other qiskit library. This class has own run method to control the classical optimization loop and job execution.

Below examples are pseudo codes.

Conventional

Initialize backend
Initialize Calibration(backend) as cal
Initialize SomeOptimization as opt

for experiment in [amp_cal, freq_cal] do
   Perform experiment.run(backend, cal) and store result
   Update cal with result

# create execution and transpile options
basis_gates = backend.configuration().basis_gates
basis_gates += ["my_gate"]

Get qubit_freqs from cal
Get inst_map from cal   # this is gate-schedule mapping

# create execution options
options = {
    "transpile_options": {
        "basis_gates": basis_gates,
        "inst_map": inst_map
    },
    "run_options ": {
        "schedule_los": qubit_freqs
    }
}

# run optimization
result = opt.run(configured_backend, **options)

As you can see, the backend is protected throughout the experiment but instead we need to manage many objects.

And we need to grantee the SomeOptimization instance can manage these options. This frustrate us to integrate calibration into some application circuits.

Re-writable backend

Initialize backend
Initialize Calibration(backend) as cal
Initialize SomeOptimization as opt

for experiment in [amp_cal, freq_cal] do
   Perform experiment.run(backend, cal) and store result
   Update cal with result

# renew backend
configured_backend = cal.export()

# run optimization
result = opt.run(configured_backend, **options)

If the backend is re-writable, we can seamlessly write calibration and the application-level experiment across qiskit libraries. Note that here I assume the runner method takes backend, but actual algorithm classes may have different implementations, i.e. QuantumInstance. In this case, we can pass the backend to it.

Usually these runner methods call the transpiler with the backend behind the scene. If the backend contains all necessary information to override the instruction set and the transpiler can handle that, we can easily inject custom instruction into applications without modifying the application side. Backend update can be delegated to the calibration instance, i.e. qiskit-experiment.

By allowing this usecase, user can also override backend without qiskit-experiment. This means we need some validations for non-rewritable settings such as number of qubits, coupling-map, etc... Perhaps, replacing backend.configuration() and backend.properties() with a dataclass with the frozen=True option can solve this issue.

eggerdj commented 3 years ago

Thanks Naoki. This also makes sense to me. Some aspects of the backend should not be changed, e.g. coupling maps, etc. Others like qubit frequencies and the schedules in the instruction schedule map should be mutable. It of course depends a bit on the view one takes. A user running algorithms does not need a mutable backend and would see the backend as frozen. However, the picture becomes more nuanced for someone calibrating the backend like in qiskit_experiments. Here, it would be awesome if we can start with a backend object reflecting our initial guesses of the defaults and update them as the backend is calibrated. In the end, the result of the calibration should be to export a calibrated backend instance with the qubit frequencies and schedules for the instructions.

mtreinish commented 3 years ago

There is nothing precluding this in the backend interface today and in fact some backends already work in a similar manner to this. For example, if you look at the AerSimulator backend it already dynamically configures things like this based on run time options. For example, if you can change the simulation method option (ie backend.set_options(method= 'stabilizer')) it adjusts the basis gates based on this. Similarly you can use .from_backend() and it adjusts the noise model and other options which in turn effect the properties and configuration. The lack of flexibility you're hitting is specific to the ibmq provider's implementation (which is what I assume you're using).

I don't think we need to build the abstract interface in terra to offer mandatory interfaces for with these use cases. As long as it doesn't prevent individual providers from implementing this level of flexibility if the backend. It's up to the individual provider's implementation and backend details on whether something like this can work or not. For example, support for pulse gates and therefore custom calibrations don't exist on AQT backends and their basis gate is fixed at the API level.

As for the backend.configuration() and backend.properties() (and the corresponding BackendConfiguration and BackendProperties classes) those are probably going to be removed in the next backend interface version BackendV2 (see https://github.com/Qiskit/qiskit-terra/pull/5885 for a very rough and very early draft) as they're poor user interfaces and the classes are basically just thin python object wrappers around the IBM quantum backend API payload formats instead of Qiskit native interfaces.

nkanazawa1989 commented 3 years ago

Thanks Matthew. So we can modify any part of backend object on our responsibility? And providers are not going to say which field is rewritable or not, i.e. if we override some non-rewritable field the job just fails.

I saw BackendV2 PR but cannot find how this data is provided. Is it expected to be a private member so that we cannot override? Seems like we can override the basis gates iff a provider provides the backend object with the setter method. How can we know if we can override the basig gates?

ajavadia commented 3 years ago

I spent some time thinking about this. I don't really have a better alternative. I agree the Backend object is probably the best place to track information about qubit frequencies, meas frequencies, cals, error rates, etc. Each of these may be updated by qiskit-experiments after some experiment is run.

So I think you can go ahead with https://github.com/Qiskit/qiskit-terra/pull/6759.

As for what things are allowed/disallowed to be changed: I think the answer depends on what "backend" is supposed to be. If it's just some model of a real backend, then once in qiskit we may change it however we want. If you change it too much then compile to that, the circuit may not run on the original backend. But still it may be useful if say I want to make a backend modeled like ibmq_manhattan, but just add iswap to its basis and see how my circuit compilation changes. I'm fine if we raise a warning for changes to backend.configuration(), but maybe being too strict here is not a good idea. I don't think novice users change backends anyway.