9. Just-in-Time Kernel Creation¶
[1] CUDA-Q provides a set of programming abstractions for dynamically constructing quantum kernel code at runtime.
[2] The callable cudaq::kernel_builder
abstraction
facilitates the dynamic definition of quantum kernels that can optionally
be parameterized by user-defined input arguments.
The kernel_builder
takes the following structure
namespace cudaq {
// Type wrapping a value in the kernel IR
struct Value;
template <typename... Args>
class kernel_builder {
private:
std::vector<Value> arguments;
public:
std::vector<Value>& getArguments();
std::string name() const;
std::size_t getNumParams() const;
Value qalloc(std::size_t nQubits = 1);
Value qalloc(Value size);
void h(Value& qubit);
//... all other quantum operations ...
// if (mz(q)) thenFunctor()
void c_if(Value& result, std::function<void()>&& thenFunctor);
// Invoke a predefined kernel
template<typename OtherKernelBuilder, typename... Values>
void call(OtherKernelBuilder&& kernelToCall, Values&... args);
// General multi-control on a predefined kernel
// models cudaq::control(...)
template<typename OtherKernelBuilder, typename... Values>
void control(OtherKernelBuilder&& kernelToControl, Value& ctrl, Values&... values);
// General adjoint on a predefined kernel
// models cudaq::adjoint(...)
template<typename OtherKernelBuilder, typename... Values>
void adjoint(OtherKernelBuilder&& kernelToAdjoint, Values&... values);
// The constructed kernel is callable
void operator()(Args... args);
void operator()(void** argsArray);
// Enable structured bindings
template <std::size_t N>
decltype(auto) get() {
if constexpr (N == 0)
return *this;
else
return arguments[N - 1];
}
};
}
/// Enable structured bindings on the kernel_builder type.
/// auto [kernel, theta, phi] = std::make_kernel<double,double>();
namespace std {
template <typename... Args>
struct tuple_size<cudaq::kernel_builder<Args...>>
: std::integral_constant<std::size_t, sizeof...(Args) + 1> {};
template <std::size_t N, typename... Args>
struct tuple_element<N, cudaq::kernel_builder<Args...>> {
using type = std::conditional_t<N == 0, cudaq::kernel_builder<Args...>,
cudaq::QuakeValue>;
};
} // namespace std
namespace cudaq {
kernel_builder<> make_kernel();
template<typename... Args>
kernel_builder<Args...> make_kernel();
}
[3] The structure above allows one to leverage the provided factory functions (make_kernel
)
to construct an empty CUDA-Q kernel with defined argument signature.
[4] For each type in the signature, the programmer is returned a new Value
instance which
can be leveraged in the construction of the kernel. The intended mechanism for
kernel creation and argument value extraction is via standard C++
structured bindings.
[5] Once the kernel is created, the programmer is free to build up the kernel expression
using the exposed API. There are methods for qubit allocation, quantum operation
invocation, control and adjoint synthesis, and conditional branching based on
boolean
values.
Here is a simple example how one might build a CUDA-Q kernel dynamically.
auto kernel = cudaq::make_kernel();
auto qubits = kernel.qalloc(2);
kernel.h(qubits[0]);
kernel.x<cudaq::ctrl>(qubits[0], qubits[1]);
kernel.mz(qubits);
// See algorithmic primitives section for more on sample
auto counts = cudaq::sample(kernel);
kernel = cudaq.make_kernel()
qubits = kernel.qalloc(2)
kernel.h(qubits[0])
kernel.cx(qubits[0], qubits[1])
kernel.mz(qubits)
Here is an example demonstrating how one may build a dynamic set of CUDA-Q kernels for executing the standard Hadamard test.
auto [xPrep, qubitIn] = cudaq::make_kernel<cudaq::qubit>();
xPrep.x(qubitIn);
// Compute <1|X|1> = 0
auto hadamardTest = cudaq::make_kernel();
auto q = hadamardTest.qalloc();
auto ancilla = hadamardTest.qalloc();
hadamardTest.call(xPrep, q);
hadamardTest.h(ancilla);
hadamardTest.control(xPrep, ancilla, q);
hadamardTest.h(ancilla);
hadamardTest.mz(ancilla);
// See algorithmic primitives section for more on sample
auto counts = cudaq::sample(hadamardTest);
xPrep, qubitIn = cudaq.make_kernel(cudaq.qubit)
# Compute <1|X|1> = 0
hadamardTest = cudaq.make_kernel()
q = hadamardTest.qalloc()
hadamardTest.call(xPrep, q)
hadamardTest.h(ancilla)
hadamardTest.control(xPrep, ancilla, q)
hadamardTest.h(ancilla)
hadamardTest.mz(ancilla)
# See algorithmic primitives section for more on sample
counts = cudaq.sample(hadamardTest)