Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QIR Output Incompatibility with Base Profile (__body and invokeWithControlQubits) #2617

Open
ryanhill1 opened this issue Feb 13, 2025 · 1 comment

Comments

@ryanhill1
Copy link

There seems to be an incompatibility between cudaq's QIR output and the qir-spec Base Profile compliant program structure.

When translating a simple quantum kernel to QIR using cudaq.translate(kernel, format="qir"), the generated QIR code differs from the QIR Base Profile specification in the following ways:

  • Missing __body suffix: The declarations of quantum operations (QIS functions) lack the __body suffix that is expected. For example, instead of __quantum__qis__h__body, cudaq generates __quantum__qis__h.
  • Non-standard controlled gate invocation: Instead of directly using functions like __quantum__qis__cnot__body (or __quantum__qis__x__ctl with an array of control qubits), cudaq appears to use a function called invokeWithControlQubits to invoke controlled gates. This function seems to rely on variadic arguments and passing the controlled gate as a function pointer, which is not part of the standard QIS or runtime (__rt__). This requires an external definition of invokeWithControlQubits to be linked, which is not readily available in contexts outside of cudaq.

Here's a minimal example demonstrating the issue:

import cudaq

bell_kernel = cudaq.make_kernel()

qubits = bell_kernel.qalloc(2)

bell_kernel.h(qubits[0])
bell_kernel.cx(qubits[0], qubits[1])

bell_kernel.mz(qubits)

llvm_ir = cudaq.translate(bell_kernel, format="qir")

print(llvm_ir)

The generated QIR code includes declarations like:

declare void @__quantum__qis__h(%Qubit*)
declare void @invokeWithControlQubits(i64, void (%Array*, %Qubit*)*, ...)

instead of the expected:

declare void @__quantum__qis__h__body(%Qubit*)
declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*)

This incompatibility prevents users from directly running cudaq-generated QIR code with tools like the qir-runner, which are designed to work with Base Profile-compliant QIR. See: Unable to run QIR generated from cudaq #217.

If modifying the cudaq QIR output syntax isn't possible, are there options in the cudaq compilation process that can avoid these different patterns, or another pass that can be added to convert cudaq's QIR output to a more conventional, Base Profile-compatible format?

@bmhowe23
Copy link
Collaborator

bmhowe23 commented Feb 14, 2025

Hi @ryanhill1 - if you want to generate QIR compatible with the Base Profile, you need to select one of the hardware targets that supports that profile like fermioniq, ionq, or oqc, and you need to need to set an environment variable to dump it to the output. (Note that quantinuum and anyon support the Adaptive Profile). Here is a slightly modified version of your code that will dump the profile-compliant QIR:

import cudaq
import os

os.environ['CUDAQ_DUMP_JIT_IR'] = '1'

bell_kernel = cudaq.make_kernel()
cudaq.set_target('oqc', emulate=True)

qubits = bell_kernel.qalloc(2)

bell_kernel.h(qubits[0])
bell_kernel.cx(qubits[0], qubits[1])

bell_kernel.mz(qubits)

cudaq.sample(bell_kernel)

Outputs:

; ModuleID = 'LLVMDialectModule'
source_filename = "LLVMDialectModule"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

%Qubit = type opaque
%Result = type opaque

@cstr.72303030303000 = private constant [7 x i8] c"r00000\00"
@cstr.72303030303100 = private constant [7 x i8] c"r00001\00"

define void @__nvqpp__mlirgen____nvqppBuilderKernel_R23EYF50OU() local_unnamed_addr #0 {
"0":
  tail call void @__quantum__qis__h__body(%Qubit* null)
  tail call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* nonnull inttoptr (i64 1 to %Qubit*))
  tail call void @__quantum__qis__mz__body(%Qubit* null, %Result* writeonly null)
  tail call void @__quantum__qis__mz__body(%Qubit* nonnull inttoptr (i64 1 to %Qubit*), %Result* nonnull writeonly inttoptr (i64 1 to %Result*))
  tail call void @__quantum__rt__result_record_output(%Result* null, i8* nonnull getelementptr inbounds ([7 x i8], [7 x i8]* @cstr.72303030303000, i64 0, i64 0))
  tail call void @__quantum__rt__result_record_output(%Result* nonnull inttoptr (i64 1 to %Result*), i8* nonnull getelementptr inbounds ([7 x i8], [7 x i8]* @cstr.72303030303100, i64 0, i64 0))
  ret void
}

declare void @__quantum__qis__h__body(%Qubit*) local_unnamed_addr

declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) local_unnamed_addr #1

declare void @__quantum__rt__result_record_output(%Result*, i8*) local_unnamed_addr

declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*) local_unnamed_addr

attributes #0 = { "entry_point" "output_labeling_schema"="schema_id" "output_names"="[[[0,[0,\22r00000\22]],[1,[1,\22r00001\22]]]]" "qir_profiles"="base_profile" "requiredQubits"="2" "requiredResults"="2" }
attributes #1 = { "irreversible" }

!llvm.module.flags = !{!0, !1, !2, !3, !4}

!0 = !{i32 2, !"Debug Info Version", i32 3}
!1 = !{i32 1, !"qir_major_version", i32 1}
!2 = !{i32 7, !"qir_minor_version", i32 0}
!3 = !{i32 1, !"dynamic_qubit_management", i1 false}
!4 = !{i32 1, !"dynamic_result_management", i1 false}

Hopefully this helps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants