Timing Code with %timeit in Notebooks
The %timeit magic is a notebook command.
Why Saying a GPU Is Faster Is Not Enough
A GPU is often described as the fastest way to run the heavy arithmetic inside neural networks, and matrix multiplication is one of the most common of those operations. Knowing that a GPU is faster in general is not the same as knowing how much faster it is for one specific operation, on one specific machine, with specific matrix sizes. This article shows how to answer that question directly. You will move two tensors onto a GPU, time the same multiplication on both devices, and reason about what the two timings tell you.
The experiment has three parts, and their order matters. First, two random matrices are created on the CPU. Second, both are moved to the GPU. Third, the same matrix multiplication is timed on each device. The timing result only makes sense once you know which device held the data at the moment the multiplication ran, so keep that question in mind as you read: when a GPU result differs from what you expected, where in this sequence of steps would you look first?
Moving a Tensor onto the GPU
Moving a tensor to a CUDA device is a single method call: a.to("cuda"). The call takes a tensor that currently lives on the CPU and places its data on the CUDA device, which is the GPU that PyTorch uses for this work. The call does not change the values in the tensor. It changes where those values are stored and therefore which processor can work on them directly.
a.to("cuda")returns a tensor whose data is on the CUDA device- The source performs the same move for the second matrix with
b.to("cuda")on the same line - Matrix sizes here are an illustrative choice, not values taken from the source
Reading the Output of %timeit
The %timeit magic is a notebook command. You place it before a statement, and it runs that statement several times and reports the typical time for one run. The result is written as a mean plus or minus a spread, followed by per loop. The mean is the central estimate of how long one run takes. The value after the plus-minus sign shows how much the individual runs varied around that estimate. A small spread means the timing was stable from run to run, and a large spread means you should be cautious about trusting the mean.
- %timeit runs the statement a @ b several times
- The reported line has the form mean plus or minus spread, per loop
Two timing results are only comparable when they measure the same statement on the same inputs. In this experiment the statement is a @ b in both cases, and the inputs are the same two matrices. The only intended difference between the two measurements is the device on which the tensors reside when the statement runs. If you changed the matrix sizes, or used different data between runs, the two numbers would no longer be a fair comparison.
Timing the Multiplication on Both Devices
Now put the pieces together. You create the matrices on the CPU and time the multiplication there first. Then you move both matrices to the GPU and time the same statement again. The transfer happens before the second %timeit line, so the timed statement is only the multiplication itself. The flowchart below shows the order of the steps on the GPU path.
- The statement a @ b is identical in both timings
- Only the device holding a and b differs between the two timings
- Reassigning a and b keeps the same variable names for the GPU run
On the CPU path the move step is skipped, because both matrices already live on the CPU. The timed statement is the same in both cases, so the only thing that changed between the two measurements is which processor runs the multiplication and where its operands sit. The comparison below sets the two paths side by side.
Calculating the Speedup Ratio
A speedup is usually reported as a ratio of the two times. In this article the ratio is the CPU time divided by the GPU time, so a value above one means the GPU run finished in less time. The relationship is written as follows, where t_{CPU} is the mean time on the CPU and t_{GPU} is the mean time on the GPU.
Speedup from two mean timings
Suppose the CPU timing reports a mean of 40 ms per loop and the GPU timing reports a mean of 10 ms per loop for the same statement on the same inputs. What is the speedup?
- 1Identify the two times
The CPU mean is 40 ms and the GPU mean is 10 ms. Both are means for the statement a @ b on the same matrices.
- 2Divide CPU time by GPU time
Using the formula, the speedup is $\frac{40}{10}$, which is the CPU time divided by the GPU time.
- 3Read the result
The quotient is 4, so the GPU run is about four times faster than the CPU run for this experiment.
The speedup is 4 on this hardware, for these matrix sizes. The numbers 40 ms and 10 ms are illustrative and not taken from any real machine.
The source summarizes its own experiment by saying the computation was approximately four times faster on a V100. That is a rounded statement about one experiment. The way to check it is to calculate the ratio from the timing values yourself, using the means reported on your own machine.
The ratio depends on the hardware because it is a measurement of one processor pair on one operation. The same code can give a different ratio on a different GPU or a different CPU, because each machine has its own processors and its own timing behaviour for this statement. The ratio also does not say that every operation will speed up by the same factor, and it does not predict what another GPU will do. A speedup number is a property of a specific comparison, not a fixed property of GPUs.
Mistakes That Make GPU Timings Misleading
Common mistake Moving only one operand to the GPU before timing
What it looks likea = a.to("cuda") is called, but b stays on the CPU, and then %timeit a @ b is reported as a GPU result.Why it failsBoth operands have to be on the same device for the multiplication to run on the GPU, so the measurement does not describe the GPU path.
Do this insteadMove both a and b to the CUDA device before timing the GPU statement.
Common mistake Comparing timings made on different inputs
What it looks likeThe CPU timing uses one pair of matrices and the GPU timing uses a larger pair.
Why it failsTwo timing results are only comparable when they measure the same statement on the same inputs, so the difference mixes device with input size.
Do this insteadKeep the statement and the matrices identical, and change only the device the tensors reside on.
Common mistake Inverting the speedup ratio
What it looks likeDividing the GPU time by the CPU time and calling the result the speedup.
Why it failsThe ratio is defined as CPU time divided by GPU time, so the inverted value would be below one when the GPU is faster.
Do this insteadDivide the CPU time by the GPU time, and read a value above one as the GPU finishing sooner.
Common mistake Reading the mean without looking at the spread
Why it failsThe mean is only a central estimate. A large plus-minus spread means the runs varied a lot, so the single number is less reliable.
Do this insteadRead the spread alongside the mean before drawing a conclusion.
Common mistake Treating one speedup as a fact about all GPUs
What it looks likeConcluding that every GPU will be four times faster than every CPU after one experiment.
Why it failsThe ratio describes one operation with one pair of matrix sizes on one machine, and it does not predict other hardware.
Do this insteadReport the speedup together with the hardware and matrix sizes it was measured on.
Checking Your Reading of a GPU Timing
A colleague moved a to the GPU with a.to("cuda") but left b on the CPU. They then ran %timeit a @ b, got a much smaller mean than on the CPU, and reported a GPU speedup. Explain what is wrong with that measurement, and describe the change you would make before timing again.
Hints
- Look at which device each operand lives on at the moment the multiplication runs.
- The source states that both operands have to be on the same device for the multiplication to run on the GPU.
- Once both operands are moved, what has to stay the same between the two timings?
Using the speedup formula, a CPU mean of 25 ms and a GPU mean of 5 ms for the same statement on the same inputs give what ratio? Then state one reason the same code might give a different ratio on another machine.
Hints
- Divide the CPU time by the GPU time.
- Think about what the ratio describes: one operation, one pair of matrix sizes, one machine.
Key Takeaways
- Calling
a.to("cuda")places a tensor's data on the CUDA device, so the multiplication can run on the GPU only when both operands have been moved there. - %timeit runs a statement several times and reports a mean plus or minus a spread per loop, and you should read the spread as well as the mean.
- Two timings are comparable only when they measure the same statement on the same inputs, with the device as the only intended difference.
- Speedup is the CPU time divided by the GPU time, and it is a hardware-specific measurement that describes one operation on one machine with specific matrix sizes.