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,413 @@
|
|
|
1
|
+
"""Fused upsample_nearest2d + convolution, and the spatially tiled convolution — band-streaming, Triton side.
|
|
2
|
+
|
|
3
|
+
Eliminates the intermediate upsampled tensor that would otherwise sit in VRAM between an upsample op and the
|
|
4
|
+
convolution that consumes it (Sana 4Kpx VAE: a [1, 256, 4096, 4096] fp32 upsample, 16 GB, that the conv consumes at
|
|
5
|
+
once). Band by band along output H: the pre-upsample input band with the conv's halo, upsampled alone, convolved, written
|
|
6
|
+
into the conv output at its offset; the full conv output is materialised once. The proxies stand for the upsample that
|
|
7
|
+
is not materialised (`FusionUpsampleProxy`) and for a broadcast clone (`BroadcastClonePyroxy`).
|
|
8
|
+
|
|
9
|
+
The PyTorch branch's versions and the dispatchers that pick a branch by tensor type are the engine's
|
|
10
|
+
(neurobrix/kernels/ops/fused_upsample_conv_torch.py); this module imports no torch.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Any, Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FusionUpsampleProxy:
|
|
18
|
+
"""Sentinel returned by an intercepted upsample op so the next conv
|
|
19
|
+
can find the pre-upsample input + scales without the upsample tensor
|
|
20
|
+
ever being materialized.
|
|
21
|
+
|
|
22
|
+
Holds: pre_upsample input tensor, scales_h, scales_w, intended output
|
|
23
|
+
shape. Stored in the CompiledSequence arena slot that would normally
|
|
24
|
+
hold the upsample output tensor. Only the paired conv interceptor reads
|
|
25
|
+
this slot — verified at Prism planning time (single consumer).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
__slots__ = ("pre_input", "scales_h", "scales_w", "output_shape", "dtype", "device")
|
|
29
|
+
|
|
30
|
+
# ── observability ──────────────────────────────────────────────────────
|
|
31
|
+
# THE RULE: a brick that replaces a call site preserves the observability
|
|
32
|
+
# of that site. This proxy stands in for an upsample op, and the per-op
|
|
33
|
+
# recorder drops anything that is not a tensor — so two of the three
|
|
34
|
+
# spatial upsamples of the Wan VAE produced no record at all, and every
|
|
35
|
+
# cross-engine walk was blind exactly there. The proxy therefore produces
|
|
36
|
+
# the record itself.
|
|
37
|
+
#
|
|
38
|
+
# It costs nothing and it is EXACT, because a nearest upsample only
|
|
39
|
+
# replicates: every input value appears scales_h x scales_w times, so
|
|
40
|
+
# l2(up(x)) == sqrt(sh*sw) * l2(x)
|
|
41
|
+
# and the first row of the result is the first row of the input with each
|
|
42
|
+
# element repeated sw times. No tensor is materialized — which is the
|
|
43
|
+
# whole reason this proxy exists.
|
|
44
|
+
|
|
45
|
+
def nbx_observable_summary(self) -> dict:
|
|
46
|
+
"""What the replaced upsample would have reported, without materializing it."""
|
|
47
|
+
import math
|
|
48
|
+
x = self.pre_input
|
|
49
|
+
sh = max(1, int(round(float(self.scales_h))))
|
|
50
|
+
sw = max(1, int(round(float(self.scales_w))))
|
|
51
|
+
# `self.dtype` is a __slots__ field the interceptor does not always set,
|
|
52
|
+
# and `getattr(x, "dtype", self.dtype)` would evaluate it EAGERLY as the
|
|
53
|
+
# default — raising AttributeError before the getattr ever runs.
|
|
54
|
+
dt = getattr(x, "dtype", None)
|
|
55
|
+
if dt is None:
|
|
56
|
+
dt = getattr(self, "dtype", None)
|
|
57
|
+
out = {"shape": list(self.output_shape), "dtype": str(dt),
|
|
58
|
+
"synthetic": True, "replaced_by": "fused_upsample_conv2d"}
|
|
59
|
+
try:
|
|
60
|
+
flat = x.reshape(-1)
|
|
61
|
+
head_src = flat[:10]
|
|
62
|
+
head_src = (head_src.tolist() if hasattr(head_src, "tolist")
|
|
63
|
+
else list(head_src))
|
|
64
|
+
head = [float(v) for v in head_src for _ in range(sw)][:10]
|
|
65
|
+
out["head10"] = head
|
|
66
|
+
if hasattr(x, "detach"): # a torch tensor (the compiled engine): its own fp32 norm, torch never imported
|
|
67
|
+
l2 = float(x.detach().reshape(-1).float().norm().item())
|
|
68
|
+
else: # NBXTensor -> numpy
|
|
69
|
+
import numpy as _np
|
|
70
|
+
l2 = float(_np.linalg.norm(
|
|
71
|
+
_np.asarray(x.numpy(), dtype=_np.float64).ravel()))
|
|
72
|
+
out["l2_norm"] = l2 * math.sqrt(sh * sw)
|
|
73
|
+
except Exception as exc: # a summary must never break a run
|
|
74
|
+
out["error"] = f"{type(exc).__name__}: {exc}"
|
|
75
|
+
return out
|
|
76
|
+
|
|
77
|
+
def __init__(self, pre_input, scales_h, scales_w, output_shape):
|
|
78
|
+
self.pre_input = pre_input
|
|
79
|
+
self.scales_h = float(scales_h)
|
|
80
|
+
self.scales_w = float(scales_w)
|
|
81
|
+
self.output_shape = tuple(output_shape)
|
|
82
|
+
self.dtype = getattr(pre_input, "dtype", None)
|
|
83
|
+
self.device = getattr(pre_input, "device", None)
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def shape(self):
|
|
87
|
+
return self.output_shape
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def ndim(self):
|
|
91
|
+
return len(self.output_shape)
|
|
92
|
+
|
|
93
|
+
# Sentinel: FusionUpsampleProxy holds no GPU allocation of its own
|
|
94
|
+
# (it just references the pre-upsample tensor + scales). The triton
|
|
95
|
+
# sequence's deferred-free accounting (sequence.py:_run_single_device)
|
|
96
|
+
# reads `_nbytes` to size the drain budget; expose 0 here so the
|
|
97
|
+
# proxy is a no-op in that accounting path. The actual pre-upsample
|
|
98
|
+
# tensor lives in its own arena slot and is freed when that slot is
|
|
99
|
+
# killed independently.
|
|
100
|
+
@property
|
|
101
|
+
def _nbytes(self):
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
def __repr__(self):
|
|
105
|
+
return (
|
|
106
|
+
f"FusionUpsampleProxy(pre={tuple(self.pre_input.shape)}, "
|
|
107
|
+
f"scales=({self.scales_h},{self.scales_w}), "
|
|
108
|
+
f"out_shape={self.output_shape})"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class BroadcastClonePyroxy:
|
|
113
|
+
"""Sentinel returned by an intercepted aten.clone op whose input is
|
|
114
|
+
a broadcast-view (stride=0 on some dim from a preceding aten.expand)
|
|
115
|
+
and whose consumer chain is `aten.view -> aten.pixel_shuffle`. Carries
|
|
116
|
+
the broadcast-view tensor itself (NBX expand returns a stride-0 view,
|
|
117
|
+
not a materialized tensor — see NBXTensor.expand line 1581) plus the
|
|
118
|
+
static broadcast metadata so pixel_shuffle can read through the view
|
|
119
|
+
directly (Triton tl.load with stride 0 aliases broadcast indices),
|
|
120
|
+
eliminating the clone materialization entirely.
|
|
121
|
+
|
|
122
|
+
Same architectural pattern as FusionUpsampleProxy: zero compute,
|
|
123
|
+
zero allocation in the interceptor; the downstream pixel_shuffle
|
|
124
|
+
consumes the proxy and performs the fused work.
|
|
125
|
+
|
|
126
|
+
`bcast_view`: the expand output (5D NBXTensor with stride 0 on
|
|
127
|
+
`bcast_dim`). Same data_ptr as the pre-expand tensor (NBX expand
|
|
128
|
+
is metadata-only).
|
|
129
|
+
`bcast_dim` / `bcast_factor`: extracted statically at register
|
|
130
|
+
time from the expand op's input/output shape diff.
|
|
131
|
+
`post_clone_shape` / `post_view_shape`: kept for the view
|
|
132
|
+
interceptor's dim arithmetic compatibility.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
__slots__ = ("bcast_view", "bcast_dim", "bcast_factor",
|
|
136
|
+
"post_clone_shape", "post_view_shape", "dtype", "device")
|
|
137
|
+
|
|
138
|
+
def __init__(self, bcast_view, bcast_dim, bcast_factor,
|
|
139
|
+
post_clone_shape, post_view_shape):
|
|
140
|
+
self.bcast_view = bcast_view
|
|
141
|
+
self.bcast_dim = int(bcast_dim)
|
|
142
|
+
self.bcast_factor = int(bcast_factor)
|
|
143
|
+
self.post_clone_shape = tuple(post_clone_shape)
|
|
144
|
+
self.post_view_shape = tuple(post_view_shape)
|
|
145
|
+
self.dtype = getattr(bcast_view, "dtype", None)
|
|
146
|
+
self.device = getattr(bcast_view, "device", None)
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def shape(self):
|
|
150
|
+
return self.post_clone_shape
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def ndim(self):
|
|
154
|
+
return len(self.post_clone_shape)
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def _nbytes(self):
|
|
158
|
+
return 0
|
|
159
|
+
|
|
160
|
+
def __repr__(self):
|
|
161
|
+
return (
|
|
162
|
+
f"BroadcastClonePyroxy(view={tuple(self.bcast_view.shape)}, "
|
|
163
|
+
f"bcast_dim={self.bcast_dim}, bcast={self.bcast_factor}, "
|
|
164
|
+
f"post_clone={self.post_clone_shape})"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def make_upsample_proxy_interceptor():
|
|
169
|
+
"""Return an interceptor that turns an upsample into a FusionUpsampleProxy
|
|
170
|
+
(no compute, no allocation of the full upsampled tensor)."""
|
|
171
|
+
|
|
172
|
+
def _proxy(input_tensor, output_size=None, scales_h=None, scales_w=None,
|
|
173
|
+
*args, **kwargs):
|
|
174
|
+
# Resolve scales: prefer explicit scales args (Sana 4Kpx graph stores
|
|
175
|
+
# both output_size and scales). Fall back to ratio if scales missing.
|
|
176
|
+
ih = input_tensor.shape[2]
|
|
177
|
+
iw = input_tensor.shape[3]
|
|
178
|
+
if scales_h is None or scales_w is None:
|
|
179
|
+
if isinstance(output_size, (list, tuple)):
|
|
180
|
+
oh, ow = int(output_size[0]), int(output_size[1])
|
|
181
|
+
else:
|
|
182
|
+
oh = ow = int(output_size)
|
|
183
|
+
sh = oh / ih
|
|
184
|
+
sw = ow / iw
|
|
185
|
+
else:
|
|
186
|
+
sh = float(scales_h)
|
|
187
|
+
sw = float(scales_w)
|
|
188
|
+
oh = int(ih * sh)
|
|
189
|
+
ow = int(iw * sw)
|
|
190
|
+
n, c = input_tensor.shape[0], input_tensor.shape[1]
|
|
191
|
+
return FusionUpsampleProxy(input_tensor, sh, sw, (n, c, oh, ow))
|
|
192
|
+
|
|
193
|
+
return _proxy
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def pair(v):
|
|
197
|
+
"""(h, w) of a convolution argument given as one int or a pair."""
|
|
198
|
+
if isinstance(v, (list, tuple)):
|
|
199
|
+
return int(v[0]), int(v[1])
|
|
200
|
+
return int(v), int(v)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def tiled_conv2d(
|
|
204
|
+
input_tensor, weight, bias, sh_st, sw_st, pad_h, pad_w,
|
|
205
|
+
dh, dw, groups, tile_factor,
|
|
206
|
+
):
|
|
207
|
+
"""Band-streaming standalone conv with REAL halo (no zero-pad seams).
|
|
208
|
+
|
|
209
|
+
POINT 5 FIX (P-SANA-4KPX-RUNTIME): mirrors `_tiled_conv2d_spatial_torch`
|
|
210
|
+
exactly. The previous version applied `padding=(pad_h, pad_w)` on every
|
|
211
|
+
band — at internal band frontiers this filled zeros where the kernel
|
|
212
|
+
should have read from the NEXT band's rows (real halo). The result was
|
|
213
|
+
bit-divergent from the non-tiled path at every band boundary, by ~50 in
|
|
214
|
+
max_abs at Sana 4Kpx (98%+ of elements affected). The torch backend has
|
|
215
|
+
always used the halo-based algorithm; the NBX backend now matches it.
|
|
216
|
+
|
|
217
|
+
Per band we slice the input with halo = (kh-1)*dh//2 rows above and
|
|
218
|
+
below; conv runs with `padding=(0,0)`. Image-edge halos are filled by
|
|
219
|
+
`constant_pad_nd_wrapper` with the original `pad_h` only on the top of
|
|
220
|
+
the first band and the bottom of the last band. Bit-identical to the
|
|
221
|
+
non-tiled `conv2d_wrapper` + `_torch` backend.
|
|
222
|
+
"""
|
|
223
|
+
from nbxtensor.tensor import NBXTensor
|
|
224
|
+
from nbxtensor.ops.conv_wrappers import conv2d_wrapper, _conv_bias_inplace
|
|
225
|
+
from nbxtensor.ops.shape_wrappers import constant_pad_nd_wrapper
|
|
226
|
+
|
|
227
|
+
N, in_c, IH, IW = input_tensor.shape
|
|
228
|
+
out_c, _, kh, kw = weight.shape
|
|
229
|
+
out_h = (IH + 2 * pad_h - dh * (kh - 1) - 1) // sh_st + 1
|
|
230
|
+
out_w = (IW + 2 * pad_w - dw * (kw - 1) - 1) // sw_st + 1
|
|
231
|
+
|
|
232
|
+
output = NBXTensor.empty(
|
|
233
|
+
(N, out_c, out_h, out_w),
|
|
234
|
+
device=input_tensor.device, dtype=input_tensor.dtype,
|
|
235
|
+
)
|
|
236
|
+
# The bands are `launch_keys.tiled_conv2d_bands` — the cut the derived census keys with.
|
|
237
|
+
from nbxtensor.ops.launch_keys import tiled_conv2d_bands
|
|
238
|
+
for (oh_start, oh_end, in_clamped_start, in_clamped_end,
|
|
239
|
+
pad_top_real, pad_bot_real, skip) in tiled_conv2d_bands(
|
|
240
|
+
IH, out_h, kh, sh_st, dh, pad_h, tile_factor):
|
|
241
|
+
# POINT 6 FIX (P-SANA-4KPX-RUNTIME): see `tiled_upsample_conv2d`
|
|
242
|
+
# for the full rationale — H-slice on an NCHW tensor produces a
|
|
243
|
+
# non-contiguous view; the downstream conv/pad Triton wrappers use
|
|
244
|
+
# flat indexing that assumes contiguous and read wrong memory
|
|
245
|
+
# otherwise. The torch backend (`_tiled_conv2d_spatial_torch`)
|
|
246
|
+
# already enforces contiguity at this point.
|
|
247
|
+
view = input_tensor[:, :, in_clamped_start:in_clamped_end, :]
|
|
248
|
+
if pad_top_real > 0 or pad_bot_real > 0 or pad_w > 0:
|
|
249
|
+
# ONE band buffer, padded: the slice copied by its strides straight into its interior (`copy_`). A
|
|
250
|
+
# contiguous copy then constant_pad_nd held two input bands where the band's price holds one (HAT-L at
|
|
251
|
+
# 2048 px on 16 GB, 2026-10-22).
|
|
252
|
+
hb, wb = int(view.shape[2]), int(view.shape[3])
|
|
253
|
+
in_band = NBXTensor.zeros((int(view.shape[0]), int(view.shape[1]), hb + pad_top_real + pad_bot_real,
|
|
254
|
+
wb + 2 * pad_w), dtype=view.dtype, device=view.device)
|
|
255
|
+
in_band.narrow(2, pad_top_real, hb).narrow(3, pad_w, wb).copy_(view)
|
|
256
|
+
else:
|
|
257
|
+
in_band = view.contiguous()
|
|
258
|
+
del view
|
|
259
|
+
|
|
260
|
+
conv_band = conv2d_wrapper(
|
|
261
|
+
in_band, weight, None,
|
|
262
|
+
stride=(sh_st, sw_st), padding=(0, 0),
|
|
263
|
+
dilation=(dh, dw), groups=groups,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
actual_band_h = oh_end - oh_start
|
|
267
|
+
# P-NBX-TILED-CONV2D-SMALL-SCALE step 2: same halo offset fix
|
|
268
|
+
# as the torch path. When halo_top > 0 (internal band frontier),
|
|
269
|
+
# conv_band[0] is the convolution at the halo row, not at the
|
|
270
|
+
# band's first useful output row. Skip the halo rows on the
|
|
271
|
+
# read side.
|
|
272
|
+
avail_after_halo = conv_band.shape[2] - skip
|
|
273
|
+
if avail_after_halo < actual_band_h:
|
|
274
|
+
actual_band_h = avail_after_halo
|
|
275
|
+
# Bias add inside band — see tiled_upsample_conv2d for rationale
|
|
276
|
+
# (avoids 8 GiB bias broadcast materialization on Sana 4Kpx).
|
|
277
|
+
if bias is not None:
|
|
278
|
+
# In place, as every conv wrapper adds its bias (`_conv_bias_inplace`): an out-of-place add held a
|
|
279
|
+
# second band beside the first, which Prism's band price does not hold.
|
|
280
|
+
_conv_bias_inplace(conv_band, bias)
|
|
281
|
+
output[:, :, oh_start:oh_start + actual_band_h, :] = conv_band[:, :, skip:skip + actual_band_h, :]
|
|
282
|
+
# Released before the next band allocates its own: held, the previous band's input and output stood beside
|
|
283
|
+
# the next band's (HAT-S at 2048 px on 16 GB: the second 1 153 MB band of conv::2 found no room, 2026-10-22).
|
|
284
|
+
del in_band, conv_band
|
|
285
|
+
return output
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# ---------------------------------------------------------------------------
|
|
289
|
+
# NBX / Triton mode — same algorithm via Triton wrappers
|
|
290
|
+
# ---------------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def tiled_upsample_conv2d(
|
|
294
|
+
proxy_or_tensor, weight, bias, stride, padding, dilation,
|
|
295
|
+
transposed, output_padding, groups, tile_factor,
|
|
296
|
+
):
|
|
297
|
+
"""NBXTensor flavor — uses the existing Triton upsample + conv2d
|
|
298
|
+
wrappers per band. Identical algorithm to the torch path; the only
|
|
299
|
+
differences are the backend ops and explicit NBXTensor allocations.
|
|
300
|
+
"""
|
|
301
|
+
from nbxtensor.tensor import NBXTensor
|
|
302
|
+
from nbxtensor.ops.shape_wrappers import upsample_nearest2d_wrapper
|
|
303
|
+
from nbxtensor.ops.conv_wrappers import conv2d_wrapper, _conv_bias_inplace
|
|
304
|
+
|
|
305
|
+
sh_st, sw_st = pair(stride)
|
|
306
|
+
pad_h, pad_w = pair(padding)
|
|
307
|
+
dh, dw = pair(dilation)
|
|
308
|
+
|
|
309
|
+
if not isinstance(proxy_or_tensor, FusionUpsampleProxy):
|
|
310
|
+
return conv2d_wrapper(
|
|
311
|
+
proxy_or_tensor, weight, bias,
|
|
312
|
+
stride=(sh_st, sw_st), padding=(pad_h, pad_w),
|
|
313
|
+
dilation=(dh, dw), groups=groups,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
proxy = proxy_or_tensor
|
|
317
|
+
pre_input = proxy.pre_input
|
|
318
|
+
up_sh, up_sw = proxy.scales_h, proxy.scales_w
|
|
319
|
+
N, _, OH, OW = proxy.output_shape
|
|
320
|
+
|
|
321
|
+
out_c, _, kh, kw = weight.shape
|
|
322
|
+
conv_out_h = (OH + 2 * pad_h - dh * (kh - 1) - 1) // sh_st + 1
|
|
323
|
+
conv_out_w = (OW + 2 * pad_w - dw * (kw - 1) - 1) // sw_st + 1
|
|
324
|
+
|
|
325
|
+
output = NBXTensor.empty(
|
|
326
|
+
(N, out_c, conv_out_h, conv_out_w),
|
|
327
|
+
device=pre_input.device, dtype=pre_input.dtype,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
pre_h = pre_input.shape[2]
|
|
331
|
+
from nbxtensor.ops.shape_wrappers import constant_pad_nd_wrapper
|
|
332
|
+
# The bands over the UPSAMPLED extent are `launch_keys.tiled_conv2d_bands` — one cut with the
|
|
333
|
+
# standalone tiled conv and the derived census (the halo rounded to whole strides).
|
|
334
|
+
from nbxtensor.ops.launch_keys import tiled_conv2d_bands
|
|
335
|
+
for (oh_start, oh_end, up_clamped_start, up_clamped_end,
|
|
336
|
+
pad_top_real, pad_bot_real, skip) in tiled_conv2d_bands(
|
|
337
|
+
OH, conv_out_h, kh, sh_st, dh, pad_h, tile_factor):
|
|
338
|
+
|
|
339
|
+
# Pre-upsample rows that produce [up_clamped_start, up_clamped_end).
|
|
340
|
+
# nearest mapping: pre_row = floor(up_row / up_sh).
|
|
341
|
+
pre_start = max(0, int(up_clamped_start // up_sh))
|
|
342
|
+
pre_end = min(pre_h, int((up_clamped_end - 1) // up_sh) + 1)
|
|
343
|
+
|
|
344
|
+
# POINT 6 FIX (P-SANA-4KPX-RUNTIME): slicing on the H dimension of
|
|
345
|
+
# an NCHW tensor produces a non-contiguous view (the C stride keeps
|
|
346
|
+
# its original H-based value while the slice covers a smaller H).
|
|
347
|
+
# The downstream Triton wrappers (`upsample_nearest2d_wrapper`,
|
|
348
|
+
# `conv2d_wrapper`) use flat 1D indexing that ASSUMES contiguous
|
|
349
|
+
# input — fed a strided view they read wrong memory and produce
|
|
350
|
+
# garbage (94% of elements differ by >1.0 vs torch reference,
|
|
351
|
+
# measured at conv::55 on Sana 4Kpx). The torch path
|
|
352
|
+
# (`_fused_upsample_conv2d_torch`) calls `.contiguous()` at the
|
|
353
|
+
# same two points; mirror that here. Bands where the slice covers
|
|
354
|
+
# the full H (first band when `tile_factor==1`) hit the early-
|
|
355
|
+
# return path in `NBXTensor.contiguous()` at zero cost.
|
|
356
|
+
pre_band = pre_input[:, :, pre_start:pre_end, :].contiguous()
|
|
357
|
+
up_band = upsample_nearest2d_wrapper(
|
|
358
|
+
pre_band,
|
|
359
|
+
output_size=[int(pre_band.shape[2] * up_sh), int(pre_band.shape[3] * up_sw)],
|
|
360
|
+
scales_h=up_sh, scales_w=up_sw,
|
|
361
|
+
)
|
|
362
|
+
up_offset_local = up_clamped_start - int(pre_start * up_sh)
|
|
363
|
+
up_local_h = up_clamped_end - up_clamped_start
|
|
364
|
+
up_band = up_band[:, :, up_offset_local:up_offset_local + up_local_h, :].contiguous()
|
|
365
|
+
|
|
366
|
+
# Apply boundary padding (image-edge halo + clipped over-read).
|
|
367
|
+
# Internal band frontiers get pad_top/bot_real == 0 because halo
|
|
368
|
+
# already provides real rows.
|
|
369
|
+
if pad_top_real > 0 or pad_bot_real > 0 or pad_w > 0:
|
|
370
|
+
up_band = constant_pad_nd_wrapper(
|
|
371
|
+
up_band, [pad_w, pad_w, pad_top_real, pad_bot_real], value=0.0,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Conv with padding=(0,0) — halo + image-edge padding already in up_band.
|
|
375
|
+
conv_band = conv2d_wrapper(
|
|
376
|
+
up_band, weight, None,
|
|
377
|
+
stride=(sh_st, sw_st), padding=(0, 0),
|
|
378
|
+
dilation=(dh, dw), groups=groups,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
actual_band_h = oh_end - oh_start
|
|
382
|
+
# P-NBX-TILED-CONV2D-SMALL-SCALE step 2: halo_top offset on the
|
|
383
|
+
# conv_band read side. Internal band frontiers (halo_top > 0)
|
|
384
|
+
# write F.conv2d output row `oh_start + halo_top - 1` at
|
|
385
|
+
# output row `oh_start` without this offset.
|
|
386
|
+
avail_after_halo = conv_band.shape[2] - skip
|
|
387
|
+
if avail_after_halo < actual_band_h:
|
|
388
|
+
actual_band_h = avail_after_halo
|
|
389
|
+
# Bias add inside band — keeps the broadcast bounded to one band's
|
|
390
|
+
# (N, out_c, actual_band_h, conv_out_w) instead of the full output
|
|
391
|
+
# (8 GiB broadcast on Sana 4Kpx 4096×4096 fp16). Mathematically
|
|
392
|
+
# identical (bias is per-channel, applies element-wise on H,W).
|
|
393
|
+
if bias is not None:
|
|
394
|
+
_conv_bias_inplace(conv_band, bias) # in place, as every conv wrapper adds its bias
|
|
395
|
+
output[:, :, oh_start:oh_start + actual_band_h, :] = conv_band[:, :, skip:skip + actual_band_h, :]
|
|
396
|
+
del pre_band, up_band, conv_band # released before the next band allocates its own
|
|
397
|
+
|
|
398
|
+
return output
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# Phase 1 opt-in cleanup: mark op-level tiling NBX interceptors as
|
|
402
|
+
# self-managing dtype. TritonSequence._compile_op checks this flag to
|
|
403
|
+
# skip the DtypeEngine wrap for these ops. Justified because:
|
|
404
|
+
# - fused_upsample_conv2d may receive a FusionUpsampleProxy as arg[0]
|
|
405
|
+
# (no nbx_dtype attribute → DtypeEngine wrap would no-op anyway)
|
|
406
|
+
# - the nested per-band calls delegate to conv2d_wrapper which is
|
|
407
|
+
# itself self-managed (narrowing + output = compute_dtype)
|
|
408
|
+
# - rms_norm tiled wrapper delegates to the rms_norm wrapper which
|
|
409
|
+
# follows AMP_FP32_OPS via DtypeEngine (or the opt-in cast-back
|
|
410
|
+
# variant when activations_fp16_safe is enabled)
|
|
411
|
+
tiled_upsample_conv2d.self_manages_dtype = True
|
|
412
|
+
tiled_conv2d.self_manages_dtype = True
|
|
413
|
+
# (the public dispatchers, mode-agnostic, are marked in the engine's fused_upsample_conv_torch.py)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Gather along dim — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Approach from FlagGems: work on flattened index space. For each element in the
|
|
4
|
+
index tensor, compute its multi-dim position, load the gather index, then load
|
|
5
|
+
from source replacing the gather dim's index with the loaded value.
|
|
6
|
+
|
|
7
|
+
This static kernel handles any rank by taking pre-computed strides as args.
|
|
8
|
+
The wrapper decomposes the gather dim into (outer, gather, inner) strides.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import triton
|
|
12
|
+
import triton.language as tl
|
|
13
|
+
|
|
14
|
+
@triton.jit
|
|
15
|
+
def gather_kernel(
|
|
16
|
+
inp, index, out,
|
|
17
|
+
N,
|
|
18
|
+
inp_dim_stride,
|
|
19
|
+
idx_stride_outer, idx_stride_dim, idx_stride_inner,
|
|
20
|
+
inp_stride_outer, inp_stride_inner,
|
|
21
|
+
out_stride_outer, out_stride_dim, out_stride_inner,
|
|
22
|
+
outer_size, dim_size, inner_size,
|
|
23
|
+
BLOCK_SIZE: tl.constexpr,
|
|
24
|
+
):
|
|
25
|
+
"""Gather kernel — works for any tensor rank via pre-computed strides.
|
|
26
|
+
|
|
27
|
+
The wrapper reshapes the problem into (outer, dim, inner) where:
|
|
28
|
+
- outer = product of dims before gather dim
|
|
29
|
+
- dim = gather dim size (from index tensor)
|
|
30
|
+
- inner = product of dims after gather dim
|
|
31
|
+
"""
|
|
32
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
33
|
+
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)
|
|
34
|
+
mask = offset < N
|
|
35
|
+
|
|
36
|
+
# Decompose flat offset into (outer, dim, inner)
|
|
37
|
+
inner_idx = offset % inner_size
|
|
38
|
+
temp = offset // inner_size
|
|
39
|
+
dim_idx = temp % dim_size
|
|
40
|
+
outer_idx = temp // dim_size
|
|
41
|
+
|
|
42
|
+
# Load the gather index value
|
|
43
|
+
idx_offset = outer_idx * idx_stride_outer + dim_idx * idx_stride_dim + inner_idx * idx_stride_inner
|
|
44
|
+
cur_index = tl.load(index + idx_offset, mask=mask, other=0)
|
|
45
|
+
|
|
46
|
+
# Load from input: same outer/inner position, but use cur_index for the gather dim
|
|
47
|
+
inp_offset = outer_idx * inp_stride_outer + cur_index * inp_dim_stride + inner_idx * inp_stride_inner
|
|
48
|
+
val = tl.load(inp + inp_offset, mask=mask, other=0.0)
|
|
49
|
+
|
|
50
|
+
# Store to output
|
|
51
|
+
out_offset = outer_idx * out_stride_outer + dim_idx * out_stride_dim + inner_idx * out_stride_inner
|
|
52
|
+
tl.store(out + out_offset, val, mask=mask)
|
nbxtensor/ops/ge.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Greater than or equal — pure @triton.jit kernel."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
|
|
6
|
+
@triton.jit
|
|
7
|
+
def ge_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 ge_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)
|
nbxtensor/ops/gelu.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""GELU — pure @triton.jit kernel (exact and approximate)."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
|
|
6
|
+
from ._common import tanh_fn
|
|
7
|
+
|
|
8
|
+
@triton.jit
|
|
9
|
+
def gelu_forward_kernel(
|
|
10
|
+
input_ptr, output_ptr,
|
|
11
|
+
n_elements,
|
|
12
|
+
approximate: tl.constexpr,
|
|
13
|
+
BLOCK_SIZE: tl.constexpr,
|
|
14
|
+
):
|
|
15
|
+
"""GELU forward. approximate=True for tanh approximation, False for exact."""
|
|
16
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
17
|
+
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)
|
|
18
|
+
mask = offset < n_elements
|
|
19
|
+
|
|
20
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
21
|
+
x_fp32 = x.to(tl.float32)
|
|
22
|
+
|
|
23
|
+
if approximate:
|
|
24
|
+
# Tanh approximation: 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))
|
|
25
|
+
cdf = 0.5 * (1.0 + tanh_fn(0.7978845608 * x_fp32 * (1.0 + 0.044715 * x_fp32 * x_fp32)))
|
|
26
|
+
else:
|
|
27
|
+
# Exact GELU: x * 0.5 * (1 + erf(x / sqrt(2)))
|
|
28
|
+
cdf = 0.5 * (1.0 + tl.math.erf(0.707106781 * x_fp32))
|
|
29
|
+
|
|
30
|
+
out = cdf * x_fp32
|
|
31
|
+
tl.store(output_ptr + offset, out, mask=mask)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Vector (SIMT) fp16 GEMV — the decode projection kernel, no tl.dot.
|
|
2
|
+
|
|
3
|
+
Second member of the SIMT decode family (after ops/decode_attn_vec.py,
|
|
4
|
+
adopted 2026-08-23). The FlagGems-ported `mv_kernel` runs the row's
|
|
5
|
+
193 per-token projections at ~280 GB/s effective (7.1 ms/token, 36 us
|
|
6
|
+
per call measured as IN-GRAPH kernel time — launch overhead already
|
|
7
|
+
excluded by the CUDA-graph replay, so the cost is kernel structure):
|
|
8
|
+
its (BLOCK_N=64, BLOCK_M=256) fp32 accumulator tile is 64 KB of
|
|
9
|
+
registers per CTA (spill class), and its N/64 grid puts 32 programs on
|
|
10
|
+
80 SMs at N=2048.
|
|
11
|
+
|
|
12
|
+
The 2026-08-24 web research (R16) says the structure for OUR layout —
|
|
13
|
+
weights pre-transposed to row-major [N, K], K contiguous per output
|
|
14
|
+
row — is llama.cpp's `mmvf.cu` shape: a program owns a small group of
|
|
15
|
+
output rows, its threads stride K with wide vectorized loads, fp32
|
|
16
|
+
scalar accumulators, fixed-order reduction (llama.cpp: warp shuffle +
|
|
17
|
+
smem, deterministic, no atomics; grid = rows). GemLite's Triton GEMV
|
|
18
|
+
splits K across programs and reduces with `tl.atomic_add` — refused
|
|
19
|
+
here: run-to-run fp ordering nondeterminism, the exact class the
|
|
20
|
+
replay verify and the drift gate forbid. TRT-LLM's cudaCoreGemm keeps
|
|
21
|
+
all of K inside one block for the same reason.
|
|
22
|
+
|
|
23
|
+
Triton translation: acc is (BLOCK_N,) — reduced PER K-CHUNK
|
|
24
|
+
(`tl.sum(a_tile * b_chunk[None, :], 1)`), 256x less register state
|
|
25
|
+
than the (BLOCK_N, BLOCK_K) end-reduced tile; BLOCK_N small (8-16
|
|
26
|
+
rows) so the N-grid alone fills the 80 SMs (N=2048 -> 128-256
|
|
27
|
+
programs); `tl.multiple_of`/`tl.max_contiguous` hints on the
|
|
28
|
+
K-contiguous weight offsets so ptxas emits 128-bit loads (llama.cpp
|
|
29
|
+
PR #9816: vectorizing exactly these loads was worth 1.27x on HBM
|
|
30
|
+
parts); num_stages=1 (no async-copy engine on sm_70).
|
|
31
|
+
|
|
32
|
+
Deterministic by construction: one program per output-row group, all
|
|
33
|
+
of K reduced inside it in fixed chunk order, no atomics.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import triton
|
|
37
|
+
import triton.language as tl
|
|
38
|
+
|
|
39
|
+
from nbxtensor.ops._configs import nbx_autotune, gemv_vec_configs, prune_gemv_vec
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# rows per program, K chunk, warps, stages: the certified configuration of the key (workstream 1, small-M GEMM), the
|
|
43
|
+
# key the derived census forms (`launch_keys.mv_launches`)
|
|
44
|
+
@nbx_autotune(configs=gemv_vec_configs(), key=['N_BUCKET', 'K_BUCKET'],
|
|
45
|
+
prune_configs_by={'early_config_prune': prune_gemv_vec}, cache_results=True)
|
|
46
|
+
@triton.jit
|
|
47
|
+
def gemv_vec_kernel(
|
|
48
|
+
A_ptr, # [N, K] weights, row-major (pre-transposed), fp16
|
|
49
|
+
B_ptr, # [K] activation (fp32 on Volta via the AMP upcast)
|
|
50
|
+
C_ptr, # [N] out
|
|
51
|
+
N, K, N_BUCKET, K_BUCKET,
|
|
52
|
+
stride_an, stride_am,
|
|
53
|
+
stride_bm,
|
|
54
|
+
stride_cn,
|
|
55
|
+
BLOCK_N: tl.constexpr, # output rows per program (small: 8-16)
|
|
56
|
+
BLOCK_K: tl.constexpr, # K-chunk width (wide: 128-512)
|
|
57
|
+
):
|
|
58
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
59
|
+
offs_n = pid * BLOCK_N + tl.arange(0, BLOCK_N)
|
|
60
|
+
mask_n = offs_n < N
|
|
61
|
+
|
|
62
|
+
acc = tl.zeros((BLOCK_N,), dtype=tl.float32)
|
|
63
|
+
offs_k = tl.arange(0, BLOCK_K)
|
|
64
|
+
# The weight rows are K-contiguous (stride_am == 1 on the
|
|
65
|
+
# pre-transposed layout): tell the compiler so the loads vectorize
|
|
66
|
+
# to 128-bit transactions.
|
|
67
|
+
offs_k = tl.max_contiguous(tl.multiple_of(offs_k, BLOCK_K), BLOCK_K)
|
|
68
|
+
|
|
69
|
+
a_ptrs = A_ptr + offs_n[:, None] * stride_an + offs_k[None, :] * stride_am
|
|
70
|
+
b_ptrs = B_ptr + offs_k * stride_bm
|
|
71
|
+
for k0 in range(0, K, BLOCK_K):
|
|
72
|
+
mask_k = k0 + offs_k < K
|
|
73
|
+
a = tl.load(a_ptrs, mask=mask_n[:, None] & mask_k[None, :],
|
|
74
|
+
other=0.0).to(tl.float32)
|
|
75
|
+
b = tl.load(b_ptrs, mask=mask_k, other=0.0).to(tl.float32)
|
|
76
|
+
acc += tl.sum(a * b[None, :], 1)
|
|
77
|
+
a_ptrs += BLOCK_K * stride_am
|
|
78
|
+
b_ptrs += BLOCK_K * stride_bm
|
|
79
|
+
|
|
80
|
+
tl.store(C_ptr + offs_n * stride_cn, acc, mask=mask_n)
|
nbxtensor/ops/glu.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""GLU (Gated Linear Unit) — pure @triton.jit kernel. Ported from attorch (MIT).
|
|
2
|
+
|
|
3
|
+
Supports arbitrary activation functions via the gate path.
|
|
4
|
+
SwiGLU = GLU with SiLU activation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import triton
|
|
8
|
+
import triton.language as tl
|
|
9
|
+
|
|
10
|
+
from ._common import sigmoid
|
|
11
|
+
|
|
12
|
+
@triton.jit
|
|
13
|
+
def glu_forward_kernel(
|
|
14
|
+
input1_ptr, input2_ptr, output_ptr,
|
|
15
|
+
size,
|
|
16
|
+
act_func: tl.constexpr,
|
|
17
|
+
BLOCK_SIZE: tl.constexpr,
|
|
18
|
+
):
|
|
19
|
+
"""GLU forward: out = input1 * act(input2).
|
|
20
|
+
|
|
21
|
+
input1, input2: [size] (the two halves of the gated input)
|
|
22
|
+
act_func: 'sigmoid' (standard GLU), 'silu' (SwiGLU), 'gelu', 'relu'
|
|
23
|
+
"""
|
|
24
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
25
|
+
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)
|
|
26
|
+
mask = offset < size
|
|
27
|
+
|
|
28
|
+
x1 = tl.load(input1_ptr + offset, mask=mask)
|
|
29
|
+
x2 = tl.load(input2_ptr + offset, mask=mask)
|
|
30
|
+
|
|
31
|
+
x2_fp32 = x2.to(tl.float32)
|
|
32
|
+
if act_func == 'sigmoid':
|
|
33
|
+
gate = sigmoid(x2_fp32)
|
|
34
|
+
elif act_func == 'silu':
|
|
35
|
+
gate = x2_fp32 * sigmoid(x2_fp32)
|
|
36
|
+
elif act_func == 'gelu':
|
|
37
|
+
gate = 0.5 * x2_fp32 * (1.0 + tl.math.erf(0.707106781 * x2_fp32))
|
|
38
|
+
elif act_func == 'relu':
|
|
39
|
+
gate = tl.maximum(0, x2_fp32)
|
|
40
|
+
else:
|
|
41
|
+
gate = sigmoid(x2_fp32)
|
|
42
|
+
|
|
43
|
+
tl.store(output_ptr + offset, x1 * gate, mask=mask)
|