miewid / scripts /export.py
james-burgess's picture
Upload folder using huggingface_hub
3f258b0 verified
Raw
History Blame Contribute Delete
4.22 kB
#!/usr/bin/env python3
"""Export conservationxlabs/miewid-msv3 to ONNX.
Clones the HF model, loads via trust_remote_code, traces forward(),
and exports as ONNX. Verifies output with onnxruntime.
The exported model accepts `[B, 3, 440, 440]` float32 and returns
`[B, 2152]` float32 embedding vectors (BatchNorm head output).
Usage::
python scripts/export.py [--upload]
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
HF_REPO = "james-burgess/miewid"
LOCAL_DIR = Path("/tmp/miewid-msv3")
def clone_repo() -> Path:
import subprocess
if LOCAL_DIR.exists():
subprocess.run(["git", "-C", str(LOCAL_DIR), "pull", "--ff-only"], check=False)
else:
subprocess.run(
[
"git",
"clone",
"--depth",
"1",
"https://huggingface.co/conservationxlabs/miewid-msv3",
str(LOCAL_DIR),
],
check=True,
)
return LOCAL_DIR
def export() -> tuple[Path, int]:
import numpy as np
import torch
repo = clone_repo()
sys.path.insert(0, str(repo))
from transformers import AutoModel
print("Loading model from local clone...")
model = AutoModel.from_pretrained(
str(repo), trust_remote_code=True, torch_dtype=torch.float32
)
model.eval()
print(f"Model loaded: {type(model).__name__}")
# Dry-run to get output dim
with torch.inference_mode():
dummy = torch.randn(1, 3, 440, 440)
result = model(dummy)
out_dim = result.shape[-1]
print(f"Dry run: {dummy.shape} -> {result.shape} (dim={out_dim})")
# Export
dest = REPO_ROOT / "miewid.onnx"
torch.onnx.export(
model,
dummy,
str(dest),
input_names=["input"],
output_names=["output"],
dynamic_axes={
"input": {0: "batch"},
"output": {0: "batch"},
},
opset_version=14,
do_constant_folding=True,
)
size_mb = dest.stat().st_size / 1_048_576
print(f"Exported -> {dest} ({size_mb:.1f} MB)")
_verify(dest, out_dim)
return dest, out_dim
def _verify(onnx_path: Path, expected_dim: int) -> None:
import numpy as np
import onnxruntime as ort
session = ort.InferenceSession(str(onnx_path))
inp_name = session.get_inputs()[0].name
out_name = session.get_outputs()[0].name
dummy = np.random.randn(1, 3, 440, 440).astype(np.float32)
result = session.run([out_name], {inp_name: dummy})[0]
assert result.shape[-1] == expected_dim, f"{result.shape[-1]} != {expected_dim}"
print(f" Verified: {dummy.shape} -> {result.shape}")
def upload(filepath: Path, out_dim: int) -> None:
try:
from huggingface_hub import HfApi, create_repo
except ImportError:
print("Install huggingface_hub: pip install huggingface_hub")
return
api = HfApi()
create_repo(HF_REPO, repo_type="model", exist_ok=True)
size_mb = filepath.stat().st_size / 1_048_576
# Upload the ONNX model
api.upload_file(
path_or_fileobj=str(filepath),
path_in_repo="miewid.onnx",
repo_id=HF_REPO,
commit_message=f"miewid.onnx ({size_mb:.1f} MB, dim={out_dim})",
)
# Upload metadata
for meta_file in ("README.md", "config.json"):
meta_path = REPO_ROOT / meta_file
if meta_path.exists():
api.upload_file(
path_or_fileobj=str(meta_path),
path_in_repo=meta_file,
repo_id=HF_REPO,
commit_message=f"Update {meta_file}",
)
print(f" Uploaded {meta_file}")
print(f"Uploaded -> https://huggingface.co/{HF_REPO}")
def main() -> None:
parser = argparse.ArgumentParser(description="Export MiewID HF model to ONNX")
parser.add_argument(
"--upload",
action="store_true",
help="Upload model + metadata to HF after export",
)
args = parser.parse_args()
onnx_path, out_dim = export()
if args.upload:
upload(onnx_path, out_dim)
if __name__ == "__main__":
main()