Introduction
In our last lesson, we built the conceptual foundation for Triton, exploring its block-centric programming model and core abstractions. We saw how it shifts the programmer's focus from individual threads to entire tiles of data, automating many of the painstaking details of CUDA programming.
Today, we transition from theory to practice. We will implement two common AI kernels in Triton, putting the abstractions of tl.program_id, tl.arange, and tl.load/tl.store to work. We'll begin with an element-wise activation function to solidify the basic pattern. Then, we will tackle a simplified version of the most important operation in deep learning: General Matrix Multiplication (GEMM). Your focus on low-level, high-performance systems makes mastering GEMM a critical step, and this lesson will show you how to approach it with Triton's powerful tiling capabilities.
Implementing an Element-wise Kernel
Let's start by implementing a kernel for an operation slightly more complex than the vector addition we've seen before: the GELU (Gaussian Error Linear Unit) activation function. The exact formula is less important than the pattern of implementation, which applies to any element-wise operation (e.g., ReLU, SiLU, bias-add).
The logic is identical to the vector addition pattern from our previous lesson:
- The host wrapper calculates the grid size and launches the kernel.
- Each program instance gets a unique
pidusingtl.program_id. - Each instance computes the starting offset and creates a block of offsets for its tile using
tl.arange. - It
loads the data for that tile from global memory into on-chip registers/SRAM, using amaskfor boundary checks. - It performs the computation on the tile. Since the data is now on-chip, this step is very fast.
- It
stores the resulting tile back to global memory, again using amask.
The following video provides an excellent walkthrough of implementing a GELU kernel in Triton, following this exact pattern.
Stanford CS336 Language Modeling from Scratch | Spring 2025 | Lecture 6: Kernels, Triton
This Stanford CS336 lecture provides a detailed demonstration of writing a Triton kernel for the GELU activation function.
Please watch the segment from this demonstration. Pay close attention to how the lecturer constructs the Triton kernel and its Python wrapper, which mirrors the structure we discussed. You'll see the block-centric model in action.
As you can see, once you understand the basic template, writing custom element-wise kernels becomes a highly productive task. You simply change the mathematical expression in the middle of the kernel, and Triton's compiler handles the low-level hardware mapping.
From Element-wise to Tiled GEMM
Element-wise operations are memory-bound; their performance is limited by the speed at which data can be fetched from global memory. The arithmetic intensity is low because each loaded element is used for only one or two calculations.
Matrix multiplication is different. It is a compute-bound operation with high arithmetic intensity, meaning many computations are performed for each element loaded. A naive implementation where each thread computes one element of the output matrix, , would be disastrously inefficient. Each thread would repeatedly load the same rows of matrix A and columns of matrix B from slow global memory, failing to exploit the GPU's fast on-chip memory.
The solution is tiling (or blocking). Instead of computing one element at a time, each Triton program instance will compute an entire tile of the output matrix C.

