Might be the case, but you should check, if the computation graph could be detached in some code, which wasn’t shown (it’s not detached in my example).
To do so, create the model and make sure that ll .grad attributes are None via:
for name, param in model.named_parameters():
    print(name, param.grad)

Skip the optimizer.zero_grad() call, create the model output and the loss, and call loss.backward().
Afterwards check the .grad attributes again.
If they show zero values, the gradients were properly calculated in the backward() op but are zero e.g. due to the model architecture etc.
If they are still None, the computation graph would be incorrect (assuming you expect all parameters to get a valid gradient).