Skip to content

Port HighLevelSynthesis pass to rust #13813

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

Merged
merged 94 commits into from
Mar 4, 2025

Conversation

alexanderivrii
Copy link
Member

@alexanderivrii alexanderivrii commented Feb 9, 2025

Summary

This PR ports [most of] the HighLevelSynthesis transpiler pass (or HLS, for short) to Rust, addressing #12211.

The test_utility_scale.py script provides several examples where HLS does not need to do anything. Running this script on my laptop, reduces the total HLS runtime from about 25% of the total transpile time to just 0.3%.

This PR is based on #13777 (a small fix for calling the definition methods from within the Rust space) and #13605 (reimplementing HLS in Python). Technically, we can skip merging #13605 and merge PR directly, I don't have a strong preference for what is the better option.

Details and comments

The part of the pass that still remains in Python is the code for synthesizing operations using plugins. As an optimization, we precompute the set of (names of) operations with available plugins and we only call this Python code when an operation is in this set. Note that annotated operations are also handled via a plugin.

The reason why porting the Python code that handles plugins is very tricky is because we allow passing arbitrary python classes in the HLSConfig, something as follows: hls_config = HLSConfig("clifford"=[MyPythonCliffordSynthesisPlugin()]).

In the case that the HighLevelSynthesis pass needs to do something (and in particular call synthesis plugins implemented in python), the runtime improvements are on the scale of 20%--50%.

@alexanderivrii
Copy link
Member Author

Thanks @raynelfss and @eliarbel (and transitively @jakelishman) for the feedback! I have applied all of Ray's and Eli's review suggestions, with the exception of modifying HighLevelSynthesisData.__str()__ (since this functionality is pure development) and using Instruction::blocks (since I believe this is not sufficient for me). I really like the ideas on how to further refactor code in the future.

Copy link
Contributor

@Cryoris Cryoris left a comment

Choose a reason for hiding this comment

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

I'm not quite through yet, but here's a first round of comments 🙂

Aside from the comments this looks like a good start to me. It would be great to set up a Trait for the Rust side of our plugins so that we can avoid going through Python to call them -- which I assume is what you already started on with #13929 😄

Some(unitary) => unitary,
None => return Err(TranspilerError::new_err("Unitary not found")),
};
match unitary.shape() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this something that should be taken care of by unitary synthesis at the moment? Otherwise we're duplicating the logic and run the risk of hardcoding the target basis here, whereas the unitary synthesis can handle more general cases, no?

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree, I would have also liked to use some readily available unitary synthesis code :).

Copy link
Member Author

Choose a reason for hiding this comment

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

As discussed with @Cryoris in private, in the future we want to synthesize unitary gates within HighLevelSynthesis using a plugin mechanism. For now, the current PR basically ports the definition method for unitary gates to Rust, without changing how they are handled.

@alexanderivrii
Copy link
Member Author

alexanderivrii commented Mar 3, 2025

Related to:

Given that there's a lot of Rust-Python back and forth, would it be simpler to write a Python function to call here?

After a discussion with @Cryoris. Long-term it's better to have the code handling control-flow ops in rust (as done right now). There will be opportunities to simplify this code once other python attributes of the quantum circuit are fully ported to rust.

Regarding:

It would be great to set up a Trait for the Rust side of our plugins so that we can avoid going through Python to call them -- which I assume is what you already started on with #13929 😄

Also after a discussion with @Cryoris. The "Trait" idea is really cool, and will allow to call the main synthesis methods for various high-level objects without going to the Python space. However, #13929 is orthogonal to this, it only aims to port more of the synthesis methods to rust.

@alexanderivrii alexanderivrii requested a review from Cryoris March 3, 2025 16:17
# the operation is defined, and the initial state of each global qubit.
tracker = options.get("qubit_tracker", None)
data = options.get("hls_data", None)
input_qubits = options.get("input_qubits", None)
Copy link
Contributor

Choose a reason for hiding this comment

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

I've probably asked this before, but couldn't we use qubits here instead?

Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately, when the HighLevelSynthesis transpiler pass was first introduced (i.e. way before we started to support auxiliary qubits), we made the decision to pass qubits=None to plugins meaning that the pass runs pre-routing. But now that we handle recursion by explicitly reasoning about the qubits in the original circuit (aka the global qubits), we also need to pass the actual qubits as well. Ideally, I would like to have qubits always be the set of global qubits, and then we wouldn't need input_qubits, but this would be a breaking change. We do need to think of a path forward to implement this and other changes to the synthesis plugins API.

Copy link
Contributor

@Cryoris Cryoris left a comment

Choose a reason for hiding this comment

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

Some more comments! 🙂

circuit = circuit.inverse()

elif isinstance(modifier, ControlModifier):
if circuit.num_clbits > 0:
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's revisit this in the future, but wouldn't the logic be exactly the same even with control flow logic? Since the quantum part would only be applied if the control are satisfied, the output should be correct, no?

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, so currently we are not allowing to control-annotate circuits with classical bits, and this PR does not change this behavior. I think you might be correct, and we should indeed revisit this in the future.

eliarbel
eliarbel previously approved these changes Mar 4, 2025
Copy link
Member

@eliarbel eliarbel left a comment

Choose a reason for hiding this comment

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

Changes related to my comments LGTM.

Copy link
Contributor

@Cryoris Cryoris left a comment

Choose a reason for hiding this comment

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

Modulo the failing test this LGTM, thanks for all the effort @alexanderivrii! There are some follow-ups we should not lose track of here, it might be good to have issues for them. The main ones I think are:

  • Improve collaboration of UnitarySynthesis and HighLevelSynthesis -- this needs some discussion to see if we want to combine them or, if not, how we can have HLS respect the unitary synthesis plugin selection
  • Use Rust-versions of HLS plugins from Rust directly (cf. Trait conversation above)
  • Improve the control flow handling

I can approve once the tests are fixed 👍🏻

@Cryoris Cryoris added this pull request to the merge queue Mar 4, 2025
Merged via the queue into Qiskit:main with commit 96ebf2a Mar 4, 2025
20 checks passed
@ElePT ElePT added Changelog: API Change Include in the "Changed" section of the changelog Changelog: New Feature Include in the "Added" section of the changelog labels Mar 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: API Change Include in the "Changed" section of the changelog Changelog: New Feature Include in the "Added" section of the changelog mod: transpiler Issues and PRs related to Transpiler performance Rust This PR or issue is related to Rust code in the repository
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Port HighLevelSynthesis to rust
8 participants