PyTorch uses a caching mechanism to reuse the device memory and thus avoid the (synchronizing) malloc/free calls. You can check the memory usage via print(torch.cuda.memory_summary()), which will report how much memory is allocated, in the cache etc.
Calling empty_cache() in each iteration will slow down the code (since you won’t be able to reuse memory) and will not lower the memory usage.
If you are seeing an increase in the allocated memory usage, you are most likely storing tensors in e.g. a list, which are still attached to the computation graph, so you would need to detach() them assuming you won’t want to backpropagate through them anymore.