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,1370 @@
1
+ """Linear-algebra wrappers: matmul, bmm, addmm, baddbmm, addmv, mv, dot, gemv. Cut out of wrappers.py by family (M2, 2026-10-29), unchanged; shared launch layer and run context stay in wrappers.py."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import triton
7
+ from nbxtensor.ops.autotune_bucket import bucket_of as _bucket_of
8
+ from nbxtensor.ops import launch_keys as _lk
9
+ from nbxtensor.tensor import NBXTensor
10
+ from nbxtensor.dtype.nbx_dtype import NBXDtype, dtype_size
11
+ from nbxtensor.memory.allocator import DeviceAllocator
12
+ from nbxtensor.backend.runtime import set_device
13
+ from nbxtensor.shape import MAX_NDIM as _NBX_MAX_NDIM, broadcast_shapes, max_linear_offset
14
+ from nbxtensor.dtype.itemsize import canonical_name
15
+ from nbxtensor.dtype.promotion import binary_result, float_math_input
16
+ from nbxtensor.dtype.nbx_dtype import parse_dtype
17
+ from nbxtensor.memory.allocator import DeviceOOMError
18
+ from nbxtensor.backend.capabilities import fa_min_tile as _fa_min_tile
19
+ from nbxtensor.backend.capabilities import reduce_tile_max as _reduce_tile_max
20
+ from nbxtensor.backend.faults import device_fault_buffer, device_fault_code_cached, fault_channel
21
+ from nbxtensor.tensor import _upload_int64_array as _nbx_upload_int64, _saturating_cast as _nbx_saturating_cast
22
+ from nbxtensor.ops._configs import sdpa_block_ceiling as _sdpa_block_ceiling
23
+ from nbxtensor.ops._configs import sdpa_launch_meta as _sdpa_launch_meta
24
+ from nbxtensor.ops._configs import matrix_unit as _matrix_unit, matrix_unit_mm_tile as _matrix_unit_mm_tile
25
+ from nbxtensor.ops._configs import largest_tile_within_smem as _largest_tile_within_smem
26
+ from nbxtensor.backend import launcher as _nbx_launcher
27
+ from nbxtensor.backend.launcher import install as _install_launcher
28
+ from nbxtensor.ops.relu import relu_forward_kernel
29
+ from nbxtensor.ops.silu import silu_forward_kernel
30
+ from nbxtensor.ops.gelu import gelu_forward_kernel
31
+ from nbxtensor.ops.sigmoid import sigmoid_forward_kernel
32
+ from nbxtensor.ops.tanh import tanh_forward_kernel
33
+ from nbxtensor.ops.hardsigmoid import hardsigmoid_forward_kernel
34
+ from nbxtensor.ops.hardswish import hardswish_forward_kernel
35
+ from nbxtensor.ops.leaky_relu import leaky_relu_forward_kernel
36
+ from nbxtensor.ops.elu import elu_forward_kernel
37
+ from nbxtensor.ops.mish import mish_forward_kernel
38
+ from nbxtensor.ops.selu import selu_forward_kernel
39
+ from nbxtensor.ops.neg import neg_forward_kernel
40
+ from nbxtensor.ops.exp import exp_forward_kernel
41
+ from nbxtensor.ops.sin import sin_forward_kernel
42
+ from nbxtensor.ops.cos import cos_forward_kernel
43
+ from nbxtensor.ops.rsqrt import rsqrt_forward_kernel
44
+ from nbxtensor.ops.sqrt import sqrt_forward_kernel
45
+ from nbxtensor.ops.abs import abs_forward_kernel
46
+ from nbxtensor.ops.log import log_forward_kernel
47
+ from nbxtensor.ops.reciprocal import reciprocal_forward_kernel
48
+ from nbxtensor.ops.pow import pow_forward_kernel
49
+ from nbxtensor.ops.clamp import clamp_forward_kernel
50
+ from nbxtensor.ops.erf import erf_forward_kernel
51
+ from nbxtensor.ops.copy import copy_forward_kernel
52
+ from nbxtensor.ops.add import add_forward_kernel, add_scalar_kernel, add_scalar_dev_kernel, add_bias_broadcast_kernel
53
+ from nbxtensor.ops.binary_strided import add_strided_nd_kernel, mul_strided_nd_kernel, sub_strided_nd_kernel, div_strided_nd_kernel, add_scalar_strided_nd_kernel, mul_scalar_strided_nd_kernel
54
+ from nbxtensor.ops.mul import mul_forward_kernel, mul_scalar_kernel, mul_scalar_dev_kernel
55
+ from nbxtensor.ops.div import div_forward_kernel, div_scalar_kernel, div_scalar_dev_kernel, int_div_kernel
56
+ from nbxtensor.ops.sub import sub_forward_kernel, rsub_forward_kernel
57
+ from nbxtensor.ops.where import where_forward_kernel
58
+ from nbxtensor.ops.maximum import maximum_forward_kernel
59
+ from nbxtensor.ops.minimum import minimum_forward_kernel
60
+ from nbxtensor.ops.masked_fill import masked_fill_forward_kernel
61
+ from nbxtensor.ops.gt import gt_forward_kernel, gt_scalar_kernel
62
+ from nbxtensor.ops.ge import ge_forward_kernel, ge_scalar_kernel
63
+ from nbxtensor.ops.lt import lt_forward_kernel, lt_scalar_kernel
64
+ from nbxtensor.ops.eq import eq_forward_kernel, eq_scalar_kernel
65
+ from nbxtensor.ops.ne import ne_forward_kernel, ne_scalar_kernel
66
+ from nbxtensor.ops.layernorm import layer_norm_forward_kernel
67
+ from nbxtensor.ops.rmsnorm import rms_norm_forward_kernel
68
+ from nbxtensor.ops.softmax import softmax_forward_kernel, softmax_forward_looped_kernel
69
+ from nbxtensor.ops.matmul import matmul_kernel, addmm_kernel
70
+ from nbxtensor.ops._autotune_policy import NBX_FORCE_FP32_ACCUM
71
+ from nbxtensor.ops.fused_moe import fused_moe_kernel, silu_and_mul_kernel, silu_mul_split_kernel
72
+ from nbxtensor.ops.dtype_convert import bf16_to_fp16_kernel
73
+ from nbxtensor.ops.embedding import embedding_kernel, EMBEDDING_OOB
74
+ from nbxtensor.ops.sum import sum_forward_kernel
75
+ from nbxtensor.ops.mean import mean_forward_kernel
76
+ from nbxtensor.ops.amax import amax_forward_kernel
77
+ from nbxtensor.ops.glu import glu_forward_kernel
78
+ from nbxtensor.ops.le import le_forward_kernel, le_scalar_kernel
79
+ from nbxtensor.ops.softplus import softplus_forward_kernel
80
+ from nbxtensor.ops.dropout import dropout_inference_kernel
81
+ from nbxtensor.ops.upsample_nearest2d import upsample_nearest2d_kernel
82
+ from nbxtensor.ops.groupnorm import group_norm_forward_kernel
83
+ from nbxtensor.ops.index_select import index_select_kernel, INDEX_SELECT_OOB
84
+ from nbxtensor.ops.triu import triu_kernel, triu_batch_kernel
85
+ from nbxtensor.ops.tril import tril_kernel, tril_batch_kernel
86
+ from nbxtensor.ops.argmax import argmax_kernel_1, argmax_kernel_2, argmax_kernel_inner
87
+ from nbxtensor.ops.argmin import argmin_kernel_1, argmin_kernel_2, argmin_kernel
88
+ from nbxtensor.ops.conv2d import conv2d_forward_kernel
89
+ from nbxtensor.ops.depthwise_conv2d import depthwise_conv2d_kernel
90
+ from nbxtensor.ops.batch_norm import batch_norm_forward_kernel
91
+ from nbxtensor.ops.cumsum import scan_part_sum_kernel, add_base_sum_kernel
92
+ from nbxtensor.ops.scatter_op import scatter_kernel, scatter_add_kernel, scatter_reduce_amax_kernel, scatter_reduce_amin_kernel
93
+ from nbxtensor.ops.gather_op import gather_kernel
94
+ from nbxtensor.ops.avg_pool2d import avg_pool2d_forward_kernel
95
+ from nbxtensor.ops.max_pool2d import max_pool2d_forward_kernel
96
+ from nbxtensor.ops.max_pool3d import max_pool3d_forward_kernel
97
+ from nbxtensor.ops.cross_entropy import cross_entropy_loss_forward_kernel
98
+ from nbxtensor.ops.exp2 import exp2_forward_kernel
99
+ from nbxtensor.ops.tan import tan_forward_kernel
100
+ from nbxtensor.ops.celu import celu_forward_kernel
101
+ from nbxtensor.ops.log_sigmoid import log_sigmoid_forward_kernel
102
+ from nbxtensor.ops.isfinite import isfinite_forward_kernel
103
+ from nbxtensor.ops.isinf import isinf_forward_kernel
104
+ from nbxtensor.ops.isnan import isnan_forward_kernel
105
+ from nbxtensor.ops.nan_to_num import nan_to_num_forward_kernel
106
+ from nbxtensor.ops.threshold import threshold_forward_kernel
107
+ from nbxtensor.ops.floor import floor_forward_kernel, ceil_forward_kernel, round_forward_kernel, trunc_forward_kernel
108
+ from nbxtensor.ops.addcdiv import addcdiv_forward_kernel
109
+ from nbxtensor.ops.addcmul import addcmul_forward_kernel
110
+ from nbxtensor.ops.all_reduce import all_kernel_mid, all_kernel_result, all_kernel_dim
111
+ from nbxtensor.ops.any_reduce import any_kernel_mid, any_kernel_result, any_kernel_dim
112
+ from nbxtensor.ops.bitwise_and import bitwise_and_forward_kernel
113
+ from nbxtensor.ops.bitwise_or import bitwise_or_forward_kernel
114
+ from nbxtensor.ops.bitwise_not import bitwise_not_forward_kernel
115
+ from nbxtensor.ops.logical_and import logical_and_forward_kernel
116
+ from nbxtensor.ops.logical_or import logical_or_forward_kernel
117
+ from nbxtensor.ops.logical_not import logical_not_forward_kernel
118
+ from nbxtensor.ops.lerp import lerp_tensor_forward_kernel, lerp_scalar_head_kernel, lerp_scalar_tail_kernel
119
+ from nbxtensor.ops.dot_op import dot_kernel_small, dot_kernel_partial, dot_kernel_reduce
120
+ from nbxtensor.ops.flip_op import flip_1d_kernel, flip_strided_kernel
121
+ from nbxtensor.ops.cat_op import cat_copy_kernel_4
122
+ from nbxtensor.ops.fill_op import fill_kernel
123
+ from nbxtensor.ops.arange_op import arange_kernel
124
+ from nbxtensor.ops.prod import prod_kernel_mid, prod_kernel_result, prod_kernel
125
+ from nbxtensor.ops.min_reduce import min_kernel_mid, min_kernel_result, min_kernel
126
+ from nbxtensor.ops.max_reduce import max_kernel_mid, max_kernel_result, max_kernel
127
+ from nbxtensor.ops.baddbmm_op import baddbmm_kernel
128
+ from nbxtensor.ops.addmv_op import addmv_kernel
129
+ from nbxtensor.ops.mse_loss import mse_loss_partial_kernel, mse_loss_reduce_kernel, mse_loss_none_kernel
130
+ from nbxtensor.ops.nllloss import nll_loss_forward_kernel
131
+ from nbxtensor.ops.std import std_map_kernel, std_reduce_kernel, std_dim_kernel
132
+ from nbxtensor.ops.var import var_kernel_1, var_kernel_2, var_welford_kernel
133
+ from nbxtensor.ops.index_add import index_add_gather_kernel
134
+ from nbxtensor.ops.index_put_op import index_put_kernel, INDEX_PUT_OOB
135
+ from nbxtensor.ops.sort_op import (radix_sort_histogram_kernel,
136
+ radix_sort_scatter_kernel,
137
+ radix_sort_tile_counts_kernel,
138
+ radix_sort_tile_prefix_kernel)
139
+ from nbxtensor.ops.rope import rope_forward_kernel
140
+ from nbxtensor.ops.pixel_shuffle import (
141
+ pixel_shuffle_kernel,
142
+ pixel_shuffle_broadcast_aware_kernel,
143
+ )
144
+ from nbxtensor.ops.pixel_unshuffle import pixel_unshuffle_kernel
145
+ from nbxtensor.ops.upsample_bilinear2d import upsample_bilinear2d_kernel
146
+ from nbxtensor.ops.adaptive_avg_pool2d import adaptive_avg_pool2d_kernel
147
+ from nbxtensor.ops.conv_depthwise2d import conv_depthwise2d_kernel
148
+ from nbxtensor.ops.scaled_softmax import scaled_softmax_kernel
149
+ from nbxtensor.ops.vector_norm import (
150
+ l2_norm_kernel, l1_norm_kernel, linf_norm_kernel, l0_norm_kernel, lp_norm_kernel,
151
+ l2_norm_pass1_kernel, l2_norm_pass2_kernel,
152
+ linf_norm_pass1_kernel, linf_norm_pass2_kernel,
153
+ )
154
+ from nbxtensor.ops.addr import addr_kernel
155
+ from nbxtensor.ops.logical_xor import logical_xor_kernel
156
+ from nbxtensor.ops.bitwise_left_shift import bitwise_left_shift_kernel
157
+ from nbxtensor.ops.bitwise_right_shift import bitwise_right_shift_kernel
158
+ from nbxtensor.ops.rand_op import rand_kernel, randn_kernel
159
+ from nbxtensor.ops.clamp_min import clamp_min_forward_kernel
160
+ from nbxtensor.ops.conv_transpose2d import conv_transpose2d_kernel
161
+ from nbxtensor.ops.cummax import scan_part_max_kernel
162
+ from nbxtensor.ops.cummin import scan_part_min_kernel
163
+ from nbxtensor.ops.count_nonzero import count_nonzero_kernel
164
+ from nbxtensor.ops.trace_op import trace_kernel
165
+ from nbxtensor.ops.linspace_op import linspace_kernel
166
+ from nbxtensor.ops.full_op import full_kernel
167
+ from nbxtensor.ops.stack_op import stack_copy_kernel
168
+ from nbxtensor.ops.repeat_op import repeat_1d_kernel
169
+ from nbxtensor.ops.upsample_nearest3d import upsample_nearest3d_kernel
170
+ from nbxtensor.ops.weight_norm import weight_norm_kernel_first, weight_norm_kernel_last
171
+ from nbxtensor.ops.repeat_interleave import repeat_interleave_tensor_kernel
172
+ from nbxtensor.ops.elementwise_launch import ew_meta as _ew_meta, ew_grid as _ew_grid
173
+ from nbxtensor.ops import tiling_sizes as _ts
174
+ import math
175
+ from nbxtensor.ops import elementwise_wrappers as _ew
176
+ from nbxtensor.ops import wrappers as _core
177
+
178
+ # gridDim.z architectural bound — 65535 on both CUDA and HIP (an API
179
+ # limit shared by every supported arch, not a tunable hardware param).
180
+ # Batched matmul launches put the batch on grid axis 2 and chunk past it.
181
+ _GRID_Z_MAX = 65535
182
+
183
+
184
+ # ===========================================================================
185
+ # MATMUL WRAPPERS
186
+ # ===========================================================================
187
+
188
+ def _matmul_out_dtype(a, M: int = 1, force_fp32: bool = False):
189
+ """Output dtype for mm/bmm/addmm/mv/addmv.
190
+
191
+ Kernels accumulate in fp32. Three overlapping policies decide the
192
+ *store* dtype:
193
+
194
+ 1. **Hardware gate — fp16 on pre-Ampere (no native bf16)**: force
195
+ fp32 output unconditionally for fp16 inputs. Mirrors what the
196
+ native DtypeEngine AMP already does on V100/Turing — fp16
197
+ accumulation over K can exceed 65,504 even when per-element
198
+ magnitudes look reasonable, and the store saturates to ±Inf
199
+ which cascades through residual adds (openaudio DualAR
200
+ Apr-2026, Qwen3-30B earlier). On Ampere+ / any bf16-capable
201
+ card this path is a no-op and the legacy M-gated rule applies.
202
+ The flag comes from `PrismProfile.has_native_bf16` via
203
+ `set_hardware_profile()` — ZERO V100 hardcode, data-driven
204
+ hardware capability. See the NeuroBrix universal-engine rule
205
+ in CLAUDE.md §5.
206
+
207
+ 2. **M <= 4 (decode path)**: store fp32 even with bf16 inputs or
208
+ on bf16-capable hardware. Tiny tensors (1 row × N), negligible
209
+ memory cost, catches rare decode-loop drift.
210
+
211
+ 3. **M > 4 (prefill / spatial path), bf16-capable hardware**:
212
+ store in input dtype. Keeps diffusion spatial mm (PixArt-Sigma
213
+ M=4096 × CFG=2) within VRAM budget. Prefill-magnitude
214
+ activations stay within the dtype range because a single
215
+ forward pass doesn't chain accumulations like a deep decode.
216
+
217
+ `force_fp32=True` overrides everything (used by bmm for diffusion
218
+ attention on all hardware).
219
+
220
+ **Phase 1.5 attempt (2026-05) — REVERTED.** A bypass of hardware
221
+ gate (1) when `activations_fp16_safe=True` was prototyped on the
222
+ intuition that fp16-safe activations would unlock HMMA-fp16 via
223
+ Triton. Empirical measurement on Sana 1024 (M=2048×2240) refuted
224
+ this: pure fp16 path was 6.52 ms vs 2.82 ms current path (2.3×
225
+ WORSE). Triton fp16×fp16 → fp32 accumulator on Volta does NOT
226
+ lower to HMMA-fp16 in this kernel — it falls back to fp32
227
+ emulation. cuBLAS reaches 0.295 ms via its own dedicated HMMA-fp16
228
+ code path that we cannot replicate by toggling a flag. The
229
+ force_fp32 hardware gate stays unconditional. Real speedup
230
+ requires either a HMMA-tuned Triton kernel OR adopting
231
+ FlagGems / similar — see Phase 1.5 follow-up.
232
+ """
233
+ dt = a.nbx_dtype if hasattr(a, 'nbx_dtype') else a.dtype
234
+ # The decision is `launch_keys.matmul_out_dtype` — the one the derived census computes keys
235
+ # with (NBX_FORCE_FP32_ACCUM first, then the hardware gate, then the M / force_fp32 gate).
236
+ return _lk.matmul_out_dtype(dt, M, force_fp32, _core._NBX_HAS_NATIVE_BF16, NBX_FORCE_FP32_ACCUM,
237
+ contract_half=bool(_core._NBX_ACTIVATIONS_FP16_SAFE))
238
+
239
+
240
+ def _apply_matmul_epilogue(c, epilogue_code: int):
241
+ """Unfused epilogue application for matmul fallback paths (decode
242
+ M <= 4 mv/addmv routing). Calls the SAME standalone wrappers the
243
+ unfused graph would dispatch, in the same order — byte-identical to
244
+ the unfused pair by construction, zero-risk on the decode path.
245
+ Codes mirror kernels/ops/matmul.py EPILOGUE_*."""
246
+ if epilogue_code == 0:
247
+ return c
248
+ if epilogue_code == 1:
249
+ return _ew.silu(c)
250
+ return _ew.gelu(c, approximate=('tanh' if epilogue_code == 3 else 'none'))
251
+
252
+
253
+ _DEQUANT_GEMV_MIN_PROGRAMS = None # lazy: vendor-yml read once per process
254
+
255
+
256
+ def _dequant_gemv_block_n(N: int) -> int:
257
+ """Occupancy-adapted BLOCK_N for the M=1 dequant-GEMV grid.
258
+
259
+ The tier's pinned parity BLOCK_N under-fills the device at small N
260
+ (the MoE router mv is N=128 -> a 2-program grid on 80 SMs, ~38 us
261
+ latency-bound). Halve BLOCK_N (pow2, floor 1) until the grid
262
+ reaches `block_sizes.dequant_gemv.min_programs` from the vendor
263
+ arch YAML (R7/R24). BLOCK_N only partitions OUTPUT columns; the
264
+ parity oracle runs the SAME adapted config, so fused == ref by
265
+ construction at every width. Key absent / 0 / any config error ->
266
+ the pinned BLOCK_N (prior behavior). Large-N grids that already
267
+ meet the target are untouched.
268
+ """
269
+ from nbxtensor.ops.dequant_gemv import BLOCK_N
270
+ global _DEQUANT_GEMV_MIN_PROGRAMS
271
+ if _DEQUANT_GEMV_MIN_PROGRAMS is None:
272
+ target = 0
273
+ if os.environ.get("NBX_DGEMV_ADAPT", "1") == "0":
274
+ # Kill switch: pinned width everywhere (pre-adaptation
275
+ # behavior). Cached like the resolved target.
276
+ _DEQUANT_GEMV_MIN_PROGRAMS = 0
277
+ return BLOCK_N
278
+ try:
279
+ prof = _core.get_hardware_profile()
280
+ devices = getattr(prof, "devices", None) if prof else None
281
+ if devices:
282
+ from nbxtensor.backend.device_files import describe as get_vendor_config # the device, both halves
283
+ _brand = devices[0].brand
284
+ _vendor = getattr(_brand, "value", _brand)
285
+ cfg = get_vendor_config(_vendor, devices[0].architecture)
286
+ target = int(cfg.get("block_sizes", {})
287
+ .get("dequant_gemv", {})
288
+ .get("min_programs", 0))
289
+ except Exception:
290
+ target = 0
291
+ _DEQUANT_GEMV_MIN_PROGRAMS = max(0, target)
292
+ bn = BLOCK_N
293
+ target = _DEQUANT_GEMV_MIN_PROGRAMS
294
+ if target:
295
+ while bn > 1 and -(-N // bn) < target:
296
+ bn //= 2
297
+ return bn
298
+
299
+
300
+ def _mm_quantized(a, qt, _epilogue: int = 0):
301
+ """mm where B is an int4-g128-asym QuantizedTensor (weight triplet).
302
+
303
+ The graph's `t(weight) -> mm` marks the triplet transposed — which
304
+ IS the packed layout's natural [K, N] orientation. Decode (M == 1)
305
+ runs the fused byte-gated dequant-GEMV with the family's pinned
306
+ parity config; prefill (M > 1) dequantizes to a transient dense
307
+ tensor via the family's standalone kernel (the byte oracle's own
308
+ dequant) and re-enters the normal mm path. Output dtype mirrors
309
+ the dense path (a's dtype).
310
+ """
311
+ import triton as _tr
312
+ from nbxtensor.ops.dequant_gemv import (
313
+ BLOCK_K, BLOCK_N, NUM_WARPS, GROUP_SIZE, PACK,
314
+ dequant_gemv_int4_kernel, dequant_int4_kernel)
315
+ if not qt.transposed:
316
+ raise NotImplementedError(
317
+ "quantized mm without the graph-side transpose marker is "
318
+ "outside the v1 contract (the packed layout is [K, N]; "
319
+ "the traced linear always transposes) — named follow-up.")
320
+ a = _core._ensure_cuda(a)
321
+ K, N = qt.shape
322
+ M = a.shape[0] if a.ndim == 2 else 1
323
+ wq, sc, mn = qt.qweight, qt.scales, qt.qmins
324
+ if hasattr(a, '_device_idx') and a._device_idx != wq._device_idx:
325
+ a = _core._transfer_to_device(a, wq._device_idx)
326
+ set_device(wq)
327
+
328
+ if a.ndim == 2 and M == 1 or a.ndim == 1:
329
+ x = a.contiguous().view(-1)
330
+ out32 = NBXTensor.empty((N,), dtype=NBXDtype.float32,
331
+ device=a.device)
332
+ bn = _dequant_gemv_block_n(N)
333
+ dequant_gemv_int4_kernel[(_tr.cdiv(N, bn),)](
334
+ x, wq, sc, mn, out32, K, N,
335
+ wq.stride(0), wq.stride(1), sc.stride(0), sc.stride(1),
336
+ BLOCK_K_C=BLOCK_K, BLOCK_N_C=bn,
337
+ GROUP_C=GROUP_SIZE, PACK_C=PACK,
338
+ num_warps=NUM_WARPS, num_stages=2)
339
+ out = out32.to(a.nbx_dtype) if a.nbx_dtype != NBXDtype.float32 \
340
+ else out32
341
+ return out.view(1, N) if a.ndim == 2 else out
342
+
343
+ # Prefill: transient dense via the family's standalone dequant.
344
+ dense32 = NBXTensor.empty((K, N), dtype=NBXDtype.float32,
345
+ device=a.device)
346
+ dequant_int4_kernel[(_tr.cdiv(K, BLOCK_K), _tr.cdiv(N, BLOCK_N))](
347
+ wq, sc, mn, dense32, K, N,
348
+ wq.stride(0), wq.stride(1), sc.stride(0), sc.stride(1),
349
+ dense32.stride(0), dense32.stride(1),
350
+ BLOCK_K_C=BLOCK_K, BLOCK_N_C=BLOCK_N,
351
+ GROUP_C=GROUP_SIZE, PACK_C=PACK,
352
+ num_warps=NUM_WARPS, num_stages=2)
353
+ dense = dense32.to(a.nbx_dtype) if a.nbx_dtype != NBXDtype.float32 \
354
+ else dense32
355
+ return mm(a, dense, _epilogue)
356
+
357
+
358
+ def _gemm_unit_tile(a, b, M: int, N: int):
359
+ """The matrix unit's tile for this GEMM, with how it carries each operand (`split_a` / `split_b`: the
360
+ DtypeEngine's representation, `launch_keys.matrix_unit_operands`), else None. Two native operands take a
361
+ `matrix_unit.mm` row (their products are exact in the fp32 accumulator, as on the tl.dot path); a split one
362
+ takes a `matrix_unit.fp32_split.mm` row (its three terms hold three accumulators)."""
363
+ reps = _lk.matrix_unit_operands(a.nbx_dtype, b.nbx_dtype, M) # the census keys with the same function
364
+ if reps is None:
365
+ return None
366
+ mu = _matrix_unit()
367
+ split = "split" in reps
368
+ tile = _matrix_unit_mm_tile(M, N, mu["fp32_split"] if split else mu)
369
+ tile["split_a"], tile["split_b"] = reps[0] == "split", reps[1] == "split"
370
+ return tile
371
+
372
+
373
+ def _gemm_route(tuner, args, kwargs, unit_tile):
374
+ """The tile a GEMM the matrix unit can take (`unit_tile`, `_gemm_unit_tile`) runs at on the unit, or None for its
375
+ FMA kernel: the certificate of the FMA launch's key decides, which measured both routes on this card's class and
376
+ keeps the faster (`autotune_certified.unit_choice`) — never a threshold. A census shadow and the certifier's sweep
377
+ take the FMA kernel (its key is recorded, its configurations measured, the unit's tiles beside them); a
378
+ confirmation run with no certificate takes it too, and the launch refuses the missing key by name; any other run
379
+ with no certificate takes the unit at the profile's tile, as the vendor file declares it."""
380
+ if unit_tile is None:
381
+ return None
382
+ from nbxtensor.ops import autotune_certified as _cert
383
+ from nbxtensor.ops.launch_observer import launch_observer
384
+ if launch_observer().active() or _cert.certifying():
385
+ return None
386
+ chosen = _cert.unit_choice(tuner, args, kwargs)
387
+ if chosen is None:
388
+ return None if _cert.certified_only() else unit_tile
389
+ kind, tile = chosen
390
+ if kind == "fma":
391
+ return None
392
+ return {**tile, "split_a": unit_tile["split_a"], "split_b": unit_tile["split_b"]}
393
+
394
+
395
+ def matrix_unit_runs(tuner, args, kwargs):
396
+ """(tile, run) for each matrix-unit tile the hardware profile declares for this `matmul_kernel` / `addmm_kernel`
397
+ launch (its arguments as the autotuner receives them), where the unit can take the GEMM
398
+ (`launch_keys.matrix_unit_operands`): the rows of `matrix_unit.mm`, or of `fp32_split.mm` for an operand carried
399
+ as hi + lo. Each run writes the launch's own output on the unit, so a certifier can prove it against the launch's
400
+ oracle and time it beside the FMA configurations (the certificate then names the faster). Empty otherwise."""
401
+ from nbxtensor.ops import autotune_certified as _cert
402
+ if getattr(getattr(tuner, "base_fn", None), "__name__", None) not in ("matmul_kernel", "addmm_kernel"):
403
+ return []
404
+ named = dict(zip(tuner.arg_names, args))
405
+ a, b, c = named["a_ptr"], named["b_ptr"], named["c_ptr"]
406
+ reps = _lk.matrix_unit_operands(a.nbx_dtype, b.nbx_dtype, int(named["M"]))
407
+ if reps is None:
408
+ return []
409
+ mu = _matrix_unit()
410
+ bias = named.get("bias_ptr")
411
+ alpha, beta = float(named.get("alpha", 1.0)), float(named.get("beta", 0.0))
412
+ promote_bias, epilogue = bool(kwargs.get("PROMOTE_BIAS", False)), int(kwargs.get("EPILOGUE", 0))
413
+ out = []
414
+ for row in (mu["fp32_split"] if "split" in reps else mu)["mm"]:
415
+ tile = {f: int(row[f]) for f in _cert._UNIT_TILE_FIELDS}
416
+ run_tile = {**tile, "split_a": reps[0] == "split", "split_b": reps[1] == "split"}
417
+ out.append((tile, (lambda t=run_tile: _gemm_m8n8k4(a, b, bias, c, alpha, beta, promote_bias, epilogue, t))))
418
+ return out
419
+
420
+
421
+ def _gemm_m8n8k4(a, b, bias, c, alpha, beta, promote_bias, epilogue, tile, bias_strides=None):
422
+ """C = A @ B (+ beta * bias, alpha scaling) on the m8n8k4 matrix unit (ops/matmul_m8n8k4.py). 2-D or batched
423
+ 3-D operands: `a` row-major per batch, `b` walked by its strides, `bias` None or broadcast by `bias_strides`
424
+ (batch, row, column; default a [N] / [1, N] row). The batch runs on grid axis 1, chunked at _GRID_Z_MAX."""
425
+ from nbxtensor.ops.matmul_m8n8k4 import matmul_m8n8k4_kernel, mm_layouts
426
+ batched = a.ndim == 3
427
+ Z = a.shape[0] if batched else 1
428
+ M, K = a.shape[-2:]
429
+ N = b.shape[-1]
430
+ sa = (a.stride(0) if batched else 0, a.stride(-2), a.stride(-1))
431
+ sb = (b.stride(0) if batched else 0, b.stride(-2), b.stride(-1))
432
+ sc = (c.stride(0) if batched else 0, c.stride(-2), c.stride(-1))
433
+ sx = (0, 0, bias.stride(-1)) if bias is not None and bias_strides is None else (bias_strides or (0, 0, 0))
434
+ MB, NB = tile["block_m"] // 16, tile["block_n"] // 16
435
+ b_k_contig = sb[1] == 1 and N > 1
436
+ lay = mm_layouts(MB, NB, tile["warps_m"], tile["warps_n"], tile["block_k"], b_k_contig)
437
+ from nbxtensor.dtype.operands import matrix_unit_split_scale # the library's operand rules
438
+ hi_exp, lo_shift = matrix_unit_split_scale(_matrix_unit())
439
+ set_device(a)
440
+ for z0 in range(0, Z, _GRID_Z_MAX):
441
+ zb = min(_GRID_Z_MAX, Z - z0)
442
+ grid = (triton.cdiv(M, tile["block_m"]) * triton.cdiv(N, tile["block_n"]), zb)
443
+ x = c if bias is None else bias
444
+ matmul_m8n8k4_kernel[grid](
445
+ a if z0 == 0 else a.narrow(0, z0, zb), b if z0 == 0 else b.narrow(0, z0, zb),
446
+ x if z0 == 0 or sx[0] == 0 else x.narrow(0, z0, zb), c if z0 == 0 else c.narrow(0, z0, zb),
447
+ M, N, K, *sa, *sb, *sc, *sx,
448
+ float(alpha), float(beta),
449
+ HAS_BIAS=bias is not None, PROMOTE_BIAS=bool(promote_bias), EPILOGUE=int(epilogue),
450
+ B_K_CONTIG=b_k_contig, SPLIT_A=tile["split_a"], SPLIT_B=tile["split_b"], HI_EXP=hi_exp,
451
+ LO_SHIFT=lo_shift, MB=MB, NB=NB, BK=tile["block_k"], GROUP_M=tile["group_m"],
452
+ num_warps=tile["warps_m"] * tile["warps_n"], **lay)
453
+
454
+
455
+ def mm(a, b, _epilogue: int = 0) :
456
+ """Matrix multiplication: C = A @ B.
457
+ Kernel accumulates in fp32 (hardware). Output matches input dtype.
458
+ DtypeEngine handles fp16/bf16 overflow protection externally.
459
+
460
+ `_epilogue` (fusion_vertical pass): fused elementwise epilogue code
461
+ (0 none, 1 silu, 2 gelu exact, 3 gelu tanh) executed in-kernel with
462
+ per-stage rounding emulation — see kernels/ops/matmul.py. The
463
+ M <= 4 decode path stays UNFUSED (mv routing + standalone epilogue
464
+ wrapper) so decode bytes are untouched by construction.
465
+
466
+ Small-M fast path (M <= 4): route to mv_wrapper per-row. matmul_kernel
467
+ tiles on BLOCK_M=64 and wastes 63/64 threads when M=1 (decode). mv_kernel
468
+ parallelises on N only and is ~10–15× faster in that regime. Universal:
469
+ works for LLM decode (M=1), short-sequence audio, small-batch inference.
470
+ For M > 4, stays on matmul_kernel which is optimal for larger GEMM
471
+ (diffusion spatial mm with M in the thousands).
472
+ """
473
+ from nbxtensor.quant.quantized_tensor import QuantizedTensor
474
+ if isinstance(b, QuantizedTensor):
475
+ return _mm_quantized(a, b, _epilogue)
476
+ a = _core._ensure_cuda(a)
477
+ b = _core._ensure_cuda(b)
478
+ # Multi-device: align b to a's device
479
+ if hasattr(a, '_device_idx') and hasattr(b, '_device_idx') and a._device_idx != b._device_idx:
480
+ b = _core._transfer_to_device(b, a._device_idx)
481
+
482
+ # Hardware-gated fp16 overflow protection. On GPUs without native bf16
483
+ # (Volta/Turing/older), fp16 inputs whose upstream producers had large
484
+ # magnitudes get saturated to ±Inf when a graph-level _to_copy cast
485
+ # them back to fp16 — the next mm then propagates Inf through HMMA →
486
+ # NaN cascade. Mirror the native DtypeEngine AMP path: upcast the
487
+ # ACTIVATION to fp32. On bf16-capable hardware this branch is a no-op.
488
+ # NBXTensor.dtype returns triton.language.dtype (for kernel dispatch);
489
+ # NBXTensor.nbx_dtype returns the NBXDtype enum used by these guards.
490
+ #
491
+ # NOTE (Phase 1.5 attempt, reverted): an activations_fp16_safe opt-in
492
+ # bypass was prototyped but empirically slower on Sana 1024 (6.5 vs
493
+ # 2.8 ms — Triton fp16×fp16 does NOT lower to HMMA on Volta in this
494
+ # kernel; cuBLAS reaches 0.295 ms via dedicated HMMA path we cannot
495
+ # match by flag-flipping). Real speedup needs a HMMA-tuned Triton
496
+ # kernel or FlagGems adoption — see Phase 1.5 follow-up.
497
+ # The operand decisions are `launch_keys.mm_dtypes` — the function the derived census keys
498
+ # with; the wrapper only performs the casts it names. NBX_FORCE_FP32_ACCUM (a diagnostic)
499
+ # widens both half inputs first.
500
+ #
501
+ # The fp16 activation is NOT materialised as fp32 any more: the kernels widen it in
502
+ # registers — matmul_kernel through PROMOTE_A, the GEMV kernels on every load — the same
503
+ # numbers the copy produced (an exact widening), without the copy per matmul. The store
504
+ # dtype is decided by _matmul_out_dtype from the hardware gate, as before (fp32 here).
505
+ a_to, b_to, promote_a, promote_b = _lk.mm_dtypes(a.nbx_dtype, b.nbx_dtype,
506
+ _core._NBX_HAS_NATIVE_BF16, NBX_FORCE_FP32_ACCUM)
507
+ if a.nbx_dtype != a_to:
508
+ a = a.to(a_to)
509
+ if b.nbx_dtype != b_to:
510
+ b = b.to(b_to)
511
+
512
+ # Dtype alignment (decided in `launch_keys.mm_dtypes`). Three situations:
513
+ # 1. Same dtype → no-op.
514
+ # 2. fp32 act × fp16 weight on pre-Ampere → this is the common case
515
+ # (the activation widened in-kernel). Keep the weight fp16 in memory; the kernel
516
+ # promotes the b tile to fp32 inline via PROMOTE_B. No heap alloc.
517
+ # 3. Anything else (fp32 × bf16, bf16 × fp16, etc.) → widen to the
518
+ # common dtype. Rare; typically a downstream force_fp32 bmm feeding
519
+ # the next matmul.
520
+ # A NARROW WEIGHT IS PROMOTED IN THE KERNEL, NOT MATERIALISED.
521
+ #
522
+ # `PROMOTE_B` makes the kernel do `b = b.to(a.dtype)` on the loaded tile —
523
+ # it is dtype-agnostic (matmul.py:243) and has always been able to take
524
+ # bf16. The `not _NBX_HAS_NATIVE_BF16` guard is a pre-Ampere CUDA
525
+ # condition, and it excluded every machine that HAS native bf16 from a path
526
+ # that has nothing to do with bf16 arithmetic: here `a_eff` is fp32, so the
527
+ # math is fp32 either way and the only question is whether the weight is
528
+ # widened in memory first.
529
+ #
530
+ # It was being widened, on EVERY call, for a weight that never changes.
531
+ # Measured on an M4 Pro (TinyLlama-1.1B, 120 tokens, 2026-09-18):
532
+ # `strided_copy_nd_kernel` was 1408 calls and 11.86 s of a 19.74 s decode —
533
+ # 60% — and its four dominant shapes are weight-sized with transposing
534
+ # strides, e.g. shape (2048, 5632) src (1, 2048) -> dst (5632, 1) at
535
+ # ~5.5 GB/s against the 80.97 GB/s a contiguous copy reaches. Every one came
536
+ # from this line via `b.to(widest)`.
537
+ #
538
+ # bf16 -> fp32 is lossless, so promoting per tile yields the same fp32
539
+ # values the materialised cast did; the outputs are expected to be
540
+ # bit-identical and that is checked, not assumed.
541
+ M, K = a.shape
542
+ K2, N = b.shape
543
+ assert K == K2, f"Incompatible dimensions: {K} vs {K2}"
544
+
545
+ # === TEMP DIAGNOSTIC: record M,N,K per call ===
546
+ import os as _os
547
+ if _os.environ.get("NBX_MM_SHAPES") == "1":
548
+ import collections, atexit
549
+ if "_rec" not in mm.__dict__:
550
+ mm.__dict__["_rec"] = collections.Counter()
551
+ mm.__dict__["_call_idx"] = [0]
552
+
553
+ def _dump_shapes():
554
+ rec = mm.__dict__["_rec"]
555
+ print("\n[NBX_MM_SHAPES] unique (M,N,K) × count:")
556
+ for (m_, n_, k_), c in sorted(rec.items(),
557
+ key=lambda x: -x[1]):
558
+ print(f" M={m_:5d} N={n_:5d} K={k_:5d} × {c}")
559
+ print(f"[NBX_MM_SHAPES] total calls: {sum(rec.values())}")
560
+ atexit.register(_dump_shapes)
561
+ mm.__dict__["_rec"][(M, N, K)] += 1
562
+ # ================================================
563
+
564
+ # Small-M GEMV path. Do NOT call b.contiguous() on the full weight:
565
+ # if b is a pre-transposed stride-view (from _eliminate_weight_transpose_ops),
566
+ # b.t() restores the original contiguous layout for free. Calling
567
+ # b.contiguous() first would copy the entire weight — defeating the point.
568
+ if M <= 4:
569
+ set_device(a)
570
+ a = a.contiguous()
571
+ bt = b.t() # (N, K); contiguous if b came from pre-transpose
572
+ dtype_out = _matmul_out_dtype(a, M)
573
+ dev_str = (f"cuda:{a._device_idx}" if hasattr(a, '_device_idx')
574
+ else 'cuda')
575
+ c = NBXTensor.empty((M, N), device=dev_str, dtype=dtype_out)
576
+ for i in range(M):
577
+ c[i] = mv_wrapper(bt, a[i])
578
+ return _apply_matmul_epilogue(c, _epilogue)
579
+
580
+ a = a.contiguous()
581
+ # The weight is walked by its own strides: matmul_kernel receives (stride_bk, stride_bn)
582
+ # and loads the B tile through them, so a pre-transposed weight — the (K, N) stride view of
583
+ # a (N, K) row-major buffer, stride_bk == 1 — is read in place. Materialising it here copied
584
+ # the whole model once per prefill (TinyLlama: 154 weights, 2.2 GB, per request — the copy
585
+ # census of 2026-09-07). Only a broadcast (a zero stride) is materialised: the kernel's
586
+ # integer analysis assumes every stride positive.
587
+ if any(st == 0 for st in b.stride()):
588
+ b = b.contiguous()
589
+ out_dtype = _matmul_out_dtype(a, M, force_fp32=promote_a)
590
+ c = NBXTensor.empty((M, N), device=f"cuda:{a._device_idx}" if hasattr(a, '_device_idx') else 'cuda',
591
+ dtype=out_dtype)
592
+ # IEEE mode: force strict fp32 tl.dot on pre-Ampere when we've promoted
593
+ # inputs to fp32 for overflow protection. Otherwise tl.dot silently
594
+ # casts fp32 → fp16 HMMA → saturates at ±65504 → NaN/Inf cascade.
595
+ ieee = (not _core._NBX_HAS_NATIVE_BF16) and (out_dtype == NBXDtype.float32)
596
+ # Phase 1.5 autotune: BLOCK_*/GROUP_M/num_warps/num_stages chosen
597
+ # adaptively per (M, N, K, IEEE_PRECISION, PROMOTE_B). Grid uses
598
+ # META lambda so autotune-selected blocks drive the launch shape.
599
+ set_device(a)
600
+ # ROW BANDING above 2^31 OUTPUT ELEMENTS. A launch whose C holds more than 2^31 elements
601
+ # writes nothing past row `2**31 // N` on the Metal backend: the offsets are promoted to
602
+ # int64 in the kernel (matmul.py:257) and CUDA is exact, but the Metal lowering does not
603
+ # honour it (2026-09-23; tests/unit/kernels/test_a_gemm_beyond_two_billion_elements.py).
604
+ # Three kernel-side spellings were tried and none helped, so the launch is split instead:
605
+ # each band's own C is under the boundary, and the arithmetic per band is untouched.
606
+ #
607
+ # The AUTOTUNE KEY IS PRESERVED. `M_BUCKET` is a separate argument from `M` and the key is
608
+ # keyed on the bucket, so every band is served the configuration certified for the whole
609
+ # shape — banding changes how many launches happen, never which entry answers for them.
610
+ bands = [(0, M)]
611
+ if N > 0 and M * N > _NBX_MM_MAX_OUTPUT_ELEMS:
612
+ per = max(1, _NBX_MM_MAX_OUTPUT_ELEMS // N)
613
+ bands = [(r, min(r + per, M)) for r in range(0, M, per)]
614
+ m_bucket = _bucket_of("M", M)
615
+ _mu_tile = _gemm_unit_tile(a, b, M, N)
616
+ for r0, r1 in bands:
617
+ rows = r1 - r0
618
+ a_b = a[r0:r1] if len(bands) > 1 else a
619
+ c_b = c[r0:r1] if len(bands) > 1 else c
620
+ args = (a_b, b, c_b, rows, N, K, m_bucket, a_b.stride(0), a_b.stride(1), b.stride(0), b.stride(1),
621
+ c_b.stride(0), c_b.stride(1))
622
+ kwargs = dict(IEEE_PRECISION=ieee, PROMOTE_B=promote_b, PROMOTE_A=promote_a, EPILOGUE=_epilogue)
623
+ tile = _gemm_route(matmul_kernel, args, kwargs, _mu_tile)
624
+ if tile is not None:
625
+ _gemm_m8n8k4(a_b, b, None, c_b, 1.0, 0.0, False, _epilogue, tile)
626
+ continue
627
+ grid = (lambda META, _m=rows: (triton.cdiv(_m, META['BLOCK_M']) * triton.cdiv(N, META['BLOCK_N']),))
628
+ _core._autotune_headroom_guard(matmul_kernel[grid])(*args, **kwargs)
629
+ return c
630
+
631
+
632
+ def bmm(a, b, allow_strided_b: bool = False) :
633
+ """Batched matrix multiplication: C[i] = A[i] @ B[i].
634
+
635
+ `allow_strided_b` lets the caller hand in a non-contiguous B and have
636
+ the kernel walk it by its own strides. `baddbmm_kernel` already
637
+ receives all three of B's strides; the unconditional `.contiguous()`
638
+ below simply threw that generality away. Off by default — a badly
639
+ strided operand can cost more in uncoalesced loads than the copy it
640
+ saves, so it is opt-in per call site with its measurement, never a
641
+ blanket change. See `_math_attention`, the first caller.
642
+ Kernel accumulates in fp32 (hardware). Output matches input dtype.
643
+ DtypeEngine handles overflow protection externally.
644
+
645
+ One batched 3D-grid launch via `baddbmm_kernel` (HAS_BIAS=False) for
646
+ every M — P-KERNEL-LAUNCH-HYGIENE B1. The former per-batch
647
+ matmul_kernel loop (prefill) and per-batch × per-row mv loop (decode
648
+ M<=4) issued B (resp. B·M) launches per call; on the deterministic
649
+ decode path (`_math_attention`, batch=B·H, M=1) that was thousands of
650
+ launches per generated token, pure launch-bound. The batched kernel
651
+ body is a line-for-line mirror of matmul_kernel, so results are
652
+ value-identical to the former prefill loop under the same autotune
653
+ config; the decode path leaves the mv reduction order for tl.dot
654
+ tiles (fp reassociation-level difference only — products are exact in
655
+ the fp32 accumulator for half inputs).
656
+ """
657
+ a = _core._ensure_cuda(a)
658
+ b = _core._ensure_cuda(b)
659
+ if hasattr(a, '_device_idx') and hasattr(b, '_device_idx') and a._device_idx != b._device_idx:
660
+ b = _core._transfer_to_device(b, a._device_idx)
661
+
662
+ # The matrix unit takes two operands of its operand dtype as they are in memory: the widening below exists
663
+ # because the tl.dot kernel has no PROMOTE_A, and an fp16 value is exact in fp32 with fp16 x fp16 products
664
+ # exact in the fp32 accumulator, so the unit computes the same sums (the store dtype is unchanged). Rows that
665
+ # do not fill the unit's smallest tile (decode) keep the batched kernel (launch_keys.matrix_unit_operands).
666
+ _mu_tile = _gemm_unit_tile(a, b, a.shape[1], b.shape[2])
667
+ if _mu_tile is not None:
668
+ a = a.contiguous()
669
+ if not allow_strided_b:
670
+ b = b.contiguous()
671
+ c = NBXTensor.empty((a.shape[0], a.shape[1], b.shape[2]), device=a.device,
672
+ dtype=_matmul_out_dtype(a, a.shape[1], force_fp32=True))
673
+ _gemm_m8n8k4(a, b, None, c, 1.0, 0.0, False, 0, _mu_tile)
674
+ return c
675
+
676
+ # Hardware-gated fp16→fp32 input upcast — same rationale as mm().
677
+ # Use nbx_dtype for guard comparisons; .dtype returns triton.language.dtype.
678
+ # The operand decisions are `launch_keys.bmm_dtypes` — the function the derived census keys
679
+ # with (NBX_FORCE_FP32_ACCUM first; an fp16 activation widened in memory on hardware without
680
+ # native bf16 — bmm has no PROMOTE_A; an fp16 weight under fp32 widened in the kernel there;
681
+ # any other mismatch widened to the widest). The wrapper only casts.
682
+ a_to, b_to, promote_b = _lk.bmm_dtypes(a.nbx_dtype, b.nbx_dtype,
683
+ _core._NBX_HAS_NATIVE_BF16, NBX_FORCE_FP32_ACCUM)
684
+ if a.nbx_dtype != a_to:
685
+ a = a.to(a_to)
686
+ if b.nbx_dtype != b_to:
687
+ b = b.to(b_to)
688
+
689
+ B, M, K = a.shape
690
+ B2, K2, N = b.shape
691
+ assert B == B2 and K == K2, f"bmm shape mismatch: {tuple(a.shape)} @ {tuple(b.shape)}"
692
+
693
+ a = a.contiguous()
694
+ if not allow_strided_b:
695
+ b = b.contiguous()
696
+ out_dtype = _matmul_out_dtype(a, M, force_fp32=True)
697
+ c = NBXTensor.empty((B, M, N), device=a.device, dtype=out_dtype)
698
+ ieee = (not _core._NBX_HAS_NATIVE_BF16) and (out_dtype == NBXDtype.float32)
699
+
700
+ # Match mm() — sync Triton driver to a's device immediately before launch.
701
+ # NBXTensor.empty(...) above can cudaSetDevice without updating Triton's
702
+ # driver active device, and dtype alignment paths may also have touched
703
+ # it. Without this, batched kernels on non-zero devices hit
704
+ # "Pointer argument (at 0) cannot be accessed from Triton".
705
+ set_device(a)
706
+ # Batched launch — grid axis 2 walks the batch inside the kernel
707
+ # (baddbmm_kernel HAS_BIAS=False). `c` doubles as the (dead) bias
708
+ # pointer: with HAS_BIAS=False the bias load is compiled out, so no
709
+ # uninitialized read; alpha/beta are likewise unused. gridDim.z caps
710
+ # at _GRID_Z_MAX: matmul_wrapper collapses ALL leading dims into B, so
711
+ # an extreme collapsed batch chunks into z-sized launches (one launch
712
+ # per 65535 batches — the loop body runs exactly once below the cap,
713
+ # with no narrow() on that fast path).
714
+ for z0 in range(0, B, _GRID_Z_MAX):
715
+ zb = min(_GRID_Z_MAX, B - z0)
716
+ a_z = a if zb == B else a.narrow(0, z0, zb)
717
+ b_z = b if zb == B else b.narrow(0, z0, zb)
718
+ c_z = c if zb == B else c.narrow(0, z0, zb)
719
+ grid = lambda META, _zb=zb: (
720
+ triton.cdiv(M, META['BLOCK_M']) * triton.cdiv(N, META['BLOCK_N']),
721
+ 1,
722
+ _zb,
723
+ )
724
+ _core._autotune_headroom_guard(baddbmm_kernel[grid])(
725
+ a_z, b_z, c_z, c_z,
726
+ 1.0, 0.0,
727
+ M, N, K, _bucket_of("M", M), _bucket_of("N", N), _bucket_of("K", K),
728
+ a.stride(0), a.stride(1), a.stride(2),
729
+ b.stride(0), b.stride(1), b.stride(2),
730
+ c.stride(0), c.stride(1), c.stride(2),
731
+ 0, 0, 0,
732
+ IEEE_PRECISION=ieee,
733
+ PROMOTE_B=promote_b,
734
+ HAS_BIAS=False,
735
+ )
736
+ return c
737
+
738
+
739
+ def matmul_wrapper(a, b):
740
+ """General matmul dispatcher: routes to mm, bmm, or mv based on input dims.
741
+
742
+ Matches torch.matmul semantics:
743
+ - 2D × 2D → mm
744
+ - 3D × 3D → bmm
745
+ - 2D × 1D → mv
746
+ - ND × 2D → reshape to batched mm
747
+ """
748
+ route = _lk.matmul_route(a.ndim, b.ndim) # the route the derived census keys with
749
+ if route == "mm":
750
+ return mm(a, b)
751
+ if route == "bmm":
752
+ return bmm(a, b)
753
+ if route == "mv":
754
+ return mv_wrapper(a, b)
755
+ if route == "batched":
756
+ # Batched: reshape a to (batch, M, K), mm each, reshape back
757
+ orig_shape = a.shape
758
+ M, K = orig_shape[-2], orig_shape[-1]
759
+ batch = a.numel() // (M * K)
760
+ a_3d = a.contiguous().view(batch, M, K)
761
+ result = bmm(a_3d, b.unsqueeze(0).expand(batch, K, b.shape[1]))
762
+ return result.view(*orig_shape[:-1], b.shape[1])
763
+ if route == "general":
764
+ # General batched matmul. bmm is strictly 3D, so collapse the leading
765
+ # batch dims into one, bmm, then restore the batch shape. Passing raw 4-D
766
+ # tensors straight to bmm unpacked a 3-tuple → "too many values".
767
+ a_c = a.contiguous(); b_c = b.contiguous()
768
+ M, K = a_c.shape[-2], a_c.shape[-1]
769
+ Kb, N = b_c.shape[-2], b_c.shape[-1]
770
+ batch_a = a_c.numel() // (M * K)
771
+ batch_b = b_c.numel() // (Kb * N)
772
+ a_3d = a_c.view(batch_a, M, K)
773
+ b_3d = b_c.view(batch_b, Kb, N)
774
+ lead = list(a_c.shape[:-2])
775
+ if batch_b == 1 and batch_a != 1:
776
+ b_3d = b_3d.expand(batch_a, Kb, N)
777
+ elif batch_a == 1 and batch_b != 1:
778
+ a_3d = a_3d.expand(batch_b, M, K)
779
+ lead = list(b_c.shape[:-2])
780
+ result = bmm(a_3d, b_3d)
781
+ return result.view(*lead, M, N)
782
+ raise RuntimeError(f"matmul: unsupported shapes {a.shape} × {b.shape}")
783
+
784
+
785
+ def linear_wrapper(input, weight, bias=None):
786
+ """aten::linear — y = input @ weight.T + bias.
787
+
788
+ Signature matches torch.nn.functional.linear:
789
+ input : (..., in_features)
790
+ weight: (out_features, in_features) — NOT pre-transposed
791
+ bias : (out_features,) | None
792
+ output: (..., out_features)
793
+
794
+ Routes through the existing mm / bmm / addmm primitives with an
795
+ implicit transpose on `weight`. No new kernel launches beyond what
796
+ these helpers already do.
797
+ """
798
+ w_t = weight.t()
799
+ if bias is None:
800
+ return matmul_wrapper(input, w_t)
801
+ # matmul then broadcast-add — matches torch's handling for ND inputs.
802
+ # For pure 2D we could use addmm directly, but matmul_wrapper already
803
+ # takes the mm path and `add` broadcasts cleanly.
804
+ out = matmul_wrapper(input, w_t)
805
+ return _ew.add(out, bias)
806
+
807
+
808
+ def addmm(bias, a, b,
809
+ beta: float = 1.0, alpha: float = 1.0, _epilogue: int = 0) :
810
+ """C = beta * bias + alpha * (A @ B).
811
+ Kernel accumulates in fp32 (hardware). Output matches input dtype.
812
+ DtypeEngine handles overflow protection externally.
813
+
814
+ Small-M fast path (M <= 4): per-row addmv. Bias is broadcast the same
815
+ way torch.addmm does: (N,), (1, N), or (M, N).
816
+
817
+ `_epilogue` (fusion_vertical pass): fused elementwise epilogue code
818
+ (0 none, 1 silu, 2 gelu exact, 3 gelu tanh) with per-stage rounding
819
+ emulation in-kernel; M <= 4 decode path stays unfused (see mm()).
820
+ """
821
+ a = _core._ensure_cuda(a)
822
+ b = _core._ensure_cuda(b)
823
+ bias = _core._ensure_cuda(bias)
824
+
825
+ # Hardware-gated fp16→fp32 input upcast — same rationale as mm().
826
+ # Use nbx_dtype for guard comparisons; .dtype returns triton.language.dtype.
827
+ # The operand decisions are `launch_keys.addmm_dtypes` — the function the derived census
828
+ # keys with (NBX_FORCE_FP32_ACCUM first; PROMOTE_A / PROMOTE_B in-kernel widenings on hardware
829
+ # without native bf16; any other mismatch widened to the widest; the bias widened in-kernel
830
+ # under an fp32 activation, else cast to the activation's dtype). The wrapper only casts.
831
+ a_to, b_to, bias_to, promote_a, promote_b, promote_bias = _lk.addmm_dtypes(
832
+ a.nbx_dtype, b.nbx_dtype, bias.nbx_dtype, _core._NBX_HAS_NATIVE_BF16, NBX_FORCE_FP32_ACCUM)
833
+ if a.nbx_dtype != a_to:
834
+ a = a.to(a_to)
835
+ if b.nbx_dtype != b_to:
836
+ b = b.to(b_to)
837
+ if bias.nbx_dtype != bias_to:
838
+ bias = bias.to(bias_to)
839
+
840
+ if a.ndim > 2:
841
+ orig_lead = a.shape[:-1]
842
+ K_ = a.shape[-1]
843
+ batch = a.numel() // K_
844
+ a2d = a.contiguous().view(batch, K_)
845
+ out2d = addmm(bias, a2d, b, beta=beta, alpha=alpha, _epilogue=_epilogue)
846
+ return out2d.view(*orig_lead, out2d.shape[-1])
847
+
848
+ M, K = a.shape
849
+ K2, N = b.shape
850
+ # Named, not bare: a shape mismatch here reaches the census as
851
+ # "AssertionError: " with nothing in it, and a failure that carries no
852
+ # numbers costs a whole reproduction run to say what the raise could
853
+ # have said itself (2026-09-23, the two PixArt -MS containers).
854
+ if K != K2:
855
+ raise AssertionError(
856
+ f"addmm shape mismatch: ({M}, {K}) @ ({K2}, {N}) — the contracted "
857
+ f"dimension differs, {K} against {K2}")
858
+
859
+ if M <= 4:
860
+ a = a.contiguous()
861
+ bias = bias.contiguous()
862
+ bt = b.t()
863
+ c = NBXTensor.empty((M, N), device=a.device,
864
+ dtype=_matmul_out_dtype(a, M))
865
+ bias_per_row = (bias.ndim == 2 and bias.shape[0] == M)
866
+ if bias.ndim == 2 and bias.shape[0] == 1:
867
+ bias_shared = bias[0]
868
+ elif bias.ndim == 1:
869
+ bias_shared = bias
870
+ else:
871
+ bias_shared = None
872
+ for i in range(M):
873
+ br = bias[i] if bias_per_row else bias_shared
874
+ c[i] = addmv_wrapper(br, bt, a[i], beta=beta, alpha=alpha)
875
+ return _apply_matmul_epilogue(c, _epilogue)
876
+
877
+ if bias.ndim == 2 and bias.shape[0] != 1:
878
+ # The kernel adds ONE bias row [N] to every output row: a per-row bias above the decode rows would be computed
879
+ # with its first row only. Refused by name, never computed wrong (no catalogue graph forms one: its 10 478
880
+ # addmm carry a bias [N], read 2026-10-29).
881
+ raise ValueError(f"addmm: a per-row bias {tuple(bias.shape)} is served at M <= 4 only (here M = {M}); "
882
+ f"the kernel adds one bias row [N] to every row")
883
+ a = a.contiguous()
884
+ # A pre-transposed weight is walked by its strides (as mm); only a broadcast
885
+ # (stride 0) operand is materialised, the kernel's tile loads need a real address.
886
+ if any(st == 0 for st in b.stride()):
887
+ b = b.contiguous()
888
+ bias = bias.contiguous()
889
+ out_dtype = _matmul_out_dtype(a, M, force_fp32=promote_a)
890
+ c = NBXTensor.empty((M, N), device=a.device, dtype=out_dtype)
891
+ ieee = (not _core._NBX_HAS_NATIVE_BF16) and (out_dtype == NBXDtype.float32)
892
+ # Row banding above 2^31 output elements, exactly as `mm` — see the comment there. The
893
+ # bias is indexed by COLUMN and is shared by every band, so it is passed whole.
894
+ bands = [(0, M)]
895
+ if N > 0 and M * N > _NBX_MM_MAX_OUTPUT_ELEMS:
896
+ per = max(1, _NBX_MM_MAX_OUTPUT_ELEMS // N)
897
+ bands = [(r, min(r + per, M)) for r in range(0, M, per)]
898
+ m_bucket = _bucket_of("M", M)
899
+ _mu_tile = _gemm_unit_tile(a, b, M, N) if bias.ndim == 1 or (bias.ndim == 2 and bias.shape[0] == 1) else None
900
+ for r0, r1 in bands:
901
+ rows = r1 - r0
902
+ a_b = a[r0:r1] if len(bands) > 1 else a
903
+ c_b = c[r0:r1] if len(bands) > 1 else c
904
+ args = (a_b, b, bias, c_b, rows, N, K, m_bucket, a_b.stride(0), a_b.stride(1), b.stride(0), b.stride(1),
905
+ c_b.stride(0), c_b.stride(1), alpha, beta)
906
+ kwargs = dict(IEEE_PRECISION=ieee, PROMOTE_B=promote_b, PROMOTE_A=promote_a, PROMOTE_BIAS=promote_bias,
907
+ EPILOGUE=_epilogue)
908
+ tile = _gemm_route(addmm_kernel, args, kwargs, _mu_tile)
909
+ if tile is not None:
910
+ _gemm_m8n8k4(a_b, b, bias, c_b, alpha, beta, promote_bias, _epilogue, tile)
911
+ continue
912
+ grid = (lambda META, _m=rows: (triton.cdiv(_m, META['BLOCK_M']) * triton.cdiv(N, META['BLOCK_N']),))
913
+ _core._autotune_headroom_guard(addmm_kernel[grid])(*args, **kwargs)
914
+ return c
915
+
916
+
917
+ # Epilogue string -> kernel code mapping (fusion_vertical pass). Kept in
918
+ # ONE place so the emitter, the wrappers, and the kernel constexpr codes
919
+ # cannot drift apart.
920
+ _MATMUL_EPILOGUE_CODES = {
921
+ ("silu", "none"): 1,
922
+ ("silu", "tanh"): 1, # approximate is silu-irrelevant; tolerate
923
+ ("gelu", "none"): 2,
924
+ ("gelu", "tanh"): 3,
925
+ }
926
+
927
+
928
+ def mm_epilogue_wrapper(a, b, epilogue: str = "silu",
929
+ approximate: str = "none"):
930
+ """custom::mm_epilogue — aten::mm fused with a unary elementwise
931
+ epilogue (fusion_vertical pass, EXACT gate). One kernel launch for
932
+ the pair; byte-identical to the unfused pair via per-stage rounding
933
+ emulation + shared autotune config (same kernel function)."""
934
+ return mm(a, b, _epilogue=_MATMUL_EPILOGUE_CODES[(epilogue, approximate)])
935
+
936
+
937
+ def addmm_epilogue_wrapper(bias, a, b, beta: float = 1.0,
938
+ alpha: float = 1.0, epilogue: str = "silu",
939
+ approximate: str = "none"):
940
+ """custom::addmm_epilogue — aten::addmm fused with a unary
941
+ elementwise epilogue (fusion_vertical pass, EXACT gate)."""
942
+ return addmm(bias, a, b, beta=beta, alpha=alpha,
943
+ _epilogue=_MATMUL_EPILOGUE_CODES[(epilogue, approximate)])
944
+ #: A matmul launch whose OUTPUT holds more than this many elements is split along M. The
945
+ #: boundary is 2^31 because that is where an int32 element offset wraps; the margin below it
946
+ #: is deliberate, so a tile that straddles the boundary is never the last one.
947
+ _NBX_MM_MAX_OUTPUT_ELEMS = int(os.environ.get("NBX_MM_MAX_OUTPUT_ELEMS", str(2 ** 31 - 2 ** 20)))
948
+
949
+
950
+ # ---------------------------------------------------------------------------
951
+ # Dot product (2 vectors → scalar)
952
+ # ---------------------------------------------------------------------------
953
+
954
+ def dot_wrapper(x, y) :
955
+ """Dot product: sum(x * y). Two-phase reduction for large N."""
956
+ x, y = x.contiguous().view(-1), y.contiguous().view(-1)
957
+ N = x.numel()
958
+ BLOCK_SIZE = min(triton.next_power_of_2(N), 4096)
959
+
960
+ if N <= BLOCK_SIZE:
961
+ out = NBXTensor.empty([], dtype=NBXDtype.float32, device=x.device)
962
+ set_device(x)
963
+ dot_kernel_small[(1,)](x, y, out, N, BLOCK_SIZE=BLOCK_SIZE)
964
+ return out.to(x.dtype)
965
+ else:
966
+ mid_size = triton.cdiv(N, BLOCK_SIZE)
967
+ mid = NBXTensor.empty(mid_size, dtype=NBXDtype.float32, device=x.device)
968
+ set_device(x)
969
+ dot_kernel_partial[(mid_size,)](x, y, mid, N, BLOCK_SIZE=BLOCK_SIZE)
970
+ BLOCK_MID = triton.next_power_of_2(mid_size)
971
+ out = NBXTensor.empty([], dtype=NBXDtype.float32, device=x.device)
972
+ set_device(mid)
973
+ dot_kernel_reduce[(1,)](mid, out, mid_size, BLOCK_MID=BLOCK_MID)
974
+ return out.to(x.dtype)
975
+
976
+
977
+ # ---------------------------------------------------------------------------
978
+ # MV (matrix-vector multiply)
979
+ # ---------------------------------------------------------------------------
980
+
981
+ def mv_wrapper(mat, vec) :
982
+ """Matrix-vector multiply: out[i] = sum_j(mat[i, j] * vec[j]).
983
+
984
+ The SIMT gemv_vec kernel (ops/gemv_vec.py, adopted 2026-08-24 over the FlagGems mv kernel: its (BLOCK_N,)
985
+ per-chunk-reduced accumulator, all of K inside one program in fixed order — no split-K atomics). Its tile —
986
+ rows per program, K chunk, warps, stages — is the certified configuration of the key (workstream 1, small-M
987
+ GEMM): the N and K buckets from the launcher's own `bucket_of` and the dtypes of its tensors, the key the derived
988
+ census forms (`launch_keys.mv_launches`).
989
+ """
990
+ # A wrapper that copies whatever it is given cannot honour a caller that took care not to
991
+ # give it a copy. The gemv kernels take the matrix's two strides and the vector's one; what
992
+ # they need is K-contiguous ROWS (stride(1) == 1: the pre-transposed weight layout, where
993
+ # the loads vectorise) — a matrix whose rows are strided is copied once here and the line
994
+ # says why; a vector is read through its stride, a broadcast (stride 0) materialised.
995
+ if mat.stride(1) != 1:
996
+ mat = mat.contiguous() # justified: the kernel's rows must be K-contiguous
997
+ if vec.stride(0) == 0:
998
+ vec = vec.contiguous() # justified: a broadcast vector has no stride to walk
999
+ N, K = mat.shape
1000
+ out = NBXTensor.empty(N, device=mat.device, dtype=_matmul_out_dtype(mat))
1001
+ set_device(mat)
1002
+ from nbxtensor.ops.gemv_vec import gemv_vec_kernel
1003
+ gemv_vec_kernel[lambda META: (triton.cdiv(N, META['BLOCK_N']),)](
1004
+ mat, vec, out,
1005
+ N, K, _bucket_of("N", N), _bucket_of("K", K),
1006
+ mat.stride(0), mat.stride(1),
1007
+ vec.stride(0),
1008
+ out.stride(0),
1009
+ )
1010
+ return out
1011
+
1012
+
1013
+ # ---------------------------------------------------------------------------
1014
+ # Baddbmm (batched addmm)
1015
+ # ---------------------------------------------------------------------------
1016
+
1017
+ def baddbmm_wrapper(
1018
+ input, batch1, batch2,
1019
+ beta: float = 1.0, alpha: float = 1.0,
1020
+ ) :
1021
+ """Batched addmm: out = beta * input + alpha * (batch1 @ batch2)."""
1022
+ batch1, batch2 = batch1.contiguous(), batch2.contiguous()
1023
+ input = input.contiguous()
1024
+ B, M, K = batch1.shape
1025
+ _, K2, N = batch2.shape
1026
+ if K != K2:
1027
+ raise AssertionError(
1028
+ f"baddbmm shape mismatch: ({B}, {M}, {K}) @ (..., {K2}, {N}) — the "
1029
+ f"contracted dimension differs, {K} against {K2}")
1030
+
1031
+ output = NBXTensor.empty((B, M, N), device=batch1.device, dtype=batch1.dtype)
1032
+
1033
+ # Handle bias broadcasting: input may be [M, N], [B, M, N], [1, M, N],
1034
+ # [B, 1, N], [M, 1], etc. Any size-1 dim broadcasts with stride 0 —
1035
+ # keeping the memory stride of a size-1 dim would walk PAST the bias
1036
+ # row/column (wrong values, OOB on the last batch).
1037
+ if input.ndim == 2:
1038
+ bias_batch_stride = 0
1039
+ bias_m_stride = input.stride(0) if input.shape[0] > 1 else 0
1040
+ bias_n_stride = input.stride(1) if input.shape[1] > 1 else 0
1041
+ else:
1042
+ bias_batch_stride = input.stride(0) if input.shape[0] > 1 else 0
1043
+ bias_m_stride = input.stride(-2) if input.shape[-2] > 1 else 0
1044
+ bias_n_stride = input.stride(-1) if input.shape[-1] > 1 else 0
1045
+
1046
+ _mu_tile = _gemm_unit_tile(batch1, batch2, M, N)
1047
+ if _mu_tile is not None: # the matrix unit (same epilogue: alpha * acc + beta * bias, cast to output)
1048
+ _gemm_m8n8k4(batch1, batch2, input, output, alpha, beta, False, 0, _mu_tile,
1049
+ bias_strides=(bias_batch_stride, bias_m_stride, bias_n_stride))
1050
+ return output
1051
+
1052
+ set_device(batch1)
1053
+ # IEEE_PRECISION=True preserves the kernel's historical
1054
+ # `allow_tf32=False` dot semantics for fp32 inputs (ignored by tl.dot
1055
+ # for half inputs). HAS_BIAS=True keeps the beta*bias epilogue.
1056
+ # gridDim.z caps at _GRID_Z_MAX — chunk the batch past it (the loop
1057
+ # body runs exactly once below the cap, no narrow() on the fast path).
1058
+ for z0 in range(0, B, _GRID_Z_MAX):
1059
+ zb = min(_GRID_Z_MAX, B - z0)
1060
+ b1_z = batch1 if zb == B else batch1.narrow(0, z0, zb)
1061
+ b2_z = batch2 if zb == B else batch2.narrow(0, z0, zb)
1062
+ out_z = output if zb == B else output.narrow(0, z0, zb)
1063
+ bias_z = (input if (zb == B or bias_batch_stride == 0)
1064
+ else input.narrow(0, z0, zb))
1065
+ grid = lambda META, _zb=zb: (
1066
+ triton.cdiv(M, META['BLOCK_M']) * triton.cdiv(N, META['BLOCK_N']),
1067
+ 1,
1068
+ _zb,
1069
+ )
1070
+ _core._autotune_headroom_guard(baddbmm_kernel[grid])(
1071
+ b1_z, b2_z, out_z, bias_z,
1072
+ alpha, beta,
1073
+ M, N, K, _bucket_of("M", M), _bucket_of("N", N), _bucket_of("K", K),
1074
+ batch1.stride(0), batch1.stride(1), batch1.stride(2),
1075
+ batch2.stride(0), batch2.stride(1), batch2.stride(2),
1076
+ output.stride(0), output.stride(1), output.stride(2),
1077
+ bias_batch_stride, bias_m_stride, bias_n_stride,
1078
+ IEEE_PRECISION=True,
1079
+ PROMOTE_B=False,
1080
+ HAS_BIAS=True,
1081
+ )
1082
+ return output
1083
+
1084
+
1085
+ # ---------------------------------------------------------------------------
1086
+ # Addmv (matrix-vector with bias)
1087
+ # ---------------------------------------------------------------------------
1088
+
1089
+ def addmv_wrapper(
1090
+ input, mat, vec,
1091
+ beta: float = 1.0, alpha: float = 1.0,
1092
+ ) :
1093
+ """Matrix-vector with bias: out = beta * input + alpha * (mat @ vec)."""
1094
+ mat, vec = mat.contiguous(), vec.contiguous()
1095
+ input = input.contiguous()
1096
+ N, M = mat.shape
1097
+ out = NBXTensor.empty(N, device=mat.device, dtype=_matmul_out_dtype(mat))
1098
+ set_device(mat)
1099
+ # rows, reduction tile, warps: the certified configuration of the key (workstream 1, small-M GEMM) — the N and M
1100
+ # buckets and the dtypes of its tensors, the key the derived census forms (`launch_keys.addmv_launches`)
1101
+ addmv_kernel[lambda META: (triton.cdiv(N, META['BLOCK_N']),)](
1102
+ mat, vec, input, out,
1103
+ N, M, _bucket_of("N", N), _bucket_of("M", M),
1104
+ alpha, beta,
1105
+ mat.stride(0), mat.stride(1),
1106
+ vec.stride(0),
1107
+ input.stride(0),
1108
+ out.stride(0),
1109
+ )
1110
+ return out
1111
+
1112
+
1113
+ def upsample_bilinear2d_wrapper(x, output_size, align_corners=False,
1114
+ scales_h=None, scales_w=None) :
1115
+ """Bilinear upsampling."""
1116
+ N, C, IH, IW = x.shape
1117
+ if isinstance(output_size, (list, tuple)):
1118
+ OH, OW = output_size
1119
+ else:
1120
+ OH, OW = output_size, output_size
1121
+ output = NBXTensor.empty((N, C, OH, OW), device=x.device, dtype=x.dtype)
1122
+ if align_corners and OH > 1:
1123
+ scale_h = (IH - 1) / (OH - 1)
1124
+ scale_w = (IW - 1) / (OW - 1)
1125
+ offset = 0.0
1126
+ else:
1127
+ scale_h = IH / OH
1128
+ scale_w = IW / OW
1129
+ offset = 0.5
1130
+ # Grid must match the kernel's axis use: program_id(0)→ow (OW, BLOCK_X),
1131
+ # program_id(1)→oh (OH, BLOCK_Y). The OH/OW axes were swapped here, which is
1132
+ # invisible for square outputs (OH==OW, the image-upscaler case) but drops
1133
+ # most output columns when OH != OW — e.g. the 1-D-as-2-D path used by
1134
+ # upsample_linear1d (H=1), which the Kokoro iSTFT SineGen relies on.
1135
+ grid = (
1136
+ triton.cdiv(OW, _core._BILINEAR_BX),
1137
+ triton.cdiv(OH, _core._BILINEAR_BY),
1138
+ N * C,
1139
+ )
1140
+ set_device(x.contiguous())
1141
+ upsample_bilinear2d_kernel[grid](
1142
+ x.contiguous(), output,
1143
+ N, C, IH, IW, OH, OW,
1144
+ scale_h, scale_w, offset,
1145
+ BLOCK_X=_core._BILINEAR_BX, BLOCK_Y=_core._BILINEAR_BY,
1146
+ num_warps=4)
1147
+ return output
1148
+
1149
+
1150
+ # ===========================================================================
1151
+ # INTERPOLATION — upsample_linear1d via bilinear2d
1152
+ # ===========================================================================
1153
+
1154
+ def upsample_linear1d_wrapper(x, output_size, align_corners: bool = False,
1155
+ scales=None) :
1156
+ """upsample_linear1d via bilinear2d: [N,C,L] → unsqueeze → [N,C,1,L] → bilinear2d → squeeze.
1157
+
1158
+ Like upsample_nearest2d_wrapper: the traced graph stores BOTH the concrete
1159
+ trace output_size AND the scale factor. When the runtime input length differs
1160
+ from trace (variable-length audio decoders — Kokoro iSTFT source generator
1161
+ runs at 124 vs the 256 trace), the baked output_size is stale; PyTorch
1162
+ recomputes from the live input when a scale is present, so we do the same:
1163
+ out_l = round(runtime_input_len * scale). Bit-identical when trace == runtime
1164
+ (input_len * scale == baked output_size). Without this, a linear upsample
1165
+ would desync from its sibling nearest upsample (which already scale-recomputes).
1166
+ """
1167
+ x = _core._ensure_cuda(x).contiguous()
1168
+ x_4d = x.unsqueeze(2) # [N, C, 1, L]
1169
+
1170
+ scale_val = None
1171
+ if scales is not None:
1172
+ scale_val = scales[0] if isinstance(scales, (list, tuple)) else float(scales)
1173
+
1174
+ if scale_val is not None:
1175
+ out_l = int(round(x.shape[-1] * scale_val))
1176
+ elif output_size is not None:
1177
+ out_l = output_size[0] if isinstance(output_size, (list, tuple)) else int(output_size)
1178
+ else:
1179
+ out_l = None
1180
+ output_size_2d = [1, out_l] if out_l is not None else None
1181
+
1182
+ # Pass output_size only; bilinear2d derives its interpolation scale from the
1183
+ # (input, output) shapes + align_corners.
1184
+ result_4d = upsample_bilinear2d_wrapper(x_4d, output_size_2d, align_corners)
1185
+ return result_4d.squeeze(2)
1186
+
1187
+
1188
+ def mm_cost(a, b, epilogue: int = 0, native_bf16=None, force_accum=None, contract_half=None):
1189
+ """`mm` on `a` [M, K] and `b` [K, N] (`_cost.Spec`s), in the wrapper's order: the operands cast to
1190
+ `launch_keys.mm_dtypes` (a dense copy each); then, at M <= 4, `a` made contiguous, the output [M, N] and, per row,
1191
+ the GEMV's own output (N at `matmul_out_dtype` for one row) — and a full copy of the weight when its rows are not
1192
+ K-contiguous (`mv_wrapper`: `b.t()` must have stride 1 along K; a pre-transposed weight view has it, a row-major
1193
+ one does not) — each freed once copied into the output's row, then an epilogue (SiLU, GELU) makes its output while
1194
+ the GEMM's is alive; above, `a` made contiguous, `b` only when broadcast (a zero stride), the output at
1195
+ `matmul_out_dtype` (fp32 when `a` was promoted), the matrix unit or the kernel allocating nothing more (row bands
1196
+ are views, the epilogue runs in the kernel). A local rebound frees its old buffer after the new one exists; the
1197
+ rest die at the return."""
1198
+ from nbxtensor.ops import _cost as C
1199
+ from nbxtensor.shape import is_contiguous
1200
+ native_bf16 = bool(_core._NBX_HAS_NATIVE_BF16) if native_bf16 is None else native_bf16
1201
+ force_accum = bool(NBX_FORCE_FP32_ACCUM) if force_accum is None else force_accum
1202
+ contract_half = bool(_core._NBX_ACTIVATIONS_FP16_SAFE) if contract_half is None else contract_half
1203
+ st = C._Steps()
1204
+ M, K = (int(d) for d in a.shape)
1205
+ N = int(b.shape[1])
1206
+ flops = 2 * M * N * K
1207
+ held = {}
1208
+
1209
+ def rebind(name, spec, label):
1210
+ i = st.alloc(label, spec.nbytes)
1211
+ old = held.get(name)
1212
+ held[name] = i
1213
+ if old is not None:
1214
+ st.free(old)
1215
+ return spec
1216
+
1217
+ a_to, b_to, promote_a, _promote_b = _lk.mm_dtypes(NBXDtype[a.dtype], NBXDtype[b.dtype], native_bf16, force_accum)
1218
+ if NBXDtype[a.dtype] != a_to:
1219
+ a = rebind("a", C.Spec.contiguous(a.shape, a_to.name), "a cast")
1220
+ if NBXDtype[b.dtype] != b_to:
1221
+ b = rebind("b", C.Spec.contiguous(b.shape, b_to.name), "b cast")
1222
+ if not is_contiguous(a.shape, a.strides):
1223
+ a = rebind("a", C.Spec.contiguous(a.shape, a.dtype), "a made contiguous")
1224
+ if M <= 4:
1225
+ out = _lk.matmul_out_dtype(NBXDtype[a.dtype], M, False, native_bf16, force_accum, contract_half)
1226
+ c = st.alloc("output", M * N * dtype_size(out), returned=epilogue == 0)
1227
+ row_out = _lk.matmul_out_dtype(NBXDtype[b.dtype], 1, False, native_bf16, force_accum, contract_half)
1228
+ k_contiguous_rows = int(b.strides[0]) == 1 # b.t() has stride 1 along K
1229
+ for _ in range(M):
1230
+ copy = None if k_contiguous_rows else st.alloc("the weight made K-contiguous (mv)", b.nbytes)
1231
+ r = st.alloc("a row's GEMV output", N * dtype_size(row_out))
1232
+ if copy is not None:
1233
+ st.free(copy)
1234
+ st.free(r)
1235
+ else:
1236
+ if any(int(x) == 0 for x in b.strides):
1237
+ b = rebind("b", C.Spec.contiguous(b.shape, b.dtype), "b broadcast made dense")
1238
+ out = _lk.matmul_out_dtype(NBXDtype[a.dtype], M, bool(promote_a), native_bf16, force_accum, contract_half)
1239
+ st.alloc("output", M * N * dtype_size(out), returned=True)
1240
+ return st.done(flops) # an epilogue runs inside the kernel here (EPILOGUE)
1241
+ if epilogue: # the GEMV rows: the epilogue after, its own output
1242
+ for i in st.nest(C.elementwise_unary(C.Spec.contiguous((M, N), out.name)), "epilogue"):
1243
+ st.buffers[i][4] = True # the epilogue's output is the one returned
1244
+ return st.done(flops)
1245
+
1246
+
1247
+ def addmm_cost(bias, a, b, epilogue: int = 0, native_bf16=None, force_accum=None, contract_half=None):
1248
+ """`addmm` on `bias` ([N], [1, N] or [M, N]), `a` [..., K] and `b` [K, N] (`_cost.Spec`s), in the wrapper's order:
1249
+ the three operands cast to `launch_keys.addmm_dtypes` (a dense copy each); an `a` of more than two dims made
1250
+ contiguous and flattened, the product computed on the flat rows (its view returned); then, at M <= 4, `a` and the
1251
+ bias made contiguous, the output [M, N] at `matmul_out_dtype` and per row the GEMV's own output (N for one row) —
1252
+ and the whole weight copied when `b.t()` is not contiguous (`addmv_wrapper` makes its matrix contiguous: a row-major
1253
+ weight; a pre-transposed weight view is) — each freed once copied into the output's row, then an epilogue's output
1254
+ beside the GEMM's; above, `a` and the bias made contiguous, `b` only when broadcast, the output at
1255
+ `matmul_out_dtype` (fp32 when `a` was promoted), the epilogue in the kernel. A bias of another shape is refused by
1256
+ name (the wrapper's own rows: shared [N] / [1, N], per-row [M, N] at M <= 4 only, as the wrapper)."""
1257
+ from nbxtensor.ops import _cost as C
1258
+ from nbxtensor.shape import is_contiguous
1259
+ native_bf16 = bool(_core._NBX_HAS_NATIVE_BF16) if native_bf16 is None else native_bf16
1260
+ force_accum = bool(NBX_FORCE_FP32_ACCUM) if force_accum is None else force_accum
1261
+ contract_half = bool(_core._NBX_ACTIVATIONS_FP16_SAFE) if contract_half is None else contract_half
1262
+ st = C._Steps()
1263
+ held = {}
1264
+
1265
+ def rebind(name, spec, label):
1266
+ i = st.alloc(label, spec.nbytes)
1267
+ old = held.get(name)
1268
+ held[name] = i
1269
+ if old is not None:
1270
+ st.free(old)
1271
+ return spec
1272
+
1273
+ a_to, b_to, bias_to, promote_a, _pb, _pbias = _lk.addmm_dtypes(
1274
+ NBXDtype[a.dtype], NBXDtype[b.dtype], NBXDtype[bias.dtype], native_bf16, force_accum)
1275
+ if NBXDtype[a.dtype] != a_to:
1276
+ a = rebind("a", C.Spec.contiguous(a.shape, a_to.name), "a cast")
1277
+ if NBXDtype[b.dtype] != b_to:
1278
+ b = rebind("b", C.Spec.contiguous(b.shape, b_to.name), "b cast")
1279
+ if NBXDtype[bias.dtype] != bias_to:
1280
+ bias = rebind("bias", C.Spec.contiguous(bias.shape, bias_to.name), "bias cast")
1281
+ if len(a.shape) > 2:
1282
+ if not is_contiguous(a.shape, a.strides):
1283
+ a = rebind("a", C.Spec.contiguous(a.shape, a.dtype), "a made contiguous (flattened)")
1284
+ rows = 1
1285
+ for d in a.shape[:-1]:
1286
+ rows *= int(d)
1287
+ a = C.Spec.contiguous((rows, int(a.shape[-1])), a.dtype)
1288
+ M, K = (int(d) for d in a.shape)
1289
+ N = int(b.shape[1])
1290
+ if not (len(bias.shape) == 1 or (len(bias.shape) == 2 and int(bias.shape[0]) in (1, M))):
1291
+ raise ValueError(f"addmm_cost: a bias of shape {tuple(bias.shape)} is none of the wrapper's ([N], [1, N], [M, N])")
1292
+ flops = 2 * M * N * K
1293
+ if not is_contiguous(a.shape, a.strides):
1294
+ a = rebind("a", C.Spec.contiguous(a.shape, a.dtype), "a made contiguous")
1295
+ if not is_contiguous(bias.shape, bias.strides):
1296
+ bias = rebind("bias", C.Spec.contiguous(bias.shape, bias.dtype), "bias made contiguous")
1297
+ if M <= 4:
1298
+ out = _lk.matmul_out_dtype(NBXDtype[a.dtype], M, False, native_bf16, force_accum, contract_half)
1299
+ st.alloc("output", M * N * dtype_size(out), returned=epilogue == 0)
1300
+ row_out = _lk.matmul_out_dtype(NBXDtype[b.dtype], 1, False, native_bf16, force_accum, contract_half)
1301
+ bt_contiguous = tuple(int(x) for x in b.strides) == (1, K) # b.t() is (N, K) row-major
1302
+ for _ in range(M):
1303
+ copy = None if bt_contiguous else st.alloc("the weight made contiguous (addmv)", b.nbytes)
1304
+ r = st.alloc("a row's GEMV output", N * dtype_size(row_out))
1305
+ if copy is not None:
1306
+ st.free(copy)
1307
+ st.free(r)
1308
+ if epilogue:
1309
+ for i in st.nest(C.elementwise_unary(C.Spec.contiguous((M, N), out.name)), "epilogue"):
1310
+ st.buffers[i][4] = True
1311
+ return st.done(flops)
1312
+ if len(bias.shape) == 2 and int(bias.shape[0]) != 1:
1313
+ raise ValueError(f"addmm_cost: a per-row bias {tuple(bias.shape)} is served at M <= 4 only (here M = {M}), "
1314
+ f"as the wrapper refuses it")
1315
+ if any(int(x) == 0 for x in b.strides):
1316
+ b = rebind("b", C.Spec.contiguous(b.shape, b.dtype), "b broadcast made dense")
1317
+ out = _lk.matmul_out_dtype(NBXDtype[a.dtype], M, bool(promote_a), native_bf16, force_accum, contract_half)
1318
+ st.alloc("output", M * N * dtype_size(out), returned=True)
1319
+ return st.done(flops)
1320
+
1321
+
1322
+ def bmm_cost(a, b, allow_strided_b: bool = False, native_bf16=None, force_accum=None, contract_half=None,
1323
+ unit=None):
1324
+ """`bmm` on `a` [B, M, K] and `b` [B, K, N] (`_cost.Spec`s): on the matrix unit (`launch_keys.matrix_unit_operands`)
1325
+ `a` made contiguous and `b` unless the caller lets it be walked by its strides, then the output; otherwise the
1326
+ operands at `launch_keys.bmm_dtypes` (a cast each, dense), made contiguous as above, then the output at
1327
+ `launch_keys.matmul_out_dtype` (fp32 for half inputs on hardware without native bf16 unless the component's precision
1328
+ contract holds). The facts the wrapper reads from the run (`native_bf16`, `force_accum`, `contract_half`) are the
1329
+ run's in force when None; `unit` the profile's matrix unit when None. A local rebound frees its old buffer after
1330
+ the new one exists; the rest die at the return."""
1331
+ from nbxtensor.ops import _cost as C
1332
+ from nbxtensor.shape import is_contiguous
1333
+ native_bf16 = bool(_core._NBX_HAS_NATIVE_BF16) if native_bf16 is None else native_bf16
1334
+ force_accum = bool(NBX_FORCE_FP32_ACCUM) if force_accum is None else force_accum
1335
+ contract_half = bool(_core._NBX_ACTIVATIONS_FP16_SAFE) if contract_half is None else contract_half
1336
+ st = C._Steps()
1337
+ Bz, M, K = (int(d) for d in a.shape)
1338
+ N = int(b.shape[2])
1339
+ flops = 2 * Bz * M * N * K
1340
+ ad, bd = NBXDtype[a.dtype], NBXDtype[b.dtype]
1341
+ held = {}
1342
+
1343
+ def rebind(name, spec, label):
1344
+ i = st.alloc(label, spec.nbytes)
1345
+ old = held.get(name)
1346
+ held[name] = i
1347
+ if old is not None:
1348
+ st.free(old)
1349
+ return spec
1350
+
1351
+ if _lk.matrix_unit_operands(ad, bd, M, unit) is not None:
1352
+ if not is_contiguous(a.shape, a.strides):
1353
+ a = rebind("a", C.Spec.contiguous(a.shape, a.dtype), "a made contiguous")
1354
+ if not allow_strided_b and not is_contiguous(b.shape, b.strides):
1355
+ b = rebind("b", C.Spec.contiguous(b.shape, b.dtype), "b made contiguous")
1356
+ out = _lk.matmul_out_dtype(ad, M, True, native_bf16, force_accum, contract_half)
1357
+ st.alloc("output", Bz * M * N * dtype_size(out), returned=True)
1358
+ return st.done(flops)
1359
+ a_to, b_to, _promote = _lk.bmm_dtypes(ad, bd, native_bf16, force_accum)
1360
+ if ad != a_to:
1361
+ a = rebind("a", C.Spec.contiguous(a.shape, a_to.name), "a cast")
1362
+ if bd != b_to:
1363
+ b = rebind("b", C.Spec.contiguous(b.shape, b_to.name), "b cast")
1364
+ if not is_contiguous(a.shape, a.strides):
1365
+ a = rebind("a", C.Spec.contiguous(a.shape, a.dtype), "a made contiguous")
1366
+ if not allow_strided_b and not is_contiguous(b.shape, b.strides):
1367
+ b = rebind("b", C.Spec.contiguous(b.shape, b.dtype), "b made contiguous")
1368
+ out = _lk.matmul_out_dtype(NBXDtype[a.dtype], M, True, native_bf16, force_accum, contract_half)
1369
+ st.alloc("output", Bz * M * N * dtype_size(out), returned=True)
1370
+ return st.done(flops)