No shared memory. Without a __shared__ array, each load goes from global memory (via the caches) straight into one thread's register.
No shared memory. Without a __shared__ array, each load goes from global memory (via the caches) straight into one thread's register.
One thread per output element. Thread (ty, tx) streams a whole row of A and column of B straight from global memory, the slow “main pantry”.
It uses no shared memory: without a __shared__ array each value goes from global memory into one thread's register and is dropped after its multiply-add. The counts here are per-thread load requests. The hardware softens the redundancy: loads of the same address within a warp merge into one memory transaction, and the L1 and L2 caches can serve repeats. On Volta and later, L1 is the same on-chip SRAM as shared memory. But what stays cached is up to the hardware, so the reuse is not guaranteed. NVIDIA's Best Practices Guide measures 120 GB/s for a kernel like this on a V100 and 196 GB/s once both tiles are staged in shared memory.
The block fetches a 2×2 tile of A and of B once into shared memory (the “workbench”), then every thread computes from there.
Shared memory per block is 2·T²·4 B: 2 KB at T = 16, 8 KB at T = 32. For this one-thread-per-output kernel the real cap is threads per block (T² ≤ 1024, so T ≤ 32); register-blocked kernels use bigger tiles, where shared-memory capacity and occupancy become the limit.