Catalyst Command Line Interface¶
Catalyst includes a standalone command-line tool, catalyst, that
compiles quantum programs written in our MLIR dialects into an object file,
independent of the Catalyst Python frontend.
This compiler tool combines four 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.llvm-opt: Performs lower-level optimization and compilation on the LLVM IR needed for certain features.llc: Runs LLVM backend code generation and creates the object file.
The catalyst tool will run all stages under the hood by default, but it also has the
ability to run each stage individually. For example:
# Run MLIR passes, returning LLVM-dialect MLIR
catalyst --tool=opt input.mlir -o llvm-dialect.mlir
# Translate LLVM-dialect MLIR to LLVM IR
catalyst --tool=translate llvm-dialect.mlir -o llvm-ir.ll
# Run Catalyst's LLVM transformations
catalyst --tool=llvm-opt llvm-ir.ll -o optimized.ll
# Generate object.o from backend-ready LLVM IR
catalyst --tool=llc optimized.ll -o object.o
Stages can be combined for a partial or full execution of the pipeline:
# Run MLIR passes and translation in one invocation, without LLVM transformations
catalyst --tool=opt-translate input.mlir -o llvm-ir.ll
# Run through LLVM transformations in one invocation, without object generation
catalyst --tool=opt-translate-llvm input.mlir -o optimized.ll
# Full compilation from mlir to object code
catalyst input.mlir
Note
If Catalyst is built from source, the catalyst executable will be located in
the mlir/build/bin/ directory relative to the root of your Catalyst source directory.
If Catalyst is installed via pip or from wheels, the executable will be located
in the bin/ directory relative to the environment’s installation directory.
Usage¶
catalyst [options] <input file>
Calling catalyst without any options runs the full compilation pipeline using all default
configurations, and outputs by default an object file named catalyst_module.o. Use
-o object.o to choose another output path.
Command line options¶
The complete list of options for the Catalyst CLI tool can be displayed by running catalyst --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 progress messages to stderr.
-o <filename>¶
Destination for the selected tool’s final output. opt produces MLIR. translate,
llvm-opt, opt-translate, and opt-translate-llvm produce LLVM IR. all and
llc produce object code. With --emit-bytecode, opt produces MLIR bytecode.
IR output can also be written to stdout with -o -, which is the default value. However,
object code requires a file destination. The default when -o is omitted in that case is
<module-name>.o in the current directory, using catalyst_module as the default
module name.
--tool=<opt|translate|llvm-opt|llc>¶
Select the tool to run individually. Each stage requires appropriate IR as input.
opt: Run MLIR passes on MLIR input.translate: Translate LLVM-dialect MLIR to LLVM IR.llvm-opt: Run Catalyst’s applicable LLVM transformations on LLVM IR input.llc: Run backend code generation on backend-ready LLVM IR input.
A combination of stages (starting from MLIR) can also be run. all is the default.
opt-translate: Runoptandtranslateon MLIR input.opt-translate-llvm: Runopt,translate, andllvm-opton MLIR input.all: Run all stages. LLVM IR input is also accepted, skipping the MLIR stages.
--save-ir-after-each=<pass|pipeline|changed>¶
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.changed: Keep intermediate files after each transformation/optimization pass, but only if the IR was modified by that pass.
--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.
--use-nameloc-as-prefix[=<true|false>]¶
Print SSA IDs using their name location, if provided, as prefix. By default, name location information is not used. Name location, or named source location, is a type of source location information that allows attaching a name to a child location.
--{passname}¶
Enable a specific pass. For example, to enable the cancel-inverses pass, use
--cancel-inverses.
Catalyst’s main mlir stage is split up into a sequence of pass pipelines that can also be run
individually via this option. In that case, the name of the pipeline is substituted for the pass
name. Currently, the following pipelines are available:
quantum-compilation-stage,
hlo-lowering-stage,
gradient-lowering-stage,
bufferization-stage,
llvm-dialect-lowering-stage, and finally
default-catalyst-pipeline which encompasses all the above as the default pipeline used by the
Catalyst CLI tool if no pass option is specified.
There is also user-transform-stage, a shorter version of quantum-compilation-stage that
stops right after apply-transform-sequence, the pass that applies the user transforms. It is
useful when only the program after the user transforms is needed, for example for resource
analysis. It is not meant to be followed by the remaining stages.
--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 with this option.
--module-name=<name>¶
The module name used for diagnostics, auxiliary compiler artifacts, and the default object
filename <module-name>.o. The default is "catalyst_module". An explicit -o path
takes precedence over the default object filename.
--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 stage. The stages that are currently available are:
MLIR:
mlir(start with first MLIR stage),{pipeline}such as any of the built-in pipeline names described under the--{passname}option, OR any custom pipeline names if the--catalyst-pipeline={pipeline(...),...}option is used.LLVM:
LLVMIRTranslation(start with first LLVM stage),CoroOpt,O2Opt,Enzyme. Note thatCoroOpt(Coroutine lowering),O2Opt(O2 optimization), andEnzyme(automatic differentiation) passes are only run conditionally as needed.
--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:
cancel-inverses, 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 thecancel-inversespass.
To apply these two passes to our my_circuit function, we can do so as follows:
pipe(cancel-inverses;merge-rotations)
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 my_circuit.mlir \
--tool=opt \
--catalyst-pipeline="pipe(cancel-inverses;merge-rotations)" \
--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 CancelInversesPass (cancel-inverses) //----- //
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 cancel-inverses 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 --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 and quantum-opt.
As an example, the LLVM repository includes a very simple plugin.
To build it, simply run make 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 --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 --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