Skip to content

Compile zero padding ahead of a convolution as a sampling phase, validated live - #49

Merged
noelEOS merged 5 commits into
mainfrom
raster/padded-conv
Nov 12, 2026
Merged

noelEOS merged 5 commits into
mainfrom
raster/padded-conv

Conversation

@noelEOS

@noelEOS noelEOS commented Nov 12, 2026

Copy link
Copy Markdown
Owner

Zero padding directly ahead of a convolution now compiles through compile_model, as the existing convolution blocks sampled at a phase. This covers "same" padding in the TensorFlow style, which pads more after the input than before it, and the functional form in which some encoders write their convolutions. Validated live in this pull request.

  • The setting. A keyword-only phase on conv2d, grouped_conv2d and layouts.raster.stride_sample: an integer or a (phase_y, phase_x) pair, 0 <= phase < stride, default 0. Output pixel i on an axis is the centred convolution at input pixel stride * i + phase, which is PyTorch's convolution with kernel // 2 - phase zeros before the input, with ceil((n - phase) / stride) outputs. The convolution itself is unchanged; only the sample grid moves. The default emits today's graph.
  • The recognition. A convolution is an nn.Conv2d module or F.conv2d whose weight and bias are the model's own parameters, read only by that call. Zero padding directly ahead of it, as nn.ZeroPad2d, nn.ConstantPad2d with 0, or F.pad with four fixed integers and 0, adds to the convolution's own padding. The rule is on the total: per axis, the padding before the input must be kernel // 2 - phase and PyTorch's output count must match. BatchNorm folding, analyze_receptive_field, weight collection and MatchedWeightProvider work through a recognised convolution, with state-dict paths as weight sources.
  • Models. With random weights, SMP 0.5.0 U-Nets on efficientnet-b0 and efficientnet-b3 at 256 × 256 are supported by check_compatibility after adapt_smp_model. This is a trace status: neither model's graph has been evaluated.
  • Contributing guide. One sentence where the validated record is described: a new setting on an existing block enters the record at its default value until a live run varies it.

Every graph that compiles today is unchanged. No new block, policy or dependency.

Base revision: 98ad27c5 (main).

