diff --git a/README.md b/README.md index f296ee2..d583bda 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,58 @@ tuner_cache direct_repeat`),由 `python batch_summary.py --caliber 报告,不混进 dtype 那条解释里。`elapsed_before/after` 也进表:两步流程的全部依据 就是"一对 0.8 分钟对六轮 13~44 分钟"这个比例,那这一步该记下自己那一半。 +## 算子跑不起来时的排查清单 + +`NO_ROWS` / `NO_TRITON` 说的是"这一格没测出东西",不是"开关无作用"。按下面的顺序查, +每一条都踩过: + +| # | 症状 | 查什么 | 实例 | +|---|---|---|---| +| 1 | `PREFLIGHT_FAILED check=interpreter imports triton + flag_gems` | 用的是哪个 python。triton 通常来自 PYTHONPATH 指向的编译器构建 | 一次噪声标定因此废掉 12 格 + 160 次负载 run | +| 2 | `PREFLIGHT_FAILED check=benchmark file exists` | `OPS` 元素是 `OP:OP_FILE`,写反就找不到文件 | `test_perf_sort` 在 `test_sort.py` 里,要写 `perf_sort:sort` | +| 3 | `ValueError: Given dtype(s) '...' can't be supported by this op` | 该算子只吃特定 dtype,别全局写死 `DTYPES` | `fft` 只吃 complex64,被一刀切的 fp16 判成"跑不出行",而它是 cvt 消除量最大的算子 | +| 4 | `dynamic_func() missing ... BLOCK_M, BLOCK_N ...` 之类 JIT 深处的报错 | **benchmark 是不是导入了别家厂商的实现**(见下) | `router_gemm` 拿 NVIDIA Hopper 的 kernel 跑在 MUSA 上 | +| 5 | 跑出行但 `kernels=0` | 该算子在本后端没走 Triton,走的是 aten 或厂商库 | — | + +### 第 4 条:import 成功不等于这个后端支持 + +FlagGems 的厂商实现放在 `flag_gems/runtime/backend/_/ops/`。benchmark 里常见 +这种可用性判据: + +```python +try: + from flag_gems.runtime.backend._nvidia.hopper.ops.mm import router_gemm + ROUTER_GEMM_AVAILABLE = True # ← import 成功就置 True +except Exception: + ROUTER_GEMM_AVAILABLE = False +``` + +**`import` 只证明那个 `.py` 在磁盘上,它不碰设备,所以在任何后端上都成功。** gate 于是 +形同虚设:MUSA 上照样置 True,benchmark 拿 NVIDIA Hopper 的 kernel 跑在摩尔线程卡上, +最后死在 JIT 深处的 `dynamic_func() missing BLOCK_M, BLOCK_N, BLOCK_K, SPLIT_K`—— +**那个报错完全指不向真正的原因**。而 `_mthreads/ops/mm.py` 下明明有一份能用的实现, +benchmark 从头到尾没导入过。 + +正确写法是先判 vendor(同一个文件里 Cambricon 那支就是对的): + +```python +if base.vendor_name == "nvidia": + try: + from flag_gems.runtime.backend._nvidia.hopper.ops.mm import router_gemm + ROUTER_GEMM_AVAILABLE = True + except Exception: + pass +``` + +`_device_setup_plugin` 会在 session 结束时扫 `sys.modules`,载入过别家厂商实现时打: + +``` +FOREIGN_VENDOR_MODULE active=mthreads loaded=nvidia +``` + +**它不拦,只报**——载入别家模块本身不一定有问题(有些是共享工具),但一个算子结果可疑 +时,这行是第一个该看的地方。 + ## A/B 对比测试标准流程 ```bash diff --git a/_device_setup_plugin.py b/_device_setup_plugin.py index e9dd080..82e0d1e 100644 --- a/_device_setup_plugin.py +++ b/_device_setup_plugin.py @@ -67,3 +67,46 @@ def pytest_configure(config): _setup_at_import() + +# FlagGems 的厂商实现按 flag_gems.runtime.backend._.ops.* 组织。benchmark +# 常写成 `try: import <某厂商实现>; AVAILABLE = True`——但 import 只证明那个 .py 在 +# 磁盘上,它不碰设备,所以在任何后端上都会成功。于是 gate 形同虚设,测出来的是别家 +# 的 kernel 跑在本机上。router_gemm 就是这样:test_router_gemm.py 无条件导入 +# _nvidia.hopper 版,在 MUSA 上 gate 照过,最后死在 JIT 深处的 +# "dynamic_func() missing BLOCK_M, BLOCK_N, BLOCK_K, SPLIT_K"——那个报错完全指不 +# 向真正的原因,而 _mthreads 下明明有一份能用的实现。 +# +# 这里不拦,只报:跑完看一眼载入过谁家的实现。 +_VENDOR_MOD_PREFIX = "flag_gems.runtime.backend._" + + +def foreign_vendor_modules(active_vendor: str, modules=None) -> list: + """本次进程里载入过的、不属于当前厂商的 FlagGems 后端模块。""" + if modules is None: + modules = sys.modules + out = set() + for name in list(modules): + if not name.startswith(_VENDOR_MOD_PREFIX): + continue + # 只保留厂商根,_nvidia.hopper.ops.mm 与 _nvidia.ops.mm 算同一条。 + # 按段比而不是按前缀:前缀会让 __something 被误认成自家。 + root = name[len(_VENDOR_MOD_PREFIX):].split(".")[0] + if root != active_vendor: + out.add(root) + return sorted(out) + + +def pytest_sessionfinish(session, exitstatus): + from _term_style import tag + try: + vendor = get_backend().flaggems_vendor + except Exception: + return + foreign = foreign_vendor_modules(vendor) + if not foreign: + return + print(f"{tag('[device-setup-plugin]')} FOREIGN_VENDOR_MODULE active={vendor} " + f"loaded={','.join(foreign)}", file=sys.stderr, flush=True) + print(f"{tag('[device-setup-plugin]')} 本次载入了别家厂商的 FlagGems 实现。" + "如果这个算子的结果可疑,先查 benchmark 的可用性判据是不是只做了 import " + "而没判 vendor_name", file=sys.stderr, flush=True) diff --git a/test_backends.py b/test_backends.py index 0f6baed..d8bd9dc 100644 --- a/test_backends.py +++ b/test_backends.py @@ -614,5 +614,50 @@ class TelemetryTests(unittest.TestCase): os.environ["MUSA_VISIBLE_DEVICES"] = old +class ForeignVendorModuleTests(unittest.TestCase): + """benchmark 的可用性判据常写成 `try: import <厂商实现>; AVAILABLE = True`。 + + import 只证明那个 .py 在磁盘上——它不碰设备,所以在任何后端上都成功,gate 于是 + 形同虚设。router_gemm 就这样让 NVIDIA Hopper 的 kernel 跑在了 MUSA 卡上,最后 + 死在 JIT 深处的 "dynamic_func() missing BLOCK_M ...",报错完全指不向真正的原因。 + """ + + P = "flag_gems.runtime.backend._" + + def _mods(self, *names): + return {self.P + n: object() for n in names} | {"torch": object()} + + def test_only_own_vendor_is_clean(self): + from _device_setup_plugin import foreign_vendor_modules + mods = self._mods("mthreads", "mthreads.ops.mm") + self.assertEqual(foreign_vendor_modules("mthreads", mods), []) + + def test_foreign_vendor_is_reported_once_per_root(self): + from _device_setup_plugin import foreign_vendor_modules + # 同一厂商的多个子模块只报一条 + mods = self._mods("mthreads.ops.mm", + "nvidia.hopper.ops.mm", "nvidia.ops.mm", "nvidia") + self.assertEqual(foreign_vendor_modules("mthreads", mods), ["nvidia"]) + + def test_several_foreign_vendors(self): + from _device_setup_plugin import foreign_vendor_modules + mods = self._mods("mthreads", "nvidia.ops.mm", "cambricon.ops.mm") + self.assertEqual(foreign_vendor_modules("mthreads", mods), + ["cambricon", "nvidia"]) + + def test_a_vendor_whose_name_extends_ours_is_still_foreign(self): + # 按前缀比会把 _mthreads_extra 认成自家 + from _device_setup_plugin import foreign_vendor_modules + mods = self._mods("mthreads", "mthreads_extra.ops") + self.assertEqual(foreign_vendor_modules("mthreads", mods), + ["mthreads_extra"]) + + def test_unrelated_modules_are_ignored(self): + from _device_setup_plugin import foreign_vendor_modules + mods = {"torch": object(), "triton": object(), + "flag_gems.ops.mm": object()} + self.assertEqual(foreign_vendor_modules("mthreads", mods), []) + + if __name__ == "__main__": unittest.main()