Skip to content

Commit

Permalink
Merge branch 'master' into update-test-devices
Browse files Browse the repository at this point in the history
  • Loading branch information
andrijapau authored Nov 27, 2024
2 parents 5cba9fe + 672ba48 commit b2927c9
Show file tree
Hide file tree
Showing 45 changed files with 494 additions and 315 deletions.
86 changes: 45 additions & 41 deletions doc/development/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,21 @@ For example:
circuit()
This execute method works in tandem with the optional :meth:`Device.preprocess <pennylane.devices.Device.preprocess>`, described below in more detail.
Preprocessing turns generic circuits into ones supported by the device, or raises an error if the circuit is invalid. Execution produces numerical
results from those supported circuits.
This execute method works in tandem with the optional :meth:`Device.preprocess_transforms <pennylane.devices.Device.preprocess_transforms>`
and :meth:`Device.setup_execution_config`, described below in more detail. Preprocessing transforms
turns generic circuits into ones supported by the device, or raises an error if the circuit is invalid.
Execution produces numerical results from those supported circuits.

In a more minimal example, for any initial batch of quantum tapes and a config object, we expect to be able to do:

.. code-block:: python
transform_program, execution_config = dev.preprocess(initial_config)
execution_config = dev.setup_execution_config(initial_config)
transform_program = dev.preprocess_transforms(execution_config)
circuit_batch, postprocessing = transform_program(initial_circuit_batch)
results = dev.execute(circuit_batch, execution_config)
final_results = postprocessing(results)
Shots
-----

Expand Down Expand Up @@ -114,30 +115,34 @@ then finite shots exist. If ``shots`` is falsy, then an analytic execution shoul
Preprocessing
-------------

The :meth:`~.devices.Device.preprocess` method has two main responsibilities:
There are two components of preprocessing circuits for device execution:

1) Create a :class:`~.TransformProgram` capable of turning an arbitrary batch of :class:`~.QuantumScript`\ s into a new batch of tapes supported by the ``execute`` method.
2) Setup the :class:`~.ExecutionConfig` dataclass by filling in device options and making decisions about differentiation.

These two tasks can be extracted into private methods or helper functions if that improves source
code organization. Once the transform program has been applied to a batch of circuits, the result
These two tasks are performed by :meth:`~.devices.Device.setup_execution_config` and :meth:`~.devices.Device.preprocess_transforms`
respectively. Once the transform program has been applied to a batch of circuits, the result
circuit batch produced by the program should be run via ``Device.execute`` without error:

.. code-block:: python
transform_program, execution_config = dev.preprocess(initial_config)
execution_config = dev.setup_execution_config(initial_config)
transform_program = dev.preprocess_transforms(execution_config)
batch, fn = transform_program(initial_batch)
fn(dev.execute(batch, execution_config))
PennyLane can potentially provide a default implementation of the preprocessing program which should
be sufficient for most plugin devices. This requires that a TOML-formatted configuration file is
defined for your device. The details of this configuration file is described :ref:`the next section <device_capabilities>`. The
default preprocessing program will be constructed based on what is declared in this file if provided.
This section will focus on :meth:`~.devices.Device.preprocess_transforms`, see the section on the :ref:`**Execution Config** <execution_config>`
below for more information on :meth:`~.devices.Device.setup_execution_config`.

PennyLane can potentially provide a default implementation of a transform program through :meth:`~.devices.Device.preprocess_transforms`,
which should be sufficient for most plugin devices. This requires that a TOML-formatted configuration
file is defined for your device. The details of this configuration file is described :ref:`the next section <device_capabilities>`.
The default preprocessing program will be constructed based on what is declared in this file if provided.

Alternatively, you could override the :meth:`~.devices.Device.preprocess` method with a completely
customized implementation.
You could override the :meth:`~.devices.Device.preprocess_transforms` method with a completely
customized implementation, or extend the default behaviour by adding new transforms.

The :meth:`~.devices.Device.preprocess` method should start with creating a transform program:
The :meth:`~.devices.Device.preprocess_transforms` method should start with creating a transform program:

.. code-block:: python
Expand Down Expand Up @@ -180,9 +185,9 @@ classical component of any decompositions or compilation. For example,
Allows the user to see that both ``IsingXX`` and ``CH`` are decomposed by the device, and that
the diagonalizing gates for ``qml.expval(qml.X(0))`` are applied.

