constexprint BM = 64, BN = 64, BK = 16; constexprint TM = 4, TN = 4; // each thread computes 4x4 of C
__global__ voidmatmul_2d_tile(constfloat* A, constfloat* B, float* C, int M, int N, int K){ __shared__ float As[BM][BK]; __shared__ float Bs[BK][BN];
int by = blockIdx.y, bx = blockIdx.x; int ty = threadIdx.y, tx = threadIdx.x;
int row0 = by * BM + ty * TM; int col0 = bx * BN + tx * TN;
float acc[TM][TN] = {0};
// each tile: load BM*BK + BK*BN floats with the 16x16 thread block // 16*16 = 256 threads, BM*BK = 1024 elems → each thread loads 4 of A, 4 of B constint threads_per_block = 16 * 16; constint tid = ty * 16 + tx;
for (int kt = 0; kt < K; kt += BK) { // load As (BM=64 x BK=16 = 1024 floats), 256 threads × 4 each #pragma unroll for (int i = 0; i < 4; ++i) { int load_id = i * threads_per_block + tid; int r = load_id / BK, c = load_id % BK; int gr = by * BM + r, gc = kt + c; As[r][c] = (gr < M && gc < K) ? A[gr * K + gc] : 0.f; } // load Bs (BK=16 x BN=64 = 1024 floats) #pragma unroll for (int i = 0; i < 4; ++i) { int load_id = i * threads_per_block + tid; int r = load_id / BN, c = load_id % BN; int gr = kt + r, gc = bx * BN + c; Bs[r][c] = (gr < K && gc < N) ? B[gr * N + gc] : 0.f; } __syncthreads();
#pragma unroll for (int k = 0; k < BK; ++k) { float a_reg[TM], b_reg[TN]; #pragma unroll for (int i = 0; i < TM; ++i) a_reg[i] = As[ty * TM + i][k]; #pragma unroll for (int j = 0; j < TN; ++j) b_reg[j] = Bs[k][tx * TN + j]; #pragma unroll for (int i = 0; i < TM; ++i) #pragma unroll for (int j = 0; j < TN; ++j) acc[i][j] += a_reg[i] * b_reg[j]; } __syncthreads(); }
#pragma unroll for (int i = 0; i < TM; ++i) #pragma unroll for (int j = 0; j < TN; ++j) { int r = row0 + i, c = col0 + j; if (r < M && c < N) C[r * N + c] = acc[i][j]; } }
三、应用场景
场景
Tile 的对象
收益来源
矩阵乘法
A 的行块、B 的列块
输入复用降到 O(N²)
矩阵转置
输入 / 输出 tile
合并访存,避免 uncoalesced
卷积 / Stencil
输入 halo 区域
邻域重复使用
归约
每线程多元素 + 块内树形
减少同步与原子操作
多维数组
2D → 1D 展平
访存带宽利用率最大化
以转置为例,tiling 是解决"读合并、写不合并"矛盾的必备手段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
constexprint TILE = 32;
__global__ voidtranspose(constfloat* in, float* out, int rows, int cols){ __shared__ float tile[TILE][TILE + 1]; // +1 避免 bank conflict
int x = blockIdx.x * TILE + threadIdx.x; int y = blockIdx.y * TILE + threadIdx.y;
if (x < cols && y < rows) tile[threadIdx.y][threadIdx.x] = in[y * cols + x];
__syncthreads();
x = blockIdx.y * TILE + threadIdx.x; y = blockIdx.x * TILE + threadIdx.y; if (x < rows && y < cols) out[y * rows + x] = tile[threadIdx.x][threadIdx.y]; }
四、注意
Tile 不是越大越好,需要平衡以下几个因素:
1. Tile 大小 vs 占用率(Occupancy) 共享内存是有限的(如 SM 上 48KB~164KB)。tile 越大,单个 block 占用越多,能同时驻留的 block 越少。必须在"数据复用率"和"延迟掩盖能力"之间找平衡点。
2. Bank Conflict 共享内存被划分成 32 个 bank。若同一 warp 的多个线程访问同一 bank 的不同地址,会串行化。典型对策: