Equimo 2.3.0__tar.gz → 2.4.0__tar.gz
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.
- {equimo-2.3.0 → equimo-2.4.0}/CHANGELOG.md +9 -0
- {equimo-2.3.0 → equimo-2.4.0}/NOTICE +12 -0
- {equimo-2.3.0 → equimo-2.4.0}/PKG-INFO +1 -1
- {equimo-2.3.0 → equimo-2.4.0}/pyproject.toml +1 -1
- equimo-2.4.0/src/equimo/__init__.py +1 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/pmd.py +72 -23
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/pmt.py +13 -4
- equimo-2.4.0/src/equimo/vision/query_training/__init__.py +40 -0
- equimo-2.4.0/src/equimo/vision/query_training/_assignment.py +130 -0
- equimo-2.4.0/src/equimo/vision/query_training/_loss.py +523 -0
- equimo-2.4.0/src/equimo/vision/query_training/_sampling.py +162 -0
- equimo-2.3.0/src/equimo/__init__.py +0 -1
- {equimo-2.3.0 → equimo-2.4.0}/LICENSE.md +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/Apache-2.0.txt +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/pretrained/README.md +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/pretrained/ast-BSD-3-Clause.txt +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/pretrained/convnext-Apache-2.0.txt +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/pretrained/convnextv2-CC-BY-NC-4.0.txt +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/pretrained/dinov2-Apache-2.0.txt +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/pretrained/dinov3-License.md +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/pretrained/eupe-FAIR-Noncommercial-Research-License.md +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/pretrained/lingbot-vision-Apache-2.0.txt +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/pretrained/siglip2-Apache-2.0.txt +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/pretrained/t0-alpha-Apache-2.0.txt +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/pretrained/tabpfn-3-License-v1.0.txt +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/pretrained/tips-CC-BY-4.0.txt +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/LICENSES/tfc-t0-APACHE-2.0.txt +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/README.md +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/docs/licensing/t0-source-provenance.md +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/docs/migration-v2.md +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/docs/stability.md +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/docs/timeseries.md +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/models/huggingface/NOTICE +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/models/huggingface/README.md +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/models/huggingface/convnext-conversion.json +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/_checkpoint_limits.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/_io.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/_pretrained.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/audio/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/audio/io.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/audio/layers/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/audio/layers/patch.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/audio/models/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/audio/models/ast.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/catalog.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/conversion/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/conversion/utils.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/_prng.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/emamodel.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/factory.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/implicit/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/implicit/_base.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/implicit/deq.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/implicit/injectors.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/implicit/stabilizers.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/implicit/strategies.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/intermediates.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/layers/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/layers/_registry.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/layers/activation.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/layers/attention.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/layers/dropout.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/layers/ffn.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/layers/generic.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/layers/mamba.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/layers/norm.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/layers/rotary.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/ops/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/core/ops/scan.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/_blocks.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/_serialization_codecs.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/_typing.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/audio/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/audio/recipes.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/audio/selectors.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/calibration.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/config.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/continued_ssl.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/feature_extraction.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/heads.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/inspection.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/labels.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/language/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/language/recipes.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/language/selectors.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/masks.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/merging.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/paths.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/peft/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/peft/_common.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/peft/_compat.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/peft/adapters.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/peft/base.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/peft/bitfit.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/peft/dora.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/peft/ia3.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/peft/lora.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/peft/prefix.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/peft/prompts.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/peft/scale_shift.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/peft/vera.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/pooling.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/recipes.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/regularization.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/selectors.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/serialization.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/surgery.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/tags.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/vision/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/vision/dense.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/vision/eomt.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/finetune/vision/pmt.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/language/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/language/models.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/language/tokenizers.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/registry.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/serialization.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/tabular/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/tabular/layers/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/tabular/layers/attention.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/tabular/layers/blocks.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/tabular/layers/decoder.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/tabular/layers/mlp.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/tabular/layers/preprocessing.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/tabular/layers/registry.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/tabular/models/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/tabular/models/tabpfn.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/timeseries/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/timeseries/layers/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/timeseries/layers/axis_attn.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/timeseries/layers/blocks.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/timeseries/layers/patch_encoder.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/timeseries/layers/registry.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/timeseries/layers/residual_mlp.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/timeseries/models/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/timeseries/models/t0.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/utils.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/_encoder_identity.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/io.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/layers/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/layers/attention.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/layers/convolution.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/layers/downsample.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/layers/patch.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/layers/posemb.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/layers/registry.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/layers/squeeze_excite.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/layers/wavelet.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/__init__.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/_embedding.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/_features.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/attnet.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/convnext.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/deq.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/eomt.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/fastervit.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/iformer.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/lowformer.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/mlla.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/mobilenet.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/parcae.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/partialformer.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/reduceformer.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/shvit.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/vit.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/models/vssd.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/pmt_checkpoint.py +0 -0
- {equimo-2.3.0 → equimo-2.4.0}/src/equimo/vision/segmentation.py +0 -0
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Equimo are documented here. Equimo follows Semantic Versioning from version 2.0.0 onward.
|
|
4
4
|
|
|
5
|
+
## [2.4.0] - 2026-09-24
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- JAX-native query-segmentation assignment, validity-aware point sampling, class/mask/Dice losses, auxiliary-output
|
|
10
|
+
composition, and inspectable batch/device reductions, with pinned upstream numerical references.
|
|
11
|
+
- Padding-aware PMT BatchNorm and configurable collective axes for synchronized statistics, preserving default model
|
|
12
|
+
checkpoint layouts.
|
|
13
|
+
|
|
5
14
|
## [2.3.0] - 2026-09-24
|
|
6
15
|
|
|
7
16
|
### Added
|
|
@@ -23,6 +23,18 @@ Source: https://github.com/tensorflow/models/blob/b41a080d61cd4298fea9894804e965
|
|
|
23
23
|
Equimo contributors modified the upstream code's formatting and type
|
|
24
24
|
annotations.
|
|
25
25
|
|
|
26
|
+
Query assignment
|
|
27
|
+
----------------
|
|
28
|
+
|
|
29
|
+
src/equimo/vision/query_training/_assignment.py adapts the Hungarian algorithm
|
|
30
|
+
from Optax, licensed under Apache-2.0 (LICENSES/Apache-2.0.txt).
|
|
31
|
+
Copyright 2024 DeepMind Technologies Limited. All Rights Reserved.
|
|
32
|
+
Source: https://github.com/google-deepmind/optax/blob/225a7079f4630bf75bee94ec78de9aa69c60fab3/optax/assignment/_hungarian_algorithm.py
|
|
33
|
+
|
|
34
|
+
Equimo contributors added target-slot validity, bounded loops, numerical-failure reporting,
|
|
35
|
+
fixed-capacity target-ordered outputs, explicit float32 arithmetic and stopped
|
|
36
|
+
gradients, and documented deterministic tie handling.
|
|
37
|
+
|
|
26
38
|
LingBot-Vision
|
|
27
39
|
-------------
|
|
28
40
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.4.0"
|
|
@@ -29,6 +29,7 @@ from equimo.vision.segmentation import (
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
_MISSING = object()
|
|
32
|
+
NormOption = float | bool | str | tuple[str, ...]
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
@dataclass(frozen=True)
|
|
@@ -57,7 +58,7 @@ class PMTConfig:
|
|
|
57
58
|
num_blocks: int = 6
|
|
58
59
|
norm_layer: str = "groupnorm"
|
|
59
60
|
norm_max_group: int = 32
|
|
60
|
-
norm_kwargs: tuple[tuple[str,
|
|
61
|
+
norm_kwargs: tuple[tuple[str, NormOption], ...] | Mapping[str, NormOption] = ()
|
|
61
62
|
masked_attention: bool = True
|
|
62
63
|
|
|
63
64
|
def __post_init__(self) -> None:
|
|
@@ -96,7 +97,7 @@ class PMTConfig:
|
|
|
96
97
|
raise ValueError("Unsupported PMT lateral norm_layer.")
|
|
97
98
|
allowed = {
|
|
98
99
|
"groupnorm": {"eps", "channelwise_affine"},
|
|
99
|
-
"batchnorm": {"eps", "momentum"},
|
|
100
|
+
"batchnorm": {"eps", "momentum", "axis_name"},
|
|
100
101
|
"layernorm": {"eps", "use_weight", "use_bias"},
|
|
101
102
|
"rmsnorm": {"eps", "use_weight", "use_bias"},
|
|
102
103
|
"none": set(),
|
|
@@ -179,11 +180,14 @@ def anneal_pmt_mask_state(
|
|
|
179
180
|
|
|
180
181
|
|
|
181
182
|
class ReferenceBatchNorm(eqx.nn.StatefulLayer):
|
|
182
|
-
"""
|
|
183
|
+
"""Padding-aware counterpart of the author's lateral SyncBatchNorm.
|
|
183
184
|
|
|
184
185
|
Call over one image inside ``jax.vmap(..., axis_name="pmt_batch")`` in
|
|
185
186
|
training. Statistics cover batch and tokens, including prefix tokens.
|
|
186
187
|
Inference uses the stored running mean and unbiased running variance.
|
|
188
|
+
Supply multiple axis names to synchronize over a local batch and devices.
|
|
189
|
+
False ``example_valid`` excludes an image from moments and counts. A global
|
|
190
|
+
batch with no valid observations returns zeros and leaves state unchanged.
|
|
187
191
|
"""
|
|
188
192
|
|
|
189
193
|
weight: jax.Array
|
|
@@ -193,8 +197,16 @@ class ReferenceBatchNorm(eqx.nn.StatefulLayer):
|
|
|
193
197
|
count_index: eqx.nn.StateIndex
|
|
194
198
|
eps: float = eqx.field(static=True)
|
|
195
199
|
momentum: float = eqx.field(static=True)
|
|
200
|
+
axis_name: str | tuple[str, ...] = eqx.field(static=True)
|
|
196
201
|
|
|
197
|
-
def __init__(
|
|
202
|
+
def __init__(
|
|
203
|
+
self,
|
|
204
|
+
dim: int,
|
|
205
|
+
*,
|
|
206
|
+
eps: float = 1e-5,
|
|
207
|
+
momentum: float = 0.1,
|
|
208
|
+
axis_name: str | tuple[str, ...] = "pmt_batch",
|
|
209
|
+
):
|
|
198
210
|
if dim <= 0 or eps <= 0 or not 0 < momentum <= 1:
|
|
199
211
|
raise ValueError("Invalid reference BatchNorm dimensions or options.")
|
|
200
212
|
self.weight = jnp.ones((dim,), dtype=jnp.float32)
|
|
@@ -204,39 +216,70 @@ class ReferenceBatchNorm(eqx.nn.StatefulLayer):
|
|
|
204
216
|
self.count_index = eqx.nn.StateIndex(jnp.asarray(0, dtype=jnp.int32))
|
|
205
217
|
self.eps = eps
|
|
206
218
|
self.momentum = momentum
|
|
219
|
+
names = (axis_name,) if isinstance(axis_name, str) else axis_name
|
|
220
|
+
if (
|
|
221
|
+
not isinstance(names, tuple)
|
|
222
|
+
or not names
|
|
223
|
+
or any(not isinstance(name, str) or not name for name in names)
|
|
224
|
+
or len(set(names)) != len(names)
|
|
225
|
+
):
|
|
226
|
+
raise ValueError(
|
|
227
|
+
"BatchNorm axis_name requires nonempty distinct axis names."
|
|
228
|
+
)
|
|
229
|
+
self.axis_name = axis_name
|
|
207
230
|
|
|
208
231
|
def __call__(
|
|
209
|
-
self,
|
|
232
|
+
self,
|
|
233
|
+
x: jax.Array,
|
|
234
|
+
state: eqx.nn.State,
|
|
235
|
+
*,
|
|
236
|
+
inference: bool,
|
|
237
|
+
example_valid: bool | jax.Array = True,
|
|
210
238
|
) -> tuple[jax.Array, eqx.nn.State]:
|
|
211
239
|
if x.ndim != 2 or x.shape[0] != self.weight.shape[0]:
|
|
212
240
|
raise ValueError("Reference BatchNorm expects (channels, tokens).")
|
|
213
|
-
|
|
241
|
+
valid = jnp.asarray(example_valid)
|
|
242
|
+
if valid.shape != () or valid.dtype != jnp.bool_:
|
|
243
|
+
raise ValueError("BatchNorm example_valid must be a boolean scalar.")
|
|
244
|
+
value = jnp.where(valid, x, 0).astype(jnp.float32)
|
|
214
245
|
if inference:
|
|
215
246
|
mean = state.get(self.mean_index)
|
|
216
247
|
variance = state.get(self.variance_index)
|
|
217
248
|
else:
|
|
218
|
-
if x.shape[1] * lax.axis_size(
|
|
249
|
+
if example_valid is True and x.shape[1] * lax.axis_size(self.axis_name) < 2:
|
|
219
250
|
raise ValueError("BatchNorm requires at least two observations.")
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
251
|
+
count = lax.psum(valid.astype(jnp.float32) * x.shape[1], self.axis_name)
|
|
252
|
+
count = eqx.error_if(
|
|
253
|
+
count, count == 1, "BatchNorm requires at least two valid observations."
|
|
254
|
+
)
|
|
255
|
+
denominator = jnp.maximum(count, 1)
|
|
256
|
+
mean = lax.psum(jnp.sum(value, axis=1), self.axis_name) / denominator
|
|
257
|
+
centered = jnp.where(valid, value - mean[:, None], 0)
|
|
258
|
+
variance = (
|
|
259
|
+
lax.psum(jnp.sum(jnp.square(centered), axis=1), self.axis_name)
|
|
260
|
+
/ denominator
|
|
223
261
|
)
|
|
224
|
-
|
|
225
|
-
|
|
262
|
+
unbiased = variance * count / jnp.maximum(count - 1, 1)
|
|
263
|
+
old_mean = state.get(self.mean_index)
|
|
264
|
+
old_variance = state.get(self.variance_index)
|
|
265
|
+
mean_update = (1 - self.momentum) * old_mean + self.momentum * mean
|
|
266
|
+
variance_update = (
|
|
267
|
+
1 - self.momentum
|
|
268
|
+
) * old_variance + self.momentum * unbiased
|
|
226
269
|
state = state.set(
|
|
227
|
-
self.mean_index,
|
|
228
|
-
(1 - self.momentum) * state.get(self.mean_index) + self.momentum * mean,
|
|
270
|
+
self.mean_index, jnp.where(count > 0, mean_update, old_mean)
|
|
229
271
|
)
|
|
230
272
|
state = state.set(
|
|
231
|
-
self.variance_index,
|
|
232
|
-
|
|
233
|
-
|
|
273
|
+
self.variance_index, jnp.where(count > 0, variance_update, old_variance)
|
|
274
|
+
)
|
|
275
|
+
state = state.set(
|
|
276
|
+
self.count_index,
|
|
277
|
+
state.get(self.count_index) + (count > 0).astype(jnp.int32),
|
|
234
278
|
)
|
|
235
|
-
state = state.set(self.count_index, state.get(self.count_index) + 1)
|
|
236
279
|
normalized = (value - mean[:, None]) * lax.rsqrt(variance[:, None] + self.eps)
|
|
237
|
-
return
|
|
238
|
-
|
|
239
|
-
), state
|
|
280
|
+
return jnp.where(
|
|
281
|
+
valid, normalized * self.weight[:, None] + self.bias[:, None], 0
|
|
282
|
+
).astype(x.dtype), state
|
|
240
283
|
|
|
241
284
|
|
|
242
285
|
class PMDAttention(Attention):
|
|
@@ -295,10 +338,13 @@ class Lateral(eqx.Module):
|
|
|
295
338
|
state: eqx.nn.State | None,
|
|
296
339
|
*,
|
|
297
340
|
inference: bool,
|
|
341
|
+
example_valid: bool | jax.Array = True,
|
|
298
342
|
) -> tuple[jax.Array, eqx.nn.State | None]:
|
|
299
343
|
if self.norm_layer == "batchnorm":
|
|
300
344
|
assert state is not None
|
|
301
|
-
normalized, state = self.norm(
|
|
345
|
+
normalized, state = self.norm(
|
|
346
|
+
tokens.T, state, inference=inference, example_valid=example_valid
|
|
347
|
+
)
|
|
302
348
|
normalized = normalized.T
|
|
303
349
|
elif self.norm_layer in ("groupnorm", "none"):
|
|
304
350
|
normalized = self.norm(tokens.T).T
|
|
@@ -397,6 +443,7 @@ class PlainMaskDecoder(eqx.Module):
|
|
|
397
443
|
inference: bool = True,
|
|
398
444
|
key: jax.Array | None = None,
|
|
399
445
|
mask_state: PMTMaskState | None = None,
|
|
446
|
+
example_valid: bool | jax.Array = True,
|
|
400
447
|
) -> PMTOutput | tuple[PMTOutput, eqx.nn.State | None]:
|
|
401
448
|
"""Run PMD; explicit state gives the same tuple interface for every norm."""
|
|
402
449
|
supplied = state is not _MISSING
|
|
@@ -431,7 +478,9 @@ class PlainMaskDecoder(eqx.Module):
|
|
|
431
478
|
)
|
|
432
479
|
lateral = []
|
|
433
480
|
for layer, tokens in zip(self.lateral, features.levels, strict=True):
|
|
434
|
-
result, running_state = layer(
|
|
481
|
+
result, running_state = layer(
|
|
482
|
+
tokens, running_state, inference=inference, example_valid=example_valid
|
|
483
|
+
)
|
|
435
484
|
lateral.append(result)
|
|
436
485
|
tokens = jnp.sum(jnp.stack(lateral), axis=0)
|
|
437
486
|
tokens = jnp.concatenate((self.queries.astype(tokens.dtype), tokens), axis=0)
|
|
@@ -31,9 +31,10 @@ class PMT(eqx.Module):
|
|
|
31
31
|
|
|
32
32
|
``norm_layer="batchnorm"`` reproduces the author's lateral architecture.
|
|
33
33
|
Construct that model with ``eqx.nn.make_with_state(PMT)`` and pass its state
|
|
34
|
-
on each call. Training
|
|
35
|
-
``
|
|
36
|
-
|
|
34
|
+
on each call. Training uses a named batch axis (default ``pmt_batch``).
|
|
35
|
+
Pass ``example_valid=False`` for padding; ``norm_kwargs['axis_name']`` may
|
|
36
|
+
include device axes for synchronized statistics. Normalization runs after
|
|
37
|
+
``encode`` so cached frozen features can be reused while the decoder learns.
|
|
37
38
|
"""
|
|
38
39
|
|
|
39
40
|
backbone: VisionTransformer
|
|
@@ -158,6 +159,7 @@ class PMT(eqx.Module):
|
|
|
158
159
|
inference: bool = True,
|
|
159
160
|
key: jax.Array | None = None,
|
|
160
161
|
mask_state: PMTMaskState | None = None,
|
|
162
|
+
example_valid: bool | jax.Array = True,
|
|
161
163
|
) -> PMTOutput | tuple[PMTOutput, eqx.nn.State | None]:
|
|
162
164
|
"""Decode retained features with the selected normalization state."""
|
|
163
165
|
if (
|
|
@@ -169,7 +171,12 @@ class PMT(eqx.Module):
|
|
|
169
171
|
):
|
|
170
172
|
raise ValueError("PMT features disagree with encoder or input view.")
|
|
171
173
|
return self.head(
|
|
172
|
-
features,
|
|
174
|
+
features,
|
|
175
|
+
state,
|
|
176
|
+
inference=inference,
|
|
177
|
+
key=key,
|
|
178
|
+
mask_state=mask_state,
|
|
179
|
+
example_valid=example_valid,
|
|
173
180
|
)
|
|
174
181
|
|
|
175
182
|
def __call__(
|
|
@@ -180,6 +187,7 @@ class PMT(eqx.Module):
|
|
|
180
187
|
inference: bool = True,
|
|
181
188
|
key: jax.Array | None = None,
|
|
182
189
|
mask_state: PMTMaskState | None = None,
|
|
190
|
+
example_valid: bool | jax.Array = True,
|
|
183
191
|
) -> PMTOutput | tuple[PMTOutput, eqx.nn.State | None]:
|
|
184
192
|
"""Return mask/class predictions; state is optional with GroupNorm."""
|
|
185
193
|
return self.decode(
|
|
@@ -188,6 +196,7 @@ class PMT(eqx.Module):
|
|
|
188
196
|
inference=inference,
|
|
189
197
|
key=key,
|
|
190
198
|
mask_state=mask_state,
|
|
199
|
+
example_valid=example_valid,
|
|
191
200
|
)
|
|
192
201
|
|
|
193
202
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Optimizer-independent, JAX-native query-segmentation training utilities."""
|
|
2
|
+
|
|
3
|
+
from ._assignment import QueryAssignment, match_queries
|
|
4
|
+
from ._sampling import PointSamples, sample_mask_points, sample_query_points
|
|
5
|
+
from ._loss import (
|
|
6
|
+
LossTerm,
|
|
7
|
+
MatchingCosts,
|
|
8
|
+
PredictionLoss,
|
|
9
|
+
PredictionLossContext,
|
|
10
|
+
QueryLossConfig,
|
|
11
|
+
QueryLossContext,
|
|
12
|
+
QueryLossResult,
|
|
13
|
+
QueryTargets,
|
|
14
|
+
prepare_query_loss,
|
|
15
|
+
query_matching_costs,
|
|
16
|
+
query_prediction_loss,
|
|
17
|
+
query_segmentation_loss,
|
|
18
|
+
reduce_query_losses,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"LossTerm",
|
|
23
|
+
"MatchingCosts",
|
|
24
|
+
"PointSamples",
|
|
25
|
+
"PredictionLoss",
|
|
26
|
+
"PredictionLossContext",
|
|
27
|
+
"QueryAssignment",
|
|
28
|
+
"QueryLossConfig",
|
|
29
|
+
"QueryLossContext",
|
|
30
|
+
"QueryLossResult",
|
|
31
|
+
"QueryTargets",
|
|
32
|
+
"match_queries",
|
|
33
|
+
"prepare_query_loss",
|
|
34
|
+
"query_matching_costs",
|
|
35
|
+
"query_prediction_loss",
|
|
36
|
+
"query_segmentation_loss",
|
|
37
|
+
"reduce_query_losses",
|
|
38
|
+
"sample_mask_points",
|
|
39
|
+
"sample_query_points",
|
|
40
|
+
]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Copyright 2024 DeepMind Technologies Limited. All Rights Reserved.
|
|
2
|
+
# Copyright 2026 Equimo contributors.
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
"""Padded rectangular assignment, adapted from Optax's Hungarian algorithm.
|
|
5
|
+
|
|
6
|
+
Reference: google-deepmind/optax, revision
|
|
7
|
+
225a7079f4630bf75bee94ec78de9aa69c60fab3,
|
|
8
|
+
optax/assignment/_hungarian_algorithm.py. See NOTICE for modifications.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import equinox as eqx
|
|
14
|
+
import jax
|
|
15
|
+
import jax.numpy as jnp
|
|
16
|
+
from jax import lax
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class QueryAssignment(eqx.Module):
|
|
20
|
+
"""Fixed-capacity matches in target order.
|
|
21
|
+
|
|
22
|
+
``query_indices[t]`` is the query matched to target slot ``t``. Invalid
|
|
23
|
+
slots have query index ``-1``. ``is_valid`` is false for nonfinite
|
|
24
|
+
active costs or internal arithmetic overflow; no matches are returned in
|
|
25
|
+
that case. Indices and decisions
|
|
26
|
+
are nondifferentiable. Ties use the lowest available search index, keeping
|
|
27
|
+
an existing predecessor on equal costs; this is not a lexicographic solver.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
query_indices: jax.Array
|
|
31
|
+
valid: jax.Array
|
|
32
|
+
is_valid: jax.Array
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def match_queries(costs: jax.Array, target_valid: jax.Array) -> QueryAssignment:
|
|
36
|
+
"""Minimize ``(Q,T)`` costs with ``T <= Q``, entirely on the JAX device.
|
|
37
|
+
|
|
38
|
+
Every valid target is assigned a distinct query. Invalid target columns
|
|
39
|
+
are ignored, including their contents. Complexity is O(T**2 * Q), with
|
|
40
|
+
O(T * Q) cost storage. No host solver, callback, or differentiation through
|
|
41
|
+
assignment is used. Nonfinite active costs or internal arithmetic overflow
|
|
42
|
+
produce ``is_valid=False``. Every augmenting-path loop is explicitly bounded.
|
|
43
|
+
"""
|
|
44
|
+
if costs.ndim != 2 or target_valid.shape != (costs.shape[1],):
|
|
45
|
+
raise ValueError("Expected (queries, targets) costs and (targets,) validity.")
|
|
46
|
+
queries, targets = costs.shape
|
|
47
|
+
if queries == 0 or targets > queries:
|
|
48
|
+
raise ValueError("Assignment requires 0 <= target capacity <= query count.")
|
|
49
|
+
if target_valid.dtype != jnp.bool_:
|
|
50
|
+
raise TypeError("Target validity must be boolean.")
|
|
51
|
+
matrix = lax.stop_gradient(costs.astype(jnp.float32).T)
|
|
52
|
+
finite = jnp.all(jnp.where(target_valid[:, None], jnp.isfinite(matrix), True))
|
|
53
|
+
active = target_valid & finite
|
|
54
|
+
matrix = jnp.where(active[:, None], matrix, 0)
|
|
55
|
+
if targets == 0:
|
|
56
|
+
return QueryAssignment(jnp.zeros((0,), dtype=jnp.int32), active, finite)
|
|
57
|
+
|
|
58
|
+
def insert(row, state):
|
|
59
|
+
def augment(state):
|
|
60
|
+
row_potential, column_potential, owner, feasible = state
|
|
61
|
+
owner = owner.at[0].set(row + 1)
|
|
62
|
+
|
|
63
|
+
def search(carry):
|
|
64
|
+
u, v, visited, distance, previous, column, steps = carry
|
|
65
|
+
visited = visited.at[column].set(True)
|
|
66
|
+
available = ~visited[1:]
|
|
67
|
+
reduced = matrix[owner[column] - 1] - u[owner[column]] - v[1:]
|
|
68
|
+
improves = available & (reduced < distance)
|
|
69
|
+
previous = jnp.where(improves, column, previous)
|
|
70
|
+
distance = jnp.where(improves, reduced, distance)
|
|
71
|
+
candidate = jnp.where(available, distance, jnp.inf)
|
|
72
|
+
next_column = jnp.argmin(candidate).astype(jnp.int32) + 1
|
|
73
|
+
delta = candidate[next_column - 1]
|
|
74
|
+
# Out-of-range scatter updates are dropped; unvisited columns
|
|
75
|
+
# must not repeatedly add to the dummy row's potential.
|
|
76
|
+
indices = jnp.where(visited, owner, targets + 1)
|
|
77
|
+
u = u.at[indices].add(delta, mode="drop")
|
|
78
|
+
v = jnp.where(visited, v - delta, v)
|
|
79
|
+
distance = jnp.where(available, distance - delta, distance)
|
|
80
|
+
return u, v, visited, distance, previous, next_column, steps + 1
|
|
81
|
+
|
|
82
|
+
def searching(carry):
|
|
83
|
+
*_, column, steps = carry
|
|
84
|
+
return (owner[column] != 0) & (steps <= row)
|
|
85
|
+
|
|
86
|
+
initial = (
|
|
87
|
+
row_potential,
|
|
88
|
+
column_potential,
|
|
89
|
+
jnp.zeros((queries + 1,), dtype=jnp.bool_),
|
|
90
|
+
jnp.full((queries,), jnp.inf, dtype=jnp.float32),
|
|
91
|
+
jnp.zeros((queries,), dtype=jnp.int32),
|
|
92
|
+
jnp.asarray(0, dtype=jnp.int32),
|
|
93
|
+
jnp.asarray(0, dtype=jnp.int32),
|
|
94
|
+
)
|
|
95
|
+
u, v, _, _, previous, column, _ = lax.while_loop(searching, search, initial)
|
|
96
|
+
feasible &= (
|
|
97
|
+
(owner[column] == 0)
|
|
98
|
+
& jnp.all(jnp.isfinite(u))
|
|
99
|
+
& jnp.all(jnp.isfinite(v))
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def backtrack(carry):
|
|
103
|
+
owner, column, steps = carry
|
|
104
|
+
predecessor = previous[column - 1]
|
|
105
|
+
return owner.at[column].set(owner[predecessor]), predecessor, steps + 1
|
|
106
|
+
|
|
107
|
+
owner, column, _ = lax.while_loop(
|
|
108
|
+
lambda carry: feasible & (carry[1] != 0) & (carry[2] <= row),
|
|
109
|
+
backtrack,
|
|
110
|
+
(owner, column, jnp.asarray(0, dtype=jnp.int32)),
|
|
111
|
+
)
|
|
112
|
+
return u, v, owner, feasible & (column == 0)
|
|
113
|
+
|
|
114
|
+
return lax.cond(active[row] & state[-1], augment, lambda value: value, state)
|
|
115
|
+
|
|
116
|
+
initial = (
|
|
117
|
+
jnp.zeros((targets + 1,), dtype=jnp.float32),
|
|
118
|
+
jnp.zeros((queries + 1,), dtype=jnp.float32),
|
|
119
|
+
jnp.zeros((queries + 1,), dtype=jnp.int32),
|
|
120
|
+
finite,
|
|
121
|
+
)
|
|
122
|
+
_, _, owner, feasible = lax.fori_loop(0, targets, insert, initial)
|
|
123
|
+
indices = jnp.full((targets,), -1, dtype=jnp.int32)
|
|
124
|
+
destinations = jnp.where(owner[1:] > 0, owner[1:] - 1, targets)
|
|
125
|
+
indices = indices.at[destinations].set(
|
|
126
|
+
jnp.arange(queries, dtype=jnp.int32), mode="drop"
|
|
127
|
+
)
|
|
128
|
+
return QueryAssignment(
|
|
129
|
+
jnp.where(feasible, indices, -1), active & feasible, feasible
|
|
130
|
+
)
|