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,417 @@
|
|
|
1
|
+
"""Fused MoE kernels — pure @triton.jit, zero torch.
|
|
2
|
+
|
|
3
|
+
fused_moe_kernel: Grouped GEMM for all experts in one launch.
|
|
4
|
+
Expert weights accessed via absolute pointer table (int64 per expert).
|
|
5
|
+
Same approach works for scattered arena allocations — no offset arithmetic.
|
|
6
|
+
|
|
7
|
+
silu_and_mul_kernel: Fused SwiGLU activation.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import triton
|
|
11
|
+
import triton.language as tl
|
|
12
|
+
|
|
13
|
+
from nbxtensor.ops._configs import nbx_autotune
|
|
14
|
+
|
|
15
|
+
# The tile of a fused MoE launch beyond its row block: BLOCK_SIZE_M is the alignment's block (`moe_align_block_size`,
|
|
16
|
+
# the caller's), every other parameter a candidate the certification measures per key — the former fixed tile
|
|
17
|
+
# (64 x 32, 4 warps, 2 stages: the best of a nine-tile grid on DeepSeek and Qwen3 prefills, 2026-11-03) among them.
|
|
18
|
+
# BLOCK_SIZE_K divides the int4 kernel's quantisation group (128), so a K tile never straddles two groups.
|
|
19
|
+
_FUSED_MOE_CONFIGS = [
|
|
20
|
+
triton.Config({'BLOCK_SIZE_N': bn, 'BLOCK_SIZE_K': bk, 'GROUP_SIZE_M': 8}, num_warps=w, num_stages=st)
|
|
21
|
+
for bn in (32, 64, 128) for bk in (32, 64) for w in (4, 8) for st in (2, 3)
|
|
22
|
+
]
|
|
23
|
+
#: the autotune key of a fused MoE launch: the GEMM's dims, the valid (token, expert) pairs' bucket — the prefill's
|
|
24
|
+
#: length class, the same for the gate/up and the down launch of a layer — the experts the pointer table holds (how
|
|
25
|
+
#: many rows each expert's blocks get), and the epilogue flags
|
|
26
|
+
_FUSED_MOE_KEY = ['N', 'K', 'NV_BUCKET', 'NUM_EXPERTS', 'MUL_ROUTED_WEIGHT', 'TOPK_DIVIDE']
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@nbx_autotune(configs=_FUSED_MOE_CONFIGS, key=_FUSED_MOE_KEY, cache_results=True)
|
|
30
|
+
@triton.jit
|
|
31
|
+
def fused_moe_kernel(
|
|
32
|
+
# Pointers to matrices
|
|
33
|
+
a_ptr, # activations [M, K]
|
|
34
|
+
expert_ptrs_ptr, # [E] int64 — absolute weight pointers
|
|
35
|
+
c_ptr, # output [num_tokens_post_padded, N]
|
|
36
|
+
topk_weights_ptr, # routing scores [M * top_k]
|
|
37
|
+
sorted_token_ids_ptr, # sorted token indices
|
|
38
|
+
expert_ids_ptr, # expert id per BLOCK_M group
|
|
39
|
+
num_tokens_post_padded_ptr, # [1] total tokens after padding
|
|
40
|
+
# Matrix dimensions
|
|
41
|
+
N, # output feature dim
|
|
42
|
+
K, # input feature dim (reduction)
|
|
43
|
+
EM, # total sorted entries (with padding)
|
|
44
|
+
num_valid_tokens, # M * top_k (before padding)
|
|
45
|
+
NV_BUCKET, # num_valid_tokens' bucket: the autotune key's length class (unused in the body)
|
|
46
|
+
NUM_EXPERTS, # the pointer table's experts: the autotune key's (unused in the body)
|
|
47
|
+
# Strides (shared across all experts — same shape)
|
|
48
|
+
stride_am, stride_ak,
|
|
49
|
+
stride_bk, stride_bn,
|
|
50
|
+
stride_cm, stride_cn,
|
|
51
|
+
# Meta-parameters
|
|
52
|
+
BLOCK_SIZE_M: tl.constexpr,
|
|
53
|
+
MUL_ROUTED_WEIGHT: tl.constexpr,
|
|
54
|
+
top_k: tl.constexpr,
|
|
55
|
+
compute_type: tl.constexpr,
|
|
56
|
+
TOPK_DIVIDE: tl.constexpr = True,
|
|
57
|
+
BLOCK_SIZE_N: tl.constexpr = 64,
|
|
58
|
+
BLOCK_SIZE_K: tl.constexpr = 32,
|
|
59
|
+
GROUP_SIZE_M: tl.constexpr = 8,
|
|
60
|
+
):
|
|
61
|
+
"""Grouped GEMM with absolute expert pointer table.
|
|
62
|
+
|
|
63
|
+
Each BLOCK_M block of sorted tokens shares one expert. The kernel loads
|
|
64
|
+
that expert's absolute weight pointer from the table and uses it directly.
|
|
65
|
+
"""
|
|
66
|
+
pid = tl.program_id(axis=0).to(tl.int64)
|
|
67
|
+
num_pid_m = tl.cdiv(EM, BLOCK_SIZE_M)
|
|
68
|
+
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
|
69
|
+
num_pid_in_group = GROUP_SIZE_M * num_pid_n
|
|
70
|
+
group_id = pid // num_pid_in_group
|
|
71
|
+
first_pid_m = group_id * GROUP_SIZE_M
|
|
72
|
+
group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
|
|
73
|
+
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
|
|
74
|
+
pid_n = (pid % num_pid_in_group) // group_size_m
|
|
75
|
+
|
|
76
|
+
num_tokens_post_padded = tl.load(num_tokens_post_padded_ptr)
|
|
77
|
+
# A block beyond the padded token count reads and writes nothing: its
|
|
78
|
+
# sorted-token and expert entries are not written by moe_align, so every
|
|
79
|
+
# access below carries `block_valid` (no early exit — unstructured
|
|
80
|
+
# control flow has no lowering on every backend).
|
|
81
|
+
block_valid = pid_m * BLOCK_SIZE_M < num_tokens_post_padded
|
|
82
|
+
|
|
83
|
+
# Load sorted token IDs for this block
|
|
84
|
+
offs_token_id = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
|
85
|
+
offs_token = tl.load(sorted_token_ids_ptr + offs_token_id, mask=block_valid, other=0).to(tl.int64)
|
|
86
|
+
token_mask = (offs_token < num_valid_tokens) & block_valid
|
|
87
|
+
|
|
88
|
+
# Load expert id → load absolute weight pointer from table
|
|
89
|
+
off_experts = tl.load(expert_ids_ptr + pid_m, mask=block_valid, other=0)
|
|
90
|
+
expert_ptr_int = tl.load(expert_ptrs_ptr + off_experts, mask=block_valid, other=0)
|
|
91
|
+
# Bitcast int64 to pointer — reinterpret bits as address
|
|
92
|
+
b_base = tl.cast(expert_ptr_int, tl.pointer_type(compute_type), bitcast=True)
|
|
93
|
+
|
|
94
|
+
# INT64 offsets for pointer arithmetic within the expert weight matrix
|
|
95
|
+
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N).to(tl.int64)) % N
|
|
96
|
+
offs_k = tl.arange(0, BLOCK_SIZE_K).to(tl.int64)
|
|
97
|
+
|
|
98
|
+
if TOPK_DIVIDE:
|
|
99
|
+
a_idx = offs_token[:, None] // top_k
|
|
100
|
+
else:
|
|
101
|
+
a_idx = offs_token[:, None]
|
|
102
|
+
a_ptrs = a_ptr + (a_idx * stride_am + offs_k[None, :] * stride_ak)
|
|
103
|
+
|
|
104
|
+
# B pointers: absolute expert pointer + in-matrix offsets
|
|
105
|
+
b_ptrs = b_base + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
|
|
106
|
+
|
|
107
|
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
|
108
|
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
|
109
|
+
a = tl.load(
|
|
110
|
+
a_ptrs,
|
|
111
|
+
mask=token_mask[:, None] & (offs_k[None, :] < K - k * BLOCK_SIZE_K),
|
|
112
|
+
other=0.0,
|
|
113
|
+
)
|
|
114
|
+
b = tl.load(
|
|
115
|
+
b_ptrs,
|
|
116
|
+
mask=(offs_k[:, None] < K - k * BLOCK_SIZE_K) & block_valid,
|
|
117
|
+
other=0.0,
|
|
118
|
+
)
|
|
119
|
+
# NOTE: Phase 1.5 Step 1 (2026-05) tested 3-arg `tl.dot(a, b, acc)`
|
|
120
|
+
# form — measured 0% gain on V100 sm_70. Reverted to 2-arg form.
|
|
121
|
+
# See kernels/ops/matmul.py:matmul_kernel docstring for full audit.
|
|
122
|
+
accumulator += tl.dot(a, b)
|
|
123
|
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
|
124
|
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
|
125
|
+
|
|
126
|
+
if MUL_ROUTED_WEIGHT:
|
|
127
|
+
moe_weight = tl.load(
|
|
128
|
+
topk_weights_ptr + offs_token,
|
|
129
|
+
mask=token_mask,
|
|
130
|
+
other=0,
|
|
131
|
+
)
|
|
132
|
+
accumulator = accumulator * moe_weight[:, None]
|
|
133
|
+
|
|
134
|
+
accumulator = accumulator.to(compute_type)
|
|
135
|
+
offs_cn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)).to(tl.int64)
|
|
136
|
+
c_ptrs = c_ptr + stride_cm * offs_token[:, None] + stride_cn * offs_cn[None, :]
|
|
137
|
+
c_mask = token_mask[:, None] & (offs_cn[None, :] < N)
|
|
138
|
+
tl.store(c_ptrs, accumulator, mask=c_mask)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@triton.jit
|
|
142
|
+
def fused_moe_wna16_kernel(
|
|
143
|
+
a_ptr, # activations [M, K]
|
|
144
|
+
qw_ptrs_ptr, # [E] int64 — packed int4 [K//8, N] per expert
|
|
145
|
+
sc_ptrs_ptr, # [E] int64 — scales fp16 [K//G, N]
|
|
146
|
+
mn_ptrs_ptr, # [E] int64 — mins fp16 [K//G, N]
|
|
147
|
+
c_ptr,
|
|
148
|
+
topk_weights_ptr,
|
|
149
|
+
sorted_token_ids_ptr,
|
|
150
|
+
expert_ids_ptr,
|
|
151
|
+
num_tokens_post_padded_ptr,
|
|
152
|
+
N, K, EM, num_valid_tokens,
|
|
153
|
+
stride_am, stride_ak,
|
|
154
|
+
stride_qk, stride_qn, # packed strides (rows of int32)
|
|
155
|
+
stride_sg, stride_sn, # scales/mins strides
|
|
156
|
+
stride_cm, stride_cn,
|
|
157
|
+
BLOCK_SIZE_M: tl.constexpr,
|
|
158
|
+
BLOCK_SIZE_N: tl.constexpr,
|
|
159
|
+
BLOCK_SIZE_K: tl.constexpr,
|
|
160
|
+
GROUP_SIZE_M: tl.constexpr,
|
|
161
|
+
MUL_ROUTED_WEIGHT: tl.constexpr,
|
|
162
|
+
top_k: tl.constexpr,
|
|
163
|
+
compute_type: tl.constexpr,
|
|
164
|
+
QGROUP: tl.constexpr, # quant group size along K (128)
|
|
165
|
+
QPACK: tl.constexpr, # nibbles per int32 (8)
|
|
166
|
+
TOPK_DIVIDE: tl.constexpr = True,
|
|
167
|
+
):
|
|
168
|
+
"""Grouped GEMM over int4-g128-asym experts — in-register dequant.
|
|
169
|
+
|
|
170
|
+
Structure mirrors fused_moe_kernel exactly (same pid mapping, same
|
|
171
|
+
K-loop, same routed-weight epilogue); the B tile is rebuilt each
|
|
172
|
+
iteration from the expert's packed triplet with the tier's
|
|
173
|
+
CANONICAL dequant (pure fp32: q*scale+min — contraction-immune,
|
|
174
|
+
int4 x fp16 products exact in fp32) and the dot runs fp32 x fp32
|
|
175
|
+
(on sm_70 fp16 tl.dot lowers to FMA fp32 anyway — Phase 1.5
|
|
176
|
+
audit). BLOCK_SIZE_K must divide QGROUP so a K-tile never crosses
|
|
177
|
+
a quant-group boundary (one scales/mins row vector per
|
|
178
|
+
iteration). SPLIT_K = 1, no atomics (tier byte contract). The
|
|
179
|
+
byte oracle is fused_moe_fp32b_kernel below — textually identical
|
|
180
|
+
minus the unpack.
|
|
181
|
+
"""
|
|
182
|
+
pid = tl.program_id(axis=0).to(tl.int64)
|
|
183
|
+
num_pid_m = tl.cdiv(EM, BLOCK_SIZE_M)
|
|
184
|
+
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
|
185
|
+
num_pid_in_group = GROUP_SIZE_M * num_pid_n
|
|
186
|
+
group_id = pid // num_pid_in_group
|
|
187
|
+
first_pid_m = group_id * GROUP_SIZE_M
|
|
188
|
+
group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
|
|
189
|
+
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
|
|
190
|
+
pid_n = (pid % num_pid_in_group) // group_size_m
|
|
191
|
+
|
|
192
|
+
num_tokens_post_padded = tl.load(num_tokens_post_padded_ptr)
|
|
193
|
+
# A block beyond the padded token count reads and writes nothing: its
|
|
194
|
+
# sorted-token and expert entries are not written by moe_align, so every
|
|
195
|
+
# access below carries `block_valid` (no early exit — unstructured
|
|
196
|
+
# control flow has no lowering on every backend).
|
|
197
|
+
block_valid = pid_m * BLOCK_SIZE_M < num_tokens_post_padded
|
|
198
|
+
|
|
199
|
+
offs_token_id = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
|
200
|
+
offs_token = tl.load(sorted_token_ids_ptr + offs_token_id, mask=block_valid, other=0).to(tl.int64)
|
|
201
|
+
token_mask = (offs_token < num_valid_tokens) & block_valid
|
|
202
|
+
|
|
203
|
+
off_experts = tl.load(expert_ids_ptr + pid_m, mask=block_valid, other=0)
|
|
204
|
+
qw_base = tl.cast(tl.load(qw_ptrs_ptr + off_experts, mask=block_valid, other=0),
|
|
205
|
+
tl.pointer_type(tl.int32), bitcast=True)
|
|
206
|
+
sc_base = tl.cast(tl.load(sc_ptrs_ptr + off_experts, mask=block_valid, other=0),
|
|
207
|
+
tl.pointer_type(tl.float16), bitcast=True)
|
|
208
|
+
mn_base = tl.cast(tl.load(mn_ptrs_ptr + off_experts, mask=block_valid, other=0),
|
|
209
|
+
tl.pointer_type(tl.float16), bitcast=True)
|
|
210
|
+
|
|
211
|
+
offs_bn = (pid_n * BLOCK_SIZE_N
|
|
212
|
+
+ tl.arange(0, BLOCK_SIZE_N).to(tl.int64)) % N
|
|
213
|
+
offs_k = tl.arange(0, BLOCK_SIZE_K).to(tl.int64)
|
|
214
|
+
|
|
215
|
+
if TOPK_DIVIDE:
|
|
216
|
+
a_idx = offs_token[:, None] // top_k
|
|
217
|
+
else:
|
|
218
|
+
a_idx = offs_token[:, None]
|
|
219
|
+
a_ptrs = a_ptr + (a_idx * stride_am + offs_k[None, :] * stride_ak)
|
|
220
|
+
|
|
221
|
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
|
222
|
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
|
223
|
+
k_rem = K - k * BLOCK_SIZE_K
|
|
224
|
+
a = tl.load(
|
|
225
|
+
a_ptrs,
|
|
226
|
+
mask=token_mask[:, None] & (offs_k[None, :] < k_rem),
|
|
227
|
+
other=0.0,
|
|
228
|
+
)
|
|
229
|
+
cur_k = k * BLOCK_SIZE_K + offs_k
|
|
230
|
+
packed = tl.load(
|
|
231
|
+
qw_base + (cur_k[:, None] // QPACK) * stride_qk
|
|
232
|
+
+ offs_bn[None, :] * stride_qn,
|
|
233
|
+
mask=(offs_k[:, None] < k_rem) & block_valid, other=0)
|
|
234
|
+
q = (packed >> ((cur_k[:, None] % QPACK) * 4)) & 0xF
|
|
235
|
+
g = k * BLOCK_SIZE_K // QGROUP # BLOCK_SIZE_K divides QGROUP
|
|
236
|
+
scale = tl.load(sc_base + g * stride_sg + offs_bn * stride_sn, mask=block_valid, other=0.0)
|
|
237
|
+
mn = tl.load(mn_base + g * stride_sg + offs_bn * stride_sn, mask=block_valid, other=0.0)
|
|
238
|
+
# Canonical dequant — the tier's contraction-immune fp32 form,
|
|
239
|
+
# textually identical to the GEMV family's.
|
|
240
|
+
b = (q.to(tl.float32) * scale[None, :].to(tl.float32)
|
|
241
|
+
+ mn[None, :].to(tl.float32))
|
|
242
|
+
b = tl.where(offs_k[:, None] < k_rem, b, 0.0)
|
|
243
|
+
accumulator += tl.dot(a.to(tl.float32), b)
|
|
244
|
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
|
245
|
+
|
|
246
|
+
if MUL_ROUTED_WEIGHT:
|
|
247
|
+
moe_weight = tl.load(
|
|
248
|
+
topk_weights_ptr + offs_token,
|
|
249
|
+
mask=token_mask,
|
|
250
|
+
other=0,
|
|
251
|
+
)
|
|
252
|
+
accumulator = accumulator * moe_weight[:, None]
|
|
253
|
+
|
|
254
|
+
accumulator = accumulator.to(compute_type)
|
|
255
|
+
offs_cn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)).to(tl.int64)
|
|
256
|
+
c_ptrs = c_ptr + stride_cm * offs_token[:, None] + stride_cn * offs_cn[None, :]
|
|
257
|
+
c_mask = token_mask[:, None] & (offs_cn[None, :] < N)
|
|
258
|
+
tl.store(c_ptrs, accumulator, mask=c_mask)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@triton.jit
|
|
262
|
+
def fused_moe_fp32b_kernel(
|
|
263
|
+
a_ptr,
|
|
264
|
+
expert_ptrs_ptr, # [E] int64 — dense FP32 [K, N] per expert
|
|
265
|
+
c_ptr,
|
|
266
|
+
topk_weights_ptr,
|
|
267
|
+
sorted_token_ids_ptr,
|
|
268
|
+
expert_ids_ptr,
|
|
269
|
+
num_tokens_post_padded_ptr,
|
|
270
|
+
N, K, EM, num_valid_tokens,
|
|
271
|
+
stride_am, stride_ak,
|
|
272
|
+
stride_bk, stride_bn,
|
|
273
|
+
stride_cm, stride_cn,
|
|
274
|
+
BLOCK_SIZE_M: tl.constexpr,
|
|
275
|
+
BLOCK_SIZE_N: tl.constexpr,
|
|
276
|
+
BLOCK_SIZE_K: tl.constexpr,
|
|
277
|
+
GROUP_SIZE_M: tl.constexpr,
|
|
278
|
+
MUL_ROUTED_WEIGHT: tl.constexpr,
|
|
279
|
+
top_k: tl.constexpr,
|
|
280
|
+
compute_type: tl.constexpr,
|
|
281
|
+
TOPK_DIVIDE: tl.constexpr = True,
|
|
282
|
+
):
|
|
283
|
+
"""The wna16 kernel's BYTE ORACLE: dense fp32 expert weights (the
|
|
284
|
+
family's standalone dequant output), identical pid mapping, K-loop,
|
|
285
|
+
fp32 dot and epilogue — textually the kernel above minus the
|
|
286
|
+
unpack. Never used on a hot path; exists so the fused W4 kernel is
|
|
287
|
+
provably byte-identical to dequantize-then-grouped-GEMM."""
|
|
288
|
+
pid = tl.program_id(axis=0).to(tl.int64)
|
|
289
|
+
num_pid_m = tl.cdiv(EM, BLOCK_SIZE_M)
|
|
290
|
+
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
|
291
|
+
num_pid_in_group = GROUP_SIZE_M * num_pid_n
|
|
292
|
+
group_id = pid // num_pid_in_group
|
|
293
|
+
first_pid_m = group_id * GROUP_SIZE_M
|
|
294
|
+
group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
|
|
295
|
+
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
|
|
296
|
+
pid_n = (pid % num_pid_in_group) // group_size_m
|
|
297
|
+
|
|
298
|
+
num_tokens_post_padded = tl.load(num_tokens_post_padded_ptr)
|
|
299
|
+
# A block beyond the padded token count reads and writes nothing: its
|
|
300
|
+
# sorted-token and expert entries are not written by moe_align, so every
|
|
301
|
+
# access below carries `block_valid` (no early exit — unstructured
|
|
302
|
+
# control flow has no lowering on every backend).
|
|
303
|
+
block_valid = pid_m * BLOCK_SIZE_M < num_tokens_post_padded
|
|
304
|
+
|
|
305
|
+
offs_token_id = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
|
306
|
+
offs_token = tl.load(sorted_token_ids_ptr + offs_token_id, mask=block_valid, other=0).to(tl.int64)
|
|
307
|
+
token_mask = (offs_token < num_valid_tokens) & block_valid
|
|
308
|
+
|
|
309
|
+
off_experts = tl.load(expert_ids_ptr + pid_m, mask=block_valid, other=0)
|
|
310
|
+
b_base = tl.cast(tl.load(expert_ptrs_ptr + off_experts, mask=block_valid, other=0),
|
|
311
|
+
tl.pointer_type(tl.float32), bitcast=True)
|
|
312
|
+
|
|
313
|
+
offs_bn = (pid_n * BLOCK_SIZE_N
|
|
314
|
+
+ tl.arange(0, BLOCK_SIZE_N).to(tl.int64)) % N
|
|
315
|
+
offs_k = tl.arange(0, BLOCK_SIZE_K).to(tl.int64)
|
|
316
|
+
|
|
317
|
+
if TOPK_DIVIDE:
|
|
318
|
+
a_idx = offs_token[:, None] // top_k
|
|
319
|
+
else:
|
|
320
|
+
a_idx = offs_token[:, None]
|
|
321
|
+
a_ptrs = a_ptr + (a_idx * stride_am + offs_k[None, :] * stride_ak)
|
|
322
|
+
b_ptrs = b_base + (offs_k[:, None] * stride_bk
|
|
323
|
+
+ offs_bn[None, :] * stride_bn)
|
|
324
|
+
|
|
325
|
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
|
326
|
+
for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)):
|
|
327
|
+
k_rem = K - k * BLOCK_SIZE_K
|
|
328
|
+
a = tl.load(
|
|
329
|
+
a_ptrs,
|
|
330
|
+
mask=token_mask[:, None] & (offs_k[None, :] < k_rem),
|
|
331
|
+
other=0.0,
|
|
332
|
+
)
|
|
333
|
+
b = tl.load(
|
|
334
|
+
b_ptrs,
|
|
335
|
+
mask=(offs_k[:, None] < k_rem) & block_valid,
|
|
336
|
+
other=0.0,
|
|
337
|
+
)
|
|
338
|
+
accumulator += tl.dot(a.to(tl.float32), b)
|
|
339
|
+
a_ptrs += BLOCK_SIZE_K * stride_ak
|
|
340
|
+
b_ptrs += BLOCK_SIZE_K * stride_bk
|
|
341
|
+
|
|
342
|
+
if MUL_ROUTED_WEIGHT:
|
|
343
|
+
moe_weight = tl.load(
|
|
344
|
+
topk_weights_ptr + offs_token,
|
|
345
|
+
mask=token_mask,
|
|
346
|
+
other=0,
|
|
347
|
+
)
|
|
348
|
+
accumulator = accumulator * moe_weight[:, None]
|
|
349
|
+
|
|
350
|
+
accumulator = accumulator.to(compute_type)
|
|
351
|
+
offs_cn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)).to(tl.int64)
|
|
352
|
+
c_ptrs = c_ptr + stride_cm * offs_token[:, None] + stride_cn * offs_cn[None, :]
|
|
353
|
+
c_mask = token_mask[:, None] & (offs_cn[None, :] < N)
|
|
354
|
+
tl.store(c_ptrs, accumulator, mask=c_mask)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@triton.jit
|
|
358
|
+
def silu_and_mul_kernel(
|
|
359
|
+
input_ptr, output_ptr,
|
|
360
|
+
M, N,
|
|
361
|
+
stride_im, stride_in,
|
|
362
|
+
stride_om, stride_on,
|
|
363
|
+
BLOCK_M: tl.constexpr,
|
|
364
|
+
BLOCK_N: tl.constexpr,
|
|
365
|
+
):
|
|
366
|
+
"""Fused SwiGLU: output = silu(input[:, :N]) * input[:, N:2*N]."""
|
|
367
|
+
pid_m = tl.program_id(0).to(tl.int64)
|
|
368
|
+
pid_n = tl.program_id(1).to(tl.int64)
|
|
369
|
+
offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
|
|
370
|
+
offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
|
|
371
|
+
mask = (offs_m[:, None] < M) & (offs_n[None, :] < N)
|
|
372
|
+
|
|
373
|
+
gate_ptrs = input_ptr + offs_m[:, None] * stride_im + offs_n[None, :] * stride_in
|
|
374
|
+
up_ptrs = input_ptr + offs_m[:, None] * stride_im + (offs_n[None, :] + N) * stride_in
|
|
375
|
+
|
|
376
|
+
gate = tl.load(gate_ptrs, mask=mask, other=0.0).to(tl.float32)
|
|
377
|
+
up = tl.load(up_ptrs, mask=mask, other=0.0).to(tl.float32)
|
|
378
|
+
|
|
379
|
+
silu_gate = gate * tl.sigmoid(gate)
|
|
380
|
+
result = silu_gate * up
|
|
381
|
+
|
|
382
|
+
out_ptrs = output_ptr + offs_m[:, None] * stride_om + offs_n[None, :] * stride_on
|
|
383
|
+
tl.store(out_ptrs, result, mask=mask)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@triton.jit
|
|
387
|
+
def silu_mul_split_kernel(
|
|
388
|
+
gate_ptr, up_ptr, output_ptr,
|
|
389
|
+
M, N,
|
|
390
|
+
stride_gm, stride_gn,
|
|
391
|
+
stride_um, stride_un,
|
|
392
|
+
stride_om, stride_on,
|
|
393
|
+
BLOCK_M: tl.constexpr,
|
|
394
|
+
BLOCK_N: tl.constexpr,
|
|
395
|
+
):
|
|
396
|
+
"""Fused SwiGLU with split gate/up tensors: output = silu(gate) * up.
|
|
397
|
+
|
|
398
|
+
Variant of silu_and_mul_kernel for the common case where gate and up
|
|
399
|
+
are produced as two separate tensors (no concat needed).
|
|
400
|
+
"""
|
|
401
|
+
pid_m = tl.program_id(0).to(tl.int64)
|
|
402
|
+
pid_n = tl.program_id(1).to(tl.int64)
|
|
403
|
+
offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
|
|
404
|
+
offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
|
|
405
|
+
mask = (offs_m[:, None] < M) & (offs_n[None, :] < N)
|
|
406
|
+
|
|
407
|
+
gate_ptrs = gate_ptr + offs_m[:, None] * stride_gm + offs_n[None, :] * stride_gn
|
|
408
|
+
up_ptrs = up_ptr + offs_m[:, None] * stride_um + offs_n[None, :] * stride_un
|
|
409
|
+
|
|
410
|
+
gate = tl.load(gate_ptrs, mask=mask, other=0.0).to(tl.float32)
|
|
411
|
+
up = tl.load(up_ptrs, mask=mask, other=0.0).to(tl.float32)
|
|
412
|
+
|
|
413
|
+
silu_gate = gate * tl.sigmoid(gate)
|
|
414
|
+
result = silu_gate * up
|
|
415
|
+
|
|
416
|
+
out_ptrs = output_ptr + offs_m[:, None] * stride_om + offs_n[None, :] * stride_on
|
|
417
|
+
tl.store(out_ptrs, result, mask=mask)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""The fused mixture-of-experts grouped GEMM on the m8n8k4 matrix unit — `fused_moe_kernel`'s contract where the
|
|
2
|
+
hardware profile declares `matrix_unit` and the experts are the unit's operand dtype in memory.
|
|
3
|
+
|
|
4
|
+
Same contract as `fused_moe_kernel` (ops/fused_moe.py): the routing alignment's blocks of sorted (token, expert)
|
|
5
|
+
pairs, one expert per block whose weights are read through the absolute pointer table, the activation row of each
|
|
6
|
+
pair (the token, or the pair itself without the top-k division), the routed weight on the down projection, the output
|
|
7
|
+
row written at the pair. The block of rows is the alignment's and the unit tile's at once: the rows of the
|
|
8
|
+
`matrix_unit.mm` row the certificate chose (`moe_wrappers.invoke_fused_moe`, the routing aligned at that block).
|
|
9
|
+
|
|
10
|
+
The products of two fp16 operands are exact in fp32; the unit sums one K tile into a zeroed accumulator and the tiles
|
|
11
|
+
are summed on the FMA units with round to nearest, as `matmul_m8n8k4_kernel` does (the same staging, fragments and
|
|
12
|
+
HMMA, ops/mma_m8n8k4.py).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from triton.experimental import gluon
|
|
16
|
+
from triton.experimental.gluon import language as gl
|
|
17
|
+
|
|
18
|
+
from .mma_m8n8k4 import acc_rows_cols, mma_k4, stage_k_inner, stage_k_outer
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@gluon.jit
|
|
22
|
+
def fused_moe_m8n8k4_kernel(
|
|
23
|
+
A, expert_ptrs, C, topk_w, sorted_ids, expert_ids, num_post_padded,
|
|
24
|
+
N, K, EM, num_valid,
|
|
25
|
+
stride_am, stride_ak, stride_bk, stride_bn, stride_cm, stride_cn,
|
|
26
|
+
MUL_ROUTED_WEIGHT: gl.constexpr, TOP_K: gl.constexpr, TOPK_DIVIDE: gl.constexpr, B_K_CONTIG: gl.constexpr,
|
|
27
|
+
MB: gl.constexpr, NB: gl.constexpr, BK: gl.constexpr, GROUP_M: gl.constexpr,
|
|
28
|
+
L: gl.constexpr, LA: gl.constexpr, LB: gl.constexpr, GA: gl.constexpr, GB: gl.constexpr,
|
|
29
|
+
):
|
|
30
|
+
BM: gl.constexpr = 16 * MB
|
|
31
|
+
BN: gl.constexpr = 16 * NB
|
|
32
|
+
J: gl.constexpr = BK // 4
|
|
33
|
+
SH: gl.constexpr = gl.SwizzledSharedLayout(1, 1, 1, order=[1, 0])
|
|
34
|
+
|
|
35
|
+
pid = gl.program_id(0).to(gl.int64)
|
|
36
|
+
num_pid_m = gl.cdiv(EM, BM)
|
|
37
|
+
num_pid_n = gl.cdiv(N, BN)
|
|
38
|
+
num_pid_in_group = GROUP_M * num_pid_n
|
|
39
|
+
first_pid_m = (pid // num_pid_in_group) * GROUP_M
|
|
40
|
+
group_size_m = gl.minimum(num_pid_m - first_pid_m, GROUP_M)
|
|
41
|
+
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
|
|
42
|
+
pid_n = (pid % num_pid_in_group) // group_size_m
|
|
43
|
+
m0 = pid_m * BM
|
|
44
|
+
n0 = pid_n * BN
|
|
45
|
+
# a block past the padded pair count reads and writes nothing (its alignment entries are not written)
|
|
46
|
+
block_valid = m0 < gl.load(num_post_padded)
|
|
47
|
+
|
|
48
|
+
sa = gl.allocate_shared_memory(gl.float16, [J * BM, 4], SH)
|
|
49
|
+
sb = gl.allocate_shared_memory(gl.float16, [J * BN, 4], SH)
|
|
50
|
+
|
|
51
|
+
# A: the block's pairs' activation rows [BM, BK], k along the columns
|
|
52
|
+
ra = gl.arange(0, BM, layout=gl.SliceLayout(1, GA))
|
|
53
|
+
pair_a = gl.load(sorted_ids + m0 + ra, mask=(ra >= 0) & block_valid, other=0).to(gl.int64)
|
|
54
|
+
ok_a = (pair_a < num_valid) & block_valid
|
|
55
|
+
if TOPK_DIVIDE:
|
|
56
|
+
row_a = pair_a // TOP_K
|
|
57
|
+
else:
|
|
58
|
+
row_a = pair_a
|
|
59
|
+
ka = gl.arange(0, BK, layout=gl.SliceLayout(0, GA))[None, :]
|
|
60
|
+
pa = A + row_a[:, None] * stride_am + ka * stride_ak
|
|
61
|
+
ma = ok_a[:, None]
|
|
62
|
+
# B: the block's expert, through the pointer table
|
|
63
|
+
expert = gl.load(expert_ids + pid_m, mask=block_valid, other=0)
|
|
64
|
+
B = gl.cast(gl.load(expert_ptrs + expert, mask=block_valid, other=0), gl.pointer_type(gl.float16), bitcast=True)
|
|
65
|
+
if B_K_CONTIG:
|
|
66
|
+
nb_ = gl.arange(0, BN, layout=gl.SliceLayout(1, GB))[:, None]
|
|
67
|
+
kb = gl.arange(0, BK, layout=gl.SliceLayout(0, GB))[None, :]
|
|
68
|
+
else:
|
|
69
|
+
kb = gl.arange(0, BK, layout=gl.SliceLayout(1, GB))[:, None]
|
|
70
|
+
nb_ = gl.arange(0, BN, layout=gl.SliceLayout(0, GB))[None, :]
|
|
71
|
+
pb = B + kb * stride_bk + (n0 + nb_) * stride_bn
|
|
72
|
+
mb_ = ((n0 + nb_) < N) & block_valid
|
|
73
|
+
|
|
74
|
+
acc = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
|
|
75
|
+
ta = gl.load(pa, mask=ma & (ka < K), other=0.0)
|
|
76
|
+
tb = gl.load(pb, mask=mb_ & (kb < K), other=0.0)
|
|
77
|
+
for k0 in range(0, K, BK):
|
|
78
|
+
gl.barrier() # every warp is done reading the previous tile
|
|
79
|
+
stage_k_inner(sa, ta, BM, BK)
|
|
80
|
+
if B_K_CONTIG:
|
|
81
|
+
stage_k_inner(sb, tb, BN, BK)
|
|
82
|
+
else:
|
|
83
|
+
stage_k_outer(sb, tb, BK, BN)
|
|
84
|
+
gl.barrier()
|
|
85
|
+
if k0 + BK < K: # the next tile loads while the HMMAs run
|
|
86
|
+
ta = gl.load(pa + (k0 + BK) * stride_ak, mask=ma & (ka + k0 + BK < K), other=0.0)
|
|
87
|
+
tb = gl.load(pb + (k0 + BK) * stride_bk, mask=mb_ & (kb + k0 + BK < K), other=0.0)
|
|
88
|
+
t = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
|
|
89
|
+
for j in gl.static_range(J):
|
|
90
|
+
t = mma_k4(sa, j * BM, sb, j * BN, t, MB, NB, LA, LB, L)
|
|
91
|
+
acc = acc + t # across tiles on the FMA units, round to nearest
|
|
92
|
+
|
|
93
|
+
rows, cols = acc_rows_cols(L, MB, NB)
|
|
94
|
+
pair = gl.load(sorted_ids + m0 + rows, mask=(rows >= 0) & block_valid, other=0).to(gl.int64)
|
|
95
|
+
ok = (pair < num_valid) & block_valid
|
|
96
|
+
cols = n0 + cols
|
|
97
|
+
if MUL_ROUTED_WEIGHT:
|
|
98
|
+
acc = acc * gl.load(topk_w + pair, mask=ok, other=0.0).to(gl.float32)
|
|
99
|
+
gl.store(C + pair * stride_cm + cols.to(gl.int64) * stride_cn, acc.to(C.dtype.element_ty), mask=ok & (cols < N))
|