Skip to content

Merging UnrollCustomDefinitions into HighLevelSynthesis as a single pass #10965

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 47 commits into from
Oct 19, 2023

Conversation

alexanderivrii
Copy link
Member

@alexanderivrii alexanderivrii commented Oct 4, 2023

Summary

This PR is implemented on top of #9846, following Matthew's review comment below and offline discussions:

I think in my ideal scenario we'd merge UnrollCustomInstructions into HLS as a single pass, so HLS recurses if something is not in the plugin list, not in the target, and not in the equivalence lib, and adds a default recursive unroll in the absence of a plugin. But that is definitely a standalone PR.

Details and comments

In a nutshell, HLS is now combined with UCD and supports recursion. (Not supporting recursion was an oversight in the original implementation).

The constructor for HighLevelSynthesis has additional arguments equivalence_library, basis_gates, and min_qubits,
similarly to UnrollCustomDefinitions.

Consider the general case that a quantum circuit may have high-level-objects and annotated gates in addition to custom gates.

When either target or basis_gates are specified we recursively synthesize/unroll everything till gates in target/basis_gates/equivalence library, in accordance with Matthew's comment above.

In particular, if there are no high-level-objects/annotated operations, the behavior of HLS should be exactly the same as of UCD. Testing-wise, I took the two suites for checking the correctness of UCD: TestUnrollerCompatability from test_basis_translator and TestUnrollCustomDefinitions from test_unroll_custom_definitions, and copied them to test_high_level_synthesis with UCD replaced by HLS.

When neither target nor basis_gates are specified, I aimed to make the behavior both compatible to UCD (which would not do anything) and with the older behavior of HLS (which allowed to synthesize some of the top-level high-level-objects based on the HLSConfig). The pass synthesizes only top-level high-level-objects/annotated gates, but does not unroll gate definitions.

I have modified preset pass managers in common.py extending HLS with new arguments and removing UCD. It was also important to pass min_qubits=3 when unrolling, as forgetting to do so results in HLS synthesizing unitary gates over 2-qubits (and in addition to not wanting to do this, this also results in test failures as it does not use unitary synthesis plugins/respect basis_gates).


Things to pay attention to when reviewing:

  • Integration in preset pass managers. Btw, do we need HLS there at all in the "synthesis" flow?
  • Integration with control-flow blocks. I will need to write more tests specific to that.

I may also want to add a few more complex tests for checking corner cases with recursion + combinations of annotated ops/high-level-objects/custom gates. (Update: I did, which required a few fixes to support high-level-objects annotated with control or power modifiers -- after synthesizing the base object and constructing the controlled/powered version of that, we need to unroll the complex circuit definition).

@alexanderivrii alexanderivrii requested review from a team and ikkoham as code owners October 4, 2023 08:23
@qiskit-bot
Copy link
Collaborator

One or more of the the following people are requested to review this:

  • @Qiskit/terra-core
  • @ikkoham

@alexanderivrii alexanderivrii added this to the 0.45.0 milestone Oct 4, 2023
@alexanderivrii alexanderivrii added the Changelog: New Feature Include in the "Added" section of the changelog label Oct 4, 2023
@mtreinish mtreinish self-assigned this Oct 4, 2023
@coveralls
Copy link

Pull Request Test Coverage Report for Build 6545469464

  • 47 of 49 (95.92%) changed or added relevant lines in 1 file are covered.
  • 10 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+0.002%) to 87.078%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/transpiler/passes/synthesis/high_level_synthesis.py 47 49 95.92%
Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/lex.rs 3 91.41%
qiskit/pulse/library/waveform.py 3 93.75%
qiskit/transpiler/passes/basis/unroll_custom_definitions.py 4 90.7%
Totals Coverage Status
Change from base Build 6541699793: 0.002%
Covered Lines: 74064
Relevant Lines: 85055

💛 - Coveralls

@alexanderivrii
Copy link
Member Author

On a second thought, the number of changes is not that big. Here is the summary:

  1. Following UnrollCustomDefinitions, the HLS pass accepts additional arguments equivalence_library, basis_gates, min_qubits.
  2. The HLS pass also accepts an argument top_level_only, but it could be easily removed -- and maybe it's best to do so?
  3. Most of the changes in run and _recursively_handle_op is adapting code from UnrollCustomDefinitions.
  4. The function _recursively_handle_op may also return a DAGCircuit to avoid back-and-forth conversions (this point came up somewhere before).
  5. The key (but small) changes are in _synthesize_op_using_plugins, where after adding control or power logic to the synthesized logic for base operation, we need to call the pass again to unroll gates (created by the control/power) synthesis into gates supported by the target/the equivalence library.
  6. Preset_pass_managers are modified to remove UnrollCustomDefinitions. I am not sure if it makes sense to deprecate UCD now, but definitely best in a separate PR.
  7. Multiple tests exercising the fully recursive nature of HLS
  8. Backward compatibility tests for UCD replaced by HLS.

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 looks really nice, the changes to make high level synthesis were as straightforward as I was hoping and this makes HighLevelSynthesis the omnibus pass for all synthesis including unrolling custom gates. I left a few small comments inline.

Comment on lines -202 to -206
# If there are no high level operations / annotated gates to synthesize, return fast
hls_names = set(self.hls_plugin_manager.plugins_by_op)
node_names = dag.count_ops()
if "annotated" not in node_names and not hls_names.intersection(node_names):
return dag
Copy link
Member

Choose a reason for hiding this comment

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

I realize there isn't a quick lookup anymore because the equivalence libraries' lookup keys are not pure strings anymore. But it's a real shame to lose this, because it makes the pass much faster in the case when everything is in standard library gates (especially for really large circuits). I think if we did:

gates = {node[0] for node in self._equiv_lib.graph.nodes()}

or

gates = {node[0] for node in self._equiv_lib._key_to_node_index}

That will give us a list of gate names in the equivalence library and we could retain this (along with a corresponding target and/or basis gates lookup). What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a nice idea, let me experiment with this. I believe that the previous code was unnecessary slow because I was calling dag.substitute_node(node, decomposition) even when node wasn't changed, but now this no longer happens since we are explicitly tracking whether the node was modified.

Copy link
Member Author

Choose a reason for hiding this comment

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

An update after thinking about this: intuitively, to restore the previous optimization we need to compute

  1. A = list of names dag.count_ops()
  2. B = list of names in equivalence library (the second suggested command works)
  3. C = list of names supported by the target. Looking at the code, I believe it should be something like self._target._gate_map.keys()
  4. D = list of names in self.hls_plugin_manager.plugins_by_op

and check if A is a subset of (B | C) \ D.

However I believe we have the problem for open-controlled gates (as per the code adapted from UnrollCustomDefinitions): a gate's name might be supported by the target or belong to equivalence library, yet because it's open-control we actually can't skip synthesis and use the definition. Any suggestions?

If this is not a trivial task, maybe we can postpone restoring this optimization to a follow-up PR?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I think it's fine to defer it, I didn't have a good answer for this either in UnrollCustomDefinitions for #10788 we could potentially implement this a performance fix for 0.45.0 post rc1 if needed.

Comment on lines +423 to +424
if isinstance(synthesized_op, DAGCircuit):
synthesized_op = dag_to_circuit(synthesized_op, copy_operations=False)
Copy link
Member

Choose a reason for hiding this comment

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

I look forward to the day when the modifier synthesis can operate on dags and we're not using the circuit and or gate methods. The worst case here we are essentially doing:

circuit_to_dag(dag_to_circuit(synthesized_op).to_gate().control().definition)

I don't see a way around it for now, but longer term it'd be good if we didn't do this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed!

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.

This LGTM now, thanks for the quick update. Let's open an issue about figuring out a fast path when there's nothing to synthesize so we can track that in a follow up.

@mtreinish mtreinish added this pull request to the merge queue Oct 19, 2023
Merged via the queue into Qiskit:main with commit 8d69dd0 Oct 19, 2023
@alexanderivrii alexanderivrii deleted the hls-with-recursion branch October 23, 2023 07:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog priority: medium
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants