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,413 @@
1
+ """Fused upsample_nearest2d + convolution, and the spatially tiled convolution — band-streaming, Triton side.
2
+
3
+ Eliminates the intermediate upsampled tensor that would otherwise sit in VRAM between an upsample op and the
4
+ convolution that consumes it (Sana 4Kpx VAE: a [1, 256, 4096, 4096] fp32 upsample, 16 GB, that the conv consumes at
5
+ once). Band by band along output H: the pre-upsample input band with the conv's halo, upsampled alone, convolved, written
6
+ into the conv output at its offset; the full conv output is materialised once. The proxies stand for the upsample that
7
+ is not materialised (`FusionUpsampleProxy`) and for a broadcast clone (`BroadcastClonePyroxy`).
8
+
9
+ The PyTorch branch's versions and the dispatchers that pick a branch by tensor type are the engine's
10
+ (neurobrix/kernels/ops/fused_upsample_conv_torch.py); this module imports no torch.
11
+ """
12
+
13
+ from typing import Any, Optional
14
+
15
+
16
+
17
+ class FusionUpsampleProxy:
18
+ """Sentinel returned by an intercepted upsample op so the next conv
19
+ can find the pre-upsample input + scales without the upsample tensor
20
+ ever being materialized.
21
+
22
+ Holds: pre_upsample input tensor, scales_h, scales_w, intended output
23
+ shape. Stored in the CompiledSequence arena slot that would normally
24
+ hold the upsample output tensor. Only the paired conv interceptor reads
25
+ this slot — verified at Prism planning time (single consumer).
26
+ """
27
+
28
+ __slots__ = ("pre_input", "scales_h", "scales_w", "output_shape", "dtype", "device")
29
+
30
+ # ── observability ──────────────────────────────────────────────────────
31
+ # THE RULE: a brick that replaces a call site preserves the observability
32
+ # of that site. This proxy stands in for an upsample op, and the per-op
33
+ # recorder drops anything that is not a tensor — so two of the three
34
+ # spatial upsamples of the Wan VAE produced no record at all, and every
35
+ # cross-engine walk was blind exactly there. The proxy therefore produces
36
+ # the record itself.
37
+ #
38
+ # It costs nothing and it is EXACT, because a nearest upsample only
39
+ # replicates: every input value appears scales_h x scales_w times, so
40
+ # l2(up(x)) == sqrt(sh*sw) * l2(x)
41
+ # and the first row of the result is the first row of the input with each
42
+ # element repeated sw times. No tensor is materialized — which is the
43
+ # whole reason this proxy exists.
44
+
45
+ def nbx_observable_summary(self) -> dict:
46
+ """What the replaced upsample would have reported, without materializing it."""
47
+ import math
48
+ x = self.pre_input
49
+ sh = max(1, int(round(float(self.scales_h))))
50
+ sw = max(1, int(round(float(self.scales_w))))
51
+ # `self.dtype` is a __slots__ field the interceptor does not always set,
52
+ # and `getattr(x, "dtype", self.dtype)` would evaluate it EAGERLY as the
53
+ # default — raising AttributeError before the getattr ever runs.
54
+ dt = getattr(x, "dtype", None)
55
+ if dt is None:
56
+ dt = getattr(self, "dtype", None)
57
+ out = {"shape": list(self.output_shape), "dtype": str(dt),
58
+ "synthetic": True, "replaced_by": "fused_upsample_conv2d"}
59
+ try:
60
+ flat = x.reshape(-1)
61
+ head_src = flat[:10]
62
+ head_src = (head_src.tolist() if hasattr(head_src, "tolist")
63
+ else list(head_src))
64
+ head = [float(v) for v in head_src for _ in range(sw)][:10]
65
+ out["head10"] = head
66
+ if hasattr(x, "detach"): # a torch tensor (the compiled engine): its own fp32 norm, torch never imported
67
+ l2 = float(x.detach().reshape(-1).float().norm().item())
68
+ else: # NBXTensor -> numpy
69
+ import numpy as _np
70
+ l2 = float(_np.linalg.norm(
71
+ _np.asarray(x.numpy(), dtype=_np.float64).ravel()))
72
+ out["l2_norm"] = l2 * math.sqrt(sh * sw)
73
+ except Exception as exc: # a summary must never break a run
74
+ out["error"] = f"{type(exc).__name__}: {exc}"
75
+ return out
76
+
77
+ def __init__(self, pre_input, scales_h, scales_w, output_shape):
78
+ self.pre_input = pre_input
79
+ self.scales_h = float(scales_h)
80
+ self.scales_w = float(scales_w)
81
+ self.output_shape = tuple(output_shape)
82
+ self.dtype = getattr(pre_input, "dtype", None)
83
+ self.device = getattr(pre_input, "device", None)
84
+
85
+ @property
86
+ def shape(self):
87
+ return self.output_shape
88
+
89
+ @property
90
+ def ndim(self):
91
+ return len(self.output_shape)
92
+
93
+ # Sentinel: FusionUpsampleProxy holds no GPU allocation of its own
94
+ # (it just references the pre-upsample tensor + scales). The triton
95
+ # sequence's deferred-free accounting (sequence.py:_run_single_device)
96
+ # reads `_nbytes` to size the drain budget; expose 0 here so the
97
+ # proxy is a no-op in that accounting path. The actual pre-upsample
98
+ # tensor lives in its own arena slot and is freed when that slot is
99
+ # killed independently.
100
+ @property
101
+ def _nbytes(self):
102
+ return 0
103
+
104
+ def __repr__(self):
105
+ return (
106
+ f"FusionUpsampleProxy(pre={tuple(self.pre_input.shape)}, "
107
+ f"scales=({self.scales_h},{self.scales_w}), "
108
+ f"out_shape={self.output_shape})"
109
+ )
110
+
111
+
112
+ class BroadcastClonePyroxy:
113
+ """Sentinel returned by an intercepted aten.clone op whose input is
114
+ a broadcast-view (stride=0 on some dim from a preceding aten.expand)
115
+ and whose consumer chain is `aten.view -> aten.pixel_shuffle`. Carries
116
+ the broadcast-view tensor itself (NBX expand returns a stride-0 view,
117
+ not a materialized tensor — see NBXTensor.expand line 1581) plus the
118
+ static broadcast metadata so pixel_shuffle can read through the view
119
+ directly (Triton tl.load with stride 0 aliases broadcast indices),
120
+ eliminating the clone materialization entirely.
121
+
122
+ Same architectural pattern as FusionUpsampleProxy: zero compute,
123
+ zero allocation in the interceptor; the downstream pixel_shuffle
124
+ consumes the proxy and performs the fused work.
125
+
126
+ `bcast_view`: the expand output (5D NBXTensor with stride 0 on
127
+ `bcast_dim`). Same data_ptr as the pre-expand tensor (NBX expand
128
+ is metadata-only).
129
+ `bcast_dim` / `bcast_factor`: extracted statically at register
130
+ time from the expand op's input/output shape diff.
131
+ `post_clone_shape` / `post_view_shape`: kept for the view
132
+ interceptor's dim arithmetic compatibility.
133
+ """
134
+
135
+ __slots__ = ("bcast_view", "bcast_dim", "bcast_factor",
136
+ "post_clone_shape", "post_view_shape", "dtype", "device")
137
+
138
+ def __init__(self, bcast_view, bcast_dim, bcast_factor,
139
+ post_clone_shape, post_view_shape):
140
+ self.bcast_view = bcast_view
141
+ self.bcast_dim = int(bcast_dim)
142
+ self.bcast_factor = int(bcast_factor)
143
+ self.post_clone_shape = tuple(post_clone_shape)
144
+ self.post_view_shape = tuple(post_view_shape)
145
+ self.dtype = getattr(bcast_view, "dtype", None)
146
+ self.device = getattr(bcast_view, "device", None)
147
+
148
+ @property
149
+ def shape(self):
150
+ return self.post_clone_shape
151
+
152
+ @property
153
+ def ndim(self):
154
+ return len(self.post_clone_shape)
155
+
156
+ @property
157
+ def _nbytes(self):
158
+ return 0
159
+
160
+ def __repr__(self):
161
+ return (
162
+ f"BroadcastClonePyroxy(view={tuple(self.bcast_view.shape)}, "
163
+ f"bcast_dim={self.bcast_dim}, bcast={self.bcast_factor}, "
164
+ f"post_clone={self.post_clone_shape})"
165
+ )
166
+
167
+
168
+ def make_upsample_proxy_interceptor():
169
+ """Return an interceptor that turns an upsample into a FusionUpsampleProxy
170
+ (no compute, no allocation of the full upsampled tensor)."""
171
+
172
+ def _proxy(input_tensor, output_size=None, scales_h=None, scales_w=None,
173
+ *args, **kwargs):
174
+ # Resolve scales: prefer explicit scales args (Sana 4Kpx graph stores
175
+ # both output_size and scales). Fall back to ratio if scales missing.
176
+ ih = input_tensor.shape[2]
177
+ iw = input_tensor.shape[3]
178
+ if scales_h is None or scales_w is None:
179
+ if isinstance(output_size, (list, tuple)):
180
+ oh, ow = int(output_size[0]), int(output_size[1])
181
+ else:
182
+ oh = ow = int(output_size)
183
+ sh = oh / ih
184
+ sw = ow / iw
185
+ else:
186
+ sh = float(scales_h)
187
+ sw = float(scales_w)
188
+ oh = int(ih * sh)
189
+ ow = int(iw * sw)
190
+ n, c = input_tensor.shape[0], input_tensor.shape[1]
191
+ return FusionUpsampleProxy(input_tensor, sh, sw, (n, c, oh, ow))
192
+
193
+ return _proxy
194
+
195
+
196
+ def pair(v):
197
+ """(h, w) of a convolution argument given as one int or a pair."""
198
+ if isinstance(v, (list, tuple)):
199
+ return int(v[0]), int(v[1])
200
+ return int(v), int(v)
201
+
202
+
203
+ def tiled_conv2d(
204
+ input_tensor, weight, bias, sh_st, sw_st, pad_h, pad_w,
205
+ dh, dw, groups, tile_factor,
206
+ ):
207
+ """Band-streaming standalone conv with REAL halo (no zero-pad seams).
208
+
209
+ POINT 5 FIX (P-SANA-4KPX-RUNTIME): mirrors `_tiled_conv2d_spatial_torch`
210
+ exactly. The previous version applied `padding=(pad_h, pad_w)` on every
211
+ band — at internal band frontiers this filled zeros where the kernel
212
+ should have read from the NEXT band's rows (real halo). The result was
213
+ bit-divergent from the non-tiled path at every band boundary, by ~50 in
214
+ max_abs at Sana 4Kpx (98%+ of elements affected). The torch backend has
215
+ always used the halo-based algorithm; the NBX backend now matches it.
216
+
217
+ Per band we slice the input with halo = (kh-1)*dh//2 rows above and
218
+ below; conv runs with `padding=(0,0)`. Image-edge halos are filled by
219
+ `constant_pad_nd_wrapper` with the original `pad_h` only on the top of
220
+ the first band and the bottom of the last band. Bit-identical to the
221
+ non-tiled `conv2d_wrapper` + `_torch` backend.
222
+ """
223
+ from nbxtensor.tensor import NBXTensor
224
+ from nbxtensor.ops.conv_wrappers import conv2d_wrapper, _conv_bias_inplace
225
+ from nbxtensor.ops.shape_wrappers import constant_pad_nd_wrapper
226
+
227
+ N, in_c, IH, IW = input_tensor.shape
228
+ out_c, _, kh, kw = weight.shape
229
+ out_h = (IH + 2 * pad_h - dh * (kh - 1) - 1) // sh_st + 1
230
+ out_w = (IW + 2 * pad_w - dw * (kw - 1) - 1) // sw_st + 1
231
+
232
+ output = NBXTensor.empty(
233
+ (N, out_c, out_h, out_w),
234
+ device=input_tensor.device, dtype=input_tensor.dtype,
235
+ )
236
+ # The bands are `launch_keys.tiled_conv2d_bands` — the cut the derived census keys with.
237
+ from nbxtensor.ops.launch_keys import tiled_conv2d_bands
238
+ for (oh_start, oh_end, in_clamped_start, in_clamped_end,
239
+ pad_top_real, pad_bot_real, skip) in tiled_conv2d_bands(
240
+ IH, out_h, kh, sh_st, dh, pad_h, tile_factor):
241
+ # POINT 6 FIX (P-SANA-4KPX-RUNTIME): see `tiled_upsample_conv2d`
242
+ # for the full rationale — H-slice on an NCHW tensor produces a
243
+ # non-contiguous view; the downstream conv/pad Triton wrappers use
244
+ # flat indexing that assumes contiguous and read wrong memory
245
+ # otherwise. The torch backend (`_tiled_conv2d_spatial_torch`)
246
+ # already enforces contiguity at this point.
247
+ view = input_tensor[:, :, in_clamped_start:in_clamped_end, :]
248
+ if pad_top_real > 0 or pad_bot_real > 0 or pad_w > 0:
249
+ # ONE band buffer, padded: the slice copied by its strides straight into its interior (`copy_`). A
250
+ # contiguous copy then constant_pad_nd held two input bands where the band's price holds one (HAT-L at
251
+ # 2048 px on 16 GB, 2026-10-22).
252
+ hb, wb = int(view.shape[2]), int(view.shape[3])
253
+ in_band = NBXTensor.zeros((int(view.shape[0]), int(view.shape[1]), hb + pad_top_real + pad_bot_real,
254
+ wb + 2 * pad_w), dtype=view.dtype, device=view.device)
255
+ in_band.narrow(2, pad_top_real, hb).narrow(3, pad_w, wb).copy_(view)
256
+ else:
257
+ in_band = view.contiguous()
258
+ del view
259
+
260
+ conv_band = conv2d_wrapper(
261
+ in_band, weight, None,
262
+ stride=(sh_st, sw_st), padding=(0, 0),
263
+ dilation=(dh, dw), groups=groups,
264
+ )
265
+
266
+ actual_band_h = oh_end - oh_start
267
+ # P-NBX-TILED-CONV2D-SMALL-SCALE step 2: same halo offset fix
268
+ # as the torch path. When halo_top > 0 (internal band frontier),
269
+ # conv_band[0] is the convolution at the halo row, not at the
270
+ # band's first useful output row. Skip the halo rows on the
271
+ # read side.
272
+ avail_after_halo = conv_band.shape[2] - skip
273
+ if avail_after_halo < actual_band_h:
274
+ actual_band_h = avail_after_halo
275
+ # Bias add inside band — see tiled_upsample_conv2d for rationale
276
+ # (avoids 8 GiB bias broadcast materialization on Sana 4Kpx).
277
+ if bias is not None:
278
+ # In place, as every conv wrapper adds its bias (`_conv_bias_inplace`): an out-of-place add held a
279
+ # second band beside the first, which Prism's band price does not hold.
280
+ _conv_bias_inplace(conv_band, bias)
281
+ output[:, :, oh_start:oh_start + actual_band_h, :] = conv_band[:, :, skip:skip + actual_band_h, :]
282
+ # Released before the next band allocates its own: held, the previous band's input and output stood beside
283
+ # the next band's (HAT-S at 2048 px on 16 GB: the second 1 153 MB band of conv::2 found no room, 2026-10-22).
284
+ del in_band, conv_band
285
+ return output
286
+
287
+
288
+ # ---------------------------------------------------------------------------
289
+ # NBX / Triton mode — same algorithm via Triton wrappers
290
+ # ---------------------------------------------------------------------------
291
+
292
+
293
+ def tiled_upsample_conv2d(
294
+ proxy_or_tensor, weight, bias, stride, padding, dilation,
295
+ transposed, output_padding, groups, tile_factor,
296
+ ):
297
+ """NBXTensor flavor — uses the existing Triton upsample + conv2d
298
+ wrappers per band. Identical algorithm to the torch path; the only
299
+ differences are the backend ops and explicit NBXTensor allocations.
300
+ """
301
+ from nbxtensor.tensor import NBXTensor
302
+ from nbxtensor.ops.shape_wrappers import upsample_nearest2d_wrapper
303
+ from nbxtensor.ops.conv_wrappers import conv2d_wrapper, _conv_bias_inplace
304
+
305
+ sh_st, sw_st = pair(stride)
306
+ pad_h, pad_w = pair(padding)
307
+ dh, dw = pair(dilation)
308
+
309
+ if not isinstance(proxy_or_tensor, FusionUpsampleProxy):
310
+ return conv2d_wrapper(
311
+ proxy_or_tensor, weight, bias,
312
+ stride=(sh_st, sw_st), padding=(pad_h, pad_w),
313
+ dilation=(dh, dw), groups=groups,
314
+ )
315
+
316
+ proxy = proxy_or_tensor
317
+ pre_input = proxy.pre_input
318
+ up_sh, up_sw = proxy.scales_h, proxy.scales_w
319
+ N, _, OH, OW = proxy.output_shape
320
+
321
+ out_c, _, kh, kw = weight.shape
322
+ conv_out_h = (OH + 2 * pad_h - dh * (kh - 1) - 1) // sh_st + 1
323
+ conv_out_w = (OW + 2 * pad_w - dw * (kw - 1) - 1) // sw_st + 1
324
+
325
+ output = NBXTensor.empty(
326
+ (N, out_c, conv_out_h, conv_out_w),
327
+ device=pre_input.device, dtype=pre_input.dtype,
328
+ )
329
+
330
+ pre_h = pre_input.shape[2]
331
+ from nbxtensor.ops.shape_wrappers import constant_pad_nd_wrapper
332
+ # The bands over the UPSAMPLED extent are `launch_keys.tiled_conv2d_bands` — one cut with the
333
+ # standalone tiled conv and the derived census (the halo rounded to whole strides).
334
+ from nbxtensor.ops.launch_keys import tiled_conv2d_bands
335
+ for (oh_start, oh_end, up_clamped_start, up_clamped_end,
336
+ pad_top_real, pad_bot_real, skip) in tiled_conv2d_bands(
337
+ OH, conv_out_h, kh, sh_st, dh, pad_h, tile_factor):
338
+
339
+ # Pre-upsample rows that produce [up_clamped_start, up_clamped_end).
340
+ # nearest mapping: pre_row = floor(up_row / up_sh).
341
+ pre_start = max(0, int(up_clamped_start // up_sh))
342
+ pre_end = min(pre_h, int((up_clamped_end - 1) // up_sh) + 1)
343
+
344
+ # POINT 6 FIX (P-SANA-4KPX-RUNTIME): slicing on the H dimension of
345
+ # an NCHW tensor produces a non-contiguous view (the C stride keeps
346
+ # its original H-based value while the slice covers a smaller H).
347
+ # The downstream Triton wrappers (`upsample_nearest2d_wrapper`,
348
+ # `conv2d_wrapper`) use flat 1D indexing that ASSUMES contiguous
349
+ # input — fed a strided view they read wrong memory and produce
350
+ # garbage (94% of elements differ by >1.0 vs torch reference,
351
+ # measured at conv::55 on Sana 4Kpx). The torch path
352
+ # (`_fused_upsample_conv2d_torch`) calls `.contiguous()` at the
353
+ # same two points; mirror that here. Bands where the slice covers
354
+ # the full H (first band when `tile_factor==1`) hit the early-
355
+ # return path in `NBXTensor.contiguous()` at zero cost.
356
+ pre_band = pre_input[:, :, pre_start:pre_end, :].contiguous()
357
+ up_band = upsample_nearest2d_wrapper(
358
+ pre_band,
359
+ output_size=[int(pre_band.shape[2] * up_sh), int(pre_band.shape[3] * up_sw)],
360
+ scales_h=up_sh, scales_w=up_sw,
361
+ )
362
+ up_offset_local = up_clamped_start - int(pre_start * up_sh)
363
+ up_local_h = up_clamped_end - up_clamped_start
364
+ up_band = up_band[:, :, up_offset_local:up_offset_local + up_local_h, :].contiguous()
365
+
366
+ # Apply boundary padding (image-edge halo + clipped over-read).
367
+ # Internal band frontiers get pad_top/bot_real == 0 because halo
368
+ # already provides real rows.
369
+ if pad_top_real > 0 or pad_bot_real > 0 or pad_w > 0:
370
+ up_band = constant_pad_nd_wrapper(
371
+ up_band, [pad_w, pad_w, pad_top_real, pad_bot_real], value=0.0,
372
+ )
373
+
374
+ # Conv with padding=(0,0) — halo + image-edge padding already in up_band.
375
+ conv_band = conv2d_wrapper(
376
+ up_band, weight, None,
377
+ stride=(sh_st, sw_st), padding=(0, 0),
378
+ dilation=(dh, dw), groups=groups,
379
+ )
380
+
381
+ actual_band_h = oh_end - oh_start
382
+ # P-NBX-TILED-CONV2D-SMALL-SCALE step 2: halo_top offset on the
383
+ # conv_band read side. Internal band frontiers (halo_top > 0)
384
+ # write F.conv2d output row `oh_start + halo_top - 1` at
385
+ # output row `oh_start` without this offset.
386
+ avail_after_halo = conv_band.shape[2] - skip
387
+ if avail_after_halo < actual_band_h:
388
+ actual_band_h = avail_after_halo
389
+ # Bias add inside band — keeps the broadcast bounded to one band's
390
+ # (N, out_c, actual_band_h, conv_out_w) instead of the full output
391
+ # (8 GiB broadcast on Sana 4Kpx 4096×4096 fp16). Mathematically
392
+ # identical (bias is per-channel, applies element-wise on H,W).
393
+ if bias is not None:
394
+ _conv_bias_inplace(conv_band, bias) # in place, as every conv wrapper adds its bias
395
+ output[:, :, oh_start:oh_start + actual_band_h, :] = conv_band[:, :, skip:skip + actual_band_h, :]
396
+ del pre_band, up_band, conv_band # released before the next band allocates its own
397
+
398
+ return output
399
+
400
+
401
+ # Phase 1 opt-in cleanup: mark op-level tiling NBX interceptors as
402
+ # self-managing dtype. TritonSequence._compile_op checks this flag to
403
+ # skip the DtypeEngine wrap for these ops. Justified because:
404
+ # - fused_upsample_conv2d may receive a FusionUpsampleProxy as arg[0]
405
+ # (no nbx_dtype attribute → DtypeEngine wrap would no-op anyway)
406
+ # - the nested per-band calls delegate to conv2d_wrapper which is
407
+ # itself self-managed (narrowing + output = compute_dtype)
408
+ # - rms_norm tiled wrapper delegates to the rms_norm wrapper which
409
+ # follows AMP_FP32_OPS via DtypeEngine (or the opt-in cast-back
410
+ # variant when activations_fp16_safe is enabled)
411
+ tiled_upsample_conv2d.self_manages_dtype = True
412
+ tiled_conv2d.self_manages_dtype = True
413
+ # (the public dispatchers, mode-agnostic, are marked in the engine's fused_upsample_conv_torch.py)
@@ -0,0 +1,52 @@
1
+ """Gather along dim — pure @triton.jit kernel.
2
+
3
+ Approach from FlagGems: work on flattened index space. For each element in the
4
+ index tensor, compute its multi-dim position, load the gather index, then load
5
+ from source replacing the gather dim's index with the loaded value.
6
+
7
+ This static kernel handles any rank by taking pre-computed strides as args.
8
+ The wrapper decomposes the gather dim into (outer, gather, inner) strides.
9
+ """
10
+
11
+ import triton
12
+ import triton.language as tl
13
+
14
+ @triton.jit
15
+ def gather_kernel(
16
+ inp, index, out,
17
+ N,
18
+ inp_dim_stride,
19
+ idx_stride_outer, idx_stride_dim, idx_stride_inner,
20
+ inp_stride_outer, inp_stride_inner,
21
+ out_stride_outer, out_stride_dim, out_stride_inner,
22
+ outer_size, dim_size, inner_size,
23
+ BLOCK_SIZE: tl.constexpr,
24
+ ):
25
+ """Gather kernel — works for any tensor rank via pre-computed strides.
26
+
27
+ The wrapper reshapes the problem into (outer, dim, inner) where:
28
+ - outer = product of dims before gather dim
29
+ - dim = gather dim size (from index tensor)
30
+ - inner = product of dims after gather dim
31
+ """
32
+ pid = tl.program_id(0).to(tl.int64)
33
+ 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)
34
+ mask = offset < N
35
+
36
+ # Decompose flat offset into (outer, dim, inner)
37
+ inner_idx = offset % inner_size
38
+ temp = offset // inner_size
39
+ dim_idx = temp % dim_size
40
+ outer_idx = temp // dim_size
41
+
42
+ # Load the gather index value
43
+ idx_offset = outer_idx * idx_stride_outer + dim_idx * idx_stride_dim + inner_idx * idx_stride_inner
44
+ cur_index = tl.load(index + idx_offset, mask=mask, other=0)
45
+
46
+ # Load from input: same outer/inner position, but use cur_index for the gather dim
47
+ inp_offset = outer_idx * inp_stride_outer + cur_index * inp_dim_stride + inner_idx * inp_stride_inner
48
+ val = tl.load(inp + inp_offset, mask=mask, other=0.0)
49
+
50
+ # Store to output
51
+ out_offset = outer_idx * out_stride_outer + dim_idx * out_stride_dim + inner_idx * out_stride_inner
52
+ tl.store(out + out_offset, val, mask=mask)
nbxtensor/ops/ge.py ADDED
@@ -0,0 +1,32 @@
1
+ """Greater than or equal — pure @triton.jit kernel."""
2
+
3
+ import triton
4
+ import triton.language as tl
5
+
6
+ @triton.jit
7
+ def ge_forward_kernel(
8
+ x_ptr, y_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(x_ptr + offset, mask=mask)
17
+ y = tl.load(y_ptr + offset, mask=mask)
18
+ tl.store(output_ptr + offset, (x >= y).to(tl.int1), mask=mask)
19
+
20
+ @triton.jit
21
+ def ge_scalar_kernel(
22
+ x_ptr, output_ptr,
23
+ n_elements,
24
+ scalar,
25
+ BLOCK_SIZE: tl.constexpr,
26
+ ):
27
+ pid = tl.program_id(0).to(tl.int64)
28
+ 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)
29
+ mask = offset < n_elements
30
+
31
+ x = tl.load(x_ptr + offset, mask=mask)
32
+ tl.store(output_ptr + offset, (x >= scalar).to(tl.int1), mask=mask)
nbxtensor/ops/gelu.py ADDED
@@ -0,0 +1,31 @@
1
+ """GELU — pure @triton.jit kernel (exact and approximate)."""
2
+
3
+ import triton
4
+ import triton.language as tl
5
+
6
+ from ._common import tanh_fn
7
+
8
+ @triton.jit
9
+ def gelu_forward_kernel(
10
+ input_ptr, output_ptr,
11
+ n_elements,
12
+ approximate: tl.constexpr,
13
+ BLOCK_SIZE: tl.constexpr,
14
+ ):
15
+ """GELU forward. approximate=True for tanh approximation, False for exact."""
16
+ pid = tl.program_id(0).to(tl.int64)
17
+ 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)
18
+ mask = offset < n_elements
19
+
20
+ x = tl.load(input_ptr + offset, mask=mask)
21
+ x_fp32 = x.to(tl.float32)
22
+
23
+ if approximate:
24
+ # Tanh approximation: 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))
25
+ cdf = 0.5 * (1.0 + tanh_fn(0.7978845608 * x_fp32 * (1.0 + 0.044715 * x_fp32 * x_fp32)))
26
+ else:
27
+ # Exact GELU: x * 0.5 * (1 + erf(x / sqrt(2)))
28
+ cdf = 0.5 * (1.0 + tl.math.erf(0.707106781 * x_fp32))
29
+
30
+ out = cdf * x_fp32
31
+ tl.store(output_ptr + offset, out, mask=mask)
@@ -0,0 +1,80 @@
1
+ """Vector (SIMT) fp16 GEMV — the decode projection kernel, no tl.dot.
2
+
3
+ Second member of the SIMT decode family (after ops/decode_attn_vec.py,
4
+ adopted 2026-08-23). The FlagGems-ported `mv_kernel` runs the row's
5
+ 193 per-token projections at ~280 GB/s effective (7.1 ms/token, 36 us
6
+ per call measured as IN-GRAPH kernel time — launch overhead already
7
+ excluded by the CUDA-graph replay, so the cost is kernel structure):
8
+ its (BLOCK_N=64, BLOCK_M=256) fp32 accumulator tile is 64 KB of
9
+ registers per CTA (spill class), and its N/64 grid puts 32 programs on
10
+ 80 SMs at N=2048.
11
+
12
+ The 2026-08-24 web research (R16) says the structure for OUR layout —
13
+ weights pre-transposed to row-major [N, K], K contiguous per output
14
+ row — is llama.cpp's `mmvf.cu` shape: a program owns a small group of
15
+ output rows, its threads stride K with wide vectorized loads, fp32
16
+ scalar accumulators, fixed-order reduction (llama.cpp: warp shuffle +
17
+ smem, deterministic, no atomics; grid = rows). GemLite's Triton GEMV
18
+ splits K across programs and reduces with `tl.atomic_add` — refused
19
+ here: run-to-run fp ordering nondeterminism, the exact class the
20
+ replay verify and the drift gate forbid. TRT-LLM's cudaCoreGemm keeps
21
+ all of K inside one block for the same reason.
22
+
23
+ Triton translation: acc is (BLOCK_N,) — reduced PER K-CHUNK
24
+ (`tl.sum(a_tile * b_chunk[None, :], 1)`), 256x less register state
25
+ than the (BLOCK_N, BLOCK_K) end-reduced tile; BLOCK_N small (8-16
26
+ rows) so the N-grid alone fills the 80 SMs (N=2048 -> 128-256
27
+ programs); `tl.multiple_of`/`tl.max_contiguous` hints on the
28
+ K-contiguous weight offsets so ptxas emits 128-bit loads (llama.cpp
29
+ PR #9816: vectorizing exactly these loads was worth 1.27x on HBM
30
+ parts); num_stages=1 (no async-copy engine on sm_70).
31
+
32
+ Deterministic by construction: one program per output-row group, all
33
+ of K reduced inside it in fixed chunk order, no atomics.
34
+ """
35
+
36
+ import triton
37
+ import triton.language as tl
38
+
39
+ from nbxtensor.ops._configs import nbx_autotune, gemv_vec_configs, prune_gemv_vec
40
+
41
+
42
+ # rows per program, K chunk, warps, stages: the certified configuration of the key (workstream 1, small-M GEMM), the
43
+ # key the derived census forms (`launch_keys.mv_launches`)
44
+ @nbx_autotune(configs=gemv_vec_configs(), key=['N_BUCKET', 'K_BUCKET'],
45
+ prune_configs_by={'early_config_prune': prune_gemv_vec}, cache_results=True)
46
+ @triton.jit
47
+ def gemv_vec_kernel(
48
+ A_ptr, # [N, K] weights, row-major (pre-transposed), fp16
49
+ B_ptr, # [K] activation (fp32 on Volta via the AMP upcast)
50
+ C_ptr, # [N] out
51
+ N, K, N_BUCKET, K_BUCKET,
52
+ stride_an, stride_am,
53
+ stride_bm,
54
+ stride_cn,
55
+ BLOCK_N: tl.constexpr, # output rows per program (small: 8-16)
56
+ BLOCK_K: tl.constexpr, # K-chunk width (wide: 128-512)
57
+ ):
58
+ pid = tl.program_id(0).to(tl.int64)
59
+ offs_n = pid * BLOCK_N + tl.arange(0, BLOCK_N)
60
+ mask_n = offs_n < N
61
+
62
+ acc = tl.zeros((BLOCK_N,), dtype=tl.float32)
63
+ offs_k = tl.arange(0, BLOCK_K)
64
+ # The weight rows are K-contiguous (stride_am == 1 on the
65
+ # pre-transposed layout): tell the compiler so the loads vectorize
66
+ # to 128-bit transactions.
67
+ offs_k = tl.max_contiguous(tl.multiple_of(offs_k, BLOCK_K), BLOCK_K)
68
+
69
+ a_ptrs = A_ptr + offs_n[:, None] * stride_an + offs_k[None, :] * stride_am
70
+ b_ptrs = B_ptr + offs_k * stride_bm
71
+ for k0 in range(0, K, BLOCK_K):
72
+ mask_k = k0 + offs_k < K
73
+ a = tl.load(a_ptrs, mask=mask_n[:, None] & mask_k[None, :],
74
+ other=0.0).to(tl.float32)
75
+ b = tl.load(b_ptrs, mask=mask_k, other=0.0).to(tl.float32)
76
+ acc += tl.sum(a * b[None, :], 1)
77
+ a_ptrs += BLOCK_K * stride_am
78
+ b_ptrs += BLOCK_K * stride_bm
79
+
80
+ tl.store(C_ptr + offs_n * stride_cn, acc, mask=mask_n)
nbxtensor/ops/glu.py ADDED
@@ -0,0 +1,43 @@
1
+ """GLU (Gated Linear Unit) — pure @triton.jit kernel. Ported from attorch (MIT).
2
+
3
+ Supports arbitrary activation functions via the gate path.
4
+ SwiGLU = GLU with SiLU activation.
5
+ """
6
+
7
+ import triton
8
+ import triton.language as tl
9
+
10
+ from ._common import sigmoid
11
+
12
+ @triton.jit
13
+ def glu_forward_kernel(
14
+ input1_ptr, input2_ptr, output_ptr,
15
+ size,
16
+ act_func: tl.constexpr,
17
+ BLOCK_SIZE: tl.constexpr,
18
+ ):
19
+ """GLU forward: out = input1 * act(input2).
20
+
21
+ input1, input2: [size] (the two halves of the gated input)
22
+ act_func: 'sigmoid' (standard GLU), 'silu' (SwiGLU), 'gelu', 'relu'
23
+ """
24
+ pid = tl.program_id(0).to(tl.int64)
25
+ 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)
26
+ mask = offset < size
27
+
28
+ x1 = tl.load(input1_ptr + offset, mask=mask)
29
+ x2 = tl.load(input2_ptr + offset, mask=mask)
30
+
31
+ x2_fp32 = x2.to(tl.float32)
32
+ if act_func == 'sigmoid':
33
+ gate = sigmoid(x2_fp32)
34
+ elif act_func == 'silu':
35
+ gate = x2_fp32 * sigmoid(x2_fp32)
36
+ elif act_func == 'gelu':
37
+ gate = 0.5 * x2_fp32 * (1.0 + tl.math.erf(0.707106781 * x2_fp32))
38
+ elif act_func == 'relu':
39
+ gate = tl.maximum(0, x2_fp32)
40
+ else:
41
+ gate = sigmoid(x2_fp32)
42
+
43
+ tl.store(output_ptr + offset, x1 * gate, mask=mask)