diff --git a/.claude/skills/mblt-vision/SKILL.md b/.claude/skills/mblt-vision/SKILL.md index 643c86b..f8d3055 100644 --- a/.claude/skills/mblt-vision/SKILL.md +++ b/.claude/skills/mblt-vision/SKILL.md @@ -28,6 +28,14 @@ description: >- Use file_cfg.filename for MXQ and derive the same-stem ONNX artifact unless onnx_filename is required. - Every post_cfg declares dataset; resolve output taxonomy from the dataset/task pair. +- Additional published EfficientViT/LeViT/ViT classifiers use `bases/imagenet_classifier.yaml`. + Preserve the recorded ops transforms checked by `tests/test_published_classifiers.py`, + including the TF-normalized `ViT_Large_Patch14_CLIP_224` exception. Their MXQ-only + repositories use `onnx_local_artifact_only: true`: require trusted local ONNX and + retain automatic MXQ loading; metadata validation skips only the marked framework. +- New pending family siblings use `file_cfg.artifact_status: pending_hf_family_sibling` + and require trusted local artifacts for runtime/compile. Report them as pending without + network; retain byte-identical existing sibling YAMLs. - For Hub linkage changes, run `tests/test_hub_validation.py` as described in `tests/TEST.md`. The metadata checker follows runtime board paths without downloading weights; live commits are diagnostics, not substitutes for missing historical approval provenance. diff --git a/AGENTS.md b/AGENTS.md index a6f72b3..93e02fe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -72,6 +72,15 @@ The current ownership boundary is deliberate: floors, and rounding differs by a pixel at 224/0.9 and 224/0.95. Several names (ConvNext_Base) resolve in timm yet carry torchvision's transform here, so read the expected value from that model's source.yaml provenance rather than from the name. +- Published EfficientViT, non-convolutional LeViT and additional ViT classifiers inherit + `bases/imagenet_classifier.yaml`. Their exact recorded preprocessing and dataset connection + are pinned by `tests/test_published_classifiers.py`; preserve TF normalization for + `ViT_Large_Patch14_CLIP_224` rather than inferring normalization from its name. + These 24 repositories are MXQ-only: their base uses `onnx_local_artifact_only: true`. + Keep automatic MXQ loading enabled, require trusted local ONNX, and test both guard paths. + Newly added EfficientViT B0/B1/B2 sibling configs carry + `artifact_status: pending_hf_family_sibling`: verify as pending without network and + require trusted local runtime/compile inputs. Existing sibling configs stay unchanged. - To share processing defaults without changing legacy models, opt new family YAMLs into `extends: bases/family.yaml#DEFAULT`. See the configuration-inheritance section of `mblt_vision/README.md` for ordering and merge rules. Keep legacy `update` shallow and diff --git a/mblt_vision/README.md b/mblt_vision/README.md index 0187f74..da1334e 100644 --- a/mblt_vision/README.md +++ b/mblt_vision/README.md @@ -100,6 +100,41 @@ This section lists the publicly pre-trained models supported by the vision frame ### Image Classification +Additional published ImageNet classifiers are available through the same task +subpackage, top-level lazy exports and `MBLT_Engine`: + +- EfficientViT: `EfficientViT_B3`, `EfficientViT_L1`, `EfficientViT_L2`, `EfficientViT_L3`. +- Non-convolutional LeViT: `LeViT_128`, `LeViT_128S`, `LeViT_192`, `LeViT_256`, `LeViT_384`. +- ViT RPN, relative-position, SRelPos, GAP and CLIP families: discover exact names with + `list_models("image_classification")`. + +These configurations extend `models/bases/imagenet_classifier.yaml` and override +only recorded processing differences. The source is each model's +`models/image_classification//_source/predict_float.input.json` and matching +`distribution.yaml` in mblt-model-ops at `6fdbf471a3bd6ec22035a42c8730fe88a68881e4`. +Input crop/resize and normalization are model-specific: in particular, +`ViT_Large_Patch14_CLIP_224` uses TF normalization despite its CLIP name. +All 24 additions retain the existing `revision: main` convention because their +historical approved artifact HF commits are not recorded; current Hub tips and +upstream source-model commits are not substituted as approval pins. This is a +configuration/CPU processing parity addition, not a new inference accuracy claim. +All 24 Hub repositories expose MXQ only, with no published ONNX file. Their base +sets `file_cfg.onnx_local_artifact_only: true`: MXQ Hub loading remains enabled, +but ONNX requires a trusted local `model_path` or `onnx_path`. Automatic ONNX +loading fails with a specific message, and metadata verification skips only that +framework. Existing configurations without this marker retain their behavior. + +`EfficientViT_B0`, `EfficientViT_B1` and `EfficientViT_B2` are intentional family +sibling configurations using official timm 1.0.14 `r224_in1k` defaults (224 crop, +bicubic resize to `floor(224 / 0.95) = 235`, ImageNet normalization). They are +**pending HF (family sibling)**, not published-model claims. Their +`file_cfg.artifact_status: pending_hf_family_sibling` makes metadata verification +skip both frameworks without network access and allows runtime/compile use only +with trusted local artifacts. Existing sibling configurations are not rewritten. +EfficientViT M0–M5 is a distinct architecture and is not added here. Alternative +ViT CLIP/GAP resolutions require exact upstream squash-mode preprocessing support; +this change does not approximate them with center cropping. + | Model | Input Size
(H,W,C) | AccTop1
(NPU) | AccTop1
(GPU) | FLOPs (B) | params (M) | Source | Note | | --- | --- | --- | --- | --- | --- | --- | --- | | AlexNet | (224,224,3) | 56.084 | 56.556 | 1.43 | 61.10 | [Link](https://docs.pytorch.org/vision/stable/models/generated/torchvision.models.alexnet.html) | | diff --git a/mblt_vision/_hub_validation.py b/mblt_vision/_hub_validation.py index c387ce1..4c1862a 100644 --- a/mblt_vision/_hub_validation.py +++ b/mblt_vision/_hub_validation.py @@ -71,7 +71,21 @@ def check_hub_artifacts( paths.append( (framework, f"{board}/{filename}" if framework == "mxq" else filename) ) - if files.get("local_artifact_only"): + + if files.get("artifact_status") == "pending_hf_family_sibling": + return tuple( + HubArtifactCheck( + repo_id, revision, None, framework, path, "pending_hf_family_sibling" + ) + for framework, path in paths + ) + + def is_local_only(framework: str) -> bool: + return bool(files.get("local_artifact_only")) or ( + framework == "onnx" and bool(files.get("onnx_local_artifact_only")) + ) + + if all(is_local_only(framework) for framework, _ in paths): return tuple( HubArtifactCheck( repo_id, revision, None, framework, path, "local_artifact_only" @@ -91,10 +105,14 @@ def check_hub_artifacts( HubArtifactCheck( repo_id, revision, - info.sha, + None if is_local_only(framework) else info.sha, framework, path, - "resolved" if path in available else "missing", + "local_artifact_only" + if is_local_only(framework) + else "resolved" + if path in available + else "missing", ) for framework, path in paths ) diff --git a/mblt_vision/compile/vision.py b/mblt_vision/compile/vision.py index 50560b2..d57cbf8 100644 --- a/mblt_vision/compile/vision.py +++ b/mblt_vision/compile/vision.py @@ -786,8 +786,10 @@ def resolve_quantization_values( _validate_ratio("topk_ratio", topk_ratio) if topk_ratio is not None else None ) hosted_config: dict[str, Any] | None = None - if (resolved_percentile is None or resolved_topk is None) and not file_cfg.get( - "local_artifact_only" + if ( + (resolved_percentile is None or resolved_topk is None) + and not file_cfg.get("local_artifact_only") + and file_cfg.get("artifact_status") != "pending_hf_family_sibling" ): repo_id = file_cfg.get("repo_id") revision = file_cfg.get("revision", "main") @@ -884,6 +886,11 @@ def _resolve_compile_onnx_path( repo_id = file_cfg.get("repo_id") revision = file_cfg.get("revision") filename = _configured_onnx_filename(file_cfg) + if file_cfg.get("artifact_status") == "pending_hf_family_sibling": + raise RuntimeError( + "This configuration is pending HF (family sibling). Supply a trusted local " + "model_path or onnx_path for compilation; automatic downloads are disabled." + ) if file_cfg.get("local_artifact_only"): repository = repo_id if isinstance(repo_id, str) and repo_id else "this model" raise RuntimeError( @@ -891,6 +898,12 @@ def _resolve_compile_onnx_path( "have a repository-pinned immutable revision and SHA-256 digest. Supply a " "trusted local model_path or onnx_path instead." ) + if file_cfg.get("onnx_local_artifact_only"): + repository = repo_id if isinstance(repo_id, str) and repo_id else "this model" + raise RuntimeError( + f"'{repository}' publishes MXQ artifacts only. ONNX compilation requires a trusted " + "local model_path or onnx_path; automatic ONNX downloads are disabled." + ) if isinstance(repo_id, str) and isinstance(revision, str) and filename: downloaded_path = Path( hf_hub_download( diff --git a/mblt_vision/image_classification/__init__.py b/mblt_vision/image_classification/__init__.py index 3223c59..4bcab7c 100644 --- a/mblt_vision/image_classification/__init__.py +++ b/mblt_vision/image_classification/__init__.py @@ -5,6 +5,14 @@ from .._compat import create_model_class __all__: list[str] = [ + "VGG11", + "VGG11_BN", + "VGG13", + "VGG13_BN", + "VGG16", + "VGG16_BN", + "VGG19", + "VGG19_BN", "AlexNet", "CAFormer_B36", "CAFormer_M36", @@ -21,10 +29,6 @@ "ConvNeXt_Large", "ConvNeXt_Small", "ConvNeXt_Tiny", - "DeiT_Base_Patch16_224", - "DeiT_Base_Patch16_384", - "DeiT_Small_Patch16_224", - "DeiT_Tiny_Patch16_224", "DeiT3_Base_Patch16_224", "DeiT3_Base_Patch16_384", "DeiT3_Large_Patch16_224", @@ -32,6 +36,10 @@ "DeiT3_Medium_Patch16_224", "DeiT3_Small_Patch16_224", "DeiT3_Small_Patch16_384", + "DeiT_Base_Patch16_224", + "DeiT_Base_Patch16_384", + "DeiT_Small_Patch16_224", + "DeiT_Tiny_Patch16_224", "DenseNet121", "DenseNet161", "DenseNet169", @@ -50,11 +58,23 @@ "EfficientNet_V2_L", "EfficientNet_V2_M", "EfficientNet_V2_S", + "EfficientViT_B0", + "EfficientViT_B1", + "EfficientViT_B2", + "EfficientViT_B3", + "EfficientViT_L1", + "EfficientViT_L2", + "EfficientViT_L3", "FlexiViT_Base", "FlexiViT_Large", "FlexiViT_Small", "GoogLeNet", "Inception_V3", + "LeViT_128", + "LeViT_128S", + "LeViT_192", + "LeViT_256", + "LeViT_384", "LeViT_Conv_128", "LeViT_Conv_128S", "LeViT_Conv_192", @@ -89,14 +109,14 @@ "RepViT_M2", "RepViT_M2_3", "RepViT_M3", + "ResNeXt50_32x4d", + "ResNeXt101_32x8d", + "ResNeXt101_64x4d", "ResNet18", "ResNet34", "ResNet50", "ResNet101", "ResNet152", - "ResNeXt50_32x4d", - "ResNeXt101_32x8d", - "ResNeXt101_64x4d", "ShuffleNet_V2_X0_5", "ShuffleNet_V2_X1_0", "ShuffleNet_V2_X1_5", @@ -106,34 +126,41 @@ "Swin_B", "Swin_S", "Swin_T", - "VGG11", - "VGG11_BN", - "VGG13", - "VGG13_BN", - "VGG16", - "VGG16_BN", - "VGG19", - "VGG19_BN", - "VisFormer_Small", - "VisFormer_Tiny", "ViT_B_16", "ViT_B_32", "ViT_Base_Patch8_224", "ViT_Base_Patch16_224", "ViT_Base_Patch16_384", + "ViT_Base_Patch16_CLIP_224", + "ViT_Base_Patch16_RPN_224", "ViT_Base_Patch32_224", "ViT_Base_Patch32_384", + "ViT_Base_Patch32_CLIP_224", + "ViT_Base_Patch32_CLIP_384", + "ViT_Base_Patch32_CLIP_448", + "ViT_Huge_Patch14_CLIP_224", "ViT_L_16", "ViT_L_32", + "ViT_Large_Patch14_CLIP_224", "ViT_Large_Patch16_224", "ViT_Large_Patch16_384", "ViT_Large_Patch32_384", + "ViT_Medium_Patch16_GAP_256", + "ViT_RelPos_Base_Patch16_224", + "ViT_RelPos_Base_Patch32_Plus_RPN_256", + "ViT_RelPos_Medium_Patch16_224", + "ViT_RelPos_Medium_Patch16_RPN_224", + "ViT_RelPos_Small_Patch16_224", + "ViT_SRelPos_Medium_Patch16_224", + "ViT_SRelPos_Small_Patch16_224", "ViT_Small_Patch16_224", "ViT_Small_Patch16_384", "ViT_Small_Patch32_224", "ViT_Small_Patch32_384", "ViT_Tiny_Patch16_224", "ViT_Tiny_Patch16_384", + "VisFormer_Small", + "VisFormer_Tiny", "Wide_ResNet50_2", "Wide_ResNet101_2", "YOLO11lCls", @@ -158,6 +185,33 @@ "YOLOv8xCls", ] +EfficientViT_B0 = create_model_class("EfficientViT_B0", __name__) +EfficientViT_B1 = create_model_class("EfficientViT_B1", __name__) +EfficientViT_B2 = create_model_class("EfficientViT_B2", __name__) +EfficientViT_B3 = create_model_class("EfficientViT_B3", __name__) +EfficientViT_L1 = create_model_class("EfficientViT_L1", __name__) +EfficientViT_L2 = create_model_class("EfficientViT_L2", __name__) +EfficientViT_L3 = create_model_class("EfficientViT_L3", __name__) +LeViT_128 = create_model_class("LeViT_128", __name__) +LeViT_128S = create_model_class("LeViT_128S", __name__) +LeViT_192 = create_model_class("LeViT_192", __name__) +LeViT_256 = create_model_class("LeViT_256", __name__) +LeViT_384 = create_model_class("LeViT_384", __name__) +ViT_RelPos_Medium_Patch16_RPN_224 = create_model_class("ViT_RelPos_Medium_Patch16_RPN_224", __name__) +ViT_RelPos_Base_Patch32_Plus_RPN_256 = create_model_class("ViT_RelPos_Base_Patch32_Plus_RPN_256", __name__) +ViT_Base_Patch16_RPN_224 = create_model_class("ViT_Base_Patch16_RPN_224", __name__) +ViT_Base_Patch16_CLIP_224 = create_model_class("ViT_Base_Patch16_CLIP_224", __name__) +ViT_Base_Patch32_CLIP_448 = create_model_class("ViT_Base_Patch32_CLIP_448", __name__) +ViT_Medium_Patch16_GAP_256 = create_model_class("ViT_Medium_Patch16_GAP_256", __name__) +ViT_Large_Patch14_CLIP_224 = create_model_class("ViT_Large_Patch14_CLIP_224", __name__) +ViT_Base_Patch32_CLIP_224 = create_model_class("ViT_Base_Patch32_CLIP_224", __name__) +ViT_RelPos_Small_Patch16_224 = create_model_class("ViT_RelPos_Small_Patch16_224", __name__) +ViT_Base_Patch32_CLIP_384 = create_model_class("ViT_Base_Patch32_CLIP_384", __name__) +ViT_SRelPos_Medium_Patch16_224 = create_model_class("ViT_SRelPos_Medium_Patch16_224", __name__) +ViT_Huge_Patch14_CLIP_224 = create_model_class("ViT_Huge_Patch14_CLIP_224", __name__) +ViT_RelPos_Base_Patch16_224 = create_model_class("ViT_RelPos_Base_Patch16_224", __name__) +ViT_RelPos_Medium_Patch16_224 = create_model_class("ViT_RelPos_Medium_Patch16_224", __name__) +ViT_SRelPos_Small_Patch16_224 = create_model_class("ViT_SRelPos_Small_Patch16_224", __name__) AlexNet = create_model_class("AlexNet", __name__) CAFormer_B36 = create_model_class("CAFormer_B36", __name__) CAFormer_M36 = create_model_class("CAFormer_M36", __name__) diff --git a/mblt_vision/models/EfficientViT_B0.yaml b/mblt_vision/models/EfficientViT_B0.yaml new file mode 100644 index 0000000..406152d --- /dev/null +++ b/mblt_vision/models/EfficientViT_B0.yaml @@ -0,0 +1,13 @@ +# Official timm 1.0.14 efficientvit_b0.r224_in1k pretrained_cfg: +# 224 crop, bicubic, crop_pct 0.95 -> floor(224 / 0.95) = 235, ImageNet torch stats. +# Family sibling configuration; no published artifact or approval is claimed. +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/EfficientViT_B0 + filename: efficientvit_b0.mxq + revision: main + artifact_status: pending_hf_family_sibling + pre_cfg: + Resize: + size: 235 diff --git a/mblt_vision/models/EfficientViT_B1.yaml b/mblt_vision/models/EfficientViT_B1.yaml new file mode 100644 index 0000000..9bda945 --- /dev/null +++ b/mblt_vision/models/EfficientViT_B1.yaml @@ -0,0 +1,13 @@ +# Official timm 1.0.14 efficientvit_b1.r224_in1k pretrained_cfg: +# 224 crop, bicubic, crop_pct 0.95 -> floor(224 / 0.95) = 235, ImageNet torch stats. +# Family sibling configuration; no published artifact or approval is claimed. +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/EfficientViT_B1 + filename: efficientvit_b1.mxq + revision: main + artifact_status: pending_hf_family_sibling + pre_cfg: + Resize: + size: 235 diff --git a/mblt_vision/models/EfficientViT_B2.yaml b/mblt_vision/models/EfficientViT_B2.yaml new file mode 100644 index 0000000..879995c --- /dev/null +++ b/mblt_vision/models/EfficientViT_B2.yaml @@ -0,0 +1,13 @@ +# Official timm 1.0.14 efficientvit_b2.r224_in1k pretrained_cfg: +# 224 crop, bicubic, crop_pct 0.95 -> floor(224 / 0.95) = 235, ImageNet torch stats. +# Family sibling configuration; no published artifact or approval is claimed. +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/EfficientViT_B2 + filename: efficientvit_b2.mxq + revision: main + artifact_status: pending_hf_family_sibling + pre_cfg: + Resize: + size: 235 diff --git a/mblt_vision/models/EfficientViT_B3.yaml b/mblt_vision/models/EfficientViT_B3.yaml new file mode 100644 index 0000000..7f90dff --- /dev/null +++ b/mblt_vision/models/EfficientViT_B3.yaml @@ -0,0 +1,12 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/EfficientViT_B3/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/EfficientViT_B3 + filename: efficientvit_b3.mxq + revision: main + pre_cfg: + Resize: + interpolation: bicubic + size: 235 diff --git a/mblt_vision/models/EfficientViT_L1.yaml b/mblt_vision/models/EfficientViT_L1.yaml new file mode 100644 index 0000000..6dc9ab3 --- /dev/null +++ b/mblt_vision/models/EfficientViT_L1.yaml @@ -0,0 +1,8 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/EfficientViT_L1/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/EfficientViT_L1 + filename: efficientvit_l1.mxq + revision: main diff --git a/mblt_vision/models/EfficientViT_L2.yaml b/mblt_vision/models/EfficientViT_L2.yaml new file mode 100644 index 0000000..f09469d --- /dev/null +++ b/mblt_vision/models/EfficientViT_L2.yaml @@ -0,0 +1,8 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/EfficientViT_L2/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/EfficientViT_L2 + filename: efficientvit_l2.mxq + revision: main diff --git a/mblt_vision/models/EfficientViT_L3.yaml b/mblt_vision/models/EfficientViT_L3.yaml new file mode 100644 index 0000000..20f5c75 --- /dev/null +++ b/mblt_vision/models/EfficientViT_L3.yaml @@ -0,0 +1,8 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/EfficientViT_L3/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/EfficientViT_L3 + filename: efficientvit_l3.mxq + revision: main diff --git a/mblt_vision/models/LeViT_128.yaml b/mblt_vision/models/LeViT_128.yaml new file mode 100644 index 0000000..dd200ef --- /dev/null +++ b/mblt_vision/models/LeViT_128.yaml @@ -0,0 +1,12 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/LeViT_128/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/LeViT_128 + filename: levit_128.mxq + revision: main + pre_cfg: + Resize: + interpolation: bicubic + size: 248 diff --git a/mblt_vision/models/LeViT_128S.yaml b/mblt_vision/models/LeViT_128S.yaml new file mode 100644 index 0000000..5b347aa --- /dev/null +++ b/mblt_vision/models/LeViT_128S.yaml @@ -0,0 +1,12 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/LeViT_128S/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/LeViT_128S + filename: levit_128s.mxq + revision: main + pre_cfg: + Resize: + interpolation: bicubic + size: 248 diff --git a/mblt_vision/models/LeViT_192.yaml b/mblt_vision/models/LeViT_192.yaml new file mode 100644 index 0000000..536a983 --- /dev/null +++ b/mblt_vision/models/LeViT_192.yaml @@ -0,0 +1,12 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/LeViT_192/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/LeViT_192 + filename: levit_192.mxq + revision: main + pre_cfg: + Resize: + interpolation: bicubic + size: 248 diff --git a/mblt_vision/models/LeViT_256.yaml b/mblt_vision/models/LeViT_256.yaml new file mode 100644 index 0000000..b7dfb0a --- /dev/null +++ b/mblt_vision/models/LeViT_256.yaml @@ -0,0 +1,12 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/LeViT_256/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/LeViT_256 + filename: levit_256.mxq + revision: main + pre_cfg: + Resize: + interpolation: bicubic + size: 248 diff --git a/mblt_vision/models/LeViT_384.yaml b/mblt_vision/models/LeViT_384.yaml new file mode 100644 index 0000000..e0502d2 --- /dev/null +++ b/mblt_vision/models/LeViT_384.yaml @@ -0,0 +1,12 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/LeViT_384/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/LeViT_384 + filename: levit_384.mxq + revision: main + pre_cfg: + Resize: + interpolation: bicubic + size: 248 diff --git a/mblt_vision/models/ViT_Base_Patch16_CLIP_224.yaml b/mblt_vision/models/ViT_Base_Patch16_CLIP_224.yaml new file mode 100644 index 0000000..7f31c24 --- /dev/null +++ b/mblt_vision/models/ViT_Base_Patch16_CLIP_224.yaml @@ -0,0 +1,14 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_Base_Patch16_CLIP_224/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_Base_Patch16_CLIP_224 + filename: vit_base_patch16_clip_224.mxq + revision: main + pre_cfg: + Normalize: + style: openai + Resize: + interpolation: bicubic + size: 235 diff --git a/mblt_vision/models/ViT_Base_Patch16_RPN_224.yaml b/mblt_vision/models/ViT_Base_Patch16_RPN_224.yaml new file mode 100644 index 0000000..013fe25 --- /dev/null +++ b/mblt_vision/models/ViT_Base_Patch16_RPN_224.yaml @@ -0,0 +1,14 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_Base_Patch16_RPN_224/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_Base_Patch16_RPN_224 + filename: vit_base_patch16_rpn_224.mxq + revision: main + pre_cfg: + Normalize: + style: tf + Resize: + interpolation: bicubic + size: 248 diff --git a/mblt_vision/models/ViT_Base_Patch32_CLIP_224.yaml b/mblt_vision/models/ViT_Base_Patch32_CLIP_224.yaml new file mode 100644 index 0000000..5c6464a --- /dev/null +++ b/mblt_vision/models/ViT_Base_Patch32_CLIP_224.yaml @@ -0,0 +1,14 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_Base_Patch32_CLIP_224/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_Base_Patch32_CLIP_224 + filename: vit_base_patch32_clip_224.mxq + revision: main + pre_cfg: + Normalize: + style: openai + Resize: + interpolation: bicubic + size: 248 diff --git a/mblt_vision/models/ViT_Base_Patch32_CLIP_384.yaml b/mblt_vision/models/ViT_Base_Patch32_CLIP_384.yaml new file mode 100644 index 0000000..8086fc2 --- /dev/null +++ b/mblt_vision/models/ViT_Base_Patch32_CLIP_384.yaml @@ -0,0 +1,18 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_Base_Patch32_CLIP_384/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_Base_Patch32_CLIP_384 + filename: vit_base_patch32_clip_384.mxq + revision: main + pre_cfg: + CenterCrop: + size: + - 384 + - 384 + Normalize: + style: openai + Resize: + interpolation: bicubic + size: 384 diff --git a/mblt_vision/models/ViT_Base_Patch32_CLIP_448.yaml b/mblt_vision/models/ViT_Base_Patch32_CLIP_448.yaml new file mode 100644 index 0000000..c29252b --- /dev/null +++ b/mblt_vision/models/ViT_Base_Patch32_CLIP_448.yaml @@ -0,0 +1,18 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_Base_Patch32_CLIP_448/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_Base_Patch32_CLIP_448 + filename: vit_base_patch32_clip_448.mxq + revision: main + pre_cfg: + CenterCrop: + size: + - 448 + - 448 + Normalize: + style: openai + Resize: + interpolation: bicubic + size: 448 diff --git a/mblt_vision/models/ViT_Huge_Patch14_CLIP_224.yaml b/mblt_vision/models/ViT_Huge_Patch14_CLIP_224.yaml new file mode 100644 index 0000000..8fbe652 --- /dev/null +++ b/mblt_vision/models/ViT_Huge_Patch14_CLIP_224.yaml @@ -0,0 +1,11 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_Huge_Patch14_CLIP_224/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_Huge_Patch14_CLIP_224 + filename: vit_huge_patch14_clip_224.mxq + revision: main + pre_cfg: + Normalize: + style: openai diff --git a/mblt_vision/models/ViT_Large_Patch14_CLIP_224.yaml b/mblt_vision/models/ViT_Large_Patch14_CLIP_224.yaml new file mode 100644 index 0000000..d30d8c2 --- /dev/null +++ b/mblt_vision/models/ViT_Large_Patch14_CLIP_224.yaml @@ -0,0 +1,11 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_Large_Patch14_CLIP_224/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_Large_Patch14_CLIP_224 + filename: vit_large_patch14_clip_224.mxq + revision: main + pre_cfg: + Normalize: + style: tf diff --git a/mblt_vision/models/ViT_Medium_Patch16_GAP_256.yaml b/mblt_vision/models/ViT_Medium_Patch16_GAP_256.yaml new file mode 100644 index 0000000..d26adc3 --- /dev/null +++ b/mblt_vision/models/ViT_Medium_Patch16_GAP_256.yaml @@ -0,0 +1,18 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_Medium_Patch16_GAP_256/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_Medium_Patch16_GAP_256 + filename: vit_medium_patch16_gap_256.mxq + revision: main + pre_cfg: + CenterCrop: + size: + - 256 + - 256 + Normalize: + style: tf + Resize: + interpolation: bicubic + size: 269 diff --git a/mblt_vision/models/ViT_RelPos_Base_Patch16_224.yaml b/mblt_vision/models/ViT_RelPos_Base_Patch16_224.yaml new file mode 100644 index 0000000..d5d0831 --- /dev/null +++ b/mblt_vision/models/ViT_RelPos_Base_Patch16_224.yaml @@ -0,0 +1,14 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_RelPos_Base_Patch16_224/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_RelPos_Base_Patch16_224 + filename: vit_relpos_base_patch16_224.mxq + revision: main + pre_cfg: + Normalize: + style: tf + Resize: + interpolation: bicubic + size: 248 diff --git a/mblt_vision/models/ViT_RelPos_Base_Patch32_Plus_RPN_256.yaml b/mblt_vision/models/ViT_RelPos_Base_Patch32_Plus_RPN_256.yaml new file mode 100644 index 0000000..2549da1 --- /dev/null +++ b/mblt_vision/models/ViT_RelPos_Base_Patch32_Plus_RPN_256.yaml @@ -0,0 +1,18 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_RelPos_Base_Patch32_Plus_RPN_256/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_RelPos_Base_Patch32_Plus_RPN_256 + filename: vit_relpos_base_patch32_plus_rpn_256.mxq + revision: main + pre_cfg: + CenterCrop: + size: + - 256 + - 256 + Normalize: + style: tf + Resize: + interpolation: bicubic + size: 284 diff --git a/mblt_vision/models/ViT_RelPos_Medium_Patch16_224.yaml b/mblt_vision/models/ViT_RelPos_Medium_Patch16_224.yaml new file mode 100644 index 0000000..033ebef --- /dev/null +++ b/mblt_vision/models/ViT_RelPos_Medium_Patch16_224.yaml @@ -0,0 +1,14 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_RelPos_Medium_Patch16_224/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_RelPos_Medium_Patch16_224 + filename: vit_relpos_medium_patch16_224.mxq + revision: main + pre_cfg: + Normalize: + style: tf + Resize: + interpolation: bicubic + size: 248 diff --git a/mblt_vision/models/ViT_RelPos_Medium_Patch16_RPN_224.yaml b/mblt_vision/models/ViT_RelPos_Medium_Patch16_RPN_224.yaml new file mode 100644 index 0000000..4674b70 --- /dev/null +++ b/mblt_vision/models/ViT_RelPos_Medium_Patch16_RPN_224.yaml @@ -0,0 +1,14 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_RelPos_Medium_Patch16_RPN_224/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_RelPos_Medium_Patch16_RPN_224 + filename: vit_relpos_medium_patch16_rpn_224.mxq + revision: main + pre_cfg: + Normalize: + style: tf + Resize: + interpolation: bicubic + size: 248 diff --git a/mblt_vision/models/ViT_RelPos_Small_Patch16_224.yaml b/mblt_vision/models/ViT_RelPos_Small_Patch16_224.yaml new file mode 100644 index 0000000..ba1f738 --- /dev/null +++ b/mblt_vision/models/ViT_RelPos_Small_Patch16_224.yaml @@ -0,0 +1,14 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_RelPos_Small_Patch16_224/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_RelPos_Small_Patch16_224 + filename: vit_relpos_small_patch16_224.mxq + revision: main + pre_cfg: + Normalize: + style: tf + Resize: + interpolation: bicubic + size: 248 diff --git a/mblt_vision/models/ViT_SRelPos_Medium_Patch16_224.yaml b/mblt_vision/models/ViT_SRelPos_Medium_Patch16_224.yaml new file mode 100644 index 0000000..74e32bf --- /dev/null +++ b/mblt_vision/models/ViT_SRelPos_Medium_Patch16_224.yaml @@ -0,0 +1,14 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_SRelPos_Medium_Patch16_224/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_SRelPos_Medium_Patch16_224 + filename: vit_srelpos_medium_patch16_224.mxq + revision: main + pre_cfg: + Normalize: + style: tf + Resize: + interpolation: bicubic + size: 248 diff --git a/mblt_vision/models/ViT_SRelPos_Small_Patch16_224.yaml b/mblt_vision/models/ViT_SRelPos_Small_Patch16_224.yaml new file mode 100644 index 0000000..809b7c0 --- /dev/null +++ b/mblt_vision/models/ViT_SRelPos_Small_Patch16_224.yaml @@ -0,0 +1,14 @@ +# Approval HF commit: not recorded. Do not substitute upstream source-model commits. +# Preprocessing: mblt-model-ops models/image_classification/ViT_SRelPos_Small_Patch16_224/_source/predict_float.input.json +DEFAULT: + extends: bases/imagenet_classifier.yaml + file_cfg: + repo_id: mobilint/ViT_SRelPos_Small_Patch16_224 + filename: vit_srelpos_small_patch16_224.mxq + revision: main + pre_cfg: + Normalize: + style: tf + Resize: + interpolation: bicubic + size: 248 diff --git a/mblt_vision/models/bases/imagenet_classifier.yaml b/mblt_vision/models/bases/imagenet_classifier.yaml new file mode 100644 index 0000000..ab0129e --- /dev/null +++ b/mblt_vision/models/bases/imagenet_classifier.yaml @@ -0,0 +1,21 @@ +# Shared ordered ImageNet preprocessing; model-specific values follow recorded ops inputs. +DEFAULT: + file_cfg: + onnx_local_artifact_only: true + pre_cfg: + Reader: + style: pil + Resize: + size: 224 + interpolation: bicubic + CenterCrop: + size: + - 224 + - 224 + SetOrder: + shape: HWC + Normalize: + style: torch + post_cfg: + task: image_classification + dataset: imagenet diff --git a/mblt_vision/wrapper.py b/mblt_vision/wrapper.py index d451284..9f99a43 100644 --- a/mblt_vision/wrapper.py +++ b/mblt_vision/wrapper.py @@ -628,6 +628,8 @@ def _mxq_backend_kwargs(self) -> dict[str, Any]: "revision", "onnx_filename", "onnx_path", + "onnx_local_artifact_only", + "artifact_status", } return { key: value @@ -722,6 +724,11 @@ def file_config_cleansing(self) -> None: revision = self.file_cfg.pop("revision", None) if not repo_id or not revision: return + if self.file_cfg.get("artifact_status") == "pending_hf_family_sibling": + raise RuntimeError( + f"'{repo_id}' is pending HF (family sibling). Supply a trusted local " + "model_path, mxq_path, or onnx_path; automatic downloads are disabled." + ) if local_artifact_only: raise RuntimeError( f"Remote artifacts for '{repo_id}' are disabled because this model does not " @@ -739,6 +746,11 @@ def file_config_cleansing(self) -> None: ) if onnx_filename and framework == "onnx" and not self.file_cfg.get("onnx_path"): + if self.file_cfg.get("onnx_local_artifact_only"): + raise RuntimeError( + f"'{repo_id}' publishes MXQ artifacts only. ONNX requires a trusted local " + "model_path or onnx_path; automatic ONNX downloads are disabled." + ) self.file_cfg["onnx_path"] = self._download_hub_artifact( repo_id=repo_id, filename=onnx_filename, diff --git a/tests/test_config_inheritance.py b/tests/test_config_inheritance.py index a36907b..7b561d4 100644 --- a/tests/test_config_inheritance.py +++ b/tests/test_config_inheritance.py @@ -54,7 +54,9 @@ def legacy_config(document: dict, variant: str) -> dict: CONFIG_VARIANTS = [ (path, variant) for path in sorted(MODEL_CONFIG_DIR.glob("*.yaml")) - for variant in yaml.safe_load(path.read_text(encoding="utf-8")) + for variant, definition in yaml.safe_load(path.read_text(encoding="utf-8")).items() + # Only pre-existing alias/update configurations have a legacy counterpart. + if not isinstance(definition, dict) or "extends" not in definition ] diff --git a/tests/test_onnx_local_artifact.py b/tests/test_onnx_local_artifact.py new file mode 100644 index 0000000..a26a5d1 --- /dev/null +++ b/tests/test_onnx_local_artifact.py @@ -0,0 +1,111 @@ +"""Positive and negative CPU tests for framework-specific local-only artifacts.""" + +from __future__ import annotations + +from unittest.mock import Mock + +import pytest + +from mblt_vision._hub_validation import check_hub_artifacts +from mblt_vision.compile import vision as compile_module +from mblt_vision.wrapper import MBLT_Engine, resolve_model_config + + +def engine_config(framework): + engine = object.__new__(MBLT_Engine) + engine.framework = framework + engine.file_cfg = resolve_model_config("EfficientViT_B3")["file_cfg"] + engine._download_hub_artifact = Mock(return_value="/downloaded/artifact") + return engine + + +def test_marked_remote_onnx_fails_without_download() -> None: + engine = engine_config("onnx") + with pytest.raises(RuntimeError, match="trusted local"): + engine.file_config_cleansing() + engine._download_hub_artifact.assert_not_called() + + +def test_marked_local_onnx_still_works(tmp_path) -> None: + engine = engine_config("onnx") + source = tmp_path / "trusted.onnx" + source.write_bytes(b"local fixture; runtime not constructed") + engine.file_cfg["onnx_path"] = str(source) + engine.file_config_cleansing() + assert engine.file_cfg["onnx_path"] == str(source) + engine._download_hub_artifact.assert_not_called() + + +def test_marked_mxq_keeps_hub_download() -> None: + engine = engine_config("mxq") + engine.file_config_cleansing() + engine._download_hub_artifact.assert_called_once_with( + repo_id="mobilint/EfficientViT_B3", + filename="efficientvit_b3.mxq", + revision="main", + subfolders=["aries-rb"], + ) + kwargs = engine._mxq_backend_kwargs() + assert "onnx_local_artifact_only" not in kwargs + assert kwargs["mxq_path"] == "/downloaded/artifact" + + +def test_unmarked_remote_onnx_keeps_hub_download() -> None: + engine = engine_config("onnx") + engine.file_cfg.pop("onnx_local_artifact_only") + engine.file_config_cleansing() + engine._download_hub_artifact.assert_called_once_with( + repo_id="mobilint/EfficientViT_B3", + filename="efficientvit_b3.onnx", + revision="main", + ) + + +def test_verifier_skips_only_marked_framework() -> None: + api = Mock() + api.model_info.return_value.sha = "observed" + api.list_repo_files.return_value = ["aries-rb/efficientvit_b3.mxq"] + checks = check_hub_artifacts(resolve_model_config("EfficientViT_B3"), api=api) + assert [item.status for item in checks] == ["resolved", "local_artifact_only"] + assert checks[1].commit is None + assert api.model_info.call_count == 1 + + +def test_onnx_only_verification_stays_offline() -> None: + api = Mock() + checks = check_hub_artifacts( + resolve_model_config("EfficientViT_B3"), frameworks=("onnx",), api=api + ) + assert checks[0].status == "local_artifact_only" + assert not api.mock_calls + + +def test_compile_resolver_requires_local_onnx(monkeypatch, tmp_path) -> None: + download = Mock() + monkeypatch.setattr(compile_module, "hf_hub_download", download) + config = resolve_model_config("EfficientViT_B3")["file_cfg"] + with pytest.raises(RuntimeError, match="trusted local"): + compile_module._resolve_compile_onnx_path(config, None) + download.assert_not_called() + local = tmp_path / "trusted.onnx" + local.write_bytes(b"fixture") + assert compile_module._resolve_compile_onnx_path(config, local) == local.resolve() + assert ( + compile_module._resolve_compile_onnx_path( + {**config, "onnx_path": str(local)}, None + ) + == local.resolve() + ) + download.assert_not_called() + + +def test_compile_unmarked_config_retains_download(monkeypatch, tmp_path) -> None: + local = tmp_path / "downloaded.onnx" + local.write_bytes(b"fixture") + download = Mock(return_value=str(local)) + monkeypatch.setattr(compile_module, "hf_hub_download", download) + monkeypatch.setattr(compile_module, "get_mobilint_cache_dir", lambda: str(tmp_path)) + config = resolve_model_config("EfficientViT_B3")["file_cfg"] + config.pop("onnx_local_artifact_only") + assert compile_module._resolve_compile_onnx_path(config, None) == local.resolve() + download.assert_called_once() diff --git a/tests/test_pending_family_siblings.py b/tests/test_pending_family_siblings.py new file mode 100644 index 0000000..b33dec5 --- /dev/null +++ b/tests/test_pending_family_siblings.py @@ -0,0 +1,70 @@ +"""Intentional family-sibling configs are discoverable but never auto-download.""" + +from __future__ import annotations + +from unittest.mock import Mock + +import pytest + +import mblt_vision +from mblt_vision._hub_validation import check_hub_artifacts +from mblt_vision.compile import vision as compile_module +from mblt_vision.wrapper import MBLT_Engine, resolve_model_config + + +@pytest.mark.parametrize( + "name", ["EfficientViT_B0", "EfficientViT_B1", "EfficientViT_B2"] +) +def test_official_sibling_pipeline_and_pending_metadata(name) -> None: + config = resolve_model_config(name) + reference = resolve_model_config("EfficientViT_B3") + assert config["pre_cfg"] == reference["pre_cfg"] + assert config["pre_cfg"]["Resize"] == {"size": 235, "interpolation": "bicubic"} + assert config["file_cfg"]["artifact_status"] == "pending_hf_family_sibling" + assert ( + name in mblt_vision.list_models("image_classification")["image_classification"] + ) + assert getattr(mblt_vision, name) is getattr(mblt_vision.image_classification, name) + api = Mock() + checks = check_hub_artifacts(config, api=api) + assert all(item.status == "pending_hf_family_sibling" for item in checks) + assert not api.mock_calls + + +@pytest.mark.parametrize("framework", ["mxq", "onnx"]) +def test_pending_engine_never_downloads(framework) -> None: + engine = object.__new__(MBLT_Engine) + engine.framework = framework + engine.file_cfg = resolve_model_config("EfficientViT_B0")["file_cfg"] + engine._download_hub_artifact = Mock() + with pytest.raises(RuntimeError, match="pending HF"): + engine.file_config_cleansing() + engine._download_hub_artifact.assert_not_called() + + +@pytest.mark.parametrize("framework", ["mxq", "onnx"]) +def test_pending_engine_accepts_trusted_local_artifact(framework, tmp_path) -> None: + engine = object.__new__(MBLT_Engine) + engine.framework = framework + engine.file_cfg = resolve_model_config("EfficientViT_B0")["file_cfg"] + source = tmp_path / f"trusted.{framework}" + source.write_bytes(b"fixture") + engine.file_cfg[f"{framework}_path"] = str(source) + engine._download_hub_artifact = Mock() + engine.file_config_cleansing() + assert "artifact_status" not in engine._mxq_backend_kwargs() + engine._download_hub_artifact.assert_not_called() + + +def test_pending_compile_never_downloads_and_accepts_local( + monkeypatch, tmp_path +) -> None: + config = resolve_model_config("EfficientViT_B0")["file_cfg"] + download = Mock() + monkeypatch.setattr(compile_module, "hf_hub_download", download) + with pytest.raises(RuntimeError, match="pending HF"): + compile_module._resolve_compile_onnx_path(config, None) + local = tmp_path / "trusted.onnx" + local.write_bytes(b"fixture") + assert compile_module._resolve_compile_onnx_path(config, local) == local.resolve() + download.assert_not_called() diff --git a/tests/test_published_classifiers.py b/tests/test_published_classifiers.py new file mode 100644 index 0000000..c1439c3 --- /dev/null +++ b/tests/test_published_classifiers.py @@ -0,0 +1,113 @@ +"""CPU processing and discovery contracts for additional published classifiers.""" + +from __future__ import annotations + +import numpy as np +import pytest +from PIL import Image + +import mblt_vision +from mblt_vision.utils.preprocess import build_preprocess +from mblt_vision.wrapper import resolve_model_config + +# Model-specific values from the recorded ops predict_float inputs and distribution +# contracts at 6fdbf471a3bd6ec22035a42c8730fe88a68881e4. Keep the surprising TF +# normalization of ViT_Large_Patch14_CLIP_224: names do not define transforms. +SPECS = { + "EfficientViT_B3": (235, 224, "torch"), + "EfficientViT_L1": (224, 224, "torch"), + "EfficientViT_L2": (224, 224, "torch"), + "EfficientViT_L3": (224, 224, "torch"), + "LeViT_128": (248, 224, "torch"), + "LeViT_128S": (248, 224, "torch"), + "LeViT_192": (248, 224, "torch"), + "LeViT_256": (248, 224, "torch"), + "LeViT_384": (248, 224, "torch"), + "ViT_RelPos_Medium_Patch16_RPN_224": (248, 224, "tf"), + "ViT_RelPos_Base_Patch32_Plus_RPN_256": (284, 256, "tf"), + "ViT_Base_Patch16_RPN_224": (248, 224, "tf"), + "ViT_Base_Patch16_CLIP_224": (235, 224, "openai"), + "ViT_Base_Patch32_CLIP_448": (448, 448, "openai"), + "ViT_Medium_Patch16_GAP_256": (269, 256, "tf"), + "ViT_Large_Patch14_CLIP_224": (224, 224, "tf"), + "ViT_Base_Patch32_CLIP_224": (248, 224, "openai"), + "ViT_RelPos_Small_Patch16_224": (248, 224, "tf"), + "ViT_Base_Patch32_CLIP_384": (384, 384, "openai"), + "ViT_SRelPos_Medium_Patch16_224": (248, 224, "tf"), + "ViT_Huge_Patch14_CLIP_224": (224, 224, "openai"), + "ViT_RelPos_Base_Patch16_224": (248, 224, "tf"), + "ViT_RelPos_Medium_Patch16_224": (248, 224, "tf"), + "ViT_SRelPos_Small_Patch16_224": (248, 224, "tf"), +} +STATS = { + "torch": ([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), + "tf": ([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]), + "openai": ( + [0.48145466, 0.4578275, 0.40821073], + [0.26862954, 0.26130258, 0.27577711], + ), +} + + +@pytest.mark.parametrize("model", SPECS) +def test_configs_match_recorded_ops_inputs_and_discover(model: str) -> None: + resize, crop, style = SPECS[model] + config = resolve_model_config(model) + assert config["pre_cfg"] == { + "Reader": {"style": "pil"}, + "Resize": {"size": resize, "interpolation": "bicubic"}, + "CenterCrop": {"size": [crop, crop]}, + "SetOrder": {"shape": "HWC"}, + "Normalize": {"style": style}, + } + assert list(config["pre_cfg"]) == [ + "Reader", + "Resize", + "CenterCrop", + "SetOrder", + "Normalize", + ] + assert config["post_cfg"] == {"task": "image_classification", "dataset": "imagenet"} + assert config["file_cfg"]["repo_id"] == f"mobilint/{model}" + assert config["file_cfg"]["revision"] == "main" + assert config["file_cfg"]["onnx_local_artifact_only"] is True + assert ( + model in mblt_vision.list_models("image_classification")["image_classification"] + ) + assert getattr(mblt_vision, model) is getattr( + mblt_vision.image_classification, model + ) + + +@pytest.mark.parametrize("model", SPECS) +@pytest.mark.parametrize("shape", [(277, 391), (399, 271), (257, 257)]) +def test_cpu_numeric_parity_with_ops_adapter_math( + model: str, shape: tuple[int, int], tmp_path +) -> None: + resize, crop, style = SPECS[model] + image = Image.fromarray( + np.random.default_rng(0).integers(0, 256, (*shape, 3), dtype=np.uint8) + ) + source = tmp_path / "input.png" + image.save(source) + # Independent equivalent of ops customer_preprocessing_imagenet_torch.py; + # TF/OpenAI adapters use the same resize/crop with their own recorded stats. + width, height = image.size + target = ( + (resize, int(resize * height / width)) + if width <= height + else (int(resize * width / height), resize) + ) + image = image.resize(target, Image.Resampling.BICUBIC) + left, top = round((image.width - crop) / 2), round((image.height - crop) / 2) + reference = ( + np.asarray(image.crop((left, top, left + crop, top + crop)), dtype=np.float32) + / 255 + ) + mean, std = STATS[style] + reference = (reference - np.asarray(mean, dtype=np.float32)) / np.asarray( + std, dtype=np.float32 + ) + actual = build_preprocess(resolve_model_config(model)["pre_cfg"])(str(source)) + assert actual.dtype == np.float32 + np.testing.assert_allclose(actual, reference, rtol=0, atol=5e-7)