| from __future__ import annotations |
|
|
| import argparse |
| import json |
| from pathlib import Path |
| import re |
| import subprocess |
| import sys |
| import zipfile |
|
|
|
|
| PROJECT_ROOT = Path(__file__).resolve().parents[1] |
| if str(PROJECT_ROOT) not in sys.path: |
| sys.path.insert(0, str(PROJECT_ROOT)) |
|
|
|
|
| REQUIRED_KEYS = { |
| "cls_token", |
| "pos_embed", |
| "mask_token", |
| "decoder_pos_embed", |
| "patch_embed.proj.weight", |
| "decoder_embed.weight", |
| "decoder_norm.weight", |
| "decoder_pred.weight", |
| } |
|
|
| PARAMETER_KEY_PATTERN = re.compile( |
| r"(?:decoder_blocks|blocks)\.\d+\.[A-Za-z0-9_.]*?(?:weight|bias|w1|b1|w2|b2)" |
| r"|(?:patch_embed\.proj|decoder_embed|decoder_norm|decoder_pred)\.(?:weight|bias)" |
| r"|(?:cls_token|pos_embed|mask_token|decoder_pos_embed)" |
| ) |
|
|
|
|
| def inspect_archive(path: Path) -> dict: |
| if not zipfile.is_zipfile(path): |
| raise ValueError(f"{path} is not a zip-based PyTorch checkpoint.") |
| with zipfile.ZipFile(path) as archive: |
| names = archive.namelist() |
| data_pickle = next((name for name in names if name.endswith("/data.pkl")), None) |
| if data_pickle is None: |
| raise ValueError("Checkpoint archive does not contain data.pkl.") |
| payload = archive.read(data_pickle) |
|
|
| strings = subprocess.run( |
| ["strings"], input=payload, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True |
| ).stdout.decode("utf-8", errors="ignore").splitlines() |
| parameter_keys = { |
| match.group(0) |
| for line in strings |
| for match in [PARAMETER_KEY_PATTERN.search(line)] |
| if match |
| } |
| encoder_indices = sorted( |
| {int(match.group(1)) for key in parameter_keys if (match := re.match(r"blocks\.(\d+)\.", key))} |
| ) |
| decoder_indices = sorted( |
| {int(match.group(1)) for key in parameter_keys if (match := re.match(r"decoder_blocks\.(\d+)\.", key))} |
| ) |
| missing = sorted(REQUIRED_KEYS - parameter_keys) |
| return { |
| "path": str(path), |
| "archive_entries": len(names), |
| "parameter_key_count": len(parameter_keys), |
| "encoder_blocks": len(encoder_indices), |
| "decoder_blocks": len(decoder_indices), |
| "missing_required_keys": missing, |
| "status": "compatible_structure" if not missing and len(encoder_indices) == 12 and len(decoder_indices) == 6 else "mismatch", |
| } |
|
|
|
|
| def inspect_with_torch(path: Path) -> dict: |
| try: |
| import torch |
| from model.w_mae import w_mae_base |
| except (ImportError, OSError) as error: |
| return {"status": "unavailable", "reason": str(error)} |
|
|
| try: |
| checkpoint = torch.load(path, map_location="cpu") |
| except Exception as error: |
| return {"status": "unavailable", "reason": f"checkpoint deserialization failed: {error}"} |
| state_dict = checkpoint.get("model", checkpoint) |
| model = w_mae_base() |
| model_state = model.state_dict() |
| missing = sorted(set(model_state) - set(state_dict)) |
| unexpected = sorted(set(state_dict) - set(model_state)) |
| shape_mismatches = { |
| key: {"model": list(model_state[key].shape), "checkpoint": list(state_dict[key].shape)} |
| for key in model_state.keys() & state_dict.keys() |
| if tuple(model_state[key].shape) != tuple(state_dict[key].shape) |
| } |
| return { |
| "status": "compatible" if not missing and not unexpected and not shape_mismatches else "mismatch", |
| "missing_keys": missing, |
| "unexpected_keys": unexpected, |
| "shape_mismatches": shape_mismatches, |
| } |
|
|
|
|
| def main() -> None: |
| parser = argparse.ArgumentParser(description="Inspect W-MAE checkpoint structure and model compatibility.") |
| parser.add_argument("checkpoint", type=Path) |
| parser.add_argument("--torch-check", action="store_true") |
| args = parser.parse_args() |
|
|
| report = {"archive": inspect_archive(args.checkpoint)} |
| if args.torch_check: |
| report["torch"] = inspect_with_torch(args.checkpoint) |
| print(json.dumps(report, indent=2)) |
| archive_ok = report["archive"]["status"] == "compatible_structure" |
| torch_ok = not args.torch_check or report["torch"]["status"] == "compatible" |
| raise SystemExit(0 if archive_ok and torch_ok else 1) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|