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,384 @@
|
|
|
1
|
+
"""Flash-attention forward on the m8n8k4 matrix unit (sm_70 tensor cores) — our Gluon kernel.
|
|
2
|
+
|
|
3
|
+
The same contract as `flash_attention.py` (the wrapper's flash path): Q is scaled by `softmax_scale` and rounded to
|
|
4
|
+
its own dtype before QK^T, a memory-resident additive bias (the wrapper's "matrix" bias, stride 0 over the query
|
|
5
|
+
axis when there is no mask) is added to every score, softmax online, GQA by `GQA_GROUPS`, the log-sum-exp stored to
|
|
6
|
+
`Lse`, rows and keys of any length (tails masked), and a fully-masked row left to the wrapper's guard.
|
|
7
|
+
|
|
8
|
+
Built on `mma_m8n8k4`: each warp owns MB / WM bands of 16 query rows (the row statistics never cross a warp), S = Q K^T
|
|
9
|
+
and O += P V are HMMA, Q (once per program), K and V (fp16) are staged through shared memory as [k-step x rows, 4],
|
|
10
|
+
and P never leaves the registers: its layout carries the k-step index as a register dim, and each step's A fragment
|
|
11
|
+
is selected from it exactly. The zero bias of an unmasked call is not read (HAS_BIAS).
|
|
12
|
+
The head dim is cut into at most two power-of-two parts DA + DB (the wrapper rounds it up to such a sum, the
|
|
13
|
+
extra columns loaded as zeros), so 96 runs as 64 + 32 and never as 128.
|
|
14
|
+
|
|
15
|
+
SPLIT (the DtypeEngine's "split" representation, nbxtensor/dtype/operands.matrix_unit_flash_representation, the profile's
|
|
16
|
+
`matrix_unit.fp32_split.flash`): operands of any float dtype in memory are widened to fp32 and carried to the fp16 unit
|
|
17
|
+
as hi + lo, as ops/matmul_m8n8k4.py carries a GEMM operand (its `split_exponent` / `hi_lo`). Each operand takes a
|
|
18
|
+
power-of-two scale constant along the axis it is reduced over, so the scale factors out of the product: Q (after
|
|
19
|
+
`softmax_scale`) per query row and K per key, both over the whole head dim (DA + DB together); V per head-dim column
|
|
20
|
+
of each key tile; P (in [0, 1]) is split unscaled. S = (Qh Kh + (Ql Kh + Qh Kl) 2^-LO_SHIFT) 2^-sq 2^-sk and the
|
|
21
|
+
tile's P V = (Ph Vh + (Pl Vh + Ph Vl) 2^-LO_SHIFT) 2^-sv; the unit sums at most `block_k` head-dim columns of S
|
|
22
|
+
(BLOCK_N keys of P V) into a zeroed accumulator — it rounds toward zero (Fasi et al. 2021) — and every such partial
|
|
23
|
+
is added on the FMA units with round to nearest. lo*lo (< 2^-22 of a product) is not formed (Ootomo & Yokota 2022,
|
|
24
|
+
arXiv 2203.03341). The row statistics, the online softmax and the output are those of the native path, in fp32.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import functools
|
|
28
|
+
|
|
29
|
+
from nbxtensor.ops._configs import nbx_autotune
|
|
30
|
+
|
|
31
|
+
from triton.experimental import gluon
|
|
32
|
+
from triton.experimental.gluon import language as gl
|
|
33
|
+
|
|
34
|
+
from nbxtensor.ops.matmul_m8n8k4 import _bcast_cols, _bcast_rows, _pow2, hi_lo, split_exponent
|
|
35
|
+
from nbxtensor.ops.mma_m8n8k4 import (MMA_ASM, MMA_CONSTRAINTS, acc_rows_cols, bcast_rows, mma_k4, row_layout_rows, row_max,
|
|
36
|
+
row_sum, stage_k_inner, stage_k_outer)
|
|
37
|
+
|
|
38
|
+
LOG2E: gl.constexpr = gl.constexpr(1.4426950408889634)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def unit_flash_configs():
|
|
42
|
+
"""The tiles the certifier proves per key (workstream 1): 64 or 128 query rows (MB bands of 16), 32 or 64 keys
|
|
43
|
+
per step (NS), 4 or 8 warps — the six the profile's hand table was chosen from (2026-10-07, flash_tiles4) and the
|
|
44
|
+
pairs it did not try. MB and the warps decide which rows a program owns (bit-identical); NS sets the online
|
|
45
|
+
softmax's accumulation order (proven against the fp64 oracle's tolerance)."""
|
|
46
|
+
import triton
|
|
47
|
+
return [triton.Config({"MB": mb, "NS": ns}, num_warps=w, num_stages=1)
|
|
48
|
+
for mb in (4, 8) for ns in (2, 4) for w in (4, 8)]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _prune_unit_flash(configs, named_args, **kwargs):
|
|
52
|
+
"""Each warp owns MB / warps whole 16-row bands: a tile whose bands do not divide among its warps is not built; nor
|
|
53
|
+
one whose tiles staged by name (`staged_shared_bytes`) are over the device's shared memory per block — a tile
|
|
54
|
+
within that figure may still be refused by the compiler's scratch, which the launcher then refuses as Triton's
|
|
55
|
+
does (`OutOfResources`). None left is refused by name, never answered with the first config."""
|
|
56
|
+
from nbxtensor.backend.launcher import max_shared_memory_per_block
|
|
57
|
+
keep = [c for c in configs if c.kwargs["MB"] % c.num_warps == 0]
|
|
58
|
+
limit = max_shared_memory_per_block()
|
|
59
|
+
if limit is not None and "headdim" in named_args:
|
|
60
|
+
da, db = head_parts(int(named_args["headdim"]))
|
|
61
|
+
split = bool(named_args.get("SPLIT", False))
|
|
62
|
+
keep = [c for c in keep if staged_shared_bytes(16 * c.kwargs["MB"], 16 * c.kwargs["NS"], da, db, split) <= limit]
|
|
63
|
+
if not keep:
|
|
64
|
+
raise ValueError(f"matrix-unit flash: no tile of head dim {named_args.get('headdim')} fits "
|
|
65
|
+
f"{limit} bytes of shared memory per block")
|
|
66
|
+
return keep
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@nbx_autotune(configs=unit_flash_configs(), key=["Q_BUCKET", "K_BUCKET", "headdim", "SPLIT"],
|
|
70
|
+
prune_configs_by={"early_config_prune": _prune_unit_flash}, cache_results=True)
|
|
71
|
+
@gluon.jit
|
|
72
|
+
def flash_attention_m8n8k4_kernel(
|
|
73
|
+
Q, K, V, Bias, Out, Lse,
|
|
74
|
+
softmax_scale,
|
|
75
|
+
stride_qb, stride_qh, stride_qm,
|
|
76
|
+
stride_kb, stride_kh, stride_kn,
|
|
77
|
+
stride_vb, stride_vh, stride_vn,
|
|
78
|
+
stride_bb, stride_bh, stride_bm,
|
|
79
|
+
stride_ob, stride_oh, stride_om,
|
|
80
|
+
nheads, seqlen_q, seqlen_k, seqlen_q_rounded, headdim, Q_BUCKET, K_BUCKET,
|
|
81
|
+
MB: gl.constexpr, NS: gl.constexpr, DA: gl.constexpr, DB: gl.constexpr, GQA_GROUPS: gl.constexpr,
|
|
82
|
+
HAS_BIAS: gl.constexpr,
|
|
83
|
+
SPLIT: gl.constexpr, HI_EXP: gl.constexpr, LO_SHIFT: gl.constexpr, KSA: gl.constexpr, KSB: gl.constexpr,
|
|
84
|
+
):
|
|
85
|
+
# every layout from the tile's constexprs and the launch's warps, built in the kernel (`flash_layout`): the tile
|
|
86
|
+
# is then a plain (MB, NS, num_warps) the autotuner can sweep and the certified directory can hold (workstream 1)
|
|
87
|
+
WM: gl.constexpr = gl.num_warps()
|
|
88
|
+
LS: gl.constexpr = flash_layout("LS", MB, NS, DA, DB, WM)
|
|
89
|
+
LOA: gl.constexpr = flash_layout("LOA", MB, NS, DA, DB, WM)
|
|
90
|
+
LOB: gl.constexpr = flash_layout("LOB", MB, NS, DA, DB, WM)
|
|
91
|
+
LAS: gl.constexpr = flash_layout("LAS", MB, NS, DA, DB, WM)
|
|
92
|
+
LAOA: gl.constexpr = flash_layout("LAOA", MB, NS, DA, DB, WM)
|
|
93
|
+
LAOB: gl.constexpr = flash_layout("LAOB", MB, NS, DA, DB, WM)
|
|
94
|
+
LBK: gl.constexpr = flash_layout("LBK", MB, NS, DA, DB, WM)
|
|
95
|
+
LBVA: gl.constexpr = flash_layout("LBVA", MB, NS, DA, DB, WM)
|
|
96
|
+
LBVB: gl.constexpr = flash_layout("LBVB", MB, NS, DA, DB, WM)
|
|
97
|
+
GR: gl.constexpr = flash_layout("GR", MB, NS, DA, DB, WM)
|
|
98
|
+
GV: gl.constexpr = flash_layout("GV", MB, NS, DA, DB, WM)
|
|
99
|
+
LX: gl.constexpr = flash_layout("LX", MB, NS, DA, DB, WM)
|
|
100
|
+
BM: gl.constexpr = 16 * MB
|
|
101
|
+
BN: gl.constexpr = 16 * NS
|
|
102
|
+
SH: gl.constexpr = gl.SwizzledSharedLayout(1, 1, 1, order=[1, 0])
|
|
103
|
+
m0 = gl.program_id(0).to(gl.int64) * BM
|
|
104
|
+
off_hb = gl.program_id(1).to(gl.int64)
|
|
105
|
+
off_b = off_hb // nheads
|
|
106
|
+
off_h = off_hb % nheads
|
|
107
|
+
off_h_kv = off_h // GQA_GROUPS
|
|
108
|
+
q_base = Q + off_b * stride_qb + off_h * stride_qh
|
|
109
|
+
k_base = K + off_b * stride_kb + off_h_kv * stride_kh
|
|
110
|
+
v_base = V + off_b * stride_vb + off_h_kv * stride_vh
|
|
111
|
+
b_base = Bias + off_b * stride_bb + off_h * stride_bh
|
|
112
|
+
|
|
113
|
+
sq_a = gl.allocate_shared_memory(gl.float16, [(DA // 4) * BM, 4], SH)
|
|
114
|
+
sk_a = gl.allocate_shared_memory(gl.float16, [(DA // 4) * BN, 4], SH)
|
|
115
|
+
sv_a = gl.allocate_shared_memory(gl.float16, [(BN // 4) * DA, 4], SH)
|
|
116
|
+
|
|
117
|
+
# Global tiles: GR reads rows along the head dim (Q, K), GV gives each thread 4 consecutive keys of a column (V).
|
|
118
|
+
r_m = gl.arange(0, BM, layout=gl.SliceLayout(1, GR))[:, None]
|
|
119
|
+
r_n = gl.arange(0, BN, layout=gl.SliceLayout(1, GR))[:, None]
|
|
120
|
+
c_a = gl.arange(0, DA, layout=gl.SliceLayout(0, GR))[None, :]
|
|
121
|
+
v_n = gl.arange(0, BN, layout=gl.SliceLayout(1, GV))[:, None]
|
|
122
|
+
v_a = gl.arange(0, DA, layout=gl.SliceLayout(0, GV))[None, :]
|
|
123
|
+
q_rows = m0 + r_m
|
|
124
|
+
q_a = gl.load(q_base + q_rows * stride_qm + c_a, mask=(q_rows < seqlen_q) & (c_a < headdim), other=0.0)
|
|
125
|
+
if not SPLIT:
|
|
126
|
+
stage_k_inner(sq_a, (q_a * softmax_scale).to(gl.float16), BM, DA)
|
|
127
|
+
acc_a = gl.zeros([MB, 2, 8, DA // 16, 2, 2, 4], gl.float32, layout=LOA)
|
|
128
|
+
if DB > 0:
|
|
129
|
+
sq_b = gl.allocate_shared_memory(gl.float16, [(DB // 4) * BM, 4], SH)
|
|
130
|
+
sk_b = gl.allocate_shared_memory(gl.float16, [(DB // 4) * BN, 4], SH)
|
|
131
|
+
sv_b = gl.allocate_shared_memory(gl.float16, [(BN // 4) * DB, 4], SH)
|
|
132
|
+
c_b = DA + gl.arange(0, DB, layout=gl.SliceLayout(0, GR))[None, :]
|
|
133
|
+
v_b = DA + gl.arange(0, DB, layout=gl.SliceLayout(0, GV))[None, :]
|
|
134
|
+
q_b = gl.load(q_base + q_rows * stride_qm + c_b, mask=(q_rows < seqlen_q) & (c_b < headdim), other=0.0)
|
|
135
|
+
if not SPLIT:
|
|
136
|
+
stage_k_inner(sq_b, (q_b * softmax_scale).to(gl.float16), BM, DB)
|
|
137
|
+
acc_b = gl.zeros([MB, 2, 8, DB // 16, 2, 2, 4], gl.float32, layout=LOB)
|
|
138
|
+
if SPLIT:
|
|
139
|
+
# the lo halves; Q split once per program under one scale per query row over the whole head dim
|
|
140
|
+
sql_a = gl.allocate_shared_memory(gl.float16, [(DA // 4) * BM, 4], SH)
|
|
141
|
+
skl_a = gl.allocate_shared_memory(gl.float16, [(DA // 4) * BN, 4], SH)
|
|
142
|
+
svl_a = gl.allocate_shared_memory(gl.float16, [(BN // 4) * DA, 4], SH)
|
|
143
|
+
xq_a = q_a.to(gl.float32) * softmax_scale
|
|
144
|
+
mq = gl.max(gl.abs(xq_a), axis=1)
|
|
145
|
+
if DB > 0:
|
|
146
|
+
sql_b = gl.allocate_shared_memory(gl.float16, [(DB // 4) * BM, 4], SH)
|
|
147
|
+
skl_b = gl.allocate_shared_memory(gl.float16, [(DB // 4) * BN, 4], SH)
|
|
148
|
+
svl_b = gl.allocate_shared_memory(gl.float16, [(BN // 4) * DB, 4], SH)
|
|
149
|
+
xq_b = q_b.to(gl.float32) * softmax_scale
|
|
150
|
+
mq = gl.maximum(mq, gl.max(gl.abs(xq_b), axis=1))
|
|
151
|
+
eq = split_exponent(mq, HI_EXP)
|
|
152
|
+
hq, lq = hi_lo(xq_a * gl.expand_dims(_pow2(eq), 1), LO_SHIFT)
|
|
153
|
+
stage_k_inner(sq_a, hq, BM, DA)
|
|
154
|
+
stage_k_inner(sql_a, lq, BM, DA)
|
|
155
|
+
if DB > 0:
|
|
156
|
+
hq, lq = hi_lo(xq_b * gl.expand_dims(_pow2(eq), 1), LO_SHIFT)
|
|
157
|
+
stage_k_inner(sq_b, hq, BM, DB)
|
|
158
|
+
stage_k_inner(sql_b, lq, BM, DB)
|
|
159
|
+
|
|
160
|
+
s_rows, s_cols = acc_rows_cols(LS, MB, NS)
|
|
161
|
+
s_rows = m0 + s_rows
|
|
162
|
+
RS: gl.constexpr = gl.SliceLayout(3, gl.SliceLayout(4, gl.SliceLayout(5, gl.SliceLayout(6, LS))))
|
|
163
|
+
m_i = gl.full([MB, 2, 8], float("-inf"), gl.float32, layout=RS) # running max, log2 domain
|
|
164
|
+
l_i = gl.zeros([MB, 2, 8], gl.float32, layout=RS)
|
|
165
|
+
if SPLIT:
|
|
166
|
+
inv_q = _bcast_rows(gl.reshape(_pow2(-eq), [MB, 2, 8]), LS)
|
|
167
|
+
|
|
168
|
+
for n0 in range(0, seqlen_k, BN):
|
|
169
|
+
gl.barrier() # every warp is done reading the previous tile
|
|
170
|
+
k_rows = n0 + r_n
|
|
171
|
+
v_rows = n0 + v_n
|
|
172
|
+
if SPLIT:
|
|
173
|
+
# K under one scale per key over the whole head dim; V under one scale per column of this key tile
|
|
174
|
+
xk_a = gl.load(k_base + k_rows * stride_kn + c_a, mask=(k_rows < seqlen_k) & (c_a < headdim),
|
|
175
|
+
other=0.0).to(gl.float32)
|
|
176
|
+
xv_a = gl.load(v_base + v_rows * stride_vn + v_a, mask=(v_rows < seqlen_k) & (v_a < headdim),
|
|
177
|
+
other=0.0).to(gl.float32)
|
|
178
|
+
mk = gl.max(gl.abs(xk_a), axis=1)
|
|
179
|
+
if DB > 0:
|
|
180
|
+
xk_b = gl.load(k_base + k_rows * stride_kn + c_b, mask=(k_rows < seqlen_k) & (c_b < headdim),
|
|
181
|
+
other=0.0).to(gl.float32)
|
|
182
|
+
xv_b = gl.load(v_base + v_rows * stride_vn + v_b, mask=(v_rows < seqlen_k) & (v_b < headdim),
|
|
183
|
+
other=0.0).to(gl.float32)
|
|
184
|
+
mk = gl.maximum(mk, gl.max(gl.abs(xk_b), axis=1))
|
|
185
|
+
ek = split_exponent(mk, HI_EXP)
|
|
186
|
+
hk, lk = hi_lo(xk_a * gl.expand_dims(_pow2(ek), 1), LO_SHIFT)
|
|
187
|
+
stage_k_inner(sk_a, hk, BN, DA)
|
|
188
|
+
stage_k_inner(skl_a, lk, BN, DA)
|
|
189
|
+
ev_a = split_exponent(gl.max(gl.abs(xv_a), axis=0), HI_EXP)
|
|
190
|
+
hv, lv = hi_lo(xv_a * gl.expand_dims(_pow2(ev_a), 0), LO_SHIFT)
|
|
191
|
+
stage_k_outer(sv_a, hv, BN, DA)
|
|
192
|
+
stage_k_outer(svl_a, lv, BN, DA)
|
|
193
|
+
if DB > 0:
|
|
194
|
+
hk, lk = hi_lo(xk_b * gl.expand_dims(_pow2(ek), 1), LO_SHIFT)
|
|
195
|
+
stage_k_inner(sk_b, hk, BN, DB)
|
|
196
|
+
stage_k_inner(skl_b, lk, BN, DB)
|
|
197
|
+
ev_b = split_exponent(gl.max(gl.abs(xv_b), axis=0), HI_EXP)
|
|
198
|
+
hv, lv = hi_lo(xv_b * gl.expand_dims(_pow2(ev_b), 0), LO_SHIFT)
|
|
199
|
+
stage_k_outer(sv_b, hv, BN, DB)
|
|
200
|
+
stage_k_outer(svl_b, lv, BN, DB)
|
|
201
|
+
else:
|
|
202
|
+
stage_k_inner(sk_a, gl.load(k_base + k_rows * stride_kn + c_a,
|
|
203
|
+
mask=(k_rows < seqlen_k) & (c_a < headdim), other=0.0), BN, DA)
|
|
204
|
+
stage_k_outer(sv_a, gl.load(v_base + v_rows * stride_vn + v_a,
|
|
205
|
+
mask=(v_rows < seqlen_k) & (v_a < headdim), other=0.0), BN, DA)
|
|
206
|
+
if DB > 0:
|
|
207
|
+
stage_k_inner(sk_b, gl.load(k_base + k_rows * stride_kn + c_b,
|
|
208
|
+
mask=(k_rows < seqlen_k) & (c_b < headdim), other=0.0), BN, DB)
|
|
209
|
+
stage_k_outer(sv_b, gl.load(v_base + v_rows * stride_vn + v_b,
|
|
210
|
+
mask=(v_rows < seqlen_k) & (v_b < headdim), other=0.0), BN, DB)
|
|
211
|
+
gl.barrier()
|
|
212
|
+
s = gl.zeros([MB, 2, 8, NS, 2, 2, 4], gl.float32, layout=LS)
|
|
213
|
+
if SPLIT:
|
|
214
|
+
# KSA (KSB) k-steps of 4 head-dim columns per partial in a zeroed accumulator, added round-to-nearest
|
|
215
|
+
for c in gl.static_range(DA // (4 * KSA)):
|
|
216
|
+
t_hh = gl.zeros([MB, 2, 8, NS, 2, 2, 4], gl.float32, layout=LS)
|
|
217
|
+
t_lo = gl.zeros([MB, 2, 8, NS, 2, 2, 4], gl.float32, layout=LS)
|
|
218
|
+
for i in gl.static_range(KSA):
|
|
219
|
+
t_hh = mma_k4(sq_a, (c * KSA + i) * BM, sk_a, (c * KSA + i) * BN, t_hh, MB, NS, LAS, LBK, LS)
|
|
220
|
+
t_lo = mma_k4(sql_a, (c * KSA + i) * BM, sk_a, (c * KSA + i) * BN, t_lo, MB, NS, LAS, LBK, LS)
|
|
221
|
+
t_lo = mma_k4(sq_a, (c * KSA + i) * BM, skl_a, (c * KSA + i) * BN, t_lo, MB, NS, LAS, LBK, LS)
|
|
222
|
+
s = s + (t_hh + t_lo * (1.0 / (1 << LO_SHIFT)))
|
|
223
|
+
if DB > 0:
|
|
224
|
+
for c in gl.static_range(DB // (4 * KSB)):
|
|
225
|
+
t_hh = gl.zeros([MB, 2, 8, NS, 2, 2, 4], gl.float32, layout=LS)
|
|
226
|
+
t_lo = gl.zeros([MB, 2, 8, NS, 2, 2, 4], gl.float32, layout=LS)
|
|
227
|
+
for i in gl.static_range(KSB):
|
|
228
|
+
t_hh = mma_k4(sq_b, (c * KSB + i) * BM, sk_b, (c * KSB + i) * BN, t_hh, MB, NS, LAS, LBK, LS)
|
|
229
|
+
t_lo = mma_k4(sql_b, (c * KSB + i) * BM, sk_b, (c * KSB + i) * BN, t_lo, MB, NS, LAS, LBK,
|
|
230
|
+
LS)
|
|
231
|
+
t_lo = mma_k4(sq_b, (c * KSB + i) * BM, skl_b, (c * KSB + i) * BN, t_lo, MB, NS, LAS, LBK,
|
|
232
|
+
LS)
|
|
233
|
+
s = s + (t_hh + t_lo * (1.0 / (1 << LO_SHIFT)))
|
|
234
|
+
s = s * inv_q * _bcast_cols(gl.reshape(_pow2(-ek), [NS, 2, 2, 4]), LS)
|
|
235
|
+
else:
|
|
236
|
+
for j in gl.static_range(DA // 4):
|
|
237
|
+
s = mma_k4(sq_a, j * BM, sk_a, j * BN, s, MB, NS, LAS, LBK, LS)
|
|
238
|
+
if DB > 0:
|
|
239
|
+
for j in gl.static_range(DB // 4):
|
|
240
|
+
s = mma_k4(sq_b, j * BM, sk_b, j * BN, s, MB, NS, LAS, LBK, LS)
|
|
241
|
+
cols = n0 + s_cols
|
|
242
|
+
if HAS_BIAS:
|
|
243
|
+
bias = gl.load(b_base + s_rows * stride_bm + cols, mask=(s_rows < seqlen_q) & (cols < seqlen_k),
|
|
244
|
+
other=0.0).to(gl.float32)
|
|
245
|
+
s = s + bias
|
|
246
|
+
s = gl.where(cols < seqlen_k, s * LOG2E, float("-inf"))
|
|
247
|
+
m_new = gl.maximum(m_i, row_max(s))
|
|
248
|
+
p = gl.exp2(s - bcast_rows(m_new, LS))
|
|
249
|
+
alpha = gl.exp2(m_i - m_new)
|
|
250
|
+
l_i = l_i * alpha + row_sum(p)
|
|
251
|
+
m_i = m_new
|
|
252
|
+
# P never leaves the registers: (mb, mi3, mi, nb, ni3, ni2, k) -> (mb, mi3, mi, k, nb, ni3, ni2) in LX, whose
|
|
253
|
+
# k-step index j = 4 nb + 2 ni3 + ni2 is a register dim; one A fragment per j by select + sum (exact).
|
|
254
|
+
jj = gl.arange(0, BN // 4, layout=gl.SliceLayout(0, gl.SliceLayout(1, gl.SliceLayout(2, gl.SliceLayout(3, LX)))))
|
|
255
|
+
jj = jj[None, None, None, None, :]
|
|
256
|
+
# P V of this key tile in a zeroed accumulator (the unit rounds toward zero, Fasi et al. 2021); the tiles
|
|
257
|
+
# are summed on the FMA units with round to nearest, under the running rescale.
|
|
258
|
+
if SPLIT:
|
|
259
|
+
ph, pl = hi_lo(p, LO_SHIFT) # p in [0, 1] and l >= 1: no scale
|
|
260
|
+
pxh = gl.convert_layout(gl.reshape(gl.permute(ph, [0, 1, 2, 6, 3, 4, 5]), [MB, 2, 8, 4, BN // 4]), LX)
|
|
261
|
+
pxl = gl.convert_layout(gl.reshape(gl.permute(pl, [0, 1, 2, 6, 3, 4, 5]), [MB, 2, 8, 4, BN // 4]), LX)
|
|
262
|
+
t_a = gl.zeros([MB, 2, 8, DA // 16, 2, 2, 4], gl.float32, layout=LOA)
|
|
263
|
+
u_a = gl.zeros([MB, 2, 8, DA // 16, 2, 2, 4], gl.float32, layout=LOA)
|
|
264
|
+
if DB > 0:
|
|
265
|
+
t_b = gl.zeros([MB, 2, 8, DB // 16, 2, 2, 4], gl.float32, layout=LOB)
|
|
266
|
+
u_b = gl.zeros([MB, 2, 8, DB // 16, 2, 2, 4], gl.float32, layout=LOB)
|
|
267
|
+
for j in gl.static_range(BN // 4):
|
|
268
|
+
fh = gl.sum(gl.where(jj == j, pxh, 0.0), axis=4).to(gl.float16)
|
|
269
|
+
fl = gl.sum(gl.where(jj == j, pxl, 0.0), axis=4).to(gl.float16)
|
|
270
|
+
t_a = _mma_frag(fh, sv_a, j * DA, t_a, MB, DA // 16, LAOA, LBVA, LOA)
|
|
271
|
+
u_a = _mma_frag(fh, svl_a, j * DA, u_a, MB, DA // 16, LAOA, LBVA, LOA)
|
|
272
|
+
u_a = _mma_frag(fl, sv_a, j * DA, u_a, MB, DA // 16, LAOA, LBVA, LOA)
|
|
273
|
+
if DB > 0:
|
|
274
|
+
t_b = _mma_frag(fh, sv_b, j * DB, t_b, MB, DB // 16, LAOB, LBVB, LOB)
|
|
275
|
+
u_b = _mma_frag(fh, svl_b, j * DB, u_b, MB, DB // 16, LAOB, LBVB, LOB)
|
|
276
|
+
u_b = _mma_frag(fl, sv_b, j * DB, u_b, MB, DB // 16, LAOB, LBVB, LOB)
|
|
277
|
+
t_a = (t_a + u_a * (1.0 / (1 << LO_SHIFT))) * _bcast_cols(gl.reshape(_pow2(-ev_a), [DA // 16, 2, 2, 4]), LOA)
|
|
278
|
+
if DB > 0:
|
|
279
|
+
t_b = (t_b + u_b * (1.0 / (1 << LO_SHIFT))) * _bcast_cols(
|
|
280
|
+
gl.reshape(_pow2(-ev_b), [DB // 16, 2, 2, 4]), LOB)
|
|
281
|
+
else:
|
|
282
|
+
px = gl.convert_layout(gl.reshape(gl.permute(p.to(gl.float16), [0, 1, 2, 6, 3, 4, 5]),
|
|
283
|
+
[MB, 2, 8, 4, BN // 4]), LX)
|
|
284
|
+
t_a = gl.zeros([MB, 2, 8, DA // 16, 2, 2, 4], gl.float32, layout=LOA)
|
|
285
|
+
if DB > 0:
|
|
286
|
+
t_b = gl.zeros([MB, 2, 8, DB // 16, 2, 2, 4], gl.float32, layout=LOB)
|
|
287
|
+
for j in gl.static_range(BN // 4):
|
|
288
|
+
f = gl.sum(gl.where(jj == j, px, 0.0), axis=4).to(gl.float16)
|
|
289
|
+
t_a = _mma_frag(f, sv_a, j * DA, t_a, MB, DA // 16, LAOA, LBVA, LOA)
|
|
290
|
+
if DB > 0:
|
|
291
|
+
t_b = _mma_frag(f, sv_b, j * DB, t_b, MB, DB // 16, LAOB, LBVB, LOB)
|
|
292
|
+
acc_a = acc_a * bcast_rows(alpha, LOA) + t_a
|
|
293
|
+
if DB > 0:
|
|
294
|
+
acc_b = acc_b * bcast_rows(alpha, LOB) + t_b
|
|
295
|
+
|
|
296
|
+
lse_rows = m0 + row_layout_rows(LS, MB)
|
|
297
|
+
gl.store(Lse + off_hb * seqlen_q_rounded + lse_rows, (m_i + gl.log2(l_i)) / LOG2E,
|
|
298
|
+
mask=lse_rows < seqlen_q_rounded)
|
|
299
|
+
o_base = Out + off_b * stride_ob + off_h * stride_oh
|
|
300
|
+
o_rows, o_cols = acc_rows_cols(LOA, MB, DA // 16)
|
|
301
|
+
o_rows = m0 + o_rows
|
|
302
|
+
gl.store(o_base + o_rows * stride_om + o_cols, (acc_a / bcast_rows(l_i, LOA)).to(Out.dtype.element_ty),
|
|
303
|
+
mask=(o_rows < seqlen_q) & (o_cols < headdim))
|
|
304
|
+
if DB > 0:
|
|
305
|
+
o_rows_b, o_cols_b = acc_rows_cols(LOB, MB, DB // 16)
|
|
306
|
+
o_rows_b = m0 + o_rows_b
|
|
307
|
+
o_cols_b = DA + o_cols_b
|
|
308
|
+
gl.store(o_base + o_rows_b * stride_om + o_cols_b, (acc_b / bcast_rows(l_i, LOB)).to(Out.dtype.element_ty),
|
|
309
|
+
mask=(o_rows_b < seqlen_q) & (o_cols_b < headdim))
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@gluon.jit
|
|
313
|
+
def _mma_frag(fa, sb, b_row0, acc, MB: gl.constexpr, NB: gl.constexpr, LA: gl.constexpr, LB: gl.constexpr,
|
|
314
|
+
L: gl.constexpr):
|
|
315
|
+
"""mma_k4 with the A fragment already in registers ([MB, 2, 8, 4] = (mb, mi3, mi, k))."""
|
|
316
|
+
fa = gl.convert_layout(gl.reshape(fa, [16 * MB, 4]), LA)
|
|
317
|
+
fa = gl.reshape(fa, [MB, 2, 2, 2, 2, 2, 2])
|
|
318
|
+
fa = gl.convert_layout(gl.reshape(gl.permute(fa, [0, 1, 2, 5, 4, 3, 6]), [MB, 2, 8, 1, 1, 1, 4]), L,
|
|
319
|
+
assert_trivial=True)
|
|
320
|
+
fb = sb.slice(b_row0, 16 * NB).load(LB)
|
|
321
|
+
fb = gl.reshape(fb, [NB, 2, 2, 2, 2, 2, 2])
|
|
322
|
+
fb = gl.convert_layout(gl.reshape(gl.permute(fb, [2, 5, 4, 0, 1, 3, 6]), [1, 1, 8, NB, 2, 1, 4]), L,
|
|
323
|
+
assert_trivial=True)
|
|
324
|
+
return gl.inline_asm_elementwise(MMA_ASM, MMA_CONSTRAINTS, [fa, fb, acc], dtype=gl.float32, is_pure=True,
|
|
325
|
+
pack=8)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def p_layout(MB: int, NS: int, NBO: int, WM: int):
|
|
329
|
+
"""P as [MB, 2, 8, 4, BN/4] = (mb, mi3, mi, k, j): the k-step index j in registers, then exactly
|
|
330
|
+
frag_a_layout(MB, NBO, WM, 1)'s bases."""
|
|
331
|
+
from nbxtensor.ops.mma_m8n8k4 import _bits
|
|
332
|
+
def e(d, x):
|
|
333
|
+
v = [0] * 5
|
|
334
|
+
v[d] = x
|
|
335
|
+
return v
|
|
336
|
+
z = [0] * 5
|
|
337
|
+
return gl.DistributedLinearLayout(
|
|
338
|
+
reg_bases=[e(4, x) for x in _bits(4 * NS)] + [e(3, 1), e(3, 2), z]
|
|
339
|
+
+ [z] * len(_bits(NBO)) + [e(0, x * WM) for x in _bits(MB // WM)],
|
|
340
|
+
lane_bases=[e(2, 1), e(2, 2), e(1, 1), z, e(2, 4)],
|
|
341
|
+
warp_bases=[e(0, x) for x in _bits(WM)], block_bases=[], shape=[MB, 2, 8, 4, 4 * NS])
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def staged_shared_bytes(BM: int, BN: int, DA: int, DB: int, split: bool) -> int:
|
|
345
|
+
"""The shared memory the kernel above allocates by name for one program: the Q, K and V tiles of each head part
|
|
346
|
+
(`sq_*`, `sk_*`, `sv_*`, fp16), doubled by the lo halves under SPLIT. The compiler's own scratch (layout
|
|
347
|
+
conversions) comes on top, so a tile within this figure may still be refused by the compiler; a tile beyond it
|
|
348
|
+
never compiles. `_configs.matrix_unit` prices every profile row with it against the profile's budget."""
|
|
349
|
+
per_part = 2 * (BM + 2 * BN) # fp16: Q [D, BM], K [D, BN], V [BN, D]
|
|
350
|
+
return (DA + DB) * per_part * (2 if split else 1)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def head_parts(headdim: int):
|
|
354
|
+
"""The head dim as DA + DB, each a power of two >= 16 (DB may be 0): the smallest such sum >= headdim."""
|
|
355
|
+
d = -(-headdim // 16) * 16
|
|
356
|
+
while bin(d // 16).count("1") > 2:
|
|
357
|
+
d += 16
|
|
358
|
+
da = 1 << (d.bit_length() - 1)
|
|
359
|
+
return da, d - da
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@functools.lru_cache(maxsize=None)
|
|
363
|
+
def flash_layouts(MB: int, NS: int, DA: int, DB: int, WM: int) -> dict:
|
|
364
|
+
"""Every layout the kernel takes, for MB 16-row bands over WM warps (WN = 1)."""
|
|
365
|
+
from nbxtensor.ops.mma_m8n8k4 import acc_layout, frag_a_layout, frag_b_layout
|
|
366
|
+
na, nb = DA // 16, max(DB // 16, 1)
|
|
367
|
+
return dict(
|
|
368
|
+
LS=acc_layout(MB, NS, WM, 1), LOA=acc_layout(MB, na, WM, 1), LOB=acc_layout(MB, nb, WM, 1),
|
|
369
|
+
LAS=frag_a_layout(MB, NS, WM, 1), LAOA=frag_a_layout(MB, na, WM, 1), LAOB=frag_a_layout(MB, nb, WM, 1),
|
|
370
|
+
LBK=frag_b_layout(MB, NS, WM, 1), LBVA=frag_b_layout(MB, na, WM, 1), LBVB=frag_b_layout(MB, nb, WM, 1),
|
|
371
|
+
GR=gl.BlockedLayout([1, 8], [8, 4], [WM, 1], [1, 0]),
|
|
372
|
+
GV=gl.BlockedLayout([4, 2], [1, 32], [WM, 1], [1, 0]), LX=p_layout(MB, NS, na, WM))
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
@gluon.constexpr_function
|
|
376
|
+
def flash_layout(name, MB, NS, DA, DB, WM):
|
|
377
|
+
"""One of the kernel's layouts (`flash_layouts`), evaluated at compile time from its constexprs."""
|
|
378
|
+
# imported here, a local name: the JIT's dependency scan refuses a cached (functools) callable among a constexpr
|
|
379
|
+
# function's globals ("Unsupported function referenced")
|
|
380
|
+
from nbxtensor.ops.flash_attention_m8n8k4 import flash_layouts as layouts
|
|
381
|
+
|
|
382
|
+
def v(x):
|
|
383
|
+
return getattr(x, "value", x)
|
|
384
|
+
return layouts(int(v(MB)), int(v(NS)), int(v(DA)), int(v(DB)), int(v(WM)))[str(v(name))]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""The fixed-order reduce of split-KV decode attention (T_q == 1), pure @triton.jit.
|
|
2
|
+
|
|
3
|
+
Decode attention splits the KV sequence across programs so the SMs fill even at batch 1; each split writes a
|
|
4
|
+
partial output with its running max and sum of exponentials (ONLINE SOFTMAX), and this kernel combines the splits.
|
|
5
|
+
Its users are the vector decode kernels (`ops/decode_attn_vec.py`, `ops/paged_decode_attn.py`).
|
|
6
|
+
|
|
7
|
+
DETERMINISM IS A DESIGN REQUIREMENT. The replay engine verifies frozen plans byte-equal before adopting them, so a
|
|
8
|
+
kernel that varies run to run is unusable there: each split writes its own partial slice (no atomics) and the
|
|
9
|
+
reduction iterates the splits in a FIXED ascending order in one program per (batch, kv-head), so the floating-point
|
|
10
|
+
combination order is a constant of the shapes.
|
|
11
|
+
|
|
12
|
+
The tensor-core split kernel that once shared this file (`flash_decode_split_kernel`) was served only behind an
|
|
13
|
+
environment switch and measured slower than the vector kernels; it was removed with its route (2026-11-03).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import triton
|
|
17
|
+
import triton.language as tl
|
|
18
|
+
|
|
19
|
+
from nbxtensor.ops._configs import nbx_autotune
|
|
20
|
+
|
|
21
|
+
# The warps of the reduce, candidates the certification measures per key (the launch fixed 4 before 2026-11-04); one
|
|
22
|
+
# stage — the loop reads each split's partials once.
|
|
23
|
+
_REDUCE_CONFIGS = [triton.Config({}, num_warps=w, num_stages=1) for w in (1, 2, 4, 8)]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@nbx_autotune(configs=_REDUCE_CONFIGS, key=['SPLIT', 'GROUPS', 'D'], cache_results=True)
|
|
27
|
+
@triton.jit
|
|
28
|
+
def flash_decode_reduce_kernel(
|
|
29
|
+
opart_ptr, mpart_ptr, lpart_ptr,
|
|
30
|
+
out_ptr, # [B*H_kv, GROUPS, D] output dtype
|
|
31
|
+
stride_oh, stride_os, stride_og,
|
|
32
|
+
stride_mh, stride_ms,
|
|
33
|
+
stride_outh, stride_outg,
|
|
34
|
+
SPLIT: tl.constexpr,
|
|
35
|
+
GROUPS: tl.constexpr,
|
|
36
|
+
BLOCK_G: tl.constexpr,
|
|
37
|
+
BLOCK_D: tl.constexpr,
|
|
38
|
+
D: tl.constexpr,
|
|
39
|
+
):
|
|
40
|
+
"""Combine the split partials via log-sum-exp correction.
|
|
41
|
+
|
|
42
|
+
One program per (batch, kv-head); splits iterated in FIXED ascending
|
|
43
|
+
order — the combination order is a constant, hence deterministic.
|
|
44
|
+
"""
|
|
45
|
+
pid_h = tl.program_id(0).to(tl.int64)
|
|
46
|
+
offs_g = tl.arange(0, BLOCK_G)
|
|
47
|
+
offs_d = tl.arange(0, BLOCK_D)
|
|
48
|
+
mask_g = offs_g < GROUPS
|
|
49
|
+
mask = mask_g[:, None] & (offs_d[None, :] < D)
|
|
50
|
+
|
|
51
|
+
m_i = tl.full([BLOCK_G], float("-inf"), dtype=tl.float32)
|
|
52
|
+
l_i = tl.zeros([BLOCK_G], dtype=tl.float32)
|
|
53
|
+
acc = tl.zeros([BLOCK_G, BLOCK_D], dtype=tl.float32)
|
|
54
|
+
|
|
55
|
+
for s in range(SPLIT):
|
|
56
|
+
m_s = tl.load(mpart_ptr + pid_h * stride_mh + s * stride_ms + offs_g,
|
|
57
|
+
mask=mask_g, other=float("-inf"))
|
|
58
|
+
l_s = tl.load(lpart_ptr + pid_h * stride_mh + s * stride_ms + offs_g,
|
|
59
|
+
mask=mask_g, other=0.0)
|
|
60
|
+
o_s = tl.load(opart_ptr + pid_h * stride_oh + s * stride_os
|
|
61
|
+
+ offs_g[:, None] * stride_og + offs_d[None, :],
|
|
62
|
+
mask=mask, other=0.0)
|
|
63
|
+
m_new = tl.maximum(m_i, m_s)
|
|
64
|
+
m_safe = tl.where(m_new == float("-inf"), 0.0, m_new)
|
|
65
|
+
c_old = tl.exp(tl.where(m_i == float("-inf"), float("-inf"),
|
|
66
|
+
m_i - m_safe))
|
|
67
|
+
c_new = tl.exp(tl.where(m_s == float("-inf"), float("-inf"),
|
|
68
|
+
m_s - m_safe))
|
|
69
|
+
acc = acc * c_old[:, None] + o_s * c_new[:, None]
|
|
70
|
+
l_i = l_i * c_old + l_s * c_new
|
|
71
|
+
m_i = m_new
|
|
72
|
+
|
|
73
|
+
# A row whose every key was masked has l == 0: emit 0, matching the
|
|
74
|
+
# fully-masked-row guard of both existing SDPA paths (nan_to_num).
|
|
75
|
+
l_safe = tl.where(l_i == 0.0, 1.0, l_i)
|
|
76
|
+
out = acc / l_safe[:, None]
|
|
77
|
+
tl.store(out_ptr + pid_h * stride_outh + offs_g[:, None] * stride_outg
|
|
78
|
+
+ offs_d[None, :], out, mask=mask)
|
nbxtensor/ops/flip_op.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Flip — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Reverses elements along one dimension. The wrapper computes the
|
|
4
|
+
flipped strides and offset; this kernel just copies the data.
|
|
5
|
+
|
|
6
|
+
Ported from FlagGems flip (Apache-2.0 license).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import triton
|
|
10
|
+
import triton.language as tl
|
|
11
|
+
|
|
12
|
+
@triton.jit
|
|
13
|
+
def flip_1d_kernel(
|
|
14
|
+
src_ptr, dst_ptr,
|
|
15
|
+
n_elements,
|
|
16
|
+
BLOCK_SIZE: tl.constexpr,
|
|
17
|
+
):
|
|
18
|
+
"""Reverse a 1D contiguous tensor: dst[i] = src[n_elements - 1 - i]."""
|
|
19
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
20
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
21
|
+
mask = offset < n_elements
|
|
22
|
+
|
|
23
|
+
src_idx = n_elements - 1 - offset
|
|
24
|
+
data = tl.load(src_ptr + src_idx, mask=mask)
|
|
25
|
+
tl.store(dst_ptr + offset, data, mask=mask)
|
|
26
|
+
|
|
27
|
+
@triton.jit
|
|
28
|
+
def flip_strided_kernel(
|
|
29
|
+
src_ptr, dst_ptr,
|
|
30
|
+
n_elements,
|
|
31
|
+
# Dimension being flipped
|
|
32
|
+
dim_size,
|
|
33
|
+
stride_dim,
|
|
34
|
+
stride_post,
|
|
35
|
+
BLOCK_SIZE: tl.constexpr,
|
|
36
|
+
):
|
|
37
|
+
"""Flip along a single dimension of an N-D contiguous tensor.
|
|
38
|
+
|
|
39
|
+
For each element, decomposes the flat index into (pre, dim, post)
|
|
40
|
+
coordinates and reverses the dim coordinate.
|
|
41
|
+
|
|
42
|
+
src and dst are both contiguous with the same layout; only the
|
|
43
|
+
dim-axis index is mirrored.
|
|
44
|
+
"""
|
|
45
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
46
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
47
|
+
mask = offset < n_elements
|
|
48
|
+
|
|
49
|
+
# Decompose flat index
|
|
50
|
+
post_size = stride_dim # product of all dims after the flip dim
|
|
51
|
+
pre_idx = offset // (dim_size * post_size)
|
|
52
|
+
dim_idx = (offset // post_size) % dim_size
|
|
53
|
+
post_idx = offset % post_size
|
|
54
|
+
|
|
55
|
+
# Flip the dim coordinate
|
|
56
|
+
flipped_dim_idx = dim_size - 1 - dim_idx
|
|
57
|
+
|
|
58
|
+
src_flat = pre_idx * (dim_size * post_size) + flipped_dim_idx * post_size + post_idx
|
|
59
|
+
data = tl.load(src_ptr + src_flat, mask=mask)
|
|
60
|
+
tl.store(dst_ptr + offset, data, mask=mask)
|
nbxtensor/ops/floor.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Floor — pure @triton.jit kernel."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@triton.jit
|
|
8
|
+
def _round_half_even(x):
|
|
9
|
+
"""Round to nearest, ties to even — what torch.round and libm nearbyint do.
|
|
10
|
+
|
|
11
|
+
Built from `tl.floor` because Triton 3.8.0 exposes no portable `round`,
|
|
12
|
+
`rint` or `nearbyint`; the only implementation available was
|
|
13
|
+
`tl.extra.CUDA.libdevice.nearbyint`, which is NVIDIA's device library and
|
|
14
|
+
fails to lower on every other backend ("PassManager::run failed" on
|
|
15
|
+
triton-ext, measured 2026-09-17).
|
|
16
|
+
"""
|
|
17
|
+
r = tl.floor(x + 0.5)
|
|
18
|
+
tie = (r - x) == 0.5
|
|
19
|
+
odd = (r - tl.floor(r * 0.5) * 2.0) != 0.0
|
|
20
|
+
out = tl.where(tie & odd, r - 1.0, r)
|
|
21
|
+
# A zero result keeps the SIGN of its input: round(-0.5) is -0.0, and so are
|
|
22
|
+
# round(-0.3) and round(-0.0). `floor(x + 0.5)` cannot produce -0.0 at all —
|
|
23
|
+
# it maps every x in (-0.5, 0] to +0.0 — so the sign is restored here. The
|
|
24
|
+
# Dell measured this on CUDA against an fp64 oracle: round(-0.5) gave +0.0
|
|
25
|
+
# where main and the oracle give -0.0. The ties were already right; only the
|
|
26
|
+
# sign was lost, and a sign of zero is not cosmetic — it is what tells a
|
|
27
|
+
# downstream divide which infinity to produce.
|
|
28
|
+
#
|
|
29
|
+
# Read from the sign bit rather than from `x < 0`, which is false for -0.0:
|
|
30
|
+
# a bitcast to int32 is negative exactly when the sign bit is set.
|
|
31
|
+
# Restored through the INTEGER view, not a floating select: on CUDA the
|
|
32
|
+
# Dell measured `tl.where(zero & neg, -0.0, out)` returning +0.0 for -0.5,
|
|
33
|
+
# -0.0 and -1e-7 (2026-09-20, against main and the fp64 oracle). Triton
|
|
34
|
+
# compiles floating arithmetic with no-signed-zeros permitted, so a -0.0
|
|
35
|
+
# constant selected into a zero is a value the compiler may write as +0.0.
|
|
36
|
+
# The sign bit copied as a bit is not arithmetic and is never folded.
|
|
37
|
+
xb = x.to(tl.int32, bitcast=True)
|
|
38
|
+
sign_bits = (xb >> 31) << 31 # 0x80000000 when x is negative, else 0
|
|
39
|
+
ob = out.to(tl.int32, bitcast=True)
|
|
40
|
+
ob = tl.where(out == 0.0, ob | sign_bits, ob)
|
|
41
|
+
return ob.to(tl.float32, bitcast=True)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Triton's floor / ceil / trunc take fp32 or fp64 only ("Expected dtype
|
|
45
|
+
# ['fp32', 'fp64'] but got fp16" — parakeet --triton, 2026-09-05). A half
|
|
46
|
+
# input is rounded in fp32 — exact: every half value is an fp32 value and
|
|
47
|
+
# the integer result is representable — and stored back in its own dtype;
|
|
48
|
+
# fp32 inputs take the same path unchanged.
|
|
49
|
+
@triton.jit
|
|
50
|
+
def floor_forward_kernel(input_ptr, output_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
|
|
51
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
52
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
53
|
+
mask = offset < n_elements
|
|
54
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
55
|
+
tl.store(output_ptr + offset, tl.math.floor(x.to(tl.float32)).to(x.dtype), mask=mask)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@triton.jit
|
|
59
|
+
def ceil_forward_kernel(input_ptr, output_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
|
|
60
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
61
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
62
|
+
mask = offset < n_elements
|
|
63
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
64
|
+
tl.store(output_ptr + offset, tl.math.ceil(x.to(tl.float32)).to(x.dtype), mask=mask)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@triton.jit
|
|
68
|
+
def round_forward_kernel(input_ptr, output_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
|
|
69
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
70
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
71
|
+
mask = offset < n_elements
|
|
72
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
73
|
+
# Round-half-to-even, matching torch.round. `libdevice.nearbyint` stood here
|
|
74
|
+
# and is NVIDIA-only: Kokoro-82M died at aten.round::0 (1, 55) with
|
|
75
|
+
# "PassManager::run failed" on triton-ext. A half input is rounded in fp32
|
|
76
|
+
# (exact) and stored back in its dtype (Kokoro --triton, 2026-09-05).
|
|
77
|
+
tl.store(output_ptr + offset,
|
|
78
|
+
_round_half_even(x.to(tl.float32)).to(x.dtype), mask=mask)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@triton.jit
|
|
82
|
+
def trunc_forward_kernel(input_ptr, output_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
|
|
83
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
84
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
85
|
+
mask = offset < n_elements
|
|
86
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
87
|
+
# No trunc in Triton's math: toward zero = floor of the positives, ceil of
|
|
88
|
+
# the negatives (a kernel that could never compile before the bank reached it).
|
|
89
|
+
x32 = x.to(tl.float32)
|
|
90
|
+
tl.store(output_ptr + offset, tl.where(x32 >= 0, tl.math.floor(x32), tl.math.ceil(x32)).to(x.dtype), mask=mask)
|
nbxtensor/ops/full_op.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Full (fill) — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Creates a tensor filled with a scalar value.
|
|
4
|
+
Simple element-wise write: tl.store(out + offset, value, mask=mask).
|
|
5
|
+
|
|
6
|
+
Grid: (ceil(n_elements / BLOCK_SIZE),)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import triton
|
|
10
|
+
import triton.language as tl
|
|
11
|
+
|
|
12
|
+
@triton.jit
|
|
13
|
+
def full_kernel(
|
|
14
|
+
output_ptr,
|
|
15
|
+
fill_value,
|
|
16
|
+
n_elements,
|
|
17
|
+
BLOCK_SIZE: tl.constexpr,
|
|
18
|
+
):
|
|
19
|
+
"""Fill output tensor with a constant scalar value."""
|
|
20
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
21
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
22
|
+
mask = offset < n_elements
|
|
23
|
+
|
|
24
|
+
tl.store(output_ptr + offset, fill_value, mask=mask)
|