Changes by layer

  • Layouts: tensor_ee/layouts/raster.py (stride_sample's phase, sample_grids, phase_pair, canonical_phase).
  • Raster blocks: tensor_ee/blocks/raster/conv.py (phase on both blocks; metadata).
  • Compiler and analysis: semantics.py (recognition, the fold), compiler.py, receptive_field.py, compatibility.py, weight_assets.py.
  • Fixtures: tools/fixtures/raster.py (selection padded_conv, six fixtures on stand-in modules, integer-generated).
  • Tests: tests/test_raster_padded_conv.py (new), tests/reference/raster_conv.py (a padding keyword on the existing mirror), tests/test_live_fixtures.py, tests/test_raster_blocks.py, tests/test_raster_grouped_conv.py, tests/test_raster_global_pool_multiply.py.
  • Docs and evidence: CONTRIBUTING.md, docs/api.md, docs/smp-models.md, docs/building-blocks.md (regenerated), docs/evidence/raster-padded-conv-offline.json, docs/evidence/live-fixtures/20261112T185127Z-padded_conv.json.

Checks on the head commit

Run locally on 5457c60 with a clean tree (Python 3.14, all extras, trained checkpoints present):

  • G1: 45 of 45 graphs equal (9 trained, 36 fixtures, none skipped).
  • G1-ext: 19 of 19 graphs equal.
  • Both gates at the base: 45 of 45 and 19 of 19, run on 98ad27c5 before the first edit.
  • pytest -q: 4134 passed, 20 skipped.
  • Catalogue check: in sync.
  • --offline padded_conv: 6 fixtures passed, zero cloud calls.
  • --plan padded_conv: 12 requests, largest 400 values and 22,716 bytes, 0 assets, 0 tasks, within budget.
  • Existing raster selections: the --plan graphs and request counts of calibration, p02, hard_activations, batch_norm, bilinear, grouped_conv, standardize and global_pool_multiply are identical to the base's.

Not reproduced locally: the hosted jobs on Python 3.11 and 3.12 without extras.

Largest absolute error per fixture (tolerance 1e-4). The three columns are separate claims:

Fixture Offline: mirror vs float32 PyTorch Live: Earth Engine vs mirror Live: Earth Engine vs float32 PyTorch
raster/conv2d/pad0101-k3-s2-h8-w10 no mirror no mirror 2.98e-08
raster/conv2d/pad0101-k3-s2-h7-w9 no mirror no mirror 5.96e-08
raster/grouped_conv2d/depthwise-pad0101-k3-s2 5.96e-08 0 5.96e-08
raster/grouped_conv2d/depthwise-pad1212-k5-s2 5.96e-08 0 5.96e-08
raster/grouped_conv2d/depthwise-pad2222-k5-s2 4.47e-08 0 4.47e-08
raster/grouped_conv2d/depthwise-pad1101-k3-s2 1.49e-08 0 1.49e-08

The dense conv2d block has no NumPy mirror (its existing fixture has none either), so its two fixtures are compared with PyTorch only.

Live run

The selection padded_conv ran once on the clean revision d162897c: 6 fixtures, 12 requests (the planned count), 0 assets, 0 tasks, no refused calls, 12.1 s. Every fixture passed. The record is committed unedited. The head commit's 12 planned graph hashes are identical to that revision's.

Changes to the two blocks' catalogue entries, each following from the run:

  • Validated records. Before the run, phase was named at its default in both records, so a nonzero phase was reported outside them. The run varied it (0, 1 and (1, 0)), so it leaves both records, which read as on main: conv2d "any accepted call", grouped_conv2d clip_footprints False.
  • grouped_conv2d status: "5×5 stride 1" becomes "5×5 stride 1 or 2".
  • grouped_conv2d radius: 2 becomes 3, the reach of a 5 × 5 kernel at phase 1.

Limitations

  • What ran live. At stride 2: dense 3 × 3 at phase 1 (even and odd input sizes); depthwise 3 × 3 at phase 1 and at phase (1, 0); depthwise 5 × 5 at phase 1 and, through a symmetric pad, at phase 0. Other kernels and phases, such as a dense 5 × 5 or 7 × 7 at a nonzero phase or a dense mixed phase, compile but are not validated.
  • Models. Only efficientnet-b0 and efficientnet-b3 were checked, as trace status. No whole model was evaluated offline or live, and there is no numerical or accuracy claim for these encoders. Without the adapter, the decoder's 10 in-place ReLUs still block the raw model.
  • Refused. Padding computed from a tensor's shape, even where it would resolve to fixed numbers; negative pads; nonzero values; reflection, replication and circular padding; a padding node with any other use; padding with no convolution after it; a total padding whose phase is outside [0, stride) or whose output count differs, such as an unpadded stride-1 convolution.
  • A consequence of judging the total padding. An unpadded 3 × 3 stride-2 convolution on an odd-sized map is the phase-1 form, compiles to the same graph, and is accepted; on an even-sized map it is refused.
  • Pooling has no phase; the sampler is shared and could carry it later.
  • Tiling. analyze_receptive_field uses the left and top pads. On the two U-Nets it still stops at the decoder's shape read, as before, and their global means rule out tiling in any case.

conv2d and grouped_conv2d take a keyword-only phase (default 0, today's
graph): output pixel i on each axis is the centred convolution at input
pixel stride * i + phase, which is PyTorch's convolution zero-padded
kernel // 2 - phase pixels before the input. stride_sample takes the same
phase. Both records name phase at its default until a live run varies it.

The compiler recognises nn.ZeroPad2d, nn.ConstantPad2d with zero and F.pad
with zero directly ahead of an nn.Conv2d or of F.conv2d with get_attr
weights, and an Identity ahead of F.conv2d. BatchNorm folding, the
receptive field and the weight collection reach through them.
…et trace checks

A new raster selection, padded_conv: six integer-generated fixtures through
compile_model on a stand-in padding module and F.conv2d, with the distinct
graphs only (dense 3 x 3 stride 2 at phase 1 on an even and an odd map;
depthwise 3 x 3 and 5 x 5 at phase 1; depthwise 5 x 5 at phase 0 through a
symmetric pad; phases (1, 0)). The grouped mirror takes PyTorch's zero
padding. SMP's efficientnet-b0 and efficientnet-b3 U-Nets are supported
through adapt_smp_model at 256 x 256. A new setting on an existing block
enters its record at its default until a live run varies it.
specialize_plumbing could turn a bare shape element in an F.pad into a
literal at the compiled input size; zero padding must be written
statically, so that pad is refused as padding computed from a tensor's
shape.
tools/live_fixtures.py --offline padded_conv on a clean tree: six
fixtures, twelve planned requests, zero cloud calls; the depthwise mirror
within 5.96e-08 of float32 PyTorch, the dense fixtures compared with
PyTorch only.
The padded_conv selection ran live once: 6 fixtures, 12 requests, all passed. Earth
Engine equals the NumPy mirror in every value on the four depthwise fixtures; the two
dense fixtures, which have no mirror, are within 5.96e-08 of float32 PyTorch.

The run varied phase (0, 1 and (1, 0)), so it leaves both records, which return to their
earlier wording. grouped_conv2d's status gains 5x5 at stride 2 and its radius becomes 3,
the reach of a 5x5 kernel at phase 1. The docstrings and guides list the forms that ran;
other kernels and phases are not validated.
@noelEOS
noelEOS enabled auto-merge (squash) November 12, 2026 19:05
@noelEOS
noelEOS merged commit add5acf into main Nov 12, 2026
3 checks passed
@noelEOS
noelEOS deleted the raster/padded-conv branch November 12, 2026 19:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant