Skip to content
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

[TAPE] Add support for tape execution #796

Merged
merged 23 commits into from
Sep 15, 2020
Merged

[TAPE] Add support for tape execution #796

merged 23 commits into from
Sep 15, 2020

Conversation

josh146
Copy link
Member

@josh146 josh146 commented Sep 13, 2020

Context: Second PR in the tape series, following on from #792

Description of the Change:

Adds support for tape execution on devices. In particular, it adds the following methods:

  • tape.execute(device, params=None): the user-facing call for tape execution on a given device. This is always the method users should use; if an interface is being used, this method will be wrapped to support autodifferentiation.

    Note that parameters need not be passed; if not passed, the current tape parameters will be used for execution.

  • tape.execute_device(device, params): a low level method for executing the tape on a device. This is where the main logic takes place. When using an interface, this will be behind the interface; calling this method will not support autodifferentiation.

  • tape._execute: an optional method (simply an alias to tape.execute_device). The interface may override this method if it needs to insert logic inbetween the two execute calls. It has the same signature as execute_device.

Note: the three methods, as well as their signatures, and constant getting and setting of tape parameters might seem overly complex. I arrived at this working solution after getting all three interfaces to work. In particular:

  • Autograd requires that the method it wraps accepts differentiable numeric parameters as a positional argument. So it must have params in the signature, and it cannot have device be a positional argument!

  • In TensorFlow and PyTorch, however, you want the opposite; the tape has already been constructed with tensors; you simply want to execute it as-is, and avoid needing to 're-submit' the trainable tensors to the execute method.

Benefits: Tape is now executable using all devices

Possible Drawbacks:

@mariaschuld pointed out that there are some potential advantages to instead having

dev.execute(tape)

rather than

tape.execute(dev)

be the user-facing call. However, this will require significant changes:

  • The interface will need to be applied to the device, not the tape

  • Similar interface requirements as to the device.execute method signature would need to be considered

  • We are in a position where the interface jacobian call is in a different location in the code base to the wrapped execute method!

I think this will still be doable, but I propose we explore this after the tape refactor, as it will definitely require interface + device API modifications. We may even be able to support both approaches.

Related GitHub Issues: n/a

Copy link
Contributor

@trbromley trbromley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

pennylane/beta/tapes/tape.py Show resolved Hide resolved
pennylane/beta/tapes/tape.py Show resolved Hide resolved
@trbromley
Copy link
Contributor

@mariaschuld pointed out that there are some potential advantages to instead having

Is there a link to the conversation? I'm curious what the advantages are.

Base automatically changed from tape-pr-1 to master September 14, 2020 12:55
@codecov
Copy link

codecov bot commented Sep 14, 2020

Codecov Report

Merging #796 into master will increase coverage by 0.00%.
The diff coverage is 96.15%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master     #796   +/-   ##
=======================================
  Coverage   92.30%   92.30%           
=======================================
  Files         121      121           
  Lines        7715     7737   +22     
=======================================
+ Hits         7121     7142   +21     
- Misses        594      595    +1     
Impacted Files Coverage Δ
pennylane/beta/tapes/tape.py 98.66% <96.15%> (-0.35%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5f8064f...c647fd1. Read the comment docs.

@josh146
Copy link
Member Author

josh146 commented Sep 14, 2020

Is there a link to the conversation? I'm curious what the advantages are.

It was on Slack unfortunately! We didn't explore in too much detail. More so that dev.execute(tape) expands easily to multiple tapes, whereas tape.execute(dev) doesn't.

Copy link
Member

@co9olguy co9olguy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @josh146, looks great


except (AttributeError, TypeError):
# unable to determine the output dimension
pass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codecov seems to have a legitimate complaint here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woah! This in-line commenting by codecov is new

pennylane/beta/tapes/tape.py Outdated Show resolved Hide resolved
pennylane/beta/tapes/tape.py Show resolved Hide resolved
pennylane/beta/tapes/tape.py Outdated Show resolved Hide resolved
pennylane/beta/tapes/tape.py Show resolved Hide resolved
tests/beta/tapes/test_tape.py Show resolved Hide resolved
tests/beta/tapes/test_tape.py Outdated Show resolved Hide resolved
tests/beta/tapes/test_tape.py Outdated Show resolved Hide resolved
tests/beta/tapes/test_tape.py Outdated Show resolved Hide resolved
tests/beta/tapes/test_tape.py Show resolved Hide resolved
Copy link
Contributor

@antalszava antalszava left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! 💯 Nice @josh146 😊

pennylane/beta/tapes/tape.py Show resolved Hide resolved
# execution methods
# ========================================================

def execute(self, device, params=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering about the naming here: previously one had QNode.evaluate. Could be good to have evaluate here too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to admit, I'm a bit personally biased towards execute over evaluate. Partially because I find execute is more descriptive in that it will be actually calling the quantum device for the result.

Having a different term here will also make the final find-all/replace-all easier when we replace core!

But happy to change it back if everyone agrees

pennylane/beta/tapes/tape.py Show resolved Hide resolved
else:
res = device.execute(self.operations, self.observables, {})

# Update output dim if incorrect.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come that in certain cases the output dim needs to be updated? Might be worth noting here perhaps.

Copy link
Member Author

@josh146 josh146 Sep 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output dimension of the QNode needs to be known in order for the Jacobian to be created properly. Currently, the QNode computes the output dimension when processing the queue:

  • If a measurement process that returns a scalar is included, the output dim is incremented by 1
  • If probability is included, the output dim is incremented by 2**len(wires)
  • If a CV probability is included, the output dim is incremented by cutoff ** len(wires).

However, this is quite inelegent. It means that devices aren't free to define their own output without needing to always make the QNode aware of what the output dimension is (example: when we added CV probability, we needed to update the output dim in the QNode).

Instead, what we can do is add another approach; the tape simply looks out the output size after execution, without needing to know anything in advance!

This has a drawback though --- you always need to evaluate the QNode before computing the Jacobian.

assert tape.output_dim == sum([2, 2])

def test_incorrect_ragged_output_dim_estimate(self):
"""Test that a quantum tape with an incorrect *ragged* output dimension
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not completely sure about the distinction between this test case and the previous one. Perhaps might be worth parametrizing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference is in the tape measurements 🙂 The previous test had output [probs(wires=0), probs(wires=1)] which will output a NumPy array with shape (2,2).

This test instead has a tape with output [probs(wires=0), probs(wires=[1, 2])], which will be a ragged NumPy object array that looks like [[a, b], [c, d, e, f]]. So we are dealing with a completely different dtype to the previous case.

Since we are testing different logic here, I don't think this should be parametrized (parametrization should be restricted to testing the same logic with different data).

Co-authored-by: Nathan Killoran <co9olguy@users.noreply.github.com>
pennylane/beta/tapes/tape.py Outdated Show resolved Hide resolved
@mariaschuld
Copy link
Contributor

@mariaschuld pointed out that there are some potential advantages to instead having

Is there a link to the conversation? I'm curious what the advantages are.

Hey @trbromley ...this came from the idea of batching: if you want to send a list of tapes, you could do

dev.execute([tape1, tape2,...])

But my latest favourite solution for batching would be to design a tape so that it can represent a batch of tapes itself, in which case tape.execute() should work fine!

@josh146 josh146 merged commit 78baf91 into master Sep 15, 2020
@josh146 josh146 deleted the tape-pr-2 branch September 15, 2020 06:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants