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.
Files changed (84) hide show
  1. tensor_ee/__init__.py +28 -0
  2. tensor_ee/_swin_parameters.py +304 -0
  3. tensor_ee/blocks/__init__.py +0 -0
  4. tensor_ee/blocks/raster/__init__.py +36 -0
  5. tensor_ee/blocks/raster/activations.py +183 -0
  6. tensor_ee/blocks/raster/conv.py +183 -0
  7. tensor_ee/blocks/raster/merge.py +66 -0
  8. tensor_ee/blocks/raster/outputs.py +211 -0
  9. tensor_ee/blocks/raster/pooling.py +182 -0
  10. tensor_ee/blocks/raster/resample.py +64 -0
  11. tensor_ee/blocks/window/__init__.py +44 -0
  12. tensor_ee/blocks/window/activations.py +54 -0
  13. tensor_ee/blocks/window/attention.py +366 -0
  14. tensor_ee/blocks/window/conv.py +323 -0
  15. tensor_ee/blocks/window/linear.py +129 -0
  16. tensor_ee/blocks/window/merge.py +67 -0
  17. tensor_ee/blocks/window/normalization.py +197 -0
  18. tensor_ee/blocks/window/patch.py +362 -0
  19. tensor_ee/blocks/window/pooling.py +275 -0
  20. tensor_ee/blocks/window/resample.py +286 -0
  21. tensor_ee/blocks/window/segformer.py +718 -0
  22. tensor_ee/blocks/window/swin.py +602 -0
  23. tensor_ee/blocks/window/swin_upernet_config.py +361 -0
  24. tensor_ee/blocks/window/upernet.py +1016 -0
  25. tensor_ee/comparison.py +213 -0
  26. tensor_ee/compatibility.py +180 -0
  27. tensor_ee/compiler.py +356 -0
  28. tensor_ee/converter/__init__.py +72 -0
  29. tensor_ee/converter/build.py +120 -0
  30. tensor_ee/converter/convert.py +388 -0
  31. tensor_ee/converter/export.py +846 -0
  32. tensor_ee/converter/graph.py +417 -0
  33. tensor_ee/converter/operations.py +790 -0
  34. tensor_ee/converter/plan.py +675 -0
  35. tensor_ee/converter/recognizers.py +1273 -0
  36. tensor_ee/converter/request.py +146 -0
  37. tensor_ee/converter/run.py +950 -0
  38. tensor_ee/core/__init__.py +8 -0
  39. tensor_ee/core/errors.py +25 -0
  40. tensor_ee/core/grid.py +323 -0
  41. tensor_ee/core/metadata.py +398 -0
  42. tensor_ee/core/ops.py +336 -0
  43. tensor_ee/core/precision.py +133 -0
  44. tensor_ee/core/tensor.py +129 -0
  45. tensor_ee/examples/__init__.py +1 -0
  46. tensor_ee/examples/nonwater.py +251 -0
  47. tensor_ee/layouts/__init__.py +17 -0
  48. tensor_ee/layouts/boundaries.py +103 -0
  49. tensor_ee/layouts/raster.py +117 -0
  50. tensor_ee/layouts/shift.py +226 -0
  51. tensor_ee/layouts/window.py +201 -0
  52. tensor_ee/models/__init__.py +16 -0
  53. tensor_ee/models/fixtures.py +119 -0
  54. tensor_ee/models/segformer_b0/__init__.py +9 -0
  55. tensor_ee/models/segformer_b0/configuration.py +209 -0
  56. tensor_ee/models/segformer_b0/decoder.py +130 -0
  57. tensor_ee/models/segformer_b0/encoder.py +90 -0
  58. tensor_ee/models/swin_t224/__init__.py +30 -0
  59. tensor_ee/models/swin_t224/configuration.py +103 -0
  60. tensor_ee/models/swin_t224/decoder.py +53 -0
  61. tensor_ee/models/swin_t224/encoder.py +50 -0
  62. tensor_ee/onnx_import.py +431 -0
  63. tensor_ee/outputs.py +16 -0
  64. tensor_ee/primitives.py +110 -0
  65. tensor_ee/receptive_field.py +257 -0
  66. tensor_ee/resnet_models.py +185 -0
  67. tensor_ee/segwater_inference.py +235 -0
  68. tensor_ee/segwater_preprocessing.py +182 -0
  69. tensor_ee/segwater_pytorch.py +249 -0
  70. tensor_ee/semantics.py +389 -0
  71. tensor_ee/smp.py +155 -0
  72. tensor_ee/swin.py +197 -0
  73. tensor_ee/swin_workflow.py +470 -0
  74. tensor_ee/tiling.py +287 -0
  75. tensor_ee/weight_assets.py +371 -0
  76. tensor_ee/weight_mapping.py +51 -0
  77. tensor_ee/weights/__init__.py +14 -0
  78. tensor_ee/weights/assets.py +160 -0
  79. tensor_ee/weights/constants.py +156 -0
  80. tensor_ee-0.1.0.dist-info/METADATA +172 -0
  81. tensor_ee-0.1.0.dist-info/RECORD +84 -0
  82. tensor_ee-0.1.0.dist-info/WHEEL +5 -0
  83. tensor_ee-0.1.0.dist-info/licenses/LICENSE +21 -0
  84. 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
+ })