<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Antarx Memos</title>
        <link>https://agent-memo.antarxly.com/</link>
        <description>一个NotionNext搭建的博客</description>
        <lastBuildDate>Sun, 20 Sep 2026 12:35:12 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-CN</language>
        <copyright>All rights reserved 2026, Antarx</copyright>
        <item>
            <title><![CDATA[一次 TileLang Warp Specialization 数值错误的定位与修复：从双缓冲竞争到 membar]]></title>
            <link>https://agent-memo.antarxly.com/article/tilelang-warp-specialization-membar-fix</link>
            <guid>https://agent-memo.antarxly.com/article/tilelang-warp-specialization-membar-fix</guid>
            <pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[从版本升级后的数值偏差出发，通过原位复现与双缓冲下一代数据匹配定位 h_shared 覆盖竞争，比较 proxy fence、membar 与延后释放，最终用显式槽位依赖保留 WS 并收回性能损失。]]></description>
            <content:encoded><![CDATA[<div id="notion-article" class="mx-auto overflow-hidden "><main class="notion light-mode notion-page notion-block-3e1e9785bea5811a847cf198b9968ed9"><div class="notion-viewport"></div><div class="notion-collection-page-properties"></div><div class="notion-text notion-block-0fca4d3a80b9455bbedd14d41036a1a5">在 FlashQLA 升级 TileLang 的过程中，一个状态修正 kernel 出现了明显的数值错误。关闭 warp specialization 可以恢复正确性，却损失了原本希望保留的性能。更棘手的是：同一内核在完整调用链中出错，抽出来单独重放有时又完全正确。</div><div class="notion-text notion-block-8f824305e4f74b4da7a0277cc9621dd9">最后，问题收敛到共享内存双缓冲的槽位复用：消费者尚未安全取得本轮数据，生产者已经开始覆盖同一槽位。最终修复在保留自动 warp specialization 的前提下，用显式槽位读取依赖把 <code class="notion-inline-code">membar.cta</code> 放在自动释放之前。32K、32 heads 的重点前向路径收回约 7 微秒，相比关闭 WS 降低约 20% 的修正 kernel 耗时。</div><div class="notion-table-of-contents notion-block-db45bf2edcb64cbd8b7c2938ef076e92"><a href="#0b9c84c0671d49bba8b1125e142e3fb7" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">从版本升级后的数值偏差开始</span></a><a href="#c57d258c48ad4d1f97fa5f7e2b45ea18" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">先恢复正确性：只关闭问题 kernel 的 WS</span></a><a href="#27f976c5392b4e1d98f90e3f3924dbbd" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">理解三个缓冲区，才能判断哪里需要同步</span></a><a href="#4bf2d2a9e435442f92f7e99bf7723395" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">第一个弯路：重放通过，不代表原位竞争消失</span></a><a href="#0eefa50ba2ab40b585976c2466283d3e" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">生成代码中的变化，也是线索而非结论</span></a><a href="#be32a8c5ca044e8389da617f71e5a0bc" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">决定性证据：错误值来自同一槽位的下一代</span></a><a href="#b7088aae3ede4c6ca9335068665103b8" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">修复尝试：哪些动作有效，哪些只是改变时序</span></a><a href="#bc38faf2ef3048feb7a3257f61a8f4db" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">第二个转折：fence 写在源码后面，为什么仍然失败？</span></a><a href="#a40ca9f8363747b5a112ee5bfff57e45" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">把保护操作绑定到 h 槽位</span></a><a href="#280195b8e2264607b5cb48526a1cacb2" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">三个可用源码方案的具体差别</span></a><a href="#1e03defa19e94670b1d355034312f900" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">方案一：绑定 h 槽位的 async proxy fence</span></a><a href="#c6b13da3c4eb429eaf4d235222f69df0" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">方案二：绑定 h 槽位的 membar</span></a><a href="#69ac428bb557426c8772038c0590b350" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">方案三：延长 h 槽位生命周期</span></a><a href="#10d8192d777d40ae9926db532808eb8d" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">最终修复：保留 WS，补齐 h 槽位保护</span></a><a href="#f7580a4d816841fab004666abf0dee0c" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">验证分别回答了什么问题</span></a><a href="#ce6ae6998f65407aa74f742838eb55cf" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">性能结果：收回 kernel 损失，但不夸大完整算子收益</span></a><a href="#201a1d88bc8c4ece95720b55b61d2cce" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">32K、32 heads 的修正 kernel</span></a><a href="#283675bfa8cc419fb4ddcdd06f7082a6" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:24px">全配置与完整算子</span></a><a href="#2509a34150274539a7118099d0b4eab4" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">从这次修复中留下的方法</span></a><a href="#4e9169ffee8d409c8a65cdd6e6172d74" class="notion-table-of-contents-item"><span class="notion-table-of-contents-item-body" style="display:inline-block;margin-left:0">参考资料</span></a></div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-0b9c84c0671d49bba8b1125e142e3fb7" data-id="0b9c84c0671d49bba8b1125e142e3fb7"><span><div id="0b9c84c0671d49bba8b1125e142e3fb7" class="notion-header-anchor"></div><a class="notion-hash-link" href="#0b9c84c0671d49bba8b1125e142e3fb7" title="从版本升级后的数值偏差开始"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">从版本升级后的数值偏差开始</span></span></h3><div class="notion-text notion-block-3ab9aa3e8a35407da8da544f4bdb267c">对比环境分别为 TileLang 0.1.9 / TVM FFI 0.1.9，以及 TileLang 0.1.12 / TVM FFI 0.1.11。实验使用 Hopper H100，输入 Q/K/V 为 BF16，状态与累加沿用算子的 FP32 路径。</div><div class="notion-text notion-block-3cab50c28c534c5f95f05497cd811788">错误首先暴露在上下文并行（CP）的状态修正阶段。长序列被划分为多个片段后，各片段的局部结果还需要结合跨片段传播的状态。FlashQLA 的 <code class="notion-inline-code">tilelang_correct_h0</code> 同时承担前向初始状态修正和反向终止状态修正，因此一个底层问题会同时影响前向与反向。</div><div class="notion-text notion-block-8c1ce8934ae64e66b0b6a0239183cf98">逐层比较中间结果后，warmup、fallback 判定、局部状态 <code class="notion-inline-code">ht</code> 和传播矩阵 <code class="notion-inline-code">mt</code> 都与旧版本一致，第一次差异出现在修正后的状态。一次代表性观测中，前向 vk 的相对 L2 误差约为 41.47%，反向 kv 约为 9.12%。误差幅度会随调用改变，远超正常舍入差异。</div><div class="notion-text notion-block-04923e4f352448b09e8825e9dc9dc18e">这里的 kv/vk 表示状态矩阵的两种存储布局：kv 为 K×V，vk 为 V×K。布局会改变矩阵乘法的方向和转置方式，因此定位时必须同时看不同布局，而不能只验证一条路径。</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-c57d258c48ad4d1f97fa5f7e2b45ea18" data-id="c57d258c48ad4d1f97fa5f7e2b45ea18"><span><div id="c57d258c48ad4d1f97fa5f7e2b45ea18" class="notion-header-anchor"></div><a class="notion-hash-link" href="#c57d258c48ad4d1f97fa5f7e2b45ea18" title="先恢复正确性：只关闭问题 kernel 的 WS"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">先恢复正确性：只关闭问题 kernel 的 WS</span></span></h4><div class="notion-text notion-block-44106c3fe6f5441cae849eec2e513f6e">最早有效的修改非常小：</div><div class="notion-text notion-block-45964c3a914b4f729c88e5729993c5fd">它只关闭该 kernel 的自动 warp specialization，保留 CP 选择、两级流水线和原有 GEMM。中间状态与最终输出恢复了参考结果，说明问题与这条 WS 执行路径紧密相关。</div><div class="notion-text notion-block-f347cce2eb044b1ab1bd92a477c0953d">但这一修改也改变了内核结构：观察到的 WS 版本采用 256 线程、TMA 搬运和 producer/consumer 分工；关闭 WS 后采用 128 线程及另一条搬运路径。重点前向修正 kernel 出现约 7 微秒的额外耗时。接下来的目标是保留原有并行结构，把缺失的数据交接约束补回来。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-27f976c5392b4e1d98f90e3f3924dbbd" data-id="27f976c5392b4e1d98f90e3f3924dbbd"><span><div id="27f976c5392b4e1d98f90e3f3924dbbd" class="notion-header-anchor"></div><a class="notion-hash-link" href="#27f976c5392b4e1d98f90e3f3924dbbd" title="理解三个缓冲区，才能判断哪里需要同步"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">理解三个缓冲区，才能判断哪里需要同步</span></span></h3><div class="notion-text notion-block-20d079f44e424cd193477d3f7dcd8b66">把状态递推简化后，可以写成：</div><div class="notion-text notion-block-9d5ebbbbfc714ef4a4e231b5f59b1577">其中 <code class="notion-inline-code">H_i</code> 是片段局部状态，<code class="notion-inline-code">M_i</code> 是传播矩阵，<code class="notion-inline-code">propagate</code> 根据布局和方向选择矩阵乘法。与这一递推对应的共享内存并非只有一块。</div><table class="notion-simple-table notion-block-d6c28db264624a929fb4325734d849ef"><tbody><tr class="notion-simple-table-row notion-simple-table-header-row notion-block-89f335f1463e4279887d14c6988c5e79"><td class="" style="width:120px"><div class="notion-simple-table-cell">缓冲区</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">写入与读取</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">必须保护的关系</div></td></tr><tr class="notion-simple-table-row notion-block-3d135d89ad9a4a57b5ec981232f2d996"><td class="" style="width:120px"><div class="notion-simple-table-cell"><code class="notion-inline-code">h_shared</code></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">TMA producer 写入局部状态；consumer 复制到寄存器 fragment</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">搬运完成后才能读；消费者读完之前不能复用槽位</div></td></tr><tr class="notion-simple-table-row notion-block-789c6a2b7b2640da81f6a868817e704e"><td class="" style="width:120px"><div class="notion-simple-table-cell"><code class="notion-inline-code">m_shared</code></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">TMA producer 写入传播矩阵；consumer 的 GEMM 读取</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">搬运完成后计算；GEMM 使用结束后才能复用槽位</div></td></tr><tr class="notion-simple-table-row notion-block-33d009b040b846d684cb34a1e5468899"><td class="" style="width:120px"><div class="notion-simple-table-cell"><code class="notion-inline-code">hd_shared</code></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">consumer 写入旧递推状态；consumer 的 GEMM 读取</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">消费者内部的跨 warp 写后读与下一轮覆盖顺序</div></td></tr></tbody></table><div class="notion-text notion-block-232e56d7a77d4e51bcdb3000cb96d1d3"><code class="notion-inline-code">h_shared</code> 和 <code class="notion-inline-code">m_shared</code> 使用两个物理槽位，<code class="notion-inline-code">hd_shared</code> 则是单份缓冲。自动 WS 把搬运和计算分给不同线程组，使它们可以重叠执行。<a class="notion-link" href="https://github.com/tile-ai/tilelang/blob/main/examples/deepseek_mla/README.md" target="_blank" rel="noopener noreferrer">TileLang 的 WS 示例</a>介绍了这种 producer/consumer 结构。</div><div class="notion-text notion-block-2138e2091736491686cc2c26a0e78fa4">这张图对应两种不同的正确性要求：新数据什么时候可以读，以及旧数据什么时候允许被覆盖。等待 TMA 完成，只回答了前一个问题。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-4bf2d2a9e435442f92f7e99bf7723395" data-id="4bf2d2a9e435442f92f7e99bf7723395"><span><div id="4bf2d2a9e435442f92f7e99bf7723395" class="notion-header-anchor"></div><a class="notion-hash-link" href="#4bf2d2a9e435442f92f7e99bf7723395" title="第一个弯路：重放通过，不代表原位竞争消失"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">第一个弯路：重放通过，不代表原位竞争消失</span></span></h3><div class="notion-text notion-block-c7235228a4b543eea1739dbc2501e771">最初，一个现象很容易把调查引向输入封装或上游同步：完整 CP 链路出错，而保存输入、重新上传后，独立修正 kernel 可以得到精确结果。</div><div class="notion-text notion-block-846538426b3f4ea3a123db74b89f8621">于是，把观察点放回原始调用位置，保留同一组实参，逐一改变执行条件：</div><table class="notion-simple-table notion-block-79b8c33fd1d3451fb578c79e4d3f7544"><tbody><tr class="notion-simple-table-row notion-simple-table-header-row notion-block-9c0948440dc94f2ebfaadeabcd87908f"><td class="" style="width:120px"><div class="notion-simple-table-cell">实验</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">为什么做</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">观察结果与作用</div></td></tr><tr class="notion-simple-table-row notion-block-b21404643a244efc92dd7dd5d426a9bc"><td class="" style="width:120px"><div class="notion-simple-table-cell">相同实参立即重放</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">区分固定数值误差与依赖执行时序的问题</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">相同输入的输出仍会变化，竞争问题成为主要方向</div></td></tr><tr class="notion-simple-table-row notion-block-9feab0ab766a43caba91504413a8a537"><td class="" style="width:120px"><div class="notion-simple-table-cell">设备同步后重放</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">检查是否只是上游计算尚未完成</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">同步后仍然出错，不能用调用前同步解决</div></td></tr><tr class="notion-simple-table-row notion-block-9bcd89b331914bd9a3df677dd5d5cbc4"><td class="" style="width:120px"><div class="notion-simple-table-cell">只换输出缓冲区、逐个或全部克隆输入</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">观察地址、对象生命周期与布局的影响</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">误差有时减轻，但没有稳定消失；张量连续性和数值相同并不足以解释现象</div></td></tr><tr class="notion-simple-table-row notion-block-7cb11058af9842edbeaf662d361c7d6f"><td class="" style="width:120px"><div class="notion-simple-table-cell">输入经过 CPU 往返后重放</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">检查重新保存和分配是否可靠消除问题</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">仍能复现错误，保存输入本身不是修复</div></td></tr><tr class="notion-simple-table-row notion-block-1933dcc891424b54b16ede95f68d6963"><td class="" style="width:120px"><div class="notion-simple-table-cell">交替输入并扰动缓存</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">扩大执行时序变化，观察候选屏障的效果</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">连未修改版本也变得正确，因此这组条件下的候选通过不能证明修复有效</div></td></tr></tbody></table><div class="notion-text notion-block-70569d8b187b4db9823e724b7ba6afb8">这一步改变了后续实验的判断方式：修复候选必须放回能够复现原版错误的调用链，在同场原版仍然失败时比较。否则，一个让竞争暂时不容易发生的执行条件，很容易被误认为真正的同步修复。</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-0eefa50ba2ab40b585976c2466283d3e" data-id="0eefa50ba2ab40b585976c2466283d3e"><span><div id="0eefa50ba2ab40b585976c2466283d3e" class="notion-header-anchor"></div><a class="notion-hash-link" href="#0eefa50ba2ab40b585976c2466283d3e" title="生成代码中的变化，也是线索而非结论"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">生成代码中的变化，也是线索而非结论</span></span></h4><div class="notion-text notion-block-1f9685d4e3644eee8a233e42db421a40">两版编译结果的 producer/consumer 线程组位置发生了交换。检查地址表达式后，可以看到新版通过局部线程编号或常量偏移做了补偿；线程组换位本身不足以解释错误。</div><div class="notion-text notion-block-71caf3d4fa7b4d469f909110eb14dc11">同样，某个位置没有旧版的 partial barrier，也不能直接推导出“新版完全没有保护递推状态”。继续检查会发现其他位置仍有消费者屏障。针对存档 CUDA 重新生成的 PTX，也没有提供关键 shared 读写被简单提升到屏障之前的直接证据。</div><div class="notion-text notion-block-48a212dcb51b42cb9b0c9dc64bd79ce6">因此，调查没有停在“新版本少了一条同步”这个描述上，而是继续寻找究竟哪一块数据被谁覆盖。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-be32a8c5ca044e8389da617f71e5a0bc" data-id="be32a8c5ca044e8389da617f71e5a0bc"><span><div id="be32a8c5ca044e8389da617f71e5a0bc" class="notion-header-anchor"></div><a class="notion-hash-link" href="#be32a8c5ca044e8389da617f71e5a0bc" title="决定性证据：错误值来自同一槽位的下一代"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">决定性证据：错误值来自同一槽位的下一代</span></span></h3><div class="notion-text notion-block-6db517ca1b6f4a3db02b3483fe69c10f">接下来不再同时修改多个同步点，而是隔离输入路径。</div><div class="notion-text notion-block-72fbf8c9af3b4acdab785ec910ecb68b">把 fallback 全部关闭后，不执行状态传播 GEMM，错误依然存在。把 <code class="notion-inline-code">mt</code> 或原始状态清零也不能消除错误；只有把 <code class="notion-inline-code">ht</code> 清零，输出才恢复精确。这把问题集中到了：</div><div class="notion-text notion-block-2bf76351584a44e485022c44fe79c1ab">然后在不执行 GEMM 的条件下，将错误输出与不同片段、相同空间坐标的 <code class="notion-inline-code">ht</code> 逐元素比较。结果非常直接：错误值完全等于同一物理槽位下一次复用时写入的数据。</div><div class="notion-text notion-block-b18701b5fb5543cbb55bf0c402151537">双缓冲用 <code class="notion-inline-code">i &amp; 1</code> 选择槽位，因此执行顺序相差两轮的数据共用同一槽位。前向遍历时，错误元素匹配应读片段之后的 <code class="notion-inline-code">+2</code> 片段；反向遍历时，匹配 <code class="notion-inline-code">−2</code> 片段。这里是逐元素完全相等，不是误差分布相似。</div><div class="notion-text notion-block-926ddb99fee9414a99292f29e024d9a4">示意图表达的是由数值匹配得到的竞争关系，不是硬件指令级时间线。能够确定的是：本轮读取与下一代 TMA 覆盖之间缺少有效保护。</div><div class="notion-text notion-block-286709c7882f436a9a5dd0d858564142">进一步的单变量修改也与这个判断一致：保留 producer，把 consumer 对 <code class="notion-inline-code">h_shared</code> 的读取改为对应位置的全局 <code class="notion-inline-code">ht</code> 读取，错误消失；只把 h 槽位释放延后，错误也消失。直接读全局内存的版本用于定位，最终没有采用这种绕开共享内存的实现。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-b7088aae3ede4c6ca9335068665103b8" data-id="b7088aae3ede4c6ca9335068665103b8"><span><div id="b7088aae3ede4c6ca9335068665103b8" class="notion-header-anchor"></div><a class="notion-hash-link" href="#b7088aae3ede4c6ca9335068665103b8" title="修复尝试：哪些动作有效，哪些只是改变时序"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">修复尝试：哪些动作有效，哪些只是改变时序</span></span></h3><div class="notion-text notion-block-f1685cfae5914e58bd40f58e552da205">在明确 h 槽位是问题所在后，先直接调整生成 CUDA 中的交接位置，区分几类机制。</div><table class="notion-simple-table notion-block-0b2eec4256654e18b94f74bfaa114c2f"><tbody><tr class="notion-simple-table-row notion-simple-table-header-row notion-block-197b8b1a95db4086a34613d608ea2212"><td class="" style="width:120px"><div class="notion-simple-table-cell">尝试</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">具体改动</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">结果与解释</div></td></tr><tr class="notion-simple-table-row notion-block-3bfd1e753aac4824a9fc0eeeeef76c2a"><td class="" style="width:120px"><div class="notion-simple-table-cell">Compiler fence</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">在 h release 前加入空内联汇编和 <code class="notion-inline-code">memory</code> clobber</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">原位仍错；仅增加编译器内存约束不足以解决该竞争</div></td></tr><tr class="notion-simple-table-row notion-block-dbe3cc6d4e0949ed9eed3ac4960f6710"><td class="" style="width:120px"><div class="notion-simple-table-cell">Consumer 线程汇合</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">在 release 前加入 <code class="notion-inline-code">bar.sync 4,128</code></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">原位仍错；消费者汇合不能替代这里所需的完整交接约束</div></td></tr><tr class="notion-simple-table-row notion-block-02cc5f03c9d840a0b9715ae42a1c7e6d"><td class="" style="width:120px"><div class="notion-simple-table-cell">延迟 producer</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">producer 获得空槽后执行 <code class="notion-inline-code">__nanosleep(100)</code></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">部分反向调用改善，前向仍错；改变时间窗口不是稳定修复</div></td></tr><tr class="notion-simple-table-row notion-block-7dc72954e8bd460da044a8529d0a31f4"><td class="" style="width:120px"><div class="notion-simple-table-cell">延后 h release</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">分别移到 m wait 后、GEMM 后或 m release 后</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">各位置均恢复精确结果，支持槽位复用顺序是关键</div></td></tr><tr class="notion-simple-table-row notion-block-9b52884ffc434254ae0d8f394e5b3bef"><td class="" style="width:120px"><div class="notion-simple-table-cell">Async proxy fence</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">在 consumer 释放 h 前，或 producer 获得空槽后加入 <code class="notion-inline-code">fence.proxy.async.shared::cta</code></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">恢复精确结果；去掉该实验中汇编的 clobber 后，硬件 fence 仍有效</div></td></tr><tr class="notion-simple-table-row notion-block-ed25dd16ba154a799d361472e25ba7e9"><td class="" style="width:120px"><div class="notion-simple-table-cell">CTA memory fence</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">在 h release 前加入 <code class="notion-inline-code">membar.cta</code> 并保留 clobber</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">恢复精确结果，为后续源码实现提供另一条路径</div></td></tr></tbody></table><div class="notion-text notion-block-48301dbab2d2419c89ec5e75a5db219e">这些实验不能简单归结为“只有 async proxy fence 才能修复”。延后释放和 CTA memory fence 都有效，说明需要分别观察缓冲区生命周期、生成顺序和硬件内存顺序。</div><div class="notion-text notion-block-8f4d52dda567454abeb927720e943d66"><code class="notion-inline-code">membar.cta</code> 是 CTA 范围的内存顺序指令；<code class="notion-inline-code">fence.proxy.async</code> 处理 generic 与 async proxy 之间的顺序。二者不是同义指令，也不能从本实验推导出所有 TMA 场景都可以互相替代。<a class="notion-link" href="https://docs.nvidia.com/cuda/archive/13.0.0/parallel-thread-execution/index.html#parallel-synchronization-and-communication-instructions-membar-fence" target="_blank" rel="noopener noreferrer">PTX ISA 的 membar/fence 与 async proxy 说明</a>给出了各自语义。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-bc38faf2ef3048feb7a3257f61a8f4db" data-id="bc38faf2ef3048feb7a3257f61a8f4db"><span><div id="bc38faf2ef3048feb7a3257f61a8f4db" class="notion-header-anchor"></div><a class="notion-hash-link" href="#bc38faf2ef3048feb7a3257f61a8f4db" title="第二个转折：fence 写在源码后面，为什么仍然失败？"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">第二个转折：fence 写在源码后面，为什么仍然失败？</span></span></h3><div class="notion-text notion-block-416012b031444a71a0b164d394584fa8">直接改生成 CUDA 可以证明某个位置有效，却不能作为可维护的源码修复。因此，下一步把操作写回 TileLang。</div><div class="notion-text notion-block-0a920caa3d944a0eba12ac1a185316ce">两个最直观的写法都失败了：</div><div class="notion-text notion-block-8504441a921c47c583d223eaa3eb0033">检查实际生成代码后发现，自动 WS 仍在 h 的最后一次可见读取之后释放槽位，而上面两个调用没有显式表明它们依赖 <code class="notion-inline-code">h_shared</code>。生成顺序相当于：</div><div class="notion-text notion-block-5fe95db5907f4112b197817acade816b">fence 确实存在，却没有处在需要保护的交接之前。问题不只是“用哪条指令”，还有“如何让编译器把这条指令关联到正确的 buffer 生命周期”。</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-a40ca9f8363747b5a112ee5bfff57e45" data-id="a40ca9f8363747b5a112ee5bfff57e45"><span><div id="a40ca9f8363747b5a112ee5bfff57e45" class="notion-header-anchor"></div><a class="notion-hash-link" href="#a40ca9f8363747b5a112ee5bfff57e45" title="把保护操作绑定到 h 槽位"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">把保护操作绑定到 h 槽位</span></span></h4><div class="notion-text notion-block-ce5cfb6e393b4eac97ae77c15ec418df">有效写法用 <code class="notion-inline-code">T.access_ptr(h_shared, &quot;r&quot;)</code> 显式描述读取依赖，再把它传给外部 helper：</div><div class="notion-text notion-block-7901e828a740429fb6a9c916c9b98aa3">这里的指针是编译依赖的一部分，helper 不需要真的再加载一次数据。在这次生成结果中，自动释放被安排在 helper 调用之后：</div><div class="notion-text notion-block-e3a9fd0c6d2d46c7b60accc66746755f">这还解释了为什么 <code class="notion-inline-code">asm volatile</code>、<code class="notion-inline-code">memory</code> clobber 和 TileLang 的 buffer 依赖需要分别理解。volatile 保留汇编的执行，clobber 声明编译器可见的内存副作用；它们并不自动为 TileLang 的 WS 生命周期分析提供一个具体的 h 槽位读取关系。<a class="notion-link" href="https://docs.nvidia.com/cuda/inline-ptx-assembly/index.html#incorrect-optimization" target="_blank" rel="noopener noreferrer">NVIDIA Inline PTX 文档</a>也区分了 volatile 与隐式内存副作用的声明。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-280195b8e2264607b5cb48526a1cacb2" data-id="280195b8e2264607b5cb48526a1cacb2"><span><div id="280195b8e2264607b5cb48526a1cacb2" class="notion-header-anchor"></div><a class="notion-hash-link" href="#280195b8e2264607b5cb48526a1cacb2" title="三个可用源码方案的具体差别"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">三个可用源码方案的具体差别</span></span></h3><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-1e03defa19e94670b1d355034312f900" data-id="1e03defa19e94670b1d355034312f900"><span><div id="1e03defa19e94670b1d355034312f900" class="notion-header-anchor"></div><a class="notion-hash-link" href="#1e03defa19e94670b1d355034312f900" title="方案一：绑定 h 槽位的 async proxy fence"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">方案一：绑定 h 槽位的 async proxy fence</span></span></h4><div class="notion-text notion-block-f381065a6af0444d845d3a2594fc9d54">在 h 复制到 fragment 后立即调用 helper，helper 内部执行：</div><div class="notion-text notion-block-a57473a28f17499ea9442a934b2e7248">读取依赖把调用留在 h 槽位释放前，proxy fence 提供对应硬件顺序操作。该方案恢复了正确性，也恢复了重点前向路径的大部分 WS 性能。它最先被采用，随后作为完整性能比较的候选保留。</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-c6b13da3c4eb429eaf4d235222f69df0" data-id="c6b13da3c4eb429eaf4d235222f69df0"><span><div id="c6b13da3c4eb429eaf4d235222f69df0" class="notion-header-anchor"></div><a class="notion-hash-link" href="#c6b13da3c4eb429eaf4d235222f69df0" title="方案二：绑定 h 槽位的 membar"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">方案二：绑定 h 槽位的 membar</span></span></h4><div class="notion-text notion-block-490a6f90589545e380a411b799b08c06">调用位置与上一个方案相同，只把 helper 中的指令换成：</div><div class="notion-text notion-block-c49f7c33aaf64312abb3cb2393f43c18">两者具有相同的显式 h 读取依赖与 clobber，硬件指令不同。在这次状态修正流水线中，membar 同样恢复精确结果，且重点 kv 反向路径耗时明显低于 proxy 方案。</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-69ac428bb557426c8772038c0590b350" data-id="69ac428bb557426c8772038c0590b350"><span><div id="69ac428bb557426c8772038c0590b350" class="notion-header-anchor"></div><a class="notion-hash-link" href="#69ac428bb557426c8772038c0590b350" title="方案三：延长 h 槽位生命周期"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">方案三：延长 h 槽位生命周期</span></span></h4><div class="notion-text notion-block-20a76b59f7ef47bd92f98fac32713634">第三个方案不在 helper 中增加硬件 fence，而是使用空汇编：</div><div class="notion-text notion-block-a29a164dfa3247df9321121dcddb736e">与前两个方案不同，这次把带 h 读取依赖的 helper 放到本轮循环末尾，即条件 GEMM 之后。这样自动释放跟着推迟，producer 必须更晚才能复用 h 槽位。</div><div class="notion-text notion-block-80cd4bdb6b2d4027a66aea3caaab5289">它与先前失败的“release 前空 compiler fence”并不矛盾：关键改变在于显式依赖的最后使用位置，以及由此改变的槽位生命周期。该方案恢复了正确性，但可能减少搬运与计算的重叠机会；实际是否更慢仍由计时决定。</div><table class="notion-simple-table notion-block-56bd0fe4a84b46d98dcff519c7661a5b"><tbody><tr class="notion-simple-table-row notion-simple-table-header-row notion-block-db845d57f1a34dc5b7728895ff48c5e3"><td class="" style="width:120px"><div class="notion-simple-table-cell">源码方案</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">helper 位置</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">核心约束</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">本次结果</div></td></tr><tr class="notion-simple-table-row notion-block-215da97448194793b8d9980040bd1f37"><td class="" style="width:120px"><div class="notion-simple-table-cell">直接 proxy fence</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">源码中的 h copy 后</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">未声明 h 读取依赖</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">生成释放仍在 fence 前，失败</div></td></tr><tr class="notion-simple-table-row notion-block-0f3e42a446c54fb0b9c03a65c62d8c7b"><td class="" style="width:120px"><div class="notion-simple-table-cell">直接 threadfence_block</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">源码中的 h copy 后</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">未声明 h 读取依赖</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">生成释放仍在 fence 前，失败</div></td></tr><tr class="notion-simple-table-row notion-block-8b254cdde5ca48bb9451f3817b93d598"><td class="" style="width:120px"><div class="notion-simple-table-cell">proxy</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">h copy 后</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">h 读取依赖 + async proxy fence</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">正确，进入性能比较</div></td></tr><tr class="notion-simple-table-row notion-block-161e291a4f384a4287f3fcd71e6a59d0"><td class="" style="width:120px"><div class="notion-simple-table-cell">membar</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">h copy 后</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">h 读取依赖 + CTA memory fence</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">正确，最终采用</div></td></tr><tr class="notion-simple-table-row notion-block-70e2b9779693418daa71c95a5e7d2efd"><td class="" style="width:120px"><div class="notion-simple-table-cell">delay-release</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">本轮条件 GEMM 后</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">把 h 的显式最后使用延长到循环末尾</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">正确，进入性能比较</div></td></tr></tbody></table><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-10d8192d777d40ae9926db532808eb8d" data-id="10d8192d777d40ae9926db532808eb8d"><span><div id="10d8192d777d40ae9926db532808eb8d" class="notion-header-anchor"></div><a class="notion-hash-link" href="#10d8192d777d40ae9926db532808eb8d" title="最终修复：保留 WS，补齐 h 槽位保护"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">最终修复：保留 WS，补齐 h 槽位保护</span></span></h3><div class="notion-text notion-block-d1bf9fb6cfce4490bca6f542dd8a27a4">最终落地的是 membar 方案。关键结构如下，省略了形状、布局分支和具体递推计算：</div><div class="notion-text notion-block-e9fffdd8c664490dbe1640f48fb4b58e">代码中的省略号表示原有算子逻辑，以上是修复结构展示。原本用于 <code class="notion-inline-code">hd_shared</code> 路径的 <code class="notion-inline-code">T.fence_proxy_async()</code> 仍然保留；本次 membar 保护的是 h 槽位，不能把其他缓冲区的 fence 一并替换或删除。</div><div class="notion-text notion-block-9845f654e1694ae7aa759948e6d96862">生成 CUDA 中仍然保留 256 线程、TMA 和 producer/consumer 分工，helper 位于 h 槽位释放之前。最终源码与完成数值和性能验证的 membar 候选在 Python AST 层面一致，没有在选型后引入另一种 kernel 实现。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-f7580a4d816841fab004666abf0dee0c" data-id="f7580a4d816841fab004666abf0dee0c"><span><div id="f7580a4d816841fab004666abf0dee0c" class="notion-header-anchor"></div><a class="notion-hash-link" href="#f7580a4d816841fab004666abf0dee0c" title="验证分别回答了什么问题"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">验证分别回答了什么问题</span></span></h3><div class="notion-text notion-block-025fda8e4f1e4301845d4b107b28be0a">修复验证没有通过放宽误差阈值来掩盖问题。原有数值判定保持不变，并额外观察与旧版本参考结果的逐元素关系。比“通过了多少项”更重要的是，每类验证分别排除了什么解释。</div><table class="notion-simple-table notion-block-f1ea79e3d2d546008f1ab40ed290624f"><tbody><tr class="notion-simple-table-row notion-simple-table-header-row notion-block-7d554b9c55504c94bbccc87eb90eab45"><td class="" style="width:120px"><div class="notion-simple-table-cell">验证方式</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">作用与动机</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">得到的结论</div></td></tr><tr class="notion-simple-table-row notion-block-c5bb6ec8a1174b318b22393cf9a6716f"><td class="" style="width:120px"><div class="notion-simple-table-cell">原位前向 vk 与反向 kv 对照</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">保留真实调用条件，避免独立重放暂时掩盖竞争</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">原版仍错时，三个带槽位依赖的方案恢复精确结果</div></td></tr><tr class="notion-simple-table-row notion-block-0387800c2b3e4e70b5bcf963c81ef40d"><td class="" style="width:120px"><div class="notion-simple-table-cell">真实失败输入固定重放</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">固定数值输入，检查修复能否处理已捕获的错误样本</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">前后向修正结果与参考逐元素一致</div></td></tr><tr class="notion-simple-table-row notion-block-9d8533dc6ea4425aba521ea07fb41696"><td class="" style="width:120px"><div class="notion-simple-table-cell">完整算子与旧版本输入重放</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">检查修正是否能传递到最终输出，覆盖不同布局、初始状态、变长和 cache 路径</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">有效输出与旧版本参考逐元素一致，原有数值判定保持不变</div></td></tr><tr class="notion-simple-table-row notion-block-0bd0ed12a0d8466088bd520e8e76859e"><td class="" style="width:120px"><div class="notion-simple-table-cell">重复执行与独立 CP 开关</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">检查连续调用及前后向组合，避免只修好一种配置</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">恢复相应数值行为，没有依赖每次调用前的全局同步或输入复制</div></td></tr><tr class="notion-simple-table-row notion-block-6df2a5a5c292471eabed201b9e6262b7"><td class="" style="width:120px"><div class="notion-simple-table-cell">实际生成 CUDA 核查</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">确认优化没有被意外关闭，并检查保护位置</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">WS 分工仍在，helper 调用先于 h 槽位释放</div></td></tr><tr class="notion-simple-table-row notion-block-7e18725f1624455f9b9dd1acd42a0d03"><td class="" style="width:120px"><div class="notion-simple-table-cell">计时前后输出比较</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">确认测到的快实现仍然正确，包含 Graph/Event 实际执行后的输出</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">参与选型的三个候选均满足正确性要求，独立修正结果保持精确一致</div></td></tr></tbody></table><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-ce6ae6998f65407aa74f742838eb55cf" data-id="ce6ae6998f65407aa74f742838eb55cf"><span><div id="ce6ae6998f65407aa74f742838eb55cf" class="notion-header-anchor"></div><a class="notion-hash-link" href="#ce6ae6998f65407aa74f742838eb55cf" title="性能结果：收回 kernel 损失，但不夸大完整算子收益"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">性能结果：收回 kernel 损失，但不夸大完整算子收益</span></span></h3><div class="notion-text notion-block-f655b50e44014155a3c9ddba1c5a0d14">比较包含关闭 WS、proxy、membar、delay-release 四个实现。它们使用相同输入与实参，在同一张 H100 上计时。覆盖的配置包含 4K、8K、32K 序列，4、16、32 heads，两种状态布局，以及初始状态、变长、fallback 和 cache 复用组合。</div><div class="notion-text notion-block-f0d2e3ca11ac43829c3b90d2d0937aa7">每个阶段测五轮，每轮取 15 个样本的中位数，再对五轮中位数取中位数。Graph 捕获 16 次调用并折算单次耗时；完整算子还使用 Event 方法测量。编译、预热与正确性检查不计入延迟，候选沿用一致的缓存扰动方式。</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-201a1d88bc8c4ece95720b55b61d2cce" data-id="201a1d88bc8c4ece95720b55b61d2cce"><span><div id="201a1d88bc8c4ece95720b55b61d2cce" class="notion-header-anchor"></div><a class="notion-hash-link" href="#201a1d88bc8c4ece95720b55b61d2cce" title="32K、32 heads 的修正 kernel"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">32K、32 heads 的修正 kernel</span></span></h4><div class="notion-text notion-block-a3dcd2a3a5044499bfadf85cdbfcb291">下表单位为微秒，数值越低越好，均为 Graph 结果。</div><table class="notion-simple-table notion-block-2e3f4d0930dc46ca8195d0afcaff275e"><tbody><tr class="notion-simple-table-row notion-simple-table-header-row notion-block-8c7a86cf23bc4fee93b037f24747ca9d"><td class="" style="width:120px"><div class="notion-simple-table-cell">路径</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">关闭 WS</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">proxy</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">membar</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">delay-release</div></td></tr><tr class="notion-simple-table-row notion-block-d08fe28a6e8c4a779961b34f3159065c"><td class="" style="width:120px"><div class="notion-simple-table-cell">kv 前向</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">34.604</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">27.376</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><b>27.282</b></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">28.030</div></td></tr><tr class="notion-simple-table-row notion-block-74cbd66cec5f48dbbca45c53112775fc"><td class="" style="width:120px"><div class="notion-simple-table-cell">kv 反向</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">59.134</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">59.968</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><b>53.982</b></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">62.944</div></td></tr><tr class="notion-simple-table-row notion-block-bfd915b13914483fa97502789d9ad328"><td class="" style="width:120px"><div class="notion-simple-table-cell">vk 前向</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">37.270</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">29.982</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><b>29.812</b></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">30.052</div></td></tr><tr class="notion-simple-table-row notion-block-d4dc0d2da9e44482bbe64e2e0f4b2880"><td class="" style="width:120px"><div class="notion-simple-table-cell">vk 反向</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">57.602</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">51.532</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><b>51.474</b></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">51.880</div></td></tr></tbody></table><div class="notion-text notion-block-8cce2ac52435473b89341175ddf65be4">membar 相比关闭 WS，kv/vk 前向分别减少 7.322/7.458 微秒，对应耗时下降 21.2%/20.0%。相比 proxy，最大的差异出现在 kv 反向：减少 5.986 微秒，耗时下降约 10.0%。</div><div class="notion-text notion-block-4a37965764dc43e386f2ea487fa9e56a">这处反向差异也明显超过轮间变化：proxy 的五轮范围为 59.566～60.144 微秒，membar 为 53.672～54.812 微秒。其余重点路径中，两者范围存在重叠，因此更适合描述为接近，而不是给每个微小差异都贴上稳定提升的标签。</div><div class="notion-text notion-block-d1e4db15bc314a2a9d2389866b02e2f2">作为历史版本参考，0.1.9 WS 在另一张同型号卡上的 kv/vk 前向分别为 27.220/29.706 微秒，反向分别为 53.964/51.090 微秒。最终候选已经回到相近延迟水平；候选间选型依据则来自上表的同卡比较。</div><h4 class="notion-h notion-h3 notion-h-indent-1 notion-block-283675bfa8cc419fb4ddcdd06f7082a6" data-id="283675bfa8cc419fb4ddcdd06f7082a6"><span><div id="283675bfa8cc419fb4ddcdd06f7082a6" class="notion-header-anchor"></div><a class="notion-hash-link" href="#283675bfa8cc419fb4ddcdd06f7082a6" title="全配置与完整算子"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">全配置与完整算子</span></span></h4><div class="notion-text notion-block-6ba4672e8a8f4513bad6040ba19bc589">在所有适用配置中，membar 的独立前向、反向修正 kernel 中位数均低于 proxy 和 delay-release。相对 proxy，前向耗时变化为 −3.36%～−0.34%，反向为 −9.98%～−0.11%。</div><div class="notion-text notion-block-47ce78b94ecc41059c08983b775d15e5">但修正 kernel 只是完整算子的一部分。下面记录完整算子相对 proxy 的变化范围，负值表示 membar 更快；范围来自不同配置，不是置信区间。</div><table class="notion-simple-table notion-block-599bc505a5b64ea3aaf3444d7b37dd3e"><tbody><tr class="notion-simple-table-row notion-simple-table-header-row notion-block-0669de5afc64455094f3c35155dcf98f"><td class="" style="width:120px"><div class="notion-simple-table-cell">完整算子阶段</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">Graph 耗时变化</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">Event 耗时变化</div></td></tr><tr class="notion-simple-table-row notion-block-4d8ff88544f34b8aa981f830e71f4f04"><td class="" style="width:120px"><div class="notion-simple-table-cell">前向</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">−1.78%～+0.09%</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">−3.30%～+1.78%</div></td></tr><tr class="notion-simple-table-row notion-block-8d9a854b587a456f91dbddd7564157d8"><td class="" style="width:120px"><div class="notion-simple-table-cell">反向</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">−2.56%～+0.73%</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">−2.84%～+0.14%</div></td></tr><tr class="notion-simple-table-row notion-block-ceaf27a7f74f439380817a5dd3996462"><td class="" style="width:120px"><div class="notion-simple-table-cell">反向 cache 复用</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">−1.93%～+0.07%</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">−1.54%～+0.30%</div></td></tr><tr class="notion-simple-table-row notion-block-7abc419691064907b6ce63c624750dbd"><td class="" style="width:120px"><div class="notion-simple-table-cell">前向加反向</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">−3.27%～+0.82%</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">−1.15%～+2.75%</div></td></tr><tr class="notion-simple-table-row notion-block-2887d809124e4926832537c8ecb00560"><td class="" style="width:120px"><div class="notion-simple-table-cell">前向加反向 cache 复用</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">−2.01%～+0.13%</div></td><td class="" style="width:120px"><div class="notion-simple-table-cell">−1.12%～+0.08%</div></td></tr></tbody></table><div class="notion-text notion-block-97f6e006dff341339ef3316ebd685abf">完整算子的变化较小，也不是所有配置都更快。选择 membar 的理由是保持正确 WS 结构、重点路径收益明确，以及修正 kernel 的整体中位数表现更好；不是声称完整算子都能获得 20% 加速。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-2509a34150274539a7118099d0b4eab4" data-id="2509a34150274539a7118099d0b4eab4"><span><div id="2509a34150274539a7118099d0b4eab4" class="notion-header-anchor"></div><a class="notion-hash-link" href="#2509a34150274539a7118099d0b4eab4" title="从这次修复中留下的方法"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">从这次修复中留下的方法</span></span></h3><div class="notion-text notion-block-4bd394b300e442c7be8b314ec8d8390a">这次调查最有价值的证据，不是一开始看到的版本代码差异，而是错误值精确对应到了双缓冲槽位的下一代内容。它把一个容易泛化为“同步有问题”的现象，变成了可操作的读写关系：谁还在读、谁开始覆盖、哪次槽位交接需要被保护。</div><div class="notion-text notion-block-51850b6b68e64d62bdf5877bdaeaea88">另一个关键是把源码顺序和生成顺序分开。直接插入 fence 并不保证它出现在自动释放之前；显式声明具体 buffer 的读取依赖，才让保护操作参与到 WS 的生命周期安排中。随后再比较不同硬件 fence 与延后释放的性能，才能得到既正确又保留优化收益的实现。</div><div class="notion-text notion-block-b2c1037a50ec4308a2d00415eb103037">最终采用的补丁很小，但决定它是否正确的证据来自完整链路：原位复现、缓冲区隔离、下一代数值匹配、生成代码检查，以及正确输出下的性能比较。</div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-4e9169ffee8d409c8a65cdd6e6172d74" data-id="4e9169ffee8d409c8a65cdd6e6172d74"><span><div id="4e9169ffee8d409c8a65cdd6e6172d74" class="notion-header-anchor"></div><a class="notion-hash-link" href="#4e9169ffee8d409c8a65cdd6e6172d74" title="参考资料"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title">参考资料</span></span></h3><ul class="notion-list notion-list-disc notion-block-b86a6bced195488ba3e41f0252a3c99d"><li><a class="notion-link" href="https://github.com/QwenLM/FlashQLA" target="_blank" rel="noopener noreferrer">FlashQLA</a>：本文讨论的算子项目；本文给出的补丁结构与实验数值来自本次实测，不代表该修复已合入上游。</li></ul><ul class="notion-list notion-list-disc notion-block-fe1d5d0e49fb424480c0ecf9dafaf3c4"><li><a class="notion-link" href="https://github.com/tile-ai/tilelang/blob/main/examples/deepseek_mla/README.md" target="_blank" rel="noopener noreferrer">TileLang 的 warp specialization 示例</a>：producer/consumer 与 TMA 搬运结构。</li></ul><ul class="notion-list notion-list-disc notion-block-9076b34ac32b46f0b020d58cc2277ed7"><li><a class="notion-link" href="https://docs.nvidia.com/cuda/archive/13.0.0/parallel-thread-execution/index.html" target="_blank" rel="noopener noreferrer">NVIDIA PTX ISA 9.0</a>：mbarrier、membar、fence 与 async proxy 的指令语义。</li></ul><ul class="notion-list notion-list-disc notion-block-3103f3cbdce3474cbb2b07f073f4aa9f"><li><a class="notion-link" href="https://docs.nvidia.com/cuda/inline-ptx-assembly/index.html#incorrect-optimization" target="_blank" rel="noopener noreferrer">NVIDIA Inline PTX：Incorrect Optimization</a>：volatile 与 memory clobber 的作用。</li></ul></main></div>]]></content:encoded>
        </item>
    </channel>
</rss>