
Mojo GPU 内核的正确性检测在 MAX 仓库中落地 NVIDIA Compute Sanitizer 四通道扫描【免费下载链接】mojoThe Modular Platform (includes MAX Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojoNVIDIA Compute Sanitizer 是一套随 CUDA Toolkit 分发的运行时正确性检查工具能够在启动时对 CUDA 应用进行插桩监控每一次设备内存访问与同步事件以 Valgrind/AddressSanitizer 之于主机代码的方式报告越界访问、数据竞争、未初始化读取与屏障误用。本文以 MAX 仓库中的 compute_sanitizer 测试目录 为核心完整讲解其四工具memcheck/racecheck/initcheck/synccheck 两个 MAX 原生补充redzone、NaN-poison的落地设计、本地运行方法、环境变量旋钮以及 SM100/Blackwell 上已知的覆盖缺口与规避策略帮助你直接复用它为自己的 Mojo GPU 内核做机制级而非数值级的正确性验证。为什么需要 Compute Sanitizer从“数值效应”到“机制”的检测升级MAX 仓库的 GPU 内核测试以差分测试differential testing为主将 Mojo 内核的输出与参考实现逐元素比较。这套网络的局限在于它只能通过数值效应间接发现 bug而且常常是间歇性的——一次越界写如果恰好落在未被使用的内存上数值比较可能毫无反应。Compute Sanitizer 则不同它直接检测错误的机制mechanism并且是确定性的。当内核以 GPU line tables 编译时每一条发现都能被精确钉在 Mojo 源码的file:line上而不是停留在 SASS 指令层面。这正是该目录存在的根本动机差分测试回答“结果对不对”Sanitizer 回答“内存访问和同步行为本身是否合法”。工具速览一次调用只跑一个工具Compute Sanitizer 每次调用只能运行一个工具无法在一次 pass 中组合多个检查——这正是本目录将每个工具作为独立“车道”lane的原因。四个工具及它们对应的内核 bug 类别如下工具检测内容对 MAX 内核对应的 bug 类别memcheck全局/局部/共享内存的越界与未对齐访问以及泄漏与硬件错误UnsafePointer/LayoutTensor的 stride 越界、async_copy过度读取racecheck同一 block 内线程间的共享内存数据竞争SMEM 归约与cp.asynctile 周围缺失/错位的barrier()initcheck对未初始化设备全局内存的读取scratch/workspace/accumulator 缓冲区在完整写入前被读取synccheck同步原语__syncthreads、__syncwarp、命名屏障的误用SM90/SM100 warp-specialized 内核中发散或计数不匹配的屏障此外 Compute Sanitizer 还提供公开的APIcallback、patching、memory 组件用于构建自定义追踪工具但本目录并未使用。两个驱动整个目录设计的限制理解本目录的结构必须先接受两个硬性约束racecheck只能看到 block 内的竞争。全局内存上的跨 block 竞争——例如 MoE 的跨 block atomic-offset claim、split-K / stream-K 归约顺序、allreduce——对四个工具全部不可见。这个缺口由 stress_schedule.sh 用调度放大来弥补。因此一次全绿的racecheck并不等于竞争自由。MAX 的设备缓存分配器会掩盖小型越界和未初始化读取。每个缓冲区都从约 205 MB 的单一内存池中切出一个现实的 off-by-one 会落在池内Sanitizer 什么都看不到。因此memcheck/initcheck需要禁用内存池见下文“池禁用”小节。目录布局驱动脚本、放大工具与阳性对照max/kernels/test/gpu/compute_sanitizer/ ├── BUILD.bazel ├── README.md ├── positive_control_memcheck_oob.mojo # memcheck 车道的阳性对照 ├── positive_control_poison_uninit.mojo # NaN-poison 模式的阳性对照 ├── run_sanitizer.sh # 本地驱动跑四个 Sanitizer 工具 redzone └── stress_schedule.sh # 调度放大暴露 racecheck 看不到的跨 block 竞争run_sanitizer.sh —— 本地驱动。在开启 GPU line tables 的前提下以指定工具运行tags[gpu]的 Mojo 内核测试然后逐个 grep 每个test.log中的真实违规标记写出发现汇总。stress_schedule.sh —— 面向racecheck不可见的跨 block 竞争的“调度放大”工具让每个已通过参考校验的测试在多个并发副本 可切换的同步模式下反复运行只有在放大下才 flake 的目标即存在调度相关的竞争。positive_control_memcheck_oob.mojo —— 一个故意做全局越界写的内核。它只在memcheck下“通过”用于证明该车道确实能抓住真实 bug 并把它归属到 Mojo 源码行。被标记为manual因此永远不会进入常规测试套件。positive_control_poison_uninit.mojo —— 读取一个从未初始化的缓冲区在 NaN-poison 分配器开启时它会浮出 NaN证明未初始化读取之网确实在工作。同样为manual。两个阳性对照在 BUILD.bazel 中均以mojo_test定义并同时带有gpu与manual两个 tag——manual保证它们不会被//...或//max/kernels/test/gpu/...的通配展开波及从而绝不会在普通 GPU 套件或夜间 Sanitizer 车道中运行。本地运行命令与旋钮驱动脚本的调用形式为run_sanitizer.sh tool bazel target...其中tool可取memcheck | racecheck | initcheck | synccheck | redzone# tool ∈ memcheck | racecheck | initcheck | synccheck | redzone max/kernels/test/gpu/compute_sanitizer/run_sanitizer.sh memcheck \ //max/kernels/test/gpu/memory/... # 用阳性对照端到端验证车道本身是否有效 max/kernels/test/gpu/compute_sanitizer/run_sanitizer.sh memcheck \ //max/kernels/test/gpu/compute_sanitizer:positive_control_memcheck_oob.mojo.test可用的环境变量旋钮变量作用默认值CS_JOBS本地测试并行度6CS_GPU固定到某一块物理 GPU0CS_RESULTS_DIR结果输出目录repo/.derived/cs-findingsCOMPUTE_SANITIZERcompute-sanitizer二进制路径/usr/local/cuda/bin/compute-sanitizerSTRESS_REPS调度放大每个目标的独立重复次数40STRESS_JOBS调度放大并发副本数8驱动脚本的内部行为源码级从 run_sanitizer.sh 的实现可以看到几个关键细节构建配置统一使用--configci-local-gpu与 CI 共享配置一致将繁重的 Mojo 编译路由到远端/缓存构建机并以opt而非默认dbg构建——否则--debug-level line-tables编译大内核如 test_flash_attention会膨胀到约 200GB RSS 导致 OOM。运行约束--test_timeout900,2400,5400,10800分级超时--local_resourcesgpu-memory1000声明本地 GPU 内存资源否则测试无法在本地调度CUDA_VISIBLE_DEVICES${CS_GPU:-0}把扫描钉在单块物理 GPU 上方便多块 B200 上并发跑互不干扰的扫描。--run_under包装Sanitizer 车道通过--run_under$CS --tool $TOOL --target-processes all --launch-timeout 0 --error-exitcode 1 $EXTRA注入同时用--test_tag_filtersgpu,-filecheck排除 filecheck 测试它们无法在--run_under下运行且其中test_gather_nd_oob是故意的expect_crash越界测试会制造虚假的 memcheck 发现。发现提取脚本用一个精确的MARKERS正则Invalid __(global|shared|local|device)__|Race reported|Barrier error|Uninitialized __global__|misaligned address|is out of bounds|MemoryManager detected a device buffer (under|over)flow|CUDA_EXCEPTION|illegal memory access在bazel-testlogs/.../test.log中搜真实违规。刻意只认具体违规短语绝不看ERROR SUMMARY: N计数——该计数会把 SM100 上 cuBLASnvjet_sm100_*内核的 “Internal Sanitizer Error” 也算进去导致每个使用厂商参考实现的 GEMM 测试都误报。退出码即 CI 门禁发现任何真实违规标记即非零退出Internal Sanitizer Error 等其他 bazel 失败故意不设门禁它们属于需要人工 triage 的覆盖缺口而非回归。设计要点七个经得起推敲的工程决策1. 只要 line tables不要完整调试信息工具链以--mojocopt--debug-level --mojocoptline-tables编译。刻意不用full即-G-G会关闭ptxas优化并扰动时序既拖慢运行还可能掩盖我们正要抓的竞争line-tables保留-O同时仍能把发现归属到 Mojo 源码。2. 池禁用memcheck/initcheck的必需品这两个车道会加上落地构建标志--//:gpu_disable_memory_manager脚本自动完成让每个缓冲区变成 1:1 的cuMemAllocSanitizer 才能看到真实的逐缓冲区边界。racecheck/synccheck与池无关不加该标志。注意事项池关闭后向量化尾部的过度读取、以及假设缓冲区相邻的内核可能产生误报——需要 triage而不是第一天就上线门禁。3. 两个 MAX 原生补充无需 Compute Sanitizer约原生速度redzone模式——MODULAR_DEBUG_DEVICE_ALLOCATORout-of-bounds在释放时校验 guard 区域能在池内抓住小型全局越界写作为日常快速越界扫描。缺点抓不住越界读。NaN-poison 模式——MODULAR_DEBUG_DEVICE_ALLOCATORpoison-all把每一个新分配填满0xFFfloat 的 NaN 位模式未初始化读取会把 NaN 传播进输出从而被现有的差分测试抓住——无需initcheck的 copy-back 噪音即可堵住 uninit-use的缺口。这是分配器层、与类型无关的兜底对 graph tensor 而言graph driver 还提供uninitialized-poison——一种 dtype 感知、非 NaN 的哨兵值配一个插桩的 Mojo 加载检查源码定位精确但仅限 tensor。两者可组合使用。4. SM100 / Blackwell 覆盖缺口不做门禁、按车道解读Compute Sanitizer 无法对 cuBLAS 的nvjet_sm100_*内核插桩“didnt track the launch”因此调用厂商参考实现的 GEMM 测试会刷出成片的 Internal Sanitizer Error。由于发现 grep 只认特定违规短语这些缺口不会误报。优先在 Sanitizer 下跑无参考实现的 MAX 内核测试。5.initcheck车道配合-D FA4_WS_POISON1launch_workspacesm100/dispatch.mojo会故意不填 split-K 的o_partial/lse_partialworkspace空 partition 会跳过自己的 O store但仍写lse_p -inf由fa4_splitk_combine中scale ! 0的 select 用字面量 0 代替0 * garbage。initcheck 只看到 load所以未毒化的车道会把每个空 partition 报成未初始化读取。用 NaN 填充 workspace 一举两得既消除了噪音内存确实已初始化又把该车道变成对“无初始化契约”的真实判卷器——丢掉 select 或漏写-infLSE 现在都会把 NaN 传播进输出触发测试自身的 assert。该车道还会额外 grep_startup.mojo打印的 “Unhandled exception caught during execution” 标记排除CUDA call failed这类驱动/API 错误它们与 workspace 填充无关。6.synccheck在 SM100 上无法有效报告预期失败warp-specialized 内核用named_barrier2 * WARPGROUP_SIZE即bar.sync id, 256汇合两个 warpgroupPTX 允许参与 warp 在共享同一屏障名的不同bar.sync指令处到达而synccheck只接受来自单一指令地址的到达——于是它对整个 SM100 attention/MLA 家族约 48 个目标都报 “Divergent thread(s) in block”基本全是噪音。NVIDIA 只为memcheck、initcheck、racecheck提供--suppressions不支持synccheck无法过滤。该车道预期失败由 KERN-3536 跟踪解决前应逐车道解读扫描结果而不是当作单一 pass/fail。7. 没有 known-failure 清单一条车道要么报告干净、要么发现被修复不存在一份会随时间腐烂的“停用目标”清单——这也是为什么synccheck噪音不被压制压一次就意味着要维护一份永不更新的例外表。跨 block 竞争的补充武器调度放大racecheck的 intra-block 局限意味着 MoE atomic-offset claim、split-K/stream-K 归约顺序、allreduce 这些跨 block 全局内存竞争需要另一套思路。经典方法是调度放大Chao Peng, GPGPU 20在同一输入上跑大量不同执行调度检查与参考比较的输出是否保持不变。stress_schedule.sh 用三个无需改内核的旋钮放大调度且充分利用了“每个 MAX 内核测试都已自带参考自检”这一事实并发/争用—— 在单块 GPU 上一次跑 N 个测试副本--runs_per_testN --local_test_jobsN它们争夺 SM 与内存最大化扰动 block 到 SM 的调度与全局内存时序重复—— N 次独立运行暴露依赖时序的竞争同步模式——MODULAR_DEVICE_CONTEXT_SYNC_MODE1改变主机同步时机平移内核重叠。max/kernels/test/gpu/compute_sanitizer/stress_schedule.sh \ //max/kernels/test/gpu/algorithm/... # 面向 atomics/reduction 内核两遍 pass第一遍并发争用STRESS_JOBS份 ×STRESS_REPS次第二遍切到同步模式 串行重复。单跑通过但在放大下 FAIL/FLAKY 的目标即存在跨 block 竞争或其他调度相关的不确定性。脚本通过--runs_per_test_detects_flakes让 bazel 自己标记 FLAKY并从日志中提取FAILED|FLAKY汇成摘要需要说明的是干净结果只代表“这个调度预算没触发竞争”而非竞争自由的证明。阳性对照如何证明车道真的会咬人两个manual对照是整套设施可信度的基石。以 positive_control_memcheck_oob.mojo 为例内核先写一次界内dst[thread_id]保证内核真实启动、不被优化掉再写dst[thread_id n]制造一个教科书式的 off-by-one 缓冲区溢出。它同时被两条网捕获redzone 分配器在释放时校验 guard以及禁用缓存分配器之后的memcheck此时memcheck看到的是真实的 64 字节缓冲区边界。而 positive_control_poison_uninit.mojo 则读取一个只分配、从不初始化的src并拷入dst——poison 开启时isnan检查断言触发poison 关闭时它可能安静地“通过”于池垃圾之上恰好演示了差分测试网对这类 bug 的盲区。验证方式# 预期非零退出报出指向 dst[...] store 的 Invalid __global__ write ./bazelw test --configcs-memcheck \ //max/kernels/test/gpu/compute_sanitizer:positive_control_memcheck_oob.mojo.test # 预期poison 开启时断言触发dst 全为 NaN ./bazelw test --test_envMODULAR_DEBUG_DEVICE_ALLOCATORpoison-all \ --local_resourcesgpu-memory1000 --nocache_test_results \ //max/kernels/test/gpu/compute_sanitizer:positive_control_poison_uninit.mojo.test操作注意事项最后是几条直接影响可运行性的实践约束本地跑 GPU 测试需要--local_resourcesgpu-memory1000GPU 测试声明了gpu-memory资源只有远端执行器会跟踪需本地显式声明才能调度。排除mojo_filecheck_test无法在--run_under下运行与性能基准在racecheck下慢得病态。绝不要对 bazel 测试 action 使用kill -9——它会直接崩溃 bazel server。解读结果时按车道逐条看synccheck在 SM100 上预期全红KERN-3536覆盖缺口Internal Sanitizer Error与真实回归要分开 triage门禁只应挂在具体的违规标记上。小结一套可复用的 Mojo GPU 内核正确性分层综合来看这个目录展示了一套完整的分层检测策略memcheck 池禁用抓越界与未对齐访问initcheckFA4_WS_POISON抓未初始化读取并顺带判卷无初始化契约racecheck抓 block 内共享内存竞争redzone与 NaN-poison 提供两条接近原生速度的 MAX 原生日常防线stress_schedule.sh补上跨 block 调度的盲区两个阳性对照则为整条链路提供端到端的可信度验证。对任何在 MAX/Mojo 上开发 GPU 内核的团队这套“机制级检测 调度放大 阳性对照”的组合都值得直接照搬——它把“结果对不对”的差分测试升级成了“内存访问合不合法、同步行为正不正确”的确定性验证。【免费下载链接】mojoThe Modular Platform (includes MAX Mojo)项目地址: https://gitcode.com/GitHub_Trending/mo/mojo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考