算子开发知识整理

引言

在CUDA并行编程中,Reduce(归约)和SGEMM(单精度矩阵乘法)是两种基础且关键的运算模式,其性能优化直接影响GPU计算效率。本文系统梳理了Reduce操作从基础到高级的优化技术路线(v1-v8),并简要介绍SGEMM优化思路,旨在帮助开发者深入理解GPU内存架构、线程调度机制,掌握性能瓶颈分析与优化方法,从而在实际项目中实现高效的GPU计算加速。

GPU内存架构图

避免线程束分化

Reduce版本优化

v1

关键优化点:使用共享内存(shared memory)减少全局内存访问次数,提升访存效率。

cpp 复制代码
__global__ void reduce_v1(float *d_in, float *d_out) {
    extern __shared__ float sdata[];
    
    unsigned int tid = threadIdx.x;
    unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
    
    // 从全局内存加载数据到共享内存
    sdata[tid] = d_in[i];
    __syncthreads();
    
    // 在共享内存中进行归约
    for (unsigned int s = 1; s < blockDim.x; s *= 2) {
        if (tid % (2 * s) == 0) {
            sdata[tid] += sdata[tid + s];
        }
        __syncthreads();
    }
    
    // 将结果写回全局内存
    if (tid == 0) {
        d_out[blockIdx.x] = sdata[0];
    }
}
v2

关键优化点:消除线程束分化(warp divergence),通过改变循环步长避免条件分支,提高warp执行效率。

cpp 复制代码
__global__ void reduce_v2(float *d_in, float *d_out) {
    extern __shared__ float sdata[];
    
    unsigned int tid = threadIdx.x;
    unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
    
    sdata[tid] = d_in[i];
    __syncthreads();
    
    // 优化:消除warp divergence
    for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
        if (tid < s) {
            sdata[tid] += sdata[tid + s];
        }
        __syncthreads();
    }
    
    if (tid == 0) {
        d_out[blockIdx.x] = sdata[0];
    }
}
v3

关键优化点:消除bank conflict,通过调整共享内存访问模式避免多个线程同时访问同一bank。

cpp 复制代码
__global__ void reduce_v3(float *d_in, float *d_out) {
    extern __shared__ float sdata[];
    
    unsigned int tid = threadIdx.x;
    unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
    
    sdata[tid] = d_in[i];
    __syncthreads();
    
    // 优化:消除bank conflict
    for (unsigned int s = blockDim.x / 2; s > 32; s >>= 1) {
        if (tid < s) {
            sdata[tid] += sdata[tid + s];
        }
        __syncthreads();
    }
    
    // 处理最后32个元素(一个warp)
    if (tid < 32) {
        // 使用warp shuffle或直接计算
        sdata[tid] += sdata[tid + 32];
        sdata[tid] += sdata[tid + 16];
        sdata[tid] += sdata[tid + 8];
        sdata[tid] += sdata[tid + 4];
        sdata[tid] += sdata[tid + 2];
        sdata[tid] += sdata[tid + 1];
    }
    
    if (tid == 0) {
        d_out[blockIdx.x] = sdata[0];
    }
}
v4.1,主要用来解决idle线程

关键优化点:减少block数量,保持每个block中线程数不变,让每个线程处理更多数据,提高线程利用率。

cpp 复制代码
__global__ void reduce_v4_1(float *d_in, float *d_out, int n) {
    extern __shared__ float sdata[];
    
    unsigned int tid = threadIdx.x;
    unsigned int i = blockIdx.x * (blockDim.x * 2) + threadIdx.x;
    unsigned int gridSize = blockDim.x * 2 * gridDim.x;
    
    // 每个线程处理多个元素
    float sum = 0;
    while (i < n) {
        sum += d_in[i];
        if (i + blockDim.x < n) {
            sum += d_in[i + blockDim.x];
        }
        i += gridSize;
    }
    
    sdata[tid] = sum;
    __syncthreads();
    
    // 归约(使用v2/v3的优化)
    for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
        if (tid < s) {
            sdata[tid] += sdata[tid + s];
        }
        __syncthreads();
    }
    
    if (tid == 0) {
        d_out[blockIdx.x] = sdata[0];
    }
}
v4.2,主要用来解决idle线程

关键优化点:保持block数量不变,减少block中idle线程数量,调整线程分配策略。

cpp 复制代码
__global__ void reduce_v4_2(float *d_in, float *d_out, int n) {
    extern __shared__ float sdata[];
    
    unsigned int tid = threadIdx.x;
    unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
    unsigned int stride = blockDim.x * gridDim.x;
    
    // 每个线程处理多个元素,减少idle线程
    float sum = 0;
    while (i < n) {
        sum += d_in[i];
        i += stride;
    }
    
    sdata[tid] = sum;
    __syncthreads();
    
    // 归约
    for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
        if (tid < s) {
            sdata[tid] += sdata[tid + s];
        }
        __syncthreads();
    }
    
    if (tid == 0) {
        d_out[blockIdx.x] = sdata[0];
    }
}
v5,unroll last warp
  1. __syncthreads()是用来同步不同warp的,但是到最后小于一个warp数量的时候可能会出现warp divergence,那怎么办呢?直接一个warp内的线程做一样的操作就好了
  2. volatile关键字
v6. completely unroll

就是不用循环,直接展开

v7. 设置block的合理数量
  1. 一个thread累加多次,让block不要空闲,可以更好地隐藏延迟
v8. shuffle
  1. 通过__shfl_down_sync()进行reduce,避免走shared memory,加速

SGEMM优化

Reference

  1. CUDA优化基础
  2. B站CUDA优化视频
  3. CUDA_QuanShuang GitHub
  4. CUDA性能优化
  5. NVIDIA GPU性能优化基础
  6. CUDA Tutorial
  7. 算子手撕
  8. 面试经验1
  9. B站视频
  10. 深入浅出GPU优化
相关推荐
叶子Talk3 天前
OpenAI破解80年数学猜想,AI首次做出原创证明
人工智能·数学·算法·机器学习·ai·openai·ai推理
苏渡苇25 天前
DeepSeek V4 实战:自然语言生成 SQL + 智能优化引擎
ai·springboot·spring ai·deepseek·ai推理·deepseek v4·自然语言生成sql
李大锤同学1 个月前
Qwen3.5-4B-Claude-Opus部署教程:GPU显存监控与llama.cpp参数调优
大语言模型·ai推理·gpu优化
tiger1191 个月前
FPGA独立实现LLM推理方案——FlighLLM
fpga开发·llm·fpga·ai推理
DO_Community2 个月前
DigitalOcean 收购 Katanemo Labs:迎接 Agent 时代,重塑基础设施
人工智能·ai推理
DO_Community2 个月前
教程:让OpenClaw一次接入Claude、Qwen、DeepSeek 多个模型
人工智能·aigc·ai编程·ai推理
DO_Community2 个月前
如何使用DigitalOcean Gradient 平台上的无服务器推理
人工智能·aigc·ai编程·ai推理
DO_Community2 个月前
使用 DigitalOcean 实现 Claude Code “低配订阅 + 外部 Token”
人工智能·aigc·ai编程·ai推理
DO_Community2 个月前
高性能、低成本推理新标准:NVIDIA Dynamo 1.0 现已上线 DigitalOcean 推理云平台
人工智能·aigc·ai推理