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
nbxtensor/ops/matmul.py
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""Matrix multiplication — pure @triton.jit kernel with autotune.
|
|
2
|
+
|
|
3
|
+
Ported from Triton official tutorial (BSD license)
|
|
4
|
+
https://triton-lang.org/main/getting-started/tutorials/03-matrix-multiplication.html
|
|
5
|
+
adapted for NeuroBrix:
|
|
6
|
+
- NBXTensor inputs (handled by wrappers, kernel sees raw ptrs/strides)
|
|
7
|
+
- Volta-aware static dtype path (PROMOTE_B + IEEE_PRECISION constexpr)
|
|
8
|
+
- @triton.autotune across 18 configs (Phase 1.5, 2026-05): the only
|
|
9
|
+
proven path to ≥70% cuBLAS HMMA on Sana DiT shapes — see CLAUDE.md
|
|
10
|
+
"Autotune policy" section for the doctrinal exception that allows
|
|
11
|
+
# PROMOTE_A (and PROMOTE_BIAS) are NOT part of the key: the directory's certified
|
|
12
|
+
# settings are indexed by the shape and the pre-existing flags, and a promotion of
|
|
13
|
+
# an operand on load changes neither the tiles' legality nor the accumulation
|
|
14
|
+
# order — the same setting applies; keying on it made every matmul of the tree a
|
|
15
|
+
# miss (5,628 entries unserved, a runtime sweep per key, 2026-09-07 lever 2).
|
|
16
|
+
@triton.autotune on mm/bmm/addmm/conv2d.
|
|
17
|
+
- tl.dot 3-arg HMMA-FMA fused form
|
|
18
|
+
- tl.assume integer-analyzer hints
|
|
19
|
+
- in-kernel cast accum → output dtype
|
|
20
|
+
Handles mm (2D) + addmm (with bias). bmm handled in wrappers via batch loop.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import triton
|
|
24
|
+
|
|
25
|
+
from nbxtensor.ops._configs import arch_smem_budget, configs_within_smem_budget, nbx_autotune
|
|
26
|
+
import triton.language as tl
|
|
27
|
+
|
|
28
|
+
from nbxtensor.ops._autotune_policy import maybe_pin_single, is_matmul_pinned
|
|
29
|
+
from nbxtensor.ops._common import sigmoid, tanh_fn
|
|
30
|
+
|
|
31
|
+
# Vertical-fusion epilogue codes (fusion_vertical pass, EXACT gate).
|
|
32
|
+
# 0 = none, 1 = silu, 2 = gelu exact, 3 = gelu tanh. The epilogue is a
|
|
33
|
+
# tl.constexpr NOT in the autotune key: both fused and unfused variants
|
|
34
|
+
# share ONE tuning-cache entry per (M, N, K, IEEE, PROMOTE_B), so the
|
|
35
|
+
# selected config — and therefore the tl.dot accumulation order — is
|
|
36
|
+
# identical by construction (the two-leg byte-exactness contract: the
|
|
37
|
+
# R16 research showed rounding emulation alone is insufficient if the
|
|
38
|
+
# fused and unfused kernels can tune to different BLOCK_K).
|
|
39
|
+
#
|
|
40
|
+
# Byte-exactness emulation: the unfused pair is
|
|
41
|
+
# matmul_kernel: fp32 acc -> cast to C dtype -> store (round 1)
|
|
42
|
+
# silu/gelu kernel: load C dtype -> .to(fp32) -> f(x) -> store (round 2)
|
|
43
|
+
# The fused epilogue reproduces BOTH rounding points in-register:
|
|
44
|
+
# acc -> .to(C dtype) -> .to(fp32) -> f(x) -> store
|
|
45
|
+
# using the SAME formula text and the SAME _common helpers as the
|
|
46
|
+
# standalone kernels, so the arithmetic lowers identically.
|
|
47
|
+
EPILOGUE_NONE: int = 0
|
|
48
|
+
EPILOGUE_SILU: int = 1
|
|
49
|
+
EPILOGUE_GELU_EXACT: int = 2
|
|
50
|
+
EPILOGUE_GELU_TANH: int = 3
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Per-architecture autotune configs (tutorial pattern
|
|
54
|
+
# `get_cuda_autotune_config()` adapted to NeuroBrix: gate by detected
|
|
55
|
+
# compute capability so each hardware explores ONLY its viable subspace).
|
|
56
|
+
# Volta sm_70 has 96 KB SMEM/SM (vs 192 KB sm_80 / 228 KB sm_90); large
|
|
57
|
+
# blocks (BM≥128 BK≥64 warps=8) saturate SMEM → register spill →
|
|
58
|
+
# catastrophic perf (98-145 ms measured Phase 1.5 Step 2 FlagGems
|
|
59
|
+
# bench). The Volta-viable subspace is restricted to BM∈{32,64},
|
|
60
|
+
# BN∈{32,64,128}, BK∈{32,64}, warps∈{2,4}, stages∈{2..5} — ~20
|
|
61
|
+
# combinations giving the autotuner a denser space of fitting configs.
|
|
62
|
+
# Ampere+ (sm_80+) gates BM/BN/BK larger as the hardware supports it.
|
|
63
|
+
|
|
64
|
+
_MATMUL_AUTOTUNE_VOLTA = [
|
|
65
|
+
# BM=32 row
|
|
66
|
+
triton.Config({'BLOCK_M': 32, 'BLOCK_N': 32, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=2),
|
|
67
|
+
triton.Config({'BLOCK_M': 32, 'BLOCK_N': 32, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=4, num_warps=2),
|
|
68
|
+
triton.Config({'BLOCK_M': 32, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=2),
|
|
69
|
+
triton.Config({'BLOCK_M': 32, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=5, num_warps=2),
|
|
70
|
+
triton.Config({'BLOCK_M': 32, 'BLOCK_N': 64, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=3, num_warps=4),
|
|
71
|
+
triton.Config({'BLOCK_M': 32, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=3, num_warps=4),
|
|
72
|
+
# BM=64 row (the historical NeuroBrix default neighborhood)
|
|
73
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 32, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=5, num_warps=2),
|
|
74
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 32, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=4, num_warps=2),
|
|
75
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=2, num_warps=4),
|
|
76
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=3, num_warps=4),
|
|
77
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
|
|
78
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 64, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=3, num_warps=4),
|
|
79
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 64, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=4, num_warps=4),
|
|
80
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=2, num_warps=4),
|
|
81
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=3, num_warps=4),
|
|
82
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
|
|
83
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=3, num_warps=4),
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# Ampere/Hopper subspace (sm_80+) — larger blocks allowed thanks to
|
|
87
|
+
# SMEM doubling and tensor-core throughput. Tutorial canonical configs.
|
|
88
|
+
_MATMUL_AUTOTUNE_AMPERE_PLUS = [
|
|
89
|
+
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 256, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=3, num_warps=8),
|
|
90
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 256, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
|
|
91
|
+
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
|
|
92
|
+
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
|
|
93
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
|
|
94
|
+
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=4, num_warps=4),
|
|
95
|
+
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 64, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=4, num_warps=4),
|
|
96
|
+
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=4, num_warps=4),
|
|
97
|
+
] + _MATMUL_AUTOTUNE_VOLTA # Ampere+ also benefits from smaller configs
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _detect_arch_configs():
|
|
101
|
+
"""Return the autotune config space for the active architecture.
|
|
102
|
+
|
|
103
|
+
WHY THIS ASKS THE DRIVER, when R23/R24 put hardware parameters in
|
|
104
|
+
`config/vendors/<vendor>/<arch>.yml`: autotune configs are bound at
|
|
105
|
+
DECORATION time, which is module import — before `set_hardware_profile()`
|
|
106
|
+
has run at executor construction. There is no profile to read yet. This is
|
|
107
|
+
the documented bootstrap exception, not a licence to query the driver
|
|
108
|
+
elsewhere; every parameter that CAN wait for the profile reads the YAML
|
|
109
|
+
(see `_safe_num_stages`, which moved to `pipelining.max_num_stages`).
|
|
110
|
+
|
|
111
|
+
The spaces:
|
|
112
|
+
|
|
113
|
+
* sm_80+ (Ampere/Hopper) — tutorial canonical plus the Volta subset.
|
|
114
|
+
* sm_70 (Volta) — Volta-viable subset only: larger blocks saturate the
|
|
115
|
+
96 KB SMEM, spill registers and collapse throughput (measured, Phase 1.5).
|
|
116
|
+
* CDNA (gfx*) — the Volta-viable subset. AMD's Triton target reports `arch`
|
|
117
|
+
as a STRING ("gfx90a"), so the old `isinstance(cap, int)` test silently
|
|
118
|
+
dropped every AMD card into the Volta space *by accident*. It lands in the
|
|
119
|
+
same place deliberately now, and for a stated reason: CDNA exposes 64 KB
|
|
120
|
+
of LDS against Volta's 96 KB, so the small-block space is the safe one
|
|
121
|
+
until an MI-series card measures a better one. Named as a first-light
|
|
122
|
+
task, not left as a coincidence.
|
|
123
|
+
* anything else (Apple, unknown) — the Volta subset as the STARTING space.
|
|
124
|
+
|
|
125
|
+
Whichever space is chosen, it is then filtered against the executing
|
|
126
|
+
hardware's `memory.max_shared_memory_per_block`, read from its vendor YAML.
|
|
127
|
+
Choosing by architecture NAME alone was wrong in two directions and both
|
|
128
|
+
are now decided rather than inherited:
|
|
129
|
+
|
|
130
|
+
* **Apple** was sent to the Volta subset because that space "fits the
|
|
131
|
+
smallest budget". It does not: Volta declares 96 KB and an Apple GPU has
|
|
132
|
+
32 KB, so a BM=64/BN=128/BK=64 tile at 3 stages wants 72 KB and cannot
|
|
133
|
+
run there. Nine of the seventeen Volta configs survive the real budget.
|
|
134
|
+
* **CDNA** landed in the Volta space by accident (a string arch failed an
|
|
135
|
+
`isinstance(cap, int)` test), was later made deliberate, and still had no
|
|
136
|
+
budget applied — CDNA1/2 declare 64 KB, which excludes the largest Volta
|
|
137
|
+
tile at 72 KB.
|
|
138
|
+
|
|
139
|
+
Filtering never empties the space and never invents a budget: with no
|
|
140
|
+
matching profile the space is returned untouched.
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
from nbxtensor.backend.launcher import target as _nbx_target # engine data, no driver probe (R33)
|
|
144
|
+
arch = _nbx_target().arch
|
|
145
|
+
except Exception:
|
|
146
|
+
return _MATMUL_AUTOTUNE_VOLTA
|
|
147
|
+
|
|
148
|
+
# NVIDIA: Triton reports compute capability x 10 (sm_70 -> 70, sm_80 -> 80)
|
|
149
|
+
if isinstance(arch, int):
|
|
150
|
+
space = (_MATMUL_AUTOTUNE_AMPERE_PLUS if arch >= 80
|
|
151
|
+
else _MATMUL_AUTOTUNE_VOLTA)
|
|
152
|
+
else:
|
|
153
|
+
# AMD CDNA/RDNA and any other string-arch target (Apple included).
|
|
154
|
+
space = _MATMUL_AUTOTUNE_VOLTA
|
|
155
|
+
|
|
156
|
+
return configs_within_smem_budget(space, arch_smem_budget())
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
_MATMUL_AUTOTUNE_CONFIGS = maybe_pin_single(
|
|
160
|
+
_detect_arch_configs(), is_matmul_pinned)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@nbx_autotune(configs=_MATMUL_AUTOTUNE_CONFIGS,
|
|
164
|
+
key=['M_BUCKET', 'N', 'K', 'IEEE_PRECISION', 'PROMOTE_B'],
|
|
165
|
+
cache_results=True)
|
|
166
|
+
@triton.jit
|
|
167
|
+
def matmul_kernel(
|
|
168
|
+
a_ptr, b_ptr, c_ptr,
|
|
169
|
+
M, N, K, M_BUCKET,
|
|
170
|
+
stride_am, stride_ak,
|
|
171
|
+
stride_bk, stride_bn,
|
|
172
|
+
stride_cm, stride_cn,
|
|
173
|
+
IEEE_PRECISION: tl.constexpr = False,
|
|
174
|
+
PROMOTE_B: tl.constexpr = False,
|
|
175
|
+
PROMOTE_A: tl.constexpr = False,
|
|
176
|
+
EPILOGUE: tl.constexpr = 0,
|
|
177
|
+
BLOCK_M: tl.constexpr = 64,
|
|
178
|
+
BLOCK_N: tl.constexpr = 64,
|
|
179
|
+
BLOCK_K: tl.constexpr = 32,
|
|
180
|
+
GROUP_M: tl.constexpr = 8,
|
|
181
|
+
):
|
|
182
|
+
"""C = A @ B where A is [M, K], B is [K, N], C is [M, N].
|
|
183
|
+
|
|
184
|
+
Accumulates in fp32 for numerical stability.
|
|
185
|
+
Output dtype determined by output pointer's dtype (in-kernel cast).
|
|
186
|
+
|
|
187
|
+
IEEE_PRECISION=True forces `tl.dot(input_precision="ieee")` — required
|
|
188
|
+
when fp32 inputs carry magnitudes > fp16_max on pre-Ampere GPUs,
|
|
189
|
+
because `tl.dot` otherwise lowers through fp16 HMMA tensor cores which
|
|
190
|
+
saturate the inputs to fp16 before the multiply. Set by the wrapper
|
|
191
|
+
when `not _NBX_HAS_NATIVE_BF16` and inputs were promoted to fp32.
|
|
192
|
+
|
|
193
|
+
PROMOTE_B=True casts the b tile to a's dtype after load and before
|
|
194
|
+
tl.dot. Triton's type checker rejects `tl.dot(fp32, fp16)` at compile
|
|
195
|
+
time; the cast is the cheapest way to bridge the mismatch — fused
|
|
196
|
+
with the load, register-level, no heap allocation. Set by the wrapper
|
|
197
|
+
when the activation was upcast fp16→fp32 (step 2) but the weight
|
|
198
|
+
was left fp16 in memory (to save VRAM). The bit-exact fp32 promotion
|
|
199
|
+
of a fp16 tile is free numerically (fp16 values are a subset of
|
|
200
|
+
fp32); the accumulator is fp32 so the final dot product is identical
|
|
201
|
+
to the path that widens the full weight pre-kernel.
|
|
202
|
+
|
|
203
|
+
# PROMOTE_A / PROMOTE_BIAS are not part of the key — see matmul_kernel above.
|
|
204
|
+
Phase 1.5 (2026-05): @triton.autotune ENABLED. The autotune key
|
|
205
|
+
includes IEEE_PRECISION + PROMOTE_B so each (Volta-fp32 / Volta-fp16-mixed
|
|
206
|
+
/ Ampere+ pure fp16) path gets its own selected config.
|
|
207
|
+
"""
|
|
208
|
+
# tl.assume hints (tutorial pattern) — combined with autotune they
|
|
209
|
+
# carry through; without autotune they were inactive (rollback 396fef1).
|
|
210
|
+
tl.assume(stride_am > 0); tl.assume(stride_ak > 0)
|
|
211
|
+
tl.assume(stride_bk > 0); tl.assume(stride_bn > 0)
|
|
212
|
+
tl.assume(stride_cm > 0); tl.assume(stride_cn > 0)
|
|
213
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
214
|
+
num_pid_m = tl.cdiv(M, BLOCK_M)
|
|
215
|
+
num_pid_n = tl.cdiv(N, BLOCK_N)
|
|
216
|
+
num_pid_in_group = GROUP_M * num_pid_n
|
|
217
|
+
group_id = pid // num_pid_in_group
|
|
218
|
+
first_pid_m = group_id * GROUP_M
|
|
219
|
+
group_size_m = min(num_pid_m - first_pid_m, GROUP_M)
|
|
220
|
+
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
|
|
221
|
+
pid_n = (pid % num_pid_in_group) // group_size_m
|
|
222
|
+
|
|
223
|
+
offs_am = (pid_m * BLOCK_M + tl.arange(0, BLOCK_M)) % M
|
|
224
|
+
offs_bn = (pid_n * BLOCK_N + tl.arange(0, BLOCK_N)) % N
|
|
225
|
+
offs_k = tl.arange(0, BLOCK_K)
|
|
226
|
+
# Pointer offsets in int64: an int32 row offset times a stride wraps past
|
|
227
|
+
# 2^31 elements (Mochi's VAE mm, M=1 068 480 x N=2048 — CUDA 700 at every
|
|
228
|
+
# attempt since 2026-09-10; test_a_gemm_beyond_two_billion_elements).
|
|
229
|
+
a_ptrs = a_ptr + (offs_am[:, None].to(tl.int64) * stride_am + offs_k[None, :] * stride_ak)
|
|
230
|
+
b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_bn[None, :].to(tl.int64) * stride_bn)
|
|
231
|
+
|
|
232
|
+
accumulator = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
|
|
233
|
+
for k in range(0, tl.cdiv(K, BLOCK_K)):
|
|
234
|
+
a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_K, other=0.0)
|
|
235
|
+
b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_K, other=0.0)
|
|
236
|
+
if PROMOTE_A:
|
|
237
|
+
# The activation tile widened in registers — the same numbers the wrapper
|
|
238
|
+
# produced by materialising `a.to(float32)` before the call (an exact widening),
|
|
239
|
+
# without that copy per matmul (TinyLlama decode: 143 a token, 2026-09-07).
|
|
240
|
+
a = a.to(tl.float32)
|
|
241
|
+
if PROMOTE_B:
|
|
242
|
+
b = b.to(a.dtype)
|
|
243
|
+
# 3-arg HMMA-FMA fused form (tutorial pattern).
|
|
244
|
+
if IEEE_PRECISION:
|
|
245
|
+
accumulator = tl.dot(a, b, accumulator, input_precision="ieee")
|
|
246
|
+
else:
|
|
247
|
+
accumulator = tl.dot(a, b, accumulator)
|
|
248
|
+
a_ptrs += BLOCK_K * stride_ak
|
|
249
|
+
b_ptrs += BLOCK_K * stride_bk
|
|
250
|
+
|
|
251
|
+
# In-kernel cast accum → output dtype (tutorial pattern; faster than
|
|
252
|
+
# tl.store auto-cast since the conversion happens in registers).
|
|
253
|
+
c = accumulator.to(c_ptr.dtype.element_ty)
|
|
254
|
+
offs_cm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
|
|
255
|
+
offs_cn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
|
|
256
|
+
c_ptrs = c_ptr + stride_cm * offs_cm[:, None].to(tl.int64) + stride_cn * offs_cn[None, :].to(tl.int64)
|
|
257
|
+
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
|
258
|
+
if EPILOGUE != 0:
|
|
259
|
+
# Per-stage rounding emulation: `c` already carries round 1 (the
|
|
260
|
+
# unfused mm's store cast); .to(fp32) is the standalone epilogue
|
|
261
|
+
# kernel's lossless load upcast. Formulas are copied VERBATIM
|
|
262
|
+
# from ops/silu.py / ops/gelu.py; the store's implicit downcast
|
|
263
|
+
# mirrors the standalone kernel's tl.store.
|
|
264
|
+
x_fp32 = c.to(tl.float32)
|
|
265
|
+
if EPILOGUE == 1:
|
|
266
|
+
out = x_fp32 * sigmoid(x_fp32)
|
|
267
|
+
elif EPILOGUE == 2:
|
|
268
|
+
cdf = 0.5 * (1.0 + tl.math.erf(0.707106781 * x_fp32))
|
|
269
|
+
out = cdf * x_fp32
|
|
270
|
+
else:
|
|
271
|
+
cdf = 0.5 * (1.0 + tanh_fn(0.7978845608 * x_fp32 * (1.0 + 0.044715 * x_fp32 * x_fp32)))
|
|
272
|
+
out = cdf * x_fp32
|
|
273
|
+
tl.store(c_ptrs, out, mask=c_mask)
|
|
274
|
+
else:
|
|
275
|
+
tl.store(c_ptrs, c, mask=c_mask)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@nbx_autotune(configs=_MATMUL_AUTOTUNE_CONFIGS,
|
|
279
|
+
key=['M_BUCKET', 'N', 'K', 'IEEE_PRECISION', 'PROMOTE_B'],
|
|
280
|
+
cache_results=True)
|
|
281
|
+
@triton.jit
|
|
282
|
+
def addmm_kernel(
|
|
283
|
+
a_ptr, b_ptr, bias_ptr, c_ptr,
|
|
284
|
+
M, N, K, M_BUCKET,
|
|
285
|
+
stride_am, stride_ak,
|
|
286
|
+
stride_bk, stride_bn,
|
|
287
|
+
stride_cm, stride_cn,
|
|
288
|
+
alpha, beta,
|
|
289
|
+
IEEE_PRECISION: tl.constexpr = False,
|
|
290
|
+
PROMOTE_B: tl.constexpr = False,
|
|
291
|
+
PROMOTE_A: tl.constexpr = False,
|
|
292
|
+
PROMOTE_BIAS: tl.constexpr = False, # the bias widened to fp32 on load (exact), not cast beforehand
|
|
293
|
+
EPILOGUE: tl.constexpr = 0,
|
|
294
|
+
BLOCK_M: tl.constexpr = 64,
|
|
295
|
+
BLOCK_N: tl.constexpr = 64,
|
|
296
|
+
BLOCK_K: tl.constexpr = 32,
|
|
297
|
+
GROUP_M: tl.constexpr = 8,
|
|
298
|
+
):
|
|
299
|
+
"""C = beta * bias + alpha * (A @ B) where bias is [N].
|
|
300
|
+
|
|
301
|
+
PROMOTE_B: see matmul_kernel docstring. Same in-kernel fp16→fp32
|
|
302
|
+
tile cast; enables the wrapper to keep fp16 weights fp16 in memory
|
|
303
|
+
while still running tl.dot with matched dtypes.
|
|
304
|
+
|
|
305
|
+
Phase 1.5 (2026-05): @triton.autotune ENABLED — same configs as
|
|
306
|
+
matmul_kernel. Tutorial pattern adapted with NeuroBrix's bias
|
|
307
|
+
addition + alpha/beta scaling.
|
|
308
|
+
"""
|
|
309
|
+
tl.assume(stride_am > 0); tl.assume(stride_ak > 0)
|
|
310
|
+
tl.assume(stride_bk > 0); tl.assume(stride_bn > 0)
|
|
311
|
+
tl.assume(stride_cm > 0); tl.assume(stride_cn > 0)
|
|
312
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
313
|
+
num_pid_m = tl.cdiv(M, BLOCK_M)
|
|
314
|
+
num_pid_n = tl.cdiv(N, BLOCK_N)
|
|
315
|
+
num_pid_in_group = GROUP_M * num_pid_n
|
|
316
|
+
group_id = pid // num_pid_in_group
|
|
317
|
+
first_pid_m = group_id * GROUP_M
|
|
318
|
+
group_size_m = min(num_pid_m - first_pid_m, GROUP_M)
|
|
319
|
+
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
|
|
320
|
+
pid_n = (pid % num_pid_in_group) // group_size_m
|
|
321
|
+
|
|
322
|
+
offs_am = (pid_m * BLOCK_M + tl.arange(0, BLOCK_M)) % M
|
|
323
|
+
offs_bn = (pid_n * BLOCK_N + tl.arange(0, BLOCK_N)) % N
|
|
324
|
+
offs_k = tl.arange(0, BLOCK_K)
|
|
325
|
+
# Pointer offsets in int64: an int32 row offset times a stride wraps past
|
|
326
|
+
# 2^31 elements (Mochi's VAE mm, M=1 068 480 x N=2048 — CUDA 700 at every
|
|
327
|
+
# attempt since 2026-09-10; test_a_gemm_beyond_two_billion_elements).
|
|
328
|
+
a_ptrs = a_ptr + (offs_am[:, None].to(tl.int64) * stride_am + offs_k[None, :] * stride_ak)
|
|
329
|
+
b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_bn[None, :].to(tl.int64) * stride_bn)
|
|
330
|
+
|
|
331
|
+
accumulator = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
|
|
332
|
+
for k in range(0, tl.cdiv(K, BLOCK_K)):
|
|
333
|
+
a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_K, other=0.0)
|
|
334
|
+
b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_K, other=0.0)
|
|
335
|
+
if PROMOTE_A:
|
|
336
|
+
# The activation tile widened in registers — the same numbers the wrapper
|
|
337
|
+
# produced by materialising `a.to(float32)` before the call (an exact widening),
|
|
338
|
+
# without that copy per matmul (TinyLlama decode: 143 a token, 2026-09-07).
|
|
339
|
+
a = a.to(tl.float32)
|
|
340
|
+
if PROMOTE_B:
|
|
341
|
+
b = b.to(a.dtype)
|
|
342
|
+
if IEEE_PRECISION:
|
|
343
|
+
accumulator = tl.dot(a, b, accumulator, input_precision="ieee")
|
|
344
|
+
else:
|
|
345
|
+
accumulator = tl.dot(a, b, accumulator)
|
|
346
|
+
a_ptrs += BLOCK_K * stride_ak
|
|
347
|
+
b_ptrs += BLOCK_K * stride_bk
|
|
348
|
+
|
|
349
|
+
# Add bias: C = alpha * matmul + beta * bias
|
|
350
|
+
offs_cn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
|
|
351
|
+
bias_mask = offs_cn < N
|
|
352
|
+
bias = tl.load(bias_ptr + offs_cn, mask=bias_mask)
|
|
353
|
+
if PROMOTE_BIAS:
|
|
354
|
+
bias = bias.to(tl.float32)
|
|
355
|
+
accumulator = alpha * accumulator + beta * bias[None, :]
|
|
356
|
+
|
|
357
|
+
offs_cm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
|
|
358
|
+
c_ptrs = c_ptr + stride_cm * offs_cm[:, None].to(tl.int64) + stride_cn * offs_cn[None, :].to(tl.int64)
|
|
359
|
+
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
|
360
|
+
if EPILOGUE != 0:
|
|
361
|
+
# Per-stage rounding emulation (see matmul_kernel): the unfused
|
|
362
|
+
# addmm stores the fp32 accumulator with tl.store's implicit
|
|
363
|
+
# downcast (round 1); the standalone epilogue kernel loads and
|
|
364
|
+
# upcasts to fp32. Reproduce both in-register, then apply the
|
|
365
|
+
# VERBATIM standalone formulas.
|
|
366
|
+
x_fp32 = accumulator.to(c_ptr.dtype.element_ty).to(tl.float32)
|
|
367
|
+
if EPILOGUE == 1:
|
|
368
|
+
out = x_fp32 * sigmoid(x_fp32)
|
|
369
|
+
elif EPILOGUE == 2:
|
|
370
|
+
cdf = 0.5 * (1.0 + tl.math.erf(0.707106781 * x_fp32))
|
|
371
|
+
out = cdf * x_fp32
|
|
372
|
+
else:
|
|
373
|
+
cdf = 0.5 * (1.0 + tanh_fn(0.7978845608 * x_fp32 * (1.0 + 0.044715 * x_fp32 * x_fp32)))
|
|
374
|
+
out = cdf * x_fp32
|
|
375
|
+
tl.store(c_ptrs, out, mask=c_mask)
|
|
376
|
+
else:
|
|
377
|
+
tl.store(c_ptrs, accumulator, mask=c_mask)
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""GEMM on the m8n8k4 matrix unit — `mm` and `addmm` where the hardware profile declares `matrix_unit`.
|
|
2
|
+
|
|
3
|
+
Same contract as `matmul_kernel` / `addmm_kernel` / `baddbmm_kernel` (ops/matmul.py) for operands that are both the unit's operand
|
|
4
|
+
dtype in memory: C[z] = A[z] @ B[z] (+ beta * bias + alpha scaling; the bias broadcast by its strides; batch on
|
|
5
|
+
grid axis 1, stride 0 for mm / addmm), fp32 accumulation, any strides on B (a
|
|
6
|
+
pre-transposed weight is read in place), int64 offsets, M/N/K tails masked, the output cast to C's dtype, and the
|
|
7
|
+
fused epilogues (silu, gelu exact, gelu tanh) with the per-stage rounding emulation of the unfused pair: the
|
|
8
|
+
accumulator is rounded to C's dtype, widened back, then the standalone kernel's formula is applied.
|
|
9
|
+
|
|
10
|
+
The products of two fp16 operands are exact in fp32. The unit's own accumulation rounds toward zero (Fasi et al.
|
|
11
|
+
2021, PeerJ CS 7:e330), so it sums only one K tile into a zeroed accumulator; the tiles are summed on the FMA units
|
|
12
|
+
with round to nearest, as the fp32 `tl.dot(input_precision="ieee")` path sums, in another order. The tile is the profile's
|
|
13
|
+
(`matrix_unit.mm`), never tuned at run time: no autotune key, nothing to certify.
|
|
14
|
+
|
|
15
|
+
Operands go global -> registers (coalesced) -> shared memory as [k-step x rows, 4] -> HMMA fragments
|
|
16
|
+
(ops/mma_m8n8k4.py); the next K tile is loaded while the current one is multiplied.
|
|
17
|
+
|
|
18
|
+
SPLIT representation (SPLIT_A / SPLIT_B, decided by the DtypeEngine: nbxtensor/dtype/operands.matrix_unit_representation, the
|
|
19
|
+
profile's `matrix_unit.fp32_split`): an operand of any float dtype in memory is widened to fp32, each row of its K tile (each
|
|
20
|
+
column for B) scaled by the power of two 2^s that brings that row's largest magnitude to [2^HI_EXP, 2^(HI_EXP+1)),
|
|
21
|
+
and carried as hi = fp16(x 2^s) and lo = fp16((x 2^s - hi) 2^LO_SHIFT). The scale is constant along k, so it factors
|
|
22
|
+
out of the dot product: a small row next to a large one keeps its own precision. Per K tile the unit computes hi*hi
|
|
23
|
+
into one zeroed accumulator and hi*lo + lo*hi into another; the tile's sum t_hh + t_lo 2^-LO_SHIFT, times 2^-s of
|
|
24
|
+
each split operand's row (column), is added to the fp32 accumulator on the FMA units (round to nearest). The unit's own accumulation rounds
|
|
25
|
+
toward zero (Fasi et al. 2021, PeerJ CS 7:e330), so it never carries the sum across tiles; lo*lo (< 2^-22 of the
|
|
26
|
+
product) is not formed (Ootomo & Yokota 2022, arXiv 2203.03341, eq. 24). Every power-of-two scaling is exact.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import functools
|
|
30
|
+
|
|
31
|
+
from triton.experimental import gluon
|
|
32
|
+
from triton.experimental.gluon import language as gl
|
|
33
|
+
|
|
34
|
+
from .mma_m8n8k4 import (acc_layout, acc_rows_cols, bcast_rows, frag_a_layout, frag_b_layout, mma_k4,
|
|
35
|
+
stage_k_inner, stage_k_outer)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@gluon.jit
|
|
39
|
+
def _sigmoid(x):
|
|
40
|
+
return 1.0 / (1.0 + gl.exp(-x))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@gluon.jit
|
|
44
|
+
def _tanh(x):
|
|
45
|
+
return 2.0 * _sigmoid(2.0 * x) - 1.0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@gluon.jit
|
|
49
|
+
def matmul_m8n8k4_kernel(
|
|
50
|
+
A, B, Bias, C,
|
|
51
|
+
M, N, K,
|
|
52
|
+
stride_az, stride_am, stride_ak, stride_bz, stride_bk, stride_bn, stride_cz, stride_cm, stride_cn,
|
|
53
|
+
stride_xz, stride_xm, stride_xn,
|
|
54
|
+
alpha, beta,
|
|
55
|
+
HAS_BIAS: gl.constexpr, PROMOTE_BIAS: gl.constexpr, EPILOGUE: gl.constexpr, B_K_CONTIG: gl.constexpr,
|
|
56
|
+
SPLIT_A: gl.constexpr, SPLIT_B: gl.constexpr, HI_EXP: gl.constexpr, LO_SHIFT: gl.constexpr,
|
|
57
|
+
MB: gl.constexpr, NB: gl.constexpr, BK: gl.constexpr, GROUP_M: gl.constexpr,
|
|
58
|
+
L: gl.constexpr, LA: gl.constexpr, LB: gl.constexpr, GA: gl.constexpr, GB: gl.constexpr,
|
|
59
|
+
):
|
|
60
|
+
BM: gl.constexpr = 16 * MB
|
|
61
|
+
BN: gl.constexpr = 16 * NB
|
|
62
|
+
J: gl.constexpr = BK // 4
|
|
63
|
+
SH: gl.constexpr = gl.SwizzledSharedLayout(1, 1, 1, order=[1, 0])
|
|
64
|
+
|
|
65
|
+
pid = gl.program_id(0).to(gl.int64)
|
|
66
|
+
z = gl.program_id(1).to(gl.int64) # the batch (0 for mm / addmm)
|
|
67
|
+
A = A + z * stride_az
|
|
68
|
+
B = B + z * stride_bz
|
|
69
|
+
C = C + z * stride_cz
|
|
70
|
+
num_pid_m = gl.cdiv(M, BM)
|
|
71
|
+
num_pid_n = gl.cdiv(N, BN)
|
|
72
|
+
num_pid_in_group = GROUP_M * num_pid_n
|
|
73
|
+
first_pid_m = (pid // num_pid_in_group) * GROUP_M
|
|
74
|
+
group_size_m = gl.minimum(num_pid_m - first_pid_m, GROUP_M)
|
|
75
|
+
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
|
|
76
|
+
pid_n = (pid % num_pid_in_group) // group_size_m
|
|
77
|
+
m0 = pid_m * BM
|
|
78
|
+
n0 = pid_n * BN
|
|
79
|
+
|
|
80
|
+
sa = gl.allocate_shared_memory(gl.float16, [J * BM, 4], SH)
|
|
81
|
+
sb = gl.allocate_shared_memory(gl.float16, [J * BN, 4], SH)
|
|
82
|
+
if SPLIT_A:
|
|
83
|
+
sal = gl.allocate_shared_memory(gl.float16, [J * BM, 4], SH)
|
|
84
|
+
if SPLIT_B:
|
|
85
|
+
sbl = gl.allocate_shared_memory(gl.float16, [J * BN, 4], SH)
|
|
86
|
+
|
|
87
|
+
# A [BM, BK], k along the columns (the wrapper makes A row-major).
|
|
88
|
+
ra = gl.arange(0, BM, layout=gl.SliceLayout(1, GA))[:, None]
|
|
89
|
+
ka = gl.arange(0, BK, layout=gl.SliceLayout(0, GA))[None, :]
|
|
90
|
+
pa = A + (m0 + ra) * stride_am + ka * stride_ak
|
|
91
|
+
ma = (m0 + ra) < M
|
|
92
|
+
# B either [BN, BK] (k contiguous: a pre-transposed weight) or [BK, BN] (n contiguous).
|
|
93
|
+
if B_K_CONTIG:
|
|
94
|
+
nb_ = gl.arange(0, BN, layout=gl.SliceLayout(1, GB))[:, None]
|
|
95
|
+
kb = gl.arange(0, BK, layout=gl.SliceLayout(0, GB))[None, :]
|
|
96
|
+
else:
|
|
97
|
+
kb = gl.arange(0, BK, layout=gl.SliceLayout(1, GB))[:, None]
|
|
98
|
+
nb_ = gl.arange(0, BN, layout=gl.SliceLayout(0, GB))[None, :]
|
|
99
|
+
pb = B + kb * stride_bk + (n0 + nb_) * stride_bn
|
|
100
|
+
mb_ = (n0 + nb_) < N
|
|
101
|
+
|
|
102
|
+
acc = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
|
|
103
|
+
ta = gl.load(pa, mask=ma & (ka < K), other=0.0)
|
|
104
|
+
tb = gl.load(pb, mask=mb_ & (kb < K), other=0.0)
|
|
105
|
+
for k0 in range(0, K, BK):
|
|
106
|
+
gl.barrier() # every warp is done reading the previous tile
|
|
107
|
+
if SPLIT_A:
|
|
108
|
+
inv_a = _stage_split(sa, sal, ta, True, BM, BK, HI_EXP, LO_SHIFT)
|
|
109
|
+
else:
|
|
110
|
+
stage_k_inner(sa, ta, BM, BK)
|
|
111
|
+
if SPLIT_B:
|
|
112
|
+
inv_b = _stage_split(sb, sbl, tb, B_K_CONTIG, BN, BK, HI_EXP, LO_SHIFT)
|
|
113
|
+
elif B_K_CONTIG:
|
|
114
|
+
stage_k_inner(sb, tb, BN, BK)
|
|
115
|
+
else:
|
|
116
|
+
stage_k_outer(sb, tb, BK, BN)
|
|
117
|
+
gl.barrier()
|
|
118
|
+
if k0 + BK < K: # the next tile loads while the HMMAs run
|
|
119
|
+
ta = gl.load(pa + (k0 + BK) * stride_ak, mask=ma & (ka + k0 + BK < K), other=0.0)
|
|
120
|
+
tb = gl.load(pb + (k0 + BK) * stride_bk, mask=mb_ & (kb + k0 + BK < K), other=0.0)
|
|
121
|
+
if SPLIT_A or SPLIT_B:
|
|
122
|
+
t_hh = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
|
|
123
|
+
t_lo = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
|
|
124
|
+
for j in gl.static_range(J):
|
|
125
|
+
t_hh = mma_k4(sa, j * BM, sb, j * BN, t_hh, MB, NB, LA, LB, L)
|
|
126
|
+
if SPLIT_A:
|
|
127
|
+
t_lo = mma_k4(sal, j * BM, sb, j * BN, t_lo, MB, NB, LA, LB, L)
|
|
128
|
+
if SPLIT_B:
|
|
129
|
+
t_lo = mma_k4(sa, j * BM, sbl, j * BN, t_lo, MB, NB, LA, LB, L)
|
|
130
|
+
t = t_hh + t_lo * (1.0 / (1 << LO_SHIFT))
|
|
131
|
+
if SPLIT_A:
|
|
132
|
+
t = t * _bcast_rows(gl.reshape(inv_a, [MB, 2, 8]), L)
|
|
133
|
+
if SPLIT_B:
|
|
134
|
+
t = t * _bcast_cols(gl.reshape(inv_b, [NB, 2, 2, 4]), L)
|
|
135
|
+
acc = acc + t
|
|
136
|
+
else:
|
|
137
|
+
t = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
|
|
138
|
+
for j in gl.static_range(J):
|
|
139
|
+
t = mma_k4(sa, j * BM, sb, j * BN, t, MB, NB, LA, LB, L)
|
|
140
|
+
acc = acc + t # across tiles on the FMA units, round to nearest
|
|
141
|
+
|
|
142
|
+
rows, cols = acc_rows_cols(L, MB, NB)
|
|
143
|
+
rows = m0 + rows
|
|
144
|
+
cols = n0 + cols
|
|
145
|
+
if HAS_BIAS:
|
|
146
|
+
# any broadcast of the bias: a [N] row has (0, 0, 1) strides, a full [B, M, N] its own
|
|
147
|
+
bias = gl.load(Bias + z * stride_xz + rows.to(gl.int64) * stride_xm + cols.to(gl.int64) * stride_xn,
|
|
148
|
+
mask=(rows < M) & (cols < N), other=0.0)
|
|
149
|
+
if PROMOTE_BIAS:
|
|
150
|
+
bias = bias.to(gl.float32)
|
|
151
|
+
acc = alpha * acc + beta * bias
|
|
152
|
+
c = acc.to(C.dtype.element_ty)
|
|
153
|
+
if EPILOGUE != 0:
|
|
154
|
+
# Per-stage rounding emulation, as matmul_kernel: `c` carries the unfused GEMM's store rounding, the
|
|
155
|
+
# widening is the standalone epilogue kernel's load; the formulas are those of ops/silu.py / ops/gelu.py.
|
|
156
|
+
x = c.to(gl.float32)
|
|
157
|
+
if EPILOGUE == 1:
|
|
158
|
+
out = x * _sigmoid(x)
|
|
159
|
+
elif EPILOGUE == 2:
|
|
160
|
+
out = 0.5 * (1.0 + gl.erf(0.707106781 * x)) * x
|
|
161
|
+
else:
|
|
162
|
+
out = 0.5 * (1.0 + _tanh(0.7978845608 * x * (1.0 + 0.044715 * x * x))) * x
|
|
163
|
+
c = out.to(C.dtype.element_ty)
|
|
164
|
+
gl.store(C + rows.to(gl.int64) * stride_cm + cols.to(gl.int64) * stride_cn, c, mask=(rows < M) & (cols < N))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@gluon.jit
|
|
168
|
+
def _pow2(e):
|
|
169
|
+
"""2^e as fp32 for an integer exponent in [-126, 127], built from its bits (exact)."""
|
|
170
|
+
return ((e + 127) << 23).to(gl.float32, bitcast=True)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@gluon.jit
|
|
174
|
+
def split_exponent(m, HI_EXP: gl.constexpr):
|
|
175
|
+
"""The exponent s of the power-of-two scale 2^s that brings a largest magnitude m (fp32) into
|
|
176
|
+
[2^HI_EXP, 2^(HI_EXP+1)): 0 for m = 0, clamped to the fp32 normal range."""
|
|
177
|
+
e = ((m.to(gl.int32, bitcast=True) >> 23) & 0xFF) - 127
|
|
178
|
+
s = gl.where(m == 0.0, 0, HI_EXP - e)
|
|
179
|
+
return gl.minimum(gl.maximum(s, -126), 126)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@gluon.jit
|
|
183
|
+
def hi_lo(x, LO_SHIFT: gl.constexpr):
|
|
184
|
+
"""fp32 x (already scaled) as hi = fp16(x) and lo = fp16((x - hi) 2^LO_SHIFT); lo is 0 where hi holds x whole
|
|
185
|
+
(an infinity among them)."""
|
|
186
|
+
hi = x.to(gl.float16)
|
|
187
|
+
h32 = hi.to(gl.float32)
|
|
188
|
+
return hi, gl.where(h32 == x, 0.0, (x - h32) * (1 << LO_SHIFT)).to(gl.float16)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@gluon.jit
|
|
192
|
+
def _stage_split(s_hi, s_lo, tile, K_INNER: gl.constexpr, R: gl.constexpr, KK: gl.constexpr,
|
|
193
|
+
HI_EXP: gl.constexpr, LO_SHIFT: gl.constexpr):
|
|
194
|
+
"""Stage one operand tile as hi + lo of the unit's operand dtype, each row (k along the columns) or column (k
|
|
195
|
+
along the rows) under its own power-of-two scale 2^s; returns 2^-s per row / column [R]. A zero row keeps
|
|
196
|
+
s = 0; s is clamped to the fp32 normal range. A value hi already holds whole (an infinity among them) has lo 0."""
|
|
197
|
+
x = tile.to(gl.float32)
|
|
198
|
+
KA: gl.constexpr = 1 if K_INNER else 0
|
|
199
|
+
s = split_exponent(gl.max(gl.abs(x), axis=KA), HI_EXP)
|
|
200
|
+
hi, lo = hi_lo(x * gl.expand_dims(_pow2(s), KA), LO_SHIFT)
|
|
201
|
+
if K_INNER:
|
|
202
|
+
stage_k_inner(s_hi, hi, R, KK)
|
|
203
|
+
stage_k_inner(s_lo, lo, R, KK)
|
|
204
|
+
else:
|
|
205
|
+
stage_k_outer(s_hi, hi, KK, R)
|
|
206
|
+
stage_k_outer(s_lo, lo, KK, R)
|
|
207
|
+
return _pow2(-s)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@gluon.jit
|
|
211
|
+
def _bcast_rows(r, L: gl.constexpr):
|
|
212
|
+
"""A [MB, 2, 8] row statistic in any layout -> broadcastable over L."""
|
|
213
|
+
RL: gl.constexpr = gl.SliceLayout(3, gl.SliceLayout(4, gl.SliceLayout(5, gl.SliceLayout(6, L))))
|
|
214
|
+
return bcast_rows(gl.convert_layout(r, RL), L)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@gluon.jit
|
|
218
|
+
def _bcast_cols(c, L: gl.constexpr):
|
|
219
|
+
"""A [NB, 2, 2, 4] column statistic (column = 16 o3 + 8 o4 + 4 o5 + o6, acc_rows_cols) -> broadcastable over L."""
|
|
220
|
+
CL: gl.constexpr = gl.SliceLayout(0, gl.SliceLayout(1, gl.SliceLayout(2, L)))
|
|
221
|
+
c = gl.convert_layout(c, CL)
|
|
222
|
+
return gl.expand_dims(gl.expand_dims(gl.expand_dims(c, 0), 1), 2)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@functools.lru_cache(maxsize=None)
|
|
226
|
+
def mm_layouts(MB: int, NB: int, WM: int, WN: int, BK: int, b_k_contig: bool) -> dict:
|
|
227
|
+
"""Every layout the kernel takes for a tile of 16 MB x 16 NB x BK over WM x WN warps."""
|
|
228
|
+
nw = WM * WN
|
|
229
|
+
tk = min(BK // 8, 32)
|
|
230
|
+
g_rows = gl.BlockedLayout([1, 8], [32 // tk, tk], [nw, 1], [1, 0]) # [rows, k], 8 halves along k
|
|
231
|
+
return dict(
|
|
232
|
+
L=acc_layout(MB, NB, WM, WN), LA=frag_a_layout(MB, NB, WM, WN), LB=frag_b_layout(MB, NB, WM, WN),
|
|
233
|
+
GA=g_rows,
|
|
234
|
+
# [n, k] like A, or [k, n] with 4 consecutive k per thread (one STS.64 per transposing store)
|
|
235
|
+
GB=g_rows if b_k_contig else gl.BlockedLayout([4, 2], [1, 32], [nw, 1], [1, 0]))
|