Closed
flash_attn算子的softmax VF优化s2尾块的性能 #7031
jiang-liruiAdd on 12 days agoClosed at 12 days ago
jiang-lirui
12 days ago Comments:
12 days ago Comments:
/assign @jiang-lirui


12 days ago Assign jiang-lirui as responsible person
周云飞
12 days ago Comments:
12 days ago Comments:
感谢提供详细的布局设计、精度对照和性能数据。
需求分析
这项优化更适合作为不合轴 DN 模板(templateId=2)的性能取舍评估,而不是直接认定现有实现存在正确性缺陷。现有 ProcessVec1DnLineVF 的 P 布局与后续搬运方式相互配合;按您描述的列打包方式,尾块仍需执行较多无效列计算,因此优化需要同时调整 VF 写出和 P→L1 搬运,不能只缩短循环。
您提供的数据表明,收益主要集中在尾块占比较高的场景:32/256 尾块的加速比为 1.130,而满块对照约有 2% 的性能损失,全语料总耗时口径接近持平。因此,目前更适合将其定位为特定形状下的优化,尚不能据此判断适合全面替换原路径。
处理方向
按 32 列分组、每个 fractal 增加 32B padding,并同步调整搬运以保持 L1 的 NZ 布局,是一个可行的候选方向。其代价是 P 缓冲容量增加约 4.6%~5.4%,还需结合整体 UB 分配和满块开销评估;也可以比较保留满块原路径、仅对尾块采用新路径的收益与维护成本。
正确性上,补值范围必须覆盖四路 exp 实际读取的无效列,避免这些列进入 running sum,进而影响输出归一化和 LSE。满组与末组应分别确定边界,尤其需要明确 actS2 % 32 == 0 时不执行末组补值。mask_mode=0 若切换到本模板,也必须同步使用与新 P 布局匹配的搬运方式。
您提供的基线数值对照有助于区分新增问题与既有误差,但 TND varlen 仍是未覆盖范围,bf16 失败原因也暂不能仅凭结果相同认定为量化口径问题。我们将继续评估该方案在尾块收益、满块代价及布局兼容性之间的取舍。


