Skip to content

MLIR Plugin #881

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 210 commits into
base: main
Choose a base branch
from
Draft

MLIR Plugin #881

wants to merge 210 commits into from

Conversation

flowerthrower
Copy link
Collaborator

Description

This PR introduces a MLIR plguin such that the round-trip pass can be executed with Pennylane Catalyst.
It additionally provides the plugin as a wheel.

Checklist:

  • The pull request only contains commits that are related to it.
  • I have added appropriate tests and documentation.
  • I have made sure that all CI jobs on GitHub pass.
  • The pull request introduces no new warnings and follows the project's style guidelines.

Copy link

codecov bot commented Mar 20, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@flowerthrower flowerthrower added MLIR Anything related to MLIR feature New feature or request labels Mar 20, 2025
@burgholzer
Copy link
Member

Now that #879 is merged, this needs to be rebased

@burgholzer
Copy link
Member

Alright. I considerably cleaned up the implementation today.
This is getting closer to becoming mergable.
I'll leave some comments inline, where I think that changes are still due.

The biggest thing missing is a packaging setup that would allow us to release packages to PyPI. I'll try to get this done asap.

Copy link
Member

@burgholzer burgholzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@flowerthrower @BertiFlorea Tagging you here explicitly so you hopefully see the comments. 😌

Comment on lines +20 to +120
# name: 🧩 Test MLIR Catalyst Plugin with LLVM@${{ matrix.llvm-version }} on ${{ matrix.runs-on }}
# runs-on: ${{ matrix.runs-on }}
# strategy:
# matrix:
# runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-14]
# llvm-version: [19, 20]
# fail-fast: false
# env:
# CMAKE_BUILD_PARALLEL_LEVEL: 4
# CTEST_PARALLEL_LEVEL: 4
# FORCE_COLOR: 3
# steps:
# # Check out the repository
# - uses: actions/checkout@v4
# with:
# fetch-depth: 0
#
# # install a specific version of the LLVM toolchain
# - name: Install llvm and mlir
# if: runner.os == 'Linux'
# run: |
# sudo apt-get update
# wget https://apt.llvm.org/llvm.sh -O ${{ runner.temp }}/llvm_install.sh
# chmod +x ${{ runner.temp }}/llvm_install.sh
# if sudo ${{ runner.temp }}/llvm_install.sh ${{ matrix.llvm-version }}; then
# sudo apt-get install -y libmlir-${{ matrix.llvm-version }}-dev \
# mlir-${{ matrix.llvm-version }}-tools \
# clang-${{ matrix.llvm-version}} \
# clang-tools-${{ matrix.llvm-version }} \
# || exit 1
# else
# echo "Installation from script failed."
# exit 1
# fi
# echo "CC=gcc-14" >> $GITHUB_ENV
# echo "CXX=g++-14" >> $GITHUB_ENV
# echo "MLIR_DIR=/usr/lib/llvm-${{ matrix.llvm-version }}/lib/cmake/mlir" >> $GITHUB_ENV
# echo "LLVM_DIR=/usr/lib/llvm-${{ matrix.llvm-version }}/lib/cmake/llvm" >> $GITHUB_ENV
#
# # Install llvm and ninja
# - name: Install llvm@${{ matrix.llvm-version }} and Ninja via Homebrew
# if: runner.os == 'macOS'
# run: brew install llvm@${{ matrix.llvm-version }} ninja
#
# # Set compiler and environment variables
# - name: Set compiler and CMake environment variables
# if: runner.os == 'macOS'
# run: |
# if [ "$(uname -m)" = "x86_64" ]; then
# LLVM_PREFIX="/usr/local/opt/llvm@${{ matrix.llvm-version }}"
# else
# LLVM_PREFIX="/opt/homebrew/opt/llvm@${{ matrix.llvm-version }}"
# fi
#
# echo "CC=$LLVM_PREFIX/bin/clang" >> $GITHUB_ENV
# echo "CXX=$LLVM_PREFIX/bin/clang++" >> $GITHUB_ENV
# echo "LLVM_DIR=$LLVM_PREFIX/lib/cmake/llvm" >> $GITHUB_ENV
# echo "MLIR_DIR=$LLVM_PREFIX/lib/cmake/mlir" >> $GITHUB_ENV
#
# # set up ccache for faster C++ builds
# - name: Setup ccache
# uses: Chocobo1/setup-ccache-action@v1
# with:
# prepend_symlinks_to_path: false
# override_cache_key: mlir-plugin-${{ matrix.runs-on }}-llvm-${{ matrix.llvm-version }}
#
# # Set up uv for faster Python package management
# - name: Install the latest version of uv
# uses: astral-sh/setup-uv@v6
# with:
# python-version: 3.13
# activate-environment: true
#
# # Build the plugin package
# - name: Build mqt-core-catalyst-plugin
# working-directory: plugins/catalyst
# run: uv sync --verbose --active
#
# # Print shared library dependencies for the plugin
# - name: Print shared library dependencies for mqt-core-catalyst-plugin
# run: |
# if [ "$(uname)" = "Linux" ]; then
# ldd .venv/lib/python3.13/site-packages/mqt/core/plugins/catalyst/mqt-core-catalyst-plugin.so || true
# elif [ "$(uname)" = "Darwin" ]; then
# otool -l .venv/lib/python3.13/site-packages/mqt/core/plugins/catalyst/mqt-core-catalyst-plugin.dylib || true
# fi
#
# # Print shared library dependencies for Catalyst
# - name: Print shared library dependencies for Catalyst
# run: |
# if [ "$(uname)" = "Linux" ]; then
# ldd $(which catalyst) || true
# elif [ "$(uname)" = "Darwin" ]; then
# otool -l $(which catalyst) || true
# fi
#
# # Run the plugin tests
# - name: Run pytest for mqt-core-catalyst-plugin
# working-directory: plugins/catalyst
# run: python -m pytest -s --verbose
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd still like to investigate at some point whether there is anything we can do to make this work with pre-compiled LLVM versions. But this will have to wait for a future PR. It would be great to create a tracking issue for this though.

