-
Notifications
You must be signed in to change notification settings - Fork 39
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
[dyn-alloc-base] Lightning dynamically allocate qubit memory #1043
base: master
Are you sure you want to change the base?
Conversation
Hello. You may have forgotten to update the changelog!
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1043 +/- ##
==========================================
- Coverage 91.89% 91.79% -0.10%
==========================================
Files 178 178
Lines 27465 27523 +58
==========================================
+ Hits 25238 25266 +28
- Misses 2227 2257 +30 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice job! A few comments and suggestions.
batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) | ||
results = tuple( | ||
self.simulate_and_jacobian( | ||
c, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map | ||
results = [] | ||
for circuit in circuits: | ||
if self.wires is None: # Dynamic wires allocation | ||
if self._statevector is None: | ||
self._statevector = self.LightningStateVector( | ||
num_wires=circuit.num_wires, dtype=self._c_dtype | ||
) | ||
else: | ||
if self._statevector.num_wires != circuit.num_wires: | ||
self._statevector.update_num_qubits(circuit.num_wires) | ||
circuit = circuit.map_to_standard_wires() | ||
|
||
results.append( | ||
self.simulate_and_jacobian( | ||
circuit, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map | ||
) | ||
) | ||
for c in circuits | ||
) | ||
return tuple(zip(*results)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And you know what? I'm not saying you need to do that, but the whole logic inside compute_derivative
and execute_and_compute derivatives
is essentially the same, right? We could have a single internal method that would depend on an external (to it) defined function (self.jacobian
and self.simulate_and_jacobian
in the case) and we call it in both cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed it's a bit confusing to have the combinations like [execute_and_, nothing] x [compute_derivative, vjp, ...]. I'll look at if this could be simplified
pennylane_lightning/core/src/simulators/lightning_qubit/StateVectorLQubit.hpp
Show resolved
Hide resolved
void updateNumQubits(std::size_t num_qubits) { | ||
BaseType::num_qubits_ = num_qubits; | ||
BaseType::setKernels(num_qubits, BaseType::threading_, | ||
BaseType::memory_model_); | ||
data_.resize(exp2(num_qubits)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is changing the number of qubits in place in the current scope?
If yes we may need to implement how we treat the data already there when we change the statevector's number of qubits. @maliasadi has experience with it, he might have a good intuition about this decision.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I believe we need to update num qubits in place is that we want the same device qml.device('lightning.qubit')
to be able to execute circuit with different sizes one after another, so resizing is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to determine what is the expected behaviour for the data already at the statevector when resizing.
def test_dynamic_allocate_qubit(self, qubit_device, tol): | ||
"""Test the dynamic allocation of qubits in Lightning devices""" | ||
|
||
dev = qubit_device(None) | ||
dev_default = qml.device("default.qubit") | ||
|
||
def circuit0(): | ||
qml.Hadamard(wires=0) | ||
qml.Identity(wires=1) | ||
return qml.state() | ||
|
||
def circuit1(): | ||
qml.Identity(wires=2) | ||
qml.Identity(wires=1) | ||
qml.Hadamard(wires=0) | ||
return qml.state() | ||
|
||
def circuit2(): | ||
qml.Identity(wires=3) | ||
qml.Identity(wires=1) | ||
qml.Hadamard(wires=0) | ||
return qml.state() | ||
|
||
def circuit3(): | ||
qml.Identity(wires=3) | ||
qml.Hadamard(wires=0) | ||
return qml.state() | ||
|
||
with pytest.raises(TypeError, match="missing 1 required positional argument: 'wires'"): | ||
qml.device(device_name) | ||
for circuit in [circuit0, circuit1, circuit2, circuit3]: | ||
results = qml.qnode(dev)(circuit)() | ||
expected = qml.qnode(dev_default)(circuit)() | ||
assert np.allclose(results, expected, atol=tol, rtol=0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about making the circuit cases a pytest parameter and providing the expected result explicitly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am happy to use the explicit results later, just before we merge the PR; for now it's convenient to make sure the results are consistent with default.qubit (the results can be counter-intuitive depending on the wires!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. So you are going to make the change when the PR is already fully reviewed, right?
void updateNumQubits(std::size_t num_qubits) { | ||
BaseType::num_qubits_ = num_qubits; | ||
length_ = exp2(num_qubits); | ||
BaseType::setKernels(num_qubits, BaseType::threading_, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to double check whether I need to manually dealloc and alloc, or will this setKernels call handle the memory correctly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setKernel
calls the method of a new instance created by getInstance
, and all the data stored in an OperationKernelMap
instance is stored in STL classes with their memory management. Right?
Please double-check, though.
Before submitting
Please complete the following checklist when submitting a PR:
All new features must include a unit test.
If you've fixed a bug or added code that should be tested, add a test to the
tests
directory!All new functions and code must be clearly commented and documented.
If you do make documentation changes, make sure that the docs build and
render correctly by running
make docs
.Ensure that the test suite passes, by running
make test
.Add a new entry to the
.github/CHANGELOG.md
file, summarizing thechange, and including a link back to the PR.
Ensure that code is properly formatted by running
make format
.When all the above are checked, delete everything above the dashed
line and fill in the pull request template.
Context:
This PR enables lightning devices to dynamically allocate memory for statevectors just before execution. The usual
qml.device("lightning.x", wires=y)
is still supported, but user can also not providewires
, i.e. `qml.device("lightning.x"), and the qubits required for the statevector is provided by the circuitDescription of the Change:
Benefits:
Possible Drawbacks:
Related GitHub Issues:
[sc-82702]