Skip to content

Commit f6876c3

Browse files
committed
feat: show pass/fail for each identity check, fix HF metadata discovery
- Verification banner now shows OK/!! for each check explicitly - Fixed model_identity probe to find HF commit hash from .cache/huggingface/download/*.metadata (hf download --local-dir format) - Added IdentityCheckResult dataclass for structured pass/fail results
1 parent 5130d07 commit f6876c3

2 files changed

Lines changed: 100 additions & 54 deletions

File tree

src/srtctl/cli/mixins/benchmark_stage.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ def run_benchmark(
100100
try:
101101
fingerprints = collect_worker_fingerprints(self.runtime.log_dir)
102102
if fingerprints and hasattr(self.config, "identity"):
103-
id_warnings = verify_identity(self.config.identity, fingerprints)
104-
banner = format_identity_verification(id_warnings, self.config.identity)
103+
results = verify_identity(self.config.identity, fingerprints)
104+
banner = format_identity_verification(results, self.config.identity)
105105
for line in banner.splitlines():
106106
logger.info(line)
107107
except Exception as e:

src/srtctl/core/fingerprint.py

Lines changed: 98 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -580,21 +580,30 @@ def check_against_fingerprint(
580580
# ============================================================================
581581

582582

583+
@dataclass
584+
class IdentityCheckResult:
585+
"""Result of a single identity check."""
586+
587+
field: str
588+
passed: bool
589+
message: str
590+
591+
583592
def verify_identity(
584593
identity: Any,
585594
fingerprints: dict[str, Any],
586-
) -> list[str]:
595+
) -> list[IdentityCheckResult]:
587596
"""Compare recipe identity block against collected runtime fingerprints.
588597
589-
Returns a list of warning messages. Empty list means everything matches.
598+
Returns a list of check results (both passes and failures).
590599
591600
Args:
592601
identity: IdentityConfig from the recipe (has .model and .frameworks)
593602
fingerprints: dict of worker_name -> fingerprint dict
594603
"""
595-
warnings: list[str] = []
604+
results: list[IdentityCheckResult] = []
596605
if not fingerprints:
597-
return warnings
606+
return results
598607

599608
# Use the first worker's fingerprint for verification (they all run in the same container)
600609
fp = next(iter(fingerprints.values()))
@@ -605,73 +614,97 @@ def verify_identity(
605614

606615
if identity.model.repo:
607616
fp_repo = fp_model.get("hf_repo") or fp_model.get("model_id")
608-
if fp_repo and identity.model.repo != fp_repo:
609-
warnings.append(f"Model repo mismatch: recipe says '{identity.model.repo}', runtime has '{fp_repo}'")
610-
elif not fp_repo:
611-
warnings.append(
612-
f"Model repo '{identity.model.repo}' declared in recipe but "
613-
f"could not be verified (no HF metadata found at /model)"
617+
if fp_repo and identity.model.repo == fp_repo:
618+
results.append(IdentityCheckResult("model.repo", True, f"{identity.model.repo}"))
619+
elif fp_repo:
620+
results.append(
621+
IdentityCheckResult(
622+
"model.repo",
623+
False,
624+
f"expected '{identity.model.repo}', got '{fp_repo}'",
625+
)
626+
)
627+
else:
628+
results.append(
629+
IdentityCheckResult(
630+
"model.repo",
631+
False,
632+
f"'{identity.model.repo}' declared but no HF metadata found at /model",
633+
)
614634
)
615635

616636
if identity.model.revision:
617637
fp_rev = fp_model.get("hf_revision")
618-
if fp_rev and not fp_rev.startswith(identity.model.revision):
619-
warnings.append(
620-
f"Model revision mismatch: recipe says '{identity.model.revision[:12]}', "
621-
f"runtime has '{fp_rev[:12]}'"
638+
if fp_rev and fp_rev.startswith(identity.model.revision):
639+
results.append(IdentityCheckResult("model.revision", True, f"{fp_rev[:12]}"))
640+
elif fp_rev:
641+
results.append(
642+
IdentityCheckResult(
643+
"model.revision",
644+
False,
645+
f"expected '{identity.model.revision[:12]}', got '{fp_rev[:12]}'",
646+
)
622647
)
623-
elif not fp_rev:
624-
warnings.append(
625-
f"Model revision '{identity.model.revision[:12]}' declared in recipe but "
626-
f"could not be verified (no HF revision found at /model)"
648+
else:
649+
results.append(
650+
IdentityCheckResult(
651+
"model.revision",
652+
False,
653+
f"'{identity.model.revision[:12]}' declared but no HF revision found at /model",
654+
)
627655
)
628656

629657
# --- Framework versions ---
630658
fp_frameworks = fp.get("frameworks") or {}
631659
for name, expected_version in (identity.frameworks or {}).items():
632660
actual_version = fp_frameworks.get(name)
633-
if actual_version and actual_version != expected_version:
634-
warnings.append(f"Framework mismatch: {name} expected '{expected_version}', got '{actual_version}'")
635-
elif not actual_version:
636-
warnings.append(
637-
f"Framework '{name}' version '{expected_version}' declared in recipe but not detected in runtime"
661+
if actual_version and actual_version == expected_version:
662+
results.append(IdentityCheckResult(f"frameworks.{name}", True, f"{actual_version}"))
663+
elif actual_version:
664+
results.append(
665+
IdentityCheckResult(
666+
f"frameworks.{name}",
667+
False,
668+
f"expected '{expected_version}', got '{actual_version}'",
669+
)
670+
)
671+
else:
672+
results.append(
673+
IdentityCheckResult(
674+
f"frameworks.{name}",
675+
False,
676+
f"'{expected_version}' declared but not detected in runtime",
677+
)
638678
)
639679

640-
return warnings
641-
680+
return results
642681

643-
def format_identity_verification(warnings: list[str], identity: Any) -> str:
644-
"""Format identity verification results as a banner for the sweep log.
645682

646-
Args:
647-
warnings: list of warning strings from verify_identity
648-
identity: IdentityConfig from the recipe
649-
"""
683+
def format_identity_verification(results: list[IdentityCheckResult], identity: Any) -> str:
684+
"""Format identity verification results as a banner for the sweep log."""
650685
lines: list[str] = []
651686
lines.append("=" * 60)
652687
lines.append("Identity Verification")
653688
lines.append("=" * 60)
654689

655-
if not warnings:
656-
# Show what was checked
657-
checks = []
658-
if identity.model and identity.model.repo:
659-
checks.append(f" model.repo: {identity.model.repo}")
660-
if identity.model and identity.model.revision:
661-
checks.append(f" model.revision: {identity.model.revision[:12]}")
662-
for name, ver in (identity.frameworks or {}).items():
663-
checks.append(f" {name}: {ver}")
664-
if checks:
665-
lines.append("All checks passed:")
666-
lines.extend(checks)
667-
else:
668-
lines.append("No identity fields declared — nothing to verify.")
669-
else:
670-
lines.append(f"WARNING: {len(warnings)} mismatch(es) detected!")
671-
lines.append("")
672-
for w in warnings:
673-
lines.append(f" ⚠ {w}")
690+
if not results:
691+
lines.append("No identity fields declared — nothing to verify.")
692+
lines.append("=" * 60)
693+
return "\n".join(lines)
674694

695+
passes = [r for r in results if r.passed]
696+
fails = [r for r in results if not r.passed]
697+
698+
for r in passes:
699+
lines.append(f" OK {r.field}: {r.message}")
700+
for r in fails:
701+
lines.append(f" !! {r.field}: {r.message}")
702+
703+
lines.append("")
704+
if fails:
705+
lines.append(f"Result: {len(passes)} passed, {len(fails)} FAILED")
706+
else:
707+
lines.append(f"Result: {len(passes)} passed, all OK")
675708
lines.append("=" * 60)
676709
return "\n".join(lines)
677710

@@ -766,12 +799,25 @@ def model_identity(model_path):
766799
mp = Path(model_path) if model_path else None
767800
if not mp or not mp.exists():
768801
return None
769-
# HuggingFace: check refs/main for commit SHA
802+
# HuggingFace snapshot_download: refs/main has commit SHA
770803
for refs_path in [mp / '.huggingface' / 'refs' / 'main', mp / 'refs' / 'main']:
771804
if refs_path.exists():
772805
info['hf_revision'] = refs_path.read_text().strip()
773806
break
774-
# HuggingFace: check .huggingface/download_metadata.json
807+
# HuggingFace hf download --local-dir: .cache/huggingface/download/*.metadata
808+
# Line 1 of each .metadata file is the commit hash
809+
if 'hf_revision' not in info:
810+
cache_dl = mp / '.cache' / 'huggingface' / 'download'
811+
if cache_dl.is_dir():
812+
for meta_file in sorted(cache_dl.glob('*.metadata')):
813+
try:
814+
first_line = meta_file.read_text().splitlines()[0].strip()
815+
if len(first_line) == 40 and all(c in '0123456789abcdef' for c in first_line):
816+
info['hf_revision'] = first_line
817+
break
818+
except Exception:
819+
pass
820+
# HuggingFace: check .huggingface/download_metadata.json (older format)
775821
meta = mp / '.huggingface' / 'download_metadata.json'
776822
if meta.exists():
777823
try:

0 commit comments

Comments
 (0)