TileLang 同步修复:一个 block 为什么上报了两次

两个 GPU block,各自写一个数,等对方写完,再一起算 1 + 2。预期结果是 [3, 3],实际却出现了 [1, 3]。

这是我在 TileLang 的 CuTeDSL 后端修的一个同步问题。原因很短:用 tid.x == 0 选取 block 代表线程,在二维、三维线程布局里,会选出不止一个。

PR #3400 已于 2026 年 10 月 10 日合并。改动不大,但这个失败用例能说明:同步代码里,一个"只执行一次"的判断是怎么失效的。

tid.x == 0,到底选中了几个线程?

这段 sync_grid() 用全局计数器记录已经到达 barrier 的 block 数量。每个 block 派一个线程,把计数加一。最后到达的 block 重置计数器,并推进 generation,通知其他 block 继续执行。

每个 block 必须只上报一次。

一维 block 里,tid.x == 0 没问题。换成 (32, 2),下面两个线程都满足这个条件:

text 复制代码
(tid.x, tid.y, tid.z) = (0, 0, 0)
(tid.x, tid.y, tid.z) = (0, 1, 0)

于是,同一个 block 会增加两次计数。如果整个 grid 只有两个 block,它就可能独自把"人到齐了"的条件凑出来,另一个 block 此时还没写数据。

原子加法挡不住这个问题。它能保证两次更新都被正确计入,却不会判断这两次更新是不是来自同一个 block。

用两个整数,把提前放行测出来

回归用例只启动两个 block。输入初始为零,block 0 写入 1,block 1 写入 2,同步后各自读取两者的和。

关键是让 block 1 晚一点:在它写数据之前,用有限的 device-clock 循环延迟,给 block 0 留出先到达 barrier 的时间。

下面是简化流程,省略了实际的线程判断、计时和编译代码:

text 复制代码
block 0:A[0] = 1 → sync_grid() → B[0] = A[0] + A[1]
block 1:延迟 → A[1] = 2 → sync_grid() → B[1] = A[0] + A[1]

预期:B = [3, 3]

当时在 RTX 5090 上运行原生 CuTeDSL 测试,基线的一维配置通过;换成 (32, 2) 后,得到了 [1, 3]。block 0 提前读到了仍为零的 A[1]。

这里需要故意错开到达时间。如果两个 block 碰巧都写完了,即使 barrier 有问题,答案也可能正确。测试中的读写还限定在 (0, 0, 0),避免同一个 block 内多个线程抢着写结果,让问题集中在同步行为上。

修复只动 leader 的选择

修复后的代表线程是 (0, 0, 0)。实际 PTX 把 %tid.x、%tid.y、%tid.z 按位或,再判断结果是否为零。三个坐标都为零,才允许上报。

写成容易读的伪代码,就是:

python 复制代码
# 原来的判断
is_leader = tid_x == 0

# 修复后的语义
is_leader = (tid_x == 0 and tid_y == 0 and tid_z == 0)

原有的计数器与 generation 协议、内存 fence 和 cooperative launch 要求都保留。这次只修正多维 block 选出多个 leader 的问题。实际修改和回归测试见 PR diff。

验证时,缓存也要关掉

回归测试覆盖四种线程布局:

  • 32:保留原本正常的一维路径。
  • (32, 2):在 y 方向展开。
  • (32, 1, 2):在 z 方向展开。
  • (16, 2, 2):同时在 y、z 方向展开。

每种配置调用 kernel 八次,每次清零输入输出,再检查结果。这检查了 barrier 状态在多次调用间的复用;如果不清零,提前读取上一轮留下的正确数据,也可能让错误漏过去。

修复后四种配置全部通过,并在第二个新进程中再次通过。基线用了 -x,在二维配置失败后就停止了,没有跑完另外两种配置。

最终 A/B 关闭了 kernel 缓存,避免修改 helper 后仍加载旧 cubin。环境是 RTX 5090、Python 3.12、Torch 2.11.0+cu130 和 CuTeDSL 4.7.0。两组都使用 TileLang 0.1.15 的原生编译器与运行时;基线 helper 与当时 main 的 994b44e 一致,候选组只替换修改过的 Python DSL helper。

这包含真实的编译和 cooperative GPU launch,但没有完整重编 main 的 C++ 部分。以上是 原 PR 中的历史测试记录,本文整理时没有重新跑 GPU 测试,也没有做性能比较。并发 stream 共用 barrier 状态是另一个问题,不在这次修复范围内。

这份回归最后只用了两个 block、两个整数。它要抓住的也只有一件事:数据还没准备好时,谁先越过了同步点。

原文发布于我的个人博客;本文使用 AI 辅助整理,代码和历史测试结果可在上述 PR 中核对。

相关推荐
2601_960356381 小时前
2027金融科技专业求职贷后管理岗:Excel、SQL如何对应业务任务?
人工智能
久曲健的测试窝1 小时前
Testin云测探索脚本自愈落地路径
运维·人工智能·科技·aigc
CHEEVEN_QY1 小时前
热仿真在液冷板设计中的应用价值与局限性客观评估
人工智能·算法
通信大模型1 小时前
IEEE JSTSP| FlashSAM:低空无线网络中多无人机Token通信的轻量级视觉模型
人工智能·深度学习·无人机
熊猫钓鱼>_>1 小时前
告别手工贴发票:增值税发票识别+核验,搭一条报销自动化流水线
人工智能·ai·职场和发展·自动化·大模型·llm·agent
安生生申1 小时前
ESP32-CAM视觉语音AI助手(千问平台):摄像头、INMP441、MAX98357与单片机数据接入
人工智能·单片机·嵌入式硬件
BSD_CGQ1 小时前
人形机器人触觉传感器为什么集中选 FSR 柔性薄膜方案?选型边界与国产替代现状
人工智能·机器人·压力传感器·柔性薄膜压力传感器
Phoenixtree_DongZhao1 小时前
[CVPR2026] 视觉 Mamba:RNN 与线性 Transformer 的统一视角
人工智能·rnn·transformer
碧海银沙音频科技研究院2 小时前
基于炬芯ATS3089X的ST LIS2DW12 三轴 MEMS 加速度传感器(G-Sensor)敲击按键功能实现
人工智能·嵌入式硬件