To be clear, the code you pasted doesn’t copy the data unnecessarily. In particular, that line doesn’t copy the data. It creates a batch from an already contiguous block of samples - this doesn’t require the data to be copied.
If you want to make a batch using a random selection of samples instead of using a block of contiguous samples, then you would have to explicitly make sure the data was contiguous before feeding it to the model and that would generally require copying. Something like this should work.
random_indices = torch.LongTensor(np.random.randint(start, end, size=batch_size))
batchX = trX[random_indices].contiguous()
batchY = trY[random_indices].contiguous()
cost += train(model, loss, optimizer, batchX, batchY)

tensor.contiguous() 2 only copies the data if it has to. If random_indices happens to be a sequential list of indices, then the data is already contiguous and .contiguous() just returns a view to the original data.
I hope that I am not adding to your confusion.