Skip to content
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

[Oxidize BasisTranslator]: Move basis_search and BasisSearchVisitor to rust. #12811

Merged
merged 12 commits into from
Oct 7, 2024

Conversation

raynelfss
Copy link
Contributor

@raynelfss raynelfss commented Jul 24, 2024

Summary

Builds on #12585
The following commits add a rust-side version of basis_search that is also exposed to BasisTranslator in Python. Due to structural restrictions in rustworkx-core's version of dijkstra_search, these commits also consolidate the basis_search method and the BasisSearchVisitor into one method.

Details and comments

The BasisTranslator aims to map a circuit's gates into a specified target basis by performing a dijkstra search in a provided EquivalenceLibrary. Because of #12585 moving said structure mainly to rust, these commits aim to add the basis_search method of this transpiler pass to operate in rust space as well, which could save the overhead of converting the rust EquivalenceLibrary's petgraph::StableDiGraph to a PyDiGraph.

It is worth to note that the following changes have been made:

  • BasisSearchVisitor and _basis_search have been consolidated into one method basis_search, due to the visitor's data living in a different space, which would imply moving some of the rustworkx-core's dijkstra_search retrieved graph data moving outside of its expected lifetimes.
  • basis_seach is an entirely rust-based method. However, it is exposed temporarily to Python via py_basis_search to avoid cloning of source_basis and target_basis sets. This exposure however will be removed once Port BasisTranslator to Rust #12246 is fully realized.

Known issues:

When running the test test.python.transpiler.test_basis_translator.TestBasisExamples.test_cx_bell_to_ecr there's a posibility that the resulting circuit will randomly be:

        ┌────────────┐   ┌─────────────┐┌──────┐┌──────────┐
q_0: ───┤ U(π/2,0,π) ├───┤ U(0,0,-π/2) ├┤0     ├┤ U(π,0,π) ├
     ┌──┴────────────┴──┐└─────────────┘│  Ecr │└──────────┘
q_1: ┤ U(-π/2,-π/2,π/2) ├───────────────┤1     ├────────────
     └──────────────────┘               └──────┘            

or:

        ┌────────────┐  ┌─────────────┐┌──────────┐┌──────┐
q_0: ───┤ U(π/2,0,π) ├──┤ U(0,0,-π/2) ├┤ U(π,0,0) ├┤0     ├
     ┌──┴────────────┴─┐└─────────────┘└──────────┘│  Ecr │
q_1: ┤ U(π/2,-π/2,π/2) ├───────────────────────────┤1     ├
     └─────────────────┘                           └──────┘

This is suspectedly due to rustworkx-core not being able to decide between these two equivalent alternatives. Fixed by e7b3cb0

Blockers:

This branch will be rebased once each blocker merges. Feel free to leave any comments or review. Thank you 🚀

@raynelfss raynelfss added on hold Can not fix yet performance Rust This PR or issue is related to Rust code in the repository mod: transpiler Issues and PRs related to Transpiler labels Jul 24, 2024
@raynelfss raynelfss added this to the 1.3 beta milestone Jul 24, 2024
@raynelfss raynelfss requested a review from a team as a code owner July 24, 2024 15:19
@qiskit-bot
Copy link
Collaborator

One or more of the following people are relevant to this code:

  • @Qiskit/terra-core
  • @kevinhartman
  • @mtreinish

@raynelfss
Copy link
Contributor Author

Although the performance gains are very niche, here are some benchmarks:

All benchmarks:

