矩阵转置算子优化-利用padding 解决 bank conflict

相关概念

sm: 硬件层面的计算核心,SM 是调度的基本单位。GPU 的硬件调度器会把你的 Block "分发"给空闲的 SM 去执行

grid: 是 CUDA 编程模型中最高层级的线程组织结构,它代表了一次 Kernel 启动所创建的所有线程的集合。你可以把它理解为整个并行计算任务的"总指挥部"或"任务总表"。

block: 软件层面的任务分组,它是线程协作的基本单位。同一个 Block 里的线程可以互相通信(通过共享内存 SMEM),也可以通过 __syncthreads() 进行同步。

**warp:**GPU 实际调度线程的基本小组,假如一个block包含256个thread, 那么这个block里就包含 256/32=8 个warp

lane: lane 就是 warp 内部线程的编号。一个warp包含32个lane

bank: bank 是 shared memory 的物理存储分区和并行访问单元, 硬件根据shared memory地址把访问请求分发到对应的 bank,bank id的计算方式是(字节地址 ÷ 4) % 32,举个例子 我们有共享内存int sdata1617, 线程访问 sdatatytx,那么它的线性字节地址是:

字节地址 = (ty * 17 + tx) * 4,代入 Bank 公式:

Bank ID = ((ty * 17 + tx) * 4 ÷ 4) % 32 = (ty * 17 + tx) % 32;在这里,线程的二维地址直接对应sdata共享内存的二维地址 ,线程是(tx,ty),这个线程访问的也是sdatatxty。

lane: 通道 就是 Warp 里的"线程编号",范围是 0 到 31。假如在一个16x16的block中,我们知道一个线程的二维坐标 tx, ty,那么lane的编号 = (ty*16+tx) % 32

层级关系 :

Block 管线程组织;Warp 管 32 个线程的执行组;Lane 标识 Warp 中的线程;Shared Memory 是 Block 内线程共享的数据空间;Bank 是 Shared Memory 在硬件上的并行存储分区。

层级关系图示:

bash 复制代码
GPU
 │
 ├── Block 0
 │    │
 │    ├── Thread
 │    ├── Thread
 │    ├── ...
 │    │
 │    ├── Warp 0
 │    │    ├── lane 0
 │    │    ├── lane 1
 │    │    ├── ...
 │    │    └── lane 31
 │    │
 │    ├── Warp 1
 │    └── ...
 │
 │    └── Shared Memory
 │         ├── Bank 0
 │         ├── Bank 1
 │         ├── ...
 │         └── Bank 31
 │
 ├── Block 1
 ├── Block 2
 └── ...

bank conflict: 是一个 warp 中多个 lane 在同一条 shared-memory 指令中访问同一个 bank 的不同地址。由于这些请求不能像访问不同 bank 那样并行服务,硬件需要将访问拆分处理,从而产生额外的访问周期,使相关线程产生等待,降低 shared-memory 的有效吞吐量;

Wave(调度波次) : 是 GPU 硬件调度器在分配和执行线程块时形成的一个"批次"或"轮次",它代表了一组被同时调度到流多处理器上执行的线程块集合。

Wave 的核心机制

调度的基本单位 :GPU 不会逐个调度线程块,而是以"波"为单位批量调度。一个 Wave 包含多个 Block,这些 Block 会被同时分配到不同的 SM 上执行。

Wave 的大小受限于硬件资源 :一个 Wave 能容纳多少 Block,取决于 SM 的数量、每个 SM 能驻留的 Block 数、寄存器数量、共享内存大小等。例如,如果 GPU 有 80 个 SM,每个 SM 最多能驻留 16 个 Block,那么理论上一个 Wave 最多可以调度 80 × 16 = 1280 个 Block。

Wave 是性能分析的关键指标 :如果一个 Kernel 需要多个 Wave 才能完成,意味着部分 SM 在后几个 Wave 中会空闲等待,导致资源利用率下降。减少 Wave 数量是提升 GPU 利用率的重要手段。

为什么 Wave 会影响性能?

减少调度开销 :每个 Wave 的启动和同步都有开销。Wave 越少,总开销越低。

提高 SM 利用率 :如果所有 Block 能在一个 Wave 内完成,所有 SM 都能满负荷运行;如果需要多个 Wave,后几个 Wave 中部分 SM 会空闲。

配合循环展开和计算强度提升:如你提到的"增加每个线程处理的元素个数",这不仅能提升计算强度,还能减少总 Block 数量,从而可能将整个 Kernel 的执行压缩到更少的 Wave 中完成。

利用padding解决bank conflic

代码示例:

