Check, if all parameters get valid gradients after the first backward call via:
for param in model.parameters():
    print(param.grad)

to make sure the computation graph isn’t detached.
If you are seeing valid tensors using the code snippet (and not None), the gradients are at least properly computed and you could then check the, if the optimizer is indeed updating them.
Once this is done (and everything looks good), try to overfit a small dataset (e.g. just 10 samples) by playing around with some hyperparameters.