| Change   | Before [ffead590] <move-target~1^2>   | After [b6d36cf1] <move-basis-search-and-visit>   | Ratio   | Benchmark (Parameter)                                                                                     |
|----------|---------------------------------------|--------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------|
|          | 8.20±0s                               | 6.84±0.1s                                        | ~0.83   | randomized_benchmarking.RandomizedBenchmarkingBenchmark.time_ibmq_backend_transpile_single_thread([0, 1]) |
|          | 8.19±0s                               | 6.70±0.02s                                       | ~0.82   | randomized_benchmarking.RandomizedBenchmarkingBenchmark.time_ibmq_backend_transpile([0, 1])               |
|          | 111±0.3ms                             | 113±0.4ms                                        | 1.02    | quantum_info.RandomCliffordBench.time_random_clifford('4,1500')                                           |
|          | 126±0.8ms                             | 126±0.3ms                                        | 1.00    | quantum_info.RandomCliffordBench.time_random_clifford('3,2000')                                           |
|          | 104±0.2ms                             | 104±0.2ms                                        | 1.00    | quantum_info.RandomCliffordBench.time_random_clifford('5,1000')                                           |
|          | 72.8±0.4ms                            | 72.9±0.03ms                                      | 1.00    | quantum_info.RandomCnotDihedralBench.time_random_cnotdihedral('1,2000')                                   |
|          | 46.1±0.06ms                           | 46.2±0.2ms                                       | 1.00    | quantum_info.RandomCnotDihedralBench.time_random_cnotdihedral('3,1200')                                   |
|          | 39.5±0.07ms                           | 39.3±0.04ms                                      | 1.00    | quantum_info.RandomCnotDihedralBench.time_random_cnotdihedral('4,1000')                                   |
|          | 32.3±0.04ms                           | 32.3±0.2ms                                       | 1.00    | quantum_info.RandomCnotDihedralBench.time_random_cnotdihedral('5,800')                                    |
|          | 29.5±0.07ms                           | 29.3±0.4ms                                       | 1.00    | quantum_info.RandomCnotDihedralBench.time_random_cnotdihedral('6,700')                                    |
|          | 17.9±0.4ms                            | 18.0±0.2ms                                       | 1.00    | random_circuit_hex.BenchRandomCircuitHex.time_ibmq_backend_transpile(10)                                  |
|          | 41                                    | 41                                               | 1.00    | random_circuit_hex.BenchRandomCircuitHex.track_depth_ibmq_backend_transpile(10)                           |
|          | 49                                    | 49                                               | 1.00    | random_circuit_hex.BenchRandomCircuitHex.track_depth_ibmq_backend_transpile(12)                           |
|          | 169                                   | 169                                              | 1.00    | random_circuit_hex.BenchRandomCircuitHex.track_depth_ibmq_backend_transpile(14)                           |
|          | 17                                    | 17                                               | 1.00    | random_circuit_hex.BenchRandomCircuitHex.track_depth_ibmq_backend_transpile(4)                            |
|          | 25                                    | 25                                               | 1.00    | random_circuit_hex.BenchRandomCircuitHex.track_depth_ibmq_backend_transpile(6)                            |
|          | 33                                    | 33                                               | 1.00    | random_circuit_hex.BenchRandomCircuitHex.track_depth_ibmq_backend_transpile(8)                            |
|          | 144±0.3ms                             | 143±0.7ms                                        | 0.99    | quantum_info.RandomCliffordBench.time_random_clifford('1,3000')                                           |
|          | 133±1ms                               | 132±0.5ms                                        | 0.99    | quantum_info.RandomCliffordBench.time_random_clifford('2,2500')                                           |
|          | 80.5±0.5ms                            | 79.8±0.7ms                                       | 0.99    | quantum_info.RandomCliffordBench.time_random_clifford('6,700')                                            |
|          | 56.6±0.2ms                            | 56.2±0.07ms                                      | 0.99    | quantum_info.RandomCnotDihedralBench.time_random_cnotdihedral('2,1500')                                   |
|          | 24.4±0.2ms                            | 24.2±0.3ms                                       | 0.99    | random_circuit_hex.BenchRandomCircuitHex.time_ibmq_backend_transpile(12)                                  |
|          | 37.8±0.4ms                            | 37.3±0.2ms                                       | 0.99    | random_circuit_hex.BenchRandomCircuitHex.time_ibmq_backend_transpile(14)                                  |
|          | 8.72±0.1ms                            | 8.55±0.2ms                                       | 0.98    | random_circuit_hex.BenchRandomCircuitHex.time_ibmq_backend_transpile(6)                                   |
|          | 13.2±0.2ms                            | 12.9±0.07ms                                      | 0.98    | random_circuit_hex.BenchRandomCircuitHex.time_ibmq_backend_transpile(8)                                   |
|          | 5.93±0.1ms                            | 5.63±0.1ms                                       | 0.95    | random_circuit_hex.BenchRandomCircuitHex.time_ibmq_backend_transpile(4)                                   |
| -        | 4.20±0s                               | 3.25±0s                                          | 0.77    | randomized_benchmarking.RandomizedBenchmarkingBenchmark.time_ibmq_backend_transpile([0])                  |
| -        | 4.21±0s                               | 3.24±0s                                          | 0.77    | randomized_benchmarking.RandomizedBenchmarkingBenchmark.time_ibmq_backend_transpile_single_thread([0])    |

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
PERFORMANCE INCREASED.

These changes seem to perform best without multithreading as serializing objects from rust to python is a bit complex at times.

@raynelfss raynelfss force-pushed the move-basis-search-and-visit branch from 78b5f1a to e7af19d Compare July 24, 2024 18:54
@coveralls
Copy link

coveralls commented Jul 24, 2024

Pull Request Test Coverage Report for Build 11224488724

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 162 of 163 (99.39%) changed or added relevant lines in 5 files are covered.
  • 882 unchanged lines in 27 files lost coverage.
  • Overall coverage decreased (-0.01%) to 88.872%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/accelerate/src/basis/basis_translator/basis_search.rs 153 154 99.35%
Files with Coverage Reduction New Missed Lines %
qiskit/transpiler/passes/optimization/elide_permutations.py 1 96.0%
qiskit/circuit/library/quantum_volume.py 1 97.44%
crates/qasm2/src/expr.rs 1 94.02%
crates/accelerate/src/split_2q_unitaries.rs 1 96.0%
qiskit/transpiler/passes/optimization/collect_cliffords.py 2 92.59%
qiskit/circuit/library/standard_gates/u3.py 2 97.03%
crates/accelerate/src/equivalence.rs 3 79.71%
qiskit/circuit/library/blueprintcircuit.py 4 96.46%
qiskit/circuit/library/standard_gates/u1.py 4 94.9%
qiskit/circuit/library/data_preparation/pauli_feature_map.py 6 91.8%
Totals Coverage Status
Change from base Build 11076723215: -0.01%
Covered Lines: 74600
Relevant Lines: 83941

💛 - Coveralls

@raynelfss raynelfss force-pushed the move-basis-search-and-visit branch 4 times, most recently from bf84dc0 to 2091413 Compare July 25, 2024 19:14
@raynelfss raynelfss force-pushed the move-basis-search-and-visit branch 2 times, most recently from e03ef64 to 6972a74 Compare August 2, 2024 00:04
@raynelfss raynelfss force-pushed the move-basis-search-and-visit branch 2 times, most recently from 0b25327 to d3309a0 Compare August 6, 2024 21:17
@raynelfss raynelfss force-pushed the move-basis-search-and-visit branch 3 times, most recently from 9674018 to 6e2f011 Compare September 5, 2024 13:33
@mtreinish mtreinish modified the milestones: 1.3 beta, 1.3.0 Sep 5, 2024
raynelfss and others added 4 commits September 26, 2024 09:29
- Add rust counterpart for `basis_search`.
- Consolidated the `BasisSearchVisitor` into the function due to differences in rust behavior.
raynelfss and others added 4 commits September 26, 2024 09:29
- Due to the nature of `hashbrown` we must use owned Strings instead of `&str`.
- Remove import of `random` in `basis_translator`.
@raynelfss raynelfss removed the on hold Can not fix yet label Sep 26, 2024
@raynelfss raynelfss added Changelog: None Do not include in changelog and removed mod: transpiler Issues and PRs related to Transpiler labels Sep 26, 2024
raynelfss added a commit to raynelfss/qiskit that referenced this pull request Sep 27, 2024
Fixes Port `BasisTranslator` to Rust Qiskit#12246

This is the final act of the efforts to move the `BasisTranslator` transpiler pass into Rust. With many of the parts of this pass already living in Rust, the following commits attempt to bring in the final changes to allow complete operation of this pass in the Rust space (with of course some interaction with Python.)

