Pipelines

The primary way to access QAT’s compilation and execution capabilities is through its pipelines API. Pipelines allow us to define the compilation and execution process in a configurable and modular way.

Using QAT pipelines

QAT has a number of pipelines that are pre-defined and ready to use. We start by creating a a QAT object.

from qat import QAT
core = QAT()

We can now add a pipeline to it. Let’s add a pipeline that uses the EchoEngine:

from qat.pipelines.echo import echo8
core.pipeline.add(echo8, default=True)

This will add the “echo8” pipeline to core, which can be used to compile and execute programs using a simulator that simply returns all readouts as zeroes. The “8” in “echo8” specifies that the simulator has 8 qubits available. The pipeline already has its compilation modules chosen so that it is compatible with the echo engine: we’ll cover this in more detail in Compilation. Now we can use this to execute a QASM program.

 1from qat import QAT
 2from qat.pipelines.echo import echo8
 3from compiler_config.config import CompilerConfig, QuantumResultsFormat
 4
 5core = QAT()
 6core.pipelines.add(echo8, default=True)
 7
 8qasm_str = """
 9OPENQASM 2.0;
10include "qelib1.inc";
11qreg q[2];
12creg c[2];
13h q[0];
14cx q[0], q[1];
15measure q -> c;
16"""
17
18config = CompilerConfig(results_format=QuantumResultsFormat().binary_count())
19results, metrics = core.run(qasm_str, config, pipeline="echo8")

There’s a couple of things to unpack here.

  • The program qasm_str describes a simple QASM2 program to create a bell state on two qubits.

  • Finally, you will note that metrics is returned. This is an object that contains various metrics regarding compilation, such as circuits after they have been optimized, or the total number of pulse-level instructions. Since the echo pipeline just returns zeros for readouts, the result returned here is results = {'c': {'00': 1000}}.

We could also achieve the same workflow by compiling and executing separately:

pkg, metrics = core.compile(qasm_str, config, pipeline="echo8")
results, metrics = core.execute(pkg, config, pipeline="echo8")

The package pkg contains the native instructions to be sent to the target (in this case, the echo simulator).

Default pipelines that are available in QAT

There are a number of pipelines in QAT that are available to use off-the-shelf.

There are also pipelines that use legacy hardware and engines, but wrapped in the new pipeline API:

Defining custom pipelines

Pipelines in QAT are highly customisable to allow for diverse compilation behaviour for a range of targets, such as live hardware or custom simulators. Compilation is broken down into three parts: the frontend, the middleend and the backend. We will not go into the details of each module here, but they will be covered in Compilation. Similarly,the execution part of the pipeline is defined by two objects: the engine and the runtime. The engine acts as an adapter to the target, and deals with communicating the instructions and results from the runtime to the target. The runtime handles the the engine, and deals with software post-processing of the results. See Execution for more details.

Let us quickly show how to define a custom pipeline by recreating the “echo8” pipeline.

 1from qat import QAT
 2from qat.core.pipeline import Pipeline
 3from qat.frontend.frontends import DefaultFrontend
 4from qat.middleend.middleends import DefaultMiddleend
 5from qat.backend.waveform_v1 import WaveformV1Backend
 6from qat.engines.waveform_v1 import EchoEngine
 7from qat.runtime import SimpleRuntime
 8from qat.model.loaders.legacy import EchoModelLoader
 9from compiler_config.config import CompilerConfig, QuantumResultsFormat
10
11model = EchoModelLoader(8).load()
12new_echo8 = Pipeline(
13    name="new_echo8",
14    frontend=DefaultFrontend(model),
15    middleend=DefaultMiddleend(model),
16    backend=WaveformV1Backend(model),
17    runtime=SimpleRuntime(EchoEngine()),
18    model=model
19)
20
21core = QAT()
22core.pipelines.add(new_echo8)
23
24qasm_str = """
25OPENQASM 2.0;
26include "qelib1.inc";
27qreg q[2];
28creg c[2];
29h q[0];
30cx q[0], q[1];
31measure q -> c;
32"""
33
34config = CompilerConfig(results_format=QuantumResultsFormat().binary_count())
35results, metrics = core.run(qasm_str, config, pipeline="new_echo8")

Notice that the EchoEngine and the WaveformV1Backend are both contained in a waveform_v1 package. This is not by coincidence: the engine has to be appropriately picked to match the code generated from the backend. There will be more details on the responsibilities of backends and engines in later sections.

Defining pipelines using a configuration file

So far we have manually imported pipelines and added them to a QAT to use the pipeline API QAT.compile and QAT.execute. However, we can specify some default pipelines to use via a configuration file.

 1MAX_REPEATS_LIMIT: 1000
 2PIPELINES:
 3- name: echo8-alt
 4  pipeline: qat.pipelines.echo.echo8
 5  default: false
 6- name: echo16-alt
 7  pipeline: qat.pipelines.echo.echo16
 8  default: true
 9- name: echo32-alt
10  pipeline: qat.pipelines.echo.echo32
11  default: false
12- name: echo6-alt
13  pipeline: qat.pipelines.echo.get_pipeline
14  hardware_loader: echo6loader
15  default: false
16
17HARDWARE:
18- name: echo6loader
19  loader: qat.model.loaders.legacy.EchoModelLoader
20  init:
21    qubit_count: 6

This file currently allows us to specify the maximum number of shots that can be done for each job, and a number of pipelines. Notice that the first three pipelines just point to an already defined pipeline. The fourth points to a function that lets us provide our own hardware model, which is specified under HARDWARE.

To use this within QAT, we can simply use the directory of the file to instantiate the QAT object.

 1from qat import QAT
 2qat = QAT(qatconfig="path_to_file.yaml")
 3
 4qasm_str = """
 5OPENQASM 2.0;
 6include "qelib1.inc";
 7qreg q[2];
 8creg c[2];
 9h q[0];
10cx q[0], q[1];
11measure q -> c;
12"""
13
14results = qat.run(qasm_str, pipeline="echo6-alt")