Catalyst Command Line Interface¶
Catalyst includes a standalone command-line-interface compiler tool catalyst-cli
that
compiles quantum programs written in our MLIR dialects into an object file,
independent of the Catalyst Python frontend.
This compiler tool combines three stages of compilation:
quantum-opt
: Performs the MLIR-level optimizations, including quantum optimizations, and translates the input dialect to the LLVM dialect.mlir-translate
: Translates the input LLVM dialect into LLVM IR.llc
: Performs lower-level optimizations on the LLVM IR input and creates the object file.
catalyst-cli
runs all three stages under the hood by default, but it also has the ability to run
each stage individually. For example:
# Creates both the optimized IR and an object file
catalyst-cli input.mlir -o output.o
# Only performs MLIR optimizations and translates to LLVM dialect
catalyst-cli --tool=opt input.mlir -o llvm-dialect.mlir
# Only lowers LLVM dialect input to LLVM IR
catalyst-cli --tool=translate llvm-dialect.mlir -o llvm-ir.ll
# Only performs lower-level optimizations and creates object file (object.o)
catalyst-cli --tool=llc llvm-ir.ll -o output.ll --module-name object
Note
The Catalyst CLI tool is currently only available when Catalyst is built from source, and is not included when installing Catalyst via pip or from wheels.
After building Catalyst, the catalyst-cli
executable will be available in the
mlir/build/bin/
directory.
Usage¶
catalyst-cli [options] <input file>
Calling catalyst-cli
without any options runs the three compilation stages (quantum-opt
,
mlir-translate
and llc
) using all default configurations, and outputs by default an object
file named catalyst_module.o
. The name of the output file can be set by changing the output
module name using the --module-name
option (the default module name is catalyst_module
).
Command line options¶
The complete list of options for the Catalyst CLI tool can be displayed by running catalyst-cli --help
.
As this list contains all available options, including those for configuring LLVM, the options
most relevant to the usage of the Catalyst CLI tool are covered in more detail below.
--help
¶
Show available command-line options and exit.
--verbose
¶
Emit verbose messages.
-o <filename>
¶
Output IR filename. If no output filename is provided, the resulting IR is output to stdout.
--tool=<opt|translate|llc|all>
¶
Select the tool to run individually. The default is all
.
opt
: Runquantum-opt
on the MLIR input.translate
: Runmlir-translate
on the LLVM dialect input.llc
: Runllc
on the LLVM IR input.all
: Run all ofopt
,translate
andllc
on the MLIR input.
--save-ir-after-each=<pass|pipeline>
¶
Keep intermediate files after each pass or after each pipeline in the compilation. By default, no intermediate files are saved.
pass
: Keep intermediate files after each transformation/optimization pass.pipeline
: Keep intermediate files after each pipeline, where a pipeline is a sequence of transformation/optimization passes.
--keep-intermediate[=<true|false>]
¶
Keep intermediate files after each pipeline in the compilation. By default, no intermediate files
are saved. Using --keep-intermediate
is equivalent to using --save-ir-after-each=pipeline
.
--catalyst-pipeline=<pipeline1(pass1[;pass2[;...]])[,pipeline2(...)]>
¶
Specify the Catalyst compilation pass pipelines.
A pipeline is composed of a semicolon-delimited sequence of one or more transformation or optimization passes. Multiple pass pipelines can be specified and input as a comma-delimited sequence of pipelines.
For example, if we wanted to specify two pass pipelines, pipe1
and pipe2
, where pipe1
applies the passes split-multiple-tapes
and apply-transform-sequence
, and where pipe2
applies the pass inline-nested-module
, we would specify this pipeline configuration as:
--catalyst-pipeline=pipe1(split-multiple-tapes;apply-transform-sequence),pipe2(inline-nested-module)
--workspace=<path>
¶
The workspace directory where intermediate files are saved. The default is the current working directory.
Note that the workspace directory must exist before running catalyst-cli
with this option.
--module-name=<name>
¶
The module name used in naming the output file(s). The default is "catalyst_module"
. Using the
-o
option to specify the output filename overrides this option.
--async-qnodes[=<true|false>]
¶
Enable asynchronous QNodes.
--checkpoint-stage=<stage name>
¶
Define a checkpoint stage, used to indicate that the compiler should start only after reaching the given pass.
--dump-catalyst-pipeline[=<true|false>]
¶
Print (to stderr) the pipeline(s) that will be run.
Examples¶
To illustrate how to use the Catalyst CLI tool, consider the simple MLIR code, my_circuit.mlir
,
which defines a function my_circuit
that implements a single-qubit quantum circuit that applies
the sequence of gates \(R_x(\theta) \to H \to H \to R_x(\theta)\) to the input qubit for some
rotation angle \(\theta\):
module {
func.func @my_circuit(%in_qubit: !quantum.bit, %angle: f64) -> !quantum.bit {
%0 = quantum.custom "RX"(%angle) %in_qubit : !quantum.bit
%1 = quantum.custom "Hadamard"() %0 : !quantum.bit
%2 = quantum.custom "Hadamard"() %1 : !quantum.bit
%3 = quantum.custom "RX"(%angle) %2 : !quantum.bit
return %3 : !quantum.bit
}
}
We’ll use the Catalyst CLI tool to run the quantum-opt
compiler to perform the MLIR-level
optimizations and translate the input to the LLVM dialect. We’ll define a pass pipeline that applies
two quantum-optimization passes:
remove-chained-self-inverse
, which removes any operations that are applied next to their (self-)inverses or adjoint, in this case the two adjacent Hadamard gates.merge-rotations
, which combines rotation gates of the same type that act sequentially, in this case the two RX gates the become adjacent after the two Hadamard gates have been removed by theremove-chained-self-inverse
pass.
To define the pass pipeline, we must supply the name of the function to which each pass applies
using the func-name
argument. The func-name
argument is specific to the two passes we are
applying and is not a general requirement. To apply these two passes to our my_circuit
function,
we can do so as follows:
pipe(remove-chained-self-inverse{func-name=my_circuit};merge-rotations{func-name=my_circuit})
Finally, we’ll use the option --mlir-print-ir-after-all
to print the resulting MLIR after each
pass that is applied, and the -o
option to set the name of the output IR file:
catalyst-cli my_circuit.mlir \
--tool=opt \
--catalyst-pipeline="pipe(remove-chained-self-inverse{func-name=my_circuit};merge-rotations{func-name=my_circuit})" \
--mlir-print-ir-after-all \
-o my_circuit-llvm.mlir
Running this command will output the following intermediate IR to the console:
// -----// IR Dump After RemoveChainedSelfInversePass (remove-chained-self-inverse) //----- //
module {
func.func @my_circuit(%arg0: !quantum.bit, %arg1: f64) -> !quantum.bit {
%out_qubits = quantum.custom "RX"(%arg1) %arg0 : !quantum.bit
%out_qubits_0 = quantum.custom "RX"(%arg1) %out_qubits : !quantum.bit
return %out_qubits_0 : !quantum.bit
}
}
// -----// IR Dump After MergeRotationsPass (merge-rotations) //----- //
module {
func.func @my_circuit(%arg0: !quantum.bit, %arg1: f64) -> !quantum.bit {
%0 = arith.addf %arg1, %arg1 : f64
%out_qubits = quantum.custom "RX"(%0) %arg0 : !quantum.bit
return %out_qubits : !quantum.bit
}
}
and produce a new file my_circuit-llvm.mlir
containing the resulting module in the LLVM dialect:
module {
func.func @my_circuit(%arg0: !quantum.bit, %arg1: f64) -> !quantum.bit {
%0 = arith.addf %arg1, %arg1 : f64
%out_qubits = quantum.custom "RX"(%0) %arg0 : !quantum.bit
return %out_qubits : !quantum.bit
}
}
We can see in the intermediate IR after the remove-chained-self-inverse
pass that the two
adjacent Hadamard gates were removed and that the two RX gates were merged into one after the
merge-rotations
pass, with the input angle to the single RX gate being the sum of the two input
angles to the original two gates. The result in my_circuit-llvm.mlir
contains the final,
optimized MLIR.
For a list of transformation passes currently available in Catalyst, see the
Catalyst’s Transformation Library documentation. The available passes are also listed in the
catalyst-cli --help
message.
MLIR Plugins¶
mlir-opt
-like tools are able to take plugins as inputs.
These plugins are shared objects that include dialects and passes written by third parties.
This means that you can write dialects and passes that can be used with catalyst-cli
and quantum-opt
.
As an example, the LLVM repository includes a very simple plugin.
To build it, simply run make standalone-plugin
and the standalone plugin
will be built in the root directory of the Catalyst project.
With this, you can now run your own passes by using the following flags:
catalyst-cli --load-dialect-plugin=$YOUR_PLUGIN --load-pass-plugin=$YOUR_PLUGIN $YOUR_PASS_NAME file.mlir
Concretely for the example plugin, you can use the following command:
catalyst-cli --tool=opt --load-pass-plugin=standalone/build/lib/StandalonePlugin.so --load-dialect-plugin=standalone/build/lib/StandalonePlugin.so --pass-pipeline='builtin.module(standalone-switch-bar-foo)' a.mlir