Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions tests/reference/upernet/helpers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
"""Small NumPy and PyTorch helpers the ported decoder tests use, copied unchanged (apart from
their imports) from research modules archived at the tag ``research-archive-2026-10-12``:
"""Small NumPy and PyTorch helpers copied unchanged (apart from imports and the historical
fixture's name) from research modules archived at ``research-archive-2026-10-12``,
plus a new portable rounding probe:

- ``pixel_ids_numpy``: ``upernet/earth_engine/feature_interface.py``;
- ``fold``: ``upernet/earth_engine/normalization.py`` (BatchNorm folded into a conv weight;
the decoder runs explicit BatchNorm instead, and this shows why);
- ``grouped_probe_inputs``: ``upernet/earth_engine/validate_fusion.py`` (the Phase 16 fixture
that separates the fusion formulations);
- ``grouped_probe_inputs_historical``: ``upernet/earth_engine/validate_fusion.py``
(the original Phase 16 fixture, unchanged);
- ``grouped_probe_inputs``: a new portable fixture whose double sums are independent
of accumulation order;
- ``torch_resize_matrix``: ``upernet/reference/m12_architecture_contract.py`` (PyTorch's own
bilinear resize matrix, read off one-hot inputs).
"""
Expand Down Expand Up @@ -34,6 +37,27 @@ def fold(weight: np.ndarray, gamma, beta, mean, var, eps: float = BN_EPS) -> tup


def grouped_probe_inputs():
"""A one-window fixture (7 x 7 tokens, four groups of two channels) whose fusion conv
separates A's float32 partials from B and the monolithic convolution. Channel 0
is 2^40 + 1 (group 1) - 2^40 (group 2); channel 1 is 2^40 + 1 (group 3) + 1 -
2^40 (group 4). All products and every subtotal are integers of magnitude at most
2^41 + 2, exactly representable in double in any accumulation order. Monolithic
and B give (1, 2); A rounds away the units in each partial and gives (0, 0).
This is an offline probe, not the historical live fixture's 2^53 example."""
x = np.zeros((7, 7, 8), np.float32)
x[3, 3, [0, 1, 2]] = [2.0 ** 20, 1.0, -(2.0 ** 20)]
x[3, 3, [4, 5, 6, 7]] = [2.0 ** 20, 1.0, 1.0, -(2.0 ** 20)]
w = np.zeros((2, 8, 3, 3), np.float32)
w[0, [0, 1, 2], 1, 1] = [2.0 ** 20, 1.0, 2.0 ** 20]
w[1, [4, 5, 6, 7], 1, 1] = [2.0 ** 20, 1.0, 1.0, 2.0 ** 20]
params = {R.WEIGHT: w, f"{R.PREFIX}.1.weight": np.ones(2, np.float32),
f"{R.PREFIX}.1.bias": np.zeros(2, np.float32),
f"{R.PREFIX}.1.running_mean": np.zeros(2, np.float32),
f"{R.PREFIX}.1.running_var": np.ones(2, np.float32)}
return x, w, params


def grouped_probe_inputs_historical():
"""A one-window fixture (7 x 7 tokens, four groups of two channels) whose fusion conv
reveals each formulation's rounding. Output channel 0 at the centre token is
2^40 (group 1) + 1 (group 1) - 2^40 (group 2); channel 1 is 2^53 + 1 (group 3) + 1 -
Expand Down
43 changes: 40 additions & 3 deletions tests/test_upernet_fusion_optimized.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,53 @@ def test_grouped_double_differs_only_at_rounding_boundaries(case):


def test_rounding_probe_fixture_separates_the_formulations():
"""Phase 16 probe (run live by the archived validate_fusion): channel 0 = 2^40 + 1 - 2^40 across groups,
channel 1 = 2^53 + 1 | + 1 - 2^53: monolithic (1, 0), A (0, 0), B (1, 1)."""
"""Exact double sums in any order: monolithic and B (1, 2), rounded A (0, 0).

Each product is an integer and the sum of absolute products is at most 2^41 + 2,
below double's exact-integer limit. Float32 partials still lose the unit terms.
"""
from reference.upernet.helpers import grouped_probe_inputs
x, w, params = grouped_probe_inputs()
resized = {R.RESIZED[n]: x[..., 2 * g:2 * g + 2].astype(np.float64) for g, n in enumerate(R.PYRAMID)}
assert list(MF.conv3x3(x, w)[3, 3]) == [1.0, 0.0]
assert list(MF.conv3x3(x, w)[3, 3]) == [1.0, 2.0]
assert list(MU.grouped_rounded(resized, params)["grouped_sum"][3, 3]) == [0.0, 0.0]
assert list(MU.grouped_double(resized, params)["double_sum"][3, 3]) == [1.0, 2.0]


def test_historical_rounding_probe_grouped_formulations():
"""Original 2^53 probe: real grouped A (0, 0), B (1, 1).

Each group dot has at most two nonzero terms, so its result is independent of
the BLAS accumulation order. The four partials are added in MU's fixed order.
"""
from reference.upernet.helpers import grouped_probe_inputs_historical
x, _, params = grouped_probe_inputs_historical()
resized = {R.RESIZED[n]: x[..., 2 * g:2 * g + 2].astype(np.float64) for g, n in enumerate(R.PYRAMID)}
assert list(MU.grouped_rounded(resized, params)["grouped_sum"][3, 3]) == [0.0, 0.0]
assert list(MU.grouped_double(resized, params)["double_sum"][3, 3]) == [1.0, 1.0]


def test_historical_rounding_probe_with_explicit_sequential_accumulation():
"""Historical Phase 16 monolithic dot in sequential order gives (1, 0).

This result depends on accumulation order: sequential 2^53 + 1 + 1 - 2^53
loses both units, while regrouping can retain them. Use fixed scalar additions,
never a BLAS/MKL reduction whose SIMD path may reassociate the double sum.
A and B are checked through the real grouped code in the test above.
"""
from reference.upernet.helpers import grouped_probe_inputs_historical
x, w, _ = grouped_probe_inputs_historical()
# Only the centre tap is nonzero; preserve its channel order explicitly.
products = x[3, 3].astype(np.float64) * w[:, :, 1, 1].astype(np.float64)
monolithic = []
for channel in products:
total = 0.0
for term in channel:
total = total + float(term)
monolithic.append(float(np.float32(total)))
assert monolithic == [1.0, 0.0]


def test_order_study(case):
"""Phase 17: B is insensitive to the group order except at rounding boundaries; A's result
depends on the order in a fraction of the outputs; all within 1e-6 of float64."""
Expand Down
Loading