nbxtensor 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. nbxtensor/THIRD_PARTY_NOTICES.md +287 -0
  2. nbxtensor/__init__.py +17 -0
  3. nbxtensor/backend/__init__.py +1 -0
  4. nbxtensor/backend/capabilities.py +207 -0
  5. nbxtensor/backend/cpu/__init__.py +1 -0
  6. nbxtensor/backend/cpu/blas.py +102 -0
  7. nbxtensor/backend/cpu/pool.py +204 -0
  8. nbxtensor/backend/cpu_backend.py +162 -0
  9. nbxtensor/backend/device_facts.py +105 -0
  10. nbxtensor/backend/device_files.py +129 -0
  11. nbxtensor/backend/device_names.py +18 -0
  12. nbxtensor/backend/devices/amd/cdna.yml +228 -0
  13. nbxtensor/backend/devices/amd/cdna2.yml +228 -0
  14. nbxtensor/backend/devices/amd/cdna3.yml +227 -0
  15. nbxtensor/backend/devices/apple/apple_m1.yml +266 -0
  16. nbxtensor/backend/devices/apple/apple_m1_max.yml +266 -0
  17. nbxtensor/backend/devices/apple/apple_m1_pro.yml +266 -0
  18. nbxtensor/backend/devices/apple/apple_m1_ultra.yml +266 -0
  19. nbxtensor/backend/devices/apple/apple_m2.yml +266 -0
  20. nbxtensor/backend/devices/apple/apple_m2_max.yml +266 -0
  21. nbxtensor/backend/devices/apple/apple_m2_pro.yml +266 -0
  22. nbxtensor/backend/devices/apple/apple_m2_ultra.yml +266 -0
  23. nbxtensor/backend/devices/apple/apple_m3.yml +266 -0
  24. nbxtensor/backend/devices/apple/apple_m3_max.yml +266 -0
  25. nbxtensor/backend/devices/apple/apple_m3_pro.yml +266 -0
  26. nbxtensor/backend/devices/apple/apple_m3_ultra.yml +266 -0
  27. nbxtensor/backend/devices/apple/apple_m4.yml +266 -0
  28. nbxtensor/backend/devices/apple/apple_m4_max.yml +266 -0
  29. nbxtensor/backend/devices/apple/apple_m4_pro.yml +308 -0
  30. nbxtensor/backend/devices/apple/apple_m5.yml +269 -0
  31. nbxtensor/backend/devices/apple/apple_m5_max.yml +269 -0
  32. nbxtensor/backend/devices/apple/apple_m5_pro.yml +269 -0
  33. nbxtensor/backend/devices/apple/apple_m5_ultra.yml +269 -0
  34. nbxtensor/backend/devices/apple/apple_m6.yml +269 -0
  35. nbxtensor/backend/devices/apple/apple_silicon.yml +263 -0
  36. nbxtensor/backend/devices/nvidia/ampere.yml +214 -0
  37. nbxtensor/backend/devices/nvidia/hopper.yml +213 -0
  38. nbxtensor/backend/devices/nvidia/volta.yml +411 -0
  39. nbxtensor/backend/faults.py +151 -0
  40. nbxtensor/backend/host_memory.py +185 -0
  41. nbxtensor/backend/launcher.py +2350 -0
  42. nbxtensor/backend/launcher_contract.py +429 -0
  43. nbxtensor/backend/metal_backend.py +661 -0
  44. nbxtensor/backend/metal_device.py +1274 -0
  45. nbxtensor/backend/process_memory.py +73 -0
  46. nbxtensor/backend/replay_graph.py +322 -0
  47. nbxtensor/backend/runtime.py +409 -0
  48. nbxtensor/backend/triton_ext_driver.py +735 -0
  49. nbxtensor/dtype/__init__.py +1 -0
  50. nbxtensor/dtype/convert.py +107 -0
  51. nbxtensor/dtype/dtypes.yml +70 -0
  52. nbxtensor/dtype/itemsize.py +166 -0
  53. nbxtensor/dtype/nbx_dtype.py +72 -0
  54. nbxtensor/dtype/operands.py +158 -0
  55. nbxtensor/dtype/outputs.py +64 -0
  56. nbxtensor/dtype/policy.py +155 -0
  57. nbxtensor/dtype/policy.yml +51 -0
  58. nbxtensor/dtype/promotion.py +112 -0
  59. nbxtensor/host.py +55 -0
  60. nbxtensor/io/__init__.py +1 -0
  61. nbxtensor/io/read_rate.py +197 -0
  62. nbxtensor/io/safetensors.py +314 -0
  63. nbxtensor/memory/__init__.py +9 -0
  64. nbxtensor/memory/allocator.py +1988 -0
  65. nbxtensor/memory/arena.py +75 -0
  66. nbxtensor/memory/device_memory.py +150 -0
  67. nbxtensor/memory/device_transfer.py +154 -0
  68. nbxtensor/memory/layout.py +43 -0
  69. nbxtensor/memory/memory_pool.py +191 -0
  70. nbxtensor/memory/metal.py +96 -0
  71. nbxtensor/memory/oom_advice.py +91 -0
  72. nbxtensor/memory/pointer_tables.py +323 -0
  73. nbxtensor/memory/pool.py +507 -0
  74. nbxtensor/memory/shadow.py +43 -0
  75. nbxtensor/memory/slab.py +273 -0
  76. nbxtensor/memory/trace_replay.py +122 -0
  77. nbxtensor/memory/traces/sana-video-2b-720p.16g.2026-11-02.tsv +13610 -0
  78. nbxtensor/memory/vmm.py +118 -0
  79. nbxtensor/ops/__init__.py +1 -0
  80. nbxtensor/ops/_autotune_policy.py +90 -0
  81. nbxtensor/ops/_common.py +32 -0
  82. nbxtensor/ops/_configs.py +1041 -0
  83. nbxtensor/ops/_cost.py +231 -0
  84. nbxtensor/ops/abs.py +17 -0
  85. nbxtensor/ops/adaptive_avg_pool2d.py +161 -0
  86. nbxtensor/ops/add.py +96 -0
  87. nbxtensor/ops/addcdiv.py +29 -0
  88. nbxtensor/ops/addcmul.py +29 -0
  89. nbxtensor/ops/addmv_op.py +64 -0
  90. nbxtensor/ops/addr.py +68 -0
  91. nbxtensor/ops/all_reduce.py +83 -0
  92. nbxtensor/ops/amax.py +40 -0
  93. nbxtensor/ops/any_reduce.py +83 -0
  94. nbxtensor/ops/arange_op.py +27 -0
  95. nbxtensor/ops/argmax.py +107 -0
  96. nbxtensor/ops/argmin.py +93 -0
  97. nbxtensor/ops/atan2.py +71 -0
  98. nbxtensor/ops/attention_wrappers.py +1648 -0
  99. nbxtensor/ops/autotune_bucket.py +137 -0
  100. nbxtensor/ops/autotune_cache.py +430 -0
  101. nbxtensor/ops/autotune_certified.py +1262 -0
  102. nbxtensor/ops/autotune_refusals.py +380 -0
  103. nbxtensor/ops/autotune_settings.py +58 -0
  104. nbxtensor/ops/avg_pool2d.py +73 -0
  105. nbxtensor/ops/baddbmm_op.py +132 -0
  106. nbxtensor/ops/batch_norm.py +163 -0
  107. nbxtensor/ops/binary_strided.py +196 -0
  108. nbxtensor/ops/bitwise_and.py +26 -0
  109. nbxtensor/ops/bitwise_left_shift.py +47 -0
  110. nbxtensor/ops/bitwise_not.py +24 -0
  111. nbxtensor/ops/bitwise_or.py +26 -0
  112. nbxtensor/ops/bitwise_right_shift.py +47 -0
  113. nbxtensor/ops/bucketize.py +37 -0
  114. nbxtensor/ops/cat_op.py +87 -0
  115. nbxtensor/ops/celu.py +20 -0
  116. nbxtensor/ops/clamp.py +24 -0
  117. nbxtensor/ops/clamp_min.py +22 -0
  118. nbxtensor/ops/conv2d.py +265 -0
  119. nbxtensor/ops/conv2d_m8n8k4.py +165 -0
  120. nbxtensor/ops/conv_depthwise2d.py +154 -0
  121. nbxtensor/ops/conv_transpose2d.py +148 -0
  122. nbxtensor/ops/conv_wrappers.py +1482 -0
  123. nbxtensor/ops/copy.py +18 -0
  124. nbxtensor/ops/copy_op.py +39 -0
  125. nbxtensor/ops/cos.py +17 -0
  126. nbxtensor/ops/count_nonzero.py +64 -0
  127. nbxtensor/ops/cross_entropy.py +67 -0
  128. nbxtensor/ops/cummax.py +109 -0
  129. nbxtensor/ops/cummin.py +105 -0
  130. nbxtensor/ops/cumsum.py +244 -0
  131. nbxtensor/ops/decode_attn_vec.py +162 -0
  132. nbxtensor/ops/depthwise_conv2d.py +132 -0
  133. nbxtensor/ops/dequant_gemv.py +190 -0
  134. nbxtensor/ops/dft.py +67 -0
  135. nbxtensor/ops/dispatch.py +1228 -0
  136. nbxtensor/ops/div.py +75 -0
  137. nbxtensor/ops/dot_op.py +67 -0
  138. nbxtensor/ops/dropout.py +23 -0
  139. nbxtensor/ops/dtype_convert.py +42 -0
  140. nbxtensor/ops/elementwise_launch.py +81 -0
  141. nbxtensor/ops/elementwise_wrappers.py +2315 -0
  142. nbxtensor/ops/elu.py +20 -0
  143. nbxtensor/ops/embedding.py +69 -0
  144. nbxtensor/ops/eq.py +32 -0
  145. nbxtensor/ops/erf.py +18 -0
  146. nbxtensor/ops/exp.py +17 -0
  147. nbxtensor/ops/exp2.py +18 -0
  148. nbxtensor/ops/fft_op.py +125 -0
  149. nbxtensor/ops/fill_op.py +20 -0
  150. nbxtensor/ops/flash_attention.py +307 -0
  151. nbxtensor/ops/flash_attention_m8n8k4.py +384 -0
  152. nbxtensor/ops/flash_decode.py +78 -0
  153. nbxtensor/ops/flip_op.py +60 -0
  154. nbxtensor/ops/floor.py +90 -0
  155. nbxtensor/ops/full_op.py +24 -0
  156. nbxtensor/ops/fused_moe.py +417 -0
  157. nbxtensor/ops/fused_moe_m8n8k4.py +99 -0
  158. nbxtensor/ops/fused_upsample_conv.py +413 -0
  159. nbxtensor/ops/gather_op.py +52 -0
  160. nbxtensor/ops/ge.py +32 -0
  161. nbxtensor/ops/gelu.py +31 -0
  162. nbxtensor/ops/gemv_vec.py +80 -0
  163. nbxtensor/ops/glu.py +43 -0
  164. nbxtensor/ops/grid_sampler.py +145 -0
  165. nbxtensor/ops/group_norm_tiled.py +67 -0
  166. nbxtensor/ops/groupnorm.py +123 -0
  167. nbxtensor/ops/gt.py +32 -0
  168. nbxtensor/ops/hardsigmoid.py +18 -0
  169. nbxtensor/ops/hardswish.py +19 -0
  170. nbxtensor/ops/im2col.py +57 -0
  171. nbxtensor/ops/index_add.py +95 -0
  172. nbxtensor/ops/index_put_op.py +92 -0
  173. nbxtensor/ops/index_select.py +121 -0
  174. nbxtensor/ops/index_wrappers.py +770 -0
  175. nbxtensor/ops/isfinite.py +20 -0
  176. nbxtensor/ops/isinf.py +20 -0
  177. nbxtensor/ops/isnan.py +18 -0
  178. nbxtensor/ops/launch_keys.py +1054 -0
  179. nbxtensor/ops/launch_observer.py +54 -0
  180. nbxtensor/ops/layernorm.py +71 -0
  181. nbxtensor/ops/le.py +35 -0
  182. nbxtensor/ops/leaky_relu.py +19 -0
  183. nbxtensor/ops/lerp.py +76 -0
  184. nbxtensor/ops/linalg_wrappers.py +1370 -0
  185. nbxtensor/ops/linspace_op.py +47 -0
  186. nbxtensor/ops/log.py +17 -0
  187. nbxtensor/ops/log_sigmoid.py +23 -0
  188. nbxtensor/ops/logical_and.py +26 -0
  189. nbxtensor/ops/logical_not.py +24 -0
  190. nbxtensor/ops/logical_or.py +26 -0
  191. nbxtensor/ops/logical_xor.py +37 -0
  192. nbxtensor/ops/loss_wrappers.py +282 -0
  193. nbxtensor/ops/lt.py +32 -0
  194. nbxtensor/ops/masked_fill.py +21 -0
  195. nbxtensor/ops/matmul.py +377 -0
  196. nbxtensor/ops/matmul_m8n8k4.py +235 -0
  197. nbxtensor/ops/max_pool2d.py +74 -0
  198. nbxtensor/ops/max_pool3d.py +93 -0
  199. nbxtensor/ops/max_reduce.py +91 -0
  200. nbxtensor/ops/maximum.py +18 -0
  201. nbxtensor/ops/mean.py +40 -0
  202. nbxtensor/ops/min_reduce.py +91 -0
  203. nbxtensor/ops/minimum.py +18 -0
  204. nbxtensor/ops/mish.py +22 -0
  205. nbxtensor/ops/mma_m8n8k4.py +152 -0
  206. nbxtensor/ops/moe_align.py +195 -0
  207. nbxtensor/ops/moe_decode_vec.py +374 -0
  208. nbxtensor/ops/moe_device_residency.py +141 -0
  209. nbxtensor/ops/moe_wrappers.py +345 -0
  210. nbxtensor/ops/mse_loss.py +79 -0
  211. nbxtensor/ops/mul.py +51 -0
  212. nbxtensor/ops/nan_to_num.py +23 -0
  213. nbxtensor/ops/ne.py +32 -0
  214. nbxtensor/ops/neg.py +17 -0
  215. nbxtensor/ops/nllloss.py +124 -0
  216. nbxtensor/ops/nonzero.py +33 -0
  217. nbxtensor/ops/nonzero_op.py +62 -0
  218. nbxtensor/ops/norm_wrappers.py +579 -0
  219. nbxtensor/ops/ones_op.py +20 -0
  220. nbxtensor/ops/oracles/__init__.py +6 -0
  221. nbxtensor/ops/oracles/conv2d_fp64.py +450 -0
  222. nbxtensor/ops/pad_op.py +159 -0
  223. nbxtensor/ops/paged_decode_attn.py +215 -0
  224. nbxtensor/ops/pixel_shuffle.py +144 -0
  225. nbxtensor/ops/pixel_unshuffle.py +72 -0
  226. nbxtensor/ops/pow.py +137 -0
  227. nbxtensor/ops/prod.py +81 -0
  228. nbxtensor/ops/rand_op.py +52 -0
  229. nbxtensor/ops/random_wrappers.py +366 -0
  230. nbxtensor/ops/reciprocal.py +17 -0
  231. nbxtensor/ops/reduction_wrappers.py +845 -0
  232. nbxtensor/ops/relu.py +18 -0
  233. nbxtensor/ops/remainder.py +46 -0
  234. nbxtensor/ops/repeat_interleave.py +48 -0
  235. nbxtensor/ops/repeat_op.py +132 -0
  236. nbxtensor/ops/residual_chain.py +374 -0
  237. nbxtensor/ops/rmsnorm.py +68 -0
  238. nbxtensor/ops/rope.py +142 -0
  239. nbxtensor/ops/rsqrt.py +17 -0
  240. nbxtensor/ops/scaled_softmax.py +96 -0
  241. nbxtensor/ops/scatter_op.py +139 -0
  242. nbxtensor/ops/screen_oracle.py +479 -0
  243. nbxtensor/ops/selu.py +22 -0
  244. nbxtensor/ops/shape_wrappers.py +1032 -0
  245. nbxtensor/ops/sigmoid.py +19 -0
  246. nbxtensor/ops/silu.py +27 -0
  247. nbxtensor/ops/sin.py +17 -0
  248. nbxtensor/ops/softmax.py +169 -0
  249. nbxtensor/ops/softplus.py +27 -0
  250. nbxtensor/ops/sort_op.py +304 -0
  251. nbxtensor/ops/spectral_wrappers.py +481 -0
  252. nbxtensor/ops/sqrt.py +17 -0
  253. nbxtensor/ops/stack_op.py +84 -0
  254. nbxtensor/ops/std.py +113 -0
  255. nbxtensor/ops/strided_copy.py +200 -0
  256. nbxtensor/ops/sub.py +35 -0
  257. nbxtensor/ops/sum.py +40 -0
  258. nbxtensor/ops/tan.py +19 -0
  259. nbxtensor/ops/tanh.py +21 -0
  260. nbxtensor/ops/threshold.py +19 -0
  261. nbxtensor/ops/tiling.yml +91 -0
  262. nbxtensor/ops/tiling_policy.py +43 -0
  263. nbxtensor/ops/tiling_sizes.py +370 -0
  264. nbxtensor/ops/topk.py +267 -0
  265. nbxtensor/ops/trace_op.py +64 -0
  266. nbxtensor/ops/tril.py +64 -0
  267. nbxtensor/ops/triu.py +62 -0
  268. nbxtensor/ops/unfold_op.py +37 -0
  269. nbxtensor/ops/unique_op.py +97 -0
  270. nbxtensor/ops/upsample_bilinear2d.py +106 -0
  271. nbxtensor/ops/upsample_nearest2d.py +52 -0
  272. nbxtensor/ops/upsample_nearest3d.py +61 -0
  273. nbxtensor/ops/var.py +136 -0
  274. nbxtensor/ops/vector_norm.py +216 -0
  275. nbxtensor/ops/weight_norm.py +104 -0
  276. nbxtensor/ops/where.py +39 -0
  277. nbxtensor/ops/wrappers.py +825 -0
  278. nbxtensor/ops/zeros_op.py +20 -0
  279. nbxtensor/quant/__init__.py +1 -0
  280. nbxtensor/quant/quantized_tensor.py +187 -0
  281. nbxtensor/random/__init__.py +1 -0
  282. nbxtensor/random/mt_randn.py +154 -0
  283. nbxtensor/random/rng_pin.py +59 -0
  284. nbxtensor/random/rng_stream.py +118 -0
  285. nbxtensor/random/seeded_draw.py +223 -0
  286. nbxtensor/shape/__init__.py +157 -0
  287. nbxtensor/tensor.py +1867 -0
  288. nbxtensor-0.0.1.dist-info/METADATA +379 -0
  289. nbxtensor-0.0.1.dist-info/RECORD +291 -0
  290. nbxtensor-0.0.1.dist-info/WHEEL +4 -0
  291. nbxtensor-0.0.1.dist-info/licenses/LICENSE +189 -0
