-
Notifications
You must be signed in to change notification settings - Fork 575
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 support for @tf.function
on non-TF devices
#1886
Conversation
Hello. You may have forgotten to update the changelog!
|
Codecov Report
@@ Coverage Diff @@
## master #1886 +/- ##
=======================================
Coverage 98.84% 98.84%
=======================================
Files 220 221 +1
Lines 16843 16937 +94
=======================================
+ Hits 16648 16742 +94
Misses 195 195
Continue to review full report at Codecov.
|
@tf.function
on non-TF devices
@tf.function
on non-TF devices@tf.function
on non-TF devices
Hi @josh146. I checked the changes in code and everything looks OK. I'm defining a QNODE in the same way you did in your example, and I'm getting:
In the last one I'm getting a warning:
Comparing gradient execution times for a given cost function: >>> def cost(circuit, x, y):
... res = circuit(x, y)
... return res[0, 0] - res[1, 1]
>>> %%timeit
... with tf.GradientTape() as tape:
... loss = cost(circuit, x, y)
... grad = tape.gradient(loss, [x, y])
12.3 ms ± 527 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) Here I'm getting:
%%timeit
... with tf.GradientTape() as tape:
... loss = cost(circuit_autograph, x, y)
... grad = tape.gradient(loss, [x, y])
4 ms ± 218 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) But here I'm getting an error:
I can provide you a traceback of this error if you like. Note that, while a large improvement, native backprop with autograph still beats both: >>> @tf.function
... @qml.beta.qnode(dev, diff_method="backprop", interface="tf")
... def circuit_backprop_autograph(x, y):
... qml.RX(x, wires=[0])
... qml.RY(y, wires=[1])
... qml.CNOT(wires=[0, 1])
... return qml.probs(wires=[0]), qml.probs(wires=[1])
>>> %%timeit
... with tf.GradientTape() as tape:
... loss = cost(circuit_backprop_autograph, x, y)
... grad = tape.gradient(loss, [x, y])
2.47 ms ± 459 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) If, on the other hand, I repeat this benchmark tests with
8.87 ms ± 28 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.07 ms ± 383 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.08 ms ± 502 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) Let me know your thoughts about it. |
Thanks @AmintorDusko for taking a look!
That is odd, I cannot recreate that 🤔 For me, autograph is always faster. When you are performing the timing, are you including the initial compilation time or not?
If you know how to get rid of this warning, that would be very much appreciated! I could not find it.
Ah, I also received this at one point. This could be because you are first calling/compiling
Yep! this is expected I think |
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.
Great PR! 🚀 As of my experience with tf.Graph
, I think it should be documented somewhere that using @tf.function
may not necessarily bring any speed-up as it can be faster than eager execution particularly for graphs with many small operations but not for those with not many and expensive operations. For this PR, I only have a few minor comments.
@@ -23,7 +23,9 @@ | |||
from pennylane import numpy as np | |||
|
|||
|
|||
def execute(tapes, device, execute_fn, gradient_fn, gradient_kwargs, _n=1, max_diff=2): | |||
def execute( | |||
tapes, device, execute_fn, gradient_fn, gradient_kwargs, _n=1, max_diff=2, mode="backward" |
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 seems that mode
is never used in this function, so, I guess you can remove it from the list of Args. Oh! wait! This is a required argument for execute
at ./tensorflow_autograph.py
and so you added this unused arg to the execute
function of all other interfaces. There are two concerns here,
- Is this really the best approach to tackle this incompatibility issue?
- Why is the default mode always
"backward"
for allautograd
,jax
,tensorflow
, andtorch
?
I suppose that the mode
description is also missed in the list of Args.
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.
Good catch @maliasadi, I've made the following changes:
mode
is documented in all interfacesmode=None
is the new default.
Is this really the best approach to tackle this incompatibility issue?
I thought about this a lot. I think it was something that was missed from the original interfaces, and should be included - the existing interfaces are currently querying the shape of the forward pass result to implicitly determine the mode, which is not ideal.
Context: Currently, TensorFlow's AutoGraph mode (enabled by decorating QNodes with
@tf.function
) only works with backpropagation mode. This is because, for devices that do not support TensorFlow natively, we define a custom gradient interface that calls arbitrary Python code for execution.Description of the Change:
Adds a new interface,
tf-autograph
, that is re-written usingtf.numpy_function
to execute quantum devices. This converts the Python execution to a graph node, allowing the computation to be converted to a TF graph.A few changes to
vjp.py
need to be made to ensure that it is graph-compatible. In particular, when TF traces the QNode execution, tensors have no value and no shape, so we need to explicitly pass the size of thedy
vector tocompute_vjp
.Benefits:
When decorating a QNode (or cost function) with
@tf.function
, PennyLane automatically detects this, and applies the newtf-autograph
interface, resulting in no special requirements from a user-perspective.Jacobians and nth-order derivatives continue to work as expected.
For example, consider the following QNode:
We can autograph it:
Comparing execution times:
Comparing gradient execution times for a given cost function:
Note that, while a large improvement, native backprop with autograph still beats both:
Possible Drawbacks:
The initial tracing stage is significantly slower.
QNodes that return combinations of samples and other measurement types are not supported.
numpy_function
must know the returned output types ahead of time, which means that onlyfloat64
returns for measurement statistics are currently supported.@tf.function(jit_compile=True)
is not supported.Related GitHub Issues: n.a