nbxtensor 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. nbxtensor/THIRD_PARTY_NOTICES.md +287 -0
  2. nbxtensor/__init__.py +17 -0
  3. nbxtensor/backend/__init__.py +1 -0
  4. nbxtensor/backend/capabilities.py +207 -0
  5. nbxtensor/backend/cpu/__init__.py +1 -0
  6. nbxtensor/backend/cpu/blas.py +102 -0
  7. nbxtensor/backend/cpu/pool.py +204 -0
  8. nbxtensor/backend/cpu_backend.py +162 -0
  9. nbxtensor/backend/device_facts.py +105 -0
  10. nbxtensor/backend/device_files.py +129 -0
  11. nbxtensor/backend/device_names.py +18 -0
  12. nbxtensor/backend/devices/amd/cdna.yml +228 -0
  13. nbxtensor/backend/devices/amd/cdna2.yml +228 -0
  14. nbxtensor/backend/devices/amd/cdna3.yml +227 -0
  15. nbxtensor/backend/devices/apple/apple_m1.yml +266 -0
  16. nbxtensor/backend/devices/apple/apple_m1_max.yml +266 -0
  17. nbxtensor/backend/devices/apple/apple_m1_pro.yml +266 -0
  18. nbxtensor/backend/devices/apple/apple_m1_ultra.yml +266 -0
  19. nbxtensor/backend/devices/apple/apple_m2.yml +266 -0
  20. nbxtensor/backend/devices/apple/apple_m2_max.yml +266 -0
  21. nbxtensor/backend/devices/apple/apple_m2_pro.yml +266 -0
  22. nbxtensor/backend/devices/apple/apple_m2_ultra.yml +266 -0
  23. nbxtensor/backend/devices/apple/apple_m3.yml +266 -0
  24. nbxtensor/backend/devices/apple/apple_m3_max.yml +266 -0
  25. nbxtensor/backend/devices/apple/apple_m3_pro.yml +266 -0
  26. nbxtensor/backend/devices/apple/apple_m3_ultra.yml +266 -0
  27. nbxtensor/backend/devices/apple/apple_m4.yml +266 -0
  28. nbxtensor/backend/devices/apple/apple_m4_max.yml +266 -0
  29. nbxtensor/backend/devices/apple/apple_m4_pro.yml +308 -0
  30. nbxtensor/backend/devices/apple/apple_m5.yml +269 -0
  31. nbxtensor/backend/devices/apple/apple_m5_max.yml +269 -0
  32. nbxtensor/backend/devices/apple/apple_m5_pro.yml +269 -0
  33. nbxtensor/backend/devices/apple/apple_m5_ultra.yml +269 -0
  34. nbxtensor/backend/devices/apple/apple_m6.yml +269 -0
  35. nbxtensor/backend/devices/apple/apple_silicon.yml +263 -0
  36. nbxtensor/backend/devices/nvidia/ampere.yml +214 -0
  37. nbxtensor/backend/devices/nvidia/hopper.yml +213 -0
  38. nbxtensor/backend/devices/nvidia/volta.yml +411 -0
  39. nbxtensor/backend/faults.py +151 -0
  40. nbxtensor/backend/host_memory.py +185 -0
  41. nbxtensor/backend/launcher.py +2350 -0
  42. nbxtensor/backend/launcher_contract.py +429 -0
  43. nbxtensor/backend/metal_backend.py +661 -0
  44. nbxtensor/backend/metal_device.py +1274 -0
  45. nbxtensor/backend/process_memory.py +73 -0
  46. nbxtensor/backend/replay_graph.py +322 -0
  47. nbxtensor/backend/runtime.py +409 -0
  48. nbxtensor/backend/triton_ext_driver.py +735 -0
  49. nbxtensor/dtype/__init__.py +1 -0
  50. nbxtensor/dtype/convert.py +107 -0
  51. nbxtensor/dtype/dtypes.yml +70 -0
  52. nbxtensor/dtype/itemsize.py +166 -0
  53. nbxtensor/dtype/nbx_dtype.py +72 -0
  54. nbxtensor/dtype/operands.py +158 -0
  55. nbxtensor/dtype/outputs.py +64 -0
  56. nbxtensor/dtype/policy.py +155 -0
  57. nbxtensor/dtype/policy.yml +51 -0
  58. nbxtensor/dtype/promotion.py +112 -0
  59. nbxtensor/host.py +55 -0
  60. nbxtensor/io/__init__.py +1 -0
  61. nbxtensor/io/read_rate.py +197 -0
  62. nbxtensor/io/safetensors.py +314 -0
  63. nbxtensor/memory/__init__.py +9 -0
  64. nbxtensor/memory/allocator.py +1988 -0
  65. nbxtensor/memory/arena.py +75 -0
  66. nbxtensor/memory/device_memory.py +150 -0
  67. nbxtensor/memory/device_transfer.py +154 -0
  68. nbxtensor/memory/layout.py +43 -0
  69. nbxtensor/memory/memory_pool.py +191 -0
  70. nbxtensor/memory/metal.py +96 -0
  71. nbxtensor/memory/oom_advice.py +91 -0
  72. nbxtensor/memory/pointer_tables.py +323 -0
  73. nbxtensor/memory/pool.py +507 -0
  74. nbxtensor/memory/shadow.py +43 -0
  75. nbxtensor/memory/slab.py +273 -0
  76. nbxtensor/memory/trace_replay.py +122 -0
  77. nbxtensor/memory/traces/sana-video-2b-720p.16g.2026-11-02.tsv +13610 -0
  78. nbxtensor/memory/vmm.py +118 -0
  79. nbxtensor/ops/__init__.py +1 -0
  80. nbxtensor/ops/_autotune_policy.py +90 -0
  81. nbxtensor/ops/_common.py +32 -0
  82. nbxtensor/ops/_configs.py +1041 -0
  83. nbxtensor/ops/_cost.py +231 -0
  84. nbxtensor/ops/abs.py +17 -0
  85. nbxtensor/ops/adaptive_avg_pool2d.py +161 -0
  86. nbxtensor/ops/add.py +96 -0
  87. nbxtensor/ops/addcdiv.py +29 -0
  88. nbxtensor/ops/addcmul.py +29 -0
  89. nbxtensor/ops/addmv_op.py +64 -0
  90. nbxtensor/ops/addr.py +68 -0
  91. nbxtensor/ops/all_reduce.py +83 -0
  92. nbxtensor/ops/amax.py +40 -0
  93. nbxtensor/ops/any_reduce.py +83 -0
  94. nbxtensor/ops/arange_op.py +27 -0
  95. nbxtensor/ops/argmax.py +107 -0
  96. nbxtensor/ops/argmin.py +93 -0
  97. nbxtensor/ops/atan2.py +71 -0
  98. nbxtensor/ops/attention_wrappers.py +1648 -0
  99. nbxtensor/ops/autotune_bucket.py +137 -0
  100. nbxtensor/ops/autotune_cache.py +430 -0
  101. nbxtensor/ops/autotune_certified.py +1262 -0
  102. nbxtensor/ops/autotune_refusals.py +380 -0
  103. nbxtensor/ops/autotune_settings.py +58 -0
  104. nbxtensor/ops/avg_pool2d.py +73 -0
  105. nbxtensor/ops/baddbmm_op.py +132 -0
  106. nbxtensor/ops/batch_norm.py +163 -0
  107. nbxtensor/ops/binary_strided.py +196 -0
  108. nbxtensor/ops/bitwise_and.py +26 -0
  109. nbxtensor/ops/bitwise_left_shift.py +47 -0
  110. nbxtensor/ops/bitwise_not.py +24 -0
  111. nbxtensor/ops/bitwise_or.py +26 -0
  112. nbxtensor/ops/bitwise_right_shift.py +47 -0
  113. nbxtensor/ops/bucketize.py +37 -0
  114. nbxtensor/ops/cat_op.py +87 -0
  115. nbxtensor/ops/celu.py +20 -0
  116. nbxtensor/ops/clamp.py +24 -0
  117. nbxtensor/ops/clamp_min.py +22 -0
  118. nbxtensor/ops/conv2d.py +265 -0
  119. nbxtensor/ops/conv2d_m8n8k4.py +165 -0
  120. nbxtensor/ops/conv_depthwise2d.py +154 -0
  121. nbxtensor/ops/conv_transpose2d.py +148 -0
  122. nbxtensor/ops/conv_wrappers.py +1482 -0
  123. nbxtensor/ops/copy.py +18 -0
  124. nbxtensor/ops/copy_op.py +39 -0
  125. nbxtensor/ops/cos.py +17 -0
  126. nbxtensor/ops/count_nonzero.py +64 -0
  127. nbxtensor/ops/cross_entropy.py +67 -0
  128. nbxtensor/ops/cummax.py +109 -0
  129. nbxtensor/ops/cummin.py +105 -0
  130. nbxtensor/ops/cumsum.py +244 -0
  131. nbxtensor/ops/decode_attn_vec.py +162 -0
  132. nbxtensor/ops/depthwise_conv2d.py +132 -0
  133. nbxtensor/ops/dequant_gemv.py +190 -0
  134. nbxtensor/ops/dft.py +67 -0
  135. nbxtensor/ops/dispatch.py +1228 -0
  136. nbxtensor/ops/div.py +75 -0
  137. nbxtensor/ops/dot_op.py +67 -0
  138. nbxtensor/ops/dropout.py +23 -0
  139. nbxtensor/ops/dtype_convert.py +42 -0
  140. nbxtensor/ops/elementwise_launch.py +81 -0
  141. nbxtensor/ops/elementwise_wrappers.py +2315 -0
  142. nbxtensor/ops/elu.py +20 -0
  143. nbxtensor/ops/embedding.py +69 -0
  144. nbxtensor/ops/eq.py +32 -0
  145. nbxtensor/ops/erf.py +18 -0
  146. nbxtensor/ops/exp.py +17 -0
  147. nbxtensor/ops/exp2.py +18 -0
  148. nbxtensor/ops/fft_op.py +125 -0
  149. nbxtensor/ops/fill_op.py +20 -0
  150. nbxtensor/ops/flash_attention.py +307 -0
  151. nbxtensor/ops/flash_attention_m8n8k4.py +384 -0
  152. nbxtensor/ops/flash_decode.py +78 -0
  153. nbxtensor/ops/flip_op.py +60 -0
  154. nbxtensor/ops/floor.py +90 -0
  155. nbxtensor/ops/full_op.py +24 -0
  156. nbxtensor/ops/fused_moe.py +417 -0
  157. nbxtensor/ops/fused_moe_m8n8k4.py +99 -0
  158. nbxtensor/ops/fused_upsample_conv.py +413 -0
  159. nbxtensor/ops/gather_op.py +52 -0
  160. nbxtensor/ops/ge.py +32 -0
  161. nbxtensor/ops/gelu.py +31 -0
  162. nbxtensor/ops/gemv_vec.py +80 -0
  163. nbxtensor/ops/glu.py +43 -0
  164. nbxtensor/ops/grid_sampler.py +145 -0
  165. nbxtensor/ops/group_norm_tiled.py +67 -0
  166. nbxtensor/ops/groupnorm.py +123 -0
  167. nbxtensor/ops/gt.py +32 -0
  168. nbxtensor/ops/hardsigmoid.py +18 -0
  169. nbxtensor/ops/hardswish.py +19 -0
  170. nbxtensor/ops/im2col.py +57 -0
  171. nbxtensor/ops/index_add.py +95 -0
  172. nbxtensor/ops/index_put_op.py +92 -0
  173. nbxtensor/ops/index_select.py +121 -0
  174. nbxtensor/ops/index_wrappers.py +770 -0
  175. nbxtensor/ops/isfinite.py +20 -0
  176. nbxtensor/ops/isinf.py +20 -0
  177. nbxtensor/ops/isnan.py +18 -0
  178. nbxtensor/ops/launch_keys.py +1054 -0
  179. nbxtensor/ops/launch_observer.py +54 -0
  180. nbxtensor/ops/layernorm.py +71 -0
  181. nbxtensor/ops/le.py +35 -0
  182. nbxtensor/ops/leaky_relu.py +19 -0
  183. nbxtensor/ops/lerp.py +76 -0
  184. nbxtensor/ops/linalg_wrappers.py +1370 -0
  185. nbxtensor/ops/linspace_op.py +47 -0
  186. nbxtensor/ops/log.py +17 -0
  187. nbxtensor/ops/log_sigmoid.py +23 -0
  188. nbxtensor/ops/logical_and.py +26 -0
  189. nbxtensor/ops/logical_not.py +24 -0
  190. nbxtensor/ops/logical_or.py +26 -0
  191. nbxtensor/ops/logical_xor.py +37 -0
  192. nbxtensor/ops/loss_wrappers.py +282 -0
  193. nbxtensor/ops/lt.py +32 -0
  194. nbxtensor/ops/masked_fill.py +21 -0
  195. nbxtensor/ops/matmul.py +377 -0
  196. nbxtensor/ops/matmul_m8n8k4.py +235 -0
  197. nbxtensor/ops/max_pool2d.py +74 -0
  198. nbxtensor/ops/max_pool3d.py +93 -0
  199. nbxtensor/ops/max_reduce.py +91 -0
  200. nbxtensor/ops/maximum.py +18 -0
  201. nbxtensor/ops/mean.py +40 -0
  202. nbxtensor/ops/min_reduce.py +91 -0
  203. nbxtensor/ops/minimum.py +18 -0
  204. nbxtensor/ops/mish.py +22 -0
  205. nbxtensor/ops/mma_m8n8k4.py +152 -0
  206. nbxtensor/ops/moe_align.py +195 -0
  207. nbxtensor/ops/moe_decode_vec.py +374 -0
  208. nbxtensor/ops/moe_device_residency.py +141 -0
  209. nbxtensor/ops/moe_wrappers.py +345 -0
  210. nbxtensor/ops/mse_loss.py +79 -0
  211. nbxtensor/ops/mul.py +51 -0
  212. nbxtensor/ops/nan_to_num.py +23 -0
  213. nbxtensor/ops/ne.py +32 -0
  214. nbxtensor/ops/neg.py +17 -0
  215. nbxtensor/ops/nllloss.py +124 -0
  216. nbxtensor/ops/nonzero.py +33 -0
  217. nbxtensor/ops/nonzero_op.py +62 -0
  218. nbxtensor/ops/norm_wrappers.py +579 -0
  219. nbxtensor/ops/ones_op.py +20 -0
  220. nbxtensor/ops/oracles/__init__.py +6 -0
  221. nbxtensor/ops/oracles/conv2d_fp64.py +450 -0
  222. nbxtensor/ops/pad_op.py +159 -0
  223. nbxtensor/ops/paged_decode_attn.py +215 -0
  224. nbxtensor/ops/pixel_shuffle.py +144 -0
  225. nbxtensor/ops/pixel_unshuffle.py +72 -0
  226. nbxtensor/ops/pow.py +137 -0
  227. nbxtensor/ops/prod.py +81 -0
  228. nbxtensor/ops/rand_op.py +52 -0
  229. nbxtensor/ops/random_wrappers.py +366 -0
  230. nbxtensor/ops/reciprocal.py +17 -0
  231. nbxtensor/ops/reduction_wrappers.py +845 -0
  232. nbxtensor/ops/relu.py +18 -0
  233. nbxtensor/ops/remainder.py +46 -0
  234. nbxtensor/ops/repeat_interleave.py +48 -0
  235. nbxtensor/ops/repeat_op.py +132 -0
  236. nbxtensor/ops/residual_chain.py +374 -0
  237. nbxtensor/ops/rmsnorm.py +68 -0
  238. nbxtensor/ops/rope.py +142 -0
  239. nbxtensor/ops/rsqrt.py +17 -0
  240. nbxtensor/ops/scaled_softmax.py +96 -0
  241. nbxtensor/ops/scatter_op.py +139 -0
  242. nbxtensor/ops/screen_oracle.py +479 -0
  243. nbxtensor/ops/selu.py +22 -0
  244. nbxtensor/ops/shape_wrappers.py +1032 -0
  245. nbxtensor/ops/sigmoid.py +19 -0
  246. nbxtensor/ops/silu.py +27 -0
  247. nbxtensor/ops/sin.py +17 -0
  248. nbxtensor/ops/softmax.py +169 -0
  249. nbxtensor/ops/softplus.py +27 -0
  250. nbxtensor/ops/sort_op.py +304 -0
  251. nbxtensor/ops/spectral_wrappers.py +481 -0
  252. nbxtensor/ops/sqrt.py +17 -0
  253. nbxtensor/ops/stack_op.py +84 -0
  254. nbxtensor/ops/std.py +113 -0
  255. nbxtensor/ops/strided_copy.py +200 -0
  256. nbxtensor/ops/sub.py +35 -0
  257. nbxtensor/ops/sum.py +40 -0
  258. nbxtensor/ops/tan.py +19 -0
  259. nbxtensor/ops/tanh.py +21 -0
  260. nbxtensor/ops/threshold.py +19 -0
  261. nbxtensor/ops/tiling.yml +91 -0
  262. nbxtensor/ops/tiling_policy.py +43 -0
  263. nbxtensor/ops/tiling_sizes.py +370 -0
  264. nbxtensor/ops/topk.py +267 -0
  265. nbxtensor/ops/trace_op.py +64 -0
  266. nbxtensor/ops/tril.py +64 -0
  267. nbxtensor/ops/triu.py +62 -0
  268. nbxtensor/ops/unfold_op.py +37 -0
  269. nbxtensor/ops/unique_op.py +97 -0
  270. nbxtensor/ops/upsample_bilinear2d.py +106 -0
  271. nbxtensor/ops/upsample_nearest2d.py +52 -0
  272. nbxtensor/ops/upsample_nearest3d.py +61 -0
  273. nbxtensor/ops/var.py +136 -0
  274. nbxtensor/ops/vector_norm.py +216 -0
  275. nbxtensor/ops/weight_norm.py +104 -0
  276. nbxtensor/ops/where.py +39 -0
  277. nbxtensor/ops/wrappers.py +825 -0
  278. nbxtensor/ops/zeros_op.py +20 -0
  279. nbxtensor/quant/__init__.py +1 -0
  280. nbxtensor/quant/quantized_tensor.py +187 -0
  281. nbxtensor/random/__init__.py +1 -0
  282. nbxtensor/random/mt_randn.py +154 -0
  283. nbxtensor/random/rng_pin.py +59 -0
  284. nbxtensor/random/rng_stream.py +118 -0
  285. nbxtensor/random/seeded_draw.py +223 -0
  286. nbxtensor/shape/__init__.py +157 -0
  287. nbxtensor/tensor.py +1867 -0
  288. nbxtensor-0.0.1.dist-info/METADATA +379 -0
  289. nbxtensor-0.0.1.dist-info/RECORD +291 -0
  290. nbxtensor-0.0.1.dist-info/WHEEL +4 -0
  291. nbxtensor-0.0.1.dist-info/licenses/LICENSE +189 -0
