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

Add Mid-Measurement support to single-GPU LGPU #931

Merged
merged 33 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
29cf4f0
Initial commit
multiphaseCFD Oct 2, 2024
0c449bf
Auto update version from '0.39.0-dev34' to '0.39.0-dev36'
ringo-but-quantum Oct 2, 2024
1582064
Merge branch 'master' into add_collapse_lgpu
multiphaseCFD Oct 7, 2024
6c36e5c
Auto update version from '0.39.0-dev39' to '0.39.0-dev40'
ringo-but-quantum Oct 7, 2024
80c3a62
tidy up code and add mpi support
multiphaseCFD Oct 8, 2024
b2a3758
Auto update version from '0.39.0-dev40' to '0.39.0-dev41'
ringo-but-quantum Oct 8, 2024
960dc84
Add native `setStateVector` support to `lightning.gpu` (#930)
multiphaseCFD Oct 8, 2024
c7ea1a8
Auto update version from '0.39.0-dev40' to '0.39.0-dev41'
ringo-but-quantum Oct 8, 2024
208782f
Merge branch 'master' into add_collapse_lgpu
multiphaseCFD Oct 8, 2024
829f8eb
Auto update version from '0.39.0-dev40' to '0.39.0-dev41'
ringo-but-quantum Oct 8, 2024
2493260
update collapse with custatevec apis
multiphaseCFD Oct 8, 2024
4238aeb
Auto update version from '0.39.0-dev41' to '0.39.0-dev42'
ringo-but-quantum Oct 8, 2024
15c7e2f
add python layer
multiphaseCFD Oct 8, 2024
6dc7c03
update changelog
multiphaseCFD Oct 8, 2024
5294b59
Merge branch 'master' into add_collapse_lgpu
multiphaseCFD Oct 8, 2024
a57b5ee
Auto update version from '0.39.0-dev41' to '0.39.0-dev42'
ringo-but-quantum Oct 8, 2024
d608f06
make format
multiphaseCFD Oct 9, 2024
63facdd
drop mpi support for mid-measurement
multiphaseCFD Oct 9, 2024
53ec72b
make format
multiphaseCFD Oct 9, 2024
410d694
test mpi support
multiphaseCFD Oct 10, 2024
7d631f5
Merge branch 'master' into add_collapse_lgpu
multiphaseCFD Oct 10, 2024
6a010f1
tidy up the code
multiphaseCFD Oct 10, 2024
7f4c97f
revert mpi support
multiphaseCFD Oct 10, 2024
85dc575
Auto update version from '0.39.0-dev42' to '0.39.0-dev43'
ringo-but-quantum Oct 10, 2024
0e9f1db
tidy up code
multiphaseCFD Oct 10, 2024
aad13c9
drop mpi implementation
multiphaseCFD Oct 15, 2024
82b59e3
Add todos
multiphaseCFD Oct 15, 2024
168b436
tidy up docstring
multiphaseCFD Oct 15, 2024
ab021b7
update C++ layer
multiphaseCFD Oct 15, 2024
19b3ce1
update
multiphaseCFD Oct 15, 2024
44a3f77
remove use_mpi api
multiphaseCFD Oct 15, 2024
2ade110
Merge branch 'master' into add_collapse_lgpu
multiphaseCFD Oct 15, 2024
00184e0
Auto update version from '0.39.0-dev43' to '0.39.0-dev44'
ringo-but-quantum Oct 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

### New features since last release

* Add `mid-circuit measurements` support to `lightning.gpu`'s single-GPU backend.
multiphaseCFD marked this conversation as resolved.
Show resolved Hide resolved
[(#931)](https://github.com/PennyLaneAI/pennylane-lightning/pull/931)

* Add Matrix Product Operator (MPO) for all gates support to `lightning.tensor`. Note current C++ implementation only works for MPO sites data provided by users.
[(#859)](https://github.com/PennyLaneAI/pennylane-lightning/pull/859)

Expand Down
43 changes: 43 additions & 0 deletions mpitests/test_native_mcm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2024 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for default qubit preprocessing."""
import numpy as np
import pennylane as qml
import pytest
from conftest import LightningDevice, device_name
from mpi4py import MPI

if not LightningDevice._CPP_BINARY_AVAILABLE: # pylint: disable=protected-access
pytest.skip("No binary module found. Skipping.", allow_module_level=True)


def test_unspported_mid_measurement():
"""Test unsupported mid_measurement for Lightning-GPU-MPI."""
comm = MPI.COMM_WORLD
dev = qml.device(device_name, wires=2, mpi=True, shots=1000)
params = np.pi / 4 * np.ones(2)

@qml.qnode(dev)
def func(x, y):
qml.RX(x, wires=0)
m0 = qml.measure(0)
qml.cond(m0, qml.RY)(y, wires=1)
return qml.probs(wires=0)

comm.Barrier()

with pytest.raises(
qml.DeviceError, match="Lightning-GPU-MPI does not support Mid-circuit measurements."
):
func(*params)
2 changes: 1 addition & 1 deletion pennylane_lightning/core/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@
Version number (major.minor.patch[-label])
"""

__version__ = "0.39.0-dev43"
__version__ = "0.39.0-dev44"
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,56 @@ class StateVectorCudaManaged
applyMatrix(gate_matrix.data(), wires, adjoint);
}

/**
* @brief Collapse the state vector after having measured one of the qubit.
*
* Note: The branch parameter imposes the measurement result on the given
* wire.
*
* @param wire Wire to measure.
* @param branch Branch 0 or 1.
*/
void collapse(std::size_t wire, bool branch) {
PL_ABORT_IF_NOT(wire < BaseType::getNumQubits(), "Invalid wire index.");
cudaDataType_t data_type;

if constexpr (std::is_same_v<CFP_t, cuDoubleComplex> ||
std::is_same_v<CFP_t, double2>) {
data_type = CUDA_C_64F;
} else {
data_type = CUDA_C_32F;
}

std::vector<int> basisBits(1, BaseType::getNumQubits() - 1 - wire);

double abs2sum0;
double abs2sum1;

PL_CUSTATEVEC_IS_SUCCESS(custatevecAbs2SumOnZBasis(
/* custatevecHandle_t */ handle_.get(),
/* void *sv */ BaseType::getData(),
/* cudaDataType_t */ data_type,
/* const uint32_t nIndexBits */ BaseType::getNumQubits(),
/* double * */ &abs2sum0,
/* double * */ &abs2sum1,
/* const int32_t * */ basisBits.data(),
/* const uint32_t nBasisBits */ basisBits.size()));

const double norm = branch ? abs2sum1 : abs2sum0;

const int parity = static_cast<int>(branch);

PL_CUSTATEVEC_IS_SUCCESS(custatevecCollapseOnZBasis(
/* custatevecHandle_t */ handle_.get(),
/* void *sv */ BaseType::getData(),
/* cudaDataType_t */ data_type,
/* const uint32_t nIndexBits */ BaseType::getNumQubits(),
/* const int32_t parity */ parity,
/* const int32_t *basisBits */ basisBits.data(),
/* const uint32_t nBasisBits */ basisBits.size(),
/* double norm */ norm));
}

//****************************************************************************//
// Explicit gate calls for bindings
//****************************************************************************//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ void registerBackendClassSpecificBindings(PyClass &pyclass) {
},
py::arg("async") = false,
"Initialize the statevector data to the |0...0> state")
.def("collapse", &StateVectorT::collapse,
maliasadi marked this conversation as resolved.
Show resolved Hide resolved
"Collapse the statevector onto the 0 or 1 branch of a given wire.")
.def(
"apply",
[](StateVectorT &sv, const std::string &str,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ void setBasisState_CUDA(cuDoubleComplex *sv, cuDoubleComplex &value,
cudaStream_t stream_id);

/**
* @brief The CUDA kernel that setS state vector data on GPU device from the
* @brief The CUDA kernel that sets state vector data on GPU device from the
* input values (on device) and their corresponding indices (on device)
* information.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,45 @@ TEMPLATE_TEST_CASE("StateVectorCudaManaged::StateVectorCudaManaged",
REQUIRE(std::is_constructible_v<StateVectorT, const StateVectorT &>);
}
}

TEMPLATE_TEST_CASE("StateVectorCudaManaged::collapse",
"[StateVectorCudaManaged]", float, double) {
using PrecisionT = TestType;
using ComplexT = typename StateVectorCudaManaged<PrecisionT>::ComplexT;
using CFP_t = typename StateVectorCudaManaged<PrecisionT>::CFP_t;
using TestVectorT = TestVector<ComplexT>;

std::size_t wire = GENERATE(0, 1, 2);
std::size_t branch = GENERATE(0, 1);
multiphaseCFD marked this conversation as resolved.
Show resolved Hide resolved
constexpr std::size_t num_qubits = 3;

// TODO @tomlqc use same template for testing all Lightning flavours?

SECTION("Collapse the state vector after having measured one of the "
"qubits.") {
TestVectorT init_state = createPlusState_<ComplexT>(num_qubits);

const ComplexT coef{0.5, PrecisionT{0.0}};
const ComplexT zero{PrecisionT{0.0}, PrecisionT{0.0}};

std::vector<std::vector<std::vector<ComplexT>>> expected_state = {
{{coef, coef, coef, coef, zero, zero, zero, zero},
{coef, coef, zero, zero, coef, coef, zero, zero},
{coef, zero, coef, zero, coef, zero, coef, zero}},
{{zero, zero, zero, zero, coef, coef, coef, coef},
{zero, zero, coef, coef, zero, zero, coef, coef},
{zero, coef, zero, coef, zero, coef, zero, coef}},
};

StateVectorCudaManaged<PrecisionT> sv(
reinterpret_cast<CFP_t *>(init_state.data()), init_state.size());

sv.collapse(wire, branch);
multiphaseCFD marked this conversation as resolved.
Show resolved Hide resolved

PrecisionT eps = std::numeric_limits<PrecisionT>::epsilon() * 1e2;
REQUIRE(isApproxEqual(sv.getDataVector().data(),
sv.getDataVector().size(),
expected_state[branch][wire].data(),
expected_state[branch][wire].size(), eps));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -317,4 +317,4 @@ TEMPLATE_PRODUCT_TEST_CASE("StateVectorCudaMPI::applyOperations",
{false, false}, {{0.0}}),
LightningException, "must all be equal"); // invalid parameters
}
}
}
35 changes: 32 additions & 3 deletions pennylane_lightning/lightning_gpu/_state_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,17 @@
import numpy as np
import pennylane as qml
from pennylane import DeviceError
from pennylane.measurements import MidMeasureMP
from pennylane.ops import Conditional
from pennylane.ops.op_math import Adjoint
from pennylane.tape import QuantumScript
from pennylane.wires import Wires

# pylint: disable=ungrouped-imports
from pennylane_lightning.core._serialize import global_phase_diagonal
from pennylane_lightning.core._state_vector_base import LightningBaseStateVector

from ._measurements import LightningGPUMeasurements
from ._mpi_handler import MPIHandler

gate_cache_needs_hash = (
Expand Down Expand Up @@ -247,15 +251,33 @@
matrix = global_phase_diagonal(param, self.wires, control_wires, control_values)
state.apply(name, wires, inv, [[param]], matrix)

def _apply_lightning_midmeasure(self):
def _apply_lightning_midmeasure(
self, operation: MidMeasureMP, mid_measurements: dict, postselect_mode: str
):
"""Execute a MidMeasureMP operation and return the sample in mid_measurements.

Args:
operation (~pennylane.operation.Operation): mid-circuit measurement
mid_measurements (None, dict): Dictionary of mid-circuit measurements
postselect_mode (str): Configuration for handling shots with mid-circuit measurement
postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to
keep the same number of shots.

Returns:
None
"""
raise DeviceError("LightningGPU does not support Mid-circuit measurements.")
wires = self.wires.indices(operation.wires)
wire = list(wires)[0]
if postselect_mode == "fill-shots" and operation.postselect is not None:
sample = operation.postselect

Check warning on line 272 in pennylane_lightning/lightning_gpu/_state_vector.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/_state_vector.py#L269-L272

Added lines #L269 - L272 were not covered by tests
multiphaseCFD marked this conversation as resolved.
Show resolved Hide resolved
else:
circuit = QuantumScript([], [qml.sample(wires=operation.wires)], shots=1)
sample = LightningGPUMeasurements(self).measure_final_state(circuit)
sample = np.squeeze(sample)
mid_measurements[operation] = sample
getattr(self.state_vector, "collapse")(wire, bool(sample))
if operation.reset and bool(sample):
self.apply_operations([qml.PauliX(operation.wires)], mid_measurements=mid_measurements)

Check warning on line 280 in pennylane_lightning/lightning_gpu/_state_vector.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/_state_vector.py#L274-L280

Added lines #L274 - L280 were not covered by tests
multiphaseCFD marked this conversation as resolved.
Show resolved Hide resolved

# pylint: disable=unused-argument
def _apply_lightning(
Expand Down Expand Up @@ -289,7 +311,14 @@
method = getattr(state, name, None)
wires = list(operation.wires)

if method is not None: # apply specialized gate
if isinstance(operation, Conditional):
if operation.meas_val.concretize(mid_measurements):
self._apply_lightning([operation.base])

Check warning on line 316 in pennylane_lightning/lightning_gpu/_state_vector.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/_state_vector.py#L315-L316

Added lines #L315 - L316 were not covered by tests
multiphaseCFD marked this conversation as resolved.
Show resolved Hide resolved
elif isinstance(operation, MidMeasureMP):
self._apply_lightning_midmeasure(

Check warning on line 318 in pennylane_lightning/lightning_gpu/_state_vector.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/_state_vector.py#L318

Added line #L318 was not covered by tests
operation, mid_measurements, postselect_mode=postselect_mode
)
elif method is not None: # apply specialized gate
param = operation.parameters
method(wires, invert_param, param)
elif isinstance(operation, qml.ops.Controlled) and isinstance(
Expand Down
35 changes: 30 additions & 5 deletions pennylane_lightning/lightning_gpu/lightning_gpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,7 @@
def stopping_condition_shots(op: Operator) -> bool:
"""A function that determines whether or not an operation is supported by ``lightning.gpu``
with finite shots."""
if isinstance(op, (MidMeasureMP, qml.ops.op_math.Conditional)):
# LightningGPU does not support Mid-circuit measurements.
return False
return stopping_condition(op)
return stopping_condition(op) or isinstance(op, (MidMeasureMP, qml.ops.op_math.Conditional))


def accepted_observables(obs: Operator) -> bool:
Expand Down Expand Up @@ -460,6 +457,7 @@
self.simulate(
circuit,
self._statevector,
postselect_mode=execution_config.mcm_config.postselect_mode,
)
)

Expand Down Expand Up @@ -494,20 +492,47 @@
self,
circuit: QuantumScript,
state: LightningGPUStateVector,
postselect_mode: Optional[str] = None,
) -> Result:
"""Simulate a single quantum script.

Args:
circuit (QuantumTape): The single circuit to simulate
state (LightningGPUStateVector): handle to Lightning state vector
postselect_mode (str): Configuration for handling shots with mid-circuit measurement
postselection. Use ``"hw-like"`` to discard invalid shots and ``"fill-shots"`` to
keep the same number of shots. Default is ``None``.

Returns:
Tuple[TensorLike]: The results of the simulation

Note that this function can return measurements for non-commuting observables simultaneously.
"""
if circuit.shots and (any(isinstance(op, MidMeasureMP) for op in circuit.operations)):
raise qml.DeviceError("LightningGPU does not support Mid-circuit measurements.")
if self._mpi_handler.use_mpi:
raise qml.DeviceError(
"Lightning-GPU-MPI does not support Mid-circuit measurements."
)

results = []
aux_circ = QuantumScript(

Check warning on line 518 in pennylane_lightning/lightning_gpu/lightning_gpu.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/lightning_gpu.py#L517-L518

Added lines #L517 - L518 were not covered by tests
circuit.operations,
circuit.measurements,
shots=[1],
trainable_params=circuit.trainable_params,
)
for _ in range(circuit.shots.total_shots):
state.reset_state()
mid_measurements = {}
final_state = state.get_final_state(

Check warning on line 527 in pennylane_lightning/lightning_gpu/lightning_gpu.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/lightning_gpu.py#L524-L527

Added lines #L524 - L527 were not covered by tests
aux_circ, mid_measurements=mid_measurements, postselect_mode=postselect_mode
)
results.append(

Check warning on line 530 in pennylane_lightning/lightning_gpu/lightning_gpu.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/lightning_gpu.py#L530

Added line #L530 was not covered by tests
self.LightningMeasurements(final_state).measure_final_state(
aux_circ, mid_measurements=mid_measurements
)
)
return tuple(results)

Check warning on line 535 in pennylane_lightning/lightning_gpu/lightning_gpu.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_gpu/lightning_gpu.py#L535

Added line #L535 was not covered by tests

state.reset_state()
final_state = state.get_final_state(circuit)
Expand Down
2 changes: 1 addition & 1 deletion pennylane_lightning/lightning_gpu/lightning_gpu.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ qjit_compatible = false
# If the device requires run time generation of the quantum circuit.
runtime_code_generation = false
# If the device supports mid circuit measurements natively
mid_circuit_measurement = false
mid_circuit_measurement = true

# This field is currently unchecked but it is reserved for the purpose of
# determining if the device supports dynamic qubit allocation/deallocation.
Expand Down
4 changes: 2 additions & 2 deletions tests/test_native_mcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from conftest import LightningDevice, device_name, validate_measurements
from flaky import flaky

if device_name not in ("lightning.qubit", "lightning.kokkos"):
if device_name not in ("lightning.qubit", "lightning.kokkos", "lightning.gpu"):
multiphaseCFD marked this conversation as resolved.
Show resolved Hide resolved
pytest.skip("Native MCM not supported. Skipping.", allow_module_level=True)

if not LightningDevice._CPP_BINARY_AVAILABLE: # pylint: disable=protected-access
Expand Down Expand Up @@ -89,7 +89,7 @@ def func(x, y):
match=f"not accepted with finite shots on lightning.qubit",
):
func(*params)
if device_name == "lightning.kokkos":
if device_name in ("lightning.kokkos", "lightning.gpu"):
with pytest.raises(
qml.DeviceError,
match=r"Measurement shadow\(wires=\[0\]\) not accepted with finite shots on "
Expand Down
Loading