朝阳市网站建设_网站建设公司_Ruby_seo优化
2026/1/10 1:56:55 网站建设 项目流程

CUDA高性能计算系列10:实战手写深度学习算子(Softmax)

摘要:纸上得来终觉浅,绝知此事要躬行。学了这么多优化技巧,是时候检验真功夫了。本篇我们将深入深度学习中最常见的算子之一——Softmax。看似简单的公式背后,隐藏着数值溢出的陷阱和并行归约的挑战。我们将手写一个能够与 PyTorch 原生性能抗衡的 Softmax Kernel。


1. Softmax 的数学原理与挑战

Softmax 函数将一个向量x xx映射为概率分布y yy
y i = e x i ∑ j e x j y_i = \frac{e^{x_i}}{\sum_{j} e^{x_j}}yi=jexjexi

1.1 数值稳定性问题 (Numerical Stability)

直接计算e x i e^{x_i}exi非常危险。
如果x i = 100 x_i = 100xi=100,则e 100 ≈ 2.6 × 10 43 e^{100} \approx 2.6 \times 10^{43}e1002.6×1043,这在 FP32 范围内没问题。
但如果x i = 1000 x_i = 1000xi=1000,则e 1000 → ∞ e^{1000} \to \inftye1000(Inf),导致 NaN 错误。

解决方案:减去最大值。
y i = e x i − max ⁡ ( x ) ∑ j e x j − max ⁡ ( x ) y_i = \frac{e^{x_i - \max(x)}}{\sum_{j} e^{x_j - \max(x)}}yi=jexjmax(x)eximax(x)
这样所有指数的指数项都在( − ∞ , 0 ] (-\infty, 0](,0]之间,结果在( 0 , 1 ] (0, 1](0,1]之间,永远不会上溢。

1.2 计算流程

这就将一个 Softmax 变成了三个阶段的计算:

  1. Reduce Max: 找到当前行的最大值m mm
  2. Reduce Sum: 计算S = ∑ e x i − m S = \sum e^{x_i - m}S=exim
  3. Element-wise Update: 计算y i = e x i − m / S y_i = e^{x_i - m} / Syi=exim/S

这就意味着我们需要遍历数据三次!如何高效地由 GPU 完成?


2. 架构设计:Grid, Block, Warp

假设输入张量形状为[Batch_Size, Dim]
通常Batch_Size很大,Dim变化范围广(从 100 到 10000+)。

2.1 策略:一行一个 Block

  • Grid Size:Batch_Size。每个 Block 处理一行数据。
  • Block Size: 256 或 1024。

如果Dim很小(< 1024),一个 Block 刚好能装下,直接用 Shared Memory 归约。
如果Dim很大,Block 需要循环处理(Grid-Stride Loop 变体)。


3. Kernel 实现:One-Pass 还是 Three-Pass?

为了教学清晰,我们先实现一个标准的Three-Pass逻辑,但在同一个 Kernel 内完成(避免多次启动 Kernel 的开销)。

#include<cuda_runtime.h>#include<math.h>// 辅助函数:Warp 内求最大值__device__floatwarpReduceMax(floatval){for(intoffset=16;offset>0;offset/=2)val=fmaxf(val,__shfl_down_sync(0xffffffff,val,offset));returnval;}// 辅助函数:Warp 内求和__device__floatwarpReduceSum(floatval){for(intoffset=16;offset>0;offset/=2)val+=__shfl_down_sync(0xffffffff,val,offset);returnval;}__global__voidsoftmax_kernel(float*input,float*output,intdim){// 1. 设置索引// blockIdx.x 对应 batch 维度(行号)introw_idx=blockIdx.x;// 指向当前行的起始地址float*row_input=input+row_idx*dim;float*row_output=output+row_idx*dim;// 2. 阶段一:求最大值 (Reduce Max)floatmax_val=-INFINITY;// 循环处理,防止 dim > blockDim.xfor(inti=threadIdx.x;i<dim;i+=blockDim.x){max_val=fmaxf(max_val,row_input[i]);}// Block 内规约最大值// 这里使用 Shared Memory 进行 Block 级规约(简化版,假设 Block=256,1个Warp处理不了)// 为了简单,我们只展示 Warp 级规约逻辑,实际需配合 Shared Memorymax_val=warpReduceMax(max_val);// 通过 Shared Memory 广播最大值给所有线程__shared__floats_max;if(threadIdx.x==0)s_max=max_val;__syncthreads();max_val=s_max;// 3. 阶段二:求指数和 (Reduce Sum)floatsum=0.0f;for(inti=threadIdx.x;i<dim;i+=blockDim.x){sum+=expf(row_input[i]-max_val);}sum=warpReduceSum(sum);__shared__floats_sum;if(threadIdx.x==0)s_sum=sum;__syncthreads();sum=s_sum;// 4. 阶段三:计算最终结果for(inti=threadIdx.x;i<dim;i+=blockDim.x){row_output[i]=expf(row_input[i]-max_val)/sum;}}

3.1 深度优化:Online Softmax

传统的 Softmax 需要遍历数据 3 次(Max -> Sum -> Update)。
有一种算法叫Online Softmax,利用数学技巧只需要遍历 2 次甚至更少。

公式推导:
维护当前的局部最大值m mm和局部和d dd
当遇到一个新的元素x xx时:

  • x > m x > mx>mm n e w = x m_{new} = xmnew=x,d n e w = d × e m − x + 1 d_{new} = d \times e^{m - x} + 1dnew=d×emx+1
  • x ≤ m x \le mxmm n e w = m m_{new} = mmnew=m,d n e w = d + e x − m d_{new} = d + e^{x - m}dnew=d+exm

这种方法可以在一次遍历中同时更新最大值和和,极大减少 Global Memory 访问。


4. 性能瓶颈分析

  1. Memory Bound: Softmax 是典型的Element-wise操作,计算量很小(也就 exp 和 div),主要时间都花在读写内存上。
  2. 优化方向
    • 确保 Global Memory 的合并访问(我们已经做到了,行内元素是连续的)。
    • 尽量把数据留在寄存器或 Shared Memory 中,避免重复读取 input。

5. 向量化读取 (Vectorized Load)

在处理 FP32 时,我们可以使用float4类型,一次读取 128 bit(4 个 float)。这能显著提高带宽利用率,减少指令数。

// 重新解释指针float4*vec_input=reinterpret_cast<float4*>(row_input);// 每次处理 4 个元素float4 data=vec_input[threadIdx.x];// ... 分别处理 data.x, data.y, data.z, data.w ...

限制:要求Dim必须是 4 的倍数,且地址必须对齐。实际工程中需要处理边界条件。


6. 总结与下篇预告

编写一个高性能的 Softmax 算子,不仅需要 CUDA 编程技巧(Shared Memory, Warp Shuffle),还需要深厚的数值分析功底(防止溢出)和算法优化思路(Online Softmax)。

至此,我们的 Kernel 代码已经能够跑在 GPU 上了。但是,怎么让 Python 里的 PyTorch 调用它呢?难道每次都要把数据存成文件,用 C++ 跑完再读回来吗?

当然不是!
下一篇CUDA系列11_PyTorch自定义C++扩展(Binding),我们将打通任督二脉,教你使用torch.utils.cpp_extension将我们写的 CUDA Kernel 编译成 Python 模块。届时,你只需要import my_cuda_ops,就能在 Python 里直接享用你亲手打造的高性能算子!


参考文献

  1. Milakov, M., & Gimelshein, N.Online Normalizer Calculation for Softmax. arXiv:1805.02867.
  2. OneFlow Team.How to Implement an Efficient Softmax Kernel.

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询