Catalyst

repository·main·Indexed 42 days ago

https://github.com/pennylaneai/catalyst

An experimental package for the just-in-time (JIT) compilation of hybrid quantum-classical programs. It utilizes a compiler built with MLIR and a C++ runtime to compile entire workflows, including optimization loops, into machine binaries for significant performance improvements. It provides a PennyLane JAX frontend featuring the `@qjit` decorator for JIT-compiling hybrid programs with support for end-to-end differentiability, advanced control flow, and compatibility with Amazon Braket and PennyLane-Lightning.

Tokens
85.8K
Snippets
265
Records
375
Agent score
53%

What's inside Catalyst

  1. Overview of Catalyst MLIR dialects

    main

    Catalyst provides several specialized MLIR (Multi-Level Intermediate Representation) dialects to represent different aspects of quantum computing and compiler support. These dialects allow for fine-grained control over quantum programs, automatic differentiation, error mitigation, and hardware-specific optimizations.

    Key dialects include:

    • quantum: The core dialect used for defining quantum programs.
    • gradient: Provides support for automatic differentiation.
    • mitigation: Implements quantum error mitigation techniques.
    • catalyst: A support dialect specifically for the Catalyst compiler.
    • ion: A special-purpose dialect optimized for trapped ion quantum computers.
    • pbc: A special-purpose dialect for Pauli-based computation (PBC) and error correction.
    • mbqc: A special-purpose dialect for measurement-based quantum computing.
    • transport: A special-purpose dialect for Backline's data-movement operations.
    • executor: A support dialect used for dispatching kernels and calling symbols on an executor.
  2. Overview of the Catalyst Frontend for PennyLane

    main

    The Catalyst frontend enables Just-In-Time (JIT) and Ahead-Of-Time (AOT) compilation for PennyLane and JAX Python programs. It provides an alternative compilation and device execution pipeline for PennyLane through a custom function decorator.

    How it works

    The frontend uses a two-step lowering process:

    1. Tracing: It uses the JAX tracing framework to capture classical and quantum instructions into a computational graph stored in JAX's internal program representation (JAXPR).
    2. Lowering: It uses the JAX support library and custom MLIR Python bindings to lower the JAXPR into the quantum MLIR representation required by the Catalyst compiler core.

    Key Capabilities

    • Quantum-Native JAXPR: The frontend introduces extensions to JAXPR primitives to natively trace and represent quantum instructions.
    • Control Flow: Extensions to PennyLane allow for the compilation of arbitrary control flow inside quantum functions.
    • Mid-Circuit Operations: Supports real-time mid-circuit measurements and measurement result feedback.
    • JAX Compatibility: Any program compatible with jax.jit is supported by default.
  3. What is Catalyst

    main
    Catalyst is an experimental package designed to enable just-in-time (JIT) compilation of PennyLane programs. It allows developers to compile the entire quantum-classical workflow, optimizing the interaction between quantum circuits and classical control logic.
  4. Overview of Catalyst Quantum Runtime

    main

    The Catalyst Runtime is a C++ QIR (Quantum Intermediate Representation) runtime designed to execute Catalyst-compiled quantum programs. It provides an extensible architecture for backend quantum devices through the QuantumDevice public interface.

    The runtime is split into two main instruction types implemented via the QuantumDevice interface:

    1. Quantum Runtime (QR) instructions: Handled by methods for Qubit management, device shot noise, and quantum tape recording.
    2. Quantum Instruction Set (QIS) instructions: Handled by methods for quantum operations, observables, measurements, and gradients.

    A full list of supported instructions is available in RuntimeCAPI.h.

  5. Overview of the Catalyst Quantum Compiler

    main

    Catalyst is an MLIR-based compiler core designed for hybrid quantum-classical programs. It uses a custom quantum dialect within the MLIR framework to represent, transform, and optimize hybrid programs using a single unified representation.

    Key technical characteristics include:

    • Value-Semantics Representation: Quantum operations consume and produce qubit "state values". While these values are removed during lowering, they allow the dataflow of quantum operations to be represented directly in the SSA graph (similar to a DAG), enabling easier IR traversal without explicit dataflow analysis.
    • Gradient Methods: Catalyst implements quantum and classical gradient methods (including parameter-shift, adjoint, and finite-difference) at the IR level. These methods support programs containing embedded classical instructions and control flow.
  6. Overview of Catalyst callback functions

    main

    Catalyst provides three main types of callbacks depending on your needs:

    1. debug.callback: For functions with no return values. Ideal for runtime debugging, logging, or printing.
    2. pure_callback: For pure functions (no side-effects) that return values. Requires explicit type signatures and manual VJP rules for differentiation.
    3. accelerate: For jax.jit compatible functions. Does not require upfront type signatures and supports automatic autodifferentiation and execution on accelerators (GPUs/TPUs).

    Additionally, catalyst.debug.print is available as a convenient wrapper around the Python print function for runtime printing support.

  7. Use Ahead-of-time (AOT) compilation with @qjit

    main

    Ahead-of-time (AOT) compilation allows you to trigger compilation at the function definition stage without providing concrete values. This is achieved by specifying the argument signature in the function definition using Python scalar types or JAX's ShapedArray. This results in faster initial execution because the function is already compiled when first called.

    from jax.core import ShapedArray
    
    @qjit  # compilation happens at definition
    @qp.qnode(qp.device("lightning.qubit", wires=2))
    def circuit(x: complex, z: ShapedArray(shape=(3,), dtype=jnp.float64)):
        theta = jnp.abs(x)
        qp.RY(theta, wires=0)
        qp.Rot(z[0], z[1], z[2], wires=0)
        return qp.state()
    
    >>> circuit(0.2j, jnp.array([0.3, 0.6, 0.9]))  # calls precompiled function
    Array([0.75634905-0.58210002j, 0. +0.j, 0.35962678+0.14074839j, 0. +0.j], dtype=complex128)
  8. Understand measurement behavior differences in Catalyst

    main

    The behavior of catalyst.measure differs from pennylane.measure in how final measurement statistics are handled:

    • pennylane.measure: Averages over all potential measurements, weighted by their likelihood.
    • catalyst.measure: Post-selects on the specific outcome that was measured. This post-selected measurement changes with every execution.
  9. The structure of a Catalyst dialect

    main

    Catalyst follows the MLIR design principle of avoiding boilerplate by using a two-part specification for dialects and their components:

    1. Declarative Part (TableGen): Uses the Operation Definition Specification (ODS) language in .td files. These files serve as the single source of truth and automatically generate C++ header and implementation files via the build system.

      • Location: catalyst/mlir/include/<DialectName>/IR
    2. C++ Part: Contains hand-written logic, transformations, storage classes, or interface method implementations that cannot be expressed in ODS.

      • Location: catalyst/mlir/lib/<DialectName>/IR

    While ODS is strongly recommended for defining the structure, you can always achieve full control by working directly with the underlying C++ classes.

  10. How the Catalyst compilation pipeline works

    main

    The Catalyst frontend and compiler driver execute compilation in three successive stages:

    1. Program Capture / IR Generation: The frontend captures PennyLane/JAX programs using a tracing and op-queueing mechanism. It extends the JAX program representation (JAXPR) with quantum primitives. These primitives are then lowered from JAXPR to MLIR.
    2. Program Transformation: The Catalyst compiler libraries operate on the MLIR-based representation. The driver invokes a sequence of transformations that lower the program through various levels of abstraction, eventually outputting LLVM IR with QIR syntax.
    3. Code Generation: The LLVM IR is compiled into native object code for the local architecture using llc. A native linker then links the program to the Catalyst Runtime library. Finally, the frontend loads this library into the Python environment and attaches the entry point to the user's @qjit decorated object.
  11. Manage dynamic arraylists with Catalyst list operations

    main

    Catalyst provides a set of operations to manage dynamically resizable arraylists:

    • catalyst.list_init: Initializes a new dynamically resizable arraylist.
    • catalyst.list_push: Appends a value to the end of a list.
    • catalyst.list_pop: Removes and returns the last element from a list.
    • catalyst.list_load_data: Retrieves the underlying memref storing the data of the list.
    • catalyst.list_dealloc: Deallocates the underlying memory of an arraylist.
    // Initialize
    %list = catalyst.list_init : type($list)
    
    // Push value
    // catalyst.list_push $value, $list : type($list)
    
    // Pop value
    // %result, %list = catalyst.list_pop $list : type($list) -> type($result)
    
    // Load data as memref
    // %data = catalyst.list_load_data $list : type($list) -> type($data)
    
    // Deallocate
    // catalyst.list_dealloc $list : type($list)
  12. Compatibility with PennyLane transforms

    main

    Most PennyLane transforms are compatible with Catalyst, provided the transformation does not depend on the numeric value of dynamic variables.

    Transform Application Modes

    When composing transforms with @qjit, they are applied in one of two ways:

    1. Tape Transforms: Applied to the PennyLane tape before MLIR compilation. This occurs if the transform lacks a Catalyst MLIR implementation, or if it is preceded by any transform that lacks an MLIR implementation.
    2. MLIR Passes: Applied directly at the MLIR level. These are more efficient but may have different keyword argument support than their tape-based counterparts.

    Warning: Swapping the order of transforms can change whether a transform is applied as a tape transform or an MLIR pass. If an MLIR pass is triggered, certain PennyLane-specific keyword arguments (like recursive=True in cancel_inverses) might not be supported, leading to a CompileError.

    import pennylane as qp
    
    dev = qp.device("lightning.qubit", wires=1)
    
    @qp.qjit
    @qp.transforms.merge_rotations
    @qp.qnode(dev)
    def circuit(x):
        qp.RX(x, wires=0)
        qp.RX(x ** 2, wires=0)
        return qp.expval(qp.PauliZ(0))