|
@@ -32,7 +32,13 @@ SAMPLE_BYTES = 1 << 20
|
|
|
|
|
|
|
|
|
|
|
|
|
def repo_dir(src_root, repo):
|
|
def repo_dir(src_root, repo):
|
|
|
- return os.path.join(src_root, "models--" + repo.replace("/", "--"))
|
|
|
|
|
|
|
+ """The repo's cache dir: HF hub layout is `<root>/hub/models--org--name`,
|
|
|
|
|
+ but a caller may hand us the hub dir directly."""
|
|
|
|
|
+ name = "models--" + repo.replace("/", "--")
|
|
|
|
|
+ for cand in (os.path.join(src_root, "hub", name), os.path.join(src_root, name)):
|
|
|
|
|
+ if os.path.isdir(cand):
|
|
|
|
|
+ return cand
|
|
|
|
|
+ return os.path.join(src_root, "hub", name)
|
|
|
|
|
|
|
|
|
|
|
|
|
def snapshot_dir(src_root, repo, revision=None):
|
|
def snapshot_dir(src_root, repo, revision=None):
|
|
@@ -79,13 +85,26 @@ def sample_hash(path):
|
|
|
return h.hexdigest()[:32]
|
|
return h.hexdigest()[:32]
|
|
|
|
|
|
|
|
|
|
|
|
|
-def local_bytes(snapshot, globs):
|
|
|
|
|
|
|
+def local_bytes(snapshot, globs, dst=None):
|
|
|
|
|
+ """Bytes the `local` copies cost — minus whatever is already in place.
|
|
|
|
|
+
|
|
|
|
|
+ Already-staged nodes must not be reported as short: a file that exists in
|
|
|
|
|
+ `dst` as a regular file of the same size counts as done here (the `apply`
|
|
|
|
|
+ pass still verifies it by sampled content hash and re-copies on mismatch,
|
|
|
|
|
+ so this is only the preflight's arithmetic, not the verification)."""
|
|
|
need = 0
|
|
need = 0
|
|
|
files = []
|
|
files = []
|
|
|
for rel in list_files(snapshot):
|
|
for rel in list_files(snapshot):
|
|
|
- if matches(rel, globs):
|
|
|
|
|
- need += os.path.getsize(os.path.join(snapshot, rel))
|
|
|
|
|
- files.append(rel)
|
|
|
|
|
|
|
+ if not matches(rel, globs):
|
|
|
|
|
+ continue
|
|
|
|
|
+ files.append(rel)
|
|
|
|
|
+ size = os.path.getsize(os.path.join(snapshot, rel))
|
|
|
|
|
+ if dst:
|
|
|
|
|
+ existing = os.path.join(dst, rel)
|
|
|
|
|
+ if os.path.isfile(existing) and not os.path.islink(existing):
|
|
|
|
|
+ if os.path.getsize(existing) == size:
|
|
|
|
|
+ continue
|
|
|
|
|
+ need += size
|
|
|
return need, files
|
|
return need, files
|
|
|
|
|
|
|
|
|
|
|
|
@@ -145,7 +164,8 @@ def main():
|
|
|
a = ap.parse_args()
|
|
a = ap.parse_args()
|
|
|
|
|
|
|
|
snapshot, revision = snapshot_dir(a.src, a.repo)
|
|
snapshot, revision = snapshot_dir(a.src, a.repo)
|
|
|
- need, files = local_bytes(snapshot, a.local)
|
|
|
|
|
|
|
+ root = os.path.join(a.dst, a.repo)
|
|
|
|
|
+ need, files = local_bytes(snapshot, a.local, root)
|
|
|
free = free_bytes(a.dst)
|
|
free = free_bytes(a.dst)
|
|
|
# A preflight may ask for extra headroom on top of the copies themselves.
|
|
# A preflight may ask for extra headroom on top of the copies themselves.
|
|
|
floor = parse_size(a.min_free) if a.min_free else 0
|
|
floor = parse_size(a.min_free) if a.min_free else 0
|
|
@@ -160,14 +180,16 @@ def main():
|
|
|
print(f"NEED {required} FREE {free} DST {a.dst}", file=sys.stderr)
|
|
print(f"NEED {required} FREE {free} DST {a.dst}", file=sys.stderr)
|
|
|
return 3
|
|
return 3
|
|
|
|
|
|
|
|
- os.makedirs(a.dst, exist_ok=True)
|
|
|
|
|
- ledger = load_ledger(a.dst)
|
|
|
|
|
|
|
+ # One view per repo under dst — `model` becomes <container-root>/<repo>.
|
|
|
|
|
+ root = os.path.join(a.dst, a.repo)
|
|
|
|
|
+ os.makedirs(root, exist_ok=True)
|
|
|
|
|
+ ledger = load_ledger(root)
|
|
|
seen = ledger.setdefault("files", {})
|
|
seen = ledger.setdefault("files", {})
|
|
|
copied = kept = 0
|
|
copied = kept = 0
|
|
|
for rel in list_files(snapshot):
|
|
for rel in list_files(snapshot):
|
|
|
src = os.path.join(snapshot, rel)
|
|
src = os.path.join(snapshot, rel)
|
|
|
- dst = os.path.join(a.dst, rel)
|
|
|
|
|
- os.makedirs(os.path.dirname(dst) or a.dst, exist_ok=True)
|
|
|
|
|
|
|
+ dst = os.path.join(root, rel)
|
|
|
|
|
+ os.makedirs(os.path.dirname(dst) or root, exist_ok=True)
|
|
|
if not matches(rel, a.local):
|
|
if not matches(rel, a.local):
|
|
|
target = container_path(a.container_root, a.repo, revision, rel)
|
|
target = container_path(a.container_root, a.repo, revision, rel)
|
|
|
if os.path.islink(dst) and os.readlink(dst) == target:
|
|
if os.path.islink(dst) and os.readlink(dst) == target:
|
|
@@ -178,10 +200,16 @@ def main():
|
|
|
seen.pop(rel, None)
|
|
seen.pop(rel, None)
|
|
|
continue
|
|
continue
|
|
|
want = None if a.verify == "presence" else sample_hash(src)
|
|
want = None if a.verify == "presence" else sample_hash(src)
|
|
|
- have = seen.get(rel)
|
|
|
|
|
- if not a.force and os.path.isfile(dst) and not os.path.islink(dst) and have == want:
|
|
|
|
|
- kept += 1
|
|
|
|
|
- continue
|
|
|
|
|
|
|
+ if not a.force and os.path.isfile(dst) and not os.path.islink(dst):
|
|
|
|
|
+ # No ledger entry (a hand-staged tree, e.g. rsync'd by the operator):
|
|
|
|
|
+ # hash what is already there and adopt it instead of re-copying.
|
|
|
|
|
+ have = seen.get(rel)
|
|
|
|
|
+ if have is None and want is not None:
|
|
|
|
|
+ have = sample_hash(dst)
|
|
|
|
|
+ if have == want:
|
|
|
|
|
+ seen[rel] = have
|
|
|
|
|
+ kept += 1
|
|
|
|
|
+ continue
|
|
|
tmp = dst + ".sm121-tmp"
|
|
tmp = dst + ".sm121-tmp"
|
|
|
shutil.copyfile(src, tmp)
|
|
shutil.copyfile(src, tmp)
|
|
|
os.replace(tmp, dst)
|
|
os.replace(tmp, dst)
|
|
@@ -190,7 +218,7 @@ def main():
|
|
|
ledger["repo"] = a.repo
|
|
ledger["repo"] = a.repo
|
|
|
ledger["revision"] = revision
|
|
ledger["revision"] = revision
|
|
|
ledger["local"] = a.local
|
|
ledger["local"] = a.local
|
|
|
- save_ledger(a.dst, ledger)
|
|
|
|
|
|
|
+ save_ledger(root, ledger)
|
|
|
print(f"local-view {a.repo}@{revision[:12]}: {copied} copied, {kept} verified, "
|
|
print(f"local-view {a.repo}@{revision[:12]}: {copied} copied, {kept} verified, "
|
|
|
f"{need} bytes local, {free} free")
|
|
f"{need} bytes local, {free} free")
|
|
|
return 0
|
|
return 0
|