6.2.13 · D4GPU Architecture

Exercises — CUDA programming model basics

2,118 words10 min readBack to topic

Before we start, let us lock down the one formula the whole page leans on, because every symbol in it must be earned.

Look at the figure: crate , seat , with lockers per crate, lands on locker .

Figure — CUDA programming model basics

L1 — Recognition

Exercise 1.1 (L1)

Match each term to its home. For Host, Device, Kernel, Warp, say in one phrase what it is.

Recall Solution
  • Host ::: the CPU and its system RAM — where main() runs.
  • Device ::: the GPU and its VRAM — where kernels run.
  • Kernel ::: a __global__ function launched from the host but executed on the device by many threads at once.
  • Warp ::: a hardware bundle of threads that step through instructions in lockstep (SIMT). See Thread Warps and SIMT.

Exercise 1.2 (L1)

In vectorAdd<<<numBlocks, threadsPerBlock>>>(...), which of the two triple-angle numbers is the grid size and which is the block size?

Recall Solution
  • First number, numBlocks ::: the grid size — how many blocks.
  • Second number, threadsPerBlock ::: the block size — how many threads per block. So the logical order is Grid → Blocks → Threads, exactly the order they appear.

Exercise 1.3 (L1)

Order these memories fastest→slowest: Global Memory, Registers, Shared Memory, Host Memory.

Recall Solution

Registers ( cycle) → Shared Memory ( cycles) → Global Memory ( cycles) → Host Memory ( cycles). Closer to the compute cores = fewer wires to cross = faster. See Memory Hierarchy.


L2 — Application

Exercise 2.1 (L2)

A thread has blockIdx.x = 3, blockDim.x = 256, threadIdx.x = 17. Compute its globalIdx.

Recall Solution

What we did: counted the threads in crates , then added our seat .

Exercise 2.2 (L2)

You have elements and choose . How many blocks do you launch?

Recall Solution

Use ceiling division so no element is left uncovered: Check: . Good — a few spare threads, which the boundary check handles.

Exercise 2.3 (L2)

For a D image of width = 640, a thread computes row = 5, col = 12. What is its flattened globalIdx in a row-major array?

Recall Solution

Row-major means: walk all the way across each finished row before starting the next.


L3 — Analysis

Exercise 3.1 (L3)

With and , exactly how many threads are launched, and how many are idle (do no work) thanks to if (idx < N)?

Recall Solution

. Total threads launched . Working threads = the ones with , i.e. of them. Idle threads (indices through ). They pass the if test as false and simply exit.

Exercise 3.2 (L3)

Look at the figure. The tail crate is only partly used. Explain why removing if (idx < N) from the vectorAdd kernel is dangerous, and which thread first misbehaves for .

Figure — CUDA programming model basics
Recall Solution

Threads come in whole blocks; you cannot launch "exactly ." The last block overshoots to index . Without the guard, thread would read A[1000]/B[1000] and write C[1000]one element past the array end (valid indices stop at ). That is an out-of-bounds access: crash or corrupted neighbouring memory. The first offender is .

Exercise 3.3 (L3)

A math op costs cycle; a global memory read costs cycles. In naïve vectorAdd, each output needs global reads + global write + add. Roughly what fraction of the time is spent on the actual arithmetic?

Recall Solution

Memory cycles . Arithmetic cycle. Interpretation: vectorAdd is utterly memory-bound — the GPU spends of its time moving data. This is why Memory Hierarchy and coalescing matter more than raw FLOPs here.


L4 — Synthesis

Exercise 4.1 (L4)

Write a kernel scaleAdd computing (a "SAXPY") with a proper index and boundary check. State the launch config for , .

Recall Solution
__global__ void scaleAdd(float a, float *A, float *B, float *C, int N) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < N) {
        C[idx] = a * A[idx] + B[idx];
    }
}

Launch: Check: . Call: scaleAdd<<<1954,256>>>(a,d_A,d_B,d_C,N);

Exercise 4.2 (L4)

List the six host-side steps (in order) needed to run any kernel on device data, and give the correct cudaMemcpy direction enum for input and output copies.

Recall Solution
  1. Allocate host memory (malloc).
  2. Allocate device memory (cudaMalloc).
  3. Copy inputs Host→Device — enum cudaMemcpyHostToDevice.
  4. Launch kernel <<<numBlocks, threadsPerBlock>>>.
  5. Copy results Device→Host — enum cudaMemcpyDeviceToHost.
  6. Free device (cudaFree) and host (free) memory.

Exercise 4.3 (L4)

For a D kernel on a image (width=640, height=480) with block dims , compute the grid dimensions (gridX, gridY).

Recall Solution

Total blocks ; total threads , which matches pixels exactly (no idle threads here since both dims divide evenly).


L5 — Mastery

Exercise 5.1 (L5)

A kernel launches with grid blocks, block threads. The thread that should process array index — what are its blockIdx.x and threadIdx.x?

Recall Solution

Invert the index formula. Divide by block size: , so . Verify forward: . ✓

Exercise 5.2 (L5)

Suppose reading from global memory costs cycles but from shared memory costs cycles. A tiling kernel loads each element from global memory once into shared memory, then reuses it times from shared memory. Compare total memory cycles per element against a naïve kernel that reads global memory all times.

Recall Solution
  • Naïve: global reads cycles.
  • Tiled: global read shared reads cycles.
  • Speedup . This is exactly why shared memory exists: pay the expensive off-chip trip once, then reuse cheaply. See Memory Hierarchy and Streaming Multiprocessors.

Exercise 5.3 (L5)

A GPU has SMs and can hold at most resident blocks per SM. You launch a grid of blocks. Are all blocks resident at once? If not, how many "waves" of blocks does the GPU need?

Recall Solution

Max simultaneously resident blocks . Since , not all fit at once. Number of waves: Actually . The GPU processes blocks in waves; the scheduler retires finished blocks and swaps in waiting ones. This is what lets one kernel scale from a small GPU to a huge one without changing code.


Recall Quick self-test recap

Ceiling block count for N=1000, tpb=256 ::: blocks, idle threads. Global index for block 3, blockDim 256, thread 17 ::: . Why if (idx < N) ::: last block overshoots array; guard stops out-of-bounds access. Fastest CUDA memory ::: registers ( cycle), on-chip, in the compute unit.