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] Add rust-native compose_transforms() #13137

Merged
merged 21 commits into from
Sep 27, 2024

Conversation

raynelfss
Copy link
Contributor

@raynelfss raynelfss commented Sep 11, 2024

Summary

The following commits add the helper function compose_transforms from the BasisTranslator transpiler pass into rust. This is a small progression towards completing #12246. This function will be temporarily exposed to Python until #12246 is fully realized.

Details and comments

As we continue moving parts of the BasisTranslator into Rust thanks to #12585 and #12811. The next logical step is to bring in those functions that relied heavily on the DAGCircuit and since it now lives in Rust we can now leverage its usage by moving this helper function into Rust.

compose_transforms basically returns a mapping of (gate_name, num_qubits) : (parameters, gate_definition_as_dag) for each gate present in the DAGCircuit that is currently being processed. It also breaks down ControlFlowOp to include its definitions as well.

Using the latest additions by #13036 we can perform most of these conversions from rust using both circuit_to_dag or DAGCirctut::from_circuit_data. This function will be temporarily exposed to Python until #12246 is fully realized.

Blockers

These are not strictly blockers but some nice-to-haves.

raynelfss and others added 5 commits September 10, 2024 14:39
-  Use `PackedOperation.control_flow` instead of `CONTROL_FLOW_OP_NAMES` to check if an instructuion is a control flow operation.
- Remove panic statement when checking for example gates.
- Use qreg when adding an instruction to the mock_dag in `compose_transforms`.
- Add missing comparison check in for loop to compare the mapped instructions.
- Use borrowed `DAGCircuit` instances in the recursion of `get_example_gates`, do not use extract.
- Fix behavior of `get_example_gates` to return `PackedInstruction` instances instead of `NodeIndex`.
- Use `circuit_to_dag` and `DAGCircuit::from_circuit_data` to convert `QuantumCircuit` instances.
@raynelfss raynelfss requested a review from a team as a code owner September 11, 2024 22:10
@qiskit-bot
Copy link
Collaborator

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

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

@raynelfss raynelfss added this to the 1.3.0 milestone Sep 11, 2024
@raynelfss raynelfss added performance Changelog: None Do not include in changelog Rust This PR or issue is related to Rust code in the repository labels Sep 11, 2024
@coveralls
Copy link

coveralls commented Sep 11, 2024

Pull Request Test Coverage Report for Build 11073229544

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

  • 158 of 162 (97.53%) changed or added relevant lines in 8 files are covered.
  • 468 unchanged lines in 2 files lost coverage.
  • Overall coverage increased (+0.09%) to 88.879%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/accelerate/src/basis/mod.rs 4 5 80.0%
crates/accelerate/src/basis/basis_translator/compose_transforms.rs 125 128 97.66%
Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/lex.rs 4 92.48%
crates/circuit/src/dag_circuit.rs 464 88.24%
Totals Coverage Status
Change from base Build 11052352075: 0.09%
Covered Lines: 74142
Relevant Lines: 83419

💛 - Coveralls

@raynelfss
Copy link
Contributor Author

Here are some benchmarks

All benchmarks:

| Change   | Before [81063a79] <fix-abi3-issues~1>   | After [e8c957df] <rust-compose-trans>   |   Ratio | Benchmark (Parameter)                                                                                    |
|----------|-----------------------------------------|-----------------------------------------|---------|----------------------------------------------------------------------------------------------------------|
|          | 306±1ms                                 | 303±2ms                                 |    0.99 | passes.MultipleBasisPassBenchmarks.time_basis_translator(14, 1024, ['rx', 'ry', 'rz', 'r', 'rxx', 'id']) |
|          | 443±3ms                                 | 437±3ms                                 |    0.99 | passes.MultipleBasisPassBenchmarks.time_basis_translator(20, 1024, ['rx', 'ry', 'rz', 'r', 'rxx', 'id']) |
|          | 188±0.8ms                               | 182±2ms                                 |    0.97 | passes.MultipleBasisPassBenchmarks.time_basis_translator(14, 1024, ['rz', 'x', 'sx', 'cx', 'id'])        |
|          | 217±0.7ms                               | 207±0.6ms                               |    0.96 | passes.MultipleBasisPassBenchmarks.time_basis_translator(20, 1024, ['u', 'cx', 'id'])                    |
|          | 102±0.5ms                               | 98.1±1ms                                |    0.96 | passes.MultipleBasisPassBenchmarks.time_basis_translator(5, 1024, ['rx', 'ry', 'rz', 'r', 'rxx', 'id'])  |
|          | 266±4ms                                 | 253±0.5ms                               |    0.95 | passes.MultipleBasisPassBenchmarks.time_basis_translator(20, 1024, ['rz', 'x', 'sx', 'cx', 'id'])        |
|          | 155±3ms                                 | 146±0.6ms                               |    0.94 | passes.MultipleBasisPassBenchmarks.time_basis_translator(14, 1024, ['u', 'cx', 'id'])                    |
|          | 68.6±1ms                                | 62.6±0.2ms                              |    0.91 | passes.MultipleBasisPassBenchmarks.time_basis_translator(5, 1024, ['rz', 'x', 'sx', 'cx', 'id'])         |
| -        | 57.5±0.4ms                              | 51.8±1ms                                |    0.9  | passes.MultipleBasisPassBenchmarks.time_basis_translator(5, 1024, ['u', 'cx', 'id'])                     |

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
PERFORMANCE INCREASED.
All benchmarks:

| Change   | Before [81063a79] <fix-abi3-issues~1>   | After [e8c957df] <rust-compose-trans>   | Ratio   | Benchmark (Parameter)                                                                                     |
|----------|-----------------------------------------|-----------------------------------------|---------|-----------------------------------------------------------------------------------------------------------|
|          | 5.91±0s                                 | 5.13±0.01s                              | ~0.87   | randomized_benchmarking.RandomizedBenchmarkingBenchmark.time_ibmq_backend_transpile([0, 1])               |
|          | 5.93±0.02s                              | 5.11±0s                                 | ~0.86   | randomized_benchmarking.RandomizedBenchmarkingBenchmark.time_ibmq_backend_transpile_single_thread([0, 1]) |
| -        | 2.53±0.01s                              | 2.26±0.01s                              | 0.89    | randomized_benchmarking.RandomizedBenchmarkingBenchmark.time_ibmq_backend_transpile([0])                  |
| -        | 2.56±0.01s                              | 2.25±0.01s                              | 0.88    | randomized_benchmarking.RandomizedBenchmarkingBenchmark.time_ibmq_backend_transpile_single_thread([0])    |

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
PERFORMANCE INCREASED.

@raynelfss raynelfss added the on hold Can not fix yet label Sep 13, 2024
@raynelfss raynelfss removed the on hold Can not fix yet label Sep 18, 2024
@raynelfss raynelfss changed the title [Oxidize BaisTranslator] Add rust-native compose_transforms() [Oxidize BasisTranslator] Add rust-native compose_transforms() Sep 18, 2024
Copy link
Contributor

@kevinhartman kevinhartman left a comment

Choose a reason for hiding this comment

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

Looks like a good start, thanks for working on this.

Unfortunately, the Python code you're starting with is probably sorely in need of a rewrite. I'm not entirely familiar with it, but it seems riddled with wrong / bad docs and tuple abuse.

I'll need to do a second pass once you've had a look at the comments thus far. I haven't spent enough time reviewing the parameter mapping stuff, specifically.

@@ -0,0 +1,21 @@
// This code is part of Qiskit.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a clear benefit to having a separate folder here for basis_translator?

I don't know that I mind it, but it's different from the folder structure we had in Python, and none of the other accelerate modules really go more than one module deep (except for synthesis, which seems to copy the organization of the Python module).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is just to plan ahead, since the BasisTranslator has a couple of moving parts and since I am separating things at the moment to make them more readable. If you take a look at #12811, that feature also exists in that same folder. but in a different file If you think it shouldn't be this way it can be changed.

Copy link
Contributor

Choose a reason for hiding this comment

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

It suppose it doesn't really matter much, so if you think it helps to organize I'm good with it 🙂.

crates/circuit/src/dag_circuit.rs Outdated Show resolved Hide resolved

for (gate_name, gate_num_qubits) in source_basis.iter().cloned() {
// Need to grab a gate instance to find num_qubits and num_params.
// Can be removed following https://github.com/Qiskit/qiskit-terra/pull/3947 .
Copy link
Contributor

Choose a reason for hiding this comment

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

This issue appears to be closed. I think the comment may be outdated in your implementation, anyhow?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this was a question I had. Not sure how to tackle it since it seems that issue has been abandoned.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would probably just remove the comment, and add a method comment to get_example_gates that explains that its purpose is just to get the number of parameters for a gate with a specific name and number of qubits, since that info is only found on an instruction.

(...also, we should probably rename get_example_gates to get_num_params and change the return type to HashMap<(String, u32), usize>. As it is, we don't use the PackedInstructions coming out of the function (do we?) and they are being cloned)

dag.apply_operation_back(placeholder_gate, qr, (), check=False)
mapped_instrs[gate_name, gate_num_qubits] = placeholder_params, dag

for gate_name, gate_num_qubits, equiv_params, equiv in basis_transforms:
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like the documentation is wrong then for basis_transforms in the original Python code, no? Since the tuple does not have the number of bits there.

- Remove unused `circuit_to_dag` and `OperationRef` imports.
- Streamline complex types into simpler aliases.
- Rename `CircuitRep` to `CircuitFromPython`.
- Reshape `get_example_gates` to work with `CircuitData` instances during its recursion.
- Remove unnecessary clone of `gate_name` in `compose_transform`.
- Use `mapped_instructions` values when iterating in `compose_transforms`.
- Rename `DAGCircuit::substitute_node_with_dag` to `DAGCircuit::py_*` in rust.
- Adapted `_basis_search` to return a tuple of tuples instead of a 4-tuple.
Copy link
Contributor

@kevinhartman kevinhartman left a comment

Choose a reason for hiding this comment

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

Just a few more comments. Thanks! :)

Comment on lines +152 to +159
let op_node = dag.get_node(py, node)?;
dag.py_substitute_node_with_dag(
py,
op_node.bind(py),
&replace_dag,
None,
true,
)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

I imagine this is slower than it ought to be, but it'd be non-trivial to expose a native-Rust substitute_node_with_dag. Maybe we can revisit doing so in a future PR.

raynelfss and others added 2 commits September 24, 2024 09:50
- Remove stale comment related to Qiskit#3947
- Rename tuple instances to `GateIdentifier`.
- Rename `doomed_nodes` to `nodes_to_replace`.
- Add docstring for `get_example_gates`.
- Rename `get_example_gates` to `get_gate_num_params`.

Co-authored-by: Kevin Hartman <kevin@hart.mn>
Copy link
Contributor

@kevinhartman kevinhartman left a comment

Choose a reason for hiding this comment

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

Last two minor comments. Otherwise, it looks reasonable to me.

- Remove `CircuitFromPython` and re-use original instance from `EquivalenceLibrary` after Qiskit#12585 merged.
Copy link
Contributor

@kevinhartman kevinhartman left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for doing this work!

@kevinhartman kevinhartman added this pull request to the merge queue Sep 27, 2024
Merged via the queue into Qiskit:main with commit 43feab3 Sep 27, 2024
15 checks passed
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
emilkovacev pushed a commit to emilkovacev/qiskit that referenced this pull request Oct 1, 2024
…kit#13137)

* Initial: Add `compose_transforms` and `get_example_gates` to Rust.

* Add: `compose_transform` logic in rust

* Fix: Correct the behavior of `compose_transforms`.
-  Use `PackedOperation.control_flow` instead of `CONTROL_FLOW_OP_NAMES` to check if an instructuion is a control flow operation.
- Remove panic statement when checking for example gates.
- Use qreg when adding an instruction to the mock_dag in `compose_transforms`.
- Add missing comparison check in for loop to compare the mapped instructions.
- Use borrowed `DAGCircuit` instances in the recursion of `get_example_gates`, do not use extract.
- Fix behavior of `get_example_gates` to return `PackedInstruction` instances instead of `NodeIndex`.

* Fix: Leverage rust-based `circuit_to_dag` converters.
- Use `circuit_to_dag` and `DAGCircuit::from_circuit_data` to convert `QuantumCircuit` instances.

* Format: Fix indentation of import lines in `compose_transforms.rs`

* Formatting: Separate complicated type aliases

* Fix: Adapt to new `DAGCircuit` limitations

* Format: Remove unused imports in BasisTranslator

* Fix: Adapt to Qiskit#13143

* Fix: Code review comments
- Remove unused `circuit_to_dag` and `OperationRef` imports.
- Streamline complex types into simpler aliases.
- Rename `CircuitRep` to `CircuitFromPython`.
- Reshape `get_example_gates` to work with `CircuitData` instances during its recursion.
- Remove unnecessary clone of `gate_name` in `compose_transform`.
- Use `mapped_instructions` values when iterating in `compose_transforms`.
- Rename `DAGCircuit::substitute_node_with_dag` to `DAGCircuit::py_*` in rust.
- Adapted `_basis_search` to return a tuple of tuples instead of a 4-tuple.

* Fix: More commments from code review
- Remove stale comment related to Qiskit#3947
- Rename tuple instances to `GateIdentifier`.
- Rename `doomed_nodes` to `nodes_to_replace`.
- Add docstring for `get_example_gates`.
- Rename `get_example_gates` to `get_gate_num_params`.

Co-authored-by: Kevin Hartman <kevin@hart.mn>

* Refactor: Rename `example_gates` to `gate_param_counts`
- Remove `CircuitFromPython` and re-use original instance from `EquivalenceLibrary` after Qiskit#12585 merged.

---------

Co-authored-by: Kevin Hartman <kevin@hart.mn>
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
ElePT pushed a commit to ElePT/qiskit that referenced this pull request Oct 9, 2024
…kit#13137)

* Initial: Add `compose_transforms` and `get_example_gates` to Rust.

* Add: `compose_transform` logic in rust

* Fix: Correct the behavior of `compose_transforms`.
-  Use `PackedOperation.control_flow` instead of `CONTROL_FLOW_OP_NAMES` to check if an instructuion is a control flow operation.
- Remove panic statement when checking for example gates.
- Use qreg when adding an instruction to the mock_dag in `compose_transforms`.
- Add missing comparison check in for loop to compare the mapped instructions.
- Use borrowed `DAGCircuit` instances in the recursion of `get_example_gates`, do not use extract.
- Fix behavior of `get_example_gates` to return `PackedInstruction` instances instead of `NodeIndex`.

* Fix: Leverage rust-based `circuit_to_dag` converters.
- Use `circuit_to_dag` and `DAGCircuit::from_circuit_data` to convert `QuantumCircuit` instances.

* Format: Fix indentation of import lines in `compose_transforms.rs`

* Formatting: Separate complicated type aliases

* Fix: Adapt to new `DAGCircuit` limitations

* Format: Remove unused imports in BasisTranslator

* Fix: Adapt to Qiskit#13143

* Fix: Code review comments
- Remove unused `circuit_to_dag` and `OperationRef` imports.
- Streamline complex types into simpler aliases.
- Rename `CircuitRep` to `CircuitFromPython`.
- Reshape `get_example_gates` to work with `CircuitData` instances during its recursion.
- Remove unnecessary clone of `gate_name` in `compose_transform`.
- Use `mapped_instructions` values when iterating in `compose_transforms`.
- Rename `DAGCircuit::substitute_node_with_dag` to `DAGCircuit::py_*` in rust.
- Adapted `_basis_search` to return a tuple of tuples instead of a 4-tuple.

* Fix: More commments from code review
- Remove stale comment related to Qiskit#3947
- Rename tuple instances to `GateIdentifier`.
- Rename `doomed_nodes` to `nodes_to_replace`.
- Add docstring for `get_example_gates`.
- Rename `get_example_gates` to `get_gate_num_params`.

Co-authored-by: Kevin Hartman <kevin@hart.mn>

* Refactor: Rename `example_gates` to `gate_param_counts`
- Remove `CircuitFromPython` and re-use original instance from `EquivalenceLibrary` after Qiskit#12585 merged.

---------

Co-authored-by: Kevin Hartman <kevin@hart.mn>
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 <57907331+ElePT@users.noreply.github.com>

* 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 <57907331+ElePT@users.noreply.github.com>
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