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
nbxtensor/ops/_cost.py ADDED
@@ -0,0 +1,231 @@
1
+ """The cost an op declares: what it allocates, for how long, what it moves and computes (R-NBXT 4) — prototype.
2
+
3
+ A planner (NeuroBrix's Prism) prices a model from these declarations instead of re-deriving what the allocator and the
4
+ kernels do. Everything here is plain data (R-NBXT 11): an operand is a `Spec(shape, strides, dtype)`, a cost is a
5
+ `Cost` of `Buffer`s with integer lifetimes, byte counts and flop counts. The declarations read the same layout rules
6
+ the ops execute (`nbxtensor.shape`) and the same widths (`nbxtensor.dtype`).
7
+
8
+ Prototype scope (docs/nbxtensor/COST.md): the element-wise, reduction and normalisation families, mirroring today's
9
+ wrappers (NeuroBrix `kernels/wrappers.py`) line by line, each declaration citing the allocation it mirrors. Where a
10
+ wrapper takes a decision the library does not hold yet (the binary ops' result dtype, the DtypeEngine's output
11
+ dtype), the caller passes it in; when the wrappers move into the library (M2/M3), `cost` reads the same function the
12
+ kernel launch reads, so the declaration and the execution cannot drift.
13
+
14
+ Lifetimes are steps of the op's own sequence: a buffer lives over [first, last]; today every transient lives to the
15
+ op's return (the wrappers keep it in a local name); the operands are the caller's and are never counted. `peak_bytes` is the most the op holds at once beyond its operands, outputs included.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass
20
+ from typing import Optional, Sequence, Tuple
21
+
22
+ from nbxtensor.dtype.itemsize import itemsize
23
+ from nbxtensor.shape import contiguous_strides, is_contiguous, numel
24
+
25
+ __all__ = ["Spec", "Buffer", "Cost", "elementwise_unary", "elementwise_binary", "softmax", "rms_norm",
26
+ "native_layer_norm", "reduction"]
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class Spec:
31
+ """An operand: its shape, its strides in elements, its dtype name."""
32
+ shape: Tuple[int, ...]
33
+ strides: Tuple[int, ...]
34
+ dtype: str
35
+
36
+ @staticmethod
37
+ def contiguous(shape: Sequence[int], dtype: str) -> "Spec":
38
+ return Spec(tuple(shape), contiguous_strides(tuple(shape)), dtype)
39
+
40
+ @property
41
+ def nbytes(self) -> int:
42
+ return numel(self.shape) * itemsize(self.dtype)
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class Buffer:
47
+ """A buffer the op allocates: its bytes, what it is, and the steps it lives over (`first` to `last`, inclusive).
48
+ `returned` buffers are the op's outputs: they outlive the op (their `last` is the op's last step)."""
49
+ name: str
50
+ nbytes: int
51
+ first: int
52
+ last: int
53
+ returned: bool = False
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class Cost:
58
+ buffers: Tuple[Buffer, ...]
59
+ bytes_moved: int # every byte read and written, copies included
60
+ flops: int
61
+
62
+ @property
63
+ def outputs(self) -> Tuple[Buffer, ...]:
64
+ return tuple(b for b in self.buffers if b.returned)
65
+
66
+ @property
67
+ def workspace(self) -> Tuple[Buffer, ...]:
68
+ return tuple(b for b in self.buffers if not b.returned)
69
+
70
+ @property
71
+ def peak_bytes(self) -> int:
72
+ steps = {s for b in self.buffers for s in (b.first, b.last)}
73
+ return max((sum(b.nbytes for b in self.buffers if b.first <= s <= b.last) for s in steps), default=0)
74
+
75
+
76
+ class _Steps:
77
+ """The op's sequence: each allocation takes the next step. Every buffer lives until the op returns: the wrappers
78
+ hold their transients in local names (the cast, the contiguous copy, the accumulator), which Python releases at
79
+ the return, never earlier."""
80
+
81
+ def __init__(self):
82
+ self.buffers = []
83
+ self.moved = 0
84
+ self.step = 0
85
+
86
+ def alloc(self, name: str, nbytes: int, returned: bool = False) -> int:
87
+ self.step += 1
88
+ self.buffers.append([name, int(nbytes), self.step, None, returned])
89
+ return len(self.buffers) - 1
90
+
91
+ def free(self, index: int) -> None:
92
+ """The buffer dies here (a transient the wrapper drops before it returns: a reassigned local, a callee's
93
+ return): it lives over [its step, this step]."""
94
+ self.step += 1
95
+ self.buffers[index][3] = self.step
96
+
97
+ def done(self, flops: int) -> Cost:
98
+ end = self.step
99
+ return Cost(tuple(Buffer(n, b, f, end if l is None else l, r) for n, b, f, l, r in self.buffers),
100
+ self.moved, int(flops))
101
+
102
+ def nest(self, cost: "Cost", prefix: str) -> list:
103
+ """A callee's cost inside this op's sequence: its buffers allocated in its order, its transients freed when it
104
+ returns; its returned buffers are the caller's to free (`free` on the indices returned)."""
105
+ base = self.step
106
+ kept = []
107
+ for b in cost.buffers:
108
+ self.buffers.append([f"{prefix}: {b.name}", b.nbytes, base + b.first,
109
+ None if b.returned else base + b.last, False])
110
+ if b.returned:
111
+ kept.append(len(self.buffers) - 1)
112
+ self.step = base + max((b.last for b in cost.buffers), default=0)
113
+ self.moved += cost.bytes_moved
114
+ return kept
115
+
116
+
117
+ def _contiguous_copy(st: _Steps, x: Spec, name: str) -> Optional[int]:
118
+ """`x.contiguous()`: no buffer when `x` already lies row-major (`shape.is_contiguous`), else one copy of its
119
+ elements, read strided and written dense."""
120
+ if is_contiguous(x.shape, x.strides):
121
+ return None
122
+ st.moved += 2 * x.nbytes
123
+ return st.alloc(name, x.nbytes)
124
+
125
+
126
+ def elementwise_unary(x: Spec, out_dtype: Optional[str] = None, flops_per_element: int = 1) -> Cost:
127
+ """A unary element-wise op (`wrappers._prepare_unary`: `x.contiguous()`, then `empty_like` the output; `out_dtype`
128
+ when the op writes another dtype, e.g. a comparison's bool)."""
129
+ st = _Steps()
130
+ _contiguous_copy(st, x, "contiguous input")
131
+ out = Spec.contiguous(x.shape, out_dtype or x.dtype)
132
+ st.alloc("output", out.nbytes, returned=True)
133
+ st.moved += x.nbytes + out.nbytes
134
+ return st.done(flops_per_element * numel(x.shape))
135
+
136
+
137
+ def elementwise_binary(a: Spec, b: Spec, common_dtype: str, out_shape: Sequence[int],
138
+ out_dtype: Optional[str] = None, flops_per_element: int = 1) -> Cost:
139
+ """A binary element-wise op of two tensors (`wrappers._prepare_binary_strided`): each operand not already at
140
+ `common_dtype` is cast — a COPY at that dtype (the RoPE lesson: an in-kernel widen moves the compiler's
141
+ contraction) —, then both are read by strides as broadcast VIEWS (no expand copy), and the output is dense at
142
+ the broadcast shape. `common_dtype` and `out_shape` are the wrapper's decisions, passed in until the wrapper's
143
+ promotion lives here."""
144
+ st = _Steps()
145
+ for name, x in (("a cast", a), ("b cast", b)):
146
+ if x.dtype != common_dtype:
147
+ cast = Spec(x.shape, x.strides, common_dtype)
148
+ st.moved += x.nbytes + cast.nbytes
149
+ st.alloc(name, cast.nbytes)
150
+ out = Spec.contiguous(out_shape, out_dtype or common_dtype)
151
+ st.alloc("output", out.nbytes, returned=True)
152
+ st.moved += numel(a.shape) * itemsize(common_dtype) + numel(b.shape) * itemsize(common_dtype) + out.nbytes
153
+ return st.done(flops_per_element * numel(out_shape))
154
+
155
+
156
+ def softmax(x: Spec, dim: int) -> Cost:
157
+ """`wrappers.softmax`: the reduced dim moved last (`movedim(dim, -1).contiguous()`: a copy unless that view is
158
+ already dense), the output `empty_like` the 2-D view, returned moved back (a view). Fused and looped kernels
159
+ alike hold nothing else. Arithmetic per element: the row max, the shift, the exponential, the row sum, the
160
+ division."""
161
+ dim = dim % len(x.shape)
162
+ order = [d for d in range(len(x.shape)) if d != dim] + [dim]
163
+ moved = Spec(tuple(x.shape[d] for d in order), tuple(x.strides[d] for d in order), x.dtype)
164
+ st = _Steps()
165
+ _contiguous_copy(st, moved, "rows made contiguous")
166
+ st.alloc("output", x.nbytes, returned=True)
167
+ st.moved += x.nbytes + x.nbytes
168
+ return st.done(5 * numel(x.shape))
169
+
170
+
171
+ def rms_norm(x: Spec, has_weight: bool, out_dtype: Optional[str] = None) -> Cost:
172
+ """`wrappers.rms_norm`: `x.contiguous()`; when that made a fresh copy and the output dtype is the input's, the
173
+ output is written INTO the copy (no second buffer); otherwise a new output. Arithmetic per element: the square,
174
+ the row sum, the scale by the reciprocal root, and the weight's product when there is one."""
175
+ out_dt = out_dtype or x.dtype
176
+ st = _Steps()
177
+ copy = _contiguous_copy(st, x, "contiguous input")
178
+ out_bytes = numel(x.shape) * itemsize(out_dt)
179
+ if copy is not None and out_dt == x.dtype:
180
+ st.buffers[copy][4] = True # the copy IS the output
181
+ st.buffers[copy][0] = "output (in the contiguous copy)"
182
+ else:
183
+ st.alloc("output", out_bytes, returned=True)
184
+ st.moved += x.nbytes + out_bytes + (x.shape[-1] * itemsize(x.dtype) if has_weight else 0)
185
+ return st.done((4 if has_weight else 3) * numel(x.shape))
186
+
187
+
188
+ def native_layer_norm(x: Spec, normalized_shape: Sequence[int], has_weight: bool, has_bias: bool) -> Cost:
189
+ """`wrappers.native_layer_norm`: `x.contiguous()`, the output `empty_like` it, and the row statistics (mean and
190
+ 1/std, float32, one per row) — returned, as aten::native_layer_norm returns (output, mean, rstd). Arithmetic per
191
+ element: the mean's sum, the centring, the square, the variance's sum, the scale; the weight's product and the
192
+ bias's sum when present."""
193
+ feat = numel(normalized_shape)
194
+ rows = numel(x.shape) // feat
195
+ st = _Steps()
196
+ _contiguous_copy(st, x, "contiguous input")
197
+ st.alloc("output", x.nbytes, returned=True)
198
+ st.alloc("mean", rows * itemsize("float32"), returned=True)
199
+ st.alloc("rstd", rows * itemsize("float32"), returned=True)
200
+ affine = feat * itemsize(x.dtype) * (int(has_weight) + int(has_weight and has_bias))
201
+ st.moved += 2 * x.nbytes + 2 * rows * itemsize("float32") + affine
202
+ return st.done((5 + int(has_weight) + int(has_weight and has_bias)) * numel(x.shape))
203
+
204
+
205
+ def reduction(x: Spec, dim: Optional[int], keepdim: bool, out_dtype: str, cast_to: Optional[str] = None) -> Cost:
206
+ """`wrappers.sum_wrapper` / `mean_wrapper` over one dim or all: the input cast to the op's `dtype` keyword when
207
+ given (`cast_to`, a copy), the reduced dim moved last and made contiguous (a copy unless already dense), one
208
+ float32 accumulator per output element, then cast to the result dtype (`out_dtype`: the input's for a float
209
+ input, int64 for sum of an integer one) — a second buffer unless that dtype is float32 (`.to` returns the tensor
210
+ itself then). One addition per input element."""
211
+ st = _Steps()
212
+ src = x
213
+ if cast_to is not None and cast_to != x.dtype:
214
+ src = Spec(x.shape, x.strides, cast_to)
215
+ st.moved += x.nbytes + src.nbytes
216
+ st.alloc("dtype keyword cast", src.nbytes)
217
+ if dim is None:
218
+ rows = 1
219
+ _contiguous_copy(st, src, "flattened input")
220
+ else:
221
+ d = dim % len(src.shape)
222
+ order = [i for i in range(len(src.shape)) if i != d] + [d]
223
+ moved = Spec(tuple(src.shape[i] for i in order), tuple(src.strides[i] for i in order), src.dtype)
224
+ rows = numel(src.shape) // src.shape[d]
225
+ _contiguous_copy(st, moved, "reduced dim made contiguous")
226
+ st.alloc("float32 accumulator", rows * itemsize("float32"), returned=out_dtype == "float32")
227
+ st.moved += src.nbytes + rows * itemsize("float32")
228
+ if out_dtype != "float32":
229
+ st.alloc("result", rows * itemsize(out_dtype), returned=True)
230
+ st.moved += rows * itemsize("float32") + rows * itemsize(out_dtype)
231
+ return st.done(numel(x.shape))
nbxtensor/ops/abs.py ADDED
@@ -0,0 +1,17 @@
1
+ """Absolute value — pure @triton.jit kernel."""
2
+
3
+ import triton
4
+ import triton.language as tl
5
+
6
+ @triton.jit
7
+ def abs_forward_kernel(
8
+ input_ptr, output_ptr,
9
+ n_elements,
10
+ BLOCK_SIZE: tl.constexpr,
11
+ ):
12
+ pid = tl.program_id(0).to(tl.int64)
13
+ 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)
14
+ mask = offset < n_elements
15
+
16
+ x = tl.load(input_ptr + offset, mask=mask)
17
+ tl.store(output_ptr + offset, tl.abs(x), mask=mask)
@@ -0,0 +1,161 @@
1
+ """Adaptive average pooling 2D — pure @triton.jit kernel.
2
+
3
+ For each output element (n, c, oh, ow), the kernel computes the input
4
+ window boundaries using the standard PyTorch adaptive pooling formula:
5
+ start_h = floor(oh * IH / OH)
6
+ end_h = ceil((oh + 1) * IH / OH)
7
+ and averages all input elements in [start_h, end_h) x [start_w, end_w).
8
+
9
+ The end index is a CEILING. It was a floor here, which is the same value
10
+ whenever the output size divides the input size and a smaller window
11
+ whenever it does not — so every exactly-dividing shape agreed and every
12
+ ragged one was quietly averaging too few elements. Measured against torch
13
+ 2026-09-09 by one-hot probing which input rows reach each output row:
14
+ IH=7,OH=3 gives torch [(0,3),(2,5),(4,7)] and the floor form
15
+ [(0,2),(2,4),(4,7)].
16
+
17
+ The window loop was also fixed at 8x8, on a comment that the adaptive
18
+ window is "typically 1-3 pixels each dim". That is false for any pooling
19
+ that downsamples by more than 8, and global average pooling is the common
20
+ case: a 16x16 plane to 1x1 has a 16x16 window, of which the kernel summed
21
+ the first 8x8 and divided by 64. Measured: 0.1807 returned where the plane
22
+ mean is 0.0019. The bound is now derived from the shape.
23
+
24
+ BOTH defects are in this kernel, not in any backend, so both were wrong on
25
+ EVERY vendor. This changes NVIDIA's numbers, and is meant to: keeping them
26
+ would be keeping the bug.
27
+ """
28
+
29
+ import triton
30
+ import triton.language as tl
31
+
32
+ @triton.jit
33
+ def adaptive_avg_pool2d_kernel(
34
+ input_ptr,
35
+ output_ptr,
36
+ n_elements,
37
+ # Input shape: [N, C, IH, IW]
38
+ IH, IW,
39
+ # Output shape: [N, C, OH, OW]
40
+ OH, OW,
41
+ # Strides (input contiguous NCHW)
42
+ stride_n, stride_c, stride_h, stride_w,
43
+ BLOCK_SIZE: tl.constexpr,
44
+ ):
45
+ """Adaptive average pool 2D.
46
+
47
+ Each thread computes one output element by averaging over the
48
+ corresponding adaptive window in the input.
49
+ """
50
+ pid = tl.program_id(0).to(tl.int64)
51
+ idx = 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)
52
+ mask = idx < n_elements
53
+
54
+ # Decompose flat index -> (n, c, oh, ow)
55
+ ow = idx % OW
56
+ tmp = idx // OW
57
+ oh = tmp % OH
58
+ tmp2 = tmp // OH
59
+ c = tmp2 % (stride_n // stride_c) # C = stride_n / stride_c for contiguous
60
+ n = tmp2 // (stride_n // stride_c)
61
+
62
+ # Adaptive pooling window bounds (PyTorch formula)
63
+ # start = floor(out_idx * in_size / out_size)
64
+ # end = CEIL((out_idx + 1) * in_size / out_size)
65
+ h_start = (oh * IH) // OH
66
+ h_end = ((oh + 1) * IH + OH - 1) // OH
67
+ w_start = (ow * IW) // OW
68
+ w_end = ((ow + 1) * IW + OW - 1) // OW
69
+
70
+ # Base pointer for this (n, c) plane
71
+ base = n * stride_n + c * stride_c
72
+
73
+ # Accumulate sum over the window
74
+ # Window size varies per output position in adaptive pooling
75
+ acc = tl.zeros([BLOCK_SIZE], dtype=tl.float32)
76
+ count = tl.zeros([BLOCK_SIZE], dtype=tl.float32)
77
+
78
+ # The widest window this shape can produce, derived rather than assumed.
79
+ # end - start <= ceil((i+1)*IN/OUT) - floor(i*IN/OUT) < IN/OUT + 2, so
80
+ # floor(IN/OUT) + 2 bounds it for every i. The old fixed 8 silently
81
+ # truncated any window wider than that — every global average pool over
82
+ # a plane larger than 8x8 summed a corner and divided by its own count.
83
+ KH = IH // OH + 2
84
+ KW = IW // OW + 2
85
+ for dh in range(0, KH):
86
+ for dw in range(0, KW):
87
+ h_idx = h_start + dh
88
+ w_idx = w_start + dw
89
+ in_bounds = (h_idx < h_end) & (w_idx < w_end)
90
+ valid = mask & in_bounds
91
+
92
+ offset = base + h_idx * stride_h + w_idx * stride_w
93
+ val = tl.load(input_ptr + offset, mask=valid, other=0.0).to(tl.float32)
94
+ acc += tl.where(valid, val, 0.0)
95
+ count += tl.where(valid, 1.0, 0.0)
96
+
97
+ # Average
98
+ count = tl.maximum(count, 1.0) # Avoid division by zero
99
+ result = acc / count
100
+
101
+ # Store
102
+ out_offset = idx
103
+ tl.store(output_ptr + out_offset, result, mask=mask)
104
+
105
+ @triton.jit
106
+ def adaptive_avg_pool2d_tiled_kernel(
107
+ input_ptr,
108
+ output_ptr,
109
+ N, C,
110
+ IH, IW,
111
+ OH, OW,
112
+ BLOCK_X: tl.constexpr,
113
+ BLOCK_Y: tl.constexpr,
114
+ ):
115
+ """Tiled variant for spatial parallelism.
116
+
117
+ Grid: (cdiv(OW, BLOCK_X), cdiv(OH, BLOCK_Y), N*C)
118
+ """
119
+ pid_x = tl.program_id(0).to(tl.int64)
120
+ pid_y = tl.program_id(1).to(tl.int64)
121
+ pid_nc = tl.program_id(2).to(tl.int64)
122
+
123
+ ow = pid_x * BLOCK_X + tl.arange(0, BLOCK_X) # [BLOCK_X]
124
+ oh = pid_y * BLOCK_Y + tl.arange(0, BLOCK_Y) # [BLOCK_Y]
125
+
126
+ mask_h = oh < OH
127
+ mask_w = ow < OW
128
+ mask_2d = mask_h[:, None] & mask_w[None, :]
129
+
130
+ # Window boundaries
131
+ h_start = (oh * IH) // OH # [BLOCK_Y]
132
+ h_end = ((oh + 1) * IH) // OH
133
+ w_start = (ow * IW) // OW # [BLOCK_X]
134
+ w_end = ((ow + 1) * IW) // OW
135
+
136
+ # Window sizes
137
+ kh = h_end - h_start # [BLOCK_Y]
138
+ kw = w_end - w_start # [BLOCK_X]
139
+
140
+ base = pid_nc * IH * IW
141
+
142
+ acc = tl.zeros([BLOCK_Y, BLOCK_X], dtype=tl.float32)
143
+
144
+ for dh in range(0, 4):
145
+ for dw in range(0, 4):
146
+ h_idx = h_start[:, None] + dh
147
+ w_idx = w_start[None, :] + dw
148
+ in_bounds = (dh < kh[:, None]) & (dw < kw[None, :])
149
+ valid = mask_2d & in_bounds
150
+
151
+ offset = base + h_idx * IW + w_idx
152
+ val = tl.load(input_ptr + offset, mask=valid, other=0.0).to(tl.float32)
153
+ acc += tl.where(valid, val, 0.0)
154
+
155
+ area = (kh[:, None] * kw[None, :]).to(tl.float32)
156
+ area = tl.maximum(area, 1.0)
157
+ result = acc / area
158
+
159
+ out_base = pid_nc * OH * OW
160
+ out_off = out_base + oh[:, None] * OW + ow[None, :]
161
+ tl.store(output_ptr + out_off, result, mask=mask_2d)
nbxtensor/ops/add.py ADDED
@@ -0,0 +1,96 @@
1
+ """Add — pure @triton.jit kernel (tensor + tensor, tensor + scalar)."""
2
+
3
+ import triton
4
+ import triton.language as tl
5
+
6
+ @triton.jit
7
+ def add_forward_kernel(
8
+ x_ptr, y_ptr, output_ptr,
9
+ n_elements,
10
+ alpha,
11
+ BLOCK_SIZE: tl.constexpr,
12
+ ):
13
+ """out = x + alpha * y (tensor + tensor)"""
14
+ pid = tl.program_id(0).to(tl.int64)
15
+ 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)
16
+ mask = offset < n_elements
17
+
18
+ x = tl.load(x_ptr + offset, mask=mask)
19
+ y = tl.load(y_ptr + offset, mask=mask)
20
+ tl.store(output_ptr + offset, x + alpha * y, mask=mask)
21
+
22
+ @triton.jit
23
+ def add_scalar_kernel(
24
+ x_ptr, output_ptr,
25
+ n_elements,
26
+ scalar,
27
+ BLOCK_SIZE: tl.constexpr,
28
+ ):
29
+ """out = x + scalar"""
30
+ pid = tl.program_id(0).to(tl.int64)
31
+ 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)
32
+ mask = offset < n_elements
33
+
34
+ x = tl.load(x_ptr + offset, mask=mask)
35
+ tl.store(output_ptr + offset, x + scalar, mask=mask)
36
+
37
+
38
+ @triton.jit
39
+ def add_scalar_dev_kernel(
40
+ x_ptr, s_ptr, output_ptr,
41
+ n_elements,
42
+ alpha,
43
+ BLOCK_SIZE: tl.constexpr,
44
+ ):
45
+ """out = x + (s * alpha) where s is a 0-d DEVICE tensor.
46
+
47
+ Bit-exact mirror of add_scalar_kernel's `x + scalar` path: the
48
+ host path computes `float(s) * alpha` in Python float64 then
49
+ narrows to the f32 kernel argument; here the same math runs
50
+ in-register (load -> f64 -> *alpha -> f32). Replaces the host
51
+ .item() sync so the scalar stays device-resident — the replay
52
+ device-scalar increment (Ming timestep class, 2026-08-15).
53
+
54
+ Bit-exactness constraint: `alpha` binds as an f32 runtime arg, so
55
+ the f64 multiply sees f32(alpha) where the host path sees the full
56
+ f64 alpha — a 1-ulp double-rounding risk for alpha values not
57
+ exactly representable in f32. Every ATen call site passes alpha=±1
58
+ (exact); a non-representable alpha would need an f64-carried arg.
59
+ """
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
+ s = (tl.load(s_ptr).to(tl.float64) * alpha).to(tl.float32)
64
+ x = tl.load(x_ptr + offset, mask=mask)
65
+ tl.store(output_ptr + offset, x + s, mask=mask)
66
+
67
+
68
+ @triton.jit
69
+ def add_bias_broadcast_kernel(
70
+ x_ptr, bias_ptr, output_ptr,
71
+ n_elements, feat_dim,
72
+ alpha,
73
+ BLOCK_SIZE: tl.constexpr,
74
+ ):
75
+ """out[i] = x[i] + alpha * bias[i % feat_dim].
76
+
77
+ Used by `add` wrapper when `y` is a 1D bias of size `feat_dim` that
78
+ broadcasts against `x`'s last dim. Avoids materializing the 8 GiB
79
+ contiguous broadcast view of bias on Sana 4Kpx VAE add::88
80
+ (`mul::58::out_0 (1, 4096, 4096, 128) + bias (128)`). Reads bias
81
+ directly via `offset % feat_dim` indexing — same effect as a
82
+ stride-0 broadcast but compatible with the kernel's flat 1D
83
+ addressing.
84
+ """
85
+ pid = tl.program_id(0).to(tl.int64)
86
+ # Cast to int64 — tensors >= 2^31 elements (e.g. Sana 4Kpx VAE
87
+ # add::88 input 1x4096x4096x128 = 2^31) overflow int32 offset
88
+ # arithmetic silently and corrupt `offset % feat_dim`, producing
89
+ # garbage output. P-SANA-4KPX-RUNTIME 2026-05-07.
90
+ 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)
91
+ mask = offset < n_elements
92
+
93
+ c = offset % feat_dim
94
+ x = tl.load(x_ptr + offset, mask=mask)
95
+ b = tl.load(bias_ptr + c, mask=mask)
96
+ tl.store(output_ptr + offset, x + alpha * b, mask=mask)
@@ -0,0 +1,29 @@
1
+ """Addcdiv kernel — extracted from FlagGems.
2
+
3
+ Pure Triton. ZERO PyTorch imports.
4
+ Logic: result = input + value * (tensor1 / tensor2)
5
+ """
6
+
7
+ import triton
8
+ import triton.language as tl
9
+
10
+ @triton.jit
11
+ def addcdiv_forward_kernel(
12
+ input_ptr,
13
+ tensor1_ptr,
14
+ tensor2_ptr,
15
+ output_ptr,
16
+ n_elements,
17
+ value,
18
+ BLOCK_SIZE: tl.constexpr,
19
+ ):
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
+ x = tl.load(input_ptr + offset, mask=mask)
25
+ t1 = tl.load(tensor1_ptr + offset, mask=mask)
26
+ t2 = tl.load(tensor2_ptr + offset, mask=mask)
27
+
28
+ result = x + value * (t1 / t2)
29
+ tl.store(output_ptr + offset, result, mask=mask)
@@ -0,0 +1,29 @@
1
+ """Addcmul kernel — extracted from FlagGems.
2
+
3
+ Pure Triton. ZERO PyTorch imports.
4
+ Logic: result = input + value * tensor1 * tensor2
5
+ """
6
+
7
+ import triton
8
+ import triton.language as tl
9
+
10
+ @triton.jit
11
+ def addcmul_forward_kernel(
12
+ input_ptr,
13
+ tensor1_ptr,
14
+ tensor2_ptr,
15
+ output_ptr,
16
+ n_elements,
17
+ value,
18
+ BLOCK_SIZE: tl.constexpr,
19
+ ):
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
+ x = tl.load(input_ptr + offset, mask=mask)
25
+ t1 = tl.load(tensor1_ptr + offset, mask=mask)
26
+ t2 = tl.load(tensor2_ptr + offset, mask=mask)
27
+
28
+ result = x + value * t1 * t2
29
+ tl.store(output_ptr + offset, result, mask=mask)
@@ -0,0 +1,64 @@
1
+ """Addmv — pure @triton.jit kernel.
2
+
3
+ Ported from FlagGems addmv (Apache-2.0 license).
4
+ Computes: out = beta * self + alpha * (mat @ vec)
5
+ where mat is [N, M], vec is [M], self is broadcastable to [N].
6
+ """
7
+
8
+ import triton
9
+ import triton.language as tl
10
+
11
+ from nbxtensor.ops._configs import nbx_autotune, addmv_configs, prune_addmv
12
+
13
+
14
+ # rows, reduction tile, warps: the certified configuration of the key (workstream 1, small-M GEMM), the key the derived
15
+ # census forms (`launch_keys.addmv_launches`)
16
+ @nbx_autotune(configs=addmv_configs(), key=['N_BUCKET', 'M_BUCKET'],
17
+ prune_configs_by={'early_config_prune': prune_addmv}, cache_results=True)
18
+ @triton.jit
19
+ def addmv_kernel(
20
+ A_ptr,
21
+ B_ptr,
22
+ inp_ptr,
23
+ out_ptr,
24
+ N, M, N_BUCKET, M_BUCKET,
25
+ alpha,
26
+ beta,
27
+ stride_an, stride_am,
28
+ stride_bm,
29
+ stride_in,
30
+ stride_outn,
31
+ BLOCK_M: tl.constexpr,
32
+ BLOCK_N: tl.constexpr,
33
+ ):
34
+ """Matrix-vector multiply with bias: out = beta*inp + alpha*(A @ B).
35
+
36
+ Each program handles BLOCK_N rows of A, iterating over M in BLOCK_M tiles.
37
+ Accumulates in fp32 for stability.
38
+ """
39
+ pid = tl.program_id(0).to(tl.int64)
40
+ offset_n = pid * BLOCK_N + tl.arange(0, BLOCK_N)[:, None]
41
+ offset_m = tl.arange(0, BLOCK_M)[None, :]
42
+ n_mask = offset_n < N
43
+
44
+ A_ptrs = A_ptr + offset_n * stride_an + offset_m * stride_am
45
+ B_ptrs = B_ptr + offset_m * stride_bm
46
+
47
+ acc = tl.zeros((BLOCK_N, BLOCK_M), dtype=tl.float32)
48
+ for m in range(0, M, BLOCK_M):
49
+ m_mask = m + offset_m < M
50
+ a = tl.load(A_ptrs, mask=n_mask & m_mask, other=0.0).to(tl.float32)
51
+ b = tl.load(B_ptrs, mask=m_mask, other=0.0).to(tl.float32)
52
+ acc += a * b
53
+ A_ptrs += BLOCK_M * stride_am
54
+ B_ptrs += BLOCK_M * stride_bm
55
+
56
+ # Reduce over M dimension, then add bias
57
+ mv_result = tl.sum(acc, axis=1)[:, None]
58
+ inp_ptrs = inp_ptr + offset_n * stride_in
59
+ inp = tl.load(inp_ptrs, mask=n_mask, other=0.0).to(tl.float32)
60
+
61
+ out = mv_result * alpha + inp * beta
62
+
63
+ out_ptrs = out_ptr + offset_n * stride_outn
64
+ tl.store(out_ptrs, out, mask=n_mask)