Private repo for adapting QIR for use in XACC.
The Quantum Intermediate Representation (QIR) provides an abstraction of programs that include a mixture of conventional and quantum instructions. This project develops a tool that translates a QIR program into a hardware-specific implementation using the XACC programming framework. This tool provides a unique capability to translating abstract QIR programs into executable programs on specific hardware. By mapping specific LLVM function calls to their corresponding C++ implementations, the code bridges the LLVM Execution Engine's classical execution environment with XACC's quantum computing capabilities. The integration allows for the smooth transition and combination of classical and quantum instructions, providing a unified platform for hybrid quantum-classical computing. The results from the quantum computations are captured in a globally accessible buffer, facilitating their use in classical post-processing or control logic.
- Dependencies (skip this step if you already did it):
- Install LLVM and XACC.
- Check that your
cmake
prefixes for XACC are correct.- Typing
echo $CMAKE_PREFIX_PATH
should give you the path to your xacc installation. - If empty, then add it:
export CMAKE_PREFIX_PATH=[path to your xacc install]
- Example:
[path to your xacc install]
might be like$HOME/.xacc
- Typing
- Check your
$PYTHONPATH
for the correct xacc. If empty or points to something else, then add it. -
export PYTHONPATH=$PYTHONPATH:$HOME/.xacc
- QIR-EE Setup:
- Clone this repo. Enter the repo.
-
mkdir build; cd build
-
cmake ..
-
make
- To execute QIR:
[
a.]
[
b.]
[
c.]
[
d.]
written without the brackets, where:-
./QuantumExecutionEngine
or an equivalent path to your executable -
../examples/[name of *.ll file]
-
number of shots
(if left blank, then default is 1024) -
sim choice
can be one ofaer, qpp, qsim, honeywell:H1-1SC, honeywell:H1-1E, ionq
(if left blank, then default isaer
)
-
The src/QuantumExecutionEngine.cpp
integrates the XACC quantum computing framework with LLVM's Execution Engine to provide a seamless environment for executing both classical and quantum code. The Execution Engine runs the quantum code by calling mapped C++ functions, which in turn use XACC's quantum accelerators to execute the quantum circuit. These functions serve as the bridge between the LLVM Execution Engine and XACC's quantum execution environment.
The XACC execution results are stored in a buffer, accessible through a Singleton pattern to ensure consistency and global access. C++ functions like quantum__qis__read_result__body
and quantum__rt__result_record_output
fetch data from this buffer. These functions are then dynamically linked to their LLVM counterparts (specified in the QIR) using LLVM's Execution Engine. This way, the quantum execution results can be used by subsequent classical computations in the LLVM code.
The flexible architecture enables the combination of quantum and classical instructions. Quantum instructions are executed on the quantum accelerator via XACC, and the results are stored back into a common buffer. Classical operations can then read this buffer for further computation or conditional logic. Classical operations in the LLVM code can operate on the results of quantum computations through the mapped C++ functions. This makes it possible to design algorithms that interplay between classical and quantum computations, using the buffer as the common data link.
- XACC: The eXtreme-scale Accelerator programming framework is used for quantum computation.
- LLVM: The LLVM compiler infrastructure is required for the LLVM Execution Engine and for parsing LLVM IR files.
- Link LLVM via
CMakeLists.txt
- Quantum Execution Engine (parses and executes quantum instructions only)
- Declare accelerator.
-
auto accelerator = xacc::getAccelerator(accelerator_name);
-
- Parse
*.ll
for quantum instructions. - Main block parsing. Example:
bell.ll
- Persist through entire level one scope. Examples:
teleport.ll, loop.ll
- Mutiple calls to an outside function. Example:
multiple.ll
- Parameterized functions. Example:
rotation.ll
- Implementations of functions for basic arithmetic with some user input. Example:
input.ll
- Users can add instructions using the
addMapping
function. - Construct quantum ciruit or kernel in XACCIR.
- Persist through entire level one scope.
- Extend to level two scope. This might include calls to external functions and user input.
- Use pointers to save readout.
- Execute quantum circuit on accelerator by checking global flag from readout.
-
EE->addGlobalMapping(quantumFunc, (void*)&executeQuantumWithXACC)
- JIT Compilation and Execution
- LLVM EE created for JIT compilation.
- Quantum functions within LLVM module are mapped (or bound) to the QEE function.
- Main function of LLVM module is executed using LLVM EE.
- Quantum readout from hardware stored in buffer.
- Cleanup ouptut for user. Clear cache (if needed).
- Handling of multiple functions, multiple blocks, multiple entry points.
- Handling of user created functions (related to above).
- Handling of basic arithmetic with user input.
- Check difference between
requiredQubits
andnum_required_qubits
in attributes. - If only classical code is present in
*.ll
file, insert"num_required_qubits"="0"
inside listattributes #0
. - Fix 'target' qubit output of
if_else.ll
. - Handling MIXED instruction with user input (what if user input is quantum input?).
- Handle parameterized quantum circuits and operations.
- LLVM version handling!
- Add a variable for 'number of shots' input (but keep default at 1024).
- Complete basic C++ implementations for quantum gate.
- Two more examples to illustrate functionality (multiple gates and phase estimation algorithm).
- Working with vendor emulators/simulators. Done: Quantinuum/Honeywell, IonQ.
- Add 'enhanced' phase estimation to include loops and function calls (similar to qiskit's version in their tutorial).
- Validation: Construct unit tests where we already know what the output should be.
- Connect to IBM's hardware.
-
${\color{red}ALERT}$ teleport.ll
only runs correctly onaer
. We expect it won't work on other simulators or hardware unless hardware can handle mid-circuit measurements. - Documentation 1: For Developers + In-Line
- Documentation 2: Readthedocs for Users (What is our tool good for and how can users use it? This will depend on the integration step.)
- Public usage: Release a self-contained package QIR-EE (pronouned 'curee'?) which wraps the Quantum Execution Engine with LLVM. Make into a cohesive whole with XACC.
- Write the paper for the above implementation (shared in ShareLaTeX).
- Possible integration with XACC as a utility? (But maybe not needed is package is standalone.)
- Working beyond just XACC (possibly directly with different hardware backends).
- Next level examples: variational algorithms, circuit optimizations.
- Improve error handling: We should try to catch XACC failures. Also, what if the QIR contains
quantum_rt_fail
? When would this happen and how would we handle it? - Enhanced output record keeping: what other diagnostics can we expose from the XACC buffer? Currently we are doing bare minimum: shots. Can we do more?
- Check/understand parallel implementations from Microsoft, Xanadu, Intel, Nvidia, Munich Team. Also: what is Quantinuum doing with runtime?
-
void @__quantum__qis__h__body(%Qubit*)
-
void @__quantum__qis__x__body(%Qubit*)
-
void @__quantum__qis__y__body(%Qubit*)
-
void @__quantum__qis__z__body(%Qubit*)
-
void @__quantum__qis__t__body(%Qubit*)
-
void @__quantum__qis__t__adj(%Qubit*)
-
void @__quantum__qis__s__body(%Qubit*)
-
void @__quantum__qis__s__adj(%Qubit*)
-
void @__quantum__qis__mz__body(%Qubit*, %Result*)
-
void @__quantum__qis__reset__body(%Qubit*)
-
void @__quantum__qis__rx__body(double, %Qubit*)
-
void @__quantum__qis__ry__body(double, %Qubit*)
-
void @__quantum__qis__rz__body(double, %Qubit*)
-
void @__quantum__qis__cnot__body(%Qubit*, %Qubit*)
-
void @__quantum__qis__swap__body(%Qubit*, %Qubit*)
-
void @__quantum__qis__ccx__body(%Qubit*, %Qubit*)
-
void @__quantum__qis__cx__body(%Qubit*, %Qubit*)
-
void @__quantum__qis__cy__body(%Qubit*, %Qubit*)
-
void @__quantum__qis__cz__body(%Qubit*, %Qubit*)
-
void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*)
-
void @__quantum__qis__rxx__body(double, %Qubit*, %Qubit*)
-
void @__quantum__qis__ryy__body(double, %Qubit*, %Qubit*)
-
void @__quantum__qis__r__body(i2, double, %Qubit*)
-
void @__quantum__qis__r__adj(i2, double, %Qubit*)
-
void @__quantum__qis__h__ctl(%Array*, %Qubit*)
-
void @__quantum__qis__x__ctl(%Array*, %Qubit*)
-
void @__quantum__qis__y__ctl(%Array*, %Qubit*)
-
void @__quantum__qis__z__ctl(%Array*, %Qubit*)
-
void @__quantum__qis__t__ctl(%Array*, %Qubit*)
-
void @__quantum__qis__t__ctladj(%Array*, %Qubit*)
-
void @__quantum__qis__rx__ctl(%Array*, %Tuple*)
-
void @__quantum__qis__ry__ctl(%Array*, %Tuple*)
-
void @__quantum__qis__rz__ctl(%Array*, %Tuple*)
-
void @__quantum__qis__r__ctl(%Array*, %Tuple*)
-
void @__quantum__qis__r__ctladj(%Array*, %Tuple*)
-
void @__quantum__qis__s__ctl(%Array*, %Qubit*)
-
void @__quantum__qis__s__ctladj(%Array*, %Qubit*)
-
%Array* @__quantum__rt__array_concatenate(%Array*, %Array*)
-
%Array* @__quantum__rt__array_copy(%Array*, bool)
-
%Array* @__quantum__rt__array_create_1d(i32, i64)
-
i8* @__quantum__rt__array_get_element_ptr_1d(%Array*, i64)
-
i64 @__quantum__rt__array_get_size_1d(%Array*)
-
%Array* @__quantum__rt__array_slice_1d(%Array*, %Range, i1)
-
void @__quantum__rt__array_record_output(i64, i8*)
-
void @__quantum__rt__array_update_alias_count(%Array*, i32)
-
void @__quantum__rt__array_update_reference_count(%Array*, i32)
-
%Result* @__quantum__qis__m__body(%Qubit*)
-
%Result* @__quantum__qis__measure__body(%Array*, %Array*)
-
%Result* @__quantum__qis__mresetz__body(%Qubit*)
-
bool @__quantum__qis__read_result__body(%Result*)
-
bool @__quantum__rt__result_equal(%Result*, %Result*)
-
%Result* @__quantum__rt__result_get_one()
-
%Result* @__quantum__rt__result_get_zero()
-
void @__quantum__rt__result_record_output(%Result*, i8*)
-
void @__quantum__rt__result_update_reference_count(%Result*, i32)
-
%String* @__quantum__rt__result_to_string(%Result*)
-
%String* @__quantum__rt__string_concatenate(%String*, %String*)
-
%String* @__quantum__rt__string_create(i8*)
-
bool @__quantum__rt__string_equal(%String*, %String*)
-
i8* @__quantum__rt__string_get_data(%String*)
-
i32 @__quantum__rt__string_get_length(%String*)
-
void @__quantum__rt__string_update_reference_count(%String*, i32)
-
%Tuple* @__quantum__rt__tuple_copy(%Tuple*, i1)
-
%Tuple* @__quantum__rt__tuple_create(i64)
-
void @__quantum__rt__tuple_record_output(i64, i8*)
-
void @__quantum__rt__tuple_update_alias_count(%Tuple*, i32)
-
void @__quantum__rt__tuple_update_reference_count(%Tuple*, i32)
This work was performed at Oak Ridge National Laboratory, operated by UT-Battelle, LLC under contract DE-AC05-00OR22725 for the US Department of Energy (DOE). Support for the work came from the DOE Advanced Scientific Computing Research (ASCR) Accelerated Research in Quantum Computing (ARQC) Program under field work proposal ERKJ332.