cuda-quantum icon indicating copy to clipboard operation
cuda-quantum copied to clipboard

[RFC] Mid-circuit measurement and sampling in library mode

Open amccaskey opened this issue 1 year ago • 0 comments

Background

Now that we are moving simulation based targets to runtime-only library mode (no MLIR compilation), we need to revisit how we enable sampling of dynamic circuits (those circuits with mid-circuit measurement + conditional feedback). An example circuit for this is

struct kernel {
  void operator()() __qpu__ {
    cudaq::qreg<3> q;
    // Initial state preparation
    x(q[0]);

    // Create Bell pair
    h(q[1]);
    x<cudaq::ctrl>(q[1], q[2]);

    x<cudaq::ctrl>(q[0], q[1]);
    h(q[0]);

    auto b0 = mz(q[0]);
    auto b1 = mz(q[1]);

    if (b1)
      x(q[2]);
    if (b0)
      z(q[2]);

    mz(q[2]);
  }
};

For a standard kernel with no measurements or measurements appended at the end of the function, backend simulations can simulate the circuit a single time and sample the final state, thereby producing the histogram of bit strings and counts. In the presence of non-trivial control flow with conditional statements like above, we cannot do that, and instead must invoke the circuit numShots times, collecting measured bit strings each time. The question becomes, in a purely runtime model, how does one indicate or know that a CUDA Quantum kernel has conditional statements on measurement results, and thus switch to this second model of sampling (invoking the kernel numShots times)? In the MLIR compilation mode, this is straightforward because we have the MLIR representation of the kernel at runtime and can look and see if it has cc.if() operations on values coming from a quake.mz() operation.

Potential solutions

Tracing the kernel

The first solution here is to borrow from the tracer PR (#92) with the addition of a defined type for measurement results. Imagine a measure_result type with the following structure

class measure_result {
private:
  bool result = false;

public:
  measure_result(const bool &b) : result(b) {};
  operator bool();
};

One could implement the implicit bool conversion operator on a type like this to trip some sort of flag indicating that the current kernel execution has conditional statements on measurement results (if (b0) invokes measure_result::operator bool()). With this in place, cudaq::sample(...) could be updated to first trace the function (no execution), and pick up any flag that was tripped by an implicit operator bool conversion on a measurement result.

This approach is nice because it requires no change to the language specification or the structure of kernel expressions / cudaq::sample(). I have a prototype for this here.

Kernel function indicator

Another approach could rely on some sort of helper function + user input on when a kernel has a mid-circuit measurement + conditional statement. Something like

struct kernel {
  void operator()() __qpu__ {
    cudaq::qreg<3> q;

    // For simulation / library mode, programmers 
    // have to indicate this is a dynamic circuit, 
    // and provide the names of any classical registers 
    // we'll create
    cudaq::is_dynamic_kernel("b0", "b1");

    // Initial state preparation
    x(q[0]);

    // Create Bell pair
    h(q[1]);
    x<cudaq::ctrl>(q[1], q[2]);

    x<cudaq::ctrl>(q[0], q[1]);
    h(q[0]);

    auto b0 = mz(q[0]);
    auto b1 = mz(q[1]);

    if (b1)
      x(q[2]);
    if (b0)
      z(q[2]);

    mz(q[2]);
  }
};

... sampling invoked via 
auto counts = cudaq::sample({nShots, /* is dynamic circuit */ true}, kernel{});

This is not as preferable to me, in that we have a different sample signature for physical vs simulated backends. I'd like those to stay the same. Moreover, it makes kernels targeting physical vs simulated backends different, with the addition of some kind of indicator function.

amccaskey avatar May 17 '23 12:05 amccaskey