• CUDA优化之PReLU性能调优


    022ac3b25636b4e3a4aa689c989f356f.png

    撰文|郑泽康

    InsightFace模型里大量使用了PReLU激活函数,而PReLU的工作模式有两种:

    1. PReLU(1),此时权重alpha的形状为(1, ),等价于一个Elementwise操作。

    2. PReLU(channels),此时权重alpha的形状为(channels, ),和输入特征(N, C, H, W)中C的大小是对应的。此时PReLU等价于一个Binary Broadcast操作。

    InsightFace模型里的PReLU工作模式是第二种,之前已经介绍过CUDA Elementwise操作优化,而在Broadcast情形下也存在一定的优化机会。

    1

    朴素实现

    一个朴素实现的思想就是在循环内部,根据当前元素的索引,推算出该元素对应需要使用的alpha权重的索引。然后判断当前元素x是否大于0,若大于0则返回x,小于0则返回alpha*x。对应代码如下:

    1. template<typename T>
    2. __global__ void PReluForwardGpu(const int32_t elem_cnt, const int32_t alpha_size,
    3.                                 const int32_t inner_size, const T* x, const T* alpha, T* y) {
    4.   CUDA_1D_KERNEL_LOOP(i, elem_cnt) {
    5.     const T x_i = x[i];
    6.     const T alpha_i = alpha[(i / inner_size) % alpha_size];
    7.     y[i] = x_i > 0 ? x_i : x_i * alpha_i;
    8.   }
    9. }

    其中:

    • inner_size表示的是通道维后面维度乘积,以NCHW格式为例,inner_size=H*W

    •  alpha_size表示通道维大小

    在CUDA中,整数除法的计算代价是比较昂贵的(https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#maximize-instruction-throughput)关于计算指令耗时这一章中有提到:

    Integer division and modulo operation are costly as they compile to up to 20 instructions.

    整数除法,取余操作会被编译成多达20条指令。而我们这里计算alpha的索引的时候,分别用到一次除法,一次取余,占整个Kernel的主要计算量,下面我们将用向量化的思路来提高读写带宽的同时,减少整数除法,取余的计算次数。

    2

    Pack向量化优化

    我们考虑一个比较简单的例子,输入为(1, 2, 4, 4),对应PReLU(2)

    6f10dfc52185c803d92cff0afeafe815.png

    显然,输入在hw维上是连续的,在 inner_size 满足被pack整除的条件下,一个pack内的元素应用到的是同一个alpha权重**。参见下图:

    41e5095345a367635cac03dd0fb16e56.png

    这样我们就能以向量化形式去处理元素,以提升读写带宽。并且每一个pack内部只需要计算一次,向量化处理相比逐元素计算能节省不小计算量。对应代码如下:

    1. template<typename T, typename IndexType, int pack_size>
    2. __global__ void PReluForwardMultiAlphaGpu(const IndexType elem_cnt, const IndexType alpha_size,
    3.                                           const IndexType inner_size, const T* x, const T* alpha, T* y) {
    4. int32_t global_thread_id = blockIdx.x * blockDim.x + threadIdx.x;
    5.   using LoadType = cuda::elementwise::PackType<T, pack_size>;
    6.   using LoadPack = cuda::elementwise::Pack<T, pack_size>;
    7.   T zero_val = static_cast<T>(0);
    8.   for (int64_t linear_index = global_thread_id * pack_size; linear_index < elem_cnt;
    9. linear_index += gridDim.x * blockDim.x * pack_size) {
    10.     // 计算当前Pack所使用到Alpha的索引
    11.     
    12.     IndexType alpha_idx = (linear_index/inner_size%alpha_size);
    13.     const LoadType* x_load = reinterpret_cast<const LoadType*>(x + linear_index);
    14.     // 以向量化的形式加载输入x
    15.     LoadPack x_vec;
    16. x_vec.storage = *x_load;
    17.     LoadPack y_vec;
    18.     // 循环展开,逐个处理Pack内的元素
    19. #pragma unroll
    20.     for (int i = 0; i < pack_size; i++) {
    21.       y_vec.elem[i] = x_vec.elem[i] > zero_val ? x_vec.elem[i] : x_vec.elem[i] * alpha[alpha_idx];
    22.     }
    23.     // 以向量化的形式存储输出y
    24.     *(reinterpret_cast<LoadType*>(y + linear_index)) = y_vec.storage;
    25.   }
    26. }

    我们在Nsight Compute内简单比较下优化前后的结果,测试数据为(96, 64, 112, 112),机器为A100-40GB。蓝色一栏是使用向量化优化过的kernel,而绿色一栏是朴素实现的kernel。可以看到,经过优化后,我们计算占比降低20%-30%,吞吐提升了30+%。优化后的kernel带宽能达到1350GB/s,已经很接近A100上的理论带宽1555GB/s。

    c291cc547f4163495c940dbb8a88e71f.png

    当然也不是所有形状都支持向量化操作,当inner_size无法被对应的pack_size 整除时,只能退回到朴素实现上。

    3

    基准测试

    在A100-40GB测试机器上,我们对Insightface涉及到的Tensor形状,与PyTorch实现进行比较,测试数据如下:

    06e3561f667c2b3382083953fd10b86a.png

    经过优化PReLU的OneFlow,在大部分情况下均有比PyTorch接近2倍的领先优势,在最后一种情况由于形状较为特殊,无法应用向量化的优化,所以表现与PyTorch持平。

    其他人都在看

    欢迎下载体验OneFlow v0.7.0:

    GitHub - Oneflow-Inc/oneflow: OneFlow is a performance-centered and open-source deep learning framework.icon-default.png?t=M4ADhttps://github.com/Oneflow-Inc/oneflow/

  • 相关阅读:
    WSL和双系统Ubuntu的CPU性能差异
    Pytest教程:Pytest参数化测试
    FF300R12ME7B11BPSA1 2个独立式 1200V 300A IGBT模块
    Linux常用命令
    计算机毕业设计springboot健身会馆预约管理系统o14kl源码+系统+程序+lw文档+部署
    一个对安卓日志输出功能的优化
    【CSS in Depth 2精译】2.5 无单位的数值与行高
    如果再写for循环,我就锤自己了
    创建一个基本的win32窗口
    IDEA报错:前言中不允许有内容
  • 原文地址:https://blog.csdn.net/OneFlow_Official/article/details/124834019