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