Implementation Notes¶
Non-obvious architectural decisions, quirks, and Keras-specific gotchas that are easy to "fix" incorrectly if you don't know why they're there.
AnySat release port (foundation/anysat_release.py)¶
iRPE head-0 broadcasting quirk. The released config only turns on the
contextual relative-position-encoding bias on keys (rpe_on="k"), shared
across heads, with 8 relative-distance buckets. A non-obvious,
empirically-confirmed quirk of the reference implementation's pure-Python
(non-CUDA) fallback path is that torch.gather's under-specified-dim
broadcasting silently restricts the bias computation to only the first
attention head's queries — the other heads' queries never participate —
with the resulting bias then broadcast back across all heads when added to
the attention scores. This is reproduced bit-for-bit in _rpe_bias_k since
the released checkpoint's weights were trained against exactly that
behavior; "fixing" it would silently break loading the real weights, not
correct a bug.
LayerNorm eps. AnyModule's own blocks use eps=1e-6, but the global
and cross-pooling blocks use PyTorch's nn.LayerNorm default of eps=1e-5
(Keras's own default is 1e-3) — the split must be reproduced exactly, not
unified to one value, to match the checkpoint numerically.
Every reshape/grouping op (the nested "unfold" sequences that interleave a
modality's finer patch grid with the coarser scale-tile grid) was
validated index-for-index against the actual gastruc/AnySat source, not
re-derived from the paper.
Clay and CROMA: LayerNorm eps¶
Both foundation/clay.py and foundation/croma.py hardcode
_LN_EPS = 1e-5: PyTorch's plain nn.LayerNorm/nn.TransformerEncoderLayer
default to eps=1e-5, while Keras's own default is 1e-3 and this repo's
other ViT blocks use 1e-6 (matching timm/MAE). Each model's eps must
match its own reference implementation's default exactly for checkpoint
compatibility — there is no single "correct" value across the repo.
Pangu-Weather (weather/pangu_weather.py)¶
Earth-Specific Positional Bias. Earth's atmosphere is not translation-invariant in latitude or pressure level (polar vs. equatorial dynamics genuinely differ, as does behavior at different altitudes), so the attention bias is not shared/relative across window positions in those two axes — each (pressure-level-window, latitude-window) pair gets its own full, densely-parameterized bias table. Longitude is treated as translation-invariant, so the bias table has no longitude-window axis.
Batch size. The official reference hardcodes several reshapes assuming
batch_size=1 (the realistic use case for a single 721×1440 global weather
state). This port generalizes those reshapes to carry a real batch axis
(merging it into the longitude-window axis, which the bias never depends
on) so batch_size > 1 also works, while remaining bit-exact with the
reference for batch=1.
attn_mask, not mask. EarthAttention3D.call's optional mask
argument is deliberately named attn_mask, not mask — Keras's
Layer.__call__ reserves the literal name mask for its own automatic
mask-propagation protocol. A custom call() kwarg named mask can be
silently intercepted by the framework instead of reaching your own code as
a plain value.
Naming mirrors the reference exactly, including unusual choices (the
MLP submodule is called linear, its own two Dense layers linear1/
linear2; attention QKV/output projections are also linear1/linear2
rather than qkv/proj) — kept as-is (rather than "improved") specifically
so weights/mappings/pangu_weather_mapping.py's regex rules stay a direct
one-to-one translation of the real checkpoint's own key names. The shifted
window mask generator (_gen_shift_mask) similarly keeps a latitude-axis
slicing asymmetry byte-for-byte faithful to the reference, whether or not
it was intentional upstream.
Scope boundary. PatchEmbedding expects its auxiliary inputs
(per-variable normalization stats, land-sea/soil-type/topography masks, a
constant per-level field) already normalized and concatenated by the
caller — the official aux_data.zip preprocessing is not baked into the
model itself.
PatchTST and TFT: the Functional-graph "dead branch" gotcha¶
A layer whose weights are only ever touched outside of call() never
becomes a node in the Functional graph, so Keras has no way to know it
belongs to the model — those weights would then silently be missing from
model.weights entirely, meaning model.fit() never updates them and
save_weights() silently drops them. This showed up twice:
PatchTST'sRevINinvokesnormalize/denormalizethrough the realcall()/__call__()path (revin(x, mode="norm")), not via custom methods that bypass it.PatchTST'sLearnedPositionalEmbeddingadds its raw weight directly insidecall()on the real (symbolic) token tensor, rather than doing anEmbeddinglookup over an eagerly-computed constant index array (which would never register as a graph node).TFTdeliberately does not implement the paper's extrastatic_context_selectionGRN input, since there's no point constructing a GRN whose output would never be consumed — an unconsumed branch's weights wouldn't register as part of the model either.
TimesNet: a fixed fusion bug¶
The per-period fusion weights must actually depend on the input's FFT
amplitude, not be a uniform average. An earlier version had a bug where
xf/amp were computed and immediately discarded, so every period got the
same fusion weight regardless of input — covered by a regression test.
weights/converter.py: checkpoint batch-broadcast axis¶
A very common real-world checkpoint quirk: a positional/token embedding
saved with an extra leading batch-broadcast axis of size 1 (e.g. official
MAE's pos_embed is (1, N, D)) where this repo's Keras weight is stored
without it ((N, D)) and adds the axis back at use time. convert()
squeezes it before shape inference so it isn't treated as a hard mismatch.
Also: an explicit param_kind override is always honored before the
"shapes already match" shortcut in infer_transpose — a square
nn.Linear.weight (out == in) has the exact same shape as its Keras
Dense.kernel counterpart while still needing the (out, in) -> (in, out)
transpose, so shape equality alone can't be trusted to mean "no transform
needed" for a known dense kernel.
utils/layers.py: "same" padding vs. PyTorch's symmetric padding¶
Keras's padding="same" pads asymmetrically (extra on the bottom/right)
whenever strides > 1 and the input size isn't an exact multiple of the
stride in a way that makes the needed padding even. PyTorch reference convs
almost always pad symmetrically via an explicit
padding=dilation*(k-1)//2, so relying on "same" would silently misalign
every ported strided conv (stem/downsampling convs) against its source
checkpoint. ConvBNAct pads explicitly + "valid" whenever strides > 1
to reproduce PyTorch's convention exactly; for strides == 1, "same" is
already symmetric.
SatCLIP: reverse-engineered spherical harmonics normalization¶
The official checkpoint's default harmonics_calculation="analytic"
sign/normalization convention (a ~40,000-line sympy-generated closed-form
expansion upstream, one function per (l, m) pair) is not reproduced
directly — it was reverse-engineered from its output values instead:
standard 4π-normalized real spherical harmonics without the
Condon-Shortley phase, except the m=0 (zonal) terms carry an extra
factor of π that the official "analytic" functions apply but the
"closed-form" alternative in the same codebase does not. Confirmed against
known reference outputs (Yl0_m0 = 0.886226925..., Yl1_m0 =
1.534990062..., both exactly π times the standard-normalized value).
Models with no ported or verifiable pretrained checkpoint¶
Several modules include a scope note on why a real, publicly available
checkpoint was not ported even though one technically exists — the
underlying issue is always the same shape: the checkpoint's state_dict
reveals a computation graph that can't be reconstructed from tensor
names/shapes alone without the original source code, so porting it would
risk silently assigning real weights into the wrong graph. See
Pretrained Weights for the
full list (RingMo, Clay, this repo's own AnySatEncoder, Autoformer,
Informer, N-BEATS, DLinear, Earthformer, MetNet).