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,162 @@
|
|
|
1
|
+
"""Vector (SIMT) decode attention — Tq == 1, pure @triton.jit, no tl.dot.
|
|
2
|
+
|
|
3
|
+
Second attempt at the decode-attention bar (~0.5 ms/token at 4k = the
|
|
4
|
+
KV bytes at HBM speed). The first attempt (ops/flash_decode.py) kept
|
|
5
|
+
the flash-decoding two-pass SPLIT structure but computed QK/PV with
|
|
6
|
+
`tl.dot` on a 16-row Q tile carrying 4 real GQA rows — and lost on the
|
|
7
|
+
row three times. The 2026-08-23 web research (R16) showed the split
|
|
8
|
+
was never the problem and the dot was: every production Tq=1 kernel on
|
|
9
|
+
pre-Ampere silicon is a SIMT kernel — FasterTransformer's
|
|
10
|
+
masked_multihead_attention (per-thread fp16 FMA + shuffle reduce, no
|
|
11
|
+
MMA anywhere), vLLM's paged_attention v1/v2 (16-byte vectorized K
|
|
12
|
+
loads, fixed-order v2 partition reduce), llama.cpp's fattn-vec/tile
|
|
13
|
+
(picked over MMA on Volta at exactly our effective batch), lightllm's
|
|
14
|
+
token_attention (`tl.sum(q[None, :] * k, 1)`, BLOCK_N=32, no dot).
|
|
15
|
+
Triton lowers fp16 `tl.dot` to FFMA on sm_70 (no HMMA lowering — the
|
|
16
|
+
Phase 1.5 measurement), so the MMA tile staging is pure cost there.
|
|
17
|
+
|
|
18
|
+
Structure here (the lightllm/vLLM shape):
|
|
19
|
+
- grid = (B * H_q, split): ONE QUERY ROW per program — no G tile at
|
|
20
|
+
all. The GQA K re-read (4 Q heads share a KV head) is absorbed by
|
|
21
|
+
L2 temporal locality, exactly as in vLLM/FT which organise the
|
|
22
|
+
same way.
|
|
23
|
+
- QK: `tl.sum(q[None, :] * k_tile, 1)` — an FMA reduction over D,
|
|
24
|
+
product tile (BLOCK_N, BLOCK_D) only.
|
|
25
|
+
- online softmax with SCALAR m/l state, fp32 accumulator [BLOCK_D].
|
|
26
|
+
- split-KV partials (out, m, l) merged by the EXISTING fixed-order
|
|
27
|
+
`flash_decode_reduce_kernel` (ops/flash_decode.py) viewed at
|
|
28
|
+
GROUPS=1 — deterministic by construction, no atomics, proven.
|
|
29
|
+
- K/V are read by their OWN strides: the bucketed KV cache's
|
|
30
|
+
prefix-slice views flow in directly (the strided-KV lot's
|
|
31
|
+
contract), no materialisation.
|
|
32
|
+
- num_stages=1 (sm_70 has no async-copy engine to pipeline with).
|
|
33
|
+
|
|
34
|
+
Born with its float64 oracle at D = 127, 128 AND 129
|
|
35
|
+
(test_decode_vec_oracle.py) and a route ACTIVATION proof — the
|
|
36
|
+
2026-08-23 lesson: a route-dependent claim needs the route pinned.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
import triton
|
|
40
|
+
import triton.language as tl
|
|
41
|
+
|
|
42
|
+
from nbxtensor.ops._configs import nbx_autotune
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@triton.jit
|
|
46
|
+
def _round_q(q, Q_TO: tl.constexpr, Q_SATURATE: tl.constexpr):
|
|
47
|
+
"""Q as the caller asked it read: rounded to Q_TO (the KV cache's
|
|
48
|
+
dtype) with the stored cast's protected fp16 conversion when
|
|
49
|
+
Q_SATURATE, then widened to the fp32 the dot runs in — the bytes of a
|
|
50
|
+
cast copy of Q, without the copy."""
|
|
51
|
+
if Q_TO is not None:
|
|
52
|
+
f = q.to(tl.float32)
|
|
53
|
+
if Q_SATURATE:
|
|
54
|
+
is_finite = (f == f) & (f != float("inf")) & (f != float("-inf"))
|
|
55
|
+
clamped = tl.minimum(tl.maximum(f, -65504.0), 65504.0)
|
|
56
|
+
f = tl.where(is_finite, clamped, f)
|
|
57
|
+
q = f.to(Q_TO)
|
|
58
|
+
return q.to(tl.float32)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
#: the split: one program per DECODE_VEC_SEG keys (the judged split, locked pinned protocol, 2026-08-23: 256 won the
|
|
62
|
+
#: row at all three contexts), at most DECODE_VEC_MAX_SPLIT — it defines the partials, so it is the algorithm's, not a
|
|
63
|
+
#: tile
|
|
64
|
+
DECODE_VEC_SEG = 256
|
|
65
|
+
DECODE_VEC_MAX_SPLIT = 64
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def decode_vec_splits(seqlen_k: int) -> int:
|
|
69
|
+
"""The splits the vector decode cuts `seqlen_k` keys into — its grid, and the shared reduce's autotune key (one
|
|
70
|
+
rule for the wrapper, the census and the certifier)."""
|
|
71
|
+
return max(1, min(-(-int(seqlen_k) // DECODE_VEC_SEG), DECODE_VEC_MAX_SPLIT))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# The key block and warps of the vector decode, candidates the certification measures per key (the judged row,
|
|
75
|
+
# 2026-08-23: 32 keys a step, 4 warps, among them); one stage — the loop streams K and V once.
|
|
76
|
+
_DECODE_VEC_CONFIGS = [triton.Config({'BLOCK_N': bn}, num_warps=w, num_stages=1)
|
|
77
|
+
for bn in (16, 32, 64, 128) for w in (1, 2, 4, 8)]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@nbx_autotune(configs=_DECODE_VEC_CONFIGS, key=['T_BUCKET', 'D', 'D_V', 'GQA_GROUPS', 'HAS_BIAS'], cache_results=True)
|
|
81
|
+
@triton.jit
|
|
82
|
+
def decode_attn_vec_split_kernel(
|
|
83
|
+
q_ptr, # [B*H_q, D] one query row per program (fp16)
|
|
84
|
+
k_ptr, # [B*H_kv, T_k, D] native cache layout, own strides
|
|
85
|
+
v_ptr, # [B*H_kv, T_k, D_v]
|
|
86
|
+
bias_ptr, # [T_k] additive fp32/fp16 (unused when HAS_BIAS=0)
|
|
87
|
+
opart_ptr, # [B*H_q, SPLIT, D_v] fp32 partial outputs
|
|
88
|
+
mpart_ptr, # [B*H_q, SPLIT] fp32 running max per split
|
|
89
|
+
lpart_ptr, # [B*H_q, SPLIT] fp32 sum-of-exp per split
|
|
90
|
+
T_k, seg_len,
|
|
91
|
+
T_BUCKET, # T_k's bucket: the autotune key's length class (unused in the body)
|
|
92
|
+
sm_scale,
|
|
93
|
+
stride_qh,
|
|
94
|
+
stride_kh, stride_kn,
|
|
95
|
+
stride_vh, stride_vn,
|
|
96
|
+
stride_oh, stride_os,
|
|
97
|
+
stride_mh,
|
|
98
|
+
GQA_GROUPS: tl.constexpr, # H_q // H_kv — maps Q head -> KV head
|
|
99
|
+
BLOCK_D: tl.constexpr,
|
|
100
|
+
D: tl.constexpr,
|
|
101
|
+
D_V: tl.constexpr,
|
|
102
|
+
HAS_BIAS: tl.constexpr,
|
|
103
|
+
Q_TO: tl.constexpr = None, # a dtype: Q is rounded to it on load (the cache's)
|
|
104
|
+
Q_SATURATE: tl.constexpr = False, # the protected fp16 rounding of that cast
|
|
105
|
+
BLOCK_N: tl.constexpr = 32,
|
|
106
|
+
):
|
|
107
|
+
pid_q = tl.program_id(0).to(tl.int64) # b * H_q + h_q
|
|
108
|
+
pid_s = tl.program_id(1).to(tl.int64) # split index
|
|
109
|
+
pid_kv = pid_q // GQA_GROUPS # owning KV head row
|
|
110
|
+
|
|
111
|
+
offs_d = tl.arange(0, BLOCK_D)
|
|
112
|
+
mask_d = offs_d < D
|
|
113
|
+
mask_dv = offs_d < D_V
|
|
114
|
+
|
|
115
|
+
q = tl.load(q_ptr + pid_q * stride_qh + offs_d,
|
|
116
|
+
mask=mask_d, other=0.0)
|
|
117
|
+
q = _round_q(q, Q_TO, Q_SATURATE)
|
|
118
|
+
|
|
119
|
+
seg_start = pid_s * seg_len
|
|
120
|
+
seg_start = seg_start.to(tl.int32) # loop bounds are 32-bit counts: the Metal lowering refuses a 64-bit scf.for bound (addressing stays 64-bit through the program ids)
|
|
121
|
+
seg_end = tl.minimum(seg_start + seg_len, T_k).to(tl.int32)
|
|
122
|
+
|
|
123
|
+
m_i = float("-inf")
|
|
124
|
+
l_i = 0.0
|
|
125
|
+
acc = tl.zeros((BLOCK_D,), dtype=tl.float32)
|
|
126
|
+
|
|
127
|
+
for n0 in range(seg_start, seg_end, BLOCK_N):
|
|
128
|
+
offs_n = n0 + tl.arange(0, BLOCK_N)
|
|
129
|
+
mask_n = offs_n < seg_end
|
|
130
|
+
|
|
131
|
+
k_tile = tl.load(k_ptr + pid_kv * stride_kh
|
|
132
|
+
+ offs_n[:, None] * stride_kn + offs_d[None, :],
|
|
133
|
+
mask=mask_n[:, None] & mask_d[None, :], other=0.0)
|
|
134
|
+
# scores [BLOCK_N] — FMA reduction over D, no tl.dot
|
|
135
|
+
s = tl.sum(q[None, :] * k_tile.to(tl.float32), 1) * sm_scale
|
|
136
|
+
if HAS_BIAS:
|
|
137
|
+
b = tl.load(bias_ptr + offs_n, mask=mask_n, other=0.0)
|
|
138
|
+
s = s + b.to(tl.float32)
|
|
139
|
+
s = tl.where(mask_n, s, float("-inf"))
|
|
140
|
+
|
|
141
|
+
m_new = tl.maximum(m_i, tl.max(s, 0))
|
|
142
|
+
# empty tail keeps m_new=-inf; pin the exp arguments (the
|
|
143
|
+
# flash_decode kernel's own guard, scalar form)
|
|
144
|
+
m_safe = tl.where(m_new == float("-inf"), 0.0, m_new)
|
|
145
|
+
corr = tl.exp(tl.where(m_i == float("-inf"), float("-inf"),
|
|
146
|
+
m_i - m_safe))
|
|
147
|
+
p = tl.exp(s - m_safe)
|
|
148
|
+
p = tl.where(mask_n, p, 0.0)
|
|
149
|
+
|
|
150
|
+
v_tile = tl.load(v_ptr + pid_kv * stride_vh
|
|
151
|
+
+ offs_n[:, None] * stride_vn + offs_d[None, :],
|
|
152
|
+
mask=mask_n[:, None] & mask_dv[None, :], other=0.0)
|
|
153
|
+
acc = acc * corr + tl.sum(p[:, None] * v_tile.to(tl.float32), 0)
|
|
154
|
+
l_i = l_i * corr + tl.sum(p, 0)
|
|
155
|
+
m_i = m_new
|
|
156
|
+
|
|
157
|
+
tl.store(opart_ptr + pid_q * stride_oh + pid_s * stride_os + offs_d,
|
|
158
|
+
acc, mask=mask_dv)
|
|
159
|
+
tl.store(mpart_ptr + pid_q * stride_mh + pid_s, m_i)
|
|
160
|
+
tl.store(lpart_ptr + pid_q * stride_mh + pid_s, l_i)
|
|
161
|
+
|
|
162
|
+
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Depthwise conv2d — direct stencil @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Routed by `kernels/wrappers.py:conv2d_wrapper` when the convolution
|
|
4
|
+
matches the depthwise signature (`groups == in_c == out_c`, weight
|
|
5
|
+
shape `(C, 1, kh, kw)`). The generic `conv2d_forward_kernel` (im2col +
|
|
6
|
+
tl.dot) is structurally inefficient for this case: each group has K =
|
|
7
|
+
kh*kw (often 9) so the inner reduction loop runs once with most of the
|
|
8
|
+
BLOCK_INF lane idle, yet the kernel still pays full launch + im2col
|
|
9
|
+
cost for every group. On Sana 4Kpx VAE (the DC-AE depthwise blocks
|
|
10
|
+
groups=in=out=11200), the generic kernel measures ~4.8 s per call
|
|
11
|
+
versus ~2.6 ms for cuDNN's dedicated depthwise path — a ~1800× gap
|
|
12
|
+
that closes under the dedicated stencil.
|
|
13
|
+
|
|
14
|
+
Pattern reference (P-SANA-4KPX-RUNTIME Step 3 study):
|
|
15
|
+
- MultiPath/DepthwiseConv2d (CUTLASS sm_70 NCHW depthwise iterator,
|
|
16
|
+
threadblock 64x128x32, warp 32x32x32, instruction 8x8x4 HMMA)
|
|
17
|
+
- PyTorch PR #22302 — confirmed cuDNN has a separate depthwise code
|
|
18
|
+
path on Volta/Turing fp16 (cudnn >= 7600)
|
|
19
|
+
|
|
20
|
+
Our Triton-pure design: each program computes a (BC channels x TH rows x TW columns) output tile of one batch
|
|
21
|
+
sample, width the fastest axis (NCHW's contiguous one), accumulating the kh*kw stencil contributions in fp32: the
|
|
22
|
+
kh*kw shifted input windows of a compact 2-D tile overlap and are served from the L1. No im2col buffer, no
|
|
23
|
+
cross-channel reduction, no HMMA pseudo-GEMM packing; the stencil is unrolled at compile time (kh, kw constexpr).
|
|
24
|
+
The tile was (BLOCK_HW flattened positions x BLOCK_C channels) with the channels fastest, H*W elements apart in
|
|
25
|
+
memory: no load coalesced — SANA-Video's 11200-channel 3x3 fp32 [2, 11200, 24, 40] ran 2.63 ms where this one runs
|
|
26
|
+
0.33 ms (529 GB/s; torch eager 0.38 ms), [22, 13440, 21, 42] 18.9 -> 4.2 ms (V100-SXM2, 2026-11-03).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import triton
|
|
30
|
+
|
|
31
|
+
from nbxtensor.ops._configs import nbx_autotune
|
|
32
|
+
import triton.language as tl
|
|
33
|
+
|
|
34
|
+
from nbxtensor.ops._autotune_policy import maybe_pin_single, is_depthwise_conv2d_pinned
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_DEPTHWISE_CONV2D_CONFIGS = [
|
|
38
|
+
triton.Config({'BC': 1, 'TH': 8, 'TW': 64}, num_stages=2, num_warps=4),
|
|
39
|
+
triton.Config({'BC': 1, 'TH': 16, 'TW': 64}, num_stages=2, num_warps=4),
|
|
40
|
+
triton.Config({'BC': 1, 'TH': 32, 'TW': 64}, num_stages=2, num_warps=8),
|
|
41
|
+
triton.Config({'BC': 2, 'TH': 8, 'TW': 64}, num_stages=2, num_warps=4),
|
|
42
|
+
triton.Config({'BC': 4, 'TH': 8, 'TW': 32}, num_stages=2, num_warps=4),
|
|
43
|
+
triton.Config({'BC': 4, 'TH': 8, 'TW': 64}, num_stages=2, num_warps=8),
|
|
44
|
+
triton.Config({'BC': 8, 'TH': 4, 'TW': 64}, num_stages=2, num_warps=8),
|
|
45
|
+
triton.Config({'BC': 16, 'TH': 4, 'TW': 32}, num_stages=2, num_warps=8),
|
|
46
|
+
triton.Config({'BC': 1, 'TH': 4, 'TW': 128}, num_stages=2, num_warps=4),
|
|
47
|
+
# one-row tiles: the census's depthwise rows are mostly conv1d run at H = 1 (1 x 7, 64-1024 channels), where a
|
|
48
|
+
# taller tile is all masked rows — measured best there (1024 x 2048 fp16: 0.027 ms, the flattened tile 0.062)
|
|
49
|
+
triton.Config({'BC': 4, 'TH': 1, 'TW': 128}, num_stages=2, num_warps=4),
|
|
50
|
+
triton.Config({'BC': 8, 'TH': 1, 'TW': 64}, num_stages=2, num_warps=4),
|
|
51
|
+
triton.Config({'BC': 16, 'TH': 1, 'TW': 32}, num_stages=2, num_warps=4),
|
|
52
|
+
triton.Config({'BC': 16, 'TH': 1, 'TW': 64}, num_stages=2, num_warps=8),
|
|
53
|
+
triton.Config({'BC': 32, 'TH': 1, 'TW': 32}, num_stages=2, num_warps=8),
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@nbx_autotune(
|
|
58
|
+
configs=maybe_pin_single(_DEPTHWISE_CONV2D_CONFIGS, is_depthwise_conv2d_pinned),
|
|
59
|
+
key=['C', 'H_in', 'W_in_key', 'H_out', 'W_out_key', 'kh', 'kw',
|
|
60
|
+
'stride_h', 'stride_w', 'pad_h', 'pad_w', 'fp16'],
|
|
61
|
+
cache_results=True,
|
|
62
|
+
)
|
|
63
|
+
@triton.jit
|
|
64
|
+
def depthwise_conv2d_kernel(
|
|
65
|
+
x_ptr, w_ptr, out_ptr,
|
|
66
|
+
N, C,
|
|
67
|
+
H_in, W_in, H_out, W_out,
|
|
68
|
+
W_in_key, W_out_key,
|
|
69
|
+
x_n_stride, x_c_stride, x_h_stride, x_w_stride,
|
|
70
|
+
w_c_stride, w_kh_stride, w_kw_stride,
|
|
71
|
+
out_n_stride, out_c_stride, out_h_stride, out_w_stride,
|
|
72
|
+
kh: tl.constexpr, kw: tl.constexpr,
|
|
73
|
+
stride_h: tl.constexpr, stride_w: tl.constexpr,
|
|
74
|
+
pad_h: tl.constexpr, pad_w: tl.constexpr,
|
|
75
|
+
fp16: tl.constexpr,
|
|
76
|
+
BC: tl.constexpr = 2,
|
|
77
|
+
TH: tl.constexpr = 8,
|
|
78
|
+
TW: tl.constexpr = 64,
|
|
79
|
+
WIDE_OFFSETS: tl.constexpr = True,
|
|
80
|
+
):
|
|
81
|
+
# 64-bit offsets only where an operand's extent needs them (`WIDE_OFFSETS`, the wrapper's `_conv_offsets_wide`, as
|
|
82
|
+
# conv2d): 32-bit offsets are exact below 2^31, and the 64-bit ones are emulated on Apple's ALUs — under the i64 fix of
|
|
83
|
+
# the Metal backend (b06cbb0d) the fp32 3x3 depthwise (13440 ch, 42x21) ran 8.4 -> 12.5 ms (Mac, 2026-10-21)
|
|
84
|
+
if WIDE_OFFSETS:
|
|
85
|
+
pid_nc = tl.program_id(0).to(tl.int64)
|
|
86
|
+
pid_h = tl.program_id(1).to(tl.int64)
|
|
87
|
+
pid_w = tl.program_id(2).to(tl.int64)
|
|
88
|
+
else:
|
|
89
|
+
pid_nc = tl.program_id(0)
|
|
90
|
+
pid_h = tl.program_id(1)
|
|
91
|
+
pid_w = tl.program_id(2)
|
|
92
|
+
n_cb = tl.cdiv(C, BC)
|
|
93
|
+
pid_n = pid_nc // n_cb
|
|
94
|
+
offs_c = (pid_nc % n_cb) * BC + tl.arange(0, BC)
|
|
95
|
+
oh = pid_h * TH + tl.arange(0, TH)
|
|
96
|
+
ow = pid_w * TW + tl.arange(0, TW)
|
|
97
|
+
c_mask = offs_c < C
|
|
98
|
+
|
|
99
|
+
accum = tl.zeros((BC, TH, TW), dtype=tl.float32)
|
|
100
|
+
x_base = x_ptr + pid_n * x_n_stride + offs_c[:, None, None] * x_c_stride
|
|
101
|
+
|
|
102
|
+
# Unrolled stencil over kh x kw (constexpr): each tap reads the input window the output tile shifted by it.
|
|
103
|
+
for kh_i in tl.static_range(0, kh):
|
|
104
|
+
ih = oh * stride_h + kh_i - pad_h
|
|
105
|
+
h_ok = (ih >= 0) & (ih < H_in)
|
|
106
|
+
for kw_i in tl.static_range(0, kw):
|
|
107
|
+
iw = ow * stride_w + kw_i - pad_w
|
|
108
|
+
w_ok = (iw >= 0) & (iw < W_in)
|
|
109
|
+
full_mask = c_mask[:, None, None] & h_ok[None, :, None] & w_ok[None, None, :]
|
|
110
|
+
x_block = tl.load(x_base + ih[None, :, None] * x_h_stride + iw[None, None, :] * x_w_stride,
|
|
111
|
+
mask=full_mask, other=0.0)
|
|
112
|
+
w_block = tl.load(w_ptr + offs_c * w_c_stride + kh_i * w_kh_stride + kw_i * w_kw_stride,
|
|
113
|
+
mask=c_mask, other=0.0)
|
|
114
|
+
# Upcast for EVERY dtype, not just fp16. The accumulator is fp32 either way, so
|
|
115
|
+
# this costs nothing — and multiplying in the operands' own dtype made bf16 with
|
|
116
|
+
# padding wrong on Metal: deviation 0.754 against the fp64 oracle where unpadded
|
|
117
|
+
# bf16 sat at its 3.2e-03 mantissa floor, with the error in the ALL-IN-BOUNDS
|
|
118
|
+
# interior while the genuinely masked border row was clean. fp32 and fp16 were
|
|
119
|
+
# unaffected at both paddings, and the same source is exact to the mantissa on
|
|
120
|
+
# CUDA (Triton 3.8.0, V100 sm_70: bf16 pad0 == pad1), so the bad lowering is the
|
|
121
|
+
# Metal backend's for a native bf16 product of a masked-loaded operand. Upcasting
|
|
122
|
+
# removes the dependence on it rather than relying on it being fixed below us.
|
|
123
|
+
# Gate: tests/unit/kernels/test_depthwise_bf16_padding.py (2026-09-22).
|
|
124
|
+
accum += x_block.to(tl.float32) * w_block.to(tl.float32)[:, None, None]
|
|
125
|
+
|
|
126
|
+
out_addr = (out_ptr + pid_n * out_n_stride + offs_c[:, None, None] * out_c_stride
|
|
127
|
+
+ oh[None, :, None] * out_h_stride + ow[None, None, :] * out_w_stride)
|
|
128
|
+
out_mask = c_mask[:, None, None] & (oh < H_out)[None, :, None] & (ow < W_out)[None, None, :]
|
|
129
|
+
if fp16:
|
|
130
|
+
tl.store(out_addr, accum.to(tl.float16), mask=out_mask)
|
|
131
|
+
else:
|
|
132
|
+
tl.store(out_addr, accum, mask=out_mask)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""E7 — fused int4 dequant-GEMV family (quantized tier, pure Triton).
|
|
2
|
+
|
|
3
|
+
Weight-only int4, groupwise g=128, ASYMMETRIC: per group along K an
|
|
4
|
+
fp16 scale and an fp16 min; dequant is `q * scale + min` with q an
|
|
5
|
+
UNSIGNED nibble (llama.cpp q4_1's exact form — no -8 offset, no
|
|
6
|
+
AutoGPTQ +1 storage quirk). Packing: 8 nibbles per int32, flat
|
|
7
|
+
low-nibble-first (nibble i of word r encodes element k = 8*r + i),
|
|
8
|
+
W_q laid out (K//8, N) K-major so loads coalesce along N — the
|
|
9
|
+
GemLite convention.
|
|
10
|
+
|
|
11
|
+
THE TIER'S BYTE ORACLE (scoping "quantized tier contract", clause 1):
|
|
12
|
+
the fused kernel must be byte-identical to dequantize-then-GEMV. A
|
|
13
|
+
cuBLAS/torch reference can never be byte-matched (its reduction order
|
|
14
|
+
is internal); the oracle is therefore built from THIS file's own
|
|
15
|
+
kernels: `dequant_int4_kernel` (standalone dequant whose dequant
|
|
16
|
+
expression is textually identical to the fused kernel's) feeding
|
|
17
|
+
`gemv_ref_kernel` (same BLOCK_N/BLOCK_K, same ascending-K chunk
|
|
18
|
+
loop, same tl.sum tree, same fp32 accumulator). Fused == oracle by
|
|
19
|
+
construction; the microtest proves it byte-for-byte with ONE pinned
|
|
20
|
+
config for both kernels (autotune would change the tl.sum tree — E7
|
|
21
|
+
GEMV sits OUTSIDE the mm/bmm/addmm/conv2d autotune sanction).
|
|
22
|
+
|
|
23
|
+
sm_70 discipline (sourced, R38 study + 2026-08-16 research): M=1
|
|
24
|
+
decode GEMV, no tl.dot (Volta fp16 dot lowers to FMA anyway), SPLIT_K
|
|
25
|
+
= 1 — a single program per N-tile walks all of K, NO atomics
|
|
26
|
+
(atomic-splitK is arrival-order nondeterministic and fp16 atomics are
|
|
27
|
+
version-fragile on our pin; both break the byte contract). The win is
|
|
28
|
+
weight bandwidth: 4x fewer weight bytes than fp16. num_stages <= 2
|
|
29
|
+
(cp.async pipelining is sm_80+; more stages only add register
|
|
30
|
+
pressure to the unpack-heavy loop).
|
|
31
|
+
|
|
32
|
+
Constraints enforced by the wrappers: K % BLOCK_K == 0 handled by
|
|
33
|
+
masking; BLOCK_K in {32, 64, 128} divides group_size=128 so a K-chunk
|
|
34
|
+
never crosses a group boundary (one scale/min scalar per (chunk, n) —
|
|
35
|
+
the GemLite pruner rule).
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
import triton
|
|
39
|
+
import triton.language as tl
|
|
40
|
+
|
|
41
|
+
GROUP_SIZE: int = 128
|
|
42
|
+
PACK: int = 8 # nibbles per int32
|
|
43
|
+
|
|
44
|
+
# Pinned parity config: oracle AND fused always run the SAME values —
|
|
45
|
+
# the tl.sum reduction tree must be identical between the two, which
|
|
46
|
+
# is the tier contract (fused == dequant->ref byte-for-byte).
|
|
47
|
+
# BLOCK_K and NUM_WARPS stay pinned. BLOCK_N adapts at runtime for
|
|
48
|
+
# small-N grids (wrappers._dequant_gemv_block_n, 2026-08-25: halved
|
|
49
|
+
# pow2 until the grid reaches the vendor-yml occupancy target,
|
|
50
|
+
# NBX_DGEMV_ADAPT=0 kill) — legitimate because BLOCK_N only
|
|
51
|
+
# partitions OUTPUT columns while each column's K-reduction tree is
|
|
52
|
+
# BLOCK_K-fixed; the parity microtest proves fused == oracle
|
|
53
|
+
# byte-for-byte at the adapted widths (1/8) as well as the pinned 64.
|
|
54
|
+
# Cross-WIDTH outputs differ in low bits (the per-column lowering
|
|
55
|
+
# changes with BLOCK_N) — width changes are drift-class and go
|
|
56
|
+
# through the model byte gate, never silently.
|
|
57
|
+
BLOCK_N: int = 64
|
|
58
|
+
BLOCK_K: int = 128
|
|
59
|
+
NUM_WARPS: int = 4
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@triton.jit
|
|
63
|
+
def dequant_int4_kernel(
|
|
64
|
+
wq_ptr, scales_ptr, mins_ptr, out_ptr,
|
|
65
|
+
K, N,
|
|
66
|
+
stride_wk, stride_wn,
|
|
67
|
+
stride_sg, stride_sn,
|
|
68
|
+
stride_ok, stride_on,
|
|
69
|
+
BLOCK_K_C: tl.constexpr, BLOCK_N_C: tl.constexpr,
|
|
70
|
+
GROUP_C: tl.constexpr, PACK_C: tl.constexpr,
|
|
71
|
+
):
|
|
72
|
+
"""Standalone dequant: packed int4 -> dense fp32 [K, N].
|
|
73
|
+
|
|
74
|
+
Oracle part 1. The dequant expression below is the TEXTUAL
|
|
75
|
+
contract shared with the fused kernel: same loads, same shifts,
|
|
76
|
+
same pure-fp32 dtype path.
|
|
77
|
+
"""
|
|
78
|
+
pid_k = tl.program_id(0).to(tl.int64)
|
|
79
|
+
pid_n = tl.program_id(1).to(tl.int64)
|
|
80
|
+
offs_k = pid_k * BLOCK_K_C + tl.arange(0, BLOCK_K_C)
|
|
81
|
+
offs_n = pid_n * BLOCK_N_C + tl.arange(0, BLOCK_N_C)
|
|
82
|
+
mask_k = offs_k < K
|
|
83
|
+
mask_n = offs_n < N
|
|
84
|
+
mask = mask_k[:, None] & mask_n[None, :]
|
|
85
|
+
|
|
86
|
+
packed = tl.load(
|
|
87
|
+
wq_ptr + (offs_k[:, None] // PACK_C) * stride_wk
|
|
88
|
+
+ offs_n[None, :] * stride_wn, mask=mask, other=0)
|
|
89
|
+
q = (packed >> ((offs_k[:, None] % PACK_C) * 4)) & 0xF
|
|
90
|
+
|
|
91
|
+
g = pid_k * BLOCK_K_C // GROUP_C # BLOCK_K_C divides GROUP_C
|
|
92
|
+
scale = tl.load(scales_ptr + g * stride_sg + offs_n * stride_sn,
|
|
93
|
+
mask=mask_n, other=0.0)
|
|
94
|
+
mn = tl.load(mins_ptr + g * stride_sg + offs_n * stride_sn,
|
|
95
|
+
mask=mask_n, other=0.0)
|
|
96
|
+
# Canonical dequant: PURE fp32 (llama.cpp dmmv default,
|
|
97
|
+
# dfloat=float — the fp16 weight never exists at runtime).
|
|
98
|
+
# Contraction-immune: q*scale is EXACT in fp32 (4-bit int x fp16
|
|
99
|
+
# mantissa), so fma(q,s,m) == q*s+m bit-for-bit whatever the
|
|
100
|
+
# compiler emits. The dense oracle tensor is fp32.
|
|
101
|
+
w = (q.to(tl.float32) * scale[None, :].to(tl.float32)
|
|
102
|
+
+ mn[None, :].to(tl.float32))
|
|
103
|
+
|
|
104
|
+
tl.store(out_ptr + offs_k[:, None] * stride_ok
|
|
105
|
+
+ offs_n[None, :] * stride_on, w, mask=mask)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@triton.jit
|
|
109
|
+
def gemv_ref_kernel(
|
|
110
|
+
x_ptr, w_ptr, out_ptr,
|
|
111
|
+
K, N,
|
|
112
|
+
stride_wk, stride_wn,
|
|
113
|
+
BLOCK_K_C: tl.constexpr, BLOCK_N_C: tl.constexpr,
|
|
114
|
+
):
|
|
115
|
+
"""Reference dense-weight GEMV: out[n] = sum_k x[k] * w[k, n].
|
|
116
|
+
|
|
117
|
+
Oracle part 2. Fixed reduction structure — ascending-K chunk loop,
|
|
118
|
+
per-chunk tl.sum over axis 0, fp32 accumulator, one store — the
|
|
119
|
+
EXACT structure of the fused kernel below. (A nibble-major
|
|
120
|
+
restructure with per-nibble partial sums was MEASURED and
|
|
121
|
+
reverted: it slowed the fused kernel 0.090 -> 0.224 ms at
|
|
122
|
+
4096x11008 — the lane-selection sums cost more than the redundant
|
|
123
|
+
packed loads they save; the fused kernel is ALU-issue-bound, not
|
|
124
|
+
load-bound.)
|
|
125
|
+
"""
|
|
126
|
+
pid_n = tl.program_id(0).to(tl.int64)
|
|
127
|
+
offs_n = pid_n * BLOCK_N_C + tl.arange(0, BLOCK_N_C)
|
|
128
|
+
mask_n = offs_n < N
|
|
129
|
+
acc = tl.zeros((BLOCK_N_C,), dtype=tl.float32)
|
|
130
|
+
for kc in range(0, tl.cdiv(K, BLOCK_K_C)):
|
|
131
|
+
offs_k = kc * BLOCK_K_C + tl.arange(0, BLOCK_K_C)
|
|
132
|
+
mask_k = offs_k < K
|
|
133
|
+
a = tl.load(x_ptr + offs_k, mask=mask_k, other=0.0)
|
|
134
|
+
b = tl.load(w_ptr + offs_k[:, None] * stride_wk
|
|
135
|
+
+ offs_n[None, :] * stride_wn,
|
|
136
|
+
mask=mask_k[:, None] & mask_n[None, :], other=0.0)
|
|
137
|
+
acc += tl.sum(a.to(tl.float32)[:, None] * b.to(tl.float32),
|
|
138
|
+
axis=0)
|
|
139
|
+
tl.store(out_ptr + offs_n, acc, mask=mask_n)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@triton.jit
|
|
143
|
+
def dequant_gemv_int4_kernel(
|
|
144
|
+
x_ptr, wq_ptr, scales_ptr, mins_ptr, out_ptr,
|
|
145
|
+
K, N,
|
|
146
|
+
stride_wk, stride_wn,
|
|
147
|
+
stride_sg, stride_sn,
|
|
148
|
+
BLOCK_K_C: tl.constexpr, BLOCK_N_C: tl.constexpr,
|
|
149
|
+
GROUP_C: tl.constexpr, PACK_C: tl.constexpr,
|
|
150
|
+
):
|
|
151
|
+
"""Fused: out[n] = sum_k x[k] * dequant(wq)[k, n], fp32 accum.
|
|
152
|
+
|
|
153
|
+
Dequant expression textually identical to dequant_int4_kernel;
|
|
154
|
+
reduction structure textually identical to gemv_ref_kernel —
|
|
155
|
+
byte-equal to the two-step oracle by construction (proven by the
|
|
156
|
+
parity microtest with the pinned config). The packed load gathers
|
|
157
|
+
`offs_k // 8` (each int32 requested 8x, absorbed by L1) — the
|
|
158
|
+
nibble-major single-load variant was MEASURED SLOWER (see the
|
|
159
|
+
oracle kernel's docstring); the kernel is ALU-issue-bound.
|
|
160
|
+
"""
|
|
161
|
+
pid_n = tl.program_id(0).to(tl.int64)
|
|
162
|
+
offs_n = pid_n * BLOCK_N_C + tl.arange(0, BLOCK_N_C)
|
|
163
|
+
mask_n = offs_n < N
|
|
164
|
+
acc = tl.zeros((BLOCK_N_C,), dtype=tl.float32)
|
|
165
|
+
for kc in range(0, tl.cdiv(K, BLOCK_K_C)):
|
|
166
|
+
offs_k = kc * BLOCK_K_C + tl.arange(0, BLOCK_K_C)
|
|
167
|
+
mask_k = offs_k < K
|
|
168
|
+
mask = mask_k[:, None] & mask_n[None, :]
|
|
169
|
+
a = tl.load(x_ptr + offs_k, mask=mask_k, other=0.0)
|
|
170
|
+
|
|
171
|
+
packed = tl.load(
|
|
172
|
+
wq_ptr + (offs_k[:, None] // PACK_C) * stride_wk
|
|
173
|
+
+ offs_n[None, :] * stride_wn, mask=mask, other=0)
|
|
174
|
+
q = (packed >> ((offs_k[:, None] % PACK_C) * 4)) & 0xF
|
|
175
|
+
g = kc * BLOCK_K_C // GROUP_C # BLOCK_K_C divides GROUP_C
|
|
176
|
+
scale = tl.load(scales_ptr + g * stride_sg + offs_n * stride_sn,
|
|
177
|
+
mask=mask_n, other=0.0)
|
|
178
|
+
mn = tl.load(mins_ptr + g * stride_sg + offs_n * stride_sn,
|
|
179
|
+
mask=mask_n, other=0.0)
|
|
180
|
+
# Canonical dequant — textually identical to
|
|
181
|
+
# dequant_int4_kernel: PURE fp32, no fp16 materialization
|
|
182
|
+
# (contraction-immune: q*scale exact in fp32).
|
|
183
|
+
w = (q.to(tl.float32) * scale[None, :].to(tl.float32)
|
|
184
|
+
+ mn[None, :].to(tl.float32))
|
|
185
|
+
# Masked K-tail rows dequant to `mn` (q=0), not 0 — zero them
|
|
186
|
+
# through the mask so the tail contributes exact +0.0, same as
|
|
187
|
+
# the oracle GEMV's masked b-load.
|
|
188
|
+
b = tl.where(mask, w, 0.0)
|
|
189
|
+
acc += tl.sum(a.to(tl.float32)[:, None] * b, axis=0)
|
|
190
|
+
tl.store(out_ptr + offs_n, acc, mask=mask_n)
|
nbxtensor/ops/dft.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""DFT / iDFT basis matrices — pure @triton.jit kernels.
|
|
2
|
+
|
|
3
|
+
Builds the real cos/sin basis matrices for a Discrete Fourier Transform computed
|
|
4
|
+
by matrix multiplication. This is the standard small-N / non-power-of-2 path
|
|
5
|
+
(the DFT is an N×N matrix of complex exponentials applied to the signal) — used
|
|
6
|
+
when the radix-2 butterfly cannot run (n_fft not a power of 2, e.g. an iSTFT head
|
|
7
|
+
with n_fft=20). Model-agnostic: any non-pow2 FFT length routes here.
|
|
8
|
+
|
|
9
|
+
Layout (matches the rfft convention): frequency bins k in [0, N//2+1), samples
|
|
10
|
+
n in [0, N). The matrices are [N_bins, N], applied as `frames @ mat.T`.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import triton
|
|
14
|
+
import triton.language as tl
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@triton.jit
|
|
18
|
+
def dft_rfft_matrix_kernel(
|
|
19
|
+
cos_ptr, # (N_bins, N) — cos(2π k n / N)
|
|
20
|
+
nsin_ptr, # (N_bins, N) — -sin(2π k n / N)
|
|
21
|
+
N_bins, N,
|
|
22
|
+
two_pi_over_N,
|
|
23
|
+
BLOCK_SIZE: tl.constexpr,
|
|
24
|
+
):
|
|
25
|
+
"""Forward rfft basis: X[k] = Σ_n x[n] (cos - i sin)(2π k n / N).
|
|
26
|
+
|
|
27
|
+
cos[k,n] = cos(2π k n / N), nsin[k,n] = -sin(2π k n / N) so that
|
|
28
|
+
X_real = x @ cos.T and X_imag = x @ nsin.T.
|
|
29
|
+
"""
|
|
30
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
31
|
+
offs = 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)
|
|
32
|
+
total = N_bins * N
|
|
33
|
+
mask = offs < total
|
|
34
|
+
k = offs // N
|
|
35
|
+
n = offs % N
|
|
36
|
+
angle = (k * n).to(tl.float32) * two_pi_over_N
|
|
37
|
+
tl.store(cos_ptr + offs, tl.cos(angle), mask=mask)
|
|
38
|
+
tl.store(nsin_ptr + offs, -tl.sin(angle), mask=mask)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@triton.jit
|
|
42
|
+
def idft_c2r_matrix_kernel(
|
|
43
|
+
cos_ptr, # (N_bins, N) — weight_k cos(2π k n / N) / N
|
|
44
|
+
sin_ptr, # (N_bins, N) — -weight_k sin(2π k n / N) / N
|
|
45
|
+
N_bins, N,
|
|
46
|
+
two_pi_over_N,
|
|
47
|
+
inv_N,
|
|
48
|
+
n_even, # 1 if N is even (Nyquist bin present), else 0
|
|
49
|
+
BLOCK_SIZE: tl.constexpr,
|
|
50
|
+
):
|
|
51
|
+
"""Inverse rfft (irfft) basis, exploiting Hermitian symmetry.
|
|
52
|
+
|
|
53
|
+
x[n] = (1/N) Σ_k weight_k [Re(X[k]) cos(2π k n / N) - Im(X[k]) sin(2π k n / N)]
|
|
54
|
+
weight_k = 1 for k=0 and (N even) k=N/2, else 2. So
|
|
55
|
+
real_out = X_real @ cos + X_imag @ sin.
|
|
56
|
+
"""
|
|
57
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
58
|
+
offs = 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)
|
|
59
|
+
total = N_bins * N
|
|
60
|
+
mask = offs < total
|
|
61
|
+
k = offs // N
|
|
62
|
+
n = offs % N
|
|
63
|
+
is_edge = (k == 0) | ((n_even == 1) & (k == N_bins - 1))
|
|
64
|
+
weight = tl.where(is_edge, 1.0, 2.0) * inv_N
|
|
65
|
+
angle = (k * n).to(tl.float32) * two_pi_over_N
|
|
66
|
+
tl.store(cos_ptr + offs, weight * tl.cos(angle), mask=mask)
|
|
67
|
+
tl.store(sin_ptr + offs, -weight * tl.sin(angle), mask=mask)
|