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.
- nbxtensor/THIRD_PARTY_NOTICES.md +287 -0
- nbxtensor/__init__.py +17 -0
- nbxtensor/backend/__init__.py +1 -0
- nbxtensor/backend/capabilities.py +207 -0
- nbxtensor/backend/cpu/__init__.py +1 -0
- nbxtensor/backend/cpu/blas.py +102 -0
- nbxtensor/backend/cpu/pool.py +204 -0
- nbxtensor/backend/cpu_backend.py +162 -0
- nbxtensor/backend/device_facts.py +105 -0
- nbxtensor/backend/device_files.py +129 -0
- nbxtensor/backend/device_names.py +18 -0
- nbxtensor/backend/devices/amd/cdna.yml +228 -0
- nbxtensor/backend/devices/amd/cdna2.yml +228 -0
- nbxtensor/backend/devices/amd/cdna3.yml +227 -0
- nbxtensor/backend/devices/apple/apple_m1.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4_pro.yml +308 -0
- nbxtensor/backend/devices/apple/apple_m5.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_max.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_pro.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_ultra.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m6.yml +269 -0
- nbxtensor/backend/devices/apple/apple_silicon.yml +263 -0
- nbxtensor/backend/devices/nvidia/ampere.yml +214 -0
- nbxtensor/backend/devices/nvidia/hopper.yml +213 -0
- nbxtensor/backend/devices/nvidia/volta.yml +411 -0
- nbxtensor/backend/faults.py +151 -0
- nbxtensor/backend/host_memory.py +185 -0
- nbxtensor/backend/launcher.py +2350 -0
- nbxtensor/backend/launcher_contract.py +429 -0
- nbxtensor/backend/metal_backend.py +661 -0
- nbxtensor/backend/metal_device.py +1274 -0
- nbxtensor/backend/process_memory.py +73 -0
- nbxtensor/backend/replay_graph.py +322 -0
- nbxtensor/backend/runtime.py +409 -0
- nbxtensor/backend/triton_ext_driver.py +735 -0
- nbxtensor/dtype/__init__.py +1 -0
- nbxtensor/dtype/convert.py +107 -0
- nbxtensor/dtype/dtypes.yml +70 -0
- nbxtensor/dtype/itemsize.py +166 -0
- nbxtensor/dtype/nbx_dtype.py +72 -0
- nbxtensor/dtype/operands.py +158 -0
- nbxtensor/dtype/outputs.py +64 -0
- nbxtensor/dtype/policy.py +155 -0
- nbxtensor/dtype/policy.yml +51 -0
- nbxtensor/dtype/promotion.py +112 -0
- nbxtensor/host.py +55 -0
- nbxtensor/io/__init__.py +1 -0
- nbxtensor/io/read_rate.py +197 -0
- nbxtensor/io/safetensors.py +314 -0
- nbxtensor/memory/__init__.py +9 -0
- nbxtensor/memory/allocator.py +1988 -0
- nbxtensor/memory/arena.py +75 -0
- nbxtensor/memory/device_memory.py +150 -0
- nbxtensor/memory/device_transfer.py +154 -0
- nbxtensor/memory/layout.py +43 -0
- nbxtensor/memory/memory_pool.py +191 -0
- nbxtensor/memory/metal.py +96 -0
- nbxtensor/memory/oom_advice.py +91 -0
- nbxtensor/memory/pointer_tables.py +323 -0
- nbxtensor/memory/pool.py +507 -0
- nbxtensor/memory/shadow.py +43 -0
- nbxtensor/memory/slab.py +273 -0
- nbxtensor/memory/trace_replay.py +122 -0
- nbxtensor/memory/traces/sana-video-2b-720p.16g.2026-11-02.tsv +13610 -0
- nbxtensor/memory/vmm.py +118 -0
- nbxtensor/ops/__init__.py +1 -0
- nbxtensor/ops/_autotune_policy.py +90 -0
- nbxtensor/ops/_common.py +32 -0
- nbxtensor/ops/_configs.py +1041 -0
- nbxtensor/ops/_cost.py +231 -0
- nbxtensor/ops/abs.py +17 -0
- nbxtensor/ops/adaptive_avg_pool2d.py +161 -0
- nbxtensor/ops/add.py +96 -0
- nbxtensor/ops/addcdiv.py +29 -0
- nbxtensor/ops/addcmul.py +29 -0
- nbxtensor/ops/addmv_op.py +64 -0
- nbxtensor/ops/addr.py +68 -0
- nbxtensor/ops/all_reduce.py +83 -0
- nbxtensor/ops/amax.py +40 -0
- nbxtensor/ops/any_reduce.py +83 -0
- nbxtensor/ops/arange_op.py +27 -0
- nbxtensor/ops/argmax.py +107 -0
- nbxtensor/ops/argmin.py +93 -0
- nbxtensor/ops/atan2.py +71 -0
- nbxtensor/ops/attention_wrappers.py +1648 -0
- nbxtensor/ops/autotune_bucket.py +137 -0
- nbxtensor/ops/autotune_cache.py +430 -0
- nbxtensor/ops/autotune_certified.py +1262 -0
- nbxtensor/ops/autotune_refusals.py +380 -0
- nbxtensor/ops/autotune_settings.py +58 -0
- nbxtensor/ops/avg_pool2d.py +73 -0
- nbxtensor/ops/baddbmm_op.py +132 -0
- nbxtensor/ops/batch_norm.py +163 -0
- nbxtensor/ops/binary_strided.py +196 -0
- nbxtensor/ops/bitwise_and.py +26 -0
- nbxtensor/ops/bitwise_left_shift.py +47 -0
- nbxtensor/ops/bitwise_not.py +24 -0
- nbxtensor/ops/bitwise_or.py +26 -0
- nbxtensor/ops/bitwise_right_shift.py +47 -0
- nbxtensor/ops/bucketize.py +37 -0
- nbxtensor/ops/cat_op.py +87 -0
- nbxtensor/ops/celu.py +20 -0
- nbxtensor/ops/clamp.py +24 -0
- nbxtensor/ops/clamp_min.py +22 -0
- nbxtensor/ops/conv2d.py +265 -0
- nbxtensor/ops/conv2d_m8n8k4.py +165 -0
- nbxtensor/ops/conv_depthwise2d.py +154 -0
- nbxtensor/ops/conv_transpose2d.py +148 -0
- nbxtensor/ops/conv_wrappers.py +1482 -0
- nbxtensor/ops/copy.py +18 -0
- nbxtensor/ops/copy_op.py +39 -0
- nbxtensor/ops/cos.py +17 -0
- nbxtensor/ops/count_nonzero.py +64 -0
- nbxtensor/ops/cross_entropy.py +67 -0
- nbxtensor/ops/cummax.py +109 -0
- nbxtensor/ops/cummin.py +105 -0
- nbxtensor/ops/cumsum.py +244 -0
- nbxtensor/ops/decode_attn_vec.py +162 -0
- nbxtensor/ops/depthwise_conv2d.py +132 -0
- nbxtensor/ops/dequant_gemv.py +190 -0
- nbxtensor/ops/dft.py +67 -0
- nbxtensor/ops/dispatch.py +1228 -0
- nbxtensor/ops/div.py +75 -0
- nbxtensor/ops/dot_op.py +67 -0
- nbxtensor/ops/dropout.py +23 -0
- nbxtensor/ops/dtype_convert.py +42 -0
- nbxtensor/ops/elementwise_launch.py +81 -0
- nbxtensor/ops/elementwise_wrappers.py +2315 -0
- nbxtensor/ops/elu.py +20 -0
- nbxtensor/ops/embedding.py +69 -0
- nbxtensor/ops/eq.py +32 -0
- nbxtensor/ops/erf.py +18 -0
- nbxtensor/ops/exp.py +17 -0
- nbxtensor/ops/exp2.py +18 -0
- nbxtensor/ops/fft_op.py +125 -0
- nbxtensor/ops/fill_op.py +20 -0
- nbxtensor/ops/flash_attention.py +307 -0
- nbxtensor/ops/flash_attention_m8n8k4.py +384 -0
- nbxtensor/ops/flash_decode.py +78 -0
- nbxtensor/ops/flip_op.py +60 -0
- nbxtensor/ops/floor.py +90 -0
- nbxtensor/ops/full_op.py +24 -0
- nbxtensor/ops/fused_moe.py +417 -0
- nbxtensor/ops/fused_moe_m8n8k4.py +99 -0
- nbxtensor/ops/fused_upsample_conv.py +413 -0
- nbxtensor/ops/gather_op.py +52 -0
- nbxtensor/ops/ge.py +32 -0
- nbxtensor/ops/gelu.py +31 -0
- nbxtensor/ops/gemv_vec.py +80 -0
- nbxtensor/ops/glu.py +43 -0
- nbxtensor/ops/grid_sampler.py +145 -0
- nbxtensor/ops/group_norm_tiled.py +67 -0
- nbxtensor/ops/groupnorm.py +123 -0
- nbxtensor/ops/gt.py +32 -0
- nbxtensor/ops/hardsigmoid.py +18 -0
- nbxtensor/ops/hardswish.py +19 -0
- nbxtensor/ops/im2col.py +57 -0
- nbxtensor/ops/index_add.py +95 -0
- nbxtensor/ops/index_put_op.py +92 -0
- nbxtensor/ops/index_select.py +121 -0
- nbxtensor/ops/index_wrappers.py +770 -0
- nbxtensor/ops/isfinite.py +20 -0
- nbxtensor/ops/isinf.py +20 -0
- nbxtensor/ops/isnan.py +18 -0
- nbxtensor/ops/launch_keys.py +1054 -0
- nbxtensor/ops/launch_observer.py +54 -0
- nbxtensor/ops/layernorm.py +71 -0
- nbxtensor/ops/le.py +35 -0
- nbxtensor/ops/leaky_relu.py +19 -0
- nbxtensor/ops/lerp.py +76 -0
- nbxtensor/ops/linalg_wrappers.py +1370 -0
- nbxtensor/ops/linspace_op.py +47 -0
- nbxtensor/ops/log.py +17 -0
- nbxtensor/ops/log_sigmoid.py +23 -0
- nbxtensor/ops/logical_and.py +26 -0
- nbxtensor/ops/logical_not.py +24 -0
- nbxtensor/ops/logical_or.py +26 -0
- nbxtensor/ops/logical_xor.py +37 -0
- nbxtensor/ops/loss_wrappers.py +282 -0
- nbxtensor/ops/lt.py +32 -0
- nbxtensor/ops/masked_fill.py +21 -0
- nbxtensor/ops/matmul.py +377 -0
- nbxtensor/ops/matmul_m8n8k4.py +235 -0
- nbxtensor/ops/max_pool2d.py +74 -0
- nbxtensor/ops/max_pool3d.py +93 -0
- nbxtensor/ops/max_reduce.py +91 -0
- nbxtensor/ops/maximum.py +18 -0
- nbxtensor/ops/mean.py +40 -0
- nbxtensor/ops/min_reduce.py +91 -0
- nbxtensor/ops/minimum.py +18 -0
- nbxtensor/ops/mish.py +22 -0
- nbxtensor/ops/mma_m8n8k4.py +152 -0
- nbxtensor/ops/moe_align.py +195 -0
- nbxtensor/ops/moe_decode_vec.py +374 -0
- nbxtensor/ops/moe_device_residency.py +141 -0
- nbxtensor/ops/moe_wrappers.py +345 -0
- nbxtensor/ops/mse_loss.py +79 -0
- nbxtensor/ops/mul.py +51 -0
- nbxtensor/ops/nan_to_num.py +23 -0
- nbxtensor/ops/ne.py +32 -0
- nbxtensor/ops/neg.py +17 -0
- nbxtensor/ops/nllloss.py +124 -0
- nbxtensor/ops/nonzero.py +33 -0
- nbxtensor/ops/nonzero_op.py +62 -0
- nbxtensor/ops/norm_wrappers.py +579 -0
- nbxtensor/ops/ones_op.py +20 -0
- nbxtensor/ops/oracles/__init__.py +6 -0
- nbxtensor/ops/oracles/conv2d_fp64.py +450 -0
- nbxtensor/ops/pad_op.py +159 -0
- nbxtensor/ops/paged_decode_attn.py +215 -0
- nbxtensor/ops/pixel_shuffle.py +144 -0
- nbxtensor/ops/pixel_unshuffle.py +72 -0
- nbxtensor/ops/pow.py +137 -0
- nbxtensor/ops/prod.py +81 -0
- nbxtensor/ops/rand_op.py +52 -0
- nbxtensor/ops/random_wrappers.py +366 -0
- nbxtensor/ops/reciprocal.py +17 -0
- nbxtensor/ops/reduction_wrappers.py +845 -0
- nbxtensor/ops/relu.py +18 -0
- nbxtensor/ops/remainder.py +46 -0
- nbxtensor/ops/repeat_interleave.py +48 -0
- nbxtensor/ops/repeat_op.py +132 -0
- nbxtensor/ops/residual_chain.py +374 -0
- nbxtensor/ops/rmsnorm.py +68 -0
- nbxtensor/ops/rope.py +142 -0
- nbxtensor/ops/rsqrt.py +17 -0
- nbxtensor/ops/scaled_softmax.py +96 -0
- nbxtensor/ops/scatter_op.py +139 -0
- nbxtensor/ops/screen_oracle.py +479 -0
- nbxtensor/ops/selu.py +22 -0
- nbxtensor/ops/shape_wrappers.py +1032 -0
- nbxtensor/ops/sigmoid.py +19 -0
- nbxtensor/ops/silu.py +27 -0
- nbxtensor/ops/sin.py +17 -0
- nbxtensor/ops/softmax.py +169 -0
- nbxtensor/ops/softplus.py +27 -0
- nbxtensor/ops/sort_op.py +304 -0
- nbxtensor/ops/spectral_wrappers.py +481 -0
- nbxtensor/ops/sqrt.py +17 -0
- nbxtensor/ops/stack_op.py +84 -0
- nbxtensor/ops/std.py +113 -0
- nbxtensor/ops/strided_copy.py +200 -0
- nbxtensor/ops/sub.py +35 -0
- nbxtensor/ops/sum.py +40 -0
- nbxtensor/ops/tan.py +19 -0
- nbxtensor/ops/tanh.py +21 -0
- nbxtensor/ops/threshold.py +19 -0
- nbxtensor/ops/tiling.yml +91 -0
- nbxtensor/ops/tiling_policy.py +43 -0
- nbxtensor/ops/tiling_sizes.py +370 -0
- nbxtensor/ops/topk.py +267 -0
- nbxtensor/ops/trace_op.py +64 -0
- nbxtensor/ops/tril.py +64 -0
- nbxtensor/ops/triu.py +62 -0
- nbxtensor/ops/unfold_op.py +37 -0
- nbxtensor/ops/unique_op.py +97 -0
- nbxtensor/ops/upsample_bilinear2d.py +106 -0
- nbxtensor/ops/upsample_nearest2d.py +52 -0
- nbxtensor/ops/upsample_nearest3d.py +61 -0
- nbxtensor/ops/var.py +136 -0
- nbxtensor/ops/vector_norm.py +216 -0
- nbxtensor/ops/weight_norm.py +104 -0
- nbxtensor/ops/where.py +39 -0
- nbxtensor/ops/wrappers.py +825 -0
- nbxtensor/ops/zeros_op.py +20 -0
- nbxtensor/quant/__init__.py +1 -0
- nbxtensor/quant/quantized_tensor.py +187 -0
- nbxtensor/random/__init__.py +1 -0
- nbxtensor/random/mt_randn.py +154 -0
- nbxtensor/random/rng_pin.py +59 -0
- nbxtensor/random/rng_stream.py +118 -0
- nbxtensor/random/seeded_draw.py +223 -0
- nbxtensor/shape/__init__.py +157 -0
- nbxtensor/tensor.py +1867 -0
- nbxtensor-0.0.1.dist-info/METADATA +379 -0
- nbxtensor-0.0.1.dist-info/RECORD +291 -0
- nbxtensor-0.0.1.dist-info/WHEEL +4 -0
- 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)
|