Skip to content

[core] Don't let DQE eliminate an entry point's last allocation - #5135

Closed
mitchdz wants to merge 1 commit into
NVIDIA:mainfrom
mitchdz:fix-dqe-entry-point-register
Closed

[core] Don't let DQE eliminate an entry point's last allocation#5135
mitchdz wants to merge 1 commit into
NVIDIA:mainfrom
mitchdz:fix-dqe-entry-point-register

Conversation

@mitchdz

@mitchdz mitchdz commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

A kernel that allocates qubits but applies no operation to them loses its register entirely. sample() returns no counts at all and get_state() segfaults dereferencing a null SimulationState:

@cudaq.kernel
def allocate_only(n: int):
    q = cudaq.qvector(n)

cudaq.sample(allocate_only, 3)     # {} instead of { 000:1000 }
cudaq.get_state(allocate_only, 3)  # SIGSEGV

DQE erases an alloca whose only use is a dealloc, and a null_wire whose only use is a sink. That is sound when nothing observes the register, but an entry point's register is observable: sample() implicitly measures every allocated qubit and get_state() returns the whole register. Since #4993 enabled value semantics for local simulators as well, such a kernel now reaches the runtime with no qubits at all.

Keep the allocation when it is the last one in a cudaq-entrypoint function. Partially used registers are unaffected, and dead qubits in non-entry-point functions are still eliminated.

Adds a FileCheck test for both halves of that rule and an execution test asserting the observable contract. Both fail without the fix.

A kernel that allocates qubits but applies no operation to them loses its
register entirely. sample() returns no counts at all and get_state()
segfaults dereferencing a null SimulationState:

    @cudaq.kernel
    def allocate_only(n: int):
        q = cudaq.qvector(n)

    cudaq.sample(allocate_only, 3)     # {} instead of { 000:1000 }
    cudaq.get_state(allocate_only, 3)  # SIGSEGV

DQE erases an alloca whose only use is a dealloc, and a null_wire whose
only use is a sink. That is sound when nothing observes the register, but
an entry point's register is observable: sample() implicitly measures every
allocated qubit and get_state() returns the whole register. Since NVIDIA#4993
enabled value semantics for local simulators as well, such a kernel now
reaches the runtime with no qubits at all.

Keep the allocation when it is the last one in a cudaq-entrypoint function.
Partially used registers are unaffected, and dead qubits in non-entry-point
functions are still eliminated.

Adds a FileCheck test for both halves of that rule and an execution test
asserting the observable contract. Both fail without the fix.

Signed-off-by: mdzurick <mitch_dz@hotmail.com>
@github-actions github-actions Bot added python-lang Anything related to the Python CUDA Quantum language implementation core compiler labels Aug 13, 2026
@github-actions

Copy link
Copy Markdown

CI Summary (push) — ❌ failed

Run #31724679744 · ✅ 5 · ⏩ 7 · ❌ 1 · ⛔ 0

❌ Failed or cancelled
Job Result Link
build_and_test ❌ failure view
Top-level jobs (13)
Job Result
binaries ⏩ skipped
build_and_test ❌ failure
config_devdeps ✅ success
config_source_build ⏩ skipped
config_wheeldeps ✅ success
devdeps ✅ success
docker_image ⏩ skipped
gen_code_coverage ⏩ skipped
metadata ✅ success
python_metapackages ⏩ skipped
python_wheels ⏩ skipped
source_build ⏩ skipped
wheeldeps ✅ success
⏩ Skipped jobs (7) — intentionally skipped on PR builds; run on merge_group / workflow_dispatch
Job
binaries
config_source_build
docker_image
gen_code_coverage
python_metapackages
python_wheels
source_build
All sub-jobs (42) — every matrix leg, with links
Job Status Link
Build and test (amd64, gcc12, openmpi) / Dev environment (Debug) ❌ failure view
Build and test (amd64, gcc12, openmpi) / Dev environment (Python) ❌ failure view
Build and test (amd64, llvm, openmpi) / Dev environment (Debug) ❌ failure view
Build and test (amd64, llvm, openmpi) / Dev environment (Python) ❌ failure view
Build and test (arm64, llvm, openmpi) / Dev environment (Debug) ❌ failure view
Build and test (arm64, llvm, openmpi) / Dev environment (Python) ❌ failure view
CI Summary ❔ in_progress view
Configure build (devdeps) ✅ success view
Configure build (source_build) ⏩ skipped view
Configure build (wheeldeps) ✅ success view
Create CUDA Quantum installer ⏩ skipped view
Create Docker images ⏩ skipped view
Create Python metapackages ⏩ skipped view
Create Python wheels ⏩ skipped 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 ✅ success view
Retrieve PR info ✅ success view
⚠️ Required checks (0/6) — 6 missing — declared in .github/required-checks.yml for push
Required check Status Link
Build and test (amd64, llvm, openmpi) / Dev environment (Debug) ❌ failure view
Build and test (amd64, llvm, openmpi) / Dev environment (Python) ❌ failure view
Build and test (arm64, llvm, openmpi) / Dev environment (Debug) ❌ failure view
Build and test (arm64, llvm, openmpi) / Dev environment (Python) ❌ failure view
Build and test (amd64, gcc12, openmpi) / Dev environment (Debug) ❌ failure view
Build and test (amd64, gcc12, openmpi) / Dev environment (Python) ❌ failure view

