Skip to content

[fix] Move codegen out of QPU::launchKernel - #5770

Merged
lmondada merged 6 commits into
NVIDIA:mainfrom
lmondada:lm/fix-compile-target-qpu-fetch
Oct 30, 2026
Merged

lmondada merged 6 commits into
NVIDIA:mainfrom
lmondada:lm/fix-compile-target-qpu-fetch

Conversation

@lmondada

Copy link
Copy Markdown
Collaborator

These changes were originally motivated by a bug that @cabreraam has pointed out in #5746, all credits go to him! Thank you!

The issue

#5005 introduced the possibility to define a custom CompileTarget and store it directly in the platform. However, the QPUs were not consulting the platform when getting a hold of the current CompileTarget instance, which is needed to drive codegen within QPU::launchKernel. As a result, the codegen config was still derived from the QPU's compile target object, even if it had been overridden in the quantum platform.

The solution

QPU::launchKernel shouldn't need to know about CompileTarget: it should only concern itself with runtime matters and leave compiler-y stuff to the compiler. Instead of adding a way of reaching back from the QPU into the platform, the 'right' thing to do is to move codegen out of QPU::launchKernel.

This is what this PR does: codegen is now run right after the compilation pipeline, within the JIT compiler's Compiler::compile method. The codegen-ed code is stored in CompiledModule as a string. This gets passed to QPU::launchKernel, which instead of running codegen can simply read that string and proceed with launching the kernel as before.

Whilst I was modifying this code, I've made some clean ups and renames:

  • refactored get_compile_options to reuse the same logic in multiple places
  • renamed QPU::unifiedLaunchModule to QPU::compileAndLaunchKernel. This describes much better what it does and makes the relationship to the 'normal' launchKernel methods obvious. Its function signature also needed an update to take the compiler options explicitly as argument.
  • renamed Compiler::runPassPipeline to Compiler::compile. The method does much more than just running a pipeline.

Signed-off-by: Luca Mondada <luca@mondada.net>
Signed-off-by: Luca Mondada <luca@mondada.net>
Signed-off-by: Luca Mondada <luca@mondada.net>
@github-actions github-actions Bot added runtime CUDA quantum runtime python-lang Anything related to the Python CUDA Quantum language implementation testing Relates to testing labels Oct 29, 2026
@lmondada

lmondada commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator Author

This PR has already been reviewed and approved in #5755

@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 @lmondada.

@lmondada
lmondada enabled auto-merge October 29, 2026 12:49
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

CI Summary (push) — ✅ passed

Run #41016991800 · ✅ 8 · ⏩ 10 · ❌ 0 · ⛔ 0

Top-level jobs (18)
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

Signed-off-by: Luca Mondada <luca@mondada.net>
@lmondada
lmondada force-pushed the lm/fix-compile-target-qpu-fetch branch from 88f71f2 to 166e35f Compare October 29, 2026 13:28
Signed-off-by: Luca Mondada <luca@mondada.net>
Comment thread runtime/common/CompiledModule.h Resolved

@schweitzpgi schweitzpgi 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.

The size of this change is a bit surprising. I was thinking with all the work on unifying the launch process this would be more terse and just moving some compilation steps.

@lmondada
lmondada disabled auto-merge October 29, 2026 18:07
@lmondada

lmondada commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator Author

The size of this change is a bit surprising

Yeah, we never got to work on codegen (that's why it was still bundled in the launch). The diff in this PR is mostly due to clean-ups: name changes, breaking out code into separate functions etc. It's quite mechanical.

We keep getting closer to perfection, I guess 😅

Signed-off-by: Luca Mondada <luca@mondada.net>
@lmondada
lmondada force-pushed the lm/fix-compile-target-qpu-fetch branch from 1441ebb to dcaed50 Compare October 30, 2026 06:55
@lmondada
lmondada enabled auto-merge October 30, 2026 07:28
@lmondada
lmondada added this pull request to the merge queue Oct 30, 2026
@github-actions

Copy link
Copy Markdown

CI Summary (merge_group) — ✅ passed

Run #41024973119 · ✅ 5 · ⏩ 13 · ❌ 0 · ⛔ 0

⚡ Fast merge queue — only the llvm dev-environment build+test legs ran.
Wheels, metapackages, installers, Docker images, and the gcc12 legs were skipped.
Set the repository variable MERGE_QUEUE_FAST to false to restore the full matrix.

Top-level jobs (18)
Job Result
binaries ⏩ skipped
build_and_test ✅ success
changes ✅ success
config_devdeps ✅ success
config_source_build ⏩ skipped
config_wheeldeps ⏩ skipped
devdeps ✅ success
docker_image ⏩ skipped
documentation ⏩ skipped
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 ⏩ skipped
⏩ Skipped jobs (13) — intentionally skipped on PR builds; run on merge_group / workflow_dispatch
Job
binaries
config_source_build
config_wheeldeps
docker_image
documentation
gen_code_coverage
python_devel_wheel
python_metapackages
python_wheels_cu12_validation
python_wheels_cu13
python_wheels
source_build
wheeldeps
All sub-jobs (28) — every matrix leg, with links
Job 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
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) ⏩ skipped 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 ⏩ skipped view
Gen code coverage ⏩ skipped 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, 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 ⏩ skipped view
Prepare cache clean-up ❔ in_progress view
Retrieve PR info ✅ success view
Validate CUDA 12 Python wheels ⏩ skipped view
✅ Required checks (4/4) — declared in .github/required-checks.yml for merge_group_fast
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

