3838_FP8_MM_C_DTYPE = "float32"
3939
4040
41- def _coop_tile_for (M : int , N : int , K : int ) -> tuple [int , int , int , int ]:
41+ def _coop_tile_for (M : int , N : int , K : int ) -> tuple [int , int , int , int ] | None :
4242 """Pick a Metal-4 cooperative-tensor (matmul2d) tile (BM, BN, BK, threads).
4343
4444 The cooperative ``mpp::tensor_ops::matmul2d`` micro-tile is 16x32x16: the
@@ -48,6 +48,14 @@ def _coop_tile_for(M: int, N: int, K: int) -> tuple[int, int, int, int]:
4848 warp tiles. These tiles were measured on M4 Max to beat both the legacy
4949 LUT-dot4 Path C accumulation and the pure-MLX FP8 reference across the
5050 production matmul shapes (128^3..1024^3); see the bench receipt.
51+
52+ Returns ``None`` when no legal cooperative tile cleanly divides ``M/N/K``
53+ (e.g. tiny or non-aligned shapes like 16x16x32 or 8x12x16). The cooperative
54+ matmul2d kernel does not bounds-guard a partial output tile, so launching it
55+ on an undividable shape returns numerically WRONG (column-shuffled) output.
56+ Callers must route a ``None`` result to the legacy dot4 kernel, which is
57+ correct for any shape -- this is shape-correct dispatch, NOT a silent
58+ gate-off (RULE #1): the production matmul shapes always yield a real tile.
5159 """
5260 # Ordered best-first per shape (measured on M4 Max). Each entry keeps the
5361 # cooperative-tensor threadgroup memory under the 32 KiB Metal budget that
@@ -79,8 +87,9 @@ def _coop_tile_for(M: int, N: int, K: int) -> tuple[int, int, int, int]:
7987 if shmem > shmem_budget :
8088 continue
8189 return bm , bn , bk , threads
82- # Fallback: smallest legal cooperative tile (single warp, 16x32x16).
83- return 16 , 32 , 16 , 32
90+ # No legal cooperative tile divides this shape: signal the caller to use the
91+ # shape-agnostic dot4 kernel instead of emitting a wrong partial-tile GEMM.
92+ return None
8493
8594
8695@dataclass (frozen = True )
@@ -247,12 +256,19 @@ def _make_scaled_matmul2d_kernel(
247256 (``BM % 16``, ``BN % 32``, ``BK % 16``, warp partition); the legacy dot4
248257 ``BM/BN/BK`` arguments are not necessarily legal cooperative tiles, so a
249258 measured-best cooperative tile is derived from the GEMM extents.
259+
260+ Returns ``None`` when ``_coop_tile_for`` reports that no legal cooperative
261+ tile divides ``M/N/K`` -- the caller must then use the shape-agnostic dot4
262+ kernel (the cooperative kernel would emit a wrong partial-tile result).
250263 """
251264 import tilelang
252265 from tilelang import language as T
253266 from tvm .target import Target
254267
255- bm , bn , bk , threads = _coop_tile_for (int (M ), int (N ), int (K ))
268+ tile = _coop_tile_for (int (M ), int (N ), int (K ))
269+ if tile is None :
270+ return None
271+ bm , bn , bk , threads = tile
256272
257273 globals ().update (
258274 T = T ,
@@ -276,27 +292,34 @@ def _make_scaled_matmul2d_kernel(
276292def _matmul2d_owner_output_enabled () -> bool :
277293 """Whether to route the owner-output GEMM through Metal-4 ``matmul2d``.
278294
279- The cooperative-tensor ``matmul2d`` kernel is numerically exact (maxdiff 0
280- vs the FP8 reference) and 1.5x-10x faster than both the legacy LUT-dot4
281- accumulation and the pure-MLX FP8 reference on M4+ when launched via a
282- Metal runtime that supports cooperative-tensor kernels (verified through
283- TileLang's torch / ``torch.mps`` execution backend).
284-
285- It is OFF by default because the owner-output contract here dispatches via
286- the **tvm-ffi** execution backend, and the tvm-ffi Metal runtime in the
287- pinned TileLang build cannot launch cooperative-tensor kernels: such
288- kernels lower to a host module the adapter cannot introspect
289- (``device_mod is None``), so the recovered launch config collapses to a
290- degenerate ``(1,1,1) x (1,1,1)`` grid and the kernel returns numerically
291- wrong output. Until that runtime path lands, the safe owner-output route
292- stays on the dot4 kernel. Set ``CPPMEGA_FP8_PATH_C_MATMUL2D=1`` to opt in
293- once the active TileLang build can launch cooperative kernels through
294- tvm-ffi.
295+ matmul2d is the **default and only** owner-output GEMM path (RULE #1: the
296+ clear path; no silent gate-off to dot4). The cooperative-tensor ``matmul2d``
297+ kernel is numerically exact (maxdiff 0 vs the FP8 reference) and is launched
298+ correctly by the production **tvm-ffi** owner-output route on Apple M4+:
299+ verified end-to-end through ``fp8_scaled_matmul_path_c_direct`` /
300+ ``NativeTileLangKernel`` at 128^3..1024^3 (maxdiff 0.00000, up to ~4.4x
301+ faster than the legacy LUT-dot4 accumulation at 1024^3).
302+
303+ This requires a TileLang build whose tvm-ffi Metal runtime launches the
304+ cooperative-tensor kernel with the recovered launch config (the
305+ ``_restore_metal_device_mod`` / ``_metal_launch_config`` recovery in
306+ ``tilelang/jit/adapter/tvm_ffi.py``) and commits + syncs the owner output
307+ buffer. Earlier the route was gated OFF because that runtime path collapsed
308+ the launch config to a degenerate ``(1,1,1) x (1,1,1)`` grid and the owner
309+ output came back zeroed; both are fixed in the live build, so the gate is
310+ removed and matmul2d is unconditional.
311+
312+ ``CPPMEGA_FP8_PATH_C_MATMUL2D`` is retained only as an explicit emergency
313+ opt-OUT (set it to ``0``/``false``/``off`` to force the legacy dot4 kernel
314+ on a host whose tvm-ffi runtime regresses). It is **not** consulted to
315+ silently fall back; matmul2d failures RAISE.
295316 """
296317 import os
297318
298319 val = os .environ .get (FP8_PATH_C_MATMUL2D_ENV , "" ).strip ().lower ()
299- return val in {"1" , "true" , "yes" , "on" }
320+ if val in {"0" , "false" , "no" , "off" }:
321+ return False
322+ return True
300323
301324
302325_FP8_MATMUL_TVM_FFI_KERNEL_CACHE : dict [
@@ -336,34 +359,40 @@ def _fp8_matmul_tvm_ffi_kernel_for(
336359
337360 target = _msl_transform ._as_metal_target (TILELANG_METAL_MATMUL_TARGET )
338361
339- # Prefer the Metal-4 cooperative-tensor matmul2d emission when explicitly
340- # enabled (and the active TileLang build can launch cooperative kernels
341- # through tvm-ffi): on M4+ it is numerically exact (maxdiff 0 vs the FP8
342- # reference) and 1.5x-10x faster than both the legacy LUT-dot4
343- # accumulation and the pure-MLX FP8 reference across 128^3..1024^3.
344- # Otherwise dispatch the dot4 owner-output kernel, which the pinned
345- # tvm-ffi Metal runtime launches correctly, so the owner-output contract
346- # never returns numerically wrong output. See _matmul2d_owner_output_enabled.
347- kernel = None
362+ # RULE #1: the Metal-4 cooperative-tensor matmul2d emission is the clear,
363+ # default owner-output GEMM path. It is launched correctly by the production
364+ # tvm-ffi owner-output route on Apple M4+ (verified end-to-end via
365+ # fp8_scaled_matmul_path_c_direct / NativeTileLangKernel): numerically exact
366+ # (maxdiff 0.00000 vs the FP8 reference) and up to ~4.4x faster than the
367+ # legacy LUT-dot4 accumulation at 1024^3. A matmul2d *compile* failure RAISES
368+ # here (caught and re-raised as FP8MatmulPathCDirectError by the caller) --
369+ # it does NOT silently fall back to dot4.
370+ #
371+ # The one shape-correct exception: when M/N/K admit no legal cooperative
372+ # tile (`_make_scaled_matmul2d_kernel` -> None, e.g. tiny/non-aligned shapes
373+ # like 16x16x32), the cooperative kernel cannot tile the output without
374+ # emitting a wrong partial-tile result, so the shape-agnostic dot4 kernel is
375+ # used. That is shape-correct dispatch, not a silent gate-off: every
376+ # production matmul shape (128^3..1024^3) yields a real cooperative tile and
377+ # runs matmul2d. dot4 is also used when the caller explicitly opts out via
378+ # CPPMEGA_FP8_PATH_C_MATMUL2D=0.
379+ coop_prim = None
348380 if _matmul2d_owner_output_enabled ():
349- try :
350- coop_prim = _make_scaled_matmul2d_kernel (
351- M = M ,
352- N = N ,
353- K = K ,
354- num_stages = num_stages ,
355- c_dtype = c_dtype ,
356- )
357- kernel = tilelang .compile (
358- coop_prim ,
359- target = target ,
360- execution_backend = "tvm_ffi" ,
361- out_idx = - 1 ,
362- )
363- except Exception :
364- kernel = None
365-
366- if kernel is None :
381+ coop_prim = _make_scaled_matmul2d_kernel (
382+ M = M ,
383+ N = N ,
384+ K = K ,
385+ num_stages = num_stages ,
386+ c_dtype = c_dtype ,
387+ )
388+ if coop_prim is not None :
389+ kernel = tilelang .compile (
390+ coop_prim ,
391+ target = target ,
392+ execution_backend = "tvm_ffi" ,
393+ out_idx = - 1 ,
394+ )
395+ else :
367396 prim = _make_scaled_matmul_kernel (
368397 M = M ,
369398 N = N ,
0 commit comments