Skip to content

← Orientation and the Grade step 1 of 3

Easy Primitives

Three passes, or two

The previous problem measured memory: did you copy a buffer you could have viewed. This one measures work: how many times did you walk over the data.

Every operation is a pass

PyTorch does not read your expression and plan it. It executes each operation as you write it, and each one reads its whole input and writes a whole new output:

y = (x + 1) * 2 - 3
#    ^^^^^         one full pass, allocating a new tensor
#    ^^^^^^^^^     a second pass over that tensor
#    ^^^^^^^^^^^^^ a third

Three passes, two throwaway tensors. For a tensor that fits in cache this is nothing. For a tensor that does not, memory bandwidth is the entire cost of the computation, and you have just tripled it.

The arithmetic you already know

(x + 1) * 2 - 3
= 2x + 2 - 3
= 2x - 1

Same numbers, exactly. Two operations instead of three. The compiler will not do this for you, because in eager mode there is no compiler: there is a dispatcher handing each call to a kernel, one at a time.

This is the smallest possible version of a habit that matters for the rest of the course. Before you reach for a fused kernel or torch.compile, look at whether the expression needed to be that long.

How the grader counts

Every call into PyTorch goes through a dispatcher, and a harness can sit on top of it and count. So the grader does not guess from your source; it counts the kernels that actually ran:

(x + 1) * 2 - 3   ->  aten.add, aten.mul, aten.sub    3 ops
x * 2 - 1         ->  aten.mul, aten.sub              2 ops

Deterministic, and identical on every run.

Your task

def transform(x: torch.Tensor) -> torch.Tensor

Return (x + 1) * 2 - 3 for every element, using at most two tensor operations.

The starter is the literal translation and returns the right numbers. It uses three.