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 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.waveform import echo8
core.pipelines.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.waveform 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")
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).
Updateable Pipelines
The pipeline from the previous example was a default pipeline instance that you can import from QAT. But more likely, you would want to configure your own pipeline using your own hardware model and target data for a given type of hardware. Updateable pipelines provide a prescribed way to create pipelines that offer utility to rebuild pipelines using new hardware models or target data. Let’s demonstrate with the Waveform example and the echo engine from the previous section, but by configuring our own.
1from qat.pipelines.waveform import EchoPipeline, PipelineConfig
2from qat.model.loaders.lucy import LucyModelLoader
3
4loader = LucyModelLoader(qubit_count=8)
5config = PipelineConfig(name="echo_pipeline")
6pipeline = EchoPipeline(loader=loader, config=config)
7pipeline.update(reload_model=True)
There’s a few things to notice here. First, the updateable pipeline actually takes
ownership of the pipeline instance it creates, and in the way, can be used in-place of the
pipeline instance. Secondly, we instantiated it using the model loader, allowing us to
reload the model directly from the loader. However, updateable pipelines can be configured
using a hardware model directly, and similarly, they can be updated by providing a new model
directly. The target data can also be provided at instantiation, and updated to using
pipeline.update(target_data=target_data). Finally, each updateable pipeline is
paired with a PipelineConfig object that
stores configuration data for the pipeline, such as its name, and additional compiler
settings.
Compile and Execute (updateable) pipelines
The example seen previously uses a “full pipeline” that is capable of both compiling and
executing a program. However, we can also express pipelines that can only compile
CompilePipeline or only execute
ExecutePipeline. The benefits of this are that
We can clearly separate out the compilation and execution steps over distributed systems.
We can mix-and-match compilation and execution pipelines. For example, we could compile a program for a specific hardware target, but execute it on a simulator. On the contrary, we could also define multiple compile pipelines that expose different compiler features, but execute them all on the same hardware target.
We can compile and execute against particular pipelines by using QAT.compile and
QAT.execute, specifying the pipeline to use.
1from qat.pipelines.waveform import WaveformCompilePipeline, EchoExecutePipeline, PipelineConfig
2from qat.model.loaders.lucy import LucyModelLoader
3from qat import QAT
4
5# Define pipelines
6model = LucyModelLoader(qubit_count=16).load()
7compile_pipeline = WaveformCompilePipeline(config=PipelineConfig(name="compile"), model=model)
8execute_pipeline = EchoExecutePipeline(config=PipelineConfig(name="execute"), model=model)
9
10# Register pipelines
11core = QAT()
12core.pipelines.add(compile_pipeline, default=True)
13core.pipelines.add(execute_pipeline, default=True)
14
15# Execute against pipelines
16qasm_str = """
17OPENQASM 2.0;
18include "qelib1.inc";
19qreg q[2];
20creg c[2];
21h q[0];
22cx q[0], q[1];
23measure q -> c;
24"""
25executable, compile_metrics = core.compile(qasm_str, pipeline="compile")
26results, execute_metrics = core.execute(executable, pipeline="execute")
Default pipelines that are available in QAT
There are a number of pipelines in QAT that are available to use off-the-shelf.
qat.pipelines.waveform: Pipelines that execute using theEchoEngine. The pipelines available by default areecho8,echo16,echo32. The updateable pipelines are available throughEchoPipeline,WaveformCompilePipelineandEchoExecutePipeline.
There are also pipelines that use legacy hardware and engines, but wrapped in the new pipeline API:
qat.pipelines.legacy.echo: Pipelines that execute using the legacyEchoEngine. The pipelines available by default arelegacy_echo8,legacy_echo16,lgeacy_echo32. For a custom amount of qubits, the methodget_pipelinecan be used.qat.pipelines.legacy.rtcs: Pipelines that execute using the legacyRealtimeChipSimEngine. The only available pipeline is for two qubits,legacy_rtcs2.qat.pipelines.legacy.qiskit: Pipelines that execute using the legacyQiskitEngine. The available pipelines arelegacy_qiskit8,legacy_qiskit16andlegacy_qiskit32. For a custom amount of qubits, the methodget_pipelinecan be used.
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 engine, and deals with software post-processing of the results. See Execution for more details.
See ../notebooks/tutorials/custom_pipeline for a working example of defining a custom pipeline and updateable pipeline.