Question bank — CUDA programming model basics
Every term used here (host, device, kernel, thread, block, grid, warp, coalescing, boundary check) is built in the parent note — if a word feels unfamiliar, define it there first. Related depth: Thread Warps and SIMT, Memory Hierarchy, Streaming Multiprocessors.
Pictures to lean on
Three traps below are geometric — keep these open while you answer.
The three-level hierarchy and how a 2D global index is built (used in the "2D indexing" traps):
Why warps interleave on one SM — the latency-hiding timeline (used in the "warp scheduling" traps):
The full memory-space map — registers, shared, constant, texture, local, global (used in the "which memory" traps):
True or false — justify
A kernel launch runs the code once, and the GPU loops it internally.
Every thread in a grid can share data through shared memory.
threadIdx.x is unique across the whole grid.
blockDim.x-1); every block restarts it at 0. The globally unique id is blockIdx.x * blockDim.x + threadIdx.x.For a 2D grid, threadIdx.x alone tells me my row in the image.
row = blockIdx.y*blockDim.y + threadIdx.y and col = blockIdx.x*blockDim.x + threadIdx.x — see the left panel of figure s01.A 2D block is a genuinely different hardware thing from a 1D block.
dim3 block/grid shapes are purely a labelling convenience; the hardware still flattens them into warps of 32. 2D/3D just makes image and volume indexing readable.A block with 256 threads always runs all 256 at literally the same instant.
Interleaving warps means the GPU is slower because it keeps switching.
If I launch fewer threads than elements, the leftover elements just get skipped silently and safely.
cudaMalloc gives you a pointer the CPU can dereference directly.
cudaMemcpy first.Registers are the smallest and slowest memory in CUDA.
Constant memory is faster than global memory for every access pattern.
"Local memory" lives close to the thread like a register.
Using more threads per block is always faster because more parallelism.
Spot the error
int idx = threadIdx.x; used as the global index in a multi-block launch.
blockIdx.x. Every block would write indices 0..blockDim-1, so all blocks stomp the same elements and most of the array is never touched.For a 2D image kernel, int idx = blockIdx.x*blockDim.x + threadIdx.x; used as the pixel index.
row*width + col, using both the x and y global indices (figure s01, right panel).cudaMemcpy(h_C, d_C, bytes, cudaMemcpyHostToDevice); after the kernel.
cudaMemcpyDeviceToHost; as written it overwrites the GPU result with host garbage.numBlocks = N / threadsPerBlock; for N = 1000, threadsPerBlock = 256.
(N + tpb - 1) / tpb.Kernel body: A[idx] = idx; with no if (idx < N).
A, causing out-of-bounds memory corruption or a crash.A grid-stride loop written as for (int i = idx; i < N; i++) to cover a huge array.
for (int i = idx; i < N; i += blockDim.x*gridDim.x).vectorAdd<<<numBlocks, threadsPerBlock>>>(h_A, h_B, h_C, N); passing host pointers.
d_A, d_B, d_C, not the host arrays.Freeing device memory with free(d_A);.
free is for host allocations from malloc; device memory from cudaMalloc must be released with cudaFree.Why questions
Why does CUDA split work into blocks instead of one flat pool of threads?
Why use a grid-stride loop when a plain one-thread-per-element launch already works?
N: a fixed grid of, say, 4096 threads can process an array of any size by each thread hopping forward by blockDim.x*gridDim.x, so the same launch handles arrays bigger than the GPU's thread count.Why do warps interleave on one SM instead of finishing one before starting the next?
Why offer constant and texture memory at all when global memory can hold anything?
Why is the boundary check if (idx < N) needed if I computed numBlocks correctly?
numBlocks * threadsPerBlock is usually larger than N, so a few extra threads exist and must be told to do nothing.Why copy data to the device at all — why not let the GPU read system RAM?
cudaMemcpy to stage data in fast device memory.Why prefer 256 threads per block rather than, say, 30?
Why does moving data cost so much more than doing math?
Why does thread cooperation only exist within a block, not across blocks?
Edge cases
What happens when N is smaller than threadsPerBlock (e.g. N = 10, tpb = 256)?
numBlocks becomes 1, launching 256 threads; the boundary check lets only threads 0–9 do work and the other 246 sit idle — correct, just underutilized.What if N is exactly divisible by threadsPerBlock?
N changes.What if N = 0?
numBlocks = 0, so the kernel launches with an empty grid and simply does nothing — a valid no-op, not an error.What if a grid-stride loop is launched with more threads than N?
idx >= N never enter the loop body — the i < N condition is the built-in boundary check, so grid-stride loops are safe at any launch size.What if a 2D block is 32×32 = 1024 threads?
What if two threads write to the same global address without coordination?
What if a warp's 32 threads take different branches of an if?
If I forget to check the return code of cudaMalloc and it fails, what do I see?
d_A stays an invalid pointer and the kernel silently corrupts memory or crashes later — the failure surfaces far from its cause, which is why every CUDA call should be error-checked.Recall Self-test before you leave
Global unique thread id formula (1D)? ::: blockIdx.x * blockDim.x + threadIdx.x.
2D pixel index into a width-wide image? ::: (blockIdx.y*blockDim.y+threadIdx.y)*width + (blockIdx.x*blockDim.x+threadIdx.x).
Grid-stride loop stride value? ::: blockDim.x * gridDim.x — the total number of threads launched.
Which copy direction loads inputs onto the GPU? ::: cudaMemcpyHostToDevice.
Why does the boundary check exist at all? ::: Ceiling division over-launches threads; the check idles the extras so they don't write out of bounds.