Methodology:
The way this works is by keeping the original `BasisTranslator` python class, and have it store the rust-space counterpart (now called `CoreBasisTranslator`) which will perform all of the operations leveraging the existent Rust API's available for the `Target` (Qiskit#12292), `EquivalenceLibrary`(Qiskit#12585) and the `BasisTranslator` methods `basis_search` (Qiskit#12811) and `compose_transforms`(Qiskit#13137).

All of the inner methods will have private visibility and will not be accessible to `Python` as they're intended to be internal by design.

By removing the extra layers of conversion we should be seeing a considerable speed-up, alongside all of the other incremental improvements we have made.

Changes:

- Add the pyo3 class/struct `BasisTranslator` that will contain allof the main data used by the transpiler pass to perform its operation.
- Convert the `target_basis` into a set manually from python before sending it into the Rust space.
- Remove the exposure of `basis_search` and `compose_transforms` to python.
- Change `basis_search` so that it accepts references to `HashSet` instances instead of accepting a `HashSet<&str>` instance.
- Change inner method's visibility for `basis_search` and `compose_transform` modules in rust.
- Expose the exception imports from `Target` to the `accelerate` crate.
- Expose `DAGCircuit::copy_empty_like` to the rest of the crates.
- Remove all of the unused imports in the Python-side `BasisTranslator`.

Blockers:
- [ ] Qiskit#12811
Copy link
Member

@mtreinish mtreinish left a comment

Choose a reason for hiding this comment

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

Overall this LGTM, thanks for doing this. I left a few minor comments inline but overall this is a pretty straightforward porting of the Python search algorithm into Rust. I'm just glad we had that rust search interface in rustworkx-core as it makes this quite simply to move into rust.

