-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Optimise QuantumCircuit.assign_parameters
for single-parameter binding
#10548
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
Optimise QuantumCircuit.assign_parameters
for single-parameter binding
#10548
Conversation
When assigning parameters in a heavily parametrised circuit, a significant amount of time was spent constructing a new set of all the `Parameter` instances used in a circuit. This completely dominated the execution time for the case of binding a single parameter out of a many-parameters circuit in place. This modifies the internal-only method `QuantumCircuit._unsorted_parameters` to directly return the set object already constructed by the `ParameterTable` tracking the operations, which makes the function close to free to call, at the cost that internal users of that function must take care not to mutate the output. This is not generally a problem, since no code in Terra outside of `QuantumCircuit` uses that method (nor should it!), and `QuantumCircuit` never needs to mutate that set.
One or more of the the following people are requested to review this:
|
Pull Request Test Coverage Report for Build 5741733957
💛 - Coveralls |
@@ -2632,14 +2632,26 @@ def parameters(self) -> ParameterView: | |||
@property | |||
def num_parameters(self) -> int: | |||
"""The number of parameter objects in the circuit.""" | |||
# Avoid a (potential) object creation if we can. | |||
if self._parameters is not None: | |||
return len(self._parameters) | |||
return len(self._unsorted_parameters()) |
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.
In the current format calling num_parameters
would call _unsorted_parameters
every time if _parameters
is not yet set. Since sorting the parameters wouldn't be a big overhead once we gather them, how about replacing this whole property simply by
return len(self.parameters)
?
That would be a bit more expensive the first time, since it sorts the parameters on top of gathering them, but then the cached _parameters
would be set.
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.
Sorting the parameters actually is a big overhead - it scales as O(n lg(n))
in number of parameters and for PEC-style circuits that's a lot of parameters. One of the things I needed to make sure of in #10284 was that the sort cache was maintained when normalising the inputs for inplace=True
because it tanked performance if it was lost. I also had to optimise the comparison key there because it was really showing up in benchmarking, and in the case of assigning single parameters, we're going to lose the cache each time which would mean that a subsequent num_parameters
would retrigger it.
If parameters only get bound in the dictionary format only, then we never need to pay the sort cost at all in this format, and num_parameters
would still avoid it.
The QuantumCircuit.parameters
getter also (unfortunately) creates a new set object on every call, which is quite unfortunate for a property, really. It's potentially worth a follow-up PR to change the internal cache to a frozenset
so we can return it directly, although it's annoying that that would be a breaking API change.
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 QuantumCircuit.parameters getter also (unfortunately) creates a new set object on every call, which is quite unfortunate for a property, really. It's potentially worth a follow-up PR to change the internal cache to a frozenset so we can return it directly, although it's annoying that that would be a breaking API change.
That would mean changing from a sorted ParameterView
to returning a set of the parameters? Yeah would be worth a discussion whether sorting only when we really need to is better. A first issue that comes to mind is that users can no longer easily check the order of parameters and that binding via dict(zip(circuit.parameters, values))
isn't valid anymore.
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'm not proposing removing the sort from QuantumCircuit.parameters
, this is just avoiding producing new temporary collections when we can get use data from something else already. The other part is that the parameters
field is marked as an attribute, but it actually does a non-trivial amount of work to get itself; even if _parameters
is cached, it returns a copy of it. It does that so a user can't mutate the inners of QuantumCircuit
, but it comes with the cost of these performance bugs; it's not at all clear from accessing qc.parameters
that what you're actually getting is amortised qc._parameters.copy()
, which is somewhat costly for large numbers of parameters.
Pull Request Test Coverage Report for Build 6036368171
💛 - Coveralls |
The returned object may directly view onto the ``ParameterTable`` internals, and so | ||
should not be mutated. This is an internal performance detail. Code outside of this | ||
package should not use this 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.
Do you want to link the object here?
The returned object may directly view onto the ``ParameterTable`` internals, and so | |
should not be mutated. This is an internal performance detail. Code outside of this | |
package should not use this method. | |
The returned object may directly view onto the :class:`.ParameterTable` internals, and so | |
should not be mutated. This is an internal performance detail. Code outside of this | |
package should not use this 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.
ParameterTable
isn't part of the documented API, since it's only meant to be used in the private QuantumCircuit._parameters
, so there's no actual cross-ref that'll work for it. That said, this whole docstring is for a private method and not documented itself haha. (I guess I just wrote rST by force of habit)
Summary
When assigning parameters in a heavily parametrised circuit, a significant amount of time was spent constructing a new set of all the
Parameter
instances used in a circuit. This completely dominated the execution time for the case of binding a single parameter out of a many-parameters circuit in place.This modifies the internal-only method
QuantumCircuit._unsorted_parameters
to directly return the set object already constructed by theParameterTable
tracking the operations, which makes the function close to free to call, at the cost that internal users of that function must take care not to mutate the output. This is not generally a problem, since no code in Terra outside ofQuantumCircuit
uses that method (nor should it!), andQuantumCircuit
never needs to mutate that set.Details and comments
Given setup code:
this PR now takes 3.51(6)s on my machine to run:
Before this PR, that would have taken 30 minutes to an hour by my very approximate estimate.
To compare, one might have achieved the same thing using internal methods in Terra 0.24.2 that were removed in #10284 with:
which took 3.03(5)s on my machine. There's a little more overhead in the new form (~15%), most likely from the additional cost to build the dicts, but that's hopefully acceptable given that a) it now uses only public methods and b) the new form is more efficient for full binding of
ParameterVector
elements against array and massively more efficient for binding wrapped instructions as used by algorithms.cc: @chriseclectic.