thundercode commited on
Commit
b6e40ec
·
verified ·
1 Parent(s): 5a89f02

release: add tools/generate_model_manifest.py

Browse files
Files changed (1) hide show
  1. tools/generate_model_manifest.py +182 -0
tools/generate_model_manifest.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generate models/manifest.json and models/checksums.sha256 from the real artifact files.
2
+
3
+ RULE: nothing in the manifest is typed by hand. Every byte count and every sha256 is computed
4
+ here by reading the file. Where a value cannot be determined from disk it is emitted as null,
5
+ never guessed.
6
+
7
+ Read-only with respect to the artifacts. Writes only the two generated files.
8
+ """
9
+ import hashlib
10
+ import json
11
+ import os
12
+ import sys
13
+ import datetime
14
+
15
+ SRC = r"C:/Users/anish/satquery-ai"
16
+ OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "repo", "models")
17
+ OUT_DIR = os.path.abspath(OUT_DIR)
18
+
19
+ CONFIG_HASH = "78f1e3700da15aa1" # verified by running get_config().hash
20
+
21
+ # The six released artifacts. Paths are relative to SRC.
22
+ ARTIFACTS = [
23
+ {
24
+ "id": "change_head",
25
+ "task": "change",
26
+ "kind": "trained_head",
27
+ "path": "artifacts/change/levir_change_v001/head.pt",
28
+ "hf_path": "change/head.pt",
29
+ "backbone": None,
30
+ "architecture": "STANet-style Siamese change detector (ResNet-18 + PAM)",
31
+ "source_metric_artifact": "artifacts/change/eval_test/eval_result.json",
32
+ },
33
+ {
34
+ "id": "change_vqa_head",
35
+ "task": "change_vqa",
36
+ "kind": "trained_head",
37
+ "path": "artifacts/change_vqa/run/head.pt",
38
+ "hf_path": "change_vqa/head.pt",
39
+ "backbone": "STANet change detector (frozen, backing the head's change features)",
40
+ "architecture": "change_vqa_head_v1",
41
+ "source_metric_artifact": "artifacts/change_vqa/run/PROMOTION.json",
42
+ },
43
+ {
44
+ "id": "optical_sar_fusion_head",
45
+ "task": "optical_sar",
46
+ "kind": "trained_head",
47
+ "path": "artifacts/optical_sar/fusion_head_production_v001/head.pt",
48
+ "hf_path": "optical_sar/head.pt",
49
+ "backbone": "antofuller/CROMA (CROMA_base.pt, revision 0dd28e3d633b)",
50
+ "architecture": "CROMA-base fusion head (input_dim 2318 -> hidden 512 -> 19 classes)",
51
+ "source_metric_artifact": "artifacts/optical_sar/fusion_head_production_v001/pre_registered_115_metric.json",
52
+ },
53
+ {
54
+ "id": "grounding_head",
55
+ "task": "grounding",
56
+ "kind": "trained_head",
57
+ "path": "artifacts/grounding/remoteclip_grounding_v001/head.pt",
58
+ "hf_path": "grounding/head.pt",
59
+ "backbone": "chendelong/RemoteCLIP (RemoteCLIP-ViT-B-32.pt, revision bf1d8a3ccf2d)",
60
+ "architecture": "RemoteCLIP ViT-B/32 grounding head (feature_dim 2048, hidden 512)",
61
+ "source_metric_artifact": "artifacts/grounding/remoteclip_grounding_v001/eval_result_canonical.json",
62
+ },
63
+ {
64
+ "id": "router_adapter",
65
+ "task": "router",
66
+ "kind": "trained_adapter",
67
+ "path": "artifacts/router/router_adapter_v001/adapter.pt",
68
+ "hf_path": "router/adapter.pt",
69
+ "backbone": "sentence-transformers/all-MiniLM-L6-v2 (revision 1110a243fdf4)",
70
+ "architecture": "task/modality adapter over frozen MiniLM embeddings (~50,822 params)",
71
+ "source_metric_artifact": "artifacts/router/threshold_sweep_val.json",
72
+ },
73
+ {
74
+ "id": "vlm_lora_adapter",
75
+ "task": "vlm",
76
+ "kind": "lora_adapter",
77
+ "path": ".scratch/phase6_real_adapter/phase6_adapter/adapter_model.safetensors",
78
+ "hf_path": "vlm/adapter_model.safetensors",
79
+ "backbone": "HuggingFaceTB/SmolVLM-500M-Instruct (revision a7da5b986cb5)",
80
+ "architecture": "PEFT LoRA (r=16, alpha=32, dropout=0.05) on text_model projections",
81
+ "source_metric_artifact": "artifacts/vlm/phase6_closure.json",
82
+ "acceptance": "ACCEPTANCE-REJECTED (metrics usable; not promoted)",
83
+ },
84
+ ]
85
+
86
+
87
+ def sha256_of(path, chunk=1 << 20):
88
+ h = hashlib.sha256()
89
+ with open(path, "rb") as fh:
90
+ while True:
91
+ b = fh.read(chunk)
92
+ if not b:
93
+ break
94
+ h.update(b)
95
+ return h.hexdigest()
96
+
97
+
98
+ def read_json(path):
99
+ try:
100
+ with open(path, encoding="utf-8") as fh:
101
+ return json.load(fh)
102
+ except Exception:
103
+ return None
104
+
105
+
106
+ def main():
107
+ entries = []
108
+ missing = []
109
+ for a in ARTIFACTS:
110
+ full = os.path.join(SRC, a["path"])
111
+ e = dict(a)
112
+ e["config_hash"] = CONFIG_HASH
113
+ if not os.path.exists(full):
114
+ e["bytes"] = None
115
+ e["sha256"] = None
116
+ e["status"] = "MISSING_ON_DISK"
117
+ missing.append(a["path"])
118
+ else:
119
+ e["bytes"] = os.path.getsize(full)
120
+ e["sha256"] = sha256_of(full)
121
+ e["status"] = "PRESENT"
122
+ # pull the artifact's own declared parameter count where it records one
123
+ e["parameters"] = None
124
+ src_metric = os.path.join(SRC, a.get("source_metric_artifact") or "")
125
+ if os.path.exists(src_metric):
126
+ d = read_json(src_metric)
127
+ if isinstance(d, dict):
128
+ art = d.get("artifact")
129
+ if isinstance(art, dict):
130
+ e["parameters"] = art.get("parameters")
131
+ entries.append(e)
132
+
133
+ manifest = {
134
+ "schema": "satquery_model_manifest_v1",
135
+ "generated_utc": datetime.datetime.now(datetime.timezone.utc)
136
+ .replace(microsecond=0)
137
+ .isoformat(),
138
+ "generator": "release/tools/generate_model_manifest.py",
139
+ "note": (
140
+ "Generated by reading the files. No byte count or hash is typed by hand. "
141
+ "Backbones are NOT redistributed; they are fetched from the Hugging Face Hub, "
142
+ "pinned by revision."
143
+ ),
144
+ "config_hash": CONFIG_HASH,
145
+ "release_repo": "thundercode/SatQuery",
146
+ "artifact_count": len(entries),
147
+ "artifacts": entries,
148
+ }
149
+
150
+ os.makedirs(OUT_DIR, exist_ok=True)
151
+ mpath = os.path.join(OUT_DIR, "manifest.json")
152
+ with open(mpath, "w", encoding="utf-8", newline="\n") as fh:
153
+ json.dump(manifest, fh, indent=2, ensure_ascii=False)
154
+ fh.write("\n")
155
+
156
+ cpath = os.path.join(OUT_DIR, "checksums.sha256")
157
+ with open(cpath, "w", encoding="utf-8", newline="\n") as fh:
158
+ fh.write("# sha256 of the six released artifacts, keyed by their path in this repository.\n")
159
+ fh.write("# Verify with: sha256sum -c checksums.sha256\n")
160
+ for e in entries:
161
+ if e["sha256"]:
162
+ # sha256sum format: "<hash> <name>"
163
+ fh.write(f"{e['sha256']} {e['hf_path']}\n")
164
+
165
+ print(f"wrote {mpath}")
166
+ print(f"wrote {cpath}")
167
+ print()
168
+ for e in entries:
169
+ b = f"{e['bytes']:,}" if e["bytes"] is not None else "-"
170
+ h = (e["sha256"] or "-")[:16]
171
+ print(f" {e['status']:16} {e['id']:24} {b:>14} {h}… {e['path']}")
172
+ if missing:
173
+ print()
174
+ print("MISSING FILES (manifest records null, never a guess):")
175
+ for m in missing:
176
+ print(" " + m)
177
+ return 1
178
+ return 0
179
+
180
+
181
+ if __name__ == "__main__":
182
+ sys.exit(main())