From eeacd68d9764f58532fb803b29a4c76a0bf40171 Mon Sep 17 00:00:00 2001 From: Pradnya Khalate Date: Wed, 19 Nov 2025 03:17:48 -0800 Subject: [PATCH] Strict enforcing on sampled kernels when targeting hardware backends - no explicit measurement operation allowed. For simulators, issue a warning. Signed-off-by: Pradnya Khalate --- lib/Optimizer/Transforms/QuakeAddMetadata.cpp | 13 ++++++++ .../Transforms/QuakePropagateMetadata.cpp | 18 ++++++----- python/cudaq/kernel/kernel_builder.py | 4 +++ python/cudaq/runtime/sample.py | 32 ++++++++++++++++--- runtime/common/DeviceCodeRegistry.cpp | 7 ++++ runtime/cudaq.h | 2 ++ runtime/cudaq/algorithms/sample.h | 26 +++++++++++++++ 7 files changed, 89 insertions(+), 13 deletions(-) diff --git a/lib/Optimizer/Transforms/QuakeAddMetadata.cpp b/lib/Optimizer/Transforms/QuakeAddMetadata.cpp index b31a19100e8..bd5717dcdd7 100644 --- a/lib/Optimizer/Transforms/QuakeAddMetadata.cpp +++ b/lib/Optimizer/Transforms/QuakeAddMetadata.cpp @@ -27,6 +27,7 @@ namespace { /// Define a type to contain the Quake Function Metadata struct QuakeMetadata { + bool hasMeasurements = false; bool hasConditionalsOnMeasure = false; // If the following flag is set, it means we've detected quantum to classical @@ -99,6 +100,13 @@ struct QuakeFunctionAnalysis { LLVM_DEBUG(llvm::dbgs() << "Function to analyze: " << funcOp.getName() << '\n'); QuakeMetadata data; + + // Check for any measurements + funcOp->walk([&](quake::MeasurementInterface meas) { + data.hasMeasurements = true; + return WalkResult::interrupt(); + }); + SmallPtrSet dirtySet; funcOp->walk([&](quake::DiscriminateOp disc) { dirtySet.insert(disc.getOperation()); @@ -209,6 +217,11 @@ class QuakeAddMetadataPass assert(iter != funcAnalysisInfo.end()); const auto &info = iter->second; + if (info.hasMeasurements) { + auto builder = OpBuilder::atBlockBegin(&funcOp.getBody().front()); + funcOp->setAttr("hasMeasurements", builder.getBoolAttr(true)); + } + // Did this function have conditionals on measures? if (info.hasConditionalsOnMeasure) { // if so, add a function attribute diff --git a/lib/Optimizer/Transforms/QuakePropagateMetadata.cpp b/lib/Optimizer/Transforms/QuakePropagateMetadata.cpp index 73415a94d4d..db3f099f63d 100644 --- a/lib/Optimizer/Transforms/QuakePropagateMetadata.cpp +++ b/lib/Optimizer/Transforms/QuakePropagateMetadata.cpp @@ -37,6 +37,8 @@ class QuakePropagateMetadataPass /// positives. expand-measurements and loop-unrolling may further reduce /// false positives. void runOnOperation() override { + static const std::vector attributesToPropagate = { + "qubitMeasurementFeedback", "hasMeasurements"}; ModuleOp moduleOp = getOperation(); /// NOTE: If the module has an occurrence of `quake.apply` then the step to /// build call graph fails. Hence, we skip the pass in such cases. @@ -90,15 +92,15 @@ class QuakePropagateMetadataPass LLVM_DEBUG(llvm::dbgs() << "Visiting callee: " << callee.getName() << "\n\n"); for (auto caller : callers) { - LLVM_DEBUG(llvm::dbgs() << " Caller: " << caller.getName() << "\n\n"); - if (auto boolAttr = callee->getAttr("qubitMeasurementFeedback") - .dyn_cast_or_null()) { - if (boolAttr.getValue()) { - LLVM_DEBUG(llvm::dbgs() - << " Propagating qubitMeasurementFeedback attr: " - << boolAttr << "\n"); - caller->setAttr("qubitMeasurementFeedback", boolAttr); + for (auto attribute : attributesToPropagate) { + if (auto boolAttr = callee->getAttr(attribute) + .dyn_cast_or_null()) { + if (boolAttr.getValue()) { + LLVM_DEBUG(llvm::dbgs() << " Propagating " << attribute + << " attr: " << boolAttr << "\n"); + caller->setAttr(attribute, boolAttr); + } } } } diff --git a/python/cudaq/kernel/kernel_builder.py b/python/cudaq/kernel/kernel_builder.py index b73ba1774ad..fefe048a156 100644 --- a/python/cudaq/kernel/kernel_builder.py +++ b/python/cudaq/kernel/kernel_builder.py @@ -273,6 +273,7 @@ def __init__(self, argTypeList): cc.register_dialect(context=self.ctx) cudaq_runtime.registerLLVMDialectTranslation(self.ctx) + self.hasMeasurements = False self.conditionalOnMeasure = False self.regCounter = 0 self.loc = Location.unknown(context=self.ctx) @@ -1135,6 +1136,7 @@ def mz(self, target, regName=None): kernel.mz(target=qubit)) ``` """ + self.hasMeasurements = True with self.ctx, self.insertPoint, self.loc: i1Ty = IntegerType.get_signless(1) qubitTy = target.mlirValue.type @@ -1182,6 +1184,7 @@ def mx(self, target, regName=None): kernel.mx(qubit)) ``` """ + self.hasMeasurements = True with self.ctx, self.insertPoint, self.loc: i1Ty = IntegerType.get_signless(1) qubitTy = target.mlirValue.type @@ -1230,6 +1233,7 @@ def my(self, target, regName=None): kernel.my(qubit)) ``` """ + self.hasMeasurements = True with self.ctx, self.insertPoint, self.loc: i1Ty = IntegerType.get_signless(1) qubitTy = target.mlirValue.type diff --git a/python/cudaq/runtime/sample.py b/python/cudaq/runtime/sample.py index 37f606f3e72..9ea59d666b4 100644 --- a/python/cudaq/runtime/sample.py +++ b/python/cudaq/runtime/sample.py @@ -71,6 +71,7 @@ def sample(kernel, or a list of such results in the case of `sample` function broadcasting. """ + has_measurements = False has_conditionals_on_measure_result = False if isinstance(kernel, PyKernelDecorator): @@ -85,20 +86,41 @@ def sample(kernel, if not hasattr(operation, 'name'): continue if nvqppPrefix + kernel.name == operation.name.value: + has_measurements = 'hasMeasurements' in operation.attributes has_conditionals_on_measure_result = 'qubitMeasurementFeedback' in operation.attributes break - elif isinstance(kernel, PyKernel) and kernel.conditionalOnMeasure: - has_conditionals_on_measure_result = True + elif isinstance(kernel, PyKernel): + if kernel.hasMeasurements: + has_measurements = True + if kernel.conditionalOnMeasure: + has_conditionals_on_measure_result = True if explicit_measurements: if not cudaq_runtime.supportsExplicitMeasurements(): raise RuntimeError( - "The sampling option `explicit_measurements` is not supported on this target." - ) + "The sampling option `explicit_measurements` is not supported " + "on this target.") if has_conditionals_on_measure_result: raise RuntimeError( - "The sampling option `explicit_measurements` is not supported on kernel with conditional logic on a measurement result." + "The sampling option `explicit_measurements` is not supported " + "on kernel with conditional logic on a measurement result.") + if has_measurements: + if cudaq_runtime.isQuantumDevice(): + raise RuntimeError( + "Kernels with explicit measurement operations cannot be used with " + "`cudaq.sample` on hardware targets. Please remove all measurements " + "from the kernel, qubits will be automatically measured at the end " + "when sampling a kernel.\n" + "Alternatively, use `cudaq.run` API, if supported on this target." ) + elif not explicit_measurements: + print( + "WARNING: Using `cudaq.sample` with a kernel that contains explicit " + "measurements is deprecated and will be disallowed in a future release. " + "Please remove all measurements from the kernel, qubits will be " + "automatically measured at the end when sampling a kernel.\n" + "Alternatively, use `cudaq.run` API which preserves individual " + "measurement results.") if noise_model != None: cudaq_runtime.set_noise(noise_model) diff --git a/runtime/common/DeviceCodeRegistry.cpp b/runtime/common/DeviceCodeRegistry.cpp index 8398b176c56..ef1df39cb91 100644 --- a/runtime/common/DeviceCodeRegistry.cpp +++ b/runtime/common/DeviceCodeRegistry.cpp @@ -203,4 +203,11 @@ bool kernelHasConditionalFeedback(const std::string &kernelName) { return !quakeCode.empty() && quakeCode.find("qubitMeasurementFeedback = true") != std::string::npos; } + +bool kernelHasMeasurements(const std::string &kernelName) { + auto quakeCode = get_quake_by_name(kernelName, false); + return !quakeCode.empty() && + quakeCode.find("hasMeasurements = true") != std::string::npos; +} + } // namespace cudaq diff --git a/runtime/cudaq.h b/runtime/cudaq.h index 6d48d8530c5..453e49203ef 100644 --- a/runtime/cudaq.h +++ b/runtime/cudaq.h @@ -216,6 +216,8 @@ KernelArgsCreator getArgsCreator(const std::string &kernelName); bool kernelHasConditionalFeedback(const std::string &kernelName); +bool kernelHasMeasurements(const std::string &kernelName); + /// @brief Provide a hook to set the target backend. void set_target_backend(const char *backend); diff --git a/runtime/cudaq/algorithms/sample.h b/runtime/cudaq/algorithms/sample.h index 60cb1ef8355..da45e43ef28 100644 --- a/runtime/cudaq/algorithms/sample.h +++ b/runtime/cudaq/algorithms/sample.h @@ -16,6 +16,7 @@ namespace cudaq { bool kernelHasConditionalFeedback(const std::string &); +bool kernelHasMeasurements(const std::string &); namespace detail { bool isKernelGenerated(const std::string &); } @@ -99,6 +100,31 @@ runSampling(KernelFunctor &&wrappedKernel, quantum_platform &platform, auto isQuantumDevice = !isRemoteSimulator && (platform.is_remote() || platform.is_emulated()); + auto hasMeasurements = cudaq::kernelHasMeasurements(kernelName); + if (hasMeasurements) { + if (isQuantumDevice) { + // Hardware: Error immediately + throw std::runtime_error( + "Kernels with explicit measurement operations cannot be used with " + "`cudaq::sample` on hardware targets. Please remove all " + "measurements from the kernel, qubits will be automatically measured " + "at the end when sampling a kernel." + "Alternatively, use `cudaq::run` API, if supported on this target."); + } else { + // Simulators: Warning for now, but indicate future deprecation + if (!explicitMeasurements) { + printf( + "WARNING: Using `cudaq::sample` with a kernel that contains " + "explicit measurements is deprecated and will be disallowed in a " + "future release. Please remove all measurements from the kernel, " + "qubits will be automatically measured at the end when sampling a " + "kernel.\n" + "Alternatively, use `cudaq::run` which preserves individual " + "measurement results."); + } + } + } + // Loop until all shots are returned. cudaq::sample_result counts; while (counts.get_total_shots() < static_cast(shots)) {