CHAPTER 8
How do threads combine their results?
Trace a small sum through shared partial results, then explain the ordering that makes each read safe.
Start with the operation
PyTorch
import torch
x = torch.arange(1, 5, dtype=torch.float32, device="cuda")
total = x.sum()
print(total.cpu())x.sum() combines all inputs into one scalar tensor. torch.arange(1, 5) gives values 1 through 4: its endpoint is excluded. dtype=torch.float32 requests 32-bit floating-point values. PyTorch chooses the reduction implementation and arranges the necessary storage and launches.
We use four values, then eight, so the pairwise combination repeatedly halves the number of partial sums. Earlier five-value examples made an incomplete group visible; a power of two keeps this first cooperation example free of extra boundary rules. The browser’s one-block tree is a chosen algorithm, not a trace of torch.sum.
Run the examples below in your browser with no installation or GPU required. The browser simulates the calculation; it does not execute PyTorch or GPU code. To run the PyTorch snippet itself, use a Python environment with PyTorch and an NVIDIA GPU.
Idea 1 of 3
Build a safe tree of partial sums
Multiplication gave each thread an independent output. A sum is different: values handled by different threads must meet. We need somewhere to exchange partial results and a way to wait until those results are ready.
Definitions
- Reduction
- Combining many values into fewer values using an operation such as addition; here all inputs become one sum.
- Shared memory
- Storage that threads in the same block can read and write. In this model thread i first writes input i into shared word i.
- Partial sum
- A sum containing only some of the original inputs. The diagram lists those input indices beside each partial sum.
- Block barrier
- A point that all threads in the block must reach before any continues. It also makes preceding shared-memory writes visible to participating threads after the barrier.
Each thread first writes one input into shared memory. The initial barrier ensures all those writes precede the first partner reads. At partner distance 2, threads 0 and 1 add values from words 2 and 3. After another barrier, thread 0 adds the two partial sums at distance 1.
Step through the writes before the arithmetic. “Unwritten” means a thread has not yet stored its input there. Each model arithmetic stage reads a snapshot of the preceding stage, representing the ordering between stages. Only some threads add, but every thread reaches each block barrier.
Try it
Predict which two input indices contribute to thread 0’s first partial sum. Run and step through the writes, initial barrier and first addition. Explain why the first two array neighbours are not the first pair combined.
Inspect the PyTorch example for these settings
PyTorch
import torch
x = torch.arange(1, 5, dtype=torch.float32, device="cuda")
total = x.sum()
print(total.cpu())Predict the result, then press Run.
- Inputs
- 4
- Ordering hazard
- None
- Final valid sum
- 10
Thread 0 writes input 1
Value 1
Unwritten
Unwritten
Unwritten
| Arithmetic level | Shared word receiving partial sum | Input indices contributing | Partial sum |
|---|---|---|---|
| Inputs | 0 | 0 | 1 |
| Inputs | 1 | 1 | 2 |
| Inputs | 2 | 2 | 3 |
| Inputs | 3 | 3 | 4 |
The complete synchronised tree combines all 4 inputs into 10. Shared-memory values above are the snapshot after the selected event; the final sum metric describes the whole run.
Idea 2 of 3
Add another level without losing inputs
The synchronised algorithm starts at partner distance N / 2, then halves the distance down to 1. Each new partial sum combines disjoint sets of input contributions. The final node should contain every input index exactly once.
Change from four to eight inputs. Follow the same write, barrier and combine sequence, now with one more arithmetic stage. Both examples use one block with one partially filled warp. The small input count does not remove the need to express the required ordering.
Try it
Predict how the partner distances change for eight inputs. Run with four, then change only the size to eight and run again. Inspect the final contribution list and explain why every original value is included once.
Inspect the PyTorch example for these settings
PyTorch
import torch
x = torch.arange(1, 5, dtype=torch.float32, device="cuda")
total = x.sum()
print(total.cpu())Predict the result, then press Run.
- Inputs
- 4
- Ordering hazard
- None
- Final valid sum
- 10
Thread 0 writes input 1
Value 1
Unwritten
Unwritten
Unwritten
| Arithmetic level | Shared word receiving partial sum | Input indices contributing | Partial sum |
|---|---|---|---|
| Inputs | 0 | 0 | 1 |
| Inputs | 1 | 1 | 2 |
| Inputs | 2 | 2 | 3 |
| Inputs | 3 | 3 | 4 |
The complete synchronised tree combines all 4 inputs into 10. Shared-memory values above are the snapshot after the selected event; the final sum metric describes the whole run.
Idea 3 of 3
Diagnose a missing ordering guarantee
Definition
- Data race
- Conflicting accesses to the same storage by different threads without the required ordering, with at least one write. A useful numeric outcome is not guaranteed.
Start with the safe eight-input sum. Then select the diagnostic without synchronisation. The model deliberately lets thread 0 read its first partner before that partner writes. This shows one missing ordering guarantee; it does not enumerate every possible interleaving.
The later partner write cannot repair the earlier read. The diagnostic therefore reports the unwritten access and assigns no numeric output to the racy program. Seeing a plausible sum in some other execution would not establish safety. The PyTorch example still expresses the safe sum; the diagnostic is a change to our teaching algorithm, not a PyTorch option.
Try it
Run the synchronised case first. Predict which write must precede thread 0’s first partner read, then change only the ordering to the diagnostic. Step to the flagged read and explain the missing dependency; do not guess a numeric racy result.
Predict the result, then press Run.
- Inputs
- 8
- Ordering hazard
- None
- Final valid sum
- 36
Thread 0 writes input 1
Value 1
Unwritten
Unwritten
Unwritten
Unwritten
Unwritten
Unwritten
Unwritten
| Arithmetic level | Shared word receiving partial sum | Input indices contributing | Partial sum |
|---|---|---|---|
| Inputs | 0 | 0 | 1 |
| Inputs | 1 | 1 | 2 |
| Inputs | 2 | 2 | 3 |
| Inputs | 3 | 3 | 4 |
| Inputs | 4 | 4 | 5 |
| Inputs | 5 | 5 | 6 |
| Inputs | 6 | 6 | 7 |
| Inputs | 7 | 7 | 8 |
The complete synchronised tree combines all 8 inputs into 36. Shared-memory values above are the snapshot after the selected event; the final sum metric describes the whole run.
Optional: how does shared-memory layout affect reads?
The barriers establish when a shared value may be read. The shared-memory banks lab explores a different question: how the addresses requested together affect the service of those reads. You can continue directly to matrix multiplication and return to that layout experiment later.
Try a new case
Apply it
In the four-input tree, thread 0 reads shared word 2 at the first addition. Which ordering must hold? Would a block barrier after that read fix a read that happened before thread 2 wrote?
Compare your answer
Thread 2’s write must happen before thread 0 reads shared word 2. The initial block barrier establishes that ordering when all threads take part. A barrier after an early read cannot make that read safe retroactively. The stage barriers also order partial-sum writes before subsequent stages read them.
Run the example on your desktop
Download this chapter’s opening PyTorch example. See the complete example index and setup appendix for CPU PyTorch, the supported GPGPU-Sim programs and optional NVIDIA GPU execution.
The browser still requires no installation. Downloads are generated from the displayed source; CPU checks and simulator checks do not establish GPU performance.
References
Use these official references to check the concepts and code on this page.
- PyTorch: Tensor.sum (new tab)
Follow the torch.sum link to check the complete sum requested by x.sum().
- CUDA: synchronisation functions (new tab)
Check which threads must reach the barrier and which writes it makes visible.
- CUDA: thread block synchronisation (new tab)
Connect shared storage to cooperation within one block.
These browser exercises do not run an installed PyTorch or CUDA toolkit. For optional local execution, choose documentation matching your installed tools. Links open online in a new tab; the questions and answer comparisons below work offline.
Use the documentation
Use the references to explain each answer, then apply it to this page’s example. No installation is needed.