Here's the core idea:
- Partition: The output matrix
Cis partitioned into 2D tiles of sizeBLOCK_SIZE_MxBLOCK_SIZE_N. - Assign: Each Triton program instance is responsible for computing a single tile of
C. - Iterate and Accumulate: To compute its tile, the program instance iterates along the shared
Kdimension in chunks of sizeBLOCK_SIZE_K. In each step of the loop, it:- Loads a
BLOCK_SIZE_MxBLOCK_SIZE_Ktile fromAinto fast on-chip memory. - Loads a
BLOCK_SIZE_KxBLOCK_SIZE_Ntile fromBinto fast on-chip memory. - Performs a matrix multiplication on these two small, on-chip tiles.
- Adds the result to an accumulator tile that lives in registers.
- Loads a
- Store: After the loop over
Kis finished, the final accumulator tile is written to its corresponding position in the global memory of matrixC.
This strategy maximizes data reuse. A tile loaded from matrix A is used for all BLOCK_SIZE_N columns of the output tile, and a tile from B is used for all BLOCK_SIZE_M rows. This keeps the compute units fed with data from fast memory, which is the key to high performance.
This next resource provides a fantastic animated visualization and explanation of why tiling is so effective.
Introduction to GPU Programming with Triton - Medium
This article from Medium gives a very intuitive explanation of blocked GEMM.
Please read the section titled Blocked/Tiled Matrix Multiplication. Focus on the "Why this is inefficient on GPUs" and "Blocked GEMM" subsections, and pay close attention to the animated GIF illustrating the data flow.
Implementing Tiled GEMM in Triton
Now, let's see how this tiled algorithm translates into Triton code. The implementation requires two key steps beyond what we saw in the element-wise kernel: multi-dimensional pointer arithmetic and an accumulation loop.
The official Triton documentation provides a full, high-performance GEMM kernel. We will walk through the key parts of this implementation.
Matrix Multiplication - Triton documentation
This tutorial from the official Triton documentation is the definitive guide to writing a high-performance GEMM kernel.
First, read the section on the compute kernel to understand the overall algorithm. Next, study the Pointer Arithmetic section. This is the most complex part. It explains how to construct 2D blocks of pointers to load the tiles from A and B. The key is to combine 1D arange blocks for rows and columns with the matrix strides. Finally, read through the complete kernel implementation in the section titled Final Result. Try to map the code blocks to the tiled algorithm we've discussed: Program ID Mapping: Lines that convert the 1D pid into 2D tile coordinates pid_m and pid_n. Pointer Initialization: The a_ptrs and b_ptrs blocks. Accumulator: The accumulator = tl.zeros(...) line. Main Loop: The for k in ... loop, which contains tl.load, tl.dot, and pointer updates. Storing Result: The code after the loop that writes c back to global memory.
Let's break down the most crucial components from that code:
-
2D Pointers: Unlike the 1D offsets in our GELU kernel, for a tile of matrix
A, we need a 2D block of pointers. This is constructed using NumPy-style broadcasting:# From the Triton tutorial offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M offs_k = tl.arange(0, BLOCK_SIZE_K) a_ptrs = a_ptr + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)Here,
offs_am[:, None](a column vector of row offsets) is broadcast-added tooffs_k[None, :](a row vector of column offsets) to create a 2D grid of offsets, which are then added to the base pointera_ptr. -
The Accumulator and
tl.dot:# From the Triton tutorial accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32) for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)): a = tl.load(a_ptrs, ...) b = tl.load(b_ptrs, ...) accumulator = tl.dot(a, b, accumulator) # Fused-Multiply-Add a_ptrs += BLOCK_SIZE_K * stride_ak b_ptrs += BLOCK_SIZE_K * stride_bkThe
accumulatoris initialized to zeros infloat32for better precision. Inside the loop,tl.dotperforms the matrix multiplication on the local tilesaandband adds the result to theaccumulator. This single function call is where the bulk of the computation happens. It is a highly optimized intrinsic that can map directly to the GPU's Tensor Core hardware. After the dot product, the pointers forAandBare advanced to the next slice in theKdimension.
The Triton Compilation Pipeline
Your interest in low-level systems warrants a brief look at what happens when you run a @triton.jit function. Triton is not an interpreter; it's a real compiler. The Python code you write is parsed and progressively "lowered" through several stages of intermediate representations (IR) until it becomes machine code.

This process ensures that the high-level, Pythonic code you write is translated into the low-level, highly-optimized instructions that the GPU actually executes. The compiler handles the complex register allocation, instruction scheduling, and memory access patterns that you would otherwise need to manage by hand in CUDA.
Conclusion
In this lesson, we have successfully translated Triton's programming model into practical, working AI kernels. We started with a familiar element-wise pattern for a GELU activation and then progressed to the far more complex and performance-critical tiled matrix multiplication.
Key Takeaways:
- Element-wise Kernels: Follow a simple template of defining a tile, loading it, computing on it, and storing the result.
- Tiled GEMM: The key to high performance is maximizing data reuse from fast on-chip memory. This is achieved by having each program instance compute a tile of the output matrix
Cwhile iterating through theKdimension in blocks. - Core GEMM Components: The implementation relies on 2D pointer arithmetic to address tiles, a register-based
accumulatorto hold partial sums, and thetl.dotintrinsic to perform the on-chip matrix multiplication. - Triton as a Compiler: Triton is not just a library; it's a sophisticated compiler that transforms high-level Python code into optimized low-level machine code, abstracting away immense complexity.
We have now written a high-performance GEMM kernel in about 25 lines of Python. In the next lesson, we will do a direct, critical comparison of this implementation against a raw CUDA C++ version. We will analyze the code complexity, developer productivity, and benchmark the performance to fully appreciate the trade-offs Triton offers.