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 UnitarySynthesis pass #13141

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

ElePT
Copy link
Contributor

@ElePT ElePT commented Sep 12, 2024

Summary

The code is an almost literal copy of the Python one (not very rusty), but it's a start.

Details and comments

TODO:

  • Add unit tests for qsd path and 1q synthesis path
  • Address drawer issue after calling dag_to_circuit (not reflected in unit tests)
  • Clean up comments
  • Review use of dag.push_back and see if it can be done with the new dag.apply_operation_back method

@mtreinish mtreinish added performance Rust This PR or issue is related to Rust code in the repository mod: transpiler Issues and PRs related to Transpiler labels Sep 12, 2024
@mtreinish mtreinish added this to the 1.3.0 milestone Sep 12, 2024
@coveralls
Copy link

coveralls commented Sep 12, 2024

Pull Request Test Coverage Report for Build 10922534515

Details

  • 736 of 796 (92.46%) changed or added relevant lines in 7 files are covered.
  • 160 unchanged lines in 4 files lost coverage.
  • Overall coverage increased (+0.4%) to 88.769%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/accelerate/src/two_qubit_decompose.rs 11 15 73.33%
crates/accelerate/src/unitary_synthesis.rs 709 765 92.68%
Files with Coverage Reduction New Missed Lines %
crates/accelerate/src/two_qubit_decompose.rs 1 91.68%
crates/qasm2/src/lex.rs 2 92.48%
crates/qasm2/src/parse.rs 6 96.23%
qiskit/transpiler/passes/synthesis/unitary_synthesis.py 151 62.15%
Totals Coverage Status
Change from base Build 10919208944: 0.4%
Covered Lines: 74129
Relevant Lines: 83508

💛 - Coveralls

@ElePT
Copy link
Contributor Author

ElePT commented Sep 13, 2024

Performance is starting to look promising:

Benchmarks that have improved:

| Change   | Before [2fa6dd65] <main>   | After [739a5c84] <oxidize-unitary-synthesis-benchmarking-3>   |   Ratio | Benchmark (Parameter)                                              |
|----------|----------------------------|---------------------------------------------------------------|---------|--------------------------------------------------------------------|
| -        | 1.30±0.04s                 | 1.17±0.02s                                                    |    0.9  | utility_scale.UtilityScaleBenchmarks.time_qaoa('ecr')              |
| -        | 483±60ms                   | 375±2ms                                                       |    0.78 | utility_scale.UtilityScaleBenchmarks.time_square_heisenberg('cx')  |
| -        | 695±90ms                   | 476±20ms                                                      |    0.69 | utility_scale.UtilityScaleBenchmarks.time_square_heisenberg('cz')  |
| -        | 705±100ms                  | 481±10ms                                                      |    0.68 | utility_scale.UtilityScaleBenchmarks.time_square_heisenberg('ecr') |

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
PERFORMANCE INCREASED.

Comment on lines 345 to 348
let new_qargs = dag.qargs_interner.get(packed_instr.qubits).to_vec();
let mut owned_instr = packed_instr.clone();
owned_instr.qubits = out_dag.qargs_interner.insert_owned(new_qargs);
let _ = out_dag.push_back(py, owned_instr);
Copy link
Contributor Author

@ElePT ElePT Sep 16, 2024

Choose a reason for hiding this comment

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

This (and all other places where dag.push_back is used) could be replaced by #13143.

@ElePT
Copy link
Contributor Author

ElePT commented Sep 16, 2024

Benchmarking status as of eb815d1 (more benchmarks show improvement but the improvement looks slightly smaller).

| Change   | Before [2fa6dd65] <unitary-test>   | After [266a22f0] <oxidize-unitary-synthesis>   |   Ratio | Benchmark (Parameter)                                              |
|----------|------------------------------------|------------------------------------------------|---------|--------------------------------------------------------------------|
| -        | 1.38±0.04s                         | 1.21±0.03s                                     |    0.87 | utility_scale.UtilityScaleBenchmarks.time_qaoa('cz')               |
| -        | 425±8ms                            | 361±8ms                                        |    0.85 | utility_scale.UtilityScaleBenchmarks.time_square_heisenberg('cx')  |
| -        | 566±10ms                           | 467±10ms                                       |    0.83 | utility_scale.UtilityScaleBenchmarks.time_square_heisenberg('cz')  |
| -        | 3.94±0.2s                          | 3.04±0.02s                                     |    0.77 | utility_scale.UtilityScaleBenchmarks.time_qv('ecr')                |
| -        | 4.21±0.1s                          | 3.15±0.04s                                     |    0.75 | utility_scale.UtilityScaleBenchmarks.time_qv('cz')                 |
| -        | 626±40ms                           | 456±4ms                                        |    0.73 | utility_scale.UtilityScaleBenchmarks.time_square_heisenberg('ecr') |

@ElePT ElePT marked this pull request as ready for review September 16, 2024 16:06
@qiskit-bot
Copy link
Collaborator

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

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

@ElePT ElePT changed the title [WIP] Oxidize UnitarySynthesis pass Oxidize UnitarySynthesis pass Sep 16, 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 took a first pass through this, thanks for doing this @ElePT! I left some inline comments. The two high level ones that stuck out the most to me from my first pass is that this PR seems to make a lot of things public, some of them make sense like the dagcircuit methods, but others I'm skeptical we need to be reusing or accessing outside of their definition. Some of the internal attributes of structures for example.

The other thing that I think is potentially a bigger issue especially for performance is around some of the typing choices and how that leads to a lot of conversions. The most concrete example is things are normalized on DAGCircuit between all the different decomposer, but this leads to creating an expensive object to be constructed when it'd be far more efficient to iterate over the synthesized sequence directly and add the gates directly to the dag.

crates/accelerate/src/two_qubit_decompose.rs Outdated Show resolved Hide resolved
qiskit/transpiler/passes/synthesis/unitary_synthesis.py Outdated Show resolved Hide resolved
Comment on lines +186 to +187
let circuit_to_dag = imports::CIRCUIT_TO_DAG.get_bound(py);
let dag_to_circuit = imports::DAG_TO_CIRCUIT.get_bound(py);
Copy link
Member

Choose a reason for hiding this comment

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

These exist in rust now, see: https://github.com/Qiskit/qiskit/blob/main/crates/circuit/src/converters.rs so I'd suggest calling them directly. Especially if you're doing circuit_to_dag you can call the inner DAGCircuit constructor and avoid the python overhead when you've built a CircuitData directly in rust (not that I think that's the case here).

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

#[derive(Clone, Debug)]
enum UnitarySynthesisReturnType {
DAGType(Box<DAGCircuit>),
Copy link
Member

Choose a reason for hiding this comment

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

Why does this need a box?

Copy link
Contributor Author

@ElePT ElePT Sep 17, 2024

Choose a reason for hiding this comment

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

This was a suggestion from clippy, it raised a warning because of the size difference between variants. Apparently (citing the docs) "Enum size is bounded by the largest variant. Having one large variant can penalize the memory layout of that enum."

warning: large size difference between variants
  --> crates/accelerate/src/unitary_synthesis.rs:65:1
   |
65 | / enum UnitarySynthesisReturnType {
66 | |     DAGType(DAGCircuit),
   | |     ------------------- the largest variant contains at least 776 bytes
67 | |     TwoQSequenceType(TwoQubitUnitarySequence),
   | |     ----------------------------------------- the second-largest variant contains at least 120 bytes
68 | | }
   | |_^ the entire enum is at least 776 bytes
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#large_enum_variant
   = note: `#[warn(clippy::large_enum_variant)]` on by default
help: consider boxing the large fields to reduce the total size of the enum
   |
66 |     DAGType(Box<DAGCircuit>),
   |             ~~~~~~~~~~~~~~~

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, that's true DAGCircuit is huge compared to the Vec. But I guess what my concern here is that we're adding a layer of pointer indirection to an owned object that we're creating in the pass. I think maybe this is pointing to the larger concern I had around some of the switching logic here in that we're treating a DAGCircuit and sequence identically when there is an optimized path when we're working with the TwoQubitUnitarySequence because it's a simplified data type.

crates/accelerate/src/unitary_synthesis.rs Outdated Show resolved Hide resolved
@ElePT ElePT force-pushed the oxidize-unitary-synthesis branch 2 times, most recently from e560bf8 to 7498419 Compare September 18, 2024 10:08
 * Fix details after applying inline suggestions
 * Keep TwoQubitWeilDecomposition attributes private. Add getter.
 * Initialize new_blocks using size hint
 * Remove basis_set as it's not used if there is a target.
 * Use ref_qubits ([PhysicalQubit; 2]) instead of wire_map (IndexMap<Qubit, Physicalqubit>)
 * Define static GOODBYE_SET as suggested
 * Use ImportOnceCell for XXDecomposer and XXEmbodiments to avoid importing in a loop.
 * Set preferred_direction without making it mutable.
 * Fix check_goodbye
 * Privatize assets
 * Use the add_global_phase method instead of the private function.
 * Add qs_decomposition to imports
 * Simplify flip_bits
 * Use NormalOperation to pass around decomposer gate and params info
 * First attempt at attaching synth circuits directly
 * Second attempt at attaching synth circuits directly
 * Use edge set for coupling map
 * Avoid exposing internals from NullableIndexMap
 * Use unitary_to_gate_sequence_inner instead of optimize_1q_gates_decomposition.
 * Use concat! in long error message.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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.

4 participants