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,215 @@
1
+ """Paged KV: the append and the decode attention read through a page table (state engine step 2,
2
+ docs/design/state-engine.md §4.3; the shared bookkeeping is core/state/pages.py).
3
+
4
+ The state of a decoding sequence lives in fixed-size pages of a per-node pool, granted as the sequence grows and never
5
+ at max_len. One pool per layer and per K / V, laid out [num_pages, kv_heads, page_tokens, D]: the positions of one KV
6
+ head inside one page are contiguous rows, so a tile of BLOCK_N positions of one head is one strided block read.
7
+
8
+ - `paged_kv_append_kernel` writes new positions through the slot mapping (vLLM's `reshape_and_cache` shape): slot =
9
+ page * page_tokens + position-in-page, one int32 per new token, computed by the PageTable.
10
+ - `paged_decode_attn_split_kernel` is the vector (SIMT) split-KV decode read of ops/decode_attn_vec.py — one query row
11
+ per program, FMA reduction over D, scalar online softmax, fp32 accumulator, partials merged by the fixed-order
12
+ `flash_decode_reduce_kernel` — with K / V addressed through the block table (one page id per page of the sequence,
13
+ int32, padded with -1). A tile never straddles two pages: every candidate tile divides the page size
14
+ (`PAGED_DECODE_TILE_CANDIDATES`, ops/tiling_sizes, read by core/state/price.page_tokens) and each split starts on a tile boundary.
15
+ Each sequence of a batch reads its own length (`seq_lens`), so one launch serves a batch of different lengths.
16
+
17
+ Pure @triton.jit, no tl.dot (sm_70: the vector kernel's measured structure), one source for every backend.
18
+ """
19
+
20
+ import triton
21
+ import triton.language as tl
22
+
23
+ from nbxtensor.ops.decode_attn_vec import _round_q
24
+
25
+
26
+ # The page tiles are sizes: nbxtensor.ops.tiling_sizes owns them (torch- and triton-free — the compiled branch sizes
27
+ # its paged cache with them in an environment without triton).
28
+ from nbxtensor.ops.tiling_sizes import PAGED_DECODE_TILE_CANDIDATES # noqa: F401
29
+ from nbxtensor.ops._configs import nbx_autotune
30
+
31
+ # The split shape: positions per split and the most splits one launch makes — the vector decode kernel's judged config
32
+ # (its locked protocol of 2026-08-23, the defaults of `_decode_attn_vec`'s NBX_DV_SEG and its 64-split cap), so the
33
+ # paged read partitions a sequence exactly as the contiguous read it replaces.
34
+ PAGED_DECODE_SPLIT_POSITIONS = 256
35
+ PAGED_DECODE_MAX_SPLITS = 64
36
+
37
+
38
+ def paged_decode_splits(longest: int) -> int:
39
+ """The splits one decode launch makes for its longest sequence — the read's grid, the autotune key's length class
40
+ (what a split's work depends on) and the replayed decode's bucket (`PagedTritonKVCache.replay_key`): one rule, so
41
+ a replayed step never meets a launch the certificate keys differently. Keyed on the length's own bucket instead,
42
+ which moves at every token below 64, a replay recorded at one step was verified against another step's
43
+ configuration, and the bytes differed: TinyLlama's decode stopped replaying (25 -> 135 ms a token, 2026-11-04)."""
44
+ return max(1, min(-(-max(int(longest), 1) // PAGED_DECODE_SPLIT_POSITIONS), PAGED_DECODE_MAX_SPLITS))
45
+
46
+
47
+ @triton.jit
48
+ def paged_kv_append_kernel(
49
+ new_ptr, # [N_tok, H_kv, D] the new positions' K (or V), any float dtype
50
+ pool_ptr, # [num_pages, H_kv, page_tokens, D] the layer's pool
51
+ slot_ptr, # [N_tok] int32: page * page_tokens + position in page
52
+ page_tokens,
53
+ stride_nt, stride_nh,
54
+ stride_pp, stride_ph, stride_ps,
55
+ BLOCK_D: tl.constexpr,
56
+ D: tl.constexpr,
57
+ ):
58
+ """One program per (new token, KV head): its D values to the page slot the slot mapping names, cast to the pool's
59
+ dtype on store."""
60
+ pid_t = tl.program_id(0).to(tl.int64)
61
+ pid_h = tl.program_id(1).to(tl.int64)
62
+ offs_d = tl.arange(0, BLOCK_D)
63
+ mask_d = offs_d < D
64
+ slot = tl.load(slot_ptr + pid_t).to(tl.int64)
65
+ page = slot // page_tokens
66
+ pos = slot % page_tokens
67
+ x = tl.load(new_ptr + pid_t * stride_nt + pid_h * stride_nh + offs_d, mask=mask_d, other=0.0)
68
+ tl.store(pool_ptr + page * stride_pp + pid_h * stride_ph + pos * stride_ps + offs_d,
69
+ x.to(pool_ptr.dtype.element_ty), mask=mask_d)
70
+
71
+
72
+ #: the position tiles the paged read is certified over (each dividing the page, `_tiles_in_the_page`), and the warps
73
+ _PAGED_DECODE_CONFIGS = [triton.Config({'BLOCK_N': bn}, num_warps=w, num_stages=1)
74
+ for bn in PAGED_DECODE_TILE_CANDIDATES for w in (2, 4, 8)]
75
+ #: a split's positions are a whole number of the LARGEST tile, so every candidate tile cuts the splits the same way:
76
+ #: the partials, and the oracle a certification proves them against, are the split's, not the tile's
77
+ PAGED_DECODE_SPLIT_ALIGN = max(PAGED_DECODE_TILE_CANDIDATES)
78
+
79
+
80
+ def _tiles_in_the_page(configs, named_args, **kwargs):
81
+ """The candidates whose tile divides the page (a tile never straddles two pages)."""
82
+ pt = int(named_args["page_tokens"])
83
+ return [c for c in configs if pt % int(c.kwargs["BLOCK_N"]) == 0]
84
+
85
+
86
+ @nbx_autotune(configs=_PAGED_DECODE_CONFIGS, key=['SPLITS', 'page_tokens', 'D', 'D_V', 'GQA_GROUPS'],
87
+ prune_configs_by={'early_config_prune': _tiles_in_the_page}, cache_results=True)
88
+ @triton.jit
89
+ def paged_decode_attn_split_kernel(
90
+ q_ptr, # [B*H_q, D] one query row per program
91
+ k_pool_ptr, # [num_pages, H_kv, page_tokens, D]
92
+ v_pool_ptr, # [num_pages, H_kv, page_tokens, D_v]
93
+ block_table_ptr, # [B, max_pages] int32 page ids, -1 past the sequence
94
+ seq_lens_ptr, # [B] int32 positions each sequence holds
95
+ opart_ptr, # [B*H_q, SPLIT, D_v] fp32 partial outputs
96
+ mpart_ptr, # [B*H_q, SPLIT] fp32 running max per split
97
+ lpart_ptr, # [B*H_q, SPLIT] fp32 sum-of-exp per split
98
+ n_seqs, # B: the split's length is read from the device's lengths, never passed (see below)
99
+ page_tokens, # positions per page, a multiple of BLOCK_N
100
+ SPLITS, # the grid's splits (`paged_decode_splits`): the autotune key's length class (unused in the body)
101
+ sm_scale,
102
+ H_Q,
103
+ stride_qh,
104
+ stride_kp, stride_kh, stride_ks,
105
+ stride_vp, stride_vh, stride_vs,
106
+ stride_bt,
107
+ stride_oh, stride_os,
108
+ stride_mh,
109
+ GQA_GROUPS: tl.constexpr, # H_q // H_kv
110
+ BLOCK_D: tl.constexpr,
111
+ D: tl.constexpr,
112
+ D_V: tl.constexpr,
113
+ SPLIT_ALIGN: tl.constexpr, # a split's positions: a whole number of these (PAGED_DECODE_SPLIT_ALIGN)
114
+ Q_TO: tl.constexpr = None, # a dtype: Q is rounded to it on load (the cache's), as the contiguous read does
115
+ Q_SATURATE: tl.constexpr = False,
116
+ BLOCK_N: tl.constexpr = 32,
117
+ ):
118
+ pid_q = tl.program_id(0).to(tl.int64) # b * H_q + h_q
119
+ pid_s = tl.program_id(1).to(tl.int64) # split index
120
+ b = pid_q // H_Q
121
+ h_kv = (pid_q % H_Q) // GQA_GROUPS
122
+
123
+ offs_d = tl.arange(0, BLOCK_D)
124
+ mask_d = offs_d < D
125
+ mask_dv = offs_d < D_V
126
+ q = _round_q(tl.load(q_ptr + pid_q * stride_qh + offs_d, mask=mask_d, other=0.0), Q_TO, Q_SATURATE)
127
+
128
+ T_b = tl.load(seq_lens_ptr + b).to(tl.int32)
129
+ # Positions per split, read from the DEVICE lengths: the longest sequence over the grid's splits, in whole tiles
130
+ # — the host's figure from the same lengths, so the same value at every step. Passed as a scalar it was frozen into
131
+ # a replayed decode's launch: the split count only moves every PAGED_DECODE_SPLIT_POSITIONS positions, the length
132
+ # every token, and a replayed TinyLlama stopped attending to its newest positions (2026-10-28).
133
+ longest = tl.load(seq_lens_ptr).to(tl.int32)
134
+ for i in range(1, n_seqs):
135
+ longest = tl.maximum(longest, tl.load(seq_lens_ptr + i).to(tl.int32))
136
+ longest = tl.maximum(longest, 1)
137
+ n_split = tl.num_programs(1)
138
+ seg_len = tl.cdiv(tl.cdiv(longest, n_split), SPLIT_ALIGN) * SPLIT_ALIGN
139
+ seg_start = (pid_s * seg_len).to(tl.int32) # 32-bit loop bounds (the Metal lowering refuses a 64-bit scf.for bound)
140
+ seg_end = tl.minimum(seg_start + seg_len, T_b).to(tl.int32)
141
+
142
+ m_i = float("-inf")
143
+ l_i = 0.0
144
+ acc = tl.zeros((BLOCK_D,), dtype=tl.float32)
145
+
146
+ for n0 in range(seg_start, seg_end, BLOCK_N):
147
+ # the tile's page: n0 is a multiple of BLOCK_N and BLOCK_N divides page_tokens, so the tile is in one page
148
+ page = tl.load(block_table_ptr + b * stride_bt + n0 // page_tokens).to(tl.int64)
149
+ in_page = (n0 % page_tokens) + tl.arange(0, BLOCK_N)
150
+ mask_n = (n0 + tl.arange(0, BLOCK_N)) < seg_end
151
+
152
+ k_tile = tl.load(k_pool_ptr + page * stride_kp + h_kv * stride_kh
153
+ + in_page[:, None] * stride_ks + offs_d[None, :],
154
+ mask=mask_n[:, None] & mask_d[None, :], other=0.0)
155
+ s = tl.sum(q[None, :] * k_tile.to(tl.float32), 1) * sm_scale
156
+ s = tl.where(mask_n, s, float("-inf"))
157
+
158
+ m_new = tl.maximum(m_i, tl.max(s, 0))
159
+ m_safe = tl.where(m_new == float("-inf"), 0.0, m_new)
160
+ corr = tl.exp(tl.where(m_i == float("-inf"), float("-inf"), m_i - m_safe))
161
+ p = tl.exp(s - m_safe)
162
+ p = tl.where(mask_n, p, 0.0)
163
+
164
+ v_tile = tl.load(v_pool_ptr + page * stride_vp + h_kv * stride_vh
165
+ + in_page[:, None] * stride_vs + offs_d[None, :],
166
+ mask=mask_n[:, None] & mask_dv[None, :], other=0.0)
167
+ acc = acc * corr + tl.sum(p[:, None] * v_tile.to(tl.float32), 0)
168
+ l_i = l_i * corr + tl.sum(p, 0)
169
+ m_i = m_new
170
+
171
+ tl.store(opart_ptr + pid_q * stride_oh + pid_s * stride_os + offs_d, acc, mask=mask_dv)
172
+ tl.store(mpart_ptr + pid_q * stride_mh + pid_s, m_i)
173
+ tl.store(lpart_ptr + pid_q * stride_mh + pid_s, l_i)
174
+
175
+
176
+ @triton.jit
177
+ def paged_kv_append_step_kernel(
178
+ k_new_ptr, v_new_ptr, # [B, H_kv, D] / [B, H_kv, D_v] the step's one new position per sequence
179
+ k_pool_ptr, v_pool_ptr, # [num_pages, H_kv, page_tokens, D(_v)]
180
+ block_table_ptr, # [B, max_pages] int32
181
+ lens_ptr, # [B] int32: the layer's positions held — the step writes at lens[b]
182
+ page_tokens,
183
+ stride_kb, stride_kh, stride_vb, stride_vh,
184
+ stride_kp, stride_kph, stride_ks,
185
+ stride_vp, stride_vph, stride_vs,
186
+ stride_bt,
187
+ BLOCK_D: tl.constexpr,
188
+ D_K: tl.constexpr,
189
+ D_V: tl.constexpr,
190
+ ):
191
+ """A decode step's append with no host input: the position is read from the layer's DEVICE length and its page from
192
+ the block table, so the launch's arguments are the same at every step (a recorded replay replays it unchanged). One
193
+ program per (sequence, KV head); `paged_lens_advance_kernel`, launched after it on the same stream, moves the
194
+ length."""
195
+ pid_b = tl.program_id(0).to(tl.int64)
196
+ pid_h = tl.program_id(1).to(tl.int64)
197
+ pos = tl.load(lens_ptr + pid_b).to(tl.int64)
198
+ page = tl.load(block_table_ptr + pid_b * stride_bt + pos // page_tokens).to(tl.int64)
199
+ slot = pos % page_tokens
200
+ offs = tl.arange(0, BLOCK_D)
201
+ mk = offs < D_K
202
+ mv = offs < D_V
203
+ k = tl.load(k_new_ptr + pid_b * stride_kb + pid_h * stride_kh + offs, mask=mk, other=0.0)
204
+ v = tl.load(v_new_ptr + pid_b * stride_vb + pid_h * stride_vh + offs, mask=mv, other=0.0)
205
+ tl.store(k_pool_ptr + page * stride_kp + pid_h * stride_kph + slot * stride_ks + offs,
206
+ k.to(k_pool_ptr.dtype.element_ty), mask=mk)
207
+ tl.store(v_pool_ptr + page * stride_vp + pid_h * stride_vph + slot * stride_vs + offs,
208
+ v.to(v_pool_ptr.dtype.element_ty), mask=mv)
209
+
210
+
211
+ @triton.jit
212
+ def paged_lens_advance_kernel(lens_ptr, n):
213
+ """lens[b] += n for every sequence: one program per sequence, after the step's append (stream order)."""
214
+ b = tl.program_id(0).to(tl.int64)
215
+ tl.store(lens_ptr + b, tl.load(lens_ptr + b) + n)
@@ -0,0 +1,144 @@
1
+ """Pixel shuffle (sub-pixel convolution) — pure @triton.jit kernel.
2
+
3
+ Rearranges: [N, C*r*r, H, W] -> [N, C, H*r, W*r]
4
+
5
+ For each output element (n, c, oh, ow):
6
+ ih = oh // r
7
+ iw = ow // r
8
+ ic = c * (r * r) + (oh % r) * r + (ow % r)
9
+ output[n, c, oh, ow] = input[n, ic, ih, iw]
10
+ """
11
+
12
+ import triton
13
+ import triton.language as tl
14
+
15
+ @triton.jit
16
+ def pixel_shuffle_kernel(
17
+ input_ptr,
18
+ output_ptr,
19
+ n_elements,
20
+ # Input shape: [N, C_in, H, W] where C_in = C_out * r * r
21
+ C_out,
22
+ H,
23
+ W,
24
+ r,
25
+ # Output shape: [N, C_out, H*r, W*r]
26
+ OH, # = H * r
27
+ OW, # = W * r
28
+ # Input strides (contiguous NCHW)
29
+ stride_in_n,
30
+ stride_in_c,
31
+ stride_in_h,
32
+ stride_in_w,
33
+ # Output strides (contiguous NCHW)
34
+ stride_out_n,
35
+ stride_out_c,
36
+ stride_out_h,
37
+ stride_out_w,
38
+ BLOCK_SIZE: tl.constexpr,
39
+ ):
40
+ """Pixel shuffle: [N, C*r*r, H, W] -> [N, C, H*r, W*r].
41
+
42
+ Each thread handles one output element.
43
+ """
44
+ pid = tl.program_id(0).to(tl.int64)
45
+ 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)
46
+ mask = idx < n_elements
47
+
48
+ # Decompose flat output index -> (n, c, oh, ow)
49
+ ow = idx % OW
50
+ tmp = idx // OW
51
+ oh = tmp % OH
52
+ tmp2 = tmp // OH
53
+ c = tmp2 % C_out
54
+ n = tmp2 // C_out
55
+
56
+ # Map output coords to input coords
57
+ ih = oh // r
58
+ iw = ow // r
59
+ # Sub-pixel position within the r x r block
60
+ sub_h = oh % r
61
+ sub_w = ow % r
62
+ ic = c * (r * r) + sub_h * r + sub_w
63
+
64
+ # Load from input, store to output
65
+ in_offset = n * stride_in_n + ic * stride_in_c + ih * stride_in_h + iw * stride_in_w
66
+ out_offset = n * stride_out_n + c * stride_out_c + oh * stride_out_h + ow * stride_out_w
67
+
68
+ val = tl.load(input_ptr + in_offset, mask=mask)
69
+ tl.store(output_ptr + out_offset, val, mask=mask)
70
+
71
+
72
+ @triton.jit
73
+ def pixel_shuffle_broadcast_aware_kernel(
74
+ bcast_view_ptr, # 5D NBX expand-output view (N, C_pre, B, H, W) with stride_b == 0
75
+ output_ptr, # 4D contiguous output (N, C_out, OH, OW)
76
+ n_elements,
77
+ C_out, # output channels = (C_pre * B) // (r*r)
78
+ OH, # = H * r
79
+ OW, # = W * r
80
+ r, # pixel_shuffle upscale factor
81
+ bcast, # broadcast factor (= B, the size on the stride-0 dim)
82
+ # 5D bcast_view strides (NBX expand sets stride_b = 0).
83
+ stride_v_n,
84
+ stride_v_c,
85
+ stride_v_b,
86
+ stride_v_h,
87
+ stride_v_w,
88
+ # 4D output strides (contiguous NCHW).
89
+ stride_out_n,
90
+ stride_out_c,
91
+ stride_out_h,
92
+ stride_out_w,
93
+ BLOCK_SIZE: tl.constexpr,
94
+ ):
95
+ """Pixel shuffle that reads through an unmaterialized NBX broadcast view.
96
+
97
+ Input layout: (N, C_pre, B, H, W) where the original DAG pattern is
98
+ `unsqueeze(dim=2) -> expand(dim=2, B) -> clone -> view -> pixel_shuffle`.
99
+ NBX expand sets stride_b=0 (kernels/nbx_tensor.py:1581), so the 5D view
100
+ aliases the pre-expand tensor with no materialization. We read through
101
+ that view directly and skip the clone's 8 GiB copy.
102
+
103
+ The post-clone-post-view shape consumed by a normal pixel_shuffle is
104
+ (N, C_pre*B, H, W); here the kernel does the equivalent decomposition
105
+ inline:
106
+ ic_view = c*r*r + (oh%r)*r + (ow%r) in [0, C_pre*B)
107
+ c_pre = ic_view // bcast in [0, C_pre)
108
+ b = ic_view % bcast in [0, B)
109
+ ih = oh // r
110
+ iw = ow // r
111
+ Read offset uses all 5D strides; with stride_v_b == 0, the `b` index
112
+ contributes nothing and the load aliases naturally.
113
+ """
114
+ pid = tl.program_id(0).to(tl.int64)
115
+ # Cast to int64 to support tensors with >= 2^31 elements
116
+ # (e.g. Sana 4Kpx VAE 1*128*4096*4096 = 2^31 — at INT32_MAX
117
+ # boundary; signed int32 multiplication of strides overflows
118
+ # silently and corrupts offsets, producing garbage output).
119
+ 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)
120
+ mask = idx < n_elements
121
+
122
+ ow = idx % OW
123
+ tmp = idx // OW
124
+ oh = tmp % OH
125
+ tmp2 = tmp // OH
126
+ c = tmp2 % C_out
127
+ n = tmp2 // C_out
128
+
129
+ sub_h = oh % r
130
+ sub_w = ow % r
131
+ ic_view = c * (r * r) + sub_h * r + sub_w
132
+ c_pre = ic_view // bcast
133
+ b = ic_view % bcast
134
+
135
+ ih = oh // r
136
+ iw = ow // r
137
+
138
+ in_offset = (n * stride_v_n + c_pre * stride_v_c + b * stride_v_b
139
+ + ih * stride_v_h + iw * stride_v_w)
140
+ out_offset = (n * stride_out_n + c * stride_out_c
141
+ + oh * stride_out_h + ow * stride_out_w)
142
+
143
+ val = tl.load(bcast_view_ptr + in_offset, mask=mask)
144
+ tl.store(output_ptr + out_offset, val, mask=mask)
@@ -0,0 +1,72 @@
1
+ """Pixel unshuffle (inverse of pixel shuffle) — pure @triton.jit kernel.
2
+
3
+ Rearranges: [N, C, H*r, W*r] -> [N, C*r*r, H, W]
4
+
5
+ For each output element (n, oc, oh, ow):
6
+ c = oc // (r * r)
7
+ sub_h = (oc % (r * r)) // r
8
+ sub_w = (oc % (r * r)) % r
9
+ ih = oh * r + sub_h
10
+ iw = ow * r + sub_w
11
+ output[n, oc, oh, ow] = input[n, c, ih, iw]
12
+ """
13
+
14
+ import triton
15
+ import triton.language as tl
16
+
17
+ @triton.jit
18
+ def pixel_unshuffle_kernel(
19
+ input_ptr,
20
+ output_ptr,
21
+ n_elements,
22
+ # Input shape: [N, C, IH, IW] where IH = H*r, IW = W*r
23
+ C,
24
+ IH,
25
+ IW,
26
+ r,
27
+ # Output shape: [N, C*r*r, H, W]
28
+ C_out, # = C * r * r
29
+ H, # = IH // r
30
+ W, # = IW // r
31
+ # Input strides (contiguous NCHW)
32
+ stride_in_n,
33
+ stride_in_c,
34
+ stride_in_h,
35
+ stride_in_w,
36
+ # Output strides (contiguous NCHW)
37
+ stride_out_n,
38
+ stride_out_c,
39
+ stride_out_h,
40
+ stride_out_w,
41
+ BLOCK_SIZE: tl.constexpr,
42
+ ):
43
+ """Pixel unshuffle: [N, C, H*r, W*r] -> [N, C*r*r, H, W].
44
+
45
+ Each thread handles one output element.
46
+ """
47
+ pid = tl.program_id(0).to(tl.int64)
48
+ 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)
49
+ mask = idx < n_elements
50
+
51
+ # Decompose flat output index -> (n, oc, oh, ow)
52
+ ow = idx % W
53
+ tmp = idx // W
54
+ oh = tmp % H
55
+ tmp2 = tmp // H
56
+ oc = tmp2 % C_out
57
+ n = tmp2 // C_out
58
+
59
+ # Map output coords to input coords
60
+ c = oc // (r * r)
61
+ sub_idx = oc % (r * r)
62
+ sub_h = sub_idx // r
63
+ sub_w = sub_idx % r
64
+ ih = oh * r + sub_h
65
+ iw = ow * r + sub_w
66
+
67
+ # Load from input, store to output
68
+ in_offset = n * stride_in_n + c * stride_in_c + ih * stride_in_h + iw * stride_in_w
69
+ out_offset = n * stride_out_n + oc * stride_out_c + oh * stride_out_h + ow * stride_out_w
70
+
71
+ val = tl.load(input_ptr + in_offset, mask=mask)
72
+ tl.store(output_ptr + out_offset, val, mask=mask)
nbxtensor/ops/pow.py ADDED
@@ -0,0 +1,137 @@
1
+ """Power — pure @triton.jit kernel."""
2
+
3
+ import triton
4
+ import triton.language as tl
5
+
6
+ @triton.jit
7
+ def pow_forward_kernel(
8
+ input_ptr, output_ptr,
9
+ n_elements,
10
+ exponent,
11
+ BLOCK_SIZE: tl.constexpr,
12
+ INT_EXP: tl.constexpr = 0,
13
+ ):
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(input_ptr + offset, mask=mask)
19
+ x_fp32 = x.to(tl.float32)
20
+ # `tl.extra.CUDA.libdevice.pow` used to stand here. It is NVIDIA's device
21
+ # library, and asking any other backend to lower it fails at compile time —
22
+ # measured on triton-ext, Kokoro-82M dying at aten.pow::0 (1, 55, 2048) with
23
+ # "PassManager::run failed", and reproduced with a bare kernel containing no
24
+ # engine code (repro_pow_libdevice_cuda_namespace.py):
25
+ # tl.extra.cuda.libdevice.pow FAILED PassManager::run failed
26
+ # portable exp/log/sign COMPILED max rel err 2.522e-07
27
+ # Triton 3.8.0 exposes no backend-neutral `pow`, so the semantics are built
28
+ # here rather than borrowed from one vendor.
29
+ #
30
+ # libm semantics, which is what the libdevice call was chosen for:
31
+ # x > 0 exp(e * ln x)
32
+ # x < 0, e an integer sign * exp(e * ln|x|), sign = -1 when e is odd
33
+ # x < 0, e not an integer NaN (no real root)
34
+ # x = +-0 the THREE cases below, not one
35
+ #
36
+ # The zero lane read `0.0` for every exponent until 2026-09-19. That is
37
+ # wrong for two thirds of it, and the Dell measured it on CUDA against an
38
+ # fp64 oracle: `0.0 ** 0.0` gave 0.0 where the oracle gives 1.0, and
39
+ # `0.0 ** -1.0` gave 0.0 where it gives inf. Mine, and the shape of the
40
+ # mistake is worth naming: replacing a vendor intrinsic means reimplementing
41
+ # its whole DOMAIN, and I reimplemented the interesting part and not the
42
+ # edges. C99 / IEEE-754 pow at zero:
43
+ #
44
+ # pow(+-0, 0) 1 (even 0**0, and even nan**0)
45
+ # pow(+-0, e) e > 0 +-0, negative only for -0 with e an odd int
46
+ # pow(+-0, e) e < 0 +-inf, same sign rule
47
+ #
48
+ # The sign of the zero has to be read from the SIGN BIT, because `x < 0` is
49
+ # false for -0.0 and `x == 0` cannot tell the two apart. A bitcast to int32
50
+ # is negative exactly when the sign bit is set, which covers -0.0.
51
+ _ax = tl.abs(x_fp32)
52
+ _mag = tl.exp(exponent * tl.log(_ax))
53
+ _is_int = tl.floor(exponent) == exponent
54
+ _is_odd = _is_int & ((tl.floor(exponent * 0.5) * 2.0) != exponent)
55
+ _sign = tl.where(_is_odd, -1.0, 1.0)
56
+ _neg = tl.where(_is_int, _sign * _mag, float("nan"))
57
+
58
+ _signbit = x_fp32.to(tl.int32, bitcast=True) < 0 # true for -0.0 too
59
+ _zsign = tl.where(_signbit & _is_odd, -1.0, 1.0)
60
+ _zero = tl.where(exponent == 0.0, 1.0,
61
+ tl.where(exponent > 0.0,
62
+ _zsign * 0.0, # +-0
63
+ _zsign * float("inf"))) # +-inf
64
+ out = tl.where(x_fp32 > 0, _mag, tl.where(x_fp32 < 0, _neg, _zero))
65
+ # THE EXACT ROUTE FOR A SMALL INTEGER EXPONENT — the branch the comment
66
+ # below records as "available rather than taken". Taken on 2026-09-20,
67
+ # because the Dell measured the general route on CUDA against the fp64
68
+ # oracle and it is not a rounding footnote: x**2 lands up to 15 ulps from
69
+ # the oracle where main's libdevice call sat within 2 (38 147 lanes, |x| a
70
+ # standard normal with zero lanes), x**0.5 at 5 vs 2, x**-1 at 7 vs 2. A
71
+ # square computed as exp(2 log|x|) is not a square. Repeated multiplication
72
+ # is correctly rounded at e=2 and within two roundings at e=3, needs no
73
+ # vendor library, and carries IEEE's own sign rules for free: (-0)**2 is
74
+ # +0, (-0)**3 is -0, 1/(+-0) is +-inf, so none of the select logic above is
75
+ # needed on this route. The wrapper sets INT_EXP when the exponent is an
76
+ # integer-valued scalar with 1 <= |e| <= 8; INT_EXP == 0 keeps the general
77
+ # route, which still owns e == 0, non-integers and large integers.
78
+ if INT_EXP != 0:
79
+ _n: tl.constexpr = INT_EXP if INT_EXP > 0 else -INT_EXP
80
+ _p = x_fp32
81
+ if _n >= 2:
82
+ _p = _p * x_fp32
83
+ if _n >= 3:
84
+ _p = _p * x_fp32
85
+ if _n >= 4:
86
+ _p = _p * x_fp32
87
+ if _n >= 5:
88
+ _p = _p * x_fp32
89
+ if _n >= 6:
90
+ _p = _p * x_fp32
91
+ if _n >= 7:
92
+ _p = _p * x_fp32
93
+ if _n >= 8:
94
+ _p = _p * x_fp32
95
+ if INT_EXP < 0:
96
+ _p = 1.0 / _p
97
+ out = _p
98
+ # ---------------------------------------------------------------------
99
+ # THE ACCURACY COST OF THIS ROUTE, AND WHAT IT IS AND IS NOT INHERENT TO.
100
+ #
101
+ # `exp(e * log|x|)` loses precision as |x| moves away from 1, because the
102
+ # error in `log|x|` is multiplied by `e` before `exp` amplifies it. The Dell
103
+ # measured pow-to-the-third at 19x worse relative error than main's
104
+ # libdevice call on CUDA. Measured here against fp64, on the ranges a
105
+ # vocoder actually sees (probe_pow_error.py):
106
+ #
107
+ # range e this kernel x*x(*x) ratio
108
+ # |x| in [0.5, 2] 2 2.56e-07 5.94e-08 4.3x
109
+ # |x| in [0.5, 2] 3 3.95e-07 1.14e-07 3.4x
110
+ # |x| in [1e-3, 1e3] 3 3.58e-06 1.15e-07 31x
111
+ # |x| in [1e-6, 1e-3] 3 7.24e-06 1.13e-07 64x
112
+ #
113
+ # So the 19x is real and it is range-dependent: 3.4x on typical activations,
114
+ # 64x on small ones.
115
+ #
116
+ # WHAT IT COSTS ON THE MODELS THAT USE IT. `aten::pow` appears 194 times
117
+ # across the 16 cached containers, and only in the two vocoder TTS models:
118
+ # exponent 2 182x Kokoro-82M 49, chatterbox 133
119
+ # exponent 3.0 12x Kokoro-82M only
120
+ # Nothing else in the catalogue calls pow at all, and no site uses a
121
+ # non-integer exponent. Both models are judged by third-party ASR and both
122
+ # transcribe correctly; the error is ~1e-7 relative on their activation
123
+ # range, which is below fp32 round-off for the accumulations downstream of
124
+ # it. So the cost TODAY is not audible and not measurable at the artefact.
125
+ #
126
+ # BUT IT IS AVOIDABLE, and that is the part worth recording rather than
127
+ # accepting: the cost is inherent to `exp(e * log|x|)`, NOT to `pow`. Every
128
+ # exponent the catalogue uses is a small integer, and for those, repeated
129
+ # multiplication is 3-64x MORE accurate and no more expensive — one
130
+ # multiply for e=2 against a log, a multiply and an exp. A `POW_SMALL_INT`
131
+ # constexpr branch would take the exact route at compile time and leave this
132
+ # path for the general case.
133
+ #
134
+ # It is NOT done here, deliberately: it changes the numerics of two SHIPPED
135
+ # models, so it owes a fresh set of judged cells and a fresh CUDA column,
136
+ # and 0.5.4 has just been cut. Recorded as available rather than taken.
137
+ tl.store(output_ptr + offset, out, mask=mask)
nbxtensor/ops/prod.py ADDED
@@ -0,0 +1,81 @@
1
+ """Product reduction — pure @triton.jit kernels.
2
+
3
+ Global reduction via two-pass (mid → result) and dim-specific reduction.
4
+ Extracted from FlagGems (Apache-2.0).
5
+ """
6
+
7
+ import triton
8
+ import triton.language as tl
9
+
10
+
11
+ @triton.jit
12
+ def reduce_mul(a, b):
13
+ return a * b
14
+
15
+
16
+ # ---------------------------------------------------------------------------
17
+ # Global reduction: two-pass partial products
18
+ # ---------------------------------------------------------------------------
19
+
20
+ @triton.jit
21
+ def prod_kernel_mid(
22
+ inp,
23
+ mid,
24
+ M,
25
+ BLOCK_SIZE: tl.constexpr,
26
+ ):
27
+ """Pass 1: each program computes a partial product over its BLOCK_SIZE chunk."""
28
+ pid = tl.program_id(0).to(tl.int64)
29
+ 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)
30
+ inp_ptrs = inp + offset
31
+ mask = offset < M
32
+ inp_val = tl.load(inp_ptrs, mask=mask, other=1.0).to(tl.float32)
33
+ mid_value = tl.reduce(inp_val, axis=0, combine_fn=reduce_mul)
34
+ mid_ptr = mid + pid
35
+ tl.store(mid_ptr, mid_value.to(inp_val.dtype))
36
+
37
+
38
+ @triton.jit
39
+ def prod_kernel_result(mid, out, mid_size, BLOCK_MID: tl.constexpr):
40
+ """Pass 2: reduce all partial products into a single scalar."""
41
+ offset = tl.arange(0, BLOCK_MID)
42
+ mid_ptrs = mid + offset
43
+ mask = offset < mid_size
44
+ mid_val = tl.load(mid_ptrs, mask=mask, other=1.0).to(tl.float32)
45
+ prod_val = tl.reduce(mid_val, axis=0, combine_fn=reduce_mul)
46
+ tl.store(out, prod_val)
47
+
48
+
49
+ # ---------------------------------------------------------------------------
50
+ # Dim-specific reduction: product along one axis
51
+ # ---------------------------------------------------------------------------
52
+
53
+ @triton.jit
54
+ def prod_kernel(
55
+ inp,
56
+ out,
57
+ M,
58
+ N,
59
+ BLOCK_M: tl.constexpr,
60
+ BLOCK_N: tl.constexpr,
61
+ ):
62
+ """Reduce product along the compressed inner dimension N.
63
+
64
+ inp: [M, N] (dim-compressed) → out: [M]
65
+ """
66
+ pid_m = tl.program_id(0).to(tl.int64)
67
+ m_offset = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
68
+
69
+ acc = tl.full((BLOCK_M, BLOCK_N), value=1.0, dtype=tl.float32)
70
+ for start_n in range(0, N, BLOCK_N):
71
+ n_offset = start_n + tl.arange(0, BLOCK_N)
72
+ offset = m_offset[:, None] * N + n_offset[None, :]
73
+ mask = (m_offset[:, None] < M) & (n_offset[None, :] < N)
74
+ inp_ptrs = inp + offset
75
+ inp_vals = tl.load(inp_ptrs, mask=mask, other=1.0).to(tl.float32)
76
+ acc *= inp_vals
77
+ result = tl.reduce(acc, axis=1, combine_fn=reduce_mul)
78
+
79
+ out_ptrs = out + m_offset
80
+ mask1 = m_offset < M
81
+ tl.store(out_ptrs, result, mask=mask1)