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,450 @@
|
|
|
1
|
+
"""The convolution family's float64 reference — the screen's uncovered 11.5%.
|
|
2
|
+
|
|
3
|
+
WHY THIS FAMILY AND NOT ANOTHER
|
|
4
|
+
--------------------------------
|
|
5
|
+
Measured 2026-09-12 against this rack's certified directory: of 7 158 certified
|
|
6
|
+
keys the screen oracle covers 6 336 (88.5%), and the 822 it does not are exactly
|
|
7
|
+
`conv2d_forward_kernel` (770) and `depthwise_conv2d_kernel` (52). Nothing else.
|
|
8
|
+
Full count: `docs/reference/owed-proofs.md` item 3.
|
|
9
|
+
|
|
10
|
+
Without a reference, those keys are decided by a consensus vote, and a vote has
|
|
11
|
+
two failure modes it cannot see — a majority wrong in the same way, and a
|
|
12
|
+
unanimous space that is wrong. Both were measured on another backend: four
|
|
13
|
+
`addmm` shapes where the emitted code declared `alpha`/`beta` as `int`, every
|
|
14
|
+
candidate wrong identically, the vote unanimous, and the bare screen seating a
|
|
15
|
+
wrong configuration every time while the same screen with an oracle refused every
|
|
16
|
+
time.
|
|
17
|
+
|
|
18
|
+
WHAT MAKES A REFERENCE A REFERENCE
|
|
19
|
+
-----------------------------------
|
|
20
|
+
It must not share an implementation with the thing it checks. So: pure numpy in
|
|
21
|
+
float64, no Triton, no torch, and the arithmetic written out rather than
|
|
22
|
+
delegated. It is slow and that is irrelevant — it runs once per shape, inside a
|
|
23
|
+
screen that already refuses to look at anything over the profile's byte budget.
|
|
24
|
+
|
|
25
|
+
The kernels' signatures make this tractable: every dimension, every stride and
|
|
26
|
+
every compile-time parameter is a NAMED argument, so nothing is inferred.
|
|
27
|
+
|
|
28
|
+
conv2d_forward_kernel(input, weight, output, N, Cin, H, W, Cout, Hout, Wout,
|
|
29
|
+
<12 strides>, kernel_h/w, stride_h/w, padding_h/w,
|
|
30
|
+
groups, fp16, dilation_h/w, <3 block sizes>)
|
|
31
|
+
depthwise_conv2d_kernel(x, w, out, N, C, H_in, W_in, H_out, W_out,
|
|
32
|
+
<11 strides>, kh, kw, stride_h/w, pad_h/w, fp16,
|
|
33
|
+
<2 block sizes>)
|
|
34
|
+
|
|
35
|
+
Neither takes a bias, so the reference is the convolution alone.
|
|
36
|
+
|
|
37
|
+
HOW IT REFUSES
|
|
38
|
+
--------------
|
|
39
|
+
Silence, never a guess. An operand that cannot be read here, a declared extent
|
|
40
|
+
that contradicts the array it names, an output extent that does not follow from
|
|
41
|
+
the inputs — each returns None, and the screen then says out loud that this key
|
|
42
|
+
is unscreened. A reference that answers when it should not is worse than none:
|
|
43
|
+
it turns healthy work into a loud stop, and a refusal reads as vigilance.
|
|
44
|
+
"""
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
from typing import Any, NamedTuple, Optional, Sequence
|
|
48
|
+
|
|
49
|
+
import numpy as np
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _out_extent(size: int, pad: int, dilation: int, kernel: int, stride: int) -> int:
|
|
53
|
+
"""The spatial extent a convolution produces, by the standard formula."""
|
|
54
|
+
return (size + 2 * pad - dilation * (kernel - 1) - 1) // stride + 1
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def conv2d_reference(x: np.ndarray, w: np.ndarray, *,
|
|
58
|
+
stride: Sequence[int], padding: Sequence[int],
|
|
59
|
+
dilation: Sequence[int], groups: int) -> np.ndarray:
|
|
60
|
+
"""`[N, Cin, H, W]` ⊛ `[Cout, Cin//groups, KH, KW]` → `[N, Cout, Ho, Wo]`, fp64.
|
|
61
|
+
|
|
62
|
+
Written as an accumulation over the KH·KW taps rather than an im2col: the
|
|
63
|
+
strided view per tap is exact and allocates nothing, and the loop makes the
|
|
64
|
+
padding and dilation arithmetic visible instead of hidden in a reshape.
|
|
65
|
+
"""
|
|
66
|
+
x = np.asarray(x, dtype=np.float64)
|
|
67
|
+
w = np.asarray(w, dtype=np.float64)
|
|
68
|
+
if x.ndim != 4 or w.ndim != 4:
|
|
69
|
+
raise ValueError(f"expected 4-D input and weight, got {x.shape} {w.shape}")
|
|
70
|
+
n, c_in, h, width = x.shape
|
|
71
|
+
c_out, c_in_group, k_h, k_w = w.shape
|
|
72
|
+
if groups < 1 or c_in % groups or c_out % groups:
|
|
73
|
+
raise ValueError(f"{c_in} in / {c_out} out do not divide into {groups} groups")
|
|
74
|
+
if c_in // groups != c_in_group:
|
|
75
|
+
raise ValueError(f"weight declares {c_in_group} input channels per group, "
|
|
76
|
+
f"the input has {c_in // groups}")
|
|
77
|
+
s_h, s_w = int(stride[0]), int(stride[1])
|
|
78
|
+
p_h, p_w = int(padding[0]), int(padding[1])
|
|
79
|
+
d_h, d_w = int(dilation[0]), int(dilation[1])
|
|
80
|
+
|
|
81
|
+
out_h = _out_extent(h, p_h, d_h, k_h, s_h)
|
|
82
|
+
out_w = _out_extent(width, p_w, d_w, k_w, s_w)
|
|
83
|
+
if out_h <= 0 or out_w <= 0:
|
|
84
|
+
raise ValueError(f"the geometry produces a {out_h}x{out_w} output")
|
|
85
|
+
|
|
86
|
+
padded = np.pad(x, ((0, 0), (0, 0), (p_h, p_h), (p_w, p_w)))
|
|
87
|
+
out = np.zeros((n, c_out, out_h, out_w), dtype=np.float64)
|
|
88
|
+
per_in, per_out = c_in // groups, c_out // groups
|
|
89
|
+
for g in range(groups):
|
|
90
|
+
x_g = padded[:, g * per_in:(g + 1) * per_in]
|
|
91
|
+
w_g = w[g * per_out:(g + 1) * per_out]
|
|
92
|
+
acc = out[:, g * per_out:(g + 1) * per_out]
|
|
93
|
+
for i in range(k_h):
|
|
94
|
+
top = i * d_h
|
|
95
|
+
rows = slice(top, top + (out_h - 1) * s_h + 1, s_h)
|
|
96
|
+
for j in range(k_w):
|
|
97
|
+
left = j * d_w
|
|
98
|
+
cols = slice(left, left + (out_w - 1) * s_w + 1, s_w)
|
|
99
|
+
acc += np.einsum("nchw,oc->nohw", x_g[:, :, rows, cols],
|
|
100
|
+
w_g[:, :, i, j])
|
|
101
|
+
return out
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def receptive_slab(h: int, wd: int, k_h: int, k_w: int, *, stride, padding, dilation, window):
|
|
105
|
+
"""The input slab an output window `(r0, r1, c0, c1)` reads, and the padding it still needs.
|
|
106
|
+
|
|
107
|
+
Returns `((u0, u1, v0, v1), (top, bottom, left, right))`: the UNPADDED input rows `u0:u1`
|
|
108
|
+
and columns `v0:v1` the window's receptive field covers, and the zero padding to put back
|
|
109
|
+
around that slab so the tap loop below sees the same coordinates it would see on the whole
|
|
110
|
+
padded input. One arithmetic for the certifier and the runtime screen — written twice it
|
|
111
|
+
would be two places to be wrong.
|
|
112
|
+
"""
|
|
113
|
+
r0, r1, c0, c1 = window
|
|
114
|
+
s_h, s_w = int(stride[0]), int(stride[1])
|
|
115
|
+
p_h, p_w = int(padding[0]), int(padding[1])
|
|
116
|
+
d_h, d_w = int(dilation[0]), int(dilation[1])
|
|
117
|
+
R0, R1 = r0 * s_h, (r1 - 1) * s_h + d_h * (k_h - 1) + 1 # padded coordinates
|
|
118
|
+
C0, C1 = c0 * s_w, (c1 - 1) * s_w + d_w * (k_w - 1) + 1
|
|
119
|
+
u0, u1 = max(0, R0 - p_h), min(h, R1 - p_h)
|
|
120
|
+
v0, v1 = max(0, C0 - p_w), min(wd, C1 - p_w)
|
|
121
|
+
top, bottom = max(0, p_h - R0), max(0, (R1 - p_h) - h)
|
|
122
|
+
left, right = max(0, p_w - C0), max(0, (C1 - p_w) - wd)
|
|
123
|
+
return (u0, u1, v0, v1), (top, bottom, left, right)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def conv2d_reference_window(x, w: np.ndarray, *, stride: Sequence[int], padding: Sequence[int],
|
|
127
|
+
dilation: Sequence[int], groups: int, window=None,
|
|
128
|
+
shape: Optional[Sequence[int]] = None, slab_of=None) -> np.ndarray:
|
|
129
|
+
"""`conv2d_reference` on one output block only: `window` = `(n_idx, r0, r1, c0, c1)` selects
|
|
130
|
+
`out[n_idx, :, r0:r1, c0:c1]`, exact on every position of it, reading ONLY its receptive
|
|
131
|
+
field. `window=None` is the whole output.
|
|
132
|
+
|
|
133
|
+
The input arrives either as a host array `x` or — when `slab_of` is given — through a reader
|
|
134
|
+
`slab_of(n0, n1, u0, u1, v0, v1) -> float64 [n1-n0, Cin, u1-u0, v1-v0]` that fetches just the
|
|
135
|
+
slab, with `shape` naming the whole input's `[N, Cin, H, W]`. The reader is what bounds the
|
|
136
|
+
runtime screen: a 128-channel 2048x1024 input is 2.1 GB in float64 whole (the Mac's 27.5 GB
|
|
137
|
+
footprint, 2026-09-25) and a few megabytes as the slab of a few output rows.
|
|
138
|
+
|
|
139
|
+
One BLAS product per tap, the depthwise case as one broadcast product per tap — the
|
|
140
|
+
certifier's arithmetic since 2026-09-07, now the one copy of it.
|
|
141
|
+
"""
|
|
142
|
+
w = np.asarray(w, dtype=np.float64)
|
|
143
|
+
if slab_of is None:
|
|
144
|
+
x = np.asarray(x)
|
|
145
|
+
shape = x.shape
|
|
146
|
+
n, c_in, h, wd = (int(v) for v in shape)
|
|
147
|
+
c_out, ci_g, k_h, k_w = w.shape
|
|
148
|
+
if groups < 1 or c_in % groups or c_out % groups or c_in // groups != ci_g:
|
|
149
|
+
raise ValueError(f"{c_in} in / {c_out} out / {ci_g} per group do not divide into {groups} groups")
|
|
150
|
+
s_h, s_w = int(stride[0]), int(stride[1])
|
|
151
|
+
d_h, d_w = int(dilation[0]), int(dilation[1])
|
|
152
|
+
oh = _out_extent(h, int(padding[0]), d_h, k_h, s_h)
|
|
153
|
+
ow = _out_extent(wd, int(padding[1]), d_w, k_w, s_w)
|
|
154
|
+
if oh <= 0 or ow <= 0:
|
|
155
|
+
raise ValueError(f"the geometry produces a {oh}x{ow} output")
|
|
156
|
+
if window is None:
|
|
157
|
+
n0, n1, r0, r1, c0, c1 = 0, n, 0, oh, 0, ow
|
|
158
|
+
else:
|
|
159
|
+
ni, r0, r1, c0, c1 = window
|
|
160
|
+
n0, n1 = int(ni), int(ni) + 1
|
|
161
|
+
(u0, u1, v0, v1), pads = receptive_slab(h, wd, k_h, k_w, stride=stride, padding=padding,
|
|
162
|
+
dilation=dilation, window=(r0, r1, c0, c1))
|
|
163
|
+
if slab_of is None:
|
|
164
|
+
slab = x[n0:n1, :, u0:u1, v0:v1].astype(np.float64)
|
|
165
|
+
else:
|
|
166
|
+
slab = np.asarray(slab_of(n0, n1, u0, u1, v0, v1), dtype=np.float64)
|
|
167
|
+
if slab.shape != (n1 - n0, c_in, u1 - u0, v1 - v0):
|
|
168
|
+
raise ValueError(f"the slab reader returned {slab.shape}, "
|
|
169
|
+
f"the window needs {(n1 - n0, c_in, u1 - u0, v1 - v0)}")
|
|
170
|
+
top, bottom, left, right = pads
|
|
171
|
+
xp = np.pad(slab, ((0, 0), (0, 0), (top, bottom), (left, right)))
|
|
172
|
+
nb, rh, rw = n1 - n0, r1 - r0, c1 - c0
|
|
173
|
+
out = np.zeros((nb, c_out, rh, rw), dtype=np.float64)
|
|
174
|
+
co_g = c_out // groups
|
|
175
|
+
if groups == c_in == c_out and ci_g == 1:
|
|
176
|
+
for i in range(k_h):
|
|
177
|
+
for j in range(k_w):
|
|
178
|
+
patch = xp[:, :, i * d_h:i * d_h + rh * s_h:s_h, j * d_w:j * d_w + rw * s_w:s_w]
|
|
179
|
+
out += patch * w[:, 0, i, j][None, :, None, None]
|
|
180
|
+
return out
|
|
181
|
+
for g in range(groups):
|
|
182
|
+
xg = xp[:, g * ci_g:(g + 1) * ci_g]
|
|
183
|
+
wg = w[g * co_g:(g + 1) * co_g]
|
|
184
|
+
for i in range(k_h):
|
|
185
|
+
for j in range(k_w):
|
|
186
|
+
patch = xg[:, :, i * d_h:i * d_h + rh * s_h:s_h, j * d_w:j * d_w + rw * s_w:s_w]
|
|
187
|
+
prod = patch.transpose(0, 2, 3, 1).reshape(-1, ci_g) @ wg[:, :, i, j].T
|
|
188
|
+
out[:, g * co_g:(g + 1) * co_g] += prod.reshape(nb, rh, rw, co_g).transpose(0, 3, 1, 2)
|
|
189
|
+
return out
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _row_segments(rows, c_out: int, oh: int):
|
|
193
|
+
"""Flat output rows `[r0, r1)` of the `[N*Cout*Ho, Wo]` view, cut at every channel boundary:
|
|
194
|
+
yields `(n, c, oh_lo, oh_hi, flat_lo, flat_hi)`, the output rows `oh_lo:oh_hi` of channel `c`
|
|
195
|
+
of element `n`, which are exactly the flat rows `[flat_lo, flat_hi)`.
|
|
196
|
+
|
|
197
|
+
One segment per `(n, c)` run, never a widening: a window that straddles a channel used to
|
|
198
|
+
be widened to every row of the element (`(0, oh)`), whose receptive field is the WHOLE
|
|
199
|
+
input — for a 128 x 2048 x 1024 input the 2.1 GB of float64 this file exists to avoid,
|
|
200
|
+
reachable by window position alone (the guardian, 2026-09-26). The bound must not depend
|
|
201
|
+
on where the windows fall."""
|
|
202
|
+
r0, r1 = int(rows[0]), int(rows[1])
|
|
203
|
+
per_n = c_out * oh
|
|
204
|
+
r = r0
|
|
205
|
+
while r < r1:
|
|
206
|
+
n, rem = divmod(r, per_n)
|
|
207
|
+
c, oh_lo = divmod(rem, oh)
|
|
208
|
+
end = min(r1, r + (oh - oh_lo)) # the last flat row of this channel, or r1
|
|
209
|
+
yield n, c, oh_lo, oh_lo + (end - r), r, end
|
|
210
|
+
r = end
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _conv_geometry(named: dict):
|
|
214
|
+
"""The `[N, Cin, H, W]` input, `[Cout, Cin/groups, KH, KW]` weight shape, stride, padding,
|
|
215
|
+
dilation, groups and declared output extents of `conv2d_forward_kernel`'s named arguments —
|
|
216
|
+
refusing, with the reason, a declaration that contradicts the formula."""
|
|
217
|
+
shape = (int(named["batch_dim"]), int(named["in_feat_dim"]),
|
|
218
|
+
int(named["in_height"]), int(named["in_width"]))
|
|
219
|
+
groups = int(named.get("groups", 1))
|
|
220
|
+
k_h, k_w = int(named["kernel_height"]), int(named["kernel_width"])
|
|
221
|
+
w_shape = (int(named["out_feat_dim"]), shape[1] // groups, k_h, k_w)
|
|
222
|
+
stride = (int(named["stride_height"]), int(named["stride_width"]))
|
|
223
|
+
padding = (int(named["padding_height"]), int(named["padding_width"]))
|
|
224
|
+
dilation = (int(named.get("dilation_height", 1)), int(named.get("dilation_width", 1)))
|
|
225
|
+
oh, ow = int(named["out_height"]), int(named["out_width"])
|
|
226
|
+
formula = (_out_extent(shape[2], padding[0], dilation[0], k_h, stride[0]),
|
|
227
|
+
_out_extent(shape[3], padding[1], dilation[1], k_w, stride[1]))
|
|
228
|
+
if (oh, ow) != formula:
|
|
229
|
+
raise ValueError(f"the declared output {oh}x{ow} is not the formula's {formula[0]}x{formula[1]}")
|
|
230
|
+
return shape, w_shape, stride, padding, dilation, groups, oh, ow
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _depthwise_geometry(named: dict):
|
|
234
|
+
c = int(named["C"])
|
|
235
|
+
k_h, k_w = int(named["kh"]), int(named["kw"])
|
|
236
|
+
shape = (int(named["N"]), c, int(named["H_in"]), int(named["W_in"]))
|
|
237
|
+
stride = (int(named["stride_h"]), int(named["stride_w"]))
|
|
238
|
+
padding = (int(named["pad_h"]), int(named["pad_w"]))
|
|
239
|
+
oh, ow = int(named["H_out"]), int(named["W_out"])
|
|
240
|
+
formula = (_out_extent(shape[2], padding[0], 1, k_h, stride[0]),
|
|
241
|
+
_out_extent(shape[3], padding[1], 1, k_w, stride[1]))
|
|
242
|
+
if (oh, ow) != formula:
|
|
243
|
+
raise ValueError(f"the declared output {oh}x{ow} is not the formula's {formula[0]}x{formula[1]}")
|
|
244
|
+
return shape, (c, 1, k_h, k_w), stride, padding, (1, 1), c, oh, ow
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _rows_of(geometry, slab_of, weight_f64, rows) -> np.ndarray:
|
|
248
|
+
shape, w_shape, stride, padding, dilation, groups, oh, ow = geometry
|
|
249
|
+
w = np.asarray(weight_f64, dtype=np.float64)
|
|
250
|
+
if w.size != int(np.prod(w_shape)):
|
|
251
|
+
raise ValueError(f"the weight holds {w.size} elements, the declared {w_shape} needs {int(np.prod(w_shape))}")
|
|
252
|
+
w = w.reshape(w_shape)
|
|
253
|
+
parts = []
|
|
254
|
+
for n, c, oh_lo, oh_hi, lo, hi in _row_segments(rows, w_shape[0], oh):
|
|
255
|
+
block = conv2d_reference_window(None, w, stride=stride, padding=padding, dilation=dilation,
|
|
256
|
+
groups=groups, window=(n, oh_lo, oh_hi, 0, ow),
|
|
257
|
+
shape=shape, slab_of=slab_of)
|
|
258
|
+
parts.append(block[0, c].reshape(hi - lo, ow))
|
|
259
|
+
if not parts:
|
|
260
|
+
raise ValueError(f"the rows {rows} are empty")
|
|
261
|
+
return np.concatenate(parts, axis=0)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def conv2d_rows(named: dict, slab_of, weight_f64, rows) -> np.ndarray:
|
|
265
|
+
"""The reference for the flat output rows `rows = (r0, r1)` of `conv2d_forward_kernel`,
|
|
266
|
+
the output read as `[N*Cout*Ho, Wo]` — the form the runtime screen windows every kernel in.
|
|
267
|
+
Reads only the receptive field of those rows through `slab_of`. Raises `ValueError` with
|
|
268
|
+
the reason when it cannot: a declaration that contradicts the arrays it names is a finding
|
|
269
|
+
the screen must say out loud, never a silent consensus."""
|
|
270
|
+
return _rows_of(_conv_geometry(named), slab_of, weight_f64, rows)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def depthwise_rows(named: dict, slab_of, weight_f64, rows) -> np.ndarray:
|
|
274
|
+
"""The same for `depthwise_conv2d_kernel` (`groups == C`, weight `[C, KH, KW]`)."""
|
|
275
|
+
return _rows_of(_depthwise_geometry(named), slab_of, weight_f64, rows)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _row_bytes_of(geometry) -> int:
|
|
279
|
+
"""What ONE flat output row costs the reference on the host, from the geometry: the slab of
|
|
280
|
+
input it reads — every input channel, `stride + dilation*(k-1)` input rows (the row and its
|
|
281
|
+
halo), the full input width, 8 bytes an element, counted TWICE because the slab is padded
|
|
282
|
+
into a second array — plus the tap patch and the output row. The screen sizes its windows by
|
|
283
|
+
this, not by the output row's own bytes: a 3-channel output row is 24 KB while its
|
|
284
|
+
128-channel receptive field is a megabyte, and a window sized to the former reads
|
|
285
|
+
gigabytes."""
|
|
286
|
+
shape, w_shape, stride, _padding, dilation, groups, _oh, ow = geometry
|
|
287
|
+
c_in, w_in = shape[1], shape[3]
|
|
288
|
+
c_out, k_h = w_shape[0], w_shape[2]
|
|
289
|
+
rows_read = max(1, int(stride[0])) + int(dilation[0]) * (k_h - 1)
|
|
290
|
+
return 8 * (2 * c_in * rows_read * w_in + (c_in // groups + c_out) * ow)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class RowOracle(NamedTuple):
|
|
294
|
+
"""One kernel's row-windowed reference: the rows function, the names of its INPUT and WEIGHT
|
|
295
|
+
arguments, the `[N, C, H, W]` shape of the input from the named arguments, and what one
|
|
296
|
+
flat output row costs the reference on the host. Everything the screen needs to window the
|
|
297
|
+
kernel is in this row — no branch on the kernel's name lives outside the table."""
|
|
298
|
+
rows: Any
|
|
299
|
+
input_name: str
|
|
300
|
+
weight_name: str
|
|
301
|
+
input_shape: Any
|
|
302
|
+
row_bytes: Any
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def oracle_row_bytes(kernel: str, named: dict) -> Optional[int]:
|
|
306
|
+
"""What one flat output row costs `kernel`'s reference, or None for a kernel this table does
|
|
307
|
+
not window. A kernel the table DOES window whose cost cannot be formed raises `ValueError`:
|
|
308
|
+
that is a refusal, and a refusal sized to the output row would be the Mac's defect one layer
|
|
309
|
+
down."""
|
|
310
|
+
entry = ROW_ORACLES.get(kernel)
|
|
311
|
+
if entry is None:
|
|
312
|
+
return None
|
|
313
|
+
try:
|
|
314
|
+
return int(entry.row_bytes(named))
|
|
315
|
+
except (KeyError, TypeError, ValueError) as exc:
|
|
316
|
+
raise ValueError(f"{kernel}: the row cost of its reference cannot be formed from the "
|
|
317
|
+
f"named arguments ({type(exc).__name__}: {exc})") from None
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def depthwise_reference(x: np.ndarray, w: np.ndarray, *,
|
|
321
|
+
stride: Sequence[int], padding: Sequence[int]) -> np.ndarray:
|
|
322
|
+
"""`[N, C, H, W]` ⊛ `[C, KH, KW]` → `[N, C, Ho, Wo]`, fp64.
|
|
323
|
+
|
|
324
|
+
The `groups == C` case, which the engine routes to its own kernel because a
|
|
325
|
+
grouped convolution with one channel per group is a stencil and not a GEMM
|
|
326
|
+
(453x measured on a Sana 4Kpx VAE op). The reference keeps them separate for
|
|
327
|
+
the same reason the kernels are separate: a shared path would let a defect in
|
|
328
|
+
one hide behind the other.
|
|
329
|
+
"""
|
|
330
|
+
x = np.asarray(x, dtype=np.float64)
|
|
331
|
+
w = np.asarray(w, dtype=np.float64)
|
|
332
|
+
if x.ndim != 4 or w.ndim != 3:
|
|
333
|
+
raise ValueError(f"expected [N,C,H,W] and [C,KH,KW], got {x.shape} {w.shape}")
|
|
334
|
+
n, c, h, width = x.shape
|
|
335
|
+
c_w, k_h, k_w = w.shape
|
|
336
|
+
if c_w != c:
|
|
337
|
+
raise ValueError(f"weight declares {c_w} channels, the input has {c}")
|
|
338
|
+
s_h, s_w = int(stride[0]), int(stride[1])
|
|
339
|
+
p_h, p_w = int(padding[0]), int(padding[1])
|
|
340
|
+
out_h = _out_extent(h, p_h, 1, k_h, s_h)
|
|
341
|
+
out_w = _out_extent(width, p_w, 1, k_w, s_w)
|
|
342
|
+
if out_h <= 0 or out_w <= 0:
|
|
343
|
+
raise ValueError(f"the geometry produces a {out_h}x{out_w} output")
|
|
344
|
+
|
|
345
|
+
padded = np.pad(x, ((0, 0), (0, 0), (p_h, p_h), (p_w, p_w)))
|
|
346
|
+
out = np.zeros((n, c, out_h, out_w), dtype=np.float64)
|
|
347
|
+
for i in range(k_h):
|
|
348
|
+
rows = slice(i, i + (out_h - 1) * s_h + 1, s_h)
|
|
349
|
+
for j in range(k_w):
|
|
350
|
+
cols = slice(j, j + (out_w - 1) * s_w + 1, s_w)
|
|
351
|
+
out += padded[:, :, rows, cols] * w[None, :, i, j, None, None]
|
|
352
|
+
return out
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _read(named: dict, key: str, to_f64) -> Optional[np.ndarray]:
|
|
356
|
+
tensor = named.get(key)
|
|
357
|
+
if tensor is None:
|
|
358
|
+
return None
|
|
359
|
+
return to_f64(tensor)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def conv2d_from_args(named: dict, to_f64) -> Optional[np.ndarray]:
|
|
363
|
+
"""The reference output for `conv2d_forward_kernel`, or None.
|
|
364
|
+
|
|
365
|
+
`to_f64` is the caller's one host-copy path — the provider owns it, because
|
|
366
|
+
reading device memory correctly is a question this module must not answer
|
|
367
|
+
twice (a raw device read was measured returning bytes a kernel had not yet
|
|
368
|
+
landed, and it contradicted every correct candidate as loudly as a wrong
|
|
369
|
+
one).
|
|
370
|
+
"""
|
|
371
|
+
try:
|
|
372
|
+
x = _read(named, "input_pointer", to_f64)
|
|
373
|
+
w = _read(named, "weight_pointer", to_f64)
|
|
374
|
+
if x is None or w is None:
|
|
375
|
+
return None
|
|
376
|
+
shape = (int(named["batch_dim"]), int(named["in_feat_dim"]),
|
|
377
|
+
int(named["in_height"]), int(named["in_width"]))
|
|
378
|
+
groups = int(named.get("groups", 1))
|
|
379
|
+
k_h, k_w = int(named["kernel_height"]), int(named["kernel_width"])
|
|
380
|
+
w_shape = (int(named["out_feat_dim"]), shape[1] // groups, k_h, k_w)
|
|
381
|
+
if x.size != int(np.prod(shape)) or w.size != int(np.prod(w_shape)):
|
|
382
|
+
# A declared extent that contradicts the array it names. Refuse:
|
|
383
|
+
# reshaping anyway would produce a confident wrong reference.
|
|
384
|
+
return None
|
|
385
|
+
out = conv2d_reference(
|
|
386
|
+
x.reshape(shape), w.reshape(w_shape),
|
|
387
|
+
stride=(int(named["stride_height"]), int(named["stride_width"])),
|
|
388
|
+
padding=(int(named["padding_height"]), int(named["padding_width"])),
|
|
389
|
+
dilation=(int(named.get("dilation_height", 1)),
|
|
390
|
+
int(named.get("dilation_width", 1))),
|
|
391
|
+
groups=groups)
|
|
392
|
+
declared = (int(named["batch_dim"]), int(named["out_feat_dim"]),
|
|
393
|
+
int(named["out_height"]), int(named["out_width"]))
|
|
394
|
+
if out.shape != declared:
|
|
395
|
+
# The kernel and the formula disagree about the output geometry.
|
|
396
|
+
# That is a finding, not something to paper over with a reshape.
|
|
397
|
+
return None
|
|
398
|
+
return out
|
|
399
|
+
except (KeyError, TypeError, ValueError):
|
|
400
|
+
return None
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def depthwise_from_args(named: dict, to_f64) -> Optional[np.ndarray]:
|
|
404
|
+
"""The reference output for `depthwise_conv2d_kernel`, or None."""
|
|
405
|
+
try:
|
|
406
|
+
x = _read(named, "x_ptr", to_f64)
|
|
407
|
+
w = _read(named, "w_ptr", to_f64)
|
|
408
|
+
if x is None or w is None:
|
|
409
|
+
return None
|
|
410
|
+
shape = (int(named["N"]), int(named["C"]),
|
|
411
|
+
int(named["H_in"]), int(named["W_in"]))
|
|
412
|
+
w_shape = (int(named["C"]), int(named["kh"]), int(named["kw"]))
|
|
413
|
+
if x.size != int(np.prod(shape)) or w.size != int(np.prod(w_shape)):
|
|
414
|
+
return None
|
|
415
|
+
out = depthwise_reference(
|
|
416
|
+
x.reshape(shape), w.reshape(w_shape),
|
|
417
|
+
stride=(int(named["stride_h"]), int(named["stride_w"])),
|
|
418
|
+
padding=(int(named["pad_h"]), int(named["pad_w"])))
|
|
419
|
+
declared = (int(named["N"]), int(named["C"]),
|
|
420
|
+
int(named["H_out"]), int(named["W_out"]))
|
|
421
|
+
if out.shape != declared:
|
|
422
|
+
return None
|
|
423
|
+
return out
|
|
424
|
+
except (KeyError, TypeError, ValueError):
|
|
425
|
+
return None
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
#: What a provider's `ORACLES` table absorbs: kernel short name -> (reference
|
|
429
|
+
#: from the live named arguments, the name of its OUTPUT argument).
|
|
430
|
+
#:
|
|
431
|
+
#: The output argument name is not decoration. A provider that matched the
|
|
432
|
+
#: output by byte length once placed the reference on an INPUT of the same size
|
|
433
|
+
#: and refused ten correct candidates of a shape whose certified deviation is
|
|
434
|
+
#: 1.7e-06. It is matched by ADDRESS, and this table is what says which address.
|
|
435
|
+
ORACLES = {
|
|
436
|
+
"conv2d_forward_kernel": (conv2d_from_args, "output_pointer"),
|
|
437
|
+
"depthwise_conv2d_kernel": (depthwise_from_args, "out_ptr"),
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
#: The same kernels on FLAT OUTPUT ROWS, for the runtime screen's windows.
|
|
441
|
+
ROW_ORACLES = {
|
|
442
|
+
"conv2d_forward_kernel": RowOracle(
|
|
443
|
+
conv2d_rows, "input_pointer", "weight_pointer",
|
|
444
|
+
lambda named: _conv_geometry(named)[0],
|
|
445
|
+
lambda named: _row_bytes_of(_conv_geometry(named))),
|
|
446
|
+
"depthwise_conv2d_kernel": RowOracle(
|
|
447
|
+
depthwise_rows, "x_ptr", "w_ptr",
|
|
448
|
+
lambda named: _depthwise_geometry(named)[0],
|
|
449
|
+
lambda named: _row_bytes_of(_depthwise_geometry(named))),
|
|
450
|
+
}
|
nbxtensor/ops/pad_op.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Constant padding — pure @triton.jit kernel.
|
|
2
|
+
|
|
3
|
+
Handles constant_pad_nd for the common 4D case (N,C,H,W) and generic N-D
|
|
4
|
+
via a flat-index approach. For each output element, compute the corresponding
|
|
5
|
+
input coordinate. If in bounds, load from input; otherwise, store pad_value.
|
|
6
|
+
|
|
7
|
+
Logic derived from FlagGems pad.py codegen approach.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import triton
|
|
11
|
+
import triton.language as tl
|
|
12
|
+
|
|
13
|
+
@triton.jit
|
|
14
|
+
def constant_pad_2d_kernel(
|
|
15
|
+
x_ptr, output_ptr,
|
|
16
|
+
total,
|
|
17
|
+
in_h, in_w, out_h, out_w,
|
|
18
|
+
channels_times_batch,
|
|
19
|
+
x_stride_n, x_stride_c, x_stride_h, x_stride_w,
|
|
20
|
+
out_stride_n, out_stride_c, out_stride_h, out_stride_w,
|
|
21
|
+
pad_left, pad_top,
|
|
22
|
+
pad_value,
|
|
23
|
+
BLOCK_SIZE: tl.constexpr,
|
|
24
|
+
WIDE: tl.constexpr = True,
|
|
25
|
+
):
|
|
26
|
+
"""Constant pad for 4D tensors (N,C,H,W). Most common case."""
|
|
27
|
+
if WIDE:
|
|
28
|
+
offsets = tl.program_id(0).to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 64-bit from the program id: the product itself wraps past 2^31 elements (register 58)
|
|
29
|
+
else:
|
|
30
|
+
offsets = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 32-bit below 2^30 (`_index_wide`): Apple emulates 64-bit % and //
|
|
31
|
+
mask = offsets < total
|
|
32
|
+
|
|
33
|
+
# Decompose flat index into (n*c, h, w)
|
|
34
|
+
ow = offsets % out_w
|
|
35
|
+
temp = offsets // out_w
|
|
36
|
+
oh = temp % out_h
|
|
37
|
+
nc = temp // out_h
|
|
38
|
+
|
|
39
|
+
# Map output coords to input coords
|
|
40
|
+
iw = ow - pad_left
|
|
41
|
+
ih = oh - pad_top
|
|
42
|
+
|
|
43
|
+
# Check if within input bounds
|
|
44
|
+
in_bounds = (ih >= 0) & (ih < in_h) & (iw >= 0) & (iw < in_w) & (nc < channels_times_batch)
|
|
45
|
+
|
|
46
|
+
# Flat stride approach: assumes contiguous input (wrapper ensures contiguous)
|
|
47
|
+
inp_offset = nc * in_h * in_w + ih * in_w + iw
|
|
48
|
+
|
|
49
|
+
val = tl.load(x_ptr + inp_offset, mask=mask & in_bounds, other=pad_value)
|
|
50
|
+
out_offset = nc * out_h * out_w + oh * out_w + ow
|
|
51
|
+
tl.store(output_ptr + out_offset, val, mask=mask)
|
|
52
|
+
|
|
53
|
+
@triton.jit
|
|
54
|
+
def constant_pad_1d_kernel(
|
|
55
|
+
x_ptr, output_ptr,
|
|
56
|
+
total,
|
|
57
|
+
in_size, out_size,
|
|
58
|
+
batch_size,
|
|
59
|
+
pad_left,
|
|
60
|
+
pad_value,
|
|
61
|
+
BLOCK_SIZE: tl.constexpr,
|
|
62
|
+
WIDE: tl.constexpr = True,
|
|
63
|
+
):
|
|
64
|
+
"""Constant pad for 2D/3D tensors along last dim."""
|
|
65
|
+
if WIDE:
|
|
66
|
+
offsets = tl.program_id(0).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)
|
|
67
|
+
else:
|
|
68
|
+
offsets = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 32-bit below 2^30 (`_index_wide`): Apple emulates 64-bit % and //
|
|
69
|
+
mask = offsets < total
|
|
70
|
+
|
|
71
|
+
out_idx = offsets % out_size
|
|
72
|
+
batch_idx = offsets // out_size
|
|
73
|
+
|
|
74
|
+
in_idx = out_idx - pad_left
|
|
75
|
+
in_bounds = (in_idx >= 0) & (in_idx < in_size) & (batch_idx < batch_size)
|
|
76
|
+
|
|
77
|
+
inp_offset = batch_idx * in_size + in_idx
|
|
78
|
+
val = tl.load(x_ptr + inp_offset, mask=mask & in_bounds, other=pad_value)
|
|
79
|
+
|
|
80
|
+
out_offset = batch_idx * out_size + out_idx
|
|
81
|
+
tl.store(output_ptr + out_offset, val, mask=mask)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@triton.jit
|
|
85
|
+
def constant_pad_nd_kernel(
|
|
86
|
+
x_ptr, output_ptr,
|
|
87
|
+
total,
|
|
88
|
+
in0, in1, in2, in3, in4,
|
|
89
|
+
out0, out1, out2, out3, out4,
|
|
90
|
+
xs0, xs1, xs2, xs3, xs4,
|
|
91
|
+
pb0, pb1, pb2, pb3, pb4,
|
|
92
|
+
pad_value,
|
|
93
|
+
BLOCK_SIZE: tl.constexpr,
|
|
94
|
+
WIDE: tl.constexpr = True,
|
|
95
|
+
):
|
|
96
|
+
"""Constant pad of up to 5 dims in ONE pass: each output element maps to its input element (any dim padded,
|
|
97
|
+
a middle one included) or to `pad_value`. Dims a tensor lacks are given extent 1 and pad 0 by the wrapper, so one
|
|
98
|
+
kernel serves 1-D to 5-D. The input is read through its strides (xs*); the output is written contiguous."""
|
|
99
|
+
if WIDE:
|
|
100
|
+
offsets = tl.program_id(0).to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
|
101
|
+
else:
|
|
102
|
+
offsets = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 32-bit below 2^30 (`_index_wide`): Apple emulates 64-bit % and //
|
|
103
|
+
mask = offsets < total
|
|
104
|
+
o4 = offsets % out4
|
|
105
|
+
t = offsets // out4
|
|
106
|
+
o3 = t % out3
|
|
107
|
+
t = t // out3
|
|
108
|
+
o2 = t % out2
|
|
109
|
+
t = t // out2
|
|
110
|
+
o1 = t % out1
|
|
111
|
+
o0 = t // out1
|
|
112
|
+
i0 = o0 - pb0
|
|
113
|
+
i1 = o1 - pb1
|
|
114
|
+
i2 = o2 - pb2
|
|
115
|
+
i3 = o3 - pb3
|
|
116
|
+
i4 = o4 - pb4
|
|
117
|
+
inside = ((i0 >= 0) & (i0 < in0) & (i1 >= 0) & (i1 < in1) & (i2 >= 0) & (i2 < in2)
|
|
118
|
+
& (i3 >= 0) & (i3 < in3) & (i4 >= 0) & (i4 < in4))
|
|
119
|
+
src = i0 * xs0 + i1 * xs1 + i2 * xs2 + i3 * xs3 + i4 * xs4
|
|
120
|
+
val = tl.load(x_ptr + src, mask=mask & inside, other=pad_value)
|
|
121
|
+
tl.store(output_ptr + offsets, val, mask=mask)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@triton.jit
|
|
125
|
+
def replication_pad_nd_kernel(
|
|
126
|
+
x_ptr, output_ptr,
|
|
127
|
+
total,
|
|
128
|
+
in0, in1, in2, in3, in4,
|
|
129
|
+
out0, out1, out2, out3, out4,
|
|
130
|
+
xs0, xs1, xs2, xs3, xs4,
|
|
131
|
+
pb0, pb1, pb2, pb3, pb4,
|
|
132
|
+
BLOCK_SIZE: tl.constexpr,
|
|
133
|
+
WIDE: tl.constexpr = True,
|
|
134
|
+
):
|
|
135
|
+
"""Replication pad of up to 5 dims in ONE pass (PyTorch `replicate`): each output element reads the input element
|
|
136
|
+
at its position clamped into the input, so a padded border repeats the edge. The twin of `constant_pad_nd_kernel`,
|
|
137
|
+
the same index map with a clamp where that one masks; the input is read through its strides, the output written
|
|
138
|
+
contiguous."""
|
|
139
|
+
if WIDE:
|
|
140
|
+
offsets = tl.program_id(0).to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
|
141
|
+
else:
|
|
142
|
+
offsets = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) # 32-bit below 2^30 (`_index_wide`)
|
|
143
|
+
mask = offsets < total
|
|
144
|
+
o4 = offsets % out4
|
|
145
|
+
t = offsets // out4
|
|
146
|
+
o3 = t % out3
|
|
147
|
+
t = t // out3
|
|
148
|
+
o2 = t % out2
|
|
149
|
+
t = t // out2
|
|
150
|
+
o1 = t % out1
|
|
151
|
+
o0 = t // out1
|
|
152
|
+
i0 = tl.minimum(tl.maximum(o0 - pb0, 0), in0 - 1)
|
|
153
|
+
i1 = tl.minimum(tl.maximum(o1 - pb1, 0), in1 - 1)
|
|
154
|
+
i2 = tl.minimum(tl.maximum(o2 - pb2, 0), in2 - 1)
|
|
155
|
+
i3 = tl.minimum(tl.maximum(o3 - pb3, 0), in3 - 1)
|
|
156
|
+
i4 = tl.minimum(tl.maximum(o4 - pb4, 0), in4 - 1)
|
|
157
|
+
src = i0 * xs0 + i1 * xs1 + i2 * xs2 + i3 * xs3 + i4 * xs4
|
|
158
|
+
val = tl.load(x_ptr + src, mask=mask)
|
|
159
|
+
tl.store(output_ptr + offsets, val, mask=mask)
|