@@ -0,0 +1,377 @@
1
+ """Matrix multiplication — pure @triton.jit kernel with autotune.
2
+
3
+ Ported from Triton official tutorial (BSD license)
4
+ https://triton-lang.org/main/getting-started/tutorials/03-matrix-multiplication.html
5
+ adapted for NeuroBrix:
6
+ - NBXTensor inputs (handled by wrappers, kernel sees raw ptrs/strides)
7
+ - Volta-aware static dtype path (PROMOTE_B + IEEE_PRECISION constexpr)
8
+ - @triton.autotune across 18 configs (Phase 1.5, 2026-05): the only
9
+ proven path to ≥70% cuBLAS HMMA on Sana DiT shapes — see CLAUDE.md
10
+ "Autotune policy" section for the doctrinal exception that allows
11
+ # PROMOTE_A (and PROMOTE_BIAS) are NOT part of the key: the directory's certified
12
+ # settings are indexed by the shape and the pre-existing flags, and a promotion of
13
+ # an operand on load changes neither the tiles' legality nor the accumulation
14
+ # order — the same setting applies; keying on it made every matmul of the tree a
15
+ # miss (5,628 entries unserved, a runtime sweep per key, 2026-09-07 lever 2).
16
+ @triton.autotune on mm/bmm/addmm/conv2d.
17
+ - tl.dot 3-arg HMMA-FMA fused form
18
+ - tl.assume integer-analyzer hints
19
+ - in-kernel cast accum → output dtype
20
+ Handles mm (2D) + addmm (with bias). bmm handled in wrappers via batch loop.
21
+ """
22
+
23
+ import triton
24
+
25
+ from nbxtensor.ops._configs import arch_smem_budget, configs_within_smem_budget, nbx_autotune
26
+ import triton.language as tl
27
+
28
+ from nbxtensor.ops._autotune_policy import maybe_pin_single, is_matmul_pinned
29
+ from nbxtensor.ops._common import sigmoid, tanh_fn
30
+
31
+ # Vertical-fusion epilogue codes (fusion_vertical pass, EXACT gate).
32
+ # 0 = none, 1 = silu, 2 = gelu exact, 3 = gelu tanh. The epilogue is a
33
+ # tl.constexpr NOT in the autotune key: both fused and unfused variants
34
+ # share ONE tuning-cache entry per (M, N, K, IEEE, PROMOTE_B), so the
35
+ # selected config — and therefore the tl.dot accumulation order — is
36
+ # identical by construction (the two-leg byte-exactness contract: the
37
+ # R16 research showed rounding emulation alone is insufficient if the
38
+ # fused and unfused kernels can tune to different BLOCK_K).
39
+ #
40
+ # Byte-exactness emulation: the unfused pair is
41
+ # matmul_kernel: fp32 acc -> cast to C dtype -> store (round 1)
42
+ # silu/gelu kernel: load C dtype -> .to(fp32) -> f(x) -> store (round 2)
43
+ # The fused epilogue reproduces BOTH rounding points in-register:
44
+ # acc -> .to(C dtype) -> .to(fp32) -> f(x) -> store
45
+ # using the SAME formula text and the SAME _common helpers as the
46
+ # standalone kernels, so the arithmetic lowers identically.
47
+ EPILOGUE_NONE: int = 0
48
+ EPILOGUE_SILU: int = 1
49
+ EPILOGUE_GELU_EXACT: int = 2
50
+ EPILOGUE_GELU_TANH: int = 3
51
+
52
+
53
+ # Per-architecture autotune configs (tutorial pattern
54
+ # `get_cuda_autotune_config()` adapted to NeuroBrix: gate by detected
55
+ # compute capability so each hardware explores ONLY its viable subspace).
56
+ # Volta sm_70 has 96 KB SMEM/SM (vs 192 KB sm_80 / 228 KB sm_90); large
57
+ # blocks (BM≥128 BK≥64 warps=8) saturate SMEM → register spill →
58
+ # catastrophic perf (98-145 ms measured Phase 1.5 Step 2 FlagGems
59
+ # bench). The Volta-viable subspace is restricted to BM∈{32,64},
60
+ # BN∈{32,64,128}, BK∈{32,64}, warps∈{2,4}, stages∈{2..5} — ~20
61
+ # combinations giving the autotuner a denser space of fitting configs.
62
+ # Ampere+ (sm_80+) gates BM/BN/BK larger as the hardware supports it.
63
+
64
+ _MATMUL_AUTOTUNE_VOLTA = [
65
+ # BM=32 row
66
+ triton.Config({'BLOCK_M': 32, 'BLOCK_N': 32, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=2),
67
+ triton.Config({'BLOCK_M': 32, 'BLOCK_N': 32, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=4, num_warps=2),
68
+ triton.Config({'BLOCK_M': 32, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=2),
69
+ triton.Config({'BLOCK_M': 32, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=5, num_warps=2),
70
+ triton.Config({'BLOCK_M': 32, 'BLOCK_N': 64, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=3, num_warps=4),
71
+ triton.Config({'BLOCK_M': 32, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=3, num_warps=4),
72
+ # BM=64 row (the historical NeuroBrix default neighborhood)
73
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 32, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=5, num_warps=2),
74
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 32, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=4, num_warps=2),
75
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=2, num_warps=4),
76
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=3, num_warps=4),
77
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
78
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 64, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=3, num_warps=4),
79
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 64, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=4, num_warps=4),
80
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=2, num_warps=4),
81
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=3, num_warps=4),
82
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
83
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=3, num_warps=4),
84
+ ]
85
+
86
+ # Ampere/Hopper subspace (sm_80+) — larger blocks allowed thanks to
87
+ # SMEM doubling and tensor-core throughput. Tutorial canonical configs.
88
+ _MATMUL_AUTOTUNE_AMPERE_PLUS = [
89
+ triton.Config({'BLOCK_M': 128, 'BLOCK_N': 256, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=3, num_warps=8),
90
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 256, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
91
+ triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
92
+ triton.Config({'BLOCK_M': 128, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
93
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
94
+ triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=4, num_warps=4),
95
+ triton.Config({'BLOCK_M': 128, 'BLOCK_N': 64, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=4, num_warps=4),
96
+ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=4, num_warps=4),
97
+ ] + _MATMUL_AUTOTUNE_VOLTA # Ampere+ also benefits from smaller configs
98
+
99
+
100
+ def _detect_arch_configs():
101
+ """Return the autotune config space for the active architecture.
102
+
103
+ WHY THIS ASKS THE DRIVER, when R23/R24 put hardware parameters in
104
+ `config/vendors/<vendor>/<arch>.yml`: autotune configs are bound at
105
+ DECORATION time, which is module import — before `set_hardware_profile()`
106
+ has run at executor construction. There is no profile to read yet. This is
107
+ the documented bootstrap exception, not a licence to query the driver
108
+ elsewhere; every parameter that CAN wait for the profile reads the YAML
109
+ (see `_safe_num_stages`, which moved to `pipelining.max_num_stages`).
110
+
111
+ The spaces:
112
+
113
+ * sm_80+ (Ampere/Hopper) — tutorial canonical plus the Volta subset.
114
+ * sm_70 (Volta) — Volta-viable subset only: larger blocks saturate the
115
+ 96 KB SMEM, spill registers and collapse throughput (measured, Phase 1.5).
116
+ * CDNA (gfx*) — the Volta-viable subset. AMD's Triton target reports `arch`
117
+ as a STRING ("gfx90a"), so the old `isinstance(cap, int)` test silently
118
+ dropped every AMD card into the Volta space *by accident*. It lands in the
119
+ same place deliberately now, and for a stated reason: CDNA exposes 64 KB
120
+ of LDS against Volta's 96 KB, so the small-block space is the safe one
121
+ until an MI-series card measures a better one. Named as a first-light
122
+ task, not left as a coincidence.
123
+ * anything else (Apple, unknown) — the Volta subset as the STARTING space.
124
+
125
+ Whichever space is chosen, it is then filtered against the executing
126
+ hardware's `memory.max_shared_memory_per_block`, read from its vendor YAML.
127
+ Choosing by architecture NAME alone was wrong in two directions and both
128
+ are now decided rather than inherited:
129
+
130
+ * **Apple** was sent to the Volta subset because that space "fits the
131
+ smallest budget". It does not: Volta declares 96 KB and an Apple GPU has
132
+ 32 KB, so a BM=64/BN=128/BK=64 tile at 3 stages wants 72 KB and cannot
133
+ run there. Nine of the seventeen Volta configs survive the real budget.
134
+ * **CDNA** landed in the Volta space by accident (a string arch failed an
135
+ `isinstance(cap, int)` test), was later made deliberate, and still had no
136
+ budget applied — CDNA1/2 declare 64 KB, which excludes the largest Volta
137
+ tile at 72 KB.
138
+
139
+ Filtering never empties the space and never invents a budget: with no
140
+ matching profile the space is returned untouched.
141
+ """
142
+ try:
143
+ from nbxtensor.backend.launcher import target as _nbx_target # engine data, no driver probe (R33)
144
+ arch = _nbx_target().arch
145
+ except Exception:
146
+ return _MATMUL_AUTOTUNE_VOLTA
147
+
148
+ # NVIDIA: Triton reports compute capability x 10 (sm_70 -> 70, sm_80 -> 80)
149
+ if isinstance(arch, int):
150
+ space = (_MATMUL_AUTOTUNE_AMPERE_PLUS if arch >= 80
151
+ else _MATMUL_AUTOTUNE_VOLTA)
152
+ else:
153
+ # AMD CDNA/RDNA and any other string-arch target (Apple included).
154
+ space = _MATMUL_AUTOTUNE_VOLTA
155
+
156
+ return configs_within_smem_budget(space, arch_smem_budget())
157
+
158
+
159
+ _MATMUL_AUTOTUNE_CONFIGS = maybe_pin_single(
160
+ _detect_arch_configs(), is_matmul_pinned)
161
+
162
+
163
+ @nbx_autotune(configs=_MATMUL_AUTOTUNE_CONFIGS,
164
+ key=['M_BUCKET', 'N', 'K', 'IEEE_PRECISION', 'PROMOTE_B'],
165
+ cache_results=True)
166
+ @triton.jit
167
+ def matmul_kernel(
168
+ a_ptr, b_ptr, c_ptr,
169
+ M, N, K, M_BUCKET,
170
+ stride_am, stride_ak,
171
+ stride_bk, stride_bn,
172
+ stride_cm, stride_cn,
173
+ IEEE_PRECISION: tl.constexpr = False,
174
+ PROMOTE_B: tl.constexpr = False,
175
+ PROMOTE_A: tl.constexpr = False,
176
+ EPILOGUE: tl.constexpr = 0,
177
+ BLOCK_M: tl.constexpr = 64,
178
+ BLOCK_N: tl.constexpr = 64,
179
+ BLOCK_K: tl.constexpr = 32,
180
+ GROUP_M: tl.constexpr = 8,
181
+ ):
182
+ """C = A @ B where A is [M, K], B is [K, N], C is [M, N].
183
+
184
+ Accumulates in fp32 for numerical stability.
185
+ Output dtype determined by output pointer's dtype (in-kernel cast).
186
+
187
+ IEEE_PRECISION=True forces `tl.dot(input_precision="ieee")` — required
188
+ when fp32 inputs carry magnitudes > fp16_max on pre-Ampere GPUs,
189
+ because `tl.dot` otherwise lowers through fp16 HMMA tensor cores which
190
+ saturate the inputs to fp16 before the multiply. Set by the wrapper
191
+ when `not _NBX_HAS_NATIVE_BF16` and inputs were promoted to fp32.
192
+
193
+ PROMOTE_B=True casts the b tile to a's dtype after load and before
194
+ tl.dot. Triton's type checker rejects `tl.dot(fp32, fp16)` at compile
195
+ time; the cast is the cheapest way to bridge the mismatch — fused
196
+ with the load, register-level, no heap allocation. Set by the wrapper
197
+ when the activation was upcast fp16→fp32 (step 2) but the weight
198
+ was left fp16 in memory (to save VRAM). The bit-exact fp32 promotion
199
+ of a fp16 tile is free numerically (fp16 values are a subset of
200
+ fp32); the accumulator is fp32 so the final dot product is identical
201
+ to the path that widens the full weight pre-kernel.
202
+
203
+ # PROMOTE_A / PROMOTE_BIAS are not part of the key — see matmul_kernel above.
204
+ Phase 1.5 (2026-05): @triton.autotune ENABLED. The autotune key
205
+ includes IEEE_PRECISION + PROMOTE_B so each (Volta-fp32 / Volta-fp16-mixed
206
+ / Ampere+ pure fp16) path gets its own selected config.
207
+ """
208
+ # tl.assume hints (tutorial pattern) — combined with autotune they
209
+ # carry through; without autotune they were inactive (rollback 396fef1).
210
+ tl.assume(stride_am > 0); tl.assume(stride_ak > 0)
211
+ tl.assume(stride_bk > 0); tl.assume(stride_bn > 0)
212
+ tl.assume(stride_cm > 0); tl.assume(stride_cn > 0)
213
+ pid = tl.program_id(0).to(tl.int64)
214
+ num_pid_m = tl.cdiv(M, BLOCK_M)
215
+ num_pid_n = tl.cdiv(N, BLOCK_N)
216
+ num_pid_in_group = GROUP_M * num_pid_n
217
+ group_id = pid // num_pid_in_group
218
+ first_pid_m = group_id * GROUP_M
219
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_M)
220
+ pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
221
+ pid_n = (pid % num_pid_in_group) // group_size_m
222
+
223
+ offs_am = (pid_m * BLOCK_M + tl.arange(0, BLOCK_M)) % M
224
+ offs_bn = (pid_n * BLOCK_N + tl.arange(0, BLOCK_N)) % N
225
+ offs_k = tl.arange(0, BLOCK_K)
226
+ # Pointer offsets in int64: an int32 row offset times a stride wraps past
227
+ # 2^31 elements (Mochi's VAE mm, M=1 068 480 x N=2048 — CUDA 700 at every
228
+ # attempt since 2026-09-10; test_a_gemm_beyond_two_billion_elements).
229
+ a_ptrs = a_ptr + (offs_am[:, None].to(tl.int64) * stride_am + offs_k[None, :] * stride_ak)
230
+ b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_bn[None, :].to(tl.int64) * stride_bn)
231
+
232
+ accumulator = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
233
+ for k in range(0, tl.cdiv(K, BLOCK_K)):
234
+ a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_K, other=0.0)
235
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_K, other=0.0)
236
+ if PROMOTE_A:
237
+ # The activation tile widened in registers — the same numbers the wrapper
238
+ # produced by materialising `a.to(float32)` before the call (an exact widening),
239
+ # without that copy per matmul (TinyLlama decode: 143 a token, 2026-09-07).
240
+ a = a.to(tl.float32)
241
+ if PROMOTE_B:
242
+ b = b.to(a.dtype)
243
+ # 3-arg HMMA-FMA fused form (tutorial pattern).
244
+ if IEEE_PRECISION:
245
+ accumulator = tl.dot(a, b, accumulator, input_precision="ieee")
246
+ else:
247
+ accumulator = tl.dot(a, b, accumulator)
248
+ a_ptrs += BLOCK_K * stride_ak
249
+ b_ptrs += BLOCK_K * stride_bk
250
+
251
+ # In-kernel cast accum → output dtype (tutorial pattern; faster than
252
+ # tl.store auto-cast since the conversion happens in registers).
253
+ c = accumulator.to(c_ptr.dtype.element_ty)
254
+ offs_cm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
255
+ offs_cn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
256
+ c_ptrs = c_ptr + stride_cm * offs_cm[:, None].to(tl.int64) + stride_cn * offs_cn[None, :].to(tl.int64)
257
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
258
+ if EPILOGUE != 0:
259
+ # Per-stage rounding emulation: `c` already carries round 1 (the
260
+ # unfused mm's store cast); .to(fp32) is the standalone epilogue
261
+ # kernel's lossless load upcast. Formulas are copied VERBATIM
262
+ # from ops/silu.py / ops/gelu.py; the store's implicit downcast
263
+ # mirrors the standalone kernel's tl.store.
264
+ x_fp32 = c.to(tl.float32)
265
+ if EPILOGUE == 1:
266
+ out = x_fp32 * sigmoid(x_fp32)
267
+ elif EPILOGUE == 2:
268
+ cdf = 0.5 * (1.0 + tl.math.erf(0.707106781 * x_fp32))
269
+ out = cdf * x_fp32
270
+ else:
271
+ cdf = 0.5 * (1.0 + tanh_fn(0.7978845608 * x_fp32 * (1.0 + 0.044715 * x_fp32 * x_fp32)))
272
+ out = cdf * x_fp32
273
+ tl.store(c_ptrs, out, mask=c_mask)
274
+ else:
275
+ tl.store(c_ptrs, c, mask=c_mask)
276
+
277
+
278
+ @nbx_autotune(configs=_MATMUL_AUTOTUNE_CONFIGS,
279
+ key=['M_BUCKET', 'N', 'K', 'IEEE_PRECISION', 'PROMOTE_B'],
280
+ cache_results=True)
281
+ @triton.jit
282
+ def addmm_kernel(
283
+ a_ptr, b_ptr, bias_ptr, c_ptr,
284
+ M, N, K, M_BUCKET,
285
+ stride_am, stride_ak,
286
+ stride_bk, stride_bn,
287
+ stride_cm, stride_cn,
288
+ alpha, beta,
289
+ IEEE_PRECISION: tl.constexpr = False,
290
+ PROMOTE_B: tl.constexpr = False,
291
+ PROMOTE_A: tl.constexpr = False,
292
+ PROMOTE_BIAS: tl.constexpr = False, # the bias widened to fp32 on load (exact), not cast beforehand
293
+ EPILOGUE: tl.constexpr = 0,
294
+ BLOCK_M: tl.constexpr = 64,
295
+ BLOCK_N: tl.constexpr = 64,
296
+ BLOCK_K: tl.constexpr = 32,
297
+ GROUP_M: tl.constexpr = 8,
298
+ ):
299
+ """C = beta * bias + alpha * (A @ B) where bias is [N].
300
+
301
+ PROMOTE_B: see matmul_kernel docstring. Same in-kernel fp16→fp32
302
+ tile cast; enables the wrapper to keep fp16 weights fp16 in memory
303
+ while still running tl.dot with matched dtypes.
304
+
305
+ Phase 1.5 (2026-05): @triton.autotune ENABLED — same configs as
306
+ matmul_kernel. Tutorial pattern adapted with NeuroBrix's bias
307
+ addition + alpha/beta scaling.
308
+ """
309
+ tl.assume(stride_am > 0); tl.assume(stride_ak > 0)
310
+ tl.assume(stride_bk > 0); tl.assume(stride_bn > 0)
311
+ tl.assume(stride_cm > 0); tl.assume(stride_cn > 0)
312
+ pid = tl.program_id(0).to(tl.int64)
313
+ num_pid_m = tl.cdiv(M, BLOCK_M)
314
+ num_pid_n = tl.cdiv(N, BLOCK_N)
315
+ num_pid_in_group = GROUP_M * num_pid_n
316
+ group_id = pid // num_pid_in_group
317
+ first_pid_m = group_id * GROUP_M
318
+ group_size_m = min(num_pid_m - first_pid_m, GROUP_M)
319
+ pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
320
+ pid_n = (pid % num_pid_in_group) // group_size_m
321
+
322
+ offs_am = (pid_m * BLOCK_M + tl.arange(0, BLOCK_M)) % M
323
+ offs_bn = (pid_n * BLOCK_N + tl.arange(0, BLOCK_N)) % N
324
+ offs_k = tl.arange(0, BLOCK_K)
325
+ # Pointer offsets in int64: an int32 row offset times a stride wraps past
326
+ # 2^31 elements (Mochi's VAE mm, M=1 068 480 x N=2048 — CUDA 700 at every
327
+ # attempt since 2026-09-10; test_a_gemm_beyond_two_billion_elements).
328
+ a_ptrs = a_ptr + (offs_am[:, None].to(tl.int64) * stride_am + offs_k[None, :] * stride_ak)
329
+ b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_bn[None, :].to(tl.int64) * stride_bn)
330
+
331
+ accumulator = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
332
+ for k in range(0, tl.cdiv(K, BLOCK_K)):
333
+ a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_K, other=0.0)
334
+ b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_K, other=0.0)
335
+ if PROMOTE_A:
336
+ # The activation tile widened in registers — the same numbers the wrapper
337
+ # produced by materialising `a.to(float32)` before the call (an exact widening),
338
+ # without that copy per matmul (TinyLlama decode: 143 a token, 2026-09-07).
339
+ a = a.to(tl.float32)
340
+ if PROMOTE_B:
341
+ b = b.to(a.dtype)
342
+ if IEEE_PRECISION:
343
+ accumulator = tl.dot(a, b, accumulator, input_precision="ieee")
344
+ else:
345
+ accumulator = tl.dot(a, b, accumulator)
346
+ a_ptrs += BLOCK_K * stride_ak
347
+ b_ptrs += BLOCK_K * stride_bk
348
+
349
+ # Add bias: C = alpha * matmul + beta * bias
350
+ offs_cn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
351
+ bias_mask = offs_cn < N
352
+ bias = tl.load(bias_ptr + offs_cn, mask=bias_mask)
353
+ if PROMOTE_BIAS:
354
+ bias = bias.to(tl.float32)
355
+ accumulator = alpha * accumulator + beta * bias[None, :]
356
+
357
+ offs_cm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
358
+ c_ptrs = c_ptr + stride_cm * offs_cm[:, None].to(tl.int64) + stride_cn * offs_cn[None, :].to(tl.int64)
359
+ c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
360
+ if EPILOGUE != 0:
361
+ # Per-stage rounding emulation (see matmul_kernel): the unfused
362
+ # addmm stores the fp32 accumulator with tl.store's implicit
363
+ # downcast (round 1); the standalone epilogue kernel loads and
364
+ # upcasts to fp32. Reproduce both in-register, then apply the
365
+ # VERBATIM standalone formulas.
366
+ x_fp32 = accumulator.to(c_ptr.dtype.element_ty).to(tl.float32)
367
+ if EPILOGUE == 1:
368
+ out = x_fp32 * sigmoid(x_fp32)
369
+ elif EPILOGUE == 2:
370
+ cdf = 0.5 * (1.0 + tl.math.erf(0.707106781 * x_fp32))
371
+ out = cdf * x_fp32
372
+ else:
373
+ cdf = 0.5 * (1.0 + tanh_fn(0.7978845608 * x_fp32 * (1.0 + 0.044715 * x_fp32 * x_fp32)))
374
+ out = cdf * x_fp32
375
+ tl.store(c_ptrs, out, mask=c_mask)
376
+ else:
377
+ tl.store(c_ptrs, accumulator, mask=c_mask)
@@ -0,0 +1,235 @@
1
+ """GEMM on the m8n8k4 matrix unit — `mm` and `addmm` where the hardware profile declares `matrix_unit`.
2
+
3
+ Same contract as `matmul_kernel` / `addmm_kernel` / `baddbmm_kernel` (ops/matmul.py) for operands that are both the unit's operand
4
+ dtype in memory: C[z] = A[z] @ B[z] (+ beta * bias + alpha scaling; the bias broadcast by its strides; batch on
5
+ grid axis 1, stride 0 for mm / addmm), fp32 accumulation, any strides on B (a
6
+ pre-transposed weight is read in place), int64 offsets, M/N/K tails masked, the output cast to C's dtype, and the
7
+ fused epilogues (silu, gelu exact, gelu tanh) with the per-stage rounding emulation of the unfused pair: the
8
+ accumulator is rounded to C's dtype, widened back, then the standalone kernel's formula is applied.
9
+
10
+ The products of two fp16 operands are exact in fp32. The unit's own accumulation rounds toward zero (Fasi et al.
11
+ 2021, PeerJ CS 7:e330), so it sums only one K tile into a zeroed accumulator; the tiles are summed on the FMA units
12
+ with round to nearest, as the fp32 `tl.dot(input_precision="ieee")` path sums, in another order. The tile is the profile's
13
+ (`matrix_unit.mm`), never tuned at run time: no autotune key, nothing to certify.
14
+
15
+ Operands go global -> registers (coalesced) -> shared memory as [k-step x rows, 4] -> HMMA fragments
16
+ (ops/mma_m8n8k4.py); the next K tile is loaded while the current one is multiplied.
17
+
18
+ SPLIT representation (SPLIT_A / SPLIT_B, decided by the DtypeEngine: nbxtensor/dtype/operands.matrix_unit_representation, the
19
+ profile's `matrix_unit.fp32_split`): an operand of any float dtype in memory is widened to fp32, each row of its K tile (each
20
+ column for B) scaled by the power of two 2^s that brings that row's largest magnitude to [2^HI_EXP, 2^(HI_EXP+1)),
21
+ and carried as hi = fp16(x 2^s) and lo = fp16((x 2^s - hi) 2^LO_SHIFT). The scale is constant along k, so it factors
22
+ out of the dot product: a small row next to a large one keeps its own precision. Per K tile the unit computes hi*hi
23
+ into one zeroed accumulator and hi*lo + lo*hi into another; the tile's sum t_hh + t_lo 2^-LO_SHIFT, times 2^-s of
24
+ each split operand's row (column), is added to the fp32 accumulator on the FMA units (round to nearest). The unit's own accumulation rounds
25
+ toward zero (Fasi et al. 2021, PeerJ CS 7:e330), so it never carries the sum across tiles; lo*lo (< 2^-22 of the
26
+ product) is not formed (Ootomo & Yokota 2022, arXiv 2203.03341, eq. 24). Every power-of-two scaling is exact.
27
+ """
28
+
29
+ import functools
30
+
31
+ from triton.experimental import gluon
32
+ from triton.experimental.gluon import language as gl
33
+
34
+ from .mma_m8n8k4 import (acc_layout, acc_rows_cols, bcast_rows, frag_a_layout, frag_b_layout, mma_k4,
35
+ stage_k_inner, stage_k_outer)
36
+
37
+
38
+ @gluon.jit
39
+ def _sigmoid(x):
40
+ return 1.0 / (1.0 + gl.exp(-x))
41
+
42
+
43
+ @gluon.jit
44
+ def _tanh(x):
45
+ return 2.0 * _sigmoid(2.0 * x) - 1.0
46
+
47
+
48
+ @gluon.jit
49
+ def matmul_m8n8k4_kernel(
50
+ A, B, Bias, C,
51
+ M, N, K,
52
+ stride_az, stride_am, stride_ak, stride_bz, stride_bk, stride_bn, stride_cz, stride_cm, stride_cn,
53
+ stride_xz, stride_xm, stride_xn,
54
+ alpha, beta,
55
+ HAS_BIAS: gl.constexpr, PROMOTE_BIAS: gl.constexpr, EPILOGUE: gl.constexpr, B_K_CONTIG: gl.constexpr,
56
+ SPLIT_A: gl.constexpr, SPLIT_B: gl.constexpr, HI_EXP: gl.constexpr, LO_SHIFT: gl.constexpr,
57
+ MB: gl.constexpr, NB: gl.constexpr, BK: gl.constexpr, GROUP_M: gl.constexpr,
58
+ L: gl.constexpr, LA: gl.constexpr, LB: gl.constexpr, GA: gl.constexpr, GB: gl.constexpr,
59
+ ):
60
+ BM: gl.constexpr = 16 * MB
61
+ BN: gl.constexpr = 16 * NB
62
+ J: gl.constexpr = BK // 4
63
+ SH: gl.constexpr = gl.SwizzledSharedLayout(1, 1, 1, order=[1, 0])
64
+
65
+ pid = gl.program_id(0).to(gl.int64)
66
+ z = gl.program_id(1).to(gl.int64) # the batch (0 for mm / addmm)
67
+ A = A + z * stride_az
68
+ B = B + z * stride_bz
69
+ C = C + z * stride_cz
70
+ num_pid_m = gl.cdiv(M, BM)
71
+ num_pid_n = gl.cdiv(N, BN)
72
+ num_pid_in_group = GROUP_M * num_pid_n
73
+ first_pid_m = (pid // num_pid_in_group) * GROUP_M
74
+ group_size_m = gl.minimum(num_pid_m - first_pid_m, GROUP_M)
75
+ pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
76
+ pid_n = (pid % num_pid_in_group) // group_size_m
77
+ m0 = pid_m * BM
78
+ n0 = pid_n * BN
79
+
80
+ sa = gl.allocate_shared_memory(gl.float16, [J * BM, 4], SH)
81
+ sb = gl.allocate_shared_memory(gl.float16, [J * BN, 4], SH)
82
+ if SPLIT_A:
83
+ sal = gl.allocate_shared_memory(gl.float16, [J * BM, 4], SH)
84
+ if SPLIT_B:
85
+ sbl = gl.allocate_shared_memory(gl.float16, [J * BN, 4], SH)
86
+
87
+ # A [BM, BK], k along the columns (the wrapper makes A row-major).
88
+ ra = gl.arange(0, BM, layout=gl.SliceLayout(1, GA))[:, None]
89
+ ka = gl.arange(0, BK, layout=gl.SliceLayout(0, GA))[None, :]
90
+ pa = A + (m0 + ra) * stride_am + ka * stride_ak
91
+ ma = (m0 + ra) < M
92
+ # B either [BN, BK] (k contiguous: a pre-transposed weight) or [BK, BN] (n contiguous).
93
+ if B_K_CONTIG:
94
+ nb_ = gl.arange(0, BN, layout=gl.SliceLayout(1, GB))[:, None]
95
+ kb = gl.arange(0, BK, layout=gl.SliceLayout(0, GB))[None, :]
96
+ else:
97
+ kb = gl.arange(0, BK, layout=gl.SliceLayout(1, GB))[:, None]
98
+ nb_ = gl.arange(0, BN, layout=gl.SliceLayout(0, GB))[None, :]
99
+ pb = B + kb * stride_bk + (n0 + nb_) * stride_bn
100
+ mb_ = (n0 + nb_) < N
101
+
102
+ acc = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
103
+ ta = gl.load(pa, mask=ma & (ka < K), other=0.0)
104
+ tb = gl.load(pb, mask=mb_ & (kb < K), other=0.0)
105
+ for k0 in range(0, K, BK):
106
+ gl.barrier() # every warp is done reading the previous tile
107
+ if SPLIT_A:
108
+ inv_a = _stage_split(sa, sal, ta, True, BM, BK, HI_EXP, LO_SHIFT)
109
+ else:
110
+ stage_k_inner(sa, ta, BM, BK)
111
+ if SPLIT_B:
112
+ inv_b = _stage_split(sb, sbl, tb, B_K_CONTIG, BN, BK, HI_EXP, LO_SHIFT)
113
+ elif B_K_CONTIG:
114
+ stage_k_inner(sb, tb, BN, BK)
115
+ else:
116
+ stage_k_outer(sb, tb, BK, BN)
117
+ gl.barrier()
118
+ if k0 + BK < K: # the next tile loads while the HMMAs run
119
+ ta = gl.load(pa + (k0 + BK) * stride_ak, mask=ma & (ka + k0 + BK < K), other=0.0)
120
+ tb = gl.load(pb + (k0 + BK) * stride_bk, mask=mb_ & (kb + k0 + BK < K), other=0.0)
121
+ if SPLIT_A or SPLIT_B:
122
+ t_hh = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
123
+ t_lo = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
124
+ for j in gl.static_range(J):
125
+ t_hh = mma_k4(sa, j * BM, sb, j * BN, t_hh, MB, NB, LA, LB, L)
126
+ if SPLIT_A:
127
+ t_lo = mma_k4(sal, j * BM, sb, j * BN, t_lo, MB, NB, LA, LB, L)
128
+ if SPLIT_B:
129
+ t_lo = mma_k4(sa, j * BM, sbl, j * BN, t_lo, MB, NB, LA, LB, L)
130
+ t = t_hh + t_lo * (1.0 / (1 << LO_SHIFT))
131
+ if SPLIT_A:
132
+ t = t * _bcast_rows(gl.reshape(inv_a, [MB, 2, 8]), L)
133
+ if SPLIT_B:
134
+ t = t * _bcast_cols(gl.reshape(inv_b, [NB, 2, 2, 4]), L)
135
+ acc = acc + t
136
+ else:
137
+ t = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
138
+ for j in gl.static_range(J):
139
+ t = mma_k4(sa, j * BM, sb, j * BN, t, MB, NB, LA, LB, L)
140
+ acc = acc + t # across tiles on the FMA units, round to nearest
141
+
142
+ rows, cols = acc_rows_cols(L, MB, NB)
143
+ rows = m0 + rows
144
+ cols = n0 + cols
145
+ if HAS_BIAS:
146
+ # any broadcast of the bias: a [N] row has (0, 0, 1) strides, a full [B, M, N] its own
147
+ bias = gl.load(Bias + z * stride_xz + rows.to(gl.int64) * stride_xm + cols.to(gl.int64) * stride_xn,
148
+ mask=(rows < M) & (cols < N), other=0.0)
149
+ if PROMOTE_BIAS:
150
+ bias = bias.to(gl.float32)
151
+ acc = alpha * acc + beta * bias
152
+ c = acc.to(C.dtype.element_ty)
153
+ if EPILOGUE != 0:
154
+ # Per-stage rounding emulation, as matmul_kernel: `c` carries the unfused GEMM's store rounding, the
155
+ # widening is the standalone epilogue kernel's load; the formulas are those of ops/silu.py / ops/gelu.py.
156
+ x = c.to(gl.float32)
157
+ if EPILOGUE == 1:
158
+ out = x * _sigmoid(x)
159
+ elif EPILOGUE == 2:
160
+ out = 0.5 * (1.0 + gl.erf(0.707106781 * x)) * x
161
+ else:
162
+ out = 0.5 * (1.0 + _tanh(0.7978845608 * x * (1.0 + 0.044715 * x * x))) * x
163
+ c = out.to(C.dtype.element_ty)
164
+ gl.store(C + rows.to(gl.int64) * stride_cm + cols.to(gl.int64) * stride_cn, c, mask=(rows < M) & (cols < N))
165
+
166
+
167
+ @gluon.jit
168
+ def _pow2(e):
169
+ """2^e as fp32 for an integer exponent in [-126, 127], built from its bits (exact)."""
170
+ return ((e + 127) << 23).to(gl.float32, bitcast=True)
171
+
172
+
173
+ @gluon.jit
174
+ def split_exponent(m, HI_EXP: gl.constexpr):
175
+ """The exponent s of the power-of-two scale 2^s that brings a largest magnitude m (fp32) into
176
+ [2^HI_EXP, 2^(HI_EXP+1)): 0 for m = 0, clamped to the fp32 normal range."""
177
+ e = ((m.to(gl.int32, bitcast=True) >> 23) & 0xFF) - 127
178
+ s = gl.where(m == 0.0, 0, HI_EXP - e)
179
+ return gl.minimum(gl.maximum(s, -126), 126)
180
+
181
+
182
+ @gluon.jit
183
+ def hi_lo(x, LO_SHIFT: gl.constexpr):
184
+ """fp32 x (already scaled) as hi = fp16(x) and lo = fp16((x - hi) 2^LO_SHIFT); lo is 0 where hi holds x whole
185
+ (an infinity among them)."""
186
+ hi = x.to(gl.float16)
187
+ h32 = hi.to(gl.float32)
188
+ return hi, gl.where(h32 == x, 0.0, (x - h32) * (1 << LO_SHIFT)).to(gl.float16)
189
+
190
+
191
+ @gluon.jit
192
+ def _stage_split(s_hi, s_lo, tile, K_INNER: gl.constexpr, R: gl.constexpr, KK: gl.constexpr,
193
+ HI_EXP: gl.constexpr, LO_SHIFT: gl.constexpr):
194
+ """Stage one operand tile as hi + lo of the unit's operand dtype, each row (k along the columns) or column (k
195
+ along the rows) under its own power-of-two scale 2^s; returns 2^-s per row / column [R]. A zero row keeps
196
+ s = 0; s is clamped to the fp32 normal range. A value hi already holds whole (an infinity among them) has lo 0."""
197
+ x = tile.to(gl.float32)
198
+ KA: gl.constexpr = 1 if K_INNER else 0
199
+ s = split_exponent(gl.max(gl.abs(x), axis=KA), HI_EXP)
200
+ hi, lo = hi_lo(x * gl.expand_dims(_pow2(s), KA), LO_SHIFT)
201
+ if K_INNER:
202
+ stage_k_inner(s_hi, hi, R, KK)
203
+ stage_k_inner(s_lo, lo, R, KK)
204
+ else:
205
+ stage_k_outer(s_hi, hi, KK, R)
206
+ stage_k_outer(s_lo, lo, KK, R)
207
+ return _pow2(-s)
208
+
209
+
210
+ @gluon.jit
211
+ def _bcast_rows(r, L: gl.constexpr):
212
+ """A [MB, 2, 8] row statistic in any layout -> broadcastable over L."""
213
+ RL: gl.constexpr = gl.SliceLayout(3, gl.SliceLayout(4, gl.SliceLayout(5, gl.SliceLayout(6, L))))
214
+ return bcast_rows(gl.convert_layout(r, RL), L)
215
+
216
+
217
+ @gluon.jit
218
+ def _bcast_cols(c, L: gl.constexpr):
219
+ """A [NB, 2, 2, 4] column statistic (column = 16 o3 + 8 o4 + 4 o5 + o6, acc_rows_cols) -> broadcastable over L."""
220
+ CL: gl.constexpr = gl.SliceLayout(0, gl.SliceLayout(1, gl.SliceLayout(2, L)))
221
+ c = gl.convert_layout(c, CL)
222
+ return gl.expand_dims(gl.expand_dims(gl.expand_dims(c, 0), 1), 2)
223
+
224
+
225
+ @functools.lru_cache(maxsize=None)
226
+ def mm_layouts(MB: int, NB: int, WM: int, WN: int, BK: int, b_k_contig: bool) -> dict:
227
+ """Every layout the kernel takes for a tile of 16 MB x 16 NB x BK over WM x WN warps."""
228
+ nw = WM * WN
229
+ tk = min(BK // 8, 32)
230
+ g_rows = gl.BlockedLayout([1, 8], [32 // tk, tk], [nw, 1], [1, 0]) # [rows, k], 8 halves along k
231
+ return dict(
232
+ L=acc_layout(MB, NB, WM, WN), LA=frag_a_layout(MB, NB, WM, WN), LB=frag_b_layout(MB, NB, WM, WN),
233
+ GA=g_rows,
234
+ # [n, k] like A, or [k, n] with 4 consecutive k per thread (one STS.64 per transposing store)
235
+ GB=g_rows if b_k_contig else gl.BlockedLayout([4, 2], [1, 32], [nw, 1], [1, 0]))