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,162 @@
1
+ """Vector (SIMT) decode attention — Tq == 1, pure @triton.jit, no tl.dot.
2
+
3
+ Second attempt at the decode-attention bar (~0.5 ms/token at 4k = the
4
+ KV bytes at HBM speed). The first attempt (ops/flash_decode.py) kept
5
+ the flash-decoding two-pass SPLIT structure but computed QK/PV with
6
+ `tl.dot` on a 16-row Q tile carrying 4 real GQA rows — and lost on the
7
+ row three times. The 2026-08-23 web research (R16) showed the split
8
+ was never the problem and the dot was: every production Tq=1 kernel on
9
+ pre-Ampere silicon is a SIMT kernel — FasterTransformer's
10
+ masked_multihead_attention (per-thread fp16 FMA + shuffle reduce, no
11
+ MMA anywhere), vLLM's paged_attention v1/v2 (16-byte vectorized K
12
+ loads, fixed-order v2 partition reduce), llama.cpp's fattn-vec/tile
13
+ (picked over MMA on Volta at exactly our effective batch), lightllm's
14
+ token_attention (`tl.sum(q[None, :] * k, 1)`, BLOCK_N=32, no dot).
15
+ Triton lowers fp16 `tl.dot` to FFMA on sm_70 (no HMMA lowering — the
16
+ Phase 1.5 measurement), so the MMA tile staging is pure cost there.
17
+
18
+ Structure here (the lightllm/vLLM shape):
19
+ - grid = (B * H_q, split): ONE QUERY ROW per program — no G tile at
20
+ all. The GQA K re-read (4 Q heads share a KV head) is absorbed by
21
+ L2 temporal locality, exactly as in vLLM/FT which organise the
22
+ same way.
23
+ - QK: `tl.sum(q[None, :] * k_tile, 1)` — an FMA reduction over D,
24
+ product tile (BLOCK_N, BLOCK_D) only.
25
+ - online softmax with SCALAR m/l state, fp32 accumulator [BLOCK_D].
26
+ - split-KV partials (out, m, l) merged by the EXISTING fixed-order
27
+ `flash_decode_reduce_kernel` (ops/flash_decode.py) viewed at
28
+ GROUPS=1 — deterministic by construction, no atomics, proven.
29
+ - K/V are read by their OWN strides: the bucketed KV cache's
30
+ prefix-slice views flow in directly (the strided-KV lot's
31
+ contract), no materialisation.
32
+ - num_stages=1 (sm_70 has no async-copy engine to pipeline with).
33
+
34
+ Born with its float64 oracle at D = 127, 128 AND 129
35
+ (test_decode_vec_oracle.py) and a route ACTIVATION proof — the
36
+ 2026-08-23 lesson: a route-dependent claim needs the route pinned.
37
+ """
38
+
39
+ import triton
40
+ import triton.language as tl
41
+
42
+ from nbxtensor.ops._configs import nbx_autotune
43
+
44
+
45
+ @triton.jit
46
+ def _round_q(q, Q_TO: tl.constexpr, Q_SATURATE: tl.constexpr):
47
+ """Q as the caller asked it read: rounded to Q_TO (the KV cache's
48
+ dtype) with the stored cast's protected fp16 conversion when
49
+ Q_SATURATE, then widened to the fp32 the dot runs in — the bytes of a
50
+ cast copy of Q, without the copy."""
51
+ if Q_TO is not None:
52
+ f = q.to(tl.float32)
53
+ if Q_SATURATE:
54
+ is_finite = (f == f) & (f != float("inf")) & (f != float("-inf"))
55
+ clamped = tl.minimum(tl.maximum(f, -65504.0), 65504.0)
56
+ f = tl.where(is_finite, clamped, f)
57
+ q = f.to(Q_TO)
58
+ return q.to(tl.float32)
59
+
60
+
61
+ #: the split: one program per DECODE_VEC_SEG keys (the judged split, locked pinned protocol, 2026-08-23: 256 won the
62
+ #: row at all three contexts), at most DECODE_VEC_MAX_SPLIT — it defines the partials, so it is the algorithm's, not a
63
+ #: tile
64
+ DECODE_VEC_SEG = 256
65
+ DECODE_VEC_MAX_SPLIT = 64
66
+
67
+
68
+ def decode_vec_splits(seqlen_k: int) -> int:
69
+ """The splits the vector decode cuts `seqlen_k` keys into — its grid, and the shared reduce's autotune key (one
70
+ rule for the wrapper, the census and the certifier)."""
71
+ return max(1, min(-(-int(seqlen_k) // DECODE_VEC_SEG), DECODE_VEC_MAX_SPLIT))
72
+
73
+
74
+ # The key block and warps of the vector decode, candidates the certification measures per key (the judged row,
75
+ # 2026-08-23: 32 keys a step, 4 warps, among them); one stage — the loop streams K and V once.
76
+ _DECODE_VEC_CONFIGS = [triton.Config({'BLOCK_N': bn}, num_warps=w, num_stages=1)
77
+ for bn in (16, 32, 64, 128) for w in (1, 2, 4, 8)]
78
+
79
+
80
+ @nbx_autotune(configs=_DECODE_VEC_CONFIGS, key=['T_BUCKET', 'D', 'D_V', 'GQA_GROUPS', 'HAS_BIAS'], cache_results=True)
81
+ @triton.jit
82
+ def decode_attn_vec_split_kernel(
83
+ q_ptr, # [B*H_q, D] one query row per program (fp16)
84
+ k_ptr, # [B*H_kv, T_k, D] native cache layout, own strides
85
+ v_ptr, # [B*H_kv, T_k, D_v]
86
+ bias_ptr, # [T_k] additive fp32/fp16 (unused when HAS_BIAS=0)
87
+ opart_ptr, # [B*H_q, SPLIT, D_v] fp32 partial outputs
88
+ mpart_ptr, # [B*H_q, SPLIT] fp32 running max per split
89
+ lpart_ptr, # [B*H_q, SPLIT] fp32 sum-of-exp per split
90
+ T_k, seg_len,
91
+ T_BUCKET, # T_k's bucket: the autotune key's length class (unused in the body)
92
+ sm_scale,
93
+ stride_qh,
94
+ stride_kh, stride_kn,
95
+ stride_vh, stride_vn,
96
+ stride_oh, stride_os,
97
+ stride_mh,
98
+ GQA_GROUPS: tl.constexpr, # H_q // H_kv — maps Q head -> KV head
99
+ BLOCK_D: tl.constexpr,
100
+ D: tl.constexpr,
101
+ D_V: tl.constexpr,
102
+ HAS_BIAS: tl.constexpr,
103
+ Q_TO: tl.constexpr = None, # a dtype: Q is rounded to it on load (the cache's)
104
+ Q_SATURATE: tl.constexpr = False, # the protected fp16 rounding of that cast
105
+ BLOCK_N: tl.constexpr = 32,
106
+ ):
107
+ pid_q = tl.program_id(0).to(tl.int64) # b * H_q + h_q
108
+ pid_s = tl.program_id(1).to(tl.int64) # split index
109
+ pid_kv = pid_q // GQA_GROUPS # owning KV head row
110
+
111
+ offs_d = tl.arange(0, BLOCK_D)
112
+ mask_d = offs_d < D
113
+ mask_dv = offs_d < D_V
114
+
115
+ q = tl.load(q_ptr + pid_q * stride_qh + offs_d,
116
+ mask=mask_d, other=0.0)
117
+ q = _round_q(q, Q_TO, Q_SATURATE)
118
+
119
+ seg_start = pid_s * seg_len
120
+ seg_start = seg_start.to(tl.int32) # loop bounds are 32-bit counts: the Metal lowering refuses a 64-bit scf.for bound (addressing stays 64-bit through the program ids)
121
+ seg_end = tl.minimum(seg_start + seg_len, T_k).to(tl.int32)
122
+
123
+ m_i = float("-inf")
124
+ l_i = 0.0
125
+ acc = tl.zeros((BLOCK_D,), dtype=tl.float32)
126
+
127
+ for n0 in range(seg_start, seg_end, BLOCK_N):
128
+ offs_n = n0 + tl.arange(0, BLOCK_N)
129
+ mask_n = offs_n < seg_end
130
+
131
+ k_tile = tl.load(k_ptr + pid_kv * stride_kh
132
+ + offs_n[:, None] * stride_kn + offs_d[None, :],
133
+ mask=mask_n[:, None] & mask_d[None, :], other=0.0)
134
+ # scores [BLOCK_N] — FMA reduction over D, no tl.dot
135
+ s = tl.sum(q[None, :] * k_tile.to(tl.float32), 1) * sm_scale
136
+ if HAS_BIAS:
137
+ b = tl.load(bias_ptr + offs_n, mask=mask_n, other=0.0)
138
+ s = s + b.to(tl.float32)
139
+ s = tl.where(mask_n, s, float("-inf"))
140
+
141
+ m_new = tl.maximum(m_i, tl.max(s, 0))
142
+ # empty tail keeps m_new=-inf; pin the exp arguments (the
143
+ # flash_decode kernel's own guard, scalar form)
144
+ m_safe = tl.where(m_new == float("-inf"), 0.0, m_new)
145
+ corr = tl.exp(tl.where(m_i == float("-inf"), float("-inf"),
146
+ m_i - m_safe))
147
+ p = tl.exp(s - m_safe)
148
+ p = tl.where(mask_n, p, 0.0)
149
+
150
+ v_tile = tl.load(v_ptr + pid_kv * stride_vh
151
+ + offs_n[:, None] * stride_vn + offs_d[None, :],
152
+ mask=mask_n[:, None] & mask_dv[None, :], other=0.0)
153
+ acc = acc * corr + tl.sum(p[:, None] * v_tile.to(tl.float32), 0)
154
+ l_i = l_i * corr + tl.sum(p, 0)
155
+ m_i = m_new
156
+
157
+ tl.store(opart_ptr + pid_q * stride_oh + pid_s * stride_os + offs_d,
158
+ acc, mask=mask_dv)
159
+ tl.store(mpart_ptr + pid_q * stride_mh + pid_s, m_i)
160
+ tl.store(lpart_ptr + pid_q * stride_mh + pid_s, l_i)
161
+
162
+
@@ -0,0 +1,132 @@
1
+ """Depthwise conv2d — direct stencil @triton.jit kernel.
2
+
3
+ Routed by `kernels/wrappers.py:conv2d_wrapper` when the convolution
4
+ matches the depthwise signature (`groups == in_c == out_c`, weight
5
+ shape `(C, 1, kh, kw)`). The generic `conv2d_forward_kernel` (im2col +
6
+ tl.dot) is structurally inefficient for this case: each group has K =
7
+ kh*kw (often 9) so the inner reduction loop runs once with most of the
8
+ BLOCK_INF lane idle, yet the kernel still pays full launch + im2col
9
+ cost for every group. On Sana 4Kpx VAE (the DC-AE depthwise blocks
10
+ groups=in=out=11200), the generic kernel measures ~4.8 s per call
11
+ versus ~2.6 ms for cuDNN's dedicated depthwise path — a ~1800× gap
12
+ that closes under the dedicated stencil.
13
+
14
+ Pattern reference (P-SANA-4KPX-RUNTIME Step 3 study):
15
+ - MultiPath/DepthwiseConv2d (CUTLASS sm_70 NCHW depthwise iterator,
16
+ threadblock 64x128x32, warp 32x32x32, instruction 8x8x4 HMMA)
17
+ - PyTorch PR #22302 — confirmed cuDNN has a separate depthwise code
18
+ path on Volta/Turing fp16 (cudnn >= 7600)
19
+
20
+ Our Triton-pure design: each program computes a (BC channels x TH rows x TW columns) output tile of one batch
21
+ sample, width the fastest axis (NCHW's contiguous one), accumulating the kh*kw stencil contributions in fp32: the
22
+ kh*kw shifted input windows of a compact 2-D tile overlap and are served from the L1. No im2col buffer, no
23
+ cross-channel reduction, no HMMA pseudo-GEMM packing; the stencil is unrolled at compile time (kh, kw constexpr).
24
+ The tile was (BLOCK_HW flattened positions x BLOCK_C channels) with the channels fastest, H*W elements apart in
25
+ memory: no load coalesced — SANA-Video's 11200-channel 3x3 fp32 [2, 11200, 24, 40] ran 2.63 ms where this one runs
26
+ 0.33 ms (529 GB/s; torch eager 0.38 ms), [22, 13440, 21, 42] 18.9 -> 4.2 ms (V100-SXM2, 2026-11-03).
27
+ """
28
+
29
+ import triton
30
+
31
+ from nbxtensor.ops._configs import nbx_autotune
32
+ import triton.language as tl
33
+
34
+ from nbxtensor.ops._autotune_policy import maybe_pin_single, is_depthwise_conv2d_pinned
35
+
36
+
37
+ _DEPTHWISE_CONV2D_CONFIGS = [
38
+ triton.Config({'BC': 1, 'TH': 8, 'TW': 64}, num_stages=2, num_warps=4),
39
+ triton.Config({'BC': 1, 'TH': 16, 'TW': 64}, num_stages=2, num_warps=4),
40
+ triton.Config({'BC': 1, 'TH': 32, 'TW': 64}, num_stages=2, num_warps=8),
41
+ triton.Config({'BC': 2, 'TH': 8, 'TW': 64}, num_stages=2, num_warps=4),
42
+ triton.Config({'BC': 4, 'TH': 8, 'TW': 32}, num_stages=2, num_warps=4),
43
+ triton.Config({'BC': 4, 'TH': 8, 'TW': 64}, num_stages=2, num_warps=8),
44
+ triton.Config({'BC': 8, 'TH': 4, 'TW': 64}, num_stages=2, num_warps=8),
45
+ triton.Config({'BC': 16, 'TH': 4, 'TW': 32}, num_stages=2, num_warps=8),
46
+ triton.Config({'BC': 1, 'TH': 4, 'TW': 128}, num_stages=2, num_warps=4),
47
+ # one-row tiles: the census's depthwise rows are mostly conv1d run at H = 1 (1 x 7, 64-1024 channels), where a
48
+ # taller tile is all masked rows — measured best there (1024 x 2048 fp16: 0.027 ms, the flattened tile 0.062)
49
+ triton.Config({'BC': 4, 'TH': 1, 'TW': 128}, num_stages=2, num_warps=4),
50
+ triton.Config({'BC': 8, 'TH': 1, 'TW': 64}, num_stages=2, num_warps=4),
51
+ triton.Config({'BC': 16, 'TH': 1, 'TW': 32}, num_stages=2, num_warps=4),
52
+ triton.Config({'BC': 16, 'TH': 1, 'TW': 64}, num_stages=2, num_warps=8),
53
+ triton.Config({'BC': 32, 'TH': 1, 'TW': 32}, num_stages=2, num_warps=8),
54
+ ]
55
+
56
+
57
+ @nbx_autotune(
58
+ configs=maybe_pin_single(_DEPTHWISE_CONV2D_CONFIGS, is_depthwise_conv2d_pinned),
59
+ key=['C', 'H_in', 'W_in_key', 'H_out', 'W_out_key', 'kh', 'kw',
60
+ 'stride_h', 'stride_w', 'pad_h', 'pad_w', 'fp16'],
61
+ cache_results=True,
62
+ )
63
+ @triton.jit
64
+ def depthwise_conv2d_kernel(
65
+ x_ptr, w_ptr, out_ptr,
66
+ N, C,
67
+ H_in, W_in, H_out, W_out,
68
+ W_in_key, W_out_key,
69
+ x_n_stride, x_c_stride, x_h_stride, x_w_stride,
70
+ w_c_stride, w_kh_stride, w_kw_stride,
71
+ out_n_stride, out_c_stride, out_h_stride, out_w_stride,
72
+ kh: tl.constexpr, kw: tl.constexpr,
73
+ stride_h: tl.constexpr, stride_w: tl.constexpr,
74
+ pad_h: tl.constexpr, pad_w: tl.constexpr,
75
+ fp16: tl.constexpr,
76
+ BC: tl.constexpr = 2,
77
+ TH: tl.constexpr = 8,
78
+ TW: tl.constexpr = 64,
79
+ WIDE_OFFSETS: tl.constexpr = True,
80
+ ):
81
+ # 64-bit offsets only where an operand's extent needs them (`WIDE_OFFSETS`, the wrapper's `_conv_offsets_wide`, as
82
+ # conv2d): 32-bit offsets are exact below 2^31, and the 64-bit ones are emulated on Apple's ALUs — under the i64 fix of
83
+ # the Metal backend (b06cbb0d) the fp32 3x3 depthwise (13440 ch, 42x21) ran 8.4 -> 12.5 ms (Mac, 2026-10-21)
84
+ if WIDE_OFFSETS:
85
+ pid_nc = tl.program_id(0).to(tl.int64)
86
+ pid_h = tl.program_id(1).to(tl.int64)
87
+ pid_w = tl.program_id(2).to(tl.int64)
88
+ else:
89
+ pid_nc = tl.program_id(0)
90
+ pid_h = tl.program_id(1)
91
+ pid_w = tl.program_id(2)
92
+ n_cb = tl.cdiv(C, BC)
93
+ pid_n = pid_nc // n_cb
94
+ offs_c = (pid_nc % n_cb) * BC + tl.arange(0, BC)
95
+ oh = pid_h * TH + tl.arange(0, TH)
96
+ ow = pid_w * TW + tl.arange(0, TW)
97
+ c_mask = offs_c < C
98
+
99
+ accum = tl.zeros((BC, TH, TW), dtype=tl.float32)
100
+ x_base = x_ptr + pid_n * x_n_stride + offs_c[:, None, None] * x_c_stride
101
+
102
+ # Unrolled stencil over kh x kw (constexpr): each tap reads the input window the output tile shifted by it.
103
+ for kh_i in tl.static_range(0, kh):
104
+ ih = oh * stride_h + kh_i - pad_h
105
+ h_ok = (ih >= 0) & (ih < H_in)
106
+ for kw_i in tl.static_range(0, kw):
107
+ iw = ow * stride_w + kw_i - pad_w
108
+ w_ok = (iw >= 0) & (iw < W_in)
109
+ full_mask = c_mask[:, None, None] & h_ok[None, :, None] & w_ok[None, None, :]
110
+ x_block = tl.load(x_base + ih[None, :, None] * x_h_stride + iw[None, None, :] * x_w_stride,
111
+ mask=full_mask, other=0.0)
112
+ w_block = tl.load(w_ptr + offs_c * w_c_stride + kh_i * w_kh_stride + kw_i * w_kw_stride,
113
+ mask=c_mask, other=0.0)
114
+ # Upcast for EVERY dtype, not just fp16. The accumulator is fp32 either way, so
115
+ # this costs nothing — and multiplying in the operands' own dtype made bf16 with
116
+ # padding wrong on Metal: deviation 0.754 against the fp64 oracle where unpadded
117
+ # bf16 sat at its 3.2e-03 mantissa floor, with the error in the ALL-IN-BOUNDS
118
+ # interior while the genuinely masked border row was clean. fp32 and fp16 were
119
+ # unaffected at both paddings, and the same source is exact to the mantissa on
120
+ # CUDA (Triton 3.8.0, V100 sm_70: bf16 pad0 == pad1), so the bad lowering is the
121
+ # Metal backend's for a native bf16 product of a masked-loaded operand. Upcasting
122
+ # removes the dependence on it rather than relying on it being fixed below us.
123
+ # Gate: tests/unit/kernels/test_depthwise_bf16_padding.py (2026-09-22).
124
+ accum += x_block.to(tl.float32) * w_block.to(tl.float32)[:, None, None]
125
+
126
+ out_addr = (out_ptr + pid_n * out_n_stride + offs_c[:, None, None] * out_c_stride
127
+ + oh[None, :, None] * out_h_stride + ow[None, None, :] * out_w_stride)
128
+ out_mask = c_mask[:, None, None] & (oh < H_out)[None, :, None] & (ow < W_out)[None, None, :]
129
+ if fp16:
130
+ tl.store(out_addr, accum.to(tl.float16), mask=out_mask)
131
+ else:
132
+ tl.store(out_addr, accum, mask=out_mask)
@@ -0,0 +1,190 @@
1
+ """E7 — fused int4 dequant-GEMV family (quantized tier, pure Triton).
2
+
3
+ Weight-only int4, groupwise g=128, ASYMMETRIC: per group along K an
4
+ fp16 scale and an fp16 min; dequant is `q * scale + min` with q an
5
+ UNSIGNED nibble (llama.cpp q4_1's exact form — no -8 offset, no
6
+ AutoGPTQ +1 storage quirk). Packing: 8 nibbles per int32, flat
7
+ low-nibble-first (nibble i of word r encodes element k = 8*r + i),
8
+ W_q laid out (K//8, N) K-major so loads coalesce along N — the
9
+ GemLite convention.
10
+
11
+ THE TIER'S BYTE ORACLE (scoping "quantized tier contract", clause 1):
12
+ the fused kernel must be byte-identical to dequantize-then-GEMV. A
13
+ cuBLAS/torch reference can never be byte-matched (its reduction order
14
+ is internal); the oracle is therefore built from THIS file's own
15
+ kernels: `dequant_int4_kernel` (standalone dequant whose dequant
16
+ expression is textually identical to the fused kernel's) feeding
17
+ `gemv_ref_kernel` (same BLOCK_N/BLOCK_K, same ascending-K chunk
18
+ loop, same tl.sum tree, same fp32 accumulator). Fused == oracle by
19
+ construction; the microtest proves it byte-for-byte with ONE pinned
20
+ config for both kernels (autotune would change the tl.sum tree — E7
21
+ GEMV sits OUTSIDE the mm/bmm/addmm/conv2d autotune sanction).
22
+
23
+ sm_70 discipline (sourced, R38 study + 2026-08-16 research): M=1
24
+ decode GEMV, no tl.dot (Volta fp16 dot lowers to FMA anyway), SPLIT_K
25
+ = 1 — a single program per N-tile walks all of K, NO atomics
26
+ (atomic-splitK is arrival-order nondeterministic and fp16 atomics are
27
+ version-fragile on our pin; both break the byte contract). The win is
28
+ weight bandwidth: 4x fewer weight bytes than fp16. num_stages <= 2
29
+ (cp.async pipelining is sm_80+; more stages only add register
30
+ pressure to the unpack-heavy loop).
31
+
32
+ Constraints enforced by the wrappers: K % BLOCK_K == 0 handled by
33
+ masking; BLOCK_K in {32, 64, 128} divides group_size=128 so a K-chunk
34
+ never crosses a group boundary (one scale/min scalar per (chunk, n) —
35
+ the GemLite pruner rule).
36
+ """
37
+
38
+ import triton
39
+ import triton.language as tl
40
+
41
+ GROUP_SIZE: int = 128
42
+ PACK: int = 8 # nibbles per int32
43
+
44
+ # Pinned parity config: oracle AND fused always run the SAME values —
45
+ # the tl.sum reduction tree must be identical between the two, which
46
+ # is the tier contract (fused == dequant->ref byte-for-byte).
47
+ # BLOCK_K and NUM_WARPS stay pinned. BLOCK_N adapts at runtime for
48
+ # small-N grids (wrappers._dequant_gemv_block_n, 2026-08-25: halved
49
+ # pow2 until the grid reaches the vendor-yml occupancy target,
50
+ # NBX_DGEMV_ADAPT=0 kill) — legitimate because BLOCK_N only
51
+ # partitions OUTPUT columns while each column's K-reduction tree is
52
+ # BLOCK_K-fixed; the parity microtest proves fused == oracle
53
+ # byte-for-byte at the adapted widths (1/8) as well as the pinned 64.
54
+ # Cross-WIDTH outputs differ in low bits (the per-column lowering
55
+ # changes with BLOCK_N) — width changes are drift-class and go
56
+ # through the model byte gate, never silently.
57
+ BLOCK_N: int = 64
58
+ BLOCK_K: int = 128
59
+ NUM_WARPS: int = 4
60
+
61
+
62
+ @triton.jit
63
+ def dequant_int4_kernel(
64
+ wq_ptr, scales_ptr, mins_ptr, out_ptr,
65
+ K, N,
66
+ stride_wk, stride_wn,
67
+ stride_sg, stride_sn,
68
+ stride_ok, stride_on,
69
+ BLOCK_K_C: tl.constexpr, BLOCK_N_C: tl.constexpr,
70
+ GROUP_C: tl.constexpr, PACK_C: tl.constexpr,
71
+ ):
72
+ """Standalone dequant: packed int4 -> dense fp32 [K, N].
73
+
74
+ Oracle part 1. The dequant expression below is the TEXTUAL
75
+ contract shared with the fused kernel: same loads, same shifts,
76
+ same pure-fp32 dtype path.
77
+ """
78
+ pid_k = tl.program_id(0).to(tl.int64)
79
+ pid_n = tl.program_id(1).to(tl.int64)
80
+ offs_k = pid_k * BLOCK_K_C + tl.arange(0, BLOCK_K_C)
81
+ offs_n = pid_n * BLOCK_N_C + tl.arange(0, BLOCK_N_C)
82
+ mask_k = offs_k < K
83
+ mask_n = offs_n < N
84
+ mask = mask_k[:, None] & mask_n[None, :]
85
+
86
+ packed = tl.load(
87
+ wq_ptr + (offs_k[:, None] // PACK_C) * stride_wk
88
+ + offs_n[None, :] * stride_wn, mask=mask, other=0)
89
+ q = (packed >> ((offs_k[:, None] % PACK_C) * 4)) & 0xF
90
+
91
+ g = pid_k * BLOCK_K_C // GROUP_C # BLOCK_K_C divides GROUP_C
92
+ scale = tl.load(scales_ptr + g * stride_sg + offs_n * stride_sn,
93
+ mask=mask_n, other=0.0)
94
+ mn = tl.load(mins_ptr + g * stride_sg + offs_n * stride_sn,
95
+ mask=mask_n, other=0.0)
96
+ # Canonical dequant: PURE fp32 (llama.cpp dmmv default,
97
+ # dfloat=float — the fp16 weight never exists at runtime).
98
+ # Contraction-immune: q*scale is EXACT in fp32 (4-bit int x fp16
99
+ # mantissa), so fma(q,s,m) == q*s+m bit-for-bit whatever the
100
+ # compiler emits. The dense oracle tensor is fp32.
101
+ w = (q.to(tl.float32) * scale[None, :].to(tl.float32)
102
+ + mn[None, :].to(tl.float32))
103
+
104
+ tl.store(out_ptr + offs_k[:, None] * stride_ok
105
+ + offs_n[None, :] * stride_on, w, mask=mask)
106
+
107
+
108
+ @triton.jit
109
+ def gemv_ref_kernel(
110
+ x_ptr, w_ptr, out_ptr,
111
+ K, N,
112
+ stride_wk, stride_wn,
113
+ BLOCK_K_C: tl.constexpr, BLOCK_N_C: tl.constexpr,
114
+ ):
115
+ """Reference dense-weight GEMV: out[n] = sum_k x[k] * w[k, n].
116
+
117
+ Oracle part 2. Fixed reduction structure — ascending-K chunk loop,
118
+ per-chunk tl.sum over axis 0, fp32 accumulator, one store — the
119
+ EXACT structure of the fused kernel below. (A nibble-major
120
+ restructure with per-nibble partial sums was MEASURED and
121
+ reverted: it slowed the fused kernel 0.090 -> 0.224 ms at
122
+ 4096x11008 — the lane-selection sums cost more than the redundant
123
+ packed loads they save; the fused kernel is ALU-issue-bound, not
124
+ load-bound.)
125
+ """
126
+ pid_n = tl.program_id(0).to(tl.int64)
127
+ offs_n = pid_n * BLOCK_N_C + tl.arange(0, BLOCK_N_C)
128
+ mask_n = offs_n < N
129
+ acc = tl.zeros((BLOCK_N_C,), dtype=tl.float32)
130
+ for kc in range(0, tl.cdiv(K, BLOCK_K_C)):
131
+ offs_k = kc * BLOCK_K_C + tl.arange(0, BLOCK_K_C)
132
+ mask_k = offs_k < K
133
+ a = tl.load(x_ptr + offs_k, mask=mask_k, other=0.0)
134
+ b = tl.load(w_ptr + offs_k[:, None] * stride_wk
135
+ + offs_n[None, :] * stride_wn,
136
+ mask=mask_k[:, None] & mask_n[None, :], other=0.0)
137
+ acc += tl.sum(a.to(tl.float32)[:, None] * b.to(tl.float32),
138
+ axis=0)
139
+ tl.store(out_ptr + offs_n, acc, mask=mask_n)
140
+
141
+
142
+ @triton.jit
143
+ def dequant_gemv_int4_kernel(
144
+ x_ptr, wq_ptr, scales_ptr, mins_ptr, out_ptr,
145
+ K, N,
146
+ stride_wk, stride_wn,
147
+ stride_sg, stride_sn,
148
+ BLOCK_K_C: tl.constexpr, BLOCK_N_C: tl.constexpr,
149
+ GROUP_C: tl.constexpr, PACK_C: tl.constexpr,
150
+ ):
151
+ """Fused: out[n] = sum_k x[k] * dequant(wq)[k, n], fp32 accum.
152
+
153
+ Dequant expression textually identical to dequant_int4_kernel;
154
+ reduction structure textually identical to gemv_ref_kernel —
155
+ byte-equal to the two-step oracle by construction (proven by the
156
+ parity microtest with the pinned config). The packed load gathers
157
+ `offs_k // 8` (each int32 requested 8x, absorbed by L1) — the
158
+ nibble-major single-load variant was MEASURED SLOWER (see the
159
+ oracle kernel's docstring); the kernel is ALU-issue-bound.
160
+ """
161
+ pid_n = tl.program_id(0).to(tl.int64)
162
+ offs_n = pid_n * BLOCK_N_C + tl.arange(0, BLOCK_N_C)
163
+ mask_n = offs_n < N
164
+ acc = tl.zeros((BLOCK_N_C,), dtype=tl.float32)
165
+ for kc in range(0, tl.cdiv(K, BLOCK_K_C)):
166
+ offs_k = kc * BLOCK_K_C + tl.arange(0, BLOCK_K_C)
167
+ mask_k = offs_k < K
168
+ mask = mask_k[:, None] & mask_n[None, :]
169
+ a = tl.load(x_ptr + offs_k, mask=mask_k, other=0.0)
170
+
171
+ packed = tl.load(
172
+ wq_ptr + (offs_k[:, None] // PACK_C) * stride_wk
173
+ + offs_n[None, :] * stride_wn, mask=mask, other=0)
174
+ q = (packed >> ((offs_k[:, None] % PACK_C) * 4)) & 0xF
175
+ g = kc * BLOCK_K_C // GROUP_C # BLOCK_K_C divides GROUP_C
176
+ scale = tl.load(scales_ptr + g * stride_sg + offs_n * stride_sn,
177
+ mask=mask_n, other=0.0)
178
+ mn = tl.load(mins_ptr + g * stride_sg + offs_n * stride_sn,
179
+ mask=mask_n, other=0.0)
180
+ # Canonical dequant — textually identical to
181
+ # dequant_int4_kernel: PURE fp32, no fp16 materialization
182
+ # (contraction-immune: q*scale exact in fp32).
183
+ w = (q.to(tl.float32) * scale[None, :].to(tl.float32)
184
+ + mn[None, :].to(tl.float32))
185
+ # Masked K-tail rows dequant to `mn` (q=0), not 0 — zero them
186
+ # through the mask so the tail contributes exact +0.0, same as
187
+ # the oracle GEMV's masked b-load.
188
+ b = tl.where(mask, w, 0.0)
189
+ acc += tl.sum(a.to(tl.float32)[:, None] * b, axis=0)
190
+ tl.store(out_ptr + offs_n, acc, mask=mask_n)
nbxtensor/ops/dft.py ADDED
@@ -0,0 +1,67 @@
1
+ """DFT / iDFT basis matrices — pure @triton.jit kernels.
2
+
3
+ Builds the real cos/sin basis matrices for a Discrete Fourier Transform computed
4
+ by matrix multiplication. This is the standard small-N / non-power-of-2 path
5
+ (the DFT is an N×N matrix of complex exponentials applied to the signal) — used
6
+ when the radix-2 butterfly cannot run (n_fft not a power of 2, e.g. an iSTFT head
7
+ with n_fft=20). Model-agnostic: any non-pow2 FFT length routes here.
8
+
9
+ Layout (matches the rfft convention): frequency bins k in [0, N//2+1), samples
10
+ n in [0, N). The matrices are [N_bins, N], applied as `frames @ mat.T`.
11
+ """
12
+
13
+ import triton
14
+ import triton.language as tl
15
+
16
+
17
+ @triton.jit
18
+ def dft_rfft_matrix_kernel(
19
+ cos_ptr, # (N_bins, N) — cos(2π k n / N)
20
+ nsin_ptr, # (N_bins, N) — -sin(2π k n / N)
21
+ N_bins, N,
22
+ two_pi_over_N,
23
+ BLOCK_SIZE: tl.constexpr,
24
+ ):
25
+ """Forward rfft basis: X[k] = Σ_n x[n] (cos - i sin)(2π k n / N).
26
+
27
+ cos[k,n] = cos(2π k n / N), nsin[k,n] = -sin(2π k n / N) so that
28
+ X_real = x @ cos.T and X_imag = x @ nsin.T.
29
+ """
30
+ pid = tl.program_id(0).to(tl.int64)
31
+ offs = 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)
32
+ total = N_bins * N
33
+ mask = offs < total
34
+ k = offs // N
35
+ n = offs % N
36
+ angle = (k * n).to(tl.float32) * two_pi_over_N
37
+ tl.store(cos_ptr + offs, tl.cos(angle), mask=mask)
38
+ tl.store(nsin_ptr + offs, -tl.sin(angle), mask=mask)
39
+
40
+
41
+ @triton.jit
42
+ def idft_c2r_matrix_kernel(
43
+ cos_ptr, # (N_bins, N) — weight_k cos(2π k n / N) / N
44
+ sin_ptr, # (N_bins, N) — -weight_k sin(2π k n / N) / N
45
+ N_bins, N,
46
+ two_pi_over_N,
47
+ inv_N,
48
+ n_even, # 1 if N is even (Nyquist bin present), else 0
49
+ BLOCK_SIZE: tl.constexpr,
50
+ ):
51
+ """Inverse rfft (irfft) basis, exploiting Hermitian symmetry.
52
+
53
+ x[n] = (1/N) Σ_k weight_k [Re(X[k]) cos(2π k n / N) - Im(X[k]) sin(2π k n / N)]
54
+ weight_k = 1 for k=0 and (N even) k=N/2, else 2. So
55
+ real_out = X_real @ cos + X_imag @ sin.
56
+ """
57
+ pid = tl.program_id(0).to(tl.int64)
58
+ offs = 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)
59
+ total = N_bins * N
60
+ mask = offs < total
61
+ k = offs // N
62
+ n = offs % N
63
+ is_edge = (k == 0) | ((n_even == 1) & (k == N_bins - 1))
64
+ weight = tl.where(is_edge, 1.0, 2.0) * inv_N
65
+ angle = (k * n).to(tl.float32) * two_pi_over_N
66
+ tl.store(cos_ptr + offs, weight * tl.cos(angle), mask=mask)
67
+ tl.store(sin_ptr + offs, -weight * tl.sin(angle), mask=mask)