Merged via the queue into NVIDIA:main with commit 81d578a Oct 30, 2026
87 checks passed
@lmondada
lmondada deleted the lm/fix-compile-target-qpu-fetch branch October 30, 2026 09:20
github-actions Bot pushed a commit that referenced this pull request Oct 30, 2026
anpaz pushed a commit to justinlietz/cuda-quantum that referenced this pull request Oct 30, 2026
These changes were originally motivated by a bug that @cabreraam has
pointed out in NVIDIA#5746, all credits go to him! Thank you!

# The issue

NVIDIA#5005 introduced the possibility to define a custom `CompileTarget` and
store it directly in the platform. However, the QPUs were not consulting
the platform when getting a hold of the current `CompileTarget`
instance, which is needed to drive codegen within `QPU::launchKernel`.
As a result, the codegen config was still derived from the QPU's compile
target object, even if it had been overridden in the quantum platform.

# The solution

`QPU::launchKernel` shouldn't need to know about `CompileTarget`: it
should only concern itself with runtime matters and leave compiler-y
stuff to the compiler. Instead of adding a way of reaching back from the
QPU into the platform, the 'right' thing to do is to move codegen out of
`QPU::launchKernel`.

This is what this PR does: codegen is now run right after the
compilation pipeline, within the JIT compiler's `Compiler::compile`
method. The codegen-ed code is stored in `CompiledModule` as a string.
This gets passed to `QPU::launchKernel`, which instead of running
codegen can simply read that string and proceed with launching the
kernel as before.

Whilst I was modifying this code, I've made some clean ups and renames:
- refactored get_compile_options to reuse the same logic in multiple
places
- renamed `QPU::unifiedLaunchModule` to `QPU::compileAndLaunchKernel`.
This describes much better what it does and makes the relationship to
the 'normal' `launchKernel` methods obvious. Its function signature also
needed an update to take the compiler options explicitly as argument.
- renamed `Compiler::runPassPipeline` to `Compiler::compile`. The method
does much more than just running a pipeline.

---------

Signed-off-by: Luca Mondada <luca@mondada.net>
taalexander added a commit to taalexander/cuda-quantum that referenced this pull request Oct 30, 2026
CUDA-Q Logical compiles a kernel that it imports directly, without a
launch, and needs that compile to use a Logical target's compile target
and endpoint, whatever target is installed. Until now Python could
compile a kernel only for the current platform, so a compile under an
installed emulated target was emulated, built a JIT engine, and used that
target's pipeline.

`cudaq_runtime.compile_module(kernel_name, module, is_entry_point,
compile_target=None, is_remote=None, is_emulated=None, supports_jit=None,
args=())` compiles without launching and returns a `CompiledModule`.
The traits describe a `RuntimeEndpoint`, and an omitted trait comes from
the current platform. `getCompileConfig` takes the compile target and that
endpoint, and resolves the options for the endpoint through a new
`quantum_platform::getCompileOptions(policy, seed, endpoint)`, which the
per-QPU overload now calls, so a compile for another endpoint follows the
same rules as a launch on it. Whether the compile substitutes the
arguments follows the compile target it is given, through
`specializesArguments`, so the packing always matches the compile.
`marshal_and_retain_module` shares this implementation with no target or
endpoint given, and still never runs code generation.
`CompiledModule.entry_point` is now `None` when the module carries no JIT
artifact, instead of dereferencing a missing one.

The binding stays internal. The public `cudaq.compile`, `save_temps`,
`load_build`, and the removal of Logical's `import_cudaq` remain in
NVIDIA#5660, which can rebase onto this commit.

This is Andres Paz's binding from NVIDIA#5660, adapted to
pass the specialization decision that step 3b added and the endpoint
resolution that NVIDIA#5770 added.

Tested through CUDA-Q Logical's direct-import tests in a following
commit, which compile with an emulated target installed and check that no
JIT engine is built.

Co-authored-by: Andres Paz <andresp@nvidia.com>
Signed-off-by: Thomas Alexander <talexander@nvidia.com>
taalexander added a commit to taalexander/cuda-quantum that referenced this pull request Oct 30, 2026
CUDA-Q Logical compiles a kernel that it imports directly, without a
launch, and needs that compile to use a Logical target's compile target
and endpoint, whatever target is installed. Until now Python could
compile a kernel only for the current platform, so a compile under an
installed emulated target was emulated, built a JIT engine, and used that
target's pipeline.

`cudaq_runtime.compile_module(kernel_name, module, is_entry_point,
compile_target=None, is_remote=None, is_emulated=None, supports_jit=None,
args=())` compiles without launching and returns a `CompiledModule`.
The traits describe a `RuntimeEndpoint`, and an omitted trait comes from
the current platform. `getCompileConfig` takes the compile target and that
endpoint, and resolves the options for the endpoint through a new
`quantum_platform::getCompileOptions(policy, seed, endpoint)`, which the
per-QPU overload now calls, so a compile for another endpoint follows the
same rules as a launch on it. Whether the compile substitutes the
arguments follows the compile target it is given, through
`specializesArguments`, so the packing always matches the compile.
`marshal_and_retain_module` shares this implementation with no target or
endpoint given, and still never runs code generation.
`CompiledModule.entry_point` is now `None` when the module carries no JIT
artifact, instead of dereferencing a missing one.

The binding stays internal. The public `cudaq.compile`, `save_temps`,
`load_build`, and the removal of Logical's `import_cudaq` remain in
NVIDIA#5660, which can rebase onto this commit.

This is Andres Paz's binding from NVIDIA#5660, adapted to
pass the specialization decision that step 3b added and the endpoint
resolution that NVIDIA#5770 added.

Tested through CUDA-Q Logical's direct-import tests in a following
commit, which compile with an emulated target installed and check that no
JIT engine is built.

Co-authored-by: Andres Paz <andresp@nvidia.com>
Signed-off-by: Thomas Alexander <talexander@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python-lang Anything related to the Python CUDA Quantum language implementation runtime CUDA quantum runtime testing Relates to testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants