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,145 @@
|
|
|
1
|
+
"""aten::grid_sampler_2d — pure Triton 2D grid sampler.
|
|
2
|
+
|
|
3
|
+
Clean-room implementation from the PyTorch semantics (ATen GridSampler.h /
|
|
4
|
+
torch._decomp grid sampler decomposition — formulas only, no code vendored):
|
|
5
|
+
|
|
6
|
+
- Coordinate unnormalization:
|
|
7
|
+
align_corners=True: ix = (x + 1)/2 * (W - 1)
|
|
8
|
+
align_corners=False: ix = ((x + 1)*W - 1)/2
|
|
9
|
+
- Bicubic: cubic-convolution weights with A = -0.75 over a 4x4 tap window;
|
|
10
|
+
the fractional offset is computed on the RAW unnormalized coordinate
|
|
11
|
+
(floor before padding), padding is applied PER INTEGER TAP (this staging
|
|
12
|
+
differs from bilinear and changes border behavior).
|
|
13
|
+
- Bilinear: padding (clamp) is applied to the FLOAT coordinate before
|
|
14
|
+
flooring; 4 corners accumulated with per-corner in-bounds masks.
|
|
15
|
+
- zeros padding: out-of-bounds taps contribute 0 (masked loads);
|
|
16
|
+
border padding: tap indices clamp into [0, size-1].
|
|
17
|
+
- All coordinate/weight math and accumulation in float32 (ATen opmath),
|
|
18
|
+
output cast to the input dtype by the wrapper.
|
|
19
|
+
|
|
20
|
+
Modes follow the ATen integer encoding: interpolation 0=bilinear,
|
|
21
|
+
2=bicubic (1=nearest unsupported → wrapper raises); padding 0=zeros,
|
|
22
|
+
1=border (2=reflection unsupported → wrapper raises).
|
|
23
|
+
|
|
24
|
+
Parallelization: one program per (batch, output-location, channel-block);
|
|
25
|
+
coordinates and weights are computed once per location, taps are gathered
|
|
26
|
+
as BLOCK_C-wide strided loads (mirrors the CUDA kernel's channel loop).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import triton
|
|
30
|
+
import triton.language as tl
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@triton.jit
|
|
34
|
+
def _cc1(x, A: tl.constexpr):
|
|
35
|
+
# |x| <= 1 branch of the cubic convolution kernel
|
|
36
|
+
return ((A + 2.0) * x - (A + 3.0)) * x * x + 1.0
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@triton.jit
|
|
40
|
+
def _cc2(x, A: tl.constexpr):
|
|
41
|
+
# 1 < |x| < 2 branch of the cubic convolution kernel
|
|
42
|
+
return ((A * x - 5.0 * A) * x + 8.0 * A) * x - 4.0 * A
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@triton.jit
|
|
46
|
+
def grid_sampler_2d_kernel(
|
|
47
|
+
inp_ptr, grid_ptr, out_ptr,
|
|
48
|
+
C, H, W, OUT_SPATIAL,
|
|
49
|
+
stride_in, stride_ic, stride_ih, stride_iw,
|
|
50
|
+
stride_gn, stride_gs, stride_gc,
|
|
51
|
+
stride_on, stride_oc, stride_os,
|
|
52
|
+
MODE: tl.constexpr, # 0 = bilinear, 2 = bicubic
|
|
53
|
+
PAD_BORDER: tl.constexpr, # 0 = zeros, 1 = border
|
|
54
|
+
ALIGN: tl.constexpr, # 0 / 1
|
|
55
|
+
BLOCK_C: tl.constexpr,
|
|
56
|
+
):
|
|
57
|
+
pid_s = tl.program_id(0).to(tl.int64) # output spatial location (oH*oW flattened)
|
|
58
|
+
pid_c = tl.program_id(1).to(tl.int64) # channel block
|
|
59
|
+
pid_n = tl.program_id(2).to(tl.int64) # batch
|
|
60
|
+
|
|
61
|
+
# A program beyond the output extent reads and writes nothing (masked,
|
|
62
|
+
# no early exit — unstructured control flow has no lowering on every backend).
|
|
63
|
+
s_valid = pid_s < OUT_SPATIAL
|
|
64
|
+
|
|
65
|
+
c_offs = pid_c * BLOCK_C + tl.arange(0, BLOCK_C)
|
|
66
|
+
c_mask = (c_offs < C) & s_valid
|
|
67
|
+
|
|
68
|
+
# grid holds (x, y) pairs, channels-last
|
|
69
|
+
gx = tl.load(grid_ptr + pid_n * stride_gn + pid_s * stride_gs
|
|
70
|
+
+ 0 * stride_gc, mask=s_valid, other=0.0).to(tl.float32)
|
|
71
|
+
gy = tl.load(grid_ptr + pid_n * stride_gn + pid_s * stride_gs
|
|
72
|
+
+ 1 * stride_gc, mask=s_valid, other=0.0).to(tl.float32)
|
|
73
|
+
|
|
74
|
+
Wf = W.to(tl.float32)
|
|
75
|
+
Hf = H.to(tl.float32)
|
|
76
|
+
if ALIGN:
|
|
77
|
+
ix = (gx + 1.0) * 0.5 * (Wf - 1.0)
|
|
78
|
+
iy = (gy + 1.0) * 0.5 * (Hf - 1.0)
|
|
79
|
+
else:
|
|
80
|
+
ix = ((gx + 1.0) * Wf - 1.0) * 0.5
|
|
81
|
+
iy = ((gy + 1.0) * Hf - 1.0) * 0.5
|
|
82
|
+
|
|
83
|
+
inp_base = inp_ptr + pid_n * stride_in + c_offs * stride_ic
|
|
84
|
+
acc = tl.zeros([BLOCK_C], dtype=tl.float32)
|
|
85
|
+
|
|
86
|
+
if MODE == 0:
|
|
87
|
+
# ── bilinear: pad the FLOAT coordinate first, then corners ──
|
|
88
|
+
if PAD_BORDER:
|
|
89
|
+
ix = tl.minimum(tl.maximum(ix, 0.0), Wf - 1.0)
|
|
90
|
+
iy = tl.minimum(tl.maximum(iy, 0.0), Hf - 1.0)
|
|
91
|
+
x0 = tl.floor(ix)
|
|
92
|
+
y0 = tl.floor(iy)
|
|
93
|
+
tx = ix - x0
|
|
94
|
+
ty = iy - y0
|
|
95
|
+
for j in tl.static_range(2):
|
|
96
|
+
for i in tl.static_range(2):
|
|
97
|
+
xi = x0 + i
|
|
98
|
+
yi = y0 + j
|
|
99
|
+
wgt = (tl.where(i == 0, 1.0 - tx, tx)
|
|
100
|
+
* tl.where(j == 0, 1.0 - ty, ty))
|
|
101
|
+
inb = (xi >= 0) & (xi < Wf) & (yi >= 0) & (yi < Hf)
|
|
102
|
+
xs = tl.minimum(tl.maximum(xi, 0.0), Wf - 1.0).to(tl.int32)
|
|
103
|
+
ys = tl.minimum(tl.maximum(yi, 0.0), Hf - 1.0).to(tl.int32)
|
|
104
|
+
val = tl.load(inp_base + ys * stride_ih + xs * stride_iw,
|
|
105
|
+
mask=c_mask & inb, other=0.0).to(tl.float32)
|
|
106
|
+
acc += tl.where(inb, wgt, 0.0) * val
|
|
107
|
+
else:
|
|
108
|
+
# ── bicubic: floor on the RAW coordinate, pad per integer tap ──
|
|
109
|
+
A: tl.constexpr = -0.75
|
|
110
|
+
x0 = tl.floor(ix)
|
|
111
|
+
y0 = tl.floor(iy)
|
|
112
|
+
tx = ix - x0
|
|
113
|
+
ty = iy - y0
|
|
114
|
+
wx0 = _cc2(tx + 1.0, A)
|
|
115
|
+
wx1 = _cc1(tx, A)
|
|
116
|
+
wx2 = _cc1(1.0 - tx, A)
|
|
117
|
+
wx3 = _cc2(2.0 - tx, A)
|
|
118
|
+
wy0 = _cc2(ty + 1.0, A)
|
|
119
|
+
wy1 = _cc1(ty, A)
|
|
120
|
+
wy2 = _cc1(1.0 - ty, A)
|
|
121
|
+
wy3 = _cc2(2.0 - ty, A)
|
|
122
|
+
for j in tl.static_range(4):
|
|
123
|
+
wy = tl.where(j == 0, wy0,
|
|
124
|
+
tl.where(j == 1, wy1,
|
|
125
|
+
tl.where(j == 2, wy2, wy3)))
|
|
126
|
+
yi = y0 + (j - 1)
|
|
127
|
+
for i in tl.static_range(4):
|
|
128
|
+
wx = tl.where(i == 0, wx0,
|
|
129
|
+
tl.where(i == 1, wx1,
|
|
130
|
+
tl.where(i == 2, wx2, wx3)))
|
|
131
|
+
xi = x0 + (i - 1)
|
|
132
|
+
inb = (xi >= 0) & (xi < Wf) & (yi >= 0) & (yi < Hf)
|
|
133
|
+
xs = tl.minimum(tl.maximum(xi, 0.0), Wf - 1.0).to(tl.int32)
|
|
134
|
+
ys = tl.minimum(tl.maximum(yi, 0.0), Hf - 1.0).to(tl.int32)
|
|
135
|
+
if PAD_BORDER:
|
|
136
|
+
val = tl.load(inp_base + ys * stride_ih + xs * stride_iw,
|
|
137
|
+
mask=c_mask, other=0.0).to(tl.float32)
|
|
138
|
+
acc += wy * wx * val
|
|
139
|
+
else:
|
|
140
|
+
val = tl.load(inp_base + ys * stride_ih + xs * stride_iw,
|
|
141
|
+
mask=c_mask & inb, other=0.0).to(tl.float32)
|
|
142
|
+
acc += tl.where(inb, wy * wx, 0.0) * val
|
|
143
|
+
|
|
144
|
+
out = out_ptr + pid_n * stride_on + c_offs * stride_oc + pid_s * stride_os
|
|
145
|
+
tl.store(out, acc, mask=c_mask)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""The two passes of an exact tiled group norm on the Triton engine (`core/strategies/tiled_norm.py`'s NBXTensor twin):
|
|
2
|
+
the statistics of each (sample, group) over the WHOLE input, then a slice normalised with them.
|
|
3
|
+
|
|
4
|
+
`group_norm_stats_kernel` is the first pass of `groupnorm.group_norm_forward_kernel` alone — the same fp32 sum and
|
|
5
|
+
sum of squares, var = ss/n - mean^2 — without writing the normalised tensor (a stretch sliced along the axis holds its
|
|
6
|
+
input whole, never a whole output beside it). `group_norm_apply_kernel` is its second pass on a slice, reading the
|
|
7
|
+
given statistics. One program per (sample, group); the walk is a fixed chunk (no autotune key: nothing to certify), so
|
|
8
|
+
the summation order is fixed.
|
|
9
|
+
|
|
10
|
+
Pure @triton.jit (R33).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import triton
|
|
14
|
+
import triton.language as tl
|
|
15
|
+
|
|
16
|
+
#: the chunk a program walks its group by — fixed, so the statistics' summation order is
|
|
17
|
+
TILED_NORM_BLOCK = 4096
|
|
18
|
+
TILED_NORM_WARPS = 8
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@triton.jit
|
|
22
|
+
def group_norm_stats_kernel(X, Mean, Rstd, group_size, C, HW, num_groups, eps, BLOCK_SIZE: tl.constexpr):
|
|
23
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
24
|
+
batch_idx = pid // num_groups
|
|
25
|
+
group_idx = pid % num_groups
|
|
26
|
+
base = batch_idx * C * HW + group_idx * group_size * HW
|
|
27
|
+
hidden = group_size * HW
|
|
28
|
+
block = tl.arange(0, BLOCK_SIZE)
|
|
29
|
+
s = tl.zeros((), dtype=tl.float32)
|
|
30
|
+
ss = tl.zeros((), dtype=tl.float32)
|
|
31
|
+
for off in tl.range(0, hidden, BLOCK_SIZE):
|
|
32
|
+
idx = off + block
|
|
33
|
+
mask = idx < hidden
|
|
34
|
+
x = tl.load(X + base + idx, mask=mask, other=0.0).to(tl.float32)
|
|
35
|
+
s += tl.sum(x)
|
|
36
|
+
ss += tl.sum(x * x)
|
|
37
|
+
inv_n = 1.0 / hidden
|
|
38
|
+
mean = s * inv_n
|
|
39
|
+
var = ss * inv_n - mean * mean
|
|
40
|
+
tl.store(Mean + pid, mean)
|
|
41
|
+
tl.store(Rstd + pid, 1.0 / tl.sqrt(var + eps))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@triton.jit
|
|
45
|
+
def group_norm_apply_kernel(X, Y, W, B, Mean, Rstd, group_size, C, HW, num_groups,
|
|
46
|
+
scale_by_weight: tl.constexpr, add_bias: tl.constexpr, BLOCK_SIZE: tl.constexpr):
|
|
47
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
48
|
+
batch_idx = pid // num_groups
|
|
49
|
+
group_idx = pid % num_groups
|
|
50
|
+
chan_start = group_idx * group_size
|
|
51
|
+
base = batch_idx * C * HW + chan_start * HW
|
|
52
|
+
hidden = group_size * HW
|
|
53
|
+
mean = tl.load(Mean + pid)
|
|
54
|
+
rstd = tl.load(Rstd + pid)
|
|
55
|
+
block = tl.arange(0, BLOCK_SIZE)
|
|
56
|
+
for off in tl.range(0, hidden, BLOCK_SIZE):
|
|
57
|
+
idx = off + block
|
|
58
|
+
mask = idx < hidden
|
|
59
|
+
x = tl.load(X + base + idx, mask=mask, other=0.0).to(tl.float32)
|
|
60
|
+
y = (x - mean) * rstd
|
|
61
|
+
if scale_by_weight or add_bias:
|
|
62
|
+
chan = chan_start + idx // HW
|
|
63
|
+
if scale_by_weight:
|
|
64
|
+
y = y * tl.load(W + chan, mask=mask, other=1.0).to(tl.float32)
|
|
65
|
+
if add_bias:
|
|
66
|
+
y = y + tl.load(B + chan, mask=mask, other=0.0).to(tl.float32)
|
|
67
|
+
tl.store(Y + base + idx, y.to(Y.dtype.element_ty), mask=mask)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Group normalization — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Two-pass loop over hidden_size (= group_size * HW) in BLOCK_SIZE chunks,
|
|
4
|
+
so the kernel scales to arbitrary spatial dimensions. Replaces the
|
|
5
|
+
original FlagGems extraction whose single-tile design crashed with
|
|
6
|
+
`numel exceeds triton maximum tensor numel (1048576)` once HW reached
|
|
7
|
+
1024×1024 (PixArt VAE 1024px output).
|
|
8
|
+
|
|
9
|
+
Algorithm: standard sum-of-squares variance per (batch, group) program.
|
|
10
|
+
For each program (one (batch, group) pair):
|
|
11
|
+
1. Loop over hidden_size in BLOCK_SIZE chunks → accumulate sum and
|
|
12
|
+
sum-of-squares → mean = s / N, var = ss/N - mean².
|
|
13
|
+
2. rstd = 1 / sqrt(var + eps).
|
|
14
|
+
3. Loop a second time → normalize → multiply by per-channel weight,
|
|
15
|
+
add per-channel bias, store.
|
|
16
|
+
|
|
17
|
+
Forward only (inference). The wrapper guarantees BLOCK_SIZE ≤
|
|
18
|
+
triton.next_power_of_2(hidden_size_per_chunk_target) and ≤ 16384, well
|
|
19
|
+
under Triton's 2^20 numel ceiling on every architecture.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import triton
|
|
23
|
+
import triton.language as tl
|
|
24
|
+
from triton import next_power_of_2
|
|
25
|
+
|
|
26
|
+
from nbxtensor.ops._configs import nbx_autotune
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _group_norm_configs():
|
|
30
|
+
"""The candidates the certifier proves (chantier 1): the chunk a group is walked by x warps. The one the kernel ran
|
|
31
|
+
before certification (the group's padded extent capped at 16 384, 4 warps) is among them."""
|
|
32
|
+
return [triton.Config({'BLOCK_SIZE': block}, num_warps=warps)
|
|
33
|
+
for block in (1024, 2048, 4096, 8192, 16384) for warps in (2, 4, 8, 16)]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _prune_group_norm(configs, named_args, **kwargs):
|
|
37
|
+
"""A chunk no wider than the backend's reduce-tile cap (`nbx_tensor.reduce_tile_max`) nor than the group's padded
|
|
38
|
+
extent (a wider chunk only masks)."""
|
|
39
|
+
from nbxtensor.backend.capabilities import reduce_tile_max
|
|
40
|
+
hidden = int(named_args['group_size']) * int(named_args['HW'])
|
|
41
|
+
cap = min(reduce_tile_max(), next_power_of_2(max(1, hidden)))
|
|
42
|
+
keep = [c for c in configs if c.kwargs['BLOCK_SIZE'] <= cap]
|
|
43
|
+
return keep or sorted(configs, key=lambda c: c.kwargs['BLOCK_SIZE'])[:1]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@nbx_autotune(configs=_group_norm_configs(), key=['PROG_BUCKET', 'HIDDEN_BUCKET', 'scale_by_weight', 'add_bias'],
|
|
47
|
+
prune_configs_by={'early_config_prune': _prune_group_norm}, cache_results=True)
|
|
48
|
+
@triton.jit
|
|
49
|
+
def group_norm_forward_kernel(
|
|
50
|
+
X, # input pointer, shape (N, C, HW)
|
|
51
|
+
Y, # output pointer, shape (N, C, HW)
|
|
52
|
+
W, # weight pointer, shape (C,)
|
|
53
|
+
B, # bias pointer, shape (C,)
|
|
54
|
+
Mean, # mean output, shape (N * num_groups,)
|
|
55
|
+
Rstd, # rstd output, shape (N * num_groups,)
|
|
56
|
+
group_size, # channels per group
|
|
57
|
+
C, # total channels
|
|
58
|
+
HW, # spatial size = H * W (or H * W * D)
|
|
59
|
+
num_groups, # total groups
|
|
60
|
+
eps,
|
|
61
|
+
PROG_BUCKET, # the programs' (batch x groups) bucket: the key
|
|
62
|
+
HIDDEN_BUCKET, # the group's padded extent: the key
|
|
63
|
+
scale_by_weight: tl.constexpr,
|
|
64
|
+
add_bias: tl.constexpr,
|
|
65
|
+
BLOCK_SIZE: tl.constexpr, # chunk size along hidden_size: the certified configuration
|
|
66
|
+
):
|
|
67
|
+
"""One program handles one (batch, group) pair. Spatial dimension is
|
|
68
|
+
walked in BLOCK_SIZE chunks so the per-program tile stays bounded
|
|
69
|
+
regardless of input HW."""
|
|
70
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
71
|
+
batch_idx = pid // num_groups
|
|
72
|
+
group_idx = pid % num_groups
|
|
73
|
+
|
|
74
|
+
# Channel range of this group (each channel has HW elements contiguous
|
|
75
|
+
# in memory; the group's elements are group_size*HW elements starting
|
|
76
|
+
# at base_offset).
|
|
77
|
+
chan_start = group_idx * group_size
|
|
78
|
+
base_offset = batch_idx * C * HW + chan_start * HW
|
|
79
|
+
hidden = group_size * HW
|
|
80
|
+
|
|
81
|
+
block_range = tl.arange(0, BLOCK_SIZE)
|
|
82
|
+
|
|
83
|
+
# ── Pass 1: sum and sum-of-squares over the full hidden dim ─────────
|
|
84
|
+
s = tl.zeros((), dtype=tl.float32)
|
|
85
|
+
ss = tl.zeros((), dtype=tl.float32)
|
|
86
|
+
for off in tl.range(0, hidden, BLOCK_SIZE):
|
|
87
|
+
idx = off + block_range
|
|
88
|
+
mask = idx < hidden
|
|
89
|
+
x = tl.load(X + base_offset + idx, mask=mask, other=0.0).to(tl.float32)
|
|
90
|
+
s += tl.sum(x)
|
|
91
|
+
ss += tl.sum(x * x)
|
|
92
|
+
|
|
93
|
+
inv_n = 1.0 / hidden
|
|
94
|
+
mean = s * inv_n
|
|
95
|
+
var = ss * inv_n - mean * mean
|
|
96
|
+
rstd = 1.0 / tl.sqrt(var + eps)
|
|
97
|
+
|
|
98
|
+
tl.store(Mean + pid, mean)
|
|
99
|
+
tl.store(Rstd + pid, rstd)
|
|
100
|
+
|
|
101
|
+
# ── Pass 2: normalize → optional affine → store ──────────────────────
|
|
102
|
+
# Each chunk falls inside a single channel because chunks walk the
|
|
103
|
+
# contiguous (channel, HW) layout. We compute the channel for each
|
|
104
|
+
# element of the block to fetch the right (W, B) entries.
|
|
105
|
+
for off in tl.range(0, hidden, BLOCK_SIZE):
|
|
106
|
+
idx = off + block_range
|
|
107
|
+
mask = idx < hidden
|
|
108
|
+
x = tl.load(X + base_offset + idx, mask=mask, other=0.0).to(tl.float32)
|
|
109
|
+
x_hat = (x - mean) * rstd
|
|
110
|
+
|
|
111
|
+
if scale_by_weight or add_bias:
|
|
112
|
+
# Channel index within the GROUP, then add the group's chan_start.
|
|
113
|
+
chan_in_group = idx // HW
|
|
114
|
+
chan_global = chan_start + chan_in_group
|
|
115
|
+
chan_mask = mask & (chan_in_group < group_size)
|
|
116
|
+
if scale_by_weight:
|
|
117
|
+
w = tl.load(W + chan_global, mask=chan_mask, other=1.0).to(tl.float32)
|
|
118
|
+
x_hat = x_hat * w
|
|
119
|
+
if add_bias:
|
|
120
|
+
b = tl.load(B + chan_global, mask=chan_mask, other=0.0).to(tl.float32)
|
|
121
|
+
x_hat = x_hat + b
|
|
122
|
+
|
|
123
|
+
tl.store(Y + base_offset + idx, x_hat, mask=mask)
|
nbxtensor/ops/gt.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Greater than — pure @triton.jit kernel."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
|
|
6
|
+
@triton.jit
|
|
7
|
+
def gt_forward_kernel(
|
|
8
|
+
x_ptr, y_ptr, output_ptr,
|
|
9
|
+
n_elements,
|
|
10
|
+
BLOCK_SIZE: tl.constexpr,
|
|
11
|
+
):
|
|
12
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
13
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
14
|
+
mask = offset < n_elements
|
|
15
|
+
|
|
16
|
+
x = tl.load(x_ptr + offset, mask=mask)
|
|
17
|
+
y = tl.load(y_ptr + offset, mask=mask)
|
|
18
|
+
tl.store(output_ptr + offset, (x > y).to(tl.int1), mask=mask)
|
|
19
|
+
|
|
20
|
+
@triton.jit
|
|
21
|
+
def gt_scalar_kernel(
|
|
22
|
+
x_ptr, output_ptr,
|
|
23
|
+
n_elements,
|
|
24
|
+
scalar,
|
|
25
|
+
BLOCK_SIZE: tl.constexpr,
|
|
26
|
+
):
|
|
27
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
28
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
29
|
+
mask = offset < n_elements
|
|
30
|
+
|
|
31
|
+
x = tl.load(x_ptr + offset, mask=mask)
|
|
32
|
+
tl.store(output_ptr + offset, (x > scalar).to(tl.int1), mask=mask)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Hard Sigmoid — pure @triton.jit kernel."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
|
|
6
|
+
@triton.jit
|
|
7
|
+
def hardsigmoid_forward_kernel(
|
|
8
|
+
input_ptr, output_ptr,
|
|
9
|
+
n_elements,
|
|
10
|
+
BLOCK_SIZE: tl.constexpr,
|
|
11
|
+
):
|
|
12
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
13
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
14
|
+
mask = offset < n_elements
|
|
15
|
+
|
|
16
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
17
|
+
out = tl.maximum(0, tl.minimum(1, x / 6.0 + 0.5))
|
|
18
|
+
tl.store(output_ptr + offset, out, mask=mask)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Hard Swish — pure @triton.jit kernel."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
|
|
6
|
+
@triton.jit
|
|
7
|
+
def hardswish_forward_kernel(
|
|
8
|
+
input_ptr, output_ptr,
|
|
9
|
+
n_elements,
|
|
10
|
+
BLOCK_SIZE: tl.constexpr,
|
|
11
|
+
):
|
|
12
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
13
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
14
|
+
mask = offset < n_elements
|
|
15
|
+
|
|
16
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
17
|
+
# hardswish = x * relu6(x + 3) / 6
|
|
18
|
+
out = x * tl.minimum(tl.maximum(0, x + 3.0), 6.0) / 6.0
|
|
19
|
+
tl.store(output_ptr + offset, out, mask=mask)
|
nbxtensor/ops/im2col.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""aten::im2col (nn.Unfold) — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Extracts sliding local blocks from a batched NCHW tensor into columns:
|
|
4
|
+
input [N, C, IH, IW]
|
|
5
|
+
output [N, C*KH*KW, L] with L = OH*OW
|
|
6
|
+
|
|
7
|
+
out[n, c*KH*KW + kh*KW + kw, oh*OW + ow]
|
|
8
|
+
= input[n, c, oh*stride_h - pad_h + kh*dil_h,
|
|
9
|
+
ow*stride_w - pad_w + kw*dil_w] (0 outside the image)
|
|
10
|
+
|
|
11
|
+
This is the operation behind HAT's OCAB overlapping-window cross-attention
|
|
12
|
+
(P-TRITON-IM2COL-KERNEL). Pure Triton (R33): NBXTensor in/out, tl.load with
|
|
13
|
+
boundary masking for the padding halo (no F.pad).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import triton
|
|
17
|
+
import triton.language as tl
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@triton.jit
|
|
21
|
+
def im2col_kernel(
|
|
22
|
+
inp_ptr, out_ptr,
|
|
23
|
+
C, IH, IW,
|
|
24
|
+
OW, L,
|
|
25
|
+
KH: tl.constexpr, KW: tl.constexpr,
|
|
26
|
+
stride_h: tl.constexpr, stride_w: tl.constexpr,
|
|
27
|
+
pad_h: tl.constexpr, pad_w: tl.constexpr,
|
|
28
|
+
dil_h: tl.constexpr, dil_w: tl.constexpr,
|
|
29
|
+
in_sn, in_sc, in_sh, in_sw,
|
|
30
|
+
out_sn, out_sc, out_sl,
|
|
31
|
+
BLOCK_L: tl.constexpr,
|
|
32
|
+
):
|
|
33
|
+
# One program per (n, output-channel) = (n, c*KH*KW + kh*KW + kw); it
|
|
34
|
+
# writes one full output row of L block-positions (looped in BLOCK_L tiles).
|
|
35
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
36
|
+
CKK = C * KH * KW
|
|
37
|
+
n = pid // CKK
|
|
38
|
+
oc = pid % CKK
|
|
39
|
+
c = oc // (KH * KW)
|
|
40
|
+
rem = oc % (KH * KW)
|
|
41
|
+
kh = rem // KW
|
|
42
|
+
kw = rem % KW
|
|
43
|
+
|
|
44
|
+
in_base = n * in_sn + c * in_sc
|
|
45
|
+
out_base = n * out_sn + oc * out_sc
|
|
46
|
+
|
|
47
|
+
for l0 in range(0, L, BLOCK_L):
|
|
48
|
+
l = l0 + tl.arange(0, BLOCK_L)
|
|
49
|
+
mask_l = l < L
|
|
50
|
+
oh = l // OW
|
|
51
|
+
ow = l % OW
|
|
52
|
+
ih = oh * stride_h - pad_h + kh * dil_h
|
|
53
|
+
iw = ow * stride_w - pad_w + kw * dil_w
|
|
54
|
+
in_bounds = mask_l & (ih >= 0) & (ih < IH) & (iw >= 0) & (iw < IW)
|
|
55
|
+
vals = tl.load(inp_ptr + in_base + ih * in_sh + iw * in_sw,
|
|
56
|
+
mask=in_bounds, other=0.0)
|
|
57
|
+
tl.store(out_ptr + out_base + l * out_sl, vals, mask=mask_l)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Index add along dim — deterministic, pure @triton.jit.
|
|
2
|
+
|
|
3
|
+
out[..., index[i], ...] += alpha * src[..., i, ...]
|
|
4
|
+
|
|
5
|
+
The previous implementation used `tl.atomic_add`, whose inter-block
|
|
6
|
+
float accumulation order is non-deterministic. When several source
|
|
7
|
+
positions map to the same destination (MoE expert aggregation: many
|
|
8
|
+
routed rows -> one token), repeated runs produced different fp32
|
|
9
|
+
roundings -> a different greedy argmax on Qwen3-30B::triton's unfused
|
|
10
|
+
MoE path (workstream P-TRITON-MOE-DETERMINISM sub-workstream 2).
|
|
11
|
+
|
|
12
|
+
State of the art consulted (R16, 2026-05-19):
|
|
13
|
+
- Triton #7402: tl.atomic_add return value is incorrect across
|
|
14
|
+
threads (only thread 0 correct) — open upstream bug.
|
|
15
|
+
- Triton #4717: tl.atomic_add is slow (layout conversions).
|
|
16
|
+
- flagos-ai/FlagGems ops/index_add.py still uses
|
|
17
|
+
tl.atomic_add(sem='relaxed') (pins triton==3.3.0, no bit-exact
|
|
18
|
+
test) — no deterministic upstream to vendor.
|
|
19
|
+
- PyTorch index_add_/scatter_add_ are non-deterministic on CUDA via
|
|
20
|
+
atomicAdd; the deterministic path is sort + sequential segmented
|
|
21
|
+
reduce.
|
|
22
|
+
|
|
23
|
+
Strategy — output-owner gather (deterministic by construction):
|
|
24
|
+
invert the scatter. Each output cell is owned by exactly ONE program
|
|
25
|
+
that sums its contributing source rows in a fixed ascending source-
|
|
26
|
+
position order as a SEQUENTIAL fp32 fold. No atomics, no inter-block
|
|
27
|
+
races, no sort dependency. The ascending-j sequential fold is exactly
|
|
28
|
+
the order torch's deterministic CUDA index_add_ uses (verified
|
|
29
|
+
bit-exact fp32 for bucket sizes up to 4096). A pairwise/tree reduce
|
|
30
|
+
(`tl.sum`) would round differently and break bit-exactness, so the
|
|
31
|
+
j accumulation is a scalar loop; the feature (inner) dim is vectorised.
|
|
32
|
+
|
|
33
|
+
Out-of-range source indices never equal a valid destination d in
|
|
34
|
+
[0, inp_shape_dim) so they are naturally dropped (mirrors the old
|
|
35
|
+
masked-store semantics — no assert/crash). Model-agnostic, no autotune.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
import triton
|
|
39
|
+
import triton.language as tl
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@triton.jit
|
|
43
|
+
def index_add_gather_kernel(
|
|
44
|
+
x, src, index, out,
|
|
45
|
+
dim_size, inp_shape_dim, inner_size, alpha,
|
|
46
|
+
src_stride_outer, src_stride_dim, src_stride_inner,
|
|
47
|
+
INNER_BLOCK: tl.constexpr,
|
|
48
|
+
):
|
|
49
|
+
"""One program == one (outer, dest, inner-tile) output cell.
|
|
50
|
+
|
|
51
|
+
out[o, d, i] = x[o, d, i]
|
|
52
|
+
+ alpha * sum_{j : index[j] == d} src[o, j, i]
|
|
53
|
+
with the sum taken in ascending-j order as a sequential fp32 fold
|
|
54
|
+
(bit-exact vs torch deterministic index_add_).
|
|
55
|
+
"""
|
|
56
|
+
n_inner_tiles = (inner_size + INNER_BLOCK - 1) // INNER_BLOCK
|
|
57
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
58
|
+
tile = pid % n_inner_tiles
|
|
59
|
+
tmp = pid // n_inner_tiles
|
|
60
|
+
d = tmp % inp_shape_dim
|
|
61
|
+
o = tmp // inp_shape_dim
|
|
62
|
+
|
|
63
|
+
inner = tile * INNER_BLOCK + tl.arange(0, INNER_BLOCK)
|
|
64
|
+
inner_mask = inner < inner_size
|
|
65
|
+
|
|
66
|
+
out_base = o * (inp_shape_dim * inner_size) + d * inner_size + inner
|
|
67
|
+
base = tl.load(x + out_base, mask=inner_mask, other=0.0).to(tl.float32)
|
|
68
|
+
|
|
69
|
+
# torch's deterministic CUDA index_add_ computes the per-destination
|
|
70
|
+
# segment sum SEPARATELY (0-initialised, ascending-j sequential
|
|
71
|
+
# fold) and adds it to `self` ONCE at the end — NOT folding into
|
|
72
|
+
# self. Verified by discriminating probe (base=1, src=[1e8,-1e8]:
|
|
73
|
+
# torch -> 1.0 == base + Σ, not 0.0 == fold-into-base).
|
|
74
|
+
seg = tl.zeros([INNER_BLOCK], dtype=tl.float32)
|
|
75
|
+
src_o = o * src_stride_outer + inner * src_stride_inner
|
|
76
|
+
# Scan source positions in ascending j (deterministic order).
|
|
77
|
+
# Scalar loop on purpose: a vectorised tree reduce would round
|
|
78
|
+
# differently and break bit-exactness vs torch.
|
|
79
|
+
for j in range(0, dim_size):
|
|
80
|
+
dest = tl.load(index + j).to(tl.int64)
|
|
81
|
+
if dest == d:
|
|
82
|
+
sv = tl.load(src + src_o + j * src_stride_dim,
|
|
83
|
+
mask=inner_mask, other=0.0).to(tl.float32)
|
|
84
|
+
seg += (alpha * sv).to(tl.float32)
|
|
85
|
+
# NOTE alpha != 1: a residual 1-ULP fp32 gap vs torch
|
|
86
|
+
# remains (Triton codegen of the scaled term — bitcast /
|
|
87
|
+
# fma / reassoc barriers were tried, all no-effect). It is
|
|
88
|
+
# NOT an ordering bug: numpy fp32 sequential fold == torch
|
|
89
|
+
# bit-exact, and this kernel reproduces that for alpha == 1
|
|
90
|
+
# bit-exact. alpha != 1 never occurs on a transformer path
|
|
91
|
+
# (Qwen3-30B-A3B graph: 6144/6144 index_add use alpha=1).
|
|
92
|
+
|
|
93
|
+
# Triton implicitly casts the fp32 result to out's element type on
|
|
94
|
+
# store (same convention as the other elementwise kernels).
|
|
95
|
+
tl.store(out + out_base, base + seg, mask=inner_mask)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""index_put — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Scatter-write `values` into `out` at positions given by a single 1-D
|
|
4
|
+
advanced index tensor on the leading dim (the form decomposed-ATen
|
|
5
|
+
emits for MoE-v2 expert-output aggregation, KV-cache indexed writes,
|
|
6
|
+
and post-`aten::nonzero` masked scatter):
|
|
7
|
+
|
|
8
|
+
out[idx[s], t] = values[s, t] (ACCUMULATE == 0)
|
|
9
|
+
out[idx[s], t] += values[s, t] (ACCUMULATE == 1)
|
|
10
|
+
|
|
11
|
+
`idx` may have any shape S (flattened, numel = Sn); `t` ranges over
|
|
12
|
+
the flattened tail `x.shape[1:]` (numel = T). `out` is contiguous so
|
|
13
|
+
the leading-dim stride is exactly T. Duplicate indices follow torch
|
|
14
|
+
semantics: summed when accumulating, last-writer-wins (unspecified
|
|
15
|
+
order) otherwise.
|
|
16
|
+
|
|
17
|
+
Index decomposition logic mirrors `index_add_op.py` (FlagGems
|
|
18
|
+
lineage, Apache-2.0).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import triton
|
|
22
|
+
import triton.language as tl
|
|
23
|
+
|
|
24
|
+
#: The contract this kernel enforces. Triton forbids reading a module global
|
|
25
|
+
#: from inside a @jit body, so the kernel repeats the literal; the permanent
|
|
26
|
+
#: test pins the two equal so they cannot drift.
|
|
27
|
+
INDEX_PUT_OOB = "index_put: index out of range"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@triton.jit(debug=True)
|
|
31
|
+
def index_put_kernel(
|
|
32
|
+
out_ptr, idx_ptr, val_ptr,
|
|
33
|
+
T, N, R, fault_ptr,
|
|
34
|
+
FAULT_CODE: tl.constexpr,
|
|
35
|
+
VAL_SCALAR: tl.constexpr,
|
|
36
|
+
ACCUMULATE: tl.constexpr,
|
|
37
|
+
BLOCK_SIZE: tl.constexpr,
|
|
38
|
+
):
|
|
39
|
+
"""out[idx[off // T], off % T] {=|+=} values[off] for off < N.
|
|
40
|
+
|
|
41
|
+
`R` = rows of `out` (its leading dim). A negative index counts from
|
|
42
|
+
the end (torch semantics); an index still out of [0, R) after that
|
|
43
|
+
TRAPS via `tl.device_assert` (kept in the binary by `debug=True`)
|
|
44
|
+
where torch's index_put raises — the FlagGems-lineage kernel wrote
|
|
45
|
+
through it to memory outside the tensor, silently
|
|
46
|
+
(D-GATHER-SCATTER-OOB-SILENT, promoted to correctness 2026-09-02).
|
|
47
|
+
|
|
48
|
+
That assert is honoured on CUDA and ROCm and NOT on Metal, which
|
|
49
|
+
computes its predicate and discards it (measured 2026-09-10). Here
|
|
50
|
+
that was not a wrong value, it was the WRITE ITSELF: the store's mask
|
|
51
|
+
was `off < N` alone, so `dst = row * T + t` with an out-of-range row
|
|
52
|
+
wrote outside the tensor — 8 floats past the end of a 24-float
|
|
53
|
+
tensor in the eight-float reproducer. Two things close it. The
|
|
54
|
+
address is made legal before it is used, so nothing outside the
|
|
55
|
+
tensor is touched in the interval before the refusal arrives (on
|
|
56
|
+
CUDA the assert halts the thread first, so it changes nothing
|
|
57
|
+
there); and `FAULT_CODE`, a compile-time constant, carries the
|
|
58
|
+
refusal itself through the fault word, which `check_device_faults`
|
|
59
|
+
raises on at the next host observation. The wrapper passes 0 where
|
|
60
|
+
the assert is honoured, and then not one instruction of it is
|
|
61
|
+
emitted.
|
|
62
|
+
"""
|
|
63
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
64
|
+
off = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
65
|
+
mask = off < N
|
|
66
|
+
|
|
67
|
+
s = off // T # which index entry (row in idx)
|
|
68
|
+
t = off % T # offset within the flattened tail
|
|
69
|
+
row = tl.load(idx_ptr + s, mask=mask, other=0).to(tl.int64)
|
|
70
|
+
row = tl.where(row < 0, row + R, row)
|
|
71
|
+
# Masked lanes loaded `other=0` and are in range by construction.
|
|
72
|
+
row_valid = (row >= 0) & (row < R)
|
|
73
|
+
tl.device_assert(row_valid, "index_put: index out of range")
|
|
74
|
+
if FAULT_CODE != 0:
|
|
75
|
+
# Lanes past N loaded `other=0`; they must not report (and `R == 0`
|
|
76
|
+
# would make even row 0 invalid, so `mask` has to gate this).
|
|
77
|
+
if tl.max(tl.where(mask & (row_valid == 0), 1, 0)) != 0:
|
|
78
|
+
tl.store(fault_ptr, FAULT_CODE)
|
|
79
|
+
# A legal address, so no memory outside the tensor is touched before the
|
|
80
|
+
# refusal is observed. Not a rescue: the run refuses either way.
|
|
81
|
+
dst = tl.where(row_valid, row, 0) * T + t
|
|
82
|
+
|
|
83
|
+
if VAL_SCALAR:
|
|
84
|
+
# All lanes read element 0 (off * 0 keeps it a vector for masking).
|
|
85
|
+
v = tl.load(val_ptr + off * 0, mask=mask, other=0.0)
|
|
86
|
+
else:
|
|
87
|
+
v = tl.load(val_ptr + off, mask=mask, other=0.0)
|
|
88
|
+
|
|
89
|
+
if ACCUMULATE:
|
|
90
|
+
tl.atomic_add(out_ptr + dst, v, mask=mask & row_valid)
|
|
91
|
+
else:
|
|
92
|
+
tl.store(out_ptr + dst, v, mask=mask & row_valid)
|