1152 字
3 分钟
Softmax Attention CUDA 算子
2026-11-04

Softmax 算子#

Softmax 稳定形式公式#

对于一行输入:

x=[x0,x1,...,xL−1]x=[x_0, x_1, ..., x_{L-1}]

Softmax 的定义是:

yi=exi∑j=0L−1exjy_i=\frac{e^{x_i}}{\sum_{j=0}^{L-1}e^{x_j}}

可以拆分成两步:

  • 对每一个元素取指得到正数
  • 除以这些正数的总和,使得输出之和为 1

对于计算机而言,我们通常会先求输入的 max,因为计算机能表示的数值范围有限,如果输入元素有 1000,expf(100) 会溢出,所以解决办法是:所有元素先减去同一个最大值,再取指

即 Softmax 的稳定形式为:

yi=exi−m∑j=0L−1exj−m\boxed{y_i=\frac{e^{x_i - m}}{\sum_{j=0}^{L-1}e^{x_j-m}}}

分层归约思路#

在 CUDA 中,我们通常采用分层归约进行 Softmax

先考虑一个常见方案:一个 block 处理一整行输入。

假设一行有 1024 个元素,一个 block 256 个线程,每一个线程处理 4 个元素,即

线程负责的元素下标
thread 00、256、512、768
thread 11、257、513、769
thread 255255、511、767、1023
  1. 第一步,每一个线程先求自己的局部最大值:
mt=max⁡i∈Itxim_t=\max_{i\in I_t}x_i

其中,ItI_t 表示线程 t 负责的下标集合,然后把所有吸纳成的局部最大值进行最大值归约,得到本行最大值 mm:

m=max⁡tmim=\max_tm_i
  1. 每一个线程都用同一个 m,计算自己的局部指数和,再归约所有的局部和:
st=∑i∈Itexi−ms=∑tsts_t=\sum_{i\in I_t}e^{x_i-m} \\ s=\sum_t s_t
  1. 最后一步,每一个线程计算自己负责的输出

如果一个把一行分成若干块,每一个块求出自己的局部最大值与局部和,最终需要归约为整体最大值与局部和

需要注意的是:不同块的局部和不能直接相加,需要先把他们换成同一个基准:

exi−m=exi−mb⋅emb−m e^{x_i-m} = e^{x_i-m_b}\cdot e^{m_b-m}

因此,正确的整体指数和:

s=∑bsb emb−m\boxed{ s=\sum_b s_b\,e^{m_b-m} }

Softmax Attention 算子#

缩放点积注意力公式#

O=softmax(QKTd)V\boxed{O = \text{softmax}(\frac{QK^T}{\sqrt{d}})V}

这是 Transformer 中使用的一种注意力计算,下面分别讲解一下每一个变量的含义:

矩阵形状直观含义
QQ:QueryM×dM\times d有 MM 个查询,每个查询用 dd 维向量表示
KK:KeyN×dN\times d有 NN 个用于匹配的键
VV:ValueN×dN\times d每个键对应的实际内容
OO:OutputM×dM\times d每个查询得到一个 dd 维结果

其中:KK 的第 jj 行与 VV 的第 jj 行是一对,用 Key 判断匹配程度,用 Value 提供最终参与加权的内容。注意力的输出就是这些 Value 的加权和

1. 计算匹配分数:S=QKT/dS=QK^T/\sqrt{d}#

取第 ii 个 Query(Q 的第 ii 行),与第 jj 个 Key(KTK^T 的第 jj 列) 做点积:

Sij=1d∑r=0d−1QirKjr S_{ij} = \frac{1}{\sqrt d} \sum_{r=0}^{d-1}Q_{ir}K_{jr}

SijS_{ij} 越大,表示按照这里的点积评分方式,Query ii 与 key jj 越匹配。一行 Si,:S_{i,:} 就是一个 Query 对全部 NN 个 Key 的评分

