Thousands of threads — provided you feed them
CuArray ≠ Array: the bus can cost more than the maths.▶ Script:
4_memory_and_gpu.jl(sections 4–7) · Notebook: Memory & GPU · 🐍 PyTorch CPU vs CUDA (Colab)
Same wall: memory is far away. Opposite bets.
| CPU | GPU | |
|---|---|---|
| threads | few, fast | thousands, slow |
| caches | big (24 MiB L3) | small |
| hides the wait by | caching | switching threads |
CPU avoids the wait, GPU covers it. Too little work: the wait shows.
cu copies across the bus: two objects, two memories.CuArray(A) keeps Float64).Ag[1] is an error: one element = one bus round trip.▶ Script: cell “4. The GPU — a CuArray is NOT a CPU array”
The type change is a copy you paid for.
Two 4096² Float32 matrices (64 MiB each):
| step | time |
|---|---|
host → device, cu(A) |
8.5 ms |
compute, Ag * Bg |
35 ms |
device → host, Array(Ag) |
52 ms |
▶ Script: cell “What the copy costs”
Same *, 4096², Float32:
| time | |
|---|---|
CPU, A * B |
hundreds of ms (varies) |
GPU, CUDA.@sync Ag * Bg |
~35 ms: ~15–20× |
CUDA.@sync: GPU calls return immediately — else you time the launch.▶ Script: cell “The matrix product, CPU vs GPU”
Big, regular job: what the GPU wants. Smaller ones?
N × N matmul, CPU vs GPU, N from 64 to 4096.
Does the GPU always win?
▶ Script: cell “5. Feed the GPU — when does it start paying off?”
Float32, CPU measured first:
| N | CPU | GPU | speedup |
|---|---|---|---|
| 64 | 0.01 ms | 0.03 ms | 0.25× loses |
| 128 | 0.03 ms | 0.03 ms | 1.2× |
| 512 | 1.15 ms | 0.09 ms | 13.0× |
| 2048 | 69.8 ms | 3.02 ms | 23.1× |
| 4096 | hundreds of ms | ~35 ms | ~15–20× |
Not faster: wider.
One layer W (4096²) × B examples, as in the batching deck:
| B | GPU total | GFLOP/s |
|---|---|---|
| 1 | 0.41 ms | 82.8 |
| 8 | 0.42 ms | 638 |
| 64 | 0.45 ms | 4761 |
| 256 | 2.23 ms | 3853 |
▶ Script: cell “Batching on the GPU”
Warp = 32 threads in lockstep, loading together.
The column-major lesson again, on the card.
stride s |
time | |
|---|---|---|
| 1 | 2.24 ms | coalesced |
| 33 | 5.40 ms | 2.4× slower |
| 1023 | 12.3 ms | 5.5× slower |
▶ Script: cell “6. Coalescing — locality again, at the scale of a warp”
“Only 2.4×?” Which other bytes does
a[idx]touch?
a[idx] also reads idx and writes out, in order: only 1/3 of the bytes are punishable.
Index computed in the kernel → 1/2 punishable. The ratio must rise.
| stride 33 vs 1 | ratio |
|---|---|
a[idx] (1/3) |
2.4× |
| kernel (1/2) | 4.18× |
▶ Script: cell “Test that explanation”
function pi_gpu(n)
x = CUDA.rand(Float32, n) # in VRAM
y = CUDA.rand(Float32, n)
inside = (x.^2 .+ y.^2) .<= 1f0
return 4 * sum(inside) / n # one scalar back
endcount_in_disk; 100 million GPU threads.sum reduces.▶ Script: cell “7. Monte-Carlo π on the GPU”
100 million points, pi_gpu ≈ 12 ms:
| CPU baseline | GPU gain |
|---|---|
pi_cpu_vec (allocates 2 × 400 MB) |
~35× |
count_in_disk, scalar loop |
~25× |
Q3. Threads module: 22 threads → 6.3×. Why is the card so far ahead?
Q4. What in
pi_gpudoes not benefit from 100 M threads?
CuArray in VRAM: same code, you pay the copy.The four ideas of the course: Synthesis.
scripts/4_memory_and_gpu.jl, sections 4–7⬅ Previous: 4 — Memory & batching · Next: Course overview & synthesis ➡