nbxtensor 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nbxtensor/THIRD_PARTY_NOTICES.md +287 -0
- nbxtensor/__init__.py +17 -0
- nbxtensor/backend/__init__.py +1 -0
- nbxtensor/backend/capabilities.py +207 -0
- nbxtensor/backend/cpu/__init__.py +1 -0
- nbxtensor/backend/cpu/blas.py +102 -0
- nbxtensor/backend/cpu/pool.py +204 -0
- nbxtensor/backend/cpu_backend.py +162 -0
- nbxtensor/backend/device_facts.py +105 -0
- nbxtensor/backend/device_files.py +129 -0
- nbxtensor/backend/device_names.py +18 -0
- nbxtensor/backend/devices/amd/cdna.yml +228 -0
- nbxtensor/backend/devices/amd/cdna2.yml +228 -0
- nbxtensor/backend/devices/amd/cdna3.yml +227 -0
- nbxtensor/backend/devices/apple/apple_m1.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4_pro.yml +308 -0
- nbxtensor/backend/devices/apple/apple_m5.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_max.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_pro.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_ultra.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m6.yml +269 -0
- nbxtensor/backend/devices/apple/apple_silicon.yml +263 -0
- nbxtensor/backend/devices/nvidia/ampere.yml +214 -0
- nbxtensor/backend/devices/nvidia/hopper.yml +213 -0
- nbxtensor/backend/devices/nvidia/volta.yml +411 -0
- nbxtensor/backend/faults.py +151 -0
- nbxtensor/backend/host_memory.py +185 -0
- nbxtensor/backend/launcher.py +2350 -0
- nbxtensor/backend/launcher_contract.py +429 -0
- nbxtensor/backend/metal_backend.py +661 -0
- nbxtensor/backend/metal_device.py +1274 -0
- nbxtensor/backend/process_memory.py +73 -0
- nbxtensor/backend/replay_graph.py +322 -0
- nbxtensor/backend/runtime.py +409 -0
- nbxtensor/backend/triton_ext_driver.py +735 -0
- nbxtensor/dtype/__init__.py +1 -0
- nbxtensor/dtype/convert.py +107 -0
- nbxtensor/dtype/dtypes.yml +70 -0
- nbxtensor/dtype/itemsize.py +166 -0
- nbxtensor/dtype/nbx_dtype.py +72 -0
- nbxtensor/dtype/operands.py +158 -0
- nbxtensor/dtype/outputs.py +64 -0
- nbxtensor/dtype/policy.py +155 -0
- nbxtensor/dtype/policy.yml +51 -0
- nbxtensor/dtype/promotion.py +112 -0
- nbxtensor/host.py +55 -0
- nbxtensor/io/__init__.py +1 -0
- nbxtensor/io/read_rate.py +197 -0
- nbxtensor/io/safetensors.py +314 -0
- nbxtensor/memory/__init__.py +9 -0
- nbxtensor/memory/allocator.py +1988 -0
- nbxtensor/memory/arena.py +75 -0
- nbxtensor/memory/device_memory.py +150 -0
- nbxtensor/memory/device_transfer.py +154 -0
- nbxtensor/memory/layout.py +43 -0
- nbxtensor/memory/memory_pool.py +191 -0
- nbxtensor/memory/metal.py +96 -0
- nbxtensor/memory/oom_advice.py +91 -0
- nbxtensor/memory/pointer_tables.py +323 -0
- nbxtensor/memory/pool.py +507 -0
- nbxtensor/memory/shadow.py +43 -0
- nbxtensor/memory/slab.py +273 -0
- nbxtensor/memory/trace_replay.py +122 -0
- nbxtensor/memory/traces/sana-video-2b-720p.16g.2026-11-02.tsv +13610 -0
- nbxtensor/memory/vmm.py +118 -0
- nbxtensor/ops/__init__.py +1 -0
- nbxtensor/ops/_autotune_policy.py +90 -0
- nbxtensor/ops/_common.py +32 -0
- nbxtensor/ops/_configs.py +1041 -0
- nbxtensor/ops/_cost.py +231 -0
- nbxtensor/ops/abs.py +17 -0
- nbxtensor/ops/adaptive_avg_pool2d.py +161 -0
- nbxtensor/ops/add.py +96 -0
- nbxtensor/ops/addcdiv.py +29 -0
- nbxtensor/ops/addcmul.py +29 -0
- nbxtensor/ops/addmv_op.py +64 -0
- nbxtensor/ops/addr.py +68 -0
- nbxtensor/ops/all_reduce.py +83 -0
- nbxtensor/ops/amax.py +40 -0
- nbxtensor/ops/any_reduce.py +83 -0
- nbxtensor/ops/arange_op.py +27 -0
- nbxtensor/ops/argmax.py +107 -0
- nbxtensor/ops/argmin.py +93 -0
- nbxtensor/ops/atan2.py +71 -0
- nbxtensor/ops/attention_wrappers.py +1648 -0
- nbxtensor/ops/autotune_bucket.py +137 -0
- nbxtensor/ops/autotune_cache.py +430 -0
- nbxtensor/ops/autotune_certified.py +1262 -0
- nbxtensor/ops/autotune_refusals.py +380 -0
- nbxtensor/ops/autotune_settings.py +58 -0
- nbxtensor/ops/avg_pool2d.py +73 -0
- nbxtensor/ops/baddbmm_op.py +132 -0
- nbxtensor/ops/batch_norm.py +163 -0
- nbxtensor/ops/binary_strided.py +196 -0
- nbxtensor/ops/bitwise_and.py +26 -0
- nbxtensor/ops/bitwise_left_shift.py +47 -0
- nbxtensor/ops/bitwise_not.py +24 -0
- nbxtensor/ops/bitwise_or.py +26 -0
- nbxtensor/ops/bitwise_right_shift.py +47 -0
- nbxtensor/ops/bucketize.py +37 -0
- nbxtensor/ops/cat_op.py +87 -0
- nbxtensor/ops/celu.py +20 -0
- nbxtensor/ops/clamp.py +24 -0
- nbxtensor/ops/clamp_min.py +22 -0
- nbxtensor/ops/conv2d.py +265 -0
- nbxtensor/ops/conv2d_m8n8k4.py +165 -0
- nbxtensor/ops/conv_depthwise2d.py +154 -0
- nbxtensor/ops/conv_transpose2d.py +148 -0
- nbxtensor/ops/conv_wrappers.py +1482 -0
- nbxtensor/ops/copy.py +18 -0
- nbxtensor/ops/copy_op.py +39 -0
- nbxtensor/ops/cos.py +17 -0
- nbxtensor/ops/count_nonzero.py +64 -0
- nbxtensor/ops/cross_entropy.py +67 -0
- nbxtensor/ops/cummax.py +109 -0
- nbxtensor/ops/cummin.py +105 -0
- nbxtensor/ops/cumsum.py +244 -0
- nbxtensor/ops/decode_attn_vec.py +162 -0
- nbxtensor/ops/depthwise_conv2d.py +132 -0
- nbxtensor/ops/dequant_gemv.py +190 -0
- nbxtensor/ops/dft.py +67 -0
- nbxtensor/ops/dispatch.py +1228 -0
- nbxtensor/ops/div.py +75 -0
- nbxtensor/ops/dot_op.py +67 -0
- nbxtensor/ops/dropout.py +23 -0
- nbxtensor/ops/dtype_convert.py +42 -0
- nbxtensor/ops/elementwise_launch.py +81 -0
- nbxtensor/ops/elementwise_wrappers.py +2315 -0
- nbxtensor/ops/elu.py +20 -0
- nbxtensor/ops/embedding.py +69 -0
- nbxtensor/ops/eq.py +32 -0
- nbxtensor/ops/erf.py +18 -0
- nbxtensor/ops/exp.py +17 -0
- nbxtensor/ops/exp2.py +18 -0
- nbxtensor/ops/fft_op.py +125 -0
- nbxtensor/ops/fill_op.py +20 -0
- nbxtensor/ops/flash_attention.py +307 -0
- nbxtensor/ops/flash_attention_m8n8k4.py +384 -0
- nbxtensor/ops/flash_decode.py +78 -0
- nbxtensor/ops/flip_op.py +60 -0
- nbxtensor/ops/floor.py +90 -0
- nbxtensor/ops/full_op.py +24 -0
- nbxtensor/ops/fused_moe.py +417 -0
- nbxtensor/ops/fused_moe_m8n8k4.py +99 -0
- nbxtensor/ops/fused_upsample_conv.py +413 -0
- nbxtensor/ops/gather_op.py +52 -0
- nbxtensor/ops/ge.py +32 -0
- nbxtensor/ops/gelu.py +31 -0
- nbxtensor/ops/gemv_vec.py +80 -0
- nbxtensor/ops/glu.py +43 -0
- nbxtensor/ops/grid_sampler.py +145 -0
- nbxtensor/ops/group_norm_tiled.py +67 -0
- nbxtensor/ops/groupnorm.py +123 -0
- nbxtensor/ops/gt.py +32 -0
- nbxtensor/ops/hardsigmoid.py +18 -0
- nbxtensor/ops/hardswish.py +19 -0
- nbxtensor/ops/im2col.py +57 -0
- nbxtensor/ops/index_add.py +95 -0
- nbxtensor/ops/index_put_op.py +92 -0
- nbxtensor/ops/index_select.py +121 -0
- nbxtensor/ops/index_wrappers.py +770 -0
- nbxtensor/ops/isfinite.py +20 -0
- nbxtensor/ops/isinf.py +20 -0
- nbxtensor/ops/isnan.py +18 -0
- nbxtensor/ops/launch_keys.py +1054 -0
- nbxtensor/ops/launch_observer.py +54 -0
- nbxtensor/ops/layernorm.py +71 -0
- nbxtensor/ops/le.py +35 -0
- nbxtensor/ops/leaky_relu.py +19 -0
- nbxtensor/ops/lerp.py +76 -0
- nbxtensor/ops/linalg_wrappers.py +1370 -0
- nbxtensor/ops/linspace_op.py +47 -0
- nbxtensor/ops/log.py +17 -0
- nbxtensor/ops/log_sigmoid.py +23 -0
- nbxtensor/ops/logical_and.py +26 -0
- nbxtensor/ops/logical_not.py +24 -0
- nbxtensor/ops/logical_or.py +26 -0
- nbxtensor/ops/logical_xor.py +37 -0
- nbxtensor/ops/loss_wrappers.py +282 -0
- nbxtensor/ops/lt.py +32 -0
- nbxtensor/ops/masked_fill.py +21 -0
- nbxtensor/ops/matmul.py +377 -0
- nbxtensor/ops/matmul_m8n8k4.py +235 -0
- nbxtensor/ops/max_pool2d.py +74 -0
- nbxtensor/ops/max_pool3d.py +93 -0
- nbxtensor/ops/max_reduce.py +91 -0
- nbxtensor/ops/maximum.py +18 -0
- nbxtensor/ops/mean.py +40 -0
- nbxtensor/ops/min_reduce.py +91 -0
- nbxtensor/ops/minimum.py +18 -0
- nbxtensor/ops/mish.py +22 -0
- nbxtensor/ops/mma_m8n8k4.py +152 -0
- nbxtensor/ops/moe_align.py +195 -0
- nbxtensor/ops/moe_decode_vec.py +374 -0
- nbxtensor/ops/moe_device_residency.py +141 -0
- nbxtensor/ops/moe_wrappers.py +345 -0
- nbxtensor/ops/mse_loss.py +79 -0
- nbxtensor/ops/mul.py +51 -0
- nbxtensor/ops/nan_to_num.py +23 -0
- nbxtensor/ops/ne.py +32 -0
- nbxtensor/ops/neg.py +17 -0
- nbxtensor/ops/nllloss.py +124 -0
- nbxtensor/ops/nonzero.py +33 -0
- nbxtensor/ops/nonzero_op.py +62 -0
- nbxtensor/ops/norm_wrappers.py +579 -0
- nbxtensor/ops/ones_op.py +20 -0
- nbxtensor/ops/oracles/__init__.py +6 -0
- nbxtensor/ops/oracles/conv2d_fp64.py +450 -0
- nbxtensor/ops/pad_op.py +159 -0
- nbxtensor/ops/paged_decode_attn.py +215 -0
- nbxtensor/ops/pixel_shuffle.py +144 -0
- nbxtensor/ops/pixel_unshuffle.py +72 -0
- nbxtensor/ops/pow.py +137 -0
- nbxtensor/ops/prod.py +81 -0
- nbxtensor/ops/rand_op.py +52 -0
- nbxtensor/ops/random_wrappers.py +366 -0
- nbxtensor/ops/reciprocal.py +17 -0
- nbxtensor/ops/reduction_wrappers.py +845 -0
- nbxtensor/ops/relu.py +18 -0
- nbxtensor/ops/remainder.py +46 -0
- nbxtensor/ops/repeat_interleave.py +48 -0
- nbxtensor/ops/repeat_op.py +132 -0
- nbxtensor/ops/residual_chain.py +374 -0
- nbxtensor/ops/rmsnorm.py +68 -0
- nbxtensor/ops/rope.py +142 -0
- nbxtensor/ops/rsqrt.py +17 -0
- nbxtensor/ops/scaled_softmax.py +96 -0
- nbxtensor/ops/scatter_op.py +139 -0
- nbxtensor/ops/screen_oracle.py +479 -0
- nbxtensor/ops/selu.py +22 -0
- nbxtensor/ops/shape_wrappers.py +1032 -0
- nbxtensor/ops/sigmoid.py +19 -0
- nbxtensor/ops/silu.py +27 -0
- nbxtensor/ops/sin.py +17 -0
- nbxtensor/ops/softmax.py +169 -0
- nbxtensor/ops/softplus.py +27 -0
- nbxtensor/ops/sort_op.py +304 -0
- nbxtensor/ops/spectral_wrappers.py +481 -0
- nbxtensor/ops/sqrt.py +17 -0
- nbxtensor/ops/stack_op.py +84 -0
- nbxtensor/ops/std.py +113 -0
- nbxtensor/ops/strided_copy.py +200 -0
- nbxtensor/ops/sub.py +35 -0
- nbxtensor/ops/sum.py +40 -0
- nbxtensor/ops/tan.py +19 -0
- nbxtensor/ops/tanh.py +21 -0
- nbxtensor/ops/threshold.py +19 -0
- nbxtensor/ops/tiling.yml +91 -0
- nbxtensor/ops/tiling_policy.py +43 -0
- nbxtensor/ops/tiling_sizes.py +370 -0
- nbxtensor/ops/topk.py +267 -0
- nbxtensor/ops/trace_op.py +64 -0
- nbxtensor/ops/tril.py +64 -0
- nbxtensor/ops/triu.py +62 -0
- nbxtensor/ops/unfold_op.py +37 -0
- nbxtensor/ops/unique_op.py +97 -0
- nbxtensor/ops/upsample_bilinear2d.py +106 -0
- nbxtensor/ops/upsample_nearest2d.py +52 -0
- nbxtensor/ops/upsample_nearest3d.py +61 -0
- nbxtensor/ops/var.py +136 -0
- nbxtensor/ops/vector_norm.py +216 -0
- nbxtensor/ops/weight_norm.py +104 -0
- nbxtensor/ops/where.py +39 -0
- nbxtensor/ops/wrappers.py +825 -0
- nbxtensor/ops/zeros_op.py +20 -0
- nbxtensor/quant/__init__.py +1 -0
- nbxtensor/quant/quantized_tensor.py +187 -0
- nbxtensor/random/__init__.py +1 -0
- nbxtensor/random/mt_randn.py +154 -0
- nbxtensor/random/rng_pin.py +59 -0
- nbxtensor/random/rng_stream.py +118 -0
- nbxtensor/random/seeded_draw.py +223 -0
- nbxtensor/shape/__init__.py +157 -0
- nbxtensor/tensor.py +1867 -0
- nbxtensor-0.0.1.dist-info/METADATA +379 -0
- nbxtensor-0.0.1.dist-info/RECORD +291 -0
- nbxtensor-0.0.1.dist-info/WHEEL +4 -0
- nbxtensor-0.0.1.dist-info/licenses/LICENSE +189 -0
nbxtensor/ops/sigmoid.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Sigmoid — pure @triton.jit kernel."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
|
|
6
|
+
@triton.jit
|
|
7
|
+
def sigmoid_forward_kernel(
|
|
8
|
+
input_ptr, output_ptr,
|
|
9
|
+
n_elements,
|
|
10
|
+
BLOCK_SIZE: tl.constexpr,
|
|
11
|
+
):
|
|
12
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
13
|
+
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)
|
|
14
|
+
mask = offset < n_elements
|
|
15
|
+
|
|
16
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
17
|
+
x_fp32 = x.to(tl.float32)
|
|
18
|
+
out = 1.0 / (1.0 + tl.exp(-x_fp32))
|
|
19
|
+
tl.store(output_ptr + offset, out, mask=mask)
|
nbxtensor/ops/silu.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""SiLU (Swish) — pure @triton.jit kernel."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
|
|
6
|
+
from ._common import sigmoid
|
|
7
|
+
|
|
8
|
+
@triton.jit
|
|
9
|
+
def silu_forward_kernel(
|
|
10
|
+
input_ptr, output_ptr,
|
|
11
|
+
n_elements,
|
|
12
|
+
BLOCK_SIZE: tl.constexpr,
|
|
13
|
+
):
|
|
14
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
15
|
+
# Cast offset to int64 so that mask `offset < n_elements`
|
|
16
|
+
# works correctly when n_elements >= 2^31 (e.g. Sana 4Kpx VAE
|
|
17
|
+
# silu input 1*128*4096*4096 = 2^31 elements). Without this,
|
|
18
|
+
# int32 n_elements wraps to negative and the mask is all-False,
|
|
19
|
+
# silently skipping every element and leaving output as
|
|
20
|
+
# uninitialized memory (= garbage).
|
|
21
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
22
|
+
mask = offset < n_elements
|
|
23
|
+
|
|
24
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
25
|
+
x_fp32 = x.to(tl.float32)
|
|
26
|
+
out = x_fp32 * sigmoid(x_fp32)
|
|
27
|
+
tl.store(output_ptr + offset, out, mask=mask)
|
nbxtensor/ops/sin.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Sine — pure @triton.jit kernel."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
|
|
6
|
+
@triton.jit
|
|
7
|
+
def sin_forward_kernel(
|
|
8
|
+
input_ptr, output_ptr,
|
|
9
|
+
n_elements,
|
|
10
|
+
BLOCK_SIZE: tl.constexpr,
|
|
11
|
+
):
|
|
12
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
13
|
+
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)
|
|
14
|
+
mask = offset < n_elements
|
|
15
|
+
|
|
16
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
17
|
+
tl.store(output_ptr + offset, tl.sin(x.to(tl.float32)), mask=mask)
|
nbxtensor/ops/softmax.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Softmax — pure @triton.jit kernel. Ported from attorch (MIT)."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
from triton import next_power_of_2
|
|
6
|
+
|
|
7
|
+
from nbxtensor.ops._configs import nbx_autotune
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _softmax_configs():
|
|
11
|
+
"""The candidates the certifier proves (certified per key since 2026-10-19 instead of a hand table): rows per program x warps. The
|
|
12
|
+
one the kernel ran before certification is among them (1 row, 4 warps for a row of 64 or more; up to 128 rows for
|
|
13
|
+
a short row) — a certified winner equal to it changes no bit. Rows per program partition independent rows and change
|
|
14
|
+
no bit; the warps split a row's reduction and can (V100, 4096 columns: 0.001 % of the elements)."""
|
|
15
|
+
return [triton.Config({'BLOCK_SIZE_BATCH': rows}, num_warps=warps)
|
|
16
|
+
for rows in (1, 2, 4, 8, 16, 32, 64, 128) for warps in (1, 2, 4, 8)]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _prune_softmax(configs, named_args, **kwargs):
|
|
20
|
+
"""A program holds rows x the padded row: at most what the kernel already holds for ONE row — the longest row it
|
|
21
|
+
accepts (`launch_keys.SOFTMAX_FUSED_MAX_FEAT`, beyond which the looped kernel runs) — and at most the backend's
|
|
22
|
+
reduce-tile cap (`nbx_tensor.reduce_tile_max`, the profile's, where the lowering refuses beyond); never more rows
|
|
23
|
+
than exist. (The cap alone is far above the register budget on CUDA: 128 rows of a 2048-padded row compiled for
|
|
24
|
+
minutes per candidate, 2026-10-20.)"""
|
|
25
|
+
from nbxtensor.backend.capabilities import reduce_tile_max
|
|
26
|
+
from nbxtensor.ops.launch_keys import SOFTMAX_FUSED_MAX_FEAT
|
|
27
|
+
feat = next_power_of_2(int(named_args['feat_dim']))
|
|
28
|
+
rows = int(named_args['batch_dim'])
|
|
29
|
+
budget = min(reduce_tile_max(), SOFTMAX_FUSED_MAX_FEAT)
|
|
30
|
+
from nbxtensor.ops._configs import fits_thread_registers
|
|
31
|
+
keep = [c for c in configs
|
|
32
|
+
if c.kwargs['BLOCK_SIZE_BATCH'] * feat <= max(budget, feat) and c.kwargs['BLOCK_SIZE_BATCH'] <= max(1, rows)
|
|
33
|
+
and fits_thread_registers(c.kwargs['BLOCK_SIZE_BATCH'] * feat, c.num_warps)]
|
|
34
|
+
return keep or [max(configs, key=lambda c: c.num_warps)]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@nbx_autotune(configs=_softmax_configs(), key=['BATCH_BUCKET', 'FEAT_BUCKET', 'log'],
|
|
38
|
+
prune_configs_by={'early_config_prune': _prune_softmax}, cache_results=True)
|
|
39
|
+
@triton.heuristics({
|
|
40
|
+
'BLOCK_SIZE_FEAT': lambda args: next_power_of_2(args['feat_dim']),
|
|
41
|
+
})
|
|
42
|
+
@triton.jit
|
|
43
|
+
def softmax_forward_kernel(
|
|
44
|
+
input_ptr, output_ptr,
|
|
45
|
+
batch_dim, feat_dim, BATCH_BUCKET, FEAT_BUCKET,
|
|
46
|
+
input_batch_stride, input_feat_stride,
|
|
47
|
+
output_batch_stride, output_feat_stride,
|
|
48
|
+
log: tl.constexpr,
|
|
49
|
+
BLOCK_SIZE_BATCH: tl.constexpr,
|
|
50
|
+
BLOCK_SIZE_FEAT: tl.constexpr,
|
|
51
|
+
):
|
|
52
|
+
"""Softmax forward (or log_softmax if log=True).
|
|
53
|
+
|
|
54
|
+
input: [batch_dim, feat_dim]
|
|
55
|
+
output: [batch_dim, feat_dim]
|
|
56
|
+
"""
|
|
57
|
+
batch_pid = tl.program_id(0).to(tl.int64)
|
|
58
|
+
|
|
59
|
+
batch_offset = batch_pid * BLOCK_SIZE_BATCH + tl.arange(0, BLOCK_SIZE_BATCH)
|
|
60
|
+
feat_offset = tl.arange(0, BLOCK_SIZE_FEAT)
|
|
61
|
+
|
|
62
|
+
batch_mask = batch_offset < batch_dim
|
|
63
|
+
feat_mask = feat_offset < feat_dim
|
|
64
|
+
|
|
65
|
+
input_ptr += (input_batch_stride * batch_offset[:, None] +
|
|
66
|
+
input_feat_stride * feat_offset[None, :])
|
|
67
|
+
output_ptr += (output_batch_stride * batch_offset[:, None] +
|
|
68
|
+
output_feat_stride * feat_offset[None, :])
|
|
69
|
+
|
|
70
|
+
inp = tl.load(input_ptr,
|
|
71
|
+
mask=batch_mask[:, None] & feat_mask[None, :],
|
|
72
|
+
other=-float('inf')).to(tl.float32)
|
|
73
|
+
|
|
74
|
+
# Numerical stability: subtract max
|
|
75
|
+
inp -= tl.max(inp, axis=1)[:, None]
|
|
76
|
+
numerator = tl.exp(inp)
|
|
77
|
+
denominator = tl.sum(numerator, axis=1)[:, None]
|
|
78
|
+
|
|
79
|
+
if log:
|
|
80
|
+
output = inp - tl.log(denominator)
|
|
81
|
+
else:
|
|
82
|
+
output = numerator / denominator
|
|
83
|
+
|
|
84
|
+
tl.store(output_ptr, output,
|
|
85
|
+
mask=batch_mask[:, None] & feat_mask[None, :])
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _looped_softmax_configs():
|
|
89
|
+
"""The candidates the certifier proves for the looped kernel (chantier 1): the tile a row is scanned by x warps.
|
|
90
|
+
The one it ran before certification is among them (4 096 columns, 4 warps) — a certified winner equal to it changes
|
|
91
|
+
no bit; another tile or warp count changes the order of a row's running max and sum."""
|
|
92
|
+
return [triton.Config({'TILE_FEAT': tile}, num_warps=warps)
|
|
93
|
+
for tile in (1024, 2048, 4096, 8192, 16384) for warps in (2, 4, 8)]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _prune_looped_softmax(configs, named_args, **kwargs):
|
|
97
|
+
"""A tile no wider than the backend's reduce-tile cap (`nbx_tensor.reduce_tile_max`, the profile's, where the
|
|
98
|
+
lowering refuses beyond) nor than the padded row it scans (a wider tile only masks)."""
|
|
99
|
+
from nbxtensor.backend.capabilities import reduce_tile_max
|
|
100
|
+
cap = min(reduce_tile_max(), next_power_of_2(int(named_args['feat_dim'])))
|
|
101
|
+
keep = [c for c in configs if c.kwargs['TILE_FEAT'] <= cap]
|
|
102
|
+
return keep or configs[:1]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@nbx_autotune(configs=_looped_softmax_configs(), key=['BATCH_BUCKET', 'FEAT_BUCKET', 'log'],
|
|
106
|
+
prune_configs_by={'early_config_prune': _prune_looped_softmax}, cache_results=True)
|
|
107
|
+
@triton.jit
|
|
108
|
+
def softmax_forward_looped_kernel(
|
|
109
|
+
input_ptr, output_ptr,
|
|
110
|
+
feat_dim, BATCH_BUCKET, FEAT_BUCKET,
|
|
111
|
+
input_batch_stride, input_feat_stride,
|
|
112
|
+
output_batch_stride, output_feat_stride,
|
|
113
|
+
log: tl.constexpr,
|
|
114
|
+
TILE_FEAT: tl.constexpr,
|
|
115
|
+
):
|
|
116
|
+
"""Online two-pass softmax for rows too large for one register tile.
|
|
117
|
+
|
|
118
|
+
One program per row; the row is scanned in TILE_FEAT chunks with a
|
|
119
|
+
running max / rescaled running sum (pass 1), then normalized and
|
|
120
|
+
stored (pass 2). Register footprint is bounded by TILE_FEAT
|
|
121
|
+
regardless of feat_dim.
|
|
122
|
+
|
|
123
|
+
Why this exists: the single-tile kernel above sizes its tile as
|
|
124
|
+
next_power_of_2(feat_dim). At vocab-scale rows (e.g. 151936 →
|
|
125
|
+
262144-element tile) it spills to CUDA local memory, and the driver
|
|
126
|
+
allocates the local pool DEVICE-WIDE at launch — per-thread frame ×
|
|
127
|
+
max resident threads (163,840 on V100) ≈ 2 GiB, measured. The launch
|
|
128
|
+
itself then fails with CUDA_ERROR_OUT_OF_MEMORY whenever the device
|
|
129
|
+
cannot serve that hidden allocation (D9 warm-daemon incident,
|
|
130
|
+
2026-07-27). Bounded tiles keep the frame register-resident, so the
|
|
131
|
+
launch has no local-memory cost at any feat_dim.
|
|
132
|
+
"""
|
|
133
|
+
row = tl.program_id(0).to(tl.int64)
|
|
134
|
+
in_row = input_ptr + row * input_batch_stride
|
|
135
|
+
out_row = output_ptr + row * output_batch_stride
|
|
136
|
+
|
|
137
|
+
running_max = tl.full((1,), -float('inf'), tl.float32)
|
|
138
|
+
running_sum = tl.zeros((1,), tl.float32)
|
|
139
|
+
for start in range(0, feat_dim, TILE_FEAT):
|
|
140
|
+
offs = start + tl.arange(0, TILE_FEAT)
|
|
141
|
+
mask = offs < feat_dim
|
|
142
|
+
x = tl.load(in_row + offs * input_feat_stride, mask=mask,
|
|
143
|
+
other=-float('inf')).to(tl.float32)
|
|
144
|
+
new_max = tl.maximum(running_max, tl.max(x, axis=0))
|
|
145
|
+
# -inf guard (online-softmax idiom): while every element seen so
|
|
146
|
+
# far is -inf (top-k/top-p masking routinely -inf-fills whole
|
|
147
|
+
# leading tiles of an unsorted vocab row), new_max stays -inf and
|
|
148
|
+
# both exp arguments would be -inf - (-inf) = NaN, poisoning the
|
|
149
|
+
# running sum for the entire row. Pin alpha to 1 and the shifted
|
|
150
|
+
# tile to -inf (exp → 0) until the first finite value arrives.
|
|
151
|
+
# Fully -inf rows still yield NaN in pass 2 — parity with the
|
|
152
|
+
# single-tile kernel, guarded downstream by the samplers.
|
|
153
|
+
seen_only_neginf = new_max == -float('inf')
|
|
154
|
+
alpha = tl.where(seen_only_neginf, 1.0,
|
|
155
|
+
tl.exp(running_max - new_max))
|
|
156
|
+
shifted = tl.where(seen_only_neginf, -float('inf'), x - new_max)
|
|
157
|
+
running_sum = running_sum * alpha + tl.sum(tl.exp(shifted), axis=0)
|
|
158
|
+
running_max = new_max
|
|
159
|
+
|
|
160
|
+
for start in range(0, feat_dim, TILE_FEAT):
|
|
161
|
+
offs = start + tl.arange(0, TILE_FEAT)
|
|
162
|
+
mask = offs < feat_dim
|
|
163
|
+
x = tl.load(in_row + offs * input_feat_stride, mask=mask,
|
|
164
|
+
other=-float('inf')).to(tl.float32)
|
|
165
|
+
if log:
|
|
166
|
+
y = x - running_max - tl.log(running_sum)
|
|
167
|
+
else:
|
|
168
|
+
y = tl.exp(x - running_max) / running_sum
|
|
169
|
+
tl.store(out_row + offs * output_feat_stride, y, mask=mask)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Softplus activation — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Logic from FlagGems softplus.py: softplus(x) = log(1 + exp(beta * x)) / beta.
|
|
4
|
+
For large beta*x, returns x directly (numerical stability).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import triton
|
|
8
|
+
import triton.language as tl
|
|
9
|
+
|
|
10
|
+
@triton.jit
|
|
11
|
+
def softplus_forward_kernel(
|
|
12
|
+
x_ptr, output_ptr,
|
|
13
|
+
n_elements,
|
|
14
|
+
beta,
|
|
15
|
+
threshold,
|
|
16
|
+
BLOCK_SIZE: tl.constexpr,
|
|
17
|
+
):
|
|
18
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
19
|
+
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)
|
|
20
|
+
mask = offset < n_elements
|
|
21
|
+
|
|
22
|
+
x = tl.load(x_ptr + offset, mask=mask)
|
|
23
|
+
x_fp = x.to(tl.float32)
|
|
24
|
+
z = x_fp * beta
|
|
25
|
+
soft_z = tl.where(z > threshold, z, tl.log(1.0 + tl.exp(z)))
|
|
26
|
+
result = soft_z / beta
|
|
27
|
+
tl.store(output_ptr + offset, result, mask=mask)
|
nbxtensor/ops/sort_op.py
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""Sort — pure @triton.jit radix sort helper kernels.
|
|
2
|
+
|
|
3
|
+
Ported from FlagGems sort (Apache-2.0 license).
|
|
4
|
+
Extracts the uint-conversion helpers and the two main radix sort kernels
|
|
5
|
+
(global histogram + sweep). The small-N bitonic sort kernel is also included.
|
|
6
|
+
|
|
7
|
+
The radix sort orchestration (multi-pass loop, buffer allocation) must be
|
|
8
|
+
done in the wrapper since it requires memory allocation.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import triton
|
|
12
|
+
import triton.language as tl
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# --------------------------------------------------------------------------- #
|
|
16
|
+
# Uint conversion helpers — preserve sort order across signed/float types
|
|
17
|
+
# --------------------------------------------------------------------------- #
|
|
18
|
+
|
|
19
|
+
# COMPILE-TIME helpers — `@tl.constexpr`, NEVER `@triton.jit`. Upstream
|
|
20
|
+
# FlagGems has carried them as constexpr functions since the one-sweep
|
|
21
|
+
# sort's birth commit (e669aee, PR #694); our vendoring delta hardened
|
|
22
|
+
# them into `@triton.jit`, which Triton >= 3.4 structurally rejects (the
|
|
23
|
+
# `_builder`→`_semantic` frontend refactor: a JIT callee compiles as a
|
|
24
|
+
# real typed function, so it can no longer return a dtype object, and
|
|
25
|
+
# `tl.core.get_int_dtype` — a plain undecorated python function — no
|
|
26
|
+
# longer resolves from inside JIT). `@tl.constexpr` runs them as plain
|
|
27
|
+
# compile-time python (legal 3.1→3.6), exactly upstream's shape.
|
|
28
|
+
def _unwrap_constexpr(o):
|
|
29
|
+
return o.value if isinstance(o, tl.constexpr) else o
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@tl.constexpr
|
|
33
|
+
def _get_int_t(num_bits: tl.constexpr, signed: tl.constexpr):
|
|
34
|
+
num_bits = _unwrap_constexpr(num_bits)
|
|
35
|
+
signed = _unwrap_constexpr(signed)
|
|
36
|
+
return tl.core.get_int_dtype(num_bits, signed)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@tl.constexpr
|
|
40
|
+
def _one_zeros(num_bits: tl.constexpr):
|
|
41
|
+
"""1 followed by (num_bits-1) zeros, e.g. 0x80 for 8-bit."""
|
|
42
|
+
num_bits = _unwrap_constexpr(num_bits)
|
|
43
|
+
return 1 << (num_bits - 1)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@tl.constexpr
|
|
47
|
+
def _zero_ones(num_bits: tl.constexpr):
|
|
48
|
+
"""0 followed by (num_bits-1) ones, e.g. 0x7F for 8-bit."""
|
|
49
|
+
num_bits = _unwrap_constexpr(num_bits)
|
|
50
|
+
return (1 << (num_bits - 1)) - 1
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@triton.jit
|
|
54
|
+
def uint_to_uint(x, descending: tl.constexpr = False):
|
|
55
|
+
"""Unsigned → unsigned: negate bits if descending."""
|
|
56
|
+
out = ~x if descending else x
|
|
57
|
+
return out
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@triton.jit
|
|
61
|
+
def int_to_uint(x, descending: tl.constexpr = False):
|
|
62
|
+
"""Signed int → unsigned, preserving sort order."""
|
|
63
|
+
num_bits: tl.constexpr = x.dtype.primitive_bitwidth
|
|
64
|
+
udtype = _get_int_t(num_bits, False)
|
|
65
|
+
ux = tl.cast(x, udtype, bitcast=True)
|
|
66
|
+
if descending:
|
|
67
|
+
bit_mask: tl.constexpr = _zero_ones(num_bits)
|
|
68
|
+
bit_mask_tensor = tl.full((), value=bit_mask, dtype=udtype)
|
|
69
|
+
out = ux ^ bit_mask_tensor
|
|
70
|
+
else:
|
|
71
|
+
sign_bit_mask: tl.constexpr = _one_zeros(num_bits)
|
|
72
|
+
sign_bit_mask_tensor = tl.full((), value=sign_bit_mask, dtype=udtype)
|
|
73
|
+
out = ux ^ sign_bit_mask_tensor
|
|
74
|
+
return out
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@triton.jit
|
|
78
|
+
def floating_to_uint(x, descending: tl.constexpr = False):
|
|
79
|
+
"""Float → unsigned, preserving sort order (handles sign bit correctly)."""
|
|
80
|
+
num_bits: tl.constexpr = x.dtype.primitive_bitwidth
|
|
81
|
+
sdtype = _get_int_t(num_bits, True)
|
|
82
|
+
udtype = _get_int_t(num_bits, False)
|
|
83
|
+
sx = x.to(sdtype, bitcast=True)
|
|
84
|
+
ux = x.to(udtype, bitcast=True)
|
|
85
|
+
|
|
86
|
+
sign_bit_mask_v: tl.constexpr = _one_zeros(num_bits)
|
|
87
|
+
sign_bit_mask = tl.full((), value=sign_bit_mask_v, dtype=udtype)
|
|
88
|
+
rshift_bits = tl.full((), value=num_bits - 1, dtype=sdtype)
|
|
89
|
+
mask = sign_bit_mask | (sx >> rshift_bits).to(udtype, bitcast=True)
|
|
90
|
+
if descending:
|
|
91
|
+
out = ux ^ (~mask)
|
|
92
|
+
else:
|
|
93
|
+
out = ux ^ mask
|
|
94
|
+
return out.to(udtype, bitcast=True)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@triton.jit
|
|
98
|
+
def convert_to_uint_preserve_order(x, descending: tl.constexpr = False):
|
|
99
|
+
"""Convert any numeric type to unsigned, preserving sort order."""
|
|
100
|
+
if x.dtype.is_floating():
|
|
101
|
+
out = floating_to_uint(x, descending)
|
|
102
|
+
elif x.dtype.is_int_signed():
|
|
103
|
+
out = int_to_uint(x, descending)
|
|
104
|
+
elif x.dtype.is_int_unsigned():
|
|
105
|
+
out = uint_to_uint(x, descending)
|
|
106
|
+
return out
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# --------------------------------------------------------------------------- #
|
|
110
|
+
# Global histogram kernel — counts per-bin occurrences for radix sort
|
|
111
|
+
# --------------------------------------------------------------------------- #
|
|
112
|
+
|
|
113
|
+
@triton.jit
|
|
114
|
+
def radix_sort_histogram_kernel(
|
|
115
|
+
arr_ptr,
|
|
116
|
+
out_ptr,
|
|
117
|
+
num_passes,
|
|
118
|
+
m,
|
|
119
|
+
n,
|
|
120
|
+
tiles_n_per_cta,
|
|
121
|
+
TILE_N: tl.constexpr,
|
|
122
|
+
TILE_R: tl.constexpr,
|
|
123
|
+
num_bits_per_pass: tl.constexpr,
|
|
124
|
+
descending: tl.constexpr,
|
|
125
|
+
):
|
|
126
|
+
"""Compute per-bin histograms for radix sort.
|
|
127
|
+
|
|
128
|
+
arr_ptr: (m, n) — input data
|
|
129
|
+
out_ptr: (m, num_passes, r) — histogram output, r = 2^num_bits_per_pass
|
|
130
|
+
"""
|
|
131
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
132
|
+
pid_n = pid // m
|
|
133
|
+
pid_m = pid % m
|
|
134
|
+
|
|
135
|
+
r: tl.constexpr = 2 ** num_bits_per_pass
|
|
136
|
+
bfe_mask: tl.constexpr = (1 << num_bits_per_pass) - 1
|
|
137
|
+
CTA_TILE_N: tl.constexpr = TILE_N * tiles_n_per_cta
|
|
138
|
+
cta_n_start = (CTA_TILE_N * pid_n).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)
|
|
139
|
+
cta_n_end = (tl.minimum(cta_n_start + CTA_TILE_N, n)).to(tl.int32)
|
|
140
|
+
|
|
141
|
+
for p in range(0, num_passes):
|
|
142
|
+
bit_offset = p * num_bits_per_pass
|
|
143
|
+
for r_start in range(0, r, TILE_R):
|
|
144
|
+
bin_indices = r_start + tl.arange(0, TILE_R)
|
|
145
|
+
acc = tl.zeros((TILE_R, TILE_N), dtype=tl.int64)
|
|
146
|
+
for n_start in range(cta_n_start, cta_n_end, TILE_N):
|
|
147
|
+
n_offsets = n_start + tl.arange(0, TILE_N)
|
|
148
|
+
mask = n_offsets < cta_n_end
|
|
149
|
+
arr = tl.load(arr_ptr + pid_m * n + n_offsets, mask=mask)
|
|
150
|
+
arr = convert_to_uint_preserve_order(arr, descending)
|
|
151
|
+
key = (arr >> bit_offset) & bfe_mask
|
|
152
|
+
matches = tl.where(mask, (bin_indices[:, None] == key), False)
|
|
153
|
+
acc += matches
|
|
154
|
+
local_sum = tl.sum(acc, axis=1)
|
|
155
|
+
tl.atomic_add(
|
|
156
|
+
out_ptr + pid_m * num_passes * r + p * r + bin_indices,
|
|
157
|
+
local_sum,
|
|
158
|
+
sem="relaxed",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# --------------------------------------------------------------------------- #
|
|
163
|
+
# Sweep kernel — scatter elements to sorted positions using decoupled lookback
|
|
164
|
+
# --------------------------------------------------------------------------- #
|
|
165
|
+
|
|
166
|
+
@triton.jit
|
|
167
|
+
def radix_sort_tile_counts_kernel(
|
|
168
|
+
arr_ptr,
|
|
169
|
+
counts_ptr,
|
|
170
|
+
m,
|
|
171
|
+
N,
|
|
172
|
+
OUT_N,
|
|
173
|
+
TILE_N: tl.constexpr,
|
|
174
|
+
TILE_R: tl.constexpr,
|
|
175
|
+
k_bits: tl.constexpr,
|
|
176
|
+
bit_offset,
|
|
177
|
+
descending: tl.constexpr,
|
|
178
|
+
):
|
|
179
|
+
"""How many elements of each bin this TILE holds. Stage 1 of three.
|
|
180
|
+
|
|
181
|
+
This and `radix_sort_scatter_kernel` replace a single kernel that carried a
|
|
182
|
+
DECOUPLED LOOKBACK: each tile published its count and then spun on its
|
|
183
|
+
predecessors' flags to learn how many of its bin came before it.
|
|
184
|
+
|
|
185
|
+
That construction needs forward progress across CTAs and cross-CTA
|
|
186
|
+
visibility for a `.cg` store, and Triton promises neither — inside a kernel
|
|
187
|
+
it promises nothing about other programs at all. Measured 2026-09-17 on
|
|
188
|
+
triton-ext: correct to exactly 2048 elements (one tile) and wrong from 2049,
|
|
189
|
+
with elements LOST rather than misordered (9 absent at n=2049, 1726 at
|
|
190
|
+
n=4096) — two tiles computing the same destinations and overwriting each
|
|
191
|
+
other, which is what a lookback reading stale flags produces.
|
|
192
|
+
|
|
193
|
+
The replacement is not a new design. `cumsum_wrapper` in this same tree does
|
|
194
|
+
its cross-tile scan as SEPARATE LAUNCHES (`scan_part_sum_kernel`, then
|
|
195
|
+
`add_base_sum_kernel` when there is more than one part) and is correct at
|
|
196
|
+
eight tiles, measured the same day. Triton guarantees ordering BETWEEN
|
|
197
|
+
launches; that is the guarantee this needs.
|
|
198
|
+
"""
|
|
199
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
200
|
+
pid_m = pid % m
|
|
201
|
+
pid_n = pid // m
|
|
202
|
+
pid_r = tl.program_id(1).to(tl.int64)
|
|
203
|
+
|
|
204
|
+
bfe_mask: tl.constexpr = (1 << k_bits) - 1
|
|
205
|
+
r: tl.constexpr = 2 ** k_bits
|
|
206
|
+
cta_r_start = (pid_r * TILE_R).to(tl.int32)
|
|
207
|
+
cta_r_end = (tl.minimum(cta_r_start + TILE_R, r)).to(tl.int32)
|
|
208
|
+
|
|
209
|
+
n_offsets = pid_n * TILE_N + tl.arange(0, TILE_N)
|
|
210
|
+
mask = n_offsets < N
|
|
211
|
+
arr = tl.load(arr_ptr + pid_m * N + n_offsets, mask=mask)
|
|
212
|
+
arr_u = convert_to_uint_preserve_order(arr, descending)
|
|
213
|
+
key = (arr_u >> bit_offset) & bfe_mask
|
|
214
|
+
|
|
215
|
+
for bin_index in range(cta_r_start, cta_r_end):
|
|
216
|
+
matches = tl.where(mask, key == bin_index, False)
|
|
217
|
+
local_sum = tl.sum(matches.to(tl.uint32), axis=0)
|
|
218
|
+
tl.store(counts_ptr + pid_m * (r * OUT_N) + bin_index * OUT_N + pid_n,
|
|
219
|
+
local_sum)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@triton.jit
|
|
223
|
+
def radix_sort_tile_prefix_kernel(
|
|
224
|
+
counts_ptr,
|
|
225
|
+
prefix_ptr,
|
|
226
|
+
num_tiles,
|
|
227
|
+
OUT_N,
|
|
228
|
+
TILES_POW2: tl.constexpr,
|
|
229
|
+
):
|
|
230
|
+
"""Exclusive scan of the per-tile counts, along the TILE axis. Stage 2.
|
|
231
|
+
|
|
232
|
+
One program per (batch, bin) row — the rows are independent, and the row is
|
|
233
|
+
`num_tiles` long, which is `cdiv(n, 2048)`: small. This is the step the
|
|
234
|
+
lookback was doing inside the scatter, done where Triton actually orders it.
|
|
235
|
+
"""
|
|
236
|
+
row = tl.program_id(0).to(tl.int64)
|
|
237
|
+
offs = tl.arange(0, TILES_POW2)
|
|
238
|
+
mask = offs < num_tiles
|
|
239
|
+
counts = tl.load(counts_ptr + row * OUT_N + offs, mask=mask, other=0)
|
|
240
|
+
prefix = tl.cumsum(counts, axis=0) - counts # exclusive
|
|
241
|
+
tl.store(prefix_ptr + row * OUT_N + offs, prefix, mask=mask)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@triton.jit
|
|
245
|
+
def radix_sort_scatter_kernel(
|
|
246
|
+
arr_ptr,
|
|
247
|
+
associate_arr_ptr,
|
|
248
|
+
out_ptr,
|
|
249
|
+
associate_out_ptr,
|
|
250
|
+
excumsum_bins_ptr,
|
|
251
|
+
tile_prefix_ptr,
|
|
252
|
+
n_passes,
|
|
253
|
+
pass_id,
|
|
254
|
+
bit_offset,
|
|
255
|
+
m,
|
|
256
|
+
N,
|
|
257
|
+
OUT_N,
|
|
258
|
+
TILE_N: tl.constexpr,
|
|
259
|
+
TILE_R: tl.constexpr,
|
|
260
|
+
k_bits: tl.constexpr,
|
|
261
|
+
descending: tl.constexpr,
|
|
262
|
+
):
|
|
263
|
+
"""Scatter elements to their sorted positions. Stage 3.
|
|
264
|
+
|
|
265
|
+
Identical to the old `radix_sort_sweep_kernel` except that
|
|
266
|
+
`exclusive_prefix` — how many of this bin lie in EARLIER tiles — is READ
|
|
267
|
+
from the scan of stage 2 instead of being discovered by spinning on other
|
|
268
|
+
programs' flags.
|
|
269
|
+
"""
|
|
270
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
271
|
+
pid_m = pid % m
|
|
272
|
+
pid_n = pid // m
|
|
273
|
+
pid_r = tl.program_id(1).to(tl.int64)
|
|
274
|
+
|
|
275
|
+
bfe_mask: tl.constexpr = (1 << k_bits) - 1
|
|
276
|
+
r: tl.constexpr = 2 ** k_bits
|
|
277
|
+
cta_r_start = (pid_r * TILE_R).to(tl.int32)
|
|
278
|
+
cta_r_end = (tl.minimum(cta_r_start + TILE_R, r)).to(tl.int32)
|
|
279
|
+
|
|
280
|
+
n_offsets = pid_n * TILE_N + tl.arange(0, TILE_N)
|
|
281
|
+
mask = n_offsets < N
|
|
282
|
+
arr = tl.load(arr_ptr + pid_m * N + n_offsets, mask=mask)
|
|
283
|
+
arr_u = convert_to_uint_preserve_order(arr, descending)
|
|
284
|
+
key = (arr_u >> bit_offset) & bfe_mask
|
|
285
|
+
|
|
286
|
+
for bin_index in range(cta_r_start, cta_r_end):
|
|
287
|
+
matches = tl.where(mask, key == bin_index, False)
|
|
288
|
+
exclusive_prefix = tl.load(
|
|
289
|
+
tile_prefix_ptr + pid_m * (r * OUT_N) + bin_index * OUT_N + pid_n)
|
|
290
|
+
|
|
291
|
+
local_ex_cumsum = tl.cumsum(matches.to(tl.uint32), axis=0) - matches
|
|
292
|
+
ex_cumsum_in_bin = exclusive_prefix + local_ex_cumsum
|
|
293
|
+
|
|
294
|
+
ex_cumsum_bins = tl.load(
|
|
295
|
+
excumsum_bins_ptr + pid_m * (n_passes * r) + pass_id * r + bin_index
|
|
296
|
+
)
|
|
297
|
+
pos = ex_cumsum_bins + ex_cumsum_in_bin
|
|
298
|
+
|
|
299
|
+
tl.store(out_ptr + pid_m * N + pos, arr, mask=matches)
|
|
300
|
+
if associate_arr_ptr is not None:
|
|
301
|
+
associate_arr = tl.load(
|
|
302
|
+
associate_arr_ptr + pid_m * N + n_offsets, mask=mask
|
|
303
|
+
)
|
|
304
|
+
tl.store(associate_out_ptr + pid_m * N + pos, associate_arr, mask=matches)
|