Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Schedule with V2 backend fails #9488

Closed
nkanazawa1989 opened this issue Jan 31, 2023 · 4 comments · Fixed by #9987
Closed

Schedule with V2 backend fails #9488

nkanazawa1989 opened this issue Jan 31, 2023 · 4 comments · Fixed by #9987
Labels
bug Something isn't working mod: pulse Related to the Pulse module mod: transpiler Issues and PRs related to Transpiler
Milestone

Comments

@nkanazawa1989
Copy link
Contributor

nkanazawa1989 commented Jan 31, 2023

Environment

  • Qiskit Terra version: 0.23
  • Python version: any
  • Operating system: any

What is happening?

Scheduling quantum circuit with V2 backend will fail. This is caused by the complicated mechanism I describe in the following. Current Qiskit scheduler is designed based off of the old IBM devices, where the measurement instruction trigger must be synchronized among all channels. Thus there must be a special instruction measure(q_0, q_1, q_2, ..., q_N) for an N qubit device. This (old) IBM specific behavior is hard-coded in this function.
https://github.com/Qiskit/qiskit-terra/blob/a3b359b899d5d963272a7b424c8a283bc6bd4c0d/qiskit/pulse/macros.py#L22-L29

In current IBM device meas_map is somewhat outdated, because we can measure arbitrary combination of qubits simultaneously and at arbitrary time (but they are scheduled at the same t0 in current architecture). So this function can be simplified.

On the other hand, we are now migrating from transpile arguments to a backend.target (which is a collection of transpile arguments) and the InstructionScheduleMap that provides above measure instruction is absorbed in the target. This is a nested dictionary keyed on instruction name and qubits, and a pulse schedule is stored as a part of InstructionProperties class. Target can dump an instruction schedule map by consuming itself for backward compatibility, and the generated instruction schedule map contains all entries in the Target.

However, Target.add_instructions method validates qubit number agains instruction num_qubits. In case of Measure, this is single qubit instruction by definition, and thus special pulse schedule measure(q_0, q_1, q_2, ..., q_N) is not accepted by the Target while it is still provided by IBM backends.
https://github.com/Qiskit/qiskit-terra/blob/a3b359b899d5d963272a7b424c8a283bc6bd4c0d/qiskit/transpiler/target.py#L374-L377

In current implementation, the all qubit measure schedule is missing in the Target.

How can we reproduce the issue?

from qiskit_ibm_provider import IBMProvider
from qiskit import QuantumCircuit, schedule

provider = IBMProvider()
backend = provider.get_backend("ibm_washington")

qc = QuantumCircuit(2, 2)
qc.cx(0, 1)
qc.measure_all()

schedule(qc, backend)

This results in

PulseError: "We could not find a default measurement schedule called 'measure'. Please provide another name using the 'measure_name' keyword argument. For assistance, the instructions which are defined are: ['rz', 'cx', 'id', 'x', 'measure', 'sx']"

What should happen?

Circuit scheduling must work with V2 backend.

Any suggestions?

There are several approaches.

[1] We can relax target validation to accept the all qubit measure instruction. But this means we need to accept an instruction that doesn't fit in with Qiskit object representation. Thus this approach is sort of hard-coding IBM specific behavior in Qiskit and I don't prefer this approach.

[2] We can update the behavior of pulse measure macro instead. Since measure map is no longer practical, I think this is reasonable approach. However, this might be a breaking API change.

@nkanazawa1989 nkanazawa1989 added the bug Something isn't working label Jan 31, 2023
@nkanazawa1989 nkanazawa1989 added this to the 0.24.0 milestone Jan 31, 2023
@nkanazawa1989 nkanazawa1989 added the mod: pulse Related to the Pulse module label Jan 31, 2023
@nkanazawa1989
Copy link
Contributor Author

nkanazawa1989 commented Feb 2, 2023

I had offline discussion with @taalexander @wshanks .

It doesn't even reflect multiplexing, maybe that was the intention but right now the constraint that all measurements need to be together is a result of the software layer. That's why I always advocated for the need for a multiple-qubit measurement (similar to reset)
(@taalexander)

This makes me think current Qiskit transpiler underestimates the importance of lowering of measurement instruction, and it just ignores backend.meas_map.

For example,

from qiskit_ibm_provider import IBMProvider

v2_provider = IBMProvider()
backend_v2 = v2_provider.get_backend("ibmq_quito")

