Visual walkthrough — ROCm - OpenCL alternatives
This page derives one central result of the parent note in pictures: a formula that gives every single worker on a GPU its own unique home address. We will not write that formula down yet — every symbol in it has to be earned first. By the end you will see why the whole thing is just "counting boxes inside boxes", and you will know what happens at every edge — the last box, the leftover box, the empty case.
Read the parent first if you haven't: ROCm / OpenCL alternatives.
Step 1 — The problem: a huge pile of identical jobs
WHAT. Imagine we have a giant list of numbers and we want to do the same tiny operation to each one — say, add two arrays element by element: C[i] = A[i] + B[i]. Suppose the list has elements (we use a small number so it fits in a picture; the real note uses over a million).
WHY. A GPU is not one fast worker. It is thousands of slow-ish workers. To use it, we must hand each worker exactly one job and make sure no two workers grab the same job and no job is skipped. So the very first question is: how do we hand out job numbers so every worker knows which one is theirs?
PICTURE. Here is the raw pile of jobs — a single row of numbered boxes.

Step 2 — The first symbol: the global ID
WHAT. You might say: "Just number them to in one long line and we're done." That flat position — the worker's address in the whole problem — is important enough to earn a name and a symbol right now.
WHY. We give it a symbol now because it is the quantity we are ultimately hunting for. Everything else on this page exists only to reconstruct from the smaller pieces the hardware actually gives us. Hold onto this box-number picture; every later symbol will be measured against it.
PICTURE. The same row again, now with the name written under it.

Step 3 — Why we can't just number them all at once
WHAT. So why not stop here and use directly? Because the hardware won't let us hand out those flat numbers. It insists on chopping the line into equal chunks first. Each chunk is called a work-group.
WHY. Real silicon executes workers in fixed-size batches — NVIDIA runs them at a time (a warp), AMD runs them at a time (a wavefront). So we are forced to slice the flat line into equal groups that match the hardware's batch size.
PICTURE. Same boxes, but now sliced into groups of . (We pick instead of only so it fits on screen — the logic is identical.)

Step 4 — Two rulers instead of one
WHAT. The hardware never tells a worker its directly. Instead it gives each worker two smaller addresses:
- Its group ID — which box it lives in ().
- Its local ID — its seat inside that box ().
WHY. The hardware only ever knows "you are worker #2 inside group #1". That is a natural consequence of the batching from Step 3. So we are handed the two small rulers and must reconstruct the one big ruler () ourselves.
PICTURE. The same strip, labelled twice: the bottom ruler counts groups, the top ruler restarts at inside every group.

Notice the top ruler resets to 0 at the start of every group. That reset is the whole reason we need a formula: local ID appears four different times, so it cannot be an address by itself.
group_id
local_id
Step 5 — Building the formula: skip past full boxes, then step in
WHAT. Now we finally assemble from the two rulers. Take worker local ID 2 in group 1. To find its global number we do two things:
- Skip over every box before ours. There is full box in front of us, each holding workers, so we skip addresses.
- Walk in to our seat. Add our local seat number, .
That gives . Check against Step 4: yes, the box labelled global is exactly local-2-of-group-1.
WHY. This is nothing more than reading a number written in a mixed base. "Group , seat , with seats per group" is the same idea as " ten and ones ", except each place is worth instead of .
PICTURE. The red arrow first jumps over the full groups (that's the multiply), then steps to the seat (that's the add).

Step 6 — Going backwards: recovering box and seat from a global number
WHAT. Sometimes we have the global number and want the other direction: which box, which seat? That is just the formula solved backwards using integer division and remainder:
WHY. Integer division answers "how many whole boxes fit below me?" and the remainder answers "how far past the last full box am I?" — the exact inverse of skip-then-step.
PICTURE. Worker global : dividing by gives quotient (the box) and remainder (the seat). The two operations point back to the two parts of Step 5's arrow.

Step 7 — Edge case A: the very first and very last worker
WHAT. Check the extremes so nothing is ever undefined.
- First worker: group , local → . Correct: the front of the line.
- Last worker: group , local → . Correct: the final box, final seat, which is .
WHY. We must prove the formula covers the boundaries, because off-by-one errors here are the classic GPU bug: read one box too far and you touch memory you don't own.
PICTURE. Both ends lit up — the global range runs exactly through , no gap, no overshoot.

Step 8 — Edge case B: when the total doesn't divide evenly (the "guard" clause)
WHAT. Suppose we have real jobs but the hardware forces groups of . Then , which is not a whole number. The runtime rounds up the launch to groups workers — but only jobs exist. Workers and have no real job.
WHY. The hardware can only launch whole groups. So we always spawn a few extra workers and must switch them off before they read past the end of the array. That is exactly the if (i < n) guard in every kernel:
int i = blockIdx.x * blockDim.x + threadIdx.x; // = global_id
if (i < n) y[i] = a * x[i] + y[i]; // <-- the guardPICTURE. The two extra workers (global ) are drawn as ghosts; the red guard line drops them so they do nothing.

Step 9 — Edge case C: degenerate launches, including the empty one
WHAT. Three limiting shapes to check.
- One giant group (): then group_id is always , and . The two rulers collapse into one — the formula still holds.
- Groups of one (): then local_id is always , and . Again one ruler; formula still holds.
- No jobs at all (): the line is empty, , so zero workers are launched. The formula is never evaluated — there is no worker to hold a — and nothing goes wrong. An empty workload is a valid, quietly-does-nothing launch.
WHY. A formula you trust must survive its extremes. At and the "boxes inside boxes" picture flattens to a single ruler; at the picture is blank and the guard from Step 8 (if (i < n) with ) means no worker ever runs. In every case the arithmetic agrees.
PICTURE. Top: one box holding all . Middle: boxes holding one each. Bottom: the empty line — no boxes, no workers.

Recall Why do the degenerate cases matter?
Because a mapping that breaks at , , or would be an unreliable abstraction. The fact that group_id * local_size + local_id reduces gracefully to a plain identity in the collapsing cases, and simply never fires on an empty launch, is proof it is the right, general formula — not a special-case hack.
The one-picture summary
Everything above is one idea: a flat line of jobs is secretly a grid of boxes, and the address formula is how you fold between the two views. Multiply to skip full boxes, add to step into your seat; divide and remainder to go back.

Recall Feynman retelling — say it like you'd tell a friend
A GPU has thousands of tiny workers, and the only trouble is giving each one a job with no clashes. Its true seat number in the whole job list we call the global ID. But the chip won't line them all up in one row — it runs them in fixed-size gangs (32 for NVIDIA, 64 for AMD), and each gang holds workers. So each worker is told two small things: which gang it's in (group ID) and which seat within the gang (local ID). To rebuild its global ID it skips over all the earlier gangs — that's "gang number times " — then walks to its seat — "plus the seat number". To reverse it, divide by (which gang) and take the remainder (which seat). We always launch a few too many workers because gangs come in whole sizes, so we add one line, if (i < n), telling the leftovers to sit still. Check the ends: worker zero lands at , the last real worker at , never at . Squash a gang down to size one, or blow it up to hold everyone, and the formula still spits out the same flat line — and if there are zero jobs, zero workers launch and nothing happens at all. That's it — counting boxes inside boxes.