From 0a071bf6ff8fe30dc2a2e43c27013f09fcd9be7b Mon Sep 17 00:00:00 2001 From: Pablo Gomez Date: Tue, 17 Feb 2026 15:22:31 +0100 Subject: [PATCH 1/3] fix(model): resolve NFS flock error when loading pretrained timm weights Use timm's cache_dir parameter to store pretrained weights locally in anomaly_match/pretrained_cache/, avoiding fcntl.flock failures on NFS filesystems. Bundle the default tf_efficientnet_lite0.in1k weights so the model loads without network access. Also skip redundant pretrained weight download for eval_model in FixMatch, since its weights are immediately overwritten by copying from train_model. --- .gitattributes | 1 + anomaly_match/models/FixMatch.py | 9 ++++++++- ...2da5b6214e71d5fda5666bef4dfae0699407e5bef13e1 | 3 +++ .../refs/main | 1 + .../model.safetensors | 1 + anomaly_match/utils/get_net_builder.py | 16 +++++++++++++--- 6 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 .gitattributes create mode 100644 anomaly_match/pretrained_cache/models--timm--tf_efficientnet_lite0.in1k/blobs/1b2a452450b5cf3f8f42da5b6214e71d5fda5666bef4dfae0699407e5bef13e1 create mode 100644 anomaly_match/pretrained_cache/models--timm--tf_efficientnet_lite0.in1k/refs/main create mode 120000 anomaly_match/pretrained_cache/models--timm--tf_efficientnet_lite0.in1k/snapshots/e074755b39c3cd8ca77d917a7cfb62e70da6bf88/model.safetensors diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..321fa9e --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +anomaly_match/pretrained_cache/models--timm--*/blobs/* filter=lfs diff=lfs merge=lfs -text diff --git a/anomaly_match/models/FixMatch.py b/anomaly_match/models/FixMatch.py index cbc680a..f767649 100644 --- a/anomaly_match/models/FixMatch.py +++ b/anomaly_match/models/FixMatch.py @@ -54,8 +54,15 @@ def __init__( self.ema_m = ema_m # Create two versions of the model: one for training and one for evaluation with EMA + # eval_model skips pretrained weight download because its weights are immediately + # overwritten by copying from train_model below. self.train_model = net_builder(num_classes=num_classes, in_channels=in_channels) - self.eval_model = net_builder(num_classes=num_classes, in_channels=in_channels) + try: + self.eval_model = net_builder( + num_classes=num_classes, in_channels=in_channels, pretrained=False + ) + except TypeError: + self.eval_model = net_builder(num_classes=num_classes, in_channels=in_channels) self.T = T self.p_cutoff = p_cutoff self.lambda_u = lambda_u diff --git a/anomaly_match/pretrained_cache/models--timm--tf_efficientnet_lite0.in1k/blobs/1b2a452450b5cf3f8f42da5b6214e71d5fda5666bef4dfae0699407e5bef13e1 b/anomaly_match/pretrained_cache/models--timm--tf_efficientnet_lite0.in1k/blobs/1b2a452450b5cf3f8f42da5b6214e71d5fda5666bef4dfae0699407e5bef13e1 new file mode 100644 index 0000000..829e36f --- /dev/null +++ b/anomaly_match/pretrained_cache/models--timm--tf_efficientnet_lite0.in1k/blobs/1b2a452450b5cf3f8f42da5b6214e71d5fda5666bef4dfae0699407e5bef13e1 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b2a452450b5cf3f8f42da5b6214e71d5fda5666bef4dfae0699407e5bef13e1 +size 18802812 diff --git a/anomaly_match/pretrained_cache/models--timm--tf_efficientnet_lite0.in1k/refs/main b/anomaly_match/pretrained_cache/models--timm--tf_efficientnet_lite0.in1k/refs/main new file mode 100644 index 0000000..87a27ab --- /dev/null +++ b/anomaly_match/pretrained_cache/models--timm--tf_efficientnet_lite0.in1k/refs/main @@ -0,0 +1 @@ +e074755b39c3cd8ca77d917a7cfb62e70da6bf88 \ No newline at end of file diff --git a/anomaly_match/pretrained_cache/models--timm--tf_efficientnet_lite0.in1k/snapshots/e074755b39c3cd8ca77d917a7cfb62e70da6bf88/model.safetensors b/anomaly_match/pretrained_cache/models--timm--tf_efficientnet_lite0.in1k/snapshots/e074755b39c3cd8ca77d917a7cfb62e70da6bf88/model.safetensors new file mode 120000 index 0000000..4ecd189 --- /dev/null +++ b/anomaly_match/pretrained_cache/models--timm--tf_efficientnet_lite0.in1k/snapshots/e074755b39c3cd8ca77d917a7cfb62e70da6bf88/model.safetensors @@ -0,0 +1 @@ +../../blobs/1b2a452450b5cf3f8f42da5b6214e71d5fda5666bef4dfae0699407e5bef13e1 \ No newline at end of file diff --git a/anomaly_match/utils/get_net_builder.py b/anomaly_match/utils/get_net_builder.py index 777cac4..539f561 100644 --- a/anomaly_match/utils/get_net_builder.py +++ b/anomaly_match/utils/get_net_builder.py @@ -4,10 +4,16 @@ # is part of this source code package. No part of the package, including # this file, may be copied, modified, propagated, or distributed except according to # the terms contained in the file 'LICENCE.txt'. +from pathlib import Path + import timm import torch.nn as nn from loguru import logger +# Local cache directory for pretrained weights, avoids flock issues on NFS filesystems. +_PACKAGE_DIR = Path(__file__).resolve().parent.parent +_PRETRAINED_CACHE_DIR = _PACKAGE_DIR / "pretrained_cache" + # Mapping from AnomalyMatch net names to timm model identifiers. # efficientnet-lite variants use the tf_ prefix for TF-style same-padding, # which is backward compatible with the previous efficientnet_lite_pytorch package. @@ -113,7 +119,7 @@ def get_net_builder(net_name, pretrained=False, in_channels=3): if net_name == "test-cnn": logger.debug("Using test-cnn model (for testing only)") - def build_test_cnn(num_classes, in_channels): + def build_test_cnn(num_classes, in_channels, **kwargs): return TestCNN(num_classes=num_classes, in_channels=in_channels) return build_test_cnn @@ -124,12 +130,16 @@ def build_test_cnn(num_classes, in_channels): f"(timm: {timm_name})" ) - def build_model(num_classes, in_channels, _timm_name=timm_name, _pretrained=use_pretrained): + def build_model( + num_classes, in_channels, _timm_name=timm_name, _pretrained=use_pretrained, pretrained=None + ): + effective_pretrained = pretrained if pretrained is not None else _pretrained return timm.create_model( _timm_name, - pretrained=_pretrained, + pretrained=effective_pretrained, num_classes=num_classes, in_chans=in_channels, + cache_dir=str(_PRETRAINED_CACHE_DIR) if effective_pretrained else None, ) return build_model From eede4d28893c67308b7994c837a04052b44f0d22 Mon Sep 17 00:00:00 2001 From: Pablo Gomez Date: Tue, 17 Feb 2026 15:28:02 +0100 Subject: [PATCH 2/3] fix(ci): enable LFS checkout and resolve vulture dead code warning Add lfs: true to actions/checkout in CI so bundled pretrained weights are fetched. Replace **kwargs with explicit pretrained parameter in build_test_cnn to satisfy vulture dead code detection. --- .github/workflows/run_tests.yml | 2 ++ anomaly_match/utils/get_net_builder.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index d118ab9..25b1b93 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -24,6 +24,8 @@ jobs: id-token: write steps: - uses: actions/checkout@v4 + with: + lfs: true - name: Set up Python 3.11 uses: actions/setup-python@v5 with: diff --git a/anomaly_match/utils/get_net_builder.py b/anomaly_match/utils/get_net_builder.py index 539f561..c696352 100644 --- a/anomaly_match/utils/get_net_builder.py +++ b/anomaly_match/utils/get_net_builder.py @@ -119,7 +119,7 @@ def get_net_builder(net_name, pretrained=False, in_channels=3): if net_name == "test-cnn": logger.debug("Using test-cnn model (for testing only)") - def build_test_cnn(num_classes, in_channels, **kwargs): + def build_test_cnn(num_classes, in_channels, pretrained=None): return TestCNN(num_classes=num_classes, in_channels=in_channels) return build_test_cnn From 9ff46f70544eab5045dd41a7c304d8a78c6d6330 Mon Sep 17 00:00:00 2001 From: Pablo Gomez Date: Tue, 17 Feb 2026 15:42:07 +0100 Subject: [PATCH 3/3] fix(pretrained): fallback to HuggingFace download when bundled weights unavailable If the repo is cloned without git-lfs, the bundled pretrained cache contains LFS pointer files instead of actual weights. This adds a try/except fallback that downloads from HuggingFace in that case, with a warning suggesting git-lfs for offline use. --- anomaly_match/utils/get_net_builder.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/anomaly_match/utils/get_net_builder.py b/anomaly_match/utils/get_net_builder.py index c696352..8a3f2a9 100644 --- a/anomaly_match/utils/get_net_builder.py +++ b/anomaly_match/utils/get_net_builder.py @@ -134,12 +134,31 @@ def build_model( num_classes, in_channels, _timm_name=timm_name, _pretrained=use_pretrained, pretrained=None ): effective_pretrained = pretrained if pretrained is not None else _pretrained + if effective_pretrained: + try: + return timm.create_model( + _timm_name, + pretrained=True, + num_classes=num_classes, + in_chans=in_channels, + cache_dir=str(_PRETRAINED_CACHE_DIR), + ) + except Exception: + logger.warning( + f"Bundled pretrained weights not available (clone with git-lfs to avoid " + f"re-downloading). Downloading {_timm_name} from HuggingFace." + ) + return timm.create_model( + _timm_name, + pretrained=True, + num_classes=num_classes, + in_chans=in_channels, + ) return timm.create_model( _timm_name, - pretrained=effective_pretrained, + pretrained=False, num_classes=num_classes, in_chans=in_channels, - cache_dir=str(_PRETRAINED_CACHE_DIR) if effective_pretrained else None, ) return build_model