>>> backend_v2.meas_map
[[0, 1, 2, 3, 4]]

this implies all qubit measurement must be synchronized together. For this circuit

     ┌───┐┌────────────────┐┌─┐
q_0: ┤ H ├┤ Delay(160[dt]) ├┤M├
     ├───┤└──────┬─┬───────┘└╥┘
q_1: ┤ X ├───────┤M├─────────╫─
     └───┘       └╥┘         ║ 
c: 2/═════════════╩══════════╩═
                  1          0 

one may expect measure for q_0 is delayed by 160 dt from one for q_1 (indeed current circuit scheduler returns such schedule). However, this is the consequence of careless consideration of the backend meas_map. In fact, q_0 and q_1 are measured synchronously, and X gate on q_1 is scheduled in ALAP fashion (in the actual backend compiler as long as they report meas_map).

Note that this introduces a critical timing error in the dynamical decoupling pass (especially in the context of mid-circuit measurement) and IBM prover implements own scheduler.

To avoid this issue and respect meas_map that backend reports, Qiskit should implement variable width measure instruction, and transpiler must implement measure aggregation pass. In this way user can still use conventional single qubit measurement instruction, while we can perform more accurate scheduling for dynamical decoupling by considering the backend meas_map.

This also fixes the issue of missing multi-qubit measurement in the Target.

@nkanazawa1989
Copy link
Contributor Author

After discussion with @mtreinish we came up with following conclusion.

  • We don't want to relax instruction validation in Target. This easily causes programming bug, and we should be really strict on this.
  • We don't want to introduce custom (ad-hoc) measurement classes without careful discussion (probably RFC based).

According to @mtreinish

Well that's the nice thing about the target, is it's decoupled from the backend's returned data structure. So we just need to be able to go from what the backend gives us to the data model we build for the target

This indicates we should be able to generate parallel measurement calibration on the fly on the front end. If backend applies some alignment constraints (e.g. some trigger buffer for each acquire channel), this request must come with more rich meas_map data structure.

We can assume when the meas_map is conventional List[List[int]] then we can naively parallelize measurement instruction of each qubit. This means pulse measure scheduler must dispatch the scheduling logic based off of the meas_map data structure, which might be represented by a subclass.

This makes me think:

  • One can implement qiskit.transpiler.MeasMap class and ParallelMeasMap as a subclass of it. This class is generated when the backend meas_map data structure is List[List[int]].
  • Target must provide backend.target.meas_map, which must return one of MeasMap subclasses according to what the backend provides for the meas_map.
  • The pulse measure scheduler should implement the dispatcher of scheduling logic triggered by input meas_map

The new pulse measure scheduler would look like

def measure(
    qubits: List[int],
    backend=None,
    target: Optional[Target] = None,
    meas_map: Optional[MeasMap] = None,
    qubit_mem_slots: Optional[Dict[int, int]] = None,
    measure_name: str = "measure",
) -> ScheduleBlock:

then we can fix scheduling failure with V2 backend without modifying any of target / provider / measure class.

(Note)
Incorrect circuit scheduling is another issue. Having grouped measurement class still makes implementation of alignment logic easier. However, this can be a compiler directive and no need to be exposed to users.

@mtreinish
Copy link
Member

Target must provide backend.target.meas_map, which must return one of MeasMap subclasses according to what the backend provides for the meas_map.

I would relax this a bit because Target.meas_map will need to be optional (both from a backwards compatibility PoV but also because most other providers don't have the same level of information available.

Also, is there a better name than meas_map that we can use here since we're adding a new field to the Target class? I just don't think it's super descriptive for people and won't be obvious to others what the intended purpose of it is (I know I've had to explain it to people before because they didn't know what it was for).

@nkanazawa1989
Copy link
Contributor Author

nkanazawa1989 commented Mar 27, 2023

That's fair point. I think we can return individual measurement configuration if backend doesn't provide any setting.

Regarding the naming, (I think I suck at naming) something like MeasureGrouping or ParallelMeasure? I think meas_map is somewhat aligned with coupling_map (I guess the word "map" indicates topological map of concurrent measurement on the device graph, rather than dictionary mapping).

(edit) In this sense I prefer something like DeviceCouplingGraph for coupling_map.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working mod: pulse Related to the Pulse module mod: transpiler Issues and PRs related to Transpiler
Projects
None yet
2 participants