EviSuff-EvidencePlanner-8B / scripts /validate_release.py
Adonis3039's picture
Upload 19 files
f0630f0 verified
Raw History Blame Contribute Delete
4.96 kB
#!/usr/bin/env python3
"""Validate the staged EviSuff model release without loading Qwen3-8B."""
from __future__ import annotations
import hashlib
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
ADAPTERS = ("answer-sft", "no-gate", "full-evisuff")
EXPECTED_BASE = "Qwen/Qwen3-8B"
EXPECTED_TENSORS = 504
EXPECTED_PARAMETERS = 174_587_904
TEXT_SUFFIXES = {".md", ".json", ".txt", ".py", ".cff", ".gitattributes"}
FORBIDDEN_NAMES = {
".env",
"optimizer.pt",
"scheduler.pt",
"rng_state.pth",
"training_args.bin",
"trainer_state.json",
}
PRIVATE_PATH = re.compile(r"/(?:data1|data2|home|root)/")
SECRET = re.compile(r"(?:sk|hf|jina)_[A-Za-z0-9_-]{20,}")
def fail(message: str, errors: list[str]) -> None:
errors.append(message)
def main() -> int:
errors: list[str] = []
required = {
"README.md",
"LICENSE_PENDING.md",
"BASE_MODEL_LICENSE",
"CITATION.cff",
"adapter_stack.json",
"requirements.txt",
".gitattributes",
}
for name in sorted(required):
if not (ROOT / name).is_file():
fail(f"missing required file: {name}", errors)
for path in ROOT.rglob("*"):
if not path.is_file():
continue
if path.name in FORBIDDEN_NAMES or path.suffix in {".bin", ".pt", ".pth", ".pkl"}:
fail(f"forbidden training artifact: {path.relative_to(ROOT)}", errors)
if path.suffix in TEXT_SUFFIXES or path.name in {"BASE_MODEL_LICENSE", "requirements.txt"}:
text = path.read_text(encoding="utf-8", errors="replace")
if PRIVATE_PATH.search(text):
fail(f"private absolute path in {path.relative_to(ROOT)}", errors)
if SECRET.search(text):
fail(f"possible secret in {path.relative_to(ROOT)}", errors)
try:
from safetensors import safe_open
except ImportError:
fail("safetensors is required to validate tensor metadata", errors)
else:
for adapter in ADAPTERS:
directory = ROOT / adapter
config_path = directory / "adapter_config.json"
weight_path = directory / "adapter_model.safetensors"
if not config_path.is_file() or not weight_path.is_file():
fail(f"missing adapter files for {adapter}", errors)
continue
config = json.loads(config_path.read_text(encoding="utf-8"))
if config.get("base_model_name_or_path") != EXPECTED_BASE:
fail(f"unexpected base model in {adapter}/adapter_config.json", errors)
if config.get("r") != 64 or config.get("lora_alpha") != 128:
fail(f"unexpected LoRA rank/alpha in {adapter}", errors)
tensor_count = 0
parameter_count = 0
with safe_open(weight_path, framework="pt", device="cpu") as handle:
metadata = handle.metadata() or {}
if metadata.get("format") != "pt":
fail(f"unexpected safetensors format metadata in {adapter}", errors)
for key in handle.keys():
tensor = handle.get_slice(key)
tensor_count += 1
size = 1
for dim in tensor.get_shape():
size *= dim
parameter_count += size
if tensor_count != EXPECTED_TENSORS:
fail(f"{adapter} has {tensor_count} tensors, expected {EXPECTED_TENSORS}", errors)
if parameter_count != EXPECTED_PARAMETERS:
fail(
f"{adapter} has {parameter_count} parameters, expected {EXPECTED_PARAMETERS}",
errors,
)
stack = ROOT / "adapter_stack.json"
if stack.is_file():
data = json.loads(stack.read_text(encoding="utf-8"))
if data.get("stacks", {}).get("full-evisuff") != ["answer-sft", "full-evisuff"]:
fail("full-evisuff loading order is incorrect", errors)
if data.get("stacks", {}).get("no-gate") != ["answer-sft", "no-gate"]:
fail("no-gate loading order is incorrect", errors)
checksums = ROOT / "checksums.sha256"
if checksums.is_file():
for line in checksums.read_text(encoding="utf-8").splitlines():
digest, relative = line.split(" ", 1)
path = ROOT / relative
if not path.is_file():
fail(f"checksum target is missing: {relative}", errors)
continue
actual = hashlib.sha256(path.read_bytes()).hexdigest()
if actual != digest:
fail(f"checksum mismatch: {relative}", errors)
if errors:
print("Release validation failed:")
for error in errors:
print(f"- {error}")
return 1
print("Release validation passed.")
return 0
if __name__ == "__main__":
sys.exit(main())