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,1482 @@
1
+ """Convolution and pooling wrappers: conv1d/2d/3d (strided, depthwise, transposed, chunked), im2col, pooling. Cut out of wrappers.py by family (M2, 2026-10-29), unchanged; shared launch layer and run context stay in wrappers.py."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import triton
7
+ from nbxtensor.ops.autotune_bucket import bucket_of as _bucket_of
8
+ from nbxtensor.ops import launch_keys as _lk
9
+ from nbxtensor.tensor import NBXTensor
10
+ from nbxtensor.dtype.nbx_dtype import NBXDtype, dtype_size
11
+ from nbxtensor.memory.allocator import DeviceAllocator
12
+ from nbxtensor.backend.runtime import set_device
13
+ from nbxtensor.shape import MAX_NDIM as _NBX_MAX_NDIM, broadcast_shapes, max_linear_offset
14
+ from nbxtensor.dtype.itemsize import canonical_name
15
+ from nbxtensor.dtype.promotion import binary_result, float_math_input
16
+ from nbxtensor.dtype.nbx_dtype import parse_dtype
17
+ from nbxtensor.memory.allocator import DeviceOOMError
18
+ from nbxtensor.backend.capabilities import fa_min_tile as _fa_min_tile
19
+ from nbxtensor.backend.capabilities import reduce_tile_max as _reduce_tile_max
20
+ from nbxtensor.backend.faults import device_fault_buffer, device_fault_code_cached, fault_channel
21
+ from nbxtensor.tensor import _upload_int64_array as _nbx_upload_int64, _saturating_cast as _nbx_saturating_cast
22
+ from nbxtensor.ops._configs import sdpa_block_ceiling as _sdpa_block_ceiling
23
+ from nbxtensor.ops._configs import sdpa_launch_meta as _sdpa_launch_meta
24
+ from nbxtensor.ops._configs import matrix_unit as _matrix_unit, matrix_unit_mm_tile as _matrix_unit_mm_tile
25
+ from nbxtensor.ops._configs import largest_tile_within_smem as _largest_tile_within_smem
26
+ from nbxtensor.backend import launcher as _nbx_launcher
27
+ from nbxtensor.backend.launcher import install as _install_launcher
28
+ from nbxtensor.ops.relu import relu_forward_kernel
29
+ from nbxtensor.ops.silu import silu_forward_kernel
30
+ from nbxtensor.ops.gelu import gelu_forward_kernel
31
+ from nbxtensor.ops.sigmoid import sigmoid_forward_kernel
32
+ from nbxtensor.ops.tanh import tanh_forward_kernel
33
+ from nbxtensor.ops.hardsigmoid import hardsigmoid_forward_kernel
34
+ from nbxtensor.ops.hardswish import hardswish_forward_kernel
35
+ from nbxtensor.ops.leaky_relu import leaky_relu_forward_kernel
36
+ from nbxtensor.ops.elu import elu_forward_kernel
37
+ from nbxtensor.ops.mish import mish_forward_kernel
38
+ from nbxtensor.ops.selu import selu_forward_kernel
39
+ from nbxtensor.ops.neg import neg_forward_kernel
40
+ from nbxtensor.ops.exp import exp_forward_kernel
41
+ from nbxtensor.ops.sin import sin_forward_kernel
42
+ from nbxtensor.ops.cos import cos_forward_kernel
43
+ from nbxtensor.ops.rsqrt import rsqrt_forward_kernel
44
+ from nbxtensor.ops.sqrt import sqrt_forward_kernel
45
+ from nbxtensor.ops.abs import abs_forward_kernel
46
+ from nbxtensor.ops.log import log_forward_kernel
47
+ from nbxtensor.ops.reciprocal import reciprocal_forward_kernel
48
+ from nbxtensor.ops.pow import pow_forward_kernel
49
+ from nbxtensor.ops.clamp import clamp_forward_kernel
50
+ from nbxtensor.ops.erf import erf_forward_kernel
51
+ from nbxtensor.ops.copy import copy_forward_kernel
52
+ from nbxtensor.ops.add import add_forward_kernel, add_scalar_kernel, add_scalar_dev_kernel, add_bias_broadcast_kernel
53
+ from nbxtensor.ops.binary_strided import add_strided_nd_kernel, mul_strided_nd_kernel, sub_strided_nd_kernel, div_strided_nd_kernel, add_scalar_strided_nd_kernel, mul_scalar_strided_nd_kernel
54
+ from nbxtensor.ops.mul import mul_forward_kernel, mul_scalar_kernel, mul_scalar_dev_kernel
55
+ from nbxtensor.ops.div import div_forward_kernel, div_scalar_kernel, div_scalar_dev_kernel, int_div_kernel
56
+ from nbxtensor.ops.sub import sub_forward_kernel, rsub_forward_kernel
57
+ from nbxtensor.ops.where import where_forward_kernel
58
+ from nbxtensor.ops.maximum import maximum_forward_kernel
59
+ from nbxtensor.ops.minimum import minimum_forward_kernel
60
+ from nbxtensor.ops.masked_fill import masked_fill_forward_kernel
61
+ from nbxtensor.ops.gt import gt_forward_kernel, gt_scalar_kernel
62
+ from nbxtensor.ops.ge import ge_forward_kernel, ge_scalar_kernel
63
+ from nbxtensor.ops.lt import lt_forward_kernel, lt_scalar_kernel
64
+ from nbxtensor.ops.eq import eq_forward_kernel, eq_scalar_kernel
65
+ from nbxtensor.ops.ne import ne_forward_kernel, ne_scalar_kernel
66
+ from nbxtensor.ops.layernorm import layer_norm_forward_kernel
67
+ from nbxtensor.ops.rmsnorm import rms_norm_forward_kernel
68
+ from nbxtensor.ops.softmax import softmax_forward_kernel, softmax_forward_looped_kernel
69
+ from nbxtensor.ops.matmul import matmul_kernel, addmm_kernel
70
+ from nbxtensor.ops._autotune_policy import NBX_FORCE_FP32_ACCUM
71
+ from nbxtensor.ops.fused_moe import fused_moe_kernel, silu_and_mul_kernel, silu_mul_split_kernel
72
+ from nbxtensor.ops.dtype_convert import bf16_to_fp16_kernel
73
+ from nbxtensor.ops.embedding import embedding_kernel, EMBEDDING_OOB
74
+ from nbxtensor.ops.sum import sum_forward_kernel
75
+ from nbxtensor.ops.mean import mean_forward_kernel
76
+ from nbxtensor.ops.amax import amax_forward_kernel
77
+ from nbxtensor.ops.glu import glu_forward_kernel
78
+ from nbxtensor.ops.le import le_forward_kernel, le_scalar_kernel
79
+ from nbxtensor.ops.softplus import softplus_forward_kernel
80
+ from nbxtensor.ops.dropout import dropout_inference_kernel
81
+ from nbxtensor.ops.upsample_nearest2d import upsample_nearest2d_kernel
82
+ from nbxtensor.ops.groupnorm import group_norm_forward_kernel
83
+ from nbxtensor.ops.index_select import index_select_kernel, INDEX_SELECT_OOB
84
+ from nbxtensor.ops.triu import triu_kernel, triu_batch_kernel
85
+ from nbxtensor.ops.tril import tril_kernel, tril_batch_kernel
86
+ from nbxtensor.ops.argmax import argmax_kernel_1, argmax_kernel_2, argmax_kernel_inner
87
+ from nbxtensor.ops.argmin import argmin_kernel_1, argmin_kernel_2, argmin_kernel
88
+ from nbxtensor.ops.conv2d import conv2d_forward_kernel
89
+ from nbxtensor.ops.depthwise_conv2d import depthwise_conv2d_kernel
90
+ from nbxtensor.ops.batch_norm import batch_norm_forward_kernel
91
+ from nbxtensor.ops.cumsum import scan_part_sum_kernel, add_base_sum_kernel
92
+ from nbxtensor.ops.scatter_op import scatter_kernel, scatter_add_kernel, scatter_reduce_amax_kernel, scatter_reduce_amin_kernel
93
+ from nbxtensor.ops.gather_op import gather_kernel
94
+ from nbxtensor.ops.avg_pool2d import avg_pool2d_forward_kernel
95
+ from nbxtensor.ops.max_pool2d import max_pool2d_forward_kernel
96
+ from nbxtensor.ops.max_pool3d import max_pool3d_forward_kernel
97
+ from nbxtensor.ops.cross_entropy import cross_entropy_loss_forward_kernel
98
+ from nbxtensor.ops.exp2 import exp2_forward_kernel
99
+ from nbxtensor.ops.tan import tan_forward_kernel
100
+ from nbxtensor.ops.celu import celu_forward_kernel
101
+ from nbxtensor.ops.log_sigmoid import log_sigmoid_forward_kernel
102
+ from nbxtensor.ops.isfinite import isfinite_forward_kernel
103
+ from nbxtensor.ops.isinf import isinf_forward_kernel
104
+ from nbxtensor.ops.isnan import isnan_forward_kernel
105
+ from nbxtensor.ops.nan_to_num import nan_to_num_forward_kernel
106
+ from nbxtensor.ops.threshold import threshold_forward_kernel
107
+ from nbxtensor.ops.floor import floor_forward_kernel, ceil_forward_kernel, round_forward_kernel, trunc_forward_kernel
108
+ from nbxtensor.ops.addcdiv import addcdiv_forward_kernel
109
+ from nbxtensor.ops.addcmul import addcmul_forward_kernel
110
+ from nbxtensor.ops.all_reduce import all_kernel_mid, all_kernel_result, all_kernel_dim
111
+ from nbxtensor.ops.any_reduce import any_kernel_mid, any_kernel_result, any_kernel_dim
112
+ from nbxtensor.ops.bitwise_and import bitwise_and_forward_kernel
113
+ from nbxtensor.ops.bitwise_or import bitwise_or_forward_kernel
114
+ from nbxtensor.ops.bitwise_not import bitwise_not_forward_kernel
115
+ from nbxtensor.ops.logical_and import logical_and_forward_kernel
116
+ from nbxtensor.ops.logical_or import logical_or_forward_kernel
117
+ from nbxtensor.ops.logical_not import logical_not_forward_kernel
118
+ from nbxtensor.ops.lerp import lerp_tensor_forward_kernel, lerp_scalar_head_kernel, lerp_scalar_tail_kernel
119
+ from nbxtensor.ops.dot_op import dot_kernel_small, dot_kernel_partial, dot_kernel_reduce
120
+ from nbxtensor.ops.flip_op import flip_1d_kernel, flip_strided_kernel
121
+ from nbxtensor.ops.cat_op import cat_copy_kernel_4
122
+ from nbxtensor.ops.fill_op import fill_kernel
123
+ from nbxtensor.ops.arange_op import arange_kernel
124
+ from nbxtensor.ops.prod import prod_kernel_mid, prod_kernel_result, prod_kernel
125
+ from nbxtensor.ops.min_reduce import min_kernel_mid, min_kernel_result, min_kernel
126
+ from nbxtensor.ops.max_reduce import max_kernel_mid, max_kernel_result, max_kernel
127
+ from nbxtensor.ops.baddbmm_op import baddbmm_kernel
128
+ from nbxtensor.ops.addmv_op import addmv_kernel
129
+ from nbxtensor.ops.mse_loss import mse_loss_partial_kernel, mse_loss_reduce_kernel, mse_loss_none_kernel
130
+ from nbxtensor.ops.nllloss import nll_loss_forward_kernel
131
+ from nbxtensor.ops.std import std_map_kernel, std_reduce_kernel, std_dim_kernel
132
+ from nbxtensor.ops.var import var_kernel_1, var_kernel_2, var_welford_kernel
133
+ from nbxtensor.ops.index_add import index_add_gather_kernel
134
+ from nbxtensor.ops.index_put_op import index_put_kernel, INDEX_PUT_OOB
135
+ from nbxtensor.ops.sort_op import (radix_sort_histogram_kernel,
136
+ radix_sort_scatter_kernel,
137
+ radix_sort_tile_counts_kernel,
138
+ radix_sort_tile_prefix_kernel)
139
+ from nbxtensor.ops.rope import rope_forward_kernel
140
+ from nbxtensor.ops.pixel_shuffle import (
141
+ pixel_shuffle_kernel,
142
+ pixel_shuffle_broadcast_aware_kernel,
143
+ )
144
+ from nbxtensor.ops.pixel_unshuffle import pixel_unshuffle_kernel
145
+ from nbxtensor.ops.upsample_bilinear2d import upsample_bilinear2d_kernel
146
+ from nbxtensor.ops.adaptive_avg_pool2d import adaptive_avg_pool2d_kernel
147
+ from nbxtensor.ops.conv_depthwise2d import conv_depthwise2d_kernel
148
+ from nbxtensor.ops.scaled_softmax import scaled_softmax_kernel
149
+ from nbxtensor.ops.vector_norm import (
150
+ l2_norm_kernel, l1_norm_kernel, linf_norm_kernel, l0_norm_kernel, lp_norm_kernel,
151
+ l2_norm_pass1_kernel, l2_norm_pass2_kernel,
152
+ linf_norm_pass1_kernel, linf_norm_pass2_kernel,
153
+ )
154
+ from nbxtensor.ops.addr import addr_kernel
155
+ from nbxtensor.ops.logical_xor import logical_xor_kernel
156
+ from nbxtensor.ops.bitwise_left_shift import bitwise_left_shift_kernel
157
+ from nbxtensor.ops.bitwise_right_shift import bitwise_right_shift_kernel
158
+ from nbxtensor.ops.rand_op import rand_kernel, randn_kernel
159
+ from nbxtensor.ops.clamp_min import clamp_min_forward_kernel
160
+ from nbxtensor.ops.conv_transpose2d import conv_transpose2d_kernel
161
+ from nbxtensor.ops.cummax import scan_part_max_kernel
162
+ from nbxtensor.ops.cummin import scan_part_min_kernel
163
+ from nbxtensor.ops.count_nonzero import count_nonzero_kernel
164
+ from nbxtensor.ops.trace_op import trace_kernel
165
+ from nbxtensor.ops.linspace_op import linspace_kernel
166
+ from nbxtensor.ops.full_op import full_kernel
167
+ from nbxtensor.ops.stack_op import stack_copy_kernel
168
+ from nbxtensor.ops.repeat_op import repeat_1d_kernel
169
+ from nbxtensor.ops.upsample_nearest3d import upsample_nearest3d_kernel
170
+ from nbxtensor.ops.weight_norm import weight_norm_kernel_first, weight_norm_kernel_last
171
+ from nbxtensor.ops.repeat_interleave import repeat_interleave_tensor_kernel
172
+ from nbxtensor.ops.elementwise_launch import ew_meta as _ew_meta, ew_grid as _ew_grid
173
+ from nbxtensor.ops import tiling_sizes as _ts
174
+ import math
175
+ from nbxtensor.ops import elementwise_wrappers as _ew
176
+ from nbxtensor.ops import index_wrappers as _idx
177
+ from nbxtensor.ops import shape_wrappers as _shp
178
+ from nbxtensor.ops import wrappers as _core
179
+ _POOL_BH, _POOL_BW = 4, 16 # avg_pool2d / max_pool2d
180
+
181
+ # P-TRITON-LIVE-SET-AUDIT (#37) — conv3d-via-conv2d eager-free threshold.
182
+ # The temporal decomposition in _conv3d_via_conv2d materialises several full-
183
+ # resolution copies per temporal-kernel slice (input contiguous-copy, conv2d
184
+ # output, permuted output, accumulator). For large video VAE conv3d (CogVideoX
185
+ # 5D decode at (1,256,13,480,720) = 4.4 GB per copy in fp32), 5-6 of these are
186
+ # live simultaneously → 26-30 GB → single-GPU OOM at aten.convolution. When a
187
+ # per-slice intermediate is at least this many bytes, it is dropped the moment
188
+ # it is consumed so the live set falls to input + accumulator + one transient.
189
+ # free_cuda is cudaFree, which blocks until the device is idle for the relevant
190
+ # stream, so dropping a tensor whose producing/consuming kernel just ran is
191
+ # async-safe (no UAF). GATED so small convs (Wan/Mochi/SANA at their validated
192
+ # configs) keep the EXACT prior code path — no relocated free, byte- and
193
+ # timing-identical. Numerics are unchanged (frees sooner only). 1 GiB default
194
+ # fires only on the multi-GB video-VAE convs.
195
+ _NBX_CONV3D_EAGER_FREE_BYTES = int(
196
+ os.environ.get("NBX_CONV3D_EAGER_FREE_BYTES", str(1 * 1024 * 1024 * 1024)))
197
+
198
+ # Conv3d temporal chunk-streaming — kernel-level tiling INSIDE the wrapper,
199
+ # same lever as the conv2d band-streaming above (P-SANA-4KPX Step 1 class).
200
+ # Even with the #37 eager frees, the one-shot temporal decomposition needs
201
+ # several FULL folded transients simultaneously (temporal pad copy, x2 fold,
202
+ # conv2d output incl. band-streaming machinery, permuted copy, accumulator +
203
+ # accumulate transient) — ~4-5x the activation bytes. cuDNN in compiled mode
204
+ # needs input + output + a bounded workspace for the same op, so a component
205
+ # Prism correctly sized as "fits" for compiled can OOM in triton only
206
+ # (Allegro-TI2V vae_encoder at 720x1280x12f fp32: 5.66 GB per plane →
207
+ # 30.4 GB live at aten.convolution::1 on a 32 GB V100). Gate, two stages:
208
+ # 1. deterministic floor — the folded transient must exceed
209
+ # `tiling_sizes.conv3d_chunk_bytes()` (`conv3d.chunk_bytes`), so
210
+ # small/validated convs never even query the driver;
211
+ # 2. exact-fit check — the one-shot path's real allocation peak is
212
+ # compared to driver-free bytes; when it fits, the EXACT one-shot
213
+ # path runs (byte-identical behavior for every config that is green
214
+ # today, on any hardware — R23), when it cannot fit (guaranteed OOM)
215
+ # the wrapper streams over temporal output chunks whose per-transient
216
+ # size is bounded by this same value. (Stage 2 is now Prism's PLAN
217
+ # decision, `tiling_sizes.conv3d_need` against the op budget.)
218
+ # Chunking splits the folded batch axis (B*T frames): spatial conv2d is
219
+ # independent per frame and the per-frame kt accumulation order is
220
+ # preserved, so the math is unchanged.
221
+
222
+ # Diagnostic trace — when set, every conv2d_wrapper call prints the
223
+ # (in_shape, out_shape, kernel, groups, output_MB) so we can see the actual
224
+ # spatial workload arriving in triton mode. Used by P-SANA-4KPX-RUNTIME
225
+ # Step 1 verification on Sana 4Kpx (was the wrapper-internal band-streaming
226
+ # triggered? what shapes are dominant?).
227
+ _NBX_CONV2D_TRACE = os.environ.get("NBX_CONV2D_TRACE", "0") == "1"
228
+
229
+
230
+ def _conv_width_key(in_h, out_h, in_w, out_w, kw, stride_w, pad_w, dil_w):
231
+ """The width a convolution's autotune key carries. A convolution whose spatial extent is
232
+ ONE ROW is a 1-D convolution over a sequence — a vocoder's frames, a mel spectrogram's
233
+ time axis — and that extent is the request's (a speech of 1 716 tokens and one of 1 856
234
+ gave chatterbox's vocoder two different exact keys for every layer, 2026-09-21): it keys
235
+ on the profile's ladder like a GEMM's M, the output width derived from the input's top by
236
+ the convolution's own arithmetic so a certifier synthesising at the top forms the same
237
+ key. A 2-D convolution keeps its exact extents (bounded by resolutions and tile edges;
238
+ the ladder measured 37–40 % loss where the conv optimum flips)."""
239
+ return _lk.conv_width_key(in_h, out_h, in_w, out_w, kw, stride_w, pad_w, dil_w)
240
+
241
+
242
+ def conv_transpose_wrapper(
243
+ x, weight, bias=None,
244
+ stride=1, padding=0, dilation=1, output_padding=0, groups=1,
245
+ ):
246
+ """Transposed convolution — aten::convolution(transposed=True).
247
+
248
+ Handles 1D (3D tensors, via H=1 unsqueeze) and 2D (4D tensors). Weight layout
249
+ matches PyTorch ConvTranspose: (C_in, C_out/groups, *K). Scatter-based
250
+ @triton.jit kernel (kernels/ops/conv_transpose2d.py), fp32 accumulation.
251
+ R33-pure: NBXTensor + Triton only.
252
+
253
+ A regular strided conv DOWNSAMPLES (out = in // stride); a transposed conv
254
+ UPSAMPLES: out = (in-1)*stride - 2*pad + dil*(k-1) + output_padding + 1. The
255
+ Kokoro F0/N ProsodyPredictor pool is a depthwise ConvTranspose1d (stride 2)
256
+ that doubles the frame axis (62->124). conv2d_wrapper routes here when
257
+ transposed=True (which it previously dropped when delegating 1D convs).
258
+ """
259
+ is_1d = (weight.ndim == 3)
260
+ _d = lambda v: v[0] if isinstance(v, (list, tuple)) else v # noqa: E731
261
+ if is_1d and groups == 1 and _d(dilation) == 1 and _d(output_padding) == 0:
262
+ return _conv_transpose1d_gemm(x, weight, bias, _d(stride), _d(padding))
263
+ if is_1d:
264
+ x = x.unsqueeze(2) # [N, C_in, 1, L]
265
+ weight = weight.unsqueeze(2) # [C_in, C_out/g, 1, K]
266
+ def _s(v):
267
+ return v[0] if isinstance(v, (list, tuple)) else v
268
+ sh, sw = 1, _s(stride)
269
+ ph, pw = 0, _s(padding)
270
+ dh, dw = 1, _s(dilation)
271
+ oph, opw = 0, _s(output_padding)
272
+ else:
273
+ def _hw(v):
274
+ if isinstance(v, (list, tuple)):
275
+ return (v[0], v[1] if len(v) > 1 else v[0])
276
+ return (v, v)
277
+ sh, sw = _hw(stride); ph, pw = _hw(padding)
278
+ dh, dw = _hw(dilation); oph, opw = _hw(output_padding)
279
+
280
+ # dtype alignment NARROWING (mirror conv2d_wrapper; fp32 accum in-kernel)
281
+ x_nbx = x.nbx_dtype if hasattr(x, 'nbx_dtype') else x.dtype
282
+ w_nbx = weight.nbx_dtype if hasattr(weight, 'nbx_dtype') else weight.dtype
283
+ if x_nbx != w_nbx:
284
+ _order = (NBXDtype.float16, NBXDtype.bfloat16, NBXDtype.float32)
285
+ narrowest = next(d for d in _order if d in (x_nbx, w_nbx))
286
+ if x_nbx != narrowest:
287
+ x = x.to(narrowest)
288
+ if w_nbx != narrowest:
289
+ weight = weight.to(narrowest)
290
+
291
+ x_c = x.contiguous()
292
+ w_c = weight.contiguous()
293
+ out_dtype = _core._NBX_COMPUTE_DTYPE if _core._NBX_COMPUTE_DTYPE is not None else x.dtype
294
+
295
+ N, C_in, IH, IW = x_c.shape
296
+ _, C_out_per_g, KH, KW = w_c.shape
297
+ C_out = C_out_per_g * groups
298
+ C_in_per_g = C_in // groups
299
+ OH = (IH - 1) * sh - 2 * ph + dh * (KH - 1) + oph + 1
300
+ OW = (IW - 1) * sw - 2 * pw + dw * (KW - 1) + opw + 1
301
+
302
+ output = NBXTensor.empty((N, C_out, OH, OW), device=x_c.device, dtype=out_dtype)
303
+ set_device(x_c)
304
+ BLOCK = 256
305
+ grid = (N * C_out, triton.cdiv(OH * OW, BLOCK))
306
+ conv_transpose2d_kernel[grid](
307
+ x_c, w_c, output,
308
+ N, C_in, IH, IW, C_out, KH, KW, OH, OW,
309
+ sh, sw, ph, pw, dh, dw, C_in_per_g, C_out_per_g,
310
+ BLOCK_SIZE=BLOCK,
311
+ )
312
+ if bias is not None:
313
+ output = _conv_bias_inplace(output, bias)
314
+ if is_1d:
315
+ output = output.squeeze(2)
316
+ return output
317
+
318
+
319
+ def _conv_transpose1d_gemm(x, weight, bias, stride, padding):
320
+ """A 1-D transposed convolution (groups 1, dilation 1) as a GEMM and an overlap-add: Y = x^T [L_in, C_in] @
321
+ W [C_in, C_out * K] through `mm` (the matrix unit where the profile declares one) — the length on the GEMM's rows,
322
+ the dimension its key classes (the length on its columns gave one key per length: 6 135 on chatterbox, the Mac
323
+ 2026-11-03) — then `col2im1d_kernel` gathers each output position's ceil(K / stride) taps. The scatter kernel looped over every input channel and tap per output
324
+ element: the vocoders' upsamplers on a V100 ran 20-307x slower than this (768 -> 384 channels, K 16, stride 8:
325
+ 103 ms against 0.34 ms; torch eager 0.21 ms — the kernel-level benchmark, 2026-11-03). Same narrowing and output
326
+ dtype as `conv_transpose_wrapper`; `launch_keys.conv_launches` derives the GEMM's keys."""
327
+ from nbxtensor.ops.linalg_wrappers import mm
328
+ from nbxtensor.ops.conv_transpose2d import col2im1d_kernel
329
+ x_nbx = x.nbx_dtype if hasattr(x, 'nbx_dtype') else x.dtype
330
+ w_nbx = weight.nbx_dtype if hasattr(weight, 'nbx_dtype') else weight.dtype
331
+ if x_nbx != w_nbx:
332
+ _order = (NBXDtype.float16, NBXDtype.bfloat16, NBXDtype.float32)
333
+ narrowest = next(d for d in _order if d in (x_nbx, w_nbx))
334
+ x = x.to(narrowest) if x_nbx != narrowest else x
335
+ weight = weight.to(narrowest) if w_nbx != narrowest else weight
336
+ N, C_in, L_in = x.shape
337
+ _, C_out, K = weight.shape
338
+ L_out = (L_in - 1) * stride - 2 * padding + K
339
+ out_dtype = _core._NBX_COMPUTE_DTYPE if _core._NBX_COMPUTE_DTYPE is not None else x.dtype
340
+ output = NBXTensor.empty((N, C_out, L_out), device=x.device, dtype=out_dtype)
341
+ w_2d = weight.contiguous().reshape(C_in, C_out * K) # [C_in, C_out * K]
342
+ x_c = x.contiguous()
343
+ taps = -(-K // stride)
344
+ meta = _ew_meta(C_out * L_out) # a gather of TAPS loads per element: the element-wise class's certified tile
345
+ for n in range(N):
346
+ y = mm(x_c[n].t().contiguous(), w_2d) # [L_in, C_out * K]
347
+ set_device(y)
348
+ col2im1d_kernel[(C_out, triton.cdiv(L_out, meta['BLOCK_SIZE']))](
349
+ y, output[n], C_out, K, L_in, L_out, stride, padding, TAPS=taps, **meta)
350
+ if bias is not None:
351
+ output = _conv_bias_inplace(output.unsqueeze(2), bias).squeeze(2)
352
+ return output
353
+
354
+
355
+ def _conv3d_via_conv2d(x, weight, bias, stride, padding, dilation, groups):
356
+ """conv3d via temporal decomposition: conv3d(x, w) = sum over the temporal
357
+ kernel positions kt of conv2d on the kt-th temporally-shifted frames. The
358
+ spatial (H, W) convolution is delegated to conv2d_wrapper (this same triton
359
+ kernel) per temporal slice; the temporal axis is handled by slicing +
360
+ accumulation. Validated bit-close to torch F.conv3d (max|diff| ~3e-5) across
361
+ kt=3 / patch-embed kt=1 / strided / temporal-only configs. R33-pure
362
+ (NBXTensor slice/permute/reshape/add + conv2d_wrapper, no torch).
363
+ """
364
+ def _triple(v):
365
+ return (v[0], v[1], v[2]) if isinstance(v, (list, tuple)) else (v, v, v)
366
+ st, sh, sw = _triple(stride)
367
+ pt, ph, pw = _triple(padding)
368
+ dt, dh, dw = _triple(dilation)
369
+ B, Cin, T, H, W = x.shape
370
+ Cout, Cin_g, kt, kh, kw = weight.shape
371
+ _t_out = (T + 2 * pt - dt * (kt - 1) - 1) // st + 1
372
+ if _t_out <= 0:
373
+ from nbxtensor.memory.allocator import ImpossibleExtentError
374
+ raise ImpossibleExtentError(
375
+ f"convolution produces no frame: input {tuple(x.shape)}, weight "
376
+ f"{tuple(weight.shape)}, temporal stride {st}, padding {pt}, dilation {dt} -> "
377
+ f"T_out={_t_out}")
378
+
379
+ # The chunked variant is a PLAN decision (`ops/tiling_sizes.py`; the supervisor's
380
+ # decision 2, 2026-09-29): Prism registers `conv3d_chunked_wrapper` as this op's interceptor
381
+ # when the one-shot peak exceeds the op's planned budget. It read the driver's free bytes here,
382
+ # so a key a live run formed depended on the memory free at that instant.
383
+ if pt > 0:
384
+ x = _shp.constant_pad_nd_wrapper(x, [0, 0, 0, 0, pt, pt], 0.0)
385
+ Tp = x.shape[2]
386
+ T_out = (Tp - dt * (kt - 1) - 1) // st + 1
387
+ if st == 1:
388
+ # Every temporal tap read and written IN PLACE: the tap's frames x[b, :, start:start+T_out] permuted to
389
+ # [T_out, Cin, H, W] are a strided view the conv kernel reads through its strides, and its result goes through
390
+ # a [T_out, Cout, Ho, Wo] view of the [B, Cout, T_out, Ho, Wo] output. The two permute copies per tap were 65 %
391
+ # of a VAE conv3d (1,128,9,242,362: 525 of 810 ms; 9 ms each once the copy kernel indexed in 32 bits, Mac
392
+ # 2026-10-21). The first tap writes the output; each later one writes a tap buffer added to it.
393
+ Ho = (H + 2 * ph - dh * (kh - 1) - 1) // sh + 1
394
+ Wo = (W + 2 * pw - dw * (kw - 1) - 1) // sw + 1
395
+ out_dt = _core._NBX_COMPUTE_DTYPE if _core._NBX_COMPUTE_DTYPE is not None else x.dtype
396
+ out = None
397
+ for kti in range(kt):
398
+ start = kti * dt
399
+ w2 = weight[:, :, kti:kti + 1].contiguous().reshape(Cout, Cin_g, kh, kw)
400
+ tap = NBXTensor.empty((B, Cout, T_out, Ho, Wo), device=x.device, dtype=out_dt)
401
+ for b in range(B):
402
+ x2 = x[b][:, start:start + T_out].permute(1, 0, 2, 3)
403
+ conv2d_wrapper(x2, w2, None, (sh, sw), (ph, pw), (dh, dw), False, 0, groups,
404
+ out=tap[b].permute(1, 0, 2, 3))
405
+ # each later tap added INTO the sum (the same add kernel, the same order: the same bits): `out + tap` held
406
+ # the old sum, the tap and the new sum at once — three output-sized buffers where the plan priced one
407
+ # (mochi-1-preview's VAE at the 8 GB rung, 1.5 GB each, 2026-10-28)
408
+ out = tap if out is None else _ew.add_inplace_nbx(out, tap)
409
+ tap = None
410
+ if bias is not None:
411
+ out = out + bias.reshape(1, Cout, 1, 1, 1)
412
+ return out
413
+
414
+ out = None
415
+ _ef = _NBX_CONV3D_EAGER_FREE_BYTES # #37 size-gated eager-free threshold
416
+ for kti in range(kt):
417
+ start = kti * dt
418
+ if st == 1:
419
+ xs = x[:, :, start:start + T_out] # [B,Cin,T_out,H,W]
420
+ else:
421
+ import numpy as np
422
+ idx = NBXTensor.from_numpy(
423
+ (start + st * np.arange(T_out)).astype(np.int64))
424
+ xs = _idx.index_select_wrapper(x, 2, idx)
425
+ x2 = xs.permute(0, 2, 1, 3, 4).contiguous().reshape(B * T_out, Cin, H, W)
426
+ # .contiguous() before reshape: weight[:, :, kti:kti+1] is a non-contiguous
427
+ # dim-2 slice; a flat-indexed reshape would read wrong memory for kti>0
428
+ # (contiguous-guard pattern).
429
+ w2 = weight[:, :, kti:kti + 1].contiguous().reshape(Cout, Cin_g, kh, kw)
430
+ y2 = conv2d_wrapper(x2, w2, None, (sh, sw), (ph, pw), (dh, dw),
431
+ False, 0, groups)
432
+ # #37: conv2d has produced y2, so the input contiguous-copy x2 is dead.
433
+ # Drop it now for large (video-VAE) slices so the live set does not
434
+ # accumulate input + x2 + y2 + y + out simultaneously (CogVideoX 5D
435
+ # decode OOM at aten.convolution). Gated → small convs keep the exact
436
+ # prior path. cudaFree blocks until the conv2d kernel is idle (async-safe).
437
+ if x2._nbytes >= _ef:
438
+ x2 = None
439
+ Ho, Wo = y2.shape[2], y2.shape[3]
440
+ y = y2.reshape(B, T_out, Cout, Ho, Wo).permute(0, 2, 1, 3, 4).contiguous()
441
+ if y2._nbytes >= _ef:
442
+ y2 = None # conv output pre-permute copy is dead once y is materialised
443
+ if out is None:
444
+ out = y
445
+ else:
446
+ out = out + y # old accumulator auto-frees on reassignment
447
+ if y._nbytes >= _ef:
448
+ y = None # addend is dead once out+y is allocated
449
+ if bias is not None:
450
+ out = out + bias.reshape(1, Cout, 1, 1, 1)
451
+ return out
452
+
453
+
454
+ def conv3d_chunked_wrapper(x, weight, bias=None, stride=1, padding=0, dilation=1,
455
+ transposed=False, output_padding=0, groups=1, *args, **kwargs):
456
+ """A 3-D convolution Prism planned to chunk (op-level tiling, `conv3d_chunks`): the chunked
457
+ path, the chunk sized from the folded bytes per output frame (`tiling_sizes.chunk_frames`)."""
458
+ def _triple(v):
459
+ return (v[0], v[1], v[2]) if isinstance(v, (list, tuple)) else (v, v, v)
460
+ st, sh, sw = _triple(stride)
461
+ pt, ph, pw = _triple(padding)
462
+ dt, dh, dw = _triple(dilation)
463
+ ds_in = dtype_size(x.nbx_dtype if hasattr(x, 'nbx_dtype') else x._dtype)
464
+ ds_out = dtype_size(_core._NBX_COMPUTE_DTYPE) if _core._NBX_COMPUTE_DTYPE is not None else ds_in
465
+ _need, frame = _ts.conv3d_need(x.shape, weight.shape, (st, sh, sw), (pt, ph, pw), (dt, dh, dw),
466
+ ds_in, ds_out)
467
+ return _conv3d_via_conv2d_chunked(x, weight, bias, st, sh, sw, pt, ph, pw, dt, dh, dw, groups, frame)
468
+
469
+
470
+ def _conv3d_via_conv2d_chunked(x, weight, bias, st, sh, sw, pt, ph, pw,
471
+ dt, dh, dw, groups, frame_bytes):
472
+ """Temporal chunk-streaming variant of _conv3d_via_conv2d — identical
473
+ math, bounded live set (`tiling_sizes.conv3d_need` is the gate).
474
+
475
+ Streams the folded batch axis (B*T_out frames) in chunks sized so each
476
+ transient stays under `tiling_sizes.conv3d_chunk_bytes()`. Per output frame the
477
+ spatial conv2d is independent and the kt accumulation runs in the same
478
+ kti order with the same operand values as the one-shot path, so the
479
+ result is numerically equivalent (launch shapes differ, which may pick
480
+ different autotune configs — same class of variation as the conv2d
481
+ band-streaming). Live set: temporal pad copy + final 5D output +
482
+ ~3 chunk-sized transients, vs ~4-5 full folds for the one-shot path.
483
+ R33-pure (NBXTensor slice/permute/reshape/add + conv2d_wrapper +
484
+ strided-scatter __setitem__, no torch).
485
+
486
+ `frame_bytes` = max folded bytes per output frame (input-side fold vs
487
+ conv2d output), computed by the caller's gate.
488
+ """
489
+ B, Cin, T, H, W = x.shape
490
+ Cout, Cin_g, kt, kh, kw = weight.shape
491
+ if pt > 0:
492
+ x = _shp.constant_pad_nd_wrapper(x, [0, 0, 0, 0, pt, pt], 0.0)
493
+ Tp = x.shape[2]
494
+ T_out = (Tp - dt * (kt - 1) - 1) // st + 1
495
+ tc = _ts.chunk_frames(frame_bytes)
496
+ # Per-kt 2D weight slices, materialised once (tiny; contiguous-guard on
497
+ # the non-contiguous dim-2 slice, same as the one-shot path).
498
+ w2s = [weight[:, :, kti:kti + 1].contiguous().reshape(Cout, Cin_g, kh, kw)
499
+ for kti in range(kt)]
500
+ bias4 = bias.reshape(1, Cout, 1, 1) if bias is not None else None
501
+ out = None
502
+ for t0 in range(0, T_out, tc):
503
+ n = min(tc, T_out - t0)
504
+ acc = None
505
+ for kti in range(kt):
506
+ start = t0 * st + kti * dt
507
+ if st == 1:
508
+ xs = x[:, :, start:start + n] # [B,Cin,n,H,W] view
509
+ else:
510
+ import numpy as np
511
+ idx = NBXTensor.from_numpy(
512
+ (start + st * np.arange(n)).astype(np.int64))
513
+ xs = _idx.index_select_wrapper(x, 2, idx)
514
+ x2 = xs.permute(0, 2, 1, 3, 4).contiguous().reshape(
515
+ B * n, Cin, H, W)
516
+ y2 = conv2d_wrapper(x2, w2s[kti], None, (sh, sw), (ph, pw),
517
+ (dh, dw), False, 0, groups)
518
+ x2 = None # fold copy dead once conv2d returned
519
+ acc = y2 if acc is None else acc + y2
520
+ y2 = None
521
+ if bias4 is not None:
522
+ # Elementwise-identical to the one-shot path's 5D bias add
523
+ # (broadcast over the folded batch axis), chunk-sized transient.
524
+ acc = acc + bias4
525
+ Ho, Wo = acc.shape[2], acc.shape[3]
526
+ y = acc.reshape(B, n, Cout, Ho, Wo).permute(0, 2, 1, 3, 4).contiguous()
527
+ acc = None
528
+ if out is None:
529
+ out_dt = y.nbx_dtype if hasattr(y, 'nbx_dtype') else y.dtype
530
+ out = NBXTensor.empty((B, Cout, T_out, Ho, Wo),
531
+ device=y.device, dtype=out_dt)
532
+ out[:, :, t0:t0 + n] = y # strided-scatter into the temporal slice
533
+ y = None
534
+ return out
535
+
536
+
537
+ def _conv_bias_inplace(output, bias):
538
+ """Per-channel conv bias, IN PLACE on the wrapper's own fresh output.
539
+
540
+ Every conv-family wrapper ended with `add(output, bias.view(1,-1,1,1))`
541
+ — an out-of-place broadcast allocating a SECOND full-size map per
542
+ biased convolution. Measured cost: 2450 MB of the 8008 MB tile peak on
543
+ real-esrgan-x8 @1024 (malloc trace, 2026-09-21). The in-place contract
544
+ holds by construction at all four call sites: `output` is the
545
+ wrapper's own fresh contiguous NCHW allocation with no other consumer.
546
+ The bias is cast to the output's dtype (the standard add's wider-dtype
547
+ rule would upcast the full map instead — the very copy this removes).
548
+ Fixed launch config: no autotune key, nothing for any census.
549
+ """
550
+ from nbxtensor.ops.conv2d import conv2d_bias_inplace_kernel
551
+ bias_c = bias if bias._dtype == output._dtype else bias.to(output._dtype)
552
+ n = output.numel()
553
+ hw = int(output.shape[-2]) * int(output.shape[-1])
554
+ c = int(output.shape[1])
555
+ set_device(output)
556
+ _m = _ew_meta(n)
557
+ conv2d_bias_inplace_kernel[_ew_grid(n, _m)](
558
+ output, bias_c.contiguous(), n, hw, c, BLOCK=_m['BLOCK_SIZE'], num_warps=_m['num_warps'])
559
+ return output
560
+
561
+
562
+ def _conv_offsets_wide(*tensors) -> bool:
563
+ """64-bit offsets in the convolution kernel when an operand's reach (its last element's offset, over its strides)
564
+ is within a factor of two of 2^31: a masked lane computes an address past the extent (the grid's last block,
565
+ the padding rows) before its mask drops it, and the factor covers that overshoot. Below it 32-bit offsets are
566
+ exact and faster (17 % on Apple's ALUs, Mac 2026-10-20)."""
567
+ reach = max(sum((int(n) - 1) * int(st) for n, st in zip(t.shape, t.stride())) + 1 for t in tensors)
568
+ return reach >= (1 << 30)
569
+
570
+
571
+ def _conv_reads_strided(x) -> bool:
572
+ """True when the implicit-GEMM conv kernel can read `x` through its strides in place: four non-negative strides
573
+ with the width innermost (a permuted or sliced view of a channel-plane layout). Anything else is copied."""
574
+ st = x.stride()
575
+ return len(st) == 4 and all(int(v) >= 0 for v in st) and (int(st[3]) == 1 or int(x.shape[3]) == 1)
576
+
577
+
578
+ def conv2d_wrapper(
579
+ x, weight, bias=None,
580
+ stride=1, padding=0, dilation=1,
581
+ transposed=False, output_padding=0, groups=1, out=None,
582
+ ) :
583
+ """Convolution forward. Handles both 1D (3D tensors) and 2D (4D tensors).
584
+ Routes to conv1d_wrapper for 3D inputs, and to conv_transpose_wrapper when
585
+ transposed=True (1D or 2D).
586
+
587
+ Spatial band-streaming (P-SANA-4KPX-RUNTIME Step 1): when the output
588
+ tensor would exceed `tiling_sizes.conv2d_band_bytes()` (`conv2d.band_bytes`), the wrapper
589
+ splits along the H output dimension and streams band-by-band. Each band
590
+ re-enters this same wrapper with a smaller H, so the recursion bottoms
591
+ out automatically. The full output stays allocated for downstream
592
+ consumers."""
593
+ # Transposed convolutions (1D or 2D) — route BEFORE the 1D delegation, which
594
+ # historically dropped the transposed/output_padding flags (Kokoro F0/N
595
+ # ConvTranspose1d was computed as a regular strided conv → halved instead of
596
+ # doubled the frame axis).
597
+ if transposed:
598
+ return conv_transpose_wrapper(x, weight, bias, stride, padding, dilation,
599
+ output_padding, groups)
600
+
601
+ # Route 1D convolutions to conv1d_wrapper
602
+ if weight.ndim == 3:
603
+ return conv1d_wrapper(x, weight, bias, stride, padding, dilation, groups)
604
+
605
+ # 5D (video) conv3d -> temporal decomposition into conv2d (R33-pure: reuses
606
+ # this same conv2d kernel per temporal-kernel slice). Wan video models use
607
+ # Conv3D (transformer patch_embed + VAE temporal convs).
608
+ if weight.ndim == 5:
609
+ return _conv3d_via_conv2d(x, weight, bias, stride, padding, dilation, groups)
610
+
611
+ assert weight.ndim == 4
612
+
613
+ # ATen broadcasts single-element stride/padding/dilation lists over
614
+ # both spatial dims (padding="valid" convs record [0] — the
615
+ # MiniCPM-o NaViT patch_embedding hit exactly that as an
616
+ # IndexError here; same contract as the tracer-side estimator).
617
+ if isinstance(stride, (list, tuple)):
618
+ stride_h = stride[0]
619
+ stride_w = stride[1] if len(stride) > 1 else stride_h
620
+ else:
621
+ stride_h = stride_w = stride
622
+
623
+ if isinstance(padding, (list, tuple)):
624
+ pad_h = padding[0] if len(padding) > 0 else 0
625
+ pad_w = padding[1] if len(padding) > 1 else pad_h
626
+ else:
627
+ pad_h = pad_w = padding
628
+
629
+ if isinstance(dilation, (list, tuple)):
630
+ dil_h = dilation[0]
631
+ dil_w = dilation[1] if len(dilation) > 1 else dil_h
632
+ else:
633
+ dil_h = dil_w = dilation
634
+
635
+ N, in_c, in_h, in_w = x.shape
636
+ out_c, _, kh, kw = weight.shape
637
+ out_h = (in_h + 2 * pad_h - dil_h * (kh - 1) - 1) // stride_h + 1
638
+ out_w = (in_w + 2 * pad_w - dil_w * (kw - 1) - 1) // stride_w + 1
639
+ if out_h <= 0 or out_w <= 0:
640
+ from nbxtensor.memory.allocator import ImpossibleExtentError
641
+ raise ImpossibleExtentError(
642
+ f"convolution produces no output: input {tuple(x.shape)}, weight "
643
+ f"{tuple(weight.shape)}, stride ({stride_h}, {stride_w}), padding ({pad_h}, "
644
+ f"{pad_w}), dilation ({dil_h}, {dil_w}) -> out_h={out_h}, out_w={out_w}")
645
+
646
+ # Self-managed dtype doctrine — VRAM-preserving (different from the
647
+ # mm/bmm/addmm doctrine which is accumulation-overflow-protected).
648
+ #
649
+ # mm/bmm/addmm: deep matmul stacks risk fp16 accumulation overflow.
650
+ # Step 1 upcasts input fp16 → fp32 on pre-Ampere, output forced to
651
+ # fp32 (force_fp32 in _matmul_out_dtype). Precision target.
652
+ #
653
+ # conv2d: spatial activation chain (VAE/UNet). The kernel already
654
+ # accumulates in fp32 internally (conv2d.py line 47:
655
+ # `accum = tl.zeros(..., dtype=tl.float32)`), so there is no
656
+ # overflow risk requiring an input fp16→fp32 upcast. The risk here
657
+ # is VRAM (4096×4096 spatial tensors). We therefore:
658
+ # - skip Step 1 (no input upcast)
659
+ # - Step 2 narrows mismatched input/weight to the lowest-precision
660
+ # common dtype (opposite of mm widening — VRAM over precision)
661
+ # - Step 4 sets output dtype = compute_dtype from
662
+ # _NBX_COMPUTE_DTYPE (the per-component Prism dtype set by
663
+ # TritonSequence.run), mirroring what cuDNN does silently in
664
+ # compiled mode. Falls back to x.dtype if not set (run outside
665
+ # a TritonSequence context).
666
+ # Universal hardware: no _NBX_HAS_NATIVE_BF16 gate — same behavior on
667
+ # Volta and Ampere+ since it operates on dtype tags, not hardware
668
+ # capability. R33-pure: NBXTensor.to uses a @triton.jit copy kernel.
669
+
670
+ # Step 0: ensure cuda + cross-device alignment (identical to mm)
671
+ # (already implicit; weight transfer handled by upstream callers / arena)
672
+
673
+ # Step 2: dtype alignment NARROWING (opposite of mm widening)
674
+ x_nbx = x.nbx_dtype if hasattr(x, 'nbx_dtype') else x.dtype
675
+ w_nbx = weight.nbx_dtype if hasattr(weight, 'nbx_dtype') else weight.dtype
676
+ # `launch_keys.conv_dtypes`: the pair narrowed to the narrowest when they differ — the
677
+ # function the derived census keys with.
678
+ x_to, w_to = _lk.conv_dtypes(x_nbx, w_nbx)
679
+ if x_nbx != x_to:
680
+ x = x.to(x_to)
681
+ if w_nbx != w_to:
682
+ weight = weight.to(w_to)
683
+
684
+ # The implicit GEMM reads the input through its four strides, so a strided view goes in where it lies (a conv3d's
685
+ # temporal slice permuted to [T, C, H, W]: its copy was 85 ms of a 235 ms VAE conv3d, Mac 2026-10-21); the
686
+ # depthwise, band and matrix-unit routes take a contiguous copy, as before.
687
+ x_c = x.contiguous() if not x.is_contiguous() and not _conv_reads_strided(x) else x
688
+ # A weight the loader stored KRSC (`conv.weight_layout`) is read where it lies: the kernel
689
+ # takes the weight's four strides; copied back to KCRS it would lose what the layout gains.
690
+ w_c = weight if _lk.is_krsc_weight(weight) else weight.contiguous()
691
+
692
+ # Step 4: output dtype = compute_dtype from per-component Prism context.
693
+ # Falls back to x.dtype when no TritonSequence is active.
694
+ out_dtype = _core._NBX_COMPUTE_DTYPE if _core._NBX_COMPUTE_DTYPE is not None else x.dtype
695
+
696
+ # P-SANA-4KPX-RUNTIME Step 1 — kernel-level spatial band-streaming.
697
+ # When the output tensor would exceed the per-launch threshold (default
698
+ # 4 GiB), split along H and stream band-by-band. Halo handling mirrors
699
+ # tiled_conv2d: each band re-enters this same wrapper with
700
+ # a smaller H, so the recursion bottoms out on its own. The full output
701
+ # remains allocated for downstream consumers in the DAG.
702
+ # x.dtype returns a triton.language dtype; use x.nbx_dtype for the
703
+ # NBXDtype used by dtype_size().
704
+ out_nbx_dtype = x.nbx_dtype if hasattr(x, 'nbx_dtype') else x._dtype
705
+ out_dtype_bytes = dtype_size(out_nbx_dtype)
706
+ if _NBX_CONV2D_TRACE:
707
+ out_mb = N * out_c * out_h * out_w * out_dtype_bytes / 1024 / 1024
708
+ print(f"[CONV2D] in=({N},{in_c},{in_h},{in_w}) out=({N},{out_c},{out_h},{out_w}) "
709
+ f"k=({kh},{kw}) g={groups} out={out_mb:.1f}MB", flush=True)
710
+
711
+ # P-SANA-4KPX-RUNTIME Step 3 — depthwise specialization. The generic
712
+ # im2col conv2d_forward_kernel is structurally inefficient for the
713
+ # depthwise pattern (groups == in_c == out_c, weight (C,1,kh,kw)) on
714
+ # Sana 4Kpx VAE: ~4.8 s per call vs cuDNN dedicated path ~2.6 ms,
715
+ # ~1800x gap. Route to the dedicated stencil kernel instead.
716
+ _route = _lk.conv2d_route(N, in_c, out_c, out_h, out_w, dil_h, dil_w, groups, out_nbx_dtype,
717
+ _ts.conv2d_band_bytes(),
718
+ depthwise_enabled=os.environ.get("NBX_DEPTHWISE_DISABLE", "0") != "1")
719
+ if out is not None and _route in ("depthwise", "band"):
720
+ raise RuntimeError(f"conv2d out=: the {_route} route writes its own result — refused")
721
+ if _route == "depthwise":
722
+ if _NBX_CONV2D_TRACE:
723
+ print(f"[CONV2D] DEPTHWISE path (g={groups})", flush=True)
724
+ return _depthwise_conv2d_dispatch(
725
+ x_c.contiguous(), w_c, bias,
726
+ N, in_c, in_h, in_w, out_h, out_w,
727
+ kh, kw, stride_h, stride_w, pad_h, pad_w,
728
+ out_dtype,
729
+ )
730
+
731
+ if _route == "band":
732
+ if _NBX_CONV2D_TRACE:
733
+ print(f"[CONV2D] BAND-STREAM triggered (out > {_ts.conv2d_band_bytes()/1024/1024/1024:.1f}GiB)", flush=True)
734
+ return _conv2d_band_streamed(
735
+ x_c.contiguous(), w_c, bias,
736
+ N, in_c, in_h, in_w, out_c, out_h, out_w,
737
+ kh, kw, stride_h, stride_w, pad_h, pad_w, dil_h, dil_w,
738
+ groups, out_dtype, out_dtype_bytes,
739
+ )
740
+
741
+ if out is not None:
742
+ # the caller's own [N, out_c, out_h, out_w] view (any strides) receives the result: a conv3d writes each temporal
743
+ # tap straight into its [B, C, T, H, W] layout instead of a contiguous result permuted after
744
+ if (tuple(out.shape) != (N, out_c, out_h, out_w) or bias is not None
745
+ or (out._dtype != out_dtype and out.dtype != out_dtype)):
746
+ raise RuntimeError(f"conv2d out=: a {tuple(out.shape)} {out._dtype} view for a ({N}, {out_c}, {out_h}, "
747
+ f"{out_w}) {out_dtype} result{' with a bias' if bias is not None else ''} — refused")
748
+ output = out
749
+ else:
750
+ output = NBXTensor.empty((N, out_c, out_h, out_w), device=x.device, dtype=out_dtype)
751
+
752
+ unit_route = _lk.matrix_unit_conv_operands(x_c.nbx_dtype, w_c.nbx_dtype, N * out_h * out_w, out_c // groups, kh, kw)
753
+ if unit_route is not None:
754
+ reps, split_rows = unit_route
755
+ # The implicit GEMM on the profile's matrix unit (ops/conv2d_m8n8k4.py), bias fused; no autotune key. An
756
+ # operand the DtypeEngine carries split (fp32 on Volta, `matrix_unit.fp32_split`) runs as hi + lo, as the GEMM.
757
+ x_c = x_c.contiguous()
758
+ # The matrix-unit kernel writes a contiguous result: a caller's strided view (a conv3d tap's frames in their
759
+ # [B, C, T, H, W] layout) receives it by one copy. Refused instead, every CUDA video model whose conv3d runs
760
+ # its taps through here failed at its first convolution (6 gate cells, 2026-10-22).
761
+ dest = output if out is None else NBXTensor.empty((N, out_c, out_h, out_w), device=x.device, dtype=out_dtype)
762
+ _conv2d_m8n8k4(x_c, w_c, bias, dest, N, in_c, in_h, in_w, out_c, out_h, out_w, kh, kw,
763
+ stride_h, stride_w, pad_h, pad_w, dil_h, dil_w, groups,
764
+ split=(reps[0] == "split", reps[1] == "split"), split_rows=split_rows)
765
+ if out is not None:
766
+ out.copy_(dest)
767
+ return output
768
+
769
+ # The key's `fp16` flag names an fp16 INPUT. It compared `x.dtype` — the Triton element type —
770
+ # to an NBXDtype member and was False for every launch (5 378 directory entries carried it).
771
+ # The kernel's only use of the flag casts the loaded blocks to fp16 before tl.dot, a no-op on
772
+ # an fp16 input: TTIR, TTGIR, LLIR and PTX are identical with the flag right (sm_70, three
773
+ # shapes, 2026-09-29), so the certified entries were MIGRATED to the right key, not re-proven
774
+ # (tools/migrate_conv_fp16_key.py).
775
+ fp16 = (x.nbx_dtype if hasattr(x, 'nbx_dtype') else x._dtype) == NBXDtype.float16
776
+
777
+ # Phase 1.5 conv2d autotune: BLOCK_SIZE_*/num_warps/num_stages chosen
778
+ # adaptively per shape signature, persisted via cache_results=True.
779
+ grid = lambda META: (
780
+ triton.cdiv(N * out_h * out_w, META['BLOCK_SIZE_BHW']),
781
+ triton.cdiv(out_c // groups, META['BLOCK_SIZE_OUTF']),
782
+ groups,
783
+ )
784
+ set_device(x_c)
785
+ _in_w_key, _out_w_key = _conv_width_key(in_h, out_h, in_w, out_w, kw, stride_w, pad_w, dil_w)
786
+ _core._autotune_headroom_guard(conv2d_forward_kernel[grid])(
787
+ x_c, w_c, output,
788
+ N, in_c, in_h, in_w,
789
+ out_c, out_h, out_w,
790
+ _in_w_key, _out_w_key,
791
+ *x_c.stride(), *w_c.stride(), *output.stride(),
792
+ kernel_height=kh, kernel_width=kw,
793
+ stride_height=stride_h, stride_width=stride_w,
794
+ padding_height=pad_h, padding_width=pad_w,
795
+ dilation_height=dil_h, dilation_width=dil_w,
796
+ groups=groups, fp16=fp16, WIDE_OFFSETS=_conv_offsets_wide(x_c, w_c, output),
797
+ )
798
+
799
+ if bias is not None:
800
+ output = _conv_bias_inplace(output, bias)
801
+
802
+ return output
803
+
804
+
805
+ def _conv2d_m8n8k4(x, w, bias, out, N, in_c, in_h, in_w, out_c, out_h, out_w, kh, kw, sh, sw, ph, pw, dh, dw,
806
+ groups, split=(False, False), split_rows=None):
807
+ """conv2d as an implicit GEMM on the m8n8k4 matrix unit: M = N*OH*OW, N = OC/groups, K = (IC/groups)*KH*KW in
808
+ the contiguous weight's own order (read in place); groups on grid axis 1; the profile's `mm` tile, or, when
809
+ `split` (input, weight) carries an operand as hi + lo, the `fp32_split.conv` row of its window (`split_rows`,
810
+ `launch_keys.matrix_unit_conv_operands`)."""
811
+ from nbxtensor.ops.conv2d_m8n8k4 import conv2d_m8n8k4_kernel, conv_layouts
812
+ from nbxtensor.dtype.operands import matrix_unit_split_scale
813
+ M, OG, CG = N * out_h * out_w, out_c // groups, in_c // groups
814
+ mu = _matrix_unit()
815
+ tile = _matrix_unit_mm_tile(M, OG, {"mm": [{k: v for k, v in r.items() if k != "kernel"} for r in split_rows]}
816
+ if any(split) else mu)
817
+ hi_exp, lo_shift = matrix_unit_split_scale(mu)
818
+ MB, NB = tile["block_m"] // 16, tile["block_n"] // 16
819
+ lay = conv_layouts(MB, NB, tile["warps_m"], tile["warps_n"], tile["block_k"])
820
+ set_device(x)
821
+ grid = (triton.cdiv(M, tile["block_m"]) * triton.cdiv(OG, tile["block_n"]), groups)
822
+ conv2d_m8n8k4_kernel[grid](
823
+ x, w, out if bias is None else bias.contiguous(), out,
824
+ M, OG, CG * kh * kw, in_h, in_w, out_h, out_w,
825
+ *x.stride(), w.stride(0), out.stride(0), out.stride(1), out.stride(2), out.stride(3),
826
+ CG, OG,
827
+ KH=kh, KW=kw, SH_=sh, SW_=sw, PH=ph, PW=pw, DH=dh, DW=dw, HAS_BIAS=bias is not None,
828
+ SPLIT_A=bool(split[0]), SPLIT_B=bool(split[1]), HI_EXP=hi_exp, LO_SHIFT=lo_shift,
829
+ MB=MB, NB=NB, BK=tile["block_k"], GROUP_M=tile["group_m"],
830
+ num_warps=tile["warps_m"] * tile["warps_n"], **lay)
831
+
832
+
833
+ def _depthwise_conv2d_dispatch(
834
+ x_c, w_c, bias,
835
+ N, C, IH, IW, out_h, out_w,
836
+ kh, kw, stride_h, stride_w, pad_h, pad_w,
837
+ out_dtype,
838
+ ):
839
+ """Launch the depthwise stencil kernel. Caller has already verified
840
+ the depthwise signature (groups == in_c == out_c, dilation == 1) and
841
+ done dtype narrowing + contiguous(). Bias is added separately so the
842
+ kernel stays signature-clean."""
843
+ output = NBXTensor.empty((N, C, out_h, out_w), device=x_c.device, dtype=out_dtype)
844
+ x_nbx_dt = x_c.nbx_dtype if hasattr(x_c, 'nbx_dtype') else x_c._dtype
845
+ fp16 = x_nbx_dt == NBXDtype.float16
846
+
847
+ grid = lambda META: (
848
+ N * triton.cdiv(C, META['BC']),
849
+ triton.cdiv(out_h, META['TH']),
850
+ triton.cdiv(out_w, META['TW']),
851
+ )
852
+ set_device(x_c)
853
+ _iw_key, _ow_key = _conv_width_key(IH, out_h, IW, out_w, kw, stride_w, pad_w, 1)
854
+ _core._autotune_headroom_guard(depthwise_conv2d_kernel[grid])(
855
+ x_c, w_c, output,
856
+ N, C,
857
+ IH, IW, out_h, out_w,
858
+ _iw_key, _ow_key,
859
+ *x_c.stride(), *w_c.stride()[:1], *w_c.stride()[2:],
860
+ *output.stride(),
861
+ kh=kh, kw=kw,
862
+ stride_h=stride_h, stride_w=stride_w,
863
+ pad_h=pad_h, pad_w=pad_w,
864
+ fp16=fp16, WIDE_OFFSETS=_conv_offsets_wide(x_c, w_c, output),
865
+ )
866
+
867
+ if bias is not None:
868
+ output = _conv_bias_inplace(output, bias)
869
+
870
+ return output
871
+
872
+
873
+ def _conv2d_band_streamed(
874
+ x_c, w_c, bias,
875
+ N, in_c, IH, IW, out_c, out_h, out_w,
876
+ kh, kw, stride_h, stride_w, pad_h, pad_w, dil_h, dil_w,
877
+ groups, out_dtype, out_dtype_bytes,
878
+ ):
879
+ """Band-streaming inner path for conv2d_wrapper. The full output tensor
880
+ is allocated up front (downstream consumers expect it whole); per band
881
+ we slice the input H, recurse into conv2d_wrapper for the band (which
882
+ falls back to the single-launch path because the band's output is now
883
+ small), and write the band slice back into the full output.
884
+
885
+ Halo on internal frontiers: each band's input slice is extended by
886
+ pad_h on the read side (clamped to image edges); the recursive
887
+ conv2d_wrapper call uses the original padding=(pad_h, pad_w), which
888
+ inserts pad_h zeros on internal frontiers — same engineering trade
889
+ used by `tiled_conv2d` for compiled mode (faint seam at
890
+ band frontiers, accepted as a follow-up halo-correctness workstream).
891
+ """
892
+ # The TilingEngine sizes each band (`conv2d.band_budget_divisor` of the threshold);
893
+ # the headroom covers transient input slice + kernel intermediate.
894
+ band_oh = _lk.conv2d_band_rows(N, out_c, out_h, out_w, out_dtype_bytes, _ts.conv2d_band_bytes())
895
+
896
+ output = NBXTensor.empty((N, out_c, out_h, out_w), device=x_c.device, dtype=out_dtype)
897
+
898
+ for oh_start in range(0, out_h, band_oh):
899
+ oh_end = min(oh_start + band_oh, out_h)
900
+ ih_start = max(0, oh_start * stride_h - pad_h)
901
+ ih_end = min(IH, (oh_end - 1) * stride_h + dil_h * (kh - 1) + 1 - pad_h)
902
+ if ih_end <= ih_start:
903
+ continue
904
+ in_band = x_c[:, :, ih_start:ih_end, :]
905
+ # Recurse — band's out_h is now small enough to fall through to the
906
+ # single-launch path. bias is applied once at the end here (not
907
+ # per-band) to avoid double-add when the band path returns.
908
+ conv_band = conv2d_wrapper(
909
+ in_band, w_c, None,
910
+ stride=(stride_h, stride_w),
911
+ padding=(pad_h, pad_w),
912
+ dilation=(dil_h, dil_w),
913
+ groups=groups,
914
+ )
915
+ actual_band_h = oh_end - oh_start
916
+ band_first_oh = ih_start // stride_h
917
+ local_offset = max(0, oh_start - band_first_oh)
918
+ actual_band_h = min(conv_band.shape[2] - local_offset, actual_band_h)
919
+ if actual_band_h <= 0:
920
+ continue
921
+ output[:, :, oh_start:oh_start + actual_band_h, :] = conv_band[:, :, local_offset:local_offset + actual_band_h, :]
922
+
923
+ if bias is not None:
924
+ output = _conv_bias_inplace(output, bias)
925
+
926
+ return output
927
+
928
+
929
+ # ---------------------------------------------------------------------------
930
+ # Conv1D
931
+ # ---------------------------------------------------------------------------
932
+
933
+ def conv1d_wrapper(
934
+ x, weight, bias=None,
935
+ stride=1, padding=0, dilation=1, groups=1,
936
+ ) :
937
+ """Conv1D via conv2d with unsqueeze. Reuses the V100-compatible attorch conv2d kernel.
938
+ Input [N,C,L] → unsqueeze → [N,C,1,L] → conv2d → [N,Co,1,Lo] → squeeze → [N,Co,Lo]."""
939
+ # Convert 1D → 2D by adding H=1 dimension
940
+ x_2d = x.unsqueeze(2) # [N, C_in, 1, L]
941
+ w_2d = weight.unsqueeze(2) # [C_out, C_in/groups, 1, K]
942
+
943
+ if isinstance(stride, (list, tuple)):
944
+ stride = stride[0]
945
+ if isinstance(padding, (list, tuple)):
946
+ padding = padding[0]
947
+ if isinstance(dilation, (list, tuple)):
948
+ dilation = dilation[0]
949
+
950
+ out_2d = conv2d_wrapper(
951
+ x_2d, w_2d, bias,
952
+ stride=(1, stride), padding=(0, padding),
953
+ dilation=(1, dilation), groups=groups)
954
+
955
+ return out_2d.squeeze(2) # [N, C_out, L_out]
956
+
957
+
958
+ def conv_transpose1d_wrapper(x, weight, bias=None, stride=1, padding=0,
959
+ output_padding=0, groups=1, dilation=1) :
960
+ """aten::conv_transpose1d — 1D transposed convolution. conv_transpose_wrapper
961
+ already handles the 1D case (weight.ndim == 3, H=1 unsqueeze); this thin
962
+ adapter only reorders the positional args from the aten signature
963
+ (stride, padding, output_padding, groups, dilation) to the wrapper's keyword
964
+ order. Used by the chatterbox/CosyVoice s3gen HiFiGAN-style upsampler."""
965
+ return conv_transpose_wrapper(
966
+ x, weight, bias,
967
+ stride=stride, padding=padding, dilation=dilation,
968
+ output_padding=output_padding, groups=groups)
969
+
970
+
971
+ # ---------------------------------------------------------------------------
972
+ # Avg Pool 2D
973
+ # ---------------------------------------------------------------------------
974
+
975
+ def avg_pool2d_wrapper(
976
+ x, kernel_size, stride=None, padding=0,
977
+ ceil_mode=False, count_include_pad=True, divisor_override=None,
978
+ ) :
979
+ """Average pooling 2D forward."""
980
+ assert x.ndim == 4
981
+ N, C, H, W = x.shape
982
+
983
+ # ATen broadcast for single-element lists (same contract as
984
+ # conv2d_wrapper — the latent IndexError class).
985
+ if isinstance(kernel_size, int):
986
+ kh, kw = kernel_size, kernel_size
987
+ else:
988
+ kh = kernel_size[0]
989
+ kw = kernel_size[1] if len(kernel_size) > 1 else kh
990
+
991
+ if stride is None or (isinstance(stride, (list, tuple)) and not stride):
992
+ sh, sw = kh, kw
993
+ elif isinstance(stride, int):
994
+ sh, sw = stride, stride
995
+ else:
996
+ sh = stride[0]
997
+ sw = stride[1] if len(stride) > 1 else sh
998
+
999
+ if isinstance(padding, int):
1000
+ ph, pw = padding, padding
1001
+ else:
1002
+ ph = padding[0] if len(padding) > 0 else 0
1003
+ pw = padding[1] if len(padding) > 1 else ph
1004
+
1005
+ if ceil_mode:
1006
+ oh = (H + 2 * ph - kh + sh - 1) // sh + 1
1007
+ ow = (W + 2 * pw - kw + sw - 1) // sw + 1
1008
+ else:
1009
+ oh = (H + 2 * ph - kh) // sh + 1
1010
+ ow = (W + 2 * pw - kw) // sw + 1
1011
+
1012
+ output = NBXTensor.empty((N, C, oh, ow), device=x.device, dtype=x.dtype)
1013
+
1014
+ # Contiguous-guard (manifesto pattern): materialize ONCE and launch
1015
+ # with THAT tensor's strides. The previous form passed
1016
+ # x.contiguous() as data but x.stride(...) of the ORIGINAL — a
1017
+ # non-contiguous input (transpose→unsqueeze chains, e.g. the
1018
+ # MiniCPM-o audio pooler) made the kernel read wrong addresses
1019
+ # (141% relative error at the first diverging op of the audio
1020
+ # drift diff). Contiguous inputs short-circuit at zero cost.
1021
+ xc = x.contiguous()
1022
+ grid = (
1023
+ N * C,
1024
+ triton.cdiv(oh, _POOL_BH) * triton.cdiv(ow, _POOL_BW),
1025
+ )
1026
+ set_device(xc)
1027
+ avg_pool2d_forward_kernel[grid](
1028
+ xc, output,
1029
+ xc.stride(0), xc.stride(1), xc.stride(2), xc.stride(3),
1030
+ C, H, W,
1031
+ oh, ow,
1032
+ kh, kw, sh, sw, ph, pw,
1033
+ 1, 1, # dilation_h, dilation_w
1034
+ count_include_pad,
1035
+ 0 if divisor_override is None else divisor_override,
1036
+ BLOCK_H=_POOL_BH, BLOCK_W=_POOL_BW,
1037
+ num_warps=4,
1038
+ )
1039
+ return output
1040
+
1041
+
1042
+ # ---------------------------------------------------------------------------
1043
+ # Max Pool 2D
1044
+ # ---------------------------------------------------------------------------
1045
+
1046
+ def max_pool2d_wrapper(
1047
+ x, kernel_size, stride=None, padding=0,
1048
+ dilation=1, ceil_mode=False, return_indices=False,
1049
+ ) :
1050
+ """Max pooling 2D forward."""
1051
+ assert x.ndim == 4
1052
+ N, C, H, W = x.shape
1053
+
1054
+ # ATen broadcast for single-element lists (same contract as
1055
+ # conv2d_wrapper/avg_pool2d_wrapper).
1056
+ if isinstance(kernel_size, (list, tuple)) and len(kernel_size) == 1:
1057
+ kernel_size = [kernel_size[0], kernel_size[0]]
1058
+ if isinstance(stride, (list, tuple)) and len(stride) == 1:
1059
+ stride = [stride[0], stride[0]]
1060
+ if isinstance(padding, (list, tuple)) and len(padding) == 1:
1061
+ padding = [padding[0], padding[0]]
1062
+ if isinstance(dilation, (list, tuple)) and len(dilation) == 1:
1063
+ dilation = [dilation[0], dilation[0]]
1064
+
1065
+ if isinstance(kernel_size, int):
1066
+ kh, kw = kernel_size, kernel_size
1067
+ else:
1068
+ kh, kw = kernel_size[0], kernel_size[1]
1069
+
1070
+ if stride is None:
1071
+ sh, sw = kh, kw
1072
+ elif isinstance(stride, int):
1073
+ sh, sw = stride, stride
1074
+ else:
1075
+ sh, sw = stride[0], stride[1]
1076
+
1077
+ if isinstance(padding, int):
1078
+ ph, pw = padding, padding
1079
+ else:
1080
+ ph, pw = padding[0], padding[1]
1081
+
1082
+ if isinstance(dilation, int):
1083
+ dh, dw = dilation, dilation
1084
+ else:
1085
+ dh, dw = dilation[0], dilation[1]
1086
+
1087
+ if ceil_mode:
1088
+ oh = (H + 2 * ph - dh * (kh - 1) - 1 + sh - 1) // sh + 1
1089
+ ow = (W + 2 * pw - dw * (kw - 1) - 1 + sw - 1) // sw + 1
1090
+ else:
1091
+ oh = (H + 2 * ph - dh * (kh - 1) - 1) // sh + 1
1092
+ ow = (W + 2 * pw - dw * (kw - 1) - 1) // sw + 1
1093
+
1094
+ output = NBXTensor.empty((N, C, oh, ow), device=x.device, dtype=x.dtype)
1095
+ indices = NBXTensor.empty((N, C, oh, ow), device=x.device, dtype=NBXDtype.int64)
1096
+
1097
+ # Contiguous-guard: same fix as avg_pool2d_wrapper — one
1098
+ # materialization, ITS strides.
1099
+ xc = x.contiguous()
1100
+ grid = (
1101
+ N * C,
1102
+ triton.cdiv(oh, _POOL_BH) * triton.cdiv(ow, _POOL_BW),
1103
+ )
1104
+ set_device(xc)
1105
+ max_pool2d_forward_kernel[grid](
1106
+ xc, output, indices,
1107
+ xc.stride(0), xc.stride(1), xc.stride(2), xc.stride(3),
1108
+ C, H, W,
1109
+ oh, ow,
1110
+ kh, kw, sh, sw, ph, pw, dh, dw,
1111
+ BLOCK_H=_POOL_BH, BLOCK_W=_POOL_BW,
1112
+ num_warps=4,
1113
+ )
1114
+ if return_indices:
1115
+ return output, indices
1116
+ return output
1117
+
1118
+
1119
+ def max_pool3d_with_indices_wrapper(
1120
+ x, kernel_size, stride=None, padding=0,
1121
+ dilation=1, ceil_mode=False,
1122
+ ):
1123
+ """Max pooling 3D forward — aten::max_pool3d_with_indices.
1124
+
1125
+ Returns (values, indices); indices are int64 flattened (T, H, W)
1126
+ offsets per (N, C) plane (PyTorch convention). Dedicated
1127
+ @triton.jit kernel (kernels/ops/max_pool3d.py), temporal extension
1128
+ of the 2D pool kernel. Video conditioning masks (e.g. Allegro-TI2V
1129
+ state_video_mask downsampled to the latent grid) trace to this op.
1130
+ """
1131
+ assert x.ndim == 5
1132
+ N, C, T, H, W = x.shape
1133
+
1134
+ def _t3(v, default):
1135
+ if v is None:
1136
+ return default
1137
+ if isinstance(v, int):
1138
+ return (v, v, v)
1139
+ return (v[0], v[1], v[2]) if len(v) >= 3 else (v[0], v[0], v[0])
1140
+
1141
+ kt, kh, kw = _t3(kernel_size, None)
1142
+ st_, sh, sw = _t3(stride, (kt, kh, kw)) if stride not in (None, [], ()) \
1143
+ else (kt, kh, kw)
1144
+ pt, ph, pw = _t3(padding, (0, 0, 0))
1145
+ dt_, dh, dw = _t3(dilation, (1, 1, 1))
1146
+
1147
+ def _odim(inp, k, s, p, d):
1148
+ if ceil_mode:
1149
+ return (inp + 2 * p - d * (k - 1) - 1 + s - 1) // s + 1
1150
+ return (inp + 2 * p - d * (k - 1) - 1) // s + 1
1151
+
1152
+ ot = _odim(T, kt, st_, pt, dt_)
1153
+ oh = _odim(H, kh, sh, ph, dh)
1154
+ ow = _odim(W, kw, sw, pw, dw)
1155
+
1156
+ x_c = x.contiguous()
1157
+ output = NBXTensor.empty((N, C, ot, oh, ow), device=x_c.device, dtype=x_c.dtype)
1158
+ indices = NBXTensor.empty((N, C, ot, oh, ow), device=x_c.device,
1159
+ dtype=NBXDtype.int64)
1160
+
1161
+ grid = (
1162
+ N * C * ot,
1163
+ triton.cdiv(oh, _POOL_BH) * triton.cdiv(ow, _POOL_BW),
1164
+ )
1165
+ set_device(x_c)
1166
+ max_pool3d_forward_kernel[grid](
1167
+ x_c, output, indices,
1168
+ x_c.stride(0), x_c.stride(1), x_c.stride(2), x_c.stride(3), x_c.stride(4),
1169
+ C, T, H, W,
1170
+ ot, oh, ow,
1171
+ kt, kh, kw, st_, sh, sw, pt, ph, pw, dt_, dh, dw,
1172
+ BLOCK_H=_POOL_BH, BLOCK_W=_POOL_BW,
1173
+ num_warps=4,
1174
+ )
1175
+ return output, indices
1176
+
1177
+
1178
+ def max_pool3d_wrapper(
1179
+ x, kernel_size, stride=None, padding=0,
1180
+ dilation=1, ceil_mode=False,
1181
+ ):
1182
+ """aten::max_pool3d — values-only variant."""
1183
+ return max_pool3d_with_indices_wrapper(
1184
+ x, kernel_size, stride, padding, dilation, ceil_mode)[0]
1185
+
1186
+
1187
+ def adaptive_avg_pool2d_wrapper(x, output_size) :
1188
+ """Adaptive average pooling 2D."""
1189
+ N, C, IH, IW = x.shape
1190
+ if isinstance(output_size, int):
1191
+ OH = OW = output_size
1192
+ else:
1193
+ OH, OW = output_size
1194
+ output = NBXTensor.empty((N, C, OH, OW), device=x.device, dtype=x.dtype)
1195
+ total = N * C * OH * OW
1196
+ grid = _ew._1d_grid(total)
1197
+ x_c = x.contiguous()
1198
+ set_device(x_c)
1199
+ adaptive_avg_pool2d_kernel[grid](
1200
+ x_c, output, total,
1201
+ IH, IW, OH, OW,
1202
+ *x_c.stride(),
1203
+ **_ew_meta(total))
1204
+ return output
1205
+
1206
+
1207
+ def conv_depthwise2d_wrapper(x, weight, bias=None,
1208
+ stride=1, padding=0, dilation=1) :
1209
+ """Depthwise conv2d."""
1210
+ if isinstance(stride, int): stride = (stride, stride)
1211
+ if isinstance(padding, int): padding = (padding, padding)
1212
+ if isinstance(dilation, int): dilation = (dilation, dilation)
1213
+ N, C, IH, IW = x.shape
1214
+ _, _, KH, KW = weight.shape
1215
+ OH = (IH + 2*padding[0] - dilation[0]*(KH-1) - 1) // stride[0] + 1
1216
+ OW = (IW + 2*padding[1] - dilation[1]*(KW-1) - 1) // stride[1] + 1
1217
+ output = NBXTensor.empty((N, C, OH, OW), device=x.device, dtype=x.dtype)
1218
+ grid = (
1219
+ triton.cdiv(OW, _core._BILINEAR_BX),
1220
+ triton.cdiv(OH, _core._BILINEAR_BY),
1221
+ N * C,
1222
+ )
1223
+ set_device(x.contiguous())
1224
+ conv_depthwise2d_kernel[grid](
1225
+ x.contiguous(), weight.contiguous(), output,
1226
+ N, C, IH, IW, C, KH, KW, OH, OW,
1227
+ stride[0], stride[1], padding[0], padding[1], dilation[0], dilation[1],
1228
+ 1, # ch_mult
1229
+ BLOCK_X=_core._BILINEAR_BX, BLOCK_Y=_core._BILINEAR_BY,
1230
+ num_warps=4)
1231
+ if bias is not None:
1232
+ output = _conv_bias_inplace(output, bias)
1233
+ return output
1234
+
1235
+
1236
+ def unfold_backward_wrapper(grad_in, input_sizes, dim, size, step) :
1237
+ """unfold_backward (overlap-add): scatter-add the unfolded frames
1238
+ [..lead.., N_frames, size] back into the original signal [..lead.., L] (L on
1239
+ `dim`), summing overlapping positions. This is the iSTFT overlap-add.
1240
+ R33-pure (one @triton.jit scatter-add kernel)."""
1241
+ from nbxtensor.ops.unfold_op import unfold_backward_1d_kernel
1242
+ grad_in = _core._ensure_cuda(grad_in).contiguous()
1243
+ if isinstance(dim, (list, tuple)):
1244
+ dim = dim[0]
1245
+ if isinstance(size, (list, tuple)):
1246
+ size = size[0]
1247
+ if isinstance(step, (list, tuple)):
1248
+ step = step[0]
1249
+ out_shape = list(input_sizes)
1250
+ dim = dim % len(out_shape)
1251
+ L = out_shape[dim]
1252
+ # grad_in: [*out_shape[:dim], N_frames, size] (the unfold replaced dim by
1253
+ # (N_frames, size)). Collapse the leading dims into a batch.
1254
+ gshape = list(grad_in.shape)
1255
+ N_frames = gshape[dim]
1256
+ sz = gshape[-1]
1257
+ B = 1
1258
+ for d in out_shape[:dim]:
1259
+ B *= d
1260
+ g2 = grad_in.reshape(B, N_frames, sz)
1261
+ out = NBXTensor.zeros((B, L), dtype=grad_in.dtype, device=grad_in.device)
1262
+ set_device(g2)
1263
+ max_overlap = (sz // step) + 2
1264
+ BLOCK = 256
1265
+ grid = (B, triton.cdiv(L, BLOCK))
1266
+ unfold_backward_1d_kernel[grid](
1267
+ g2, out, N_frames, sz, step, L,
1268
+ MAX_OVERLAP=max_overlap, BLOCK_SIZE=BLOCK)
1269
+ return out.reshape(*out_shape)
1270
+
1271
+
1272
+ def im2col_wrapper(x, kernel_size, dilation, padding, stride):
1273
+ """aten::im2col / nn.Unfold — sliding local blocks of an NCHW tensor.
1274
+
1275
+ x [N, C, IH, IW] -> [N, C*KH*KW, L], L = OH*OW, in PyTorch's channel-major
1276
+ column order (c*KH*KW + kh*KW + kw). R33-pure: one @triton.jit kernel that
1277
+ reads the padding halo via boundary-masked tl.load (no F.pad). Drives HAT's
1278
+ OCAB overlapping-window cross-attention (P-TRITON-IM2COL-KERNEL).
1279
+ """
1280
+ from nbxtensor.ops.im2col import im2col_kernel
1281
+
1282
+ def _pair(v):
1283
+ return tuple(v) if isinstance(v, (list, tuple)) else (v, v)
1284
+
1285
+ x = _core._ensure_cuda(x).contiguous()
1286
+ N, C, IH, IW = x.shape
1287
+ KH, KW = _pair(kernel_size)
1288
+ dil_h, dil_w = _pair(dilation)
1289
+ pad_h, pad_w = _pair(padding)
1290
+ stride_h, stride_w = _pair(stride)
1291
+ OH = (IH + 2 * pad_h - dil_h * (KH - 1) - 1) // stride_h + 1
1292
+ OW = (IW + 2 * pad_w - dil_w * (KW - 1) - 1) // stride_w + 1
1293
+ L = OH * OW
1294
+ out = NBXTensor.empty((N, C * KH * KW, L), dtype=x.dtype, device=x.device)
1295
+ in_sn, in_sc, in_sh, in_sw = C * IH * IW, IH * IW, IW, 1
1296
+ out_sn, out_sc, out_sl = C * KH * KW * L, L, 1
1297
+ BLOCK_L = min(1024, triton.next_power_of_2(L))
1298
+ grid = (N * C * KH * KW,)
1299
+ set_device(x)
1300
+ im2col_kernel[grid](
1301
+ x, out, C, IH, IW, OW, L, KH, KW,
1302
+ stride_h, stride_w, pad_h, pad_w, dil_h, dil_w,
1303
+ in_sn, in_sc, in_sh, in_sw, out_sn, out_sc, out_sl,
1304
+ BLOCK_L=BLOCK_L)
1305
+ return out
1306
+
1307
+
1308
+ # ---------------------------------------------------------------------------
1309
+ # The cost each convolution path declares (M3, docs/nbxtensor/COST.md): what it allocates, in its order, read from the
1310
+ # same decisions the wrappers take (`launch_keys.conv_dtypes`, `conv2d_route`, `matrix_unit_conv_operands`, the strided read
1311
+ # rule) — a planner prices the convolution by it instead of re-deriving it. Operands are `_cost.Spec`s; a returned
1312
+ # buffer is the op's output.
1313
+ # ---------------------------------------------------------------------------
1314
+
1315
+ def _reads_strided(shape, strides) -> bool:
1316
+ """`_conv_reads_strided` on a shape and its strides: four non-negative strides, the width innermost."""
1317
+ return len(strides) == 4 and all(int(v) >= 0 for v in strides) and (int(strides[3]) == 1 or int(shape[3]) == 1)
1318
+
1319
+
1320
+ def _pair(v):
1321
+ return (v[0], v[1] if len(v) > 1 else v[0]) if isinstance(v, (list, tuple)) else (v, v)
1322
+
1323
+
1324
+ def conv2d_cost(x, w, stride=1, padding=0, dilation=1, groups=1, bias=False, out_dtype=None, out_given=False,
1325
+ krsc=False, unit=None, depthwise_enabled=True):
1326
+ """`conv2d_wrapper` on an input `x` and a weight `w` (`_cost.Spec`s): the operands narrowed to the narrowest of the
1327
+ pair when they differ (a copy each), the input made contiguous unless the implicit GEMM reads it where it lies, a
1328
+ weight not stored KRSC made contiguous, then the route — depthwise (a contiguous input, its output), band (the whole
1329
+ output, each band's conv beside it), or the plain launch: its output (none when the caller hands `out_given`), and
1330
+ on the matrix unit the input made contiguous and, for a caller's view, a dense result copied into it. The bias is
1331
+ added in place. `out_dtype`: the component's compute dtype the wrapper writes (`_NBX_COMPUTE_DTYPE`), the input's
1332
+ when None. `unit`: the matrix unit to judge by (a planner's), the profile in force's when None."""
1333
+ from nbxtensor.ops import _cost as C
1334
+ from nbxtensor.ops import tiling_sizes as _tsz
1335
+ from nbxtensor.shape import contiguous_strides, is_contiguous
1336
+ st = C._Steps()
1337
+ N, in_c, in_h, in_w = (int(d) for d in x.shape)
1338
+ out_c, _cg, kh, kw = (int(d) for d in w.shape)
1339
+ sh, sw = _pair(stride)
1340
+ ph, pw = _pair(padding)
1341
+ dh, dw = _pair(dilation)
1342
+ out_h, out_w = _lk.conv_out_hw(in_h, in_w, kh, kw, sh, sw, ph, pw, dh, dw)
1343
+ x_to, w_to = _lk.conv_dtypes(NBXDtype[x.dtype], NBXDtype[w.dtype])
1344
+ if x_to.name != x.dtype:
1345
+ x = C.Spec.contiguous(x.shape, x_to.name)
1346
+ st.alloc("input narrowed", x.nbytes)
1347
+ if w_to.name != w.dtype:
1348
+ w = C.Spec.contiguous(w.shape, w_to.name)
1349
+ st.alloc("weight narrowed", w.nbytes)
1350
+ if not is_contiguous(x.shape, x.strides) and not _reads_strided(x.shape, x.strides):
1351
+ x = C.Spec.contiguous(x.shape, x.dtype)
1352
+ st.alloc("input made contiguous", x.nbytes)
1353
+ if not krsc and not is_contiguous(w.shape, w.strides):
1354
+ st.alloc("weight made contiguous", w.nbytes)
1355
+ out_dt = out_dtype or x.dtype
1356
+ out_bytes = N * out_c * out_h * out_w * dtype_size(NBXDtype[out_dt])
1357
+ route = _lk.conv2d_route(N, in_c, out_c, out_h, out_w, dh, dw, groups, NBXDtype[x.dtype],
1358
+ _tsz.conv2d_band_bytes(), depthwise_enabled=depthwise_enabled)
1359
+ if route == "depthwise":
1360
+ if not is_contiguous(x.shape, x.strides):
1361
+ st.alloc("input made contiguous (depthwise)", x.nbytes)
1362
+ st.alloc("output", out_bytes, returned=True)
1363
+ return st.done(2 * N * out_c * out_h * out_w * kh * kw)
1364
+ if route == "band":
1365
+ if not is_contiguous(x.shape, x.strides):
1366
+ x = C.Spec.contiguous(x.shape, x.dtype)
1367
+ st.alloc("input made contiguous (bands)", x.nbytes)
1368
+ st.alloc("output", out_bytes, returned=True)
1369
+ band_oh = _lk.conv2d_band_rows(N, out_c, out_h, out_w, dtype_size(NBXDtype[x.dtype]), _tsz.conv2d_band_bytes())
1370
+ worst = None
1371
+ for oh_start in range(0, out_h, band_oh):
1372
+ oh_end = min(oh_start + band_oh, out_h)
1373
+ ih_start = max(0, oh_start * sh - ph)
1374
+ ih_end = min(in_h, (oh_end - 1) * sh + dh * (kh - 1) + 1 - ph)
1375
+ if ih_end <= ih_start:
1376
+ continue
1377
+ band = C.Spec((N, in_c, ih_end - ih_start, in_w), x.strides, x.dtype)
1378
+ c = conv2d_cost(band, w, stride, padding, dilation, groups, False, out_dt, False, True, unit,
1379
+ depthwise_enabled)
1380
+ if worst is None or c.peak_bytes > worst.peak_bytes:
1381
+ worst = c
1382
+ if worst is not None:
1383
+ for i in st.nest(worst, "the largest band"):
1384
+ st.free(i) # each band's result copied into the output, then dropped
1385
+ return st.done(2 * N * out_c * out_h * out_w * kh * kw * (in_c // max(1, groups)))
1386
+ if not out_given:
1387
+ st.alloc("output", out_bytes, returned=True)
1388
+ if _lk.matrix_unit_conv_operands(NBXDtype[x.dtype], NBXDtype[w.dtype], N * out_h * out_w, out_c // max(1, groups),
1389
+ kh, kw, unit) is not None:
1390
+ if not is_contiguous(x.shape, x.strides):
1391
+ i = st.alloc("input made contiguous (matrix unit)", x.nbytes)
1392
+ if out_given:
1393
+ st.alloc("dense result for the caller's view (matrix unit)", out_bytes)
1394
+ return st.done(2 * N * out_c * out_h * out_w * kh * kw * (in_c // max(1, groups)))
1395
+
1396
+
1397
+ def conv3d_cost(x, w, stride=1, padding=0, dilation=1, groups=1, bias=False, out_dtype=None, unit=None):
1398
+ """`_conv3d_via_conv2d` on an input `x` [B, Cin, T, H, W] and a weight `w` [Cout, Cin_g, kt, kh, kw]: the temporal
1399
+ pad (a padded copy, alive to the return), then — at temporal stride 1 — each tap's 2-D weight slice, its tap buffer
1400
+ and its per-batch conv2d writing into the tap's view (`conv2d_cost(out_given=True)`: on the matrix unit the
1401
+ frames made contiguous and a dense result), the first tap kept as the output and each later one added into it and
1402
+ dropped; at a larger stride each tap's frames gathered and folded (a copy), its conv2d output, its result permuted
1403
+ back (a copy) and the running sum (a new buffer per tap). The bias adds a new output. `out_dtype`: the compute
1404
+ dtype the wrapper writes, the input's when None."""
1405
+ from nbxtensor.ops import _cost as C
1406
+ from nbxtensor.shape import contiguous_strides
1407
+
1408
+ def _triple(v):
1409
+ return (v[0], v[1], v[2]) if isinstance(v, (list, tuple)) else (v, v, v)
1410
+ st_, sh, sw = _triple(stride)
1411
+ pt, ph, pw = _triple(padding)
1412
+ dt, dh, dw = _triple(dilation)
1413
+ B, Cin, T, H, W = (int(d) for d in x.shape)
1414
+ Cout, Cin_g, kt, kh, kw = (int(d) for d in w.shape)
1415
+ out_dt = out_dtype or x.dtype
1416
+ st = C._Steps()
1417
+ if pt > 0:
1418
+ x = C.Spec.contiguous((B, Cin, T + 2 * pt, H, W), x.dtype)
1419
+ st.alloc("temporal pad", x.nbytes)
1420
+ Tp = x.shape[2]
1421
+ T_out = (Tp - dt * (kt - 1) - 1) // st_ + 1
1422
+ Ho, Wo = _lk.conv_out_hw(H, W, kh, kw, sh, sw, ph, pw, dh, dw)
1423
+ out_bytes = B * Cout * T_out * Ho * Wo * dtype_size(NBXDtype[out_dt])
1424
+ w2 = C.Spec.contiguous((Cout, Cin_g, kh, kw), w.dtype)
1425
+ flops = 2 * B * Cout * T_out * Ho * Wo * kt * kh * kw * Cin_g
1426
+ # the wrapper's locals, as Python holds them: a name rebound frees its old buffer only after the new one exists, a
1427
+ # buffer at least `_NBX_CONV3D_EAGER_FREE_BYTES` is dropped as soon as it is dead (`x2 = None`, ...), the rest die
1428
+ # at the return
1429
+ held = {}
1430
+
1431
+ def bind(name, index):
1432
+ old = held.get(name)
1433
+ held[name] = index
1434
+ if old is not None and old not in held.values():
1435
+ st.free(old)
1436
+
1437
+ def drop(name):
1438
+ old = held.pop(name, None)
1439
+ if old is not None and old not in held.values():
1440
+ st.free(old)
1441
+
1442
+ if st_ == 1:
1443
+ xs = x.strides
1444
+ frames = C.Spec((T_out, Cin, H, W), (xs[2], xs[1], xs[3], xs[4]), x.dtype)
1445
+ for kti in range(kt):
1446
+ bind("w2", st.alloc("tap weight slice", w2.nbytes))
1447
+ bind("tap", st.alloc("tap", out_bytes))
1448
+ for _b in range(B):
1449
+ for i in st.nest(conv2d_cost(frames, w2, (sh, sw), (ph, pw), (dh, dw), groups, False, out_dt,
1450
+ True, False, unit), f"tap {kti} conv2d"):
1451
+ st.free(i)
1452
+ if "out" not in held:
1453
+ held["out"] = held["tap"] # the first tap is the sum
1454
+ drop("tap") # a later one, added into the sum in place, dropped (`tap = None`)
1455
+ else:
1456
+ fold_bytes = B * T_out * Cin * H * W * dtype_size(NBXDtype[x.dtype])
1457
+ for kti in range(kt):
1458
+ bind("idx", st.alloc("frame indices", T_out * 8))
1459
+ bind("xs", st.alloc("tap frames gathered", fold_bytes))
1460
+ bind("x2", st.alloc("tap frames folded", fold_bytes))
1461
+ bind("w2", st.alloc("tap weight slice", w2.nbytes))
1462
+ fold = C.Spec.contiguous((B * T_out, Cin, H, W), x.dtype)
1463
+ y2, = st.nest(conv2d_cost(fold, w2, (sh, sw), (ph, pw), (dh, dw), groups, False, out_dt, False, False,
1464
+ unit), f"tap {kti} conv2d")
1465
+ bind("y2", y2)
1466
+ if fold_bytes >= _NBX_CONV3D_EAGER_FREE_BYTES:
1467
+ drop("x2")
1468
+ bind("y", st.alloc("tap result permuted back", out_bytes))
1469
+ if out_bytes >= _NBX_CONV3D_EAGER_FREE_BYTES:
1470
+ drop("y2")
1471
+ if "out" not in held:
1472
+ held["out"] = held["y"]
1473
+ else:
1474
+ bind("out", st.alloc("running sum", out_bytes))
1475
+ if out_bytes >= _NBX_CONV3D_EAGER_FREE_BYTES:
1476
+ drop("y")
1477
+ if bias:
1478
+ bind("out", st.alloc("output with the bias", out_bytes))
1479
+ out = held.pop("out")
1480
+ st.buffers[out][4] = True # the result
1481
+ st.buffers[out][3] = None
1482
+ return st.done(flops) # every other local dies at the return