nbxtensor 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nbxtensor/THIRD_PARTY_NOTICES.md +287 -0
- nbxtensor/__init__.py +17 -0
- nbxtensor/backend/__init__.py +1 -0
- nbxtensor/backend/capabilities.py +207 -0
- nbxtensor/backend/cpu/__init__.py +1 -0
- nbxtensor/backend/cpu/blas.py +102 -0
- nbxtensor/backend/cpu/pool.py +204 -0
- nbxtensor/backend/cpu_backend.py +162 -0
- nbxtensor/backend/device_facts.py +105 -0
- nbxtensor/backend/device_files.py +129 -0
- nbxtensor/backend/device_names.py +18 -0
- nbxtensor/backend/devices/amd/cdna.yml +228 -0
- nbxtensor/backend/devices/amd/cdna2.yml +228 -0
- nbxtensor/backend/devices/amd/cdna3.yml +227 -0
- nbxtensor/backend/devices/apple/apple_m1.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m1_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m2_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_pro.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m3_ultra.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4_max.yml +266 -0
- nbxtensor/backend/devices/apple/apple_m4_pro.yml +308 -0
- nbxtensor/backend/devices/apple/apple_m5.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_max.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_pro.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m5_ultra.yml +269 -0
- nbxtensor/backend/devices/apple/apple_m6.yml +269 -0
- nbxtensor/backend/devices/apple/apple_silicon.yml +263 -0
- nbxtensor/backend/devices/nvidia/ampere.yml +214 -0
- nbxtensor/backend/devices/nvidia/hopper.yml +213 -0
- nbxtensor/backend/devices/nvidia/volta.yml +411 -0
- nbxtensor/backend/faults.py +151 -0
- nbxtensor/backend/host_memory.py +185 -0
- nbxtensor/backend/launcher.py +2350 -0
- nbxtensor/backend/launcher_contract.py +429 -0
- nbxtensor/backend/metal_backend.py +661 -0
- nbxtensor/backend/metal_device.py +1274 -0
- nbxtensor/backend/process_memory.py +73 -0
- nbxtensor/backend/replay_graph.py +322 -0
- nbxtensor/backend/runtime.py +409 -0
- nbxtensor/backend/triton_ext_driver.py +735 -0
- nbxtensor/dtype/__init__.py +1 -0
- nbxtensor/dtype/convert.py +107 -0
- nbxtensor/dtype/dtypes.yml +70 -0
- nbxtensor/dtype/itemsize.py +166 -0
- nbxtensor/dtype/nbx_dtype.py +72 -0
- nbxtensor/dtype/operands.py +158 -0
- nbxtensor/dtype/outputs.py +64 -0
- nbxtensor/dtype/policy.py +155 -0
- nbxtensor/dtype/policy.yml +51 -0
- nbxtensor/dtype/promotion.py +112 -0
- nbxtensor/host.py +55 -0
- nbxtensor/io/__init__.py +1 -0
- nbxtensor/io/read_rate.py +197 -0
- nbxtensor/io/safetensors.py +314 -0
- nbxtensor/memory/__init__.py +9 -0
- nbxtensor/memory/allocator.py +1988 -0
- nbxtensor/memory/arena.py +75 -0
- nbxtensor/memory/device_memory.py +150 -0
- nbxtensor/memory/device_transfer.py +154 -0
- nbxtensor/memory/layout.py +43 -0
- nbxtensor/memory/memory_pool.py +191 -0
- nbxtensor/memory/metal.py +96 -0
- nbxtensor/memory/oom_advice.py +91 -0
- nbxtensor/memory/pointer_tables.py +323 -0
- nbxtensor/memory/pool.py +507 -0
- nbxtensor/memory/shadow.py +43 -0
- nbxtensor/memory/slab.py +273 -0
- nbxtensor/memory/trace_replay.py +122 -0
- nbxtensor/memory/traces/sana-video-2b-720p.16g.2026-11-02.tsv +13610 -0
- nbxtensor/memory/vmm.py +118 -0
- nbxtensor/ops/__init__.py +1 -0
- nbxtensor/ops/_autotune_policy.py +90 -0
- nbxtensor/ops/_common.py +32 -0
- nbxtensor/ops/_configs.py +1041 -0
- nbxtensor/ops/_cost.py +231 -0
- nbxtensor/ops/abs.py +17 -0
- nbxtensor/ops/adaptive_avg_pool2d.py +161 -0
- nbxtensor/ops/add.py +96 -0
- nbxtensor/ops/addcdiv.py +29 -0
- nbxtensor/ops/addcmul.py +29 -0
- nbxtensor/ops/addmv_op.py +64 -0
- nbxtensor/ops/addr.py +68 -0
- nbxtensor/ops/all_reduce.py +83 -0
- nbxtensor/ops/amax.py +40 -0
- nbxtensor/ops/any_reduce.py +83 -0
- nbxtensor/ops/arange_op.py +27 -0
- nbxtensor/ops/argmax.py +107 -0
- nbxtensor/ops/argmin.py +93 -0
- nbxtensor/ops/atan2.py +71 -0
- nbxtensor/ops/attention_wrappers.py +1648 -0
- nbxtensor/ops/autotune_bucket.py +137 -0
- nbxtensor/ops/autotune_cache.py +430 -0
- nbxtensor/ops/autotune_certified.py +1262 -0
- nbxtensor/ops/autotune_refusals.py +380 -0
- nbxtensor/ops/autotune_settings.py +58 -0
- nbxtensor/ops/avg_pool2d.py +73 -0
- nbxtensor/ops/baddbmm_op.py +132 -0
- nbxtensor/ops/batch_norm.py +163 -0
- nbxtensor/ops/binary_strided.py +196 -0
- nbxtensor/ops/bitwise_and.py +26 -0
- nbxtensor/ops/bitwise_left_shift.py +47 -0
- nbxtensor/ops/bitwise_not.py +24 -0
- nbxtensor/ops/bitwise_or.py +26 -0
- nbxtensor/ops/bitwise_right_shift.py +47 -0
- nbxtensor/ops/bucketize.py +37 -0
- nbxtensor/ops/cat_op.py +87 -0
- nbxtensor/ops/celu.py +20 -0
- nbxtensor/ops/clamp.py +24 -0
- nbxtensor/ops/clamp_min.py +22 -0
- nbxtensor/ops/conv2d.py +265 -0
- nbxtensor/ops/conv2d_m8n8k4.py +165 -0
- nbxtensor/ops/conv_depthwise2d.py +154 -0
- nbxtensor/ops/conv_transpose2d.py +148 -0
- nbxtensor/ops/conv_wrappers.py +1482 -0
- nbxtensor/ops/copy.py +18 -0
- nbxtensor/ops/copy_op.py +39 -0
- nbxtensor/ops/cos.py +17 -0
- nbxtensor/ops/count_nonzero.py +64 -0
- nbxtensor/ops/cross_entropy.py +67 -0
- nbxtensor/ops/cummax.py +109 -0
- nbxtensor/ops/cummin.py +105 -0
- nbxtensor/ops/cumsum.py +244 -0
- nbxtensor/ops/decode_attn_vec.py +162 -0
- nbxtensor/ops/depthwise_conv2d.py +132 -0
- nbxtensor/ops/dequant_gemv.py +190 -0
- nbxtensor/ops/dft.py +67 -0
- nbxtensor/ops/dispatch.py +1228 -0
- nbxtensor/ops/div.py +75 -0
- nbxtensor/ops/dot_op.py +67 -0
- nbxtensor/ops/dropout.py +23 -0
- nbxtensor/ops/dtype_convert.py +42 -0
- nbxtensor/ops/elementwise_launch.py +81 -0
- nbxtensor/ops/elementwise_wrappers.py +2315 -0
- nbxtensor/ops/elu.py +20 -0
- nbxtensor/ops/embedding.py +69 -0
- nbxtensor/ops/eq.py +32 -0
- nbxtensor/ops/erf.py +18 -0
- nbxtensor/ops/exp.py +17 -0
- nbxtensor/ops/exp2.py +18 -0
- nbxtensor/ops/fft_op.py +125 -0
- nbxtensor/ops/fill_op.py +20 -0
- nbxtensor/ops/flash_attention.py +307 -0
- nbxtensor/ops/flash_attention_m8n8k4.py +384 -0
- nbxtensor/ops/flash_decode.py +78 -0
- nbxtensor/ops/flip_op.py +60 -0
- nbxtensor/ops/floor.py +90 -0
- nbxtensor/ops/full_op.py +24 -0
- nbxtensor/ops/fused_moe.py +417 -0
- nbxtensor/ops/fused_moe_m8n8k4.py +99 -0
- nbxtensor/ops/fused_upsample_conv.py +413 -0
- nbxtensor/ops/gather_op.py +52 -0
- nbxtensor/ops/ge.py +32 -0
- nbxtensor/ops/gelu.py +31 -0
- nbxtensor/ops/gemv_vec.py +80 -0
- nbxtensor/ops/glu.py +43 -0
- nbxtensor/ops/grid_sampler.py +145 -0
- nbxtensor/ops/group_norm_tiled.py +67 -0
- nbxtensor/ops/groupnorm.py +123 -0
- nbxtensor/ops/gt.py +32 -0
- nbxtensor/ops/hardsigmoid.py +18 -0
- nbxtensor/ops/hardswish.py +19 -0
- nbxtensor/ops/im2col.py +57 -0
- nbxtensor/ops/index_add.py +95 -0
- nbxtensor/ops/index_put_op.py +92 -0
- nbxtensor/ops/index_select.py +121 -0
- nbxtensor/ops/index_wrappers.py +770 -0
- nbxtensor/ops/isfinite.py +20 -0
- nbxtensor/ops/isinf.py +20 -0
- nbxtensor/ops/isnan.py +18 -0
- nbxtensor/ops/launch_keys.py +1054 -0
- nbxtensor/ops/launch_observer.py +54 -0
- nbxtensor/ops/layernorm.py +71 -0
- nbxtensor/ops/le.py +35 -0
- nbxtensor/ops/leaky_relu.py +19 -0
- nbxtensor/ops/lerp.py +76 -0
- nbxtensor/ops/linalg_wrappers.py +1370 -0
- nbxtensor/ops/linspace_op.py +47 -0
- nbxtensor/ops/log.py +17 -0
- nbxtensor/ops/log_sigmoid.py +23 -0
- nbxtensor/ops/logical_and.py +26 -0
- nbxtensor/ops/logical_not.py +24 -0
- nbxtensor/ops/logical_or.py +26 -0
- nbxtensor/ops/logical_xor.py +37 -0
- nbxtensor/ops/loss_wrappers.py +282 -0
- nbxtensor/ops/lt.py +32 -0
- nbxtensor/ops/masked_fill.py +21 -0
- nbxtensor/ops/matmul.py +377 -0
- nbxtensor/ops/matmul_m8n8k4.py +235 -0
- nbxtensor/ops/max_pool2d.py +74 -0
- nbxtensor/ops/max_pool3d.py +93 -0
- nbxtensor/ops/max_reduce.py +91 -0
- nbxtensor/ops/maximum.py +18 -0
- nbxtensor/ops/mean.py +40 -0
- nbxtensor/ops/min_reduce.py +91 -0
- nbxtensor/ops/minimum.py +18 -0
- nbxtensor/ops/mish.py +22 -0
- nbxtensor/ops/mma_m8n8k4.py +152 -0
- nbxtensor/ops/moe_align.py +195 -0
- nbxtensor/ops/moe_decode_vec.py +374 -0
- nbxtensor/ops/moe_device_residency.py +141 -0
- nbxtensor/ops/moe_wrappers.py +345 -0
- nbxtensor/ops/mse_loss.py +79 -0
- nbxtensor/ops/mul.py +51 -0
- nbxtensor/ops/nan_to_num.py +23 -0
- nbxtensor/ops/ne.py +32 -0
- nbxtensor/ops/neg.py +17 -0
- nbxtensor/ops/nllloss.py +124 -0
- nbxtensor/ops/nonzero.py +33 -0
- nbxtensor/ops/nonzero_op.py +62 -0
- nbxtensor/ops/norm_wrappers.py +579 -0
- nbxtensor/ops/ones_op.py +20 -0
- nbxtensor/ops/oracles/__init__.py +6 -0
- nbxtensor/ops/oracles/conv2d_fp64.py +450 -0
- nbxtensor/ops/pad_op.py +159 -0
- nbxtensor/ops/paged_decode_attn.py +215 -0
- nbxtensor/ops/pixel_shuffle.py +144 -0
- nbxtensor/ops/pixel_unshuffle.py +72 -0
- nbxtensor/ops/pow.py +137 -0
- nbxtensor/ops/prod.py +81 -0
- nbxtensor/ops/rand_op.py +52 -0
- nbxtensor/ops/random_wrappers.py +366 -0
- nbxtensor/ops/reciprocal.py +17 -0
- nbxtensor/ops/reduction_wrappers.py +845 -0
- nbxtensor/ops/relu.py +18 -0
- nbxtensor/ops/remainder.py +46 -0
- nbxtensor/ops/repeat_interleave.py +48 -0
- nbxtensor/ops/repeat_op.py +132 -0
- nbxtensor/ops/residual_chain.py +374 -0
- nbxtensor/ops/rmsnorm.py +68 -0
- nbxtensor/ops/rope.py +142 -0
- nbxtensor/ops/rsqrt.py +17 -0
- nbxtensor/ops/scaled_softmax.py +96 -0
- nbxtensor/ops/scatter_op.py +139 -0
- nbxtensor/ops/screen_oracle.py +479 -0
- nbxtensor/ops/selu.py +22 -0
- nbxtensor/ops/shape_wrappers.py +1032 -0
- nbxtensor/ops/sigmoid.py +19 -0
- nbxtensor/ops/silu.py +27 -0
- nbxtensor/ops/sin.py +17 -0
- nbxtensor/ops/softmax.py +169 -0
- nbxtensor/ops/softplus.py +27 -0
- nbxtensor/ops/sort_op.py +304 -0
- nbxtensor/ops/spectral_wrappers.py +481 -0
- nbxtensor/ops/sqrt.py +17 -0
- nbxtensor/ops/stack_op.py +84 -0
- nbxtensor/ops/std.py +113 -0
- nbxtensor/ops/strided_copy.py +200 -0
- nbxtensor/ops/sub.py +35 -0
- nbxtensor/ops/sum.py +40 -0
- nbxtensor/ops/tan.py +19 -0
- nbxtensor/ops/tanh.py +21 -0
- nbxtensor/ops/threshold.py +19 -0
- nbxtensor/ops/tiling.yml +91 -0
- nbxtensor/ops/tiling_policy.py +43 -0
- nbxtensor/ops/tiling_sizes.py +370 -0
- nbxtensor/ops/topk.py +267 -0
- nbxtensor/ops/trace_op.py +64 -0
- nbxtensor/ops/tril.py +64 -0
- nbxtensor/ops/triu.py +62 -0
- nbxtensor/ops/unfold_op.py +37 -0
- nbxtensor/ops/unique_op.py +97 -0
- nbxtensor/ops/upsample_bilinear2d.py +106 -0
- nbxtensor/ops/upsample_nearest2d.py +52 -0
- nbxtensor/ops/upsample_nearest3d.py +61 -0
- nbxtensor/ops/var.py +136 -0
- nbxtensor/ops/vector_norm.py +216 -0
- nbxtensor/ops/weight_norm.py +104 -0
- nbxtensor/ops/where.py +39 -0
- nbxtensor/ops/wrappers.py +825 -0
- nbxtensor/ops/zeros_op.py +20 -0
- nbxtensor/quant/__init__.py +1 -0
- nbxtensor/quant/quantized_tensor.py +187 -0
- nbxtensor/random/__init__.py +1 -0
- nbxtensor/random/mt_randn.py +154 -0
- nbxtensor/random/rng_pin.py +59 -0
- nbxtensor/random/rng_stream.py +118 -0
- nbxtensor/random/seeded_draw.py +223 -0
- nbxtensor/shape/__init__.py +157 -0
- nbxtensor/tensor.py +1867 -0
- nbxtensor-0.0.1.dist-info/METADATA +379 -0
- nbxtensor-0.0.1.dist-info/RECORD +291 -0
- nbxtensor-0.0.1.dist-info/WHEEL +4 -0
- nbxtensor-0.0.1.dist-info/licenses/LICENSE +189 -0
nbxtensor/ops/clamp.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Clamp — pure @triton.jit kernel."""
|
|
2
|
+
|
|
3
|
+
import triton
|
|
4
|
+
import triton.language as tl
|
|
5
|
+
|
|
6
|
+
@triton.jit
|
|
7
|
+
def clamp_forward_kernel(
|
|
8
|
+
input_ptr, output_ptr,
|
|
9
|
+
n_elements,
|
|
10
|
+
min_val, max_val,
|
|
11
|
+
has_min: tl.constexpr,
|
|
12
|
+
has_max: tl.constexpr,
|
|
13
|
+
BLOCK_SIZE: tl.constexpr,
|
|
14
|
+
):
|
|
15
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
16
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
17
|
+
mask = offset < n_elements
|
|
18
|
+
|
|
19
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
20
|
+
if has_min:
|
|
21
|
+
x = tl.maximum(min_val, x)
|
|
22
|
+
if has_max:
|
|
23
|
+
x = tl.minimum(max_val, x)
|
|
24
|
+
tl.store(output_ptr + offset, x, mask=mask)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Clamp min — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
aten::clamp_min(x, min_val) = max(x, min_val). Separate file per ATen op convention.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import triton
|
|
7
|
+
import triton.language as tl
|
|
8
|
+
|
|
9
|
+
@triton.jit
|
|
10
|
+
def clamp_min_forward_kernel(
|
|
11
|
+
input_ptr, output_ptr,
|
|
12
|
+
n_elements,
|
|
13
|
+
min_val,
|
|
14
|
+
BLOCK_SIZE: tl.constexpr,
|
|
15
|
+
):
|
|
16
|
+
pid = tl.program_id(0).to(tl.int64)
|
|
17
|
+
offset = pid.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
18
|
+
mask = offset < n_elements
|
|
19
|
+
|
|
20
|
+
x = tl.load(input_ptr + offset, mask=mask)
|
|
21
|
+
result = tl.maximum(min_val, x)
|
|
22
|
+
tl.store(output_ptr + offset, result, mask=mask)
|
nbxtensor/ops/conv2d.py
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""Conv2D forward — pure @triton.jit kernel with autotune.
|
|
2
|
+
|
|
3
|
+
From attorch (BobMcDear/attorch) conv_kernels.py — chosen over FlagGems
|
|
4
|
+
for V100 compatibility (proper num_stages handling). Im2col approach
|
|
5
|
+
with tl.dot, fp32 accumulation, groups support.
|
|
6
|
+
|
|
7
|
+
Phase 1.5 conv2d Step (b) (2026-05): @triton.autotune + cache_results=True
|
|
8
|
+
adoption — same pattern as mm/bmm/addmm/baddbmm (commits d514bdb +
|
|
9
|
+
20ed765). Configs restricted to Volta-viable subspace (BLOCK_BHW ∈
|
|
10
|
+
{32,64,128}, BLOCK_OUTF ∈ {32,64,128}, BLOCK_INF ∈ {16,32,64}, num_warps
|
|
11
|
+
∈ {2,4}, num_stages ∈ {2,3}). Triton compiler ejects configs that
|
|
12
|
+
exceed sm_70's 96 KB SMEM at compile time (native fallback). See
|
|
13
|
+
CLAUDE.md "Autotune policy" for the doctrinal exception scope.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import triton
|
|
17
|
+
|
|
18
|
+
from nbxtensor.ops._configs import nbx_autotune
|
|
19
|
+
import triton.language as tl
|
|
20
|
+
|
|
21
|
+
from nbxtensor.ops._autotune_policy import maybe_pin_single, is_conv2d_pinned
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Volta-viable conv2d autotune subspace.
|
|
25
|
+
# SMEM budget per config (rough): 2 stages × dtype_bytes × (BLOCK_BHW *
|
|
26
|
+
# BLOCK_INF [input tile] + BLOCK_INF * BLOCK_OUTF [weight tile]).
|
|
27
|
+
# fp16 (2B), 2 stages: BLOCK_BHW=128, BLOCK_INF=64, BLOCK_OUTF=128 →
|
|
28
|
+
# 2 * 2 * (128*64 + 64*128) = 65 KB → fits 96 KB sm_70.
|
|
29
|
+
# BLOCK_BHW=128, BLOCK_INF=64, BLOCK_OUTF=128, stages=3 → 98 KB → over.
|
|
30
|
+
# Triton ejects over-SMEM configs at compile time via native fallback.
|
|
31
|
+
_CONV2D_AUTOTUNE_VOLTA = [
|
|
32
|
+
triton.Config({'BLOCK_SIZE_BHW': 32, 'BLOCK_SIZE_OUTF': 32, 'BLOCK_SIZE_INF': 16}, num_stages=2, num_warps=2),
|
|
33
|
+
triton.Config({'BLOCK_SIZE_BHW': 32, 'BLOCK_SIZE_OUTF': 32, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=2),
|
|
34
|
+
triton.Config({'BLOCK_SIZE_BHW': 32, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=2),
|
|
35
|
+
triton.Config({'BLOCK_SIZE_BHW': 32, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 32}, num_stages=3, num_warps=4),
|
|
36
|
+
triton.Config({'BLOCK_SIZE_BHW': 32, 'BLOCK_SIZE_OUTF': 128, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
|
|
37
|
+
triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 32, 'BLOCK_SIZE_INF': 16}, num_stages=2, num_warps=2),
|
|
38
|
+
triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 32, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
|
|
39
|
+
triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 32, 'BLOCK_SIZE_INF': 32}, num_stages=3, num_warps=4),
|
|
40
|
+
triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 16}, num_stages=2, num_warps=4),
|
|
41
|
+
triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
|
|
42
|
+
triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 32}, num_stages=3, num_warps=4),
|
|
43
|
+
triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 64}, num_stages=2, num_warps=4),
|
|
44
|
+
triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 128, 'BLOCK_SIZE_INF': 16}, num_stages=2, num_warps=4),
|
|
45
|
+
triton.Config({'BLOCK_SIZE_BHW': 64, 'BLOCK_SIZE_OUTF': 128, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
|
|
46
|
+
triton.Config({'BLOCK_SIZE_BHW': 128, 'BLOCK_SIZE_OUTF': 32, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
|
|
47
|
+
triton.Config({'BLOCK_SIZE_BHW': 128, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 16}, num_stages=2, num_warps=4),
|
|
48
|
+
triton.Config({'BLOCK_SIZE_BHW': 128, 'BLOCK_SIZE_OUTF': 64, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
|
|
49
|
+
triton.Config({'BLOCK_SIZE_BHW': 128, 'BLOCK_SIZE_OUTF': 128, 'BLOCK_SIZE_INF': 32}, num_stages=2, num_warps=4),
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
# Every tile in BOTH orientations (`SPATIAL_FAST`): the certifier picks per key per hardware. Metal takes the
|
|
53
|
+
# spatial-fast tile (CogVideoX-2b VAE 3x3 (9,128,242x362) 109 -> 55 ms, Mac 2026-10-21); on V100 today's tile wins
|
|
54
|
+
# five shapes of six (9-40 %, Dell 2026-10-21 01:53). Same key; an entry certified before the axis existed names no
|
|
55
|
+
# orientation and keeps today's tile.
|
|
56
|
+
_CONV2D_AUTOTUNE = _CONV2D_AUTOTUNE_VOLTA + [
|
|
57
|
+
triton.Config({**c.kwargs, 'SPATIAL_FAST': True}, num_stages=c.num_stages, num_warps=c.num_warps)
|
|
58
|
+
for c in _CONV2D_AUTOTUNE_VOLTA
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@nbx_autotune(
|
|
63
|
+
configs=maybe_pin_single(_CONV2D_AUTOTUNE, is_conv2d_pinned),
|
|
64
|
+
key=['batch_dim', 'in_feat_dim', 'in_height', 'in_width_key',
|
|
65
|
+
'out_feat_dim', 'out_height', 'out_width_key',
|
|
66
|
+
'kernel_height', 'kernel_width',
|
|
67
|
+
'stride_height', 'stride_width',
|
|
68
|
+
'padding_height', 'padding_width',
|
|
69
|
+
'dilation_height', 'dilation_width',
|
|
70
|
+
'groups', 'fp16'],
|
|
71
|
+
cache_results=True,
|
|
72
|
+
)
|
|
73
|
+
@triton.jit
|
|
74
|
+
def conv2d_forward_kernel(
|
|
75
|
+
input_pointer, weight_pointer, output_pointer,
|
|
76
|
+
batch_dim, in_feat_dim, in_height, in_width,
|
|
77
|
+
out_feat_dim, out_height, out_width,
|
|
78
|
+
in_width_key, out_width_key,
|
|
79
|
+
input_batch_stride, input_in_feat_stride, input_height_stride, input_width_stride,
|
|
80
|
+
weight_out_feat_stride, weight_in_feat_stride, weight_height_stride, weight_width_stride,
|
|
81
|
+
output_batch_stride, output_out_feat_stride, output_height_stride, output_width_stride,
|
|
82
|
+
kernel_height: tl.constexpr, kernel_width: tl.constexpr,
|
|
83
|
+
stride_height: tl.constexpr, stride_width: tl.constexpr,
|
|
84
|
+
padding_height: tl.constexpr, padding_width: tl.constexpr,
|
|
85
|
+
groups: tl.constexpr, fp16: tl.constexpr,
|
|
86
|
+
dilation_height: tl.constexpr = 1, dilation_width: tl.constexpr = 1,
|
|
87
|
+
BLOCK_SIZE_BHW: tl.constexpr = 64,
|
|
88
|
+
BLOCK_SIZE_INF: tl.constexpr = 32,
|
|
89
|
+
BLOCK_SIZE_OUTF: tl.constexpr = 64,
|
|
90
|
+
WIDE_OFFSETS: tl.constexpr = True,
|
|
91
|
+
SPATIAL_FAST: tl.constexpr = False,
|
|
92
|
+
):
|
|
93
|
+
# 64-bit offsets only where an operand's extent needs them (`WIDE_OFFSETS`, chosen by the wrapper from the
|
|
94
|
+
# tensors' extents): 32-bit offsets are exact below 2^31 and the 64-bit ones cost 17 % on Apple's ALUs once the
|
|
95
|
+
# Metal backend stopped narrowing them (Mac, 2026-10-20, 60 bf16 keys re-proven)
|
|
96
|
+
if WIDE_OFFSETS:
|
|
97
|
+
bhw_pid = tl.program_id(0).to(tl.int64)
|
|
98
|
+
outf_pid = tl.program_id(1).to(tl.int64)
|
|
99
|
+
group_pid = tl.program_id(2).to(tl.int64)
|
|
100
|
+
else:
|
|
101
|
+
bhw_pid = tl.program_id(0)
|
|
102
|
+
outf_pid = tl.program_id(1)
|
|
103
|
+
group_pid = tl.program_id(2)
|
|
104
|
+
|
|
105
|
+
in_group_dim = in_feat_dim // groups
|
|
106
|
+
out_group_dim = out_feat_dim // groups
|
|
107
|
+
|
|
108
|
+
if SPATIAL_FAST:
|
|
109
|
+
# The tile is [OUTF, BHW] with the SPATIAL axis fast: NCHW holds a channel plane contiguous along width, so the
|
|
110
|
+
# input block is loaded [INF, BHW] and the output stored [OUTF, BHW] along it, and the weight block [OUTF, INF]
|
|
111
|
+
# steps the small kh*kw stride. Loaded [BHW, INF] and stored [BHW, OUTF], both fast axes stepped a whole channel
|
|
112
|
+
# plane: the Metal backend gathered them element by element — CogVideoX-2b's VAE 3x3 (9,128,242x362) 109 ms
|
|
113
|
+
# against 55 ms transposed, same values (max |diff| 0), native 31 ms (Mac, 2026-10-21).
|
|
114
|
+
bhw_offset = bhw_pid * BLOCK_SIZE_BHW + tl.arange(0, BLOCK_SIZE_BHW)
|
|
115
|
+
bh_offset = bhw_offset // out_width
|
|
116
|
+
batch_offset = bh_offset // out_height
|
|
117
|
+
|
|
118
|
+
outf_offset = outf_pid * BLOCK_SIZE_OUTF + tl.arange(0, BLOCK_SIZE_OUTF)
|
|
119
|
+
oh_offset = bh_offset % out_height
|
|
120
|
+
ow_offset = bhw_offset % out_width
|
|
121
|
+
|
|
122
|
+
input_pointer += (input_batch_stride * batch_offset +
|
|
123
|
+
input_in_feat_stride * group_pid * in_group_dim)[None, :]
|
|
124
|
+
weight_pointer += (weight_out_feat_stride * outf_offset +
|
|
125
|
+
weight_out_feat_stride * group_pid * out_group_dim)[:, None]
|
|
126
|
+
|
|
127
|
+
accum = tl.zeros((BLOCK_SIZE_OUTF, BLOCK_SIZE_BHW), dtype=tl.float32)
|
|
128
|
+
|
|
129
|
+
for h in range(kernel_height):
|
|
130
|
+
for w in range(kernel_width):
|
|
131
|
+
ih_offset = h * dilation_height - padding_height + stride_height * oh_offset
|
|
132
|
+
iw_offset = w * dilation_width - padding_width + stride_width * ow_offset
|
|
133
|
+
spatial = input_height_stride * ih_offset + input_width_stride * iw_offset
|
|
134
|
+
spatial_ok = ((batch_offset < batch_dim) &
|
|
135
|
+
(ih_offset >= 0) & (ih_offset < in_height) &
|
|
136
|
+
(iw_offset >= 0) & (iw_offset < in_width))
|
|
137
|
+
for c in range(0, in_group_dim, BLOCK_SIZE_INF):
|
|
138
|
+
# 64-bit like every offset above it when the extents need it: `input_in_feat_stride *
|
|
139
|
+
# inf_offset` is a product of the channel plane (36 000 000 elements at 6000x6000) and the
|
|
140
|
+
# channel index, and past channel 59 it exceeds 2^31 — the 8x upscaler's last conv (64 -> 3, a
|
|
141
|
+
# 6344x6344 tile) faulted with an illegal address, four of four, 2026-09-20 (register 58's
|
|
142
|
+
# class; the output being small, the band-streaming that saves the 64 -> 64 convs never engaged).
|
|
143
|
+
inf_offset = c + tl.arange(0, BLOCK_SIZE_INF)
|
|
144
|
+
if WIDE_OFFSETS:
|
|
145
|
+
inf_offset = inf_offset.to(tl.int64)
|
|
146
|
+
|
|
147
|
+
curr_inp = (input_pointer +
|
|
148
|
+
(input_in_feat_stride * inf_offset)[:, None] +
|
|
149
|
+
spatial[None, :])
|
|
150
|
+
curr_wt = (weight_pointer +
|
|
151
|
+
(weight_in_feat_stride * inf_offset)[None, :] +
|
|
152
|
+
(weight_height_stride * h) +
|
|
153
|
+
(weight_width_stride * w))
|
|
154
|
+
|
|
155
|
+
inp_mask = (inf_offset < in_group_dim)[:, None] & spatial_ok[None, :]
|
|
156
|
+
wt_mask = ((outf_offset < out_group_dim)[:, None] &
|
|
157
|
+
(inf_offset < in_group_dim)[None, :])
|
|
158
|
+
|
|
159
|
+
inp_block = tl.load(curr_inp, mask=inp_mask, other=0.0)
|
|
160
|
+
wt_block = tl.load(curr_wt, mask=wt_mask, other=0.0)
|
|
161
|
+
|
|
162
|
+
if fp16:
|
|
163
|
+
inp_block = inp_block.to(tl.float16)
|
|
164
|
+
wt_block = wt_block.to(tl.float16)
|
|
165
|
+
|
|
166
|
+
accum += tl.dot(wt_block, inp_block)
|
|
167
|
+
|
|
168
|
+
out_ptr = (output_pointer +
|
|
169
|
+
(output_batch_stride * batch_offset)[None, :] +
|
|
170
|
+
(output_out_feat_stride * (group_pid * out_group_dim + outf_offset))[:, None] +
|
|
171
|
+
(output_height_stride * oh_offset)[None, :] +
|
|
172
|
+
(output_width_stride * ow_offset)[None, :])
|
|
173
|
+
out_mask = ((batch_offset < batch_dim)[None, :] &
|
|
174
|
+
(outf_offset < out_group_dim)[:, None] &
|
|
175
|
+
(oh_offset < out_height)[None, :] &
|
|
176
|
+
(ow_offset < out_width)[None, :])
|
|
177
|
+
|
|
178
|
+
tl.store(out_ptr, accum, mask=out_mask)
|
|
179
|
+
else:
|
|
180
|
+
bhw_offset = bhw_pid * BLOCK_SIZE_BHW + tl.arange(0, BLOCK_SIZE_BHW)
|
|
181
|
+
bh_offset = bhw_offset // out_width
|
|
182
|
+
batch_offset = bh_offset // out_height
|
|
183
|
+
|
|
184
|
+
outf_offset = outf_pid * BLOCK_SIZE_OUTF + tl.arange(0, BLOCK_SIZE_OUTF)
|
|
185
|
+
oh_offset = bh_offset % out_height
|
|
186
|
+
ow_offset = bhw_offset % out_width
|
|
187
|
+
|
|
188
|
+
input_pointer += (input_batch_stride * batch_offset +
|
|
189
|
+
input_in_feat_stride * group_pid * in_group_dim)[:, None]
|
|
190
|
+
weight_pointer += (weight_out_feat_stride * outf_offset +
|
|
191
|
+
weight_out_feat_stride * group_pid * out_group_dim)[None, :]
|
|
192
|
+
|
|
193
|
+
accum = tl.zeros((BLOCK_SIZE_BHW, BLOCK_SIZE_OUTF), dtype=tl.float32)
|
|
194
|
+
|
|
195
|
+
for h in range(kernel_height):
|
|
196
|
+
for w in range(kernel_width):
|
|
197
|
+
for c in range(0, in_group_dim, BLOCK_SIZE_INF):
|
|
198
|
+
# 64-bit like every offset above it: `input_in_feat_stride *
|
|
199
|
+
# inf_offset` is a product of the channel plane (36 000 000
|
|
200
|
+
# elements at 6000x6000) and the channel index, and past channel
|
|
201
|
+
# 59 it exceeds 2^31 — the 8x upscaler's last conv (64 -> 3, a
|
|
202
|
+
# 6344x6344 tile) faulted with an illegal address, four of four,
|
|
203
|
+
# 2026-09-20 (register 58's class; the output being small, the
|
|
204
|
+
# band-streaming that saves the 64 -> 64 convs never engaged).
|
|
205
|
+
inf_offset = c + tl.arange(0, BLOCK_SIZE_INF)
|
|
206
|
+
if WIDE_OFFSETS:
|
|
207
|
+
inf_offset = inf_offset.to(tl.int64)
|
|
208
|
+
ih_offset = h * dilation_height - padding_height + stride_height * oh_offset
|
|
209
|
+
iw_offset = w * dilation_width - padding_width + stride_width * ow_offset
|
|
210
|
+
|
|
211
|
+
curr_inp = (input_pointer +
|
|
212
|
+
(input_in_feat_stride * inf_offset)[None, :] +
|
|
213
|
+
(input_height_stride * ih_offset)[:, None] +
|
|
214
|
+
(input_width_stride * iw_offset)[:, None])
|
|
215
|
+
curr_wt = (weight_pointer +
|
|
216
|
+
(weight_in_feat_stride * inf_offset)[:, None] +
|
|
217
|
+
(weight_height_stride * h) +
|
|
218
|
+
(weight_width_stride * w))
|
|
219
|
+
|
|
220
|
+
inp_mask = ((batch_offset < batch_dim)[:, None] &
|
|
221
|
+
(inf_offset < in_group_dim)[None, :] &
|
|
222
|
+
(ih_offset >= 0)[:, None] &
|
|
223
|
+
(ih_offset < in_height)[:, None] &
|
|
224
|
+
(iw_offset >= 0)[:, None] &
|
|
225
|
+
(iw_offset < in_width)[:, None])
|
|
226
|
+
wt_mask = ((inf_offset < in_group_dim)[:, None] &
|
|
227
|
+
(outf_offset < out_group_dim)[None, :])
|
|
228
|
+
|
|
229
|
+
inp_block = tl.load(curr_inp, mask=inp_mask, other=0.0)
|
|
230
|
+
wt_block = tl.load(curr_wt, mask=wt_mask, other=0.0)
|
|
231
|
+
|
|
232
|
+
if fp16:
|
|
233
|
+
inp_block = inp_block.to(tl.float16)
|
|
234
|
+
wt_block = wt_block.to(tl.float16)
|
|
235
|
+
|
|
236
|
+
accum += tl.dot(inp_block, wt_block)
|
|
237
|
+
|
|
238
|
+
out_ptr = (output_pointer +
|
|
239
|
+
(output_batch_stride * batch_offset)[:, None] +
|
|
240
|
+
(output_out_feat_stride * (group_pid * out_group_dim + outf_offset))[None, :] +
|
|
241
|
+
(output_height_stride * oh_offset)[:, None] +
|
|
242
|
+
(output_width_stride * ow_offset)[:, None])
|
|
243
|
+
out_mask = ((batch_offset < batch_dim)[:, None] &
|
|
244
|
+
(outf_offset < out_group_dim)[None, :] &
|
|
245
|
+
(oh_offset < out_height)[:, None] &
|
|
246
|
+
(ow_offset < out_width)[:, None])
|
|
247
|
+
|
|
248
|
+
tl.store(out_ptr, accum, mask=out_mask)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@triton.jit
|
|
252
|
+
def conv2d_bias_inplace_kernel(out_ptr, bias_ptr, n, HW, C, BLOCK: tl.constexpr):
|
|
253
|
+
"""out[i] += bias[(i // HW) % C], in place, flat over a CONTIGUOUS NCHW
|
|
254
|
+
output. The conv wrapper's bias epilogue: the out-of-place `add` here
|
|
255
|
+
allocated a second full-size output per biased convolution — 2450 MB of
|
|
256
|
+
the measured 8008 MB tile peak on real-esrgan-x8 @1024 (malloc trace,
|
|
257
|
+
2026-09-21). Elementwise same-offset read/write, so aliasing the input
|
|
258
|
+
as the output is exact by construction. Fixed launch config — this
|
|
259
|
+
kernel takes no autotune key and adds nothing to any census."""
|
|
260
|
+
i = tl.program_id(0).to(tl.int64) * BLOCK + tl.arange(0, BLOCK)
|
|
261
|
+
m = i < n
|
|
262
|
+
c = (i // HW) % C
|
|
263
|
+
v = tl.load(out_ptr + i, mask=m)
|
|
264
|
+
b = tl.load(bias_ptr + c, mask=m)
|
|
265
|
+
tl.store(out_ptr + i, v + b, mask=m)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Convolution on the m8n8k4 matrix unit — `conv2d` where the hardware profile declares `matrix_unit`.
|
|
2
|
+
|
|
3
|
+
An implicit GEMM over the operands in place: per group g, C[m, n] = sum_k A[m, k] B[n, k] with
|
|
4
|
+
m = (batch, oh, ow), n = the group's output channel and k = (c, r, s) flattened in the weight's own order, so a
|
|
5
|
+
contiguous weight [OC, IC/g, KH, KW] is a k-contiguous B read in place (no copy, no im2col buffer) and A is gathered
|
|
6
|
+
from the input through its four strides (padding, stride and dilation are masks and index arithmetic). Same contract
|
|
7
|
+
as `conv2d_forward_kernel` (ops/conv2d.py): fp32
|
|
8
|
+
accumulation (one K tile in the unit, which rounds toward zero, the tiles summed on the FMA units with round to
|
|
9
|
+
nearest — ops/matmul_m8n8k4.py), int64 offsets, every tail masked, the output cast to C's dtype; the bias, when given, is added to the
|
|
10
|
+
fp32 accumulator before that single rounding (the product of two fp16 values is exact in fp32, so the sums are the
|
|
11
|
+
tl.dot kernel's in another order). The tile is the profile's (`matrix_unit.mm`), never tuned at run time: no
|
|
12
|
+
autotune key, nothing to certify.
|
|
13
|
+
|
|
14
|
+
An operand the DtypeEngine carries SPLIT (SPLIT_A / SPLIT_B: `nbxtensor/dtype/operands.matrix_unit_representation`, the
|
|
15
|
+
profile's `matrix_unit.fp32_split` — the video VAEs' fp32 convolutions) is staged as the GEMM stages it
|
|
16
|
+
(ops/matmul_m8n8k4.py `_stage_split`): hi + lo of the unit's operand dtype under a power-of-two scale per row of A
|
|
17
|
+
(an output position) or column of B (an output channel) of each K tile, three MMAs into two accumulators, the tile's
|
|
18
|
+
sum rescaled and added to the fp32 accumulator on the FMA units.
|
|
19
|
+
|
|
20
|
+
A is loaded with consecutive lanes on consecutive output rows (consecutive `ow`: coalesced at stride 1), each lane
|
|
21
|
+
holding 4 consecutive k — one 8-byte store into the [k-step x rows, 4] staging of ops/mma_m8n8k4.py.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import functools
|
|
25
|
+
|
|
26
|
+
from triton.experimental import gluon
|
|
27
|
+
from triton.experimental.gluon import language as gl
|
|
28
|
+
|
|
29
|
+
from .mma_m8n8k4 import acc_rows_cols, mma_k4, stage_k_inner
|
|
30
|
+
from .matmul_m8n8k4 import _bcast_cols, _bcast_rows, _stage_split, mm_layouts
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@gluon.jit
|
|
34
|
+
def conv2d_m8n8k4_kernel(
|
|
35
|
+
X, Wt, Bias, C,
|
|
36
|
+
M, NG, K, IH, IW, OH, OW,
|
|
37
|
+
stride_xn, stride_xc, stride_xh, stride_xw,
|
|
38
|
+
stride_wo, stride_cn, stride_cc, stride_ch, stride_cw,
|
|
39
|
+
CG, OG,
|
|
40
|
+
KH: gl.constexpr, KW: gl.constexpr, SH_: gl.constexpr, SW_: gl.constexpr, PH: gl.constexpr,
|
|
41
|
+
PW: gl.constexpr, DH: gl.constexpr, DW: gl.constexpr,
|
|
42
|
+
HAS_BIAS: gl.constexpr,
|
|
43
|
+
SPLIT_A: gl.constexpr, SPLIT_B: gl.constexpr, HI_EXP: gl.constexpr, LO_SHIFT: gl.constexpr,
|
|
44
|
+
MB: gl.constexpr, NB: gl.constexpr, BK: gl.constexpr, GROUP_M: gl.constexpr,
|
|
45
|
+
L: gl.constexpr, LA: gl.constexpr, LB: gl.constexpr, GA: gl.constexpr, GB: gl.constexpr,
|
|
46
|
+
):
|
|
47
|
+
BM: gl.constexpr = 16 * MB
|
|
48
|
+
BN: gl.constexpr = 16 * NB
|
|
49
|
+
J: gl.constexpr = BK // 4
|
|
50
|
+
KHW: gl.constexpr = KH * KW
|
|
51
|
+
SH: gl.constexpr = gl.SwizzledSharedLayout(1, 1, 1, order=[1, 0])
|
|
52
|
+
|
|
53
|
+
pid = gl.program_id(0).to(gl.int64)
|
|
54
|
+
g = gl.program_id(1).to(gl.int64) # the group
|
|
55
|
+
X = X + g * CG * stride_xc
|
|
56
|
+
Wt = Wt + g * OG * stride_wo
|
|
57
|
+
C = C + g * OG * stride_cc
|
|
58
|
+
num_pid_m = gl.cdiv(M, BM)
|
|
59
|
+
num_pid_n = gl.cdiv(NG, BN)
|
|
60
|
+
num_pid_in_group = GROUP_M * num_pid_n
|
|
61
|
+
first_pid_m = (pid // num_pid_in_group) * GROUP_M
|
|
62
|
+
group_size_m = gl.minimum(num_pid_m - first_pid_m, GROUP_M)
|
|
63
|
+
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
|
|
64
|
+
pid_n = (pid % num_pid_in_group) // group_size_m
|
|
65
|
+
m0 = pid_m * BM
|
|
66
|
+
n0 = pid_n * BN
|
|
67
|
+
|
|
68
|
+
sa = gl.allocate_shared_memory(gl.float16, [J * BM, 4], SH)
|
|
69
|
+
sb = gl.allocate_shared_memory(gl.float16, [J * BN, 4], SH)
|
|
70
|
+
if SPLIT_A:
|
|
71
|
+
sal = gl.allocate_shared_memory(gl.float16, [J * BM, 4], SH)
|
|
72
|
+
if SPLIT_B:
|
|
73
|
+
sbl = gl.allocate_shared_memory(gl.float16, [J * BN, 4], SH)
|
|
74
|
+
|
|
75
|
+
# A rows: (batch, oh, ow) once per tile; the k columns are decoded per K tile against constexpr divisors.
|
|
76
|
+
ma1 = m0 + gl.arange(0, BM, layout=gl.SliceLayout(1, GA))
|
|
77
|
+
ow_ = ma1 % OW
|
|
78
|
+
oh_ = (ma1 // OW) % OH
|
|
79
|
+
xrow = ((ma1 // OW) // OH) * stride_xn
|
|
80
|
+
ih0 = (oh_ * SH_ - PH)[:, None]
|
|
81
|
+
iw0 = (ow_ * SW_ - PW)[:, None]
|
|
82
|
+
ma = (ma1 < M)[:, None]
|
|
83
|
+
xrow = xrow[:, None]
|
|
84
|
+
ka1 = gl.arange(0, BK, layout=gl.SliceLayout(0, GA))
|
|
85
|
+
# B [BN, BK], k contiguous: the weight read in place.
|
|
86
|
+
nb_ = gl.arange(0, BN, layout=gl.SliceLayout(1, GB))[:, None]
|
|
87
|
+
kb = gl.arange(0, BK, layout=gl.SliceLayout(0, GB))[None, :]
|
|
88
|
+
pb = Wt + (n0 + nb_) * stride_wo + kb
|
|
89
|
+
mb_ = (n0 + nb_) < NG
|
|
90
|
+
|
|
91
|
+
acc = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
|
|
92
|
+
ta = _gather(X, xrow, ih0, iw0, ma, ka1, 0, K, IH, IW, stride_xc, stride_xh, stride_xw, KHW, KW, DH, DW)
|
|
93
|
+
tb = gl.load(pb, mask=mb_ & (kb < K), other=0.0)
|
|
94
|
+
for k0 in range(0, K, BK):
|
|
95
|
+
gl.barrier() # every warp is done reading the previous tile
|
|
96
|
+
if SPLIT_A:
|
|
97
|
+
inv_a = _stage_split(sa, sal, ta, True, BM, BK, HI_EXP, LO_SHIFT)
|
|
98
|
+
else:
|
|
99
|
+
stage_k_inner(sa, ta, BM, BK)
|
|
100
|
+
if SPLIT_B:
|
|
101
|
+
inv_b = _stage_split(sb, sbl, tb, True, BN, BK, HI_EXP, LO_SHIFT)
|
|
102
|
+
else:
|
|
103
|
+
stage_k_inner(sb, tb, BN, BK)
|
|
104
|
+
gl.barrier()
|
|
105
|
+
if k0 + BK < K: # the next tile loads while the HMMAs run
|
|
106
|
+
ta = _gather(X, xrow, ih0, iw0, ma, ka1, k0 + BK, K, IH, IW, stride_xc, stride_xh, stride_xw, KHW, KW,
|
|
107
|
+
DH, DW)
|
|
108
|
+
tb = gl.load(pb + (k0 + BK), mask=mb_ & (kb + k0 + BK < K), other=0.0)
|
|
109
|
+
if SPLIT_A or SPLIT_B:
|
|
110
|
+
t_hh = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
|
|
111
|
+
t_lo = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
|
|
112
|
+
for j in gl.static_range(J):
|
|
113
|
+
t_hh = mma_k4(sa, j * BM, sb, j * BN, t_hh, MB, NB, LA, LB, L)
|
|
114
|
+
if SPLIT_A:
|
|
115
|
+
t_lo = mma_k4(sal, j * BM, sb, j * BN, t_lo, MB, NB, LA, LB, L)
|
|
116
|
+
if SPLIT_B:
|
|
117
|
+
t_lo = mma_k4(sa, j * BM, sbl, j * BN, t_lo, MB, NB, LA, LB, L)
|
|
118
|
+
t = t_hh + t_lo * (1.0 / (1 << LO_SHIFT))
|
|
119
|
+
if SPLIT_A:
|
|
120
|
+
t = t * _bcast_rows(gl.reshape(inv_a, [MB, 2, 8]), L)
|
|
121
|
+
if SPLIT_B:
|
|
122
|
+
t = t * _bcast_cols(gl.reshape(inv_b, [NB, 2, 2, 4]), L)
|
|
123
|
+
acc = acc + t
|
|
124
|
+
else:
|
|
125
|
+
t = gl.zeros([MB, 2, 8, NB, 2, 2, 4], gl.float32, layout=L)
|
|
126
|
+
for j in gl.static_range(J):
|
|
127
|
+
t = mma_k4(sa, j * BM, sb, j * BN, t, MB, NB, LA, LB, L)
|
|
128
|
+
acc = acc + t # across tiles on the FMA units, round to nearest
|
|
129
|
+
|
|
130
|
+
rows, cols = acc_rows_cols(L, MB, NB)
|
|
131
|
+
rows = m0 + rows
|
|
132
|
+
cols = n0 + cols
|
|
133
|
+
mask = (rows < M) & (cols < NG)
|
|
134
|
+
if HAS_BIAS:
|
|
135
|
+
bias = gl.load(Bias + g * OG + cols.to(gl.int64), mask=cols < NG, other=0.0)
|
|
136
|
+
acc = acc + bias.to(gl.float32)
|
|
137
|
+
r64 = rows.to(gl.int64)
|
|
138
|
+
ow_o = r64 % OW
|
|
139
|
+
oh_o = (r64 // OW) % OH
|
|
140
|
+
n_o = (r64 // OW) // OH
|
|
141
|
+
gl.store(C + n_o * stride_cn + oh_o * stride_ch + ow_o * stride_cw + cols.to(gl.int64) * stride_cc,
|
|
142
|
+
acc.to(C.dtype.element_ty), mask=mask)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@gluon.jit
|
|
146
|
+
def _gather(X, xrow, ih0, iw0, ma, ka1, k0, K, IH, IW, stride_xc, stride_xh, stride_xw,
|
|
147
|
+
KHW: gl.constexpr, KW: gl.constexpr, DH: gl.constexpr, DW: gl.constexpr):
|
|
148
|
+
"""A[rows, k0:k0+BK]: input[batch, c, oh*sh - ph + r*dh, ow*sw - pw + s*dw] for k = (c, r, s), 0 outside."""
|
|
149
|
+
k = k0 + ka1
|
|
150
|
+
c = k // KHW
|
|
151
|
+
rs = k % KHW
|
|
152
|
+
ih = ih0 + ((rs // KW) * DH)[None, :]
|
|
153
|
+
iw = iw0 + ((rs % KW) * DW)[None, :]
|
|
154
|
+
m = ma & (k < K)[None, :] & (ih >= 0) & (ih < IH) & (iw >= 0) & (iw < IW)
|
|
155
|
+
p = X + xrow + (c.to(gl.int64) * stride_xc)[None, :] + ih.to(gl.int64) * stride_xh + iw.to(gl.int64) * stride_xw
|
|
156
|
+
return gl.load(p, mask=m, other=0.0)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@functools.lru_cache(maxsize=None)
|
|
160
|
+
def conv_layouts(MB: int, NB: int, WM: int, WN: int, BK: int) -> dict:
|
|
161
|
+
"""The GEMM's layouts (ops/matmul_m8n8k4.py, B k-contiguous) with the gathered A walked row-fastest: 32
|
|
162
|
+
consecutive output rows per warp, 4 consecutive k per lane."""
|
|
163
|
+
lay = dict(mm_layouts(MB, NB, WM, WN, BK, True))
|
|
164
|
+
lay["GA"] = gl.BlockedLayout([1, 4], [32, 1], [WM * WN, 1], [0, 1])
|
|
165
|
+
return lay
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Depthwise 2D convolution — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Depthwise convolution: each input channel is convolved independently with
|
|
4
|
+
its own filter (groups = C_in). Output channels = C_in * channel_multiplier
|
|
5
|
+
(typically channel_multiplier = 1).
|
|
6
|
+
|
|
7
|
+
Input: [N, C, IH, IW]
|
|
8
|
+
Weight: [C_out, 1, KH, KW] (groups = C_in)
|
|
9
|
+
Output: [N, C_out, OH, OW]
|
|
10
|
+
|
|
11
|
+
OH = (IH + 2*pad_h - dilation_h*(KH-1) - 1) // stride_h + 1
|
|
12
|
+
OW = (IW + 2*pad_w - dilation_w*(KW-1) - 1) // stride_w + 1
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import triton
|
|
16
|
+
import triton.language as tl
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@triton.jit
|
|
20
|
+
def conv_depthwise2d_kernel(
|
|
21
|
+
input_ptr,
|
|
22
|
+
weight_ptr,
|
|
23
|
+
output_ptr,
|
|
24
|
+
# Input shape
|
|
25
|
+
N, C_in, IH, IW,
|
|
26
|
+
# Weight shape: [C_out, 1, KH, KW]
|
|
27
|
+
C_out, KH, KW,
|
|
28
|
+
# Output shape
|
|
29
|
+
OH, OW,
|
|
30
|
+
# Conv parameters
|
|
31
|
+
stride_h, stride_w,
|
|
32
|
+
pad_h, pad_w,
|
|
33
|
+
dilation_h, dilation_w,
|
|
34
|
+
# Channel multiplier: C_out // C_in (usually 1)
|
|
35
|
+
ch_mult,
|
|
36
|
+
BLOCK_X: tl.constexpr, # OW tile
|
|
37
|
+
BLOCK_Y: tl.constexpr, # OH tile
|
|
38
|
+
):
|
|
39
|
+
"""Depthwise conv2d kernel.
|
|
40
|
+
|
|
41
|
+
Grid: (cdiv(OW, BLOCK_X), cdiv(OH, BLOCK_Y), N * C_out)
|
|
42
|
+
Each program computes a BLOCK_Y x BLOCK_X tile of one output channel.
|
|
43
|
+
"""
|
|
44
|
+
pid_x = tl.program_id(0).to(tl.int64)
|
|
45
|
+
pid_y = tl.program_id(1).to(tl.int64)
|
|
46
|
+
pid_nc = tl.program_id(2).to(tl.int64)
|
|
47
|
+
|
|
48
|
+
n = pid_nc // C_out
|
|
49
|
+
c_out = pid_nc % C_out
|
|
50
|
+
c_in = c_out // ch_mult
|
|
51
|
+
|
|
52
|
+
ow = pid_x * BLOCK_X + tl.arange(0, BLOCK_X) # [BLOCK_X]
|
|
53
|
+
oh = pid_y * BLOCK_Y + tl.arange(0, BLOCK_Y) # [BLOCK_Y]
|
|
54
|
+
|
|
55
|
+
mask_h = oh < OH
|
|
56
|
+
mask_w = ow < OW
|
|
57
|
+
mask_2d = mask_h[:, None] & mask_w[None, :]
|
|
58
|
+
|
|
59
|
+
acc = tl.zeros([BLOCK_Y, BLOCK_X], dtype=tl.float32)
|
|
60
|
+
|
|
61
|
+
# Base pointer for input channel
|
|
62
|
+
in_base = n * (C_in * IH * IW) + c_in * (IH * IW)
|
|
63
|
+
|
|
64
|
+
# Base pointer for weight
|
|
65
|
+
w_base = c_out * (KH * KW) # weight shape is [C_out, 1, KH, KW], 1 is implicit
|
|
66
|
+
|
|
67
|
+
for kh in range(0, KH):
|
|
68
|
+
for kw in range(0, KW):
|
|
69
|
+
# Input coordinates
|
|
70
|
+
ih = oh[:, None] * stride_h - pad_h + kh * dilation_h # [BLOCK_Y, 1]
|
|
71
|
+
iw = ow[None, :] * stride_w - pad_w + kw * dilation_w # [1, BLOCK_X]
|
|
72
|
+
|
|
73
|
+
# Bounds check
|
|
74
|
+
valid_h = (ih >= 0) & (ih < IH)
|
|
75
|
+
valid_w = (iw >= 0) & (iw < IW)
|
|
76
|
+
valid = mask_2d & valid_h & valid_w
|
|
77
|
+
|
|
78
|
+
# Load input value
|
|
79
|
+
in_offset = in_base + ih * IW + iw
|
|
80
|
+
x_val = tl.load(input_ptr + in_offset, mask=valid, other=0.0).to(tl.float32)
|
|
81
|
+
|
|
82
|
+
# Load weight value (scalar per kh, kw)
|
|
83
|
+
w_offset = w_base + kh * KW + kw
|
|
84
|
+
w_val = tl.load(weight_ptr + w_offset).to(tl.float32)
|
|
85
|
+
|
|
86
|
+
acc += x_val * w_val
|
|
87
|
+
|
|
88
|
+
# Store output
|
|
89
|
+
out_base = n * (C_out * OH * OW) + c_out * (OH * OW)
|
|
90
|
+
out_offset = out_base + oh[:, None] * OW + ow[None, :]
|
|
91
|
+
tl.store(output_ptr + out_offset, acc, mask=mask_2d)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@triton.jit
|
|
95
|
+
def conv_depthwise2d_bias_kernel(
|
|
96
|
+
input_ptr,
|
|
97
|
+
weight_ptr,
|
|
98
|
+
bias_ptr,
|
|
99
|
+
output_ptr,
|
|
100
|
+
N, C_in, IH, IW,
|
|
101
|
+
C_out, KH, KW,
|
|
102
|
+
OH, OW,
|
|
103
|
+
stride_h, stride_w,
|
|
104
|
+
pad_h, pad_w,
|
|
105
|
+
dilation_h, dilation_w,
|
|
106
|
+
ch_mult,
|
|
107
|
+
BLOCK_X: tl.constexpr,
|
|
108
|
+
BLOCK_Y: tl.constexpr,
|
|
109
|
+
):
|
|
110
|
+
"""Depthwise conv2d with bias, same structure as above."""
|
|
111
|
+
pid_x = tl.program_id(0).to(tl.int64)
|
|
112
|
+
pid_y = tl.program_id(1).to(tl.int64)
|
|
113
|
+
pid_nc = tl.program_id(2).to(tl.int64)
|
|
114
|
+
|
|
115
|
+
n = pid_nc // C_out
|
|
116
|
+
c_out = pid_nc % C_out
|
|
117
|
+
c_in = c_out // ch_mult
|
|
118
|
+
|
|
119
|
+
ow = pid_x * BLOCK_X + tl.arange(0, BLOCK_X)
|
|
120
|
+
oh = pid_y * BLOCK_Y + tl.arange(0, BLOCK_Y)
|
|
121
|
+
|
|
122
|
+
mask_h = oh < OH
|
|
123
|
+
mask_w = ow < OW
|
|
124
|
+
mask_2d = mask_h[:, None] & mask_w[None, :]
|
|
125
|
+
|
|
126
|
+
acc = tl.zeros([BLOCK_Y, BLOCK_X], dtype=tl.float32)
|
|
127
|
+
|
|
128
|
+
in_base = n * (C_in * IH * IW) + c_in * (IH * IW)
|
|
129
|
+
w_base = c_out * (KH * KW)
|
|
130
|
+
|
|
131
|
+
for kh in range(0, KH):
|
|
132
|
+
for kw in range(0, KW):
|
|
133
|
+
ih = oh[:, None] * stride_h - pad_h + kh * dilation_h
|
|
134
|
+
iw = ow[None, :] * stride_w - pad_w + kw * dilation_w
|
|
135
|
+
|
|
136
|
+
valid_h = (ih >= 0) & (ih < IH)
|
|
137
|
+
valid_w = (iw >= 0) & (iw < IW)
|
|
138
|
+
valid = mask_2d & valid_h & valid_w
|
|
139
|
+
|
|
140
|
+
in_offset = in_base + ih * IW + iw
|
|
141
|
+
x_val = tl.load(input_ptr + in_offset, mask=valid, other=0.0).to(tl.float32)
|
|
142
|
+
|
|
143
|
+
w_offset = w_base + kh * KW + kw
|
|
144
|
+
w_val = tl.load(weight_ptr + w_offset).to(tl.float32)
|
|
145
|
+
|
|
146
|
+
acc += x_val * w_val
|
|
147
|
+
|
|
148
|
+
# Add bias
|
|
149
|
+
bias_val = tl.load(bias_ptr + c_out).to(tl.float32)
|
|
150
|
+
acc += bias_val
|
|
151
|
+
|
|
152
|
+
out_base = n * (C_out * OH * OW) + c_out * (OH * OW)
|
|
153
|
+
out_offset = out_base + oh[:, None] * OW + ow[None, :]
|
|
154
|
+
tl.store(output_ptr + out_offset, acc, mask=mask_2d)
|