From 0653191a5000d3f34b45c10573e05003fab617cf Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Mon, 31 Jul 2023 10:01:35 +0300 Subject: [PATCH 01/10] Create ####-Pulse-Channels-IR-and-Compiler.md --- ####-Pulse-Channels-IR-and-Compiler.md | 145 +++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 ####-Pulse-Channels-IR-and-Compiler.md diff --git a/####-Pulse-Channels-IR-and-Compiler.md b/####-Pulse-Channels-IR-and-Compiler.md new file mode 100644 index 0000000..e560106 --- /dev/null +++ b/####-Pulse-Channels-IR-and-Compiler.md @@ -0,0 +1,145 @@ +# RFC Title + +| **Status** | **Proposed** | +|:------------------|:---------------------------------------------| +| **RFC #** | #### | +| **Authors** | Tsafrir Armon (tsafrir.armon@ibm.com), Naoki Kanazawa (knzwnao@jp.ibm.com) | +| **Deprecates** | - | +| **Submitted** | 2023-07-31 | +| **Updated** | YYYY-MM-DD | + +## Summary +This RFC summarizes the proposed changes to Qiskit Pulse's channel model, and the introduction of Pulse IR and compiler. The proposal is based on a series of discussions +within Qiskit Pulse's development team, and is brought here for the community to weigh in. The main changes proposed include: + +- Introduce new model to supplement the existing `Channel` model, that will allow writing backend-agnostic template pulse programs. +- Introduce new Pulse IR (intermediate representation) and compiler to support the new channels model, and streamline pulse programs compilation needs. + +Comments are welcome. + + + +## Motivation +The legacy Channels correspond to what the backend calls a mixed frame - a combination of specific HW port, and the frame (frequency and phase) needed to play pulses. +However, to specify a Channel one must be aware of the backend mapping. For example, the following pulse schedule is an ECR pulse for qubits 3 and 4: + +``` +with builder.build() as ecr_schedule_q3q4: + with align_sequential(): + with align_left(): + Play(GaussianSquare(...), ControlChannel(6)) + Play(GaussianSquare(...), DriveChannel(4)) + Play(Gaussian(...), DriveChannel(3)) + with align_left(): + Play(GaussianSquare(...), ControlChannel(6)) + Play(GaussianSquare(...), DriveChannel(4)) +``` + +Note how one can't make sense of the code, without knowing the mapping of the control channels. +Similarly, frame synchronization also has to be carried out manually and depends on the backend mapping. +This dependency on the backend prohibits the ability to write a template code, which in turn complicates modules like Qiskit Experiments. + +Under the new proposal, the dependency on backend mapping will be removed, and Qiskit Pulse users (and particularly Qiskit Experiments users) will be able to write +backend-agnostic template programs, which could be used across devices. Additionally, custom frames will make it easier to experiment with qudit control. + +The extra compilation needs introduced by the new channel model motivate the introduction the new Pulse IR and compiler. However, this was also +a long standing goal on its own, further motivated by the desire to unify pulse compilation and allow for more flexibility in output formats. + +## User Benefit +- Qiskit Pulse users will have a clearer and more efficient way to write their Pulse programs. +- Qiskit Experiments users will be able to write more streamlined template experiments. +- Qiskit Pulse users will have easier access to qudit control. +- Maintainers and contributors to Qiskit Pulse will have a unified compilation code which will be simpler to adjust for future needs and changes. + +## Design Proposal +One can schematically split the user interface of Qiskit Pulse into three layers - + +- Pulse level (`SymbolicPulse`,`WaveForm`) +- Instruction level (`Play`,`Delay`...) +- Program level (`Schedule`,`ScheduleBlock`) +This proposal focuses on the instruction level, and the necessary compilation needs that will arise. +The main goal is to allow for clear and simple backend agnostic (but not architecture agnostic) pulse templates. +To do this, one must move away from the `Channel`s setup, which can't be used without specific backend mapping. + +The new Pulse IR and compiler will support the transition, and provide a unified compilation tool for every need and desired output format. + +No API breaking changes are needed. New and legacy options can coexist. + +## Detailed Design +### Channel rework +To replace the legacy Channels we propose to specify instructions in terms of `LogicalElement`s and `Frame`s: + +- `LogicalElement` - every type of element in the HW which the user can control ("play pulses on"). For example, a qubit is a `LogicalElement`, and not the specific port used to drive it. The most notable example of a logical element is of course the `Qubit`, but in the future one can imagine dedicated couplers, flux controls and so on. +- `Frame` - a combination of frequency and phase. Subclasses used to identify the frame with a backend default. Notable examples are `QubitFrame` and `MeasurementFrame` associated with the default driving and measurement frequencies (respectively) of a qubit. +Using these objects, the above code takes the form: + +``` +with builder.build() as ecr_schedule_q3q4: + with align_sequential(): + with align_left(): + Play(GaussianSquare(...), Qubit(3),QubitFrame(4)) + Play(GaussianSquare(...), Qubit(3),QubitFrame(3)) + Play(Gaussian(...), Qubit(3),QubitFrame(3)) + with align_left(): + Play(GaussianSquare(...), Qubit(3),QubitFrame(4)) + Play(GaussianSquare(...), Qubit(3),QubitFrame(3)) +``` + +The new code not only allows to work with logical backend-agnostic qubits, but is also much clearer in conveying the actual actions in play (acting on qubit 3 with the frame of qubit 4 vs with the frame of qubit 3). + +On top of the `LogicalElement` and the `Frame`, another layer of `MixedFrame` will be introduced. +The `MixedFrame` is simply a combination of a LogicalElement and a Frame, and in many ways is similar to the legacy `Channel`. +The difference being that a `MixedFrame` does not depend on the backend mapping and has clear relations with other `MixedFrames` as opposed to a `Channel` which hasn't. + +It should be noted that custom `Frame` and `MixedFrame` will make it easier to control qudit experiments. + +### Pulse IR +The Pulse IR will provide the framework for the compiler to work on and perform the necessary compilation steps. The main components of the IR will be: + +- IR Block - A block of instructions (or nested blocks) with an alignment context. This structure is similar to the structure of ScheduleBlock and needed to support one of the main tasks of the compiler - scheduling. +- IR Instruction - A pulse program instruction. Typically, it will be initialized without concrete timing, and will be scheduled during the compiler operation. + +The typical attributes of the instructions will be: t0 (starting time), duration, payload (instruction details) and the target associated with the instruction (`Frame`, `MixedFrame` or the legacy `Channel`). For example: + +- `MixedFrameInstruction` - target is `MixedFrame` (or `Channel`). Additional attribute operand (Play\Delay). The payload attribute will be the pulse to be played (for Play) or delay duration (for Delay). +- `FrameInstruction` - target is `Frame` (or `MixedFrame`/`Channel`). Additional attribute operand (Set\Shift Frequency\Phase). The payload will be the phase\frequency value. +- `AcquireInstruction` - target is a tuple of `Qubit` and classical registry. + +### Compiler +The compiler will be built with a shared design of Qiskit's pass manager (based on PR #10474), and will thus be easily extendable to other tasks. Here we will outline the minimal requirements from the compiler in light of the new proposal. + +#### Scheduling +- IR initialization - For a given ScheduleBlock the IR will initialized as nested blocks (with no concrete timing). +- Canonicalization (transform pass) - If legacy `Channel`s were used, they will be mapped to `MixedFrame`s using the backend mapping. This is the only scenario where backend mapping will be needed for this stage. +- Identify frames and mixed frames (characterization pass) - `FrameInstruction`s will need to be broadcasted to every `MixedFrame` associated with that `Frame`. Therefore, we need identify every `MixedFrame`, `Frame` and the relations between them. +- Schedule each block - + - starting from the lowest blocks, each block will be scheduled on its own. + - `FrameInstruction` defined on a `Frame` will be broadcasted to every associated `MixedFrame`, and these `MixedFrame`s will be considered "blocked" by this IR block. + - `FrameInstruction` defined on a `MixedFrame` will not be broadcasted to other `MixedFrame`. This will leave the user the ability to control the frame of `MixedFrame`s individually if wanted. + - According to the alignment, the instructions will be scheduled within the block. + - Nested blocks will be shifted according to the alignment, but their internal timing will not be altered by parent block alignment. +- Apply backend scheduling constraints. +- Validate timing and constraints. + +#### Mapping to backend aware "channels" +The `Frame`s and `MixedFrame`s will have to be converted to backend aware "channels". +It remains to be seen if this conversion will be done on the frontend or the backend, but a scheme for frontend mapping could look like this: + +- First, map `MixedFrame`s which natively map to the backend. For example, a `MixedFrame` associated with `Qubit(1)` and `QubitFrame(1)` is natively mapped to the backend's Q1 "channel". + Similarly MixedFrame associate with `Qubit(1)` and `QubitFrame(2)` is natively mapped to the backend's CR12 "channel". +- Next, map remaining `MixedFrames` into unused "channels". +- Lastly, provide the backend with `MixedFrame` which couldn't have been mapped by the frontend, in hope that the backend could support them. (This will obviously require dedicated backend support). + +#### Conversion to output format +There is still ongoing discussion about desired output formats, but every choice of output format could be supported at the cost of creating a conversion mechanism from IR to that format. + +## Alternative Approaches +This proposal combines two seemingly separate issues - the channel model rework and the introduction of Pulse IR and Compiler. It would be possible to do one without the other, but it seems like this is +a good opportunity to do both. + +## Questions +- Naming - Names were not finalized. While not in the same namespace, some names here clash with other Qiskit modules (most notably `Qubit`), and it might be better to modify the names. +- IR instructions - The existing `Instruction` class provides similar functionality. Should we use it instead of introducing new IR Instruction classes? + +## Future Extensions +- Frontend & backend coordination to support custom frames and mixed frames. This has the potential to better utilize HW control options (as discussed above). From 2301b9507fae3473a39c33c8d8541b9746c498ad Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Tue, 1 Aug 2023 00:29:42 +0300 Subject: [PATCH 02/10] Update ####-Pulse-Channels-IR-and-Compiler.md --- ####-Pulse-Channels-IR-and-Compiler.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/####-Pulse-Channels-IR-and-Compiler.md b/####-Pulse-Channels-IR-and-Compiler.md index e560106..4681c63 100644 --- a/####-Pulse-Channels-IR-and-Compiler.md +++ b/####-Pulse-Channels-IR-and-Compiler.md @@ -1,4 +1,4 @@ -# RFC Title +# Pulse Compiler & IR | **Status** | **Proposed** | |:------------------|:---------------------------------------------| @@ -9,18 +9,23 @@ | **Updated** | YYYY-MM-DD | ## Summary -This RFC summarizes the proposed changes to Qiskit Pulse's channel model, and the introduction of Pulse IR and compiler. The proposal is based on a series of discussions +This RFC summarizes the proposal for new Pulse Compiler & IR. The introduction of the new compiler paves the way to the transition to frame aware model, which is also discussed. +The proposal is based on a series of discussions within Qiskit Pulse's development team, and is brought here for the community to weigh in. The main changes proposed include: +- Introduce new pass based Pulse Compiler and the supporting Pulse IR (intermediate representation). - Introduce new model to supplement the existing `Channel` model, that will allow writing backend-agnostic template pulse programs. -- Introduce new Pulse IR (intermediate representation) and compiler to support the new channels model, and streamline pulse programs compilation needs. Comments are welcome. +## Motivation +Qiskit Pulse currently has no unified compilation pathway. Different tasks are handled by the different code segments. This not only creates code clutter which is hard to maintain, +but also limits the ability of vendors to adapt Qiskit Pulse, or adjust Qiskit Pulse to new HW developments. The circuit module of Qiskit already uses a pass based compilation process, +which gives the flexibility to add\change\adapt passes according to the changing needs. +A prime example for the need for a better compilation process, is given by the second part of this proposal - rework of the `Channel` model. -## Motivation -The legacy Channels correspond to what the backend calls a mixed frame - a combination of specific HW port, and the frame (frequency and phase) needed to play pulses. +The legacy `Channel`s correspond to what the backend calls a mixed frame - a combination of specific HW port, and the frame (frequency and phase) needed to play pulses. However, to specify a Channel one must be aware of the backend mapping. For example, the following pulse schedule is an ECR pulse for qubits 3 and 4: ``` @@ -41,15 +46,13 @@ This dependency on the backend prohibits the ability to write a template code, w Under the new proposal, the dependency on backend mapping will be removed, and Qiskit Pulse users (and particularly Qiskit Experiments users) will be able to write backend-agnostic template programs, which could be used across devices. Additionally, custom frames will make it easier to experiment with qudit control. - -The extra compilation needs introduced by the new channel model motivate the introduction the new Pulse IR and compiler. However, this was also -a long standing goal on its own, further motivated by the desire to unify pulse compilation and allow for more flexibility in output formats. +However, supporting this change adds significant compilation needs, which are hard to implement in the current workflow. ## User Benefit +- Qiskit contributors as well as vendors will have a unified compilation code which will be simpler to adjust for future needs and changes. - Qiskit Pulse users will have a clearer and more efficient way to write their Pulse programs. - Qiskit Experiments users will be able to write more streamlined template experiments. - Qiskit Pulse users will have easier access to qudit control. -- Maintainers and contributors to Qiskit Pulse will have a unified compilation code which will be simpler to adjust for future needs and changes. ## Design Proposal One can schematically split the user interface of Qiskit Pulse into three layers - From 778e3d7007054428208d32712ade35eabf56b339 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Tue, 1 Aug 2023 00:59:00 +0300 Subject: [PATCH 03/10] Update ####-Pulse-Channels-IR-and-Compiler.md --- ####-Pulse-Channels-IR-and-Compiler.md | 103 +++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 6 deletions(-) diff --git a/####-Pulse-Channels-IR-and-Compiler.md b/####-Pulse-Channels-IR-and-Compiler.md index 4681c63..08681aa 100644 --- a/####-Pulse-Channels-IR-and-Compiler.md +++ b/####-Pulse-Channels-IR-and-Compiler.md @@ -55,16 +55,18 @@ However, supporting this change adds significant compilation needs, which are ha - Qiskit Pulse users will have easier access to qudit control. ## Design Proposal -One can schematically split the user interface of Qiskit Pulse into three layers - +The new pulse compiler will be a pass based compiler. Dedicated passes will perform every analysis, +transformation and validation operations needed to convert a pulse program into a desired output format. A detailed workflow will be described below, but the main tasks include +concretely scheduling `ScheduleBlock` programs, mapping logical qubits to physical ones, applying backend constraints, and converting to desired output format. To support this, +the new IR will consist of nested instruction blocks, which will be transformed by the compiler as it operates. + +On the user facing interface, one can schematically split Qiskit Pulse into three layers - - Pulse level (`SymbolicPulse`,`WaveForm`) - Instruction level (`Play`,`Delay`...) - Program level (`Schedule`,`ScheduleBlock`) -This proposal focuses on the instruction level, and the necessary compilation needs that will arise. -The main goal is to allow for clear and simple backend agnostic (but not architecture agnostic) pulse templates. -To do this, one must move away from the `Channel`s setup, which can't be used without specific backend mapping. - -The new Pulse IR and compiler will support the transition, and provide a unified compilation tool for every need and desired output format. +This proposal focuses on the instruction level. The main goal is to allow for clear and simple backend agnostic (but not architecture agnostic) pulse templates. +To do this, the legacy `Channel`s will be supplemented by `Frame`s and `LogicalElement`s. No API breaking changes are needed. New and legacy options can coexist. @@ -74,6 +76,7 @@ To replace the legacy Channels we propose to specify instructions in terms of `L - `LogicalElement` - every type of element in the HW which the user can control ("play pulses on"). For example, a qubit is a `LogicalElement`, and not the specific port used to drive it. The most notable example of a logical element is of course the `Qubit`, but in the future one can imagine dedicated couplers, flux controls and so on. - `Frame` - a combination of frequency and phase. Subclasses used to identify the frame with a backend default. Notable examples are `QubitFrame` and `MeasurementFrame` associated with the default driving and measurement frequencies (respectively) of a qubit. + Using these objects, the above code takes the form: ``` @@ -93,9 +96,97 @@ The new code not only allows to work with logical backend-agnostic qubits, but i On top of the `LogicalElement` and the `Frame`, another layer of `MixedFrame` will be introduced. The `MixedFrame` is simply a combination of a LogicalElement and a Frame, and in many ways is similar to the legacy `Channel`. The difference being that a `MixedFrame` does not depend on the backend mapping and has clear relations with other `MixedFrames` as opposed to a `Channel` which hasn't. +Play\Delay instructions will be allowed to take one of `LogicalElement`+`Frame`, `MixedFrame` or `Channel` (for backwards compatibility). +Set\Shift Frequency\Phase will be allowed to take one of `Frame` (to be broadcast to every associated `MixedFrame`), `MixedFrame` (not to be broadcasted) or `Channel`. It should be noted that custom `Frame` and `MixedFrame` will make it easier to control qudit experiments. +Classes hierarchy: + +``` +class LogicalElement(ABC): + @property + def index(self) -> Tuple[int, ...] + # index + + @property + @abstractmethod + def name(self) -> str: + # opcode/string identifier + +class Qubit(LogicalElement): + + def __init__(self, index: int): + .... + + @property + def index(self) -> int + return self._index + + @property + def name(self) -> str: + return f"Q{index}" + +class Coupler(LogicalElement): + + def __init__(self, *index: int): + .... + + @property + def index(self) -> Tuple[int, ...] + return self._index + + @property + def name(self) -> str: + qubits = ",".join(self._index) + return f"Coupler({qubits})" + +class Frame(ABC): + + @property + def name(self) -> str: + # opcode/string identifier + +class GenericFrame(Frame): + def __init__(self, name, frequency, phase): + .... + + @property + def name(self) -> str: + return self._name + + @property + def frequency(self) -> float: + + @property + def phase(self) -> float: + + +class QubitFrame(Frame): + def __init__(self, index): + .... + + @property + def index(self) -> int + # qubit index + + @property + def name(self) -> str: + return f"QFrame{self.index}" + +class MeasurementFrame(Frame): + def __init__(self, index): + .... + + @property + def index(self) -> int + # qubit index + + @property + def name(self) -> str: + return f"MFrame{self.index}" +``` + ### Pulse IR The Pulse IR will provide the framework for the compiler to work on and perform the necessary compilation steps. The main components of the IR will be: From 07417915508e900421588bb5110a3f2915d694a4 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Wed, 2 Aug 2023 15:47:55 +0300 Subject: [PATCH 04/10] Update ####-Pulse-Channels-IR-and-Compiler.md --- ####-Pulse-Channels-IR-and-Compiler.md | 82 +++++++++++++++++++------- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/####-Pulse-Channels-IR-and-Compiler.md b/####-Pulse-Channels-IR-and-Compiler.md index 08681aa..9945240 100644 --- a/####-Pulse-Channels-IR-and-Compiler.md +++ b/####-Pulse-Channels-IR-and-Compiler.md @@ -129,11 +129,11 @@ class Qubit(LogicalElement): class Coupler(LogicalElement): - def __init__(self, *index: int): - .... + def __init__(self, index: Tuple[int, int]): + self._index = index @property - def index(self) -> Tuple[int, ...] + def index(self) -> Tuple[int, int] return self._index @property @@ -149,22 +149,24 @@ class Frame(ABC): class GenericFrame(Frame): def __init__(self, name, frequency, phase): - .... + self._name = name + self._frequency = frequency + self._phase = phase @property def name(self) -> str: return self._name @property - def frequency(self) -> float: + def frequency(self) -> float: # The initial frequency of the frame @property - def phase(self) -> float: + def phase(self) -> float: # The initial phase of the frame class QubitFrame(Frame): def __init__(self, index): - .... + self._index = index @property def index(self) -> int @@ -176,7 +178,7 @@ class QubitFrame(Frame): class MeasurementFrame(Frame): def __init__(self, index): - .... + self._index = index @property def index(self) -> int @@ -190,42 +192,78 @@ class MeasurementFrame(Frame): ### Pulse IR The Pulse IR will provide the framework for the compiler to work on and perform the necessary compilation steps. The main components of the IR will be: -- IR Block - A block of instructions (or nested blocks) with an alignment context. This structure is similar to the structure of ScheduleBlock and needed to support one of the main tasks of the compiler - scheduling. +- IR Block - A block of instructions (or nested blocks) with an alignment context. This structure is similar to the structure of `ScheduleBlock` and needed to support one of the main tasks of the compiler - scheduling. - IR Instruction - A pulse program instruction. Typically, it will be initialized without concrete timing, and will be scheduled during the compiler operation. -The typical attributes of the instructions will be: t0 (starting time), duration, payload (instruction details) and the target associated with the instruction (`Frame`, `MixedFrame` or the legacy `Channel`). For example: +Two instructions types are used: +- `GenericInstruction` - for play, delay, set\shift frequency\phase. Assignment of `LogicalElement` will be optional for set\shift frequency\phase and will determine whether or not the instruction will be +broadcaseted to all mixed frames associated with the frame or not. +- `AcquireInstruction` - for acquire instructions (due to the different elements involved). -- `MixedFrameInstruction` - target is `MixedFrame` (or `Channel`). Additional attribute operand (Play\Delay). The payload attribute will be the pulse to be played (for Play) or delay duration (for Delay). -- `FrameInstruction` - target is `Frame` (or `MixedFrame`/`Channel`). Additional attribute operand (Set\Shift Frequency\Phase). The payload will be the phase\frequency value. -- `AcquireInstruction` - target is a tuple of `Qubit` and classical registry. +``` +class PulseIR: + def __init__(self): + self.instructions # List of instructions or nested PulseIR objects + self.alignment + +class GenericInstruction: + def __init__( + self, + instruction_type: str, + duration: int, + logical_element: Optional[LogicalElement] = None, + frame: Optional[Frame] = None, + **operands, + ): + self.t0 = None + self.instruction = instruction_type # opcode + self.duration = duration + self.logical_element = logical_element + self.frame = frame + self.operands = operands +``` ### Compiler -The compiler will be built with a shared design of Qiskit's pass manager (based on PR #10474), and will thus be easily extendable to other tasks. Here we will outline the minimal requirements from the compiler in light of the new proposal. +The compiler will be built with a shared design of Qiskit's pass manager (based on PR #10474), and will thus be easily extendable to other tasks. The pass based approach allows for simple modification of the lowering process for vendors or Qiskit team. +A typical compiler call will look like: +``` +payload = compile(my_pulse_prog, backend, target="pulse_qobj") +``` +where the backend is provided to accomodate the compiled program to the backend constraints and mappings. An optional argument will be a mapping between logical and physical qubits, as under the new model all indices will be assumed logical. If no mapping is provided, the trivial one will be used. + +The first step of every compiler run will be to initialize the IR of the pulse program. The IR will be initialized with no concrete timing, and the scheduling will be carried out next. + +Next we highlight some of the tasks handled by the compiler. #### Scheduling -- IR initialization - For a given ScheduleBlock the IR will initialized as nested blocks (with no concrete timing). - Canonicalization (transform pass) - If legacy `Channel`s were used, they will be mapped to `MixedFrame`s using the backend mapping. This is the only scenario where backend mapping will be needed for this stage. - Identify frames and mixed frames (characterization pass) - `FrameInstruction`s will need to be broadcasted to every `MixedFrame` associated with that `Frame`. Therefore, we need identify every `MixedFrame`, `Frame` and the relations between them. - Schedule each block - - - starting from the lowest blocks, each block will be scheduled on its own. - - `FrameInstruction` defined on a `Frame` will be broadcasted to every associated `MixedFrame`, and these `MixedFrame`s will be considered "blocked" by this IR block. - - `FrameInstruction` defined on a `MixedFrame` will not be broadcasted to other `MixedFrame`. This will leave the user the ability to control the frame of `MixedFrame`s individually if wanted. + - Starting from the lowest blocks, each block will be scheduled on its own. + - Set\shift frequency\phase instructions will be broadcasted when necessary, and the additional mixed frames will be added to the block. - According to the alignment, the instructions will be scheduled within the block. - Nested blocks will be shifted according to the alignment, but their internal timing will not be altered by parent block alignment. - Apply backend scheduling constraints. - Validate timing and constraints. #### Mapping to backend aware "channels" -The `Frame`s and `MixedFrame`s will have to be converted to backend aware "channels". -It remains to be seen if this conversion will be done on the frontend or the backend, but a scheme for frontend mapping could look like this: +For a target format consumed by a backend, the `Frame`s and `MixedFrame`s will have to be converted to backend aware "channels". +It remains to be seen if this conversion will be done on the frontend or the backend, but a scheme for frontend mapping could look like this. -- First, map `MixedFrame`s which natively map to the backend. For example, a `MixedFrame` associated with `Qubit(1)` and `QubitFrame(1)` is natively mapped to the backend's Q1 "channel". - Similarly MixedFrame associate with `Qubit(1)` and `QubitFrame(2)` is natively mapped to the backend's CR12 "channel". +- Consume the backend accepted channels (For example, "D0" is the driving mixed frame of qubit 0 while "CR12" is the cross resonance mixed frame of qubit 1 in the frame of qubit 2.). +- First, map `MixedFrame`s which natively map to the backend. For example, a `MixedFrame` associated with `Qubit(1)` and `QubitFrame(1)` is natively mapped to the backend's "D1" "channel" in the example above. + Similarly MixedFrame associate with `Qubit(1)` and `QubitFrame(2)` is natively mapped to the backend's "CR12" "channel". - Next, map remaining `MixedFrames` into unused "channels". - Lastly, provide the backend with `MixedFrame` which couldn't have been mapped by the frontend, in hope that the backend could support them. (This will obviously require dedicated backend support). #### Conversion to output format There is still ongoing discussion about desired output formats, but every choice of output format could be supported at the cost of creating a conversion mechanism from IR to that format. +One can imagine various tasks associated with this step: +- Conversion of `SymbolicPulse` to `Waveform` for non-backend-supported pulses. +- Creation of pulse dictionary to reduce payload memory foot print. + +#### Optimizations +While pulse level programs are low level, one might consider optimization passes, like dynamical decoupling addition. The pass based compiler will allow such optimization passes to be created and applied. ## Alternative Approaches This proposal combines two seemingly separate issues - the channel model rework and the introduction of Pulse IR and Compiler. It would be possible to do one without the other, but it seems like this is From dfd5ebccf4f34c389e7871d85d7c11da05f93099 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Wed, 2 Aug 2023 16:22:53 +0300 Subject: [PATCH 05/10] Update ####-Pulse-Channels-IR-and-Compiler.md --- ####-Pulse-Channels-IR-and-Compiler.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/####-Pulse-Channels-IR-and-Compiler.md b/####-Pulse-Channels-IR-and-Compiler.md index 9945240..738a21f 100644 --- a/####-Pulse-Channels-IR-and-Compiler.md +++ b/####-Pulse-Channels-IR-and-Compiler.md @@ -229,7 +229,7 @@ A typical compiler call will look like: ``` payload = compile(my_pulse_prog, backend, target="pulse_qobj") ``` -where the backend is provided to accomodate the compiled program to the backend constraints and mappings. An optional argument will be a mapping between logical and physical qubits, as under the new model all indices will be assumed logical. If no mapping is provided, the trivial one will be used. +where the backend is provided to accomodate the compiled program to the backend constraints and mappings. An optional argument will be a mapping between logical and physical qubits, as under the new model all indices will be assumed logical. If no mapping is provided, the trivial one will be used. The mapping should be done before all other operations. The first step of every compiler run will be to initialize the IR of the pulse program. The IR will be initialized with no concrete timing, and the scheduling will be carried out next. From 8ed911654a4da6976e194f827d5e83d063ebe33f Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Thu, 3 Aug 2023 09:28:03 +0300 Subject: [PATCH 06/10] Update ####-Pulse-Channels-IR-and-Compiler.md --- ####-Pulse-Channels-IR-and-Compiler.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/####-Pulse-Channels-IR-and-Compiler.md b/####-Pulse-Channels-IR-and-Compiler.md index 738a21f..8bb6b61 100644 --- a/####-Pulse-Channels-IR-and-Compiler.md +++ b/####-Pulse-Channels-IR-and-Compiler.md @@ -28,7 +28,7 @@ A prime example for the need for a better compilation process, is given by the s The legacy `Channel`s correspond to what the backend calls a mixed frame - a combination of specific HW port, and the frame (frequency and phase) needed to play pulses. However, to specify a Channel one must be aware of the backend mapping. For example, the following pulse schedule is an ECR pulse for qubits 3 and 4: -``` +```python with builder.build() as ecr_schedule_q3q4: with align_sequential(): with align_left(): @@ -79,7 +79,7 @@ To replace the legacy Channels we propose to specify instructions in terms of `L Using these objects, the above code takes the form: -``` +```python with builder.build() as ecr_schedule_q3q4: with align_sequential(): with align_left(): @@ -103,7 +103,7 @@ It should be noted that custom `Frame` and `MixedFrame` will make it easier to c Classes hierarchy: -``` +```python class LogicalElement(ABC): @property def index(self) -> Tuple[int, ...] @@ -200,7 +200,7 @@ Two instructions types are used: broadcaseted to all mixed frames associated with the frame or not. - `AcquireInstruction` - for acquire instructions (due to the different elements involved). -``` +```python class PulseIR: def __init__(self): self.instructions # List of instructions or nested PulseIR objects @@ -226,7 +226,7 @@ class GenericInstruction: ### Compiler The compiler will be built with a shared design of Qiskit's pass manager (based on PR #10474), and will thus be easily extendable to other tasks. The pass based approach allows for simple modification of the lowering process for vendors or Qiskit team. A typical compiler call will look like: -``` +```python payload = compile(my_pulse_prog, backend, target="pulse_qobj") ``` where the backend is provided to accomodate the compiled program to the backend constraints and mappings. An optional argument will be a mapping between logical and physical qubits, as under the new model all indices will be assumed logical. If no mapping is provided, the trivial one will be used. The mapping should be done before all other operations. From 7a196478ff54f73943b5fcb9b6d57dea5fa89382 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Thu, 3 Aug 2023 09:30:38 +0300 Subject: [PATCH 07/10] Update ####-Pulse-Channels-IR-and-Compiler.md --- ####-Pulse-Channels-IR-and-Compiler.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/####-Pulse-Channels-IR-and-Compiler.md b/####-Pulse-Channels-IR-and-Compiler.md index 8bb6b61..1d827e0 100644 --- a/####-Pulse-Channels-IR-and-Compiler.md +++ b/####-Pulse-Channels-IR-and-Compiler.md @@ -237,7 +237,7 @@ Next we highlight some of the tasks handled by the compiler. #### Scheduling - Canonicalization (transform pass) - If legacy `Channel`s were used, they will be mapped to `MixedFrame`s using the backend mapping. This is the only scenario where backend mapping will be needed for this stage. -- Identify frames and mixed frames (characterization pass) - `FrameInstruction`s will need to be broadcasted to every `MixedFrame` associated with that `Frame`. Therefore, we need identify every `MixedFrame`, `Frame` and the relations between them. +- Collect frames and mixed frames (analysis pass) - Shift\set frequency\phase instructions will need to be broadcasted to every `MixedFrame` associated with that `Frame`. Therefore, we need collect every `MixedFrame`, `Frame` and the relations between them. - Schedule each block - - Starting from the lowest blocks, each block will be scheduled on its own. - Set\shift frequency\phase instructions will be broadcasted when necessary, and the additional mixed frames will be added to the block. From b178ba2c411311afa2ec04dfdea7c0fef84ed519 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Thu, 3 Aug 2023 09:48:59 +0300 Subject: [PATCH 08/10] Update ####-Pulse-Channels-IR-and-Compiler.md --- ####-Pulse-Channels-IR-and-Compiler.md | 46 ++++++++++++++++++-------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/####-Pulse-Channels-IR-and-Compiler.md b/####-Pulse-Channels-IR-and-Compiler.md index 1d827e0..a19b026 100644 --- a/####-Pulse-Channels-IR-and-Compiler.md +++ b/####-Pulse-Channels-IR-and-Compiler.md @@ -13,13 +13,13 @@ This RFC summarizes the proposal for new Pulse Compiler & IR. The introduction o The proposal is based on a series of discussions within Qiskit Pulse's development team, and is brought here for the community to weigh in. The main changes proposed include: -- Introduce new pass based Pulse Compiler and the supporting Pulse IR (intermediate representation). -- Introduce new model to supplement the existing `Channel` model, that will allow writing backend-agnostic template pulse programs. +- Introduction of a new pass based Pulse Compiler and the supporting Pulse IR (intermediate representation). +- Introduction of a new model to supplement the existing `Channel` model, that will allow writing backend-agnostic template pulse programs. Comments are welcome. ## Motivation -Qiskit Pulse currently has no unified compilation pathway. Different tasks are handled by the different code segments. This not only creates code clutter which is hard to maintain, +Qiskit Pulse currently has no unified compilation pathway - Different tasks are handled by the different code segments. This not only creates code clutter which is hard to maintain, but also limits the ability of vendors to adapt Qiskit Pulse, or adjust Qiskit Pulse to new HW developments. The circuit module of Qiskit already uses a pass based compilation process, which gives the flexibility to add\change\adapt passes according to the changing needs. @@ -57,22 +57,22 @@ However, supporting this change adds significant compilation needs, which are ha ## Design Proposal The new pulse compiler will be a pass based compiler. Dedicated passes will perform every analysis, transformation and validation operations needed to convert a pulse program into a desired output format. A detailed workflow will be described below, but the main tasks include -concretely scheduling `ScheduleBlock` programs, mapping logical qubits to physical ones, applying backend constraints, and converting to desired output format. To support this, +concretely scheduling `ScheduleBlock` programs, mapping virtual qubits to physical ones, applying backend constraints, and converting to desired output format. To support this, the new IR will consist of nested instruction blocks, which will be transformed by the compiler as it operates. -On the user facing interface, one can schematically split Qiskit Pulse into three layers - +Regarding the `Channel` model rework, one can schematically split Qiskit Pulse's user interface into three layers - - Pulse level (`SymbolicPulse`,`WaveForm`) - Instruction level (`Play`,`Delay`...) - Program level (`Schedule`,`ScheduleBlock`) -This proposal focuses on the instruction level. The main goal is to allow for clear and simple backend agnostic (but not architecture agnostic) pulse templates. +This rework focuses on the instruction level. The main goal is to allow for clear and simple backend agnostic (but not architecture agnostic) pulse templates. To do this, the legacy `Channel`s will be supplemented by `Frame`s and `LogicalElement`s. No API breaking changes are needed. New and legacy options can coexist. ## Detailed Design ### Channel rework -To replace the legacy Channels we propose to specify instructions in terms of `LogicalElement`s and `Frame`s: +To replace the legacy `Channel`s we propose to specify instructions in terms of `LogicalElement`s and `Frame`s: - `LogicalElement` - every type of element in the HW which the user can control ("play pulses on"). For example, a qubit is a `LogicalElement`, and not the specific port used to drive it. The most notable example of a logical element is of course the `Qubit`, but in the future one can imagine dedicated couplers, flux controls and so on. - `Frame` - a combination of frequency and phase. Subclasses used to identify the frame with a backend default. Notable examples are `QubitFrame` and `MeasurementFrame` associated with the default driving and measurement frequencies (respectively) of a qubit. @@ -91,10 +91,10 @@ with builder.build() as ecr_schedule_q3q4: Play(GaussianSquare(...), Qubit(3),QubitFrame(3)) ``` -The new code not only allows to work with logical backend-agnostic qubits, but is also much clearer in conveying the actual actions in play (acting on qubit 3 with the frame of qubit 4 vs with the frame of qubit 3). +The new code not only allows to work with virtual backend-agnostic qubits, but is also much clearer in conveying the actual actions in play (acting on qubit 3 with the frame of qubit 4 vs with the frame of qubit 3). On top of the `LogicalElement` and the `Frame`, another layer of `MixedFrame` will be introduced. -The `MixedFrame` is simply a combination of a LogicalElement and a Frame, and in many ways is similar to the legacy `Channel`. +The `MixedFrame` is simply a combination of a `LogicalElement` and a `Frame`, and in many ways is similar to the legacy `Channel`. The difference being that a `MixedFrame` does not depend on the backend mapping and has clear relations with other `MixedFrames` as opposed to a `Channel` which hasn't. Play\Delay instructions will be allowed to take one of `LogicalElement`+`Frame`, `MixedFrame` or `Channel` (for backwards compatibility). Set\Shift Frequency\Phase will be allowed to take one of `Frame` (to be broadcast to every associated `MixedFrame`), `MixedFrame` (not to be broadcasted) or `Channel`. @@ -187,12 +187,29 @@ class MeasurementFrame(Frame): @property def name(self) -> str: return f"MFrame{self.index}" + +class MixedFrame(): + def __init__(self, logical_element: LogicalElement, frame: Frame): + self._logical_element = logical_element + self._frame = frame + + @property + def logical_element(self) -> LogicalElement + return self._logical_element + + @property + def frame(self) -> Frame + return self._frame + + @property + def name(self) -> str: + return f"MixedFrame({self._logical_element.name},{self._frame.name})" ``` ### Pulse IR The Pulse IR will provide the framework for the compiler to work on and perform the necessary compilation steps. The main components of the IR will be: -- IR Block - A block of instructions (or nested blocks) with an alignment context. This structure is similar to the structure of `ScheduleBlock` and needed to support one of the main tasks of the compiler - scheduling. +- IR Block - A block of instructions (or nested blocks) with an alignment context. This structure is similar to the structure of `ScheduleBlock` and is needed to support one of the main tasks of the compiler - scheduling. - IR Instruction - A pulse program instruction. Typically, it will be initialized without concrete timing, and will be scheduled during the compiler operation. Two instructions types are used: @@ -229,7 +246,7 @@ A typical compiler call will look like: ```python payload = compile(my_pulse_prog, backend, target="pulse_qobj") ``` -where the backend is provided to accomodate the compiled program to the backend constraints and mappings. An optional argument will be a mapping between logical and physical qubits, as under the new model all indices will be assumed logical. If no mapping is provided, the trivial one will be used. The mapping should be done before all other operations. +where the backend is provided to accomodate the compiled program to the backend constraints and mappings. An optional argument will be a mapping between virtual and physical qubits, as under the new model all indices will be assumed virtual. If no mapping is provided, the trivial one will be used. The mapping will be done before all other operations, during the initialization of the IR. The first step of every compiler run will be to initialize the IR of the pulse program. The IR will be initialized with no concrete timing, and the scheduling will be carried out next. @@ -237,7 +254,7 @@ Next we highlight some of the tasks handled by the compiler. #### Scheduling - Canonicalization (transform pass) - If legacy `Channel`s were used, they will be mapped to `MixedFrame`s using the backend mapping. This is the only scenario where backend mapping will be needed for this stage. -- Collect frames and mixed frames (analysis pass) - Shift\set frequency\phase instructions will need to be broadcasted to every `MixedFrame` associated with that `Frame`. Therefore, we need collect every `MixedFrame`, `Frame` and the relations between them. +- Collect frames and mixed frames (analysis pass) - Shift\set frequency\phase instructions on a `Frame` will need to be broadcasted to every `MixedFrame` associated with that `Frame`. Therefore, we need collect every `MixedFrame`, `Frame` and the relations between them. - Schedule each block - - Starting from the lowest blocks, each block will be scheduled on its own. - Set\shift frequency\phase instructions will be broadcasted when necessary, and the additional mixed frames will be added to the block. @@ -247,14 +264,14 @@ Next we highlight some of the tasks handled by the compiler. - Validate timing and constraints. #### Mapping to backend aware "channels" -For a target format consumed by a backend, the `Frame`s and `MixedFrame`s will have to be converted to backend aware "channels". +For a target format consumed by a backend, the `Frame`s and `MixedFrame`s will have to be converted to backend aware "channels" (for a lack of a better generic term). It remains to be seen if this conversion will be done on the frontend or the backend, but a scheme for frontend mapping could look like this. - Consume the backend accepted channels (For example, "D0" is the driving mixed frame of qubit 0 while "CR12" is the cross resonance mixed frame of qubit 1 in the frame of qubit 2.). - First, map `MixedFrame`s which natively map to the backend. For example, a `MixedFrame` associated with `Qubit(1)` and `QubitFrame(1)` is natively mapped to the backend's "D1" "channel" in the example above. Similarly MixedFrame associate with `Qubit(1)` and `QubitFrame(2)` is natively mapped to the backend's "CR12" "channel". - Next, map remaining `MixedFrames` into unused "channels". -- Lastly, provide the backend with `MixedFrame` which couldn't have been mapped by the frontend, in hope that the backend could support them. (This will obviously require dedicated backend support). +- Lastly, provide the backend with `MixedFrame`s which couldn't have been mapped by the frontend, in hope that the backend could support them. (This will obviously require dedicated backend support). #### Conversion to output format There is still ongoing discussion about desired output formats, but every choice of output format could be supported at the cost of creating a conversion mechanism from IR to that format. @@ -275,3 +292,4 @@ a good opportunity to do both. ## Future Extensions - Frontend & backend coordination to support custom frames and mixed frames. This has the potential to better utilize HW control options (as discussed above). +- Introuction of new optimizations at the pulse level programing. From 4d97bf0854580312cb6aa246cb1f7931f9eeb0fc Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Mon, 7 Aug 2023 18:04:14 +0300 Subject: [PATCH 09/10] Update and rename ####-Pulse-Channels-IR-and-Compiler.md to 0012-Pulse-Compiler-and-IR.md --- ...annels-IR-and-Compiler.md => 0012-Pulse-Compiler-and-IR.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename ####-Pulse-Channels-IR-and-Compiler.md => 0012-Pulse-Compiler-and-IR.md (99%) diff --git a/####-Pulse-Channels-IR-and-Compiler.md b/0012-Pulse-Compiler-and-IR.md similarity index 99% rename from ####-Pulse-Channels-IR-and-Compiler.md rename to 0012-Pulse-Compiler-and-IR.md index a19b026..948bae8 100644 --- a/####-Pulse-Channels-IR-and-Compiler.md +++ b/0012-Pulse-Compiler-and-IR.md @@ -2,11 +2,11 @@ | **Status** | **Proposed** | |:------------------|:---------------------------------------------| -| **RFC #** | #### | +| **RFC #** | 0012 | | **Authors** | Tsafrir Armon (tsafrir.armon@ibm.com), Naoki Kanazawa (knzwnao@jp.ibm.com) | | **Deprecates** | - | | **Submitted** | 2023-07-31 | -| **Updated** | YYYY-MM-DD | +| **Updated** | 20223-08-07 | ## Summary This RFC summarizes the proposal for new Pulse Compiler & IR. The introduction of the new compiler paves the way to the transition to frame aware model, which is also discussed. From 559f644ae567b572d162a0f32ce59466ab672d34 Mon Sep 17 00:00:00 2001 From: TsafrirA <113579969+TsafrirA@users.noreply.github.com> Date: Mon, 7 Aug 2023 18:06:45 +0300 Subject: [PATCH 10/10] Update 0012-Pulse-Compiler-and-IR.md --- 0012-Pulse-Compiler-and-IR.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/0012-Pulse-Compiler-and-IR.md b/0012-Pulse-Compiler-and-IR.md index 948bae8..8b497a3 100644 --- a/0012-Pulse-Compiler-and-IR.md +++ b/0012-Pulse-Compiler-and-IR.md @@ -6,7 +6,7 @@ | **Authors** | Tsafrir Armon (tsafrir.armon@ibm.com), Naoki Kanazawa (knzwnao@jp.ibm.com) | | **Deprecates** | - | | **Submitted** | 2023-07-31 | -| **Updated** | 20223-08-07 | +| **Updated** | 2023-08-07 | ## Summary This RFC summarizes the proposal for new Pulse Compiler & IR. The introduction of the new compiler paves the way to the transition to frame aware model, which is also discussed. @@ -200,10 +200,6 @@ class MixedFrame(): @property def frame(self) -> Frame return self._frame - - @property - def name(self) -> str: - return f"MixedFrame({self._logical_element.name},{self._frame.name})" ``` ### Pulse IR