-
Notifications
You must be signed in to change notification settings - Fork 586
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
Nailing down automatic circuit cutter's API #2168
Conversation
- helper function for deriving best default cut parameters - 2 container dataclesses (CutConfig and CutSpec) for automatic cutter's input and output.
Hello. You may have forgotten to update the changelog!
|
Codecov Report
@@ Coverage Diff @@
## master #2168 +/- ##
========================================
Coverage 99.26% 99.26%
========================================
Files 231 231
Lines 18228 18330 +102
========================================
+ Hits 18094 18196 +102
Misses 134 134
Continue to review full report at Codecov.
|
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.
Thanks @zeyueN!
[sc-14728] |
Edited the description. Promoting to real PR. |
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.
Thanks @zeyueN! I left a few suggestions/comments, but overall this looks great and happy to approve once these are resolved.
pennylane/transforms/qcut.py
Outdated
``devices`` is provided where it defaults to the maximum number of wires among | ||
``devices``. | ||
min_free_wires (int): Number of wires for the smallest available device. | ||
Optional, defaults to ``max_device_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.
It might be good to add a sentence explaining the implication of setting this number not to be default.
pennylane/transforms/qcut.py
Outdated
**Example** | ||
|
||
.. code-block:: python | ||
|
||
import pennylane as qml | ||
from pennylane.transforms import qcut | ||
|
||
dev = qml.device('default.qubit', wires=4) | ||
|
||
with qml.tape.QuantumTape() as tape: | ||
qml.RX(0.4, wires=0) | ||
qml.RY(0.9, wires=0) | ||
qml.CNOT(wires=[0, 1]) | ||
qml.expval(qml.PauliZ(1)) | ||
|
||
tape_dag = qcut.tape_to_graph(tape) | ||
|
||
Deriving kwargs for a given circuit and feed it to a custom partitioner along with an extra | ||
custom parameter: | ||
>>> cut_strategy = qcut.CutStrategy(devices=dev) | ||
>>> cut_kwargs = cut_strategy.get_cut_kwargs(tape_dag) | ||
>>> cut_kwargs.update({'extra_param': 0}) | ||
>>> my_partitioner(tape_dag, **cut_kwargs) |
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.
Would be good to move the example below args and returns
pennylane/transforms/qcut.py
Outdated
Deriving kwargs for a given circuit and feed it to a custom partitioner along with an extra | ||
custom parameter: | ||
>>> cut_strategy = qcut.CutStrategy(devices=dev) | ||
>>> cut_kwargs = cut_strategy.get_cut_kwargs(tape_dag) | ||
>>> cut_kwargs.update({'extra_param': 0}) | ||
>>> my_partitioner(tape_dag, **cut_kwargs) |
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.
Perhaps this section is the only bit we need.
pennylane/transforms/qcut.py
Outdated
List[Dict[str, Any]]: A list of minimal set of kwargs being passed to a graph | ||
partitioner method. |
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 think you might have answered in previous comments, but why a list? Also it seems at odds with the example:
>>> cut_kwargs = cut_strategy.get_cut_kwargs(tape_dag)
>>> cut_kwargs.update({'extra_param': 0})
>>> my_partitioner(tape_dag, **cut_kwargs)
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 returned list contains all potential cut kwargs to explore, i.e. different k
s and imbalance
s. You are right the example is wrong, have fixed it.
pennylane/transforms/qcut.py
Outdated
assert isinstance(max_wires_by_fragment, (list, tuple)) | ||
assert all(isinstance(i, int) and i > 0 for i in max_wires_by_fragment) |
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.
Are asserts the best choice for user validation? It's more length by if-raise
seems more natural.
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.
switched.
): | ||
"""Deriving cutting constraints from given devices and parameters.""" | ||
|
||
self.max_free_wires = self.max_free_wires or self.min_free_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.
I don't really understand what this is doing, why is there the or self.min_free_wires
here?
Couldn't we just have these values set as in lines 617
and 618
?
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.
This is me wanting to be slightly more forgiving before checking if one of devices
and max_free_wires
is provided, where if neither are provided but min_free_wires
are, then I interpret max_free_wires
to be the same as min_free_wires
. Unlikely situation, but if it happens this interpretation should be safe.
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.
Thanks this makes sense, though now I'm not sure I understand the docstring where for max_free_wires
we have "Optional only when ``devices`` is provided
" and vice versa for devices
. Maybe I'm misunderstanding the use of Optional
but this seems to suggest that at least one of the two must be passed?
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.
You are correct that the docstring doesn't accurately describe this behaviour. This was intentional since this is sort of a "failsafe" behaviour which doesn't need to be advertised to users since it adds nothing functionally new and could potentially confuse people even more. In the end, I do want to force users to provide either devices
or max_free_wires
.
pennylane/transforms/qcut.py
Outdated
if max_wires_by_fragment is not None and max_gates_by_fragment is not None: | ||
assert len(max_wires_by_fragment) == len(max_gates_by_fragment) |
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.
why do these need to be of equal length?
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.
Because the length implicitly signifies the number of fragments, i.e. k
.
pennylane/transforms/qcut.py
Outdated
assert free_wires >= avg_fragment_wires | ||
assert free_gates >= avg_fragment_gates |
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 think free_wires
can be a user defined variable (max_wires_by_fragment
) so this might be well suited to raising an exception. Same for gates.
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.
Done. Also added a test since these raises won't be able to get triggered by user facing functions due to all the checks before the function is invoked.
Co-authored-by: anthayes92 <34694788+anthayes92@users.noreply.github.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Co-authored-by: anthayes92 <34694788+anthayes92@users.noreply.github.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
@trbromley I think I've addressed all the comments and ready for review.
|
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.
Thanks @zeyueN, just a few more comments.
pennylane/transforms/qcut.py
Outdated
(not isinstance(d, qml.Device) for d in devices) | ||
): | ||
raise ValueError( | ||
f"Argument `devices` contain at least one `Device` instance, got {type(devices)}." |
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 this the right error message? We raise an error if:
- Devices is not a list of tuple, or
- Any element in device is not a
qml.Device
Maybe the error message should just read:
Argument `devices` must be a list or tuple containing elements of type qml.Device
(Also, should we just check for Sequence
rather than list/tuple?)
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.
Ya definitely screwed up that sentence, switched to your suggestion.
pennylane/transforms/qcut.py
Outdated
devices: InitVar[Sequence[qml.Device]] = None | ||
max_free_wires: int = None | ||
min_free_wires: int = None | ||
num_fragments_probed: Union[int, Sequence[int]] = None | ||
max_free_gates: int = None | ||
min_free_gates: int = None | ||
imbalance_tolerance: float = None |
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.
Since the docs shows these attributes without docstrings:
Would it be possible to add quick one line explanations? E.g.,
imbalance_tolerance: float = None
#: The global maximum allowed imbalance for all partition trials
(I'm not completely sure on the UI to get this to show up in the docs so might need some trial and error)
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.
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.
Awesome, thanks for figuring out the right orientation for the comment!
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
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.
Awesome, looks great - thanks @zeyueN! I left some final comments, but otherwise good to go! Approved 🟢
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Context:
When passing a tape to the automatic circuit cutter (KaHyPar), several hyperparameters (e.g.
k
,imbalance
, etc.) need to be supplied. Depending on use cases, sensible default values should be derived based on known information (which usually comes at different points in time) such as the available devices and the circuits. However, power users should also be able to override and supply their own parameters. We thus must provide a unified interface that's general enough to allow these types of interactions.Drawing similarities between distributing circuit fragments to devices and the classical problem of distributing computation tasks dags to multiple workers, the constraints coming from devices should be separated from the requirements coming from the computations. This separation is realized inside the added class
CutStrategy
, whose attributes contain constraints from devices and whose methodget_cut_kwargs()
takes circuit requirements at runtime and returns complete sets of cutting hyperparameters. In terms of UI, both the device constrains and the calling ofget_cut_kwargs()
could be done either manually by users or within decorators.dataclass
was elected to make the implementation of the init function cleaner.Description of the Change:
Adds:
CutStrategy
that can be used as an interface to coordinate devices info, user constraints, and circuit requirements into partition hyperparameters ready for partitioning methods.Possible Drawbacks:
Gate cut compatibility not fully explored