cpp 复制代码
template <int BLOCK_SZ>
__global__ void mat_transpose_kernel_v2(const float* idata, float* odata, int M, int N) {
    const int bx = blockIdx.x, by = blockIdx.y;
    const int tx = threadIdx.x, ty = threadIdx.y;

    __shared__ float sdata[BLOCK_SZ][BLOCK_SZ+1];    // padding

    int x = bx * BLOCK_SZ + tx;
    int y = by * BLOCK_SZ + ty;

    if (y < M && x < N) {
        sdata[ty][tx] = idata[y * N + x];
    }
    __syncthreads();

    x = by * BLOCK_SZ + tx;
    y = bx * BLOCK_SZ + ty;
    if (y < N && x < M) {
        odata[y * M + x] = sdata[tx][ty];
    }
}

void mat_transpose_v2(const float* idata, float* odata, int M, int N) {
    constexpr int BLOCK_SZ = 16;
    dim3 block(BLOCK_SZ, BLOCK_SZ);
    dim3 grid(Ceil(N, BLOCK_SZ), Ceil(M, BLOCK_SZ));
    mat_transpose_kernel_v2<BLOCK_SZ><<<grid, block>>>(idata, odata, M, N);
}

在示例代码中 BLOCK_SZ=16 表示 一个block里开启了16x16=256个线程;

blockIdx.x, blockIdx.y 代表当前线程所在的block在grid中的坐标, x是列坐标,y是行坐标;

threadIdx.x threadIdx.y 代表当前线程在block中的坐标,x是列坐标,y是行坐标;

这段代码中可优化的空间:

考虑线程(0,0)和线程(1,15) 这两个线程分别对应的bank index 为

017+0 %32 = 0
1
17+15 %32 = 0

存在 bank conflic导致效率下降;解决方案 将block_size 改为32

修改之后的代码

cpp 复制代码
template <int BLOCK_SZ>
__global__ void mat_transpose_kernel_v2(const float* idata, float* odata, int M, int N) {
    const int bx = blockIdx.x, by = blockIdx.y;
    const int tx = threadIdx.x, ty = threadIdx.y;

    __shared__ float sdata[BLOCK_SZ][BLOCK_SZ+1];    // padding

    int x = bx * BLOCK_SZ + tx;
    int y = by * BLOCK_SZ + ty;

    if (y < M && x < N) {
        sdata[ty][tx] = idata[y * N + x];
    }
    __syncthreads();

    x = by * BLOCK_SZ + tx;
    y = bx * BLOCK_SZ + ty;
    if (y < N && x < M) {
        odata[y * M + x] = sdata[tx][ty];
    }
}

void mat_transpose_v2(const float* idata, float* odata, int M, int N) {
    constexpr int BLOCK_SZ = 32; // 仅修改这一行即可
    dim3 block(BLOCK_SZ, BLOCK_SZ);
    dim3 grid(Ceil(N, BLOCK_SZ), Ceil(M, BLOCK_SZ));
    mat_transpose_kernel_v2<BLOCK_SZ><<<grid, block>>>(idata, odata, M, N);
}
相关推荐
basketball6165 天前
Python FastAPI 介绍以及常用方法
python·fastapi·vllm·ai infra
天涯明月19937 天前
SGLang 设计与实现——RadixAttention 与前后端协同,让 KV 缓存不再用完即弃
人工智能·大模型·推理框架·ai infra
百度智能云技术站7 天前
《百度天池超节点系统架构设计规范》正式开放下载
模型训练·模型推理·ai infra·超节点·agent infra
爱笑的k1112 天前
分布式通信原语
分布式·ai infra
basketball61617 天前
AI Infra 推理部署技术总结:2. vLLM 内核——PagedAttention 与调度器原理
android·人工智能·vllm·ai infra
爱笑的k1119 天前
深度学习常见层输入输出维度对照参考
大模型·ai infra
DevOps老兵22 天前
AI Infra实战05:用Helm在K8s中部署vLLM,从安装到压测全流程
人工智能·kubernetes·helm·vllm·大模型推理·ai infra
minhuan23 天前
AI Infra全栈拆解:大模型应用背后的基础设施,算力集群、网络、存储与服务治理体系26.0
人工智能·架构·ai infra·ai基础设施·ai任务调度
DevOps老兵24 天前
AI Infra实战02:GPU监控实战,用DCGM+Prometheus+Grafana看清每一张卡
人工智能·grafana·prometheus·ai infra·gpu监控·dcgm
寒霜雨刃1 个月前
【原创】海光Z100 DCU适配vLLM实录(二):W4A16大M Prefill结构重写、GDN融合与长上下文HIP Attention
深度学习·神经网络·amd·rocm·ai infra·海光z100·gfx906