The journey every GPU computation makes — memorize once, write forever
From host memory to verified result, a GPU computation always passes through the same nine boxes. GEMM doesn't add new boxes; it just makes the existing ones bigger and busier.
| Step | What you do | CUDA | Metal (unified memory simplifies) |
|---|---|---|---|
| 1 | Allocate host memory | malloc / std::vector | Swift Array or step 3 covers it |
| 2 | Initialize input data | loop / std::iota / RNG | same |
| 3 | Allocate device memory | cudaMalloc | device.makeBuffer(.storageModeShared) |
| 4 | Copy host → device | cudaMemcpyAsync | no-op (write directly into the buffer) |
| 5 | Define kernel | __global__ void kernel | kernel void kernel |
| 6 | Launch kernel | kernel<<<G,B>>>() | encoder.dispatchThreadgroups |
| 7 | Copy device → host | cudaMemcpyAsync back | no-op (read from same buffer) |
| 8 | Validate | host loop / thrust::equal | Swift loop |
| 9 | Garbage-collect | cudaFree + free | buffer = nil (ARC handles it) |
On Apple Silicon, steps 4 and 7 are no-ops because of unified memory (we covered this in Track 2). The conceptual steps still exist — they're just zero-cost. Don't skip them in your mental model; you'll thank yourself when you read CUDA code later.
The real production complexity hides in step 6 (launch geometry choices) and step 8 (which validation strategy: memcmp, thrust::equal, numpy.allclose against a CPU reference, or numerical-tolerance comparison for FP).