Comment on lines +21 to +66
# MQT Core Catalyst MLIR Plugin

This sub-package of MQT Core provides a [Catalyst](https://github.com/PennyLaneAI/catalyst) plugin for the [MLIR](https://mlir.llvm.org/) framework.
It allows you to use MQT Core's MLIR dialects and transformations within the Catalyst framework, enabling advanced quantum circuit optimizations and transformations.

TODO: extend this section with more details about the Catalyst plugin, its features, and how to use it.

If you have any questions, feel free to create a [discussion](https://github.com/munich-quantum-toolkit/core/discussions) or an [issue](https://github.com/munich-quantum-toolkit/core/issues) on [GitHub](https://github.com/munich-quantum-toolkit/core).

## Contributors and Supporters

The _[Munich Quantum Toolkit (MQT)](https://mqt.readthedocs.io)_ is developed by the [Chair for Design Automation](https://www.cda.cit.tum.de/) at the [Technical University of Munich](https://www.tum.de/) and supported by the [Munich Quantum Software Company (MQSC)](https://munichquantum.software).
Among others, it is part of the [Munich Quantum Software Stack (MQSS)](https://www.munich-quantum-valley.de/research/research-areas/mqss) ecosystem, which is being developed as part of the [Munich Quantum Valley (MQV)](https://www.munich-quantum-valley.de) initiative.

<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/munich-quantum-toolkit/.github/refs/heads/main/docs/_static/mqt-logo-banner-dark.svg" width="90%">
<img src="https://raw.githubusercontent.com/munich-quantum-toolkit/.github/refs/heads/main/docs/_static/mqt-logo-banner-light.svg" width="90%" alt="MQT Partner Logos">
</picture>
</p>

Thank you to all the contributors who have helped make MQT Core a reality!

<p align="center">
<a href="https://github.com/munich-quantum-toolkit/core/graphs/contributors">
<img src="https://contrib.rocks/image?repo=munich-quantum-toolkit/core" />
</a>
</p>

## Getting Started

TODO

## System Requirements

TODO

## Cite This

If you want to cite MQT Core, please use the following BibTeX entry:

```bibtex
TODO
```

---
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still pretty much to be done

Comment on lines +36 to +47
mlirGetDialectPluginInfo() {
return {MLIR_PLUGIN_API_VERSION, "MQTOpt", LLVM_VERSION_STRING,
[](DialectRegistry* registry) {
registry->insert<::mqt::ir::opt::MQTOptDialect>();
}};
}

/// The pass plugin registration mechanism.
/// Necessary symbol to register the pass plugin.
extern "C" LLVM_ATTRIBUTE_WEAK PassPluginLibraryInfo mlirGetPassPluginInfo() {
return {MLIR_PLUGIN_API_VERSION, "MQTOptPasses", LLVM_VERSION_STRING, []() {
mqt::ir::opt::registerMQTOptPasses();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, we will most likely want to expose all of our dialects and transformations/conversions here. Could be worth tracking that in an issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These files are here, but they are not at all executed.
I'd very much argue that we need more explicit testing for the plugin.
Either in the form of C++ tests running these FileCheck files or via more extensive Python-side tests. I am fairly confident that some of the conversions are not yet correct.
It might be easier to write FileCheck tests than Python tests. Although I am not 100% sure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The amount of testing of the plugin is clearly still lacking quite a bit.
This has become quite obvious because it didn't even catch a bug that would flip the controls and targets of any controlled operation that got translated leading to incorrect programs.
So this definitely needs more tests that actually test more than the simple error-free execution of the passes.

* Licensed under the MIT License
*/

#include "mlir/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need to do something about clang-tidy not working correctly for the plugin.
The main reason for that is that the plugin build is completely loose from the main mqt-core build. Hence, there is no compilation database, and clang-tidy has problems finding the headers in the build directory.

As a first step, we should probably figure out how to best ignore the plugins directory from the clang-tidy run. However, mid-term, I would really want to have a clang-tidy check running properly over the code here. CLion locally revealed dozens of warnings that were fixed in the last couple of commits.

Comment on lines +501 to +520
// RZX(θ) = H(q2) CNOT(q1,q2) RZ(θ)(q2) CNOT(q1,q2) H(q2)
auto h1 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{}, inQubitsValues,
"H", false, inCtrlQubits, ValueRange{});

auto cnot1 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{}, h1.getResults(),
"CNOT", false, inCtrlQubits, ValueRange{});

auto rz = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{theta},
cnot1.getResults(), "RZ", false, inCtrlQubits, ValueRange{});

auto cnot2 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{}, rz.getResults(),
"CNOT", false, inCtrlQubits, ValueRange{});

auto h2 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{},
cnot2.getResults(), "H", false, inCtrlQubits, ValueRange{});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (as well as some of the constructions below) cannot be correct yet as the number of qubits in the various operations is not correct.
A Hadamard only acts on one qubit, so it cannot take both input qubits as an argument. Similarly, the CNOT can not work just on the qubit resulting from the CNOT.
Proper testing would have revealed that.

In this case, the code can even be simplified: the sequence of CNOT, Rz, CNOT forms an RZZ gate. May as well use that. And the controls only need to be applied to that RZZ gate; both H gates can remain uncontrolled because they cancel whenever the controls of the RZZ gate are not activated.

Comment on lines +552 to +595
// XXminusYY(θ,β) = RX(π/2)(q1) RY(π/2)(q2) CNOT(q1,q2) RZ(θ)(q2)
// CNOT(q1,q2) RZ(β)(q1) RZ(β)(q2) RX(-π/2)(q1) RY(-π/2)(q2)
auto pi2 = rewriter.create<ConstantOp>(op.getLoc(),
rewriter.getF64FloatAttr(M_PI_2));
auto negPi2 = rewriter.create<ConstantOp>(
op.getLoc(), rewriter.getF64FloatAttr(-M_PI_2));

auto rx1 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{pi2},
inQubitsValues, "RX", false, inCtrlQubits, ValueRange{});

auto ry1 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{pi2},
rx1.getResults(), "RY", false, inCtrlQubits, ValueRange{});

