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,384 @@
1
+ """Flash-attention forward on the m8n8k4 matrix unit (sm_70 tensor cores) — our Gluon kernel.
2
+
3
+ The same contract as `flash_attention.py` (the wrapper's flash path): Q is scaled by `softmax_scale` and rounded to
4
+ its own dtype before QK^T, a memory-resident additive bias (the wrapper's "matrix" bias, stride 0 over the query
5
+ axis when there is no mask) is added to every score, softmax online, GQA by `GQA_GROUPS`, the log-sum-exp stored to
6
+ `Lse`, rows and keys of any length (tails masked), and a fully-masked row left to the wrapper's guard.
7
+
8
+ Built on `mma_m8n8k4`: each warp owns MB / WM bands of 16 query rows (the row statistics never cross a warp), S = Q K^T
9
+ and O += P V are HMMA, Q (once per program), K and V (fp16) are staged through shared memory as [k-step x rows, 4],
10
+ and P never leaves the registers: its layout carries the k-step index as a register dim, and each step's A fragment
11
+ is selected from it exactly. The zero bias of an unmasked call is not read (HAS_BIAS).
12
+ The head dim is cut into at most two power-of-two parts DA + DB (the wrapper rounds it up to such a sum, the
13
+ extra columns loaded as zeros), so 96 runs as 64 + 32 and never as 128.
14
+
15
+ SPLIT (the DtypeEngine's "split" representation, nbxtensor/dtype/operands.matrix_unit_flash_representation, the profile's
16
+ `matrix_unit.fp32_split.flash`): operands of any float dtype in memory are widened to fp32 and carried to the fp16 unit
17
+ as hi + lo, as ops/matmul_m8n8k4.py carries a GEMM operand (its `split_exponent` / `hi_lo`). Each operand takes a
18
+ power-of-two scale constant along the axis it is reduced over, so the scale factors out of the product: Q (after
19
+ `softmax_scale`) per query row and K per key, both over the whole head dim (DA + DB together); V per head-dim column
20
+ of each key tile; P (in [0, 1]) is split unscaled. S = (Qh Kh + (Ql Kh + Qh Kl) 2^-LO_SHIFT) 2^-sq 2^-sk and the
21
+ tile's P V = (Ph Vh + (Pl Vh + Ph Vl) 2^-LO_SHIFT) 2^-sv; the unit sums at most `block_k` head-dim columns of S
22
+ (BLOCK_N keys of P V) into a zeroed accumulator — it rounds toward zero (Fasi et al. 2021) — and every such partial
23
+ is added on the FMA units with round to nearest. lo*lo (< 2^-22 of a product) is not formed (Ootomo & Yokota 2022,
24
+ arXiv 2203.03341). The row statistics, the online softmax and the output are those of the native path, in fp32.
25
+ """
26
+
27
+ import functools
28
+
29
+ from nbxtensor.ops._configs import nbx_autotune
30
+
31
+ from triton.experimental import gluon
32
+ from triton.experimental.gluon import language as gl
33
+
34
+ from nbxtensor.ops.matmul_m8n8k4 import _bcast_cols, _bcast_rows, _pow2, hi_lo, split_exponent
35
+ from nbxtensor.ops.mma_m8n8k4 import (MMA_ASM, MMA_CONSTRAINTS, acc_rows_cols, bcast_rows, mma_k4, row_layout_rows, row_max,
36
+ row_sum, stage_k_inner, stage_k_outer)
37
+
38
+ LOG2E: gl.constexpr = gl.constexpr(1.4426950408889634)
39
+
40
+
41
+ def unit_flash_configs():
42
+ """The tiles the certifier proves per key (workstream 1): 64 or 128 query rows (MB bands of 16), 32 or 64 keys
43
+ per step (NS), 4 or 8 warps — the six the profile's hand table was chosen from (2026-10-07, flash_tiles4) and the
44
+ pairs it did not try. MB and the warps decide which rows a program owns (bit-identical); NS sets the online
45
+ softmax's accumulation order (proven against the fp64 oracle's tolerance)."""
46
+ import triton
47
+ return [triton.Config({"MB": mb, "NS": ns}, num_warps=w, num_stages=1)
48
+ for mb in (4, 8) for ns in (2, 4) for w in (4, 8)]
49
+
50
+
51
+ def _prune_unit_flash(configs, named_args, **kwargs):
52
+ """Each warp owns MB / warps whole 16-row bands: a tile whose bands do not divide among its warps is not built; nor
53
+ one whose tiles staged by name (`staged_shared_bytes`) are over the device's shared memory per block — a tile
54
+ within that figure may still be refused by the compiler's scratch, which the launcher then refuses as Triton's
55
+ does (`OutOfResources`). None left is refused by name, never answered with the first config."""
56
+ from nbxtensor.backend.launcher import max_shared_memory_per_block
57
+ keep = [c for c in configs if c.kwargs["MB"] % c.num_warps == 0]
58
+ limit = max_shared_memory_per_block()
59
+ if limit is not None and "headdim" in named_args:
60
+ da, db = head_parts(int(named_args["headdim"]))
61
+ split = bool(named_args.get("SPLIT", False))
62
+ keep = [c for c in keep if staged_shared_bytes(16 * c.kwargs["MB"], 16 * c.kwargs["NS"], da, db, split) <= limit]
63
+ if not keep:
64
+ raise ValueError(f"matrix-unit flash: no tile of head dim {named_args.get('headdim')} fits "
65
+ f"{limit} bytes of shared memory per block")
66
+ return keep
67
+
68
+
69
+ @nbx_autotune(configs=unit_flash_configs(), key=["Q_BUCKET", "K_BUCKET", "headdim", "SPLIT"],
70
+ prune_configs_by={"early_config_prune": _prune_unit_flash}, cache_results=True)
71
+ @gluon.jit
72
+ def flash_attention_m8n8k4_kernel(
73
+ Q, K, V, Bias, Out, Lse,
74
+ softmax_scale,
75
+ stride_qb, stride_qh, stride_qm,
76
+ stride_kb, stride_kh, stride_kn,
77
+ stride_vb, stride_vh, stride_vn,
78
+ stride_bb, stride_bh, stride_bm,
79
+ stride_ob, stride_oh, stride_om,
80
+ nheads, seqlen_q, seqlen_k, seqlen_q_rounded, headdim, Q_BUCKET, K_BUCKET,
81
+ MB: gl.constexpr, NS: gl.constexpr, DA: gl.constexpr, DB: gl.constexpr, GQA_GROUPS: gl.constexpr,
82
+ HAS_BIAS: gl.constexpr,
83
+ SPLIT: gl.constexpr, HI_EXP: gl.constexpr, LO_SHIFT: gl.constexpr, KSA: gl.constexpr, KSB: gl.constexpr,
84
+ ):
85
+ # every layout from the tile's constexprs and the launch's warps, built in the kernel (`flash_layout`): the tile
86
+ # is then a plain (MB, NS, num_warps) the autotuner can sweep and the certified directory can hold (workstream 1)
87
+ WM: gl.constexpr = gl.num_warps()
88
+ LS: gl.constexpr = flash_layout("LS", MB, NS, DA, DB, WM)
89
+ LOA: gl.constexpr = flash_layout("LOA", MB, NS, DA, DB, WM)
90
+ LOB: gl.constexpr = flash_layout("LOB", MB, NS, DA, DB, WM)
91
+ LAS: gl.constexpr = flash_layout("LAS", MB, NS, DA, DB, WM)
92
+ LAOA: gl.constexpr = flash_layout("LAOA", MB, NS, DA, DB, WM)
93
+ LAOB: gl.constexpr = flash_layout("LAOB", MB, NS, DA, DB, WM)
94
+ LBK: gl.constexpr = flash_layout("LBK", MB, NS, DA, DB, WM)
95
+ LBVA: gl.constexpr = flash_layout("LBVA", MB, NS, DA, DB, WM)
96
+ LBVB: gl.constexpr = flash_layout("LBVB", MB, NS, DA, DB, WM)
97
+ GR: gl.constexpr = flash_layout("GR", MB, NS, DA, DB, WM)
98
+ GV: gl.constexpr = flash_layout("GV", MB, NS, DA, DB, WM)
99
+ LX: gl.constexpr = flash_layout("LX", MB, NS, DA, DB, WM)
100
+ BM: gl.constexpr = 16 * MB
101
+ BN: gl.constexpr = 16 * NS
102
+ SH: gl.constexpr = gl.SwizzledSharedLayout(1, 1, 1, order=[1, 0])
103
+ m0 = gl.program_id(0).to(gl.int64) * BM
104
+ off_hb = gl.program_id(1).to(gl.int64)
105
+ off_b = off_hb // nheads
106
+ off_h = off_hb % nheads
107
+ off_h_kv = off_h // GQA_GROUPS
108
+ q_base = Q + off_b * stride_qb + off_h * stride_qh
109
+ k_base = K + off_b * stride_kb + off_h_kv * stride_kh
110
+ v_base = V + off_b * stride_vb + off_h_kv * stride_vh
111
+ b_base = Bias + off_b * stride_bb + off_h * stride_bh
112
+
113
+ sq_a = gl.allocate_shared_memory(gl.float16, [(DA // 4) * BM, 4], SH)
114
+ sk_a = gl.allocate_shared_memory(gl.float16, [(DA // 4) * BN, 4], SH)
115
+ sv_a = gl.allocate_shared_memory(gl.float16, [(BN // 4) * DA, 4], SH)
116
+
117
+ # Global tiles: GR reads rows along the head dim (Q, K), GV gives each thread 4 consecutive keys of a column (V).
118
+ r_m = gl.arange(0, BM, layout=gl.SliceLayout(1, GR))[:, None]
119
+ r_n = gl.arange(0, BN, layout=gl.SliceLayout(1, GR))[:, None]
120
+ c_a = gl.arange(0, DA, layout=gl.SliceLayout(0, GR))[None, :]
121
+ v_n = gl.arange(0, BN, layout=gl.SliceLayout(1, GV))[:, None]
122
+ v_a = gl.arange(0, DA, layout=gl.SliceLayout(0, GV))[None, :]
123
+ q_rows = m0 + r_m
124
+ q_a = gl.load(q_base + q_rows * stride_qm + c_a, mask=(q_rows < seqlen_q) & (c_a < headdim), other=0.0)
125
+ if not SPLIT:
126
+ stage_k_inner(sq_a, (q_a * softmax_scale).to(gl.float16), BM, DA)
127
+ acc_a = gl.zeros([MB, 2, 8, DA // 16, 2, 2, 4], gl.float32, layout=LOA)
128
+ if DB > 0:
129
+ sq_b = gl.allocate_shared_memory(gl.float16, [(DB // 4) * BM, 4], SH)
130
+ sk_b = gl.allocate_shared_memory(gl.float16, [(DB // 4) * BN, 4], SH)
131
+ sv_b = gl.allocate_shared_memory(gl.float16, [(BN // 4) * DB, 4], SH)
132
+ c_b = DA + gl.arange(0, DB, layout=gl.SliceLayout(0, GR))[None, :]
133
+ v_b = DA + gl.arange(0, DB, layout=gl.SliceLayout(0, GV))[None, :]
134
+ q_b = gl.load(q_base + q_rows * stride_qm + c_b, mask=(q_rows < seqlen_q) & (c_b < headdim), other=0.0)
135
+ if not SPLIT:
136
+ stage_k_inner(sq_b, (q_b * softmax_scale).to(gl.float16), BM, DB)
137
+ acc_b = gl.zeros([MB, 2, 8, DB // 16, 2, 2, 4], gl.float32, layout=LOB)
138
+ if SPLIT:
139
+ # the lo halves; Q split once per program under one scale per query row over the whole head dim
140
+ sql_a = gl.allocate_shared_memory(gl.float16, [(DA // 4) * BM, 4], SH)
141
+ skl_a = gl.allocate_shared_memory(gl.float16, [(DA // 4) * BN, 4], SH)
142
+ svl_a = gl.allocate_shared_memory(gl.float16, [(BN // 4) * DA, 4], SH)
143
+ xq_a = q_a.to(gl.float32) * softmax_scale
144
+ mq = gl.max(gl.abs(xq_a), axis=1)
145
+ if DB > 0:
146
+ sql_b = gl.allocate_shared_memory(gl.float16, [(DB // 4) * BM, 4], SH)
147
+ skl_b = gl.allocate_shared_memory(gl.float16, [(DB // 4) * BN, 4], SH)
148
+ svl_b = gl.allocate_shared_memory(gl.float16, [(BN // 4) * DB, 4], SH)
149
+ xq_b = q_b.to(gl.float32) * softmax_scale
150
+ mq = gl.maximum(mq, gl.max(gl.abs(xq_b), axis=1))
151
+ eq = split_exponent(mq, HI_EXP)
152
+ hq, lq = hi_lo(xq_a * gl.expand_dims(_pow2(eq), 1), LO_SHIFT)
153
+ stage_k_inner(sq_a, hq, BM, DA)
154
+ stage_k_inner(sql_a, lq, BM, DA)
155
+ if DB > 0:
156
+ hq, lq = hi_lo(xq_b * gl.expand_dims(_pow2(eq), 1), LO_SHIFT)
157
+ stage_k_inner(sq_b, hq, BM, DB)
158
+ stage_k_inner(sql_b, lq, BM, DB)
159
+
160
+ s_rows, s_cols = acc_rows_cols(LS, MB, NS)
161
+ s_rows = m0 + s_rows
162
+ RS: gl.constexpr = gl.SliceLayout(3, gl.SliceLayout(4, gl.SliceLayout(5, gl.SliceLayout(6, LS))))
163
+ m_i = gl.full([MB, 2, 8], float("-inf"), gl.float32, layout=RS) # running max, log2 domain
164
+ l_i = gl.zeros([MB, 2, 8], gl.float32, layout=RS)
165
+ if SPLIT:
166
+ inv_q = _bcast_rows(gl.reshape(_pow2(-eq), [MB, 2, 8]), LS)
167
+
168
+ for n0 in range(0, seqlen_k, BN):
169
+ gl.barrier() # every warp is done reading the previous tile
170
+ k_rows = n0 + r_n
171
+ v_rows = n0 + v_n
172
+ if SPLIT:
173
+ # K under one scale per key over the whole head dim; V under one scale per column of this key tile
174
+ xk_a = gl.load(k_base + k_rows * stride_kn + c_a, mask=(k_rows < seqlen_k) & (c_a < headdim),
175
+ other=0.0).to(gl.float32)
176
+ xv_a = gl.load(v_base + v_rows * stride_vn + v_a, mask=(v_rows < seqlen_k) & (v_a < headdim),
177
+ other=0.0).to(gl.float32)
178
+ mk = gl.max(gl.abs(xk_a), axis=1)
179
+ if DB > 0:
180
+ xk_b = gl.load(k_base + k_rows * stride_kn + c_b, mask=(k_rows < seqlen_k) & (c_b < headdim),
181
+ other=0.0).to(gl.float32)
182
+ xv_b = gl.load(v_base + v_rows * stride_vn + v_b, mask=(v_rows < seqlen_k) & (v_b < headdim),
183
+ other=0.0).to(gl.float32)
184
+ mk = gl.maximum(mk, gl.max(gl.abs(xk_b), axis=1))
185
+ ek = split_exponent(mk, HI_EXP)
186
+ hk, lk = hi_lo(xk_a * gl.expand_dims(_pow2(ek), 1), LO_SHIFT)
187
+ stage_k_inner(sk_a, hk, BN, DA)
188
+ stage_k_inner(skl_a, lk, BN, DA)
189
+ ev_a = split_exponent(gl.max(gl.abs(xv_a), axis=0), HI_EXP)
190
+ hv, lv = hi_lo(xv_a * gl.expand_dims(_pow2(ev_a), 0), LO_SHIFT)
191
+ stage_k_outer(sv_a, hv, BN, DA)
192
+ stage_k_outer(svl_a, lv, BN, DA)
193
+ if DB > 0:
194
+ hk, lk = hi_lo(xk_b * gl.expand_dims(_pow2(ek), 1), LO_SHIFT)
195
+ stage_k_inner(sk_b, hk, BN, DB)
196
+ stage_k_inner(skl_b, lk, BN, DB)
197
+ ev_b = split_exponent(gl.max(gl.abs(xv_b), axis=0), HI_EXP)
198
+ hv, lv = hi_lo(xv_b * gl.expand_dims(_pow2(ev_b), 0), LO_SHIFT)
199
+ stage_k_outer(sv_b, hv, BN, DB)
200
+ stage_k_outer(svl_b, lv, BN, DB)
201
+ else:
202
+ stage_k_inner(sk_a, gl.load(k_base + k_rows * stride_kn + c_a,
203
+ mask=(k_rows < seqlen_k) & (c_a < headdim), other=0.0), BN, DA)
204
+ stage_k_outer(sv_a, gl.load(v_base + v_rows * stride_vn + v_a,
205
+ mask=(v_rows < seqlen_k) & (v_a < headdim), other=0.0), BN, DA)
206
+ if DB > 0:
207
+ stage_k_inner(sk_b, gl.load(k_base + k_rows * stride_kn + c_b,
208
+ mask=(k_rows < seqlen_k) & (c_b < headdim), other=0.0), BN, DB)
209
+ stage_k_outer(sv_b, gl.load(v_base + v_rows * stride_vn + v_b,
210
+ mask=(v_rows < seqlen_k) & (v_b < headdim), other=0.0), BN, DB)
211
+ gl.barrier()
212
+ s = gl.zeros([MB, 2, 8, NS, 2, 2, 4], gl.float32, layout=LS)
213
+ if SPLIT:
214
+ # KSA (KSB) k-steps of 4 head-dim columns per partial in a zeroed accumulator, added round-to-nearest
215
+ for c in gl.static_range(DA // (4 * KSA)):
216
+ t_hh = gl.zeros([MB, 2, 8, NS, 2, 2, 4], gl.float32, layout=LS)
217
+ t_lo = gl.zeros([MB, 2, 8, NS, 2, 2, 4], gl.float32, layout=LS)
218
+ for i in gl.static_range(KSA):
219
+ t_hh = mma_k4(sq_a, (c * KSA + i) * BM, sk_a, (c * KSA + i) * BN, t_hh, MB, NS, LAS, LBK, LS)
220
+ t_lo = mma_k4(sql_a, (c * KSA + i) * BM, sk_a, (c * KSA + i) * BN, t_lo, MB, NS, LAS, LBK, LS)
221
+ t_lo = mma_k4(sq_a, (c * KSA + i) * BM, skl_a, (c * KSA + i) * BN, t_lo, MB, NS, LAS, LBK, LS)
222
+ s = s + (t_hh + t_lo * (1.0 / (1 << LO_SHIFT)))
223
+ if DB > 0:
224
+ for c in gl.static_range(DB // (4 * KSB)):
225
+ t_hh = gl.zeros([MB, 2, 8, NS, 2, 2, 4], gl.float32, layout=LS)
226
+ t_lo = gl.zeros([MB, 2, 8, NS, 2, 2, 4], gl.float32, layout=LS)
227
+ for i in gl.static_range(KSB):
228
+ t_hh = mma_k4(sq_b, (c * KSB + i) * BM, sk_b, (c * KSB + i) * BN, t_hh, MB, NS, LAS, LBK, LS)
229
+ t_lo = mma_k4(sql_b, (c * KSB + i) * BM, sk_b, (c * KSB + i) * BN, t_lo, MB, NS, LAS, LBK,
230
+ LS)
231
+ t_lo = mma_k4(sq_b, (c * KSB + i) * BM, skl_b, (c * KSB + i) * BN, t_lo, MB, NS, LAS, LBK,
232
+ LS)
233
+ s = s + (t_hh + t_lo * (1.0 / (1 << LO_SHIFT)))
234
+ s = s * inv_q * _bcast_cols(gl.reshape(_pow2(-ek), [NS, 2, 2, 4]), LS)
235
+ else:
236
+ for j in gl.static_range(DA // 4):
237
+ s = mma_k4(sq_a, j * BM, sk_a, j * BN, s, MB, NS, LAS, LBK, LS)
238
+ if DB > 0:
239
+ for j in gl.static_range(DB // 4):
240
+ s = mma_k4(sq_b, j * BM, sk_b, j * BN, s, MB, NS, LAS, LBK, LS)
241
+ cols = n0 + s_cols
242
+ if HAS_BIAS:
243
+ bias = gl.load(b_base + s_rows * stride_bm + cols, mask=(s_rows < seqlen_q) & (cols < seqlen_k),
244
+ other=0.0).to(gl.float32)
245
+ s = s + bias
246
+ s = gl.where(cols < seqlen_k, s * LOG2E, float("-inf"))
247
+ m_new = gl.maximum(m_i, row_max(s))
248
+ p = gl.exp2(s - bcast_rows(m_new, LS))
249
+ alpha = gl.exp2(m_i - m_new)
250
+ l_i = l_i * alpha + row_sum(p)
251
+ m_i = m_new
252
+ # P never leaves the registers: (mb, mi3, mi, nb, ni3, ni2, k) -> (mb, mi3, mi, k, nb, ni3, ni2) in LX, whose
253
+ # k-step index j = 4 nb + 2 ni3 + ni2 is a register dim; one A fragment per j by select + sum (exact).
254
+ jj = gl.arange(0, BN // 4, layout=gl.SliceLayout(0, gl.SliceLayout(1, gl.SliceLayout(2, gl.SliceLayout(3, LX)))))
255
+ jj = jj[None, None, None, None, :]
256
+ # P V of this key tile in a zeroed accumulator (the unit rounds toward zero, Fasi et al. 2021); the tiles
257
+ # are summed on the FMA units with round to nearest, under the running rescale.
258
+ if SPLIT:
259
+ ph, pl = hi_lo(p, LO_SHIFT) # p in [0, 1] and l >= 1: no scale
260
+ pxh = gl.convert_layout(gl.reshape(gl.permute(ph, [0, 1, 2, 6, 3, 4, 5]), [MB, 2, 8, 4, BN // 4]), LX)
261
+ pxl = gl.convert_layout(gl.reshape(gl.permute(pl, [0, 1, 2, 6, 3, 4, 5]), [MB, 2, 8, 4, BN // 4]), LX)
262
+ t_a = gl.zeros([MB, 2, 8, DA // 16, 2, 2, 4], gl.float32, layout=LOA)
263
+ u_a = gl.zeros([MB, 2, 8, DA // 16, 2, 2, 4], gl.float32, layout=LOA)
264
+ if DB > 0:
265
+ t_b = gl.zeros([MB, 2, 8, DB // 16, 2, 2, 4], gl.float32, layout=LOB)
266
+ u_b = gl.zeros([MB, 2, 8, DB // 16, 2, 2, 4], gl.float32, layout=LOB)
267
+ for j in gl.static_range(BN // 4):
268
+ fh = gl.sum(gl.where(jj == j, pxh, 0.0), axis=4).to(gl.float16)
269
+ fl = gl.sum(gl.where(jj == j, pxl, 0.0), axis=4).to(gl.float16)
270
+ t_a = _mma_frag(fh, sv_a, j * DA, t_a, MB, DA // 16, LAOA, LBVA, LOA)
271
+ u_a = _mma_frag(fh, svl_a, j * DA, u_a, MB, DA // 16, LAOA, LBVA, LOA)
272
+ u_a = _mma_frag(fl, sv_a, j * DA, u_a, MB, DA // 16, LAOA, LBVA, LOA)
273
+ if DB > 0:
274
+ t_b = _mma_frag(fh, sv_b, j * DB, t_b, MB, DB // 16, LAOB, LBVB, LOB)
275
+ u_b = _mma_frag(fh, svl_b, j * DB, u_b, MB, DB // 16, LAOB, LBVB, LOB)
276
+ u_b = _mma_frag(fl, sv_b, j * DB, u_b, MB, DB // 16, LAOB, LBVB, LOB)
277
+ t_a = (t_a + u_a * (1.0 / (1 << LO_SHIFT))) * _bcast_cols(gl.reshape(_pow2(-ev_a), [DA // 16, 2, 2, 4]), LOA)
278
+ if DB > 0:
279
+ t_b = (t_b + u_b * (1.0 / (1 << LO_SHIFT))) * _bcast_cols(
280
+ gl.reshape(_pow2(-ev_b), [DB // 16, 2, 2, 4]), LOB)
281
+ else:
282
+ px = gl.convert_layout(gl.reshape(gl.permute(p.to(gl.float16), [0, 1, 2, 6, 3, 4, 5]),
283
+ [MB, 2, 8, 4, BN // 4]), LX)
284
+ t_a = gl.zeros([MB, 2, 8, DA // 16, 2, 2, 4], gl.float32, layout=LOA)
285
+ if DB > 0:
286
+ t_b = gl.zeros([MB, 2, 8, DB // 16, 2, 2, 4], gl.float32, layout=LOB)
287
+ for j in gl.static_range(BN // 4):
288
+ f = gl.sum(gl.where(jj == j, px, 0.0), axis=4).to(gl.float16)
289
+ t_a = _mma_frag(f, sv_a, j * DA, t_a, MB, DA // 16, LAOA, LBVA, LOA)
290
+ if DB > 0:
291
+ t_b = _mma_frag(f, sv_b, j * DB, t_b, MB, DB // 16, LAOB, LBVB, LOB)
292
+ acc_a = acc_a * bcast_rows(alpha, LOA) + t_a
293
+ if DB > 0:
294
+ acc_b = acc_b * bcast_rows(alpha, LOB) + t_b
295
+
296
+ lse_rows = m0 + row_layout_rows(LS, MB)
297
+ gl.store(Lse + off_hb * seqlen_q_rounded + lse_rows, (m_i + gl.log2(l_i)) / LOG2E,
298
+ mask=lse_rows < seqlen_q_rounded)
299
+ o_base = Out + off_b * stride_ob + off_h * stride_oh
300
+ o_rows, o_cols = acc_rows_cols(LOA, MB, DA // 16)
301
+ o_rows = m0 + o_rows
302
+ gl.store(o_base + o_rows * stride_om + o_cols, (acc_a / bcast_rows(l_i, LOA)).to(Out.dtype.element_ty),
303
+ mask=(o_rows < seqlen_q) & (o_cols < headdim))
304
+ if DB > 0:
305
+ o_rows_b, o_cols_b = acc_rows_cols(LOB, MB, DB // 16)
306
+ o_rows_b = m0 + o_rows_b
307
+ o_cols_b = DA + o_cols_b
308
+ gl.store(o_base + o_rows_b * stride_om + o_cols_b, (acc_b / bcast_rows(l_i, LOB)).to(Out.dtype.element_ty),
309
+ mask=(o_rows_b < seqlen_q) & (o_cols_b < headdim))
310
+
311
+
312
+ @gluon.jit
313
+ def _mma_frag(fa, sb, b_row0, acc, MB: gl.constexpr, NB: gl.constexpr, LA: gl.constexpr, LB: gl.constexpr,
314
+ L: gl.constexpr):
315
+ """mma_k4 with the A fragment already in registers ([MB, 2, 8, 4] = (mb, mi3, mi, k))."""
316
+ fa = gl.convert_layout(gl.reshape(fa, [16 * MB, 4]), LA)
317
+ fa = gl.reshape(fa, [MB, 2, 2, 2, 2, 2, 2])
318
+ fa = gl.convert_layout(gl.reshape(gl.permute(fa, [0, 1, 2, 5, 4, 3, 6]), [MB, 2, 8, 1, 1, 1, 4]), L,
319
+ assert_trivial=True)
320
+ fb = sb.slice(b_row0, 16 * NB).load(LB)
321
+ fb = gl.reshape(fb, [NB, 2, 2, 2, 2, 2, 2])
322
+ fb = gl.convert_layout(gl.reshape(gl.permute(fb, [2, 5, 4, 0, 1, 3, 6]), [1, 1, 8, NB, 2, 1, 4]), L,
323
+ assert_trivial=True)
324
+ return gl.inline_asm_elementwise(MMA_ASM, MMA_CONSTRAINTS, [fa, fb, acc], dtype=gl.float32, is_pure=True,
325
+ pack=8)
326
+
327
+
328
+ def p_layout(MB: int, NS: int, NBO: int, WM: int):
329
+ """P as [MB, 2, 8, 4, BN/4] = (mb, mi3, mi, k, j): the k-step index j in registers, then exactly
330
+ frag_a_layout(MB, NBO, WM, 1)'s bases."""
331
+ from nbxtensor.ops.mma_m8n8k4 import _bits
332
+ def e(d, x):
333
+ v = [0] * 5
334
+ v[d] = x
335
+ return v
336
+ z = [0] * 5
337
+ return gl.DistributedLinearLayout(
338
+ reg_bases=[e(4, x) for x in _bits(4 * NS)] + [e(3, 1), e(3, 2), z]
339
+ + [z] * len(_bits(NBO)) + [e(0, x * WM) for x in _bits(MB // WM)],
340
+ lane_bases=[e(2, 1), e(2, 2), e(1, 1), z, e(2, 4)],
341
+ warp_bases=[e(0, x) for x in _bits(WM)], block_bases=[], shape=[MB, 2, 8, 4, 4 * NS])
342
+
343
+
344
+ def staged_shared_bytes(BM: int, BN: int, DA: int, DB: int, split: bool) -> int:
345
+ """The shared memory the kernel above allocates by name for one program: the Q, K and V tiles of each head part
346
+ (`sq_*`, `sk_*`, `sv_*`, fp16), doubled by the lo halves under SPLIT. The compiler's own scratch (layout
347
+ conversions) comes on top, so a tile within this figure may still be refused by the compiler; a tile beyond it
348
+ never compiles. `_configs.matrix_unit` prices every profile row with it against the profile's budget."""
349
+ per_part = 2 * (BM + 2 * BN) # fp16: Q [D, BM], K [D, BN], V [BN, D]
350
+ return (DA + DB) * per_part * (2 if split else 1)
351
+
352
+
353
+ def head_parts(headdim: int):
354
+ """The head dim as DA + DB, each a power of two >= 16 (DB may be 0): the smallest such sum >= headdim."""
355
+ d = -(-headdim // 16) * 16
356
+ while bin(d // 16).count("1") > 2:
357
+ d += 16
358
+ da = 1 << (d.bit_length() - 1)
359
+ return da, d - da
360
+
361
+
362
+ @functools.lru_cache(maxsize=None)
363
+ def flash_layouts(MB: int, NS: int, DA: int, DB: int, WM: int) -> dict:
364
+ """Every layout the kernel takes, for MB 16-row bands over WM warps (WN = 1)."""
365
+ from nbxtensor.ops.mma_m8n8k4 import acc_layout, frag_a_layout, frag_b_layout
366
+ na, nb = DA // 16, max(DB // 16, 1)
367
+ return dict(
368
+ LS=acc_layout(MB, NS, WM, 1), LOA=acc_layout(MB, na, WM, 1), LOB=acc_layout(MB, nb, WM, 1),
369
+ LAS=frag_a_layout(MB, NS, WM, 1), LAOA=frag_a_layout(MB, na, WM, 1), LAOB=frag_a_layout(MB, nb, WM, 1),
370
+ LBK=frag_b_layout(MB, NS, WM, 1), LBVA=frag_b_layout(MB, na, WM, 1), LBVB=frag_b_layout(MB, nb, WM, 1),
371
+ GR=gl.BlockedLayout([1, 8], [8, 4], [WM, 1], [1, 0]),
372
+ GV=gl.BlockedLayout([4, 2], [1, 32], [WM, 1], [1, 0]), LX=p_layout(MB, NS, na, WM))
373
+
374
+
375
+ @gluon.constexpr_function
376
+ def flash_layout(name, MB, NS, DA, DB, WM):
377
+ """One of the kernel's layouts (`flash_layouts`), evaluated at compile time from its constexprs."""
378
+ # imported here, a local name: the JIT's dependency scan refuses a cached (functools) callable among a constexpr
379
+ # function's globals ("Unsupported function referenced")
380
+ from nbxtensor.ops.flash_attention_m8n8k4 import flash_layouts as layouts
381
+
382
+ def v(x):
383
+ return getattr(x, "value", x)
384
+ return layouts(int(v(MB)), int(v(NS)), int(v(DA)), int(v(DB)), int(v(WM)))[str(v(name))]
@@ -0,0 +1,78 @@
1
+ """The fixed-order reduce of split-KV decode attention (T_q == 1), pure @triton.jit.
2
+
3
+ Decode attention splits the KV sequence across programs so the SMs fill even at batch 1; each split writes a
4
+ partial output with its running max and sum of exponentials (ONLINE SOFTMAX), and this kernel combines the splits.
5
+ Its users are the vector decode kernels (`ops/decode_attn_vec.py`, `ops/paged_decode_attn.py`).
6
+
7
+ DETERMINISM IS A DESIGN REQUIREMENT. The replay engine verifies frozen plans byte-equal before adopting them, so a
8
+ kernel that varies run to run is unusable there: each split writes its own partial slice (no atomics) and the
9
+ reduction iterates the splits in a FIXED ascending order in one program per (batch, kv-head), so the floating-point
10
+ combination order is a constant of the shapes.
11
+
12
+ The tensor-core split kernel that once shared this file (`flash_decode_split_kernel`) was served only behind an
13
+ environment switch and measured slower than the vector kernels; it was removed with its route (2026-11-03).
14
+ """
15
+
16
+ import triton
17
+ import triton.language as tl
18
+
19
+ from nbxtensor.ops._configs import nbx_autotune
20
+
21
+ # The warps of the reduce, candidates the certification measures per key (the launch fixed 4 before 2026-11-04); one
22
+ # stage — the loop reads each split's partials once.
23
+ _REDUCE_CONFIGS = [triton.Config({}, num_warps=w, num_stages=1) for w in (1, 2, 4, 8)]
24
+
25
+
26
+ @nbx_autotune(configs=_REDUCE_CONFIGS, key=['SPLIT', 'GROUPS', 'D'], cache_results=True)
27
+ @triton.jit
28
+ def flash_decode_reduce_kernel(
29
+ opart_ptr, mpart_ptr, lpart_ptr,
30
+ out_ptr, # [B*H_kv, GROUPS, D] output dtype
31
+ stride_oh, stride_os, stride_og,
32
+ stride_mh, stride_ms,
33
+ stride_outh, stride_outg,
34
+ SPLIT: tl.constexpr,
35
+ GROUPS: tl.constexpr,
36
+ BLOCK_G: tl.constexpr,
37
+ BLOCK_D: tl.constexpr,
38
+ D: tl.constexpr,
39
+ ):
40
+ """Combine the split partials via log-sum-exp correction.
41
+
42
+ One program per (batch, kv-head); splits iterated in FIXED ascending
43
+ order — the combination order is a constant, hence deterministic.
44
+ """
45
+ pid_h = tl.program_id(0).to(tl.int64)
46
+ offs_g = tl.arange(0, BLOCK_G)
47
+ offs_d = tl.arange(0, BLOCK_D)
48
+ mask_g = offs_g < GROUPS
49
+ mask = mask_g[:, None] & (offs_d[None, :] < D)
50
+
51
+ m_i = tl.full([BLOCK_G], float("-inf"), dtype=tl.float32)
52
+ l_i = tl.zeros([BLOCK_G], dtype=tl.float32)
53
+ acc = tl.zeros([BLOCK_G, BLOCK_D], dtype=tl.float32)
54
+
55
+ for s in range(SPLIT):
56
+ m_s = tl.load(mpart_ptr + pid_h * stride_mh + s * stride_ms + offs_g,
57
+ mask=mask_g, other=float("-inf"))
58
+ l_s = tl.load(lpart_ptr + pid_h * stride_mh + s * stride_ms + offs_g,
59
+ mask=mask_g, other=0.0)
60
+ o_s = tl.load(opart_ptr + pid_h * stride_oh + s * stride_os
61
+ + offs_g[:, None] * stride_og + offs_d[None, :],
62
+ mask=mask, other=0.0)
63
+ m_new = tl.maximum(m_i, m_s)
64
+ m_safe = tl.where(m_new == float("-inf"), 0.0, m_new)
65
+ c_old = tl.exp(tl.where(m_i == float("-inf"), float("-inf"),
66
+ m_i - m_safe))
67
+ c_new = tl.exp(tl.where(m_s == float("-inf"), float("-inf"),
68
+ m_s - m_safe))
69
+ acc = acc * c_old[:, None] + o_s * c_new[:, None]
70
+ l_i = l_i * c_old + l_s * c_new
71
+ m_i = m_new
72
+
73
+ # A row whose every key was masked has l == 0: emit 0, matching the
74
+ # fully-masked-row guard of both existing SDPA paths (nan_to_num).
75
+ l_safe = tl.where(l_i == 0.0, 1.0, l_i)
76
+ out = acc / l_safe[:, None]
77
+ tl.store(out_ptr + pid_h * stride_outh + offs_g[:, None] * stride_outg
78
+ + offs_d[None, :], out, mask=mask)
@@ -0,0 +1,60 @@
1
+ """Flip — pure @triton.jit kernel.
2
+
3
+ Reverses elements along one dimension. The wrapper computes the
4
+ flipped strides and offset; this kernel just copies the data.
5
+
6
+ Ported from FlagGems flip (Apache-2.0 license).
7
+ """
8
+
9
+ import triton
10
+ import triton.language as tl
11
+
12
+ @triton.jit
13
+ def flip_1d_kernel(
14
+ src_ptr, dst_ptr,
15
+ n_elements,
16
+ BLOCK_SIZE: tl.constexpr,
17
+ ):
18
+ """Reverse a 1D contiguous tensor: dst[i] = src[n_elements - 1 - i]."""
19
+ pid = tl.program_id(0).to(tl.int64)
20
+ offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
21
+ mask = offset < n_elements
22
+
23
+ src_idx = n_elements - 1 - offset
24
+ data = tl.load(src_ptr + src_idx, mask=mask)
25
+ tl.store(dst_ptr + offset, data, mask=mask)
26
+
27
+ @triton.jit
28
+ def flip_strided_kernel(
29
+ src_ptr, dst_ptr,
30
+ n_elements,
31
+ # Dimension being flipped
32
+ dim_size,
33
+ stride_dim,
34
+ stride_post,
35
+ BLOCK_SIZE: tl.constexpr,
36
+ ):
37
+ """Flip along a single dimension of an N-D contiguous tensor.
38
+
39
+ For each element, decomposes the flat index into (pre, dim, post)
40
+ coordinates and reverses the dim coordinate.
41
+
42
+ src and dst are both contiguous with the same layout; only the
43
+ dim-axis index is mirrored.
44
+ """
45
+ pid = tl.program_id(0).to(tl.int64)
46
+ offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
47
+ mask = offset < n_elements
48
+
49
+ # Decompose flat index
50
+ post_size = stride_dim # product of all dims after the flip dim
51
+ pre_idx = offset // (dim_size * post_size)
52
+ dim_idx = (offset // post_size) % dim_size
53
+ post_idx = offset % post_size
54
+
55
+ # Flip the dim coordinate
56
+ flipped_dim_idx = dim_size - 1 - dim_idx
57
+
58
+ src_flat = pre_idx * (dim_size * post_size) + flipped_dim_idx * post_size + post_idx
59
+ data = tl.load(src_ptr + src_flat, mask=mask)
60
+ tl.store(dst_ptr + offset, data, mask=mask)
nbxtensor/ops/floor.py ADDED
@@ -0,0 +1,90 @@
1
+ """Floor — pure @triton.jit kernel."""
2
+
3
+ import triton
4
+ import triton.language as tl
5
+
6
+
7
+ @triton.jit
8
+ def _round_half_even(x):
9
+ """Round to nearest, ties to even — what torch.round and libm nearbyint do.
10
+
11
+ Built from `tl.floor` because Triton 3.8.0 exposes no portable `round`,
12
+ `rint` or `nearbyint`; the only implementation available was
13
+ `tl.extra.CUDA.libdevice.nearbyint`, which is NVIDIA's device library and
14
+ fails to lower on every other backend ("PassManager::run failed" on
15
+ triton-ext, measured 2026-09-17).
16
+ """
17
+ r = tl.floor(x + 0.5)
18
+ tie = (r - x) == 0.5
19
+ odd = (r - tl.floor(r * 0.5) * 2.0) != 0.0
20
+ out = tl.where(tie & odd, r - 1.0, r)
21
+ # A zero result keeps the SIGN of its input: round(-0.5) is -0.0, and so are
22
+ # round(-0.3) and round(-0.0). `floor(x + 0.5)` cannot produce -0.0 at all —
23
+ # it maps every x in (-0.5, 0] to +0.0 — so the sign is restored here. The
24
+ # Dell measured this on CUDA against an fp64 oracle: round(-0.5) gave +0.0
25
+ # where main and the oracle give -0.0. The ties were already right; only the
26
+ # sign was lost, and a sign of zero is not cosmetic — it is what tells a
27
+ # downstream divide which infinity to produce.
28
+ #
29
+ # Read from the sign bit rather than from `x < 0`, which is false for -0.0:
30
+ # a bitcast to int32 is negative exactly when the sign bit is set.
31
+ # Restored through the INTEGER view, not a floating select: on CUDA the
32
+ # Dell measured `tl.where(zero & neg, -0.0, out)` returning +0.0 for -0.5,
33
+ # -0.0 and -1e-7 (2026-09-20, against main and the fp64 oracle). Triton
34
+ # compiles floating arithmetic with no-signed-zeros permitted, so a -0.0
35
+ # constant selected into a zero is a value the compiler may write as +0.0.
36
+ # The sign bit copied as a bit is not arithmetic and is never folded.
37
+ xb = x.to(tl.int32, bitcast=True)
38
+ sign_bits = (xb >> 31) << 31 # 0x80000000 when x is negative, else 0
39
+ ob = out.to(tl.int32, bitcast=True)
40
+ ob = tl.where(out == 0.0, ob | sign_bits, ob)
41
+ return ob.to(tl.float32, bitcast=True)
42
+
43
+
44
+ # Triton's floor / ceil / trunc take fp32 or fp64 only ("Expected dtype
45
+ # ['fp32', 'fp64'] but got fp16" — parakeet --triton, 2026-09-05). A half
46
+ # input is rounded in fp32 — exact: every half value is an fp32 value and
47
+ # the integer result is representable — and stored back in its own dtype;
48
+ # fp32 inputs take the same path unchanged.
49
+ @triton.jit
50
+ def floor_forward_kernel(input_ptr, output_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
51
+ pid = tl.program_id(0).to(tl.int64)
52
+ offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
53
+ mask = offset < n_elements
54
+ x = tl.load(input_ptr + offset, mask=mask)
55
+ tl.store(output_ptr + offset, tl.math.floor(x.to(tl.float32)).to(x.dtype), mask=mask)
56
+
57
+
58
+ @triton.jit
59
+ def ceil_forward_kernel(input_ptr, output_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
60
+ pid = tl.program_id(0).to(tl.int64)
61
+ offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
62
+ mask = offset < n_elements
63
+ x = tl.load(input_ptr + offset, mask=mask)
64
+ tl.store(output_ptr + offset, tl.math.ceil(x.to(tl.float32)).to(x.dtype), mask=mask)
65
+
66
+
67
+ @triton.jit
68
+ def round_forward_kernel(input_ptr, output_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
69
+ pid = tl.program_id(0).to(tl.int64)
70
+ offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
71
+ mask = offset < n_elements
72
+ x = tl.load(input_ptr + offset, mask=mask)
73
+ # Round-half-to-even, matching torch.round. `libdevice.nearbyint` stood here
74
+ # and is NVIDIA-only: Kokoro-82M died at aten.round::0 (1, 55) with
75
+ # "PassManager::run failed" on triton-ext. A half input is rounded in fp32
76
+ # (exact) and stored back in its dtype (Kokoro --triton, 2026-09-05).
77
+ tl.store(output_ptr + offset,
78
+ _round_half_even(x.to(tl.float32)).to(x.dtype), mask=mask)
79
+
80
+
81
+ @triton.jit
82
+ def trunc_forward_kernel(input_ptr, output_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
83
+ pid = tl.program_id(0).to(tl.int64)
84
+ offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
85
+ mask = offset < n_elements
86
+ x = tl.load(input_ptr + offset, mask=mask)
87
+ # No trunc in Triton's math: toward zero = floor of the positives, ceil of
88
+ # the negatives (a kernel that could never compile before the bank reached it).
89
+ x32 = x.to(tl.float32)
90
+ tl.store(output_ptr + offset, tl.where(x32 >= 0, tl.math.floor(x32), tl.math.ceil(x32)).to(x.dtype), mask=mask)
@@ -0,0 +1,24 @@
1
+ """Full (fill) — pure @triton.jit kernel.
2
+
3
+ Creates a tensor filled with a scalar value.
4
+ Simple element-wise write: tl.store(out + offset, value, mask=mask).
5
+
6
+ Grid: (ceil(n_elements / BLOCK_SIZE),)
7
+ """
8
+
9
+ import triton
10
+ import triton.language as tl
11
+
12
+ @triton.jit
13
+ def full_kernel(
14
+ output_ptr,
15
+ fill_value,
16
+ n_elements,
17
+ BLOCK_SIZE: tl.constexpr,
18
+ ):
19
+ """Fill output tensor with a constant scalar value."""
20
+ pid = tl.program_id(0).to(tl.int64)
21
+ offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
22
+ mask = offset < n_elements
23
+
24
+ tl.store(output_ptr + offset, fill_value, mask=mask)