Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
Being able to derive FLOPs and bytes moved from raw tensor shapes — no profiler required — is a load-bearing skill you'll lean on in every module that follows. A matmul of an (M,K) tensor by a (K,N) tensor does 2MN*K FLOPs (one multiply and one add per output element, summed K times) and, in the naive case, reads A and B once and writes C once. Once you can do this arithmetic in your head, you can predict whether a shape is efficient before you run it, and you can catch a profiler lying to you — trace tools occasionally misattribute time or double count, and the fastest way to notice is to already know roughly what the number should be. This also sets up the memory-budget task next: parameter count, activation size, and optimizer state are all just this same shape arithmetic applied to a whole model instead of one op.
The demo computes FLOPs and bytes for several matmul shapes, including the 'thin' shape typical of single-token autoregressive decoding and the 'fat' shape typical of a training batch, and shows why one is memory-bound and the other isn't just from the numbers.
dtype_bytes from 2 (fp16) to 4 (fp32) and to 1 (int8) for the decode step, and note how much the arithmetic intensity moves versus the training-batch shape.def matmul_flops(M, N, K):
return 2 * M * N * K
def matmul_bytes(M, N, K, dtype_bytes=2):
# naive: read A (M*K), read B (K*N), write C (M*N)
return (M * K + K * N + M * N) * dtype_bytes
shapes = {
"training batch (M=4096, K=4096, N=4096)": (4096, 4096, 4096),
"decode step (M=1, K=4096, N=4096)": (1, 4096, 4096),
"prefill 512 tok (M=512, K=4096, N=4096)": (512, 4096, 4096),
}
for name, (M, N, K) in shapes.items():
flops = matmul_flops(M, N, K)
b = matmul_bytes(M, N, K)
print(f"{name:42s} AI={flops / b:8.1f} FLOPs/byte")python3 main.py