Browse this library

Chinese source article

5.1 · 稀疏执行与编程抽象

把逻辑候选集合转换为 GPU 工作表,并比较专用内核与可编程注意力抽象

View original Markdown ↗

稀疏算法在纸面上输出一组 token 或 blocks,GPU 需要的却是可批量执行的工作表:每个 query tile 访问哪些 KV tiles,地址如何连续装载,不同行如何归并 softmax,变长请求怎样共享一次 launch。两者之间缺少转换层时,理论 FLOPs 降低可能换来更多索引、随机访存和小 kernel。内核路线要解决的正是这段落差。

稠密 FlashAttention 的效率来自分块搬运 Q、K、V、用 online softmax 避免物化完整分数矩阵,并通过规则循环保持高占用;它仍然计算稠密的数学配对。稀疏执行保留这套分块思想,同时跳过不在候选表中的 KV tiles。候选若连续、各行数量接近,工作表容易压紧;候选若逐 token 离散、长度差异很大,地址生成和负载不均衡会吞掉节省。

从索引输出到工作表

动态 indexer 常输出每个 query 的 top-kk token id。系统对它排序、去重并映射到物理页,按 tile 边界合并候选,再构造类似块稀疏矩阵的行指针与列索引,kernel 随后按工作表加载 K/V。若多个 query、heads 或相邻层共享候选,这些步骤可以复用,否则每层都要重新整理。

逻辑预算 kk 与物理预算并不相等。设 tile 含 TT 个位置,候选散落在 mm 个 tile 中,则最少读取 mTmT 个位置。token recall 很高的选择器若把候选打散,可能比 recall 略低但集中在少数块的选择器更慢。训练目标因而可以加入块连续性或物理预算约束,但这会改变纯粹按 attention 分数选 top-kk 的结果。

变长 batch 又增加一层不规则性。不同请求的历史长度、候选页数和 GQA 分组不同,若为最长行填充,浪费会重新出现;若每行单独 launch,调度开销上升。常见办法是按候选长度分桶、使用持久化 kernel,或让一个 thread block 循环处理多个短行。选择哪种方式取决于请求分布,不能只在单一长度 microbenchmark 上判断。

稀疏 softmax 仍要保持数值一致

跨多个不连续 tile 计算 softmax 时,每个 tile 可以得到局部最大值、局部指数和与局部加权 Value,随后按 online softmax 公式合并。设前一部分状态为 (m,l,o)(m,l,o),新 tile 为 (m′,l′,o′)(m',l',o'),合并最大值为 m∗=max⁡(m,m′)m^*=\max(m,m'),归一化和按指数缩放后相加。只要候选集合相同,tile 顺序和分组不应改变数学结果,浮点舍入差异则应在容差内。

因果 mask、窗口边界、重复候选和空行都必须在这一步处理。重复 tile 若没有去重,会让同一 key 在 softmax 中出现两次;空候选若没有固定局部或 self token 回退,会产生无定义归一化。GQA 下多个 query heads 共享一组 K/V,候选可以按 KV head 共享,也可以各 query head 独立;两种语义和读取量不同。

专用内核与可编程抽象

专用内核固定某一种索引格式、tile 大小和数据类型,能针对 NSA、块路由或页选择充分优化。它适合稳定方法和生产路径,开发新 mask 时维护成本较高。可编程抽象让研究者描述 score 修改与 mask 规则,由编译器生成融合 kernel;它缩短实验周期,也要面对动态数据依赖、块 mask 构建成本和新硬件上的代码生成质量。

FlexAttention 用 score_mod 表达分数变换,用 mask_mod 表达哪些位置合法,并以 BlockMask 压缩规则 mask。对于窗口、文档边界、因果前缀等可预测结构,BlockMask 可以提前建立;对于每个 token 都由 indexer 产生的离散候选,还需要把外部索引转换为编译器接受的块布局。抽象消除了手写完整前反向内核的负担,却不会自动让坏的布局变快。

