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,145 @@
1
+ """aten::grid_sampler_2d — pure Triton 2D grid sampler.
2
+
3
+ Clean-room implementation from the PyTorch semantics (ATen GridSampler.h /
4
+ torch._decomp grid sampler decomposition — formulas only, no code vendored):
5
+
6
+ - Coordinate unnormalization:
7
+ align_corners=True: ix = (x + 1)/2 * (W - 1)
8
+ align_corners=False: ix = ((x + 1)*W - 1)/2
9
+ - Bicubic: cubic-convolution weights with A = -0.75 over a 4x4 tap window;
10
+ the fractional offset is computed on the RAW unnormalized coordinate
11
+ (floor before padding), padding is applied PER INTEGER TAP (this staging
12
+ differs from bilinear and changes border behavior).
13
+ - Bilinear: padding (clamp) is applied to the FLOAT coordinate before
14
+ flooring; 4 corners accumulated with per-corner in-bounds masks.
15
+ - zeros padding: out-of-bounds taps contribute 0 (masked loads);
16
+ border padding: tap indices clamp into [0, size-1].
17
+ - All coordinate/weight math and accumulation in float32 (ATen opmath),
18
+ output cast to the input dtype by the wrapper.
19
+
20
+ Modes follow the ATen integer encoding: interpolation 0=bilinear,
21
+ 2=bicubic (1=nearest unsupported → wrapper raises); padding 0=zeros,
22
+ 1=border (2=reflection unsupported → wrapper raises).
23
+
24
+ Parallelization: one program per (batch, output-location, channel-block);
25
+ coordinates and weights are computed once per location, taps are gathered
26
+ as BLOCK_C-wide strided loads (mirrors the CUDA kernel's channel loop).
27
+ """
28
+
29
+ import triton
30
+ import triton.language as tl
31
+
32
+
33
+ @triton.jit
34
+ def _cc1(x, A: tl.constexpr):
35
+ # |x| <= 1 branch of the cubic convolution kernel
36
+ return ((A + 2.0) * x - (A + 3.0)) * x * x + 1.0
37
+
38
+
39
+ @triton.jit
40
+ def _cc2(x, A: tl.constexpr):
41
+ # 1 < |x| < 2 branch of the cubic convolution kernel
42
+ return ((A * x - 5.0 * A) * x + 8.0 * A) * x - 4.0 * A
43
+
44
+
45
+ @triton.jit
46
+ def grid_sampler_2d_kernel(
47
+ inp_ptr, grid_ptr, out_ptr,
48
+ C, H, W, OUT_SPATIAL,
49
+ stride_in, stride_ic, stride_ih, stride_iw,
50
+ stride_gn, stride_gs, stride_gc,
51
+ stride_on, stride_oc, stride_os,
52
+ MODE: tl.constexpr, # 0 = bilinear, 2 = bicubic
53
+ PAD_BORDER: tl.constexpr, # 0 = zeros, 1 = border
54
+ ALIGN: tl.constexpr, # 0 / 1
55
+ BLOCK_C: tl.constexpr,
56
+ ):
57
+ pid_s = tl.program_id(0).to(tl.int64) # output spatial location (oH*oW flattened)
58
+ pid_c = tl.program_id(1).to(tl.int64) # channel block
59
+ pid_n = tl.program_id(2).to(tl.int64) # batch
60
+
61
+ # A program beyond the output extent reads and writes nothing (masked,
62
+ # no early exit — unstructured control flow has no lowering on every backend).
63
+ s_valid = pid_s < OUT_SPATIAL
64
+
65
+ c_offs = pid_c * BLOCK_C + tl.arange(0, BLOCK_C)
66
+ c_mask = (c_offs < C) & s_valid
67
+
68
+ # grid holds (x, y) pairs, channels-last
69
+ gx = tl.load(grid_ptr + pid_n * stride_gn + pid_s * stride_gs
70
+ + 0 * stride_gc, mask=s_valid, other=0.0).to(tl.float32)
71
+ gy = tl.load(grid_ptr + pid_n * stride_gn + pid_s * stride_gs
72
+ + 1 * stride_gc, mask=s_valid, other=0.0).to(tl.float32)
73
+
74
+ Wf = W.to(tl.float32)
75
+ Hf = H.to(tl.float32)
76
+ if ALIGN:
77
+ ix = (gx + 1.0) * 0.5 * (Wf - 1.0)
78
+ iy = (gy + 1.0) * 0.5 * (Hf - 1.0)
79
+ else:
80
+ ix = ((gx + 1.0) * Wf - 1.0) * 0.5
81
+ iy = ((gy + 1.0) * Hf - 1.0) * 0.5
82
+
83
+ inp_base = inp_ptr + pid_n * stride_in + c_offs * stride_ic
84
+ acc = tl.zeros([BLOCK_C], dtype=tl.float32)
85
+
86
+ if MODE == 0:
87
+ # ── bilinear: pad the FLOAT coordinate first, then corners ──
88
+ if PAD_BORDER:
89
+ ix = tl.minimum(tl.maximum(ix, 0.0), Wf - 1.0)
90
+ iy = tl.minimum(tl.maximum(iy, 0.0), Hf - 1.0)
91
+ x0 = tl.floor(ix)
92
+ y0 = tl.floor(iy)
93
+ tx = ix - x0
94
+ ty = iy - y0
95
+ for j in tl.static_range(2):
96
+ for i in tl.static_range(2):
97
+ xi = x0 + i
98
+ yi = y0 + j
99
+ wgt = (tl.where(i == 0, 1.0 - tx, tx)
100
+ * tl.where(j == 0, 1.0 - ty, ty))
101
+ inb = (xi >= 0) & (xi < Wf) & (yi >= 0) & (yi < Hf)
102
+ xs = tl.minimum(tl.maximum(xi, 0.0), Wf - 1.0).to(tl.int32)
103
+ ys = tl.minimum(tl.maximum(yi, 0.0), Hf - 1.0).to(tl.int32)
104
+ val = tl.load(inp_base + ys * stride_ih + xs * stride_iw,
105
+ mask=c_mask & inb, other=0.0).to(tl.float32)
106
+ acc += tl.where(inb, wgt, 0.0) * val
107
+ else:
108
+ # ── bicubic: floor on the RAW coordinate, pad per integer tap ──
109
+ A: tl.constexpr = -0.75
110
+ x0 = tl.floor(ix)
111
+ y0 = tl.floor(iy)
112
+ tx = ix - x0
113
+ ty = iy - y0
114
+ wx0 = _cc2(tx + 1.0, A)
115
+ wx1 = _cc1(tx, A)
116
+ wx2 = _cc1(1.0 - tx, A)
117
+ wx3 = _cc2(2.0 - tx, A)
118
+ wy0 = _cc2(ty + 1.0, A)
119
+ wy1 = _cc1(ty, A)
120
+ wy2 = _cc1(1.0 - ty, A)
121
+ wy3 = _cc2(2.0 - ty, A)
122
+ for j in tl.static_range(4):
123
+ wy = tl.where(j == 0, wy0,
124
+ tl.where(j == 1, wy1,
125
+ tl.where(j == 2, wy2, wy3)))
126
+ yi = y0 + (j - 1)
127
+ for i in tl.static_range(4):
128
+ wx = tl.where(i == 0, wx0,
129
+ tl.where(i == 1, wx1,
130
+ tl.where(i == 2, wx2, wx3)))
131
+ xi = x0 + (i - 1)
132
+ inb = (xi >= 0) & (xi < Wf) & (yi >= 0) & (yi < Hf)
133
+ xs = tl.minimum(tl.maximum(xi, 0.0), Wf - 1.0).to(tl.int32)
134
+ ys = tl.minimum(tl.maximum(yi, 0.0), Hf - 1.0).to(tl.int32)
135
+ if PAD_BORDER:
136
+ val = tl.load(inp_base + ys * stride_ih + xs * stride_iw,
137
+ mask=c_mask, other=0.0).to(tl.float32)
138
+ acc += wy * wx * val
139
+ else:
140
+ val = tl.load(inp_base + ys * stride_ih + xs * stride_iw,
141
+ mask=c_mask & inb, other=0.0).to(tl.float32)
142
+ acc += tl.where(inb, wy * wx, 0.0) * val
143
+
144
+ out = out_ptr + pid_n * stride_on + c_offs * stride_oc + pid_s * stride_os
145
+ tl.store(out, acc, mask=c_mask)
@@ -0,0 +1,67 @@
1
+ """The two passes of an exact tiled group norm on the Triton engine (`core/strategies/tiled_norm.py`'s NBXTensor twin):
2
+ the statistics of each (sample, group) over the WHOLE input, then a slice normalised with them.
3
+
4
+ `group_norm_stats_kernel` is the first pass of `groupnorm.group_norm_forward_kernel` alone — the same fp32 sum and
5
+ sum of squares, var = ss/n - mean^2 — without writing the normalised tensor (a stretch sliced along the axis holds its
6
+ input whole, never a whole output beside it). `group_norm_apply_kernel` is its second pass on a slice, reading the
7
+ given statistics. One program per (sample, group); the walk is a fixed chunk (no autotune key: nothing to certify), so
8
+ the summation order is fixed.
9
+
10
+ Pure @triton.jit (R33).
11
+ """
12
+
13
+ import triton
14
+ import triton.language as tl
15
+
16
+ #: the chunk a program walks its group by — fixed, so the statistics' summation order is
17
+ TILED_NORM_BLOCK = 4096
18
+ TILED_NORM_WARPS = 8
19
+
20
+
21
+ @triton.jit
22
+ def group_norm_stats_kernel(X, Mean, Rstd, group_size, C, HW, num_groups, eps, BLOCK_SIZE: tl.constexpr):
23
+ pid = tl.program_id(0).to(tl.int64)
24
+ batch_idx = pid // num_groups
25
+ group_idx = pid % num_groups
26
+ base = batch_idx * C * HW + group_idx * group_size * HW
27
+ hidden = group_size * HW
28
+ block = tl.arange(0, BLOCK_SIZE)
29
+ s = tl.zeros((), dtype=tl.float32)
30
+ ss = tl.zeros((), dtype=tl.float32)
31
+ for off in tl.range(0, hidden, BLOCK_SIZE):
32
+ idx = off + block
33
+ mask = idx < hidden
34
+ x = tl.load(X + base + idx, mask=mask, other=0.0).to(tl.float32)
35
+ s += tl.sum(x)
36
+ ss += tl.sum(x * x)
37
+ inv_n = 1.0 / hidden
38
+ mean = s * inv_n
39
+ var = ss * inv_n - mean * mean
40
+ tl.store(Mean + pid, mean)
41
+ tl.store(Rstd + pid, 1.0 / tl.sqrt(var + eps))
42
+
43
+
44
+ @triton.jit
45
+ def group_norm_apply_kernel(X, Y, W, B, Mean, Rstd, group_size, C, HW, num_groups,
46
+ scale_by_weight: tl.constexpr, add_bias: tl.constexpr, BLOCK_SIZE: tl.constexpr):
47
+ pid = tl.program_id(0).to(tl.int64)
48
+ batch_idx = pid // num_groups
49
+ group_idx = pid % num_groups
50
+ chan_start = group_idx * group_size
51
+ base = batch_idx * C * HW + chan_start * HW
52
+ hidden = group_size * HW
53
+ mean = tl.load(Mean + pid)
54
+ rstd = tl.load(Rstd + pid)
55
+ block = tl.arange(0, BLOCK_SIZE)
56
+ for off in tl.range(0, hidden, BLOCK_SIZE):
57
+ idx = off + block
58
+ mask = idx < hidden
59
+ x = tl.load(X + base + idx, mask=mask, other=0.0).to(tl.float32)
60
+ y = (x - mean) * rstd
61
+ if scale_by_weight or add_bias:
62
+ chan = chan_start + idx // HW
63
+ if scale_by_weight:
64
+ y = y * tl.load(W + chan, mask=mask, other=1.0).to(tl.float32)
65
+ if add_bias:
66
+ y = y + tl.load(B + chan, mask=mask, other=0.0).to(tl.float32)
67
+ tl.store(Y + base + idx, y.to(Y.dtype.element_ty), mask=mask)
@@ -0,0 +1,123 @@
1
+ """Group normalization — pure @triton.jit kernel.
2
+
3
+ Two-pass loop over hidden_size (= group_size * HW) in BLOCK_SIZE chunks,
4
+ so the kernel scales to arbitrary spatial dimensions. Replaces the
5
+ original FlagGems extraction whose single-tile design crashed with
6
+ `numel exceeds triton maximum tensor numel (1048576)` once HW reached
7
+ 1024×1024 (PixArt VAE 1024px output).
8
+
9
+ Algorithm: standard sum-of-squares variance per (batch, group) program.
10
+ For each program (one (batch, group) pair):
11
+ 1. Loop over hidden_size in BLOCK_SIZE chunks → accumulate sum and
12
+ sum-of-squares → mean = s / N, var = ss/N - mean².
13
+ 2. rstd = 1 / sqrt(var + eps).
14
+ 3. Loop a second time → normalize → multiply by per-channel weight,
15
+ add per-channel bias, store.
16
+
17
+ Forward only (inference). The wrapper guarantees BLOCK_SIZE ≤
18
+ triton.next_power_of_2(hidden_size_per_chunk_target) and ≤ 16384, well
19
+ under Triton's 2^20 numel ceiling on every architecture.
20
+ """
21
+
22
+ import triton
23
+ import triton.language as tl
24
+ from triton import next_power_of_2
25
+
26
+ from nbxtensor.ops._configs import nbx_autotune
27
+
28
+
29
+ def _group_norm_configs():
30
+ """The candidates the certifier proves (chantier 1): the chunk a group is walked by x warps. The one the kernel ran
31
+ before certification (the group's padded extent capped at 16 384, 4 warps) is among them."""
32
+ return [triton.Config({'BLOCK_SIZE': block}, num_warps=warps)
33
+ for block in (1024, 2048, 4096, 8192, 16384) for warps in (2, 4, 8, 16)]
34
+
35
+
36
+ def _prune_group_norm(configs, named_args, **kwargs):
37
+ """A chunk no wider than the backend's reduce-tile cap (`nbx_tensor.reduce_tile_max`) nor than the group's padded
38
+ extent (a wider chunk only masks)."""
39
+ from nbxtensor.backend.capabilities import reduce_tile_max
40
+ hidden = int(named_args['group_size']) * int(named_args['HW'])
41
+ cap = min(reduce_tile_max(), next_power_of_2(max(1, hidden)))
42
+ keep = [c for c in configs if c.kwargs['BLOCK_SIZE'] <= cap]
43
+ return keep or sorted(configs, key=lambda c: c.kwargs['BLOCK_SIZE'])[:1]
44
+
45
+
46
+ @nbx_autotune(configs=_group_norm_configs(), key=['PROG_BUCKET', 'HIDDEN_BUCKET', 'scale_by_weight', 'add_bias'],
47
+ prune_configs_by={'early_config_prune': _prune_group_norm}, cache_results=True)
48
+ @triton.jit
49
+ def group_norm_forward_kernel(
50
+ X, # input pointer, shape (N, C, HW)
51
+ Y, # output pointer, shape (N, C, HW)
52
+ W, # weight pointer, shape (C,)
53
+ B, # bias pointer, shape (C,)
54
+ Mean, # mean output, shape (N * num_groups,)
55
+ Rstd, # rstd output, shape (N * num_groups,)
56
+ group_size, # channels per group
57
+ C, # total channels
58
+ HW, # spatial size = H * W (or H * W * D)
59
+ num_groups, # total groups
60
+ eps,
61
+ PROG_BUCKET, # the programs' (batch x groups) bucket: the key
62
+ HIDDEN_BUCKET, # the group's padded extent: the key
63
+ scale_by_weight: tl.constexpr,
64
+ add_bias: tl.constexpr,
65
+ BLOCK_SIZE: tl.constexpr, # chunk size along hidden_size: the certified configuration
66
+ ):
67
+ """One program handles one (batch, group) pair. Spatial dimension is
68
+ walked in BLOCK_SIZE chunks so the per-program tile stays bounded
69
+ regardless of input HW."""
70
+ pid = tl.program_id(0).to(tl.int64)
71
+ batch_idx = pid // num_groups
72
+ group_idx = pid % num_groups
73
+
74
+ # Channel range of this group (each channel has HW elements contiguous
75
+ # in memory; the group's elements are group_size*HW elements starting
76
+ # at base_offset).
77
+ chan_start = group_idx * group_size
78
+ base_offset = batch_idx * C * HW + chan_start * HW
79
+ hidden = group_size * HW
80
+
81
+ block_range = tl.arange(0, BLOCK_SIZE)
82
+
83
+ # ── Pass 1: sum and sum-of-squares over the full hidden dim ─────────
84
+ s = tl.zeros((), dtype=tl.float32)
85
+ ss = tl.zeros((), dtype=tl.float32)
86
+ for off in tl.range(0, hidden, BLOCK_SIZE):
87
+ idx = off + block_range
88
+ mask = idx < hidden
89
+ x = tl.load(X + base_offset + idx, mask=mask, other=0.0).to(tl.float32)
90
+ s += tl.sum(x)
91
+ ss += tl.sum(x * x)
92
+
93
+ inv_n = 1.0 / hidden
94
+ mean = s * inv_n
95
+ var = ss * inv_n - mean * mean
96
+ rstd = 1.0 / tl.sqrt(var + eps)
97
+
98
+ tl.store(Mean + pid, mean)
99
+ tl.store(Rstd + pid, rstd)
100
+
101
+ # ── Pass 2: normalize → optional affine → store ──────────────────────
102
+ # Each chunk falls inside a single channel because chunks walk the
103
+ # contiguous (channel, HW) layout. We compute the channel for each
104
+ # element of the block to fetch the right (W, B) entries.
105
+ for off in tl.range(0, hidden, BLOCK_SIZE):
106
+ idx = off + block_range
107
+ mask = idx < hidden
108
+ x = tl.load(X + base_offset + idx, mask=mask, other=0.0).to(tl.float32)
109
+ x_hat = (x - mean) * rstd
110
+
111
+ if scale_by_weight or add_bias:
112
+ # Channel index within the GROUP, then add the group's chan_start.
113
+ chan_in_group = idx // HW
114
+ chan_global = chan_start + chan_in_group
115
+ chan_mask = mask & (chan_in_group < group_size)
116
+ if scale_by_weight:
117
+ w = tl.load(W + chan_global, mask=chan_mask, other=1.0).to(tl.float32)
118
+ x_hat = x_hat * w
119
+ if add_bias:
120
+ b = tl.load(B + chan_global, mask=chan_mask, other=0.0).to(tl.float32)
121
+ x_hat = x_hat + b
122
+
123
+ tl.store(Y + base_offset + idx, x_hat, mask=mask)
nbxtensor/ops/gt.py ADDED
@@ -0,0 +1,32 @@
1
+ """Greater than — pure @triton.jit kernel."""
2
+
3
+ import triton
4
+ import triton.language as tl
5
+
6
+ @triton.jit
7
+ def gt_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 gt_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)
@@ -0,0 +1,18 @@
1
+ """Hard Sigmoid — pure @triton.jit kernel."""
2
+
3
+ import triton
4
+ import triton.language as tl
5
+
6
+ @triton.jit
7
+ def hardsigmoid_forward_kernel(
8
+ input_ptr, output_ptr,
9
+ n_elements,
10
+ BLOCK_SIZE: tl.constexpr,
11
+ ):
12
+ pid = tl.program_id(0).to(tl.int64)
13
+ offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
14
+ mask = offset < n_elements
15
+
16
+ x = tl.load(input_ptr + offset, mask=mask)
17
+ out = tl.maximum(0, tl.minimum(1, x / 6.0 + 0.5))
18
+ tl.store(output_ptr + offset, out, mask=mask)
@@ -0,0 +1,19 @@
1
+ """Hard Swish — pure @triton.jit kernel."""
2
+
3
+ import triton
4
+ import triton.language as tl
5
+
6
+ @triton.jit
7
+ def hardswish_forward_kernel(
8
+ input_ptr, output_ptr,
9
+ n_elements,
10
+ BLOCK_SIZE: tl.constexpr,
11
+ ):
12
+ pid = tl.program_id(0).to(tl.int64)
13
+ offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
14
+ mask = offset < n_elements
15
+
16
+ x = tl.load(input_ptr + offset, mask=mask)
17
+ # hardswish = x * relu6(x + 3) / 6
18
+ out = x * tl.minimum(tl.maximum(0, x + 3.0), 6.0) / 6.0
19
+ tl.store(output_ptr + offset, out, mask=mask)
@@ -0,0 +1,57 @@
1
+ """aten::im2col (nn.Unfold) — pure @triton.jit kernel.
2
+
3
+ Extracts sliding local blocks from a batched NCHW tensor into columns:
4
+ input [N, C, IH, IW]
5
+ output [N, C*KH*KW, L] with L = OH*OW
6
+
7
+ out[n, c*KH*KW + kh*KW + kw, oh*OW + ow]
8
+ = input[n, c, oh*stride_h - pad_h + kh*dil_h,
9
+ ow*stride_w - pad_w + kw*dil_w] (0 outside the image)
10
+
11
+ This is the operation behind HAT's OCAB overlapping-window cross-attention
12
+ (P-TRITON-IM2COL-KERNEL). Pure Triton (R33): NBXTensor in/out, tl.load with
13
+ boundary masking for the padding halo (no F.pad).
14
+ """
15
+
16
+ import triton
17
+ import triton.language as tl
18
+
19
+
20
+ @triton.jit
21
+ def im2col_kernel(
22
+ inp_ptr, out_ptr,
23
+ C, IH, IW,
24
+ OW, L,
25
+ KH: tl.constexpr, KW: tl.constexpr,
26
+ stride_h: tl.constexpr, stride_w: tl.constexpr,
27
+ pad_h: tl.constexpr, pad_w: tl.constexpr,
28
+ dil_h: tl.constexpr, dil_w: tl.constexpr,
29
+ in_sn, in_sc, in_sh, in_sw,
30
+ out_sn, out_sc, out_sl,
31
+ BLOCK_L: tl.constexpr,
32
+ ):
33
+ # One program per (n, output-channel) = (n, c*KH*KW + kh*KW + kw); it
34
+ # writes one full output row of L block-positions (looped in BLOCK_L tiles).
35
+ pid = tl.program_id(0).to(tl.int64)
36
+ CKK = C * KH * KW
37
+ n = pid // CKK
38
+ oc = pid % CKK
39
+ c = oc // (KH * KW)
40
+ rem = oc % (KH * KW)
41
+ kh = rem // KW
42
+ kw = rem % KW
43
+
44
+ in_base = n * in_sn + c * in_sc
45
+ out_base = n * out_sn + oc * out_sc
46
+
47
+ for l0 in range(0, L, BLOCK_L):
48
+ l = l0 + tl.arange(0, BLOCK_L)
49
+ mask_l = l < L
50
+ oh = l // OW
51
+ ow = l % OW
52
+ ih = oh * stride_h - pad_h + kh * dil_h
53
+ iw = ow * stride_w - pad_w + kw * dil_w
54
+ in_bounds = mask_l & (ih >= 0) & (ih < IH) & (iw >= 0) & (iw < IW)
55
+ vals = tl.load(inp_ptr + in_base + ih * in_sh + iw * in_sw,
56
+ mask=in_bounds, other=0.0)
57
+ tl.store(out_ptr + out_base + l * out_sl, vals, mask=mask_l)
@@ -0,0 +1,95 @@
1
+ """Index add along dim — deterministic, pure @triton.jit.
2
+
3
+ out[..., index[i], ...] += alpha * src[..., i, ...]
4
+
5
+ The previous implementation used `tl.atomic_add`, whose inter-block
6
+ float accumulation order is non-deterministic. When several source
7
+ positions map to the same destination (MoE expert aggregation: many
8
+ routed rows -> one token), repeated runs produced different fp32
9
+ roundings -> a different greedy argmax on Qwen3-30B::triton's unfused
10
+ MoE path (workstream P-TRITON-MOE-DETERMINISM sub-workstream 2).
11
+
12
+ State of the art consulted (R16, 2026-05-19):
13
+ - Triton #7402: tl.atomic_add return value is incorrect across
14
+ threads (only thread 0 correct) — open upstream bug.
15
+ - Triton #4717: tl.atomic_add is slow (layout conversions).
16
+ - flagos-ai/FlagGems ops/index_add.py still uses
17
+ tl.atomic_add(sem='relaxed') (pins triton==3.3.0, no bit-exact
18
+ test) — no deterministic upstream to vendor.
19
+ - PyTorch index_add_/scatter_add_ are non-deterministic on CUDA via
20
+ atomicAdd; the deterministic path is sort + sequential segmented
21
+ reduce.
22
+
23
+ Strategy — output-owner gather (deterministic by construction):
24
+ invert the scatter. Each output cell is owned by exactly ONE program
25
+ that sums its contributing source rows in a fixed ascending source-
26
+ position order as a SEQUENTIAL fp32 fold. No atomics, no inter-block
27
+ races, no sort dependency. The ascending-j sequential fold is exactly
28
+ the order torch's deterministic CUDA index_add_ uses (verified
29
+ bit-exact fp32 for bucket sizes up to 4096). A pairwise/tree reduce
30
+ (`tl.sum`) would round differently and break bit-exactness, so the
31
+ j accumulation is a scalar loop; the feature (inner) dim is vectorised.
32
+
33
+ Out-of-range source indices never equal a valid destination d in
34
+ [0, inp_shape_dim) so they are naturally dropped (mirrors the old
35
+ masked-store semantics — no assert/crash). Model-agnostic, no autotune.
36
+ """
37
+
38
+ import triton
39
+ import triton.language as tl
40
+
41
+
42
+ @triton.jit
43
+ def index_add_gather_kernel(
44
+ x, src, index, out,
45
+ dim_size, inp_shape_dim, inner_size, alpha,
46
+ src_stride_outer, src_stride_dim, src_stride_inner,
47
+ INNER_BLOCK: tl.constexpr,
48
+ ):
49
+ """One program == one (outer, dest, inner-tile) output cell.
50
+
51
+ out[o, d, i] = x[o, d, i]
52
+ + alpha * sum_{j : index[j] == d} src[o, j, i]
53
+ with the sum taken in ascending-j order as a sequential fp32 fold
54
+ (bit-exact vs torch deterministic index_add_).
55
+ """
56
+ n_inner_tiles = (inner_size + INNER_BLOCK - 1) // INNER_BLOCK
57
+ pid = tl.program_id(0).to(tl.int64)
58
+ tile = pid % n_inner_tiles
59
+ tmp = pid // n_inner_tiles
60
+ d = tmp % inp_shape_dim
61
+ o = tmp // inp_shape_dim
62
+
63
+ inner = tile * INNER_BLOCK + tl.arange(0, INNER_BLOCK)
64
+ inner_mask = inner < inner_size
65
+
66
+ out_base = o * (inp_shape_dim * inner_size) + d * inner_size + inner
67
+ base = tl.load(x + out_base, mask=inner_mask, other=0.0).to(tl.float32)
68
+
69
+ # torch's deterministic CUDA index_add_ computes the per-destination
70
+ # segment sum SEPARATELY (0-initialised, ascending-j sequential
71
+ # fold) and adds it to `self` ONCE at the end — NOT folding into
72
+ # self. Verified by discriminating probe (base=1, src=[1e8,-1e8]:
73
+ # torch -> 1.0 == base + Σ, not 0.0 == fold-into-base).
74
+ seg = tl.zeros([INNER_BLOCK], dtype=tl.float32)
75
+ src_o = o * src_stride_outer + inner * src_stride_inner
76
+ # Scan source positions in ascending j (deterministic order).
77
+ # Scalar loop on purpose: a vectorised tree reduce would round
78
+ # differently and break bit-exactness vs torch.
79
+ for j in range(0, dim_size):
80
+ dest = tl.load(index + j).to(tl.int64)
81
+ if dest == d:
82
+ sv = tl.load(src + src_o + j * src_stride_dim,
83
+ mask=inner_mask, other=0.0).to(tl.float32)
84
+ seg += (alpha * sv).to(tl.float32)
85
+ # NOTE alpha != 1: a residual 1-ULP fp32 gap vs torch
86
+ # remains (Triton codegen of the scaled term — bitcast /
87
+ # fma / reassoc barriers were tried, all no-effect). It is
88
+ # NOT an ordering bug: numpy fp32 sequential fold == torch
89
+ # bit-exact, and this kernel reproduces that for alpha == 1
90
+ # bit-exact. alpha != 1 never occurs on a transformer path
91
+ # (Qwen3-30B-A3B graph: 6144/6144 index_add use alpha=1).
92
+
93
+ # Triton implicitly casts the fp32 result to out's element type on
94
+ # store (same convention as the other elementwise kernels).
95
+ tl.store(out + out_base, base + seg, mask=inner_mask)
@@ -0,0 +1,92 @@
1
+ """index_put — pure @triton.jit kernel.
2
+
3
+ Scatter-write `values` into `out` at positions given by a single 1-D
4
+ advanced index tensor on the leading dim (the form decomposed-ATen
5
+ emits for MoE-v2 expert-output aggregation, KV-cache indexed writes,
6
+ and post-`aten::nonzero` masked scatter):
7
+
8
+ out[idx[s], t] = values[s, t] (ACCUMULATE == 0)
9
+ out[idx[s], t] += values[s, t] (ACCUMULATE == 1)
10
+
11
+ `idx` may have any shape S (flattened, numel = Sn); `t` ranges over
12
+ the flattened tail `x.shape[1:]` (numel = T). `out` is contiguous so
13
+ the leading-dim stride is exactly T. Duplicate indices follow torch
14
+ semantics: summed when accumulating, last-writer-wins (unspecified
15
+ order) otherwise.
16
+
17
+ Index decomposition logic mirrors `index_add_op.py` (FlagGems
18
+ lineage, Apache-2.0).
19
+ """
20
+
21
+ import triton
22
+ import triton.language as tl
23
+
24
+ #: The contract this kernel enforces. Triton forbids reading a module global
25
+ #: from inside a @jit body, so the kernel repeats the literal; the permanent
26
+ #: test pins the two equal so they cannot drift.
27
+ INDEX_PUT_OOB = "index_put: index out of range"
28
+
29
+
30
+ @triton.jit(debug=True)
31
+ def index_put_kernel(
32
+ out_ptr, idx_ptr, val_ptr,
33
+ T, N, R, fault_ptr,
34
+ FAULT_CODE: tl.constexpr,
35
+ VAL_SCALAR: tl.constexpr,
36
+ ACCUMULATE: tl.constexpr,
37
+ BLOCK_SIZE: tl.constexpr,
38
+ ):
39
+ """out[idx[off // T], off % T] {=|+=} values[off] for off < N.
40
+
41
+ `R` = rows of `out` (its leading dim). A negative index counts from
42
+ the end (torch semantics); an index still out of [0, R) after that
43
+ TRAPS via `tl.device_assert` (kept in the binary by `debug=True`)
44
+ where torch's index_put raises — the FlagGems-lineage kernel wrote
45
+ through it to memory outside the tensor, silently
46
+ (D-GATHER-SCATTER-OOB-SILENT, promoted to correctness 2026-09-02).
47
+
48
+ That assert is honoured on CUDA and ROCm and NOT on Metal, which
49
+ computes its predicate and discards it (measured 2026-09-10). Here
50
+ that was not a wrong value, it was the WRITE ITSELF: the store's mask
51
+ was `off < N` alone, so `dst = row * T + t` with an out-of-range row
52
+ wrote outside the tensor — 8 floats past the end of a 24-float
53
+ tensor in the eight-float reproducer. Two things close it. The
54
+ address is made legal before it is used, so nothing outside the
55
+ tensor is touched in the interval before the refusal arrives (on
56
+ CUDA the assert halts the thread first, so it changes nothing
57
+ there); and `FAULT_CODE`, a compile-time constant, carries the
58
+ refusal itself through the fault word, which `check_device_faults`
59
+ raises on at the next host observation. The wrapper passes 0 where
60
+ the assert is honoured, and then not one instruction of it is
61
+ emitted.
62
+ """
63
+ pid = tl.program_id(0).to(tl.int64)
64
+ off = 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)
65
+ mask = off < N
66
+
67
+ s = off // T # which index entry (row in idx)
68
+ t = off % T # offset within the flattened tail
69
+ row = tl.load(idx_ptr + s, mask=mask, other=0).to(tl.int64)
70
+ row = tl.where(row < 0, row + R, row)
71
+ # Masked lanes loaded `other=0` and are in range by construction.
72
+ row_valid = (row >= 0) & (row < R)
73
+ tl.device_assert(row_valid, "index_put: index out of range")
74
+ if FAULT_CODE != 0:
75
+ # Lanes past N loaded `other=0`; they must not report (and `R == 0`
76
+ # would make even row 0 invalid, so `mask` has to gate this).
77
+ if tl.max(tl.where(mask & (row_valid == 0), 1, 0)) != 0:
78
+ tl.store(fault_ptr, FAULT_CODE)
79
+ # A legal address, so no memory outside the tensor is touched before the
80
+ # refusal is observed. Not a rescue: the run refuses either way.
81
+ dst = tl.where(row_valid, row, 0) * T + t
82
+
83
+ if VAL_SCALAR:
84
+ # All lanes read element 0 (off * 0 keeps it a vector for masking).
85
+ v = tl.load(val_ptr + off * 0, mask=mask, other=0.0)
86
+ else:
87
+ v = tl.load(val_ptr + off, mask=mask, other=0.0)
88
+
89
+ if ACCUMULATE:
90
+ tl.atomic_add(out_ptr + dst, v, mask=mask & row_valid)
91
+ else:
92
+ tl.store(out_ptr + dst, v, mask=mask & row_valid)