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,195 @@
1
+ """Device-side MoE token alignment — constant-tuple, deterministic.
2
+
3
+ Replaces the host moe_align (D2H of topk_ids -> numpy bincount/scatter
4
+ -> H2D of tables) with three tiny Triton kernels so the whole routing
5
+ pipeline stays on device. Two independent wins:
6
+
7
+ 1. Replay eligibility (P-REPLAY-KV-DECODE): the host align was a D2H
8
+ read of router output per MoE layer per step — a structural
9
+ plan-breaker (recorded H2D tables would replay ONE step's expert
10
+ choice forever; the loud refusal at recording caught exactly this).
11
+ Device-side, the recorded launches recompute the tables from the
12
+ CURRENT router output at every replay.
13
+ 2. Sync elimination: 48 D2H round-trips per decode step disappear from
14
+ the MoE band (same class as the device-scalar workstream).
15
+
16
+ Pattern source: vLLM's moe_align_block_size CUDA kernel (grid sized on
17
+ the worst case, consumers early-exit on the device num_tokens_post_
18
+ padded scalar — that guard already exists in all three fused variants).
19
+ vLLM's atomic rank assignment is NOT reproduced: atomics permute tokens
20
+ within an expert run-to-run. Here the rank is a deterministic pairwise
21
+ count (#earlier tokens with the same expert), which reproduces the host
22
+ sort's order EXACTLY — the byte gate against the removed host path
23
+ adjudicates the swap directly. n = M * top_k is tiny (8 at decode), so
24
+ the O(n^2) rank costs nothing at the scale it runs.
25
+
26
+ Outputs are sized on the shape-derived worst case
27
+ min(n * BS, n + E * (BS - 1)) rounded up to BS — the true total lives
28
+ ONLY in the device scalar. All launch tuples are constant per shape
29
+ class by construction.
30
+ """
31
+
32
+ import triton
33
+ import triton.language as tl
34
+
35
+ from nbxtensor.ops._configs import fits_shared_memory, nbx_autotune
36
+
37
+ # The chunk widths and warps of the three stages, candidates the certification measures per key; the launch before
38
+ # 2026-11-04 (BT 128, BF the experts' padded count, BLK 128 positions, BLKT = BN = 128, 4 warps — 32 and 64 wide on
39
+ # Metal) is among them. Every candidate computes the same integers: the choice is speed only.
40
+ _STAGE1_CONFIGS = [triton.Config({'BT': bt, 'BF': bf}, num_warps=w, num_stages=1)
41
+ for bt in (32, 64, 128, 256) for bf in (16, 32, 64, 128, 256) for w in (2, 4, 8)]
42
+ _STAGE2_CONFIGS = [triton.Config({'BLK': blk, 'BF': bf}, num_warps=w, num_stages=1)
43
+ for blk in (64, 128, 256) for bf in (16, 32, 64, 128, 256) for w in (2, 4, 8)]
44
+ _STAGE3_CONFIGS = [triton.Config({'BLKT': t, 'BN': bn}, num_warps=w, num_stages=1)
45
+ for t in (32, 64, 128) for bn in (32, 64, 128) for w in (2, 4, 8)]
46
+
47
+
48
+ def _fits(blocks) -> bool:
49
+ """Whether the stage's widest int32 block fits the device's shared memory per block (a cross-warp reduction stages
50
+ its block there; Metal, 32 KB: a [128, 128] block asked 65 536 bytes, Qwen3-30B-A3B)."""
51
+ return fits_shared_memory(4 * max(a * b for a, b in blocks))
52
+
53
+
54
+ def _prune_stage1(configs, named_args, **kwargs):
55
+ """An expert chunk dividing the experts' padded count (both powers of two: no wider than it), blocks that fit."""
56
+ be = int({**named_args, **kwargs}['BE']) # a constexpr arrives among the launch's keywords
57
+ return [c for c in configs if c.kwargs['BF'] <= be
58
+ and _fits(((c.kwargs['BF'], c.kwargs['BT']), (be, c.kwargs['BF'])))]
59
+
60
+
61
+ def _prune_stage2(configs, named_args, **kwargs):
62
+ """Positions per program a whole number of blocks, an expert chunk dividing the padded count, blocks that fit."""
63
+ a = {**named_args, **kwargs}
64
+ be, bs = int(a['BE']), int(a['BS'])
65
+ return [c for c in configs if c.kwargs['BF'] <= be and c.kwargs['BLK'] % bs == 0
66
+ and _fits(((c.kwargs['BLK'], c.kwargs['BF']),))]
67
+
68
+
69
+ def _prune_stage3(configs, named_args, **kwargs):
70
+ """The rank matrix [BLKT, BN] fits."""
71
+ return [c for c in configs if _fits(((c.kwargs['BLKT'], c.kwargs['BN']),))]
72
+
73
+
74
+ @nbx_autotune(configs=_STAGE1_CONFIGS, key=['BS', 'BE', 'E', 'N_BUCKET'],
75
+ prune_configs_by={'early_config_prune': _prune_stage1}, cache_results=True)
76
+ @triton.jit
77
+ def moe_align_stage1_kernel(
78
+ topk_ids_ptr, # [n] int64 — flat expert index per (token, k)
79
+ offsets_ptr, # [E] int64 out — exclusive padded starts
80
+ padded_ptr, # [E] int64 out — per-expert padded count
81
+ num_post_pad_ptr, # [1] int64 out — total padded entries
82
+ n,
83
+ N_BUCKET, # the layer's (token, expert) pairs' class (`launch_keys.moe_align_classes`): the key's (unused here)
84
+ BS: tl.constexpr, # block size (fused kernel BLOCK_SIZE_M)
85
+ BE: tl.constexpr, # padded expert count (pow2 >= E)
86
+ E: tl.constexpr, # true expert count
87
+ BT: tl.constexpr, # token chunk width
88
+ BF: tl.constexpr, # expert chunk width (divides BE)
89
+ ):
90
+ """Single program: per-expert counts -> padded counts -> exclusive
91
+ prefix offsets (masked triangular sum — no tl.cumsum dependency).
92
+ Worked over expert chunks of BF: each chunk's padded counts go to the
93
+ prefix sum as a [BE, BF] block, so no [BE, BE] or [BE, BT] matrix is
94
+ held; BT and BF are the key's certified configuration, among the widths
95
+ whose blocks fit the device's shared memory (`_prune_stage1`)."""
96
+ e = tl.arange(0, BE)
97
+ excl = tl.zeros((BE,), dtype=tl.int32)
98
+ total = tl.sum(tl.zeros((BF,), dtype=tl.int32), axis=0)
99
+ for f0 in tl.static_range(0, BE, BF):
100
+ f = f0 + tl.arange(0, BF)
101
+ counts = tl.zeros((BF,), dtype=tl.int32)
102
+ for start in range(0, n, BT):
103
+ t = start + tl.arange(0, BT)
104
+ ids = tl.load(topk_ids_ptr + t, mask=t < n, other=-1)
105
+ m = (ids[None, :] == f[:, None]) & (t[None, :] < n)
106
+ counts += tl.sum(m.to(tl.int32), axis=1)
107
+ padded = ((counts + BS - 1) // BS) * BS
108
+ mask_f = f < E
109
+ tl.store(padded_ptr + f, padded.to(tl.int64), mask=mask_f)
110
+ total += tl.sum(tl.where(mask_f, padded, 0), axis=0)
111
+ tri = f[None, :] < e[:, None]
112
+ excl += tl.sum(tl.where(tri, padded[None, :], 0), axis=1)
113
+ tl.store(offsets_ptr + e, excl.to(tl.int64), mask=e < E)
114
+ tl.store(num_post_pad_ptr, total.to(tl.int64))
115
+
116
+
117
+ @nbx_autotune(configs=_STAGE2_CONFIGS, key=['BS', 'BE', 'E', 'N_BUCKET'],
118
+ prune_configs_by={'early_config_prune': _prune_stage2}, cache_results=True)
119
+ @triton.jit
120
+ def moe_align_stage2_kernel(
121
+ offsets_ptr, # [E] int64
122
+ padded_ptr, # [E] int64
123
+ sorted_ids_ptr, # [max_total] int64 out — sentinel-filled here
124
+ expert_ids_ptr, # [max_blocks] int64 out — expert per block, -1 pad
125
+ n,
126
+ max_total,
127
+ N_BUCKET, # the autotune key's length class (unused in the body)
128
+ BS: tl.constexpr,
129
+ BE: tl.constexpr,
130
+ E: tl.constexpr,
131
+ BLK: tl.constexpr, # positions per program (multiple of BS)
132
+ BF: tl.constexpr, # expert chunk width (divides BE)
133
+ ):
134
+ """Grid over output positions: sentinel-fill sorted ids and resolve
135
+ each block's owning expert (offsets[e] <= block_start < offsets[e]
136
+ + padded[e]); blocks past the true total get -1 and are skipped by
137
+ the fused kernels' num_tokens_post_padded early-exit anyway. The
138
+ experts are read in chunks of BF ([BLK, BF] blocks, the key's certified
139
+ configuration among those that fit, as stage 1)."""
140
+ pid = tl.program_id(0).to(tl.int64)
141
+ p = pid * BLK + tl.arange(0, BLK)
142
+ in_p = p < max_total
143
+ tl.store(sorted_ids_ptr + p, tl.zeros((BLK,), dtype=tl.int64) + n,
144
+ mask=in_p)
145
+ b = p // BS
146
+ pb = (b * BS).to(tl.int64)
147
+ eid = tl.zeros((BLK,), dtype=tl.int32)
148
+ hits = tl.zeros((BLK,), dtype=tl.int32)
149
+ for f0 in tl.static_range(0, BE, BF):
150
+ e = f0 + tl.arange(0, BF)
151
+ mask_e = e < E
152
+ offs = tl.load(offsets_ptr + e, mask=mask_e, other=0)
153
+ padd = tl.load(padded_ptr + e, mask=mask_e, other=0)
154
+ m = (pb[:, None] >= offs[None, :]) \
155
+ & (pb[:, None] < (offs + padd)[None, :]) & mask_e[None, :]
156
+ eid += tl.sum(tl.where(m, e[None, :], 0), axis=1)
157
+ hits += tl.sum(m.to(tl.int32), axis=1)
158
+ eid = tl.where(hits > 0, eid, -1)
159
+ write_b = in_p & (p % BS == 0)
160
+ tl.store(expert_ids_ptr + b, eid.to(tl.int64), mask=write_b)
161
+
162
+
163
+ @nbx_autotune(configs=_STAGE3_CONFIGS, key=['E', 'N_BUCKET'],
164
+ prune_configs_by={'early_config_prune': _prune_stage3}, cache_results=True)
165
+ @triton.jit
166
+ def moe_align_stage3_kernel(
167
+ topk_ids_ptr, # [n] int64
168
+ offsets_ptr, # [E] int64
169
+ sorted_ids_ptr, # [max_total] int64 — scatter over the sentinels
170
+ n,
171
+ N_BUCKET, # the autotune key's length class (unused in the body)
172
+ E: tl.constexpr, # true expert count (out-of-range ids skipped,
173
+ # mirroring the host scatter's bounds check)
174
+ BLKT: tl.constexpr, # tokens per program
175
+ BN: tl.constexpr, # comparison chunk width
176
+ ):
177
+ """Grid over tokens: deterministic rank (#earlier tokens with the
178
+ same expert) -> scatter token index to offsets[expert] + rank.
179
+ Order-preserving, no atomics — byte-identical to the host sort it
180
+ replaces. MUST launch after stage 2 (stream-serialized): it
181
+ overwrites sentinel positions."""
182
+ pid = tl.program_id(0).to(tl.int64)
183
+ i = pid * BLKT + tl.arange(0, BLKT)
184
+ in_i = i < n
185
+ my = tl.load(topk_ids_ptr + i, mask=in_i, other=-1)
186
+ in_i = in_i & (my >= 0) & (my < E)
187
+ rank = tl.zeros((BLKT,), dtype=tl.int32)
188
+ for start in range(0, n, BN):
189
+ j = start + tl.arange(0, BN)
190
+ idj = tl.load(topk_ids_ptr + j, mask=j < n, other=-2)
191
+ m = (idj[None, :] == my[:, None]) & (j[None, :] < i[:, None])
192
+ rank += tl.sum(m.to(tl.int32), axis=1)
193
+ off_e = tl.load(offsets_ptr + my, mask=in_i, other=0)
194
+ pos = off_e + rank
195
+ tl.store(sorted_ids_ptr + pos, i.to(tl.int64), mask=in_i)
@@ -0,0 +1,374 @@
1
+ """Vector (SIMT) MoE decode band — M=1, int4 g128 asym, no tl.dot.
2
+
3
+ Fourth member of the SIMT decode family (decode_attn_vec + gemv_vec,
4
+ both adopted). The decode MoE band ran as three Grouped-GEMM launches
5
+ over sorted-token tables: at M=1 with 8 active experts of 128, every
6
+ 16-row MMA tile carries ONE real row (15/16 M-waste), plus the 3-stage
7
+ moe_align table build, a separate SwiGLU pass, and a sum_wrapper
8
+ combine — 18.4 ms of a 38.4 ms step for ~944 MB of weight reads
9
+ (~12x the streaming floor; full_step_attribution_2026_08_24).
10
+
11
+ The 2026-08-24 web research (R16) confirms the regime: vLLM itself
12
+ abandoned its grouped Triton kernel at small m (its CUDA moe_wna16 —
13
+ SIMT half2 FMA, one thread per output column, group-boundary scale
14
+ loads — wins +41% at bs=1 on A100/DeepSeek; our num_seqs*top_k/E =
15
+ 0.0625 is two orders below its own switchover). Its combine uses
16
+ atomicAdd (order-nondeterministic) — REFUSED here; the deterministic
17
+ production forms fuse only the routing-weight MULTIPLY into the
18
+ down epilogue (vLLM Triton MUL_ROUTED_WEIGHT) and keep the sum in
19
+ fixed order. This kernel pair goes one step further and keeps the
20
+ cross-expert sum INSIDE the down kernel as a FIXED-ORDER expert loop —
21
+ no intermediate per-slot outputs, no sum_wrapper, no atomics.
22
+
23
+ Three kernels replace the whole band (second pass ADOPTED 2026-08-25):
24
+ 1. `moe_gateup_vec_kernel` — grid (TOP_K, cdiv(INTER, BLOCK_N)):
25
+ each program owns one active expert's tile of the intermediate
26
+ dim, runs TWO dequant-matvec accumulators (gate and up — the
27
+ llama.cpp dual-accumulator GLU shape, no weight-layout change)
28
+ over K with int4-g128-asym dequant in the FMA loop, and applies
29
+ SwiGLU in the epilogue: h[e, tile] = silu(g) * u.
30
+ 2. `moe_down_split_vec_kernel` — grid (TOP_K, cdiv(HID, BLOCK_N)):
31
+ the expert axis lives on the GRID (8x the programs of the fused
32
+ loop — the occupancy lever: 32 -> 512 programs on 80 SMs moved
33
+ the down projection from 46 to ~248 GB/s), each program writing
34
+ its ROUTER-WEIGHTED partial.
35
+ 3. `moe_part_reduce_kernel` — fixed-order sum of the TOP_K weighted
36
+ partials; the combine stays deterministic by construction.
37
+ `moe_down_combine_vec_kernel` (the first-pass fused down + combine)
38
+ remains as the NBX_MOEV_DSPLIT=0 kill path. moe_align disappears at
39
+ M=1 (no token sort exists to build); the SwiGLU pass, the
40
+ sum_wrapper combine, and the zero-init of the grouped caches
41
+ disappear with it. A gate/up-split projection variant (one matrix
42
+ per program + separate silu-mul epilogue) was profiled and REFUSED:
43
+ 2x42.9+3.4 us vs the fused dual-accumulator gateup's 71 us at BN=32
44
+ (nsys ladder 2026-08-25). A wide-load variant (one packed int32
45
+ load per word, [KP, PACK, BN] register unpack) was also REFUSED:
46
+ x0.69 at equal blocks, best point x1.02 = rank noise — Volta's L1
47
+ serves the offs_k//PACK redundant requests nearly free, and the
48
+ cross-kernel comparison (down single-stream ~248 GB/s vs gateup
49
+ dual-stream ~200 at identical dequant ALU per byte) proves the
50
+ unpack ALU is NOT the binding constraint. DOCUMENTED WALL
51
+ (2026-08-25): the band's residual over its ~3.2 ms ALU working bar
52
+ is the dual-stream SwiGLU gateup structure at M=1 on sm_70; the
53
+ upstream reference for this exact regime (vLLM moe_wna16) is a CUDA
54
+ half2 SIMT kernel — outside Triton-pure scope. Evidence:
55
+ validation_outputs/moev2adopt_offbyte_gate_2026_08_25/WALL.md.
56
+
57
+ Dequant expression TEXTUALLY IDENTICAL to dequant_gemv_int4_kernel
58
+ (the parity-derived brick: q unpacked LSB-nibble from int32,
59
+ w = q*scale + qmin in pure fp32, masked tails contribute exact +0.0).
60
+ Expert weight/scale/qmin pointers come from the existing PtrTables
61
+ int64 tables (tl.cast(..., tl.pointer_type, bitcast=True) — the
62
+ established idiom).
63
+ """
64
+
65
+ import triton
66
+ import triton.language as tl
67
+
68
+ from nbxtensor.ops._configs import fits_shared_memory, nbx_autotune
69
+
70
+ # The half-precision band's tiles, candidates the certification measures per key: the ladder the band was first
71
+ # measured over (BLOCK_N {16, 32, 64} x BLOCK_K {64, 128, 256} x warps {2, 4, 8}, Qwen3-30B-A3B's experts on a V100,
72
+ # 2026-10-28: 193-240 us a call, the leading group around 32 / 128 / 4), and the partials' fixed-order sum's.
73
+ _HALF_VEC_CONFIGS = [triton.Config({'BLOCK_N': bn, 'BLOCK_K': bk}, num_warps=w)
74
+ for bn in (16, 32, 64) for bk in (64, 128, 256) for w in (2, 4, 8)]
75
+ _PART_REDUCE_CONFIGS = [triton.Config({'BLOCK_N': bn}, num_warps=w) for bn in (128, 256, 512, 1024) for w in (1, 2, 4)]
76
+
77
+
78
+ def _prune_half_vec(configs, named_args, **kwargs):
79
+ """The [BLOCK_K, BLOCK_N] fp32 product block each step reduces across its warps fits the device's shared memory
80
+ (Metal, 32 KB: 256 x 64 asked 65 536 bytes and its emitter aborted the certifier, the Mac's RUN 24d, 2026-11-04)."""
81
+ return [c for c in configs if fits_shared_memory(4 * c.kwargs['BLOCK_K'] * c.kwargs['BLOCK_N'])]
82
+
83
+
84
+ @triton.jit
85
+ def moe_gateup_vec_kernel(
86
+ x_ptr, # [K] activation (fp16/fp32)
87
+ topk_ids_ptr, # [TOP_K] int — active expert ids, fixed order
88
+ gate_qw_tab, # [E] int64 — qweight ptrs (int32 [K//8, N])
89
+ gate_sc_tab, # [E] int64 — scales ptrs (fp16 [G, N])
90
+ gate_mn_tab, # [E] int64 — qmins ptrs (fp16 [G, N])
91
+ up_qw_tab, up_sc_tab, up_mn_tab,
92
+ h_ptr, # [TOP_K, N] fp32 out — silu(gate) * up
93
+ K, N,
94
+ stride_wk, stride_wn, # packed-int32 strides (shared gate/up)
95
+ stride_sg, stride_sn, # scales/qmins strides
96
+ BLOCK_N: tl.constexpr,
97
+ BLOCK_K: tl.constexpr, # divides GROUP (128)
98
+ GROUP: tl.constexpr,
99
+ PACK: tl.constexpr, # 8 nibbles per int32
100
+ ):
101
+ pid_e = tl.program_id(0).to(tl.int64)
102
+ pid_n = tl.program_id(1).to(tl.int64)
103
+ eid = tl.load(topk_ids_ptr + pid_e).to(tl.int64)
104
+
105
+ offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
106
+ mask_n = offs_n < N
107
+
108
+ g_qw = tl.cast(tl.load(gate_qw_tab + eid),
109
+ tl.pointer_type(tl.int32), bitcast=True)
110
+ g_sc = tl.cast(tl.load(gate_sc_tab + eid),
111
+ tl.pointer_type(tl.float16), bitcast=True)
112
+ g_mn = tl.cast(tl.load(gate_mn_tab + eid),
113
+ tl.pointer_type(tl.float16), bitcast=True)
114
+ u_qw = tl.cast(tl.load(up_qw_tab + eid),
115
+ tl.pointer_type(tl.int32), bitcast=True)
116
+ u_sc = tl.cast(tl.load(up_sc_tab + eid),
117
+ tl.pointer_type(tl.float16), bitcast=True)
118
+ u_mn = tl.cast(tl.load(up_mn_tab + eid),
119
+ tl.pointer_type(tl.float16), bitcast=True)
120
+
121
+ acc_g = tl.zeros((BLOCK_N,), dtype=tl.float32)
122
+ acc_u = tl.zeros((BLOCK_N,), dtype=tl.float32)
123
+ for kc in range(0, tl.cdiv(K, BLOCK_K)):
124
+ offs_k = kc * BLOCK_K + tl.arange(0, BLOCK_K)
125
+ mask_k = offs_k < K
126
+ mask = mask_k[:, None] & mask_n[None, :]
127
+ a = tl.load(x_ptr + offs_k, mask=mask_k, other=0.0).to(tl.float32)
128
+ g = kc * BLOCK_K // GROUP # BLOCK_K divides GROUP
129
+ w_off = ((offs_k[:, None] // PACK) * stride_wk
130
+ + offs_n[None, :] * stride_wn)
131
+ shift = (offs_k[:, None] % PACK) * 4
132
+
133
+ packed = tl.load(g_qw + w_off, mask=mask, other=0)
134
+ q = (packed >> shift) & 0xF
135
+ scale = tl.load(g_sc + g * stride_sg + offs_n * stride_sn,
136
+ mask=mask_n, other=0.0)
137
+ mn = tl.load(g_mn + g * stride_sg + offs_n * stride_sn,
138
+ mask=mask_n, other=0.0)
139
+ w = (q.to(tl.float32) * scale[None, :].to(tl.float32)
140
+ + mn[None, :].to(tl.float32))
141
+ b = tl.where(mask, w, 0.0)
142
+ acc_g += tl.sum(a[:, None] * b, axis=0)
143
+
144
+ packed = tl.load(u_qw + w_off, mask=mask, other=0)
145
+ q = (packed >> shift) & 0xF
146
+ scale = tl.load(u_sc + g * stride_sg + offs_n * stride_sn,
147
+ mask=mask_n, other=0.0)
148
+ mn = tl.load(u_mn + g * stride_sg + offs_n * stride_sn,
149
+ mask=mask_n, other=0.0)
150
+ w = (q.to(tl.float32) * scale[None, :].to(tl.float32)
151
+ + mn[None, :].to(tl.float32))
152
+ b = tl.where(mask, w, 0.0)
153
+ acc_u += tl.sum(a[:, None] * b, axis=0)
154
+
155
+ # SwiGLU epilogue: silu(gate) * up, pure fp32.
156
+ h = acc_g * tl.sigmoid(acc_g) * acc_u
157
+ tl.store(h_ptr + pid_e * N + offs_n, h, mask=mask_n)
158
+
159
+
160
+ @triton.jit
161
+ def moe_down_combine_vec_kernel(
162
+ h_ptr, # [TOP_K, Kd] fp32 — kernel 1's output
163
+ topk_ids_ptr, # [TOP_K] int
164
+ topk_w_ptr, # [TOP_K] routing weights (fp16/fp32)
165
+ down_qw_tab, down_sc_tab, down_mn_tab,
166
+ out_ptr, # [N] out (fp32)
167
+ Kd, N,
168
+ stride_wk, stride_wn,
169
+ stride_sg, stride_sn,
170
+ TOP_K: tl.constexpr,
171
+ BLOCK_N: tl.constexpr,
172
+ BLOCK_K: tl.constexpr,
173
+ GROUP: tl.constexpr,
174
+ PACK: tl.constexpr,
175
+ ):
176
+ """The combine IS the kernel: experts iterated in FIXED order, the
177
+ routing weight applied as the per-expert epilogue multiply — the
178
+ cross-expert sum is deterministic by construction, and no
179
+ intermediate per-slot output or separate reduction exists."""
180
+ pid_n = tl.program_id(0).to(tl.int64)
181
+ offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
182
+ mask_n = offs_n < N
183
+
184
+ acc = tl.zeros((BLOCK_N,), dtype=tl.float32)
185
+ for e in tl.static_range(TOP_K):
186
+ eid = tl.load(topk_ids_ptr + e).to(tl.int64)
187
+ we = tl.load(topk_w_ptr + e).to(tl.float32)
188
+ qw = tl.cast(tl.load(down_qw_tab + eid),
189
+ tl.pointer_type(tl.int32), bitcast=True)
190
+ sc = tl.cast(tl.load(down_sc_tab + eid),
191
+ tl.pointer_type(tl.float16), bitcast=True)
192
+ mp = tl.cast(tl.load(down_mn_tab + eid),
193
+ tl.pointer_type(tl.float16), bitcast=True)
194
+ part = tl.zeros((BLOCK_N,), dtype=tl.float32)
195
+ for kc in range(0, tl.cdiv(Kd, BLOCK_K)):
196
+ offs_k = kc * BLOCK_K + tl.arange(0, BLOCK_K)
197
+ mask_k = offs_k < Kd
198
+ mask = mask_k[:, None] & mask_n[None, :]
199
+ a = tl.load(h_ptr + e * Kd + offs_k, mask=mask_k,
200
+ other=0.0)
201
+ g = kc * BLOCK_K // GROUP
202
+ packed = tl.load(qw + (offs_k[:, None] // PACK) * stride_wk
203
+ + offs_n[None, :] * stride_wn,
204
+ mask=mask, other=0)
205
+ q = (packed >> ((offs_k[:, None] % PACK) * 4)) & 0xF
206
+ scale = tl.load(sc + g * stride_sg + offs_n * stride_sn,
207
+ mask=mask_n, other=0.0)
208
+ mn = tl.load(mp + g * stride_sg + offs_n * stride_sn,
209
+ mask=mask_n, other=0.0)
210
+ w = (q.to(tl.float32) * scale[None, :].to(tl.float32)
211
+ + mn[None, :].to(tl.float32))
212
+ b = tl.where(mask, w, 0.0)
213
+ part += tl.sum(a[:, None] * b, axis=0)
214
+ acc += we * part
215
+
216
+ tl.store(out_ptr + offs_n, acc, mask=mask_n)
217
+
218
+
219
+ @triton.jit
220
+ def moe_down_split_vec_kernel(
221
+ h_ptr, topk_ids_ptr, topk_w_ptr,
222
+ down_qw_tab, down_sc_tab, down_mn_tab,
223
+ part_ptr, # [TOP_K, N] fp32 — weighted per-expert partials
224
+ Kd, N,
225
+ stride_wk, stride_wn,
226
+ stride_sg, stride_sn,
227
+ BLOCK_N: tl.constexpr,
228
+ BLOCK_K: tl.constexpr,
229
+ GROUP: tl.constexpr,
230
+ PACK: tl.constexpr,
231
+ ):
232
+ """ADOPTED second pass (2026-08-25): the expert axis moves to the
233
+ GRID (grid = (TOP_K, tiles) — 8x the programs of the fused loop),
234
+ each program writing its ROUTER-WEIGHTED partial; a separate tiny
235
+ fixed-order reduce completes the deterministic combine. Trades one
236
+ launch + a [TOP_K, N] fp32 buffer for occupancy and a straight-line
237
+ inner loop."""
238
+ pid_e = tl.program_id(0).to(tl.int64)
239
+ pid_n = tl.program_id(1).to(tl.int64)
240
+ offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
241
+ mask_n = offs_n < N
242
+ eid = tl.load(topk_ids_ptr + pid_e).to(tl.int64)
243
+ we = tl.load(topk_w_ptr + pid_e).to(tl.float32)
244
+ qw = tl.cast(tl.load(down_qw_tab + eid),
245
+ tl.pointer_type(tl.int32), bitcast=True)
246
+ sc = tl.cast(tl.load(down_sc_tab + eid),
247
+ tl.pointer_type(tl.float16), bitcast=True)
248
+ mp = tl.cast(tl.load(down_mn_tab + eid),
249
+ tl.pointer_type(tl.float16), bitcast=True)
250
+ acc = tl.zeros((BLOCK_N,), dtype=tl.float32)
251
+ for kc in range(0, tl.cdiv(Kd, BLOCK_K)):
252
+ offs_k = kc * BLOCK_K + tl.arange(0, BLOCK_K)
253
+ mask_k = offs_k < Kd
254
+ mask = mask_k[:, None] & mask_n[None, :]
255
+ a = tl.load(h_ptr + pid_e * Kd + offs_k, mask=mask_k, other=0.0)
256
+ g = kc * BLOCK_K // GROUP
257
+ packed = tl.load(qw + (offs_k[:, None] // PACK) * stride_wk
258
+ + offs_n[None, :] * stride_wn, mask=mask, other=0)
259
+ q = (packed >> ((offs_k[:, None] % PACK) * 4)) & 0xF
260
+ scale = tl.load(sc + g * stride_sg + offs_n * stride_sn,
261
+ mask=mask_n, other=0.0)
262
+ mn = tl.load(mp + g * stride_sg + offs_n * stride_sn,
263
+ mask=mask_n, other=0.0)
264
+ w = (q.to(tl.float32) * scale[None, :].to(tl.float32)
265
+ + mn[None, :].to(tl.float32))
266
+ b = tl.where(mask, w, 0.0)
267
+ acc += tl.sum(a[:, None] * b, axis=0)
268
+ tl.store(part_ptr + pid_e * N + offs_n, we * acc, mask=mask_n)
269
+
270
+
271
+ @nbx_autotune(configs=_PART_REDUCE_CONFIGS, key=['N', 'TOP_K'], cache_results=True)
272
+ @triton.jit
273
+ def moe_part_reduce_kernel(
274
+ part_ptr, out_ptr, N,
275
+ TOP_K: tl.constexpr,
276
+ BLOCK_N: tl.constexpr,
277
+ ):
278
+ """Fixed-order sum of the TOP_K weighted partials — deterministic
279
+ (the same contract as flash_decode_reduce: order is a constant)."""
280
+ pid = tl.program_id(0).to(tl.int64)
281
+ offs_n = pid * BLOCK_N + tl.arange(0, BLOCK_N)
282
+ mask_n = offs_n < N
283
+ acc = tl.zeros((BLOCK_N,), dtype=tl.float32)
284
+ for e in tl.static_range(TOP_K):
285
+ acc += tl.load(part_ptr + e * N + offs_n, mask=mask_n, other=0.0)
286
+ tl.store(out_ptr + offs_n, acc, mask=mask_n)
287
+
288
+
289
+ # ---------------------------------------------------------------------------
290
+ # Half-precision experts (fp16 / bf16), the fifth member (S3, 2026-10-28).
291
+ #
292
+ # The same band for experts stored in half precision: at M=1 the grouped GEMM
293
+ # ran Qwen3-30B-A3B's decode (fp16 experts, 8 of 128, 2048 x 768) at 0.35 ms a
294
+ # call for ~24 MB of weights (~70 GB/s, nsys on 4 V100, 52 ms of GPU a token);
295
+ # every 16-row tile carried one real row. Same grids as the int4 member — the
296
+ # expert axis on the grid for both projections, SwiGLU in the gateup epilogue,
297
+ # the router-weighted partials summed in fixed order by moe_part_reduce_kernel —
298
+ # with the weight tile read as stored (no dequant) and every product accumulated
299
+ # in fp32. W_BF16 selects the element type the pointer tables point at.
300
+ # ---------------------------------------------------------------------------
301
+
302
+
303
+ @nbx_autotune(configs=_HALF_VEC_CONFIGS, key=['K', 'N', 'TOP_K', 'W_BF16'],
304
+ prune_configs_by={'early_config_prune': _prune_half_vec}, cache_results=True)
305
+ @triton.jit
306
+ def moe_gateup_vec_half_kernel(
307
+ x_ptr, # [K] activation
308
+ topk_ids_ptr, # [TOP_K] int — active expert ids, fixed order
309
+ gate_tab, up_tab, # [E] int64 — expert weight ptrs ([N, K] or [K, N] by the strides)
310
+ h_ptr, # [TOP_K, N] fp32 out — silu(gate) * up
311
+ K, N,
312
+ stride_wk, stride_wn,
313
+ TOP_K, # the routed experts (the grid's first axis): the autotune key's, unused in the body
314
+ BLOCK_N: tl.constexpr,
315
+ BLOCK_K: tl.constexpr,
316
+ W_BF16: tl.constexpr,
317
+ ):
318
+ pid_e = tl.program_id(0).to(tl.int64)
319
+ pid_n = tl.program_id(1).to(tl.int64)
320
+ eid = tl.load(topk_ids_ptr + pid_e).to(tl.int64)
321
+ wty = tl.bfloat16 if W_BF16 else tl.float16
322
+ g_w = tl.cast(tl.load(gate_tab + eid), tl.pointer_type(wty), bitcast=True)
323
+ u_w = tl.cast(tl.load(up_tab + eid), tl.pointer_type(wty), bitcast=True)
324
+ offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
325
+ mask_n = offs_n < N
326
+ acc_g = tl.zeros((BLOCK_N,), dtype=tl.float32)
327
+ acc_u = tl.zeros((BLOCK_N,), dtype=tl.float32)
328
+ for kc in range(0, tl.cdiv(K, BLOCK_K)):
329
+ offs_k = kc * BLOCK_K + tl.arange(0, BLOCK_K)
330
+ mask_k = offs_k < K
331
+ mask = mask_k[:, None] & mask_n[None, :]
332
+ a = tl.load(x_ptr + offs_k, mask=mask_k, other=0.0).to(tl.float32)
333
+ w_off = offs_k[:, None] * stride_wk + offs_n[None, :] * stride_wn
334
+ g = tl.load(g_w + w_off, mask=mask, other=0.0).to(tl.float32)
335
+ acc_g += tl.sum(a[:, None] * g, axis=0)
336
+ u = tl.load(u_w + w_off, mask=mask, other=0.0).to(tl.float32)
337
+ acc_u += tl.sum(a[:, None] * u, axis=0)
338
+ h = acc_g * tl.sigmoid(acc_g) * acc_u
339
+ tl.store(h_ptr + pid_e * N + offs_n, h, mask=mask_n)
340
+
341
+
342
+ @nbx_autotune(configs=_HALF_VEC_CONFIGS, key=['Kd', 'N', 'TOP_K', 'W_BF16'],
343
+ prune_configs_by={'early_config_prune': _prune_half_vec}, cache_results=True)
344
+ @triton.jit
345
+ def moe_down_split_vec_half_kernel(
346
+ h_ptr, # [TOP_K, Kd] fp32 — the gateup output
347
+ topk_ids_ptr, topk_w_ptr,
348
+ down_tab, # [E] int64 — expert down weight ptrs
349
+ part_ptr, # [TOP_K, N] fp32 — router-weighted per-expert partials
350
+ Kd, N,
351
+ stride_wk, stride_wn,
352
+ TOP_K, # the routed experts (the grid's first axis): the autotune key's, unused in the body
353
+ BLOCK_N: tl.constexpr,
354
+ BLOCK_K: tl.constexpr,
355
+ W_BF16: tl.constexpr,
356
+ ):
357
+ pid_e = tl.program_id(0).to(tl.int64)
358
+ pid_n = tl.program_id(1).to(tl.int64)
359
+ eid = tl.load(topk_ids_ptr + pid_e).to(tl.int64)
360
+ rw = tl.load(topk_w_ptr + pid_e).to(tl.float32)
361
+ wty = tl.bfloat16 if W_BF16 else tl.float16
362
+ d_w = tl.cast(tl.load(down_tab + eid), tl.pointer_type(wty), bitcast=True)
363
+ offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
364
+ mask_n = offs_n < N
365
+ acc = tl.zeros((BLOCK_N,), dtype=tl.float32)
366
+ for kc in range(0, tl.cdiv(Kd, BLOCK_K)):
367
+ offs_k = kc * BLOCK_K + tl.arange(0, BLOCK_K)
368
+ mask_k = offs_k < Kd
369
+ mask = mask_k[:, None] & mask_n[None, :]
370
+ a = tl.load(h_ptr + pid_e * Kd + offs_k, mask=mask_k, other=0.0)
371
+ w_off = offs_k[:, None] * stride_wk + offs_n[None, :] * stride_wn
372
+ d = tl.load(d_w + w_off, mask=mask, other=0.0).to(tl.float32)
373
+ acc += tl.sum(a[:, None] * d, axis=0)
374
+ tl.store(part_ptr + pid_e * N + offs_n, acc * rw, mask=mask_n)