训练与 Decode 对内核的要求也不同。训练要支持反向传播,保存或重算必要统计,并处理长 query 维度;Decode 的 query 很短,瓶颈更接近 KV 读取和 launch。一个在训练吞吐上优秀的块稀疏 kernel,未必适合单 token Decode。Prefill 又介于两者之间,通常更容易通过大 tile 获得算力利用率。

如何验证内核真的兑现算法收益

验证顺序应从正确性开始。用小矩阵构造固定候选,与“先生成完整分数再 mask”的参考实现比较前向输出和梯度;覆盖因果边界、重复索引、空块、GQA、变长 batch 与不同精度。随后分离测量索引整理、工作表构建、kernel 和数据搬运,避免把预处理藏在计时区间之外。

性能分析至少观察有效 HBM 带宽、SM occupancy、矩阵单元利用率、launch 数、L2 命中和每行工作量分布。稀疏率提高而延迟不再下降时,通常意味着固定开销或不规则访存已经主导。将逻辑候选数、物理 tile 数和读取字节同时记录,能够判断问题来自选择器太分散,还是 kernel 没有合并好工作。

端到端测量还要包含模型其余部分。attention 只占总延迟的一部分,MLP、通信、采样和调度不会随稀疏率同比下降。若 attention 已从主要瓶颈变成次要部分,继续削减其中 FLOPs 对整体收益有限。Roofline 分析可以先判断算子受计算还是带宽约束,再决定优化索引、tile 或算术。

与模型并行的交界

张量并行会把 heads 或特征维切到多张卡,序列并行则可能让不同设备拥有不同 token 范围。候选集合若跨越设备边界,稀疏 attention 还要决定搬运 KV、搬运 query,还是先交换摘要再请求命中页。只统计单卡 kernel 会漏掉这部分通信。规则窗口通常只需邻接设备交换边界,动态远程候选可能产生不规则 all-to-all;跨头共享索引能够减少元数据,也可能让某张卡因热门页集中而成为热点。

因此分布式基准应同时记录每卡工作量、通信字节和最慢卡等待。平均候选数相同并不代表负载均衡,少数超长行足以决定一个同步 step 的耗时。候选路由若能在训练中加入容量约束,可以改善尾部;推理时则可按页所有者重排请求或复制热点页。任何做法都要把新增内存和通信算回总成本。

索引到 Kernel沿候选整理、地址生成、KV 装载和 online softmax 展开,并给出 profiler 口径;FlexAttention讨论规则表达、BlockMask、编译生成和动态 selector 接入。前者解释一条专用执行链怎样形成,后者说明哪些共性能够上升为编程抽象。评价二者时,最终问题始终是同一个:算法承诺跳过的工作,硬件是否真的没有做。

索引放在哪里执行

索引器可以在 GPU 上与 attention 同一条流水执行,也可以由 CPU、另一张卡或前一层异步准备。放在 GPU 上减少跨设备传输,却会与主计算争抢算力和带宽;放到 CPU 适合规则页表,复杂向量检索可能跟不上 token 生成;异步预取能隐藏延迟,但必须基于尚未完成的表示做预测,候选可能变旧。端到端时间线应画出依赖关系,不能只把索引 kernel 的孤立耗时相加。

跨层复用能够摊薄工作表构建,却需要版本协议。若一组层共享候选,组首生成索引后,后续层读取同一页表;当 batch 中某个请求结束、cache 页迁移或驱逐发生时,物理地址可能改变。逻辑 token id 与物理页地址应分离,页表更新必须与正在运行的 kernel 同步。这里的错误常表现为偶发质量下降,而不是明确崩溃。

可编程抽象的价值还包括建立统一正确性基线。新稀疏规则先用高层 mask 表达,与稠密参考逐元素比较,再逐步换成压缩 BlockMask 和专用 kernel。若一开始就同时修改算法、布局与融合代码,输出差异很难归因。成熟路线通常保留一个慢但清楚的参考实现,用来覆盖新形状、精度和硬件。