Convolution is one of the foundational operations in modern machine learning, but it is also a useful example of a broader engineering idea: many apparently specialized tensor operations can be reorganized into well-understood compute patterns that are easier to implement, optimize, and scale. This article explains a practical way to implement 2D convolution by treating it as a structured form of matrix multiplication. The goal is not to promote a specific product or benchmark result, but to show a reusable optimization mindset for engineers building GPU compute kernels. Key idea : A 1×1 convolution is effectively a matrix multiply, and larger K×K convolutions can be expressed as a sequence of matrix-like accumulations with careful attention to cache behavior and loop ordering. Why this matters Conv2D is everywhere in inference pipelines, image models, and feature extraction stages. Yet the implementation challenge is often less about the mathematical definition and more about the execution strategy: how data is laid out, how work is grouped, and how memory traffic is controlled. A useful approach is to avoid treating convolution as a completely unique primitive. Instead, interpret it as a compute pattern closely related to matrix multiplication. This can reduce implementation complexity and create a clearer path for optimization. It lowers engineering effort by reusing a familiar execution model. It reduces maintenance cost because optimizations can transfer across related kernels. It increases the chance that future hardware and software improvements benefit both matrix multiply and convolution paths. Starting from the tensor shapes A standard Conv2D operation can be described as applying a learned filter tensor across an input tensor to produce an output tensor. C = A * B Where: A is the input tensor of size N × H × W × C_in B is the weight tensor of size H_filter × W_filter × C_in × C_out C is the output tensor of size N × H_out × W_out × C_out In practice, one of the most important simplifications comes from looking at the data in a way that matches the compute engine rather than the original mathematical notation. The simplest case: Why 1×1 convolution is special A 1×1 convolution is the cleanest place to see the matrix multiplication analogy. At each spatial position, the input contributes a feature vector of length C_in . The weights map that vector into an output vector of length C_out . If the input tensor is already stored in NHWC order, each spatial feature vector is naturally arranged in a way that can be interpreted as rows of a matrix, without requiring explicit data rearrangement. Practical takeaway : For NHWC input, a 1×1 convolution can be treated directly as a matrix multiply from C_in to C_out , producing output in NHWC order with no transposition step. This matters because unnecessary data movement is often one of the fastest ways to lose performance. When the original layout already matches the desired access pattern, the implementation can stay simple and efficient. From 1×1 to full K×K convolution Once the filter grows beyond 1×1, each output element depends on multiple spatial locations. A 3×3 convolution, for example, combines nine separate spatial contributions. Conceptually, this becomes a set of matrix-like products that are accumulated together. That means the problem is no longer just “do matrix multiplication,” but “do repeated matrix-like accumulation while minimizing memory waste.” For a 3×3 filter, there are nine C_in × C_out contributions per output location. A naive implementation can achieve good arithmetic utilization, but it may repeatedly flush and reload input data through the cache hierarchy. That is where execution order becomes critical. Loop ordering is an optimization decision One of the most useful lessons in convolution optimization is that equivalent mathematics can produce very different memory behavior. A straightforward ordering may process each filter position as if it were