Pytorch has been built to work with batch of data. That allows to perform larger operations and thus better use device such as GPUs.
A batch is basically a bunch of independent data that you process at the same time.
In your case, if len(movie_ids) == 2, you have 4 pairs of indices to evaluate: (0,0), (0,1), (1,0), (1,1).
You can do this using your nested for loops or by doing a single forward with a batch of 4 samples with i = torch.LongTensor([0, 0, 1, 1]) and j=torch.LongTensor([0, 1, 0, 1]). That way, in one call to your model, you get the result for all four samples, instead of doing 4 calls to your model with the for loops.
Loss functions such as MSE are built to support such examples and you can see in the doc that it has a special argument reduction to choose how the individual values for each element in your batch should be aggregated into the final loss value.