I would recommend to add some warmup workloads to avoid accidentally profiling the CUDA context creation and general startup time as seen here:
# warmup
x = torch.randn(1024, 1024, device='cuda')
out = torch.matmul(x, x)

# create events
t1 = torch.cuda.Event(enable_timing=True)
t2 = torch.cuda.Event(enable_timing=True)        
t3 = torch.cuda.Event(enable_timing=True)        

# profile1
t1.record()
out = torch.matmul(x, x)
         
# profile2
t2.record()
for _ in range(10):
    out = torch.matmul(x, x)
 
# end timer
t3.record()

# print results
torch.cuda.synchronize()
print(" Loss time ", t1.elapsed_time(t2), t2.elapsed_time(t3))

Changing the workload (single matmul and the matmul in the loop) results in the expected swapped timings.