No, not really. All tensors used in an operation should be on the same device.
Here is a small dummy example:
modelA = modelA.to('cuda:0')
modelB = modelB.to('cuda:1')
modelC = modelC.to('cuda:2')

x = torch.randn(1, 1, device='cuda:0')
target = torch.randn(1, 1, device='cuda:2')
criterion = nn.MSELoss()

output = modelA(x) 
output = output.to('cuda:1')
output = modelB(output)  
output = output.to('cuda:2')
output = modelC(output)
loss = criterion(output, target)  # output and target are both on cuda:2
loss.backward()

As you can see in this simple example, you would just have to make sure to push the data onto the device where the next operation should take place.