I think the DataLoader just shuffles the complete dataset (i.e., creates a index array of range [0, num_examples] and shuffles this) at each epoch. Then it sweeps over these indices in sequential order. E.g., if you have a dataset like [0, 1, 2, 3, 4, 5, 6] with batch size 2, a random order could be [4, 2, 1, 6, 0, 3, 5], and the minibatches would then be
[4, 2], [1, 6], [0, 3], [5].
This way, you would have random sampling WITHOUT replacement.

I understand that, the moment of selecting which batch comes next in the training loop picks a random index among all the batches.  Is this right?

This would be random sampling WITH replacement. I am 99.9% sure that this is not what the DataLoader does (although it is also a valid approach; actually, it is even more “correct” if you think of stochastic gradient descent; as far as I know, it is less common though and maybe not working so well empirically; with large datasets sizes, e.g., > 500k, i doubt you will notice any difference in the resulting model)