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,370 @@
|
|
|
1
|
+
"""The TilingEngine's sizing half — torch-free, imported by Prism, the launch keys, the Triton
|
|
2
|
+
wrappers and the TilingEngine's executor.
|
|
3
|
+
|
|
4
|
+
One module for every caller: the Triton branch (the wrappers, the launch keys and the derived census through them)
|
|
5
|
+
and NeuroBrix's PyTorch branch (Prism, the core executors and the TilingEngine) alike — the engine keeps no copy.
|
|
6
|
+
|
|
7
|
+
Every size a memory split is cut by is computed here, from the library's `nbxtensor/ops/tiling.yml` (engine-wide
|
|
8
|
+
values, each with its source) and from the values the caller reads in the active hardware
|
|
9
|
+
profile (per-architecture values stay there). Pure arithmetic: Prism prices with it at plan time,
|
|
10
|
+
the launch keys and the census derive with it, the wrappers and the executor cut with it — one
|
|
11
|
+
definition each, so the plan, the key and the run agree.
|
|
12
|
+
|
|
13
|
+
What it owns:
|
|
14
|
+
* the conv3d one-shot peak and its temporal chunk (a PLAN decision: `need` against the op
|
|
15
|
+
budget; the wrapper sizes the chunks with the same function);
|
|
16
|
+
* the conv2d band cut;
|
|
17
|
+
* the attention scores bound and its chunk rows;
|
|
18
|
+
* Prism's op budget and its op-level band factors (conv, fusion pair, rms_norm, residual chain);
|
|
19
|
+
* the tile overlaps (spatial and temporal);
|
|
20
|
+
* the in-place and residual-chain thresholds;
|
|
21
|
+
* the paged decode read's position tiles (the page size is sized from them, core/state/price).
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import math
|
|
26
|
+
from typing import Any, Sequence, Tuple
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# The position tiles the paged read is compiled for: powers of two, each dividing the largest, so a page sized from the
|
|
30
|
+
# largest holds a whole number of tiles of every candidate. 32 is the vector decode kernel's judged tile (its locked
|
|
31
|
+
# protocol of 2026-08-23: seg 256 / BLOCK_N 32 / 4 warps won at all three contexts, ops/decode_attn_vec.py,
|
|
32
|
+
# kernels/wrappers.py `_decode_attn_vec`); 16 and 64 are its neighbours, the range the certified directory chooses in.
|
|
33
|
+
PAGED_DECODE_TILE_CANDIDATES = (16, 32, 64)
|
|
34
|
+
|
|
35
|
+
def _value(*path: str) -> Any:
|
|
36
|
+
"""One value of nbxtensor/ops/tiling.yml, refused by name when absent."""
|
|
37
|
+
from nbxtensor.ops.tiling_policy import tiling_table # the policy in force (nbxtensor.ops.tiling_policy.set_tiling)
|
|
38
|
+
node: Any = tiling_table()
|
|
39
|
+
for i, key in enumerate(path):
|
|
40
|
+
if not isinstance(node, dict) or key not in node:
|
|
41
|
+
raise KeyError(f"ZERO FALLBACK: nbxtensor/ops/tiling.yml states no {'.'.join(path[:i + 1])}")
|
|
42
|
+
node = node[key]
|
|
43
|
+
return node
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _pow2_at_most(factor: int, cap: int) -> int:
|
|
47
|
+
"""The smallest power of two >= `factor`, at most `cap` (a band factor aligned for halos)."""
|
|
48
|
+
p = 1
|
|
49
|
+
while p < factor and p < cap:
|
|
50
|
+
p *= 2
|
|
51
|
+
return p
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# --- conv3d: the one-shot peak and the temporal chunk ---------------------------------------------
|
|
55
|
+
|
|
56
|
+
def conv3d_chunk_bytes() -> int:
|
|
57
|
+
"""The per-transient bound of the chunked conv3d path (`conv3d.chunk_bytes`)."""
|
|
58
|
+
return int(_value("conv3d", "chunk_bytes"))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def conv2d_band_bytes() -> int:
|
|
62
|
+
"""The conv2d band-streaming threshold (`conv2d.band_bytes`)."""
|
|
63
|
+
return int(_value("conv2d", "band_bytes"))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _triple(v) -> Tuple[int, int, int]:
|
|
67
|
+
if isinstance(v, (list, tuple)):
|
|
68
|
+
v = list(v) + [v[-1]] * (3 - len(v))
|
|
69
|
+
return int(v[0]), int(v[1]), int(v[2])
|
|
70
|
+
return int(v), int(v), int(v)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def conv3d_need(x_shape: Sequence[int], w_shape: Sequence[int], stride, padding, dilation,
|
|
74
|
+
in_bytes: int, out_bytes: int) -> Tuple[int, int]:
|
|
75
|
+
"""(bytes the ONE-SHOT path holds at its peak, folded bytes per output frame). `need` is 0 when
|
|
76
|
+
no fold exceeds `conv3d_chunk_bytes()` — the one-shot path is taken whatever the budget. The
|
|
77
|
+
peak: the temporal pad copy, then the larger of (input fold + output fold [+ a band when the
|
|
78
|
+
output bands] [+ the kt accumulator]) and (two output folds [+ the accumulator]), and a fixed
|
|
79
|
+
slack (`conv3d.slack_bytes`). The wrapper runs conv3d as kt conv2d launches over the folded
|
|
80
|
+
batch axis (B*T_out frames); whether it chunks is Prism's decision, made from this `need`
|
|
81
|
+
against the op budget (decision 2, 2026-09-29: never from the driver's free bytes)."""
|
|
82
|
+
B, Cin, T, H, W = (int(d) for d in x_shape)
|
|
83
|
+
Cout, _Cg, kt, kh, kw = (int(d) for d in w_shape)
|
|
84
|
+
st, sh, sw = _triple(stride)
|
|
85
|
+
pt, ph, pw = _triple(padding)
|
|
86
|
+
dt, dh, dw = _triple(dilation)
|
|
87
|
+
t_out = (T + 2 * pt - dt * (kt - 1) - 1) // st + 1
|
|
88
|
+
if t_out <= 0:
|
|
89
|
+
return 0, 0
|
|
90
|
+
oh = (H + 2 * ph - dh * (kh - 1) - 1) // sh + 1
|
|
91
|
+
ow = (W + 2 * pw - dw * (kw - 1) - 1) // sw + 1
|
|
92
|
+
fold_in = B * t_out * Cin * H * W * in_bytes
|
|
93
|
+
fold_out = B * t_out * Cout * oh * ow * out_bytes
|
|
94
|
+
frame = max(fold_in, fold_out) // max(1, t_out)
|
|
95
|
+
if max(fold_in, fold_out) <= conv3d_chunk_bytes():
|
|
96
|
+
return 0, frame
|
|
97
|
+
return conv3d_one_shot_peak(x_shape, w_shape, stride, padding, dilation, in_bytes, out_bytes), frame
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def conv3d_one_shot_peak(x_shape: Sequence[int], w_shape: Sequence[int], stride, padding, dilation,
|
|
101
|
+
in_bytes: int, out_bytes: int) -> int:
|
|
102
|
+
"""The bytes the one-shot conv3d path (`_conv3d_via_conv2d`) holds at its peak BESIDE its input: the temporal pad
|
|
103
|
+
copy, then the larger of (input fold + output fold [+ a band] [+ the kt accumulator]) and (two output folds [+ the
|
|
104
|
+
accumulator]), and the slack. Its result is among them (one output fold). `conv3d_need` decides the chunking from
|
|
105
|
+
it; Prism's activation profile prices the op's in-op figure from it on the Triton engines — measured 2026-10-17 on
|
|
106
|
+
Wan2.1-T2V's VAE: 5,276 MB beside a 1,755 MB output, the formula's 3 x 1,755."""
|
|
107
|
+
B, Cin, T, H, W = (int(d) for d in x_shape)
|
|
108
|
+
Cout, _Cg, kt, kh, kw = (int(d) for d in w_shape)
|
|
109
|
+
st, sh, sw = _triple(stride)
|
|
110
|
+
pt, ph, pw = _triple(padding)
|
|
111
|
+
dt, dh, dw = _triple(dilation)
|
|
112
|
+
t_out = (T + 2 * pt - dt * (kt - 1) - 1) // st + 1
|
|
113
|
+
if t_out <= 0:
|
|
114
|
+
return 0
|
|
115
|
+
oh = (H + 2 * ph - dh * (kh - 1) - 1) // sh + 1
|
|
116
|
+
ow = (W + 2 * pw - dw * (kw - 1) - 1) // sw + 1
|
|
117
|
+
fold_in = B * t_out * Cin * H * W * in_bytes
|
|
118
|
+
fold_out = B * t_out * Cout * oh * ow * out_bytes
|
|
119
|
+
band = conv2d_band_bytes()
|
|
120
|
+
pad_bytes = B * Cin * (T + 2 * pt) * H * W * in_bytes if pt > 0 else 0
|
|
121
|
+
band_extra = band if fold_out > band else 0
|
|
122
|
+
acc_extra = fold_out if kt >= 2 else 0
|
|
123
|
+
return (pad_bytes + max(fold_out + fold_in + band_extra + acc_extra, 2 * fold_out + acc_extra)
|
|
124
|
+
+ int(_value("conv3d", "slack_bytes")))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def residual_chain_bands(H: int, tile_factor: int, halo: int):
|
|
128
|
+
"""The bands a band-streamed residual chain runs over its spatial axis: [(h_start, h_end, h_in_start, h_in_end)]
|
|
129
|
+
— the output rows of each band and the input rows it reads (the band plus `halo` rows each side, cut at the
|
|
130
|
+
edges). ONE function for the chain that runs them (`kernels/ops/residual_chain.py`) and the census that records
|
|
131
|
+
the conv keys they form (a 418-row band of Sana 4K's VAE met no certified key, 2026-10-17): the recorded key is
|
|
132
|
+
the launcher's key."""
|
|
133
|
+
H, tile_factor, halo = int(H), max(1, int(tile_factor)), int(halo)
|
|
134
|
+
band_h = (H + tile_factor - 1) // tile_factor
|
|
135
|
+
bands = []
|
|
136
|
+
for i in range(tile_factor):
|
|
137
|
+
h_start, h_end = i * band_h, min((i + 1) * band_h, H)
|
|
138
|
+
if h_start >= h_end:
|
|
139
|
+
break
|
|
140
|
+
bands.append((h_start, h_end, max(0, h_start - halo), min(H, h_end + halo)))
|
|
141
|
+
return bands
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def chunk_frames(frame_bytes: int) -> int:
|
|
145
|
+
"""Output frames per chunk: each chunk's folded transient under `conv3d_chunk_bytes()`."""
|
|
146
|
+
return max(1, int(conv3d_chunk_bytes() // max(1, frame_bytes)))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# --- conv2d: the band cut -------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
def conv2d_band_rows(N: int, out_c: int, out_h: int, out_w: int, out_bytes: int,
|
|
152
|
+
band_bytes: int) -> Tuple[int, int]:
|
|
153
|
+
"""(output rows of one band, bytes of one output row) of conv2d band streaming: bands of
|
|
154
|
+
about `band_bytes / conv2d.band_budget_divisor`, evenly cut. A band >= `out_h` means one row
|
|
155
|
+
is already over the budget — the caller refuses it."""
|
|
156
|
+
band_target = max(1, band_bytes // int(_value("conv2d", "band_budget_divisor")))
|
|
157
|
+
row_bytes = N * out_c * out_w * out_bytes
|
|
158
|
+
rows_per_band = max(1, band_target // max(1, row_bytes))
|
|
159
|
+
tile_factor = max(1, (out_h + rows_per_band - 1) // rows_per_band)
|
|
160
|
+
return (out_h + tile_factor - 1) // tile_factor, row_bytes
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def tiled_conv2d_bands(IH: int, out_h: int, kh: int, sh: int, dh: int, pad_h: int, tile_factor: int):
|
|
164
|
+
"""The bands of Prism's op-level tiled conv (real halo): per band (oh_start, oh_end, in_start, in_end, pad_top,
|
|
165
|
+
pad_bot, skip) — the output rows, the input rows read (clamped), the image-edge padding added, the band's leading
|
|
166
|
+
conv rows to skip. ONE cut for the run (`nbxtensor/ops/fused_upsample_conv.tiled_conv2d` and its torch
|
|
167
|
+
twin), the derived census (`launch_keys.tiled_conv2d_launches`) and Prism's price of the band
|
|
168
|
+
(`tiled_conv2d_band_bytes`). The image-edge padding is `max(0, -read_start)` alone: `read_start` already carries
|
|
169
|
+
-pad_h (P-NBX-TILED-CONV2D-SMALL-SCALE 2026-05-14). The halo is rounded up to a whole number of strides: the band's
|
|
170
|
+
first conv row must be an output row (at stride 2 a 1-row halo misaligned every internal band, 2026-09-29)."""
|
|
171
|
+
tf = max(1, int(tile_factor))
|
|
172
|
+
band_oh = (out_h + tf - 1) // tf
|
|
173
|
+
halo_h = -(-((kh - 1) * dh // 2) // sh) * sh
|
|
174
|
+
bands = []
|
|
175
|
+
for oh_start in range(0, out_h, band_oh):
|
|
176
|
+
oh_end = min(oh_start + band_oh, out_h)
|
|
177
|
+
halo_top = 0 if oh_start == 0 else halo_h
|
|
178
|
+
halo_bot = 0 if oh_end == out_h else halo_h
|
|
179
|
+
read_start = oh_start * sh - pad_h - halo_top
|
|
180
|
+
read_end = (oh_end - 1) * sh + dh * (kh - 1) + 1 - pad_h + halo_bot
|
|
181
|
+
start, end = max(0, read_start), min(IH, read_end)
|
|
182
|
+
if end <= start:
|
|
183
|
+
continue
|
|
184
|
+
bands.append((oh_start, oh_end, start, end, max(0, -read_start), max(0, read_end - IH),
|
|
185
|
+
halo_top // sh))
|
|
186
|
+
return bands
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def tiled_conv2d_band_bytes(x_shape: Sequence[int], w_shape: Sequence[int], stride, padding, dilation,
|
|
190
|
+
tile_factor: int, in_bytes: int, out_bytes: int) -> int:
|
|
191
|
+
"""What a tiled 2-D conv holds BESIDE its input and its whole output while its largest band runs: the band's input
|
|
192
|
+
rows copied into one buffer at the conv's compute width `in_bytes` (padded by the image-edge rows, and by pad_w on
|
|
193
|
+
both sides whenever any padding applies — one buffer, the slice copied into its interior) and the band's output at
|
|
194
|
+
`out_bytes`, before it is copied into the whole output (`kernels/ops/fused_upsample_conv._tiled_conv2d_spatial_*`,
|
|
195
|
+
both engines). Unpriced, HAT-L x4 at 2048 px on a 16 GB card ran out of memory asking a band's 482 MiB beside the
|
|
196
|
+
1 446 MiB band input (2026-10-22)."""
|
|
197
|
+
N, C, IH, IW = (int(v) for v in x_shape)
|
|
198
|
+
O, _cg, kh, kw = (int(v) for v in w_shape)
|
|
199
|
+
sh, sw = _pair(stride)
|
|
200
|
+
ph, pw = _pair(padding)
|
|
201
|
+
dh, dw = _pair(dilation)
|
|
202
|
+
out_h = (IH + 2 * ph - dh * (kh - 1) - 1) // sh + 1
|
|
203
|
+
out_w = (IW + 2 * pw - dw * (kw - 1) - 1) // sw + 1
|
|
204
|
+
worst = 0
|
|
205
|
+
for o0, o1, start, end, pt, pb, _skip in tiled_conv2d_bands(IH, out_h, kh, sh, dh, ph, tile_factor):
|
|
206
|
+
padded = pt > 0 or pb > 0 or pw > 0
|
|
207
|
+
rows_in = end - start + pt + pb
|
|
208
|
+
width_in = IW + 2 * pw if padded else IW
|
|
209
|
+
worst = max(worst, N * C * rows_in * width_in * int(in_bytes) + N * O * (o1 - o0) * out_w * int(out_bytes))
|
|
210
|
+
return worst
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _pair(v) -> Tuple[int, int]:
|
|
214
|
+
if isinstance(v, (list, tuple)):
|
|
215
|
+
return (int(v[0]), int(v[-1]))
|
|
216
|
+
return (int(v), int(v))
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# --- attention: the scores bound, the chunk rows, the device budget -------------------------------
|
|
220
|
+
|
|
221
|
+
#: The math route's scores are fp32: four bytes a score (the width of the dtype, not a size choice).
|
|
222
|
+
_SCORES_BYTES = 4
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def sdpa_scores_bytes(batch: int, nheads: int, Tq: int, Tk: int) -> int:
|
|
226
|
+
"""The bytes of an attention's fp32 scores."""
|
|
227
|
+
return batch * nheads * Tq * Tk * _SCORES_BYTES
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def sdpa_scores_bound(budget_bytes: int, head_dim_pow2: bool) -> int:
|
|
231
|
+
"""The fp32 scores bound an attention routes on: the device's budget; a non-power-of-two head
|
|
232
|
+
dim whose architecture declares none routes on `sdpa.non_pow2_head_scores_bytes`."""
|
|
233
|
+
if head_dim_pow2:
|
|
234
|
+
return budget_bytes
|
|
235
|
+
return budget_bytes or int(_value("sdpa", "non_pow2_head_scores_bytes"))
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def sdpa_chunk_rows(bound: int, batch: int, nheads: int, Tq: int, Tk: int,
|
|
239
|
+
min_chunk_rows: int, max_chunks: int) -> int:
|
|
240
|
+
"""Query rows per chunk that keep each chunk's fp32 scores within `bound`, aligned to the
|
|
241
|
+
arch's row block (`memory.sdpa_math_min_chunk_rows`); 0 when the shape cannot chunk within
|
|
242
|
+
the arch's chunk ceiling (`memory.sdpa_math_max_chunks`)."""
|
|
243
|
+
if not min_chunk_rows:
|
|
244
|
+
return 0
|
|
245
|
+
rows = (bound // (batch * nheads * Tk * _SCORES_BYTES)) // min_chunk_rows * min_chunk_rows
|
|
246
|
+
if rows >= min_chunk_rows and -(-Tq // rows) <= max_chunks:
|
|
247
|
+
return rows
|
|
248
|
+
return 0
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def sdpa_chunk_plan(bound: int, batch: int, nheads: int, Tq: int, Tk: int,
|
|
252
|
+
min_chunk_rows: int) -> Tuple[int, int]:
|
|
253
|
+
"""(slices per chunk, query rows per chunk) that keep each chunk's fp32 scores within `bound`: the rows as many as
|
|
254
|
+
fit with one (batch, head) slice, a multiple of the arch's row block (`memory.sdpa_math_min_chunk_rows`) or the
|
|
255
|
+
whole query; then as many slices as fit beside them. Every slice and every query row is its own softmax, so any
|
|
256
|
+
such cut is exact. (batch x nheads, Tq) when the whole scores fit; (0, 0) when one slice of one row block exceeds
|
|
257
|
+
`bound`."""
|
|
258
|
+
slices = batch * nheads
|
|
259
|
+
if sdpa_scores_bytes(batch, nheads, Tq, Tk) <= bound:
|
|
260
|
+
return slices, Tq
|
|
261
|
+
per_row = Tk * _SCORES_BYTES
|
|
262
|
+
rows = min(Tq, (bound // per_row) // min_chunk_rows * min_chunk_rows) if min_chunk_rows else 0
|
|
263
|
+
if rows <= 0:
|
|
264
|
+
return 0, 0
|
|
265
|
+
return max(1, min(slices, bound // (rows * per_row))), rows
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def sdpa_device_scores_budget(base_bytes: int, device_fraction: float, device_memory_mb) -> int:
|
|
269
|
+
"""The scores budget on one device: min(the arch's byte budget, `device_fraction` of THIS
|
|
270
|
+
device's memory) — the 2026-08-31 xlong-prefill OOM proved a per-arch byte budget alone is
|
|
271
|
+
wrong on heterogeneous rigs. No fraction, or no device memory known: the arch's budget."""
|
|
272
|
+
if not base_bytes:
|
|
273
|
+
return 0
|
|
274
|
+
if not device_fraction or device_memory_mb is None:
|
|
275
|
+
return base_bytes
|
|
276
|
+
return min(base_bytes, int(device_memory_mb * 1024 * 1024 * device_fraction))
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def sdpa_math_scores_budget(base_bytes: int, device_fraction: float, device_memory_mb) -> int:
|
|
280
|
+
"""The math route's scores budget on the device that executes an attention: 0 where the arch states no byte budget
|
|
281
|
+
(`memory.sdpa_math_max_scores_bytes`: a power-of-two head dim never routes on size), the arch's budget where it
|
|
282
|
+
states no device fraction (`memory.sdpa_math_scores_device_fraction`), else `sdpa_device_scores_budget`. ONE rule
|
|
283
|
+
for the wrappers' route (`wrappers._sdpa_math_scores_budget_bytes_for`) and the scratch Prism prices for it."""
|
|
284
|
+
if not base_bytes:
|
|
285
|
+
return 0
|
|
286
|
+
if not device_fraction:
|
|
287
|
+
return base_bytes
|
|
288
|
+
return sdpa_device_scores_budget(base_bytes, device_fraction, device_memory_mb)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# --- Prism: the op budget and the op-level band factors -------------------------------------------
|
|
292
|
+
|
|
293
|
+
def op_budget_fraction() -> float:
|
|
294
|
+
"""The fraction of a card an op's transient is measured against (`op_level.op_budget_card_fraction`)."""
|
|
295
|
+
return float(_value("op_level", "op_budget_card_fraction"))
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def op_budget_bytes(card_bytes: int, resident_bytes: int) -> int:
|
|
299
|
+
"""An op's budget on its card: the op budget fraction of the card minus the component's
|
|
300
|
+
resident weights."""
|
|
301
|
+
return int(card_bytes * op_budget_fraction()) - resident_bytes
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def conv_band_factor(total_bytes: int, kept_bytes: int, card_bytes: int) -> int:
|
|
305
|
+
"""Bands an overflowing convolution (or upsample->conv pair) runs in: its tiled bytes over
|
|
306
|
+
one band's budget (`conv_band_card_fraction` of the card minus the output kept whole, at
|
|
307
|
+
least `band_budget_floor_bytes`), at least `min_band_factor`, rounded up to a power of two
|
|
308
|
+
at most `max_band_factor`."""
|
|
309
|
+
budget = int(float(_value("op_level", "conv_band_card_fraction")) * card_bytes) - kept_bytes
|
|
310
|
+
budget = max(budget, int(_value("op_level", "band_budget_floor_bytes")))
|
|
311
|
+
factor = max(int(_value("op_level", "min_band_factor")), math.ceil(total_bytes / budget))
|
|
312
|
+
return _pow2_at_most(factor, int(_value("op_level", "max_band_factor")))
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def upsample_overflows(out_bytes: int, card_bytes: int) -> bool:
|
|
316
|
+
"""An upsample output worth fusing into its conv even when the conv does not overflow."""
|
|
317
|
+
return out_bytes > float(_value("op_level", "upsample_overflow_card_fraction")) * card_bytes
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def rms_norm_overflows(out_bytes: int, card_bytes: int) -> bool:
|
|
321
|
+
"""An rms_norm whose output runs in H bands."""
|
|
322
|
+
return out_bytes > float(_value("op_level", "rms_norm", "overflow_card_fraction")) * card_bytes
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def rms_norm_band_factor(out_bytes: int, card_bytes: int) -> int:
|
|
326
|
+
"""Bands an overflowing rms_norm runs in: its output over `rms_norm.band_card_fraction` of
|
|
327
|
+
the card, at least `rms_norm.min_band_factor`, a power of two, at most `rms_norm.max_band_factor`."""
|
|
328
|
+
budget = int(float(_value("op_level", "rms_norm", "band_card_fraction")) * card_bytes)
|
|
329
|
+
factor = max(int(_value("op_level", "rms_norm", "min_band_factor")), math.ceil(out_bytes / budget))
|
|
330
|
+
factor = _pow2_at_most(factor, int(_value("op_level", "max_band_factor")))
|
|
331
|
+
return min(factor, int(_value("op_level", "rms_norm", "max_band_factor")))
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def residual_chain_band_factor(bytes_fp32: int) -> int:
|
|
335
|
+
"""Bands a residual chain streams in: its half-width bytes (the fp32 estimate halved) over
|
|
336
|
+
`residual_chain.band_target_bytes`, at least `residual_chain.min_band_factor`, a power of two at most
|
|
337
|
+
`residual_chain.max_band_factor`."""
|
|
338
|
+
half_bytes = bytes_fp32 // 2
|
|
339
|
+
factor = max(int(_value("op_level", "residual_chain", "min_band_factor")),
|
|
340
|
+
math.ceil(half_bytes / int(_value("op_level", "residual_chain", "band_target_bytes"))))
|
|
341
|
+
return _pow2_at_most(factor, int(_value("op_level", "residual_chain", "max_band_factor")))
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def residual_chain_min_base_bytes_fp32() -> int:
|
|
345
|
+
"""The base-tensor size (fp32) from which a residual chain is detected."""
|
|
346
|
+
return int(_value("op_level", "residual_chain", "min_base_bytes_fp32"))
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# --- overlaps -------------------------------------------------------------------------------------
|
|
350
|
+
|
|
351
|
+
def spatial_halo(extent: int) -> int:
|
|
352
|
+
"""The proportional spatial overlap of a tile of `extent`: extent // `overlap.spatial_divisor`."""
|
|
353
|
+
return extent // int(_value("overlap", "spatial_divisor"))
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def spatial_overlap(extent: int) -> int:
|
|
357
|
+
"""A spatial tile's overlap: its proportional halo, at least `overlap.spatial_min`."""
|
|
358
|
+
return max(int(_value("overlap", "spatial_min")), spatial_halo(extent))
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def temporal_halo(t_tile: int) -> int:
|
|
362
|
+
"""The proportional temporal overlap of a tile of `t_tile` frames: t_tile // `overlap.temporal_divisor`."""
|
|
363
|
+
return t_tile // int(_value("overlap", "temporal_divisor"))
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
# --- in place -------------------------------------------------------------------------------------
|
|
367
|
+
|
|
368
|
+
def inplace_min_bytes() -> int:
|
|
369
|
+
"""The output size from which an add or an element-wise unary writes into its dead input."""
|
|
370
|
+
return int(_value("inplace", "min_bytes"))
|
nbxtensor/ops/topk.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""Top-K — pure @triton.jit kernels. Extracted from FlagGems.
|
|
2
|
+
|
|
3
|
+
Two-stage approach:
|
|
4
|
+
topk_stage1_kernel: per-chunk top-k selection via iterative max/min
|
|
5
|
+
topk_stage2_kernel: merge chunks via bitonic sort
|
|
6
|
+
|
|
7
|
+
Supporting JIT functions:
|
|
8
|
+
_get_finfo_val: dtype min/max constants for masking
|
|
9
|
+
_compare_and_swap: bitonic compare-and-swap with index tracking
|
|
10
|
+
_bitonic_merge: single merge stage of bitonic sort
|
|
11
|
+
argsort: full bitonic argsort
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import triton
|
|
15
|
+
import triton.language as tl
|
|
16
|
+
import triton.language.core as core
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from triton.language.standard import _log2, zeros_like
|
|
20
|
+
except ImportError:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# --- Dtype limit constants (inline, no torch) ---
|
|
25
|
+
|
|
26
|
+
_MIN_FLOAT32_VAL = tl.constexpr(-3.4028235e+38)
|
|
27
|
+
_MAX_FLOAT32_VAL = tl.constexpr(3.4028235e+38)
|
|
28
|
+
_MIN_FLOAT16_VAL = tl.constexpr(-65504.0)
|
|
29
|
+
_MAX_FLOAT16_VAL = tl.constexpr(65504.0)
|
|
30
|
+
_MIN_BFLOAT16_VAL = tl.constexpr(-3.3895314e+38)
|
|
31
|
+
_MAX_BFLOAT16_VAL = tl.constexpr(3.3895314e+38)
|
|
32
|
+
_MIN_INT32_VAL = tl.constexpr(-2147483648)
|
|
33
|
+
_MAX_INT32_VAL = tl.constexpr(2147483647)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@triton.jit
|
|
37
|
+
def _get_finfo_val(
|
|
38
|
+
dtype,
|
|
39
|
+
return_max,
|
|
40
|
+
):
|
|
41
|
+
"""Saturating limit for `dtype`, as a Python float literal.
|
|
42
|
+
|
|
43
|
+
The limit MUST match the dtype of the buffer being loaded, not the
|
|
44
|
+
dtype the kernel computes in. `tl.load(..., other=v)` materialises
|
|
45
|
+
`v` in the POINTER's element type, so an fp32 limit handed to an
|
|
46
|
+
fp16 buffer overflows and lands as -inf. That is not a benign
|
|
47
|
+
sentinel here: `_compare_and_swap` permutes with a masked sum, and
|
|
48
|
+
`0 * -inf` is NaN, which loses every comparison and collapses the
|
|
49
|
+
sort network (measured: pad slots surfacing into the top-k with
|
|
50
|
+
their INT32_MIN sentinel index, whenever chunk_num*k was not a
|
|
51
|
+
power of two).
|
|
52
|
+
|
|
53
|
+
Literals rather than `tl.constexpr(torch.finfo(...).max)` — the
|
|
54
|
+
upstream FlagGems form — because R33 forbids torch here and the
|
|
55
|
+
constexpr wrapper mixes fp64 into the comparison. Dropping the
|
|
56
|
+
dtype branching along with the torch call was the regression.
|
|
57
|
+
"""
|
|
58
|
+
if dtype is tl.float16:
|
|
59
|
+
if return_max:
|
|
60
|
+
return 65504.0
|
|
61
|
+
else:
|
|
62
|
+
return -65504.0
|
|
63
|
+
elif dtype is tl.bfloat16:
|
|
64
|
+
if return_max:
|
|
65
|
+
return 3.3895314e+38
|
|
66
|
+
else:
|
|
67
|
+
return -3.3895314e+38
|
|
68
|
+
else:
|
|
69
|
+
if return_max:
|
|
70
|
+
return 3.4028235e+38
|
|
71
|
+
else:
|
|
72
|
+
return -3.4028235e+38
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# --- Stage 1: per-chunk top-k ---
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@triton.jit
|
|
79
|
+
def topk_stage1_kernel(
|
|
80
|
+
y_ptr,
|
|
81
|
+
index_ptr,
|
|
82
|
+
x_ptr,
|
|
83
|
+
k,
|
|
84
|
+
N: tl.constexpr,
|
|
85
|
+
CHUNK_SIZE: tl.constexpr,
|
|
86
|
+
DESCENDING: tl.constexpr,
|
|
87
|
+
):
|
|
88
|
+
cur_batch = tl.program_id(0).to(tl.int64)
|
|
89
|
+
cur_chunk_idx = tl.program_id(1).to(tl.int64)
|
|
90
|
+
chunk_num = tl.num_programs(1)
|
|
91
|
+
|
|
92
|
+
y_ptr += cur_batch * chunk_num * k + cur_chunk_idx * k
|
|
93
|
+
index_ptr += cur_batch * chunk_num * k + cur_chunk_idx * k
|
|
94
|
+
|
|
95
|
+
chunk_offset = cur_chunk_idx * CHUNK_SIZE
|
|
96
|
+
x_ptr += cur_batch * N + chunk_offset
|
|
97
|
+
|
|
98
|
+
cols = tl.arange(0, CHUNK_SIZE)
|
|
99
|
+
mask = (chunk_offset + cols) < N
|
|
100
|
+
|
|
101
|
+
mask_val = _get_finfo_val(x_ptr.dtype.element_ty, return_max=not DESCENDING)
|
|
102
|
+
x_val = tl.load(x_ptr + cols, mask=mask, other=mask_val).to(tl.float32)
|
|
103
|
+
for k_idx in range(k):
|
|
104
|
+
if DESCENDING:
|
|
105
|
+
chunk_select_val = tl.max(x_val)
|
|
106
|
+
chunk_select_idx = tl.argmax(x_val, axis=0)
|
|
107
|
+
else:
|
|
108
|
+
chunk_select_val = tl.min(x_val)
|
|
109
|
+
chunk_select_idx = tl.argmin(x_val, axis=0)
|
|
110
|
+
|
|
111
|
+
tl.store(y_ptr + k_idx, chunk_select_val)
|
|
112
|
+
tl.store(index_ptr + k_idx, chunk_select_idx + chunk_offset)
|
|
113
|
+
|
|
114
|
+
if DESCENDING:
|
|
115
|
+
mask_v = tl.full(x_val.shape, float('-inf'), dtype=tl.float32)
|
|
116
|
+
else:
|
|
117
|
+
mask_v = tl.full(x_val.shape, float('inf'), dtype=tl.float32)
|
|
118
|
+
x_val = tl.where(cols == chunk_select_idx, mask_v, x_val)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# --- Bitonic sort helpers ---
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@triton.jit
|
|
125
|
+
def _compare_and_swap(x, ids, flip, i: core.constexpr, n_dims: core.constexpr):
|
|
126
|
+
n_outer: core.constexpr = x.numel >> n_dims
|
|
127
|
+
shape: core.constexpr = [n_outer * 2**i, 2, 2 ** (n_dims - i - 1)]
|
|
128
|
+
|
|
129
|
+
y = core.reshape(x, shape)
|
|
130
|
+
y_idx = core.reshape(ids, shape)
|
|
131
|
+
|
|
132
|
+
# Slice left/right with stride 2**(n_dims - i - 1).
|
|
133
|
+
#
|
|
134
|
+
# SELECT the unwanted lane away rather than multiplying it by zero.
|
|
135
|
+
# The upstream form is `tl.sum(y * (1 - mask), 1)`, which is exact
|
|
136
|
+
# for finite values but yields NaN on any +-inf, because 0 * inf is
|
|
137
|
+
# NaN. Selecting keeps the identity (-inf + 0 == -inf), so a
|
|
138
|
+
# non-finite entry sorts to its extreme instead of poisoning the
|
|
139
|
+
# comparison network. Callers do put -inf in the data: the sampler
|
|
140
|
+
# masks rejected logits with -inf and then takes a top-k over the
|
|
141
|
+
# result.
|
|
142
|
+
mask = core.arange(0, 2)[None, :, None]
|
|
143
|
+
left = core.broadcast_to(
|
|
144
|
+
tl.sum(core.where(mask == 0, y, zeros_like(y)), 1)[:, None, :], shape
|
|
145
|
+
).to(x.dtype)
|
|
146
|
+
right = core.broadcast_to(
|
|
147
|
+
tl.sum(core.where(mask == 1, y, zeros_like(y)), 1)[:, None, :], shape
|
|
148
|
+
).to(x.dtype)
|
|
149
|
+
left = core.reshape(left, x.shape)
|
|
150
|
+
right = core.reshape(right, x.shape)
|
|
151
|
+
|
|
152
|
+
left_idx = core.broadcast_to(
|
|
153
|
+
tl.sum(core.where(mask == 0, y_idx, zeros_like(y_idx)), 1)[:, None, :], shape
|
|
154
|
+
).to(ids.dtype)
|
|
155
|
+
right_idx = core.broadcast_to(
|
|
156
|
+
tl.sum(core.where(mask == 1, y_idx, zeros_like(y_idx)), 1)[:, None, :], shape
|
|
157
|
+
).to(ids.dtype)
|
|
158
|
+
left_idx = core.reshape(left_idx, ids.shape)
|
|
159
|
+
right_idx = core.reshape(right_idx, ids.shape)
|
|
160
|
+
|
|
161
|
+
# Compare-and-swap
|
|
162
|
+
if core.constexpr(x.dtype.primitive_bitwidth) == 8:
|
|
163
|
+
idtype = core.int8
|
|
164
|
+
elif core.constexpr(x.dtype.primitive_bitwidth) == 16:
|
|
165
|
+
idtype = core.int16
|
|
166
|
+
elif core.constexpr(x.dtype.primitive_bitwidth) == 32:
|
|
167
|
+
idtype = core.int32
|
|
168
|
+
elif core.constexpr(x.dtype.primitive_bitwidth) == 64:
|
|
169
|
+
idtype = core.int64
|
|
170
|
+
else:
|
|
171
|
+
raise ValueError("Unsupported dtype")
|
|
172
|
+
|
|
173
|
+
ileft = left.to(idtype, bitcast=True)
|
|
174
|
+
iright = right.to(idtype, bitcast=True)
|
|
175
|
+
ix = x.to(idtype, bitcast=True)
|
|
176
|
+
|
|
177
|
+
cond = (left > right) ^ flip
|
|
178
|
+
ret = ix ^ core.where(cond, ileft ^ iright, zeros_like(ix))
|
|
179
|
+
|
|
180
|
+
if core.constexpr(ids.dtype.primitive_bitwidth) == 8:
|
|
181
|
+
idx_dtype = core.int8
|
|
182
|
+
elif core.constexpr(ids.dtype.primitive_bitwidth) == 16:
|
|
183
|
+
idx_dtype = core.int16
|
|
184
|
+
elif core.constexpr(ids.dtype.primitive_bitwidth) == 32:
|
|
185
|
+
idx_dtype = core.int32
|
|
186
|
+
elif core.constexpr(ids.dtype.primitive_bitwidth) == 64:
|
|
187
|
+
idx_dtype = core.int64
|
|
188
|
+
else:
|
|
189
|
+
raise ValueError("Unsupported dtype")
|
|
190
|
+
|
|
191
|
+
ileft_idx = left_idx.to(idx_dtype, bitcast=True)
|
|
192
|
+
iright_idx = right_idx.to(idx_dtype, bitcast=True)
|
|
193
|
+
ix_idx = ids.to(idx_dtype, bitcast=True)
|
|
194
|
+
ret_idx = ix_idx ^ core.where(cond, ileft_idx ^ iright_idx, zeros_like(ix_idx))
|
|
195
|
+
|
|
196
|
+
return ret.to(x.dtype, bitcast=True), ret_idx.to(ids.dtype, bitcast=True)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@triton.jit
|
|
200
|
+
def _bitonic_merge(
|
|
201
|
+
x, ids, stage: core.constexpr, order: core.constexpr, n_dims: core.constexpr
|
|
202
|
+
):
|
|
203
|
+
"""Bitonic merge stage.
|
|
204
|
+
|
|
205
|
+
order: 0=ascending, 1=descending, 2=alternating
|
|
206
|
+
"""
|
|
207
|
+
n_outer: core.constexpr = x.numel >> n_dims
|
|
208
|
+
core.static_assert(stage <= n_dims)
|
|
209
|
+
|
|
210
|
+
if order == 2:
|
|
211
|
+
shape: core.constexpr = [n_outer * 2 ** (n_dims - 1 - stage), 2, 2**stage]
|
|
212
|
+
flip = core.reshape(
|
|
213
|
+
core.broadcast_to(core.arange(0, 2)[None, :, None], shape), x.shape
|
|
214
|
+
)
|
|
215
|
+
else:
|
|
216
|
+
flip = order
|
|
217
|
+
for i in core.static_range(stage):
|
|
218
|
+
x, ids = _compare_and_swap(x, ids, flip, i + (n_dims - stage), n_dims)
|
|
219
|
+
return x, ids
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@triton.jit
|
|
223
|
+
def argsort(x, ids, dim: tl.constexpr, descending: core.constexpr):
|
|
224
|
+
_dim: core.constexpr = dim
|
|
225
|
+
n_dims: core.constexpr = _log2(x.shape[_dim])
|
|
226
|
+
for i in core.static_range(1, n_dims + 1):
|
|
227
|
+
x, ids = _bitonic_merge(x, ids, i, 2 if i < n_dims else descending, n_dims)
|
|
228
|
+
return x, ids
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# --- Stage 2: merge chunks via bitonic sort ---
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@triton.jit
|
|
235
|
+
def topk_stage2_kernel(
|
|
236
|
+
y_ptr,
|
|
237
|
+
index_ptr,
|
|
238
|
+
chunk_x,
|
|
239
|
+
chunk_index,
|
|
240
|
+
sort_dim: tl.constexpr,
|
|
241
|
+
k: tl.constexpr,
|
|
242
|
+
N: tl.constexpr,
|
|
243
|
+
BLOCK_SIZE: tl.constexpr,
|
|
244
|
+
DESCENDING: tl.constexpr,
|
|
245
|
+
):
|
|
246
|
+
cur_batch = tl.program_id(0).to(tl.int64)
|
|
247
|
+
chunk_x += cur_batch * N
|
|
248
|
+
chunk_index += cur_batch * N
|
|
249
|
+
y_ptr += cur_batch * k
|
|
250
|
+
index_ptr += cur_batch * k
|
|
251
|
+
|
|
252
|
+
cols = tl.arange(0, BLOCK_SIZE)
|
|
253
|
+
mask = cols < N
|
|
254
|
+
|
|
255
|
+
mask_val = _get_finfo_val(chunk_x.dtype.element_ty, return_max=not DESCENDING)
|
|
256
|
+
mask_index_val = _MIN_INT32_VAL if DESCENDING else _MAX_INT32_VAL
|
|
257
|
+
|
|
258
|
+
chunk_x_val = tl.load(chunk_x + cols, mask=mask, other=mask_val).to(tl.float32)
|
|
259
|
+
chunk_index_val = tl.load(chunk_index + cols, mask=mask, other=mask_index_val).to(
|
|
260
|
+
tl.int32
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
sorted_chunk_x, sorted_chunk_index = argsort(
|
|
264
|
+
chunk_x_val, chunk_index_val, 0, descending=DESCENDING
|
|
265
|
+
)
|
|
266
|
+
tl.store(y_ptr + cols, sorted_chunk_x, mask=cols < k)
|
|
267
|
+
tl.store(index_ptr + cols, sorted_chunk_index, mask=cols < k)
|