diff --git a/python/cudaq/_experimental/runtime_endpoint.py b/python/cudaq/_experimental/runtime_endpoint.py index bde71fcf159..4f2fc6597bf 100644 --- a/python/cudaq/_experimental/runtime_endpoint.py +++ b/python/cudaq/_experimental/runtime_endpoint.py @@ -160,9 +160,11 @@ def estimate(self, module: CompiledModule, args: KernelArgs, **kwargs) -> EstimateResult: """Estimate the resources a compiled kernel would use. - Keyword arguments: ``choice``, a callable returning a `bool` that - resolves each measurement so that kernels branching on measurement - results take a definite path. + Keyword arguments include ``choice``, a callable returning a ``bool`` + that resolves each measurement so that kernels branching on measurement + results take a definite path. Additional keyword arguments passed to + :func:`cudaq.estimate` are forwarded unchanged for endpoint-specific + estimation options. """ ... diff --git a/python/cudaq/runtime/resource_count.py b/python/cudaq/runtime/resource_count.py index 6bd0638e893..f87f0f2402b 100644 --- a/python/cudaq/runtime/resource_count.py +++ b/python/cudaq/runtime/resource_count.py @@ -51,8 +51,11 @@ def estimate(kernel, *args, **kwargs): decorator = mk_decorator(kernel) processedArgs, module = decorator.prepare_call(*args) choice = kwargs.get("choice", None) + endpoint_options = { + key: value for key, value in kwargs.items() if key != "choice" + } return cudaq_runtime.estimate_impl(decorator.uniqName, module, choice, - *processedArgs) + endpoint_options, *processedArgs) @trace.traced diff --git a/python/runtime/cudaq/algorithms/py_resource_count.cpp b/python/runtime/cudaq/algorithms/py_resource_count.cpp index 9234d450c3b..e20d48e7a26 100644 --- a/python/runtime/cudaq/algorithms/py_resource_count.cpp +++ b/python/runtime/cudaq/algorithms/py_resource_count.cpp @@ -9,6 +9,7 @@ #include "py_resource_count.h" #include "common/Resources.h" #include "common/cudaq_json.h" +#include "runtime/cudaq/platform/PyRuntimeEndpoint.h" #include "runtime/cudaq/platform/py_alt_launch_kernel.h" #include "utils/JsonNanobindAdaptors.h" #include "utils/OpaqueArguments.h" @@ -25,7 +26,7 @@ using namespace cudaq; static estimate_result estimate_impl(const std::string &kernelName, MlirModule kernelMod, std::optional> choice, - nanobind::args args) { + nanobind::dict endpointOptions, nanobind::args args) { auto &platform = cudaq::get_platform(); args = simplifiedValidateInputArguments(args); @@ -47,6 +48,7 @@ estimate_impl(const std::string &kernelName, MlirModule kernelMod, estimate_policy policy{ .kernelName = kernelName, .choice = *std::move(choice), + .endpointOptions = makePythonEndpointOptions(std::move(endpointOptions)), }; return detail::launch(policy, 0, ctx, platform, [&]() { // Pass nullptr for the compiled slot to disable JIT-artifact caching: @@ -61,8 +63,10 @@ estimate_impl(const std::string &kernelName, MlirModule kernelMod, static Resources estimate_resources_impl(const std::string &kernelName, MlirModule kernelMod, std::optional> choice, - nanobind::args args) { - return estimate_impl(kernelName, kernelMod, choice, args).get_resources(); + nanobind::dict endpointOptions, nanobind::args args) { + return estimate_impl(kernelName, kernelMod, choice, + std::move(endpointOptions), args) + .get_resources(); } void cudaq::bindCountResources(nanobind::module_ &mod) { @@ -117,9 +121,12 @@ void cudaq::bindCountResources(nanobind::module_ &mod) { mod.def("estimate_impl", estimate_impl, nanobind::arg("kernel_name"), nanobind::arg("kernel_mod"), nanobind::arg("choice").none(), + nanobind::arg("endpoint_options") = nanobind::dict(), nanobind::arg("args"), "See python documentation for estimate."); mod.def("estimate_resources_impl", estimate_resources_impl, nanobind::arg("kernel_name"), nanobind::arg("kernel_mod"), - nanobind::arg("choice").none(), nanobind::arg("args"), + nanobind::arg("choice").none(), + nanobind::arg("endpoint_options") = nanobind::dict(), + nanobind::arg("args"), "See python documentation for estimate_resources."); } diff --git a/python/runtime/cudaq/platform/PyRuntimeEndpoint.cpp b/python/runtime/cudaq/platform/PyRuntimeEndpoint.cpp index a55e4528263..d0232a87ce8 100644 --- a/python/runtime/cudaq/platform/PyRuntimeEndpoint.cpp +++ b/python/runtime/cudaq/platform/PyRuntimeEndpoint.cpp @@ -158,6 +158,25 @@ struct PyKernelArgs { template struct PyProtocol {}; +/// Python-owned options that are opaque to core launch policies. +struct PythonEndpointOptions final : endpoint_options { + explicit PythonEndpointOptions(nanobind::dict options) + : options(std::move(options)) {} + + nanobind::dict options; +}; + +template +static void appendEndpointOptions(const Policy &policy, + nanobind::dict &kwargs) { + auto options = + std::dynamic_pointer_cast(policy.endpointOptions); + if (!options) + return; + for (auto [key, value] : options->options) + kwargs[key] = value; +} + template <> struct PyProtocol { static constexpr const char *Method = "sample"; @@ -261,6 +280,7 @@ pyLaunch(std::any &impl, const Policy &policy, const CompiledModule &module, nanobind::rv_policy::move); auto kwargs = Protocol::kwargs(policy); + appendEndpointOptions(policy, kwargs); auto result = obj.attr(Protocol::Method)(pyModule, pyArgs, **kwargs); if (!nanobind::isinstance(result)) { @@ -278,6 +298,25 @@ pyLaunch(std::any &impl, const Policy &policy, const CompiledModule &module, return nanobind::cast(result); } +std::shared_ptr +cudaq::makePythonEndpointOptions(nanobind::dict options) { + if (options.empty()) + return {}; + + auto optionsDestructor = +[](endpoint_options *base) { + auto *options = static_cast(base); + if (!Py_IsInitialized()) { + (void)options->options.release(); + delete options; + return; + } + nanobind::gil_scoped_acquire gil; + delete options; + }; + return std::shared_ptr( + new PythonEndpointOptions(std::move(options)), optionsDestructor); +} + static bool getAttrOrDefault(const nanobind::object &obj, const char *attr, bool defaultValue) { if (nanobind::hasattr(obj, attr)) diff --git a/python/runtime/cudaq/platform/PyRuntimeEndpoint.h b/python/runtime/cudaq/platform/PyRuntimeEndpoint.h index e4a0a024edc..43ae5e7ba9c 100644 --- a/python/runtime/cudaq/platform/PyRuntimeEndpoint.h +++ b/python/runtime/cudaq/platform/PyRuntimeEndpoint.h @@ -9,6 +9,7 @@ #pragma once #include "cudaq/Target/RuntimeEndpoint.h" +#include #include namespace cudaq { @@ -16,4 +17,8 @@ namespace cudaq { /// Create python bindings for C++ code in this compilation unit. void bindRuntimeEndpoint(nanobind::module_ &mod); +/// Preserve Python-only endpoint keyword arguments on a launch policy. +std::shared_ptr +makePythonEndpointOptions(nanobind::dict options); + } // namespace cudaq diff --git a/python/tests/backends/test_experimental_runtime_endpoint.py b/python/tests/backends/test_experimental_runtime_endpoint.py index 5ee249e93ad..19e91738d11 100644 --- a/python/tests/backends/test_experimental_runtime_endpoint.py +++ b/python/tests/backends/test_experimental_runtime_endpoint.py @@ -220,6 +220,18 @@ def test_estimate_forwards_the_choice_function(): assert kwargs["choice"]() is True +def test_estimate_forwards_endpoint_options(): + endpoint = DemoEndpoint() + set_runtime_endpoint(endpoint) + + marker = object() + cudaq.estimate(kernel, 1, [1, 2, 3], tier="logical", marker=marker) + + _, _, kwargs = endpoint.calls[0] + assert kwargs["tier"] == "logical" + assert kwargs["marker"] is marker + + def test_estimate_resources_launch(): endpoint = DemoEndpoint() set_runtime_endpoint(endpoint) diff --git a/runtime/cudaq/algorithms/dem/policy.h b/runtime/cudaq/algorithms/dem/policy.h index c3150d8630f..1684b4acc6d 100644 --- a/runtime/cudaq/algorithms/dem/policy.h +++ b/runtime/cudaq/algorithms/dem/policy.h @@ -11,6 +11,7 @@ #include "common/CompileOptions.h" #include "cudaq/algorithms/dem/options.h" #include "cudaq/algorithms/dem/result.h" +#include "cudaq/algorithms/endpoint_options.h" #include namespace cudaq { @@ -24,6 +25,7 @@ struct dem_policy { dem_options options; std::string kernelName; const noise_model *noiseModel = nullptr; + std::shared_ptr endpointOptions; friend CompileOptions get_compile_options_impl(const dem_policy &); }; diff --git a/runtime/cudaq/algorithms/endpoint_options.h b/runtime/cudaq/algorithms/endpoint_options.h new file mode 100644 index 00000000000..0ff0b1d8d93 --- /dev/null +++ b/runtime/cudaq/algorithms/endpoint_options.h @@ -0,0 +1,21 @@ +/****************************************************************-*- C++ -*-**** + * Copyright (c) 2026 NVIDIA Corporation & Affiliates. * + * All rights reserved. * + * * + * This source code and the accompanying materials are made available under * + * the terms of the Apache License 2.0 which accompanies this distribution. * + ******************************************************************************/ + +#pragma once + +#include + +namespace cudaq { + +/// Type-erased, endpoint-specific options attached to a launch policy. +/// Core policies remain independent of the language used by an endpoint. +struct endpoint_options { + virtual ~endpoint_options() = default; +}; + +} // namespace cudaq diff --git a/runtime/cudaq/algorithms/estimate/policy.h b/runtime/cudaq/algorithms/estimate/policy.h index a9b49ba1456..f130d9a5383 100644 --- a/runtime/cudaq/algorithms/estimate/policy.h +++ b/runtime/cudaq/algorithms/estimate/policy.h @@ -9,6 +9,7 @@ #pragma once #include "common/CompileOptions.h" +#include "cudaq/algorithms/endpoint_options.h" #include "cudaq/algorithms/estimate/result.h" #include #include @@ -28,6 +29,9 @@ struct estimate_policy { /// follow when the kernel branches on a measurement result. std::function choice; + /// Options for a runtime endpoint that CUDA-Q itself does not interpret. + std::shared_ptr endpointOptions; + friend CompileOptions get_compile_options_impl(const estimate_policy &); }; diff --git a/runtime/cudaq/algorithms/observe/policy.h b/runtime/cudaq/algorithms/observe/policy.h index 0a593e3588d..a499c7f39e5 100644 --- a/runtime/cudaq/algorithms/observe/policy.h +++ b/runtime/cudaq/algorithms/observe/policy.h @@ -11,6 +11,7 @@ #include "common/CompileOptions.h" #include "common/Future.h" #include "common/ObserveResult.h" +#include "cudaq/algorithms/endpoint_options.h" #include "cudaq/algorithms/observe/options.h" #include "cudaq/operators.h" @@ -44,6 +45,8 @@ struct observe_policy { mutable bool canHandleObserve = false; + std::shared_ptr endpointOptions; + friend observe_result finalize_execution_manager_impl(ExecutionManager &mgr, const observe_policy &policy, diff --git a/runtime/cudaq/algorithms/sample/policy.h b/runtime/cudaq/algorithms/sample/policy.h index 83b01f2d608..939c7e687af 100644 --- a/runtime/cudaq/algorithms/sample/policy.h +++ b/runtime/cudaq/algorithms/sample/policy.h @@ -11,6 +11,7 @@ #include "common/CompileOptions.h" #include "common/Future.h" #include "common/SampleResult.h" +#include "cudaq/algorithms/endpoint_options.h" #include "cudaq/algorithms/sample/options.h" namespace nvqir { @@ -42,6 +43,8 @@ struct sample_policy { mutable const noise_model *noiseModel = nullptr; + std::shared_ptr endpointOptions; + friend sample_result finalize_execution_manager_impl(ExecutionManager &mgr, const sample_policy &policy);