I got the gradient of loss w.r.t the input data as following:
grad = torch.autograd.grad(outputs=loss, inputs=data, allow_unused=True)
output:
 (tensor([[ 0.0073, -0.0088, -0.0019, -0.0110, -0.0023,  0.0060,  0.0012, -0.0022],
        [-0.0086,  0.0103,  0.0022,  0.0130,  0.0027, -0.0069, -0.0013,  0.0026],
        [-0.0086,  0.0103,  0.0021,  0.0131,  0.0026, -0.0070, -0.0013,  0.0026],
        [ 0.0072, -0.0086, -0.0019, -0.0109, -0.0023,  0.0058,  0.0012, -0.0021],
        [-0.0086,  0.0103,  0.0021,  0.0131,  0.0027, -0.0070, -0.0013,  0.0026]]),)

which is a tuple that I can divide it into two parts for getting  two gradients one is w.r.t x1 and the other one is w.r.t x2.
I want to know is it a right idea?
On the other hand, I want to update this model as well.
Does the following code work?
for batch_idx, (data, label) in enumerate(data_loader_new): 
    optimizer.zero_grad()
    data = data.clone().detach().requires_grad_(True)
    output= model(data)
    loss = loss_fn(pred=output, target=label)
    grad = torch.autograd.grad(outputs=loss, inputs=data, allow_unused=True)
    optimizer.step() 

and is it any difference between the last code and the following for updating the model?
for batch_idx, (data, label) in enumerate(data_loader_new): 
    optimizer.zero_grad()
    data = data.clone().detach().requires_grad_(True)
    output= model(data)
    loss = loss_fn(pred=output, target=label)
    loss.backward()
    optimizer.step()