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,370 @@
1
+ """The TilingEngine's sizing half — torch-free, imported by Prism, the launch keys, the Triton
2
+ wrappers and the TilingEngine's executor.
3
+
4
+ One module for every caller: the Triton branch (the wrappers, the launch keys and the derived census through them)
5
+ and NeuroBrix's PyTorch branch (Prism, the core executors and the TilingEngine) alike — the engine keeps no copy.
6
+
7
+ Every size a memory split is cut by is computed here, from the library's `nbxtensor/ops/tiling.yml` (engine-wide
8
+ values, each with its source) and from the values the caller reads in the active hardware
9
+ profile (per-architecture values stay there). Pure arithmetic: Prism prices with it at plan time,
10
+ the launch keys and the census derive with it, the wrappers and the executor cut with it — one
11
+ definition each, so the plan, the key and the run agree.
12
+
13
+ What it owns:
14
+ * the conv3d one-shot peak and its temporal chunk (a PLAN decision: `need` against the op
15
+ budget; the wrapper sizes the chunks with the same function);
16
+ * the conv2d band cut;
17
+ * the attention scores bound and its chunk rows;
18
+ * Prism's op budget and its op-level band factors (conv, fusion pair, rms_norm, residual chain);
19
+ * the tile overlaps (spatial and temporal);
20
+ * the in-place and residual-chain thresholds;
21
+ * the paged decode read's position tiles (the page size is sized from them, core/state/price).
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import math
26
+ from typing import Any, Sequence, Tuple
27
+
28
+
29
+ # The position tiles the paged read is compiled for: powers of two, each dividing the largest, so a page sized from the
30
+ # largest holds a whole number of tiles of every candidate. 32 is the vector decode kernel's judged tile (its locked
31
+ # protocol of 2026-08-23: seg 256 / BLOCK_N 32 / 4 warps won at all three contexts, ops/decode_attn_vec.py,
32
+ # kernels/wrappers.py `_decode_attn_vec`); 16 and 64 are its neighbours, the range the certified directory chooses in.
33
+ PAGED_DECODE_TILE_CANDIDATES = (16, 32, 64)
34
+
35
+ def _value(*path: str) -> Any:
36
+ """One value of nbxtensor/ops/tiling.yml, refused by name when absent."""
37
+ from nbxtensor.ops.tiling_policy import tiling_table # the policy in force (nbxtensor.ops.tiling_policy.set_tiling)
38
+ node: Any = tiling_table()
39
+ for i, key in enumerate(path):
40
+ if not isinstance(node, dict) or key not in node:
41
+ raise KeyError(f"ZERO FALLBACK: nbxtensor/ops/tiling.yml states no {'.'.join(path[:i + 1])}")
42
+ node = node[key]
43
+ return node
44
+
45
+
46
+ def _pow2_at_most(factor: int, cap: int) -> int:
47
+ """The smallest power of two >= `factor`, at most `cap` (a band factor aligned for halos)."""
48
+ p = 1
49
+ while p < factor and p < cap:
50
+ p *= 2
51
+ return p
52
+
53
+
54
+ # --- conv3d: the one-shot peak and the temporal chunk ---------------------------------------------
55
+
56
+ def conv3d_chunk_bytes() -> int:
57
+ """The per-transient bound of the chunked conv3d path (`conv3d.chunk_bytes`)."""
58
+ return int(_value("conv3d", "chunk_bytes"))
59
+
60
+
61
+ def conv2d_band_bytes() -> int:
62
+ """The conv2d band-streaming threshold (`conv2d.band_bytes`)."""
63
+ return int(_value("conv2d", "band_bytes"))
64
+
65
+
66
+ def _triple(v) -> Tuple[int, int, int]:
67
+ if isinstance(v, (list, tuple)):
68
+ v = list(v) + [v[-1]] * (3 - len(v))
69
+ return int(v[0]), int(v[1]), int(v[2])
70
+ return int(v), int(v), int(v)
71
+
72
+
73
+ def conv3d_need(x_shape: Sequence[int], w_shape: Sequence[int], stride, padding, dilation,
74
+ in_bytes: int, out_bytes: int) -> Tuple[int, int]:
75
+ """(bytes the ONE-SHOT path holds at its peak, folded bytes per output frame). `need` is 0 when
76
+ no fold exceeds `conv3d_chunk_bytes()` — the one-shot path is taken whatever the budget. The
77
+ peak: the temporal pad copy, then the larger of (input fold + output fold [+ a band when the
78
+ output bands] [+ the kt accumulator]) and (two output folds [+ the accumulator]), and a fixed
79
+ slack (`conv3d.slack_bytes`). The wrapper runs conv3d as kt conv2d launches over the folded
80
+ batch axis (B*T_out frames); whether it chunks is Prism's decision, made from this `need`
81
+ against the op budget (decision 2, 2026-09-29: never from the driver's free bytes)."""
82
+ B, Cin, T, H, W = (int(d) for d in x_shape)
83
+ Cout, _Cg, kt, kh, kw = (int(d) for d in w_shape)
84
+ st, sh, sw = _triple(stride)
85
+ pt, ph, pw = _triple(padding)
86
+ dt, dh, dw = _triple(dilation)
87
+ t_out = (T + 2 * pt - dt * (kt - 1) - 1) // st + 1
88
+ if t_out <= 0:
89
+ return 0, 0
90
+ oh = (H + 2 * ph - dh * (kh - 1) - 1) // sh + 1
91
+ ow = (W + 2 * pw - dw * (kw - 1) - 1) // sw + 1
92
+ fold_in = B * t_out * Cin * H * W * in_bytes
93
+ fold_out = B * t_out * Cout * oh * ow * out_bytes
94
+ frame = max(fold_in, fold_out) // max(1, t_out)
95
+ if max(fold_in, fold_out) <= conv3d_chunk_bytes():
96
+ return 0, frame
97
+ return conv3d_one_shot_peak(x_shape, w_shape, stride, padding, dilation, in_bytes, out_bytes), frame
98
+
99
+
100
+ def conv3d_one_shot_peak(x_shape: Sequence[int], w_shape: Sequence[int], stride, padding, dilation,
101
+ in_bytes: int, out_bytes: int) -> int:
102
+ """The bytes the one-shot conv3d path (`_conv3d_via_conv2d`) holds at its peak BESIDE its input: the temporal pad
103
+ copy, then the larger of (input fold + output fold [+ a band] [+ the kt accumulator]) and (two output folds [+ the
104
+ accumulator]), and the slack. Its result is among them (one output fold). `conv3d_need` decides the chunking from
105
+ it; Prism's activation profile prices the op's in-op figure from it on the Triton engines — measured 2026-10-17 on
106
+ Wan2.1-T2V's VAE: 5,276 MB beside a 1,755 MB output, the formula's 3 x 1,755."""
107
+ B, Cin, T, H, W = (int(d) for d in x_shape)
108
+ Cout, _Cg, kt, kh, kw = (int(d) for d in w_shape)
109
+ st, sh, sw = _triple(stride)
110
+ pt, ph, pw = _triple(padding)
111
+ dt, dh, dw = _triple(dilation)
112
+ t_out = (T + 2 * pt - dt * (kt - 1) - 1) // st + 1
113
+ if t_out <= 0:
114
+ return 0
115
+ oh = (H + 2 * ph - dh * (kh - 1) - 1) // sh + 1
116
+ ow = (W + 2 * pw - dw * (kw - 1) - 1) // sw + 1
117
+ fold_in = B * t_out * Cin * H * W * in_bytes
118
+ fold_out = B * t_out * Cout * oh * ow * out_bytes
119
+ band = conv2d_band_bytes()
120
+ pad_bytes = B * Cin * (T + 2 * pt) * H * W * in_bytes if pt > 0 else 0
121
+ band_extra = band if fold_out > band else 0
122
+ acc_extra = fold_out if kt >= 2 else 0
123
+ return (pad_bytes + max(fold_out + fold_in + band_extra + acc_extra, 2 * fold_out + acc_extra)
124
+ + int(_value("conv3d", "slack_bytes")))
125
+
126
+
127
+ def residual_chain_bands(H: int, tile_factor: int, halo: int):
128
+ """The bands a band-streamed residual chain runs over its spatial axis: [(h_start, h_end, h_in_start, h_in_end)]
129
+ — the output rows of each band and the input rows it reads (the band plus `halo` rows each side, cut at the
130
+ edges). ONE function for the chain that runs them (`kernels/ops/residual_chain.py`) and the census that records
131
+ the conv keys they form (a 418-row band of Sana 4K's VAE met no certified key, 2026-10-17): the recorded key is
132
+ the launcher's key."""
133
+ H, tile_factor, halo = int(H), max(1, int(tile_factor)), int(halo)
134
+ band_h = (H + tile_factor - 1) // tile_factor
135
+ bands = []
136
+ for i in range(tile_factor):
137
+ h_start, h_end = i * band_h, min((i + 1) * band_h, H)
138
+ if h_start >= h_end:
139
+ break
140
+ bands.append((h_start, h_end, max(0, h_start - halo), min(H, h_end + halo)))
141
+ return bands
142
+
143
+
144
+ def chunk_frames(frame_bytes: int) -> int:
145
+ """Output frames per chunk: each chunk's folded transient under `conv3d_chunk_bytes()`."""
146
+ return max(1, int(conv3d_chunk_bytes() // max(1, frame_bytes)))
147
+
148
+
149
+ # --- conv2d: the band cut -------------------------------------------------------------------------
150
+
151
+ def conv2d_band_rows(N: int, out_c: int, out_h: int, out_w: int, out_bytes: int,
152
+ band_bytes: int) -> Tuple[int, int]:
153
+ """(output rows of one band, bytes of one output row) of conv2d band streaming: bands of
154
+ about `band_bytes / conv2d.band_budget_divisor`, evenly cut. A band >= `out_h` means one row
155
+ is already over the budget — the caller refuses it."""
156
+ band_target = max(1, band_bytes // int(_value("conv2d", "band_budget_divisor")))
157
+ row_bytes = N * out_c * out_w * out_bytes
158
+ rows_per_band = max(1, band_target // max(1, row_bytes))
159
+ tile_factor = max(1, (out_h + rows_per_band - 1) // rows_per_band)
160
+ return (out_h + tile_factor - 1) // tile_factor, row_bytes
161
+
162
+
163
+ def tiled_conv2d_bands(IH: int, out_h: int, kh: int, sh: int, dh: int, pad_h: int, tile_factor: int):
164
+ """The bands of Prism's op-level tiled conv (real halo): per band (oh_start, oh_end, in_start, in_end, pad_top,
165
+ pad_bot, skip) — the output rows, the input rows read (clamped), the image-edge padding added, the band's leading
166
+ conv rows to skip. ONE cut for the run (`nbxtensor/ops/fused_upsample_conv.tiled_conv2d` and its torch
167
+ twin), the derived census (`launch_keys.tiled_conv2d_launches`) and Prism's price of the band
168
+ (`tiled_conv2d_band_bytes`). The image-edge padding is `max(0, -read_start)` alone: `read_start` already carries
169
+ -pad_h (P-NBX-TILED-CONV2D-SMALL-SCALE 2026-05-14). The halo is rounded up to a whole number of strides: the band's
170
+ first conv row must be an output row (at stride 2 a 1-row halo misaligned every internal band, 2026-09-29)."""
171
+ tf = max(1, int(tile_factor))
172
+ band_oh = (out_h + tf - 1) // tf
173
+ halo_h = -(-((kh - 1) * dh // 2) // sh) * sh
174
+ bands = []
175
+ for oh_start in range(0, out_h, band_oh):
176
+ oh_end = min(oh_start + band_oh, out_h)
177
+ halo_top = 0 if oh_start == 0 else halo_h
178
+ halo_bot = 0 if oh_end == out_h else halo_h
179
+ read_start = oh_start * sh - pad_h - halo_top
180
+ read_end = (oh_end - 1) * sh + dh * (kh - 1) + 1 - pad_h + halo_bot
181
+ start, end = max(0, read_start), min(IH, read_end)
182
+ if end <= start:
183
+ continue
184
+ bands.append((oh_start, oh_end, start, end, max(0, -read_start), max(0, read_end - IH),
185
+ halo_top // sh))
186
+ return bands
187
+
188
+
189
+ def tiled_conv2d_band_bytes(x_shape: Sequence[int], w_shape: Sequence[int], stride, padding, dilation,
190
+ tile_factor: int, in_bytes: int, out_bytes: int) -> int:
191
+ """What a tiled 2-D conv holds BESIDE its input and its whole output while its largest band runs: the band's input
192
+ rows copied into one buffer at the conv's compute width `in_bytes` (padded by the image-edge rows, and by pad_w on
193
+ both sides whenever any padding applies — one buffer, the slice copied into its interior) and the band's output at
194
+ `out_bytes`, before it is copied into the whole output (`kernels/ops/fused_upsample_conv._tiled_conv2d_spatial_*`,
195
+ both engines). Unpriced, HAT-L x4 at 2048 px on a 16 GB card ran out of memory asking a band's 482 MiB beside the
196
+ 1 446 MiB band input (2026-10-22)."""
197
+ N, C, IH, IW = (int(v) for v in x_shape)
198
+ O, _cg, kh, kw = (int(v) for v in w_shape)
199
+ sh, sw = _pair(stride)
200
+ ph, pw = _pair(padding)
201
+ dh, dw = _pair(dilation)
202
+ out_h = (IH + 2 * ph - dh * (kh - 1) - 1) // sh + 1
203
+ out_w = (IW + 2 * pw - dw * (kw - 1) - 1) // sw + 1
204
+ worst = 0
205
+ for o0, o1, start, end, pt, pb, _skip in tiled_conv2d_bands(IH, out_h, kh, sh, dh, ph, tile_factor):
206
+ padded = pt > 0 or pb > 0 or pw > 0
207
+ rows_in = end - start + pt + pb
208
+ width_in = IW + 2 * pw if padded else IW
209
+ worst = max(worst, N * C * rows_in * width_in * int(in_bytes) + N * O * (o1 - o0) * out_w * int(out_bytes))
210
+ return worst
211
+
212
+
213
+ def _pair(v) -> Tuple[int, int]:
214
+ if isinstance(v, (list, tuple)):
215
+ return (int(v[0]), int(v[-1]))
216
+ return (int(v), int(v))
217
+
218
+
219
+ # --- attention: the scores bound, the chunk rows, the device budget -------------------------------
220
+
221
+ #: The math route's scores are fp32: four bytes a score (the width of the dtype, not a size choice).
222
+ _SCORES_BYTES = 4
223
+
224
+
225
+ def sdpa_scores_bytes(batch: int, nheads: int, Tq: int, Tk: int) -> int:
226
+ """The bytes of an attention's fp32 scores."""
227
+ return batch * nheads * Tq * Tk * _SCORES_BYTES
228
+
229
+
230
+ def sdpa_scores_bound(budget_bytes: int, head_dim_pow2: bool) -> int:
231
+ """The fp32 scores bound an attention routes on: the device's budget; a non-power-of-two head
232
+ dim whose architecture declares none routes on `sdpa.non_pow2_head_scores_bytes`."""
233
+ if head_dim_pow2:
234
+ return budget_bytes
235
+ return budget_bytes or int(_value("sdpa", "non_pow2_head_scores_bytes"))
236
+
237
+
238
+ def sdpa_chunk_rows(bound: int, batch: int, nheads: int, Tq: int, Tk: int,
239
+ min_chunk_rows: int, max_chunks: int) -> int:
240
+ """Query rows per chunk that keep each chunk's fp32 scores within `bound`, aligned to the
241
+ arch's row block (`memory.sdpa_math_min_chunk_rows`); 0 when the shape cannot chunk within
242
+ the arch's chunk ceiling (`memory.sdpa_math_max_chunks`)."""
243
+ if not min_chunk_rows:
244
+ return 0
245
+ rows = (bound // (batch * nheads * Tk * _SCORES_BYTES)) // min_chunk_rows * min_chunk_rows
246
+ if rows >= min_chunk_rows and -(-Tq // rows) <= max_chunks:
247
+ return rows
248
+ return 0
249
+
250
+
251
+ def sdpa_chunk_plan(bound: int, batch: int, nheads: int, Tq: int, Tk: int,
252
+ min_chunk_rows: int) -> Tuple[int, int]:
253
+ """(slices per chunk, query rows per chunk) that keep each chunk's fp32 scores within `bound`: the rows as many as
254
+ fit with one (batch, head) slice, a multiple of the arch's row block (`memory.sdpa_math_min_chunk_rows`) or the
255
+ whole query; then as many slices as fit beside them. Every slice and every query row is its own softmax, so any
256
+ such cut is exact. (batch x nheads, Tq) when the whole scores fit; (0, 0) when one slice of one row block exceeds
257
+ `bound`."""
258
+ slices = batch * nheads
259
+ if sdpa_scores_bytes(batch, nheads, Tq, Tk) <= bound:
260
+ return slices, Tq
261
+ per_row = Tk * _SCORES_BYTES
262
+ rows = min(Tq, (bound // per_row) // min_chunk_rows * min_chunk_rows) if min_chunk_rows else 0
263
+ if rows <= 0:
264
+ return 0, 0
265
+ return max(1, min(slices, bound // (rows * per_row))), rows
266
+
267
+
268
+ def sdpa_device_scores_budget(base_bytes: int, device_fraction: float, device_memory_mb) -> int:
269
+ """The scores budget on one device: min(the arch's byte budget, `device_fraction` of THIS
270
+ device's memory) — the 2026-08-31 xlong-prefill OOM proved a per-arch byte budget alone is
271
+ wrong on heterogeneous rigs. No fraction, or no device memory known: the arch's budget."""
272
+ if not base_bytes:
273
+ return 0
274
+ if not device_fraction or device_memory_mb is None:
275
+ return base_bytes
276
+ return min(base_bytes, int(device_memory_mb * 1024 * 1024 * device_fraction))
277
+
278
+
279
+ def sdpa_math_scores_budget(base_bytes: int, device_fraction: float, device_memory_mb) -> int:
280
+ """The math route's scores budget on the device that executes an attention: 0 where the arch states no byte budget
281
+ (`memory.sdpa_math_max_scores_bytes`: a power-of-two head dim never routes on size), the arch's budget where it
282
+ states no device fraction (`memory.sdpa_math_scores_device_fraction`), else `sdpa_device_scores_budget`. ONE rule
283
+ for the wrappers' route (`wrappers._sdpa_math_scores_budget_bytes_for`) and the scratch Prism prices for it."""
284
+ if not base_bytes:
285
+ return 0
286
+ if not device_fraction:
287
+ return base_bytes
288
+ return sdpa_device_scores_budget(base_bytes, device_fraction, device_memory_mb)
289
+
290
+
291
+ # --- Prism: the op budget and the op-level band factors -------------------------------------------
292
+
293
+ def op_budget_fraction() -> float:
294
+ """The fraction of a card an op's transient is measured against (`op_level.op_budget_card_fraction`)."""
295
+ return float(_value("op_level", "op_budget_card_fraction"))
296
+
297
+
298
+ def op_budget_bytes(card_bytes: int, resident_bytes: int) -> int:
299
+ """An op's budget on its card: the op budget fraction of the card minus the component's
300
+ resident weights."""
301
+ return int(card_bytes * op_budget_fraction()) - resident_bytes
302
+
303
+
304
+ def conv_band_factor(total_bytes: int, kept_bytes: int, card_bytes: int) -> int:
305
+ """Bands an overflowing convolution (or upsample->conv pair) runs in: its tiled bytes over
306
+ one band's budget (`conv_band_card_fraction` of the card minus the output kept whole, at
307
+ least `band_budget_floor_bytes`), at least `min_band_factor`, rounded up to a power of two
308
+ at most `max_band_factor`."""
309
+ budget = int(float(_value("op_level", "conv_band_card_fraction")) * card_bytes) - kept_bytes
310
+ budget = max(budget, int(_value("op_level", "band_budget_floor_bytes")))
311
+ factor = max(int(_value("op_level", "min_band_factor")), math.ceil(total_bytes / budget))
312
+ return _pow2_at_most(factor, int(_value("op_level", "max_band_factor")))
313
+
314
+
315
+ def upsample_overflows(out_bytes: int, card_bytes: int) -> bool:
316
+ """An upsample output worth fusing into its conv even when the conv does not overflow."""
317
+ return out_bytes > float(_value("op_level", "upsample_overflow_card_fraction")) * card_bytes
318
+
319
+
320
+ def rms_norm_overflows(out_bytes: int, card_bytes: int) -> bool:
321
+ """An rms_norm whose output runs in H bands."""
322
+ return out_bytes > float(_value("op_level", "rms_norm", "overflow_card_fraction")) * card_bytes
323
+
324
+
325
+ def rms_norm_band_factor(out_bytes: int, card_bytes: int) -> int:
326
+ """Bands an overflowing rms_norm runs in: its output over `rms_norm.band_card_fraction` of
327
+ the card, at least `rms_norm.min_band_factor`, a power of two, at most `rms_norm.max_band_factor`."""
328
+ budget = int(float(_value("op_level", "rms_norm", "band_card_fraction")) * card_bytes)
329
+ factor = max(int(_value("op_level", "rms_norm", "min_band_factor")), math.ceil(out_bytes / budget))
330
+ factor = _pow2_at_most(factor, int(_value("op_level", "max_band_factor")))
331
+ return min(factor, int(_value("op_level", "rms_norm", "max_band_factor")))
332
+
333
+
334
+ def residual_chain_band_factor(bytes_fp32: int) -> int:
335
+ """Bands a residual chain streams in: its half-width bytes (the fp32 estimate halved) over
336
+ `residual_chain.band_target_bytes`, at least `residual_chain.min_band_factor`, a power of two at most
337
+ `residual_chain.max_band_factor`."""
338
+ half_bytes = bytes_fp32 // 2
339
+ factor = max(int(_value("op_level", "residual_chain", "min_band_factor")),
340
+ math.ceil(half_bytes / int(_value("op_level", "residual_chain", "band_target_bytes"))))
341
+ return _pow2_at_most(factor, int(_value("op_level", "residual_chain", "max_band_factor")))
342
+
343
+
344
+ def residual_chain_min_base_bytes_fp32() -> int:
345
+ """The base-tensor size (fp32) from which a residual chain is detected."""
346
+ return int(_value("op_level", "residual_chain", "min_base_bytes_fp32"))
347
+
348
+
349
+ # --- overlaps -------------------------------------------------------------------------------------
350
+
351
+ def spatial_halo(extent: int) -> int:
352
+ """The proportional spatial overlap of a tile of `extent`: extent // `overlap.spatial_divisor`."""
353
+ return extent // int(_value("overlap", "spatial_divisor"))
354
+
355
+
356
+ def spatial_overlap(extent: int) -> int:
357
+ """A spatial tile's overlap: its proportional halo, at least `overlap.spatial_min`."""
358
+ return max(int(_value("overlap", "spatial_min")), spatial_halo(extent))
359
+
360
+
361
+ def temporal_halo(t_tile: int) -> int:
362
+ """The proportional temporal overlap of a tile of `t_tile` frames: t_tile // `overlap.temporal_divisor`."""
363
+ return t_tile // int(_value("overlap", "temporal_divisor"))
364
+
365
+
366
+ # --- in place -------------------------------------------------------------------------------------
367
+
368
+ def inplace_min_bytes() -> int:
369
+ """The output size from which an add or an element-wise unary writes into its dead input."""
370
+ return int(_value("inplace", "min_bytes"))
nbxtensor/ops/topk.py ADDED
@@ -0,0 +1,267 @@
1
+ """Top-K — pure @triton.jit kernels. Extracted from FlagGems.
2
+
3
+ Two-stage approach:
4
+ topk_stage1_kernel: per-chunk top-k selection via iterative max/min
5
+ topk_stage2_kernel: merge chunks via bitonic sort
6
+
7
+ Supporting JIT functions:
8
+ _get_finfo_val: dtype min/max constants for masking
9
+ _compare_and_swap: bitonic compare-and-swap with index tracking
10
+ _bitonic_merge: single merge stage of bitonic sort
11
+ argsort: full bitonic argsort
12
+ """
13
+
14
+ import triton
15
+ import triton.language as tl
16
+ import triton.language.core as core
17
+
18
+ try:
19
+ from triton.language.standard import _log2, zeros_like
20
+ except ImportError:
21
+ pass
22
+
23
+
24
+ # --- Dtype limit constants (inline, no torch) ---
25
+
26
+ _MIN_FLOAT32_VAL = tl.constexpr(-3.4028235e+38)
27
+ _MAX_FLOAT32_VAL = tl.constexpr(3.4028235e+38)
28
+ _MIN_FLOAT16_VAL = tl.constexpr(-65504.0)
29
+ _MAX_FLOAT16_VAL = tl.constexpr(65504.0)
30
+ _MIN_BFLOAT16_VAL = tl.constexpr(-3.3895314e+38)
31
+ _MAX_BFLOAT16_VAL = tl.constexpr(3.3895314e+38)
32
+ _MIN_INT32_VAL = tl.constexpr(-2147483648)
33
+ _MAX_INT32_VAL = tl.constexpr(2147483647)
34
+
35
+
36
+ @triton.jit
37
+ def _get_finfo_val(
38
+ dtype,
39
+ return_max,
40
+ ):
41
+ """Saturating limit for `dtype`, as a Python float literal.
42
+
43
+ The limit MUST match the dtype of the buffer being loaded, not the
44
+ dtype the kernel computes in. `tl.load(..., other=v)` materialises
45
+ `v` in the POINTER's element type, so an fp32 limit handed to an
46
+ fp16 buffer overflows and lands as -inf. That is not a benign
47
+ sentinel here: `_compare_and_swap` permutes with a masked sum, and
48
+ `0 * -inf` is NaN, which loses every comparison and collapses the
49
+ sort network (measured: pad slots surfacing into the top-k with
50
+ their INT32_MIN sentinel index, whenever chunk_num*k was not a
51
+ power of two).
52
+
53
+ Literals rather than `tl.constexpr(torch.finfo(...).max)` — the
54
+ upstream FlagGems form — because R33 forbids torch here and the
55
+ constexpr wrapper mixes fp64 into the comparison. Dropping the
56
+ dtype branching along with the torch call was the regression.
57
+ """
58
+ if dtype is tl.float16:
59
+ if return_max:
60
+ return 65504.0
61
+ else:
62
+ return -65504.0
63
+ elif dtype is tl.bfloat16:
64
+ if return_max:
65
+ return 3.3895314e+38
66
+ else:
67
+ return -3.3895314e+38
68
+ else:
69
+ if return_max:
70
+ return 3.4028235e+38
71
+ else:
72
+ return -3.4028235e+38
73
+
74
+
75
+ # --- Stage 1: per-chunk top-k ---
76
+
77
+
78
+ @triton.jit
79
+ def topk_stage1_kernel(
80
+ y_ptr,
81
+ index_ptr,
82
+ x_ptr,
83
+ k,
84
+ N: tl.constexpr,
85
+ CHUNK_SIZE: tl.constexpr,
86
+ DESCENDING: tl.constexpr,
87
+ ):
88
+ cur_batch = tl.program_id(0).to(tl.int64)
89
+ cur_chunk_idx = tl.program_id(1).to(tl.int64)
90
+ chunk_num = tl.num_programs(1)
91
+
92
+ y_ptr += cur_batch * chunk_num * k + cur_chunk_idx * k
93
+ index_ptr += cur_batch * chunk_num * k + cur_chunk_idx * k
94
+
95
+ chunk_offset = cur_chunk_idx * CHUNK_SIZE
96
+ x_ptr += cur_batch * N + chunk_offset
97
+
98
+ cols = tl.arange(0, CHUNK_SIZE)
99
+ mask = (chunk_offset + cols) < N
100
+
101
+ mask_val = _get_finfo_val(x_ptr.dtype.element_ty, return_max=not DESCENDING)
102
+ x_val = tl.load(x_ptr + cols, mask=mask, other=mask_val).to(tl.float32)
103
+ for k_idx in range(k):
104
+ if DESCENDING:
105
+ chunk_select_val = tl.max(x_val)
106
+ chunk_select_idx = tl.argmax(x_val, axis=0)
107
+ else:
108
+ chunk_select_val = tl.min(x_val)
109
+ chunk_select_idx = tl.argmin(x_val, axis=0)
110
+
111
+ tl.store(y_ptr + k_idx, chunk_select_val)
112
+ tl.store(index_ptr + k_idx, chunk_select_idx + chunk_offset)
113
+
114
+ if DESCENDING:
115
+ mask_v = tl.full(x_val.shape, float('-inf'), dtype=tl.float32)
116
+ else:
117
+ mask_v = tl.full(x_val.shape, float('inf'), dtype=tl.float32)
118
+ x_val = tl.where(cols == chunk_select_idx, mask_v, x_val)
119
+
120
+
121
+ # --- Bitonic sort helpers ---
122
+
123
+
124
+ @triton.jit
125
+ def _compare_and_swap(x, ids, flip, i: core.constexpr, n_dims: core.constexpr):
126
+ n_outer: core.constexpr = x.numel >> n_dims
127
+ shape: core.constexpr = [n_outer * 2**i, 2, 2 ** (n_dims - i - 1)]
128
+
129
+ y = core.reshape(x, shape)
130
+ y_idx = core.reshape(ids, shape)
131
+
132
+ # Slice left/right with stride 2**(n_dims - i - 1).
133
+ #
134
+ # SELECT the unwanted lane away rather than multiplying it by zero.
135
+ # The upstream form is `tl.sum(y * (1 - mask), 1)`, which is exact
136
+ # for finite values but yields NaN on any +-inf, because 0 * inf is
137
+ # NaN. Selecting keeps the identity (-inf + 0 == -inf), so a
138
+ # non-finite entry sorts to its extreme instead of poisoning the
139
+ # comparison network. Callers do put -inf in the data: the sampler
140
+ # masks rejected logits with -inf and then takes a top-k over the
141
+ # result.
142
+ mask = core.arange(0, 2)[None, :, None]
143
+ left = core.broadcast_to(
144
+ tl.sum(core.where(mask == 0, y, zeros_like(y)), 1)[:, None, :], shape
145
+ ).to(x.dtype)
146
+ right = core.broadcast_to(
147
+ tl.sum(core.where(mask == 1, y, zeros_like(y)), 1)[:, None, :], shape
148
+ ).to(x.dtype)
149
+ left = core.reshape(left, x.shape)
150
+ right = core.reshape(right, x.shape)
151
+
152
+ left_idx = core.broadcast_to(
153
+ tl.sum(core.where(mask == 0, y_idx, zeros_like(y_idx)), 1)[:, None, :], shape
154
+ ).to(ids.dtype)
155
+ right_idx = core.broadcast_to(
156
+ tl.sum(core.where(mask == 1, y_idx, zeros_like(y_idx)), 1)[:, None, :], shape
157
+ ).to(ids.dtype)
158
+ left_idx = core.reshape(left_idx, ids.shape)
159
+ right_idx = core.reshape(right_idx, ids.shape)
160
+
161
+ # Compare-and-swap
162
+ if core.constexpr(x.dtype.primitive_bitwidth) == 8:
163
+ idtype = core.int8
164
+ elif core.constexpr(x.dtype.primitive_bitwidth) == 16:
165
+ idtype = core.int16
166
+ elif core.constexpr(x.dtype.primitive_bitwidth) == 32:
167
+ idtype = core.int32
168
+ elif core.constexpr(x.dtype.primitive_bitwidth) == 64:
169
+ idtype = core.int64
170
+ else:
171
+ raise ValueError("Unsupported dtype")
172
+
173
+ ileft = left.to(idtype, bitcast=True)
174
+ iright = right.to(idtype, bitcast=True)
175
+ ix = x.to(idtype, bitcast=True)
176
+
177
+ cond = (left > right) ^ flip
178
+ ret = ix ^ core.where(cond, ileft ^ iright, zeros_like(ix))
179
+
180
+ if core.constexpr(ids.dtype.primitive_bitwidth) == 8:
181
+ idx_dtype = core.int8
182
+ elif core.constexpr(ids.dtype.primitive_bitwidth) == 16:
183
+ idx_dtype = core.int16
184
+ elif core.constexpr(ids.dtype.primitive_bitwidth) == 32:
185
+ idx_dtype = core.int32
186
+ elif core.constexpr(ids.dtype.primitive_bitwidth) == 64:
187
+ idx_dtype = core.int64
188
+ else:
189
+ raise ValueError("Unsupported dtype")
190
+
191
+ ileft_idx = left_idx.to(idx_dtype, bitcast=True)
192
+ iright_idx = right_idx.to(idx_dtype, bitcast=True)
193
+ ix_idx = ids.to(idx_dtype, bitcast=True)
194
+ ret_idx = ix_idx ^ core.where(cond, ileft_idx ^ iright_idx, zeros_like(ix_idx))
195
+
196
+ return ret.to(x.dtype, bitcast=True), ret_idx.to(ids.dtype, bitcast=True)
197
+
198
+
199
+ @triton.jit
200
+ def _bitonic_merge(
201
+ x, ids, stage: core.constexpr, order: core.constexpr, n_dims: core.constexpr
202
+ ):
203
+ """Bitonic merge stage.
204
+
205
+ order: 0=ascending, 1=descending, 2=alternating
206
+ """
207
+ n_outer: core.constexpr = x.numel >> n_dims
208
+ core.static_assert(stage <= n_dims)
209
+
210
+ if order == 2:
211
+ shape: core.constexpr = [n_outer * 2 ** (n_dims - 1 - stage), 2, 2**stage]
212
+ flip = core.reshape(
213
+ core.broadcast_to(core.arange(0, 2)[None, :, None], shape), x.shape
214
+ )
215
+ else:
216
+ flip = order
217
+ for i in core.static_range(stage):
218
+ x, ids = _compare_and_swap(x, ids, flip, i + (n_dims - stage), n_dims)
219
+ return x, ids
220
+
221
+
222
+ @triton.jit
223
+ def argsort(x, ids, dim: tl.constexpr, descending: core.constexpr):
224
+ _dim: core.constexpr = dim
225
+ n_dims: core.constexpr = _log2(x.shape[_dim])
226
+ for i in core.static_range(1, n_dims + 1):
227
+ x, ids = _bitonic_merge(x, ids, i, 2 if i < n_dims else descending, n_dims)
228
+ return x, ids
229
+
230
+
231
+ # --- Stage 2: merge chunks via bitonic sort ---
232
+
233
+
234
+ @triton.jit
235
+ def topk_stage2_kernel(
236
+ y_ptr,
237
+ index_ptr,
238
+ chunk_x,
239
+ chunk_index,
240
+ sort_dim: tl.constexpr,
241
+ k: tl.constexpr,
242
+ N: tl.constexpr,
243
+ BLOCK_SIZE: tl.constexpr,
244
+ DESCENDING: tl.constexpr,
245
+ ):
246
+ cur_batch = tl.program_id(0).to(tl.int64)
247
+ chunk_x += cur_batch * N
248
+ chunk_index += cur_batch * N
249
+ y_ptr += cur_batch * k
250
+ index_ptr += cur_batch * k
251
+
252
+ cols = tl.arange(0, BLOCK_SIZE)
253
+ mask = cols < N
254
+
255
+ mask_val = _get_finfo_val(chunk_x.dtype.element_ty, return_max=not DESCENDING)
256
+ mask_index_val = _MIN_INT32_VAL if DESCENDING else _MAX_INT32_VAL
257
+
258
+ chunk_x_val = tl.load(chunk_x + cols, mask=mask, other=mask_val).to(tl.float32)
259
+ chunk_index_val = tl.load(chunk_index + cols, mask=mask, other=mask_index_val).to(
260
+ tl.int32
261
+ )
262
+
263
+ sorted_chunk_x, sorted_chunk_index = argsort(
264
+ chunk_x_val, chunk_index_val, 0, descending=DESCENDING
265
+ )
266
+ tl.store(y_ptr + cols, sorted_chunk_x, mask=cols < k)
267
+ tl.store(index_ptr + cols, sorted_chunk_index, mask=cols < k)