feat(physical): add MultiCandidateLookaheadPlacementStrategy#660
feat(physical): add MultiCandidateLookaheadPlacementStrategy#660WonJoon-Yun wants to merge 3 commits into
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6a669e4b99
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return ConcreteState( | ||
| occupied=frozenset(), | ||
| layout=layout, |
There was a problem hiding this comment.
Preserve occupied blockers in internal state construction
_make_state always sets occupied=frozenset(), so every internal call to compute_move_layers and the nested NoReturnPlacementStrategy solve ignores blocked locations from the real input state. In circuits where state.occupied is non-empty (spectator atoms / pinned traps), this lets the beam score and select transitions that route through blocked sites or place qubits onto occupied locations, producing physically invalid ExecuteCZ plans despite returning the original state.occupied in the output.
Useful? React with 👍 / 👎.
f2e12d7 to
010b10c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 010b10c620
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| layout: tuple[LocationAddress, ...], | ||
| ) -> tuple[tuple[int, int], ...]: | ||
| """Compact ``(word_id, site_id)`` tuples for fast hashing/equality.""" | ||
| return tuple((loc.word_id, loc.site_id) for loc in layout) |
There was a problem hiding this comment.
Include zone_id in layout hashing keys
_layout_locations strips each address down to (word_id, site_id), but LocationAddress identity also includes zone_id. In architectures with one CZ zone plus additional storage/transport zones, two legal layouts that differ only by zone will collide in this key, which then poisons beam dedup (best_by_layout), oracle cache keys in _oracle_move_layers, and the duplicate-location guard that uses set(from_keys)/set(to_keys). This can incorrectly discard valid candidates or reuse move-layer results from a different zone transition, producing wrong scoring/selection for multi-zone runs.
Useful? React with 👍 / 👎.
A Python-level placement strategy that preserves W=8 candidate layout trajectories across the lookahead window, scored by cum + cost + λ · next_stage_cost. Per trajectory, per stage, generates K=20 Hungarian-perturbed candidates plus the NoReturn result. Versus the 6 existing bloqade-lanes placement strategies on 8 standard QASM benchmark families (n=8): among strategies that complete a valid schedule on all 8 families, this one has the lowest aggregate lane count (354 vs NR's 437, -19.0%). The multi-candidate-preservation idea is adapted from a recent distributed-quantum-compiler paper (Athena, arXiv:2605.21795). Files: - python/bloqade/lanes/heuristics/physical/multi_candidate_lookahead.py (new, ~611 LOC) - python/tests/heuristics/test_multi_candidate_lookahead_placement.py (new, 177 LOC, 8 tests) - python/tests/heuristics/test_mcl_adversarial.py (new, 174 LOC, 7 tests) - python/benchmarks/harness/matrix.py (registers multi_candidate_lookahead) 15/15 new tests pass. 66/66 existing harness tests still pass. black + isort + ruff clean. pyright clean on new files (pre-existing upstream pyright error in arch/gemini/physical/spec.py is unrelated; SKIP=pyright-check used to bypass the project-wide hook).
Regenerated against the current main (post QuEraComputing#663 AOD-grouping and QuEraComputing#666 empty-lane fixes, which changed move-lane counts) so the committed baseline matches a fresh CI benchmark run. 9 physical rows + 3 logical rows for multi_candidate_lookahead, merged into the upstream baselines and re-sorted by (case_id, strategy_id).
Adds test_mcl_coverage.py targeting the MultiCandidateLookahead paths the smoke/adversarial suites missed, lifting patch coverage 76% -> 92%: - _hungarian_assignment: over-full matrix rejection, empty-pairs trivial candidate, and bystander-collision relocation (a non-participating qubit sitting on a slot the CZ pair is assigned to is moved to the nearest free slot, keeping the layout duplicate-free). - cz_placements guards: mismatched controls/targets -> bottom, non-ConcreteState -> top. - Multi-stage lookahead: a distinct next stage drives _lookahead_cost and the skip-repeated-stages scan. - _oracle_move_layers: cache hit on a repeated layout transition and rejection (without raising) of a duplicate-location layout.
010b10c to
99afb39
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 99afb39447
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| dedup = list(best_by_layout.values()) | ||
| dedup.sort(key=lambda e: e[-1]) | ||
| beam = [e[:-1] for e in dedup[: self.beam_width]] |
There was a problem hiding this comment.
Preserve the NoReturn fallback through beam pruning
When lookahead_cz_layers is supplied and beam_width is small, this score-based truncation can discard the unconditional NoReturn candidate before the final selection. A candidate with a more expensive current move but a lower lookahead estimate will sort ahead of NR, so the returned first-stage move_layers can exceed the plain NoReturnPlacementStrategy result despite the advertised safety-net invariant. Carry the NR trajectory outside the score-pruned beam or explicitly compare the committed first stage against the NR result before returning.
Useful? React with 👍 / 👎.
feat(physical): MultiCandidateLookaheadPlacementStrategy — multi-candidate beam-search placement
Abstract
Neutral-atom quantum compilers route qubits between zones for each CZ layer; atom movement is the dominant non-local cost. Existing placement strategies in bloqade-lanes — NoReturn (NR), NoHome, RecedingHorizon (RH) from PR #595, plus the base PhysicalPlacementStrategy family (
rust_greedy,rust_entropy_*,rust_ids) — commit to one placement layout per stage (informed by a 4-5 stage Hungarian or rollout horizon) and advance. None of them preserves multiple candidate layouts across stages.We propose
MultiCandidateLookaheadPlacementStrategy(MCL) that preserves W = 8 candidate layout trajectories across the lookahead window. Per trajectory, per stage, MCL generates K = 20 Hungarian-perturbed candidates plus the NoReturn result, scores each byscore = cum + cost + λ · next_stage_cost, and keeps the top-W. The committed first-stage layout is taken from the trajectory with the lowest cumulative cost.Evaluation on 8 standard QASM benchmark families (n = 8 qubits) vs 6 existing bloqade-lanes strategies, with per-stage CZ-pair validity checked: among strategies that complete a valid schedule on all 8 families, MCL has the lowest aggregate lane count (354 lanes, vs NR's 437). RH fails mid-schedule on 4 of 8 families.
1. Introduction
1.1 Existing strategies: what they share
bloqade-lanescurrently offers six placement strategies:NoReturn(NR)NoHomeRecedingHorizon(RH)rust_greedyrust_entropy_*rust_ids/rust_astar/ etc.All six commit one layout per stage (NR/NoHome) or three layers from one rollout (RH). RH evaluates K=5 rollouts but only commits one; the K-1 alternatives are discarded as soon as RH picks a winner.
1.2 Cross-stage cascade requires preserved alternatives
In cascade circuits, the optimal commit at stage N depends on structure that appears 5 to 20 stages ahead. We measured this on
qugann=8 by sweeping the lookahead horizon K (with our multi-candidate beam, so the "no preservation" confound is excluded):Extending the horizon alone is not enough. If we run our own strategy with
beam_width=1(a single trajectory) butlookahead_window=24, the result is no better than NR's 4-stage horizon. Single-trajectory strategies must commit to one layout per stage and discard alternatives — including alternatives where a higher-immediate-cost commit would unlock zero-cost stages downstream.Concrete example on QFT n=8: the layout that satisfies stages 14–28 "naturally" (each pair already at CZ-partner positions) requires a non-trivial commit at stage 12 with cost 6, vs the locally cheapest commit costing 4 but forcing 4–5 lane moves at every subsequent stage. The cheap local choice adds +20 cumulative lanes over the next 15 stages. None of the upstream strategies keep the higher-immediate-cost alternative alive long enough to see this payoff.
1.3 Our approach
We adapt a multi-candidate-preservation pattern from a recent distributed-quantum-compiler paper I worked on, Athena [1]. Athena's UMS (Utility-driven Multi-Candidate Block Scheduling) keeps the Top-w block schedules alive during compilation to defer commitment, rather than committing locally and discarding alternatives. The same idea fits this placement problem: instead of preserving multiple block schedules, MCL preserves multiple physical layout trajectories per CZ stage.
Per
cz_placementscall:Three building blocks:
W = 8 candidate trajectories kept alive across the 24-stage lookahead window. This is the Athena-inspired component.
K = 20 Hungarian-perturbed candidates per trajectory per stage. Uses a 2× cost-matrix (each slot pair × orientation as a separate column) so Hungarian sees orientation as a decision variable.
NoReturnPlacementStrategyresult added as one additional candidate per trajectory per stage. NR's Rust loose-goal solver produces layouts that the Python-level Hungarian K=20 set sometimes misses, so we include its result in the candidate pool.Single fixed configuration used everywhere:
W = 8, K = 20, λ = 2.0, lookahead_window = 24.2. Algorithm
2.1 Public interface
Per-call computational cost:
O(stages_in_window × W × K)extra solver-oracle calls. For typical 50-stage circuits at defaultW=8, K=20: 30 - 90 s compile time. RSS stays ~430 MB independent of W (beam state is negligible compared to bloqade-lanes baseline).3. Results
3.1 Full strategy roster — 8 standard QASM benchmark families (n = 8 qubits)
All seven strategies run end-to-end on the same initial layout per family. Every committed stage's input layout is checked for CZ-pair alignment (qubits at CZ-partner slot positions, the necessary condition for the CZ to physically execute). Stages that don't satisfy this are counted as invalid.
* INC = strategy fails mid-schedule (returns non-
ExecuteCZ/NotState). RH'ssolve_entangling_rhcrashes on these 4 families; its lane count for those is a partial schedule that does not actually compile.Among strategies that complete a valid schedule on all 8 families (NR, NoHome, rust_greedy, rust_entropy_5, rust_ids, MCL), MCL has the lowest aggregate lane count (354). RH completes on only 4 of 8 families.
3.2 Head-to-head with NR
NR is the existing baseline with the lowest lane count among strategies that complete on all 8 families. MCL vs NR:
3.3 Head-to-head with RH (where it completes)
RH completes a valid schedule on only 4 of 8 families. On those 4,
MCL vs RH:
3 W / 1 T / 0 L on the four RH-completed families.
3.4 Runtime and memory
Per-family runtime at default
W=8, K=20:Linear in
stages × W × K. Peak RSS ~430 MB (W-invariant on regular circuits).4. Files
python/bloqade/lanes/heuristics/physical/multi_candidate_lookahead.py(new, ~600 LOC)python/tests/heuristics/test_multi_candidate_lookahead_placement.py(new, 169 LOC, 8 tests)python/benchmarks/harness/matrix.py(+16 LOC, registersmulti_candidate_lookaheadstrategy)5. Test plan
pytest python/tests/heuristics/test_multi_candidate_lookahead_placement.py -v— 8/8 PASSpytest python/tests/benchmarks/— 66/66 PASS (existing tests unaffected)black --check,isort --check,ruff check,pyright— all clean6. Composition with existing components
MCL is a meta-strategy that composes existing bloqade-lanes components:
NoReturnPlacementStrategy(PR feat(heuristics): no-return placement family + loose-goal Rust solver #595) as an additional candidate per stagelinear_sum_assignmentfor Hungarian candidatescompute_move_layersfor candidate evaluationNo new Rust code. MCL is opt-in via explicit instantiation;
default
squin_to_move(...)users are unaffected unless they passplacement_strategy=MultiCandidateLookaheadPlacementStrategy(...).7. Related work and credits
The multi-candidate-preservation idea is brought in from a recent paper I worked on, Athena [1] (arXiv:2605.21795). Athena is a compiler for distributed quantum computers; it tackles a different problem (scheduling non-local CNOT blocks across chips) but solves it with an idea that turns out to be useful here too. Quoting the mechanism:
In this PR I apply the same idea at the CZ-stage layout level — instead of preserving multiple block schedules I preserve multiple physical layout trajectories per stage. The Top-w pruning and deferred commitment carry over directly.
8. Future work (out of scope for this PR)
Rust port. MCL is currently pure Python (~600 LOC) and composes existing Rust-backed components. Runtime at default
W=8, K=20is 30 - 90 s for typical 50-stage circuits. A future PR can port the beam loop, Hungarian assignment, and scoring into Rust following PR #595's pattern (Python thin wrapper → Rust solver).