Tri-Netra-AI / scripts /upload_models_to_hf.py
anannyavyas1's picture
Upload folder using huggingface_hub
1cf3825 verified
Raw
History Blame Contribute Delete
6.18 kB
"""Upload local ONNX weights to a HuggingFace Model repository.
Why this exists separately from the Space:
- HuggingFace Spaces free tier caps total repo size at 1 GB. Our 5 ONNX
models total ~440 MB which is close to the cap and crowds out everything
else.
- HuggingFace Model repos have a much larger free quota and are the
canonical place to publish trained weights.
- The dashboard's _ensure_onnx_models_downloaded() (see dashboard.py)
pulls these files at first boot and caches them locally, so the Space
image stays small while still serving the same models.
Usage:
# Once: create the Model repo on HF (web UI is fine):
# https://huggingface.co/new -> Type: Model, Name: Tri-Netra-AI-models
# Then run:
python scripts/upload_models_to_hf.py --repo anannyavyas1/Tri-Netra-AI-Models
# The HF_TOKEN env var must be set to a token with WRITE scope.
Re-running is safe: it only uploads files whose local sha256 differs from
what's already on the Hub.
"""
from __future__ import annotations
import argparse
import os
import sys
import time
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
# (local path relative to repo root, target path inside the Model repo)
ARTIFACT_MAP = [
# v8 ConvNeXt-Tiny + 384px + Tversky (current production champion).
# dynamo-exported ONNX is split into graph (.onnx) + external weights
# (.onnx.data). Both MUST be uploaded; ORT auto-loads the sibling.
('model/best_micro.onnx',
'attention_unet_v8/best_micro.onnx'),
('model/best_micro.onnx.data',
'attention_unet_v8/best_micro.onnx.data'),
# v5 (kept for fallback / A-B testing)
('segmentation_artifacts/attention_unet_v5/best_model.onnx',
'attention_unet_v5/best_model.onnx'),
('segmentation_artifacts/attention_unet_v3/best_model.onnx',
'attention_unet_v3/best_model.onnx'),
('segmentation_artifacts/attention_unet_t1c/best_model.onnx',
'attention_unet_t1c/best_model.onnx'),
('real_eval_current/cnn/best_weights.onnx',
'cnn/best_weights.onnx'),
('real_eval_current/transfer/best_weights.onnx',
'transfer/best_weights.onnx'),
('real_eval_current/vit/best_weights.onnx',
'vit/best_weights.onnx'),
]
# Conformal-counterfactual calibration artifacts. Each is a small JSON
# (~1 KB) holding one intervention's calibrated quantile q. Loaded by
# src/research/dashboard_integration.py at request time to produce
# voxelwise prediction sets with (1-alpha) post-intervention coverage.
CONFORMAL_DIR = ROOT / 'conformal_artifacts'
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--repo', required=True,
help='HF Model repo id, e.g. anannyavyas1/Tri-Netra-AI-Models')
ap.add_argument('--token', default=None,
help='HF write token. Defaults to HF_TOKEN env var.')
ap.add_argument('--create-if-missing', action='store_true',
help='Auto-create the Model repo if it doesn\'t exist.')
ap.add_argument('--commit-message', default='Upload Tri-Netra AI ONNX models')
args = ap.parse_args()
try:
from huggingface_hub import HfApi
except ImportError:
print('ERROR: huggingface_hub not installed. Run: pip install huggingface_hub')
sys.exit(2)
token = args.token or os.environ.get('HF_TOKEN')
if not token:
print('ERROR: no token. Pass --token or set HF_TOKEN env var (write scope).')
sys.exit(2)
api = HfApi(token=token)
if args.create_if_missing:
try:
api.create_repo(repo_id=args.repo, repo_type='model', exist_ok=True)
print(f'[ok] repo exists or created: {args.repo}')
except Exception as exc:
print(f'[warn] create_repo: {exc}')
# Upload each file individually so a failure on one doesn't abort the rest.
# The Hub deduplicates by sha; re-running with no changes is fast.
uploaded = 0
skipped_missing = 0
failed: list[tuple[str, str]] = []
total_bytes = 0
t_start = time.perf_counter()
upload_targets = list(ARTIFACT_MAP)
# Auto-discover conformal calibration JSONs so we don't have to edit
# ARTIFACT_MAP every time a new intervention is calibrated.
if CONFORMAL_DIR.exists():
for p in sorted(CONFORMAL_DIR.glob('*.json')):
upload_targets.append(
(str(p.relative_to(ROOT)).replace('\\', '/'),
f'conformal_artifacts/{p.name}')
)
for local_rel, repo_rel in upload_targets:
local = ROOT / local_rel
if not local.exists():
print(f'[skip] {local_rel} not found locally')
skipped_missing += 1
continue
size_mb = local.stat().st_size / 1e6
total_bytes += local.stat().st_size
print(f'[upload] {local_rel} -> {args.repo}:{repo_rel} ({size_mb:.1f} MB) ...', flush=True)
try:
t0 = time.perf_counter()
api.upload_file(
path_or_fileobj=str(local),
path_in_repo=repo_rel,
repo_id=args.repo,
repo_type='model',
commit_message=f'{args.commit_message}: {repo_rel}',
)
print(f' done in {time.perf_counter()-t0:.1f}s')
uploaded += 1
except Exception as exc:
print(f' FAILED: {type(exc).__name__}: {exc}')
failed.append((local_rel, str(exc)))
elapsed = time.perf_counter() - t_start
print('\n=== upload summary ===')
print(f' repo: {args.repo}')
print(f' uploaded: {uploaded}')
print(f' skipped: {skipped_missing} (file not found locally)')
print(f' failed: {len(failed)}')
print(f' bytes: {total_bytes/1e6:.1f} MB')
print(f' elapsed: {elapsed:.1f}s')
if failed:
print('\nfailures:')
for f, e in failed:
print(f' - {f}: {e}')
sys.exit(1)
if __name__ == '__main__':
main()