tensor-ee 0.1.0__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.
- tensor_ee/__init__.py +28 -0
- tensor_ee/_swin_parameters.py +304 -0
- tensor_ee/blocks/__init__.py +0 -0
- tensor_ee/blocks/raster/__init__.py +36 -0
- tensor_ee/blocks/raster/activations.py +183 -0
- tensor_ee/blocks/raster/conv.py +183 -0
- tensor_ee/blocks/raster/merge.py +66 -0
- tensor_ee/blocks/raster/outputs.py +211 -0
- tensor_ee/blocks/raster/pooling.py +182 -0
- tensor_ee/blocks/raster/resample.py +64 -0
- tensor_ee/blocks/window/__init__.py +44 -0
- tensor_ee/blocks/window/activations.py +54 -0
- tensor_ee/blocks/window/attention.py +366 -0
- tensor_ee/blocks/window/conv.py +323 -0
- tensor_ee/blocks/window/linear.py +129 -0
- tensor_ee/blocks/window/merge.py +67 -0
- tensor_ee/blocks/window/normalization.py +197 -0
- tensor_ee/blocks/window/patch.py +362 -0
- tensor_ee/blocks/window/pooling.py +275 -0
- tensor_ee/blocks/window/resample.py +286 -0
- tensor_ee/blocks/window/segformer.py +718 -0
- tensor_ee/blocks/window/swin.py +602 -0
- tensor_ee/blocks/window/swin_upernet_config.py +361 -0
- tensor_ee/blocks/window/upernet.py +1016 -0
- tensor_ee/comparison.py +213 -0
- tensor_ee/compatibility.py +180 -0
- tensor_ee/compiler.py +356 -0
- tensor_ee/converter/__init__.py +72 -0
- tensor_ee/converter/build.py +120 -0
- tensor_ee/converter/convert.py +388 -0
- tensor_ee/converter/export.py +846 -0
- tensor_ee/converter/graph.py +417 -0
- tensor_ee/converter/operations.py +790 -0
- tensor_ee/converter/plan.py +675 -0
- tensor_ee/converter/recognizers.py +1273 -0
- tensor_ee/converter/request.py +146 -0
- tensor_ee/converter/run.py +950 -0
- tensor_ee/core/__init__.py +8 -0
- tensor_ee/core/errors.py +25 -0
- tensor_ee/core/grid.py +323 -0
- tensor_ee/core/metadata.py +398 -0
- tensor_ee/core/ops.py +336 -0
- tensor_ee/core/precision.py +133 -0
- tensor_ee/core/tensor.py +129 -0
- tensor_ee/examples/__init__.py +1 -0
- tensor_ee/examples/nonwater.py +251 -0
- tensor_ee/layouts/__init__.py +17 -0
- tensor_ee/layouts/boundaries.py +103 -0
- tensor_ee/layouts/raster.py +117 -0
- tensor_ee/layouts/shift.py +226 -0
- tensor_ee/layouts/window.py +201 -0
- tensor_ee/models/__init__.py +16 -0
- tensor_ee/models/fixtures.py +119 -0
- tensor_ee/models/segformer_b0/__init__.py +9 -0
- tensor_ee/models/segformer_b0/configuration.py +209 -0
- tensor_ee/models/segformer_b0/decoder.py +130 -0
- tensor_ee/models/segformer_b0/encoder.py +90 -0
- tensor_ee/models/swin_t224/__init__.py +30 -0
- tensor_ee/models/swin_t224/configuration.py +103 -0
- tensor_ee/models/swin_t224/decoder.py +53 -0
- tensor_ee/models/swin_t224/encoder.py +50 -0
- tensor_ee/onnx_import.py +431 -0
- tensor_ee/outputs.py +16 -0
- tensor_ee/primitives.py +110 -0
- tensor_ee/receptive_field.py +257 -0
- tensor_ee/resnet_models.py +185 -0
- tensor_ee/segwater_inference.py +235 -0
- tensor_ee/segwater_preprocessing.py +182 -0
- tensor_ee/segwater_pytorch.py +249 -0
- tensor_ee/semantics.py +389 -0
- tensor_ee/smp.py +155 -0
- tensor_ee/swin.py +197 -0
- tensor_ee/swin_workflow.py +470 -0
- tensor_ee/tiling.py +287 -0
- tensor_ee/weight_assets.py +371 -0
- tensor_ee/weight_mapping.py +51 -0
- tensor_ee/weights/__init__.py +14 -0
- tensor_ee/weights/assets.py +160 -0
- tensor_ee/weights/constants.py +156 -0
- tensor_ee-0.1.0.dist-info/METADATA +172 -0
- tensor_ee-0.1.0.dist-info/RECORD +84 -0
- tensor_ee-0.1.0.dist-info/WHEEL +5 -0
- tensor_ee-0.1.0.dist-info/licenses/LICENSE +21 -0
- tensor_ee-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""R resampling: PyTorch nearest upsampling by an integer factor.
|
|
2
|
+
|
|
3
|
+
Moved whole from ``tensor_ee.compiler`` (``nearest_upsample``) and
|
|
4
|
+
``tensor_ee.semantics`` (``nearest_factor``). The old names are re-exports of
|
|
5
|
+
these objects.
|
|
6
|
+
|
|
7
|
+
Supported settings (contract section 8): one equal positive integer factor on
|
|
8
|
+
both axes. Factors 2, 3 and 4 are the validated settings: raster-setting validation's fixtures
|
|
9
|
+
``raster/nearest_upsample/f2``, ``f3`` and ``f4`` (``tools/fixtures/raster.py``)
|
|
10
|
+
compare them with PyTorch bit for bit. Other factors are accepted, as at the
|
|
11
|
+
release, but not validated.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any
|
|
17
|
+
import math
|
|
18
|
+
from numbers import Real
|
|
19
|
+
|
|
20
|
+
from ...core.metadata import BlockMetadata, Locality, Validated, validate_metadata
|
|
21
|
+
from ...core.precision import FLOAT32_OPERANDS, validate_policy
|
|
22
|
+
from ...core.tensor import Raster
|
|
23
|
+
from ...layouts.raster import finish as _finish, project as _project
|
|
24
|
+
from ...core.errors import reject as _reject
|
|
25
|
+
from .conv import _no_coefficients, _pair
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def nearest_factor(value: Any) -> int:
|
|
29
|
+
factors = _pair(value)
|
|
30
|
+
if (any(isinstance(x, bool) or not isinstance(x, Real) for x in factors)
|
|
31
|
+
or factors[0] != factors[1]):
|
|
32
|
+
_reject("Nearest upsampling requires one equal integer scale factor")
|
|
33
|
+
factor = factors[0]
|
|
34
|
+
if not math.isfinite(factor) or factor < 1 or int(factor) != factor:
|
|
35
|
+
_reject("Nearest upsampling requires a positive integer scale factor")
|
|
36
|
+
return int(factor)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def nearest_upsample(raster: Raster, scale_factor: Any,
|
|
40
|
+
*, clip_footprints: bool = False, policy: str = FLOAT32_OPERANDS) -> Raster:
|
|
41
|
+
validate_policy(policy, allowed=FLOAT32_OPERANDS)
|
|
42
|
+
factor = nearest_factor(scale_factor)
|
|
43
|
+
grid = raster.grid.resized(1 / factor, raster.grid.width * factor, raster.grid.height * factor)
|
|
44
|
+
# EE's default reprojection uses nearest neighbor. Aligned corner transforms
|
|
45
|
+
# produce exactly PyTorch's repeated-pixel integer upsampling.
|
|
46
|
+
return _finish(_project(raster.image, grid, clip_footprints), raster.bands, grid, clip_footprints)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# Locality and cost metadata (contract section 9)
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
METADATA = validate_metadata({
|
|
53
|
+
# receptive_field._upsample_axis: output index i reads input floor(i / factor),
|
|
54
|
+
# a single input pixel.
|
|
55
|
+
"nearest_upsample": BlockMetadata(
|
|
56
|
+
name="nearest_upsample", layout_in="R", layout_out="R", locality=Locality.POINTWISE,
|
|
57
|
+
policy=FLOAT32_OPERANDS, embedded_constants=_no_coefficients,
|
|
58
|
+
asset_arrays=_no_coefficients, status="Ready — factors 2, 3, 4",
|
|
59
|
+
evidence=("docs/evidence/live-fixtures/20261013T065257Z-p02.json", "docs/evidence/p02-raster-offline.json",
|
|
60
|
+
"docs/history/cnn/results/parity.json", "docs/history/cnn/results/resnet18_full64_float32_policy.json",
|
|
61
|
+
"docs/history/cnn/results/segwater_js_parity_1x1.json"),
|
|
62
|
+
# scale_factor as one integer (``nearest_factor`` normalizes an equal pair).
|
|
63
|
+
validated=Validated(({"scale_factor": (2, 3, 4)},))),
|
|
64
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Window-layout (W) and value (V) compute blocks of the native EE library.
|
|
2
|
+
|
|
3
|
+
The window lane of the Swin-T/UPerNet and SegFormer-B0 models (docs/library-contract.md,
|
|
4
|
+
sections 3 and 7-9):
|
|
5
|
+
|
|
6
|
+
- ``linear``: ``linear`` (torch.nn.Linear per token) and the 1 x 1 convolution,
|
|
7
|
+
with ``LinearConstants``;
|
|
8
|
+
- ``activations``: the exact GELU and the W ReLU;
|
|
9
|
+
- ``normalization``: LayerNorm, LayerNorm2d and explicit inference BatchNorm;
|
|
10
|
+
- ``attention``: the naive attention core, the relative-position bias, the head
|
|
11
|
+
split, window attention and the shifted-window mask;
|
|
12
|
+
- ``conv``: the window im2col 3 x 3 convolution, its input-channel groups and the
|
|
13
|
+
depthwise 3 x 3 convolution;
|
|
14
|
+
- ``resample``: bilinear resizing in window layout and its PyTorch-read
|
|
15
|
+
coefficients;
|
|
16
|
+
- ``pooling``: adaptive average pooling of a window-layout map;
|
|
17
|
+
- ``patch``: patch embedding and patch merging;
|
|
18
|
+
- ``merge``: addition and channel concatenation;
|
|
19
|
+
- ``segformer``: SegFormer's composites: the channel norm, the overlapping
|
|
20
|
+
embedding and its decimation, efficient attention with global keys and values, the
|
|
21
|
+
Mix-FFN, the MiT block, and the all-MLP decoder's pyramid projection and fused head;
|
|
22
|
+
- ``swin``: the Swin encoder's composites: the MLP branch, the W-MSA and SW-MSA
|
|
23
|
+
blocks, the stage, the patch embedding at a configuration and the encoder;
|
|
24
|
+
- ``upernet``: the UPerNet decoder's composites: the PSP, the FPN, the fusion
|
|
25
|
+
with its grouped-double formulation, and the segmentation head;
|
|
26
|
+
- ``swin_upernet_config``: the Swin-UPerNet settings the two read
|
|
27
|
+
(``SwinUPerNetConfig``, ``SWIN_T224``); no blocks, so no ``METADATA``.
|
|
28
|
+
|
|
29
|
+
Every block takes its settings as parameters whose defaults are validated values
|
|
30
|
+
(window 7, shift w // 2, kernel 3, patch 4, bins (1, 2, 3, 6), ...); a parameter is
|
|
31
|
+
not evidence that another value works. The values validated live are each block's
|
|
32
|
+
``METADATA`` record (``validated``, a structured record of validated settings), which ``tensor_ee.core.metadata.check_settings``
|
|
33
|
+
reads. Per-channel constants are repeated to the rows of the layout they serve
|
|
34
|
+
(``WindowLayout(7)``: 49 rows), and blocks refuse constants built for another layout.
|
|
35
|
+
Each block module declares ``METADATA`` (locality, policy, status, validated settings and
|
|
36
|
+
cost functions).
|
|
37
|
+
|
|
38
|
+
Modules here import only ``ee``, NumPy, ``torch`` (only to read PyTorch's own
|
|
39
|
+
resize and pooling coefficients, in ``resample`` and ``pooling``), the standard
|
|
40
|
+
library, ``tensor_ee.core``, ``tensor_ee.layouts``, ``tensor_ee.weights`` and each
|
|
41
|
+
other (``segformer`` composes the other modules' blocks). They never import a research folder, ``tensor_ee.blocks.raster``, the converter
|
|
42
|
+
or the models. The research modules they came from are archived at the tag
|
|
43
|
+
``research-archive-2026-10-12`` (docs/history/README.md).
|
|
44
|
+
"""
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""W/V activations: the exact GELU of the Swin MLP and the decoder's ReLU.
|
|
2
|
+
|
|
3
|
+
Moved from ``swin_block/earth_engine/linear.py`` (``gelu``, ``SQRT1_2``) and
|
|
4
|
+
``upernet/earth_engine/normalization.py`` (``relu``), research code archived at
|
|
5
|
+
the tag ``research-archive-2026-10-12`` (docs/history/README.md). Both work on any
|
|
6
|
+
ops operand ([N, C] per window or an ee.Array value).
|
|
7
|
+
|
|
8
|
+
- ``gelu``: policy ``double_round_once`` (the whole expression in double, one
|
|
9
|
+
float32 rounding). The R GELU of the CNN path (``float32_operands``) is a
|
|
10
|
+
different block with the same formula (contract section 7).
|
|
11
|
+
- ``relu``: ``max(x, 0)``; no arithmetic beyond the comparison, no cast, no
|
|
12
|
+
policy.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from ...core.metadata import ANY_SETTINGS, NO_ARITHMETIC, BlockMetadata, Locality, validate_metadata
|
|
18
|
+
from ...core.precision import DOUBLE_ROUND_ONCE, validate_policy
|
|
19
|
+
from .linear import no_constants
|
|
20
|
+
|
|
21
|
+
SQRT1_2 = 0.7071067811865476 # double nearest 1/sqrt(2), as C's M_SQRT1_2
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def gelu(ops, x, *, policy: str = DOUBLE_ROUND_ONCE):
|
|
25
|
+
"""Exact GELU, torch.nn.GELU(approximate='none'): x/2 (1 + erf(x/sqrt(2))).
|
|
26
|
+
|
|
27
|
+
EE's erf is accurate to double precision (live probe: <= 8e-16 absolute
|
|
28
|
+
on [-6, 6]); the whole expression runs in double, rounded once. The
|
|
29
|
+
non-integer constants promote the float32 input to double (probed), so no
|
|
30
|
+
explicit double copy of the [N, 384] input is made.
|
|
31
|
+
"""
|
|
32
|
+
validate_policy(policy, allowed=DOUBLE_ROUND_ONCE)
|
|
33
|
+
return ops.to_float(x.multiply(0.5).multiply(x.multiply(SQRT1_2).erf().add(1)))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def relu(ops, x):
|
|
37
|
+
"""max(x, 0) per element (float32 in, float32 out; no rounding involved)."""
|
|
38
|
+
return x.max(0)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
METADATA = validate_metadata({
|
|
42
|
+
"gelu": BlockMetadata(
|
|
43
|
+
name="gelu", layout_in="W", layout_out="W", locality=Locality.POINTWISE,
|
|
44
|
+
policy=DOUBLE_ROUND_ONCE, embedded_constants=no_constants, asset_arrays=no_constants,
|
|
45
|
+
status="Ready",
|
|
46
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#35-activations-merges-and-outputs", "docs/history/swin_block/notes/findings.md"),
|
|
47
|
+
validated=ANY_SETTINGS),
|
|
48
|
+
"relu": BlockMetadata(
|
|
49
|
+
name="relu", layout_in="W", layout_out="W", locality=Locality.POINTWISE,
|
|
50
|
+
policy=NO_ARITHMETIC, embedded_constants=no_constants, asset_arrays=no_constants,
|
|
51
|
+
status="Ready",
|
|
52
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#35-activations-merges-and-outputs", "docs/history/upernet/notes/milestone7_findings.md"),
|
|
53
|
+
validated=ANY_SETTINGS),
|
|
54
|
+
})
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""Window attention: the naive attention core, the relative-position bias, the head split,
|
|
2
|
+
W-MSA/SW-MSA up to the output projection, and Swin's shifted-window mask.
|
|
3
|
+
|
|
4
|
+
Moved from ``ee_attention/earth_engine/naive_attention.py`` (``naive_attention``),
|
|
5
|
+
``swin_block/earth_engine/relative_position.py`` (``relative_position_index``,
|
|
6
|
+
``relative_position_bias``), ``swin_block/earth_engine/swin_block.py`` (``split_heads``,
|
|
7
|
+
``window_attention``), ``swin_pair/earth_engine/coordinates.py`` (``coarse_projection``,
|
|
8
|
+
``window_coordinates``) and ``swin_pair/earth_engine/shifted_mask.py``
|
|
9
|
+
(``region_boundaries``, ``shifted_window_mask``, ``MASK_VALUE``), research code archived
|
|
10
|
+
at the tag ``research-archive-2026-10-12`` (docs/history/README.md). The Swin block
|
|
11
|
+
composites (``BlockConstants``,
|
|
12
|
+
``mlp_branch``, ``swin_block``) stay with the model.
|
|
13
|
+
|
|
14
|
+
**Attention core** (policy ``round_each_op``)::
|
|
15
|
+
|
|
16
|
+
scores = Q K^T matrixMultiply, transpose
|
|
17
|
+
scaled = scores / sqrt(d) divide
|
|
18
|
+
row_max = max_j scaled reduce(max)
|
|
19
|
+
e = exp(scaled - row_max) repeat, subtract, exp
|
|
20
|
+
attention = e / sum_j e reduce(sum), divide
|
|
21
|
+
output = attention V matrixMultiply
|
|
22
|
+
|
|
23
|
+
The full [Nq, N] score and probability matrices are materialized. There is no
|
|
24
|
+
softmax primitive in EE; the numerically stable form above is explicit.
|
|
25
|
+
|
|
26
|
+
**Relative-position bias.** The learned table has (2w-1)^2 rows and one column per
|
|
27
|
+
head. Swin's index for query token t1 = (a1, b1) and key token t2 = (a2, b2) of a
|
|
28
|
+
w x w window (row-major, t = a w + b) is
|
|
29
|
+
|
|
30
|
+
index[t1, t2] = (a1 - a2 + w - 1) * (2w - 1) + (b1 - b2 + w - 1)
|
|
31
|
+
|
|
32
|
+
(timm get_relative_position_index), and bias[h, t1, t2] = table[index, h]. Both
|
|
33
|
+
steps run on the server from the table alone: the index from integer sequences, the
|
|
34
|
+
gather as a one-hot [N*N, (2w-1)^2] matrix times the table. Each one-hot row has a
|
|
35
|
+
single 1, so every gathered value is exactly a table entry.
|
|
36
|
+
|
|
37
|
+
**Shifted-window mask.** timm (SwinTransformerBlock.get_attn_mask) labels the
|
|
38
|
+
*shifted* token raster with nine regions, rows [0, H-w), [H-w, H-s), [H-s, H) times
|
|
39
|
+
the same columns, partitions the labels into windows and sets
|
|
40
|
+
|
|
41
|
+
mask[window, i, j] = 0 if label(i) == label(j) else -100.
|
|
42
|
+
|
|
43
|
+
Only the last window row and column straddle region boundaries, so for a window with
|
|
44
|
+
flags last_row = (I == kh-1) and last_col = (J == kw-1),
|
|
45
|
+
|
|
46
|
+
different[i, j] = (last_row and R[i, j]) or (last_col and C[i, j]),
|
|
47
|
+
R[i, j] = [a_i >= w-s] != [a_j >= w-s], C[i, j] = [b_i >= w-s] != [b_j >= w-s],
|
|
48
|
+
|
|
49
|
+
with t = a w + b. R and C are two [N, N] ee.Array values built from integer
|
|
50
|
+
sequences (no request payload, computed once); per window the mask is two products, a
|
|
51
|
+
maximum and a scale. The mask depends on the chip edge (last window row and column).
|
|
52
|
+
It is float32, so adding it to float32 logits is one IEEE float32 addition, as in
|
|
53
|
+
PyTorch.
|
|
54
|
+
|
|
55
|
+
Window 7 (49 tokens), shift 3 and mask value -100.0 (timm's; production) are the
|
|
56
|
+
defaults. Live checks validated windows 5, 7, 8 and 12 with shift w // 2 (W-MSA, SW-MSA and
|
|
57
|
+
the single window, where timm disables the shift and SW-MSA is refused here), against
|
|
58
|
+
timm; other windows and shifts, and other mask values, are not validated (the
|
|
59
|
+
attention-core probes also ran 25 and 64 tokens).
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
from __future__ import annotations
|
|
63
|
+
|
|
64
|
+
import numpy as np
|
|
65
|
+
|
|
66
|
+
from ...core.errors import reject
|
|
67
|
+
from ...core.metadata import (ANY_SETTINGS, COMPOSITE, NO_ARITHMETIC, BlockMetadata, Locality, Validated,
|
|
68
|
+
validate_metadata)
|
|
69
|
+
from ...core.precision import DOUBLE_ROUND_ONCE, ROUND_EACH_OP, validate_policy
|
|
70
|
+
from ...core.tensor import WINDOW
|
|
71
|
+
from ...layouts.shift import _resolve_shift
|
|
72
|
+
from ...weights.constants import ServerArray, WindowLayout, check_rows, constant_value
|
|
73
|
+
from .linear import linear, linear_arrays, linear_values, no_constants
|
|
74
|
+
|
|
75
|
+
MASK_VALUE = -100.0
|
|
76
|
+
"""timm's shifted-window mask value (production). The original research attention
|
|
77
|
+
used -1e30 (``ee_attention.earth_engine.common``)."""
|
|
78
|
+
|
|
79
|
+
DIM, HEADS = 96, 3
|
|
80
|
+
"""``split_heads`` defaults: Swin-T stage 1 (the model passes its own)."""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
# Attention core
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
def naive_attention(ops, q, k, v, *, tokens: int, dim: int, key_bias=None,
|
|
87
|
+
query_scale: float | None = None, key_mask=None,
|
|
88
|
+
policy: str = ROUND_EACH_OP, score_scale: float | None = None) -> dict:
|
|
89
|
+
"""softmax(q k^T / sqrt(dim)) v for q [Nq, dim] and k, v [tokens, dim].
|
|
90
|
+
|
|
91
|
+
key_bias, when given, is a float32 [Nq, tokens] array added to the scaled
|
|
92
|
+
scores: 0 (valid key) or common.MASK_VALUE (excluded key), or a Swin
|
|
93
|
+
relative-position bias.
|
|
94
|
+
|
|
95
|
+
query_scale, when given, follows Swin's order instead: q is multiplied by
|
|
96
|
+
float32(query_scale) before q k^T and the scores are not divided. "scaled"
|
|
97
|
+
includes key_bias; with a bias, "unbiased" holds the logits before it, and
|
|
98
|
+
with query_scale, "query" holds the scaled q. (Without either option the
|
|
99
|
+
result keeps the original attention readout keys.)
|
|
100
|
+
|
|
101
|
+
key_mask, when given, is a second float32 [Nq, tokens] array added after
|
|
102
|
+
key_bias as its own float32 addition, as timm adds the shifted-window mask
|
|
103
|
+
(0 / -100) after the relative-position bias. "scaled" then includes both;
|
|
104
|
+
"biased" holds the logits before the mask.
|
|
105
|
+
|
|
106
|
+
score_scale, when given, multiplies the float32 q k^T scores with the
|
|
107
|
+
pre-rounded float32 factor instead of division by sqrt(dim). It is refused
|
|
108
|
+
together with query_scale. Live coverage is dim32/dim7 with 14 queries and
|
|
109
|
+
seven keys, plus dim32 with 49 global keys across four W7 query windows
|
|
110
|
+
(docs/evidence/segformer_b0-live.json). The existing block status is unchanged.
|
|
111
|
+
|
|
112
|
+
Policy ``round_each_op``: each EE operation that returns double is rounded at
|
|
113
|
+
once (the ops methods).
|
|
114
|
+
"""
|
|
115
|
+
validate_policy(policy, allowed=ROUND_EACH_OP)
|
|
116
|
+
if query_scale is not None and score_scale is not None:
|
|
117
|
+
reject("query_scale and score_scale cannot be used together")
|
|
118
|
+
if query_scale is None:
|
|
119
|
+
scores = ops.matmul(q, ops.transpose(k))
|
|
120
|
+
unbiased = ops.divide_by_sqrt(scores, dim) if score_scale is None else ops.scale(scores, score_scale)
|
|
121
|
+
else:
|
|
122
|
+
q = ops.scale(q, query_scale)
|
|
123
|
+
scores = ops.matmul(q, ops.transpose(k))
|
|
124
|
+
unbiased = scores
|
|
125
|
+
scaled = unbiased if key_bias is None else unbiased.add(key_bias)
|
|
126
|
+
biased = scaled
|
|
127
|
+
if key_mask is not None:
|
|
128
|
+
scaled = scaled.add(key_mask)
|
|
129
|
+
row_max = ops.row_max(scaled)
|
|
130
|
+
shifted_exp = ops.exp(scaled.subtract(ops.repeat_cols(row_max, tokens)))
|
|
131
|
+
row_sum = ops.row_sum(shifted_exp)
|
|
132
|
+
attention = shifted_exp.divide(ops.repeat_cols(row_sum, tokens))
|
|
133
|
+
output = ops.matmul(attention, v)
|
|
134
|
+
result = {"scores": scores, "scaled": scaled, "row_max": row_max,
|
|
135
|
+
"row_sum": row_sum, "attention": attention, "output": output}
|
|
136
|
+
if key_bias is not None:
|
|
137
|
+
result["unbiased"] = unbiased
|
|
138
|
+
if query_scale is not None:
|
|
139
|
+
result["query"] = q
|
|
140
|
+
if key_mask is not None:
|
|
141
|
+
result["biased"] = biased
|
|
142
|
+
return result
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
# Relative-position bias
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
def relative_position_index(ee, window: int = WINDOW):
|
|
149
|
+
"""[N, N] ee.Array of table rows (as doubles holding integers)."""
|
|
150
|
+
n = window * window
|
|
151
|
+
t = ee.Array(ee.List.sequence(0, n - 1)).double()
|
|
152
|
+
a, b = t.divide(window).floor(), t.mod(window)
|
|
153
|
+
|
|
154
|
+
def query(values): # varies down the rows
|
|
155
|
+
return values.reshape([n, 1]).repeat(1, n)
|
|
156
|
+
|
|
157
|
+
def key(values): # varies along the columns
|
|
158
|
+
return values.reshape([1, n]).repeat(0, n)
|
|
159
|
+
|
|
160
|
+
dy = query(a).subtract(key(a)).add(window - 1)
|
|
161
|
+
dx = query(b).subtract(key(b)).add(window - 1)
|
|
162
|
+
return dy.multiply(2 * window - 1).add(dx)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def relative_position_bias(ee, table, window: int = WINDOW, *, policy: str = DOUBLE_ROUND_ONCE) -> list:
|
|
166
|
+
"""Per-head float32 [N, N] ee.Array biases from a [(2w-1)^2, heads] table
|
|
167
|
+
(a NumPy array or a ServerArray). The gather is exact; one float32 rounding.
|
|
168
|
+
Windows 5, 7, 8 and 12 are validated."""
|
|
169
|
+
validate_policy(policy, allowed=DOUBLE_ROUND_ONCE)
|
|
170
|
+
entries, heads = table.shape if isinstance(table, ServerArray) else np.asarray(table).shape
|
|
171
|
+
if entries != (2 * window - 1) ** 2:
|
|
172
|
+
raise ValueError("table rows must be (2w-1)^2")
|
|
173
|
+
n = window * window
|
|
174
|
+
index = relative_position_index(ee, window).reshape([n * n, 1]).repeat(1, entries)
|
|
175
|
+
rows = ee.Array(ee.List.sequence(0, entries - 1)).double().reshape([1, entries]).repeat(0, n * n)
|
|
176
|
+
one_hot = index.eq(rows) # [N*N, entries] of 0/1
|
|
177
|
+
gathered = one_hot.matrixMultiply(constant_value(ee, table)) # [N*N, heads], exact
|
|
178
|
+
return [gathered.slice(1, h, h + 1).reshape([n, n]).float() for h in range(heads)]
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
# Heads and window attention
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
def split_heads(ops, qkv, dim: int = DIM, heads: int = HEADS) -> dict:
|
|
185
|
+
"""q_h, k_h, v_h [N, d] from qkv [N, 3 dim] at timm's column positions,
|
|
186
|
+
d = dim / heads: columns s*dim + d*h ... s*dim + d*h + d-1, s = 0/1/2 (q/k/v).
|
|
187
|
+
Both sizes must be positive integers, with dim divisible by heads."""
|
|
188
|
+
if any(isinstance(value, bool) or not isinstance(value, (int, np.integer)) or value < 1
|
|
189
|
+
for value in (dim, heads)):
|
|
190
|
+
reject("dim and heads must be positive integers")
|
|
191
|
+
if dim % heads:
|
|
192
|
+
reject("dim must be a multiple of the head count")
|
|
193
|
+
head_dim = dim // heads
|
|
194
|
+
tensors = {}
|
|
195
|
+
for s, name in enumerate(("q", "k", "v")):
|
|
196
|
+
for h in range(heads):
|
|
197
|
+
start = s * dim + h * head_dim
|
|
198
|
+
tensors[f"{name}_h{h}"] = ops.slice_cols(qkv, start, start + head_dim)
|
|
199
|
+
return tensors
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def window_attention(ops, ln1, c, mask=None, *, window: int = WINDOW) -> dict:
|
|
203
|
+
"""W-MSA up to (not including) the output projection.
|
|
204
|
+
|
|
205
|
+
``c`` carries the block's constants as the Swin block builds them (``qkv``, a
|
|
206
|
+
``LinearConstants`` for the window's rows; ``relative_bias``, one [N, N] bias per
|
|
207
|
+
head; ``dim``, ``heads``, ``head_dim``). ``window`` gives the tokens per window
|
|
208
|
+
(``window ** 2``; 49 by default), and constants built for another row count are
|
|
209
|
+
refused.
|
|
210
|
+
|
|
211
|
+
mask, when given (SW-MSA), is the float32 [N, N] shifted-window mask, added to
|
|
212
|
+
every head's biased logits as a separate float32 addition, as in timm;
|
|
213
|
+
"logits_biased_h{h}" then holds the logits before it.
|
|
214
|
+
|
|
215
|
+
A composite: the QKV ``linear`` runs in ``double_round_once``, the attention core
|
|
216
|
+
in ``round_each_op``. Windows 5, 7, 8 and 12 are validated, with and without
|
|
217
|
+
the shifted-window mask.
|
|
218
|
+
"""
|
|
219
|
+
tokens = window * window
|
|
220
|
+
check_rows(c.qkv, WindowLayout(window))
|
|
221
|
+
t = {"qkv": linear(ops, ln1, c.qkv)}
|
|
222
|
+
t.update(split_heads(ops, t["qkv"], c.dim, c.heads))
|
|
223
|
+
query_scale = c.head_dim ** -0.5 # QUERY_SCALE at Swin-T's head width 32
|
|
224
|
+
outputs = []
|
|
225
|
+
for h in range(c.heads):
|
|
226
|
+
result = naive_attention(ops, t[f"q_h{h}"], t[f"k_h{h}"], t[f"v_h{h}"], tokens=tokens,
|
|
227
|
+
dim=c.head_dim, key_bias=c.relative_bias[h],
|
|
228
|
+
query_scale=query_scale, key_mask=mask)
|
|
229
|
+
t.update({f"q_scaled_h{h}": result["query"], f"scores_h{h}": result["unbiased"],
|
|
230
|
+
f"bias_h{h}": c.relative_bias[h], f"logits_h{h}": result["scaled"],
|
|
231
|
+
f"probs_h{h}": result["attention"], f"attn_v_h{h}": result["output"]})
|
|
232
|
+
if mask is not None:
|
|
233
|
+
t[f"logits_biased_h{h}"] = result["biased"]
|
|
234
|
+
outputs.append(result["output"])
|
|
235
|
+
t["concat"] = ops.cat_cols(outputs)
|
|
236
|
+
return t
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# ---------------------------------------------------------------------------
|
|
240
|
+
# The shifted-window mask
|
|
241
|
+
# ---------------------------------------------------------------------------
|
|
242
|
+
def coarse_projection(ee, coarse):
|
|
243
|
+
return ee.Projection(coarse.crs, coarse.transform)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def window_coordinates(ee, coarse):
|
|
247
|
+
"""(I mod kh, J mod kw) of each coarse pixel as double images: the window
|
|
248
|
+
index, periodic outside the raster (EE's mod takes the dividend's sign)."""
|
|
249
|
+
coords = ee.Image.pixelCoordinates(coarse_projection(ee, coarse)).floor()
|
|
250
|
+
period = ee.Image.constant([coarse.width, coarse.height])
|
|
251
|
+
coords = coords.mod(period).add(period).mod(period)
|
|
252
|
+
return coords.select("y"), coords.select("x")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def region_boundaries(ee, window: int, shift: int):
|
|
256
|
+
"""R and C: [N, N] 0/1 ee.Array values (row-slab and column-slab differ)."""
|
|
257
|
+
n = window * window
|
|
258
|
+
t = ee.Array(ee.List.sequence(0, n - 1)).double()
|
|
259
|
+
a, b = t.divide(window).floor(), t.mod(window)
|
|
260
|
+
|
|
261
|
+
def differs(group): # group: [N] of 0/1
|
|
262
|
+
query = group.reshape([n, 1]).repeat(1, n)
|
|
263
|
+
key = group.reshape([1, n]).repeat(0, n)
|
|
264
|
+
return query.neq(key)
|
|
265
|
+
|
|
266
|
+
return differs(a.gte(window - shift)), differs(b.gte(window - shift))
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def shifted_window_mask(ee, coarse, window: int = WINDOW, shift: int | None = None,
|
|
270
|
+
value: float = MASK_VALUE, *, policy: str = DOUBLE_ROUND_ONCE):
|
|
271
|
+
"""float32 [N, N] array image on the coarse grid of the full raster, for at least two
|
|
272
|
+
windows along each axis. ``shift`` defaults to ``window // 2`` (3 at window 7) and must
|
|
273
|
+
satisfy 0 < shift < window. Windows 5, 7, 8 and 12 with shift window // 2 and value
|
|
274
|
+
-100.0 are validated."""
|
|
275
|
+
validate_policy(policy, allowed=DOUBLE_ROUND_ONCE)
|
|
276
|
+
if coarse.height < 2 or coarse.width < 2:
|
|
277
|
+
raise ValueError("SW-MSA needs at least two windows along each axis")
|
|
278
|
+
# A shift of 0 or of a whole window would label every token alike (an all-zero mask);
|
|
279
|
+
# the cyclic shift refuses it too. Client-side: nothing reaches the graph.
|
|
280
|
+
shift = _resolve_shift(shift, window)
|
|
281
|
+
rows_differ, cols_differ = region_boundaries(ee, window, shift)
|
|
282
|
+
i, j = window_coordinates(ee, coarse) # periodic window index
|
|
283
|
+
last_row = i.eq(coarse.height - 1)
|
|
284
|
+
last_col = j.eq(coarse.width - 1)
|
|
285
|
+
different = (ee.Image(rows_differ).multiply(last_row)
|
|
286
|
+
.max(ee.Image(cols_differ).multiply(last_col)))
|
|
287
|
+
# 0 - |value| * different: +0.0 where allowed (never -0.0, whatever the
|
|
288
|
+
# operand types; IEEE 0.0 * -100 is -0.0), value where not.
|
|
289
|
+
return ee.Image.constant(0).subtract(different.multiply(-value)).toFloat()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# ---------------------------------------------------------------------------
|
|
293
|
+
# Cost functions (contract section 9)
|
|
294
|
+
# ---------------------------------------------------------------------------
|
|
295
|
+
def relative_bias_values(heads: int, *, window: int = WINDOW) -> int:
|
|
296
|
+
"""Values embedded for a NumPy table: [(2w-1)^2, heads] (the index and the one-hot
|
|
297
|
+
gather are built on the server from integer sequences)."""
|
|
298
|
+
return (2 * window - 1) ** 2 * heads
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def relative_bias_arrays() -> int:
|
|
302
|
+
"""Server arrays read for a ServerArray table."""
|
|
303
|
+
return 1
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def window_attention_values(dim: int, heads: int, *, window: int = WINDOW) -> int:
|
|
307
|
+
"""Values embedded for NumPy weights: the QKV linear (W^T [dim, 3 dim], bias rows
|
|
308
|
+
[w^2, 3 dim]) and the relative-position table. The output projection is not part of
|
|
309
|
+
``window_attention``."""
|
|
310
|
+
return linear_values(dim, 3 * dim, bias=True, layout=WindowLayout(window)) + relative_bias_values(
|
|
311
|
+
heads, window=window)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def window_attention_arrays() -> int:
|
|
315
|
+
"""Server arrays read: the QKV weight and bias, and the relative-position table."""
|
|
316
|
+
return linear_arrays(bias=True) + relative_bias_arrays()
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
_WINDOWS = (5, 7, 8, 12)
|
|
320
|
+
"""The window sizes validated live."""
|
|
321
|
+
|
|
322
|
+
METADATA = validate_metadata({
|
|
323
|
+
# Validated: any accepted call (token counts, widths, biases, masks and both scales varied).
|
|
324
|
+
"naive_attention": BlockMetadata(
|
|
325
|
+
name="naive_attention", layout_in="W", layout_out="W", locality=Locality.WINDOW,
|
|
326
|
+
policy=ROUND_EACH_OP, embedded_constants=no_constants, asset_arrays=no_constants,
|
|
327
|
+
status="Ready",
|
|
328
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#36-linear-layers-and-attention", "docs/history/ee_attention/notes/findings.md",
|
|
329
|
+
"docs/evidence/segformer_b0-live.json"),
|
|
330
|
+
validated=ANY_SETTINGS),
|
|
331
|
+
"split_heads": BlockMetadata(
|
|
332
|
+
name="split_heads", layout_in="W", layout_out="W", locality=Locality.POINTWISE,
|
|
333
|
+
policy=NO_ARITHMETIC, embedded_constants=no_constants, asset_arrays=no_constants,
|
|
334
|
+
status="Ready",
|
|
335
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#36-linear-layers-and-attention", "docs/history/swin_block/notes/findings.md"),
|
|
336
|
+
validated=ANY_SETTINGS),
|
|
337
|
+
"relative_position_bias": BlockMetadata(
|
|
338
|
+
name="relative_position_bias", layout_in="V", layout_out="V", locality=Locality.POINTWISE,
|
|
339
|
+
policy=DOUBLE_ROUND_ONCE, embedded_constants=relative_bias_values, asset_arrays=relative_bias_arrays,
|
|
340
|
+
status="Ready — windows 5, 7, 8, 12",
|
|
341
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#36-linear-layers-and-attention", "docs/history/swin_block/notes/findings.md",
|
|
342
|
+
"docs/evidence/p01-offline.json",
|
|
343
|
+
"docs/evidence/live-fixtures/20261013T074301Z-p01a.json", "docs/evidence/live-fixtures/20261013T074330Z-p01b.json", "docs/evidence/live-fixtures/20261013T075542Z-window-relative_position_bias-w12.json"),
|
|
344
|
+
validated=Validated(({"window": _WINDOWS},))),
|
|
345
|
+
"window_attention": BlockMetadata(
|
|
346
|
+
name="window_attention", layout_in="W", layout_out="W", locality=Locality.WINDOW,
|
|
347
|
+
policy=COMPOSITE, embedded_constants=window_attention_values, asset_arrays=window_attention_arrays,
|
|
348
|
+
status="Ready — windows 5, 7, 8, 12",
|
|
349
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#36-linear-layers-and-attention",
|
|
350
|
+
"parts: linear (double_round_once), naive_attention (round_each_op)",
|
|
351
|
+
"docs/history/swin_block/notes/findings.md", "docs/history/swin_pair/notes/findings.md",
|
|
352
|
+
"docs/evidence/p01-offline.json",
|
|
353
|
+
"docs/evidence/live-fixtures/20261013T074301Z-p01a.json", "docs/evidence/live-fixtures/20261013T074330Z-p01b.json", "docs/evidence/live-fixtures/20261013T075542Z-window-relative_position_bias-w12.json"),
|
|
354
|
+
validated=Validated(({"window": _WINDOWS},))),
|
|
355
|
+
# Validated: each window with its shift w // 2 (or None, which resolves to it), and
|
|
356
|
+
# timm's mask value -100.0, never varied.
|
|
357
|
+
"shifted_window_mask": BlockMetadata(
|
|
358
|
+
name="shifted_window_mask", layout_in="W", layout_out="W", locality=Locality.WINDOW,
|
|
359
|
+
policy=DOUBLE_ROUND_ONCE, embedded_constants=no_constants, asset_arrays=no_constants,
|
|
360
|
+
chip_edge_dependent=True, status="Ready — windows 5, 7, 8, 12; shift ⌊w/2⌋",
|
|
361
|
+
evidence=("docs/history/building-blocks-2026-10-11.md#36-linear-layers-and-attention", "docs/history/swin_pair/notes/findings.md",
|
|
362
|
+
"docs/evidence/p01-offline.json",
|
|
363
|
+
"docs/evidence/live-fixtures/20261013T074301Z-p01a.json", "docs/evidence/live-fixtures/20261013T074330Z-p01b.json", "docs/evidence/live-fixtures/20261013T075542Z-window-relative_position_bias-w12.json"),
|
|
364
|
+
validated=Validated(tuple({"window": (w,), "shift": (w // 2, None), "value": (MASK_VALUE,)}
|
|
365
|
+
for w in _WINDOWS))),
|
|
366
|
+
})
|