除以 d\sqrt d 是为了控制分数的尺度:在常见的统计假设下,点积的方差随 dd 增大,缩放可以避免 Softmax 过于尖锐。

NOTE

为什么随着 dd 增大,方差变大? 对于点积公式,假设各分量相互独立,均值为 0,方差为 1,相互独立的随机变量相加,方差也相加,因此 Var(s)=ΣVar(qrkr)=d\text{Var}(s) = \Sigma\text{Var}(q_rk_r)=d 所以方差随着 dd 增长,标准差随着 d\sqrt{d} 增长,标准差更直接地反映结果的典型波动幅度。 因此除以 d\sqrt{d} 能让不同维度下的评分保持相近的波动尺度,避免维度增大时评分差距变得过大,使 Softmax 权重过度集中。

2. 对每行做 Softmax:将评分变成权重#

固定 Query ii,对他的 NN 个评分进行稳定 Softmax:

Pij=exp⁡(Sij−mi)Σt=0N−1exp⁡(Sit−mi)P_{ij} = \frac{\exp{(S_{ij} - m_i)}}{\Sigma_{t=0}^{N-1}\exp{(S_{it}-m_i)}}

得到的权重 PP 仍然为 M×NM \times N,并且每一行:

Pij>0,Σj=0N−1Pij=1P_{ij} > 0, \quad \Sigma_{j=0}^{N-1} P_{ij} = 1

这里要分清两个维度:

  • 点积沿 dd 维求和:计算一对 Query、Key 的分数。
  • Softmax 沿 NN 维归一化:比较同一个 Query 对所有 Key 的分数。

3. 对 Value 加权求和:O=PVO=PV#

对于 Query ii,用刚才得到的权重,对所有 Value 向量进行加权:

Oi,:=∑j=0N−1PijVj,:O_{i,:} = \sum_{j=0}^{N-1}P_{ij}V_{j,:}

也就是说,一个权重 PijP_{ij} 乘的是整个 Value 向量 Vj,:V_{j,:},其中所有的分量使用同一个权重。

CUDA 代码实现#

背景#

  • Q 矩阵大小:M×dM \times d,K 与 V 矩阵大小:N×dN \times d
  • 1≤1 \leq M,N ≤100,000\leq 100,000
  • 1≤1 \leq d ≤128\leq 128
  • 这里先采用 M =512= 512,N =256= 256

第一个版本先约定:

constexpr int BLOCK_SIZE = 256;
constexpr int NUM_WARPS = BLOCK_SIZE / 32;

分层归约#

在 Softmax 中,我们需要对每一个 block 求出当前的局部和与局部 max,需要用到分层归约的技巧

首先通过 __shfl_down_sync 实现 warp 内线程的互相通信

template <bool IS_MAX>
__device__ __forceinline__ float warp_reduce(float value)
{
for (int offset = 16; offset > 0; offset >>= 1)
{
const float other = __shfl_down_sync(0xffffffffu, value, offset);
if (IS_MAX)
value = fmaxf(value, other);
else
value += other;
}
return value;
}

block_reduce 调用 warp_reduce,实现一个 Block 归约的输出

template <bool IS_MAX>
__device__ __forceinline__ float block_reduce(float value)
{
__shared__ float shared[NUM_WARPS];
const int warp_id = threadIdx.x / 32;
const int lane = threadIdx.x % 32;
value = warp_reduce<IS_MAX>(value);
if (lane == 0)
shared[warp_id] = value;
__syncthreads();
if (warp_id == 0)
{
const float identity = (IS_MAX) ? -CUDART_INF_F : 0.0f;
value = (lane < NUM_WARPS) ? shared[lane] : identity;
value = warp_reduce<IS_MAX>(value);
if (lane == 0)
shared[0] = value;
}
__syncthreads();
value = shared[0];
__syncthreads();
return value;
}

核心 Kernel#

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Softmax Attention CUDA 算子
https://www.naie-char.cc/posts/cuda_softmax-attention/
作者
萘Naie_Char
发布于
2026-11-04
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录