@schweitzpgi

Copy link
Copy Markdown
Collaborator

Let's land #5113 first to see if that doesn't fix the issue.

@sacpis sacpis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM. Thanks @mitchdz.

@sacpis

sacpis commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Seems like we have a test failure

#12 199.5 FAIL: CUDAQ :: Transforms/lambda_variable-2.qke (304 of 465)
#12 199.5 ******************** TEST 'CUDAQ :: Transforms/lambda_variable-2.qke' FAILED ********************
#12 199.5 Exit Code: 1
#12 199.5 
#12 199.5 Command Output (stderr):
#12 199.5 --
#12 199.5 RUN: at line 9: cudaq-opt -lambda-lifting=constant-prop=1 -canonicalize /workspaces/cuda-quantum/cudaq/test/Transforms/lambda_variable-2.qke |    /usr/local/llvm/bin/FileCheck /workspaces/cuda-quantum/cudaq/test/Transforms/lambda_variable-2.qke
#12 199.5 + cudaq-opt -lambda-lifting=constant-prop=1 -canonicalize /workspaces/cuda-quantum/cudaq/test/Transforms/lambda_variable-2.qke
#12 199.5 + /usr/local/llvm/bin/FileCheck /workspaces/cuda-quantum/cudaq/test/Transforms/lambda_variable-2.qke
#12 199.5 RUN: at line 11: cudaq-opt -lambda-lifting=constant-prop=1 -canonicalize /workspaces/cuda-quantum/cudaq/test/Transforms/lambda_variable-2.qke |    cudaq-translate --convert-to=qir | /usr/local/llvm/bin/FileCheck --check-prefix=QIR /workspaces/cuda-quantum/cudaq/test/Transforms/lambda_variable-2.qke
#12 199.5 + cudaq-opt -lambda-lifting=constant-prop=1 -canonicalize /workspaces/cuda-quantum/cudaq/test/Transforms/lambda_variable-2.qke
#12 199.5 + /usr/local/llvm/bin/FileCheck --check-prefix=QIR /workspaces/cuda-quantum/cudaq/test/Transforms/lambda_variable-2.qke
#12 199.5 + cudaq-translate --convert-to=qir
#12 199.5 /workspaces/cuda-quantum/cudaq/test/Transforms/lambda_variable-2.qke:102:14: error: QIR-NEXT: is not on the line after the previous match
#12 199.5 // QIR-NEXT: ret void
#12 199.5              ^
#12 199.5 <stdin>:14:2: note: 'next' match was here
#12 199.5  ret void
#12 199.5  ^
#12 199.5 <stdin>:11:42: note: previous match ended here
#12 199.5 define void @__nvqpp__mlirgen__kernel_a() local_unnamed_addr {
#12 199.5                                          ^
#12 199.5 <stdin>:12:1: note: non-matching line after previous match is here
#12 199.5  %1 = tail call ptr @__quantum__rt__qubit_allocate()
#12 199.5 ^
#12 199.5 
#12 199.5 Input file: <stdin>
#12 199.5 Check file: /workspaces/cuda-quantum/cudaq/test/Transforms/lambda_variable-2.qke
#12 199.5 
#12 199.5 -dump-input=help explains the following input dump.
#12 199.5 
#12 199.5 Input was:
#12 199.5 <<<<<<
#12 199.5           .
#12 199.5           .
#12 199.5           .
#12 199.5           9: } 
#12 199.5          10:  
#12 199.5          11: define void @__nvqpp__mlirgen__kernel_a() local_unnamed_addr { 
#12 199.5          12:  %1 = tail call ptr @__quantum__rt__qubit_allocate() 
#12 199.5          13:  tail call void @__quantum__rt__qubit_release(ptr %1) 
#12 199.5          14:  ret void 
#12 199.5 next:102      !~~~~~~~  error: match on wrong line
#12 199.5          15: } 
#12 199.5          16:  
#12 199.5          17: declare ptr @__quantum__rt__qubit_allocate() local_unnamed_addr 
#12 199.5          18:  
#12 199.5          19: declare void @__quantum__rt__qubit_release(ptr) local_unnamed_addr 
#12 199.5           .
#12 199.5           .
#12 199.5           .
#12 199.5 >>>>>>
#12 199.5 
#12 199.5 --
#12 199.5 
#12 199.5 ********************

Seems like the test failure is a correct behavior. kernel_a is an entry point whose only qubit allocation is unused. Before this change, DQE deleted it and the QIR body was just ret void. With the new guard, the allocation is kept, so the QIR now has an allocate/release pair before the return. The test's expectation is written against the old behavior.
Let's update this test.

@mitchdz

mitchdz commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

It looks like #5113 fixed this issue.

@mitchdz mitchdz closed this Aug 18, 2026
github-actions Bot pushed a commit that referenced this pull request Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core compiler python-lang Anything related to the Python CUDA Quantum language implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants