Hi,
The fact that the loss is 0 does not mean the gradient is 0.
The gradient that flows back from the backward() call is actually 1 (because it computes vector jacobian product. And so when the vector is just 1, you get the jacobian).
The big difference between the two is that in the first case, the gradient that flows back to the softmax is a vector full of 1 while in the second case, that vector has been multiplied by target_onehot (backward of the multiplication) and so contains way less 1s and is not symetric anymore. I guess this is where these non-0 values come from.