crates/accelerate/src/equivalence.rs Outdated Show resolved Hide resolved
source: &mut HashMap<usize, usize>,
) {
let mut save_index = usize::MAX;
for edge_data in graph.edge_weights().flatten() {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe leave a comment here that flatten() is for iterating over defined edges and skipping any set with None. It took me a second to figure out what this was doing, it's a clever workaround though (Option<T> implementing Iterator is always confusing to me at first).

- Rename `EquivalenceLibrary`'s `mut_graph` method to `graph_mut` to keep consistent with rust naming conventions.
- Use `&HashSet<String>` instead of `HashSet<&str>` to avoid extra conversion.
- Use `u32::MAX` as num_qubits for dummy node.
- Use for loop instead of foreachj to add edges to dummy node.
- Add comment explaining usage of flatten in `initialize_num_gates_remain_for_rule`.
- Remove stale comments.
mtreinish
mtreinish previously approved these changes Oct 7, 2024
Copy link
Member

@mtreinish mtreinish left a comment

Choose a reason for hiding this comment

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

I missed this in my earlier review but the directory structure here is a bit weird relative to the rest of the modules in the crate. The double nesting of basis/basis_translator seems like 1 too many. However, this directory structure is already in use from the compose_transforms() PR. We can clean this up in a follow up PR. Realistically we're going to want to reorganize things in the near future to have a dedicated transpiler pass module or crate.

That all being said I left one nit inline. I'll apply the suggestion and reapprove then enqueue for merge.

@mtreinish mtreinish added this pull request to the merge queue Oct 7, 2024
Merged via the queue into Qiskit:main with commit 4e573f3 Oct 7, 2024
15 checks passed
raynelfss added a commit to raynelfss/qiskit that referenced this pull request Oct 8, 2024
Fixes Port `BasisTranslator` to Rust Qiskit#12246

This is the final act of the efforts to move the `BasisTranslator` transpiler pass into Rust. With many of the parts of this pass already living in Rust, the following commits attempt to bring in the final changes to allow complete operation of this pass in the Rust space (with of course some interaction with Python.)

Methodology:
The way this works is by keeping the original `BasisTranslator` python class, and have it store the rust-space counterpart (now called `CoreBasisTranslator`) which will perform all of the operations leveraging the existent Rust API's available for the `Target` (Qiskit#12292), `EquivalenceLibrary`(Qiskit#12585) and the `BasisTranslator` methods `basis_search` (Qiskit#12811) and `compose_transforms`(Qiskit#13137).

All of the inner methods will have private visibility and will not be accessible to `Python` as they're intended to be internal by design.

By removing the extra layers of conversion we should be seeing a considerable speed-up, alongside all of the other incremental improvements we have made.

Changes:

- Add the pyo3 class/struct `BasisTranslator` that will contain allof the main data used by the transpiler pass to perform its operation.
- Convert the `target_basis` into a set manually from python before sending it into the Rust space.
- Remove the exposure of `basis_search` and `compose_transforms` to python.
- Change `basis_search` so that it accepts references to `HashSet` instances instead of accepting a `HashSet<&str>` instance.
- Change inner method's visibility for `basis_search` and `compose_transform` modules in rust.
- Expose the exception imports from `Target` to the `accelerate` crate.
- Expose `DAGCircuit::copy_empty_like` to the rest of the crates.
- Remove all of the unused imports in the Python-side `BasisTranslator`.

Blockers:
- [ ] Qiskit#12811
github-merge-queue bot pushed a commit that referenced this pull request Oct 31, 2024
…Rust. (#13237)

* Initial: Move the rest of the `BasisTranslator` to Rust.

Fixes Port `BasisTranslator` to Rust #12246

This is the final act of the efforts to move the `BasisTranslator` transpiler pass into Rust. With many of the parts of this pass already living in Rust, the following commits attempt to bring in the final changes to allow complete operation of this pass in the Rust space (with of course some interaction with Python.)

Methodology:
The way this works is by keeping the original `BasisTranslator` python class, and have it store the rust-space counterpart (now called `CoreBasisTranslator`) which will perform all of the operations leveraging the existent Rust API's available for the `Target` (#12292), `EquivalenceLibrary`(#12585) and the `BasisTranslator` methods `basis_search` (#12811) and `compose_transforms`(#13137).

All of the inner methods will have private visibility and will not be accessible to `Python` as they're intended to be internal by design.

By removing the extra layers of conversion we should be seeing a considerable speed-up, alongside all of the other incremental improvements we have made.

Changes:

- Add the pyo3 class/struct `BasisTranslator` that will contain allof the main data used by the transpiler pass to perform its operation.
- Convert the `target_basis` into a set manually from python before sending it into the Rust space.
- Remove the exposure of `basis_search` and `compose_transforms` to python.
- Change `basis_search` so that it accepts references to `HashSet` instances instead of accepting a `HashSet<&str>` instance.
- Change inner method's visibility for `basis_search` and `compose_transform` modules in rust.
- Expose the exception imports from `Target` to the `accelerate` crate.
- Expose `DAGCircuit::copy_empty_like` to the rest of the crates.
- Remove all of the unused imports in the Python-side `BasisTranslator`.

Blockers:
- [ ] #12811

* Fix: Redundancies with serialization methods
- Remove extra copies of `target`, `target_basis`, `equiv_lib`, and `min_qubits`.
- Remove unnecessary mutability in `apply_transforms` and `replace_node`.

* Refactor: Remove `BasisTranslator` struct, use pymethod.
- Using this method avoids the creation of a datastructure in rust and the overhead of deserializing rust structures which can be overly slow due to multiple cloning. With this update, since the `BasisTranslator` never mutates, it is better to not store anything in Rust.

* Lint: Ignore too_many_args flag from clippy on `basis_translator::run()`

* Fix: Remove redundant clone

* Fix: Leverage using `unwrap_operation` when taking op_nodes from the dag.
- Add function signature in `base_run`.

* Update crates/accelerate/src/basis/basis_translator/mod.rs

Co-authored-by: Elena Peña Tapia <[email protected]>

* Adapt to #13164
- Use `QuantumCircuit._has_calibration_for()` when trying to obtain calibrations from a `QuantumCircuit` due to the deprecation of the `Pulse` package.

---------

Co-authored-by: Elena Peña Tapia <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: None Do not include in changelog 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.

4 participants