12 days ago Close issue
12 days ago Added label: resolved
Softmax VF 尾块优化(P 布局重构)方案说明
1. 问题
旧实现里 softmax 的列循环恒按编译期
tileCols(= s2BaseSize) 跑满:for (col = validColCount; col < tileCols; ++col)逐列 256B storefor (colGroup < tileCols/4),4 个相邻列for (colGroup < tileCols/4),4 个相隔 tileCols/4 的列所以尾块(
actS2 < tileCols)无论实际几列都要按满宽算一遍。实测(D=128,tileCols=256,32768 个全尾块):旧实现的 kernel 时间几乎与 actS2 无关 —— actS2=32 时 1293us,actS2=256 时 1839us(仅 +42%)。
根因:旧 P 布局把"相隔
tileCols/4的两列"打包进同一条 128-half store,于是"处理多少列"只能是tileCols/4的整数倍,补值(逐列 store)反而是最便宜的手段。目标:把列粒度降到 2 列,让 exp/补值/max 的工作量随
actS2走。2. 约束(VF 硬限制)
RegTensor ≤ 32、MaskReg ≤ 8uint16_t、从 0、步长 1、循环体内禁if/?:StoreAlign<DATA_BLOCK_COPY>dataBlockStride(单位 DataBlock)[m_fractal][k_fractal]读 P,本方案不改 cube3. 方案总览
{c, c+8, c+16, c+24}(c 为组内列号 0..7),按(c, c+16)/(c+8, c+24)配对,两条 store 各写 8 块,
dataBlockStride=17、repeatStride=1(等价于每次迭代列号 +1,32B)。ceil(actS2/32),末组迭代数k = min(actS2 % 32, 8);补值范围随之从
tileCols收到 "末组最远被碰列"(≤27 列)。m_fractal一条 DataCopy),L1 布局与旧版完全一致。mask_mode=0也走本模板 VF(common 的ProcessVec1VfDn是旧 P 布局,与新搬运不匹配)。4. P 布局精确规格
地址公式(byte):
4.1 为什么 pad 必须"每 fractal 一个"
一条 store 要写
(c, c+16)这一对(每列 4 个 16 行 chunk),8 个块地址必须均匀:5. VF 改造细节
5.1 计算(不变)
Muls(scale)→ 掩码Compare/Select→FusedExpSub→Cast<T2>。4 路仍是 4 个独立列,寄存器预算与旧实现一致(~24 RegTensor)。
5.2 打包与写(新)
packedA = DeInterleave(cast(expValue0), cast(expValue2)); // 列 (c, c+16) → 128 half packedB = DeInterleave(cast(expValue1), cast(expValue3)); // 列 (c+8, c+24) StoreAlign<T2, DATA_BLOCK_COPY>(ptrA, packedA, /*dataBlockStride=*/17, /*repeatStride=*/1, allLanesB16); StoreAlign<T2, DATA_BLOCK_COPY>(ptrB, packedB, /*dataBlockStride=*/17, /*repeatStride=*/1, allLanesB16);ptrA = 组基址 + c*32(写c与c+16),ptrB = ptrA + 256B(写c+8与c+24)DeInterleave(A,B)=[A 的 64 lane | B 的 64 lane]拼接5.3 循环结构(★ 实测关键)
// 满组: 外层组(uint16) + 内层组内列(uint16), 地址只由"基址 + 迭代号*常量"构成 for (uint16_t groupIdx = 0; groupIdx < fullGroupCount; ++groupIdx) { __ubuf__ float* scoreGroup = scoreBuf + groupIdx * 32 * rowStride; __ubuf__ T2* pOutGroup = pOutBuf + groupIdx * 2176; // 4352B/2 for (uint16_t colInGroup = 0; colInGroup < 8U; ++colInGroup) { ... } } // 末组: 迭代数 k = min(actS2 % 32, 8)5.4 尾块裁剪与补值上界
exp 的 4 路取列会碰到末组里最远
(k-1)+24列,而有效列只到actS2。补值必须覆盖被 exp 到的列,不能只覆盖
max需要的align4(actS2):LastDivNew(..., sumUb, ...)用 running sum 归一化 attn_out;lse = max + log(sum)。lse对 max 是不变的(max 抬 Δ,sum 乘e^-Δ,lse 抵消),所以 LSE 出错只能来自 sum 里多了项。实测把补值收到
align4/align16(actS2)后(P 已全干净),LSE 立刻崩(0.0175 / 0.1254 / 1.9614),唯一通过的是
actS2 % 32 == 31(取列集合恰好等于有效列)—— 反证成立。结论:
5.5 寄存器预算
packLanesB16与allLanesB16对 b16 是同一份"全 128 lane"掩码,合并省出 1 个;最终
MaskReg仍为 8/8(allLanesF32 / allLanesB16 / validRowMask / visibleMask0..3+ 常量表),RegTensor~24/32。6. vec1 的 P → L1 搬运
L1 目的布局不变(NZ,
m_fractal步长16*actS2Align元素,k_fractal连续)。新布局下每个槽恰好是一个 16×16 fractal,于是每个 m_fractal 一条 DataCopy:
// blockCount = actS2Align/16, blockLen = 16 DataBlock(512B), srcGap = 52 DataBlock, dstGap = 0 for (uint16_t mFrac = 0; mFrac < 4U; ++mFrac) { DataCopy(mm2AL1Tensor[dstBase + mFrac*16*actS2Align], stage1CastTensor[mFrac * (544/sizeof(INPUT_T))], {fractalCount, 16, 52, 0}); }(源步长 4×544B = 2176B = 512B + 1664B ✓;目的连续。)
7. 正确性验证
pybind11 GIL崩溃(仓库自带用例、且不走本模板的对照用例同样崩),与本次改动无关,故该格未覆盖。
8. 性能
8.1 尾块隔离测量(S2 = actS2 < tileCols,32768 个尾块,5 runs 取 min)
所以端到端收益小于 VEC 削减幅度;
8.2 全语料(223 条,背靠背 5 runs 取 min)
8.3 测量噪声
同版本两次独立测量:多数 case ~0.1%,个别大 case 5~9%。表中 ±1% 级差异应视为噪声。