GPU Architecture: Connecting Hardware Components with CUDA Concepts

Why Care About GPU Architecture?

In our daily life, we hear a lot about GPUs. I even see some people who use frameworks that utilize GPUs efficiently, yet they think of the GPU as a black box: you give it something and the result appears. To complete the Session 4 assignment in the Al-Khawarizmi Tour 2026 Online HPC & AI Hackathon I had to learn CUDA. As a physics student, I first needed the full picture of the GPU or "the system". Or at least the basic architecture. Below is a basic GPU diagram labeling its main components, such as the GPU chip, VRAM, and power connectors.

GPU schematic diagram showing the GPU chip, VRAM, and power connectors
GPU schematic diagram
Image generated using Google Gemini AI 3.0

I'll use the NVIDIA A100, based on the Ampere architecture, as an example because it became an important accelerator for modern deep learning and high-performance computing. I'm not going to list cache sizes and bandwidth numbers. Instead, I want you to see the architecture — to understand the physical structure from the top level all the way down to the individual cores.

GPU Chip

When we hear about GPU, we hear some number about transistor. The presentor talks about this component, the GPU chip, which contains tens or hundreds of billions of transistors. This is where the GPU's computation takes place (This 'computation takes place" term will come again, remember that). A100 is the GPU name, where GA100 is the chip name, which was made in TSMC. Let's zoom in on the chip.

GA100 full GPU block diagram showing 128 Streaming Multiprocessors arranged in 8 GPCs
The GA100 chip: 8 GPCs, each containing multiple TPCs, each containing Streaming Multiprocessors (SMs).
Image source: NVIDIA Developer Blog

To organize computation and data handling, GPU manufacturers arrange processing hardware in a hierarchy. I want you to look at this image very carefully. I suggest opening it in a new tab and zooming in to find the labels "GPC", "TPC", and "SM".

Let me break down the hierarchy:

GPU → GPCs → TPCs → SMs → Processing Units

GPU — the complete GPU chip
GPC (Graphics Processing Cluster) — a major processing cluster
TPC (Texture Processing Cluster) — contains two SMs in GA100
SM (Streaming Multiprocessor) — schedules and executes thread blocks
Processing units — include CUDA cores, Tensor Cores, and other execution units

So the chip is hierarchical: big groups contain smaller groups, which contain even smaller groups. For now, focus on the Streaming Multiprocessor and leave the other components aside. Understanding how SMs are organized will help you write more efficient CUDA code. The full GA100 architecture supports up to 128 SMs. The NVIDIA A100, however, has 108 enabled SMs.

Inside One Streaming Multiprocessor

Each SM is the fundamental unit of computation. When your CUDA program runs, work gets assigned to SMs. Let's look inside one:

A100 Streaming Multiprocessor block diagram showing four processing blocks with CUDA cores and Tensor Cores
Inside one A100 Streaming Multiprocessor: four processing blocks, each with its own scheduler, CUDA cores, and a Tensor Core.
Image source: NVIDIA Developer Blog

Each processing block has:

The SM contains an L1 cache and shared-memory subsystem. Shared memory provides a fast workspace that threads within the same thread block can use to exchange data and cooperate.


CUDA programming help: The Two Layers of 3D Thinking

Here is the part that trips people up when they start CUDA programming, and the part that, once you get it, everything clicks. Before looking at the CUDA code, let's connect the physical GPU components we just explored with the terms used in CUDA programming.

Think of the relationship this way: the SM is the physical hardware, while blocks, threads, and warps describe how CUDA organizes and schedules computation on that hardware. A block is not an SM, and a thread is not a CUDA core.

Now, when you write a CUDA kernel, you organize your work into two coordinate systems — both can have up to three dimensions. First, consider the following CUDA code:

dim3 grid(grid_x, grid_y, grid_z);
dim3 threads(thread_x, thread_y, thread_z);

kernel<<<grid, threads>>>(args);

Here, we specify two launch parameters: the grid dimensions and the number of threads in each block. Let's understand what these two represent in the GPU.

First 3D: Where is my block inside the grid?

The first parameter, grid, specifies the dimensions of the grid of thread blocks . Suppose you specify dim3 grid(3,2,3). This creates a grid containing 18 thread blocks. The GPU scheduler distributes these blocks across available SMs. An SM can execute multiple blocks, depending on available resources, while other blocks may wait for their turn. The grid coordinates identify a block within the grid, not its physical location on the GPU.

First 3D: Grid of Blocks → assigned to SMs Block(0,0,0) Block(1,0,0) Block(2,0,0) Block(0,1,0) Block(1,1,0) Block(2,1,0) mapped to SM 0 SM 1 SM 2 SM 3 SM 4 ... SM 105 SM 106 SM 107 Grid (up to 3D) 108 SMs on the A100 blockIdx.x, blockIdx.y, blockIdx.z → identifies a block's coordinates within the grid

Second 3D: Where is my thread inside that block?

Inside each block, you have threads — and this is another 3D coordinate space. Threads execute on the processing units inside an SM. The warp schedulers manage their execution in groups called warps. Thread coordinates identify a thread within its block. The SM's warp schedulers manage the execution of warps on the available processing units.

Second 3D: Threads inside one Block → run on cores inside one SM One Block (e.g. 7×2×6 threads) threadIdx.x, threadIdx.y, threadIdx.z runs on One SM Sub-partition 0 CUDA cores Tensor Core Warp Scheduler Sub-partition 1 CUDA cores Tensor Core Warp Scheduler Sub-partition 2 CUDA cores Tensor Core Warp Scheduler Sub-partition 3 CUDA cores Tensor Core Warp Scheduler Shared Memory / L1 Cache Each thread knows its position via threadIdx. Threads in the same block share the SM's shared memory and can cooperate.
The key insight: When you write CUDA code, you are thinking in 3D two times. First, you define a 3D grid of blocks — each block gets mapped to an SM on the chip. Then, inside each block, you define a 3D arrangement of threads. These threads are grouped into warps, and the SM's warp schedulers manage their execution on the available processing units.Your kernel's global thread ID is a combination of both. That's why CUDA gives you blockIdx (which block am I?) and threadIdx (which thread inside that block am I?).

Putting Both Layers Together

Here is the complete picture — both levels of the hierarchy in one view:

The Complete Two-Level Mapping Level 1: The A100 GPU (108 SMs) SM 0 SM 1 SM 2 SM 3 SM 4 SM 5 SM 6 SM 7 SM 8 SM 9 ... ... ... ... SM 106 SM 107 ← blockIdx identifies a block within the grid → zoom in Level 2: Inside SM 0 — Threads on Cores Sub-partition 0 Sub-partition 1 Sub-partition 2 Sub-partition 3 ■ Tensor Core ■ Tensor Core ■ Tensor Core ■ Tensor Core Shared Memory / L1 Cache (shared by all threads in this SM) ← threadIdx identifies a thread within its block →
The postal analogy:
blockIdx = your district address (which block in the grid)
threadIdx = your house address within the district (which thread in the block)
globalThreadId = your complete address within the overall thread arrangement

For framework users: You don't need to write CUDA kernels yourself. PyTorch and TensorFlow handle all of this internally. But knowing this architecture helps you understand why certain configurations are faster — Why batch size matters (it affects the workload and can influence the number of blocks launched), why mixed precision helps (Tensor Cores accelerate supported operations at reduced precision), and why memory management is critical (data movement can become a performance bottleneck).

References