nbxtensor 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nbxtensor/THIRD_PARTY_NOTICES.md +287 -0
- nbxtensor/__init__.py +17 -0
- nbxtensor/backend/__init__.py +1 -0
- nbxtensor/backend/capabilities.py +207 -0
- nbxtensor/backend/cpu/__init__.py +1 -0
- nbxtensor/backend/cpu/blas.py +102 -0
- nbxtensor/backend/cpu/pool.py +204 -0
- nbxtensor/backend/cpu_backend.py +162 -0
- nbxtensor/backend/device_facts.py +105 -0
- nbxtensor/backend/device_files.py +129 -0
- nbxtensor/backend/device_names.py +18 -0
- nbxtensor/backend/devices/amd/cdna.yml +228 -0
- nbxtensor/backend/devices/amd/cdna2.yml +228 -0
- nbxtensor/backend/devices/amd/cdna3.yml +227 -0
- nbxtensor/backend/devices/apple/apple_m1.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4_pro.yml +308 -0
- nbxtensor/backend/devices/apple/apple_m5.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_max.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_pro.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_ultra.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m6.yml +269 -0
- nbxtensor/backend/devices/apple/apple_silicon.yml +263 -0
- nbxtensor/backend/devices/nvidia/ampere.yml +214 -0
- nbxtensor/backend/devices/nvidia/hopper.yml +213 -0
- nbxtensor/backend/devices/nvidia/volta.yml +411 -0
- nbxtensor/backend/faults.py +151 -0
- nbxtensor/backend/host_memory.py +185 -0
- nbxtensor/backend/launcher.py +2350 -0
- nbxtensor/backend/launcher_contract.py +429 -0
- nbxtensor/backend/metal_backend.py +661 -0
- nbxtensor/backend/metal_device.py +1274 -0
- nbxtensor/backend/process_memory.py +73 -0
- nbxtensor/backend/replay_graph.py +322 -0
- nbxtensor/backend/runtime.py +409 -0
- nbxtensor/backend/triton_ext_driver.py +735 -0
- nbxtensor/dtype/__init__.py +1 -0
- nbxtensor/dtype/convert.py +107 -0
- nbxtensor/dtype/dtypes.yml +70 -0
- nbxtensor/dtype/itemsize.py +166 -0
- nbxtensor/dtype/nbx_dtype.py +72 -0
- nbxtensor/dtype/operands.py +158 -0
- nbxtensor/dtype/outputs.py +64 -0
- nbxtensor/dtype/policy.py +155 -0
- nbxtensor/dtype/policy.yml +51 -0
- nbxtensor/dtype/promotion.py +112 -0
- nbxtensor/host.py +55 -0
- nbxtensor/io/__init__.py +1 -0
- nbxtensor/io/read_rate.py +197 -0
- nbxtensor/io/safetensors.py +314 -0
- nbxtensor/memory/__init__.py +9 -0
- nbxtensor/memory/allocator.py +1988 -0
- nbxtensor/memory/arena.py +75 -0
- nbxtensor/memory/device_memory.py +150 -0
- nbxtensor/memory/device_transfer.py +154 -0
- nbxtensor/memory/layout.py +43 -0
- nbxtensor/memory/memory_pool.py +191 -0
- nbxtensor/memory/metal.py +96 -0
- nbxtensor/memory/oom_advice.py +91 -0
- nbxtensor/memory/pointer_tables.py +323 -0
- nbxtensor/memory/pool.py +507 -0
- nbxtensor/memory/shadow.py +43 -0
- nbxtensor/memory/slab.py +273 -0
- nbxtensor/memory/trace_replay.py +122 -0
- nbxtensor/memory/traces/sana-video-2b-720p.16g.2026-11-02.tsv +13610 -0
- nbxtensor/memory/vmm.py +118 -0
- nbxtensor/ops/__init__.py +1 -0
- nbxtensor/ops/_autotune_policy.py +90 -0
- nbxtensor/ops/_common.py +32 -0
- nbxtensor/ops/_configs.py +1041 -0
- nbxtensor/ops/_cost.py +231 -0
- nbxtensor/ops/abs.py +17 -0
- nbxtensor/ops/adaptive_avg_pool2d.py +161 -0
- nbxtensor/ops/add.py +96 -0
- nbxtensor/ops/addcdiv.py +29 -0
- nbxtensor/ops/addcmul.py +29 -0
- nbxtensor/ops/addmv_op.py +64 -0
- nbxtensor/ops/addr.py +68 -0
- nbxtensor/ops/all_reduce.py +83 -0
- nbxtensor/ops/amax.py +40 -0
- nbxtensor/ops/any_reduce.py +83 -0
- nbxtensor/ops/arange_op.py +27 -0
- nbxtensor/ops/argmax.py +107 -0
- nbxtensor/ops/argmin.py +93 -0
- nbxtensor/ops/atan2.py +71 -0
- nbxtensor/ops/attention_wrappers.py +1648 -0
- nbxtensor/ops/autotune_bucket.py +137 -0
- nbxtensor/ops/autotune_cache.py +430 -0
- nbxtensor/ops/autotune_certified.py +1262 -0
- nbxtensor/ops/autotune_refusals.py +380 -0
- nbxtensor/ops/autotune_settings.py +58 -0
- nbxtensor/ops/avg_pool2d.py +73 -0
- nbxtensor/ops/baddbmm_op.py +132 -0
- nbxtensor/ops/batch_norm.py +163 -0
- nbxtensor/ops/binary_strided.py +196 -0
- nbxtensor/ops/bitwise_and.py +26 -0
- nbxtensor/ops/bitwise_left_shift.py +47 -0
- nbxtensor/ops/bitwise_not.py +24 -0
- nbxtensor/ops/bitwise_or.py +26 -0
- nbxtensor/ops/bitwise_right_shift.py +47 -0
- nbxtensor/ops/bucketize.py +37 -0
- nbxtensor/ops/cat_op.py +87 -0
- nbxtensor/ops/celu.py +20 -0
- nbxtensor/ops/clamp.py +24 -0
- nbxtensor/ops/clamp_min.py +22 -0
- nbxtensor/ops/conv2d.py +265 -0
- nbxtensor/ops/conv2d_m8n8k4.py +165 -0
- nbxtensor/ops/conv_depthwise2d.py +154 -0
- nbxtensor/ops/conv_transpose2d.py +148 -0
- nbxtensor/ops/conv_wrappers.py +1482 -0
- nbxtensor/ops/copy.py +18 -0
- nbxtensor/ops/copy_op.py +39 -0
- nbxtensor/ops/cos.py +17 -0
- nbxtensor/ops/count_nonzero.py +64 -0
- nbxtensor/ops/cross_entropy.py +67 -0
- nbxtensor/ops/cummax.py +109 -0
- nbxtensor/ops/cummin.py +105 -0
- nbxtensor/ops/cumsum.py +244 -0
- nbxtensor/ops/decode_attn_vec.py +162 -0
- nbxtensor/ops/depthwise_conv2d.py +132 -0
- nbxtensor/ops/dequant_gemv.py +190 -0
- nbxtensor/ops/dft.py +67 -0
- nbxtensor/ops/dispatch.py +1228 -0
- nbxtensor/ops/div.py +75 -0
- nbxtensor/ops/dot_op.py +67 -0
- nbxtensor/ops/dropout.py +23 -0
- nbxtensor/ops/dtype_convert.py +42 -0
- nbxtensor/ops/elementwise_launch.py +81 -0
- nbxtensor/ops/elementwise_wrappers.py +2315 -0
- nbxtensor/ops/elu.py +20 -0
- nbxtensor/ops/embedding.py +69 -0
- nbxtensor/ops/eq.py +32 -0
- nbxtensor/ops/erf.py +18 -0
- nbxtensor/ops/exp.py +17 -0
- nbxtensor/ops/exp2.py +18 -0
- nbxtensor/ops/fft_op.py +125 -0
- nbxtensor/ops/fill_op.py +20 -0
- nbxtensor/ops/flash_attention.py +307 -0
- nbxtensor/ops/flash_attention_m8n8k4.py +384 -0
- nbxtensor/ops/flash_decode.py +78 -0
- nbxtensor/ops/flip_op.py +60 -0
- nbxtensor/ops/floor.py +90 -0
- nbxtensor/ops/full_op.py +24 -0
- nbxtensor/ops/fused_moe.py +417 -0
- nbxtensor/ops/fused_moe_m8n8k4.py +99 -0
- nbxtensor/ops/fused_upsample_conv.py +413 -0
- nbxtensor/ops/gather_op.py +52 -0
- nbxtensor/ops/ge.py +32 -0
- nbxtensor/ops/gelu.py +31 -0
- nbxtensor/ops/gemv_vec.py +80 -0
- nbxtensor/ops/glu.py +43 -0
- nbxtensor/ops/grid_sampler.py +145 -0
- nbxtensor/ops/group_norm_tiled.py +67 -0
- nbxtensor/ops/groupnorm.py +123 -0
- nbxtensor/ops/gt.py +32 -0
- nbxtensor/ops/hardsigmoid.py +18 -0
- nbxtensor/ops/hardswish.py +19 -0
- nbxtensor/ops/im2col.py +57 -0
- nbxtensor/ops/index_add.py +95 -0
- nbxtensor/ops/index_put_op.py +92 -0
- nbxtensor/ops/index_select.py +121 -0
- nbxtensor/ops/index_wrappers.py +770 -0
- nbxtensor/ops/isfinite.py +20 -0
- nbxtensor/ops/isinf.py +20 -0
- nbxtensor/ops/isnan.py +18 -0
- nbxtensor/ops/launch_keys.py +1054 -0
- nbxtensor/ops/launch_observer.py +54 -0
- nbxtensor/ops/layernorm.py +71 -0
- nbxtensor/ops/le.py +35 -0
- nbxtensor/ops/leaky_relu.py +19 -0
- nbxtensor/ops/lerp.py +76 -0
- nbxtensor/ops/linalg_wrappers.py +1370 -0
- nbxtensor/ops/linspace_op.py +47 -0
- nbxtensor/ops/log.py +17 -0
- nbxtensor/ops/log_sigmoid.py +23 -0
- nbxtensor/ops/logical_and.py +26 -0
- nbxtensor/ops/logical_not.py +24 -0
- nbxtensor/ops/logical_or.py +26 -0
- nbxtensor/ops/logical_xor.py +37 -0
- nbxtensor/ops/loss_wrappers.py +282 -0
- nbxtensor/ops/lt.py +32 -0
- nbxtensor/ops/masked_fill.py +21 -0
- nbxtensor/ops/matmul.py +377 -0
- nbxtensor/ops/matmul_m8n8k4.py +235 -0
- nbxtensor/ops/max_pool2d.py +74 -0
- nbxtensor/ops/max_pool3d.py +93 -0
- nbxtensor/ops/max_reduce.py +91 -0
- nbxtensor/ops/maximum.py +18 -0
- nbxtensor/ops/mean.py +40 -0
- nbxtensor/ops/min_reduce.py +91 -0
- nbxtensor/ops/minimum.py +18 -0
- nbxtensor/ops/mish.py +22 -0
- nbxtensor/ops/mma_m8n8k4.py +152 -0
- nbxtensor/ops/moe_align.py +195 -0
- nbxtensor/ops/moe_decode_vec.py +374 -0
- nbxtensor/ops/moe_device_residency.py +141 -0
- nbxtensor/ops/moe_wrappers.py +345 -0
- nbxtensor/ops/mse_loss.py +79 -0
- nbxtensor/ops/mul.py +51 -0
- nbxtensor/ops/nan_to_num.py +23 -0
- nbxtensor/ops/ne.py +32 -0
- nbxtensor/ops/neg.py +17 -0
- nbxtensor/ops/nllloss.py +124 -0
- nbxtensor/ops/nonzero.py +33 -0
- nbxtensor/ops/nonzero_op.py +62 -0
- nbxtensor/ops/norm_wrappers.py +579 -0
- nbxtensor/ops/ones_op.py +20 -0
- nbxtensor/ops/oracles/__init__.py +6 -0
- nbxtensor/ops/oracles/conv2d_fp64.py +450 -0
- nbxtensor/ops/pad_op.py +159 -0
- nbxtensor/ops/paged_decode_attn.py +215 -0
- nbxtensor/ops/pixel_shuffle.py +144 -0
- nbxtensor/ops/pixel_unshuffle.py +72 -0
- nbxtensor/ops/pow.py +137 -0
- nbxtensor/ops/prod.py +81 -0
- nbxtensor/ops/rand_op.py +52 -0
- nbxtensor/ops/random_wrappers.py +366 -0
- nbxtensor/ops/reciprocal.py +17 -0
- nbxtensor/ops/reduction_wrappers.py +845 -0
- nbxtensor/ops/relu.py +18 -0
- nbxtensor/ops/remainder.py +46 -0
- nbxtensor/ops/repeat_interleave.py +48 -0
- nbxtensor/ops/repeat_op.py +132 -0
- nbxtensor/ops/residual_chain.py +374 -0
- nbxtensor/ops/rmsnorm.py +68 -0
- nbxtensor/ops/rope.py +142 -0
- nbxtensor/ops/rsqrt.py +17 -0
- nbxtensor/ops/scaled_softmax.py +96 -0
- nbxtensor/ops/scatter_op.py +139 -0
- nbxtensor/ops/screen_oracle.py +479 -0
- nbxtensor/ops/selu.py +22 -0
- nbxtensor/ops/shape_wrappers.py +1032 -0
- nbxtensor/ops/sigmoid.py +19 -0
- nbxtensor/ops/silu.py +27 -0
- nbxtensor/ops/sin.py +17 -0
- nbxtensor/ops/softmax.py +169 -0
- nbxtensor/ops/softplus.py +27 -0
- nbxtensor/ops/sort_op.py +304 -0
- nbxtensor/ops/spectral_wrappers.py +481 -0
- nbxtensor/ops/sqrt.py +17 -0
- nbxtensor/ops/stack_op.py +84 -0
- nbxtensor/ops/std.py +113 -0
- nbxtensor/ops/strided_copy.py +200 -0
- nbxtensor/ops/sub.py +35 -0
- nbxtensor/ops/sum.py +40 -0
- nbxtensor/ops/tan.py +19 -0
- nbxtensor/ops/tanh.py +21 -0
- nbxtensor/ops/threshold.py +19 -0
- nbxtensor/ops/tiling.yml +91 -0
- nbxtensor/ops/tiling_policy.py +43 -0
- nbxtensor/ops/tiling_sizes.py +370 -0
- nbxtensor/ops/topk.py +267 -0
- nbxtensor/ops/trace_op.py +64 -0
- nbxtensor/ops/tril.py +64 -0
- nbxtensor/ops/triu.py +62 -0
- nbxtensor/ops/unfold_op.py +37 -0
- nbxtensor/ops/unique_op.py +97 -0
- nbxtensor/ops/upsample_bilinear2d.py +106 -0
- nbxtensor/ops/upsample_nearest2d.py +52 -0
- nbxtensor/ops/upsample_nearest3d.py +61 -0
- nbxtensor/ops/var.py +136 -0
- nbxtensor/ops/vector_norm.py +216 -0
- nbxtensor/ops/weight_norm.py +104 -0
- nbxtensor/ops/where.py +39 -0
- nbxtensor/ops/wrappers.py +825 -0
- nbxtensor/ops/zeros_op.py +20 -0
- nbxtensor/quant/__init__.py +1 -0
- nbxtensor/quant/quantized_tensor.py +187 -0
- nbxtensor/random/__init__.py +1 -0
- nbxtensor/random/mt_randn.py +154 -0
- nbxtensor/random/rng_pin.py +59 -0
- nbxtensor/random/rng_stream.py +118 -0
- nbxtensor/random/seeded_draw.py +223 -0
- nbxtensor/shape/__init__.py +157 -0
- nbxtensor/tensor.py +1867 -0
- nbxtensor-0.0.1.dist-info/METADATA +379 -0
- nbxtensor-0.0.1.dist-info/RECORD +291 -0
- nbxtensor-0.0.1.dist-info/WHEEL +4 -0
- nbxtensor-0.0.1.dist-info/licenses/LICENSE +189 -0
nbxtensor/ops/_cost.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""The cost an op declares: what it allocates, for how long, what it moves and computes (R-NBXT 4) — prototype.
|
|
2
|
+
|
|
3
|
+
A planner (NeuroBrix's Prism) prices a model from these declarations instead of re-deriving what the allocator and the
|
|
4
|
+
kernels do. Everything here is plain data (R-NBXT 11): an operand is a `Spec(shape, strides, dtype)`, a cost is a
|
|
5
|
+
`Cost` of `Buffer`s with integer lifetimes, byte counts and flop counts. The declarations read the same layout rules
|
|
6
|
+
the ops execute (`nbxtensor.shape`) and the same widths (`nbxtensor.dtype`).
|
|
7
|
+
|
|
8
|
+
Prototype scope (docs/nbxtensor/COST.md): the element-wise, reduction and normalisation families, mirroring today's
|
|
9
|
+
wrappers (NeuroBrix `kernels/wrappers.py`) line by line, each declaration citing the allocation it mirrors. Where a
|
|
10
|
+
wrapper takes a decision the library does not hold yet (the binary ops' result dtype, the DtypeEngine's output
|
|
11
|
+
dtype), the caller passes it in; when the wrappers move into the library (M2/M3), `cost` reads the same function the
|
|
12
|
+
kernel launch reads, so the declaration and the execution cannot drift.
|
|
13
|
+
|
|
14
|
+
Lifetimes are steps of the op's own sequence: a buffer lives over [first, last]; today every transient lives to the
|
|
15
|
+
op's return (the wrappers keep it in a local name); the operands are the caller's and are never counted. `peak_bytes` is the most the op holds at once beyond its operands, outputs included.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Optional, Sequence, Tuple
|
|
21
|
+
|
|
22
|
+
from nbxtensor.dtype.itemsize import itemsize
|
|
23
|
+
from nbxtensor.shape import contiguous_strides, is_contiguous, numel
|
|
24
|
+
|
|
25
|
+
__all__ = ["Spec", "Buffer", "Cost", "elementwise_unary", "elementwise_binary", "softmax", "rms_norm",
|
|
26
|
+
"native_layer_norm", "reduction"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class Spec:
|
|
31
|
+
"""An operand: its shape, its strides in elements, its dtype name."""
|
|
32
|
+
shape: Tuple[int, ...]
|
|
33
|
+
strides: Tuple[int, ...]
|
|
34
|
+
dtype: str
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def contiguous(shape: Sequence[int], dtype: str) -> "Spec":
|
|
38
|
+
return Spec(tuple(shape), contiguous_strides(tuple(shape)), dtype)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def nbytes(self) -> int:
|
|
42
|
+
return numel(self.shape) * itemsize(self.dtype)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class Buffer:
|
|
47
|
+
"""A buffer the op allocates: its bytes, what it is, and the steps it lives over (`first` to `last`, inclusive).
|
|
48
|
+
`returned` buffers are the op's outputs: they outlive the op (their `last` is the op's last step)."""
|
|
49
|
+
name: str
|
|
50
|
+
nbytes: int
|
|
51
|
+
first: int
|
|
52
|
+
last: int
|
|
53
|
+
returned: bool = False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class Cost:
|
|
58
|
+
buffers: Tuple[Buffer, ...]
|
|
59
|
+
bytes_moved: int # every byte read and written, copies included
|
|
60
|
+
flops: int
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def outputs(self) -> Tuple[Buffer, ...]:
|
|
64
|
+
return tuple(b for b in self.buffers if b.returned)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def workspace(self) -> Tuple[Buffer, ...]:
|
|
68
|
+
return tuple(b for b in self.buffers if not b.returned)
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def peak_bytes(self) -> int:
|
|
72
|
+
steps = {s for b in self.buffers for s in (b.first, b.last)}
|
|
73
|
+
return max((sum(b.nbytes for b in self.buffers if b.first <= s <= b.last) for s in steps), default=0)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class _Steps:
|
|
77
|
+
"""The op's sequence: each allocation takes the next step. Every buffer lives until the op returns: the wrappers
|
|
78
|
+
hold their transients in local names (the cast, the contiguous copy, the accumulator), which Python releases at
|
|
79
|
+
the return, never earlier."""
|
|
80
|
+
|
|
81
|
+
def __init__(self):
|
|
82
|
+
self.buffers = []
|
|
83
|
+
self.moved = 0
|
|
84
|
+
self.step = 0
|
|
85
|
+
|
|
86
|
+
def alloc(self, name: str, nbytes: int, returned: bool = False) -> int:
|
|
87
|
+
self.step += 1
|
|
88
|
+
self.buffers.append([name, int(nbytes), self.step, None, returned])
|
|
89
|
+
return len(self.buffers) - 1
|
|
90
|
+
|
|
91
|
+
def free(self, index: int) -> None:
|
|
92
|
+
"""The buffer dies here (a transient the wrapper drops before it returns: a reassigned local, a callee's
|
|
93
|
+
return): it lives over [its step, this step]."""
|
|
94
|
+
self.step += 1
|
|
95
|
+
self.buffers[index][3] = self.step
|
|
96
|
+
|
|
97
|
+
def done(self, flops: int) -> Cost:
|
|
98
|
+
end = self.step
|
|
99
|
+
return Cost(tuple(Buffer(n, b, f, end if l is None else l, r) for n, b, f, l, r in self.buffers),
|
|
100
|
+
self.moved, int(flops))
|
|
101
|
+
|
|
102
|
+
def nest(self, cost: "Cost", prefix: str) -> list:
|
|
103
|
+
"""A callee's cost inside this op's sequence: its buffers allocated in its order, its transients freed when it
|
|
104
|
+
returns; its returned buffers are the caller's to free (`free` on the indices returned)."""
|
|
105
|
+
base = self.step
|
|
106
|
+
kept = []
|
|
107
|
+
for b in cost.buffers:
|
|
108
|
+
self.buffers.append([f"{prefix}: {b.name}", b.nbytes, base + b.first,
|
|
109
|
+
None if b.returned else base + b.last, False])
|
|
110
|
+
if b.returned:
|
|
111
|
+
kept.append(len(self.buffers) - 1)
|
|
112
|
+
self.step = base + max((b.last for b in cost.buffers), default=0)
|
|
113
|
+
self.moved += cost.bytes_moved
|
|
114
|
+
return kept
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _contiguous_copy(st: _Steps, x: Spec, name: str) -> Optional[int]:
|
|
118
|
+
"""`x.contiguous()`: no buffer when `x` already lies row-major (`shape.is_contiguous`), else one copy of its
|
|
119
|
+
elements, read strided and written dense."""
|
|
120
|
+
if is_contiguous(x.shape, x.strides):
|
|
121
|
+
return None
|
|
122
|
+
st.moved += 2 * x.nbytes
|
|
123
|
+
return st.alloc(name, x.nbytes)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def elementwise_unary(x: Spec, out_dtype: Optional[str] = None, flops_per_element: int = 1) -> Cost:
|
|
127
|
+
"""A unary element-wise op (`wrappers._prepare_unary`: `x.contiguous()`, then `empty_like` the output; `out_dtype`
|
|
128
|
+
when the op writes another dtype, e.g. a comparison's bool)."""
|
|
129
|
+
st = _Steps()
|
|
130
|
+
_contiguous_copy(st, x, "contiguous input")
|
|
131
|
+
out = Spec.contiguous(x.shape, out_dtype or x.dtype)
|
|
132
|
+
st.alloc("output", out.nbytes, returned=True)
|
|
133
|
+
st.moved += x.nbytes + out.nbytes
|
|
134
|
+
return st.done(flops_per_element * numel(x.shape))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def elementwise_binary(a: Spec, b: Spec, common_dtype: str, out_shape: Sequence[int],
|
|
138
|
+
out_dtype: Optional[str] = None, flops_per_element: int = 1) -> Cost:
|
|
139
|
+
"""A binary element-wise op of two tensors (`wrappers._prepare_binary_strided`): each operand not already at
|
|
140
|
+
`common_dtype` is cast — a COPY at that dtype (the RoPE lesson: an in-kernel widen moves the compiler's
|
|
141
|
+
contraction) —, then both are read by strides as broadcast VIEWS (no expand copy), and the output is dense at
|
|
142
|
+
the broadcast shape. `common_dtype` and `out_shape` are the wrapper's decisions, passed in until the wrapper's
|
|
143
|
+
promotion lives here."""
|
|
144
|
+
st = _Steps()
|
|
145
|
+
for name, x in (("a cast", a), ("b cast", b)):
|
|
146
|
+
if x.dtype != common_dtype:
|
|
147
|
+
cast = Spec(x.shape, x.strides, common_dtype)
|
|
148
|
+
st.moved += x.nbytes + cast.nbytes
|
|
149
|
+
st.alloc(name, cast.nbytes)
|
|
150
|
+
out = Spec.contiguous(out_shape, out_dtype or common_dtype)
|
|
151
|
+
st.alloc("output", out.nbytes, returned=True)
|
|
152
|
+
st.moved += numel(a.shape) * itemsize(common_dtype) + numel(b.shape) * itemsize(common_dtype) + out.nbytes
|
|
153
|
+
return st.done(flops_per_element * numel(out_shape))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def softmax(x: Spec, dim: int) -> Cost:
|
|
157
|
+
"""`wrappers.softmax`: the reduced dim moved last (`movedim(dim, -1).contiguous()`: a copy unless that view is
|
|
158
|
+
already dense), the output `empty_like` the 2-D view, returned moved back (a view). Fused and looped kernels
|
|
159
|
+
alike hold nothing else. Arithmetic per element: the row max, the shift, the exponential, the row sum, the
|
|
160
|
+
division."""
|
|
161
|
+
dim = dim % len(x.shape)
|
|
162
|
+
order = [d for d in range(len(x.shape)) if d != dim] + [dim]
|
|
163
|
+
moved = Spec(tuple(x.shape[d] for d in order), tuple(x.strides[d] for d in order), x.dtype)
|
|
164
|
+
st = _Steps()
|
|
165
|
+
_contiguous_copy(st, moved, "rows made contiguous")
|
|
166
|
+
st.alloc("output", x.nbytes, returned=True)
|
|
167
|
+
st.moved += x.nbytes + x.nbytes
|
|
168
|
+
return st.done(5 * numel(x.shape))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def rms_norm(x: Spec, has_weight: bool, out_dtype: Optional[str] = None) -> Cost:
|
|
172
|
+
"""`wrappers.rms_norm`: `x.contiguous()`; when that made a fresh copy and the output dtype is the input's, the
|
|
173
|
+
output is written INTO the copy (no second buffer); otherwise a new output. Arithmetic per element: the square,
|
|
174
|
+
the row sum, the scale by the reciprocal root, and the weight's product when there is one."""
|
|
175
|
+
out_dt = out_dtype or x.dtype
|
|
176
|
+
st = _Steps()
|
|
177
|
+
copy = _contiguous_copy(st, x, "contiguous input")
|
|
178
|
+
out_bytes = numel(x.shape) * itemsize(out_dt)
|
|
179
|
+
if copy is not None and out_dt == x.dtype:
|
|
180
|
+
st.buffers[copy][4] = True # the copy IS the output
|
|
181
|
+
st.buffers[copy][0] = "output (in the contiguous copy)"
|
|
182
|
+
else:
|
|
183
|
+
st.alloc("output", out_bytes, returned=True)
|
|
184
|
+
st.moved += x.nbytes + out_bytes + (x.shape[-1] * itemsize(x.dtype) if has_weight else 0)
|
|
185
|
+
return st.done((4 if has_weight else 3) * numel(x.shape))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def native_layer_norm(x: Spec, normalized_shape: Sequence[int], has_weight: bool, has_bias: bool) -> Cost:
|
|
189
|
+
"""`wrappers.native_layer_norm`: `x.contiguous()`, the output `empty_like` it, and the row statistics (mean and
|
|
190
|
+
1/std, float32, one per row) — returned, as aten::native_layer_norm returns (output, mean, rstd). Arithmetic per
|
|
191
|
+
element: the mean's sum, the centring, the square, the variance's sum, the scale; the weight's product and the
|
|
192
|
+
bias's sum when present."""
|
|
193
|
+
feat = numel(normalized_shape)
|
|
194
|
+
rows = numel(x.shape) // feat
|
|
195
|
+
st = _Steps()
|
|
196
|
+
_contiguous_copy(st, x, "contiguous input")
|
|
197
|
+
st.alloc("output", x.nbytes, returned=True)
|
|
198
|
+
st.alloc("mean", rows * itemsize("float32"), returned=True)
|
|
199
|
+
st.alloc("rstd", rows * itemsize("float32"), returned=True)
|
|
200
|
+
affine = feat * itemsize(x.dtype) * (int(has_weight) + int(has_weight and has_bias))
|
|
201
|
+
st.moved += 2 * x.nbytes + 2 * rows * itemsize("float32") + affine
|
|
202
|
+
return st.done((5 + int(has_weight) + int(has_weight and has_bias)) * numel(x.shape))
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def reduction(x: Spec, dim: Optional[int], keepdim: bool, out_dtype: str, cast_to: Optional[str] = None) -> Cost:
|
|
206
|
+
"""`wrappers.sum_wrapper` / `mean_wrapper` over one dim or all: the input cast to the op's `dtype` keyword when
|
|
207
|
+
given (`cast_to`, a copy), the reduced dim moved last and made contiguous (a copy unless already dense), one
|
|
208
|
+
float32 accumulator per output element, then cast to the result dtype (`out_dtype`: the input's for a float
|
|
209
|
+
input, int64 for sum of an integer one) — a second buffer unless that dtype is float32 (`.to` returns the tensor
|
|
210
|
+
itself then). One addition per input element."""
|
|
211
|
+
st = _Steps()
|
|
212
|
+
src = x
|
|
213
|
+
if cast_to is not None and cast_to != x.dtype:
|
|
214
|
+
src = Spec(x.shape, x.strides, cast_to)
|
|
215
|
+
st.moved += x.nbytes + src.nbytes
|
|
216
|
+
st.alloc("dtype keyword cast", src.nbytes)
|
|
217
|
+
if dim is None:
|
|
218
|
+
rows = 1
|
|
219
|
+
_contiguous_copy(st, src, "flattened input")
|
|
220
|
+
else:
|
|
221
|
+
d = dim % len(src.shape)
|
|
222
|
+
order = [i for i in range(len(src.shape)) if i != d] + [d]
|
|
223
|
+
moved = Spec(tuple(src.shape[i] for i in order), tuple(src.strides[i] for i in order), src.dtype)
|
|
224
|
+
rows = numel(src.shape) // src.shape[d]
|
|
225
|
+
_contiguous_copy(st, moved, "reduced dim made contiguous")
|
|
226
|
+
st.alloc("float32 accumulator", rows * itemsize("float32"), returned=out_dtype == "float32")
|
|
227
|
+
st.moved += src.nbytes + rows * itemsize("float32")
|
|
228
|
+
if out_dtype != "float32":
|
|
229
|
+
st.alloc("result", rows * itemsize(out_dtype), returned=True)
|
|
230
|
+
st.moved += rows * itemsize("float32") + rows * itemsize(out_dtype)
|
|
231
|
+
return st.done(numel(x.shape))
|
nbxtensor/ops/abs.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Absolute value — pure @triton.jit kernel."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
|
|
6
|
+
@triton.jit
|
|
7
|
+
def abs_forward_kernel(
|
|
8
|
+
input_ptr, output_ptr,
|
|
9
|
+
n_elements,
|
|
10
|
+
BLOCK_SIZE: tl.constexpr,
|
|
11
|
+
):
|
|
12
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
13
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
14
|
+
mask = offset < n_elements
|
|
15
|
+
|
|
16
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
17
|
+
tl.store(output_ptr + offset, tl.abs(x), mask=mask)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Adaptive average pooling 2D — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
For each output element (n, c, oh, ow), the kernel computes the input
|
|
4
|
+
window boundaries using the standard PyTorch adaptive pooling formula:
|
|
5
|
+
start_h = floor(oh * IH / OH)
|
|
6
|
+
end_h = ceil((oh + 1) * IH / OH)
|
|
7
|
+
and averages all input elements in [start_h, end_h) x [start_w, end_w).
|
|
8
|
+
|
|
9
|
+
The end index is a CEILING. It was a floor here, which is the same value
|
|
10
|
+
whenever the output size divides the input size and a smaller window
|
|
11
|
+
whenever it does not — so every exactly-dividing shape agreed and every
|
|
12
|
+
ragged one was quietly averaging too few elements. Measured against torch
|
|
13
|
+
2026-09-09 by one-hot probing which input rows reach each output row:
|
|
14
|
+
IH=7,OH=3 gives torch [(0,3),(2,5),(4,7)] and the floor form
|
|
15
|
+
[(0,2),(2,4),(4,7)].
|
|
16
|
+
|
|
17
|
+
The window loop was also fixed at 8x8, on a comment that the adaptive
|
|
18
|
+
window is "typically 1-3 pixels each dim". That is false for any pooling
|
|
19
|
+
that downsamples by more than 8, and global average pooling is the common
|
|
20
|
+
case: a 16x16 plane to 1x1 has a 16x16 window, of which the kernel summed
|
|
21
|
+
the first 8x8 and divided by 64. Measured: 0.1807 returned where the plane
|
|
22
|
+
mean is 0.0019. The bound is now derived from the shape.
|
|
23
|
+
|
|
24
|
+
BOTH defects are in this kernel, not in any backend, so both were wrong on
|
|
25
|
+
EVERY vendor. This changes NVIDIA's numbers, and is meant to: keeping them
|
|
26
|
+
would be keeping the bug.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import triton
|
|
30
|
+
import triton.language as tl
|
|
31
|
+
|
|
32
|
+
@triton.jit
|
|
33
|
+
def adaptive_avg_pool2d_kernel(
|
|
34
|
+
input_ptr,
|
|
35
|
+
output_ptr,
|
|
36
|
+
n_elements,
|
|
37
|
+
# Input shape: [N, C, IH, IW]
|
|
38
|
+
IH, IW,
|
|
39
|
+
# Output shape: [N, C, OH, OW]
|
|
40
|
+
OH, OW,
|
|
41
|
+
# Strides (input contiguous NCHW)
|
|
42
|
+
stride_n, stride_c, stride_h, stride_w,
|
|
43
|
+
BLOCK_SIZE: tl.constexpr,
|
|
44
|
+
):
|
|
45
|
+
"""Adaptive average pool 2D.
|
|
46
|
+
|
|
47
|
+
Each thread computes one output element by averaging over the
|
|
48
|
+
corresponding adaptive window in the input.
|
|
49
|
+
"""
|
|
50
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
51
|
+
idx = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
52
|
+
mask = idx < n_elements
|
|
53
|
+
|
|
54
|
+
# Decompose flat index -> (n, c, oh, ow)
|
|
55
|
+
ow = idx % OW
|
|
56
|
+
tmp = idx // OW
|
|
57
|
+
oh = tmp % OH
|
|
58
|
+
tmp2 = tmp // OH
|
|
59
|
+
c = tmp2 % (stride_n // stride_c) # C = stride_n / stride_c for contiguous
|
|
60
|
+
n = tmp2 // (stride_n // stride_c)
|
|
61
|
+
|
|
62
|
+
# Adaptive pooling window bounds (PyTorch formula)
|
|
63
|
+
# start = floor(out_idx * in_size / out_size)
|
|
64
|
+
# end = CEIL((out_idx + 1) * in_size / out_size)
|
|
65
|
+
h_start = (oh * IH) // OH
|
|
66
|
+
h_end = ((oh + 1) * IH + OH - 1) // OH
|
|
67
|
+
w_start = (ow * IW) // OW
|
|
68
|
+
w_end = ((ow + 1) * IW + OW - 1) // OW
|
|
69
|
+
|
|
70
|
+
# Base pointer for this (n, c) plane
|
|
71
|
+
base = n * stride_n + c * stride_c
|
|
72
|
+
|
|
73
|
+
# Accumulate sum over the window
|
|
74
|
+
# Window size varies per output position in adaptive pooling
|
|
75
|
+
acc = tl.zeros([BLOCK_SIZE], dtype=tl.float32)
|
|
76
|
+
count = tl.zeros([BLOCK_SIZE], dtype=tl.float32)
|
|
77
|
+
|
|
78
|
+
# The widest window this shape can produce, derived rather than assumed.
|
|
79
|
+
# end - start <= ceil((i+1)*IN/OUT) - floor(i*IN/OUT) < IN/OUT + 2, so
|
|
80
|
+
# floor(IN/OUT) + 2 bounds it for every i. The old fixed 8 silently
|
|
81
|
+
# truncated any window wider than that — every global average pool over
|
|
82
|
+
# a plane larger than 8x8 summed a corner and divided by its own count.
|
|
83
|
+
KH = IH // OH + 2
|
|
84
|
+
KW = IW // OW + 2
|
|
85
|
+
for dh in range(0, KH):
|
|
86
|
+
for dw in range(0, KW):
|
|
87
|
+
h_idx = h_start + dh
|
|
88
|
+
w_idx = w_start + dw
|
|
89
|
+
in_bounds = (h_idx < h_end) & (w_idx < w_end)
|
|
90
|
+
valid = mask & in_bounds
|
|
91
|
+
|
|
92
|
+
offset = base + h_idx * stride_h + w_idx * stride_w
|
|
93
|
+
val = tl.load(input_ptr + offset, mask=valid, other=0.0).to(tl.float32)
|
|
94
|
+
acc += tl.where(valid, val, 0.0)
|
|
95
|
+
count += tl.where(valid, 1.0, 0.0)
|
|
96
|
+
|
|
97
|
+
# Average
|
|
98
|
+
count = tl.maximum(count, 1.0) # Avoid division by zero
|
|
99
|
+
result = acc / count
|
|
100
|
+
|
|
101
|
+
# Store
|
|
102
|
+
out_offset = idx
|
|
103
|
+
tl.store(output_ptr + out_offset, result, mask=mask)
|
|
104
|
+
|
|
105
|
+
@triton.jit
|
|
106
|
+
def adaptive_avg_pool2d_tiled_kernel(
|
|
107
|
+
input_ptr,
|
|
108
|
+
output_ptr,
|
|
109
|
+
N, C,
|
|
110
|
+
IH, IW,
|
|
111
|
+
OH, OW,
|
|
112
|
+
BLOCK_X: tl.constexpr,
|
|
113
|
+
BLOCK_Y: tl.constexpr,
|
|
114
|
+
):
|
|
115
|
+
"""Tiled variant for spatial parallelism.
|
|
116
|
+
|
|
117
|
+
Grid: (cdiv(OW, BLOCK_X), cdiv(OH, BLOCK_Y), N*C)
|
|
118
|
+
"""
|
|
119
|
+
pid_x = tl.program_id(0).to(tl.int64)
|
|
120
|
+
pid_y = tl.program_id(1).to(tl.int64)
|
|
121
|
+
pid_nc = tl.program_id(2).to(tl.int64)
|
|
122
|
+
|
|
123
|
+
ow = pid_x * BLOCK_X + tl.arange(0, BLOCK_X) # [BLOCK_X]
|
|
124
|
+
oh = pid_y * BLOCK_Y + tl.arange(0, BLOCK_Y) # [BLOCK_Y]
|
|
125
|
+
|
|
126
|
+
mask_h = oh < OH
|
|
127
|
+
mask_w = ow < OW
|
|
128
|
+
mask_2d = mask_h[:, None] & mask_w[None, :]
|
|
129
|
+
|
|
130
|
+
# Window boundaries
|
|
131
|
+
h_start = (oh * IH) // OH # [BLOCK_Y]
|
|
132
|
+
h_end = ((oh + 1) * IH) // OH
|
|
133
|
+
w_start = (ow * IW) // OW # [BLOCK_X]
|
|
134
|
+
w_end = ((ow + 1) * IW) // OW
|
|
135
|
+
|
|
136
|
+
# Window sizes
|
|
137
|
+
kh = h_end - h_start # [BLOCK_Y]
|
|
138
|
+
kw = w_end - w_start # [BLOCK_X]
|
|
139
|
+
|
|
140
|
+
base = pid_nc * IH * IW
|
|
141
|
+
|
|
142
|
+
acc = tl.zeros([BLOCK_Y, BLOCK_X], dtype=tl.float32)
|
|
143
|
+
|
|
144
|
+
for dh in range(0, 4):
|
|
145
|
+
for dw in range(0, 4):
|
|
146
|
+
h_idx = h_start[:, None] + dh
|
|
147
|
+
w_idx = w_start[None, :] + dw
|
|
148
|
+
in_bounds = (dh < kh[:, None]) & (dw < kw[None, :])
|
|
149
|
+
valid = mask_2d & in_bounds
|
|
150
|
+
|
|
151
|
+
offset = base + h_idx * IW + w_idx
|
|
152
|
+
val = tl.load(input_ptr + offset, mask=valid, other=0.0).to(tl.float32)
|
|
153
|
+
acc += tl.where(valid, val, 0.0)
|
|
154
|
+
|
|
155
|
+
area = (kh[:, None] * kw[None, :]).to(tl.float32)
|
|
156
|
+
area = tl.maximum(area, 1.0)
|
|
157
|
+
result = acc / area
|
|
158
|
+
|
|
159
|
+
out_base = pid_nc * OH * OW
|
|
160
|
+
out_off = out_base + oh[:, None] * OW + ow[None, :]
|
|
161
|
+
tl.store(output_ptr + out_off, result, mask=mask_2d)
|
nbxtensor/ops/add.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Add — pure @triton.jit kernel (tensor + tensor, tensor + scalar)."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
|
|
6
|
+
@triton.jit
|
|
7
|
+
def add_forward_kernel(
|
|
8
|
+
x_ptr, y_ptr, output_ptr,
|
|
9
|
+
n_elements,
|
|
10
|
+
alpha,
|
|
11
|
+
BLOCK_SIZE: tl.constexpr,
|
|
12
|
+
):
|
|
13
|
+
"""out = x + alpha * y (tensor + tensor)"""
|
|
14
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
15
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
16
|
+
mask = offset < n_elements
|
|
17
|
+
|
|
18
|
+
x = tl.load(x_ptr + offset, mask=mask)
|
|
19
|
+
y = tl.load(y_ptr + offset, mask=mask)
|
|
20
|
+
tl.store(output_ptr + offset, x + alpha * y, mask=mask)
|
|
21
|
+
|
|
22
|
+
@triton.jit
|
|
23
|
+
def add_scalar_kernel(
|
|
24
|
+
x_ptr, output_ptr,
|
|
25
|
+
n_elements,
|
|
26
|
+
scalar,
|
|
27
|
+
BLOCK_SIZE: tl.constexpr,
|
|
28
|
+
):
|
|
29
|
+
"""out = x + scalar"""
|
|
30
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
31
|
+
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)
|
|
32
|
+
mask = offset < n_elements
|
|
33
|
+
|
|
34
|
+
x = tl.load(x_ptr + offset, mask=mask)
|
|
35
|
+
tl.store(output_ptr + offset, x + scalar, mask=mask)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@triton.jit
|
|
39
|
+
def add_scalar_dev_kernel(
|
|
40
|
+
x_ptr, s_ptr, output_ptr,
|
|
41
|
+
n_elements,
|
|
42
|
+
alpha,
|
|
43
|
+
BLOCK_SIZE: tl.constexpr,
|
|
44
|
+
):
|
|
45
|
+
"""out = x + (s * alpha) where s is a 0-d DEVICE tensor.
|
|
46
|
+
|
|
47
|
+
Bit-exact mirror of add_scalar_kernel's `x + scalar` path: the
|
|
48
|
+
host path computes `float(s) * alpha` in Python float64 then
|
|
49
|
+
narrows to the f32 kernel argument; here the same math runs
|
|
50
|
+
in-register (load -> f64 -> *alpha -> f32). Replaces the host
|
|
51
|
+
.item() sync so the scalar stays device-resident — the replay
|
|
52
|
+
device-scalar increment (Ming timestep class, 2026-08-15).
|
|
53
|
+
|
|
54
|
+
Bit-exactness constraint: `alpha` binds as an f32 runtime arg, so
|
|
55
|
+
the f64 multiply sees f32(alpha) where the host path sees the full
|
|
56
|
+
f64 alpha — a 1-ulp double-rounding risk for alpha values not
|
|
57
|
+
exactly representable in f32. Every ATen call site passes alpha=±1
|
|
58
|
+
(exact); a non-representable alpha would need an f64-carried arg.
|
|
59
|
+
"""
|
|
60
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
61
|
+
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)
|
|
62
|
+
mask = offset < n_elements
|
|
63
|
+
s = (tl.load(s_ptr).to(tl.float64) * alpha).to(tl.float32)
|
|
64
|
+
x = tl.load(x_ptr + offset, mask=mask)
|
|
65
|
+
tl.store(output_ptr + offset, x + s, mask=mask)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@triton.jit
|
|
69
|
+
def add_bias_broadcast_kernel(
|
|
70
|
+
x_ptr, bias_ptr, output_ptr,
|
|
71
|
+
n_elements, feat_dim,
|
|
72
|
+
alpha,
|
|
73
|
+
BLOCK_SIZE: tl.constexpr,
|
|
74
|
+
):
|
|
75
|
+
"""out[i] = x[i] + alpha * bias[i % feat_dim].
|
|
76
|
+
|
|
77
|
+
Used by `add` wrapper when `y` is a 1D bias of size `feat_dim` that
|
|
78
|
+
broadcasts against `x`'s last dim. Avoids materializing the 8 GiB
|
|
79
|
+
contiguous broadcast view of bias on Sana 4Kpx VAE add::88
|
|
80
|
+
(`mul::58::out_0 (1, 4096, 4096, 128) + bias (128)`). Reads bias
|
|
81
|
+
directly via `offset % feat_dim` indexing — same effect as a
|
|
82
|
+
stride-0 broadcast but compatible with the kernel's flat 1D
|
|
83
|
+
addressing.
|
|
84
|
+
"""
|
|
85
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
86
|
+
# Cast to int64 — tensors >= 2^31 elements (e.g. Sana 4Kpx VAE
|
|
87
|
+
# add::88 input 1x4096x4096x128 = 2^31) overflow int32 offset
|
|
88
|
+
# arithmetic silently and corrupt `offset % feat_dim`, producing
|
|
89
|
+
# garbage output. P-SANA-4KPX-RUNTIME 2026-05-07.
|
|
90
|
+
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)
|
|
91
|
+
mask = offset < n_elements
|
|
92
|
+
|
|
93
|
+
c = offset % feat_dim
|
|
94
|
+
x = tl.load(x_ptr + offset, mask=mask)
|
|
95
|
+
b = tl.load(bias_ptr + c, mask=mask)
|
|
96
|
+
tl.store(output_ptr + offset, x + alpha * b, mask=mask)
|
nbxtensor/ops/addcdiv.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Addcdiv kernel — extracted from FlagGems.
|
|
2
|
+
|
|
3
|
+
Pure Triton. ZERO PyTorch imports.
|
|
4
|
+
Logic: result = input + value * (tensor1 / tensor2)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import triton
|
|
8
|
+
import triton.language as tl
|
|
9
|
+
|
|
10
|
+
@triton.jit
|
|
11
|
+
def addcdiv_forward_kernel(
|
|
12
|
+
input_ptr,
|
|
13
|
+
tensor1_ptr,
|
|
14
|
+
tensor2_ptr,
|
|
15
|
+
output_ptr,
|
|
16
|
+
n_elements,
|
|
17
|
+
value,
|
|
18
|
+
BLOCK_SIZE: tl.constexpr,
|
|
19
|
+
):
|
|
20
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
21
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
22
|
+
mask = offset < n_elements
|
|
23
|
+
|
|
24
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
25
|
+
t1 = tl.load(tensor1_ptr + offset, mask=mask)
|
|
26
|
+
t2 = tl.load(tensor2_ptr + offset, mask=mask)
|
|
27
|
+
|
|
28
|
+
result = x + value * (t1 / t2)
|
|
29
|
+
tl.store(output_ptr + offset, result, mask=mask)
|
nbxtensor/ops/addcmul.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Addcmul kernel — extracted from FlagGems.
|
|
2
|
+
|
|
3
|
+
Pure Triton. ZERO PyTorch imports.
|
|
4
|
+
Logic: result = input + value * tensor1 * tensor2
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import triton
|
|
8
|
+
import triton.language as tl
|
|
9
|
+
|
|
10
|
+
@triton.jit
|
|
11
|
+
def addcmul_forward_kernel(
|
|
12
|
+
input_ptr,
|
|
13
|
+
tensor1_ptr,
|
|
14
|
+
tensor2_ptr,
|
|
15
|
+
output_ptr,
|
|
16
|
+
n_elements,
|
|
17
|
+
value,
|
|
18
|
+
BLOCK_SIZE: tl.constexpr,
|
|
19
|
+
):
|
|
20
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
21
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
22
|
+
mask = offset < n_elements
|
|
23
|
+
|
|
24
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
25
|
+
t1 = tl.load(tensor1_ptr + offset, mask=mask)
|
|
26
|
+
t2 = tl.load(tensor2_ptr + offset, mask=mask)
|
|
27
|
+
|
|
28
|
+
result = x + value * t1 * t2
|
|
29
|
+
tl.store(output_ptr + offset, result, mask=mask)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Addmv — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Ported from FlagGems addmv (Apache-2.0 license).
|
|
4
|
+
Computes: out = beta * self + alpha * (mat @ vec)
|
|
5
|
+
where mat is [N, M], vec is [M], self is broadcastable to [N].
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import triton
|
|
9
|
+
import triton.language as tl
|
|
10
|
+
|
|
11
|
+
from nbxtensor.ops._configs import nbx_autotune, addmv_configs, prune_addmv
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# rows, reduction tile, warps: the certified configuration of the key (workstream 1, small-M GEMM), the key the derived
|
|
15
|
+
# census forms (`launch_keys.addmv_launches`)
|
|
16
|
+
@nbx_autotune(configs=addmv_configs(), key=['N_BUCKET', 'M_BUCKET'],
|
|
17
|
+
prune_configs_by={'early_config_prune': prune_addmv}, cache_results=True)
|
|
18
|
+
@triton.jit
|
|
19
|
+
def addmv_kernel(
|
|
20
|
+
A_ptr,
|
|
21
|
+
B_ptr,
|
|
22
|
+
inp_ptr,
|
|
23
|
+
out_ptr,
|
|
24
|
+
N, M, N_BUCKET, M_BUCKET,
|
|
25
|
+
alpha,
|
|
26
|
+
beta,
|
|
27
|
+
stride_an, stride_am,
|
|
28
|
+
stride_bm,
|
|
29
|
+
stride_in,
|
|
30
|
+
stride_outn,
|
|
31
|
+
BLOCK_M: tl.constexpr,
|
|
32
|
+
BLOCK_N: tl.constexpr,
|
|
33
|
+
):
|
|
34
|
+
"""Matrix-vector multiply with bias: out = beta*inp + alpha*(A @ B).
|
|
35
|
+
|
|
36
|
+
Each program handles BLOCK_N rows of A, iterating over M in BLOCK_M tiles.
|
|
37
|
+
Accumulates in fp32 for stability.
|
|
38
|
+
"""
|
|
39
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
40
|
+
offset_n = pid * BLOCK_N + tl.arange(0, BLOCK_N)[:, None]
|
|
41
|
+
offset_m = tl.arange(0, BLOCK_M)[None, :]
|
|
42
|
+
n_mask = offset_n < N
|
|
43
|
+
|
|
44
|
+
A_ptrs = A_ptr + offset_n * stride_an + offset_m * stride_am
|
|
45
|
+
B_ptrs = B_ptr + offset_m * stride_bm
|
|
46
|
+
|
|
47
|
+
acc = tl.zeros((BLOCK_N, BLOCK_M), dtype=tl.float32)
|
|
48
|
+
for m in range(0, M, BLOCK_M):
|
|
49
|
+
m_mask = m + offset_m < M
|
|
50
|
+
a = tl.load(A_ptrs, mask=n_mask & m_mask, other=0.0).to(tl.float32)
|
|
51
|
+
b = tl.load(B_ptrs, mask=m_mask, other=0.0).to(tl.float32)
|
|
52
|
+
acc += a * b
|
|
53
|
+
A_ptrs += BLOCK_M * stride_am
|
|
54
|
+
B_ptrs += BLOCK_M * stride_bm
|
|
55
|
+
|
|
56
|
+
# Reduce over M dimension, then add bias
|
|
57
|
+
mv_result = tl.sum(acc, axis=1)[:, None]
|
|
58
|
+
inp_ptrs = inp_ptr + offset_n * stride_in
|
|
59
|
+
inp = tl.load(inp_ptrs, mask=n_mask, other=0.0).to(tl.float32)
|
|
60
|
+
|
|
61
|
+
out = mv_result * alpha + inp * beta
|
|
62
|
+
|
|
63
|
+
out_ptrs = out_ptr + offset_n * stride_outn
|
|
64
|
+
tl.store(out_ptrs, out, mask=n_mask)
|