auto cnot1 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{}, ry1.getResults(),
"CNOT", false, inCtrlQubits, ValueRange{});

auto rz1 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{theta},
cnot1.getResults(), "RZ", false, inCtrlQubits, ValueRange{});

auto cnot2 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{}, rz1.getResults(),
"CNOT", false, inCtrlQubits, ValueRange{});

auto rz2 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{beta},
cnot2.getResults(), "RZ", false, inCtrlQubits, ValueRange{});

auto rz3 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{beta},
rz2.getResults(), "RZ", false, inCtrlQubits, ValueRange{});

auto rx2 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{negPi2},
rz3.getResults(), "RX", false, inCtrlQubits, ValueRange{});

auto ry2 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{negPi2},
rx2.getResults(), "RY", false, inCtrlQubits, ValueRange{});

rewriter.replaceOp(op, ry2.getResults());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This construction also does not work.

Comment on lines +838 to +845
// Peres = Toffoli(q2,q1,q0) CNOT(q2,q1)
auto ccnot1 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{}, inQubitsValues,
"Toffoli", false, inCtrlQubits, ValueRange{});

auto cnot2 = rewriter.create<catalyst::quantum::CustomOp>(
op.getLoc(), outQubitTypes, TypeRange{}, ValueRange{},
ccnot1.getResults(), "CNOT", false, inCtrlQubits, ValueRange{});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct. The Peres Gate in mqt-core is a two-target gate that decomposes down to a CNOT and an X gate. It does not use 3 qubits. Same holds for the inverse.

Comment on lines +890 to +895
// -- GPhaseOp (gphase)
template <>
StringRef ConvertMQTOptSimpleGate<opt::GPhaseOp>::getGateName(
[[maybe_unused]] std::size_t numControls) {
return "gphase";
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not 100% sure this is correct. If I remember correctly, Catalyst has its separate operation for the global phase. I might be mistaken, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request MLIR Anything related to MLIR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants