Skip to content

A03 Exercises

Do these yourself before peeking at ../solutions/. Write real, compiling, edge-case-correct code.

This directory has its own Makefile, so you can build and run without leaving it:

make                              # list targets
make all GPU_ARCH=gfx950          # build every exercise for your GPU
make 01_fix_divergence.exe        # build just one
make run                          # build + run all (FAIL / no-speedup expected until solved)
make clean

Set GPU_ARCH to your device (gfx950 MI350, gfx942 MI300, gfx90a MI200); find it with /opt/rocm/bin/offload-arch. The equivalent raw command, if you prefer to see it spelled out:

hipcc -O3 --offload-arch=gfx942 -I../hip 01_fix_divergence.cpp -o 01_fix_divergence.exe && ./01_fix_divergence.exe
# NVIDIA: rename to .cu, replace hip* with cuda*, swap __shfl_down for __shfl_down_sync,
#         and: nvcc -O3 -arch=sm_90 -I../cuda ...

(The exercises reuse the module's HIP_CHECK macro via -I../hip.)

# File Skill
1 01_fix_divergence.cpp Remap thread → element so a data-dependent branch becomes wavefront-uniform; measure the win and name what you traded away.
2 02_warp_reduce.cpp Write a __shfl_down reduction that is correct on a 32-lane warp and a 64-lane wavefront.
3 03_occupancy_math.md Whiteboard: occupancy as a min() over four limiters, with shared memory binding.

Each drill maps to an interview competency (divergence reasoning and the cost of fixing it / portable warp-level primitives / occupancy arithmetic and knowing which knob is live). Solutions include the reasoning, not just the answer.


What the output looks like

The programs print their own numbers — absolute ms and GB/s depend entirely on your GPU, so none are quoted here. What is fixed, and what you are grading yourself on, is the shape: which line PASSES, and which ratio moves.

Exercise 1 — Fix the divergence (01_fix_divergence.exe)

Both kernels PASS from the start; the bug is performance. Before you touch TODO 1 the two timings are the same program, so:

divergent  (thread t -> element t) : <ms> ms  PASSED
remapped   (your TODO)             : <ms> ms  PASSED     <- same value as above
speedup                            :    1.00x

No real speedup yet - TODO 1 is still the identity mapping.

After the remap, the speedup line should move clearly above 1: the divergent wavefront executes the sqrt chain and the Newton chain with half its lanes masked, while a wavefront-uniform one executes exactly one of them. The theoretical ceiling is (cost_sqrt + cost_newton) / max(cost_sqrt, cost_newton), so somewhere between 1.5× and 2× is the healthy band on current hardware — not a fixed number.

If your speedup is suspiciously close to 1.00× after the fix, check that n/2 is still a multiple of warpSize (otherwise one wavefront straddles the class boundary — harmless) and that you did not accidentally leave both branches reachable per wavefront.

Exercise 2 — Portable wavefront reduction (02_warp_reduce.exe)

This one FAILS out of the box, and the ratio tells you which bug you are looking at:

warpSize     : 64 lanes
sum          : <got>  (expected <exp>, ratio 0.125x)
kernel time  : <ms>   effective BW <GB/s>
result       : FAILED
ratio meaning
0.500x on a 64-lane wavefront TODO 1 only: the ladder started at 16, so lane 0 holds half the wavefront
0.250x with a 256-thread block on CDNA TODO 2 only: 1 of 4 wavefront partials published
0.125x both bugs (0.5 × 0.25)
1.000x + PASSED done

On a 32-lane NVIDIA warp the first bug is invisible — warpSize/2 == 16 — which is precisely why the hardcoded version survives in so many CUDA tutorials and breaks on AMD.

Exercise 3 — Occupancy math (no program)

Paper drill. Check your inequalities against ../solutions/03_occupancy_math_solution.md. Expected headline: shared memory binds at 3 resident blocks → 18.75% occupancy, capping registers does nothing, and doubling the block size doubles occupancy for free.

Module labs, for comparison

make -C .. run-hip runs the two worked labs. Read divergence for lane-divergent ≈ 2× branchless with warp-uniform ≈ 1×, and occupancy for a theoretical occupancy column that barely moves across block sizes next to one that halves on every doubling of the LDS request.