Matrix Multiplication on the GPU
GPU timing results are specific to the hardware used, so a speed-up ratio is only meaningful with its hardware and measurement spread attached.
Two Machines, Two Different Numbers
A common surprise in GPU work is that a script which ran quickly for a colleague runs more slowly on your machine, even though the lines of code match exactly. This article treats that gap as a debugging problem. Instead of asking whether GPU acceleration works in general, we ask a narrower question: when a result differs from what you expected, where in the code or the setup should you look first?
What do you think happens?
You run the same matrix multiplication on your laptop CPU and then on a cloud GPU. A colleague runs the same code on a different cloud GPU. Will the speed-up ratio be the same for both of you?
Reveal answer
No. The ratio depends on the hardware and the matrix size, so a colleague on a different cloud GPU can reasonably get a different ratio.
The source treats the GPU speed-up as specific to the machine and the matrix size it was measured on. The code is identical, but the hardware underneath it is not. The ratio is only meaningful when you know which hardware produced it.
The Experiment Being Timed
The core experiment is short. Two tensors are created, then multiplied with the @ operator, and the time for that single operation is measured with IPython's timing magic. The same measurement is repeated after moving both tensors onto the GPU. The first step is the move itself, which is a separate line from the multiplication.
- The matrix size here is an illustrative choice; the speed-up you measure depends on the size you pick.
- The move to the GPU is a separate statement from the multiplication, so it is not included in the multiplication timing.
- %timeit is an IPython magic that repeats the statement and reports a mean with a plus-or-minus spread.
Read the two lines of output carefully. The CPU figure and the GPU figure each carry a plus-or-minus value, which describes how much individual runs varied around the average. On the hardware used in the source, the GPU run was roughly four times faster. That ratio is the number worth remembering, because it is specific to that machine and that matrix size.
Tracing Where the Tensors Live
The speed-up depends on where the operation runs, and where it runs depends on where the tensors are. Calling the matrix multiplication does not by itself choose a device. The tensors carry that information. Moving them with .to("cuda") changes the device the multiplication is performed on. If either tensor is left behind on the CPU, the timing you measure is no longer a measurement of the GPU path.
Trace the flow in the diagram. The multiplication takes its device from its inputs. Two lines of your script can move the data, while a third line can look identical whether it is running on CPU or GPU. The practical check is to confirm, before reading any timing, that both tensors were actually moved to the device being measured.
Judging a Speed-Up Claim
A speed-up is a ratio of two measured times. The source reports one ratio, roughly four, but a single ratio hides how much the underlying runs varied. Written as a formula, the ratio compares the CPU time against the GPU time:
A ratio is only as trustworthy as the two times that produce it. Each time carries a plus-or-minus value describing run-to-run spread. When the spread around either time is large, a ratio that looks precise may fall anywhere within a wide band. Before repeating a speed-up claim, attach three things to it: the hardware on each side, the matrix size, and the measurement spread.
| What you report | Why it is weak on its own | What makes it trustworthy |
|---|---|---|
| A single speed-up ratio | It hides the run-to-run spread behind each time | The plus-or-minus values for both the CPU and GPU timings |
| A ratio from one machine | The number depends on that hardware | The name of the GPU and CPU used on each side |
| A ratio for one matrix size | The gap can change with size | The size of the tensors stated next to the ratio |
| A ratio where the GPU tensors were not confirmed | It may not measure the GPU path at all | Confirmation that both tensors were moved to the device being measured |
Checks to apply before trusting a speed-up claim
Pinning the Random Seed and Device
The source's second half rebuilds a model from an earlier chapter, so the rest of the material starts from a known state. Here the consistency question moves from speed to reproducibility. Two runs that build the same model with different random starting values will not produce identical training behavior, so the setup code has to pin down what it can.
Consistent model setup depends on two choices made in order. First, the device is chosen. Second, the random seed is fixed relative to model construction. The seed fixes the random starting values, so its placement matters: a seed set after the model is built does not control the values that were already drawn.
- The seed is set before the random values that depend on it are drawn during model construction.
- The device is chosen before the model is moved onto it.
MyModelis a placeholder name, not a class from the source.
When a run diverges, check the order of operations first: which device was selected, and whether the seed was fixed before the random initialization or data ordering happened. Seeding in the wrong place can look correct on the page while leaving the behavior unrepeatable.
Data Splitting and Loader Settings
Data is the third source of variation. The source loads a short story, splits its text at ninety percent, and builds two loaders, one for training and one for validation. The split point is computed from the length of the text, so the same file always produces the same boundary. The loader settings then decide how batches are formed.
- The split is computed from the length of the text, so it does not depend on chance.
- The validation portion is the remainder after the training portion.
The seed is set again immediately before the training loader is created. That placement is deliberate: the shuffled batch order is random, and seeding just before construction keeps that order repeatable. The validation loader is not shuffled, so it does not need the same protection, although the split itself still has to be computed the same way every time.
Use this pipeline to locate a divergence in the data path. If two runs see batches in a different order, look first at whether the seed was set before the training loader was built. If the validation numbers shift, look at whether the split was computed from the same text with the same ratio.
Common Mistakes in Timing and Repeating Runs
Common mistake Reporting the GPU ratio without the hardware
What it looks likeA post says the GPU is four times faster, with no GPU or CPU named.
Why it failsThe source ties the ratio to the machine and matrix size it was measured on.
Do this insteadAttach the hardware on each side and the matrix size to any ratio you share.
Common mistake Timing a line where one tensor is still on the CPU
What it looks likea_gpu is moved to the GPU but b is left on the CPU, then the multiply is timed.
Why it failsThe device of the operation comes from its tensors, so the measurement is not of the GPU path.
Do this insteadConfirm that both tensors were moved to the device being measured before reading the time.
Common mistake Ignoring the plus-or-minus value
What it looks likeComparing two single numbers and calling the gap decisive.
Why it failsEach time varies across runs, so the ratio can shift within that spread.
Do this insteadCompare the spread of the times alongside the ratio before drawing a conclusion.
Common mistake Setting the seed after the random values are drawn
What it looks likeThe model is built, and only then is the seed fixed.
Why it failsValues already drawn are not controlled by a seed that comes later.
Do this insteadFix the seed before the model and the shuffled loader are constructed.
Common mistake Assuming every loader needs the same shuffling protection
What it looks likeSeeding only the training loader and then assuming validation batches are equally tied to the seed.
Why it failsThe source's validation loader is not shuffled, so its batch order does not depend on the seed in the same way.
Do this insteadCheck each loader separately for shuffling, and still compute the split the same way every time.
Diagnosing a Divergent Run
A colleague's script reports a four-times GPU speed-up, and your run on a different GPU reports a much smaller ratio with the same code. List the three places you would check first and explain what each check rules in or out.
Hints
- Start with the hardware on each side and whether the matrix size matches.
- Check whether both tensors were moved to the device before the multiplication was timed.
- Compare the plus-or-minus spread on each time before deciding the gap is real.
Key Takeaways
- A GPU speed-up ratio is specific to the hardware and matrix size it was measured on, so it must be reported with that context.
- The multiplication runs on the device its tensors live on, so confirm both tensors were moved to the measured device before trusting a timing.
- Each timing carries a plus-or-minus spread; judge a speed-up claim against that spread, not only the single ratio.
- Repeatable training depends on device selection, fixing the seed before random construction, and computing data splits and loader shuffling consistently.