nbxtensor/ops/clamp.py ADDED
@@ -0,0 +1,24 @@
1
+ """Clamp — pure @triton.jit kernel."""
2
+
3
+ import triton
4
+ import triton.language as tl
5
+
6
+ @triton.jit
7
+ def clamp_forward_kernel(
8
+ input_ptr, output_ptr,
9
+ n_elements,
10
+ min_val, max_val,
11
+ has_min: tl.constexpr,
12
+ has_max: tl.constexpr,
13
+ BLOCK_SIZE: tl.constexpr,
14
+ ):
15
+ pid = tl.program_id(0).to(tl.int64)
16
+ 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)
17
+ mask = offset < n_elements
18
+
19
+ x = tl.load(input_ptr + offset, mask=mask)
20
+ if has_min:
21
+ x = tl.maximum(min_val, x)
22
+ if has_max:
23
+ x = tl.minimum(max_val, x)
24
+ tl.store(output_ptr + offset, x, mask=mask)
@@ -0,0 +1,22 @@
1
+ """Clamp min — pure @triton.jit kernel.
2
+
3
+ aten::clamp_min(x, min_val) = max(x, min_val). Separate file per ATen op convention.
4
+ """
5
+
6
+ import triton
7
+ import triton.language as tl
8
+
9
+ @triton.jit
10
+ def clamp_min_forward_kernel(
11
+ input_ptr, output_ptr,
12
+ n_elements,
13
+ min_val,
14
+ BLOCK_SIZE: tl.constexpr,
15
+ ):
16
+ pid = tl.program_id(0).to(tl.int64)
17
+ offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
18
+ mask = offset < n_elements
19
+
20
+ x = tl.load(input_ptr + offset, mask=mask)
21
+ result = tl.maximum(min_val, x)
22
+ tl.store(output_ptr + offset, result, mask=mask)
@@ -0,0 +1,265 @@
1
+ """Conv2D forward — pure @triton.jit kernel with autotune.
2
+
3
+ From attorch (BobMcDear/attorch) conv_kernels.py — chosen over FlagGems
4
+ for V100 compatibility (proper num_stages handling). Im2col approach
5
+ with tl.dot, fp32 accumulation, groups support.
6
+
7
+ Phase 1.5 conv2d Step (b) (2026-05): @triton.autotune + cache_results=True
8
+ adoption — same pattern as mm/bmm/addmm/baddbmm (commits d514bdb +
9
+ 20ed765). Configs restricted to Volta-viable subspace (BLOCK_BHW ∈
10
+ {32,64,128}, BLOCK_OUTF ∈ {32,64,128}, BLOCK_INF ∈ {16,32,64}, num_warps
11
+ ∈ {2,4}, num_stages ∈ {2,3}). Triton compiler ejects configs that
12
+ exceed sm_70's 96 KB SMEM at compile time (native fallback). See
13
+ CLAUDE.md "Autotune policy" for the doctrinal exception scope.
14
+ """
15
+
16
+ import triton
17
+
18
+ from nbxtensor.ops._configs import nbx_autotune
19
+ import triton.language as tl
20
+
21
+ from nbxtensor.ops._autotune_policy import maybe_pin_single, is_conv2d_pinned
22
+
23
+
24
+ # Volta-viable conv2d autotune subspace.
25
+ # SMEM budget per config (rough): 2 stages × dtype_bytes × (BLOCK_BHW *
26
+ # BLOCK_INF [input tile] + BLOCK_INF * BLOCK_OUTF [weight tile]).
27
+ # fp16 (2B), 2 stages: BLOCK_BHW=128, BLOCK_INF=64, BLOCK_OUTF=128 →
28
+ # 2 * 2 * (128*64 + 64*128) = 65 KB → fits 96 KB sm_70.
29
+ # BLOCK_BHW=128, BLOCK_INF=64, BLOCK_OUTF=128, stages=3 → 98 KB → over.
30
+ # Triton ejects over-SMEM configs at compile time via native fallback.
31
+ _CONV2D_AUTOTUNE_VOLTA = [
32
+ triton.Config({'BLOCK_SIZE_BHW': 32, 'BLOCK_SIZE_OUTF': 32, 'BLOCK_SIZE_INF': 16}, num_stages=2, num_warps=2),
33
+ triton.Config({'BLOCK_SIZE_BHW': 32, 'BLOCK_SIZE_OUTF': 32, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=2),
34
+ triton.Config({'BLOCK_SIZE_BHW': 32, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=2),
35
+ triton.Config({'BLOCK_SIZE_BHW': 32, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 32}, num_stages=3, num_warps=4),
36
+ triton.Config({'BLOCK_SIZE_BHW': 32, 'BLOCK_SIZE_OUTF': 128, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
37
+ triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 32, 'BLOCK_SIZE_INF': 16}, num_stages=2, num_warps=2),
38
+ triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 32, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
39
+ triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 32, 'BLOCK_SIZE_INF': 32}, num_stages=3, num_warps=4),
40
+ triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 16}, num_stages=2, num_warps=4),
41
+ triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
42
+ triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 32}, num_stages=3, num_warps=4),
43
+ triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 64}, num_stages=2, num_warps=4),
44
+ triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 128, 'BLOCK_SIZE_INF': 16}, num_stages=2, num_warps=4),
45
+ triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 128, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
46
+ triton.Config({'BLOCK_SIZE_BHW': 128, 'BLOCK_SIZE_OUTF': 32, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
47
+ triton.Config({'BLOCK_SIZE_BHW': 128, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 16}, num_stages=2, num_warps=4),
48
+ triton.Config({'BLOCK_SIZE_BHW': 128, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
49
+ triton.Config({'BLOCK_SIZE_BHW': 128, 'BLOCK_SIZE_OUTF': 128, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
50
+ ]
51
+
52
+ # Every tile in BOTH orientations (`SPATIAL_FAST`): the certifier picks per key per hardware. Metal takes the
53
+ # spatial-fast tile (CogVideoX-2b VAE 3x3 (9,128,242x362) 109 -> 55 ms, Mac 2026-10-21); on V100 today's tile wins
54
+ # five shapes of six (9-40 %, Dell 2026-10-21 01:53). Same key; an entry certified before the axis existed names no
55
+ # orientation and keeps today's tile.
56
+ _CONV2D_AUTOTUNE = _CONV2D_AUTOTUNE_VOLTA + [
57
+ triton.Config({**c.kwargs, 'SPATIAL_FAST': True}, num_stages=c.num_stages, num_warps=c.num_warps)
58
+ for c in _CONV2D_AUTOTUNE_VOLTA
59
+ ]
60
+
61
+
62
+ @nbx_autotune(
63
+ configs=maybe_pin_single(_CONV2D_AUTOTUNE, is_conv2d_pinned),
64
+ key=['batch_dim', 'in_feat_dim', 'in_height', 'in_width_key',
65
+ 'out_feat_dim', 'out_height', 'out_width_key',
66
+ 'kernel_height', 'kernel_width',
67
+ 'stride_height', 'stride_width',
68
+ 'padding_height', 'padding_width',
69
+ 'dilation_height', 'dilation_width',
70
+ 'groups', 'fp16'],
71
+ cache_results=True,
72
+ )
73
+ @triton.jit
74
+ def conv2d_forward_kernel(
75
+ input_pointer, weight_pointer, output_pointer,
76
+ batch_dim, in_feat_dim, in_height, in_width,
77
+ out_feat_dim, out_height, out_width,
78
+ in_width_key, out_width_key,
79
+ input_batch_stride, input_in_feat_stride, input_height_stride, input_width_stride,
80
+ weight_out_feat_stride, weight_in_feat_stride, weight_height_stride, weight_width_stride,
81
+ output_batch_stride, output_out_feat_stride, output_height_stride, output_width_stride,
82
+ kernel_height: tl.constexpr, kernel_width: tl.constexpr,
83
+ stride_height: tl.constexpr, stride_width: tl.constexpr,
84
+ padding_height: tl.constexpr, padding_width: tl.constexpr,
85
+ groups: tl.constexpr, fp16: tl.constexpr,
86
+ dilation_height: tl.constexpr = 1, dilation_width: tl.constexpr = 1,
87
+ BLOCK_SIZE_BHW: tl.constexpr = 64,
88
+ BLOCK_SIZE_INF: tl.constexpr = 32,
89
+ BLOCK_SIZE_OUTF: tl.constexpr = 64,
90
+ WIDE_OFFSETS: tl.constexpr = True,
91
+ SPATIAL_FAST: tl.constexpr = False,
92
+ ):
93
+ # 64-bit offsets only where an operand's extent needs them (`WIDE_OFFSETS`, chosen by the wrapper from the
94
+ # tensors' extents): 32-bit offsets are exact below 2^31 and the 64-bit ones cost 17 % on Apple's ALUs once the
95
+ # Metal backend stopped narrowing them (Mac, 2026-10-20, 60 bf16 keys re-proven)
96
+ if WIDE_OFFSETS:
97
+ bhw_pid = tl.program_id(0).to(tl.int64)
98
+ outf_pid = tl.program_id(1).to(tl.int64)
99
+ group_pid = tl.program_id(2).to(tl.int64)
100
+ else:
101
+ bhw_pid = tl.program_id(0)
102
+ outf_pid = tl.program_id(1)
103
+ group_pid = tl.program_id(2)
104
+
105
+ in_group_dim = in_feat_dim // groups
106
+ out_group_dim = out_feat_dim // groups
107
+
108
+ if SPATIAL_FAST:
109
+ # The tile is [OUTF, BHW] with the SPATIAL axis fast: NCHW holds a channel plane contiguous along width, so the
110
+ # input block is loaded [INF, BHW] and the output stored [OUTF, BHW] along it, and the weight block [OUTF, INF]
111
+ # steps the small kh*kw stride. Loaded [BHW, INF] and stored [BHW, OUTF], both fast axes stepped a whole channel
112
+ # plane: the Metal backend gathered them element by element — CogVideoX-2b's VAE 3x3 (9,128,242x362) 109 ms
113
+ # against 55 ms transposed, same values (max |diff| 0), native 31 ms (Mac, 2026-10-21).
114
+ bhw_offset = bhw_pid * BLOCK_SIZE_BHW + tl.arange(0, BLOCK_SIZE_BHW)
115
+ bh_offset = bhw_offset // out_width
116
+ batch_offset = bh_offset // out_height
117
+
118
+ outf_offset = outf_pid * BLOCK_SIZE_OUTF + tl.arange(0, BLOCK_SIZE_OUTF)
119
+ oh_offset = bh_offset % out_height
120
+ ow_offset = bhw_offset % out_width
121
+
122
+ input_pointer += (input_batch_stride * batch_offset +
123
+ input_in_feat_stride * group_pid * in_group_dim)[None, :]
124
+ weight_pointer += (weight_out_feat_stride * outf_offset +
125
+ weight_out_feat_stride * group_pid * out_group_dim)[:, None]
126
+
127
+ accum = tl.zeros((BLOCK_SIZE_OUTF, BLOCK_SIZE_BHW), dtype=tl.float32)
128
+
129
+ for h in range(kernel_height):
130
+ for w in range(kernel_width):
131
+ ih_offset = h * dilation_height - padding_height + stride_height * oh_offset
132
+ iw_offset = w * dilation_width - padding_width + stride_width * ow_offset
133
+ spatial = input_height_stride * ih_offset + input_width_stride * iw_offset
134
+ spatial_ok = ((batch_offset < batch_dim) &
135
+ (ih_offset >= 0) & (ih_offset < in_height) &
136
+ (iw_offset >= 0) & (iw_offset < in_width))
137
+ for c in range(0, in_group_dim, BLOCK_SIZE_INF):
138
+ # 64-bit like every offset above it when the extents need it: `input_in_feat_stride *
139
+ # inf_offset` is a product of the channel plane (36 000 000 elements at 6000x6000) and the
140
+ # channel index, and past channel 59 it exceeds 2^31 — the 8x upscaler's last conv (64 -> 3, a
141
+ # 6344x6344 tile) faulted with an illegal address, four of four, 2026-09-20 (register 58's
142
+ # class; the output being small, the band-streaming that saves the 64 -> 64 convs never engaged).
143
+ inf_offset = c + tl.arange(0, BLOCK_SIZE_INF)
144
+ if WIDE_OFFSETS:
145
+ inf_offset = inf_offset.to(tl.int64)
146
+
147
+ curr_inp = (input_pointer +
148
+ (input_in_feat_stride * inf_offset)[:, None] +
149
+ spatial[None, :])
150
+ curr_wt = (weight_pointer +
151
+ (weight_in_feat_stride * inf_offset)[None, :] +
152
+ (weight_height_stride * h) +
153
+ (weight_width_stride * w))
154
+
155
+ inp_mask = (inf_offset < in_group_dim)[:, None] & spatial_ok[None, :]
156
+ wt_mask = ((outf_offset < out_group_dim)[:, None] &
157
+ (inf_offset < in_group_dim)[None, :])
158
+
159
+ inp_block = tl.load(curr_inp, mask=inp_mask, other=0.0)
160
+ wt_block = tl.load(curr_wt, mask=wt_mask, other=0.0)
161
+
162
+ if fp16:
163
+ inp_block = inp_block.to(tl.float16)
164
+ wt_block = wt_block.to(tl.float16)
165
+
166
+ accum += tl.dot(wt_block, inp_block)
167
+
168
+ out_ptr = (output_pointer +
169
+ (output_batch_stride * batch_offset)[None, :] +
170
+ (output_out_feat_stride * (group_pid * out_group_dim + outf_offset))[:, None] +
171
+ (output_height_stride * oh_offset)[None, :] +
172
+ (output_width_stride * ow_offset)[None, :])
173
+ out_mask = ((batch_offset < batch_dim)[None, :] &
174
+ (outf_offset < out_group_dim)[:, None] &
175
+ (oh_offset < out_height)[None, :] &
176
+ (ow_offset < out_width)[None, :])
177
+
178
+ tl.store(out_ptr, accum, mask=out_mask)
179
+ else:
180
+ bhw_offset = bhw_pid * BLOCK_SIZE_BHW + tl.arange(0, BLOCK_SIZE_BHW)
181
+ bh_offset = bhw_offset // out_width
182
+ batch_offset = bh_offset // out_height
183
+
184
+ outf_offset = outf_pid * BLOCK_SIZE_OUTF + tl.arange(0, BLOCK_SIZE_OUTF)
185
+ oh_offset = bh_offset % out_height
186
+ ow_offset = bhw_offset % out_width
187
+
188
+ input_pointer += (input_batch_stride * batch_offset +
189
+ input_in_feat_stride * group_pid * in_group_dim)[:, None]
190
+ weight_pointer += (weight_out_feat_stride * outf_offset +
191
+ weight_out_feat_stride * group_pid * out_group_dim)[None, :]
192
+
193
+ accum = tl.zeros((BLOCK_SIZE_BHW, BLOCK_SIZE_OUTF), dtype=tl.float32)
194
+
195
+ for h in range(kernel_height):
196
+ for w in range(kernel_width):
197
+ for c in range(0, in_group_dim, BLOCK_SIZE_INF):
198
+ # 64-bit like every offset above it: `input_in_feat_stride *
199
+ # inf_offset` is a product of the channel plane (36 000 000
200
+ # elements at 6000x6000) and the channel index, and past channel
201
+ # 59 it exceeds 2^31 — the 8x upscaler's last conv (64 -> 3, a
202
+ # 6344x6344 tile) faulted with an illegal address, four of four,
203
+ # 2026-09-20 (register 58's class; the output being small, the
204
+ # band-streaming that saves the 64 -> 64 convs never engaged).
205
+ inf_offset = c + tl.arange(0, BLOCK_SIZE_INF)
206
+ if WIDE_OFFSETS:
207
+ inf_offset = inf_offset.to(tl.int64)
208
+ ih_offset = h * dilation_height - padding_height + stride_height * oh_offset
209
+ iw_offset = w * dilation_width - padding_width + stride_width * ow_offset
210
+
211
+ curr_inp = (input_pointer +
212
+ (input_in_feat_stride * inf_offset)[None, :] +
213
+ (input_height_stride * ih_offset)[:, None] +
214
+ (input_width_stride * iw_offset)[:, None])
215
+ curr_wt = (weight_pointer +
216
+ (weight_in_feat_stride * inf_offset)[:, None] +
217
+ (weight_height_stride * h) +
218
+ (weight_width_stride * w))
219
+
220
+ inp_mask = ((batch_offset < batch_dim)[:, None] &
221
+ (inf_offset < in_group_dim)[None, :] &
222
+ (ih_offset >= 0)[:, None] &
223
+ (ih_offset < in_height)[:, None] &
224
+ (iw_offset >= 0)[:, None] &
225
+ (iw_offset < in_width)[:, None])
226
+ wt_mask = ((inf_offset < in_group_dim)[:, None] &
227
+ (outf_offset < out_group_dim)[None, :])
228
+
229
+ inp_block = tl.load(curr_inp, mask=inp_mask, other=0.0)
230
+ wt_block = tl.load(curr_wt, mask=wt_mask, other=0.0)
231
+
232
+ if fp16:
233
+ inp_block = inp_block.to(tl.float16)
234
+ wt_block = wt_block.to(tl.float16)
235
+
236
+ accum += tl.dot(inp_block, wt_block)
237
+
238
+ out_ptr = (output_pointer +
239
+ (output_batch_stride * batch_offset)[:, None] +
240
+ (output_out_feat_stride * (group_pid * out_group_dim + outf_offset))[None, :] +
241
+ (output_height_stride * oh_offset)[:, None] +
242
+ (output_width_stride * ow_offset)[:, None])
243
+ out_mask = ((batch_offset < batch_dim)[:, None] &
244
+ (outf_offset < out_group_dim)[None, :] &
245
+ (oh_offset < out_height)[:, None] &
246
+ (ow_offset < out_width)[:, None])
247
+
248
+ tl.store(out_ptr, accum, mask=out_mask)
249
+
250
+
251
+ @triton.jit
252
+ def conv2d_bias_inplace_kernel(out_ptr, bias_ptr, n, HW, C, BLOCK: tl.constexpr):
253
+ """out[i] += bias[(i // HW) % C], in place, flat over a CONTIGUOUS NCHW
254
+ output. The conv wrapper's bias epilogue: the out-of-place `add` here
255
+ allocated a second full-size output per biased convolution — 2450 MB of
256
+ the measured 8008 MB tile peak on real-esrgan-x8 @1024 (malloc trace,
257
+ 2026-09-21). Elementwise same-offset read/write, so aliasing the input
258
+ as the output is exact by construction. Fixed launch config — this
259
+ kernel takes no autotune key and adds nothing to any census."""
260
+ i = tl.program_id(0).to(tl.int64) * BLOCK + tl.arange(0, BLOCK)
261
+ m = i < n
262
+ c = (i // HW) % C
263
+ v = tl.load(out_ptr + i, mask=m)
264
+ b = tl.load(bias_ptr + c, mask=m)
265
+ tl.store(out_ptr + i, v + b, mask=m)
@@ -0,0 +1,165 @@
1
+ """Convolution on the m8n8k4 matrix unit — `conv2d` where the hardware profile declares `matrix_unit`.
2
+
3
+ An implicit GEMM over the operands in place: per group g, C[m, n] = sum_k A[m, k] B[n, k] with
4
+ m = (batch, oh, ow), n = the group's output channel and k = (c, r, s) flattened in the weight's own order, so a
5
+ contiguous weight [OC, IC/g, KH, KW] is a k-contiguous B read in place (no copy, no im2col buffer) and A is gathered
6
+ from the input through its four strides (padding, stride and dilation are masks and index arithmetic). Same contract
7
+ as `conv2d_forward_kernel` (ops/conv2d.py): fp32
8
+ accumulation (one K tile in the unit, which rounds toward zero, the tiles summed on the FMA units with round to
9
+ nearest — ops/matmul_m8n8k4.py), int64 offsets, every tail masked, the output cast to C's dtype; the bias, when given, is added to the
10
+ fp32 accumulator before that single rounding (the product of two fp16 values is exact in fp32, so the sums are the
11
+ tl.dot kernel's in another order). The tile is the profile's (`matrix_unit.mm`), never tuned at run time: no
12
+ autotune key, nothing to certify.
13
+
14
+ An operand the DtypeEngine carries SPLIT (SPLIT_A / SPLIT_B: `nbxtensor/dtype/operands.matrix_unit_representation`, the
15
+ profile's `matrix_unit.fp32_split` — the video VAEs' fp32 convolutions) is staged as the GEMM stages it
16
+ (ops/matmul_m8n8k4.py `_stage_split`): hi + lo of the unit's operand dtype under a power-of-two scale per row of A
17
+ (an output position) or column of B (an output channel) of each K tile, three MMAs into two accumulators, the tile's
18
+ sum rescaled and added to the fp32 accumulator on the FMA units.
19
+
20
+ A is loaded with consecutive lanes on consecutive output rows (consecutive `ow`: coalesced at stride 1), each lane
21
+ holding 4 consecutive k — one 8-byte store into the [k-step x rows, 4] staging of ops/mma_m8n8k4.py.
22
+ """
23
+
24
+ import functools
25
+
26
+ from triton.experimental import gluon
27
+ from triton.experimental.gluon import language as gl
28
+
29
+ from .mma_m8n8k4 import acc_rows_cols, mma_k4, stage_k_inner
30
+ from .matmul_m8n8k4 import _bcast_cols, _bcast_rows, _stage_split, mm_layouts
31
+
32
+
33
+ @gluon.jit
34
+ def conv2d_m8n8k4_kernel(
35
+ X, Wt, Bias, C,
36
+ M, NG, K, IH, IW, OH, OW,
37
+ stride_xn, stride_xc, stride_xh, stride_xw,
38
+ stride_wo, stride_cn, stride_cc, stride_ch, stride_cw,
39
+ CG, OG,
40
+ KH: gl.constexpr, KW: gl.constexpr, SH_: gl.constexpr, SW_: gl.constexpr, PH: gl.constexpr,
41
+ PW: gl.constexpr, DH: gl.constexpr, DW: gl.constexpr,
42
+ HAS_BIAS: gl.constexpr,
43
+ SPLIT_A: gl.constexpr, SPLIT_B: gl.constexpr, HI_EXP: gl.constexpr, LO_SHIFT: gl.constexpr,
44
+ MB: gl.constexpr, NB: gl.constexpr, BK: gl.constexpr, GROUP_M: gl.constexpr,
45
+ L: gl.constexpr, LA: gl.constexpr, LB: gl.constexpr, GA: gl.constexpr, GB: gl.constexpr,
46
+ ):
47
+ BM: gl.constexpr = 16 * MB
48
+ BN: gl.constexpr = 16 * NB
49
+ J: gl.constexpr = BK // 4
50
+ KHW: gl.constexpr = KH * KW
51
+ SH: gl.constexpr = gl.SwizzledSharedLayout(1, 1, 1, order=[1, 0])
52
+
53
+ pid = gl.program_id(0).to(gl.int64)
54
+ g = gl.program_id(1).to(gl.int64) # the group
55
+ X = X + g * CG * stride_xc
56
+ Wt = Wt + g * OG * stride_wo
57
+ C = C + g * OG * stride_cc
58
+ num_pid_m = gl.cdiv(M, BM)
59
+ num_pid_n = gl.cdiv(NG, BN)
60
+ num_pid_in_group = GROUP_M * num_pid_n
61
+ first_pid_m = (pid // num_pid_in_group) * GROUP_M
62
+ group_size_m = gl.minimum(num_pid_m - first_pid_m, GROUP_M)
63
+ pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
64
+ pid_n = (pid % num_pid_in_group) // group_size_m
65
+ m0 = pid_m * BM
66
+ n0 = pid_n * BN
67
+
68
+ sa = gl.allocate_shared_memory(gl.float16, [J * BM, 4], SH)
69
+ sb = gl.allocate_shared_memory(gl.float16, [J * BN, 4], SH)
70
+ if SPLIT_A:
71
+ sal = gl.allocate_shared_memory(gl.float16, [J * BM, 4], SH)
72
+ if SPLIT_B:
73
+ sbl = gl.allocate_shared_memory(gl.float16, [J * BN, 4], SH)
74
+
75
+ # A rows: (batch, oh, ow) once per tile; the k columns are decoded per K tile against constexpr divisors.
76
+ ma1 = m0 + gl.arange(0, BM, layout=gl.SliceLayout(1, GA))
77
+ ow_ = ma1 % OW
78
+ oh_ = (ma1 // OW) % OH
79
+ xrow = ((ma1 // OW) // OH) * stride_xn
80
+ ih0 = (oh_ * SH_ - PH)[:, None]
81
+ iw0 = (ow_ * SW_ - PW)[:, None]
82
+ ma = (ma1 < M)[:, None]
83
+ xrow = xrow[:, None]
84
+ ka1 = gl.arange(0, BK, layout=gl.SliceLayout(0, GA))
85
+ # B [BN, BK], k contiguous: the weight read in place.
86
+ nb_ = gl.arange(0, BN, layout=gl.SliceLayout(1, GB))[:, None]
87
+ kb = gl.arange(0, BK, layout=gl.SliceLayout(0, GB))[None, :]
88
+ pb = Wt + (n0 + nb_) * stride_wo + kb
89
+ mb_ = (n0 + nb_) < NG
90
+
91
+ acc = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
92
+ ta = _gather(X, xrow, ih0, iw0, ma, ka1, 0, K, IH, IW, stride_xc, stride_xh, stride_xw, KHW, KW, DH, DW)
93
+ tb = gl.load(pb, mask=mb_ & (kb < K), other=0.0)
94
+ for k0 in range(0, K, BK):
95
+ gl.barrier() # every warp is done reading the previous tile
96
+ if SPLIT_A:
97
+ inv_a = _stage_split(sa, sal, ta, True, BM, BK, HI_EXP, LO_SHIFT)
98
+ else:
99
+ stage_k_inner(sa, ta, BM, BK)
100
+ if SPLIT_B:
101
+ inv_b = _stage_split(sb, sbl, tb, True, BN, BK, HI_EXP, LO_SHIFT)
102
+ else:
103
+ stage_k_inner(sb, tb, BN, BK)
104
+ gl.barrier()
105
+ if k0 + BK < K: # the next tile loads while the HMMAs run
106
+ ta = _gather(X, xrow, ih0, iw0, ma, ka1, k0 + BK, K, IH, IW, stride_xc, stride_xh, stride_xw, KHW, KW,
107
+ DH, DW)
108
+ tb = gl.load(pb + (k0 + BK), mask=mb_ & (kb + k0 + BK < K), other=0.0)
109
+ if SPLIT_A or SPLIT_B:
110
+ t_hh = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
111
+ t_lo = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
112
+ for j in gl.static_range(J):
113
+ t_hh = mma_k4(sa, j * BM, sb, j * BN, t_hh, MB, NB, LA, LB, L)
114
+ if SPLIT_A:
115
+ t_lo = mma_k4(sal, j * BM, sb, j * BN, t_lo, MB, NB, LA, LB, L)
116
+ if SPLIT_B:
117
+ t_lo = mma_k4(sa, j * BM, sbl, j * BN, t_lo, MB, NB, LA, LB, L)
118
+ t = t_hh + t_lo * (1.0 / (1 << LO_SHIFT))
119
+ if SPLIT_A:
120
+ t = t * _bcast_rows(gl.reshape(inv_a, [MB, 2, 8]), L)
121
+ if SPLIT_B:
122
+ t = t * _bcast_cols(gl.reshape(inv_b, [NB, 2, 2, 4]), L)
123
+ acc = acc + t
124
+ else:
125
+ t = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
126
+ for j in gl.static_range(J):
127
+ t = mma_k4(sa, j * BM, sb, j * BN, t, MB, NB, LA, LB, L)
128
+ acc = acc + t # across tiles on the FMA units, round to nearest
129
+
130
+ rows, cols = acc_rows_cols(L, MB, NB)
131
+ rows = m0 + rows
132
+ cols = n0 + cols
133
+ mask = (rows < M) & (cols < NG)
134
+ if HAS_BIAS:
135
+ bias = gl.load(Bias + g * OG + cols.to(gl.int64), mask=cols < NG, other=0.0)
136
+ acc = acc + bias.to(gl.float32)
137
+ r64 = rows.to(gl.int64)
138
+ ow_o = r64 % OW
139
+ oh_o = (r64 // OW) % OH
140
+ n_o = (r64 // OW) // OH
141
+ gl.store(C + n_o * stride_cn + oh_o * stride_ch + ow_o * stride_cw + cols.to(gl.int64) * stride_cc,
142
+ acc.to(C.dtype.element_ty), mask=mask)
143
+
144
+
145
+ @gluon.jit
146
+ def _gather(X, xrow, ih0, iw0, ma, ka1, k0, K, IH, IW, stride_xc, stride_xh, stride_xw,
147
+ KHW: gl.constexpr, KW: gl.constexpr, DH: gl.constexpr, DW: gl.constexpr):
148
+ """A[rows, k0:k0+BK]: input[batch, c, oh*sh - ph + r*dh, ow*sw - pw + s*dw] for k = (c, r, s), 0 outside."""
149
+ k = k0 + ka1
150
+ c = k // KHW
151
+ rs = k % KHW
152
+ ih = ih0 + ((rs // KW) * DH)[None, :]
153
+ iw = iw0 + ((rs % KW) * DW)[None, :]
154
+ m = ma & (k < K)[None, :] & (ih >= 0) & (ih < IH) & (iw >= 0) & (iw < IW)
155
+ p = X + xrow + (c.to(gl.int64) * stride_xc)[None, :] + ih.to(gl.int64) * stride_xh + iw.to(gl.int64) * stride_xw
156
+ return gl.load(p, mask=m, other=0.0)
157
+
158
+
159
+ @functools.lru_cache(maxsize=None)
160
+ def conv_layouts(MB: int, NB: int, WM: int, WN: int, BK: int) -> dict:
161
+ """The GEMM's layouts (ops/matmul_m8n8k4.py, B k-contiguous) with the gathered A walked row-fastest: 32
162
+ consecutive output rows per warp, 4 consecutive k per lane."""
163
+ lay = dict(mm_layouts(MB, NB, WM, WN, BK, True))
164
+ lay["GA"] = gl.BlockedLayout([1, 4], [32, 1], [WM * WN, 1], [0, 1])
165
+ return lay
@@ -0,0 +1,154 @@
1
+ """Depthwise 2D convolution — pure @triton.jit kernel.
2
+
3
+ Depthwise convolution: each input channel is convolved independently with
4
+ its own filter (groups = C_in). Output channels = C_in * channel_multiplier
5
+ (typically channel_multiplier = 1).
6
+
7
+ Input: [N, C, IH, IW]
8
+ Weight: [C_out, 1, KH, KW] (groups = C_in)
9
+ Output: [N, C_out, OH, OW]
10
+
11
+ OH = (IH + 2*pad_h - dilation_h*(KH-1) - 1) // stride_h + 1
12
+ OW = (IW + 2*pad_w - dilation_w*(KW-1) - 1) // stride_w + 1
13
+ """
14
+
15
+ import triton
16
+ import triton.language as tl
17
+
18
+
19
+ @triton.jit
20
+ def conv_depthwise2d_kernel(
21
+ input_ptr,
22
+ weight_ptr,
23
+ output_ptr,
24
+ # Input shape
25
+ N, C_in, IH, IW,
26
+ # Weight shape: [C_out, 1, KH, KW]
27
+ C_out, KH, KW,
28
+ # Output shape
29
+ OH, OW,
30
+ # Conv parameters
31
+ stride_h, stride_w,
32
+ pad_h, pad_w,
33
+ dilation_h, dilation_w,
34
+ # Channel multiplier: C_out // C_in (usually 1)
35
+ ch_mult,
36
+ BLOCK_X: tl.constexpr, # OW tile
37
+ BLOCK_Y: tl.constexpr, # OH tile
38
+ ):
39
+ """Depthwise conv2d kernel.
40
+
41
+ Grid: (cdiv(OW, BLOCK_X), cdiv(OH, BLOCK_Y), N * C_out)
42
+ Each program computes a BLOCK_Y x BLOCK_X tile of one output channel.
43
+ """
44
+ pid_x = tl.program_id(0).to(tl.int64)
45
+ pid_y = tl.program_id(1).to(tl.int64)
46
+ pid_nc = tl.program_id(2).to(tl.int64)
47
+
48
+ n = pid_nc // C_out
49
+ c_out = pid_nc % C_out
50
+ c_in = c_out // ch_mult
51
+
52
+ ow = pid_x * BLOCK_X + tl.arange(0, BLOCK_X) # [BLOCK_X]
53
+ oh = pid_y * BLOCK_Y + tl.arange(0, BLOCK_Y) # [BLOCK_Y]
54
+
55
+ mask_h = oh < OH
56
+ mask_w = ow < OW
57
+ mask_2d = mask_h[:, None] & mask_w[None, :]
58
+
59
+ acc = tl.zeros([BLOCK_Y, BLOCK_X], dtype=tl.float32)
60
+
61
+ # Base pointer for input channel
62
+ in_base = n * (C_in * IH * IW) + c_in * (IH * IW)
63
+
64
+ # Base pointer for weight
65
+ w_base = c_out * (KH * KW) # weight shape is [C_out, 1, KH, KW], 1 is implicit
66
+
67
+ for kh in range(0, KH):
68
+ for kw in range(0, KW):
69
+ # Input coordinates
70
+ ih = oh[:, None] * stride_h - pad_h + kh * dilation_h # [BLOCK_Y, 1]
71
+ iw = ow[None, :] * stride_w - pad_w + kw * dilation_w # [1, BLOCK_X]
72
+
73
+ # Bounds check
74
+ valid_h = (ih >= 0) & (ih < IH)
75
+ valid_w = (iw >= 0) & (iw < IW)
76
+ valid = mask_2d & valid_h & valid_w
77
+
78
+ # Load input value
79
+ in_offset = in_base + ih * IW + iw
80
+ x_val = tl.load(input_ptr + in_offset, mask=valid, other=0.0).to(tl.float32)
81
+
82
+ # Load weight value (scalar per kh, kw)
83
+ w_offset = w_base + kh * KW + kw
84
+ w_val = tl.load(weight_ptr + w_offset).to(tl.float32)
85
+
86
+ acc += x_val * w_val
87
+
88
+ # Store output
89
+ out_base = n * (C_out * OH * OW) + c_out * (OH * OW)
90
+ out_offset = out_base + oh[:, None] * OW + ow[None, :]
91
+ tl.store(output_ptr + out_offset, acc, mask=mask_2d)
92
+
93
+
94
+ @triton.jit
95
+ def conv_depthwise2d_bias_kernel(
96
+ input_ptr,
97
+ weight_ptr,
98
+ bias_ptr,
99
+ output_ptr,
100
+ N, C_in, IH, IW,
101
+ C_out, KH, KW,
102
+ OH, OW,
103
+ stride_h, stride_w,
104
+ pad_h, pad_w,
105
+ dilation_h, dilation_w,
106
+ ch_mult,
107
+ BLOCK_X: tl.constexpr,
108
+ BLOCK_Y: tl.constexpr,
109
+ ):
110
+ """Depthwise conv2d with bias, same structure as above."""
111
+ pid_x = tl.program_id(0).to(tl.int64)
112
+ pid_y = tl.program_id(1).to(tl.int64)
113
+ pid_nc = tl.program_id(2).to(tl.int64)
114
+
115
+ n = pid_nc // C_out
116
+ c_out = pid_nc % C_out
117
+ c_in = c_out // ch_mult
118
+
119
+ ow = pid_x * BLOCK_X + tl.arange(0, BLOCK_X)
120
+ oh = pid_y * BLOCK_Y + tl.arange(0, BLOCK_Y)
121
+
122
+ mask_h = oh < OH
123
+ mask_w = ow < OW
124
+ mask_2d = mask_h[:, None] & mask_w[None, :]
125
+
126
+ acc = tl.zeros([BLOCK_Y, BLOCK_X], dtype=tl.float32)
127
+
128
+ in_base = n * (C_in * IH * IW) + c_in * (IH * IW)
129
+ w_base = c_out * (KH * KW)
130
+
131
+ for kh in range(0, KH):
132
+ for kw in range(0, KW):
133
+ ih = oh[:, None] * stride_h - pad_h + kh * dilation_h
134
+ iw = ow[None, :] * stride_w - pad_w + kw * dilation_w
135
+
136
+ valid_h = (ih >= 0) & (ih < IH)
137
+ valid_w = (iw >= 0) & (iw < IW)
138
+ valid = mask_2d & valid_h & valid_w
139
+
140
+ in_offset = in_base + ih * IW + iw
141
+ x_val = tl.load(input_ptr + in_offset, mask=valid, other=0.0).to(tl.float32)
142
+
143
+ w_offset = w_base + kh * KW + kw
144
+ w_val = tl.load(weight_ptr + w_offset).to(tl.float32)
145
+
146
+ acc += x_val * w_val
147
+
148
+ # Add bias
149
+ bias_val = tl.load(bias_ptr + c_out).to(tl.float32)
150
+ acc += bias_val
151
+
152
+ out_base = n * (C_out * OH * OW) + c_out * (OH * OW)
153
+ out_offset = out_base + oh[:, None] * OW + ow[None, :]
154
+ tl.store(output_ptr + out_offset, acc, mask=mask_2d)