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,215 @@
|
|
|
1
|
+
"""Paged KV: the append and the decode attention read through a page table (state engine step 2,
|
|
2
|
+
docs/design/state-engine.md §4.3; the shared bookkeeping is core/state/pages.py).
|
|
3
|
+
|
|
4
|
+
The state of a decoding sequence lives in fixed-size pages of a per-node pool, granted as the sequence grows and never
|
|
5
|
+
at max_len. One pool per layer and per K / V, laid out [num_pages, kv_heads, page_tokens, D]: the positions of one KV
|
|
6
|
+
head inside one page are contiguous rows, so a tile of BLOCK_N positions of one head is one strided block read.
|
|
7
|
+
|
|
8
|
+
- `paged_kv_append_kernel` writes new positions through the slot mapping (vLLM's `reshape_and_cache` shape): slot =
|
|
9
|
+
page * page_tokens + position-in-page, one int32 per new token, computed by the PageTable.
|
|
10
|
+
- `paged_decode_attn_split_kernel` is the vector (SIMT) split-KV decode read of ops/decode_attn_vec.py — one query row
|
|
11
|
+
per program, FMA reduction over D, scalar online softmax, fp32 accumulator, partials merged by the fixed-order
|
|
12
|
+
`flash_decode_reduce_kernel` — with K / V addressed through the block table (one page id per page of the sequence,
|
|
13
|
+
int32, padded with -1). A tile never straddles two pages: every candidate tile divides the page size
|
|
14
|
+
(`PAGED_DECODE_TILE_CANDIDATES`, ops/tiling_sizes, read by core/state/price.page_tokens) and each split starts on a tile boundary.
|
|
15
|
+
Each sequence of a batch reads its own length (`seq_lens`), so one launch serves a batch of different lengths.
|
|
16
|
+
|
|
17
|
+
Pure @triton.jit, no tl.dot (sm_70: the vector kernel's measured structure), one source for every backend.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import triton
|
|
21
|
+
import triton.language as tl
|
|
22
|
+
|
|
23
|
+
from nbxtensor.ops.decode_attn_vec import _round_q
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# The page tiles are sizes: nbxtensor.ops.tiling_sizes owns them (torch- and triton-free — the compiled branch sizes
|
|
27
|
+
# its paged cache with them in an environment without triton).
|
|
28
|
+
from nbxtensor.ops.tiling_sizes import PAGED_DECODE_TILE_CANDIDATES # noqa: F401
|
|
29
|
+
from nbxtensor.ops._configs import nbx_autotune
|
|
30
|
+
|
|
31
|
+
# The split shape: positions per split and the most splits one launch makes — the vector decode kernel's judged config
|
|
32
|
+
# (its locked protocol of 2026-08-23, the defaults of `_decode_attn_vec`'s NBX_DV_SEG and its 64-split cap), so the
|
|
33
|
+
# paged read partitions a sequence exactly as the contiguous read it replaces.
|
|
34
|
+
PAGED_DECODE_SPLIT_POSITIONS = 256
|
|
35
|
+
PAGED_DECODE_MAX_SPLITS = 64
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def paged_decode_splits(longest: int) -> int:
|
|
39
|
+
"""The splits one decode launch makes for its longest sequence — the read's grid, the autotune key's length class
|
|
40
|
+
(what a split's work depends on) and the replayed decode's bucket (`PagedTritonKVCache.replay_key`): one rule, so
|
|
41
|
+
a replayed step never meets a launch the certificate keys differently. Keyed on the length's own bucket instead,
|
|
42
|
+
which moves at every token below 64, a replay recorded at one step was verified against another step's
|
|
43
|
+
configuration, and the bytes differed: TinyLlama's decode stopped replaying (25 -> 135 ms a token, 2026-11-04)."""
|
|
44
|
+
return max(1, min(-(-max(int(longest), 1) // PAGED_DECODE_SPLIT_POSITIONS), PAGED_DECODE_MAX_SPLITS))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@triton.jit
|
|
48
|
+
def paged_kv_append_kernel(
|
|
49
|
+
new_ptr, # [N_tok, H_kv, D] the new positions' K (or V), any float dtype
|
|
50
|
+
pool_ptr, # [num_pages, H_kv, page_tokens, D] the layer's pool
|
|
51
|
+
slot_ptr, # [N_tok] int32: page * page_tokens + position in page
|
|
52
|
+
page_tokens,
|
|
53
|
+
stride_nt, stride_nh,
|
|
54
|
+
stride_pp, stride_ph, stride_ps,
|
|
55
|
+
BLOCK_D: tl.constexpr,
|
|
56
|
+
D: tl.constexpr,
|
|
57
|
+
):
|
|
58
|
+
"""One program per (new token, KV head): its D values to the page slot the slot mapping names, cast to the pool's
|
|
59
|
+
dtype on store."""
|
|
60
|
+
pid_t = tl.program_id(0).to(tl.int64)
|
|
61
|
+
pid_h = tl.program_id(1).to(tl.int64)
|
|
62
|
+
offs_d = tl.arange(0, BLOCK_D)
|
|
63
|
+
mask_d = offs_d < D
|
|
64
|
+
slot = tl.load(slot_ptr + pid_t).to(tl.int64)
|
|
65
|
+
page = slot // page_tokens
|
|
66
|
+
pos = slot % page_tokens
|
|
67
|
+
x = tl.load(new_ptr + pid_t * stride_nt + pid_h * stride_nh + offs_d, mask=mask_d, other=0.0)
|
|
68
|
+
tl.store(pool_ptr + page * stride_pp + pid_h * stride_ph + pos * stride_ps + offs_d,
|
|
69
|
+
x.to(pool_ptr.dtype.element_ty), mask=mask_d)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
#: the position tiles the paged read is certified over (each dividing the page, `_tiles_in_the_page`), and the warps
|
|
73
|
+
_PAGED_DECODE_CONFIGS = [triton.Config({'BLOCK_N': bn}, num_warps=w, num_stages=1)
|
|
74
|
+
for bn in PAGED_DECODE_TILE_CANDIDATES for w in (2, 4, 8)]
|
|
75
|
+
#: a split's positions are a whole number of the LARGEST tile, so every candidate tile cuts the splits the same way:
|
|
76
|
+
#: the partials, and the oracle a certification proves them against, are the split's, not the tile's
|
|
77
|
+
PAGED_DECODE_SPLIT_ALIGN = max(PAGED_DECODE_TILE_CANDIDATES)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _tiles_in_the_page(configs, named_args, **kwargs):
|
|
81
|
+
"""The candidates whose tile divides the page (a tile never straddles two pages)."""
|
|
82
|
+
pt = int(named_args["page_tokens"])
|
|
83
|
+
return [c for c in configs if pt % int(c.kwargs["BLOCK_N"]) == 0]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@nbx_autotune(configs=_PAGED_DECODE_CONFIGS, key=['SPLITS', 'page_tokens', 'D', 'D_V', 'GQA_GROUPS'],
|
|
87
|
+
prune_configs_by={'early_config_prune': _tiles_in_the_page}, cache_results=True)
|
|
88
|
+
@triton.jit
|
|
89
|
+
def paged_decode_attn_split_kernel(
|
|
90
|
+
q_ptr, # [B*H_q, D] one query row per program
|
|
91
|
+
k_pool_ptr, # [num_pages, H_kv, page_tokens, D]
|
|
92
|
+
v_pool_ptr, # [num_pages, H_kv, page_tokens, D_v]
|
|
93
|
+
block_table_ptr, # [B, max_pages] int32 page ids, -1 past the sequence
|
|
94
|
+
seq_lens_ptr, # [B] int32 positions each sequence holds
|
|
95
|
+
opart_ptr, # [B*H_q, SPLIT, D_v] fp32 partial outputs
|
|
96
|
+
mpart_ptr, # [B*H_q, SPLIT] fp32 running max per split
|
|
97
|
+
lpart_ptr, # [B*H_q, SPLIT] fp32 sum-of-exp per split
|
|
98
|
+
n_seqs, # B: the split's length is read from the device's lengths, never passed (see below)
|
|
99
|
+
page_tokens, # positions per page, a multiple of BLOCK_N
|
|
100
|
+
SPLITS, # the grid's splits (`paged_decode_splits`): the autotune key's length class (unused in the body)
|
|
101
|
+
sm_scale,
|
|
102
|
+
H_Q,
|
|
103
|
+
stride_qh,
|
|
104
|
+
stride_kp, stride_kh, stride_ks,
|
|
105
|
+
stride_vp, stride_vh, stride_vs,
|
|
106
|
+
stride_bt,
|
|
107
|
+
stride_oh, stride_os,
|
|
108
|
+
stride_mh,
|
|
109
|
+
GQA_GROUPS: tl.constexpr, # H_q // H_kv
|
|
110
|
+
BLOCK_D: tl.constexpr,
|
|
111
|
+
D: tl.constexpr,
|
|
112
|
+
D_V: tl.constexpr,
|
|
113
|
+
SPLIT_ALIGN: tl.constexpr, # a split's positions: a whole number of these (PAGED_DECODE_SPLIT_ALIGN)
|
|
114
|
+
Q_TO: tl.constexpr = None, # a dtype: Q is rounded to it on load (the cache's), as the contiguous read does
|
|
115
|
+
Q_SATURATE: tl.constexpr = False,
|
|
116
|
+
BLOCK_N: tl.constexpr = 32,
|
|
117
|
+
):
|
|
118
|
+
pid_q = tl.program_id(0).to(tl.int64) # b * H_q + h_q
|
|
119
|
+
pid_s = tl.program_id(1).to(tl.int64) # split index
|
|
120
|
+
b = pid_q // H_Q
|
|
121
|
+
h_kv = (pid_q % H_Q) // GQA_GROUPS
|
|
122
|
+
|
|
123
|
+
offs_d = tl.arange(0, BLOCK_D)
|
|
124
|
+
mask_d = offs_d < D
|
|
125
|
+
mask_dv = offs_d < D_V
|
|
126
|
+
q = _round_q(tl.load(q_ptr + pid_q * stride_qh + offs_d, mask=mask_d, other=0.0), Q_TO, Q_SATURATE)
|
|
127
|
+
|
|
128
|
+
T_b = tl.load(seq_lens_ptr + b).to(tl.int32)
|
|
129
|
+
# Positions per split, read from the DEVICE lengths: the longest sequence over the grid's splits, in whole tiles
|
|
130
|
+
# — the host's figure from the same lengths, so the same value at every step. Passed as a scalar it was frozen into
|
|
131
|
+
# a replayed decode's launch: the split count only moves every PAGED_DECODE_SPLIT_POSITIONS positions, the length
|
|
132
|
+
# every token, and a replayed TinyLlama stopped attending to its newest positions (2026-10-28).
|
|
133
|
+
longest = tl.load(seq_lens_ptr).to(tl.int32)
|
|
134
|
+
for i in range(1, n_seqs):
|
|
135
|
+
longest = tl.maximum(longest, tl.load(seq_lens_ptr + i).to(tl.int32))
|
|
136
|
+
longest = tl.maximum(longest, 1)
|
|
137
|
+
n_split = tl.num_programs(1)
|
|
138
|
+
seg_len = tl.cdiv(tl.cdiv(longest, n_split), SPLIT_ALIGN) * SPLIT_ALIGN
|
|
139
|
+
seg_start = (pid_s * seg_len).to(tl.int32) # 32-bit loop bounds (the Metal lowering refuses a 64-bit scf.for bound)
|
|
140
|
+
seg_end = tl.minimum(seg_start + seg_len, T_b).to(tl.int32)
|
|
141
|
+
|
|
142
|
+
m_i = float("-inf")
|
|
143
|
+
l_i = 0.0
|
|
144
|
+
acc = tl.zeros((BLOCK_D,), dtype=tl.float32)
|
|
145
|
+
|
|
146
|
+
for n0 in range(seg_start, seg_end, BLOCK_N):
|
|
147
|
+
# the tile's page: n0 is a multiple of BLOCK_N and BLOCK_N divides page_tokens, so the tile is in one page
|
|
148
|
+
page = tl.load(block_table_ptr + b * stride_bt + n0 // page_tokens).to(tl.int64)
|
|
149
|
+
in_page = (n0 % page_tokens) + tl.arange(0, BLOCK_N)
|
|
150
|
+
mask_n = (n0 + tl.arange(0, BLOCK_N)) < seg_end
|
|
151
|
+
|
|
152
|
+
k_tile = tl.load(k_pool_ptr + page * stride_kp + h_kv * stride_kh
|
|
153
|
+
+ in_page[:, None] * stride_ks + offs_d[None, :],
|
|
154
|
+
mask=mask_n[:, None] & mask_d[None, :], other=0.0)
|
|
155
|
+
s = tl.sum(q[None, :] * k_tile.to(tl.float32), 1) * sm_scale
|
|
156
|
+
s = tl.where(mask_n, s, float("-inf"))
|
|
157
|
+
|
|
158
|
+
m_new = tl.maximum(m_i, tl.max(s, 0))
|
|
159
|
+
m_safe = tl.where(m_new == float("-inf"), 0.0, m_new)
|
|
160
|
+
corr = tl.exp(tl.where(m_i == float("-inf"), float("-inf"), m_i - m_safe))
|
|
161
|
+
p = tl.exp(s - m_safe)
|
|
162
|
+
p = tl.where(mask_n, p, 0.0)
|
|
163
|
+
|
|
164
|
+
v_tile = tl.load(v_pool_ptr + page * stride_vp + h_kv * stride_vh
|
|
165
|
+
+ in_page[:, None] * stride_vs + offs_d[None, :],
|
|
166
|
+
mask=mask_n[:, None] & mask_dv[None, :], other=0.0)
|
|
167
|
+
acc = acc * corr + tl.sum(p[:, None] * v_tile.to(tl.float32), 0)
|
|
168
|
+
l_i = l_i * corr + tl.sum(p, 0)
|
|
169
|
+
m_i = m_new
|
|
170
|
+
|
|
171
|
+
tl.store(opart_ptr + pid_q * stride_oh + pid_s * stride_os + offs_d, acc, mask=mask_dv)
|
|
172
|
+
tl.store(mpart_ptr + pid_q * stride_mh + pid_s, m_i)
|
|
173
|
+
tl.store(lpart_ptr + pid_q * stride_mh + pid_s, l_i)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@triton.jit
|
|
177
|
+
def paged_kv_append_step_kernel(
|
|
178
|
+
k_new_ptr, v_new_ptr, # [B, H_kv, D] / [B, H_kv, D_v] the step's one new position per sequence
|
|
179
|
+
k_pool_ptr, v_pool_ptr, # [num_pages, H_kv, page_tokens, D(_v)]
|
|
180
|
+
block_table_ptr, # [B, max_pages] int32
|
|
181
|
+
lens_ptr, # [B] int32: the layer's positions held — the step writes at lens[b]
|
|
182
|
+
page_tokens,
|
|
183
|
+
stride_kb, stride_kh, stride_vb, stride_vh,
|
|
184
|
+
stride_kp, stride_kph, stride_ks,
|
|
185
|
+
stride_vp, stride_vph, stride_vs,
|
|
186
|
+
stride_bt,
|
|
187
|
+
BLOCK_D: tl.constexpr,
|
|
188
|
+
D_K: tl.constexpr,
|
|
189
|
+
D_V: tl.constexpr,
|
|
190
|
+
):
|
|
191
|
+
"""A decode step's append with no host input: the position is read from the layer's DEVICE length and its page from
|
|
192
|
+
the block table, so the launch's arguments are the same at every step (a recorded replay replays it unchanged). One
|
|
193
|
+
program per (sequence, KV head); `paged_lens_advance_kernel`, launched after it on the same stream, moves the
|
|
194
|
+
length."""
|
|
195
|
+
pid_b = tl.program_id(0).to(tl.int64)
|
|
196
|
+
pid_h = tl.program_id(1).to(tl.int64)
|
|
197
|
+
pos = tl.load(lens_ptr + pid_b).to(tl.int64)
|
|
198
|
+
page = tl.load(block_table_ptr + pid_b * stride_bt + pos // page_tokens).to(tl.int64)
|
|
199
|
+
slot = pos % page_tokens
|
|
200
|
+
offs = tl.arange(0, BLOCK_D)
|
|
201
|
+
mk = offs < D_K
|
|
202
|
+
mv = offs < D_V
|
|
203
|
+
k = tl.load(k_new_ptr + pid_b * stride_kb + pid_h * stride_kh + offs, mask=mk, other=0.0)
|
|
204
|
+
v = tl.load(v_new_ptr + pid_b * stride_vb + pid_h * stride_vh + offs, mask=mv, other=0.0)
|
|
205
|
+
tl.store(k_pool_ptr + page * stride_kp + pid_h * stride_kph + slot * stride_ks + offs,
|
|
206
|
+
k.to(k_pool_ptr.dtype.element_ty), mask=mk)
|
|
207
|
+
tl.store(v_pool_ptr + page * stride_vp + pid_h * stride_vph + slot * stride_vs + offs,
|
|
208
|
+
v.to(v_pool_ptr.dtype.element_ty), mask=mv)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@triton.jit
|
|
212
|
+
def paged_lens_advance_kernel(lens_ptr, n):
|
|
213
|
+
"""lens[b] += n for every sequence: one program per sequence, after the step's append (stream order)."""
|
|
214
|
+
b = tl.program_id(0).to(tl.int64)
|
|
215
|
+
tl.store(lens_ptr + b, tl.load(lens_ptr + b) + n)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Pixel shuffle (sub-pixel convolution) — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Rearranges: [N, C*r*r, H, W] -> [N, C, H*r, W*r]
|
|
4
|
+
|
|
5
|
+
For each output element (n, c, oh, ow):
|
|
6
|
+
ih = oh // r
|
|
7
|
+
iw = ow // r
|
|
8
|
+
ic = c * (r * r) + (oh % r) * r + (ow % r)
|
|
9
|
+
output[n, c, oh, ow] = input[n, ic, ih, iw]
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import triton
|
|
13
|
+
import triton.language as tl
|
|
14
|
+
|
|
15
|
+
@triton.jit
|
|
16
|
+
def pixel_shuffle_kernel(
|
|
17
|
+
input_ptr,
|
|
18
|
+
output_ptr,
|
|
19
|
+
n_elements,
|
|
20
|
+
# Input shape: [N, C_in, H, W] where C_in = C_out * r * r
|
|
21
|
+
C_out,
|
|
22
|
+
H,
|
|
23
|
+
W,
|
|
24
|
+
r,
|
|
25
|
+
# Output shape: [N, C_out, H*r, W*r]
|
|
26
|
+
OH, # = H * r
|
|
27
|
+
OW, # = W * r
|
|
28
|
+
# Input strides (contiguous NCHW)
|
|
29
|
+
stride_in_n,
|
|
30
|
+
stride_in_c,
|
|
31
|
+
stride_in_h,
|
|
32
|
+
stride_in_w,
|
|
33
|
+
# Output strides (contiguous NCHW)
|
|
34
|
+
stride_out_n,
|
|
35
|
+
stride_out_c,
|
|
36
|
+
stride_out_h,
|
|
37
|
+
stride_out_w,
|
|
38
|
+
BLOCK_SIZE: tl.constexpr,
|
|
39
|
+
):
|
|
40
|
+
"""Pixel shuffle: [N, C*r*r, H, W] -> [N, C, H*r, W*r].
|
|
41
|
+
|
|
42
|
+
Each thread handles one output element.
|
|
43
|
+
"""
|
|
44
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
45
|
+
idx = 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)
|
|
46
|
+
mask = idx < n_elements
|
|
47
|
+
|
|
48
|
+
# Decompose flat output index -> (n, c, oh, ow)
|
|
49
|
+
ow = idx % OW
|
|
50
|
+
tmp = idx // OW
|
|
51
|
+
oh = tmp % OH
|
|
52
|
+
tmp2 = tmp // OH
|
|
53
|
+
c = tmp2 % C_out
|
|
54
|
+
n = tmp2 // C_out
|
|
55
|
+
|
|
56
|
+
# Map output coords to input coords
|
|
57
|
+
ih = oh // r
|
|
58
|
+
iw = ow // r
|
|
59
|
+
# Sub-pixel position within the r x r block
|
|
60
|
+
sub_h = oh % r
|
|
61
|
+
sub_w = ow % r
|
|
62
|
+
ic = c * (r * r) + sub_h * r + sub_w
|
|
63
|
+
|
|
64
|
+
# Load from input, store to output
|
|
65
|
+
in_offset = n * stride_in_n + ic * stride_in_c + ih * stride_in_h + iw * stride_in_w
|
|
66
|
+
out_offset = n * stride_out_n + c * stride_out_c + oh * stride_out_h + ow * stride_out_w
|
|
67
|
+
|
|
68
|
+
val = tl.load(input_ptr + in_offset, mask=mask)
|
|
69
|
+
tl.store(output_ptr + out_offset, val, mask=mask)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@triton.jit
|
|
73
|
+
def pixel_shuffle_broadcast_aware_kernel(
|
|
74
|
+
bcast_view_ptr, # 5D NBX expand-output view (N, C_pre, B, H, W) with stride_b == 0
|
|
75
|
+
output_ptr, # 4D contiguous output (N, C_out, OH, OW)
|
|
76
|
+
n_elements,
|
|
77
|
+
C_out, # output channels = (C_pre * B) // (r*r)
|
|
78
|
+
OH, # = H * r
|
|
79
|
+
OW, # = W * r
|
|
80
|
+
r, # pixel_shuffle upscale factor
|
|
81
|
+
bcast, # broadcast factor (= B, the size on the stride-0 dim)
|
|
82
|
+
# 5D bcast_view strides (NBX expand sets stride_b = 0).
|
|
83
|
+
stride_v_n,
|
|
84
|
+
stride_v_c,
|
|
85
|
+
stride_v_b,
|
|
86
|
+
stride_v_h,
|
|
87
|
+
stride_v_w,
|
|
88
|
+
# 4D output strides (contiguous NCHW).
|
|
89
|
+
stride_out_n,
|
|
90
|
+
stride_out_c,
|
|
91
|
+
stride_out_h,
|
|
92
|
+
stride_out_w,
|
|
93
|
+
BLOCK_SIZE: tl.constexpr,
|
|
94
|
+
):
|
|
95
|
+
"""Pixel shuffle that reads through an unmaterialized NBX broadcast view.
|
|
96
|
+
|
|
97
|
+
Input layout: (N, C_pre, B, H, W) where the original DAG pattern is
|
|
98
|
+
`unsqueeze(dim=2) -> expand(dim=2, B) -> clone -> view -> pixel_shuffle`.
|
|
99
|
+
NBX expand sets stride_b=0 (kernels/nbx_tensor.py:1581), so the 5D view
|
|
100
|
+
aliases the pre-expand tensor with no materialization. We read through
|
|
101
|
+
that view directly and skip the clone's 8 GiB copy.
|
|
102
|
+
|
|
103
|
+
The post-clone-post-view shape consumed by a normal pixel_shuffle is
|
|
104
|
+
(N, C_pre*B, H, W); here the kernel does the equivalent decomposition
|
|
105
|
+
inline:
|
|
106
|
+
ic_view = c*r*r + (oh%r)*r + (ow%r) in [0, C_pre*B)
|
|
107
|
+
c_pre = ic_view // bcast in [0, C_pre)
|
|
108
|
+
b = ic_view % bcast in [0, B)
|
|
109
|
+
ih = oh // r
|
|
110
|
+
iw = ow // r
|
|
111
|
+
Read offset uses all 5D strides; with stride_v_b == 0, the `b` index
|
|
112
|
+
contributes nothing and the load aliases naturally.
|
|
113
|
+
"""
|
|
114
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
115
|
+
# Cast to int64 to support tensors with >= 2^31 elements
|
|
116
|
+
# (e.g. Sana 4Kpx VAE 1*128*4096*4096 = 2^31 — at INT32_MAX
|
|
117
|
+
# boundary; signed int32 multiplication of strides overflows
|
|
118
|
+
# silently and corrupts offsets, producing garbage output).
|
|
119
|
+
idx = 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)
|
|
120
|
+
mask = idx < n_elements
|
|
121
|
+
|
|
122
|
+
ow = idx % OW
|
|
123
|
+
tmp = idx // OW
|
|
124
|
+
oh = tmp % OH
|
|
125
|
+
tmp2 = tmp // OH
|
|
126
|
+
c = tmp2 % C_out
|
|
127
|
+
n = tmp2 // C_out
|
|
128
|
+
|
|
129
|
+
sub_h = oh % r
|
|
130
|
+
sub_w = ow % r
|
|
131
|
+
ic_view = c * (r * r) + sub_h * r + sub_w
|
|
132
|
+
c_pre = ic_view // bcast
|
|
133
|
+
b = ic_view % bcast
|
|
134
|
+
|
|
135
|
+
ih = oh // r
|
|
136
|
+
iw = ow // r
|
|
137
|
+
|
|
138
|
+
in_offset = (n * stride_v_n + c_pre * stride_v_c + b * stride_v_b
|
|
139
|
+
+ ih * stride_v_h + iw * stride_v_w)
|
|
140
|
+
out_offset = (n * stride_out_n + c * stride_out_c
|
|
141
|
+
+ oh * stride_out_h + ow * stride_out_w)
|
|
142
|
+
|
|
143
|
+
val = tl.load(bcast_view_ptr + in_offset, mask=mask)
|
|
144
|
+
tl.store(output_ptr + out_offset, val, mask=mask)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Pixel unshuffle (inverse of pixel shuffle) — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Rearranges: [N, C, H*r, W*r] -> [N, C*r*r, H, W]
|
|
4
|
+
|
|
5
|
+
For each output element (n, oc, oh, ow):
|
|
6
|
+
c = oc // (r * r)
|
|
7
|
+
sub_h = (oc % (r * r)) // r
|
|
8
|
+
sub_w = (oc % (r * r)) % r
|
|
9
|
+
ih = oh * r + sub_h
|
|
10
|
+
iw = ow * r + sub_w
|
|
11
|
+
output[n, oc, oh, ow] = input[n, c, ih, iw]
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import triton
|
|
15
|
+
import triton.language as tl
|
|
16
|
+
|
|
17
|
+
@triton.jit
|
|
18
|
+
def pixel_unshuffle_kernel(
|
|
19
|
+
input_ptr,
|
|
20
|
+
output_ptr,
|
|
21
|
+
n_elements,
|
|
22
|
+
# Input shape: [N, C, IH, IW] where IH = H*r, IW = W*r
|
|
23
|
+
C,
|
|
24
|
+
IH,
|
|
25
|
+
IW,
|
|
26
|
+
r,
|
|
27
|
+
# Output shape: [N, C*r*r, H, W]
|
|
28
|
+
C_out, # = C * r * r
|
|
29
|
+
H, # = IH // r
|
|
30
|
+
W, # = IW // r
|
|
31
|
+
# Input strides (contiguous NCHW)
|
|
32
|
+
stride_in_n,
|
|
33
|
+
stride_in_c,
|
|
34
|
+
stride_in_h,
|
|
35
|
+
stride_in_w,
|
|
36
|
+
# Output strides (contiguous NCHW)
|
|
37
|
+
stride_out_n,
|
|
38
|
+
stride_out_c,
|
|
39
|
+
stride_out_h,
|
|
40
|
+
stride_out_w,
|
|
41
|
+
BLOCK_SIZE: tl.constexpr,
|
|
42
|
+
):
|
|
43
|
+
"""Pixel unshuffle: [N, C, H*r, W*r] -> [N, C*r*r, H, W].
|
|
44
|
+
|
|
45
|
+
Each thread handles one output element.
|
|
46
|
+
"""
|
|
47
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
48
|
+
idx = 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)
|
|
49
|
+
mask = idx < n_elements
|
|
50
|
+
|
|
51
|
+
# Decompose flat output index -> (n, oc, oh, ow)
|
|
52
|
+
ow = idx % W
|
|
53
|
+
tmp = idx // W
|
|
54
|
+
oh = tmp % H
|
|
55
|
+
tmp2 = tmp // H
|
|
56
|
+
oc = tmp2 % C_out
|
|
57
|
+
n = tmp2 // C_out
|
|
58
|
+
|
|
59
|
+
# Map output coords to input coords
|
|
60
|
+
c = oc // (r * r)
|
|
61
|
+
sub_idx = oc % (r * r)
|
|
62
|
+
sub_h = sub_idx // r
|
|
63
|
+
sub_w = sub_idx % r
|
|
64
|
+
ih = oh * r + sub_h
|
|
65
|
+
iw = ow * r + sub_w
|
|
66
|
+
|
|
67
|
+
# Load from input, store to output
|
|
68
|
+
in_offset = n * stride_in_n + c * stride_in_c + ih * stride_in_h + iw * stride_in_w
|
|
69
|
+
out_offset = n * stride_out_n + oc * stride_out_c + oh * stride_out_h + ow * stride_out_w
|
|
70
|
+
|
|
71
|
+
val = tl.load(input_ptr + in_offset, mask=mask)
|
|
72
|
+
tl.store(output_ptr + out_offset, val, mask=mask)
|
nbxtensor/ops/pow.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Power — pure @triton.jit kernel."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
|
|
6
|
+
@triton.jit
|
|
7
|
+
def pow_forward_kernel(
|
|
8
|
+
input_ptr, output_ptr,
|
|
9
|
+
n_elements,
|
|
10
|
+
exponent,
|
|
11
|
+
BLOCK_SIZE: tl.constexpr,
|
|
12
|
+
INT_EXP: tl.constexpr = 0,
|
|
13
|
+
):
|
|
14
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
15
|
+
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)
|
|
16
|
+
mask = offset < n_elements
|
|
17
|
+
|
|
18
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
19
|
+
x_fp32 = x.to(tl.float32)
|
|
20
|
+
# `tl.extra.CUDA.libdevice.pow` used to stand here. It is NVIDIA's device
|
|
21
|
+
# library, and asking any other backend to lower it fails at compile time —
|
|
22
|
+
# measured on triton-ext, Kokoro-82M dying at aten.pow::0 (1, 55, 2048) with
|
|
23
|
+
# "PassManager::run failed", and reproduced with a bare kernel containing no
|
|
24
|
+
# engine code (repro_pow_libdevice_cuda_namespace.py):
|
|
25
|
+
# tl.extra.cuda.libdevice.pow FAILED PassManager::run failed
|
|
26
|
+
# portable exp/log/sign COMPILED max rel err 2.522e-07
|
|
27
|
+
# Triton 3.8.0 exposes no backend-neutral `pow`, so the semantics are built
|
|
28
|
+
# here rather than borrowed from one vendor.
|
|
29
|
+
#
|
|
30
|
+
# libm semantics, which is what the libdevice call was chosen for:
|
|
31
|
+
# x > 0 exp(e * ln x)
|
|
32
|
+
# x < 0, e an integer sign * exp(e * ln|x|), sign = -1 when e is odd
|
|
33
|
+
# x < 0, e not an integer NaN (no real root)
|
|
34
|
+
# x = +-0 the THREE cases below, not one
|
|
35
|
+
#
|
|
36
|
+
# The zero lane read `0.0` for every exponent until 2026-09-19. That is
|
|
37
|
+
# wrong for two thirds of it, and the Dell measured it on CUDA against an
|
|
38
|
+
# fp64 oracle: `0.0 ** 0.0` gave 0.0 where the oracle gives 1.0, and
|
|
39
|
+
# `0.0 ** -1.0` gave 0.0 where it gives inf. Mine, and the shape of the
|
|
40
|
+
# mistake is worth naming: replacing a vendor intrinsic means reimplementing
|
|
41
|
+
# its whole DOMAIN, and I reimplemented the interesting part and not the
|
|
42
|
+
# edges. C99 / IEEE-754 pow at zero:
|
|
43
|
+
#
|
|
44
|
+
# pow(+-0, 0) 1 (even 0**0, and even nan**0)
|
|
45
|
+
# pow(+-0, e) e > 0 +-0, negative only for -0 with e an odd int
|
|
46
|
+
# pow(+-0, e) e < 0 +-inf, same sign rule
|
|
47
|
+
#
|
|
48
|
+
# The sign of the zero has to be read from the SIGN BIT, because `x < 0` is
|
|
49
|
+
# false for -0.0 and `x == 0` cannot tell the two apart. A bitcast to int32
|
|
50
|
+
# is negative exactly when the sign bit is set, which covers -0.0.
|
|
51
|
+
_ax = tl.abs(x_fp32)
|
|
52
|
+
_mag = tl.exp(exponent * tl.log(_ax))
|
|
53
|
+
_is_int = tl.floor(exponent) == exponent
|
|
54
|
+
_is_odd = _is_int & ((tl.floor(exponent * 0.5) * 2.0) != exponent)
|
|
55
|
+
_sign = tl.where(_is_odd, -1.0, 1.0)
|
|
56
|
+
_neg = tl.where(_is_int, _sign * _mag, float("nan"))
|
|
57
|
+
|
|
58
|
+
_signbit = x_fp32.to(tl.int32, bitcast=True) < 0 # true for -0.0 too
|
|
59
|
+
_zsign = tl.where(_signbit & _is_odd, -1.0, 1.0)
|
|
60
|
+
_zero = tl.where(exponent == 0.0, 1.0,
|
|
61
|
+
tl.where(exponent > 0.0,
|
|
62
|
+
_zsign * 0.0, # +-0
|
|
63
|
+
_zsign * float("inf"))) # +-inf
|
|
64
|
+
out = tl.where(x_fp32 > 0, _mag, tl.where(x_fp32 < 0, _neg, _zero))
|
|
65
|
+
# THE EXACT ROUTE FOR A SMALL INTEGER EXPONENT — the branch the comment
|
|
66
|
+
# below records as "available rather than taken". Taken on 2026-09-20,
|
|
67
|
+
# because the Dell measured the general route on CUDA against the fp64
|
|
68
|
+
# oracle and it is not a rounding footnote: x**2 lands up to 15 ulps from
|
|
69
|
+
# the oracle where main's libdevice call sat within 2 (38 147 lanes, |x| a
|
|
70
|
+
# standard normal with zero lanes), x**0.5 at 5 vs 2, x**-1 at 7 vs 2. A
|
|
71
|
+
# square computed as exp(2 log|x|) is not a square. Repeated multiplication
|
|
72
|
+
# is correctly rounded at e=2 and within two roundings at e=3, needs no
|
|
73
|
+
# vendor library, and carries IEEE's own sign rules for free: (-0)**2 is
|
|
74
|
+
# +0, (-0)**3 is -0, 1/(+-0) is +-inf, so none of the select logic above is
|
|
75
|
+
# needed on this route. The wrapper sets INT_EXP when the exponent is an
|
|
76
|
+
# integer-valued scalar with 1 <= |e| <= 8; INT_EXP == 0 keeps the general
|
|
77
|
+
# route, which still owns e == 0, non-integers and large integers.
|
|
78
|
+
if INT_EXP != 0:
|
|
79
|
+
_n: tl.constexpr = INT_EXP if INT_EXP > 0 else -INT_EXP
|
|
80
|
+
_p = x_fp32
|
|
81
|
+
if _n >= 2:
|
|
82
|
+
_p = _p * x_fp32
|
|
83
|
+
if _n >= 3:
|
|
84
|
+
_p = _p * x_fp32
|
|
85
|
+
if _n >= 4:
|
|
86
|
+
_p = _p * x_fp32
|
|
87
|
+
if _n >= 5:
|
|
88
|
+
_p = _p * x_fp32
|
|
89
|
+
if _n >= 6:
|
|
90
|
+
_p = _p * x_fp32
|
|
91
|
+
if _n >= 7:
|
|
92
|
+
_p = _p * x_fp32
|
|
93
|
+
if _n >= 8:
|
|
94
|
+
_p = _p * x_fp32
|
|
95
|
+
if INT_EXP < 0:
|
|
96
|
+
_p = 1.0 / _p
|
|
97
|
+
out = _p
|
|
98
|
+
# ---------------------------------------------------------------------
|
|
99
|
+
# THE ACCURACY COST OF THIS ROUTE, AND WHAT IT IS AND IS NOT INHERENT TO.
|
|
100
|
+
#
|
|
101
|
+
# `exp(e * log|x|)` loses precision as |x| moves away from 1, because the
|
|
102
|
+
# error in `log|x|` is multiplied by `e` before `exp` amplifies it. The Dell
|
|
103
|
+
# measured pow-to-the-third at 19x worse relative error than main's
|
|
104
|
+
# libdevice call on CUDA. Measured here against fp64, on the ranges a
|
|
105
|
+
# vocoder actually sees (probe_pow_error.py):
|
|
106
|
+
#
|
|
107
|
+
# range e this kernel x*x(*x) ratio
|
|
108
|
+
# |x| in [0.5, 2] 2 2.56e-07 5.94e-08 4.3x
|
|
109
|
+
# |x| in [0.5, 2] 3 3.95e-07 1.14e-07 3.4x
|
|
110
|
+
# |x| in [1e-3, 1e3] 3 3.58e-06 1.15e-07 31x
|
|
111
|
+
# |x| in [1e-6, 1e-3] 3 7.24e-06 1.13e-07 64x
|
|
112
|
+
#
|
|
113
|
+
# So the 19x is real and it is range-dependent: 3.4x on typical activations,
|
|
114
|
+
# 64x on small ones.
|
|
115
|
+
#
|
|
116
|
+
# WHAT IT COSTS ON THE MODELS THAT USE IT. `aten::pow` appears 194 times
|
|
117
|
+
# across the 16 cached containers, and only in the two vocoder TTS models:
|
|
118
|
+
# exponent 2 182x Kokoro-82M 49, chatterbox 133
|
|
119
|
+
# exponent 3.0 12x Kokoro-82M only
|
|
120
|
+
# Nothing else in the catalogue calls pow at all, and no site uses a
|
|
121
|
+
# non-integer exponent. Both models are judged by third-party ASR and both
|
|
122
|
+
# transcribe correctly; the error is ~1e-7 relative on their activation
|
|
123
|
+
# range, which is below fp32 round-off for the accumulations downstream of
|
|
124
|
+
# it. So the cost TODAY is not audible and not measurable at the artefact.
|
|
125
|
+
#
|
|
126
|
+
# BUT IT IS AVOIDABLE, and that is the part worth recording rather than
|
|
127
|
+
# accepting: the cost is inherent to `exp(e * log|x|)`, NOT to `pow`. Every
|
|
128
|
+
# exponent the catalogue uses is a small integer, and for those, repeated
|
|
129
|
+
# multiplication is 3-64x MORE accurate and no more expensive — one
|
|
130
|
+
# multiply for e=2 against a log, a multiply and an exp. A `POW_SMALL_INT`
|
|
131
|
+
# constexpr branch would take the exact route at compile time and leave this
|
|
132
|
+
# path for the general case.
|
|
133
|
+
#
|
|
134
|
+
# It is NOT done here, deliberately: it changes the numerics of two SHIPPED
|
|
135
|
+
# models, so it owes a fresh set of judged cells and a fresh CUDA column,
|
|
136
|
+
# and 0.5.4 has just been cut. Recorded as available rather than taken.
|
|
137
|
+
tl.store(output_ptr + offset, out, mask=mask)
|
nbxtensor/ops/prod.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Product reduction — pure @triton.jit kernels.
|
|
2
|
+
|
|
3
|
+
Global reduction via two-pass (mid → result) and dim-specific reduction.
|
|
4
|
+
Extracted from FlagGems (Apache-2.0).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import triton
|
|
8
|
+
import triton.language as tl
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@triton.jit
|
|
12
|
+
def reduce_mul(a, b):
|
|
13
|
+
return a * b
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# Global reduction: two-pass partial products
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
@triton.jit
|
|
21
|
+
def prod_kernel_mid(
|
|
22
|
+
inp,
|
|
23
|
+
mid,
|
|
24
|
+
M,
|
|
25
|
+
BLOCK_SIZE: tl.constexpr,
|
|
26
|
+
):
|
|
27
|
+
"""Pass 1: each program computes a partial product over its BLOCK_SIZE chunk."""
|
|
28
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
29
|
+
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)
|
|
30
|
+
inp_ptrs = inp + offset
|
|
31
|
+
mask = offset < M
|
|
32
|
+
inp_val = tl.load(inp_ptrs, mask=mask, other=1.0).to(tl.float32)
|
|
33
|
+
mid_value = tl.reduce(inp_val, axis=0, combine_fn=reduce_mul)
|
|
34
|
+
mid_ptr = mid + pid
|
|
35
|
+
tl.store(mid_ptr, mid_value.to(inp_val.dtype))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@triton.jit
|
|
39
|
+
def prod_kernel_result(mid, out, mid_size, BLOCK_MID: tl.constexpr):
|
|
40
|
+
"""Pass 2: reduce all partial products into a single scalar."""
|
|
41
|
+
offset = tl.arange(0, BLOCK_MID)
|
|
42
|
+
mid_ptrs = mid + offset
|
|
43
|
+
mask = offset < mid_size
|
|
44
|
+
mid_val = tl.load(mid_ptrs, mask=mask, other=1.0).to(tl.float32)
|
|
45
|
+
prod_val = tl.reduce(mid_val, axis=0, combine_fn=reduce_mul)
|
|
46
|
+
tl.store(out, prod_val)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# Dim-specific reduction: product along one axis
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
@triton.jit
|
|
54
|
+
def prod_kernel(
|
|
55
|
+
inp,
|
|
56
|
+
out,
|
|
57
|
+
M,
|
|
58
|
+
N,
|
|
59
|
+
BLOCK_M: tl.constexpr,
|
|
60
|
+
BLOCK_N: tl.constexpr,
|
|
61
|
+
):
|
|
62
|
+
"""Reduce product along the compressed inner dimension N.
|
|
63
|
+
|
|
64
|
+
inp: [M, N] (dim-compressed) → out: [M]
|
|
65
|
+
"""
|
|
66
|
+
pid_m = tl.program_id(0).to(tl.int64)
|
|
67
|
+
m_offset = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
|
|
68
|
+
|
|
69
|
+
acc = tl.full((BLOCK_M, BLOCK_N), value=1.0, dtype=tl.float32)
|
|
70
|
+
for start_n in range(0, N, BLOCK_N):
|
|
71
|
+
n_offset = start_n + tl.arange(0, BLOCK_N)
|
|
72
|
+
offset = m_offset[:, None] * N + n_offset[None, :]
|
|
73
|
+
mask = (m_offset[:, None] < M) & (n_offset[None, :] < N)
|
|
74
|
+
inp_ptrs = inp + offset
|
|
75
|
+
inp_vals = tl.load(inp_ptrs, mask=mask, other=1.0).to(tl.float32)
|
|
76
|
+
acc *= inp_vals
|
|
77
|
+
result = tl.reduce(acc, axis=1, combine_fn=reduce_mul)
|
|
78
|
+
|
|
79
|
+
out_ptrs = out + m_offset
|
|
80
|
+
mask1 = m_offset < M
|
|
81
|
+
tl.store(out_ptrs, result, mask=mask1)
|