Concepts / Matrix Multiplication on the GPU

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.

  • Programming

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?

  • Yes, the ratio is a fixed property of the code

  • No, the ratio depends on the hardware and the matrix size

Reveal answer
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.

python
Notes on the code
  • 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.

a on CPUcreated with torch.randa_gpu on GPUafter .to cudamatrix multiplyruns where both inputs areb on CPUcreated with torch.randb_gpu on GPUafter .to cudamove to cudamove to cudainputinput
Which tensors sit in CPU memory and which sit on the GPU, and when they move between the two. If one input stays on the CPU, the multiplication is not the GPU path you think you are timing.

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:

Rule

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 reportWhy it is weak on its ownWhat makes it trustworthy
A single speed-up ratioIt hides the run-to-run spread behind each timeThe plus-or-minus values for both the CPU and GPU timings
A ratio from one machineThe number depends on that hardwareThe name of the GPU and CPU used on each side
A ratio for one matrix sizeThe gap can change with sizeThe size of the tensors stated next to the ratio
A ratio where the GPU tensors were not confirmedIt may not measure the GPU path at allConfirmation 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.

python
Notes on the code
  • 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.
  • MyModel is a placeholder name, not a class from the source.
Best practice

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.

python
Notes on the code
  • 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.

full textsplit at 90 percenttraining portionshuffled batchesbatches to devicevalidation portionunshuffled batchesseed set beforeloaderlength-based boundaryfixes order
The text is split at ninety percent. The training portion is shuffled after a seed is set, so its batch order is repeatable. The validation portion is not shuffled, and both feed batches to the GPU.

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 like

    A post says the GPU is four times faster, with no GPU or CPU named.

    Why it fails

    The source ties the ratio to the machine and matrix size it was measured on.

    Do this instead

    Attach 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 like

    a_gpu is moved to the GPU but b is left on the CPU, then the multiply is timed.

    Why it fails

    The device of the operation comes from its tensors, so the measurement is not of the GPU path.

    Do this instead

    Confirm 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 like

    Comparing two single numbers and calling the gap decisive.

    Why it fails

    Each time varies across runs, so the ratio can shift within that spread.

    Do this instead

    Compare 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 like

    The model is built, and only then is the seed fixed.

    Why it fails

    Values already drawn are not controlled by a seed that comes later.

    Do this instead

    Fix the seed before the model and the shuffled loader are constructed.

  • Common mistake

    Assuming every loader needs the same shuffling protection

    What it looks like

    Seeding only the training loader and then assuming validation batches are equally tied to the seed.

    Why it fails

    The source's validation loader is not shuffled, so its batch order does not depend on the seed in the same way.

    Do this instead

    Check each loader separately for shuffling, and still compute the split the same way every time.

Diagnosing a Divergent Run

MEDIUM

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.

More concepts starting with M