colibri/models: don't false-flag a complete HF model as "incomplete"

hf leaves a stale <sha>.<etag>.incomplete orphan in blobs/ after a resumed or
retried chunk even though the real file finished — so "download complete" and the
model page disagreed (settings said done, the page showed ⚠ incomplete).

- model list: an .incomplete file now counts as an unfinished download ONLY when its
  final blob (<sha>) is absent; an orphan whose final blob exists is ignored. Fixes
  the false badge for ALL HF models, not just colibri.
- colibri catalog: _colibri_repo_complete now tests for dangling snapshot symlinks
  (the true "file missing" signal) instead of .incomplete presence, so the settings
  "present" indicator matches reality.
- colibri catalog label → the real size (~429 GB / 400 GiB).
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoSpEthysqmseCc6Geizty
parent 1b7187d4
......@@ -16,7 +16,7 @@
# Canonical product version for CoderAI — single source of truth. Both the API
# metadata and the admin web UI read from here.
__version__ = "0.1.70"
__version__ = "0.1.71"
# Configure the CUDA caching allocator BEFORE torch is imported anywhere.
# expandable_segments lets the allocator return freed pages to the driver even
......
......@@ -863,8 +863,8 @@ async def api_ds4_default_models(username: str = Depends(require_admin)):
_COLIBRI_DEFAULT_MODELS = [
{"key": "glm-5.2-int4-g64-mtp",
"repo": "mastouri/GLM-5.2-colibri-int4-g64-with-int8-mtp",
"label": "GLM-5.2 int4 g64 + int8-MTP (~372 GB) — the colibri container",
"size_gb": 372},
"label": "GLM-5.2 int4 g64 + int8-MTP (~429 GB / 400 GiB) — the colibri container",
"size_gb": 429},
]
......@@ -881,16 +881,32 @@ async def api_colibri_default_models(username: str = Depends(require_admin)):
pass
out = []
for m in _COLIBRI_DEFAULT_MODELS:
present = False
try:
from codai.backends.colibri import ColibriBackend
present = bool(ColibriBackend._hf_snapshot_dir(m["repo"]))
except Exception:
present = False
out.append({**m, "present": present})
out.append({**m, "present": _colibri_repo_complete(m["repo"])})
return {"models": out, "model_id": model_id}
def _colibri_repo_complete(repo: str) -> bool:
"""True when the HF repo is fully downloaded — the snapshot resolves AND every
file symlink points to an existing blob (no dangling links). This is hf's own
notion of "cached". Stale ``.incomplete`` temp files whose final blob already
exists are IGNORED: hf leaves them behind after a resumed/retried chunk, so their
mere presence does NOT mean the model is unfinished (a missing final blob shows up
as a dangling snapshot symlink, which is what we actually check)."""
try:
from codai.backends.colibri import ColibriBackend
snap = ColibriBackend._hf_snapshot_dir(repo)
if not snap or not os.path.isdir(snap):
return False
for root, _dirs, files in os.walk(snap):
for n in files:
p = os.path.join(root, n)
if os.path.islink(p) and not os.path.exists(p):
return False # dangling symlink = a file not yet downloaded
return True
except Exception:
return False
def _cancel_download_session(session_id: str) -> bool:
"""Cancel an active download by flagging the session and terminating its worker
process. Returns False if there is no such download session.
......@@ -1197,16 +1213,31 @@ def _scan_caches() -> dict:
if rid:
incomplete_repos.add(str(rid))
# Also scan each repo's blobs directory for .incomplete marker files
# (used by some huggingface_hub versions for in-progress downloads).
# (used by some huggingface_hub versions for in-progress downloads). A
# `.incomplete` file is only evidence of an UNFINISHED download when its
# final blob is absent — hf leaves a stale `.incomplete` orphan behind
# after a resumed/retried chunk even though the real file completed, so
# counting those would flag a fully-downloaded model as incomplete.
try:
for _repo_entry in os.scandir(hf_dir):
if not _repo_entry.is_dir() or not _repo_entry.name.startswith('models--'):
continue
_blobs = os.path.join(_repo_entry.path, 'blobs')
if os.path.isdir(_blobs) and any(
n.endswith('.incomplete') or n.endswith('.lock')
for n in os.listdir(_blobs)
):
if not os.path.isdir(_blobs):
continue
_names = os.listdir(_blobs)
_finals = set(_names)
_genuine = False
for n in _names:
if n.endswith('.lock'):
continue # transient/orphaned lock, not a partial
if n.endswith('.incomplete'):
# <sha>.<etag>.incomplete → final blob is <sha>; if that
# blob exists this is a harmless orphan, else a real partial.
if n.split('.', 1)[0] not in _finals:
_genuine = True
break
if _genuine:
_rid = _repo_entry.name[len('models--'):].replace('--', '/', 1)
incomplete_repos.add(_rid)
except Exception:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment