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,2315 @@
|
|
|
1
|
+
"""Element-wise wrappers: activations, unary and binary arithmetic, comparisons, bitwise and logical ops, interpolation, the device bf16->fp16 conversion. Cut out of wrappers.py by family (M2, 2026-10-29), unchanged; shared launch layer and run context stay in wrappers.py."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import triton
|
|
7
|
+
from nbxtensor.ops.autotune_bucket import bucket_of as _bucket_of
|
|
8
|
+
from nbxtensor.ops import launch_keys as _lk
|
|
9
|
+
from nbxtensor.tensor import NBXTensor
|
|
10
|
+
from nbxtensor.dtype.nbx_dtype import NBXDtype, dtype_size
|
|
11
|
+
from nbxtensor.memory.allocator import DeviceAllocator
|
|
12
|
+
from nbxtensor.backend.runtime import set_device
|
|
13
|
+
from nbxtensor.shape import MAX_NDIM as _NBX_MAX_NDIM, broadcast_shapes, max_linear_offset
|
|
14
|
+
from nbxtensor.dtype.itemsize import canonical_name
|
|
15
|
+
from nbxtensor.dtype.promotion import binary_result, float_math_input
|
|
16
|
+
from nbxtensor.dtype.nbx_dtype import parse_dtype
|
|
17
|
+
from nbxtensor.memory.allocator import DeviceOOMError
|
|
18
|
+
from nbxtensor.backend.capabilities import fa_min_tile as _fa_min_tile
|
|
19
|
+
from nbxtensor.backend.capabilities import reduce_tile_max as _reduce_tile_max
|
|
20
|
+
from nbxtensor.backend.faults import device_fault_buffer, device_fault_code_cached, fault_channel
|
|
21
|
+
from nbxtensor.tensor import _upload_int64_array as _nbx_upload_int64, _saturating_cast as _nbx_saturating_cast
|
|
22
|
+
from nbxtensor.ops._configs import sdpa_block_ceiling as _sdpa_block_ceiling
|
|
23
|
+
from nbxtensor.ops._configs import sdpa_launch_meta as _sdpa_launch_meta
|
|
24
|
+
from nbxtensor.ops._configs import matrix_unit as _matrix_unit, matrix_unit_mm_tile as _matrix_unit_mm_tile
|
|
25
|
+
from nbxtensor.ops._configs import largest_tile_within_smem as _largest_tile_within_smem
|
|
26
|
+
from nbxtensor.backend import launcher as _nbx_launcher
|
|
27
|
+
from nbxtensor.backend.launcher import install as _install_launcher
|
|
28
|
+
from nbxtensor.ops.relu import relu_forward_kernel
|
|
29
|
+
from nbxtensor.ops.silu import silu_forward_kernel
|
|
30
|
+
from nbxtensor.ops.gelu import gelu_forward_kernel
|
|
31
|
+
from nbxtensor.ops.sigmoid import sigmoid_forward_kernel
|
|
32
|
+
from nbxtensor.ops.tanh import tanh_forward_kernel
|
|
33
|
+
from nbxtensor.ops.hardsigmoid import hardsigmoid_forward_kernel
|
|
34
|
+
from nbxtensor.ops.hardswish import hardswish_forward_kernel
|
|
35
|
+
from nbxtensor.ops.leaky_relu import leaky_relu_forward_kernel
|
|
36
|
+
from nbxtensor.ops.elu import elu_forward_kernel
|
|
37
|
+
from nbxtensor.ops.mish import mish_forward_kernel
|
|
38
|
+
from nbxtensor.ops.selu import selu_forward_kernel
|
|
39
|
+
from nbxtensor.ops.neg import neg_forward_kernel
|
|
40
|
+
from nbxtensor.ops.exp import exp_forward_kernel
|
|
41
|
+
from nbxtensor.ops.sin import sin_forward_kernel
|
|
42
|
+
from nbxtensor.ops.cos import cos_forward_kernel
|
|
43
|
+
from nbxtensor.ops.rsqrt import rsqrt_forward_kernel
|
|
44
|
+
from nbxtensor.ops.sqrt import sqrt_forward_kernel
|
|
45
|
+
from nbxtensor.ops.abs import abs_forward_kernel
|
|
46
|
+
from nbxtensor.ops.log import log_forward_kernel
|
|
47
|
+
from nbxtensor.ops.reciprocal import reciprocal_forward_kernel
|
|
48
|
+
from nbxtensor.ops.pow import pow_forward_kernel
|
|
49
|
+
from nbxtensor.ops.clamp import clamp_forward_kernel
|
|
50
|
+
from nbxtensor.ops.erf import erf_forward_kernel
|
|
51
|
+
from nbxtensor.ops.copy import copy_forward_kernel
|
|
52
|
+
from nbxtensor.ops.add import add_forward_kernel, add_scalar_kernel, add_scalar_dev_kernel, add_bias_broadcast_kernel
|
|
53
|
+
from nbxtensor.ops.binary_strided import add_strided_nd_kernel, mul_strided_nd_kernel, sub_strided_nd_kernel, div_strided_nd_kernel, add_scalar_strided_nd_kernel, mul_scalar_strided_nd_kernel
|
|
54
|
+
from nbxtensor.ops.mul import mul_forward_kernel, mul_scalar_kernel, mul_scalar_dev_kernel
|
|
55
|
+
from nbxtensor.ops.div import div_forward_kernel, div_scalar_kernel, div_scalar_dev_kernel, int_div_kernel
|
|
56
|
+
from nbxtensor.ops.sub import sub_forward_kernel, rsub_forward_kernel
|
|
57
|
+
from nbxtensor.ops.where import where_forward_kernel
|
|
58
|
+
from nbxtensor.ops.maximum import maximum_forward_kernel
|
|
59
|
+
from nbxtensor.ops.minimum import minimum_forward_kernel
|
|
60
|
+
from nbxtensor.ops.masked_fill import masked_fill_forward_kernel
|
|
61
|
+
from nbxtensor.ops.gt import gt_forward_kernel, gt_scalar_kernel
|
|
62
|
+
from nbxtensor.ops.ge import ge_forward_kernel, ge_scalar_kernel
|
|
63
|
+
from nbxtensor.ops.lt import lt_forward_kernel, lt_scalar_kernel
|
|
64
|
+
from nbxtensor.ops.eq import eq_forward_kernel, eq_scalar_kernel
|
|
65
|
+
from nbxtensor.ops.ne import ne_forward_kernel, ne_scalar_kernel
|
|
66
|
+
from nbxtensor.ops.layernorm import layer_norm_forward_kernel
|
|
67
|
+
from nbxtensor.ops.rmsnorm import rms_norm_forward_kernel
|
|
68
|
+
from nbxtensor.ops.softmax import softmax_forward_kernel, softmax_forward_looped_kernel
|
|
69
|
+
from nbxtensor.ops.matmul import matmul_kernel, addmm_kernel
|
|
70
|
+
from nbxtensor.ops._autotune_policy import NBX_FORCE_FP32_ACCUM
|
|
71
|
+
from nbxtensor.ops.fused_moe import fused_moe_kernel, silu_and_mul_kernel, silu_mul_split_kernel
|
|
72
|
+
from nbxtensor.ops.dtype_convert import bf16_to_fp16_kernel
|
|
73
|
+
from nbxtensor.ops.embedding import embedding_kernel, EMBEDDING_OOB
|
|
74
|
+
from nbxtensor.ops.sum import sum_forward_kernel
|
|
75
|
+
from nbxtensor.ops.mean import mean_forward_kernel
|
|
76
|
+
from nbxtensor.ops.amax import amax_forward_kernel
|
|
77
|
+
from nbxtensor.ops.glu import glu_forward_kernel
|
|
78
|
+
from nbxtensor.ops.le import le_forward_kernel, le_scalar_kernel
|
|
79
|
+
from nbxtensor.ops.softplus import softplus_forward_kernel
|
|
80
|
+
from nbxtensor.ops.dropout import dropout_inference_kernel
|
|
81
|
+
from nbxtensor.ops.upsample_nearest2d import upsample_nearest2d_kernel
|
|
82
|
+
from nbxtensor.ops.groupnorm import group_norm_forward_kernel
|
|
83
|
+
from nbxtensor.ops.index_select import index_select_kernel, INDEX_SELECT_OOB
|
|
84
|
+
from nbxtensor.ops.triu import triu_kernel, triu_batch_kernel
|
|
85
|
+
from nbxtensor.ops.tril import tril_kernel, tril_batch_kernel
|
|
86
|
+
from nbxtensor.ops.argmax import argmax_kernel_1, argmax_kernel_2, argmax_kernel_inner
|
|
87
|
+
from nbxtensor.ops.argmin import argmin_kernel_1, argmin_kernel_2, argmin_kernel
|
|
88
|
+
from nbxtensor.ops.conv2d import conv2d_forward_kernel
|
|
89
|
+
from nbxtensor.ops.depthwise_conv2d import depthwise_conv2d_kernel
|
|
90
|
+
from nbxtensor.ops.batch_norm import batch_norm_forward_kernel
|
|
91
|
+
from nbxtensor.ops.cumsum import scan_part_sum_kernel, add_base_sum_kernel
|
|
92
|
+
from nbxtensor.ops.scatter_op import scatter_kernel, scatter_add_kernel, scatter_reduce_amax_kernel, scatter_reduce_amin_kernel
|
|
93
|
+
from nbxtensor.ops.gather_op import gather_kernel
|
|
94
|
+
from nbxtensor.ops.avg_pool2d import avg_pool2d_forward_kernel
|
|
95
|
+
from nbxtensor.ops.max_pool2d import max_pool2d_forward_kernel
|
|
96
|
+
from nbxtensor.ops.max_pool3d import max_pool3d_forward_kernel
|
|
97
|
+
from nbxtensor.ops.cross_entropy import cross_entropy_loss_forward_kernel
|
|
98
|
+
from nbxtensor.ops.exp2 import exp2_forward_kernel
|
|
99
|
+
from nbxtensor.ops.tan import tan_forward_kernel
|
|
100
|
+
from nbxtensor.ops.celu import celu_forward_kernel
|
|
101
|
+
from nbxtensor.ops.log_sigmoid import log_sigmoid_forward_kernel
|
|
102
|
+
from nbxtensor.ops.isfinite import isfinite_forward_kernel
|
|
103
|
+
from nbxtensor.ops.isinf import isinf_forward_kernel
|
|
104
|
+
from nbxtensor.ops.isnan import isnan_forward_kernel
|
|
105
|
+
from nbxtensor.ops.nan_to_num import nan_to_num_forward_kernel
|
|
106
|
+
from nbxtensor.ops.threshold import threshold_forward_kernel
|
|
107
|
+
from nbxtensor.ops.floor import floor_forward_kernel, ceil_forward_kernel, round_forward_kernel, trunc_forward_kernel
|
|
108
|
+
from nbxtensor.ops.addcdiv import addcdiv_forward_kernel
|
|
109
|
+
from nbxtensor.ops.addcmul import addcmul_forward_kernel
|
|
110
|
+
from nbxtensor.ops.all_reduce import all_kernel_mid, all_kernel_result, all_kernel_dim
|
|
111
|
+
from nbxtensor.ops.any_reduce import any_kernel_mid, any_kernel_result, any_kernel_dim
|
|
112
|
+
from nbxtensor.ops.bitwise_and import bitwise_and_forward_kernel
|
|
113
|
+
from nbxtensor.ops.bitwise_or import bitwise_or_forward_kernel
|
|
114
|
+
from nbxtensor.ops.bitwise_not import bitwise_not_forward_kernel
|
|
115
|
+
from nbxtensor.ops.logical_and import logical_and_forward_kernel
|
|
116
|
+
from nbxtensor.ops.logical_or import logical_or_forward_kernel
|
|
117
|
+
from nbxtensor.ops.logical_not import logical_not_forward_kernel
|
|
118
|
+
from nbxtensor.ops.lerp import lerp_tensor_forward_kernel, lerp_scalar_head_kernel, lerp_scalar_tail_kernel
|
|
119
|
+
from nbxtensor.ops.dot_op import dot_kernel_small, dot_kernel_partial, dot_kernel_reduce
|
|
120
|
+
from nbxtensor.ops.flip_op import flip_1d_kernel, flip_strided_kernel
|
|
121
|
+
from nbxtensor.ops.cat_op import cat_copy_kernel_4
|
|
122
|
+
from nbxtensor.ops.fill_op import fill_kernel
|
|
123
|
+
from nbxtensor.ops.arange_op import arange_kernel
|
|
124
|
+
from nbxtensor.ops.prod import prod_kernel_mid, prod_kernel_result, prod_kernel
|
|
125
|
+
from nbxtensor.ops.min_reduce import min_kernel_mid, min_kernel_result, min_kernel
|
|
126
|
+
from nbxtensor.ops.max_reduce import max_kernel_mid, max_kernel_result, max_kernel
|
|
127
|
+
from nbxtensor.ops.baddbmm_op import baddbmm_kernel
|
|
128
|
+
from nbxtensor.ops.addmv_op import addmv_kernel
|
|
129
|
+
from nbxtensor.ops.mse_loss import mse_loss_partial_kernel, mse_loss_reduce_kernel, mse_loss_none_kernel
|
|
130
|
+
from nbxtensor.ops.nllloss import nll_loss_forward_kernel
|
|
131
|
+
from nbxtensor.ops.std import std_map_kernel, std_reduce_kernel, std_dim_kernel
|
|
132
|
+
from nbxtensor.ops.var import var_kernel_1, var_kernel_2, var_welford_kernel
|
|
133
|
+
from nbxtensor.ops.index_add import index_add_gather_kernel
|
|
134
|
+
from nbxtensor.ops.index_put_op import index_put_kernel, INDEX_PUT_OOB
|
|
135
|
+
from nbxtensor.ops.sort_op import (radix_sort_histogram_kernel,
|
|
136
|
+
radix_sort_scatter_kernel,
|
|
137
|
+
radix_sort_tile_counts_kernel,
|
|
138
|
+
radix_sort_tile_prefix_kernel)
|
|
139
|
+
from nbxtensor.ops.rope import rope_forward_kernel
|
|
140
|
+
from nbxtensor.ops.pixel_shuffle import (
|
|
141
|
+
pixel_shuffle_kernel,
|
|
142
|
+
pixel_shuffle_broadcast_aware_kernel,
|
|
143
|
+
)
|
|
144
|
+
from nbxtensor.ops.pixel_unshuffle import pixel_unshuffle_kernel
|
|
145
|
+
from nbxtensor.ops.upsample_bilinear2d import upsample_bilinear2d_kernel
|
|
146
|
+
from nbxtensor.ops.adaptive_avg_pool2d import adaptive_avg_pool2d_kernel
|
|
147
|
+
from nbxtensor.ops.conv_depthwise2d import conv_depthwise2d_kernel
|
|
148
|
+
from nbxtensor.ops.scaled_softmax import scaled_softmax_kernel
|
|
149
|
+
from nbxtensor.ops.vector_norm import (
|
|
150
|
+
l2_norm_kernel, l1_norm_kernel, linf_norm_kernel, l0_norm_kernel, lp_norm_kernel,
|
|
151
|
+
l2_norm_pass1_kernel, l2_norm_pass2_kernel,
|
|
152
|
+
linf_norm_pass1_kernel, linf_norm_pass2_kernel,
|
|
153
|
+
)
|
|
154
|
+
from nbxtensor.ops.addr import addr_kernel
|
|
155
|
+
from nbxtensor.ops.logical_xor import logical_xor_kernel
|
|
156
|
+
from nbxtensor.ops.bitwise_left_shift import bitwise_left_shift_kernel
|
|
157
|
+
from nbxtensor.ops.bitwise_right_shift import bitwise_right_shift_kernel
|
|
158
|
+
from nbxtensor.ops.rand_op import rand_kernel, randn_kernel
|
|
159
|
+
from nbxtensor.ops.clamp_min import clamp_min_forward_kernel
|
|
160
|
+
from nbxtensor.ops.conv_transpose2d import conv_transpose2d_kernel
|
|
161
|
+
from nbxtensor.ops.cummax import scan_part_max_kernel
|
|
162
|
+
from nbxtensor.ops.cummin import scan_part_min_kernel
|
|
163
|
+
from nbxtensor.ops.count_nonzero import count_nonzero_kernel
|
|
164
|
+
from nbxtensor.ops.trace_op import trace_kernel
|
|
165
|
+
from nbxtensor.ops.linspace_op import linspace_kernel
|
|
166
|
+
from nbxtensor.ops.full_op import full_kernel
|
|
167
|
+
from nbxtensor.ops.stack_op import stack_copy_kernel
|
|
168
|
+
from nbxtensor.ops.repeat_op import repeat_1d_kernel
|
|
169
|
+
from nbxtensor.ops.upsample_nearest3d import upsample_nearest3d_kernel
|
|
170
|
+
from nbxtensor.ops.weight_norm import weight_norm_kernel_first, weight_norm_kernel_last
|
|
171
|
+
from nbxtensor.ops.repeat_interleave import repeat_interleave_tensor_kernel
|
|
172
|
+
from nbxtensor.ops.elementwise_launch import ew_meta as _ew_meta, ew_grid as _ew_grid
|
|
173
|
+
from nbxtensor.ops import tiling_sizes as _ts
|
|
174
|
+
import math
|
|
175
|
+
from nbxtensor.ops import attention_wrappers as _attn
|
|
176
|
+
from nbxtensor.ops import index_wrappers as _idx
|
|
177
|
+
from nbxtensor.ops import linalg_wrappers as _la
|
|
178
|
+
from nbxtensor.ops import random_wrappers as _rnd
|
|
179
|
+
from nbxtensor.ops import reduction_wrappers as _red
|
|
180
|
+
from nbxtensor.ops import shape_wrappers as _shp
|
|
181
|
+
from nbxtensor.ops import wrappers as _core
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# A run of a Triton sequence (both engines say so at entry, `begin_run`): the
|
|
185
|
+
# caches that live for one run empty here. `_ROPE_TABLES`: the step's rotary
|
|
186
|
+
# tables widened ONCE per run — the graph hands every layer its own view of one
|
|
187
|
+
# base tensor, and each layer widened it again (2 × the layer count a token).
|
|
188
|
+
# Keyed by the base object's identity, held strongly for the run so the id
|
|
189
|
+
# cannot be reused; a later view of another base is another entry; nothing
|
|
190
|
+
# outlives the run (the arena may reuse the base's slot after its last read,
|
|
191
|
+
# and no consumer reads a view of it after that by construction).
|
|
192
|
+
_RUN_EPOCH = 0
|
|
193
|
+
_ROPE_TABLES: dict = {}
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def begin_run() -> None:
|
|
197
|
+
"""A Triton sequence's run begins: the per-run caches empty."""
|
|
198
|
+
global _RUN_EPOCH
|
|
199
|
+
_RUN_EPOCH += 1
|
|
200
|
+
_ROPE_TABLES.clear()
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _widened_once_per_run(t, target):
|
|
204
|
+
"""`t.to(target)` computed once per run for a base tensor and its view geometry."""
|
|
205
|
+
base = t._base if getattr(t, "_base", None) is not None else t
|
|
206
|
+
key = (id(base), t.data_ptr(), tuple(t.shape), tuple(t._strides), t.nbx_dtype)
|
|
207
|
+
hit = _ROPE_TABLES.get(key)
|
|
208
|
+
if hit is not None and hit[0] is base:
|
|
209
|
+
return hit[1]
|
|
210
|
+
widened = t.to(target)
|
|
211
|
+
_ROPE_TABLES[key] = (base, widened)
|
|
212
|
+
return widened
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _1d_grid(n_elements):
|
|
216
|
+
"""Grid for element-wise (memory-bound) kernels, at the certified tile of the count's class (`_ew_meta`)."""
|
|
217
|
+
return _ew_grid(n_elements, _ew_meta(n_elements))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _batch_block(batch_dim, feat_dim):
|
|
221
|
+
"""Replicate batch_block_heuristic from _configs.py for grid computation.
|
|
222
|
+
|
|
223
|
+
Must match the @triton.heuristics decorator on norm/softmax kernels.
|
|
224
|
+
"""
|
|
225
|
+
if feat_dim < 64:
|
|
226
|
+
return min(max(1, triton.next_power_of_2(batch_dim // 1024)), 128)
|
|
227
|
+
return 1
|
|
228
|
+
_RED_BM, _RED_BN = 8, 1024 # reduction kernels (prod, min, max, std, var, norm, all, any)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _reduction_tile(M):
|
|
232
|
+
"""(grid, block_m, block_n) for a full-row reduction gridded on M."""
|
|
233
|
+
bm, bn = _red._clamp_reduce_tile(_RED_BM, _RED_BN)
|
|
234
|
+
return (triton.cdiv(M, bm),), bm, bn
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# ===========================================================================
|
|
238
|
+
# ACTIVATION WRAPPERS
|
|
239
|
+
# ===========================================================================
|
|
240
|
+
|
|
241
|
+
def _dense_span(x):
|
|
242
|
+
"""`x` as the flat run of memory its elements fill when it is DENSE in some order of its dims (a permute of a
|
|
243
|
+
contiguous tensor: its strides, sorted, are the contiguous strides of its shape so permuted), else None."""
|
|
244
|
+
shape, stride = tuple(x.shape), tuple(x.stride())
|
|
245
|
+
expect = 1
|
|
246
|
+
for i in sorted((i for i in range(len(shape)) if shape[i] != 1), key=lambda i: stride[i]):
|
|
247
|
+
if stride[i] != expect:
|
|
248
|
+
return None
|
|
249
|
+
expect *= shape[i]
|
|
250
|
+
return x.as_strided((x.numel(),), (1,))
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _flat_unary(x, kernel, out=None):
|
|
254
|
+
"""An element-wise kernel over `x`'s elements, its output laid out as `x` is. A dense input in another order of
|
|
255
|
+
its dims (a permute's output) is read where it lies and the result takes its strides, as ATen's element-wise ops
|
|
256
|
+
keep a dense layout; a contiguous copy of it first cost one input-sized buffer the graph never priced
|
|
257
|
+
(mochi-1-preview's VAE: a 3 GB silu input, 2026-10-28). `out` (a caller that proved the input dies here) keeps the
|
|
258
|
+
contiguous path."""
|
|
259
|
+
if out is None and not x.is_contiguous():
|
|
260
|
+
span = _dense_span(x)
|
|
261
|
+
if span is not None:
|
|
262
|
+
n = span.numel()
|
|
263
|
+
res = NBXTensor.empty_like(span)
|
|
264
|
+
set_device(span)
|
|
265
|
+
kernel[_1d_grid(n)](span, res, n, **_ew_meta(n))
|
|
266
|
+
return res.as_strided(tuple(x.shape), tuple(x.stride()))
|
|
267
|
+
x = x.contiguous()
|
|
268
|
+
output = out if out is not None else NBXTensor.empty_like(x)
|
|
269
|
+
set_device(x)
|
|
270
|
+
kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
271
|
+
return output
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def relu(x, out=None) :
|
|
275
|
+
# `out` lets a caller that has PROVED this tensor's input dies here
|
|
276
|
+
# write the result into the input's own buffer. These kernels are
|
|
277
|
+
# strictly element-wise with matched offsets -- one load at
|
|
278
|
+
# `input_ptr + offset`, one store at `output_ptr + offset`, no
|
|
279
|
+
# cross-lane read -- so one pointer passed twice is correct by
|
|
280
|
+
# construction, not by luck. The liveness proof is the caller's:
|
|
281
|
+
# see `OpLevelTilingEngine._detect_inplace_unary_candidates`.
|
|
282
|
+
return _flat_unary(x, relu_forward_kernel, out)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def silu(x, out=None) :
|
|
286
|
+
# `out` lets a caller that has PROVED this tensor's input dies here
|
|
287
|
+
# write the result into the input's own buffer. These kernels are
|
|
288
|
+
# strictly element-wise with matched offsets -- one load at
|
|
289
|
+
# `input_ptr + offset`, one store at `output_ptr + offset`, no
|
|
290
|
+
# cross-lane read -- so one pointer passed twice is correct by
|
|
291
|
+
# construction, not by luck. The liveness proof is the caller's:
|
|
292
|
+
# see `OpLevelTilingEngine._detect_inplace_unary_candidates`.
|
|
293
|
+
return _flat_unary(x, silu_forward_kernel, out)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def gelu(x, approximate: str = 'none', out=None) :
|
|
297
|
+
x = x.contiguous()
|
|
298
|
+
# `out` lets a caller that has PROVED this tensor's input dies here
|
|
299
|
+
# write the result into the input's own buffer. These kernels are
|
|
300
|
+
# strictly element-wise with matched offsets -- one load at
|
|
301
|
+
# `input_ptr + offset`, one store at `output_ptr + offset`, no
|
|
302
|
+
# cross-lane read -- so one pointer passed twice is correct by
|
|
303
|
+
# construction, not by luck. The liveness proof is the caller's:
|
|
304
|
+
# see `OpLevelTilingEngine._detect_inplace_unary_candidates`.
|
|
305
|
+
output = out if out is not None else NBXTensor.empty_like(x)
|
|
306
|
+
set_device(x)
|
|
307
|
+
gelu_forward_kernel[_1d_grid(x.numel())](
|
|
308
|
+
x, output, x.numel(), approximate=(approximate == 'tanh'),
|
|
309
|
+
**_ew_meta(x.numel()))
|
|
310
|
+
return output
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def sigmoid_wrapper(x) :
|
|
314
|
+
return _flat_unary(x, sigmoid_forward_kernel)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def tanh_wrapper(x) :
|
|
318
|
+
return _flat_unary(x, tanh_forward_kernel)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def hardsigmoid(x) :
|
|
322
|
+
return _flat_unary(x, hardsigmoid_forward_kernel)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def hardswish(x, out=None) :
|
|
326
|
+
# `out` lets a caller that has PROVED this tensor's input dies here
|
|
327
|
+
# write the result into the input's own buffer. These kernels are
|
|
328
|
+
# strictly element-wise with matched offsets -- one load at
|
|
329
|
+
# `input_ptr + offset`, one store at `output_ptr + offset`, no
|
|
330
|
+
# cross-lane read -- so one pointer passed twice is correct by
|
|
331
|
+
# construction, not by luck. The liveness proof is the caller's:
|
|
332
|
+
# see `OpLevelTilingEngine._detect_inplace_unary_candidates`.
|
|
333
|
+
return _flat_unary(x, hardswish_forward_kernel, out)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def leaky_relu(x, negative_slope: float = 0.01, out=None) :
|
|
337
|
+
x = x.contiguous()
|
|
338
|
+
# `out` lets a caller that has PROVED this tensor's input dies here
|
|
339
|
+
# write the result into the input's own buffer. These kernels are
|
|
340
|
+
# strictly element-wise with matched offsets -- one load at
|
|
341
|
+
# `input_ptr + offset`, one store at `output_ptr + offset`, no
|
|
342
|
+
# cross-lane read -- so one pointer passed twice is correct by
|
|
343
|
+
# construction, not by luck. The liveness proof is the caller's:
|
|
344
|
+
# see `OpLevelTilingEngine._detect_inplace_unary_candidates`.
|
|
345
|
+
output = out if out is not None else NBXTensor.empty_like(x)
|
|
346
|
+
set_device(x)
|
|
347
|
+
leaky_relu_forward_kernel[_1d_grid(x.numel())](
|
|
348
|
+
x, output, x.numel(), negative_slope,
|
|
349
|
+
**_ew_meta(x.numel()))
|
|
350
|
+
return output
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def elu(x, alpha: float = 1.0, out=None) :
|
|
354
|
+
x = x.contiguous()
|
|
355
|
+
# `out` lets a caller that has PROVED this tensor's input dies here
|
|
356
|
+
# write the result into the input's own buffer. These kernels are
|
|
357
|
+
# strictly element-wise with matched offsets -- one load at
|
|
358
|
+
# `input_ptr + offset`, one store at `output_ptr + offset`, no
|
|
359
|
+
# cross-lane read -- so one pointer passed twice is correct by
|
|
360
|
+
# construction, not by luck. The liveness proof is the caller's:
|
|
361
|
+
# see `OpLevelTilingEngine._detect_inplace_unary_candidates`.
|
|
362
|
+
output = out if out is not None else NBXTensor.empty_like(x)
|
|
363
|
+
set_device(x)
|
|
364
|
+
elu_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), alpha, **_ew_meta(x.numel()))
|
|
365
|
+
return output
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def mish(x, out=None) :
|
|
369
|
+
# `out` lets a caller that has PROVED this tensor's input dies here
|
|
370
|
+
# write the result into the input's own buffer. These kernels are
|
|
371
|
+
# strictly element-wise with matched offsets -- one load at
|
|
372
|
+
# `input_ptr + offset`, one store at `output_ptr + offset`, no
|
|
373
|
+
# cross-lane read -- so one pointer passed twice is correct by
|
|
374
|
+
# construction, not by luck. The liveness proof is the caller's:
|
|
375
|
+
# see `OpLevelTilingEngine._detect_inplace_unary_candidates`.
|
|
376
|
+
return _flat_unary(x, mish_forward_kernel, out)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def selu_wrapper(x) :
|
|
380
|
+
return _flat_unary(x, selu_forward_kernel)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
# ===========================================================================
|
|
384
|
+
# UNARY ELEMENT-WISE WRAPPERS
|
|
385
|
+
# ===========================================================================
|
|
386
|
+
|
|
387
|
+
def neg(x) :
|
|
388
|
+
return _flat_unary(x, neg_forward_kernel)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def exp(x) :
|
|
392
|
+
if isinstance(x, NBXTensor) and x.is_complex():
|
|
393
|
+
# exp(a + bi) = e^a (cos b + i sin b)
|
|
394
|
+
a = x.real.contiguous()
|
|
395
|
+
b = x.imag.contiguous()
|
|
396
|
+
ea = exp(a)
|
|
397
|
+
return complex_wrapper(mul(ea, cos(b)), mul(ea, sin(b)))
|
|
398
|
+
x = x.contiguous()
|
|
399
|
+
output = NBXTensor.empty_like(x)
|
|
400
|
+
set_device(x)
|
|
401
|
+
exp_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
402
|
+
return output
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def sin(x) :
|
|
406
|
+
return _flat_unary(x, sin_forward_kernel)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def cos(x) :
|
|
410
|
+
return _flat_unary(x, cos_forward_kernel)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _promote_int_unary(x):
|
|
414
|
+
"""Float-math unary ops (rsqrt/sqrt/log/exp/reciprocal/...) promote integer
|
|
415
|
+
or bool input to float32, matching PyTorch type promotion. Without it the
|
|
416
|
+
kernel computes in the integer dtype AND NBXTensor.empty_like keeps the int
|
|
417
|
+
output — e.g. rsqrt(int64 2) truncates 0.7071 to 0, which silently collapsed
|
|
418
|
+
the Kokoro AdainResBlk1d residual `(h + sc) * rsqrt(2)` to all-zeros (the
|
|
419
|
+
rsqrt(2) constant was traced as an int64 scalar; torch auto-promotes it).
|
|
420
|
+
No-op for tensors already in a floating dtype."""
|
|
421
|
+
want = float_math_input(canonical_name(x.nbx_dtype)) if hasattr(x, "nbx_dtype") else None
|
|
422
|
+
if want is not None and want != canonical_name(x.nbx_dtype):
|
|
423
|
+
return x.to(parse_dtype(want))
|
|
424
|
+
return x
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def rsqrt(x) :
|
|
428
|
+
x = _promote_int_unary(x).contiguous()
|
|
429
|
+
output = NBXTensor.empty_like(x)
|
|
430
|
+
set_device(x)
|
|
431
|
+
rsqrt_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
432
|
+
return output
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def sqrt_wrapper(x) :
|
|
436
|
+
x = _promote_int_unary(x).contiguous()
|
|
437
|
+
output = NBXTensor.empty_like(x)
|
|
438
|
+
set_device(x)
|
|
439
|
+
sqrt_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
440
|
+
return output
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def abs_wrapper(x) :
|
|
444
|
+
if x.is_complex():
|
|
445
|
+
# abs of a complex tensor is the REAL magnitude sqrt(re^2 + im^2). The
|
|
446
|
+
# element-wise float path below is doubly wrong for complex64: empty_like
|
|
447
|
+
# keeps the complex dtype AND the kernel runs over x.numel() interleaved
|
|
448
|
+
# [re,im] floats (|interleaved float|, not the magnitude) — half the
|
|
449
|
+
# storage, mis-typed output, heap corruption. Exposed by the Kokoro iSTFT
|
|
450
|
+
# source STFT (abs of _fft_r2c) feeding generator.noise_convs. Computed
|
|
451
|
+
# R33-pure from the stride-2 real/imag views.
|
|
452
|
+
re = x.real
|
|
453
|
+
im = x.imag
|
|
454
|
+
return sqrt_wrapper(add(mul(re, re), mul(im, im)))
|
|
455
|
+
x = x.contiguous()
|
|
456
|
+
output = NBXTensor.empty_like(x)
|
|
457
|
+
set_device(x)
|
|
458
|
+
abs_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
459
|
+
return output
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def log_wrapper(x) :
|
|
463
|
+
x = _promote_int_unary(x).contiguous()
|
|
464
|
+
output = NBXTensor.empty_like(x)
|
|
465
|
+
set_device(x)
|
|
466
|
+
log_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
467
|
+
return output
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def reciprocal(x) :
|
|
471
|
+
x = _promote_int_unary(x).contiguous()
|
|
472
|
+
output = NBXTensor.empty_like(x)
|
|
473
|
+
set_device(x)
|
|
474
|
+
reciprocal_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
475
|
+
return output
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def pow_wrapper(x, exponent) :
|
|
479
|
+
# aten::pow has four forms — the base and/or exponent may be a scalar:
|
|
480
|
+
# tensor ** scalar → fused kernel (fast path)
|
|
481
|
+
# scalar ** tensor → e.g. 10000.0 ** (arange/dim), the sinusoidal/RoPE
|
|
482
|
+
# frequency base (Flex DiT) — a**b = exp(b·ln a)
|
|
483
|
+
# tensor ** tensor → exp(b·ln a)
|
|
484
|
+
# scalar ** scalar → constant
|
|
485
|
+
# All non-fast cases route through exp/mul/log (R33-pure, no torch).
|
|
486
|
+
import math
|
|
487
|
+
x_is_t = isinstance(x, NBXTensor)
|
|
488
|
+
e_is_t = isinstance(exponent, NBXTensor)
|
|
489
|
+
if x_is_t and not e_is_t:
|
|
490
|
+
x = x.contiguous()
|
|
491
|
+
output = NBXTensor.empty_like(x)
|
|
492
|
+
set_device(x)
|
|
493
|
+
# A small integer exponent takes the exact multiplication route at
|
|
494
|
+
# compile time (kernels/ops/pow.py, INT_EXP); everything else the
|
|
495
|
+
# general exp/log route. Measured 2026-09-20 on CUDA: x**2 by exp/log is
|
|
496
|
+
# up to 15 ulps from the fp64 oracle, by multiplication within 1.
|
|
497
|
+
_e = float(exponent)
|
|
498
|
+
_int_exp = int(_e) if (_e == int(_e) and 1 <= abs(_e) <= 8) else 0
|
|
499
|
+
pow_forward_kernel[_1d_grid(x.numel())](
|
|
500
|
+
x, output, x.numel(), _e,
|
|
501
|
+
**_ew_meta(x.numel()), INT_EXP=_int_exp)
|
|
502
|
+
return output
|
|
503
|
+
if (not x_is_t) and e_is_t:
|
|
504
|
+
# scalar ** tensor = exp(exponent · ln(scalar))
|
|
505
|
+
return exp(mul(exponent, math.log(float(x))))
|
|
506
|
+
if x_is_t and e_is_t:
|
|
507
|
+
# tensor ** tensor = exp(exponent · ln(base))
|
|
508
|
+
return exp(mul(exponent, log_wrapper(x)))
|
|
509
|
+
return float(x) ** float(exponent)
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def clamp(x, min_val=None, max_val=None) :
|
|
513
|
+
# aten::clamp.Tensor: a min/max BOUND may itself be an NBXTensor (e.g. Flex
|
|
514
|
+
# DiT). The fused scalar kernel can't take a tensor bound, so route tensor
|
|
515
|
+
# bounds through elementwise maximum/minimum (R33-pure); scalar bounds keep
|
|
516
|
+
# the fused kernel. Mixed (one tensor, one scalar) handled per side.
|
|
517
|
+
if isinstance(min_val, NBXTensor) or isinstance(max_val, NBXTensor):
|
|
518
|
+
out = x
|
|
519
|
+
if min_val is not None:
|
|
520
|
+
out = (maximum_wrapper(out, min_val) if isinstance(min_val, NBXTensor)
|
|
521
|
+
else clamp(out, min_val=float(min_val), max_val=None))
|
|
522
|
+
if max_val is not None:
|
|
523
|
+
out = (minimum_wrapper(out, max_val) if isinstance(max_val, NBXTensor)
|
|
524
|
+
else clamp(out, min_val=None, max_val=float(max_val)))
|
|
525
|
+
return out
|
|
526
|
+
x = x.contiguous()
|
|
527
|
+
output = NBXTensor.empty_like(x)
|
|
528
|
+
_min = float(min_val) if min_val is not None else 0.0
|
|
529
|
+
_max = float(max_val) if max_val is not None else 0.0
|
|
530
|
+
set_device(x)
|
|
531
|
+
clamp_forward_kernel[_1d_grid(x.numel())](
|
|
532
|
+
x, output, x.numel(), _min, _max,
|
|
533
|
+
has_min=(min_val is not None), has_max=(max_val is not None),
|
|
534
|
+
**_ew_meta(x.numel()))
|
|
535
|
+
return output
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def erf(x) :
|
|
539
|
+
x = _promote_int_unary(x).contiguous()
|
|
540
|
+
output = NBXTensor.empty_like(x)
|
|
541
|
+
set_device(x)
|
|
542
|
+
erf_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
543
|
+
return output
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def floor_wrapper(x):
|
|
547
|
+
x = x.contiguous()
|
|
548
|
+
output = NBXTensor.empty_like(x)
|
|
549
|
+
set_device(x)
|
|
550
|
+
floor_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
551
|
+
return output
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def ceil_wrapper(x):
|
|
555
|
+
x = x.contiguous()
|
|
556
|
+
output = NBXTensor.empty_like(x)
|
|
557
|
+
set_device(x)
|
|
558
|
+
ceil_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
559
|
+
return output
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def floor_divide_wrapper(a, b):
|
|
563
|
+
"""aten::floor_divide — floor(a / b), result in a's dtype.
|
|
564
|
+
|
|
565
|
+
Two integer operands divide in the integers (`_int_divide`), exact at every magnitude. A
|
|
566
|
+
float operand uses the float division (floor(a/b) is the ATen semantic, no exactness cliff
|
|
567
|
+
involved); an integer tensor beside it widens through the backend's float
|
|
568
|
+
(`_int_division_widening_dtype`)."""
|
|
569
|
+
if _both_integer(a, b):
|
|
570
|
+
return _int_divide(a, b, floor=True)
|
|
571
|
+
a_dtype = a.dtype
|
|
572
|
+
_is_int = hasattr(a_dtype, "is_floating") and not a_dtype.is_floating()
|
|
573
|
+
if _is_int:
|
|
574
|
+
wide = _core._int_division_widening_dtype(a, b)
|
|
575
|
+
a_f = a.to(wide)
|
|
576
|
+
b_f = b.to(wide) if hasattr(b, "to") else b
|
|
577
|
+
else:
|
|
578
|
+
a_f = a
|
|
579
|
+
b_f = b
|
|
580
|
+
out = floor_wrapper(div(a_f, b_f))
|
|
581
|
+
return out.to(a_dtype) if out.dtype != a_dtype else out
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _both_integer(a, b):
|
|
585
|
+
return _core._is_integer_operand(a) and _core._is_integer_operand(b) and (isinstance(a, NBXTensor) or isinstance(b, NBXTensor))
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _int_divide(a, b, floor):
|
|
589
|
+
"""Integer // integer in the integers (`int_div_kernel`): truncated, or floored when `floor`.
|
|
590
|
+
A Python int operand takes the tensor's dtype; two tensors take the wider; both broadcast."""
|
|
591
|
+
import numpy as np
|
|
592
|
+
ref = a if isinstance(a, NBXTensor) else b
|
|
593
|
+
dtype = _core._wider_dtype(a.dtype, b.dtype) if isinstance(a, NBXTensor) and isinstance(b, NBXTensor) else ref.dtype
|
|
594
|
+
a, b = (x if isinstance(x, NBXTensor) else NBXTensor.from_numpy(np.array(x, dtype=np.dtype(dtype.name)))
|
|
595
|
+
for x in (a, b))
|
|
596
|
+
if hasattr(a, "_device_idx") and hasattr(b, "_device_idx") and a._device_idx != b._device_idx:
|
|
597
|
+
b = _core._transfer_to_device(b, a._device_idx)
|
|
598
|
+
a, b = (x if x.dtype == dtype else x.to(dtype) for x in (a, b))
|
|
599
|
+
shape = broadcast_shapes(a.shape, b.shape)
|
|
600
|
+
a, b = (x.expand(shape).contiguous() if tuple(x.shape) != tuple(shape) else x.contiguous() for x in (a, b))
|
|
601
|
+
set_device(a)
|
|
602
|
+
output = NBXTensor.empty_like(a)
|
|
603
|
+
n = output.numel()
|
|
604
|
+
if n:
|
|
605
|
+
int_div_kernel[_1d_grid(n)](a, b, output, n, FLOOR=floor, **_ew_meta(n))
|
|
606
|
+
return output
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def round_wrapper(x):
|
|
610
|
+
x = x.contiguous()
|
|
611
|
+
output = NBXTensor.empty_like(x)
|
|
612
|
+
set_device(x)
|
|
613
|
+
round_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
614
|
+
return output
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def trunc_wrapper(x):
|
|
618
|
+
x = x.contiguous()
|
|
619
|
+
output = NBXTensor.empty_like(x)
|
|
620
|
+
set_device(x)
|
|
621
|
+
trunc_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
622
|
+
return output
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
# ===========================================================================
|
|
626
|
+
# BINARY ELEMENT-WISE WRAPPERS — use universal launch layer
|
|
627
|
+
# ===========================================================================
|
|
628
|
+
|
|
629
|
+
def _complex_addsub(a, b, alpha: float, is_sub: bool):
|
|
630
|
+
"""Complex elementwise add/sub with correct broadcasting.
|
|
631
|
+
|
|
632
|
+
The plain float add/sub kernels broadcast the interleaved [re,im] storage of
|
|
633
|
+
a complex tensor on the wrong stride (they treat it as flat floats, so re/im
|
|
634
|
+
misalign across operands when shapes differ — e.g. the Wan rotary-embedding
|
|
635
|
+
freqs grid, built by broadcasting complex per-axis freqs [T,1,1,d]+[T,H,W,1]).
|
|
636
|
+
Routing through view_as_real appends the [.,2] real/imag axis (broadcasts as
|
|
637
|
+
2==2), keeping re aligned with re and im with im. R33-pure.
|
|
638
|
+
"""
|
|
639
|
+
ac = isinstance(a, NBXTensor) and a.is_complex()
|
|
640
|
+
bc = isinstance(b, NBXTensor) and b.is_complex()
|
|
641
|
+
if ac and bc:
|
|
642
|
+
ar, br = a.view_as_real(), b.view_as_real()
|
|
643
|
+
out = sub(ar, br, alpha) if is_sub else add(ar, br, alpha)
|
|
644
|
+
return out.view_as_complex()
|
|
645
|
+
# Mixed complex + real: the real operand affects only the real part. Rare in
|
|
646
|
+
# current models; route via the real-view interior and leave imag untouched.
|
|
647
|
+
cplx, real = (a, b) if ac else (b, a)
|
|
648
|
+
vr = cplx.view_as_real().contiguous()
|
|
649
|
+
re = vr.select(vr.ndim - 1, 0)
|
|
650
|
+
im = vr.select(vr.ndim - 1, 1)
|
|
651
|
+
if is_sub and not ac: # real - complex = (real-re) + i(-im)
|
|
652
|
+
re2 = sub(real, re, 1.0); im2 = mul(im, -1.0)
|
|
653
|
+
elif is_sub: # complex - real
|
|
654
|
+
re2 = sub(re, real, alpha); im2 = im
|
|
655
|
+
else: # complex + real
|
|
656
|
+
re2 = add(re, real, alpha); im2 = im
|
|
657
|
+
return complex_wrapper(re2.contiguous(), im2.contiguous())
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def add(a, b, alpha: float = 1.0) :
|
|
661
|
+
if (isinstance(a, NBXTensor) and a.is_complex()) or (isinstance(b, NBXTensor) and b.is_complex()):
|
|
662
|
+
return _complex_addsub(a, b, alpha, is_sub=False)
|
|
663
|
+
# Bias-broadcast fast path: when `b` is a 1D tensor matching `a`'s
|
|
664
|
+
# last dim and `a` is already contiguous, route to the broadcast-aware
|
|
665
|
+
# kernel that reads `bias[offset % feat_dim]` instead of materializing
|
|
666
|
+
# an 8 GiB contiguous expand of the bias. Sana 4Kpx VAE add::88
|
|
667
|
+
# (`mul::58::out_0 (1, 4096, 4096, 128) + bias (128)`) is the
|
|
668
|
+
# canonical case — saves an 8 GiB transient that otherwise crowds
|
|
669
|
+
# out the rms_norm output during the post-pixel_shuffle chain.
|
|
670
|
+
if (not _core._is_scalar(b)
|
|
671
|
+
and hasattr(a, '_dtype') and hasattr(b, '_dtype')
|
|
672
|
+
and a.ndim >= 1 and b.ndim == 1
|
|
673
|
+
and a.shape[-1] == b.shape[0]
|
|
674
|
+
and a.is_contiguous()):
|
|
675
|
+
# Dtype align (mirror _prepare_binary).
|
|
676
|
+
common_dtype = _core._wider_dtype(a._dtype, b._dtype)
|
|
677
|
+
if a._dtype != common_dtype:
|
|
678
|
+
a = a.to(common_dtype)
|
|
679
|
+
if b._dtype != common_dtype:
|
|
680
|
+
b = b.to(common_dtype)
|
|
681
|
+
# Device align.
|
|
682
|
+
if (hasattr(a, '_device_idx') and hasattr(b, '_device_idx')
|
|
683
|
+
and a._device_idx != b._device_idx):
|
|
684
|
+
b = _core._transfer_to_device(b, a._device_idx)
|
|
685
|
+
n = a.numel()
|
|
686
|
+
feat_dim = b.shape[0]
|
|
687
|
+
output = NBXTensor.empty_like(a)
|
|
688
|
+
set_device(a)
|
|
689
|
+
add_bias_broadcast_kernel[_1d_grid(n)](
|
|
690
|
+
a, b, output, n, feat_dim, alpha,
|
|
691
|
+
**_ew_meta(n))
|
|
692
|
+
return output
|
|
693
|
+
|
|
694
|
+
sc = _core._prepare_scalar_strided(a, b)
|
|
695
|
+
if sc is not None:
|
|
696
|
+
x, scalar, output, n, shp, x_st, ndim = sc
|
|
697
|
+
add_scalar_strided_nd_kernel[_1d_grid(n)](x, output, n, float(scalar) * alpha, shp, x_st,
|
|
698
|
+
**_ew_meta(n), NDIM=ndim)
|
|
699
|
+
return output
|
|
700
|
+
strided = _core._prepare_binary_strided(a, b)
|
|
701
|
+
if strided[0] is not None:
|
|
702
|
+
a, b, output, n, shp, a_st, b_st, ndim = strided
|
|
703
|
+
add_strided_nd_kernel[_1d_grid(n)](a, b, output, n, alpha, shp, a_st, b_st,
|
|
704
|
+
**_ew_meta(n), NDIM=ndim)
|
|
705
|
+
return output
|
|
706
|
+
_, a, b = strided
|
|
707
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_binary(a, b)
|
|
708
|
+
if scalar == "dev":
|
|
709
|
+
add_scalar_dev_kernel[_1d_grid(n)](a, b, output, n, float(alpha), **_ew_meta(n))
|
|
710
|
+
elif scalar:
|
|
711
|
+
add_scalar_kernel[_1d_grid(n)](a, output, n, float(b) * alpha, **_ew_meta(n))
|
|
712
|
+
else:
|
|
713
|
+
add_forward_kernel[_1d_grid(n)](a, b, output, n, alpha, **_ew_meta(n))
|
|
714
|
+
return output
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def add_inplace_nbx(target, other, alpha: float = 1.0):
|
|
718
|
+
"""In-place element-wise add: target += alpha * other.
|
|
719
|
+
|
|
720
|
+
Used by op-level tiling for residual adds where Prism's liveness
|
|
721
|
+
analysis proved that one input has its last use at this op (so its
|
|
722
|
+
buffer can be safely reused as output, avoiding a 3rd allocation).
|
|
723
|
+
Saves up to N bytes per call where N = output tensor size — on
|
|
724
|
+
Sana 4Kpx VAE residual adds at (1, 128, 4096, 4096) fp32 this is
|
|
725
|
+
8 GiB per call.
|
|
726
|
+
|
|
727
|
+
CONTRACT (caller-enforced by Prism liveness analysis):
|
|
728
|
+
- target.shape == other.shape (no broadcast, by construction since
|
|
729
|
+
residual adds always operate on identical shapes)
|
|
730
|
+
- target.dtype == other.dtype
|
|
731
|
+
- target.device == other.device
|
|
732
|
+
- target is contiguous (produced by upstream kernels which write
|
|
733
|
+
contiguous tensors by default)
|
|
734
|
+
- target has no other consumer in the graph (last use here)
|
|
735
|
+
|
|
736
|
+
Returns target (same object, mutated in place).
|
|
737
|
+
"""
|
|
738
|
+
assert target.shape == other.shape, (
|
|
739
|
+
f"add_inplace_nbx requires identical shapes; got "
|
|
740
|
+
f"target={target.shape} other={other.shape}")
|
|
741
|
+
# POINT 6 H2 FIX: kernel uses flat 1D indexing
|
|
742
|
+
# `tl.load/store(target_ptr + offset, ...)` which assumes target
|
|
743
|
+
# is contiguous in memory. If target is a non-contiguous view
|
|
744
|
+
# (permute, transpose, slice with stride), flat indexing reads/
|
|
745
|
+
# writes wrong addresses — silent corruption. The caller's "last-
|
|
746
|
+
# use" liveness analysis from Prism does not check contiguity.
|
|
747
|
+
# Pre-fix on Sana 4Kpx VAE: residual adds at op 649+ (add::69
|
|
748
|
+
# onward) showed +1.3% rel divergence introducing horizontal-
|
|
749
|
+
# band garbage that then amplified through downstream tiled
|
|
750
|
+
# kernels. Fall back to the standard non-in-place `add` when
|
|
751
|
+
# target is not contiguous (handles strided views correctly via
|
|
752
|
+
# `_prepare_binary` + `expand+contiguous`).
|
|
753
|
+
if not target.is_contiguous():
|
|
754
|
+
return add(target, other, alpha=alpha)
|
|
755
|
+
if hasattr(target, '_device_idx') and hasattr(other, '_device_idx') \
|
|
756
|
+
and target._device_idx != other._device_idx:
|
|
757
|
+
other = _core._transfer_to_device(other, target._device_idx)
|
|
758
|
+
# Dtype alignment. In-place semantics require writing into target's
|
|
759
|
+
# buffer, so target's dtype is the result dtype. If `other` is at a
|
|
760
|
+
# different precision, cast it (the cast allocates a transient sized
|
|
761
|
+
# like other — same VRAM cost as a non-in-place add only when other
|
|
762
|
+
# < target dtype). If target is NARROWER than other, in-place would
|
|
763
|
+
# lose precision: fall back to the standard non-in-place `add` so the
|
|
764
|
+
# standard `_wider_dtype` upcast path kicks in.
|
|
765
|
+
if target._dtype != other._dtype:
|
|
766
|
+
if _core._wider_dtype(target._dtype, other._dtype) == target._dtype:
|
|
767
|
+
other = other.to(target._dtype)
|
|
768
|
+
else:
|
|
769
|
+
return add(target, other, alpha=alpha)
|
|
770
|
+
other = other.contiguous()
|
|
771
|
+
n = target.numel()
|
|
772
|
+
set_device(target)
|
|
773
|
+
add_forward_kernel[_1d_grid(n)](
|
|
774
|
+
target, other, target, n, alpha,
|
|
775
|
+
**_ew_meta(n))
|
|
776
|
+
return target
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def _as_complex_scalar(v):
|
|
780
|
+
"""Parse a complex literal ('1j', '(1+2j)', a Python complex) → complex, else None.
|
|
781
|
+
The tracer serializes complex constants like the imaginary unit as an
|
|
782
|
+
'unknown'-typed string ('1j')."""
|
|
783
|
+
if isinstance(v, complex):
|
|
784
|
+
return v
|
|
785
|
+
if isinstance(v, str):
|
|
786
|
+
try:
|
|
787
|
+
return complex(v.strip())
|
|
788
|
+
except (ValueError, TypeError):
|
|
789
|
+
return None
|
|
790
|
+
return None
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def _cmul_scalar(x, c):
|
|
794
|
+
"""x (real or complex NBXTensor) * complex scalar c → complex64."""
|
|
795
|
+
cr, ci = c.real, c.imag
|
|
796
|
+
if isinstance(x, NBXTensor) and x.is_complex():
|
|
797
|
+
xr, xi = x.real, x.imag
|
|
798
|
+
return complex_wrapper(sub(mul(xr, cr), mul(xi, ci)),
|
|
799
|
+
add(mul(xr, ci), mul(xi, cr)))
|
|
800
|
+
real = mul(x, cr) if cr != 0.0 else NBXTensor.zeros_like(x)
|
|
801
|
+
imag = mul(x, ci) if ci != 0.0 else NBXTensor.zeros_like(x)
|
|
802
|
+
return complex_wrapper(real, imag)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def _complex_mul(a, b):
|
|
806
|
+
"""Handle a mul involving a complex scalar/tensor. Returns the complex result,
|
|
807
|
+
or None to fall through to the real elementwise mul (the common case)."""
|
|
808
|
+
ca = _as_complex_scalar(a)
|
|
809
|
+
cb = _as_complex_scalar(b)
|
|
810
|
+
a_t = isinstance(a, NBXTensor) and a.is_complex()
|
|
811
|
+
b_t = isinstance(b, NBXTensor) and b.is_complex()
|
|
812
|
+
if ca is None and cb is None and not a_t and not b_t:
|
|
813
|
+
return None # purely real → real mul
|
|
814
|
+
if cb is not None and isinstance(a, NBXTensor):
|
|
815
|
+
return _cmul_scalar(a, cb)
|
|
816
|
+
if ca is not None and isinstance(b, NBXTensor):
|
|
817
|
+
return _cmul_scalar(b, ca)
|
|
818
|
+
# complex tensor * (real or complex) tensor
|
|
819
|
+
if a_t or b_t:
|
|
820
|
+
if a_t and not b_t:
|
|
821
|
+
return complex_wrapper(mul(a.real, b), mul(a.imag, b))
|
|
822
|
+
if b_t and not a_t:
|
|
823
|
+
return complex_wrapper(mul(b.real, a), mul(b.imag, a))
|
|
824
|
+
ar, ai, br, bi = a.real, a.imag, b.real, b.imag
|
|
825
|
+
return complex_wrapper(sub(mul(ar, br), mul(ai, bi)),
|
|
826
|
+
add(mul(ar, bi), mul(ai, br)))
|
|
827
|
+
return None
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
def mul(a, b) :
|
|
831
|
+
cr = _complex_mul(a, b)
|
|
832
|
+
if cr is not None:
|
|
833
|
+
return cr
|
|
834
|
+
sc = _core._prepare_scalar_strided(a, b)
|
|
835
|
+
if sc is not None:
|
|
836
|
+
x, scalar, output, n, shp, x_st, ndim = sc
|
|
837
|
+
mul_scalar_strided_nd_kernel[_1d_grid(n)](x, output, n, float(scalar), shp, x_st,
|
|
838
|
+
**_ew_meta(n), NDIM=ndim)
|
|
839
|
+
return output
|
|
840
|
+
strided = _core._prepare_binary_strided(a, b)
|
|
841
|
+
if strided[0] is not None:
|
|
842
|
+
a, b, output, n, shp, a_st, b_st, ndim = strided
|
|
843
|
+
mul_strided_nd_kernel[_1d_grid(n)](a, b, output, n, shp, a_st, b_st,
|
|
844
|
+
**_ew_meta(n), NDIM=ndim)
|
|
845
|
+
return output
|
|
846
|
+
_, a, b = strided
|
|
847
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_binary(a, b)
|
|
848
|
+
if scalar == "dev":
|
|
849
|
+
mul_scalar_dev_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
850
|
+
elif scalar:
|
|
851
|
+
# Contract: tensor is always `a`, scalar is always `b`
|
|
852
|
+
mul_scalar_kernel[_1d_grid(n)](a, output, n, float(b), **_ew_meta(n))
|
|
853
|
+
else:
|
|
854
|
+
mul_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
855
|
+
return output
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def div(a, b, rounding_mode=None) :
|
|
859
|
+
# aten::div carries an optional rounding_mode kwarg ("floor"/"trunc",
|
|
860
|
+
# e.g. the SD3-class pos_embed crop offset (max-h)//2 traces as
|
|
861
|
+
# div(rounding_mode=floor) on a 0-dim int64). Routed onto the
|
|
862
|
+
# existing bricks — floor_divide_wrapper's fp64 int discipline for
|
|
863
|
+
# floor, the same composition with trunc for trunc.
|
|
864
|
+
if rounding_mode == "floor":
|
|
865
|
+
return floor_divide_wrapper(a, b)
|
|
866
|
+
if rounding_mode == "trunc":
|
|
867
|
+
if _both_integer(a, b):
|
|
868
|
+
return _int_divide(a, b, floor=False)
|
|
869
|
+
a_dtype = a.dtype
|
|
870
|
+
_is_int = hasattr(a_dtype, "is_floating") and not a_dtype.is_floating()
|
|
871
|
+
if _is_int:
|
|
872
|
+
wide = _core._int_division_widening_dtype(a, b)
|
|
873
|
+
a_f = a.to(wide)
|
|
874
|
+
b_f = b.to(wide) if hasattr(b, "to") else b
|
|
875
|
+
else:
|
|
876
|
+
a_f, b_f = a, b
|
|
877
|
+
out = trunc_wrapper(div(a_f, b_f))
|
|
878
|
+
return out.to(a_dtype) if out.dtype != a_dtype else out
|
|
879
|
+
if rounding_mode is not None:
|
|
880
|
+
raise RuntimeError(
|
|
881
|
+
f"ZERO FALLBACK: aten::div rounding_mode '{rounding_mode}' "
|
|
882
|
+
"is not a known ATen mode (floor/trunc/None).")
|
|
883
|
+
strided = _core._prepare_binary_strided(a, b)
|
|
884
|
+
if strided[0] is not None:
|
|
885
|
+
a, b, output, n, shp, a_st, b_st, ndim = strided
|
|
886
|
+
div_strided_nd_kernel[_1d_grid(n)](a, b, output, n, shp, a_st, b_st,
|
|
887
|
+
**_ew_meta(n), NDIM=ndim)
|
|
888
|
+
return output
|
|
889
|
+
_, a, b = strided
|
|
890
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_binary(a, b)
|
|
891
|
+
if scalar == "dev":
|
|
892
|
+
div_scalar_dev_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
893
|
+
elif scalar:
|
|
894
|
+
div_scalar_kernel[_1d_grid(n)](a, output, n, float(b), **_ew_meta(n))
|
|
895
|
+
else:
|
|
896
|
+
div_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
897
|
+
return output
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def sub(a, b, alpha: float = 1.0) :
|
|
901
|
+
if (isinstance(a, NBXTensor) and a.is_complex()) or (isinstance(b, NBXTensor) and b.is_complex()):
|
|
902
|
+
return _complex_addsub(a, b, alpha, is_sub=True)
|
|
903
|
+
strided = _core._prepare_binary_strided(a, b)
|
|
904
|
+
if strided[0] is not None:
|
|
905
|
+
a, b, output, n, shp, a_st, b_st, ndim = strided
|
|
906
|
+
sub_strided_nd_kernel[_1d_grid(n)](a, b, output, n, alpha, shp, a_st, b_st,
|
|
907
|
+
**_ew_meta(n), NDIM=ndim)
|
|
908
|
+
return output
|
|
909
|
+
_, a, b = strided
|
|
910
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_binary(a, b)
|
|
911
|
+
if scalar == "dev":
|
|
912
|
+
add_scalar_dev_kernel[_1d_grid(n)](a, b, output, n, -float(alpha), **_ew_meta(n))
|
|
913
|
+
elif scalar:
|
|
914
|
+
add_scalar_kernel[_1d_grid(n)](a, output, n, -float(b) * alpha, **_ew_meta(n))
|
|
915
|
+
else:
|
|
916
|
+
sub_forward_kernel[_1d_grid(n)](a, b, output, n, alpha, **_ew_meta(n))
|
|
917
|
+
return output
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
def rsub(a, b) :
|
|
921
|
+
"""Reverse subtraction: b - a."""
|
|
922
|
+
a, _, _, n, dev_ctx, _ = _core._prepare_binary(a, a) # just prepare a
|
|
923
|
+
output = NBXTensor.empty_like(a)
|
|
924
|
+
rsub_forward_kernel[_1d_grid(n)](a, output, n, float(_core._to_scalar(b)), **_ew_meta(n))
|
|
925
|
+
return output
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def maximum_wrapper(a, b) :
|
|
929
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_binary(a, b)
|
|
930
|
+
if scalar == "dev": # off the timestep hot path: host extract, exact old behavior
|
|
931
|
+
b, scalar = _core._to_scalar(b), True
|
|
932
|
+
if scalar:
|
|
933
|
+
# scalar maximum: clamp_min
|
|
934
|
+
clamp_min_forward_kernel[_1d_grid(n)](a, output, n, float(b), **_ew_meta(n))
|
|
935
|
+
else:
|
|
936
|
+
maximum_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
937
|
+
return output
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
def minimum_wrapper(a, b) :
|
|
941
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_binary(a, b)
|
|
942
|
+
if scalar == "dev": # off the timestep hot path: host extract, exact old behavior
|
|
943
|
+
b, scalar = _core._to_scalar(b), True
|
|
944
|
+
if not scalar:
|
|
945
|
+
minimum_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
946
|
+
else:
|
|
947
|
+
# scalar minimum: clamp(max=b)
|
|
948
|
+
clamp_forward_kernel[_1d_grid(n)](a, output, n, 0.0, float(b), has_min=False, has_max=True, **_ew_meta(n))
|
|
949
|
+
return output
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
# ===========================================================================
|
|
953
|
+
# COMPARISON WRAPPERS
|
|
954
|
+
# ===========================================================================
|
|
955
|
+
|
|
956
|
+
def gt(a, b) :
|
|
957
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_comparison(a, b)
|
|
958
|
+
if scalar:
|
|
959
|
+
gt_scalar_kernel[_1d_grid(n)](a, output, n, float(b), **_ew_meta(n))
|
|
960
|
+
else:
|
|
961
|
+
gt_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
962
|
+
return output
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
def ge(a, b) :
|
|
966
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_comparison(a, b)
|
|
967
|
+
if scalar:
|
|
968
|
+
ge_scalar_kernel[_1d_grid(n)](a, output, n, float(b), **_ew_meta(n))
|
|
969
|
+
else:
|
|
970
|
+
ge_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
971
|
+
return output
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
def lt(a, b) :
|
|
975
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_comparison(a, b)
|
|
976
|
+
if scalar:
|
|
977
|
+
lt_scalar_kernel[_1d_grid(n)](a, output, n, float(b), **_ew_meta(n))
|
|
978
|
+
else:
|
|
979
|
+
lt_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
980
|
+
return output
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
def eq(a, b) :
|
|
984
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_comparison(a, b)
|
|
985
|
+
if scalar:
|
|
986
|
+
eq_scalar_kernel[_1d_grid(n)](a, output, n, float(b), **_ew_meta(n))
|
|
987
|
+
else:
|
|
988
|
+
eq_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
989
|
+
return output
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def ne(a, b) :
|
|
993
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_comparison(a, b)
|
|
994
|
+
if scalar:
|
|
995
|
+
ne_scalar_kernel[_1d_grid(n)](a, output, n, float(b), **_ew_meta(n))
|
|
996
|
+
else:
|
|
997
|
+
ne_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
998
|
+
return output
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
# The 2-D reduction tile (min / max / prod / std / var / norm / all / any over
|
|
1002
|
+
# a dim). The adopted 8 x 1024 with 4 warps; `block_sizes.reduction_2d` in the
|
|
1003
|
+
# hardware profile overrides it.
|
|
1004
|
+
_REDUCTION_2D_TILE = (8, 1024, 4)
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
def _reduction_2d_tile() -> tuple:
|
|
1008
|
+
"""`(BLOCK_M, BLOCK_N, num_warps)` for the row-wise reductions, from
|
|
1009
|
+
`block_sizes.reduction_2d` in config/vendors/<vendor>/<arch>.yml.
|
|
1010
|
+
|
|
1011
|
+
`min` and `max` over a dim return a value AND its index, so their reduce
|
|
1012
|
+
carries a pair. A backend may need the whole tile to fit its threadgroup
|
|
1013
|
+
to aggregate one — the same property that governs `argmax` — and the
|
|
1014
|
+
threadgroup is `num_warps * 32`, which is why the warp count belongs to
|
|
1015
|
+
the tile. Absent key / no profile -> the adopted values.
|
|
1016
|
+
"""
|
|
1017
|
+
bm, bn, warps = _REDUCTION_2D_TILE
|
|
1018
|
+
try:
|
|
1019
|
+
from nbxtensor.ops._configs import active_vendor_profile
|
|
1020
|
+
cfg = (active_vendor_profile().get("block_sizes", {}) or {}).get("reduction_2d")
|
|
1021
|
+
if isinstance(cfg, dict):
|
|
1022
|
+
bm = int(cfg.get("block_m") or bm)
|
|
1023
|
+
bn = int(cfg.get("block_n") or bn)
|
|
1024
|
+
warps = int(cfg.get("num_warps") or warps)
|
|
1025
|
+
except Exception:
|
|
1026
|
+
pass
|
|
1027
|
+
return bm, bn, warps
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
def isin_wrapper(elements, test_elements, *, invert: bool = False,
|
|
1031
|
+
assume_unique: bool = False):
|
|
1032
|
+
"""aten::isin — elementwise membership test.
|
|
1033
|
+
|
|
1034
|
+
elements : NBXTensor (any int dtype), shape (...)
|
|
1035
|
+
test_elements : NBXTensor (same dtype), shape (K,) typically small
|
|
1036
|
+
returns : NBXTensor bool, same shape as `elements`
|
|
1037
|
+
|
|
1038
|
+
Small-op fast path: D2H both, numpy.isin on host, H2D the bool mask.
|
|
1039
|
+
Typical sizes in audio models are elements=(1, S<=2048), test_elements<=4K
|
|
1040
|
+
— a handful of microseconds on CPU. No torch imports.
|
|
1041
|
+
"""
|
|
1042
|
+
import numpy as np
|
|
1043
|
+
elements_np = _core._nbx_to_numpy(elements.contiguous())
|
|
1044
|
+
test_np = _core._nbx_to_numpy(test_elements.contiguous())
|
|
1045
|
+
out_np = np.isin(elements_np, test_np,
|
|
1046
|
+
assume_unique=bool(assume_unique),
|
|
1047
|
+
invert=bool(invert))
|
|
1048
|
+
# isin is a boolean membership predicate — return a true bool_ NBXTensor so
|
|
1049
|
+
# downstream bool-aware ops recognise it. Staging through uint8 (the earlier
|
|
1050
|
+
# workaround) lost the bool-ness: bitwise_not's `nbx_dtype == bool_` guard
|
|
1051
|
+
# then missed and did a raw byte complement (~0 -> 255) instead of the LOGICAL
|
|
1052
|
+
# NOT, which corrupted the openaudio DualAR attention mask (op #46 of the slow
|
|
1053
|
+
# backbone, first triton-vs-sequential divergence). from_numpy handles |b1
|
|
1054
|
+
# cleanly now (verified empirically), so the uint8 staging is no longer needed.
|
|
1055
|
+
return NBXTensor.from_numpy(out_np.astype(np.bool_))
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
def invoke_silu_and_mul(input_tensor, M, N):
|
|
1059
|
+
"""Launch fused SwiGLU kernel: silu(input[:, :N]) * input[:, N:2*N].
|
|
1060
|
+
|
|
1061
|
+
Args:
|
|
1062
|
+
input_tensor: NBXTensor [M, 2*N]
|
|
1063
|
+
M: number of rows
|
|
1064
|
+
N: output columns (half of input columns)
|
|
1065
|
+
|
|
1066
|
+
Returns:
|
|
1067
|
+
NBXTensor [M, N]
|
|
1068
|
+
"""
|
|
1069
|
+
output = NBXTensor.empty((M, N), dtype=input_tensor._dtype,
|
|
1070
|
+
device=f"cuda:{input_tensor._device_idx}")
|
|
1071
|
+
BLOCK_M = 16
|
|
1072
|
+
BLOCK_N = min(triton.next_power_of_2(N), 1024)
|
|
1073
|
+
|
|
1074
|
+
grid = (triton.cdiv(M, BLOCK_M), triton.cdiv(N, BLOCK_N))
|
|
1075
|
+
set_device(input_tensor)
|
|
1076
|
+
|
|
1077
|
+
silu_and_mul_kernel[grid](
|
|
1078
|
+
input_tensor, output,
|
|
1079
|
+
M, N,
|
|
1080
|
+
input_tensor.stride(0), input_tensor.stride(1),
|
|
1081
|
+
output.stride(0), output.stride(1),
|
|
1082
|
+
BLOCK_M=BLOCK_M, BLOCK_N=BLOCK_N,
|
|
1083
|
+
num_warps=4,
|
|
1084
|
+
)
|
|
1085
|
+
return output
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
def _row_matrix(t):
|
|
1089
|
+
"""`t` as an (M, N) matrix over its last dim, WITHOUT a copy when its rows are evenly strided — the last dim
|
|
1090
|
+
unit-stride and every leading dim mergeable into one row axis, as a split of a fused projection's last dim is (the
|
|
1091
|
+
gate and up halves of a SwiGLU: (B, S, N) views of a (B, S, 2N) buffer, row stride 2N). A copy otherwise. The
|
|
1092
|
+
kernels reading it take its row stride. Copied unconditionally, the two halves of mochi-1-preview's (2, 7 950,
|
|
1093
|
+
16 384) fp32 projection cost 2 x 497 MB the graph never priced, and its transformer overran the 8 GB rung by
|
|
1094
|
+
them (2026-10-28)."""
|
|
1095
|
+
shape, stride = tuple(t.shape), tuple(t.stride())
|
|
1096
|
+
n = shape[-1] if shape else 1
|
|
1097
|
+
m = 1
|
|
1098
|
+
for d in shape[:-1]:
|
|
1099
|
+
m *= d
|
|
1100
|
+
if len(shape) >= 2 and (stride[-1] == 1 or n == 1):
|
|
1101
|
+
dims = [i for i in range(len(shape) - 1) if shape[i] != 1]
|
|
1102
|
+
if all(stride[a] == stride[b] * shape[b] for a, b in zip(dims, dims[1:])):
|
|
1103
|
+
row = stride[dims[-1]] if dims else n
|
|
1104
|
+
return t.as_strided((m, n), (row, 1))
|
|
1105
|
+
return t.contiguous().view(m, n)
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
def swiglu_fused_wrapper(gate, up):
|
|
1109
|
+
"""Fused SwiGLU with split gate/up tensors: silu(gate) * up.
|
|
1110
|
+
|
|
1111
|
+
Used by the _fuse_swiglu_ops pass in TritonSequence to replace
|
|
1112
|
+
aten::silu + aten::mul with a single kernel launch. Gate and up may
|
|
1113
|
+
be N-D (typically [B, S, N]); we flatten everything but the last dim
|
|
1114
|
+
for the 2-D kernel.
|
|
1115
|
+
|
|
1116
|
+
Args:
|
|
1117
|
+
gate: NBXTensor [..., N]
|
|
1118
|
+
up: NBXTensor [..., N] (same shape as gate)
|
|
1119
|
+
|
|
1120
|
+
Returns:
|
|
1121
|
+
NBXTensor same shape as gate.
|
|
1122
|
+
"""
|
|
1123
|
+
# Align dtypes (matches the semantics of the pre-fusion aten::mul,
|
|
1124
|
+
# which relied on PyTorch dtype promotion).
|
|
1125
|
+
if gate.dtype != up.dtype:
|
|
1126
|
+
_order = (NBXDtype.float32, NBXDtype.bfloat16, NBXDtype.float16)
|
|
1127
|
+
widest = next(d for d in _order if d in (gate.dtype, up.dtype))
|
|
1128
|
+
if gate.dtype != widest:
|
|
1129
|
+
gate = gate.to(widest)
|
|
1130
|
+
if up.dtype != widest:
|
|
1131
|
+
up = up.to(widest)
|
|
1132
|
+
|
|
1133
|
+
orig_shape = gate.shape
|
|
1134
|
+
gate2d = _row_matrix(gate)
|
|
1135
|
+
up2d = _row_matrix(up)
|
|
1136
|
+
M, N = gate2d.shape
|
|
1137
|
+
output = NBXTensor.empty((M, N), dtype=gate2d._dtype,
|
|
1138
|
+
device=f"cuda:{gate2d._device_idx}")
|
|
1139
|
+
|
|
1140
|
+
BLOCK_M = 16
|
|
1141
|
+
BLOCK_N = min(triton.next_power_of_2(N), 1024)
|
|
1142
|
+
grid = (triton.cdiv(M, BLOCK_M), triton.cdiv(N, BLOCK_N))
|
|
1143
|
+
set_device(gate2d)
|
|
1144
|
+
|
|
1145
|
+
silu_mul_split_kernel[grid](
|
|
1146
|
+
gate2d, up2d, output,
|
|
1147
|
+
M, N,
|
|
1148
|
+
gate2d.stride(0), gate2d.stride(1),
|
|
1149
|
+
up2d.stride(0), up2d.stride(1),
|
|
1150
|
+
output.stride(0), output.stride(1),
|
|
1151
|
+
BLOCK_M=BLOCK_M, BLOCK_N=BLOCK_N,
|
|
1152
|
+
num_warps=4,
|
|
1153
|
+
)
|
|
1154
|
+
return output.view(*orig_shape)
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
def rope_fused_wrapper(q_raw, k_raw, cos, sin):
|
|
1159
|
+
"""Fused RoPE (Liger-style rotate_half) — applies to Q and K in one launch.
|
|
1160
|
+
|
|
1161
|
+
Replaces the graph chain slice×4 + neg×2 + cat×2 + mul×4 + add×2 (plus
|
|
1162
|
+
the per-side _to_copy done downstream) with a single call to
|
|
1163
|
+
rope_forward_kernel (see kernels/ops/rope.py).
|
|
1164
|
+
|
|
1165
|
+
Inputs arrive in the HF-Llama layout, post-transpose:
|
|
1166
|
+
q_raw : [B, H_q, S, D] (logical view; physical memory [B,S,H_q,D])
|
|
1167
|
+
k_raw : [B, H_kv, S, D] (logical view; physical memory [B,S,H_kv,D])
|
|
1168
|
+
cos : [1|B, 1, S, D] (broadcast over heads; from unsqueeze)
|
|
1169
|
+
sin : [1|B, 1, S, D]
|
|
1170
|
+
|
|
1171
|
+
The kernel expects physical (B, S, H, D) layout with q/k row-stride =
|
|
1172
|
+
H*D. Since transpose is a zero-copy stride permutation, we do the
|
|
1173
|
+
inverse transpose+contiguous to materialise a fresh (B, S, H, D) buffer,
|
|
1174
|
+
apply the kernel in-place on the fresh buffer, then view it back as
|
|
1175
|
+
(B, H, S, D). The contiguous copy doubles as the arena-safety clone.
|
|
1176
|
+
"""
|
|
1177
|
+
# Squeeze cos/sin broadcast dims: [1|B, 1, S, D] -> [1|B, S, D].
|
|
1178
|
+
if cos.ndim == 4 and cos.shape[1] == 1:
|
|
1179
|
+
cos = cos.view(cos.shape[0], cos.shape[2], cos.shape[3])
|
|
1180
|
+
if sin.ndim == 4 and sin.shape[1] == 1:
|
|
1181
|
+
sin = sin.view(sin.shape[0], sin.shape[2], sin.shape[3])
|
|
1182
|
+
|
|
1183
|
+
# The kernel computes in Q's dtype. A table WIDER than Q (fp32 tables, fp16 Q:
|
|
1184
|
+
# the decode case) is rounded to it on load — round-to-nearest, the stored
|
|
1185
|
+
# cast's conversion; the tables lie in [-1, 1], so the protected fp16 clamp
|
|
1186
|
+
# never applied — and the per-layer cast copy this replaces (TinyLlama: 44 a
|
|
1187
|
+
# token) is gone. A table NARROWER than Q (fp16 tables, fp32 Q: Sana's Gemma-2
|
|
1188
|
+
# encoder) is widened here, once per run (`_widened_once_per_run`). JUSTIFIED
|
|
1189
|
+
# COPY, 2 per run, no longer 2 per layer:
|
|
1190
|
+
# widened on load, the fp32 rotation's fused multiply-add moved with the
|
|
1191
|
+
# convert before it (1 ulp on a sixth of the elements, 2026-09-07), and an
|
|
1192
|
+
# explicit fma matched the fp32 path but not the fp16 one — the arithmetic
|
|
1193
|
+
# stays the plain expression, and the values arrive as they did. K is rotated
|
|
1194
|
+
# in place, so a K of another dtype than Q needs a buffer of Q's dtype.
|
|
1195
|
+
target_dt = q_raw.dtype
|
|
1196
|
+
if _core._wider_dtype(cos.nbx_dtype, q_raw.nbx_dtype) == q_raw.nbx_dtype and cos.nbx_dtype != q_raw.nbx_dtype:
|
|
1197
|
+
cos = _widened_once_per_run(cos, target_dt) # once per run, not once per layer
|
|
1198
|
+
if _core._wider_dtype(sin.nbx_dtype, q_raw.nbx_dtype) == q_raw.nbx_dtype and sin.nbx_dtype != q_raw.nbx_dtype:
|
|
1199
|
+
sin = _widened_once_per_run(sin, target_dt)
|
|
1200
|
+
if k_raw.dtype != target_dt:
|
|
1201
|
+
k_raw = k_raw.to(target_dt)
|
|
1202
|
+
|
|
1203
|
+
cos = cos.contiguous()
|
|
1204
|
+
sin = sin.contiguous()
|
|
1205
|
+
|
|
1206
|
+
# Transpose back to (B, S, H, D) physical layout, then contiguous copy:
|
|
1207
|
+
# the kernel writes in-place, and this copy is our arena-safe clone.
|
|
1208
|
+
q_bshd = q_raw.transpose(1, 2).contiguous()
|
|
1209
|
+
k_bshd = k_raw.transpose(1, 2).contiguous()
|
|
1210
|
+
|
|
1211
|
+
bs, sl, n_qh, hd = q_bshd.shape
|
|
1212
|
+
_, _, n_kh, _ = k_bshd.shape
|
|
1213
|
+
cos_bs = cos.shape[0] # 1 (broadcast over batch) or bs
|
|
1214
|
+
|
|
1215
|
+
# Row stride = H*D for (B, S, H, D) contiguous layout.
|
|
1216
|
+
q_row_stride = n_qh * hd
|
|
1217
|
+
k_row_stride = n_kh * hd
|
|
1218
|
+
|
|
1219
|
+
pad_n_qh = triton.next_power_of_2(n_qh)
|
|
1220
|
+
pad_n_kh = triton.next_power_of_2(n_kh)
|
|
1221
|
+
pad_hd = triton.next_power_of_2(hd)
|
|
1222
|
+
BLOCK_SIZE = max(pad_n_qh, pad_n_kh)
|
|
1223
|
+
|
|
1224
|
+
grid = (bs * sl,)
|
|
1225
|
+
set_device(q_bshd)
|
|
1226
|
+
|
|
1227
|
+
rope_forward_kernel[grid](
|
|
1228
|
+
q_bshd, q_row_stride,
|
|
1229
|
+
k_bshd, k_row_stride,
|
|
1230
|
+
cos, cos.stride(-2),
|
|
1231
|
+
sin, sin.stride(-2),
|
|
1232
|
+
sl,
|
|
1233
|
+
bs=bs, cos_bs=cos_bs,
|
|
1234
|
+
n_qh=n_qh, n_kh=n_kh, hd=hd,
|
|
1235
|
+
pad_n_qh=pad_n_qh, pad_n_kh=pad_n_kh, pad_hd=pad_hd,
|
|
1236
|
+
BLOCK_SIZE=BLOCK_SIZE,
|
|
1237
|
+
BACKWARD_PASS=False,
|
|
1238
|
+
num_warps=4,
|
|
1239
|
+
)
|
|
1240
|
+
|
|
1241
|
+
# View back as (B, H, S, D) to match the pre-fusion output layout.
|
|
1242
|
+
q_out = q_bshd.transpose(1, 2)
|
|
1243
|
+
k_out = k_bshd.transpose(1, 2)
|
|
1244
|
+
return q_out, k_out
|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
# ===========================================================================
|
|
1248
|
+
# DTYPE CONVERSION WRAPPER
|
|
1249
|
+
# ===========================================================================
|
|
1250
|
+
|
|
1251
|
+
def bf16_to_fp16_gpu(src: NBXTensor, out: "NBXTensor | None" = None) -> NBXTensor:
|
|
1252
|
+
"""bfloat16 patterns (a 2-byte integer tensor holding the raw bits) to float16 on the device: the one device
|
|
1253
|
+
conversion of the pair, through float32 (`ops/dtype_convert.bf16_to_fp16_kernel`).
|
|
1254
|
+
|
|
1255
|
+
`out`: a float16 tensor of the same number of elements to write into — the SAME buffer as `src` converts in
|
|
1256
|
+
place (both are two bytes an element, and each element is read before it is written); none: a new tensor."""
|
|
1257
|
+
N = src.numel()
|
|
1258
|
+
dst = out if out is not None else NBXTensor.empty(src._shape, NBXDtype.float16,
|
|
1259
|
+
device=f"cuda:{src._device_idx}")
|
|
1260
|
+
if dst.nbx_dtype != NBXDtype.float16 or dst.numel() != N:
|
|
1261
|
+
raise ValueError(f"bf16_to_fp16_gpu: out must be float16 with {N} elements, got {dst.nbx_dtype.name} "
|
|
1262
|
+
f"with {dst.numel()}")
|
|
1263
|
+
set_device(src)
|
|
1264
|
+
_m = _ew_meta(N)
|
|
1265
|
+
bf16_to_fp16_kernel[_ew_grid(N, _m)](src, dst, N, BLOCK=_m['BLOCK_SIZE'], num_warps=_m['num_warps'])
|
|
1266
|
+
return dst
|
|
1267
|
+
|
|
1268
|
+
|
|
1269
|
+
# ===========================================================================
|
|
1270
|
+
# GLU WRAPPER
|
|
1271
|
+
# ===========================================================================
|
|
1272
|
+
|
|
1273
|
+
def glu_wrapper(x, dim: int = -1, act_func: str = 'sigmoid') :
|
|
1274
|
+
"""Gated Linear Unit: out = x1 * act(x2) where x is split on dim."""
|
|
1275
|
+
dim = dim % x.ndim
|
|
1276
|
+
half_size = x.shape[dim] // 2
|
|
1277
|
+
x1, x2 = x.narrow(dim, 0, half_size), x.narrow(dim, half_size, half_size)
|
|
1278
|
+
x1, x2 = x1.contiguous(), x2.contiguous()
|
|
1279
|
+
output = NBXTensor.empty_like(x1)
|
|
1280
|
+
set_device(x1)
|
|
1281
|
+
glu_forward_kernel[_1d_grid(x1.numel())](
|
|
1282
|
+
x1, x2, output, x1.numel(), act_func,
|
|
1283
|
+
**_ew_meta(x1.numel()))
|
|
1284
|
+
return output
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
# ===========================================================================
|
|
1288
|
+
# PHASE 2 WRAPPERS — Extracted from FlagGems reference
|
|
1289
|
+
# ===========================================================================
|
|
1290
|
+
|
|
1291
|
+
def le(a, b) :
|
|
1292
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_comparison(a, b)
|
|
1293
|
+
if scalar:
|
|
1294
|
+
le_scalar_kernel[_1d_grid(n)](a, output, n, float(b), **_ew_meta(n))
|
|
1295
|
+
else:
|
|
1296
|
+
le_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
1297
|
+
return output
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
def softplus_wrapper(x, beta: float = 1.0, threshold: float = 20.0) :
|
|
1301
|
+
x = x.contiguous()
|
|
1302
|
+
output = NBXTensor.empty_like(x)
|
|
1303
|
+
set_device(x)
|
|
1304
|
+
softplus_forward_kernel[_1d_grid(x.numel())](
|
|
1305
|
+
x, output, x.numel(), beta, threshold,
|
|
1306
|
+
**_ew_meta(x.numel()))
|
|
1307
|
+
return output
|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
def remainder_wrapper(a, b) :
|
|
1311
|
+
"""Remainder (modulo): a % b."""
|
|
1312
|
+
from nbxtensor.ops.remainder import remainder_forward_kernel, remainder_scalar_kernel
|
|
1313
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_binary(a, b)
|
|
1314
|
+
if scalar == "dev": # off the timestep hot path: host extract, exact old behavior
|
|
1315
|
+
b, scalar = _core._to_scalar(b), True
|
|
1316
|
+
if scalar:
|
|
1317
|
+
remainder_scalar_kernel[_1d_grid(n)](a, output, n, float(b), **_ew_meta(n))
|
|
1318
|
+
else:
|
|
1319
|
+
remainder_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
1320
|
+
return output
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
# ===========================================================================
|
|
1324
|
+
# PHASE 3 — Simple element-wise wrappers
|
|
1325
|
+
# ===========================================================================
|
|
1326
|
+
|
|
1327
|
+
def exp2_wrapper(x) :
|
|
1328
|
+
x = _promote_int_unary(x).contiguous()
|
|
1329
|
+
output = NBXTensor.empty_like(x)
|
|
1330
|
+
set_device(x)
|
|
1331
|
+
exp2_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
1332
|
+
return output
|
|
1333
|
+
|
|
1334
|
+
|
|
1335
|
+
def tan_wrapper(x) :
|
|
1336
|
+
x = _promote_int_unary(x).contiguous()
|
|
1337
|
+
output = NBXTensor.empty_like(x)
|
|
1338
|
+
set_device(x)
|
|
1339
|
+
tan_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
1340
|
+
return output
|
|
1341
|
+
|
|
1342
|
+
|
|
1343
|
+
def celu_wrapper(x, alpha: float = 1.0) :
|
|
1344
|
+
x = x.contiguous()
|
|
1345
|
+
output = NBXTensor.empty_like(x)
|
|
1346
|
+
set_device(x)
|
|
1347
|
+
celu_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), alpha, **_ew_meta(x.numel()))
|
|
1348
|
+
return output
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
def log_sigmoid_wrapper(x) :
|
|
1352
|
+
return _flat_unary(x, log_sigmoid_forward_kernel)
|
|
1353
|
+
|
|
1354
|
+
|
|
1355
|
+
def isfinite_wrapper(x) :
|
|
1356
|
+
x = x.contiguous()
|
|
1357
|
+
output = NBXTensor.empty(x.shape, dtype=NBXDtype.bool_, device=x.device)
|
|
1358
|
+
set_device(x)
|
|
1359
|
+
isfinite_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
1360
|
+
return output
|
|
1361
|
+
|
|
1362
|
+
|
|
1363
|
+
def isinf_wrapper(x) :
|
|
1364
|
+
x = x.contiguous()
|
|
1365
|
+
output = NBXTensor.empty(x.shape, dtype=NBXDtype.bool_, device=x.device)
|
|
1366
|
+
set_device(x)
|
|
1367
|
+
isinf_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
1368
|
+
return output
|
|
1369
|
+
|
|
1370
|
+
|
|
1371
|
+
def isnan_wrapper(x) :
|
|
1372
|
+
x = x.contiguous()
|
|
1373
|
+
output = NBXTensor.empty(x.shape, dtype=NBXDtype.bool_, device=x.device)
|
|
1374
|
+
set_device(x)
|
|
1375
|
+
isnan_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
1376
|
+
return output
|
|
1377
|
+
|
|
1378
|
+
|
|
1379
|
+
def nan_to_num_wrapper(x, nan: float = 0.0,
|
|
1380
|
+
posinf: float = 3.4028235e+38,
|
|
1381
|
+
neginf: float = -3.4028235e+38, inplace: bool = False) :
|
|
1382
|
+
"""`inplace`: write over a contiguous `x` (an element-wise map: each element read once, then written) — the math
|
|
1383
|
+
attention's probabilities, whose copy was one more [B*H, Tq, Tk] buffer at its peak."""
|
|
1384
|
+
x = x.contiguous()
|
|
1385
|
+
output = x if inplace else NBXTensor.empty_like(x)
|
|
1386
|
+
set_device(x)
|
|
1387
|
+
nan_to_num_forward_kernel[_1d_grid(x.numel())](
|
|
1388
|
+
x, output, x.numel(), nan, posinf, neginf,
|
|
1389
|
+
**_ew_meta(x.numel()))
|
|
1390
|
+
return output
|
|
1391
|
+
|
|
1392
|
+
|
|
1393
|
+
def threshold_wrapper(x, threshold: float, value: float) :
|
|
1394
|
+
x = x.contiguous()
|
|
1395
|
+
output = NBXTensor.empty_like(x)
|
|
1396
|
+
set_device(x)
|
|
1397
|
+
threshold_forward_kernel[_1d_grid(x.numel())](
|
|
1398
|
+
x, output, x.numel(), threshold, value,
|
|
1399
|
+
**_ew_meta(x.numel()))
|
|
1400
|
+
return output
|
|
1401
|
+
|
|
1402
|
+
|
|
1403
|
+
# ===========================================================================
|
|
1404
|
+
# PHASE 4 — Full coverage wrappers
|
|
1405
|
+
# ===========================================================================
|
|
1406
|
+
|
|
1407
|
+
# ---------------------------------------------------------------------------
|
|
1408
|
+
# Addcdiv / Addcmul (3-tensor + scalar)
|
|
1409
|
+
# ---------------------------------------------------------------------------
|
|
1410
|
+
|
|
1411
|
+
def addcdiv_wrapper(input, tensor1,
|
|
1412
|
+
tensor2, value: float = 1.0) :
|
|
1413
|
+
"""result = input + value * (tensor1 / tensor2)"""
|
|
1414
|
+
input, tensor1, tensor2 = input.contiguous(), tensor1.contiguous(), tensor2.contiguous()
|
|
1415
|
+
output = NBXTensor.empty_like(input)
|
|
1416
|
+
set_device(input)
|
|
1417
|
+
addcdiv_forward_kernel[_1d_grid(input.numel())](
|
|
1418
|
+
input, tensor1, tensor2, output, input.numel(), value,
|
|
1419
|
+
**_ew_meta(input.numel()))
|
|
1420
|
+
return output
|
|
1421
|
+
|
|
1422
|
+
|
|
1423
|
+
def addcmul_wrapper(input, tensor1,
|
|
1424
|
+
tensor2, value: float = 1.0) :
|
|
1425
|
+
"""result = input + value * tensor1 * tensor2"""
|
|
1426
|
+
input, tensor1, tensor2 = input.contiguous(), tensor1.contiguous(), tensor2.contiguous()
|
|
1427
|
+
output = NBXTensor.empty_like(input)
|
|
1428
|
+
set_device(input)
|
|
1429
|
+
addcmul_forward_kernel[_1d_grid(input.numel())](
|
|
1430
|
+
input, tensor1, tensor2, output, input.numel(), value,
|
|
1431
|
+
**_ew_meta(input.numel()))
|
|
1432
|
+
return output
|
|
1433
|
+
|
|
1434
|
+
|
|
1435
|
+
# ---------------------------------------------------------------------------
|
|
1436
|
+
# Bitwise ops (binary: AND/OR, unary: NOT)
|
|
1437
|
+
# ---------------------------------------------------------------------------
|
|
1438
|
+
|
|
1439
|
+
def bitwise_and_wrapper(a, b) :
|
|
1440
|
+
# _prepare_binary = the universal binary contract (dtype align, device
|
|
1441
|
+
# align, BROADCAST, contiguous). The former `a.contiguous(); empty_like(a)`
|
|
1442
|
+
# form silently ignored broadcasting: bitwise_and([1,1,S,1], [1,1,1,S])
|
|
1443
|
+
# (the T5 extended-attention-mask outer product q_valid & k_valid, Allegro
|
|
1444
|
+
# text_encoder) returned the elementwise diagonal [1,1,S,1] instead of
|
|
1445
|
+
# [1,1,S,S] — same numel, so no OOB crash — and the downstream expand
|
|
1446
|
+
# stretched the q-valid column over the key axis: NO key masking, every
|
|
1447
|
+
# real token attended all pad tokens in every T5 layer (triton cond-branch
|
|
1448
|
+
# 35.8% drift vs compiled at native, uncond clean because the empty-prompt
|
|
1449
|
+
# mask is all-ones = inert).
|
|
1450
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_binary(a, b)
|
|
1451
|
+
if scalar == "dev": # off the timestep hot path: host extract, exact old behavior
|
|
1452
|
+
b, scalar = _core._to_scalar(b), True
|
|
1453
|
+
if scalar:
|
|
1454
|
+
# 0-dim / python-scalar operand (e.g. the traced `mask.all() & qside`
|
|
1455
|
+
# opening the T5 extended-mask chain): materialize to a full tensor of
|
|
1456
|
+
# a's dtype and run the tensor kernel. Cold path, exact for every
|
|
1457
|
+
# dtype. The former code fed the 0-dim tensor straight to the flat
|
|
1458
|
+
# kernel, reading a.numel() elements past its 1-element allocation
|
|
1459
|
+
# (out-of-bounds, undefined values).
|
|
1460
|
+
b = NBXTensor.empty_like(a).fill_(b)
|
|
1461
|
+
bitwise_and_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
1462
|
+
return output
|
|
1463
|
+
|
|
1464
|
+
|
|
1465
|
+
def bitwise_or_wrapper(a, b) :
|
|
1466
|
+
# Same universal binary contract as bitwise_and_wrapper (broadcast fix).
|
|
1467
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_binary(a, b)
|
|
1468
|
+
if scalar == "dev": # off the timestep hot path: host extract, exact old behavior
|
|
1469
|
+
b, scalar = _core._to_scalar(b), True
|
|
1470
|
+
if scalar:
|
|
1471
|
+
# Scalar operand: materialize (see bitwise_and_wrapper).
|
|
1472
|
+
b = NBXTensor.empty_like(a).fill_(b)
|
|
1473
|
+
bitwise_or_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
1474
|
+
return output
|
|
1475
|
+
|
|
1476
|
+
|
|
1477
|
+
def bitwise_not_wrapper(x) :
|
|
1478
|
+
# torch.bitwise_not on a BOOL tensor is the LOGICAL NOT (0<->1), NOT the
|
|
1479
|
+
# bitwise complement: ~True(1)=254, ~False(0)=255 are both non-zero, so the
|
|
1480
|
+
# bitwise kernel yields an all-True bool tensor. Route bool through
|
|
1481
|
+
# logical_not. Surfaced by the parakeet conformer attention padding mask
|
|
1482
|
+
# (`~(arange<len)`): an all-True mask made masked_fill overwrite every score
|
|
1483
|
+
# with -1e4 -> uniform softmax -> dead self-attention -> garbage transcription.
|
|
1484
|
+
if getattr(x, "nbx_dtype", None) == NBXDtype.bool_:
|
|
1485
|
+
return logical_not_wrapper(x)
|
|
1486
|
+
x = x.contiguous()
|
|
1487
|
+
output = NBXTensor.empty_like(x)
|
|
1488
|
+
set_device(x)
|
|
1489
|
+
bitwise_not_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
1490
|
+
return output
|
|
1491
|
+
|
|
1492
|
+
|
|
1493
|
+
# ---------------------------------------------------------------------------
|
|
1494
|
+
# Logical ops (binary: AND/OR → bool, unary: NOT → bool)
|
|
1495
|
+
# ---------------------------------------------------------------------------
|
|
1496
|
+
|
|
1497
|
+
def logical_and_wrapper(a, b) :
|
|
1498
|
+
# _prepare_comparison = the universal binary contract with a bool output
|
|
1499
|
+
# (dtype align, device align, BROADCAST, contiguous). Same broadcast fix
|
|
1500
|
+
# as bitwise_and_wrapper — the former form silently ignored broadcasting.
|
|
1501
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_comparison(a, b)
|
|
1502
|
+
if scalar:
|
|
1503
|
+
# Scalar operand: materialize (see bitwise_and_wrapper).
|
|
1504
|
+
b = NBXTensor.empty_like(a).fill_(b)
|
|
1505
|
+
logical_and_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
1506
|
+
return output
|
|
1507
|
+
|
|
1508
|
+
|
|
1509
|
+
def logical_or_wrapper(a, b) :
|
|
1510
|
+
# Same universal binary contract as logical_and_wrapper (broadcast fix).
|
|
1511
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_comparison(a, b)
|
|
1512
|
+
if scalar:
|
|
1513
|
+
# Scalar operand: materialize (see bitwise_and_wrapper).
|
|
1514
|
+
b = NBXTensor.empty_like(a).fill_(b)
|
|
1515
|
+
logical_or_forward_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
1516
|
+
return output
|
|
1517
|
+
|
|
1518
|
+
|
|
1519
|
+
def logical_not_wrapper(x) :
|
|
1520
|
+
x = x.contiguous()
|
|
1521
|
+
output = NBXTensor.empty(x.shape, dtype=NBXDtype.bool_, device=x.device)
|
|
1522
|
+
set_device(x)
|
|
1523
|
+
logical_not_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), **_ew_meta(x.numel()))
|
|
1524
|
+
return output
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
# ---------------------------------------------------------------------------
|
|
1528
|
+
# Lerp (linear interpolation)
|
|
1529
|
+
# ---------------------------------------------------------------------------
|
|
1530
|
+
|
|
1531
|
+
def lerp_wrapper(input, end, weight) :
|
|
1532
|
+
"""Linear interpolation: numerically stable two-branch formula."""
|
|
1533
|
+
input, end = input.contiguous(), end.contiguous()
|
|
1534
|
+
output = NBXTensor.empty_like(input)
|
|
1535
|
+
if isinstance(weight, NBXTensor):
|
|
1536
|
+
weight = weight.contiguous()
|
|
1537
|
+
set_device(input)
|
|
1538
|
+
lerp_tensor_forward_kernel[_1d_grid(input.numel())](
|
|
1539
|
+
input, end, weight, output, input.numel(),
|
|
1540
|
+
**_ew_meta(input.numel()))
|
|
1541
|
+
elif abs(weight) < 0.5:
|
|
1542
|
+
set_device(input)
|
|
1543
|
+
lerp_scalar_head_kernel[_1d_grid(input.numel())](
|
|
1544
|
+
input, end, output, input.numel(), float(weight),
|
|
1545
|
+
**_ew_meta(input.numel()))
|
|
1546
|
+
else:
|
|
1547
|
+
set_device(input)
|
|
1548
|
+
lerp_scalar_tail_kernel[_1d_grid(input.numel())](
|
|
1549
|
+
input, end, output, input.numel(), float(weight),
|
|
1550
|
+
**_ew_meta(input.numel()))
|
|
1551
|
+
return output
|
|
1552
|
+
|
|
1553
|
+
|
|
1554
|
+
# ---------------------------------------------------------------------------
|
|
1555
|
+
# Min / Max (value reductions, two-pass global)
|
|
1556
|
+
# ---------------------------------------------------------------------------
|
|
1557
|
+
|
|
1558
|
+
def min_wrapper(x, dim=None, keepdim=False):
|
|
1559
|
+
"""Min reduction. Two-pass global. Returns scalar for dim=None."""
|
|
1560
|
+
import math
|
|
1561
|
+
x = x.contiguous()
|
|
1562
|
+
if dim is None:
|
|
1563
|
+
M = x.numel()
|
|
1564
|
+
BLOCK_SIZE = triton.next_power_of_2(math.ceil(math.sqrt(M)))
|
|
1565
|
+
mid_size = triton.cdiv(M, BLOCK_SIZE)
|
|
1566
|
+
BLOCK_MID = triton.next_power_of_2(mid_size)
|
|
1567
|
+
mid = NBXTensor.empty(mid_size, dtype=x.dtype, device=x.device)
|
|
1568
|
+
out = NBXTensor.empty([], dtype=x.dtype, device=x.device)
|
|
1569
|
+
set_device(x.view(-1))
|
|
1570
|
+
min_kernel_mid[(mid_size,)](x.view(-1), mid, M, BLOCK_SIZE=BLOCK_SIZE)
|
|
1571
|
+
set_device(mid)
|
|
1572
|
+
min_kernel_result[(1,)](mid, out, mid_size, BLOCK_MID=BLOCK_MID)
|
|
1573
|
+
return out
|
|
1574
|
+
else:
|
|
1575
|
+
dim = dim % x.ndim
|
|
1576
|
+
shape = list(x.shape)
|
|
1577
|
+
N = shape[dim]
|
|
1578
|
+
M = x.numel() // N
|
|
1579
|
+
x_perm = x.movedim(dim, -1).contiguous().reshape(M, N)
|
|
1580
|
+
out = NBXTensor.empty(M, dtype=x.dtype, device=x.device)
|
|
1581
|
+
out_index = NBXTensor.empty(M, dtype=NBXDtype.int64, device=x.device)
|
|
1582
|
+
_red_bm, _red_bn, _red_warps = _reduction_2d_tile()
|
|
1583
|
+
grid = (triton.cdiv(M, _red_bm),)
|
|
1584
|
+
set_device(x_perm)
|
|
1585
|
+
# The kernel writes the value AND its index (ATen's min.dim returns
|
|
1586
|
+
# both); the wrapper passed four arguments to a five-argument kernel
|
|
1587
|
+
# — a latent defect on a path no model of the zoo exercised, found by
|
|
1588
|
+
# the kernel reference bank (2026-09-05).
|
|
1589
|
+
min_kernel[grid](x_perm, out, out_index, M, N,
|
|
1590
|
+
BLOCK_M=_red_bm, BLOCK_N=_red_bn,
|
|
1591
|
+
num_warps=_red_warps)
|
|
1592
|
+
shape[dim] = 1
|
|
1593
|
+
values = out.view(shape)
|
|
1594
|
+
indices = out_index.view(shape)
|
|
1595
|
+
if not keepdim:
|
|
1596
|
+
values = values.squeeze(dim)
|
|
1597
|
+
indices = indices.squeeze(dim)
|
|
1598
|
+
return values, indices
|
|
1599
|
+
|
|
1600
|
+
|
|
1601
|
+
def max_wrapper(x, dim=None, keepdim=False):
|
|
1602
|
+
"""Max reduction. Two-pass global. Returns scalar for dim=None."""
|
|
1603
|
+
import math
|
|
1604
|
+
x = x.contiguous()
|
|
1605
|
+
if dim is None:
|
|
1606
|
+
M = x.numel()
|
|
1607
|
+
BLOCK_SIZE = triton.next_power_of_2(math.ceil(math.sqrt(M)))
|
|
1608
|
+
mid_size = triton.cdiv(M, BLOCK_SIZE)
|
|
1609
|
+
BLOCK_MID = triton.next_power_of_2(mid_size)
|
|
1610
|
+
mid = NBXTensor.empty(mid_size, dtype=x.dtype, device=x.device)
|
|
1611
|
+
out = NBXTensor.empty([], dtype=x.dtype, device=x.device)
|
|
1612
|
+
set_device(x.view(-1))
|
|
1613
|
+
max_kernel_mid[(mid_size,)](x.view(-1), mid, M, BLOCK_SIZE=BLOCK_SIZE)
|
|
1614
|
+
set_device(mid)
|
|
1615
|
+
max_kernel_result[(1,)](mid, out, mid_size, BLOCK_MID=BLOCK_MID)
|
|
1616
|
+
return out
|
|
1617
|
+
else:
|
|
1618
|
+
dim = dim % x.ndim
|
|
1619
|
+
shape = list(x.shape)
|
|
1620
|
+
N = shape[dim]
|
|
1621
|
+
M = x.numel() // N
|
|
1622
|
+
x_perm = x.movedim(dim, -1).contiguous().reshape(M, N)
|
|
1623
|
+
out = NBXTensor.empty(M, dtype=x.dtype, device=x.device)
|
|
1624
|
+
out_index = NBXTensor.empty(M, dtype=NBXDtype.int64, device=x.device)
|
|
1625
|
+
_red_bm, _red_bn, _red_warps = _reduction_2d_tile()
|
|
1626
|
+
grid = (triton.cdiv(M, _red_bm),)
|
|
1627
|
+
set_device(x_perm)
|
|
1628
|
+
# The kernel writes the value AND its index (ATen's max.dim returns
|
|
1629
|
+
# both); the wrapper passed four arguments to a five-argument kernel
|
|
1630
|
+
# — a latent defect on a path no model of the zoo exercised, found by
|
|
1631
|
+
# the kernel reference bank (2026-09-05).
|
|
1632
|
+
max_kernel[grid](x_perm, out, out_index, M, N,
|
|
1633
|
+
BLOCK_M=_red_bm, BLOCK_N=_red_bn,
|
|
1634
|
+
num_warps=_red_warps)
|
|
1635
|
+
shape[dim] = 1
|
|
1636
|
+
values = out.view(shape)
|
|
1637
|
+
indices = out_index.view(shape)
|
|
1638
|
+
if not keepdim:
|
|
1639
|
+
values = values.squeeze(dim)
|
|
1640
|
+
indices = indices.squeeze(dim)
|
|
1641
|
+
return values, indices
|
|
1642
|
+
|
|
1643
|
+
|
|
1644
|
+
# ---------------------------------------------------------------------------
|
|
1645
|
+
# Bincount — small histogram for MoE routing
|
|
1646
|
+
# ---------------------------------------------------------------------------
|
|
1647
|
+
|
|
1648
|
+
def bincount_wrapper(x, minlength: int = 0):
|
|
1649
|
+
"""Bincount via CPU numpy (input is small 1D int tensor from MoE routing)."""
|
|
1650
|
+
import numpy as np
|
|
1651
|
+
import ctypes as _ctypes
|
|
1652
|
+
# D2H transfer (small: num_tokens * top_k ints)
|
|
1653
|
+
n = x.numel()
|
|
1654
|
+
buf = (_ctypes.c_int64 * n)()
|
|
1655
|
+
DeviceAllocator.memcpy(_ctypes.addressof(buf), x.data_ptr(), n * 8, kind=2)
|
|
1656
|
+
arr = np.frombuffer(buf, dtype=np.int64)
|
|
1657
|
+
counts = np.bincount(arr, minlength=minlength).astype(np.int64)
|
|
1658
|
+
DeviceAllocator.set_device(x._device_idx)
|
|
1659
|
+
return NBXTensor.from_numpy(counts)
|
|
1660
|
+
|
|
1661
|
+
|
|
1662
|
+
# ---------------------------------------------------------------------------
|
|
1663
|
+
# Sort (radix sort — complex, uses Triton radix sort kernels)
|
|
1664
|
+
# ---------------------------------------------------------------------------
|
|
1665
|
+
|
|
1666
|
+
def sort_wrapper(x, dim: int = -1,
|
|
1667
|
+
descending: bool = False, stable: bool = False):
|
|
1668
|
+
"""Sort via Triton radix sort. Uses histogram + cumsum + sweep pipeline.
|
|
1669
|
+
All compute via Triton kernels, allocation via NBXTensor.
|
|
1670
|
+
|
|
1671
|
+
For the exclusive prefix sum step, we use our Triton cumsum kernel
|
|
1672
|
+
uses Triton radix sort kernels.
|
|
1673
|
+
"""
|
|
1674
|
+
from nbxtensor.ops.sort_op import (
|
|
1675
|
+
convert_to_uint_preserve_order,
|
|
1676
|
+
radix_sort_histogram_kernel,
|
|
1677
|
+
radix_sort_tile_counts_kernel,
|
|
1678
|
+
radix_sort_tile_prefix_kernel,
|
|
1679
|
+
radix_sort_scatter_kernel,
|
|
1680
|
+
)
|
|
1681
|
+
import math as _math
|
|
1682
|
+
|
|
1683
|
+
if dim < 0:
|
|
1684
|
+
dim = dim + x.ndim
|
|
1685
|
+
assert dim == x.ndim - 1, "sort currently supports last dim only"
|
|
1686
|
+
|
|
1687
|
+
n = x.shape[dim]
|
|
1688
|
+
m = x.numel() // n
|
|
1689
|
+
num_bits = x.element_size() * 8
|
|
1690
|
+
k_bits = 8
|
|
1691
|
+
n_passes = triton.cdiv(num_bits, k_bits)
|
|
1692
|
+
num_bins = 2 ** k_bits
|
|
1693
|
+
|
|
1694
|
+
TILE_N = 1024
|
|
1695
|
+
tiles_per_cta = 8
|
|
1696
|
+
CTA_TILE = tiles_per_cta * TILE_N
|
|
1697
|
+
grid_n = triton.cdiv(n, CTA_TILE)
|
|
1698
|
+
|
|
1699
|
+
# Allocate all buffers
|
|
1700
|
+
global_hist = NBXTensor.zeros((m, n_passes, num_bins), device=x.device, dtype=NBXDtype.int32)
|
|
1701
|
+
|
|
1702
|
+
TILE_R = 8
|
|
1703
|
+
|
|
1704
|
+
# Stage 1: compute global histogram
|
|
1705
|
+
set_device(x.contiguous())
|
|
1706
|
+
radix_sort_histogram_kernel[(m * grid_n,)](
|
|
1707
|
+
x.contiguous(), global_hist,
|
|
1708
|
+
n_passes, m, n, tiles_per_cta,
|
|
1709
|
+
TILE_N, TILE_R, k_bits, descending)
|
|
1710
|
+
|
|
1711
|
+
# Exclusive prefix sum on histogram (use our cumsum kernel)
|
|
1712
|
+
# cumsum along last dim, then subtract self → exclusive prefix sum
|
|
1713
|
+
hist_cumsum = NBXTensor.empty_like(global_hist)
|
|
1714
|
+
for b in range(m):
|
|
1715
|
+
for p in range(n_passes):
|
|
1716
|
+
row = global_hist[b, p]
|
|
1717
|
+
cs = _red.cumsum_wrapper(row, dim=0)
|
|
1718
|
+
hist_cumsum[b, p] = cs - row
|
|
1719
|
+
ex_cumsum = hist_cumsum.to(NBXDtype.int32)
|
|
1720
|
+
|
|
1721
|
+
# Double buffers. The sweep kernel CARRIES the associate array (the
|
|
1722
|
+
# argsort indices) through the radix passes — it never synthesizes
|
|
1723
|
+
# initial positions, so the buffer must start as arange(n) PER ROW
|
|
1724
|
+
# (R33-pure via the triton arange kernel). First live firing (the
|
|
1725
|
+
# Ming windowed-ViT argsort) exposed the old init: an empty (0, n)
|
|
1726
|
+
# zeros buffer from an invalid zeros(0, n, dtype=...) call.
|
|
1727
|
+
arr_in = x.contiguous().clone()
|
|
1728
|
+
indices_in = NBXTensor.empty((m, n), dtype=NBXDtype.int64,
|
|
1729
|
+
device=x.device)
|
|
1730
|
+
set_device(indices_in)
|
|
1731
|
+
for b in range(m):
|
|
1732
|
+
arange_kernel[_1d_grid(n)](
|
|
1733
|
+
indices_in[b], 0, 1, n, **_ew_meta(n))
|
|
1734
|
+
arr_out = NBXTensor.empty_like(arr_in)
|
|
1735
|
+
indices_out = NBXTensor.empty_like(indices_in)
|
|
1736
|
+
|
|
1737
|
+
grid_r = triton.cdiv(num_bins, TILE_R)
|
|
1738
|
+
SWEEP_TILE = 2048
|
|
1739
|
+
sweep_grid_n = triton.cdiv(n, SWEEP_TILE)
|
|
1740
|
+
# `status` holds each tile's per-bin COUNT; `tile_prefix` the exclusive scan
|
|
1741
|
+
# of those counts along the tile axis. Two buffers of the same shape, and
|
|
1742
|
+
# the second is what the scatter reads instead of spinning on the first.
|
|
1743
|
+
status = NBXTensor.empty((m, num_bins, sweep_grid_n), device=x.device, dtype=NBXDtype.int32)
|
|
1744
|
+
tile_prefix = NBXTensor.empty((m, num_bins, sweep_grid_n), device=x.device, dtype=NBXDtype.int32)
|
|
1745
|
+
|
|
1746
|
+
# Stage 2: sweep per radix pass. OUT_N is the STATUS row stride the
|
|
1747
|
+
# kernel indexes with pid_n ∈ [0, sweep_grid_n) — it must be the CTA
|
|
1748
|
+
# count along n, never n itself (the old call passed n: every status
|
|
1749
|
+
# write past bin 0 landed out of the (m, bins, sweep_grid_n) buffer).
|
|
1750
|
+
for i in range(n_passes):
|
|
1751
|
+
status.fill_(0)
|
|
1752
|
+
set_device(arr_in)
|
|
1753
|
+
# THREE launches, not one. The single-kernel form carried a decoupled
|
|
1754
|
+
# lookback — a cross-CTA spin — which Triton does not order and Metal
|
|
1755
|
+
# does not make co-resident: measured wrong from n=2049 (the first size
|
|
1756
|
+
# past one 2048 tile), losing elements. `cumsum_wrapper` in this file
|
|
1757
|
+
# already does its cross-tile scan as separate launches and is correct
|
|
1758
|
+
# at eight tiles; this follows it.
|
|
1759
|
+
radix_sort_tile_counts_kernel[(m * sweep_grid_n, grid_r)](
|
|
1760
|
+
arr_in, status, m, n, sweep_grid_n,
|
|
1761
|
+
SWEEP_TILE, TILE_R, k_bits, i * k_bits, descending)
|
|
1762
|
+
radix_sort_tile_prefix_kernel[(m * num_bins,)](
|
|
1763
|
+
status, tile_prefix, sweep_grid_n, sweep_grid_n,
|
|
1764
|
+
TILES_POW2=triton.next_power_of_2(sweep_grid_n))
|
|
1765
|
+
radix_sort_scatter_kernel[(m * sweep_grid_n, grid_r)](
|
|
1766
|
+
arr_in, indices_in, arr_out, indices_out,
|
|
1767
|
+
ex_cumsum, tile_prefix,
|
|
1768
|
+
n_passes, i, i * k_bits, m, n, sweep_grid_n,
|
|
1769
|
+
SWEEP_TILE, TILE_R, k_bits, descending)
|
|
1770
|
+
arr_in, arr_out = arr_out, arr_in
|
|
1771
|
+
indices_in, indices_out = indices_out, indices_in
|
|
1772
|
+
|
|
1773
|
+
# Values keep x's shape (clone lineage); indices were built (m, n) —
|
|
1774
|
+
# fold them back to the input shape for the aten::sort contract.
|
|
1775
|
+
return arr_in, indices_in.view(x.shape)
|
|
1776
|
+
|
|
1777
|
+
|
|
1778
|
+
def logical_xor_wrapper(a, b) :
|
|
1779
|
+
# The universal binary contract (dtype align, device align, BROADCAST,
|
|
1780
|
+
# contiguous) like logical_and / logical_or: the former form read a
|
|
1781
|
+
# broadcast operand flat past its end — 34 % of the bank's broadcast
|
|
1782
|
+
# case was wrong (found by the reference bank, 2026-09-05).
|
|
1783
|
+
a, b, output, n, dev_ctx, scalar = _core._prepare_comparison(a, b)
|
|
1784
|
+
if scalar:
|
|
1785
|
+
b = NBXTensor.empty_like(a).fill_(b)
|
|
1786
|
+
logical_xor_kernel[_1d_grid(n)](a, b, output, n, **_ew_meta(n))
|
|
1787
|
+
return output
|
|
1788
|
+
|
|
1789
|
+
|
|
1790
|
+
def bitwise_left_shift_wrapper(a, b) :
|
|
1791
|
+
a = a.contiguous()
|
|
1792
|
+
output = NBXTensor.empty_like(a)
|
|
1793
|
+
if isinstance(b, NBXTensor):
|
|
1794
|
+
b = b.contiguous()
|
|
1795
|
+
set_device(a)
|
|
1796
|
+
bitwise_left_shift_kernel[_1d_grid(a.numel())](a, b, output, a.numel(), **_ew_meta(a.numel()))
|
|
1797
|
+
else:
|
|
1798
|
+
from nbxtensor.ops.bitwise_left_shift import bitwise_left_shift_scalar_kernel
|
|
1799
|
+
set_device(a)
|
|
1800
|
+
bitwise_left_shift_scalar_kernel[_1d_grid(a.numel())](a, output, a.numel(), int(b), **_ew_meta(a.numel()))
|
|
1801
|
+
return output
|
|
1802
|
+
|
|
1803
|
+
|
|
1804
|
+
def bitwise_right_shift_wrapper(a, b) :
|
|
1805
|
+
a = a.contiguous()
|
|
1806
|
+
output = NBXTensor.empty_like(a)
|
|
1807
|
+
if isinstance(b, NBXTensor):
|
|
1808
|
+
b = b.contiguous()
|
|
1809
|
+
set_device(a)
|
|
1810
|
+
bitwise_right_shift_kernel[_1d_grid(a.numel())](a, b, output, a.numel(), **_ew_meta(a.numel()))
|
|
1811
|
+
else:
|
|
1812
|
+
from nbxtensor.ops.bitwise_right_shift import bitwise_right_shift_scalar_kernel
|
|
1813
|
+
set_device(a)
|
|
1814
|
+
bitwise_right_shift_scalar_kernel[_1d_grid(a.numel())](a, output, a.numel(), int(b), **_ew_meta(a.numel()))
|
|
1815
|
+
return output
|
|
1816
|
+
|
|
1817
|
+
|
|
1818
|
+
def addr_wrapper(input, vec1, vec2,
|
|
1819
|
+
beta: float = 1.0, alpha: float = 1.0) :
|
|
1820
|
+
"""Outer product add: out = beta*input + alpha*(vec1 outer vec2)."""
|
|
1821
|
+
M = vec1.numel()
|
|
1822
|
+
N = vec2.numel()
|
|
1823
|
+
inp_c = input.contiguous()
|
|
1824
|
+
v1_c = vec1.contiguous()
|
|
1825
|
+
v2_c = vec2.contiguous()
|
|
1826
|
+
output = NBXTensor.empty((M, N), device=input.device, dtype=input.dtype)
|
|
1827
|
+
BLOCK_M = min(32, triton.next_power_of_2(M))
|
|
1828
|
+
BLOCK_N = min(32, triton.next_power_of_2(N))
|
|
1829
|
+
grid = (triton.cdiv(M, BLOCK_M), triton.cdiv(N, BLOCK_N))
|
|
1830
|
+
set_device(inp_c)
|
|
1831
|
+
addr_kernel[grid](
|
|
1832
|
+
inp_c, v1_c, v2_c, output,
|
|
1833
|
+
beta, alpha, M, N,
|
|
1834
|
+
inp_c.stride(0) if inp_c.ndim >= 2 else 0,
|
|
1835
|
+
inp_c.stride(1) if inp_c.ndim >= 2 else 1,
|
|
1836
|
+
v1_c.stride(0), v2_c.stride(0),
|
|
1837
|
+
output.stride(0), output.stride(1),
|
|
1838
|
+
BLOCK_M, BLOCK_N)
|
|
1839
|
+
return output
|
|
1840
|
+
|
|
1841
|
+
|
|
1842
|
+
def clamp_min_wrapper(x, min_val) :
|
|
1843
|
+
"""Clamp minimum: out = max(x, min_val)."""
|
|
1844
|
+
x = x.contiguous()
|
|
1845
|
+
output = NBXTensor.empty_like(x)
|
|
1846
|
+
if isinstance(min_val, NBXTensor):
|
|
1847
|
+
min_val = min_val.item()
|
|
1848
|
+
set_device(x)
|
|
1849
|
+
clamp_min_forward_kernel[_1d_grid(x.numel())](x, output, x.numel(), float(min_val), **_ew_meta(x.numel()))
|
|
1850
|
+
return output
|
|
1851
|
+
|
|
1852
|
+
|
|
1853
|
+
# Bounds for the two-stage top-k, measured on sm_70. See the comment at
|
|
1854
|
+
# the chunk-widening loop in topk_wrapper for the numbers behind them.
|
|
1855
|
+
_TOPK_MERGE_TILE_TARGET = 2048 # widen chunks to keep the merge tile here
|
|
1856
|
+
_TOPK_MERGE_TILE_MAX = 8192 # past this, refuse rather than stall
|
|
1857
|
+
_TOPK_CHUNK_MAX = 8192 # stage-1 window still flat at this width
|
|
1858
|
+
|
|
1859
|
+
|
|
1860
|
+
def topk_wrapper(x, k: int, dim: int = -1,
|
|
1861
|
+
largest: bool = True, sorted: bool = True):
|
|
1862
|
+
"""Top-k via 2-stage Triton pipeline. Stage 1: per-chunk top-k. Stage 2: merge via bitonic sort.
|
|
1863
|
+
Triton kernels + NBXTensor allocation."""
|
|
1864
|
+
from nbxtensor.ops.topk import topk_stage1_kernel, topk_stage2_kernel
|
|
1865
|
+
import math as _math
|
|
1866
|
+
|
|
1867
|
+
if dim < 0:
|
|
1868
|
+
dim = dim + x.ndim
|
|
1869
|
+
assert dim == x.ndim - 1, "topk currently supports last dim only"
|
|
1870
|
+
|
|
1871
|
+
# Kernel uses _get_finfo_val which handles fp16/bf16/fp32.
|
|
1872
|
+
# Cast fp32→fp16 for the kernel (it upcasts to fp32 internally anyway),
|
|
1873
|
+
# then cast output values back.
|
|
1874
|
+
input_dtype = x._dtype
|
|
1875
|
+
if input_dtype == NBXDtype.float32:
|
|
1876
|
+
x = x.to(NBXDtype.float16)
|
|
1877
|
+
|
|
1878
|
+
descending = largest
|
|
1879
|
+
topk_n = x.shape[dim]
|
|
1880
|
+
batch_size = x.numel() // topk_n
|
|
1881
|
+
|
|
1882
|
+
# Heuristic chunk size
|
|
1883
|
+
if topk_n < 1024:
|
|
1884
|
+
chunk_size = 256
|
|
1885
|
+
else:
|
|
1886
|
+
chunk_size = 1024
|
|
1887
|
+
if chunk_size < k:
|
|
1888
|
+
chunk_size = triton.next_power_of_2(k)
|
|
1889
|
+
|
|
1890
|
+
# Keep the stage-2 merge tile small.
|
|
1891
|
+
#
|
|
1892
|
+
# Stage 2 sorts next_power_of_2(chunk_num * k) elements inside ONE
|
|
1893
|
+
# Triton program, fully unrolled over log2(tile) bitonic stages, so
|
|
1894
|
+
# its FIRST-COMPILE cost explodes with the tile: measured on sm_70,
|
|
1895
|
+
# 2048 -> 15 s, 4096 -> 48 s, 8192 -> over 150 s, and 16384 did not
|
|
1896
|
+
# finish in the budget. That cost is paid once per (k, N, tile)
|
|
1897
|
+
# triple and then cached, but it lands on the first token of a
|
|
1898
|
+
# request, where minutes of apparent hang is indistinguishable from
|
|
1899
|
+
# a deadlock. Qwen2's 152k vocabulary with top_k=64 sat squarely in
|
|
1900
|
+
# that hole.
|
|
1901
|
+
#
|
|
1902
|
+
# Widening the chunk trades the merge tile for stage-1 work, which
|
|
1903
|
+
# is a k-iteration reduction rather than a sort — measured flat at
|
|
1904
|
+
# 1.1 s up to CHUNK 8192. Partitioning does not change the answer:
|
|
1905
|
+
# the top-k of the per-chunk top-k's IS the global top-k for any
|
|
1906
|
+
# partition. Only the index chosen among exactly-tied values can
|
|
1907
|
+
# move, so this is a FLOATING-class change, not an EXACT one.
|
|
1908
|
+
while (chunk_size < _TOPK_CHUNK_MAX
|
|
1909
|
+
and triton.cdiv(topk_n, chunk_size) * k > _TOPK_MERGE_TILE_TARGET):
|
|
1910
|
+
chunk_size *= 2
|
|
1911
|
+
|
|
1912
|
+
chunk_num = triton.cdiv(topk_n, chunk_size)
|
|
1913
|
+
|
|
1914
|
+
if chunk_num > 1 and chunk_num * k > _TOPK_MERGE_TILE_MAX:
|
|
1915
|
+
raise ValueError(
|
|
1916
|
+
f"topk: k={k} over {topk_n} elements needs a merge tile of "
|
|
1917
|
+
f"{triton.next_power_of_2(chunk_num * k)} even at the widest "
|
|
1918
|
+
f"chunk ({_TOPK_CHUNK_MAX}), past the {_TOPK_MERGE_TILE_MAX} "
|
|
1919
|
+
f"bound where the bitonic merge's first-compile cost runs to "
|
|
1920
|
+
f"minutes. Reduce k, or extend topk with a hierarchical "
|
|
1921
|
+
f"reduction stage.")
|
|
1922
|
+
|
|
1923
|
+
# Stage 1 buffers
|
|
1924
|
+
s1_vals = NBXTensor.empty(batch_size * chunk_num * k, device=x.device, dtype=x.dtype)
|
|
1925
|
+
s1_idxs = NBXTensor.empty(batch_size * chunk_num * k, device=x.device, dtype=NBXDtype.int64)
|
|
1926
|
+
|
|
1927
|
+
# Stage 1: per-chunk top-k (iterative argmax → already sorted descending
|
|
1928
|
+
# for largest=True).
|
|
1929
|
+
set_device(s1_vals)
|
|
1930
|
+
topk_stage1_kernel[batch_size, chunk_num](
|
|
1931
|
+
s1_vals, s1_idxs, x.contiguous(),
|
|
1932
|
+
k, topk_n, chunk_size, descending)
|
|
1933
|
+
|
|
1934
|
+
out_shape = x.shape[:-1] + (k,)
|
|
1935
|
+
|
|
1936
|
+
# Single-chunk fast path: stage 1 already produces the correct top-k in
|
|
1937
|
+
# sorted order, so stage 2's bitonic merge is redundant. Skipping it also
|
|
1938
|
+
# avoids a kernel bug where the BLOCK=next_pow2(k) padding corrupts the
|
|
1939
|
+
# last entries for power-of-2-misaligned k (e.g. k=6 → BLOCK=8, with
|
|
1940
|
+
# DeepSeek-MoE's 64-expert softmax the 2 pad slots leaked near-zero
|
|
1941
|
+
# garbage into the real top-k, producing negative scores for slots 5–6
|
|
1942
|
+
# and a 28× routed-MoE magnitude collapse). Also saves one kernel launch
|
|
1943
|
+
# for every MoE model whose num_experts ≤ chunk_size.
|
|
1944
|
+
if chunk_num == 1:
|
|
1945
|
+
s1_vals = s1_vals.reshape(out_shape)
|
|
1946
|
+
s1_idxs = s1_idxs.reshape(out_shape)
|
|
1947
|
+
if input_dtype == NBXDtype.float32:
|
|
1948
|
+
s1_vals = s1_vals.to(NBXDtype.float32)
|
|
1949
|
+
return s1_vals, s1_idxs
|
|
1950
|
+
|
|
1951
|
+
# Stage 2 buffers
|
|
1952
|
+
s2_vals = NBXTensor.empty(out_shape, device=x.device, dtype=x.dtype)
|
|
1953
|
+
s2_idxs = NBXTensor.empty(out_shape, device=x.device, dtype=NBXDtype.int64)
|
|
1954
|
+
|
|
1955
|
+
# Stage 2: merge chunks via bitonic sort
|
|
1956
|
+
stage2_n = chunk_num * k
|
|
1957
|
+
BLOCK = triton.next_power_of_2(stage2_n)
|
|
1958
|
+
set_device(s2_vals)
|
|
1959
|
+
topk_stage2_kernel[batch_size,](
|
|
1960
|
+
s2_vals, s2_idxs, s1_vals, s1_idxs,
|
|
1961
|
+
dim, k, stage2_n, BLOCK, descending)
|
|
1962
|
+
|
|
1963
|
+
# Cast values back to original dtype if we downcast for the kernel
|
|
1964
|
+
if input_dtype == NBXDtype.float32:
|
|
1965
|
+
s2_vals = s2_vals.to(NBXDtype.float32)
|
|
1966
|
+
|
|
1967
|
+
return s2_vals, s2_idxs
|
|
1968
|
+
|
|
1969
|
+
|
|
1970
|
+
def trace_wrapper(x) :
|
|
1971
|
+
"""Sum of diagonal elements."""
|
|
1972
|
+
assert x.ndim >= 2
|
|
1973
|
+
n = min(x.shape[-2], x.shape[-1])
|
|
1974
|
+
out = NBXTensor.empty(1, dtype=NBXDtype.float32, device=x.device)
|
|
1975
|
+
set_device(x.contiguous())
|
|
1976
|
+
trace_kernel[(1,)](
|
|
1977
|
+
x.contiguous(), out, n,
|
|
1978
|
+
x.stride(-2), x.stride(-1),
|
|
1979
|
+
BLOCK_SIZE=triton.next_power_of_2(n))
|
|
1980
|
+
return out.squeeze().to(x.dtype)
|
|
1981
|
+
|
|
1982
|
+
|
|
1983
|
+
def _stream_draw(kind, shape, dtype, device):
|
|
1984
|
+
"""A seeded run's draw (nbxtensor/random/rng_stream: the vendor's CPU stream, continued in consumption order — the same
|
|
1985
|
+
values the compiled branch draws), as an NBXTensor of `dtype` on `device`; None when the run has no seed."""
|
|
1986
|
+
from nbxtensor.random import rng_stream
|
|
1987
|
+
if not rng_stream.active():
|
|
1988
|
+
return None
|
|
1989
|
+
arr = rng_stream.next_normal(shape) if kind == "normal" else rng_stream.next_uniform(shape)
|
|
1990
|
+
if isinstance(device, NBXTensor): # a tensor is its own device context (NBXTensor.device)
|
|
1991
|
+
DeviceAllocator.set_device(int(device._device_idx))
|
|
1992
|
+
elif device is not None and ":" in str(device):
|
|
1993
|
+
DeviceAllocator.set_device(int(str(device).split(":")[1]))
|
|
1994
|
+
out = NBXTensor.from_numpy(arr)
|
|
1995
|
+
return out if out._dtype == dtype else out.to(dtype)
|
|
1996
|
+
|
|
1997
|
+
|
|
1998
|
+
def bernoulli_wrapper(x, p: float = 0.5, **kwargs) :
|
|
1999
|
+
"""bernoulli(tensor) → 0/1 values based on probabilities."""
|
|
2000
|
+
from nbxtensor.ops.rand_op import rand_kernel
|
|
2001
|
+
x = _core._ensure_cuda(x)
|
|
2002
|
+
output = NBXTensor.empty_like(x)
|
|
2003
|
+
# Generate uniform random values
|
|
2004
|
+
temp = NBXTensor.empty_like(x, dtype=NBXDtype.float32)
|
|
2005
|
+
n = temp.numel()
|
|
2006
|
+
if n == 0:
|
|
2007
|
+
return output
|
|
2008
|
+
seed = _rnd._draw_seed()
|
|
2009
|
+
rand_kernel[_1d_grid(n)](temp, n, seed, **_ew_meta(n))
|
|
2010
|
+
# Compare with probability: if rand < p → 1.0, else → 0.0
|
|
2011
|
+
# If x is the probability tensor, compare temp < x
|
|
2012
|
+
if x.is_floating_point():
|
|
2013
|
+
result = (temp < x.float()).to(output.dtype)
|
|
2014
|
+
else:
|
|
2015
|
+
result = (temp < p).to(output.dtype)
|
|
2016
|
+
output.copy_(result)
|
|
2017
|
+
return output
|
|
2018
|
+
|
|
2019
|
+
|
|
2020
|
+
# ===========================================================================
|
|
2021
|
+
# FOLD / UNFOLD_BACKWARD — compute ops
|
|
2022
|
+
# ===========================================================================
|
|
2023
|
+
|
|
2024
|
+
def fold_wrapper(*args, **kwargs) :
|
|
2025
|
+
"""fold — NOT YET IMPLEMENTED in Triton."""
|
|
2026
|
+
raise RuntimeError(
|
|
2027
|
+
"[--triton mode] fold (col2im) has no Triton kernel. "
|
|
2028
|
+
"Implement in src/neurobrix/kernels/ops/fold_op.py."
|
|
2029
|
+
)
|
|
2030
|
+
|
|
2031
|
+
|
|
2032
|
+
# ===========================================================================
|
|
2033
|
+
# ANGLE — atan2(imag, real) for complex tensors
|
|
2034
|
+
# ===========================================================================
|
|
2035
|
+
|
|
2036
|
+
# ===========================================================================
|
|
2037
|
+
# SCALED DOT-PRODUCT ATTENTION — Dao-AILab Flash Attention v2 Triton kernel
|
|
2038
|
+
#
|
|
2039
|
+
# Reference: Dao-AILab/flash-attention flash_attn_triton.py
|
|
2040
|
+
# Kernel: ops/flash_attention.py (extracted from same source)
|
|
2041
|
+
# ===========================================================================
|
|
2042
|
+
|
|
2043
|
+
# Cache of zero-bias buffers for the no-mask flash attention path. The
|
|
2044
|
+
# kernel always reads bias from memory (BIAS_TYPE="vector"|"matrix" only)
|
|
2045
|
+
# to guarantee bit-equivalent MMA selection across configurations — a
|
|
2046
|
+
# tl.zeros in-register was empirically shown to produce a different MMA
|
|
2047
|
+
# than a tl.load on Volta SIMT (and likely on AMD CDNA), so every call
|
|
2048
|
+
# without an explicit mask is routed through tl.load via this shared
|
|
2049
|
+
# zero buffer. Cache key = (device_idx, seqlen_q, seqlen_k, dtype). One
|
|
2050
|
+
# allocation per distinct shape per device — typically caps at 2-4 keys
|
|
2051
|
+
# per inference session. Memory: ~4-16 MB total for typical attention
|
|
2052
|
+
# shapes, negligible vs model weights.
|
|
2053
|
+
_zero_bias_cache: dict = {}
|
|
2054
|
+
_causal_bias_cache: dict = {}
|
|
2055
|
+
|
|
2056
|
+
|
|
2057
|
+
def _get_zero_bias(device_idx, seqlen_q, seqlen_k, dtype):
|
|
2058
|
+
"""Zero additive bias for the no-mask flash path, as a STRIDE-0
|
|
2059
|
+
BROADCAST view over the query axis: physical storage is a single
|
|
2060
|
+
[1, seqlen_k] zero row, expanded to [seqlen_q, seqlen_k] with
|
|
2061
|
+
stride_q = 0. The kernel reads the bias through its strides and only
|
|
2062
|
+
requires unit stride on the KEY axis (offs_n is indexed with implicit
|
|
2063
|
+
stride 1), so the query axis can broadcast freely — exactly the same
|
|
2064
|
+
trick the wrapper already uses for key-padding masks. A materialized
|
|
2065
|
+
[Sq, Sk] zero matrix is fatal at video-native scale: Allegro 720p×88f
|
|
2066
|
+
self-attention is 79200×79200 fp32 = 25 GB for a tensor of ZEROS
|
|
2067
|
+
(first real trigger of this path at native scale, 2026-07-04)."""
|
|
2068
|
+
key = (device_idx, seqlen_q, seqlen_k, dtype)
|
|
2069
|
+
cached = _zero_bias_cache.get(key)
|
|
2070
|
+
if cached is not None:
|
|
2071
|
+
return cached
|
|
2072
|
+
DeviceAllocator.set_device(device_idx)
|
|
2073
|
+
row = NBXTensor.zeros((1, seqlen_k), dtype=dtype,
|
|
2074
|
+
device=f"cuda:{device_idx}")
|
|
2075
|
+
bias = row.expand(seqlen_q, seqlen_k)
|
|
2076
|
+
_zero_bias_cache[key] = bias
|
|
2077
|
+
return bias
|
|
2078
|
+
|
|
2079
|
+
|
|
2080
|
+
def _get_causal_bias(device_idx, seqlen_q, seqlen_k, dtype):
|
|
2081
|
+
"""Return a cached causal additive mask [seqlen_q, seqlen_k]:
|
|
2082
|
+
0.0 for positions k <= q + (seqlen_k - seqlen_q), -inf otherwise.
|
|
2083
|
+
|
|
2084
|
+
Materialized via memory so the flash kernel reads it through
|
|
2085
|
+
tl.load — same physical path as user-provided explicit masks.
|
|
2086
|
+
Required for numerical parity: Triton compiles tl.where(constexpr,
|
|
2087
|
+
0, -inf) and tl.load(memory) with different IR optimization
|
|
2088
|
+
passes, propagating to different MMA selections downstream.
|
|
2089
|
+
Empirical: fp32 hd=256 IS_CAUSAL via constexpr produced cosine
|
|
2090
|
+
0.937; via memory produces 0.999997.
|
|
2091
|
+
"""
|
|
2092
|
+
key = (device_idx, seqlen_q, seqlen_k, dtype)
|
|
2093
|
+
cached = _causal_bias_cache.get(key)
|
|
2094
|
+
if cached is not None:
|
|
2095
|
+
return cached
|
|
2096
|
+
DeviceAllocator.set_device(device_idx)
|
|
2097
|
+
# Build via tril + masked_fill: ones[Q,K] -> tril(diagonal=offset)
|
|
2098
|
+
# gives 1 in valid positions, 0 in masked positions. masked_fill
|
|
2099
|
+
# those zero positions with -inf, the rest stays as 0 baseline.
|
|
2100
|
+
offset = seqlen_k - seqlen_q
|
|
2101
|
+
base = NBXTensor.zeros((seqlen_q, seqlen_k), dtype=dtype,
|
|
2102
|
+
device=f"cuda:{device_idx}")
|
|
2103
|
+
ones = add(base, 1.0)
|
|
2104
|
+
tril_mask = _shp.tril_wrapper(ones, diagonal=offset)
|
|
2105
|
+
inverted = eq(tril_mask, 0.0)
|
|
2106
|
+
bias = _idx.masked_fill(base, inverted, float('-inf'))
|
|
2107
|
+
_causal_bias_cache[key] = bias
|
|
2108
|
+
return bias
|
|
2109
|
+
|
|
2110
|
+
|
|
2111
|
+
def clear_bias_caches() -> None:
|
|
2112
|
+
"""Evict the module-global attention bias caches.
|
|
2113
|
+
|
|
2114
|
+
`_causal_bias_cache` stores MATERIALIZED [Sq, Sk] additive masks keyed
|
|
2115
|
+
by (device, Sq, Sk, dtype); `_zero_bias_cache` stores stride-0 broadcast
|
|
2116
|
+
rows. Both persist for the process lifetime otherwise. In a cold CLI run
|
|
2117
|
+
that is negligible (a handful of shapes), but a warm serve that sweeps
|
|
2118
|
+
many sequence lengths accumulates one materialized [Sq, Sk] tensor per
|
|
2119
|
+
distinct length — unbounded growth that the runtime never reclaimed
|
|
2120
|
+
(audit #2 F7). This hook is called from the single memory-cleanup
|
|
2121
|
+
surface (`MemoryManager.unload_weights`), so eviction rides the same
|
|
2122
|
+
phase boundaries as weight unload (encoder→loop→VAE, request teardown).
|
|
2123
|
+
The caches rebuild lazily on next use, so clearing is always safe;
|
|
2124
|
+
within a diffusion loop `unload_weights` is not called per step, so the
|
|
2125
|
+
hot reuse of a single bias table is untouched.
|
|
2126
|
+
|
|
2127
|
+
R33-pure: clears two Python dicts of NBXTensors — no torch, no compute.
|
|
2128
|
+
The dropped NBXTensor refs trigger their finalizers (cudaFree) once the
|
|
2129
|
+
caller's per-device sync in `unload_weights` has completed.
|
|
2130
|
+
"""
|
|
2131
|
+
_zero_bias_cache.clear()
|
|
2132
|
+
_causal_bias_cache.clear()
|
|
2133
|
+
|
|
2134
|
+
|
|
2135
|
+
def _is_power_of_2(n: int) -> bool:
|
|
2136
|
+
return n > 0 and (n & (n - 1)) == 0
|
|
2137
|
+
|
|
2138
|
+
|
|
2139
|
+
def _try_decode_vec(q, k, v, attn_mask, softmax_scale,
|
|
2140
|
+
batch, nheads, nheads_k, seqlen_k, headdim, q_round=None):
|
|
2141
|
+
"""Route guard for the vector decode kernel: returns the output or
|
|
2142
|
+
None when the shape/mask is outside the kernel's contract."""
|
|
2143
|
+
if not _lk.decode_vec_takes(headdim, v.shape[3],
|
|
2144
|
+
None if attn_mask is None else attn_mask.numel(), seqlen_k):
|
|
2145
|
+
return None
|
|
2146
|
+
bias = attn_mask.reshape(seqlen_k) if attn_mask is not None else None
|
|
2147
|
+
return _attn._decode_attn_vec(q, k, v, bias, softmax_scale,
|
|
2148
|
+
batch, nheads, nheads_k, seqlen_k, headdim, q_round=q_round)
|
|
2149
|
+
|
|
2150
|
+
|
|
2151
|
+
def angle_wrapper(x) :
|
|
2152
|
+
"""angle(complex_tensor) → atan2(imag, real).
|
|
2153
|
+
|
|
2154
|
+
For real inputs: 0 where x >= 0, pi where x < 0.
|
|
2155
|
+
Uses Triton atan2 via libdevice.
|
|
2156
|
+
"""
|
|
2157
|
+
x = _core._ensure_cuda(x)
|
|
2158
|
+
|
|
2159
|
+
if x.is_complex():
|
|
2160
|
+
# Complex: atan2(imag, real) element-wise (libdevice, fp32).
|
|
2161
|
+
from nbxtensor.ops.atan2 import atan2_kernel
|
|
2162
|
+
real = x.real.contiguous()
|
|
2163
|
+
imag = x.imag.contiguous()
|
|
2164
|
+
out = NBXTensor.empty(tuple(real.shape), dtype=NBXDtype.float32,
|
|
2165
|
+
device=real.device)
|
|
2166
|
+
set_device(real)
|
|
2167
|
+
n = real.numel()
|
|
2168
|
+
atan2_kernel[_1d_grid(n)](imag, real, out, n, **_ew_meta(n))
|
|
2169
|
+
return out
|
|
2170
|
+
else:
|
|
2171
|
+
# Real input: angle is 0 where x >= 0, pi where x < 0. Not on the
|
|
2172
|
+
# complex-FFT path (kept minimal: zeros for the x>=0 common case).
|
|
2173
|
+
return NBXTensor.zeros_like(x)
|
|
2174
|
+
|
|
2175
|
+
|
|
2176
|
+
# ===========================================================================
|
|
2177
|
+
# FFT — Pure Triton Cooley-Tukey radix-2 implementation
|
|
2178
|
+
# ===========================================================================
|
|
2179
|
+
|
|
2180
|
+
def _next_power_of_2(n: int) -> int:
|
|
2181
|
+
"""Round up to next power of 2."""
|
|
2182
|
+
if n <= 0:
|
|
2183
|
+
return 1
|
|
2184
|
+
return 1 << (n - 1).bit_length()
|
|
2185
|
+
|
|
2186
|
+
|
|
2187
|
+
# ===========================================================================
|
|
2188
|
+
# COMPLEX — tensor creation from real/imag parts
|
|
2189
|
+
# ===========================================================================
|
|
2190
|
+
|
|
2191
|
+
def complex_wrapper(real, imag) :
|
|
2192
|
+
"""complex(real, imag) → complex64 tensor (interleaved [real, imag] pairs).
|
|
2193
|
+
|
|
2194
|
+
Builds the NBX complex64 representation: stack real/imag on a new last dim
|
|
2195
|
+
→ [..,N,2] → reinterpret as complex64 [..,N] (NBXTensor.view_as_complex).
|
|
2196
|
+
R33-pure (cat + view; no torch)."""
|
|
2197
|
+
real = _core._ensure_cuda(real)
|
|
2198
|
+
imag = _core._ensure_cuda(imag)
|
|
2199
|
+
if real.nbx_dtype != NBXDtype.float32:
|
|
2200
|
+
real = real.to(NBXDtype.float32)
|
|
2201
|
+
if imag.nbx_dtype != NBXDtype.float32:
|
|
2202
|
+
imag = imag.to(NBXDtype.float32)
|
|
2203
|
+
stacked = NBXTensor.cat([real.unsqueeze(-1), imag.unsqueeze(-1)], dim=-1).contiguous()
|
|
2204
|
+
return stacked.view_as_complex()
|
|
2205
|
+
|
|
2206
|
+
|
|
2207
|
+
# ============================================================================
|
|
2208
|
+
# LSTM — triton-pure aten::lstm (mirror of F.lstm / cuDNN, the compiled ref).
|
|
2209
|
+
# Level-1 R33-pure: NBXTensor + Triton wrappers only (zero torch, zero NumPy
|
|
2210
|
+
# compute). Per-step recurrence assembled from mm/sigmoid/tanh/add/mul; a fused
|
|
2211
|
+
# @triton.jit LSTM is the deferred level-2 optimisation. Replaces the parakeet
|
|
2212
|
+
# NumPy LSTM (triton/flow/rnnt.py) once that flow is wired to it.
|
|
2213
|
+
# ============================================================================
|
|
2214
|
+
def _lstm_run_direction(x, w_ih_t, w_hh_t, b_ih, b_hh, h, c, hidden, reverse):
|
|
2215
|
+
"""One LSTM direction over the time axis.
|
|
2216
|
+
|
|
2217
|
+
x : [B, T, I] w_ih_t : [I, 4H] w_hh_t : [H, 4H]
|
|
2218
|
+
b_ih/b_hh : [4H] or None h, c : [B, H]
|
|
2219
|
+
returns (out [B,T,H], h_last [B,H], c_last [B,H]).
|
|
2220
|
+
|
|
2221
|
+
Gate order i/f/g/o (PyTorch), mirrors triton/flow/rnnt.py:_lstm_cell_np:
|
|
2222
|
+
gates = x@W_ih.T + b_ih + h@W_hh.T + b_hh
|
|
2223
|
+
i=sigmoid(g[:H]); f=sigmoid(g[H:2H]); g_=tanh(g[2H:3H]); o=sigmoid(g[3H:])
|
|
2224
|
+
c = f*c + i*g_; h = o*tanh(c)
|
|
2225
|
+
mm/bmm accumulate in fp32 (Triton tl.dot) → matches cuDNN's fp16 math.
|
|
2226
|
+
|
|
2227
|
+
The fp16 precision contract does NOT reach the recurrence: the calibration that grants it observed the graph's
|
|
2228
|
+
ops, and an LSTM is ONE graph op whose internal products it never saw — their state is carried through T steps,
|
|
2229
|
+
and fp16 stores there drift the recurrence (Kokoro-82M's predictor, 2026-10-26: the speech became gibberish under
|
|
2230
|
+
the contract, correct with it off inside this function only). The products run under the conservative store.
|
|
2231
|
+
"""
|
|
2232
|
+
_contract = _core.get_activations_fp16_safe()
|
|
2233
|
+
_core.set_activations_fp16_safe(False)
|
|
2234
|
+
try:
|
|
2235
|
+
return _lstm_run_direction_steps(x, w_ih_t, w_hh_t, b_ih, b_hh, h, c, hidden, reverse)
|
|
2236
|
+
finally:
|
|
2237
|
+
_core.set_activations_fp16_safe(_contract)
|
|
2238
|
+
|
|
2239
|
+
|
|
2240
|
+
def _lstm_run_direction_steps(x, w_ih_t, w_hh_t, b_ih, b_hh, h, c, hidden, reverse):
|
|
2241
|
+
"""`_lstm_run_direction`'s body, under the conservative store."""
|
|
2242
|
+
T = x.shape[1]
|
|
2243
|
+
H = hidden
|
|
2244
|
+
Wx = _la.matmul_wrapper(x, w_ih_t) # [B, T, 4H]
|
|
2245
|
+
if b_ih is not None:
|
|
2246
|
+
Wx = add(Wx, b_ih) # broadcast [4H]
|
|
2247
|
+
outs = [None] * T
|
|
2248
|
+
step_range = range(T - 1, -1, -1) if reverse else range(T)
|
|
2249
|
+
for t in step_range:
|
|
2250
|
+
gates = add(Wx.select(1, t), _la.matmul_wrapper(h, w_hh_t)) # [B, 4H]
|
|
2251
|
+
if b_hh is not None:
|
|
2252
|
+
gates = add(gates, b_hh)
|
|
2253
|
+
i_gate = sigmoid_wrapper(gates.narrow(1, 0, H))
|
|
2254
|
+
f_gate = sigmoid_wrapper(gates.narrow(1, H, H))
|
|
2255
|
+
g_gate = tanh_wrapper(gates.narrow(1, 2 * H, H))
|
|
2256
|
+
o_gate = sigmoid_wrapper(gates.narrow(1, 3 * H, H))
|
|
2257
|
+
c = add(mul(f_gate, c), mul(i_gate, g_gate)) # f*c + i*g_
|
|
2258
|
+
h = mul(o_gate, tanh_wrapper(c)) # o*tanh(c)
|
|
2259
|
+
outs[t] = h.unsqueeze(1) # [B, 1, H]
|
|
2260
|
+
return NBXTensor.cat(outs, dim=1), h, c # [B, T, H]
|
|
2261
|
+
|
|
2262
|
+
|
|
2263
|
+
def lstm_wrapper(input_, hx, params, has_biases=True, num_layers=1,
|
|
2264
|
+
dropout=0.0, train=False, bidirectional=False,
|
|
2265
|
+
batch_first=False):
|
|
2266
|
+
"""Triton-pure aten::lstm. See _lstm_run_direction for the cell math.
|
|
2267
|
+
|
|
2268
|
+
input_ : [B,T,I] (batch_first) or [T,B,I] or [T,I]
|
|
2269
|
+
hx : [h0, c0], each [num_layers*num_dir, B, H]
|
|
2270
|
+
params : flat list per layer per direction: [W_ih, W_hh, b_ih, b_hh] (biases)
|
|
2271
|
+
or [W_ih, W_hh] (no biases); reverse direction follows forward.
|
|
2272
|
+
returns (output [B,T,num_dir*H], h_n, c_n).
|
|
2273
|
+
"""
|
|
2274
|
+
num_dir = 2 if bidirectional else 1
|
|
2275
|
+
ppl = 4 if has_biases else 2
|
|
2276
|
+
stride = ppl * num_dir
|
|
2277
|
+
|
|
2278
|
+
x = input_
|
|
2279
|
+
if not batch_first and x.ndim == 3:
|
|
2280
|
+
x = x.transpose(0, 1).contiguous() # [T,B,I] -> [B,T,I]
|
|
2281
|
+
if x.ndim == 2:
|
|
2282
|
+
x = x.unsqueeze(0) # [T,I] -> [1,T,I]
|
|
2283
|
+
cdt = x.dtype
|
|
2284
|
+
|
|
2285
|
+
h0_all, c0_all = hx[0], hx[1] # [layers*dir, B, H]
|
|
2286
|
+
h_parts, c_parts = [], []
|
|
2287
|
+
layer_in = x
|
|
2288
|
+
for layer in range(int(num_layers)):
|
|
2289
|
+
dir_outs = []
|
|
2290
|
+
for d in range(num_dir):
|
|
2291
|
+
base = layer * stride + d * ppl
|
|
2292
|
+
w_ih = params[base].to(cdt)
|
|
2293
|
+
w_hh = params[base + 1].to(cdt)
|
|
2294
|
+
H = w_hh.shape[1] # w_hh: [4H, H]
|
|
2295
|
+
b_ih = params[base + 2].to(cdt) if has_biases else None
|
|
2296
|
+
b_hh = params[base + 3].to(cdt) if has_biases else None
|
|
2297
|
+
si = layer * num_dir + d
|
|
2298
|
+
h = h0_all.select(0, si).to(cdt) # [B, H]
|
|
2299
|
+
c = c0_all.select(0, si).to(cdt)
|
|
2300
|
+
out_d, h_last, c_last = _lstm_run_direction(
|
|
2301
|
+
layer_in,
|
|
2302
|
+
w_ih.transpose(0, 1).contiguous(),
|
|
2303
|
+
w_hh.transpose(0, 1).contiguous(),
|
|
2304
|
+
b_ih, b_hh, h, c, H, reverse=(d == 1))
|
|
2305
|
+
dir_outs.append(out_d)
|
|
2306
|
+
h_parts.append(h_last.unsqueeze(0))
|
|
2307
|
+
c_parts.append(c_last.unsqueeze(0))
|
|
2308
|
+
layer_in = (NBXTensor.cat(dir_outs, dim=-1).contiguous()
|
|
2309
|
+
if num_dir == 2 else dir_outs[0])
|
|
2310
|
+
h_n = NBXTensor.cat(h_parts, dim=0) # [layers*dir, B, H]
|
|
2311
|
+
c_n = NBXTensor.cat(c_parts, dim=0)
|
|
2312
|
+
return layer_in, h_n, c_n
|
|
2313
|
+
|
|
2314
|
+
|
|
2315
|
+
lstm_wrapper.self_manages_dtype = True
|