How a GPU groups its threads into blocks
A GPU, or graphics processing unit, is a chip built to run thousands of simple threads at once. It groups them into blocks, and the blocks into a grid. Every thread works out which piece of the data is its own from its place in that grid.
This lab has no GPU. It shows the same arrangement in plain Python, so you can follow every thread by eye.
By the end of this lesson
Note
I can say how a GPU organises its threads and work out the global index of one.
Key words
| Word | What it means | Example |
|---|---|---|
| Thread | one small worker that runs the kernel once | |
| Block | a group of threads that run together | |
| Grid | all the blocks of one launch | |
| Block size | how many threads are in each block | block_dim = 4 |
| Global index | a thread's place among all the threads of the grid | |
| Core | one processing unit that runs instructions |
The cells students fill in are left blank in this preview.
A CPU has few cores, a GPU many
A CPU, the central processing unit, has a few powerful cores. Each is good at running one tricky sequence of steps quickly.
A GPU has thousands of simple cores. One of its threads is slower than a CPU's, but thousands run together. That wins when the job is the same small step on a great many independent items.
Each thread works out its own element
Every thread runs the same function, so it must find its own piece of the data. It does that with one line of arithmetic:
global index = block number x block size + thread number within the block
The thread knows its block number and its place in the block. The block size is the same for every block.
Worked example: 3 blocks of 4 threads
| Block | Thread 0 | Thread 1 | Thread 2 | Thread 3 |
|---|---|---|---|---|
| 0 | 0 | 1 | 2 | 3 |
| 1 | 4 | 5 | 6 | 7 |
| 2 | 8 | 9 | 10 | 11 |
The cells students fill in are left blank in this preview.
Run the global index sum
# Which element does each thread own? 2 blocks of 5 threads.
block_dim = 5
for block_idx in range(2):
for thread_idx in range(block_dim):
i = block_idx * block_dim + thread_idx
print("block", block_idx, "thread", thread_idx, "owns element", i)
# Change block_dim to 3 and the number of blocks to 4, then run again.
The lab simulates a launch of 3 blocks
The lab's kernel.py launches 3 blocks of 4 threads over 10 elements. It prints the block, the thread and the global index of each of the 12 threads.
Twelve threads cover ten elements, so the last two have no element. The script tells you which ones. The second Run works one index by hand: block 3, thread 1, block size 4, then 7 turned back into a block and a thread.
Print the simulated launch
docker exec jupyter python3 steps/kernel.pyShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Work an index and reverse another
docker exec jupyter python3 -c "print(3 * 4 + 1, 7 // 4, 7 % 4)"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A common mistake
Warning
Many people think a GPU thread is a faster CPU thread. It is slower and simpler. The GPU's speed comes from the number of threads, so it helps only when there is a lot of independent work to give them.