Even with these benefits, devices can still opt to
place some transforms inside the ``execute`` method. For example, ``default.qubit`` maps wires to simulation indices
inside ``execute`` instead of in ``preprocess``.
Even with these benefits, devices can still opt to place some transforms inside the ``execute``
method. For example, ``default.qubit`` maps wires to simulation indices inside ``execute`` instead
of in ``preprocess_transforms``.

The :meth:`~.devices.Device.execute` method can assume that device preprocessing has been performed on the input
tapes, and has no obligation to re-validate the input or provide sensible error messages. In the below example,
Expand Down Expand Up @@ -214,8 +219,6 @@ or can include in-built transforms such as:
* :func:`pennylane.devices.preprocess.validate_adjoint_trainable_params`
* :func:`pennylane.devices.preprocess.no_sampling`

See the section on the :ref:`**Execution Config** <execution_config>` below for more information on step 2.

.. _device_capabilities:

Device Capabilities
Expand All @@ -236,8 +239,8 @@ supported by your device, i.e., what the :meth:`~pennylane.devices.Device.execut
config_filepath = path.join(path.dirname(__file__), "relative/path/to/config.toml")
This configuration file will be loaded into another class variable :attr:`~pennylane.devices.Device.capabilities`
that is used in the default implementation of :meth:`~pennylane.devices.Device.preprocess` if you
choose not to override it yourself as described above. Note that this file must be declared as
that is used in the default implementation of :meth:`~pennylane.devices.Device.preprocess_transforms`
if you choose not to override it yourself as described above. Note that this file must be declared as
package data as instructed at the end of :ref:`this section <packaging>`.

Below is an example configuration file defining all accepted fields, with inline descriptions of
Expand Down Expand Up @@ -377,12 +380,12 @@ Devices can now either:
3) Strictly require specific wire labels

Option 2 allows workflows to change the number and labeling of wires over time, but sometimes users want
to enforce a wire convention and labels. If a user does provide wires, :meth:`~.devices.Device.preprocess` should
to enforce a wire convention and labels. If a user does provide wires, :meth:`~.devices.Device.preprocess_transforms` should
validate that submitted circuits only have wires in the requested range.

>>> dev = qml.device('default.qubit', wires=1)
>>> circuit = qml.tape.QuantumScript([qml.CNOT((0,1))], [qml.state()])
>>> dev.preprocess()[0]((circuit,))
>>> dev.preprocess_transforms()((circuit,))
WireError: Cannot run circuit(s) of default.qubit as they contain wires not found on the device.

PennyLane wires can be any hashable object, where wire labels are distinguished by their equality and hash.
Expand Down Expand Up @@ -429,22 +432,24 @@ The execution config stores two kinds of information:

**Device options:**

Device options are any device specific options used to configure the behavior of an execution. For example, ``default.qubit``
has ``max_workers``, ``rng``, and ``prng_key``. ``default.tensor`` has ``contract``, ``cutoff``, ``dtype``, ``method``, and ``max_bond_dim``.
These options are often set with default values on initialization. These values should be placed into the ``ExecutionConfig.device_options``
dictionary on preprocessing.
Device options are any device specific options used to configure the behavior of an execution. For
example, ``default.qubit`` has ``max_workers``, ``rng``, and ``prng_key``. ``default.tensor`` has
``contract``, ``cutoff``, ``dtype``, ``method``, and ``max_bond_dim``. These options are often set
with default values on initialization. These values should be placed into the ``ExecutionConfig.device_options``
dictionary in :meth:`~.devices.Device.setup_execution_config`. Note that we do provide a default
implementation of this method, but you will most likely need to override it yourself.

>>> dev = qml.device('default.tensor', wires=2, max_bond_dim=4, contract="nonlocal", dtype=np.complex64)
>>> dev.preprocess()[1].device_options
>>> dev.setup_execution_config().device_options
{'contract': 'nonlocal',
'cutoff': 1.1920929e-07,
'dtype': numpy.complex64,
'method': 'mps',
'max_bond_dim': 4}

Even if the property is stored as an attribute on the device, execution should pull the
value of these properties from the config instead of from the device instance.
While not yet integrated at the top user level, we aim to allow dynamic configuration of the device.
Even if the property is stored as an attribute on the device, execution should pull the value of
these properties from the config instead of from the device instance. While not yet integrated at
the top user level, we aim to allow dynamic configuration of the device.

>>> dev = qml.device('default.qubit')
>>> config = qml.devices.ExecutionConfig(device_options={"rng": 42})
Expand All @@ -454,27 +459,26 @@ array([1, 0, 1, 1, 0, 1, 1, 1, 0, 0])
>>> dev.execute(tape, config)
array([1, 0, 1, 1, 0, 1, 1, 1, 0, 0])

By pulling options from this dictionary instead of from device properties,
we unlock two key pieces of functionality.
By pulling options from this dictionary instead of from device properties, we unlock two key
pieces of functionality:

1) Track and specify the exact configuration of the execution by only inspecting the ``ExecutionConfig`` object
2) Dynamically configure the device over the course of a workflow.


**Workflow Configuration:**

Note that these properties are only applicable to devices that provided derivatives or VJPs. If your device
does not provide derivatives, you can safely ignore these properties.

The workflow options are ``use_device_gradient``, ``use_device_jacobian_product``, and ``grad_on_execution``.
``use_device_gradient=True`` indicates that workflow should request derivatives from the device.
``grad_on_execution=True`` indicates a preference
to use ``execute_and_compute_derivatives`` instead of ``execute`` followed by ``compute_derivatives``. Finally,
``use_device_jacobian_product`` indicates a request to call ``compute_vjp`` instead of ``compute_derivatives``. Note that
if ``use_device_jacobian_product`` is ``True``, this takes precedence over calculating the full jacobian.
``grad_on_execution=True`` indicates a preference to use ``execute_and_compute_derivatives`` instead
of ``execute`` followed by ``compute_derivatives``. Finally, ``use_device_jacobian_product`` indicates
a request to call ``compute_vjp`` instead of ``compute_derivatives``. Note that if ``use_device_jacobian_product``
is ``True``, this takes precedence over calculating the full jacobian.

>>> config = qml.devices.ExecutionConfig(gradient_method="adjoint")
>>> processed_config = qml.device('default.qubit').preprocess(config)[1]
>>> processed_config = qml.device('default.qubit').setup_execution_config(config)
>>> processed_config.use_device_jacobian_product
True
>>> processed_config.use_device_gradient
Expand Down
14 changes: 12 additions & 2 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@

<h3>New features since last release</h3>

* Two new methods: `setup_execution_config` and `preprocess_transforms` are added to the `Device`
class. Device developers are encouraged to override these two methods separately instead of the
`preprocess` method. For now, to avoid ambiguity, a device is allowed to override either these
two methods or `preprocess`, but not both. In the long term, we will slowly phase out the use of
`preprocess` in favour of these two methods for better separation of concerns.
[(#6617)](https://github.com/PennyLaneAI/pennylane/pull/6617)

* Developers of plugin devices now have the option of providing a TOML-formatted configuration file
to declare the capabilities of the device. See [Device Capabilities](https://docs.pennylane.ai/en/latest/development/plugins.html#device-capabilities) for details.
[(#6407)](https://github.com/PennyLaneAI/pennylane/pull/6407)
[(#6433)](https://github.com/PennyLaneAI/pennylane/pull/6433)

* An internal module `pennylane.devices.capabilities` is added that defines a new `DeviceCapabilites`
data class, as well as functions that load and parse the TOML-formatted configuration files.
[(#6407)](https://github.com/PennyLaneAI/pennylane/pull/6407)

```pycon
>>> from pennylane.devices.capabilities import DeviceCapabilities
Expand All @@ -22,6 +28,7 @@
* Devices that extends `qml.devices.Device` now has an optional class attribute `capabilities`
that is an instance of the `DeviceCapabilities` data class, constructed from the configuration
file if it exists. Otherwise, it is set to `None`.
[(#6433)](https://github.com/PennyLaneAI/pennylane/pull/6433)

```python
from pennylane.devices import Device
Expand Down Expand Up @@ -83,6 +90,9 @@
visualizations, allowing global and per-wire customization with options like `color`, `linestyle`, and `linewidth`.
[(#6486)](https://github.com/PennyLaneAI/pennylane/pull/6486)

* `QNode` and `qml.execute` now forbid certain keyword arguments from being passed positionally.
[(#6610)](https://github.com/PennyLaneAI/pennylane/pull/6610)

* Shortened the string representation for the `qml.S`, `qml.T`, and `qml.SX` operators.
[(#6542)](https://github.com/PennyLaneAI/pennylane/pull/6542)

Expand Down
Loading

0 comments on commit b2927c9

Please sign in to comment.