Pytorch interface and positional VS keyword arguments

Hello,

The tutorial:
https://pennylane.ai/qml/tutorial/tutorial_advanced_usage.html
mentions the difference between positional and keyword arguments i.e. output will not be differentiated with respect to the keyword arguments.

Does the same pattern hold for pytorch interface or is the requires_grad used instead? I.e. if I pass into my quantum circuit some tensors which do not require grad (e.g. the ‘data’) and some tensors which do require grad (learnable parameters) will pennylane figure it out to only autodifferentiate with respect to inputs which have requires_grad=True? Or do I need to apply the same pattern of positional and keyword arguments?

Thanks,
Wojtek

Answering myself:

The pattern of positional and keyword arguments does seem to matter.

Here is a module (based on the qbit rotation tutorial) encapsulating a qnode with two (dummy) arguments and two learnable parameters:

from torch.nn.parameter import Parameter

@qml.qnode(qpu,interface='torch')
def circuit(params,inputs=None):
    qml.RX(inputs[0],wires=0)
    qml.RZ(inputs[0],wires=0)
    qml.RX(params[0],wires=0)
    qml.RZ(params[0],wires=0)
    return qml.expval(qml.PauliZ(0))



class QCircuitNet(torch.nn.Module):
    def __init__(self):
        super(QCircuitNet, self).__init__()
        self.phi=Parameter(torch.tensor(0.0))
        self.theta=Parameter(torch.tensor(0.0))
    
    def forward(self,alpha,beta):
        return circuit(alpha,beta,self.phi,self.theta)



model=QCircuitNet()

def cost(value,target):
    return torch.abs(value-target)**2

value=model(alpha,beta)
#this results in 1 job (about 4.5 for 100 shots on ibmQ)

loss=cost(value,target)

loss.backward()
#this results in 8 jobs

So the backward call in the above took 8 jobs sent to the backend
If one looks the gradients of the inputs are all None as expected though

Now if we re-write using positional arguments for the learnable parameters and keyword arguments for inputs/arguments:

@qml.qnode(qpu,interface='torch')
def circuit(params,inputs=None):
    qml.RX(inputs[0],wires=0)
    qml.RZ(inputs[0],wires=0)
    qml.RX(params[0],wires=0)
    qml.RZ(params[0],wires=0)
    return qml.expval(qml.PauliZ(0))

class QCircuitNet(torch.nn.Module):
    def __init__(self):
        super(QCircuitNet, self).__init__()
        self.params=Parameter(torch.zeros(2,dtype=torch.float32))
    
    def forward(self,args):
        return circuit(self.params,inputs=args)

The call to forward still takes 1 job, but call to backward takes only 4 evaluations. So one is definitely better off using the pattern of positional and keyword arguments. Not sure if this would be considered a bug as it seems the correct behaviour could be achieved by checking the requires_grad field. BTW this is using release 0.6.1.

Let me know if it seems I’m doing something wrong.

Cheers,
Wojtek

1 Like

@wojtek, thanks for you question (and solution!)

You’re right, currently the positional/trainable, keyword/non-trainable split is inherited by the PyTorch and TensorFlow interfaces for historical reasons. Namely, the PennyLane QNode was designed with just Autograd in mind (which does not have a way of marking variables), and the PyTorch and TensorFlow interfaces were added later, on top.

We are currently in the process of refactoring the QNode, and as a part of this, we are moving the autograd dependent code out of the QNode and into its own interface. So this should be fixed soon :slight_smile: