From 5b2bf8143dfd63c289bff88a26bdcc29135100c3 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Mon, 2 Sep 2024 14:00:19 +0300 Subject: [PATCH 1/3] improved fast-path detection in HLS --- .../passes/synthesis/high_level_synthesis.py | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 73a402dc202f..562e09af9474 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -280,6 +280,21 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: return self._run(dag, tracker) def _run(self, dag: DAGCircuit, tracker: QubitTracker) -> DAGCircuit: + # Check if HighLevelSynthesis can be skipped. + all_skipped = True + for node in dag.topological_op_nodes(): + qubits = tuple(dag.find_bit(q).index for q in node.qargs) + if not ( + dag.has_calibration_for(node) + or len(node.qargs) < self._min_qubits + or node.is_directive() + or self._definitely_skip_node(node, qubits) + ): + all_skipped = False + break + if all_skipped: + return dag + # Start by analyzing the nodes in the DAG. This for-loop is a first version of a potentially # more elaborate approach to find good operation/ancilla allocations. It greedily iterates # over the nodes, checking whether we can synthesize them, while keeping track of the @@ -312,7 +327,7 @@ def _run(self, dag: DAGCircuit, tracker: QubitTracker) -> DAGCircuit: # now we are free to synthesize else: # this returns the synthesized operation and the qubits it acts on -- note that this - # may be different than the original qubits, since we may use auxiliary qubits + # may be different from the original qubits, since we may use auxiliary qubits synthesized, used_qubits = self._synthesize_operation(node.op, qubits, tracker) # if the synthesis changed the operation (i.e. it is not None), store the result @@ -335,7 +350,7 @@ def _run(self, dag: DAGCircuit, tracker: QubitTracker) -> DAGCircuit: if len(synthesized_nodes) == 0: return dag - # Otherwise we will rebuild with the new operations. Note that we could also + # Otherwise, we will rebuild with the new operations. Note that we could also # check if no operation changed in size and substitute in-place, but rebuilding is # generally as fast or faster, unless very few operations are changed. out = dag.copy_empty_like() @@ -642,8 +657,9 @@ def _definitely_skip_node(self, node: DAGOpNode, qubits: tuple[int] | None) -> b # The fast path is just for Rust-space standard gates (which excludes # `AnnotatedOperation`). node.is_standard_gate() - # If it's a controlled gate, we might choose to do funny things to it. - and not node.is_controlled_gate() + # At the moment we don't consider fast-path handling for controlled gates over 3 or + # more controlled qubits. However, we should not abort early for CX/CZ/etc. + and not (node.is_controlled_gate() and node.num_qubits >= 3) # If there are plugins to try, they need to be tried. and not self._methods_to_try(node.name) # If all the above constraints hold, and it's already supported or the basis translator From 0962be43f9f3fef986f04aa62a7c3a3ba6b955d6 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 11 Sep 2024 16:20:39 +0300 Subject: [PATCH 2/3] apply Jake's code review comments --- .../passes/synthesis/high_level_synthesis.py | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 562e09af9474..89de016e0f36 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -281,18 +281,12 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: def _run(self, dag: DAGCircuit, tracker: QubitTracker) -> DAGCircuit: # Check if HighLevelSynthesis can be skipped. - all_skipped = True - for node in dag.topological_op_nodes(): + for node in dag.op_nodes(): qubits = tuple(dag.find_bit(q).index for q in node.qargs) - if not ( - dag.has_calibration_for(node) - or len(node.qargs) < self._min_qubits - or node.is_directive() - or self._definitely_skip_node(node, qubits) - ): - all_skipped = False + if not self._definitely_skip_node(node, qubits, dag): break - if all_skipped: + else: + # The for-loop terminates without reaching the break statement return dag # Start by analyzing the nodes in the DAG. This for-loop is a first version of a potentially @@ -308,12 +302,7 @@ def _run(self, dag: DAGCircuit, tracker: QubitTracker) -> DAGCircuit: used_qubits = None # check if synthesis for the operation can be skipped - if ( - dag.has_calibration_for(node) - or len(node.qargs) < self._min_qubits - or node.is_directive() - or self._definitely_skip_node(node, qubits) - ): + if self._definitely_skip_node(node, qubits, dag): pass # next check control flow @@ -646,19 +635,31 @@ def _apply_annotations( return synthesized - def _definitely_skip_node(self, node: DAGOpNode, qubits: tuple[int] | None) -> bool: + def _definitely_skip_node( + self, node: DAGOpNode, qubits: tuple[int] | None, dag: DAGCircuit + ) -> bool: """Fast-path determination of whether a node can certainly be skipped (i.e. nothing will attempt to synthesise it) without accessing its Python-space `Operation`. This is tightly coupled to `_recursively_handle_op`; it exists as a temporary measure to avoid Python-space `Operation` creation from a `DAGOpNode` if we wouldn't do anything to the node (which is _most_ nodes).""" + + if ( + dag.has_calibration_for(node) + or len(node.qargs) < self._min_qubits + or node.is_directive() + ): + return True + return ( # The fast path is just for Rust-space standard gates (which excludes # `AnnotatedOperation`). node.is_standard_gate() - # At the moment we don't consider fast-path handling for controlled gates over 3 or - # more controlled qubits. However, we should not abort early for CX/CZ/etc. + # We don't have the fast-path for controlled gates over 3 or more qubits. + # However, we most probably want the fast-path for CX and CZ gates, + # and "_definitely_skip_node" should not immediately return False + # when encountering a controlled gate over 2 qubits. and not (node.is_controlled_gate() and node.num_qubits >= 3) # If there are plugins to try, they need to be tried. and not self._methods_to_try(node.name) From 0c29c0a9fbb153492f561f0a940c9528f7278cac Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Wed, 11 Sep 2024 17:09:03 +0300 Subject: [PATCH 3/3] making the comment even more clearer --- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 89de016e0f36..31036de69645 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -657,9 +657,9 @@ def _definitely_skip_node( # `AnnotatedOperation`). node.is_standard_gate() # We don't have the fast-path for controlled gates over 3 or more qubits. - # However, we most probably want the fast-path for CX and CZ gates, - # and "_definitely_skip_node" should not immediately return False - # when encountering a controlled gate over 2 qubits. + # However, we most probably want the fast-path for controlled 2-qubit gates + # (such as CX, CZ, CY, CH, CRX, and so on), so "_definitely_skip_node" should + # not immediately return False when encountering a controlled gate over 2 qubits. and not (node.is_controlled_gate() and node.num_qubits >= 3) # If there are plugins to try, they need to be tried. and not self._methods_to_try(node.name)