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,1482 @@
|
|
|
1
|
+
"""Convolution and pooling wrappers: conv1d/2d/3d (strided, depthwise, transposed, chunked), im2col, pooling. Cut out of wrappers.py by family (M2, 2026-10-29), unchanged; shared launch layer and run context stay in wrappers.py."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import triton
|
|
7
|
+
from nbxtensor.ops.autotune_bucket import bucket_of as _bucket_of
|
|
8
|
+
from nbxtensor.ops import launch_keys as _lk
|
|
9
|
+
from nbxtensor.tensor import NBXTensor
|
|
10
|
+
from nbxtensor.dtype.nbx_dtype import NBXDtype, dtype_size
|
|
11
|
+
from nbxtensor.memory.allocator import DeviceAllocator
|
|
12
|
+
from nbxtensor.backend.runtime import set_device
|
|
13
|
+
from nbxtensor.shape import MAX_NDIM as _NBX_MAX_NDIM, broadcast_shapes, max_linear_offset
|
|
14
|
+
from nbxtensor.dtype.itemsize import canonical_name
|
|
15
|
+
from nbxtensor.dtype.promotion import binary_result, float_math_input
|
|
16
|
+
from nbxtensor.dtype.nbx_dtype import parse_dtype
|
|
17
|
+
from nbxtensor.memory.allocator import DeviceOOMError
|
|
18
|
+
from nbxtensor.backend.capabilities import fa_min_tile as _fa_min_tile
|
|
19
|
+
from nbxtensor.backend.capabilities import reduce_tile_max as _reduce_tile_max
|
|
20
|
+
from nbxtensor.backend.faults import device_fault_buffer, device_fault_code_cached, fault_channel
|
|
21
|
+
from nbxtensor.tensor import _upload_int64_array as _nbx_upload_int64, _saturating_cast as _nbx_saturating_cast
|
|
22
|
+
from nbxtensor.ops._configs import sdpa_block_ceiling as _sdpa_block_ceiling
|
|
23
|
+
from nbxtensor.ops._configs import sdpa_launch_meta as _sdpa_launch_meta
|
|
24
|
+
from nbxtensor.ops._configs import matrix_unit as _matrix_unit, matrix_unit_mm_tile as _matrix_unit_mm_tile
|
|
25
|
+
from nbxtensor.ops._configs import largest_tile_within_smem as _largest_tile_within_smem
|
|
26
|
+
from nbxtensor.backend import launcher as _nbx_launcher
|
|
27
|
+
from nbxtensor.backend.launcher import install as _install_launcher
|
|
28
|
+
from nbxtensor.ops.relu import relu_forward_kernel
|
|
29
|
+
from nbxtensor.ops.silu import silu_forward_kernel
|
|
30
|
+
from nbxtensor.ops.gelu import gelu_forward_kernel
|
|
31
|
+
from nbxtensor.ops.sigmoid import sigmoid_forward_kernel
|
|
32
|
+
from nbxtensor.ops.tanh import tanh_forward_kernel
|
|
33
|
+
from nbxtensor.ops.hardsigmoid import hardsigmoid_forward_kernel
|
|
34
|
+
from nbxtensor.ops.hardswish import hardswish_forward_kernel
|
|
35
|
+
from nbxtensor.ops.leaky_relu import leaky_relu_forward_kernel
|
|
36
|
+
from nbxtensor.ops.elu import elu_forward_kernel
|
|
37
|
+
from nbxtensor.ops.mish import mish_forward_kernel
|
|
38
|
+
from nbxtensor.ops.selu import selu_forward_kernel
|
|
39
|
+
from nbxtensor.ops.neg import neg_forward_kernel
|
|
40
|
+
from nbxtensor.ops.exp import exp_forward_kernel
|
|
41
|
+
from nbxtensor.ops.sin import sin_forward_kernel
|
|
42
|
+
from nbxtensor.ops.cos import cos_forward_kernel
|
|
43
|
+
from nbxtensor.ops.rsqrt import rsqrt_forward_kernel
|
|
44
|
+
from nbxtensor.ops.sqrt import sqrt_forward_kernel
|
|
45
|
+
from nbxtensor.ops.abs import abs_forward_kernel
|
|
46
|
+
from nbxtensor.ops.log import log_forward_kernel
|
|
47
|
+
from nbxtensor.ops.reciprocal import reciprocal_forward_kernel
|
|
48
|
+
from nbxtensor.ops.pow import pow_forward_kernel
|
|
49
|
+
from nbxtensor.ops.clamp import clamp_forward_kernel
|
|
50
|
+
from nbxtensor.ops.erf import erf_forward_kernel
|
|
51
|
+
from nbxtensor.ops.copy import copy_forward_kernel
|
|
52
|
+
from nbxtensor.ops.add import add_forward_kernel, add_scalar_kernel, add_scalar_dev_kernel, add_bias_broadcast_kernel
|
|
53
|
+
from nbxtensor.ops.binary_strided import add_strided_nd_kernel, mul_strided_nd_kernel, sub_strided_nd_kernel, div_strided_nd_kernel, add_scalar_strided_nd_kernel, mul_scalar_strided_nd_kernel
|
|
54
|
+
from nbxtensor.ops.mul import mul_forward_kernel, mul_scalar_kernel, mul_scalar_dev_kernel
|
|
55
|
+
from nbxtensor.ops.div import div_forward_kernel, div_scalar_kernel, div_scalar_dev_kernel, int_div_kernel
|
|
56
|
+
from nbxtensor.ops.sub import sub_forward_kernel, rsub_forward_kernel
|
|
57
|
+
from nbxtensor.ops.where import where_forward_kernel
|
|
58
|
+
from nbxtensor.ops.maximum import maximum_forward_kernel
|
|
59
|
+
from nbxtensor.ops.minimum import minimum_forward_kernel
|
|
60
|
+
from nbxtensor.ops.masked_fill import masked_fill_forward_kernel
|
|
61
|
+
from nbxtensor.ops.gt import gt_forward_kernel, gt_scalar_kernel
|
|
62
|
+
from nbxtensor.ops.ge import ge_forward_kernel, ge_scalar_kernel
|
|
63
|
+
from nbxtensor.ops.lt import lt_forward_kernel, lt_scalar_kernel
|
|
64
|
+
from nbxtensor.ops.eq import eq_forward_kernel, eq_scalar_kernel
|
|
65
|
+
from nbxtensor.ops.ne import ne_forward_kernel, ne_scalar_kernel
|
|
66
|
+
from nbxtensor.ops.layernorm import layer_norm_forward_kernel
|
|
67
|
+
from nbxtensor.ops.rmsnorm import rms_norm_forward_kernel
|
|
68
|
+
from nbxtensor.ops.softmax import softmax_forward_kernel, softmax_forward_looped_kernel
|
|
69
|
+
from nbxtensor.ops.matmul import matmul_kernel, addmm_kernel
|
|
70
|
+
from nbxtensor.ops._autotune_policy import NBX_FORCE_FP32_ACCUM
|
|
71
|
+
from nbxtensor.ops.fused_moe import fused_moe_kernel, silu_and_mul_kernel, silu_mul_split_kernel
|
|
72
|
+
from nbxtensor.ops.dtype_convert import bf16_to_fp16_kernel
|
|
73
|
+
from nbxtensor.ops.embedding import embedding_kernel, EMBEDDING_OOB
|
|
74
|
+
from nbxtensor.ops.sum import sum_forward_kernel
|
|
75
|
+
from nbxtensor.ops.mean import mean_forward_kernel
|
|
76
|
+
from nbxtensor.ops.amax import amax_forward_kernel
|
|
77
|
+
from nbxtensor.ops.glu import glu_forward_kernel
|
|
78
|
+
from nbxtensor.ops.le import le_forward_kernel, le_scalar_kernel
|
|
79
|
+
from nbxtensor.ops.softplus import softplus_forward_kernel
|
|
80
|
+
from nbxtensor.ops.dropout import dropout_inference_kernel
|
|
81
|
+
from nbxtensor.ops.upsample_nearest2d import upsample_nearest2d_kernel
|
|
82
|
+
from nbxtensor.ops.groupnorm import group_norm_forward_kernel
|
|
83
|
+
from nbxtensor.ops.index_select import index_select_kernel, INDEX_SELECT_OOB
|
|
84
|
+
from nbxtensor.ops.triu import triu_kernel, triu_batch_kernel
|
|
85
|
+
from nbxtensor.ops.tril import tril_kernel, tril_batch_kernel
|
|
86
|
+
from nbxtensor.ops.argmax import argmax_kernel_1, argmax_kernel_2, argmax_kernel_inner
|
|
87
|
+
from nbxtensor.ops.argmin import argmin_kernel_1, argmin_kernel_2, argmin_kernel
|
|
88
|
+
from nbxtensor.ops.conv2d import conv2d_forward_kernel
|
|
89
|
+
from nbxtensor.ops.depthwise_conv2d import depthwise_conv2d_kernel
|
|
90
|
+
from nbxtensor.ops.batch_norm import batch_norm_forward_kernel
|
|
91
|
+
from nbxtensor.ops.cumsum import scan_part_sum_kernel, add_base_sum_kernel
|
|
92
|
+
from nbxtensor.ops.scatter_op import scatter_kernel, scatter_add_kernel, scatter_reduce_amax_kernel, scatter_reduce_amin_kernel
|
|
93
|
+
from nbxtensor.ops.gather_op import gather_kernel
|
|
94
|
+
from nbxtensor.ops.avg_pool2d import avg_pool2d_forward_kernel
|
|
95
|
+
from nbxtensor.ops.max_pool2d import max_pool2d_forward_kernel
|
|
96
|
+
from nbxtensor.ops.max_pool3d import max_pool3d_forward_kernel
|
|
97
|
+
from nbxtensor.ops.cross_entropy import cross_entropy_loss_forward_kernel
|
|
98
|
+
from nbxtensor.ops.exp2 import exp2_forward_kernel
|
|
99
|
+
from nbxtensor.ops.tan import tan_forward_kernel
|
|
100
|
+
from nbxtensor.ops.celu import celu_forward_kernel
|
|
101
|
+
from nbxtensor.ops.log_sigmoid import log_sigmoid_forward_kernel
|
|
102
|
+
from nbxtensor.ops.isfinite import isfinite_forward_kernel
|
|
103
|
+
from nbxtensor.ops.isinf import isinf_forward_kernel
|
|
104
|
+
from nbxtensor.ops.isnan import isnan_forward_kernel
|
|
105
|
+
from nbxtensor.ops.nan_to_num import nan_to_num_forward_kernel
|
|
106
|
+
from nbxtensor.ops.threshold import threshold_forward_kernel
|
|
107
|
+
from nbxtensor.ops.floor import floor_forward_kernel, ceil_forward_kernel, round_forward_kernel, trunc_forward_kernel
|
|
108
|
+
from nbxtensor.ops.addcdiv import addcdiv_forward_kernel
|
|
109
|
+
from nbxtensor.ops.addcmul import addcmul_forward_kernel
|
|
110
|
+
from nbxtensor.ops.all_reduce import all_kernel_mid, all_kernel_result, all_kernel_dim
|
|
111
|
+
from nbxtensor.ops.any_reduce import any_kernel_mid, any_kernel_result, any_kernel_dim
|
|
112
|
+
from nbxtensor.ops.bitwise_and import bitwise_and_forward_kernel
|
|
113
|
+
from nbxtensor.ops.bitwise_or import bitwise_or_forward_kernel
|
|
114
|
+
from nbxtensor.ops.bitwise_not import bitwise_not_forward_kernel
|
|
115
|
+
from nbxtensor.ops.logical_and import logical_and_forward_kernel
|
|
116
|
+
from nbxtensor.ops.logical_or import logical_or_forward_kernel
|
|
117
|
+
from nbxtensor.ops.logical_not import logical_not_forward_kernel
|
|
118
|
+
from nbxtensor.ops.lerp import lerp_tensor_forward_kernel, lerp_scalar_head_kernel, lerp_scalar_tail_kernel
|
|
119
|
+
from nbxtensor.ops.dot_op import dot_kernel_small, dot_kernel_partial, dot_kernel_reduce
|
|
120
|
+
from nbxtensor.ops.flip_op import flip_1d_kernel, flip_strided_kernel
|
|
121
|
+
from nbxtensor.ops.cat_op import cat_copy_kernel_4
|
|
122
|
+
from nbxtensor.ops.fill_op import fill_kernel
|
|
123
|
+
from nbxtensor.ops.arange_op import arange_kernel
|
|
124
|
+
from nbxtensor.ops.prod import prod_kernel_mid, prod_kernel_result, prod_kernel
|
|
125
|
+
from nbxtensor.ops.min_reduce import min_kernel_mid, min_kernel_result, min_kernel
|
|
126
|
+
from nbxtensor.ops.max_reduce import max_kernel_mid, max_kernel_result, max_kernel
|
|
127
|
+
from nbxtensor.ops.baddbmm_op import baddbmm_kernel
|
|
128
|
+
from nbxtensor.ops.addmv_op import addmv_kernel
|
|
129
|
+
from nbxtensor.ops.mse_loss import mse_loss_partial_kernel, mse_loss_reduce_kernel, mse_loss_none_kernel
|
|
130
|
+
from nbxtensor.ops.nllloss import nll_loss_forward_kernel
|
|
131
|
+
from nbxtensor.ops.std import std_map_kernel, std_reduce_kernel, std_dim_kernel
|
|
132
|
+
from nbxtensor.ops.var import var_kernel_1, var_kernel_2, var_welford_kernel
|
|
133
|
+
from nbxtensor.ops.index_add import index_add_gather_kernel
|
|
134
|
+
from nbxtensor.ops.index_put_op import index_put_kernel, INDEX_PUT_OOB
|
|
135
|
+
from nbxtensor.ops.sort_op import (radix_sort_histogram_kernel,
|
|
136
|
+
radix_sort_scatter_kernel,
|
|
137
|
+
radix_sort_tile_counts_kernel,
|
|
138
|
+
radix_sort_tile_prefix_kernel)
|
|
139
|
+
from nbxtensor.ops.rope import rope_forward_kernel
|
|
140
|
+
from nbxtensor.ops.pixel_shuffle import (
|
|
141
|
+
pixel_shuffle_kernel,
|
|
142
|
+
pixel_shuffle_broadcast_aware_kernel,
|
|
143
|
+
)
|
|
144
|
+
from nbxtensor.ops.pixel_unshuffle import pixel_unshuffle_kernel
|
|
145
|
+
from nbxtensor.ops.upsample_bilinear2d import upsample_bilinear2d_kernel
|
|
146
|
+
from nbxtensor.ops.adaptive_avg_pool2d import adaptive_avg_pool2d_kernel
|
|
147
|
+
from nbxtensor.ops.conv_depthwise2d import conv_depthwise2d_kernel
|
|
148
|
+
from nbxtensor.ops.scaled_softmax import scaled_softmax_kernel
|
|
149
|
+
from nbxtensor.ops.vector_norm import (
|
|
150
|
+
l2_norm_kernel, l1_norm_kernel, linf_norm_kernel, l0_norm_kernel, lp_norm_kernel,
|
|
151
|
+
l2_norm_pass1_kernel, l2_norm_pass2_kernel,
|
|
152
|
+
linf_norm_pass1_kernel, linf_norm_pass2_kernel,
|
|
153
|
+
)
|
|
154
|
+
from nbxtensor.ops.addr import addr_kernel
|
|
155
|
+
from nbxtensor.ops.logical_xor import logical_xor_kernel
|
|
156
|
+
from nbxtensor.ops.bitwise_left_shift import bitwise_left_shift_kernel
|
|
157
|
+
from nbxtensor.ops.bitwise_right_shift import bitwise_right_shift_kernel
|
|
158
|
+
from nbxtensor.ops.rand_op import rand_kernel, randn_kernel
|
|
159
|
+
from nbxtensor.ops.clamp_min import clamp_min_forward_kernel
|
|
160
|
+
from nbxtensor.ops.conv_transpose2d import conv_transpose2d_kernel
|
|
161
|
+
from nbxtensor.ops.cummax import scan_part_max_kernel
|
|
162
|
+
from nbxtensor.ops.cummin import scan_part_min_kernel
|
|
163
|
+
from nbxtensor.ops.count_nonzero import count_nonzero_kernel
|
|
164
|
+
from nbxtensor.ops.trace_op import trace_kernel
|
|
165
|
+
from nbxtensor.ops.linspace_op import linspace_kernel
|
|
166
|
+
from nbxtensor.ops.full_op import full_kernel
|
|
167
|
+
from nbxtensor.ops.stack_op import stack_copy_kernel
|
|
168
|
+
from nbxtensor.ops.repeat_op import repeat_1d_kernel
|
|
169
|
+
from nbxtensor.ops.upsample_nearest3d import upsample_nearest3d_kernel
|
|
170
|
+
from nbxtensor.ops.weight_norm import weight_norm_kernel_first, weight_norm_kernel_last
|
|
171
|
+
from nbxtensor.ops.repeat_interleave import repeat_interleave_tensor_kernel
|
|
172
|
+
from nbxtensor.ops.elementwise_launch import ew_meta as _ew_meta, ew_grid as _ew_grid
|
|
173
|
+
from nbxtensor.ops import tiling_sizes as _ts
|
|
174
|
+
import math
|
|
175
|
+
from nbxtensor.ops import elementwise_wrappers as _ew
|
|
176
|
+
from nbxtensor.ops import index_wrappers as _idx
|
|
177
|
+
from nbxtensor.ops import shape_wrappers as _shp
|
|
178
|
+
from nbxtensor.ops import wrappers as _core
|
|
179
|
+
_POOL_BH, _POOL_BW = 4, 16 # avg_pool2d / max_pool2d
|
|
180
|
+
|
|
181
|
+
# P-TRITON-LIVE-SET-AUDIT (#37) — conv3d-via-conv2d eager-free threshold.
|
|
182
|
+
# The temporal decomposition in _conv3d_via_conv2d materialises several full-
|
|
183
|
+
# resolution copies per temporal-kernel slice (input contiguous-copy, conv2d
|
|
184
|
+
# output, permuted output, accumulator). For large video VAE conv3d (CogVideoX
|
|
185
|
+
# 5D decode at (1,256,13,480,720) = 4.4 GB per copy in fp32), 5-6 of these are
|
|
186
|
+
# live simultaneously → 26-30 GB → single-GPU OOM at aten.convolution. When a
|
|
187
|
+
# per-slice intermediate is at least this many bytes, it is dropped the moment
|
|
188
|
+
# it is consumed so the live set falls to input + accumulator + one transient.
|
|
189
|
+
# free_cuda is cudaFree, which blocks until the device is idle for the relevant
|
|
190
|
+
# stream, so dropping a tensor whose producing/consuming kernel just ran is
|
|
191
|
+
# async-safe (no UAF). GATED so small convs (Wan/Mochi/SANA at their validated
|
|
192
|
+
# configs) keep the EXACT prior code path — no relocated free, byte- and
|
|
193
|
+
# timing-identical. Numerics are unchanged (frees sooner only). 1 GiB default
|
|
194
|
+
# fires only on the multi-GB video-VAE convs.
|
|
195
|
+
_NBX_CONV3D_EAGER_FREE_BYTES = int(
|
|
196
|
+
os.environ.get("NBX_CONV3D_EAGER_FREE_BYTES", str(1 * 1024 * 1024 * 1024)))
|
|
197
|
+
|
|
198
|
+
# Conv3d temporal chunk-streaming — kernel-level tiling INSIDE the wrapper,
|
|
199
|
+
# same lever as the conv2d band-streaming above (P-SANA-4KPX Step 1 class).
|
|
200
|
+
# Even with the #37 eager frees, the one-shot temporal decomposition needs
|
|
201
|
+
# several FULL folded transients simultaneously (temporal pad copy, x2 fold,
|
|
202
|
+
# conv2d output incl. band-streaming machinery, permuted copy, accumulator +
|
|
203
|
+
# accumulate transient) — ~4-5x the activation bytes. cuDNN in compiled mode
|
|
204
|
+
# needs input + output + a bounded workspace for the same op, so a component
|
|
205
|
+
# Prism correctly sized as "fits" for compiled can OOM in triton only
|
|
206
|
+
# (Allegro-TI2V vae_encoder at 720x1280x12f fp32: 5.66 GB per plane →
|
|
207
|
+
# 30.4 GB live at aten.convolution::1 on a 32 GB V100). Gate, two stages:
|
|
208
|
+
# 1. deterministic floor — the folded transient must exceed
|
|
209
|
+
# `tiling_sizes.conv3d_chunk_bytes()` (`conv3d.chunk_bytes`), so
|
|
210
|
+
# small/validated convs never even query the driver;
|
|
211
|
+
# 2. exact-fit check — the one-shot path's real allocation peak is
|
|
212
|
+
# compared to driver-free bytes; when it fits, the EXACT one-shot
|
|
213
|
+
# path runs (byte-identical behavior for every config that is green
|
|
214
|
+
# today, on any hardware — R23), when it cannot fit (guaranteed OOM)
|
|
215
|
+
# the wrapper streams over temporal output chunks whose per-transient
|
|
216
|
+
# size is bounded by this same value. (Stage 2 is now Prism's PLAN
|
|
217
|
+
# decision, `tiling_sizes.conv3d_need` against the op budget.)
|
|
218
|
+
# Chunking splits the folded batch axis (B*T frames): spatial conv2d is
|
|
219
|
+
# independent per frame and the per-frame kt accumulation order is
|
|
220
|
+
# preserved, so the math is unchanged.
|
|
221
|
+
|
|
222
|
+
# Diagnostic trace — when set, every conv2d_wrapper call prints the
|
|
223
|
+
# (in_shape, out_shape, kernel, groups, output_MB) so we can see the actual
|
|
224
|
+
# spatial workload arriving in triton mode. Used by P-SANA-4KPX-RUNTIME
|
|
225
|
+
# Step 1 verification on Sana 4Kpx (was the wrapper-internal band-streaming
|
|
226
|
+
# triggered? what shapes are dominant?).
|
|
227
|
+
_NBX_CONV2D_TRACE = os.environ.get("NBX_CONV2D_TRACE", "0") == "1"
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _conv_width_key(in_h, out_h, in_w, out_w, kw, stride_w, pad_w, dil_w):
|
|
231
|
+
"""The width a convolution's autotune key carries. A convolution whose spatial extent is
|
|
232
|
+
ONE ROW is a 1-D convolution over a sequence — a vocoder's frames, a mel spectrogram's
|
|
233
|
+
time axis — and that extent is the request's (a speech of 1 716 tokens and one of 1 856
|
|
234
|
+
gave chatterbox's vocoder two different exact keys for every layer, 2026-09-21): it keys
|
|
235
|
+
on the profile's ladder like a GEMM's M, the output width derived from the input's top by
|
|
236
|
+
the convolution's own arithmetic so a certifier synthesising at the top forms the same
|
|
237
|
+
key. A 2-D convolution keeps its exact extents (bounded by resolutions and tile edges;
|
|
238
|
+
the ladder measured 37–40 % loss where the conv optimum flips)."""
|
|
239
|
+
return _lk.conv_width_key(in_h, out_h, in_w, out_w, kw, stride_w, pad_w, dil_w)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def conv_transpose_wrapper(
|
|
243
|
+
x, weight, bias=None,
|
|
244
|
+
stride=1, padding=0, dilation=1, output_padding=0, groups=1,
|
|
245
|
+
):
|
|
246
|
+
"""Transposed convolution — aten::convolution(transposed=True).
|
|
247
|
+
|
|
248
|
+
Handles 1D (3D tensors, via H=1 unsqueeze) and 2D (4D tensors). Weight layout
|
|
249
|
+
matches PyTorch ConvTranspose: (C_in, C_out/groups, *K). Scatter-based
|
|
250
|
+
@triton.jit kernel (kernels/ops/conv_transpose2d.py), fp32 accumulation.
|
|
251
|
+
R33-pure: NBXTensor + Triton only.
|
|
252
|
+
|
|
253
|
+
A regular strided conv DOWNSAMPLES (out = in // stride); a transposed conv
|
|
254
|
+
UPSAMPLES: out = (in-1)*stride - 2*pad + dil*(k-1) + output_padding + 1. The
|
|
255
|
+
Kokoro F0/N ProsodyPredictor pool is a depthwise ConvTranspose1d (stride 2)
|
|
256
|
+
that doubles the frame axis (62->124). conv2d_wrapper routes here when
|
|
257
|
+
transposed=True (which it previously dropped when delegating 1D convs).
|
|
258
|
+
"""
|
|
259
|
+
is_1d = (weight.ndim == 3)
|
|
260
|
+
_d = lambda v: v[0] if isinstance(v, (list, tuple)) else v # noqa: E731
|
|
261
|
+
if is_1d and groups == 1 and _d(dilation) == 1 and _d(output_padding) == 0:
|
|
262
|
+
return _conv_transpose1d_gemm(x, weight, bias, _d(stride), _d(padding))
|
|
263
|
+
if is_1d:
|
|
264
|
+
x = x.unsqueeze(2) # [N, C_in, 1, L]
|
|
265
|
+
weight = weight.unsqueeze(2) # [C_in, C_out/g, 1, K]
|
|
266
|
+
def _s(v):
|
|
267
|
+
return v[0] if isinstance(v, (list, tuple)) else v
|
|
268
|
+
sh, sw = 1, _s(stride)
|
|
269
|
+
ph, pw = 0, _s(padding)
|
|
270
|
+
dh, dw = 1, _s(dilation)
|
|
271
|
+
oph, opw = 0, _s(output_padding)
|
|
272
|
+
else:
|
|
273
|
+
def _hw(v):
|
|
274
|
+
if isinstance(v, (list, tuple)):
|
|
275
|
+
return (v[0], v[1] if len(v) > 1 else v[0])
|
|
276
|
+
return (v, v)
|
|
277
|
+
sh, sw = _hw(stride); ph, pw = _hw(padding)
|
|
278
|
+
dh, dw = _hw(dilation); oph, opw = _hw(output_padding)
|
|
279
|
+
|
|
280
|
+
# dtype alignment NARROWING (mirror conv2d_wrapper; fp32 accum in-kernel)
|
|
281
|
+
x_nbx = x.nbx_dtype if hasattr(x, 'nbx_dtype') else x.dtype
|
|
282
|
+
w_nbx = weight.nbx_dtype if hasattr(weight, 'nbx_dtype') else weight.dtype
|
|
283
|
+
if x_nbx != w_nbx:
|
|
284
|
+
_order = (NBXDtype.float16, NBXDtype.bfloat16, NBXDtype.float32)
|
|
285
|
+
narrowest = next(d for d in _order if d in (x_nbx, w_nbx))
|
|
286
|
+
if x_nbx != narrowest:
|
|
287
|
+
x = x.to(narrowest)
|
|
288
|
+
if w_nbx != narrowest:
|
|
289
|
+
weight = weight.to(narrowest)
|
|
290
|
+
|
|
291
|
+
x_c = x.contiguous()
|
|
292
|
+
w_c = weight.contiguous()
|
|
293
|
+
out_dtype = _core._NBX_COMPUTE_DTYPE if _core._NBX_COMPUTE_DTYPE is not None else x.dtype
|
|
294
|
+
|
|
295
|
+
N, C_in, IH, IW = x_c.shape
|
|
296
|
+
_, C_out_per_g, KH, KW = w_c.shape
|
|
297
|
+
C_out = C_out_per_g * groups
|
|
298
|
+
C_in_per_g = C_in // groups
|
|
299
|
+
OH = (IH - 1) * sh - 2 * ph + dh * (KH - 1) + oph + 1
|
|
300
|
+
OW = (IW - 1) * sw - 2 * pw + dw * (KW - 1) + opw + 1
|
|
301
|
+
|
|
302
|
+
output = NBXTensor.empty((N, C_out, OH, OW), device=x_c.device, dtype=out_dtype)
|
|
303
|
+
set_device(x_c)
|
|
304
|
+
BLOCK = 256
|
|
305
|
+
grid = (N * C_out, triton.cdiv(OH * OW, BLOCK))
|
|
306
|
+
conv_transpose2d_kernel[grid](
|
|
307
|
+
x_c, w_c, output,
|
|
308
|
+
N, C_in, IH, IW, C_out, KH, KW, OH, OW,
|
|
309
|
+
sh, sw, ph, pw, dh, dw, C_in_per_g, C_out_per_g,
|
|
310
|
+
BLOCK_SIZE=BLOCK,
|
|
311
|
+
)
|
|
312
|
+
if bias is not None:
|
|
313
|
+
output = _conv_bias_inplace(output, bias)
|
|
314
|
+
if is_1d:
|
|
315
|
+
output = output.squeeze(2)
|
|
316
|
+
return output
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _conv_transpose1d_gemm(x, weight, bias, stride, padding):
|
|
320
|
+
"""A 1-D transposed convolution (groups 1, dilation 1) as a GEMM and an overlap-add: Y = x^T [L_in, C_in] @
|
|
321
|
+
W [C_in, C_out * K] through `mm` (the matrix unit where the profile declares one) — the length on the GEMM's rows,
|
|
322
|
+
the dimension its key classes (the length on its columns gave one key per length: 6 135 on chatterbox, the Mac
|
|
323
|
+
2026-11-03) — then `col2im1d_kernel` gathers each output position's ceil(K / stride) taps. The scatter kernel looped over every input channel and tap per output
|
|
324
|
+
element: the vocoders' upsamplers on a V100 ran 20-307x slower than this (768 -> 384 channels, K 16, stride 8:
|
|
325
|
+
103 ms against 0.34 ms; torch eager 0.21 ms — the kernel-level benchmark, 2026-11-03). Same narrowing and output
|
|
326
|
+
dtype as `conv_transpose_wrapper`; `launch_keys.conv_launches` derives the GEMM's keys."""
|
|
327
|
+
from nbxtensor.ops.linalg_wrappers import mm
|
|
328
|
+
from nbxtensor.ops.conv_transpose2d import col2im1d_kernel
|
|
329
|
+
x_nbx = x.nbx_dtype if hasattr(x, 'nbx_dtype') else x.dtype
|
|
330
|
+
w_nbx = weight.nbx_dtype if hasattr(weight, 'nbx_dtype') else weight.dtype
|
|
331
|
+
if x_nbx != w_nbx:
|
|
332
|
+
_order = (NBXDtype.float16, NBXDtype.bfloat16, NBXDtype.float32)
|
|
333
|
+
narrowest = next(d for d in _order if d in (x_nbx, w_nbx))
|
|
334
|
+
x = x.to(narrowest) if x_nbx != narrowest else x
|
|
335
|
+
weight = weight.to(narrowest) if w_nbx != narrowest else weight
|
|
336
|
+
N, C_in, L_in = x.shape
|
|
337
|
+
_, C_out, K = weight.shape
|
|
338
|
+
L_out = (L_in - 1) * stride - 2 * padding + K
|
|
339
|
+
out_dtype = _core._NBX_COMPUTE_DTYPE if _core._NBX_COMPUTE_DTYPE is not None else x.dtype
|
|
340
|
+
output = NBXTensor.empty((N, C_out, L_out), device=x.device, dtype=out_dtype)
|
|
341
|
+
w_2d = weight.contiguous().reshape(C_in, C_out * K) # [C_in, C_out * K]
|
|
342
|
+
x_c = x.contiguous()
|
|
343
|
+
taps = -(-K // stride)
|
|
344
|
+
meta = _ew_meta(C_out * L_out) # a gather of TAPS loads per element: the element-wise class's certified tile
|
|
345
|
+
for n in range(N):
|
|
346
|
+
y = mm(x_c[n].t().contiguous(), w_2d) # [L_in, C_out * K]
|
|
347
|
+
set_device(y)
|
|
348
|
+
col2im1d_kernel[(C_out, triton.cdiv(L_out, meta['BLOCK_SIZE']))](
|
|
349
|
+
y, output[n], C_out, K, L_in, L_out, stride, padding, TAPS=taps, **meta)
|
|
350
|
+
if bias is not None:
|
|
351
|
+
output = _conv_bias_inplace(output.unsqueeze(2), bias).squeeze(2)
|
|
352
|
+
return output
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _conv3d_via_conv2d(x, weight, bias, stride, padding, dilation, groups):
|
|
356
|
+
"""conv3d via temporal decomposition: conv3d(x, w) = sum over the temporal
|
|
357
|
+
kernel positions kt of conv2d on the kt-th temporally-shifted frames. The
|
|
358
|
+
spatial (H, W) convolution is delegated to conv2d_wrapper (this same triton
|
|
359
|
+
kernel) per temporal slice; the temporal axis is handled by slicing +
|
|
360
|
+
accumulation. Validated bit-close to torch F.conv3d (max|diff| ~3e-5) across
|
|
361
|
+
kt=3 / patch-embed kt=1 / strided / temporal-only configs. R33-pure
|
|
362
|
+
(NBXTensor slice/permute/reshape/add + conv2d_wrapper, no torch).
|
|
363
|
+
"""
|
|
364
|
+
def _triple(v):
|
|
365
|
+
return (v[0], v[1], v[2]) if isinstance(v, (list, tuple)) else (v, v, v)
|
|
366
|
+
st, sh, sw = _triple(stride)
|
|
367
|
+
pt, ph, pw = _triple(padding)
|
|
368
|
+
dt, dh, dw = _triple(dilation)
|
|
369
|
+
B, Cin, T, H, W = x.shape
|
|
370
|
+
Cout, Cin_g, kt, kh, kw = weight.shape
|
|
371
|
+
_t_out = (T + 2 * pt - dt * (kt - 1) - 1) // st + 1
|
|
372
|
+
if _t_out <= 0:
|
|
373
|
+
from nbxtensor.memory.allocator import ImpossibleExtentError
|
|
374
|
+
raise ImpossibleExtentError(
|
|
375
|
+
f"convolution produces no frame: input {tuple(x.shape)}, weight "
|
|
376
|
+
f"{tuple(weight.shape)}, temporal stride {st}, padding {pt}, dilation {dt} -> "
|
|
377
|
+
f"T_out={_t_out}")
|
|
378
|
+
|
|
379
|
+
# The chunked variant is a PLAN decision (`ops/tiling_sizes.py`; the supervisor's
|
|
380
|
+
# decision 2, 2026-09-29): Prism registers `conv3d_chunked_wrapper` as this op's interceptor
|
|
381
|
+
# when the one-shot peak exceeds the op's planned budget. It read the driver's free bytes here,
|
|
382
|
+
# so a key a live run formed depended on the memory free at that instant.
|
|
383
|
+
if pt > 0:
|
|
384
|
+
x = _shp.constant_pad_nd_wrapper(x, [0, 0, 0, 0, pt, pt], 0.0)
|
|
385
|
+
Tp = x.shape[2]
|
|
386
|
+
T_out = (Tp - dt * (kt - 1) - 1) // st + 1
|
|
387
|
+
if st == 1:
|
|
388
|
+
# Every temporal tap read and written IN PLACE: the tap's frames x[b, :, start:start+T_out] permuted to
|
|
389
|
+
# [T_out, Cin, H, W] are a strided view the conv kernel reads through its strides, and its result goes through
|
|
390
|
+
# a [T_out, Cout, Ho, Wo] view of the [B, Cout, T_out, Ho, Wo] output. The two permute copies per tap were 65 %
|
|
391
|
+
# of a VAE conv3d (1,128,9,242,362: 525 of 810 ms; 9 ms each once the copy kernel indexed in 32 bits, Mac
|
|
392
|
+
# 2026-10-21). The first tap writes the output; each later one writes a tap buffer added to it.
|
|
393
|
+
Ho = (H + 2 * ph - dh * (kh - 1) - 1) // sh + 1
|
|
394
|
+
Wo = (W + 2 * pw - dw * (kw - 1) - 1) // sw + 1
|
|
395
|
+
out_dt = _core._NBX_COMPUTE_DTYPE if _core._NBX_COMPUTE_DTYPE is not None else x.dtype
|
|
396
|
+
out = None
|
|
397
|
+
for kti in range(kt):
|
|
398
|
+
start = kti * dt
|
|
399
|
+
w2 = weight[:, :, kti:kti + 1].contiguous().reshape(Cout, Cin_g, kh, kw)
|
|
400
|
+
tap = NBXTensor.empty((B, Cout, T_out, Ho, Wo), device=x.device, dtype=out_dt)
|
|
401
|
+
for b in range(B):
|
|
402
|
+
x2 = x[b][:, start:start + T_out].permute(1, 0, 2, 3)
|
|
403
|
+
conv2d_wrapper(x2, w2, None, (sh, sw), (ph, pw), (dh, dw), False, 0, groups,
|
|
404
|
+
out=tap[b].permute(1, 0, 2, 3))
|
|
405
|
+
# each later tap added INTO the sum (the same add kernel, the same order: the same bits): `out + tap` held
|
|
406
|
+
# the old sum, the tap and the new sum at once — three output-sized buffers where the plan priced one
|
|
407
|
+
# (mochi-1-preview's VAE at the 8 GB rung, 1.5 GB each, 2026-10-28)
|
|
408
|
+
out = tap if out is None else _ew.add_inplace_nbx(out, tap)
|
|
409
|
+
tap = None
|
|
410
|
+
if bias is not None:
|
|
411
|
+
out = out + bias.reshape(1, Cout, 1, 1, 1)
|
|
412
|
+
return out
|
|
413
|
+
|
|
414
|
+
out = None
|
|
415
|
+
_ef = _NBX_CONV3D_EAGER_FREE_BYTES # #37 size-gated eager-free threshold
|
|
416
|
+
for kti in range(kt):
|
|
417
|
+
start = kti * dt
|
|
418
|
+
if st == 1:
|
|
419
|
+
xs = x[:, :, start:start + T_out] # [B,Cin,T_out,H,W]
|
|
420
|
+
else:
|
|
421
|
+
import numpy as np
|
|
422
|
+
idx = NBXTensor.from_numpy(
|
|
423
|
+
(start + st * np.arange(T_out)).astype(np.int64))
|
|
424
|
+
xs = _idx.index_select_wrapper(x, 2, idx)
|
|
425
|
+
x2 = xs.permute(0, 2, 1, 3, 4).contiguous().reshape(B * T_out, Cin, H, W)
|
|
426
|
+
# .contiguous() before reshape: weight[:, :, kti:kti+1] is a non-contiguous
|
|
427
|
+
# dim-2 slice; a flat-indexed reshape would read wrong memory for kti>0
|
|
428
|
+
# (contiguous-guard pattern).
|
|
429
|
+
w2 = weight[:, :, kti:kti + 1].contiguous().reshape(Cout, Cin_g, kh, kw)
|
|
430
|
+
y2 = conv2d_wrapper(x2, w2, None, (sh, sw), (ph, pw), (dh, dw),
|
|
431
|
+
False, 0, groups)
|
|
432
|
+
# #37: conv2d has produced y2, so the input contiguous-copy x2 is dead.
|
|
433
|
+
# Drop it now for large (video-VAE) slices so the live set does not
|
|
434
|
+
# accumulate input + x2 + y2 + y + out simultaneously (CogVideoX 5D
|
|
435
|
+
# decode OOM at aten.convolution). Gated → small convs keep the exact
|
|
436
|
+
# prior path. cudaFree blocks until the conv2d kernel is idle (async-safe).
|
|
437
|
+
if x2._nbytes >= _ef:
|
|
438
|
+
x2 = None
|
|
439
|
+
Ho, Wo = y2.shape[2], y2.shape[3]
|
|
440
|
+
y = y2.reshape(B, T_out, Cout, Ho, Wo).permute(0, 2, 1, 3, 4).contiguous()
|
|
441
|
+
if y2._nbytes >= _ef:
|
|
442
|
+
y2 = None # conv output pre-permute copy is dead once y is materialised
|
|
443
|
+
if out is None:
|
|
444
|
+
out = y
|
|
445
|
+
else:
|
|
446
|
+
out = out + y # old accumulator auto-frees on reassignment
|
|
447
|
+
if y._nbytes >= _ef:
|
|
448
|
+
y = None # addend is dead once out+y is allocated
|
|
449
|
+
if bias is not None:
|
|
450
|
+
out = out + bias.reshape(1, Cout, 1, 1, 1)
|
|
451
|
+
return out
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def conv3d_chunked_wrapper(x, weight, bias=None, stride=1, padding=0, dilation=1,
|
|
455
|
+
transposed=False, output_padding=0, groups=1, *args, **kwargs):
|
|
456
|
+
"""A 3-D convolution Prism planned to chunk (op-level tiling, `conv3d_chunks`): the chunked
|
|
457
|
+
path, the chunk sized from the folded bytes per output frame (`tiling_sizes.chunk_frames`)."""
|
|
458
|
+
def _triple(v):
|
|
459
|
+
return (v[0], v[1], v[2]) if isinstance(v, (list, tuple)) else (v, v, v)
|
|
460
|
+
st, sh, sw = _triple(stride)
|
|
461
|
+
pt, ph, pw = _triple(padding)
|
|
462
|
+
dt, dh, dw = _triple(dilation)
|
|
463
|
+
ds_in = dtype_size(x.nbx_dtype if hasattr(x, 'nbx_dtype') else x._dtype)
|
|
464
|
+
ds_out = dtype_size(_core._NBX_COMPUTE_DTYPE) if _core._NBX_COMPUTE_DTYPE is not None else ds_in
|
|
465
|
+
_need, frame = _ts.conv3d_need(x.shape, weight.shape, (st, sh, sw), (pt, ph, pw), (dt, dh, dw),
|
|
466
|
+
ds_in, ds_out)
|
|
467
|
+
return _conv3d_via_conv2d_chunked(x, weight, bias, st, sh, sw, pt, ph, pw, dt, dh, dw, groups, frame)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _conv3d_via_conv2d_chunked(x, weight, bias, st, sh, sw, pt, ph, pw,
|
|
471
|
+
dt, dh, dw, groups, frame_bytes):
|
|
472
|
+
"""Temporal chunk-streaming variant of _conv3d_via_conv2d — identical
|
|
473
|
+
math, bounded live set (`tiling_sizes.conv3d_need` is the gate).
|
|
474
|
+
|
|
475
|
+
Streams the folded batch axis (B*T_out frames) in chunks sized so each
|
|
476
|
+
transient stays under `tiling_sizes.conv3d_chunk_bytes()`. Per output frame the
|
|
477
|
+
spatial conv2d is independent and the kt accumulation runs in the same
|
|
478
|
+
kti order with the same operand values as the one-shot path, so the
|
|
479
|
+
result is numerically equivalent (launch shapes differ, which may pick
|
|
480
|
+
different autotune configs — same class of variation as the conv2d
|
|
481
|
+
band-streaming). Live set: temporal pad copy + final 5D output +
|
|
482
|
+
~3 chunk-sized transients, vs ~4-5 full folds for the one-shot path.
|
|
483
|
+
R33-pure (NBXTensor slice/permute/reshape/add + conv2d_wrapper +
|
|
484
|
+
strided-scatter __setitem__, no torch).
|
|
485
|
+
|
|
486
|
+
`frame_bytes` = max folded bytes per output frame (input-side fold vs
|
|
487
|
+
conv2d output), computed by the caller's gate.
|
|
488
|
+
"""
|
|
489
|
+
B, Cin, T, H, W = x.shape
|
|
490
|
+
Cout, Cin_g, kt, kh, kw = weight.shape
|
|
491
|
+
if pt > 0:
|
|
492
|
+
x = _shp.constant_pad_nd_wrapper(x, [0, 0, 0, 0, pt, pt], 0.0)
|
|
493
|
+
Tp = x.shape[2]
|
|
494
|
+
T_out = (Tp - dt * (kt - 1) - 1) // st + 1
|
|
495
|
+
tc = _ts.chunk_frames(frame_bytes)
|
|
496
|
+
# Per-kt 2D weight slices, materialised once (tiny; contiguous-guard on
|
|
497
|
+
# the non-contiguous dim-2 slice, same as the one-shot path).
|
|
498
|
+
w2s = [weight[:, :, kti:kti + 1].contiguous().reshape(Cout, Cin_g, kh, kw)
|
|
499
|
+
for kti in range(kt)]
|
|
500
|
+
bias4 = bias.reshape(1, Cout, 1, 1) if bias is not None else None
|
|
501
|
+
out = None
|
|
502
|
+
for t0 in range(0, T_out, tc):
|
|
503
|
+
n = min(tc, T_out - t0)
|
|
504
|
+
acc = None
|
|
505
|
+
for kti in range(kt):
|
|
506
|
+
start = t0 * st + kti * dt
|
|
507
|
+
if st == 1:
|
|
508
|
+
xs = x[:, :, start:start + n] # [B,Cin,n,H,W] view
|
|
509
|
+
else:
|
|
510
|
+
import numpy as np
|
|
511
|
+
idx = NBXTensor.from_numpy(
|
|
512
|
+
(start + st * np.arange(n)).astype(np.int64))
|
|
513
|
+
xs = _idx.index_select_wrapper(x, 2, idx)
|
|
514
|
+
x2 = xs.permute(0, 2, 1, 3, 4).contiguous().reshape(
|
|
515
|
+
B * n, Cin, H, W)
|
|
516
|
+
y2 = conv2d_wrapper(x2, w2s[kti], None, (sh, sw), (ph, pw),
|
|
517
|
+
(dh, dw), False, 0, groups)
|
|
518
|
+
x2 = None # fold copy dead once conv2d returned
|
|
519
|
+
acc = y2 if acc is None else acc + y2
|
|
520
|
+
y2 = None
|
|
521
|
+
if bias4 is not None:
|
|
522
|
+
# Elementwise-identical to the one-shot path's 5D bias add
|
|
523
|
+
# (broadcast over the folded batch axis), chunk-sized transient.
|
|
524
|
+
acc = acc + bias4
|
|
525
|
+
Ho, Wo = acc.shape[2], acc.shape[3]
|
|
526
|
+
y = acc.reshape(B, n, Cout, Ho, Wo).permute(0, 2, 1, 3, 4).contiguous()
|
|
527
|
+
acc = None
|
|
528
|
+
if out is None:
|
|
529
|
+
out_dt = y.nbx_dtype if hasattr(y, 'nbx_dtype') else y.dtype
|
|
530
|
+
out = NBXTensor.empty((B, Cout, T_out, Ho, Wo),
|
|
531
|
+
device=y.device, dtype=out_dt)
|
|
532
|
+
out[:, :, t0:t0 + n] = y # strided-scatter into the temporal slice
|
|
533
|
+
y = None
|
|
534
|
+
return out
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def _conv_bias_inplace(output, bias):
|
|
538
|
+
"""Per-channel conv bias, IN PLACE on the wrapper's own fresh output.
|
|
539
|
+
|
|
540
|
+
Every conv-family wrapper ended with `add(output, bias.view(1,-1,1,1))`
|
|
541
|
+
— an out-of-place broadcast allocating a SECOND full-size map per
|
|
542
|
+
biased convolution. Measured cost: 2450 MB of the 8008 MB tile peak on
|
|
543
|
+
real-esrgan-x8 @1024 (malloc trace, 2026-09-21). The in-place contract
|
|
544
|
+
holds by construction at all four call sites: `output` is the
|
|
545
|
+
wrapper's own fresh contiguous NCHW allocation with no other consumer.
|
|
546
|
+
The bias is cast to the output's dtype (the standard add's wider-dtype
|
|
547
|
+
rule would upcast the full map instead — the very copy this removes).
|
|
548
|
+
Fixed launch config: no autotune key, nothing for any census.
|
|
549
|
+
"""
|
|
550
|
+
from nbxtensor.ops.conv2d import conv2d_bias_inplace_kernel
|
|
551
|
+
bias_c = bias if bias._dtype == output._dtype else bias.to(output._dtype)
|
|
552
|
+
n = output.numel()
|
|
553
|
+
hw = int(output.shape[-2]) * int(output.shape[-1])
|
|
554
|
+
c = int(output.shape[1])
|
|
555
|
+
set_device(output)
|
|
556
|
+
_m = _ew_meta(n)
|
|
557
|
+
conv2d_bias_inplace_kernel[_ew_grid(n, _m)](
|
|
558
|
+
output, bias_c.contiguous(), n, hw, c, BLOCK=_m['BLOCK_SIZE'], num_warps=_m['num_warps'])
|
|
559
|
+
return output
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _conv_offsets_wide(*tensors) -> bool:
|
|
563
|
+
"""64-bit offsets in the convolution kernel when an operand's reach (its last element's offset, over its strides)
|
|
564
|
+
is within a factor of two of 2^31: a masked lane computes an address past the extent (the grid's last block,
|
|
565
|
+
the padding rows) before its mask drops it, and the factor covers that overshoot. Below it 32-bit offsets are
|
|
566
|
+
exact and faster (17 % on Apple's ALUs, Mac 2026-10-20)."""
|
|
567
|
+
reach = max(sum((int(n) - 1) * int(st) for n, st in zip(t.shape, t.stride())) + 1 for t in tensors)
|
|
568
|
+
return reach >= (1 << 30)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def _conv_reads_strided(x) -> bool:
|
|
572
|
+
"""True when the implicit-GEMM conv kernel can read `x` through its strides in place: four non-negative strides
|
|
573
|
+
with the width innermost (a permuted or sliced view of a channel-plane layout). Anything else is copied."""
|
|
574
|
+
st = x.stride()
|
|
575
|
+
return len(st) == 4 and all(int(v) >= 0 for v in st) and (int(st[3]) == 1 or int(x.shape[3]) == 1)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def conv2d_wrapper(
|
|
579
|
+
x, weight, bias=None,
|
|
580
|
+
stride=1, padding=0, dilation=1,
|
|
581
|
+
transposed=False, output_padding=0, groups=1, out=None,
|
|
582
|
+
) :
|
|
583
|
+
"""Convolution forward. Handles both 1D (3D tensors) and 2D (4D tensors).
|
|
584
|
+
Routes to conv1d_wrapper for 3D inputs, and to conv_transpose_wrapper when
|
|
585
|
+
transposed=True (1D or 2D).
|
|
586
|
+
|
|
587
|
+
Spatial band-streaming (P-SANA-4KPX-RUNTIME Step 1): when the output
|
|
588
|
+
tensor would exceed `tiling_sizes.conv2d_band_bytes()` (`conv2d.band_bytes`), the wrapper
|
|
589
|
+
splits along the H output dimension and streams band-by-band. Each band
|
|
590
|
+
re-enters this same wrapper with a smaller H, so the recursion bottoms
|
|
591
|
+
out automatically. The full output stays allocated for downstream
|
|
592
|
+
consumers."""
|
|
593
|
+
# Transposed convolutions (1D or 2D) — route BEFORE the 1D delegation, which
|
|
594
|
+
# historically dropped the transposed/output_padding flags (Kokoro F0/N
|
|
595
|
+
# ConvTranspose1d was computed as a regular strided conv → halved instead of
|
|
596
|
+
# doubled the frame axis).
|
|
597
|
+
if transposed:
|
|
598
|
+
return conv_transpose_wrapper(x, weight, bias, stride, padding, dilation,
|
|
599
|
+
output_padding, groups)
|
|
600
|
+
|
|
601
|
+
# Route 1D convolutions to conv1d_wrapper
|
|
602
|
+
if weight.ndim == 3:
|
|
603
|
+
return conv1d_wrapper(x, weight, bias, stride, padding, dilation, groups)
|
|
604
|
+
|
|
605
|
+
# 5D (video) conv3d -> temporal decomposition into conv2d (R33-pure: reuses
|
|
606
|
+
# this same conv2d kernel per temporal-kernel slice). Wan video models use
|
|
607
|
+
# Conv3D (transformer patch_embed + VAE temporal convs).
|
|
608
|
+
if weight.ndim == 5:
|
|
609
|
+
return _conv3d_via_conv2d(x, weight, bias, stride, padding, dilation, groups)
|
|
610
|
+
|
|
611
|
+
assert weight.ndim == 4
|
|
612
|
+
|
|
613
|
+
# ATen broadcasts single-element stride/padding/dilation lists over
|
|
614
|
+
# both spatial dims (padding="valid" convs record [0] — the
|
|
615
|
+
# MiniCPM-o NaViT patch_embedding hit exactly that as an
|
|
616
|
+
# IndexError here; same contract as the tracer-side estimator).
|
|
617
|
+
if isinstance(stride, (list, tuple)):
|
|
618
|
+
stride_h = stride[0]
|
|
619
|
+
stride_w = stride[1] if len(stride) > 1 else stride_h
|
|
620
|
+
else:
|
|
621
|
+
stride_h = stride_w = stride
|
|
622
|
+
|
|
623
|
+
if isinstance(padding, (list, tuple)):
|
|
624
|
+
pad_h = padding[0] if len(padding) > 0 else 0
|
|
625
|
+
pad_w = padding[1] if len(padding) > 1 else pad_h
|
|
626
|
+
else:
|
|
627
|
+
pad_h = pad_w = padding
|
|
628
|
+
|
|
629
|
+
if isinstance(dilation, (list, tuple)):
|
|
630
|
+
dil_h = dilation[0]
|
|
631
|
+
dil_w = dilation[1] if len(dilation) > 1 else dil_h
|
|
632
|
+
else:
|
|
633
|
+
dil_h = dil_w = dilation
|
|
634
|
+
|
|
635
|
+
N, in_c, in_h, in_w = x.shape
|
|
636
|
+
out_c, _, kh, kw = weight.shape
|
|
637
|
+
out_h = (in_h + 2 * pad_h - dil_h * (kh - 1) - 1) // stride_h + 1
|
|
638
|
+
out_w = (in_w + 2 * pad_w - dil_w * (kw - 1) - 1) // stride_w + 1
|
|
639
|
+
if out_h <= 0 or out_w <= 0:
|
|
640
|
+
from nbxtensor.memory.allocator import ImpossibleExtentError
|
|
641
|
+
raise ImpossibleExtentError(
|
|
642
|
+
f"convolution produces no output: input {tuple(x.shape)}, weight "
|
|
643
|
+
f"{tuple(weight.shape)}, stride ({stride_h}, {stride_w}), padding ({pad_h}, "
|
|
644
|
+
f"{pad_w}), dilation ({dil_h}, {dil_w}) -> out_h={out_h}, out_w={out_w}")
|
|
645
|
+
|
|
646
|
+
# Self-managed dtype doctrine — VRAM-preserving (different from the
|
|
647
|
+
# mm/bmm/addmm doctrine which is accumulation-overflow-protected).
|
|
648
|
+
#
|
|
649
|
+
# mm/bmm/addmm: deep matmul stacks risk fp16 accumulation overflow.
|
|
650
|
+
# Step 1 upcasts input fp16 → fp32 on pre-Ampere, output forced to
|
|
651
|
+
# fp32 (force_fp32 in _matmul_out_dtype). Precision target.
|
|
652
|
+
#
|
|
653
|
+
# conv2d: spatial activation chain (VAE/UNet). The kernel already
|
|
654
|
+
# accumulates in fp32 internally (conv2d.py line 47:
|
|
655
|
+
# `accum = tl.zeros(..., dtype=tl.float32)`), so there is no
|
|
656
|
+
# overflow risk requiring an input fp16→fp32 upcast. The risk here
|
|
657
|
+
# is VRAM (4096×4096 spatial tensors). We therefore:
|
|
658
|
+
# - skip Step 1 (no input upcast)
|
|
659
|
+
# - Step 2 narrows mismatched input/weight to the lowest-precision
|
|
660
|
+
# common dtype (opposite of mm widening — VRAM over precision)
|
|
661
|
+
# - Step 4 sets output dtype = compute_dtype from
|
|
662
|
+
# _NBX_COMPUTE_DTYPE (the per-component Prism dtype set by
|
|
663
|
+
# TritonSequence.run), mirroring what cuDNN does silently in
|
|
664
|
+
# compiled mode. Falls back to x.dtype if not set (run outside
|
|
665
|
+
# a TritonSequence context).
|
|
666
|
+
# Universal hardware: no _NBX_HAS_NATIVE_BF16 gate — same behavior on
|
|
667
|
+
# Volta and Ampere+ since it operates on dtype tags, not hardware
|
|
668
|
+
# capability. R33-pure: NBXTensor.to uses a @triton.jit copy kernel.
|
|
669
|
+
|
|
670
|
+
# Step 0: ensure cuda + cross-device alignment (identical to mm)
|
|
671
|
+
# (already implicit; weight transfer handled by upstream callers / arena)
|
|
672
|
+
|
|
673
|
+
# Step 2: dtype alignment NARROWING (opposite of mm widening)
|
|
674
|
+
x_nbx = x.nbx_dtype if hasattr(x, 'nbx_dtype') else x.dtype
|
|
675
|
+
w_nbx = weight.nbx_dtype if hasattr(weight, 'nbx_dtype') else weight.dtype
|
|
676
|
+
# `launch_keys.conv_dtypes`: the pair narrowed to the narrowest when they differ — the
|
|
677
|
+
# function the derived census keys with.
|
|
678
|
+
x_to, w_to = _lk.conv_dtypes(x_nbx, w_nbx)
|
|
679
|
+
if x_nbx != x_to:
|
|
680
|
+
x = x.to(x_to)
|
|
681
|
+
if w_nbx != w_to:
|
|
682
|
+
weight = weight.to(w_to)
|
|
683
|
+
|
|
684
|
+
# The implicit GEMM reads the input through its four strides, so a strided view goes in where it lies (a conv3d's
|
|
685
|
+
# temporal slice permuted to [T, C, H, W]: its copy was 85 ms of a 235 ms VAE conv3d, Mac 2026-10-21); the
|
|
686
|
+
# depthwise, band and matrix-unit routes take a contiguous copy, as before.
|
|
687
|
+
x_c = x.contiguous() if not x.is_contiguous() and not _conv_reads_strided(x) else x
|
|
688
|
+
# A weight the loader stored KRSC (`conv.weight_layout`) is read where it lies: the kernel
|
|
689
|
+
# takes the weight's four strides; copied back to KCRS it would lose what the layout gains.
|
|
690
|
+
w_c = weight if _lk.is_krsc_weight(weight) else weight.contiguous()
|
|
691
|
+
|
|
692
|
+
# Step 4: output dtype = compute_dtype from per-component Prism context.
|
|
693
|
+
# Falls back to x.dtype when no TritonSequence is active.
|
|
694
|
+
out_dtype = _core._NBX_COMPUTE_DTYPE if _core._NBX_COMPUTE_DTYPE is not None else x.dtype
|
|
695
|
+
|
|
696
|
+
# P-SANA-4KPX-RUNTIME Step 1 — kernel-level spatial band-streaming.
|
|
697
|
+
# When the output tensor would exceed the per-launch threshold (default
|
|
698
|
+
# 4 GiB), split along H and stream band-by-band. Halo handling mirrors
|
|
699
|
+
# tiled_conv2d: each band re-enters this same wrapper with
|
|
700
|
+
# a smaller H, so the recursion bottoms out on its own. The full output
|
|
701
|
+
# remains allocated for downstream consumers in the DAG.
|
|
702
|
+
# x.dtype returns a triton.language dtype; use x.nbx_dtype for the
|
|
703
|
+
# NBXDtype used by dtype_size().
|
|
704
|
+
out_nbx_dtype = x.nbx_dtype if hasattr(x, 'nbx_dtype') else x._dtype
|
|
705
|
+
out_dtype_bytes = dtype_size(out_nbx_dtype)
|
|
706
|
+
if _NBX_CONV2D_TRACE:
|
|
707
|
+
out_mb = N * out_c * out_h * out_w * out_dtype_bytes / 1024 / 1024
|
|
708
|
+
print(f"[CONV2D] in=({N},{in_c},{in_h},{in_w}) out=({N},{out_c},{out_h},{out_w}) "
|
|
709
|
+
f"k=({kh},{kw}) g={groups} out={out_mb:.1f}MB", flush=True)
|
|
710
|
+
|
|
711
|
+
# P-SANA-4KPX-RUNTIME Step 3 — depthwise specialization. The generic
|
|
712
|
+
# im2col conv2d_forward_kernel is structurally inefficient for the
|
|
713
|
+
# depthwise pattern (groups == in_c == out_c, weight (C,1,kh,kw)) on
|
|
714
|
+
# Sana 4Kpx VAE: ~4.8 s per call vs cuDNN dedicated path ~2.6 ms,
|
|
715
|
+
# ~1800x gap. Route to the dedicated stencil kernel instead.
|
|
716
|
+
_route = _lk.conv2d_route(N, in_c, out_c, out_h, out_w, dil_h, dil_w, groups, out_nbx_dtype,
|
|
717
|
+
_ts.conv2d_band_bytes(),
|
|
718
|
+
depthwise_enabled=os.environ.get("NBX_DEPTHWISE_DISABLE", "0") != "1")
|
|
719
|
+
if out is not None and _route in ("depthwise", "band"):
|
|
720
|
+
raise RuntimeError(f"conv2d out=: the {_route} route writes its own result — refused")
|
|
721
|
+
if _route == "depthwise":
|
|
722
|
+
if _NBX_CONV2D_TRACE:
|
|
723
|
+
print(f"[CONV2D] DEPTHWISE path (g={groups})", flush=True)
|
|
724
|
+
return _depthwise_conv2d_dispatch(
|
|
725
|
+
x_c.contiguous(), w_c, bias,
|
|
726
|
+
N, in_c, in_h, in_w, out_h, out_w,
|
|
727
|
+
kh, kw, stride_h, stride_w, pad_h, pad_w,
|
|
728
|
+
out_dtype,
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
if _route == "band":
|
|
732
|
+
if _NBX_CONV2D_TRACE:
|
|
733
|
+
print(f"[CONV2D] BAND-STREAM triggered (out > {_ts.conv2d_band_bytes()/1024/1024/1024:.1f}GiB)", flush=True)
|
|
734
|
+
return _conv2d_band_streamed(
|
|
735
|
+
x_c.contiguous(), w_c, bias,
|
|
736
|
+
N, in_c, in_h, in_w, out_c, out_h, out_w,
|
|
737
|
+
kh, kw, stride_h, stride_w, pad_h, pad_w, dil_h, dil_w,
|
|
738
|
+
groups, out_dtype, out_dtype_bytes,
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
if out is not None:
|
|
742
|
+
# the caller's own [N, out_c, out_h, out_w] view (any strides) receives the result: a conv3d writes each temporal
|
|
743
|
+
# tap straight into its [B, C, T, H, W] layout instead of a contiguous result permuted after
|
|
744
|
+
if (tuple(out.shape) != (N, out_c, out_h, out_w) or bias is not None
|
|
745
|
+
or (out._dtype != out_dtype and out.dtype != out_dtype)):
|
|
746
|
+
raise RuntimeError(f"conv2d out=: a {tuple(out.shape)} {out._dtype} view for a ({N}, {out_c}, {out_h}, "
|
|
747
|
+
f"{out_w}) {out_dtype} result{' with a bias' if bias is not None else ''} — refused")
|
|
748
|
+
output = out
|
|
749
|
+
else:
|
|
750
|
+
output = NBXTensor.empty((N, out_c, out_h, out_w), device=x.device, dtype=out_dtype)
|
|
751
|
+
|
|
752
|
+
unit_route = _lk.matrix_unit_conv_operands(x_c.nbx_dtype, w_c.nbx_dtype, N * out_h * out_w, out_c // groups, kh, kw)
|
|
753
|
+
if unit_route is not None:
|
|
754
|
+
reps, split_rows = unit_route
|
|
755
|
+
# The implicit GEMM on the profile's matrix unit (ops/conv2d_m8n8k4.py), bias fused; no autotune key. An
|
|
756
|
+
# operand the DtypeEngine carries split (fp32 on Volta, `matrix_unit.fp32_split`) runs as hi + lo, as the GEMM.
|
|
757
|
+
x_c = x_c.contiguous()
|
|
758
|
+
# The matrix-unit kernel writes a contiguous result: a caller's strided view (a conv3d tap's frames in their
|
|
759
|
+
# [B, C, T, H, W] layout) receives it by one copy. Refused instead, every CUDA video model whose conv3d runs
|
|
760
|
+
# its taps through here failed at its first convolution (6 gate cells, 2026-10-22).
|
|
761
|
+
dest = output if out is None else NBXTensor.empty((N, out_c, out_h, out_w), device=x.device, dtype=out_dtype)
|
|
762
|
+
_conv2d_m8n8k4(x_c, w_c, bias, dest, N, in_c, in_h, in_w, out_c, out_h, out_w, kh, kw,
|
|
763
|
+
stride_h, stride_w, pad_h, pad_w, dil_h, dil_w, groups,
|
|
764
|
+
split=(reps[0] == "split", reps[1] == "split"), split_rows=split_rows)
|
|
765
|
+
if out is not None:
|
|
766
|
+
out.copy_(dest)
|
|
767
|
+
return output
|
|
768
|
+
|
|
769
|
+
# The key's `fp16` flag names an fp16 INPUT. It compared `x.dtype` — the Triton element type —
|
|
770
|
+
# to an NBXDtype member and was False for every launch (5 378 directory entries carried it).
|
|
771
|
+
# The kernel's only use of the flag casts the loaded blocks to fp16 before tl.dot, a no-op on
|
|
772
|
+
# an fp16 input: TTIR, TTGIR, LLIR and PTX are identical with the flag right (sm_70, three
|
|
773
|
+
# shapes, 2026-09-29), so the certified entries were MIGRATED to the right key, not re-proven
|
|
774
|
+
# (tools/migrate_conv_fp16_key.py).
|
|
775
|
+
fp16 = (x.nbx_dtype if hasattr(x, 'nbx_dtype') else x._dtype) == NBXDtype.float16
|
|
776
|
+
|
|
777
|
+
# Phase 1.5 conv2d autotune: BLOCK_SIZE_*/num_warps/num_stages chosen
|
|
778
|
+
# adaptively per shape signature, persisted via cache_results=True.
|
|
779
|
+
grid = lambda META: (
|
|
780
|
+
triton.cdiv(N * out_h * out_w, META['BLOCK_SIZE_BHW']),
|
|
781
|
+
triton.cdiv(out_c // groups, META['BLOCK_SIZE_OUTF']),
|
|
782
|
+
groups,
|
|
783
|
+
)
|
|
784
|
+
set_device(x_c)
|
|
785
|
+
_in_w_key, _out_w_key = _conv_width_key(in_h, out_h, in_w, out_w, kw, stride_w, pad_w, dil_w)
|
|
786
|
+
_core._autotune_headroom_guard(conv2d_forward_kernel[grid])(
|
|
787
|
+
x_c, w_c, output,
|
|
788
|
+
N, in_c, in_h, in_w,
|
|
789
|
+
out_c, out_h, out_w,
|
|
790
|
+
_in_w_key, _out_w_key,
|
|
791
|
+
*x_c.stride(), *w_c.stride(), *output.stride(),
|
|
792
|
+
kernel_height=kh, kernel_width=kw,
|
|
793
|
+
stride_height=stride_h, stride_width=stride_w,
|
|
794
|
+
padding_height=pad_h, padding_width=pad_w,
|
|
795
|
+
dilation_height=dil_h, dilation_width=dil_w,
|
|
796
|
+
groups=groups, fp16=fp16, WIDE_OFFSETS=_conv_offsets_wide(x_c, w_c, output),
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
if bias is not None:
|
|
800
|
+
output = _conv_bias_inplace(output, bias)
|
|
801
|
+
|
|
802
|
+
return output
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def _conv2d_m8n8k4(x, w, bias, out, N, in_c, in_h, in_w, out_c, out_h, out_w, kh, kw, sh, sw, ph, pw, dh, dw,
|
|
806
|
+
groups, split=(False, False), split_rows=None):
|
|
807
|
+
"""conv2d as an implicit GEMM on the m8n8k4 matrix unit: M = N*OH*OW, N = OC/groups, K = (IC/groups)*KH*KW in
|
|
808
|
+
the contiguous weight's own order (read in place); groups on grid axis 1; the profile's `mm` tile, or, when
|
|
809
|
+
`split` (input, weight) carries an operand as hi + lo, the `fp32_split.conv` row of its window (`split_rows`,
|
|
810
|
+
`launch_keys.matrix_unit_conv_operands`)."""
|
|
811
|
+
from nbxtensor.ops.conv2d_m8n8k4 import conv2d_m8n8k4_kernel, conv_layouts
|
|
812
|
+
from nbxtensor.dtype.operands import matrix_unit_split_scale
|
|
813
|
+
M, OG, CG = N * out_h * out_w, out_c // groups, in_c // groups
|
|
814
|
+
mu = _matrix_unit()
|
|
815
|
+
tile = _matrix_unit_mm_tile(M, OG, {"mm": [{k: v for k, v in r.items() if k != "kernel"} for r in split_rows]}
|
|
816
|
+
if any(split) else mu)
|
|
817
|
+
hi_exp, lo_shift = matrix_unit_split_scale(mu)
|
|
818
|
+
MB, NB = tile["block_m"] // 16, tile["block_n"] // 16
|
|
819
|
+
lay = conv_layouts(MB, NB, tile["warps_m"], tile["warps_n"], tile["block_k"])
|
|
820
|
+
set_device(x)
|
|
821
|
+
grid = (triton.cdiv(M, tile["block_m"]) * triton.cdiv(OG, tile["block_n"]), groups)
|
|
822
|
+
conv2d_m8n8k4_kernel[grid](
|
|
823
|
+
x, w, out if bias is None else bias.contiguous(), out,
|
|
824
|
+
M, OG, CG * kh * kw, in_h, in_w, out_h, out_w,
|
|
825
|
+
*x.stride(), w.stride(0), out.stride(0), out.stride(1), out.stride(2), out.stride(3),
|
|
826
|
+
CG, OG,
|
|
827
|
+
KH=kh, KW=kw, SH_=sh, SW_=sw, PH=ph, PW=pw, DH=dh, DW=dw, HAS_BIAS=bias is not None,
|
|
828
|
+
SPLIT_A=bool(split[0]), SPLIT_B=bool(split[1]), HI_EXP=hi_exp, LO_SHIFT=lo_shift,
|
|
829
|
+
MB=MB, NB=NB, BK=tile["block_k"], GROUP_M=tile["group_m"],
|
|
830
|
+
num_warps=tile["warps_m"] * tile["warps_n"], **lay)
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
def _depthwise_conv2d_dispatch(
|
|
834
|
+
x_c, w_c, bias,
|
|
835
|
+
N, C, IH, IW, out_h, out_w,
|
|
836
|
+
kh, kw, stride_h, stride_w, pad_h, pad_w,
|
|
837
|
+
out_dtype,
|
|
838
|
+
):
|
|
839
|
+
"""Launch the depthwise stencil kernel. Caller has already verified
|
|
840
|
+
the depthwise signature (groups == in_c == out_c, dilation == 1) and
|
|
841
|
+
done dtype narrowing + contiguous(). Bias is added separately so the
|
|
842
|
+
kernel stays signature-clean."""
|
|
843
|
+
output = NBXTensor.empty((N, C, out_h, out_w), device=x_c.device, dtype=out_dtype)
|
|
844
|
+
x_nbx_dt = x_c.nbx_dtype if hasattr(x_c, 'nbx_dtype') else x_c._dtype
|
|
845
|
+
fp16 = x_nbx_dt == NBXDtype.float16
|
|
846
|
+
|
|
847
|
+
grid = lambda META: (
|
|
848
|
+
N * triton.cdiv(C, META['BC']),
|
|
849
|
+
triton.cdiv(out_h, META['TH']),
|
|
850
|
+
triton.cdiv(out_w, META['TW']),
|
|
851
|
+
)
|
|
852
|
+
set_device(x_c)
|
|
853
|
+
_iw_key, _ow_key = _conv_width_key(IH, out_h, IW, out_w, kw, stride_w, pad_w, 1)
|
|
854
|
+
_core._autotune_headroom_guard(depthwise_conv2d_kernel[grid])(
|
|
855
|
+
x_c, w_c, output,
|
|
856
|
+
N, C,
|
|
857
|
+
IH, IW, out_h, out_w,
|
|
858
|
+
_iw_key, _ow_key,
|
|
859
|
+
*x_c.stride(), *w_c.stride()[:1], *w_c.stride()[2:],
|
|
860
|
+
*output.stride(),
|
|
861
|
+
kh=kh, kw=kw,
|
|
862
|
+
stride_h=stride_h, stride_w=stride_w,
|
|
863
|
+
pad_h=pad_h, pad_w=pad_w,
|
|
864
|
+
fp16=fp16, WIDE_OFFSETS=_conv_offsets_wide(x_c, w_c, output),
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
if bias is not None:
|
|
868
|
+
output = _conv_bias_inplace(output, bias)
|
|
869
|
+
|
|
870
|
+
return output
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
def _conv2d_band_streamed(
|
|
874
|
+
x_c, w_c, bias,
|
|
875
|
+
N, in_c, IH, IW, out_c, out_h, out_w,
|
|
876
|
+
kh, kw, stride_h, stride_w, pad_h, pad_w, dil_h, dil_w,
|
|
877
|
+
groups, out_dtype, out_dtype_bytes,
|
|
878
|
+
):
|
|
879
|
+
"""Band-streaming inner path for conv2d_wrapper. The full output tensor
|
|
880
|
+
is allocated up front (downstream consumers expect it whole); per band
|
|
881
|
+
we slice the input H, recurse into conv2d_wrapper for the band (which
|
|
882
|
+
falls back to the single-launch path because the band's output is now
|
|
883
|
+
small), and write the band slice back into the full output.
|
|
884
|
+
|
|
885
|
+
Halo on internal frontiers: each band's input slice is extended by
|
|
886
|
+
pad_h on the read side (clamped to image edges); the recursive
|
|
887
|
+
conv2d_wrapper call uses the original padding=(pad_h, pad_w), which
|
|
888
|
+
inserts pad_h zeros on internal frontiers — same engineering trade
|
|
889
|
+
used by `tiled_conv2d` for compiled mode (faint seam at
|
|
890
|
+
band frontiers, accepted as a follow-up halo-correctness workstream).
|
|
891
|
+
"""
|
|
892
|
+
# The TilingEngine sizes each band (`conv2d.band_budget_divisor` of the threshold);
|
|
893
|
+
# the headroom covers transient input slice + kernel intermediate.
|
|
894
|
+
band_oh = _lk.conv2d_band_rows(N, out_c, out_h, out_w, out_dtype_bytes, _ts.conv2d_band_bytes())
|
|
895
|
+
|
|
896
|
+
output = NBXTensor.empty((N, out_c, out_h, out_w), device=x_c.device, dtype=out_dtype)
|
|
897
|
+
|
|
898
|
+
for oh_start in range(0, out_h, band_oh):
|
|
899
|
+
oh_end = min(oh_start + band_oh, out_h)
|
|
900
|
+
ih_start = max(0, oh_start * stride_h - pad_h)
|
|
901
|
+
ih_end = min(IH, (oh_end - 1) * stride_h + dil_h * (kh - 1) + 1 - pad_h)
|
|
902
|
+
if ih_end <= ih_start:
|
|
903
|
+
continue
|
|
904
|
+
in_band = x_c[:, :, ih_start:ih_end, :]
|
|
905
|
+
# Recurse — band's out_h is now small enough to fall through to the
|
|
906
|
+
# single-launch path. bias is applied once at the end here (not
|
|
907
|
+
# per-band) to avoid double-add when the band path returns.
|
|
908
|
+
conv_band = conv2d_wrapper(
|
|
909
|
+
in_band, w_c, None,
|
|
910
|
+
stride=(stride_h, stride_w),
|
|
911
|
+
padding=(pad_h, pad_w),
|
|
912
|
+
dilation=(dil_h, dil_w),
|
|
913
|
+
groups=groups,
|
|
914
|
+
)
|
|
915
|
+
actual_band_h = oh_end - oh_start
|
|
916
|
+
band_first_oh = ih_start // stride_h
|
|
917
|
+
local_offset = max(0, oh_start - band_first_oh)
|
|
918
|
+
actual_band_h = min(conv_band.shape[2] - local_offset, actual_band_h)
|
|
919
|
+
if actual_band_h <= 0:
|
|
920
|
+
continue
|
|
921
|
+
output[:, :, oh_start:oh_start + actual_band_h, :] = conv_band[:, :, local_offset:local_offset + actual_band_h, :]
|
|
922
|
+
|
|
923
|
+
if bias is not None:
|
|
924
|
+
output = _conv_bias_inplace(output, bias)
|
|
925
|
+
|
|
926
|
+
return output
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
# ---------------------------------------------------------------------------
|
|
930
|
+
# Conv1D
|
|
931
|
+
# ---------------------------------------------------------------------------
|
|
932
|
+
|
|
933
|
+
def conv1d_wrapper(
|
|
934
|
+
x, weight, bias=None,
|
|
935
|
+
stride=1, padding=0, dilation=1, groups=1,
|
|
936
|
+
) :
|
|
937
|
+
"""Conv1D via conv2d with unsqueeze. Reuses the V100-compatible attorch conv2d kernel.
|
|
938
|
+
Input [N,C,L] → unsqueeze → [N,C,1,L] → conv2d → [N,Co,1,Lo] → squeeze → [N,Co,Lo]."""
|
|
939
|
+
# Convert 1D → 2D by adding H=1 dimension
|
|
940
|
+
x_2d = x.unsqueeze(2) # [N, C_in, 1, L]
|
|
941
|
+
w_2d = weight.unsqueeze(2) # [C_out, C_in/groups, 1, K]
|
|
942
|
+
|
|
943
|
+
if isinstance(stride, (list, tuple)):
|
|
944
|
+
stride = stride[0]
|
|
945
|
+
if isinstance(padding, (list, tuple)):
|
|
946
|
+
padding = padding[0]
|
|
947
|
+
if isinstance(dilation, (list, tuple)):
|
|
948
|
+
dilation = dilation[0]
|
|
949
|
+
|
|
950
|
+
out_2d = conv2d_wrapper(
|
|
951
|
+
x_2d, w_2d, bias,
|
|
952
|
+
stride=(1, stride), padding=(0, padding),
|
|
953
|
+
dilation=(1, dilation), groups=groups)
|
|
954
|
+
|
|
955
|
+
return out_2d.squeeze(2) # [N, C_out, L_out]
|
|
956
|
+
|
|
957
|
+
|
|
958
|
+
def conv_transpose1d_wrapper(x, weight, bias=None, stride=1, padding=0,
|
|
959
|
+
output_padding=0, groups=1, dilation=1) :
|
|
960
|
+
"""aten::conv_transpose1d — 1D transposed convolution. conv_transpose_wrapper
|
|
961
|
+
already handles the 1D case (weight.ndim == 3, H=1 unsqueeze); this thin
|
|
962
|
+
adapter only reorders the positional args from the aten signature
|
|
963
|
+
(stride, padding, output_padding, groups, dilation) to the wrapper's keyword
|
|
964
|
+
order. Used by the chatterbox/CosyVoice s3gen HiFiGAN-style upsampler."""
|
|
965
|
+
return conv_transpose_wrapper(
|
|
966
|
+
x, weight, bias,
|
|
967
|
+
stride=stride, padding=padding, dilation=dilation,
|
|
968
|
+
output_padding=output_padding, groups=groups)
|
|
969
|
+
|
|
970
|
+
|
|
971
|
+
# ---------------------------------------------------------------------------
|
|
972
|
+
# Avg Pool 2D
|
|
973
|
+
# ---------------------------------------------------------------------------
|
|
974
|
+
|
|
975
|
+
def avg_pool2d_wrapper(
|
|
976
|
+
x, kernel_size, stride=None, padding=0,
|
|
977
|
+
ceil_mode=False, count_include_pad=True, divisor_override=None,
|
|
978
|
+
) :
|
|
979
|
+
"""Average pooling 2D forward."""
|
|
980
|
+
assert x.ndim == 4
|
|
981
|
+
N, C, H, W = x.shape
|
|
982
|
+
|
|
983
|
+
# ATen broadcast for single-element lists (same contract as
|
|
984
|
+
# conv2d_wrapper — the latent IndexError class).
|
|
985
|
+
if isinstance(kernel_size, int):
|
|
986
|
+
kh, kw = kernel_size, kernel_size
|
|
987
|
+
else:
|
|
988
|
+
kh = kernel_size[0]
|
|
989
|
+
kw = kernel_size[1] if len(kernel_size) > 1 else kh
|
|
990
|
+
|
|
991
|
+
if stride is None or (isinstance(stride, (list, tuple)) and not stride):
|
|
992
|
+
sh, sw = kh, kw
|
|
993
|
+
elif isinstance(stride, int):
|
|
994
|
+
sh, sw = stride, stride
|
|
995
|
+
else:
|
|
996
|
+
sh = stride[0]
|
|
997
|
+
sw = stride[1] if len(stride) > 1 else sh
|
|
998
|
+
|
|
999
|
+
if isinstance(padding, int):
|
|
1000
|
+
ph, pw = padding, padding
|
|
1001
|
+
else:
|
|
1002
|
+
ph = padding[0] if len(padding) > 0 else 0
|
|
1003
|
+
pw = padding[1] if len(padding) > 1 else ph
|
|
1004
|
+
|
|
1005
|
+
if ceil_mode:
|
|
1006
|
+
oh = (H + 2 * ph - kh + sh - 1) // sh + 1
|
|
1007
|
+
ow = (W + 2 * pw - kw + sw - 1) // sw + 1
|
|
1008
|
+
else:
|
|
1009
|
+
oh = (H + 2 * ph - kh) // sh + 1
|
|
1010
|
+
ow = (W + 2 * pw - kw) // sw + 1
|
|
1011
|
+
|
|
1012
|
+
output = NBXTensor.empty((N, C, oh, ow), device=x.device, dtype=x.dtype)
|
|
1013
|
+
|
|
1014
|
+
# Contiguous-guard (manifesto pattern): materialize ONCE and launch
|
|
1015
|
+
# with THAT tensor's strides. The previous form passed
|
|
1016
|
+
# x.contiguous() as data but x.stride(...) of the ORIGINAL — a
|
|
1017
|
+
# non-contiguous input (transpose→unsqueeze chains, e.g. the
|
|
1018
|
+
# MiniCPM-o audio pooler) made the kernel read wrong addresses
|
|
1019
|
+
# (141% relative error at the first diverging op of the audio
|
|
1020
|
+
# drift diff). Contiguous inputs short-circuit at zero cost.
|
|
1021
|
+
xc = x.contiguous()
|
|
1022
|
+
grid = (
|
|
1023
|
+
N * C,
|
|
1024
|
+
triton.cdiv(oh, _POOL_BH) * triton.cdiv(ow, _POOL_BW),
|
|
1025
|
+
)
|
|
1026
|
+
set_device(xc)
|
|
1027
|
+
avg_pool2d_forward_kernel[grid](
|
|
1028
|
+
xc, output,
|
|
1029
|
+
xc.stride(0), xc.stride(1), xc.stride(2), xc.stride(3),
|
|
1030
|
+
C, H, W,
|
|
1031
|
+
oh, ow,
|
|
1032
|
+
kh, kw, sh, sw, ph, pw,
|
|
1033
|
+
1, 1, # dilation_h, dilation_w
|
|
1034
|
+
count_include_pad,
|
|
1035
|
+
0 if divisor_override is None else divisor_override,
|
|
1036
|
+
BLOCK_H=_POOL_BH, BLOCK_W=_POOL_BW,
|
|
1037
|
+
num_warps=4,
|
|
1038
|
+
)
|
|
1039
|
+
return output
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
# ---------------------------------------------------------------------------
|
|
1043
|
+
# Max Pool 2D
|
|
1044
|
+
# ---------------------------------------------------------------------------
|
|
1045
|
+
|
|
1046
|
+
def max_pool2d_wrapper(
|
|
1047
|
+
x, kernel_size, stride=None, padding=0,
|
|
1048
|
+
dilation=1, ceil_mode=False, return_indices=False,
|
|
1049
|
+
) :
|
|
1050
|
+
"""Max pooling 2D forward."""
|
|
1051
|
+
assert x.ndim == 4
|
|
1052
|
+
N, C, H, W = x.shape
|
|
1053
|
+
|
|
1054
|
+
# ATen broadcast for single-element lists (same contract as
|
|
1055
|
+
# conv2d_wrapper/avg_pool2d_wrapper).
|
|
1056
|
+
if isinstance(kernel_size, (list, tuple)) and len(kernel_size) == 1:
|
|
1057
|
+
kernel_size = [kernel_size[0], kernel_size[0]]
|
|
1058
|
+
if isinstance(stride, (list, tuple)) and len(stride) == 1:
|
|
1059
|
+
stride = [stride[0], stride[0]]
|
|
1060
|
+
if isinstance(padding, (list, tuple)) and len(padding) == 1:
|
|
1061
|
+
padding = [padding[0], padding[0]]
|
|
1062
|
+
if isinstance(dilation, (list, tuple)) and len(dilation) == 1:
|
|
1063
|
+
dilation = [dilation[0], dilation[0]]
|
|
1064
|
+
|
|
1065
|
+
if isinstance(kernel_size, int):
|
|
1066
|
+
kh, kw = kernel_size, kernel_size
|
|
1067
|
+
else:
|
|
1068
|
+
kh, kw = kernel_size[0], kernel_size[1]
|
|
1069
|
+
|
|
1070
|
+
if stride is None:
|
|
1071
|
+
sh, sw = kh, kw
|
|
1072
|
+
elif isinstance(stride, int):
|
|
1073
|
+
sh, sw = stride, stride
|
|
1074
|
+
else:
|
|
1075
|
+
sh, sw = stride[0], stride[1]
|
|
1076
|
+
|
|
1077
|
+
if isinstance(padding, int):
|
|
1078
|
+
ph, pw = padding, padding
|
|
1079
|
+
else:
|
|
1080
|
+
ph, pw = padding[0], padding[1]
|
|
1081
|
+
|
|
1082
|
+
if isinstance(dilation, int):
|
|
1083
|
+
dh, dw = dilation, dilation
|
|
1084
|
+
else:
|
|
1085
|
+
dh, dw = dilation[0], dilation[1]
|
|
1086
|
+
|
|
1087
|
+
if ceil_mode:
|
|
1088
|
+
oh = (H + 2 * ph - dh * (kh - 1) - 1 + sh - 1) // sh + 1
|
|
1089
|
+
ow = (W + 2 * pw - dw * (kw - 1) - 1 + sw - 1) // sw + 1
|
|
1090
|
+
else:
|
|
1091
|
+
oh = (H + 2 * ph - dh * (kh - 1) - 1) // sh + 1
|
|
1092
|
+
ow = (W + 2 * pw - dw * (kw - 1) - 1) // sw + 1
|
|
1093
|
+
|
|
1094
|
+
output = NBXTensor.empty((N, C, oh, ow), device=x.device, dtype=x.dtype)
|
|
1095
|
+
indices = NBXTensor.empty((N, C, oh, ow), device=x.device, dtype=NBXDtype.int64)
|
|
1096
|
+
|
|
1097
|
+
# Contiguous-guard: same fix as avg_pool2d_wrapper — one
|
|
1098
|
+
# materialization, ITS strides.
|
|
1099
|
+
xc = x.contiguous()
|
|
1100
|
+
grid = (
|
|
1101
|
+
N * C,
|
|
1102
|
+
triton.cdiv(oh, _POOL_BH) * triton.cdiv(ow, _POOL_BW),
|
|
1103
|
+
)
|
|
1104
|
+
set_device(xc)
|
|
1105
|
+
max_pool2d_forward_kernel[grid](
|
|
1106
|
+
xc, output, indices,
|
|
1107
|
+
xc.stride(0), xc.stride(1), xc.stride(2), xc.stride(3),
|
|
1108
|
+
C, H, W,
|
|
1109
|
+
oh, ow,
|
|
1110
|
+
kh, kw, sh, sw, ph, pw, dh, dw,
|
|
1111
|
+
BLOCK_H=_POOL_BH, BLOCK_W=_POOL_BW,
|
|
1112
|
+
num_warps=4,
|
|
1113
|
+
)
|
|
1114
|
+
if return_indices:
|
|
1115
|
+
return output, indices
|
|
1116
|
+
return output
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
def max_pool3d_with_indices_wrapper(
|
|
1120
|
+
x, kernel_size, stride=None, padding=0,
|
|
1121
|
+
dilation=1, ceil_mode=False,
|
|
1122
|
+
):
|
|
1123
|
+
"""Max pooling 3D forward — aten::max_pool3d_with_indices.
|
|
1124
|
+
|
|
1125
|
+
Returns (values, indices); indices are int64 flattened (T, H, W)
|
|
1126
|
+
offsets per (N, C) plane (PyTorch convention). Dedicated
|
|
1127
|
+
@triton.jit kernel (kernels/ops/max_pool3d.py), temporal extension
|
|
1128
|
+
of the 2D pool kernel. Video conditioning masks (e.g. Allegro-TI2V
|
|
1129
|
+
state_video_mask downsampled to the latent grid) trace to this op.
|
|
1130
|
+
"""
|
|
1131
|
+
assert x.ndim == 5
|
|
1132
|
+
N, C, T, H, W = x.shape
|
|
1133
|
+
|
|
1134
|
+
def _t3(v, default):
|
|
1135
|
+
if v is None:
|
|
1136
|
+
return default
|
|
1137
|
+
if isinstance(v, int):
|
|
1138
|
+
return (v, v, v)
|
|
1139
|
+
return (v[0], v[1], v[2]) if len(v) >= 3 else (v[0], v[0], v[0])
|
|
1140
|
+
|
|
1141
|
+
kt, kh, kw = _t3(kernel_size, None)
|
|
1142
|
+
st_, sh, sw = _t3(stride, (kt, kh, kw)) if stride not in (None, [], ()) \
|
|
1143
|
+
else (kt, kh, kw)
|
|
1144
|
+
pt, ph, pw = _t3(padding, (0, 0, 0))
|
|
1145
|
+
dt_, dh, dw = _t3(dilation, (1, 1, 1))
|
|
1146
|
+
|
|
1147
|
+
def _odim(inp, k, s, p, d):
|
|
1148
|
+
if ceil_mode:
|
|
1149
|
+
return (inp + 2 * p - d * (k - 1) - 1 + s - 1) // s + 1
|
|
1150
|
+
return (inp + 2 * p - d * (k - 1) - 1) // s + 1
|
|
1151
|
+
|
|
1152
|
+
ot = _odim(T, kt, st_, pt, dt_)
|
|
1153
|
+
oh = _odim(H, kh, sh, ph, dh)
|
|
1154
|
+
ow = _odim(W, kw, sw, pw, dw)
|
|
1155
|
+
|
|
1156
|
+
x_c = x.contiguous()
|
|
1157
|
+
output = NBXTensor.empty((N, C, ot, oh, ow), device=x_c.device, dtype=x_c.dtype)
|
|
1158
|
+
indices = NBXTensor.empty((N, C, ot, oh, ow), device=x_c.device,
|
|
1159
|
+
dtype=NBXDtype.int64)
|
|
1160
|
+
|
|
1161
|
+
grid = (
|
|
1162
|
+
N * C * ot,
|
|
1163
|
+
triton.cdiv(oh, _POOL_BH) * triton.cdiv(ow, _POOL_BW),
|
|
1164
|
+
)
|
|
1165
|
+
set_device(x_c)
|
|
1166
|
+
max_pool3d_forward_kernel[grid](
|
|
1167
|
+
x_c, output, indices,
|
|
1168
|
+
x_c.stride(0), x_c.stride(1), x_c.stride(2), x_c.stride(3), x_c.stride(4),
|
|
1169
|
+
C, T, H, W,
|
|
1170
|
+
ot, oh, ow,
|
|
1171
|
+
kt, kh, kw, st_, sh, sw, pt, ph, pw, dt_, dh, dw,
|
|
1172
|
+
BLOCK_H=_POOL_BH, BLOCK_W=_POOL_BW,
|
|
1173
|
+
num_warps=4,
|
|
1174
|
+
)
|
|
1175
|
+
return output, indices
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
def max_pool3d_wrapper(
|
|
1179
|
+
x, kernel_size, stride=None, padding=0,
|
|
1180
|
+
dilation=1, ceil_mode=False,
|
|
1181
|
+
):
|
|
1182
|
+
"""aten::max_pool3d — values-only variant."""
|
|
1183
|
+
return max_pool3d_with_indices_wrapper(
|
|
1184
|
+
x, kernel_size, stride, padding, dilation, ceil_mode)[0]
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
def adaptive_avg_pool2d_wrapper(x, output_size) :
|
|
1188
|
+
"""Adaptive average pooling 2D."""
|
|
1189
|
+
N, C, IH, IW = x.shape
|
|
1190
|
+
if isinstance(output_size, int):
|
|
1191
|
+
OH = OW = output_size
|
|
1192
|
+
else:
|
|
1193
|
+
OH, OW = output_size
|
|
1194
|
+
output = NBXTensor.empty((N, C, OH, OW), device=x.device, dtype=x.dtype)
|
|
1195
|
+
total = N * C * OH * OW
|
|
1196
|
+
grid = _ew._1d_grid(total)
|
|
1197
|
+
x_c = x.contiguous()
|
|
1198
|
+
set_device(x_c)
|
|
1199
|
+
adaptive_avg_pool2d_kernel[grid](
|
|
1200
|
+
x_c, output, total,
|
|
1201
|
+
IH, IW, OH, OW,
|
|
1202
|
+
*x_c.stride(),
|
|
1203
|
+
**_ew_meta(total))
|
|
1204
|
+
return output
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
def conv_depthwise2d_wrapper(x, weight, bias=None,
|
|
1208
|
+
stride=1, padding=0, dilation=1) :
|
|
1209
|
+
"""Depthwise conv2d."""
|
|
1210
|
+
if isinstance(stride, int): stride = (stride, stride)
|
|
1211
|
+
if isinstance(padding, int): padding = (padding, padding)
|
|
1212
|
+
if isinstance(dilation, int): dilation = (dilation, dilation)
|
|
1213
|
+
N, C, IH, IW = x.shape
|
|
1214
|
+
_, _, KH, KW = weight.shape
|
|
1215
|
+
OH = (IH + 2*padding[0] - dilation[0]*(KH-1) - 1) // stride[0] + 1
|
|
1216
|
+
OW = (IW + 2*padding[1] - dilation[1]*(KW-1) - 1) // stride[1] + 1
|
|
1217
|
+
output = NBXTensor.empty((N, C, OH, OW), device=x.device, dtype=x.dtype)
|
|
1218
|
+
grid = (
|
|
1219
|
+
triton.cdiv(OW, _core._BILINEAR_BX),
|
|
1220
|
+
triton.cdiv(OH, _core._BILINEAR_BY),
|
|
1221
|
+
N * C,
|
|
1222
|
+
)
|
|
1223
|
+
set_device(x.contiguous())
|
|
1224
|
+
conv_depthwise2d_kernel[grid](
|
|
1225
|
+
x.contiguous(), weight.contiguous(), output,
|
|
1226
|
+
N, C, IH, IW, C, KH, KW, OH, OW,
|
|
1227
|
+
stride[0], stride[1], padding[0], padding[1], dilation[0], dilation[1],
|
|
1228
|
+
1, # ch_mult
|
|
1229
|
+
BLOCK_X=_core._BILINEAR_BX, BLOCK_Y=_core._BILINEAR_BY,
|
|
1230
|
+
num_warps=4)
|
|
1231
|
+
if bias is not None:
|
|
1232
|
+
output = _conv_bias_inplace(output, bias)
|
|
1233
|
+
return output
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
def unfold_backward_wrapper(grad_in, input_sizes, dim, size, step) :
|
|
1237
|
+
"""unfold_backward (overlap-add): scatter-add the unfolded frames
|
|
1238
|
+
[..lead.., N_frames, size] back into the original signal [..lead.., L] (L on
|
|
1239
|
+
`dim`), summing overlapping positions. This is the iSTFT overlap-add.
|
|
1240
|
+
R33-pure (one @triton.jit scatter-add kernel)."""
|
|
1241
|
+
from nbxtensor.ops.unfold_op import unfold_backward_1d_kernel
|
|
1242
|
+
grad_in = _core._ensure_cuda(grad_in).contiguous()
|
|
1243
|
+
if isinstance(dim, (list, tuple)):
|
|
1244
|
+
dim = dim[0]
|
|
1245
|
+
if isinstance(size, (list, tuple)):
|
|
1246
|
+
size = size[0]
|
|
1247
|
+
if isinstance(step, (list, tuple)):
|
|
1248
|
+
step = step[0]
|
|
1249
|
+
out_shape = list(input_sizes)
|
|
1250
|
+
dim = dim % len(out_shape)
|
|
1251
|
+
L = out_shape[dim]
|
|
1252
|
+
# grad_in: [*out_shape[:dim], N_frames, size] (the unfold replaced dim by
|
|
1253
|
+
# (N_frames, size)). Collapse the leading dims into a batch.
|
|
1254
|
+
gshape = list(grad_in.shape)
|
|
1255
|
+
N_frames = gshape[dim]
|
|
1256
|
+
sz = gshape[-1]
|
|
1257
|
+
B = 1
|
|
1258
|
+
for d in out_shape[:dim]:
|
|
1259
|
+
B *= d
|
|
1260
|
+
g2 = grad_in.reshape(B, N_frames, sz)
|
|
1261
|
+
out = NBXTensor.zeros((B, L), dtype=grad_in.dtype, device=grad_in.device)
|
|
1262
|
+
set_device(g2)
|
|
1263
|
+
max_overlap = (sz // step) + 2
|
|
1264
|
+
BLOCK = 256
|
|
1265
|
+
grid = (B, triton.cdiv(L, BLOCK))
|
|
1266
|
+
unfold_backward_1d_kernel[grid](
|
|
1267
|
+
g2, out, N_frames, sz, step, L,
|
|
1268
|
+
MAX_OVERLAP=max_overlap, BLOCK_SIZE=BLOCK)
|
|
1269
|
+
return out.reshape(*out_shape)
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
def im2col_wrapper(x, kernel_size, dilation, padding, stride):
|
|
1273
|
+
"""aten::im2col / nn.Unfold — sliding local blocks of an NCHW tensor.
|
|
1274
|
+
|
|
1275
|
+
x [N, C, IH, IW] -> [N, C*KH*KW, L], L = OH*OW, in PyTorch's channel-major
|
|
1276
|
+
column order (c*KH*KW + kh*KW + kw). R33-pure: one @triton.jit kernel that
|
|
1277
|
+
reads the padding halo via boundary-masked tl.load (no F.pad). Drives HAT's
|
|
1278
|
+
OCAB overlapping-window cross-attention (P-TRITON-IM2COL-KERNEL).
|
|
1279
|
+
"""
|
|
1280
|
+
from nbxtensor.ops.im2col import im2col_kernel
|
|
1281
|
+
|
|
1282
|
+
def _pair(v):
|
|
1283
|
+
return tuple(v) if isinstance(v, (list, tuple)) else (v, v)
|
|
1284
|
+
|
|
1285
|
+
x = _core._ensure_cuda(x).contiguous()
|
|
1286
|
+
N, C, IH, IW = x.shape
|
|
1287
|
+
KH, KW = _pair(kernel_size)
|
|
1288
|
+
dil_h, dil_w = _pair(dilation)
|
|
1289
|
+
pad_h, pad_w = _pair(padding)
|
|
1290
|
+
stride_h, stride_w = _pair(stride)
|
|
1291
|
+
OH = (IH + 2 * pad_h - dil_h * (KH - 1) - 1) // stride_h + 1
|
|
1292
|
+
OW = (IW + 2 * pad_w - dil_w * (KW - 1) - 1) // stride_w + 1
|
|
1293
|
+
L = OH * OW
|
|
1294
|
+
out = NBXTensor.empty((N, C * KH * KW, L), dtype=x.dtype, device=x.device)
|
|
1295
|
+
in_sn, in_sc, in_sh, in_sw = C * IH * IW, IH * IW, IW, 1
|
|
1296
|
+
out_sn, out_sc, out_sl = C * KH * KW * L, L, 1
|
|
1297
|
+
BLOCK_L = min(1024, triton.next_power_of_2(L))
|
|
1298
|
+
grid = (N * C * KH * KW,)
|
|
1299
|
+
set_device(x)
|
|
1300
|
+
im2col_kernel[grid](
|
|
1301
|
+
x, out, C, IH, IW, OW, L, KH, KW,
|
|
1302
|
+
stride_h, stride_w, pad_h, pad_w, dil_h, dil_w,
|
|
1303
|
+
in_sn, in_sc, in_sh, in_sw, out_sn, out_sc, out_sl,
|
|
1304
|
+
BLOCK_L=BLOCK_L)
|
|
1305
|
+
return out
|
|
1306
|
+
|
|
1307
|
+
|
|
1308
|
+
# ---------------------------------------------------------------------------
|
|
1309
|
+
# The cost each convolution path declares (M3, docs/nbxtensor/COST.md): what it allocates, in its order, read from the
|
|
1310
|
+
# same decisions the wrappers take (`launch_keys.conv_dtypes`, `conv2d_route`, `matrix_unit_conv_operands`, the strided read
|
|
1311
|
+
# rule) — a planner prices the convolution by it instead of re-deriving it. Operands are `_cost.Spec`s; a returned
|
|
1312
|
+
# buffer is the op's output.
|
|
1313
|
+
# ---------------------------------------------------------------------------
|
|
1314
|
+
|
|
1315
|
+
def _reads_strided(shape, strides) -> bool:
|
|
1316
|
+
"""`_conv_reads_strided` on a shape and its strides: four non-negative strides, the width innermost."""
|
|
1317
|
+
return len(strides) == 4 and all(int(v) >= 0 for v in strides) and (int(strides[3]) == 1 or int(shape[3]) == 1)
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
def _pair(v):
|
|
1321
|
+
return (v[0], v[1] if len(v) > 1 else v[0]) if isinstance(v, (list, tuple)) else (v, v)
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
|
+
def conv2d_cost(x, w, stride=1, padding=0, dilation=1, groups=1, bias=False, out_dtype=None, out_given=False,
|
|
1325
|
+
krsc=False, unit=None, depthwise_enabled=True):
|
|
1326
|
+
"""`conv2d_wrapper` on an input `x` and a weight `w` (`_cost.Spec`s): the operands narrowed to the narrowest of the
|
|
1327
|
+
pair when they differ (a copy each), the input made contiguous unless the implicit GEMM reads it where it lies, a
|
|
1328
|
+
weight not stored KRSC made contiguous, then the route — depthwise (a contiguous input, its output), band (the whole
|
|
1329
|
+
output, each band's conv beside it), or the plain launch: its output (none when the caller hands `out_given`), and
|
|
1330
|
+
on the matrix unit the input made contiguous and, for a caller's view, a dense result copied into it. The bias is
|
|
1331
|
+
added in place. `out_dtype`: the component's compute dtype the wrapper writes (`_NBX_COMPUTE_DTYPE`), the input's
|
|
1332
|
+
when None. `unit`: the matrix unit to judge by (a planner's), the profile in force's when None."""
|
|
1333
|
+
from nbxtensor.ops import _cost as C
|
|
1334
|
+
from nbxtensor.ops import tiling_sizes as _tsz
|
|
1335
|
+
from nbxtensor.shape import contiguous_strides, is_contiguous
|
|
1336
|
+
st = C._Steps()
|
|
1337
|
+
N, in_c, in_h, in_w = (int(d) for d in x.shape)
|
|
1338
|
+
out_c, _cg, kh, kw = (int(d) for d in w.shape)
|
|
1339
|
+
sh, sw = _pair(stride)
|
|
1340
|
+
ph, pw = _pair(padding)
|
|
1341
|
+
dh, dw = _pair(dilation)
|
|
1342
|
+
out_h, out_w = _lk.conv_out_hw(in_h, in_w, kh, kw, sh, sw, ph, pw, dh, dw)
|
|
1343
|
+
x_to, w_to = _lk.conv_dtypes(NBXDtype[x.dtype], NBXDtype[w.dtype])
|
|
1344
|
+
if x_to.name != x.dtype:
|
|
1345
|
+
x = C.Spec.contiguous(x.shape, x_to.name)
|
|
1346
|
+
st.alloc("input narrowed", x.nbytes)
|
|
1347
|
+
if w_to.name != w.dtype:
|
|
1348
|
+
w = C.Spec.contiguous(w.shape, w_to.name)
|
|
1349
|
+
st.alloc("weight narrowed", w.nbytes)
|
|
1350
|
+
if not is_contiguous(x.shape, x.strides) and not _reads_strided(x.shape, x.strides):
|
|
1351
|
+
x = C.Spec.contiguous(x.shape, x.dtype)
|
|
1352
|
+
st.alloc("input made contiguous", x.nbytes)
|
|
1353
|
+
if not krsc and not is_contiguous(w.shape, w.strides):
|
|
1354
|
+
st.alloc("weight made contiguous", w.nbytes)
|
|
1355
|
+
out_dt = out_dtype or x.dtype
|
|
1356
|
+
out_bytes = N * out_c * out_h * out_w * dtype_size(NBXDtype[out_dt])
|
|
1357
|
+
route = _lk.conv2d_route(N, in_c, out_c, out_h, out_w, dh, dw, groups, NBXDtype[x.dtype],
|
|
1358
|
+
_tsz.conv2d_band_bytes(), depthwise_enabled=depthwise_enabled)
|
|
1359
|
+
if route == "depthwise":
|
|
1360
|
+
if not is_contiguous(x.shape, x.strides):
|
|
1361
|
+
st.alloc("input made contiguous (depthwise)", x.nbytes)
|
|
1362
|
+
st.alloc("output", out_bytes, returned=True)
|
|
1363
|
+
return st.done(2 * N * out_c * out_h * out_w * kh * kw)
|
|
1364
|
+
if route == "band":
|
|
1365
|
+
if not is_contiguous(x.shape, x.strides):
|
|
1366
|
+
x = C.Spec.contiguous(x.shape, x.dtype)
|
|
1367
|
+
st.alloc("input made contiguous (bands)", x.nbytes)
|
|
1368
|
+
st.alloc("output", out_bytes, returned=True)
|
|
1369
|
+
band_oh = _lk.conv2d_band_rows(N, out_c, out_h, out_w, dtype_size(NBXDtype[x.dtype]), _tsz.conv2d_band_bytes())
|
|
1370
|
+
worst = None
|
|
1371
|
+
for oh_start in range(0, out_h, band_oh):
|
|
1372
|
+
oh_end = min(oh_start + band_oh, out_h)
|
|
1373
|
+
ih_start = max(0, oh_start * sh - ph)
|
|
1374
|
+
ih_end = min(in_h, (oh_end - 1) * sh + dh * (kh - 1) + 1 - ph)
|
|
1375
|
+
if ih_end <= ih_start:
|
|
1376
|
+
continue
|
|
1377
|
+
band = C.Spec((N, in_c, ih_end - ih_start, in_w), x.strides, x.dtype)
|
|
1378
|
+
c = conv2d_cost(band, w, stride, padding, dilation, groups, False, out_dt, False, True, unit,
|
|
1379
|
+
depthwise_enabled)
|
|
1380
|
+
if worst is None or c.peak_bytes > worst.peak_bytes:
|
|
1381
|
+
worst = c
|
|
1382
|
+
if worst is not None:
|
|
1383
|
+
for i in st.nest(worst, "the largest band"):
|
|
1384
|
+
st.free(i) # each band's result copied into the output, then dropped
|
|
1385
|
+
return st.done(2 * N * out_c * out_h * out_w * kh * kw * (in_c // max(1, groups)))
|
|
1386
|
+
if not out_given:
|
|
1387
|
+
st.alloc("output", out_bytes, returned=True)
|
|
1388
|
+
if _lk.matrix_unit_conv_operands(NBXDtype[x.dtype], NBXDtype[w.dtype], N * out_h * out_w, out_c // max(1, groups),
|
|
1389
|
+
kh, kw, unit) is not None:
|
|
1390
|
+
if not is_contiguous(x.shape, x.strides):
|
|
1391
|
+
i = st.alloc("input made contiguous (matrix unit)", x.nbytes)
|
|
1392
|
+
if out_given:
|
|
1393
|
+
st.alloc("dense result for the caller's view (matrix unit)", out_bytes)
|
|
1394
|
+
return st.done(2 * N * out_c * out_h * out_w * kh * kw * (in_c // max(1, groups)))
|
|
1395
|
+
|
|
1396
|
+
|
|
1397
|
+
def conv3d_cost(x, w, stride=1, padding=0, dilation=1, groups=1, bias=False, out_dtype=None, unit=None):
|
|
1398
|
+
"""`_conv3d_via_conv2d` on an input `x` [B, Cin, T, H, W] and a weight `w` [Cout, Cin_g, kt, kh, kw]: the temporal
|
|
1399
|
+
pad (a padded copy, alive to the return), then — at temporal stride 1 — each tap's 2-D weight slice, its tap buffer
|
|
1400
|
+
and its per-batch conv2d writing into the tap's view (`conv2d_cost(out_given=True)`: on the matrix unit the
|
|
1401
|
+
frames made contiguous and a dense result), the first tap kept as the output and each later one added into it and
|
|
1402
|
+
dropped; at a larger stride each tap's frames gathered and folded (a copy), its conv2d output, its result permuted
|
|
1403
|
+
back (a copy) and the running sum (a new buffer per tap). The bias adds a new output. `out_dtype`: the compute
|
|
1404
|
+
dtype the wrapper writes, the input's when None."""
|
|
1405
|
+
from nbxtensor.ops import _cost as C
|
|
1406
|
+
from nbxtensor.shape import contiguous_strides
|
|
1407
|
+
|
|
1408
|
+
def _triple(v):
|
|
1409
|
+
return (v[0], v[1], v[2]) if isinstance(v, (list, tuple)) else (v, v, v)
|
|
1410
|
+
st_, sh, sw = _triple(stride)
|
|
1411
|
+
pt, ph, pw = _triple(padding)
|
|
1412
|
+
dt, dh, dw = _triple(dilation)
|
|
1413
|
+
B, Cin, T, H, W = (int(d) for d in x.shape)
|
|
1414
|
+
Cout, Cin_g, kt, kh, kw = (int(d) for d in w.shape)
|
|
1415
|
+
out_dt = out_dtype or x.dtype
|
|
1416
|
+
st = C._Steps()
|
|
1417
|
+
if pt > 0:
|
|
1418
|
+
x = C.Spec.contiguous((B, Cin, T + 2 * pt, H, W), x.dtype)
|
|
1419
|
+
st.alloc("temporal pad", x.nbytes)
|
|
1420
|
+
Tp = x.shape[2]
|
|
1421
|
+
T_out = (Tp - dt * (kt - 1) - 1) // st_ + 1
|
|
1422
|
+
Ho, Wo = _lk.conv_out_hw(H, W, kh, kw, sh, sw, ph, pw, dh, dw)
|
|
1423
|
+
out_bytes = B * Cout * T_out * Ho * Wo * dtype_size(NBXDtype[out_dt])
|
|
1424
|
+
w2 = C.Spec.contiguous((Cout, Cin_g, kh, kw), w.dtype)
|
|
1425
|
+
flops = 2 * B * Cout * T_out * Ho * Wo * kt * kh * kw * Cin_g
|
|
1426
|
+
# the wrapper's locals, as Python holds them: a name rebound frees its old buffer only after the new one exists, a
|
|
1427
|
+
# buffer at least `_NBX_CONV3D_EAGER_FREE_BYTES` is dropped as soon as it is dead (`x2 = None`, ...), the rest die
|
|
1428
|
+
# at the return
|
|
1429
|
+
held = {}
|
|
1430
|
+
|
|
1431
|
+
def bind(name, index):
|
|
1432
|
+
old = held.get(name)
|
|
1433
|
+
held[name] = index
|
|
1434
|
+
if old is not None and old not in held.values():
|
|
1435
|
+
st.free(old)
|
|
1436
|
+
|
|
1437
|
+
def drop(name):
|
|
1438
|
+
old = held.pop(name, None)
|
|
1439
|
+
if old is not None and old not in held.values():
|
|
1440
|
+
st.free(old)
|
|
1441
|
+
|
|
1442
|
+
if st_ == 1:
|
|
1443
|
+
xs = x.strides
|
|
1444
|
+
frames = C.Spec((T_out, Cin, H, W), (xs[2], xs[1], xs[3], xs[4]), x.dtype)
|
|
1445
|
+
for kti in range(kt):
|
|
1446
|
+
bind("w2", st.alloc("tap weight slice", w2.nbytes))
|
|
1447
|
+
bind("tap", st.alloc("tap", out_bytes))
|
|
1448
|
+
for _b in range(B):
|
|
1449
|
+
for i in st.nest(conv2d_cost(frames, w2, (sh, sw), (ph, pw), (dh, dw), groups, False, out_dt,
|
|
1450
|
+
True, False, unit), f"tap {kti} conv2d"):
|
|
1451
|
+
st.free(i)
|
|
1452
|
+
if "out" not in held:
|
|
1453
|
+
held["out"] = held["tap"] # the first tap is the sum
|
|
1454
|
+
drop("tap") # a later one, added into the sum in place, dropped (`tap = None`)
|
|
1455
|
+
else:
|
|
1456
|
+
fold_bytes = B * T_out * Cin * H * W * dtype_size(NBXDtype[x.dtype])
|
|
1457
|
+
for kti in range(kt):
|
|
1458
|
+
bind("idx", st.alloc("frame indices", T_out * 8))
|
|
1459
|
+
bind("xs", st.alloc("tap frames gathered", fold_bytes))
|
|
1460
|
+
bind("x2", st.alloc("tap frames folded", fold_bytes))
|
|
1461
|
+
bind("w2", st.alloc("tap weight slice", w2.nbytes))
|
|
1462
|
+
fold = C.Spec.contiguous((B * T_out, Cin, H, W), x.dtype)
|
|
1463
|
+
y2, = st.nest(conv2d_cost(fold, w2, (sh, sw), (ph, pw), (dh, dw), groups, False, out_dt, False, False,
|
|
1464
|
+
unit), f"tap {kti} conv2d")
|
|
1465
|
+
bind("y2", y2)
|
|
1466
|
+
if fold_bytes >= _NBX_CONV3D_EAGER_FREE_BYTES:
|
|
1467
|
+
drop("x2")
|
|
1468
|
+
bind("y", st.alloc("tap result permuted back", out_bytes))
|
|
1469
|
+
if out_bytes >= _NBX_CONV3D_EAGER_FREE_BYTES:
|
|
1470
|
+
drop("y2")
|
|
1471
|
+
if "out" not in held:
|
|
1472
|
+
held["out"] = held["y"]
|
|
1473
|
+
else:
|
|
1474
|
+
bind("out", st.alloc("running sum", out_bytes))
|
|
1475
|
+
if out_bytes >= _NBX_CONV3D_EAGER_FREE_BYTES:
|
|
1476
|
+
drop("y")
|
|
1477
|
+
if bias:
|
|
1478
|
+
bind("out", st.alloc("output with the bias", out_bytes))
|
|
1479
|
+
out = held.pop("out")
|
|
1480
|
+
st.buffers[out][4] = True # the result
|
|
1481
|
+
st.buffers[out][3] = None
|
|
1482
|
+
return st.done(flops) # every other local dies at the return
|