Generally, if you want to profile CUDA operations you would have to synchronize the code before starting and stopping the timer as seen here:
torch.cuda.synchronize()
t0 = time.time()

out = gpu_op(input)

torch.cuda.synchronize()
t1 = time.time()

Also, warmup iterations should be added and to stabilize the timings you should calculate the average time spent in this operation using multiple iterations.
With that being said, if you want to profile a specific op, you could use torch.utils.benchmark, which will already perform these steps for you (warmup, syncs, etc.).
I personally prefer to see a timeline if I’m profiling entire scripts, as I usually cannot easily spot the bottleneck in the output of accumulated timings.