CQCL/pytket-qir

Generate QIR compatible with azure-quantum

Opened this issue · 8 comments

Attempts to submit QIR generated by pytket_to_qir() via azure-quantum currently fail with

RuntimeError: Cannot retrieve results as job execution failed(status: Failed.error: {'additional_properties': {}, 'code': 'QIRPreProcessingFailed', 'message': 'No match found for output recording set'})

The same error occurs when submitting to ionq.simulator or quantinuum.sim.h1-1sc.

from azure.quantum import Workspace
workspace = Workspace(
    resource_id="...",
    location="...",
)
target = workspace.get_targets(name="ionq.simulator")
job = target.submit(
    input_data=open("test-qir.ll").read(),
    input_data_format="qir.v1",
    output_data_format="microsoft.quantum-results.v1",
    name="test-submission",
    input_params={"entryPoint": "main", "arguments": [], "count": 100},
)
job.wait_until_completed()
result = job.get_results()

test-qir.ll:

; ModuleID = 'Generated from input pytket circuit'
source_filename = "Generated from input pytket circuit"

%Qubit = type opaque
%Result = type opaque

@0 = internal constant [2 x i8] c"c\00"

define void @main() #0 {
entry:
  %0 = call i1* @create_creg(i64 2)
  call void @__quantum__qis__h__body(%Qubit* null)
  call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*))
  call void @mz_to_creg_bit(%Qubit* null, i1* %0, i64 0)
  call void @mz_to_creg_bit(%Qubit* inttoptr (i64 1 to %Qubit*), i1* %0, i64 1)
  call void @__quantum__rt__tuple_start_record_output()
  %1 = call i64 @get_int_from_creg(i1* %0)
  call void @__quantum__rt__int_record_output(i64 %1, i8* getelementptr inbounds ([2 x i8], [2 x i8]* @0, i32 0, i32 0))
  call void @__quantum__rt__tuple_end_record_output()
  ret void
}

declare i1 @get_creg_bit(i1*, i64)
declare void @set_creg_bit(i1*, i64, i1)
declare void @set_creg_to_int(i1*, i64)
declare i1 @__quantum__qis__read_result__body(%Result*)
declare i1* @create_creg(i64)
declare i64 @get_int_from_creg(i1*)
declare void @mz_to_creg_bit(%Qubit*, i1*, i64)
declare void @__quantum__rt__int_record_output(i64, i8*)
declare void @__quantum__rt__tuple_start_record_output()
declare void @__quantum__rt__tuple_end_record_output()
declare void @__quantum__qis__h__body(%Qubit*)
declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*)

attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" }

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

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

I think this is expected, because of the use of create_creg and friends, and the use of __quantum__rt__tuple_start_record_output .

Should we split this up into several subtasks?

I think this is expected, because of the use of create_creg and friends, and the use of __quantum__rt__tuple_start_record_output .

That was my guess too.

But I note that submitting the following QIR to quantinuum.sim.h1-1e via azure-quantum does work:

; ModuleID = 'qiskit-qir.bc'
source_filename = "circuit-171"

%Qubit = type opaque
%Result = type opaque

define void @circuit-171() #0 {
entry:
  call void @__quantum__rt__initialize(i8* null)
  call void @__quantum__qis__h__body(%Qubit* null)
  call void @__quantum__qis__cnot__body(%Qubit* null, %Qubit* inttoptr (i64 1 to %Qubit*))
  call void @__quantum__qis__mz__body(%Qubit* null, %Result* null)
  call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
  call void @__quantum__rt__array_record_output(i64 2, i8* null)
  call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null)
  call void @__quantum__rt__result_record_output(%Result* null, i8* null)
  ret void
}

declare void @__quantum__rt__initialize(i8*)
declare void @__quantum__qis__h__body(%Qubit*)
declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*)
declare void @__quantum__qis__mz__body(%Qubit*, %Result* writeonly) #1
declare void @__quantum__rt__array_record_output(i64, i8*)
declare void @__quantum__rt__result_record_output(%Result*, i8*)

attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="2" "required_num_results"="2" }
attributes #1 = { "irreversible" }

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

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

So I wonder if those functions are actually necessary, at least for this simple case...

Apparently there is some magic QIR conversion going on behind the scenes.

Not sure what you wanted to show with the example? Yes it is not using the functions that are problematic?

I think we should generally try to upgrade everything to not use them? Or what do you mean?

Apparently there is some magic QIR conversion going on behind the scenes.

I think the magic is that it updates the results handling which is required in a non profile way by the device, but sending that off to azure will be rejected for now? At least it sounded like that?

Not sure what you wanted to show with the example? Yes it is not using the functions that are problematic?

I was surprised that it worked, because I thought quantinuum needed to use those other (non-standard) methods.

I think we should generally try to upgrade everything to not use them? Or what do you mean?

Yes agreed that should be the goal.

I think it would be helpful to add more subtasks for this. One for adding the phi node generation and one for updating the result recording. Should I do that @cqc-alec ?

I think it would be helpful to add more subtasks for this. One for adding the phi node generation and one for updating the result recording. Should I do that @cqc-alec ?

It's not currently clear to me exactly what the subtasks are, but yes if you can break it down that would be great.