Skip to content

Commit

Permalink
Merge pull request #12 from qiboteam/cuQuantum_cuTensorNet
Browse files Browse the repository at this point in the history
cuQuantum cuTensorNet backend
  • Loading branch information
liweintu authored Apr 21, 2023
2 parents d1721ae + 2c7fa5d commit 6455825
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
python-version: [3.7, 3.8, 3.9, "3.10"]
python-version: [3.8, 3.9, "3.10"]
uses: qiboteam/workflows/.github/workflows/rules.yml@main
with:
os: ${{ matrix.os }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ dmypy.json
# Pyre type checker
.pyre/


# pytype static type analyzer
.pytype/

Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ def version():
"qibo>=0.1.10",
"qibojit>=0.0.7",
"quimb[tensor]>=1.4.0",
"cupy>=11.6.0",
"cuquantum-python-cu11",
],
extras_require={
"docs": [],
Expand All @@ -54,7 +56,7 @@ def version():
"pylint>=2.16.0",
],
},
python_requires=">=3.7.0",
python_requires=">=3.8.0",
long_description=(HERE / "README.md").read_text(encoding="utf-8"),
long_description_content_type="text/markdown",
)
111 changes: 111 additions & 0 deletions src/qibotn/QiboCircuitConvertor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import cupy as cp
import numpy as np


class QiboCircuitToEinsum:
"""Convert a circuit to a Tensor Network (TN) representation.
The circuit is first processed to an intermediate form by grouping each gate
matrix with its corresponding qubit it is acting on to a list. It is then
converted it to an equivalent TN expression through the class function
state_vector_operands() following the Einstein summation convention in the
interleave format.
See document for detail of the format: https://docs.nvidia.com/cuda/cuquantum/python/api/generated/cuquantum.contract.html
The output is to be used by cuQuantum's contract() for computation of the
state vectors of the circuit.
"""

def __init__(self, circuit, dtype="complex128"):
self.backend = cp
self.dtype = getattr(self.backend, dtype)
self.init_basis_map(self.backend, dtype)
self.init_intermediate_circuit(circuit)

def state_vector_operands(self):
input_bitstring = "0" * len(self.active_qubits)

input_operands = self._get_bitstring_tensors(input_bitstring)

(
mode_labels,
qubits_frontier,
next_frontier,
) = self._init_mode_labels_from_qubits(self.active_qubits)

gate_mode_labels, gate_operands = self._parse_gates_to_mode_labels_operands(
self.gate_tensors, qubits_frontier, next_frontier
)

operands = input_operands + gate_operands
mode_labels += gate_mode_labels

out_list = []
for key in qubits_frontier:
out_list.append(qubits_frontier[key])

operand_exp_interleave = [x for y in zip(
operands, mode_labels) for x in y]
operand_exp_interleave.append(out_list)
return operand_exp_interleave

def _init_mode_labels_from_qubits(self, qubits):
n = len(qubits)
frontier_dict = {q: i for i, q in enumerate(qubits)}
mode_labels = [[i] for i in range(n)]
return mode_labels, frontier_dict, n

def _get_bitstring_tensors(self, bitstring):
return [self.basis_map[ibit] for ibit in bitstring]

def _parse_gates_to_mode_labels_operands(
self, gates, qubits_frontier, next_frontier
):
mode_labels = []
operands = []

for tensor, gate_qubits in gates:
operands.append(tensor)
input_mode_labels = []
output_mode_labels = []
for q in gate_qubits:
input_mode_labels.append(qubits_frontier[q])
output_mode_labels.append(next_frontier)
qubits_frontier[q] = next_frontier
next_frontier += 1
mode_labels.append(output_mode_labels + input_mode_labels)
return mode_labels, operands

def op_shape_from_qubits(self, nqubits):
"""Modify tensor to cuQuantum shape
(qubit_states,input_output) * qubits_involved
"""
return (2, 2) * nqubits

def init_intermediate_circuit(self, circuit):
self.gate_tensors = []
gates_qubits = []

for gate in circuit.queue:
gate_qubits = gate.control_qubits + gate.target_qubits
gates_qubits.extend(gate_qubits)

# self.gate_tensors is to extract into a list the gate matrix together with the qubit id that it is acting on
# https://github.com/NVIDIA/cuQuantum/blob/6b6339358f859ea930907b79854b90b2db71ab92/python/cuquantum/cutensornet/_internal/circuit_parser_utils_cirq.py#L32
required_shape = self.op_shape_from_qubits(len(gate_qubits))
self.gate_tensors.append(
(
cp.asarray(gate.matrix).reshape(required_shape),
gate_qubits,
)
)

# self.active_qubits is to identify qubits with at least 1 gate acting on it in the whole circuit.
self.active_qubits = np.unique(gates_qubits)

def init_basis_map(self, backend, dtype):
asarray = backend.asarray
state_0 = asarray([1, 0], dtype=dtype)
state_1 = asarray([0, 1], dtype=dtype)

self.basis_map = {"0": state_0, "1": state_1}
5 changes: 3 additions & 2 deletions src/qibotn/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import argparse
from qibotn import qasm_quimb

import qibotn.quimb


def parser():
Expand All @@ -12,7 +13,7 @@ def parser():

def main(args: argparse.Namespace):
print("Testing for %d nqubits" % (args.nqubits))
qasm_quimb.eval_QI_qft(args.nqubits, args.qasm_circ, args.init_state)
qibotn.quimb.eval(args.nqubits, args.qasm_circ, args.init_state)


if __name__ == "__main__":
Expand Down
8 changes: 8 additions & 0 deletions src/qibotn/cutn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# from qibotn import quimb as qiboquimb
from qibotn.QiboCircuitConvertor import QiboCircuitToEinsum
from cuquantum import contract


def eval(qibo_circ, datatype):
myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype)
return contract(*myconvertor.state_vector_operands())
48 changes: 48 additions & 0 deletions tests/test_cuquantum_cutensor_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from timeit import default_timer as timer

import config
import numpy as np
import pytest
import qibo
from qibo.models import QFT


def qibo_qft(nqubits, swaps):
circ_qibo = QFT(nqubits, swaps)
state_vec = np.array(circ_qibo())
return circ_qibo, state_vec


def time(func):
start = timer()
res = func()
end = timer()
time = end - start
return time, res


@pytest.mark.gpu
@pytest.mark.parametrize("nqubits", [1, 2, 5, 10])
def test_eval(nqubits: int, dtype="complex128"):
"""Evaluate QASM with cuQuantum.
Args:
nqubits (int): Total number of qubits in the system.
dtype (str): The data type for precision, 'complex64' for single,
'complex128' for double.
"""
import qibotn.cutn

# Test qibo
qibo.set_backend(backend=config.qibo.backend,
platform=config.qibo.platform)
qibo_time, (qibo_circ, result_sv) = time(
lambda: qibo_qft(nqubits, swaps=True))

# Test Cuquantum
cutn_time, result_tn = time(
lambda: qibotn.cutn.eval(qibo_circ, dtype).flatten())

assert 1e-2 * qibo_time < cutn_time < 1e2 * qibo_time
assert np.allclose(
result_sv, result_tn), "Resulting dense vectors do not match"

0 comments on commit 6455825

Please sign in to comment.