Skip to content

Fix determinism in ConsolidateBlocks#16237

Open
alexanderivrii wants to merge 1 commit into
Qiskit:mainfrom
alexanderivrii:deterministic-consolidate
Open

Fix determinism in ConsolidateBlocks#16237
alexanderivrii wants to merge 1 commit into
Qiskit:mainfrom
alexanderivrii:deterministic-consolidate

Conversation

@alexanderivrii
Copy link
Copy Markdown
Member

Summary

While experimenting with the Clifford+T benchmarks (in an ongoing experiment with @jan-an and @Cryoris), I noticed that some runs were not deterministic (for the same value of transpiler seed) and managed to pinpoint the problem to which basis gate is selected inside the ConsolidateBlock.__init__ method).

The problem occurs when there are multiple parametric or multiple non-parametric 2-qubit gates to choose from. While the dict KAK_GATE_NAMES = {"cx": CXGate(), "cz": CZGate(), "iswap": iSwapGate(), "ecr": ECRGate()}has a deterministic iteration order, the computation KAK_GATE_NAMES.keys() & (basis_gates or []) returns a set, and its first element next(iter(kak_param_gates)) might be different across runs.

In Clifford+T transpilation, we often pass all 2-qubit Clifford gates as basis, leading to a selection from CZ, CX, ECR and iSWAP, however this problem could be also present for continuous basis sets where both CX and CZ can be present (thanks @mtreinish for the information).

Details

The current (very simple) approach chooses the first gate from KAK_GATE_NAMES that is also in the basis_gates (and same for parametric gates).

I did not change the order of gates in the dict, so for Clifford+T transpilation with all Clifford basis gates this ends up always using the CX-gate. However, on FT benchmarks using CZ seems consistently better than using CX (at least with the rest of our pipeline intact):

image

I am wondering if I should add an extra argument to ConsolidateBlocks to make it "Clifford+T -transpilation aware"?

I am not sure what's the best test to add here as non-deterministic behavior occurs across different Qiskit runs (see #16139 for a similar problem).

AI/LLM disclosure

  • I didn't use LLM tooling, or only used it privately.
  • I used the following tool to help write this PR description:
  • I used the following tool to generate or modify code:

@alexanderivrii alexanderivrii requested a review from a team as a code owner May 22, 2026 07:47
@alexanderivrii alexanderivrii requested a review from Cryoris May 22, 2026 07:47
@alexanderivrii alexanderivrii added the Changelog: Fixed Add a "Fixed" entry in the GitHub Release changelog. label May 22, 2026
@qiskit-bot
Copy link
Copy Markdown
Collaborator

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

  • @Qiskit/terra-core

@coveralls
Copy link
Copy Markdown

Coverage Report for CI Build 26275374846

Coverage decreased (-0.005%) to 87.489%

Details

  • Coverage decreased (-0.005%) from the base build.
  • Patch coverage: 6 of 6 lines across 1 file are fully covered (100%).
  • 13 coverage regressions across 4 files.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

13 previously-covered lines in 4 files lost coverage.

File Lines Losing Coverage Coverage
crates/qasm2/src/parse.rs 6 96.68%
crates/qasm2/src/lex.rs 5 92.03%
crates/circuit/src/parameter/parameter_expression.rs 1 91.04%
crates/circuit/src/parameter/symbol_expr.rs 1 73.88%

Coverage Stats

Coverage Status
Relevant Lines: 123872
Covered Lines: 108374
Line Coverage: 87.49%
Coverage Strength: 957540.99 hits per line

💛 - Coveralls

Copy link
Copy Markdown
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

You might be able to get away with writing a test that would randomly decompose between two options if the test is flaky, but reliably select a particular one if it's fixed. It wouldn't fail 100% of the time in a single test-suite run, but it'd have enough chance of failure it'd almost certainly be caught by any CI run.

That said, this isn't the end of the world if we can't write a test for it: it's fairly unlikely to accidentally reintroduce the bug.

Comment on lines +4 to +5
Fixed a problem in :class:`.ConsolidateBlocks` where basis gate selection was not always
deterministic, which could lead to different transpiled circuits across runs.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a quick mention of the configuration needed to make it non-deterministic too? I think most "common" use of the pass was always deterministic.

Comment on lines -120 to +123
kak_gates = KAK_GATE_NAMES.keys() & (basis_gates or [])
kak_param_gates = KAK_GATE_PARAM_NAMES.keys() & (basis_gates or [])
if kak_param_gates:
kak_gate = next((gate for gate in KAK_GATE_NAMES if gate in basis_gates), None)
kak_param_gate = next(
(gate for gate in KAK_GATE_PARAM_NAMES if gate in basis_gates), None
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Incredibly minor, but since KAK_GATE_NAMES and KAK_GATE_PARAM_NAMES are reliably dict, but basis_gates is a list, you might want to drive the iteration from basis_gates and lookup into KAK_GATE_NAMES instead, especially if you're in the habit of passing huge basis_gates lists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Changelog: Fixed Add a "Fixed" entry in the GitHub Release changelog.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants