CHAPTER 1
One operation, many numbers
Start with a short PyTorch program, then follow how the GPU handles its numbers. By the end, you will be able to trace one input to its output and explain why extra threads need a bounds check. Each example below keeps its own controls and results, so you can look back as you go.
Start with a PyTorch operation
PyTorch
import torch
x = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0], device="cuda")
y = x * 2Definition
- Tensor
- A collection of numbers arranged in one or more dimensions. Here, our tensor is a one-dimensional list of five numbers.
import torch loads PyTorch. Here x holds five inputs. device="cuda" places them on an NVIDIA GPU, and y = x * 2 multiplies each number by two to produce y.
- Input
x [1, 2, 3, 4, 5]- Result
y [2, 4, 6, 8, 10]
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.
How does the GPU calculate these results?
Idea 1 of 5
One calculation for five inputs
The PyTorch operation multiplies every input by the same amount. Follow the five numbers through the picture, then try a different multiplier.
PyTorch operation
y = x * 2Try it
Predict the outputs, then run. Change the multiplier and predict again.
Browser simulation · this example has its own controls
Predict the outputs, then press Run.
Idea 2 of 5
Which number does each thread use?
The previous picture showed the same calculation applied to five inputs. A GPU can divide that work among many executions of a small program: each reads an input, multiplies it, and writes a result. These executions are called threads, and many can make progress in parallel.
Definitions
- Thread
- One execution of a GPU program. Many threads can run the same code, each working on different data.
- Array
- An ordered collection of values.
- Index
- A number that identifies a position in an array, starting at zero.
In this picture, one thread handles one input. Its index tells it which input to read and where to write the result. This is a simplified mapping; PyTorch chooses the actual implementation.
PyTorch operation
y = x * 2Try it
Select a thread in the picture. Follow its index from one input to one output.
Browser simulation · this example has its own controls
Predict the outputs, then press Run.
Select a thread to follow its input and output.
Idea 3 of 5
Threads are organised into blocks
How do we ask the GPU to start those threads? We specify groups rather than starting each thread separately. The GPU can distribute these groups, and threads within a group can cooperate when an algorithm needs them to.
Definitions
- Launch
- Starting a GPU function with a chosen number of blocks and threads per block.
- Block
- A group of threads in a launch. Threads in the same block can cooperate.
Here each thread multiplies its own input independently, so it needs no cooperation. Threads in a block do not necessarily all execute at once. Later, we will use cooperation to add many inputs into one result.
PyTorch operation
y = x * 2For our example we choose four threads per block. Two blocks provide eight threads for five inputs. Each thread has a position within its block. Its index across the array is: block number × threads per block + position within the block.
Block 1, position 0 gives 1 × 4 + 0 = 4: the fifth input. The extra three threads still exist, but have no input. A check makes them skip the read, multiplication and write. Their cards are grey and say “No calculation”; select one to inspect its index. The next experiment lets you turn that check off.
Try it
Select thread 4 in the second block to follow the fifth input and see how its index is calculated. The three extra threads skip the arrays; we will explore that check next.
Browser simulation · this example has its own controls
Block 0
Block 1
Predict the outputs, then press Run.
Select a thread to follow its input and output.
Idea 4 of 5
More threads than numbers
Definition
- Bounds check
- A check that an index refers to an element that exists before accessing it.
Keep the same five inputs and two blocks of four threads. The extra three threads must skip the arrays because their indices have no input or output element.
PyTorch operation
y = x * 2Here, the bounds check asks whether a thread’s index is smaller than the number of inputs. Indices 0–4 pass; indices 5–7 skip the read, multiplication and write. Turn the check off below to see why it matters.
Try it
Run with the check enabled. Then turn it off and see which threads would access beyond the arrays.
Browser simulation · this example has its own controls
Block 0
Block 1
Predict the outputs, then press Run.
Select a thread to follow its input and output.
Idea 5 of 5
From Python to a GPU kernel
We have followed the work in pictures. Now we can write the small program each thread follows and show the code that starts the groups.
Definition
- Kernel
- A GPU function executed by the threads in a launch.
Each thread executes the complete function below with its own index. Here is a CUDA kernel and the launch that starts it, putting together the index, calculation and bounds check you have just explored.
PyTorch operation
y = x * 2PyTorch selects an optimised implementation and handles its launch; y = x * 2 does not choose blocks or threads. CUDA lets us write the GPU function and choose those settings ourselves. The compiler also optimises our code. Our runKernel is a separate teaching function; PyTorch does not call it when evaluating this expression. The example makes the work visible and is not intended to outperform PyTorch.
CUDA C++ · complete GPU function (kernel)
__global__ void runKernel(const float* input, float* output, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
output[i] = input[i] * 2.0f;
}
}CUDA C++ · launch from the CPU
runKernel<<<2, 4>>>(d_input, d_output, 5);- Find the input.
blockIdx.xis the block number,blockDim.xis its size, andthreadIdx.xis the position within it. Their calculation gives the integer indexi. The.xselects the first dimension of our one-dimensional launch. - Check the index.
nis the input count.if (i < n)lets only existing elements reach the calculation. - Calculate one result. The assignment reads
input[i], multiplies it and writesoutput[i].2.0fis a floating-point number;;ends the statement.
The launch’s <<<2, 4>>> starts two blocks of four threads. d_input and d_output refer to arrays already allocated in GPU memory; the final 5 supplies n. The download below includes that allocation and the copies.
Read the function declaration
__global__ marks a GPU kernel. void means it writes results through output rather than returning a value. float* passes the location of an array of floating-point values; const means this function does not change the input. int declares an integer.
Try it
Run once more, then compare the Python operation with the complete CUDA example.
Browser simulation · this example has its own controls
Block 0
Block 1
Predict the outputs, then press Run.
Select a thread to follow its input and output.
Run the CUDA example on your computer
The download contains this kernel and a separate CPU-side C++ program that allocates memory, copies inputs, launches the kernel and reads the output. It is not a Python extension.
With an NVIDIA GPU: install a compatible driver, CUDA Toolkit and host C++ compiler, then run:
nvcc -std=c++17 hello.cu -o hello ./hello
Without an NVIDIA GPU: use our GPGPU-Sim Docker container to compile and simulate the download on your desktop.
Desktop setup and output checks
CI compiles generated chapter 1 examples with the CUDA Toolkit and checks their outputs in GPGPU-Sim. Physical NVIDIA GPU execution has not yet been validated.
Try a new case
Predict a new case
Keep four threads per block. Choose an input count, then predict how many blocks you need and which thread indices should skip the arrays.
Predict first, then check.
Run it with PyTorch
Run the opening snippet in your own PyTorch environment with an NVIDIA GPU, then inspect y.cpu(). Chapter 2 explains that copy. The PyTorch companion checks the examples from the opening chapters and offers a CPU-only mode for checking values and layouts.
The companion’s GPU path has not yet been validated on an NVIDIA GPU. The CUDA download above is a separate handwritten implementation.
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.
runKernel is our example function name, not a CUDA API or a function generated by PyTorch. Follow the CUDA references for the language features it uses.
- PyTorch: torch.tensor (new tab)
Check device and dtype in the opening snippet.
- PyTorch: selecting an implementation (new tab)
Read the dispatcher paragraph under “Extending torch native API”; the rest is advanced.
- CUDA: writing and launching kernels (new tab)
Recognise the complete GPU function and the <<<blocks, threads>>> launch syntax.
Optional later reading
- PyTorch: torch.compile getting started (new tab)
Later reading: compilation can generate kernels and combine operations. The opening example uses ordinary PyTorch execution.
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.
About the simulation
This browser model computes the illustrated operations on the CPU, including offline. It does not run PyTorch, compile CUDA or execute GPU code. The picture shows which element each thread handles, not hardware placement, execution timing or a promise that all threads run at once.