Repository navigation
Conversation
CI Summary (
|
| Job | Result |
|---|---|
binaries |
⏩ skipped |
build_and_test |
✅ success |
changes |
✅ success |
config_devdeps |
✅ success |
config_source_build |
⏩ skipped |
config_wheeldeps |
✅ success |
devdeps |
✅ success |
docker_image |
⏩ skipped |
documentation |
✅ success |
gen_code_coverage |
⏩ skipped |
metadata |
✅ success |
python_devel_wheel |
⏩ skipped |
python_metapackages |
⏩ skipped |
python_wheels |
⏩ skipped |
python_wheels_cu12_validation |
⏩ skipped |
python_wheels_cu13 |
⏩ skipped |
source_build |
⏩ skipped |
wheeldeps |
✅ success |
⏩ Skipped jobs (10) — intentionally skipped on PR builds; run on merge_group / workflow_dispatch
| Job |
|---|
binaries |
config_source_build |
docker_image |
gen_code_coverage |
python_devel_wheel |
python_metapackages |
python_wheels_cu12_validation |
python_wheels_cu13 |
python_wheels |
source_build |
All sub-jobs (47) — every matrix leg, with links
| Job | Status | Link |
|---|---|---|
| Build and test (amd64, gcc12, openmpi) / Dev environment (Debug) | ✅ success | view |
| Build and test (amd64, gcc12, openmpi) / Dev environment (Python) | ✅ success | view |
| Build and test (amd64, llvm, openmpi) / Dev environment (Debug) | ✅ success | view |
| Build and test (amd64, llvm, openmpi) / Dev environment (Python) | ✅ success | view |
| Build and test (arm64, llvm, openmpi) / Dev environment (Debug) | ✅ success | view |
| Build and test (arm64, llvm, openmpi) / Dev environment (Python) | ✅ success | view |
| CI Summary | ❔ in_progress | view |
| Check for stable CUDA-Q changes | ✅ success | view |
| Configure build (devdeps) | ✅ success | view |
| Configure build (source_build) | ⏩ skipped | view |
| Configure build (wheeldeps) | ✅ success | view |
| Create CUDA 12 Python wheels | ⏩ skipped | view |
| Create CUDA 13 Python wheels | ⏩ skipped | view |
| Create CUDA Quantum installer | ⏩ skipped | view |
| Create Docker images | ⏩ skipped | view |
| Create Python metapackages | ⏩ skipped | view |
| Create shared Python development wheel | ⏩ skipped | view |
| Documentation / Build and check docs | ✅ success | view |
| Gen code coverage | ⏩ skipped | view |
| Load dependencies (amd64, gcc12) / Caching | ✅ success | view |
| Load dependencies (amd64, gcc12) / Finalize | ✅ success | view |
| Load dependencies (amd64, gcc12) / Metadata | ✅ success | view |
| Load dependencies (amd64, llvm) / Caching | ✅ success | view |
| Load dependencies (amd64, llvm) / Finalize | ✅ success | view |
| Load dependencies (amd64, llvm) / Metadata | ✅ success | view |
| Load dependencies (arm64, gcc12) / Caching | ✅ success | view |
| Load dependencies (arm64, gcc12) / Finalize | ✅ success | view |
| Load dependencies (arm64, gcc12) / Metadata | ✅ success | view |
| Load dependencies (arm64, llvm) / Caching | ✅ success | view |
| Load dependencies (arm64, llvm) / Finalize | ✅ success | view |
| Load dependencies (arm64, llvm) / Metadata | ✅ success | view |
| Load source build cache | ⏩ skipped | view |
| Load wheel dependencies (amd64, 12.6) / Caching | ✅ success | view |
| Load wheel dependencies (amd64, 12.6) / Finalize | ✅ success | view |
| Load wheel dependencies (amd64, 12.6) / Metadata | ✅ success | view |
| Load wheel dependencies (amd64, 13.0) / Caching | ✅ success | view |
| Load wheel dependencies (amd64, 13.0) / Finalize | ✅ success | view |
| Load wheel dependencies (amd64, 13.0) / Metadata | ✅ success | view |
| Load wheel dependencies (arm64, 12.6) / Caching | ✅ success | view |
| Load wheel dependencies (arm64, 12.6) / Finalize | ✅ success | view |
| Load wheel dependencies (arm64, 12.6) / Metadata | ✅ success | view |
| Load wheel dependencies (arm64, 13.0) / Caching | ✅ success | view |
| Load wheel dependencies (arm64, 13.0) / Finalize | ✅ success | view |
| Load wheel dependencies (arm64, 13.0) / Metadata | ✅ success | view |
| Prepare cache clean-up | ❔ in_progress | view |
| Retrieve PR info | ✅ success | view |
| Validate CUDA 12 Python wheels | ⏩ skipped | view |
✅ Required checks (7/7) — declared in .github/required-checks.yml for push
| Required check | Status | Link |
|---|---|---|
| Build and test (amd64, llvm, openmpi) / Dev environment (Debug) | ✅ success | view |
| Build and test (amd64, llvm, openmpi) / Dev environment (Python) | ✅ success | view |
| Build and test (arm64, llvm, openmpi) / Dev environment (Debug) | ✅ success | view |
| Build and test (arm64, llvm, openmpi) / Dev environment (Python) | ✅ success | view |
| Build and test (amd64, gcc12, openmpi) / Dev environment (Debug) | ✅ success | view |
| Build and test (amd64, gcc12, openmpi) / Dev environment (Python) | ✅ success | view |
| Documentation / Build and check docs | ✅ success | view |
9285e23 to
b23d223
Compare
| bool computed = false; | ||
| for (Operation *op = m->getPrevNode(); op && !computed; | ||
| op = op->getPrevNode()) { | ||
| if (!op->hasTrait<cudaq::QuantumGate>()) | ||
| continue; | ||
| auto gate = dyn_cast<cudaq::quake::OperatorInterface>(op); | ||
| if (!gate) | ||
| continue; | ||
| for (Value t : gate.getTargets()) | ||
| if (auto k = wireKeyOf(t); k && *k == *key) { | ||
| computed = true; | ||
| break; | ||
| } | ||
| } | ||
| if (!computed) { |
There was a problem hiding this comment.
Seems like the whole method assumes the measured qubit enters the kernel in |0>. The check only asks whether any earlier gate in the kernel targets that qubit. If the qubit is a kernel argument holding real user state, the check passes anyway. Then, with the control off, the measurement (never controlled) collapses the user's qubit and the reset sets it to |0>.
This will fail if a kernel takes a qubit, applies x, measures it, and resets on 1. Run it under a control qubit that stays |0>, with the target in |+>. The result should be unchanged, but the target will come back as |0>.
Should we accept only measured qubits that come from a quake.alloca inside the kernel? If the qview-ancilla style in the tests must keep working, let's state the "caller passes the ancilla in |0>" rule in the the docs?
There was a problem hiding this comment.
You're right, that case was unsound, thanks. The "earlier gate writes it" test is replaced by the rule you suggested: the measured ref must trace to a quake.alloca inside the kernel (directly or via a constant-index extract, through relax_size). A measured kernel argument is now rejected with "a measurement of a qubit the kernel does not allocate itself". Two tests pin it: the bare erase on a qview element, and your scenario verbatim (test_measured_argument_with_user_state_is_rejected: argument in |+⟩, x; mx; reset-on-1, control off). The tests' qview-ancilla style is gone; they allocate the ancilla in the kernel.
schweitzpgi
left a comment
There was a problem hiding this comment.
Looks like there is a general fold being added as a rando side-effect.
b23d223 to
5c3ecf1
Compare
createControlVariantOf added the new control to every op carrying the QuantumGate trait and silently skipped measurements. For a kernel that measures, the result is wrong rather than merely unsupported: the basis change feeding the measurement and the outcome-conditioned reset of the measured qubit acquire the control, so the measurement outcome distribution depends on the control and each trajectory returns a different, non-physical state (issue NVIDIA#5257). Mirror the guard createAdjointVariantOf already has: fail with a diagnostic (or plain failure under legacy-classical) when the kernel contains a measurement, and thread the failure through step1 and the adjoint-control path by returning FailureOr<func::FuncOp>. The runtime trap message that surfaces such failures no longer claims the adjoint was at fault. Tests: a verify-diagnostics case for the controlled and adjoint-controlled variants, and a Python test asserting cudaq.control over a measuring kernel raises instead of running. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
5c3ecf1 to
f4b32da
Compare
A measurement cannot take a control and is left unconditioned; for the controlled kernel to be correct, everything the measurement idiom needs to execute identically in both control branches must stay unconditioned too. Instead of rejecting every measuring kernel, recognize the measured-uncomputation shape and control it correctly: - The measured qubit is allocated by the kernel itself (`quake.alloca`, directly or through a constant-index extract), so with the control off it is still |0> when measured. - It is measured in the X or Y basis (`quake.mx`/`quake.my`): of |0> that outcome is a fair coin that reveals nothing about the control. A Z-basis measurement of |0> is deterministic, so its outcome would reveal the control; `quake.mz` is rejected. - The outcome feeds only a `cc.if` in the same block. Inside it, a gate whose only target is the measured qubit (its reset) stays unconditioned; every other gate receives the control as usual, giving (outcome AND control). Any other shape -- a measurement inside a conditional or a loop, of a register, of a dynamically indexed qubit, or of a qubit the kernel did not allocate, an outcome used other than as that `cc.if` condition, or nested control flow, calls or measurements inside its body -- is still rejected with a diagnostic naming the reason. Straight-line `cc.scope` nesting is allowed. Generated variants carry the source kernel's location so diagnostics on them (e.g. the adjoint of a measuring kernel, which remains undefined) point at the user's kernel. Tests: FileCheck for the accepted shape in both bases (compute and payload controlled, measurement and reset not) and for each rejected shape; a C++ frontend test through memtoreg; Python tests comparing the controlled block against the controlled unitary reference over seeded trajectories at control |0>, |1> and superposed, plus the rejections. Closes NVIDIA#5257. Signed-off-by: Scott Thornton <wsttiger@gmail.com>
f4b32da to
1b3aec4
Compare
|
According to the CUDA-Q Spec, § 7, ¶ 4 there aren't really any defined limitations or correctness considerations specified for I do think |
|
Decided to go in a different direction (for now) |
Closes #5257.
Problem
cudaq.control/cudaq::controlover a kernel containing a mid-circuit measurement was silentlyaccepted and produced trajectory-dependent, non-physical states.
createControlVariantOfadds thenew control to every op carrying the
QuantumGatetrait and skips measurements (which carry no suchtrait). For the measured-uncomputation (MBU) idiom
that conditions the wrong things: the outcome-conditioned reset
x(anc)acquires the control, sothe ancilla is left in |m⟩ when the control is off, and (in the
h; mzspelling) the basis changeacquires it too, so the measurement is taken in a control-dependent basis. The outcome distribution
then depends on the control, each outcome partially collapses it, and every
get_statecall followsone random trajectory. Measured, 12 seeded trajectories, max pairwise distance with global phase
removed: 0.000 uncontrolled, 1.3 under
cudaq.control, 2e-16 with this change.Semantics
A measurement cannot take a control and is left unconditioned. For the controlled kernel to be
correct, everything the idiom needs to execute identically in both control branches must stay
unconditioned too, and the unconditioned measurement must reveal nothing about the control. The
pass accepts exactly the shape where that holds, and controls it:
quake.allocain the kernel (directly orthrough a constant-index
quake.extract_ref). With the control off nothing has acted on it, soit is |0⟩ when measured. Measuring a qubit the caller passed in is rejected: the uncontrolled
measurement would collapse caller state the control-off branch must preserve.
quake.mx/quake.myonly. Of |0⟩ these are fair coins, so theoutcome is independent of the control.
quake.mzof |0⟩ is deterministic and would reveal thecontrol, so it is rejected (spell the erase as
mx, ormywith the matching feedforward).cc.ifin the measurement'sblock. Inside it, a gate whose only target is the measured qubit (its reset) stays unconditioned;
every other gate receives the control, i.e. (outcome AND control).
Anything else is rejected with a diagnostic naming the reason, never guessed: a measurement
inside a conditional or loop, of a register, of a dynamically indexed qubit, or of a qubit the
kernel did not allocate; an outcome used other than as that
cc.ifcondition; nested control flow,calls or measurements inside its body. Straight-line
cc.scopenesting (as emitted by the Pythonfrontend) is fine. The adjoint-controlled variant of a measuring kernel remains rejected (the
adjoint of a measurement is undefined); generated variants carry the source kernel's location so
diagnostics point at the user's kernel.
This makes control of MBU blocks correct, not gate-optimal; constructions that fold the control
into the compute remain cheaper and are unaffected.
Commits
createAdjointVariantOfalready has (hasMeasureOp), returningFailureOr<func::FuncOp>andthreading the failure through
step1and the adjoint-control path. The runtime trap message thatsurfaces specialization failures no longer claims the adjoint was at fault. Independently landable.
Tests
test/Optimizer/apply-measurement.qke: the accepted shape in both bases (mxon aquake.allocaref,
myon a constant extract of a localveq<1>): compute and payload controlled, measurementand reset unconditioned, feedforward controlled.
test/Optimizer/apply-measurement-reject.qke: Z-basis, argument qubit, register, escapingoutcome, dynamic index, adjoint-controlled.
test/Frontend/control_measurement.cpp: the C++ bridge's shape (cudaq::qubitmember, handlespilled through
cc.alloca) throughmemtoreg{quantum=0},canonicalize, as the default targetpipelines run before
apply-op-specialization.python/tests/kernel/test_control_measurement.py(13 tests): the controlled MBU block equals thecontrolled unitary reference (CCZ payload; and, with no payload, the two-Toffoli identity) to
1e-10 on every seeded trajectory at control |0⟩, |1⟩ and
ry(0.7), formxandmy; Z-basis,argument-qubit (bare erase, and a flip-measure-reset on a |+⟩ argument) and adjoint cases raise.