Katkat

Explain GPU Memory And Matrix Transpose Optimization

ZooxSoftware Engineer · Onsite

Last updated

Tests understanding of GPU memory hierarchy, memory coalescing, shared memory bank conflicts, and optimization techniques through the canonical matrix transpose problem.

Problem Overview

Matrix transpose is a classic memory-bound GPU kernel where optimizing memory access patterns is critical to approaching peak device bandwidth. A naive parallel implementation either reads coalesced and writes uncoalesced, or vice versa, causing significant memory bus underutilization.

Your task is to analyze the naive GPU matrix transpose, identify its hardware-level bottlenecks, and design an optimized tiled transpose algorithm.

Requirements

  1. Memory Access Analysis:

    • Explain how thread warps (32 threads) access global memory in the naive implementation below:
      __global__ void naiveTranspose(const float* in, float* out, int width, int height) {
          int x = blockIdx.x * blockDim.x + threadIdx.x;
          int y = blockIdx.y * blockDim.y + threadIdx.y;
          if (x < width && y < height) {
              out[x * height + y] = in[y * width + x];
          }
      }
      
    • Identify whether the reads, writes, or both are coalesced, and explain the performance impact on modern GPU memory controllers.
  2. Tiled Implementation with Shared Memory:

    • Present an optimized kernel using shared memory tiling (e.g., using a TILE_DIM x TILE_DIM block).
    • Outline how both global reads and global writes can be made fully coalesced.
    • Write pseudo-CUDA code or provide a precise step-by-step implementation for the kernel.
  3. Shared Memory Bank Conflicts:

    • Explain what shared memory bank conflicts are, how a 32-bank architecture responds to a naive 2D shared memory tile allocation like __shared__ float tile[32][32], and how tile padding (e.g., tile[32][33]) resolves this conflict.

Constraints & Assumptions

  • Matrix dimension width and height are multiples of 32 (e.g., 2048 x 2048 or 4096 x 4096).
  • Standard warp size is 32 threads, operating in SIMT lockstep.
  • Shared memory consists of 32 independent banks, each 4 bytes wide.

Hints

Hint 1

In the naive kernel, consecutive threads in a warp have adjacent x-indices; trace which output indices they map to when indexing out[x * height + y].

Hint 2

Shared memory allows you to decouple the global read pattern from the global write pattern by staging intermediate data.

Hint 3

If consecutive threads in a warp write or read along the column of a 32-wide 2D array, they hit the same bank modulo 32 unless you add an extra column offset.

Follow-up questions

  • How does thread coarsening (e.g., each thread processing multiple rows per tile) improve instruction-level parallelism and arithmetic intensity?
  • How would your approach change if matrix dimensions were not even multiples of the tile dimension?
  • In an autonomous driving sensor pipeline (e.g., LiDAR range image transposition or camera tensor reshaping), would you prefer a custom transpose kernel or a library call like cuBLAS / cuTENSOR, and under what conditions?