三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

FlashDecoding面经-vivj啥关系 vi就是原来的O呗 用LSE是为了防止溢出吗

FlashDecoding面经-vivj啥关系 vi就是原来的O呗 用LSE是为了防止溢出吗

你的这两个直觉全部通透了,完全点到了核心!


一、ViV_iViVjV_jVj啥关系?

这里的iiijjj代表了完全不同的两个层级:

  • jjj代表 Token 层级VjV_jVj(或vjv_jvj)是当前分块内部jjj个 Token 的原始 Value 向量(例如一个 128 维的词向量)。
  • iii代表 GPU 核心 / KV 分块层级ViV_iViiii个 GPU 核心负责的整个分块的分子加权总和(向量)

关系ViV_iVi是由这个分块内所有 Token 的VjV_jVj累加出来的:

Vi=∑j∈分块 iesj⋅VjV_i = \sum_{j \in \text{分块 } i} e^{s_j} \cdot V_jVi=j分块iesjVj


二、ViV_iVi就是原来的OOO呗?

是的,完全正确!

  • ViV_iVi就是之前 FlashAttention 伪代码里的未归一化分子累加器acc(或者公式里的OOO)。
  • 它就是“未除以分母的加权半成品”。
  • 当它在核心内部除以局部分母lil_ili后,就变成了写回 HBM 显存的局部归一化成品O~i=ViDi=accili\tilde{O}_i = \frac{V_i}{D_i} = \frac{\text{acc}_i}{l_i}O~i=DiVi=liacci

三、 用 LSE 是为了防止溢出吗?

是的!防溢出(Overflow)是它最关键的数学动机,同时也顺便解决了显存传输开销。

如果你不使用 LSE,而是直接让各个 GPU 核心把真实的局部总分母Di=∑esjD_i = \sum e^{s_j}Di=esj传出来做归并,会有两个致命问题:

1. 数值溢出(Overflow)

在计算 Attention 时,得分sjs_jsj可能很大(比如sj=120s_j = 120sj=120)。

  • e120e^{120}e120的数值巨大(约为1.3×10521.3 \times 10^{52}1.3×1052),远超 FP16/BF16 能表示的最大数值范围(FP16 最大只能存到 65504),会直接变成Inf溢出导致模型崩溃
  • LSE 的巧妙之处:通过LSEi=mi+log⁡(li)\text{LSE}_i = m_i + \log(l_i)LSEi=mi+log(li),把这个天文数字压缩回到了对数域(Log Domain)。比如e120e^{120}e120取完对数后只是120120120,用一个平平无奇的浮点数就能安全存下。
2. 归并时的绝对数值安全

在最后的归并计算中,计算权重用的是:

wi=exp⁡(LSEi−LSEglobal)w_i = \exp(\text{LSE}_i - \text{LSE}_{\text{global}})wi=exp(LSEiLSEglobal)

因为LSEglobal=max⁡i(LSEi)\text{LSE}_{\text{global}} = \max_i(\text{LSE}_i)LSEglobal=maxi(LSEi),所以指数项LSEi−LSEglobal≤0\text{LSE}_i - \text{LSE}_{\text{global}} \le 0LSEiLSEglobal0永远是小于等于 0 的负数

  • exp⁡(负数)\exp(\text{负数})exp(负数)的结果在(0,1](0, 1](0,1]之间,彻底从数学原理上杜绝了数值向上溢出的任何可能
3. 显存通信开销减半

除了数值稳定性,显存带宽也是核心优化点:

  • 如果传(mi,li)(m_i, l_i)(mi,li)两个变量,归并核心需要从显存里读写 2 个标量。
  • 打包成LSEi\text{LSE}_iLSEi后,只需向显存写回 1 个标量,直接把跨核通信的带宽开销砍掉了一半。
← 返回列表