195 lines
5.3 KiB
Python
195 lines
5.3 KiB
Python
"""Prepare AnyImage ONNX models and sync them to OmooLab R2."""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from urllib.request import Request, urlopen
|
|
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parent
|
|
MODELS_DIR = PROJECT_ROOT / "models"
|
|
R2_ENDPOINT = "https://models.omoolab.xyz"
|
|
CHUNK_SIZE = 1024 * 1024
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ModelFile:
|
|
folder: str
|
|
size: int
|
|
sha256: str
|
|
sources: tuple[str, ...]
|
|
|
|
@property
|
|
def destination(self):
|
|
filename = "model_fp16.onnx" if self.folder.endswith("/onnx") else "model.onnx"
|
|
return MODELS_DIR / self.folder / filename
|
|
|
|
|
|
def hf_url(repository, filename):
|
|
return f"https://huggingface.co/{repository}/resolve/main/{filename}"
|
|
|
|
|
|
def r2_url(folder, filename="model.onnx"):
|
|
return f"{R2_ENDPOINT}/{folder}/{filename}"
|
|
|
|
|
|
MODEL_FILES = (
|
|
ModelFile(
|
|
"DA3-SMALL",
|
|
105484029,
|
|
"7a9d81475b3e2334ace0d6d2c2a816b3c93081437e1866cc434ce1789125f631",
|
|
(r2_url("DA3-SMALL"),),
|
|
),
|
|
ModelFile(
|
|
"DA3-BASE",
|
|
412856697,
|
|
"31f38fc4990539d7902ba267924b9731db1acb00d840c248a60b1865810ff9e6",
|
|
(r2_url("DA3-BASE"),),
|
|
),
|
|
ModelFile(
|
|
"BEN2-ONNX/onnx",
|
|
219121675,
|
|
"dfdc25f421f32a0d1268e0f2ff2153d340e8f1d52d3dd16f5dc33c1ce85cedf1",
|
|
(
|
|
hf_url("onnx-community/BEN2-ONNX", "onnx/model_fp16.onnx"),
|
|
r2_url("BEN2-ONNX", "onnx/model_fp16.onnx"),
|
|
),
|
|
),
|
|
ModelFile(
|
|
"moge-2-vits-normal-onnx",
|
|
140852051,
|
|
"24eacb5dc7a2c54c7bc98f7de085ffbed79ad006ea5b664c2c2cdc02ff3a52f0",
|
|
(
|
|
hf_url("Ruicheng/moge-2-vits-normal-onnx", "model.onnx"),
|
|
r2_url("moge-2-vits-normal-onnx"),
|
|
),
|
|
),
|
|
ModelFile(
|
|
"moge-2-vitb-normal-onnx",
|
|
419411850,
|
|
"bbf14e07a30f11e69d36ab861590123f5598ababcbc8946a063eb4a966f35a21",
|
|
(
|
|
hf_url("Ruicheng/moge-2-vitb-normal-onnx", "model.onnx"),
|
|
r2_url("moge-2-vitb-normal-onnx"),
|
|
),
|
|
),
|
|
ModelFile(
|
|
"moge-2-vitl-normal-onnx",
|
|
1324265014,
|
|
"afbc4ccc3450298f3afb35b90f015f4c4f552dea21dc6470d5f7b78b77e2d751",
|
|
(
|
|
hf_url("Ruicheng/moge-2-vitl-normal-onnx", "model.onnx"),
|
|
r2_url("moge-2-vitl-normal-onnx"),
|
|
),
|
|
),
|
|
)
|
|
|
|
|
|
def file_sha256(path):
|
|
digest = hashlib.sha256()
|
|
with Path(path).open("rb") as stream:
|
|
while chunk := stream.read(CHUNK_SIZE):
|
|
digest.update(chunk)
|
|
return digest.hexdigest()
|
|
|
|
|
|
def verify_existing(model):
|
|
path = model.destination
|
|
if not path.is_file():
|
|
return False
|
|
if path.stat().st_size != model.size:
|
|
raise RuntimeError(f"Existing model has an unexpected size: {path}")
|
|
if file_sha256(path) != model.sha256:
|
|
raise RuntimeError(f"Existing model has an unexpected SHA-256: {path}")
|
|
print(f"Skip existing: {path.relative_to(PROJECT_ROOT)}")
|
|
return True
|
|
|
|
|
|
def download_once(model, url):
|
|
destination = model.destination
|
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
temporary = destination.with_suffix(".onnx.part")
|
|
temporary.unlink(missing_ok=True)
|
|
request = Request(url, headers={"User-Agent": "AnyImage model sync"})
|
|
print(f"Download: {url}")
|
|
downloaded = 0
|
|
digest = hashlib.sha256()
|
|
try:
|
|
with urlopen(request, timeout=60) as response, temporary.open("wb") as output:
|
|
while chunk := response.read(CHUNK_SIZE):
|
|
output.write(chunk)
|
|
digest.update(chunk)
|
|
downloaded += len(chunk)
|
|
print(
|
|
f"\r {downloaded / 1024**2:.1f} / "
|
|
f"{model.size / 1024**2:.1f} MiB",
|
|
end="",
|
|
flush=True,
|
|
)
|
|
print()
|
|
if downloaded != model.size:
|
|
raise RuntimeError(f"size mismatch: {downloaded} != {model.size}")
|
|
if digest.hexdigest() != model.sha256:
|
|
raise RuntimeError("SHA-256 mismatch")
|
|
temporary.replace(destination)
|
|
except Exception:
|
|
temporary.unlink(missing_ok=True)
|
|
raise
|
|
|
|
|
|
def prepare_model(model):
|
|
if verify_existing(model):
|
|
return
|
|
failures = []
|
|
for url in model.sources:
|
|
try:
|
|
download_once(model, url)
|
|
return
|
|
except Exception as error:
|
|
failures.append(f"{url}: {error}")
|
|
print(f"Source failed: {error}", file=sys.stderr)
|
|
raise RuntimeError("All sources failed:\n" + "\n".join(failures))
|
|
|
|
|
|
def sync_models():
|
|
executable = shutil.which("rclone")
|
|
if executable is None:
|
|
raise RuntimeError("rclone is not available on PATH")
|
|
subprocess.run(
|
|
[
|
|
executable,
|
|
"sync",
|
|
"models",
|
|
"omoolab-r2:ai-models",
|
|
"--progress",
|
|
],
|
|
cwd=PROJECT_ROOT,
|
|
check=True,
|
|
)
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"--download-only",
|
|
action="store_true",
|
|
help="Prepare and verify models without running rclone",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
for model in MODEL_FILES:
|
|
prepare_model(model)
|
|
if not args.download_only:
|
|
sync_models()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|