Arpan Tripathi
optimise validator competition dataset preparation pipeline to remove redundant recalculations (#183)
6b47413 unverified Download scripts/competition_dataset.py from silvanus0930/vidaio-subnet: direct link, hf CLI and curl.
- Browser
- Download file 15.4 kB
-
https://huggingface.co/silvanus0930/vidaio-subnet/resolve/main/scripts/competition_dataset.py
- Command line
-
hf download hf://silvanus0930/vidaio-subnet/scripts/competition_dataset.py
-
curl -L -o competition_dataset.py https://huggingface.co/silvanus0930/vidaio-subnet/resolve/main/scripts/competition_dataset.py
15.4 kB
| #!/usr/bin/env python3 | |
| """Prepare, validate, upload, and seal a competition evaluation dataset.""" | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| import tempfile | |
| import types | |
| from datetime import datetime, timezone | |
| from pathlib import Path, PurePosixPath | |
| from typing import Literal | |
| from pydantic import BaseModel, ConfigDict, Field | |
| ROOT = Path(__file__).resolve().parents[1] | |
| if str(ROOT) not in sys.path: | |
| sys.path.insert(0, str(ROOT)) | |
| # Import only the competition package when this file is executed directly. The | |
| # repository's top-level package eagerly imports the validator stack, which is | |
| # unrelated to dataset preparation and may not be installed in an operator CLI | |
| # environment. | |
| for package_name, package_path in ( | |
| ("vidaio_subnet_core", ROOT / "vidaio_subnet_core"), | |
| ("vidaio_subnet_core.competition", ROOT / "vidaio_subnet_core" / "competition"), | |
| ): | |
| if package_name not in sys.modules: | |
| package = types.ModuleType(package_name) | |
| package.__path__ = [str(package_path)] | |
| sys.modules[package_name] = package | |
| from vidaio_subnet_core.competition.config import ( # noqa: E402 | |
| CompetitionManifest, | |
| load_manifest, | |
| ) | |
| from vidaio_subnet_core.competition.dataset import ( # noqa: E402 | |
| DatasetError, | |
| DatasetValidationIssue, | |
| EvaluationIndex, | |
| ModalVolumeStore, | |
| format_validation_issues, | |
| prepare_index_candidates, | |
| ) | |
| from vidaio_subnet_core.competition.repository import ( # noqa: E402 | |
| CompetitionRepository, | |
| ) | |
| def _index(path: str) -> EvaluationIndex: | |
| return EvaluationIndex.model_validate_json(Path(path).read_text(encoding="utf-8")) | |
| def _write_index(path: str, index: EvaluationIndex) -> None: | |
| Path(path).write_text(index.normalized_json() + "\n", encoding="utf-8") | |
| class SourceFileSnapshot(BaseModel): | |
| """Cheap identity check for a source already hashed and probed by prepare.""" | |
| model_config = ConfigDict(extra="forbid", frozen=True) | |
| logical_source_path: str | |
| size_bytes: int = Field(ge=0) | |
| device: int | |
| inode: int | |
| modified_ns: int | |
| changed_ns: int | |
| class DatasetPipelineReceipt(BaseModel): | |
| """Local proof that the required dataset pipeline stages ran in order.""" | |
| model_config = ConfigDict(extra="forbid", frozen=True) | |
| schema_version: Literal[1] = 1 | |
| stage: Literal["prepared", "validated", "uploaded", "sealed"] | |
| manifest_digest: str = Field(pattern=r"^[0-9a-f]{64}$") | |
| index_digest: str = Field(pattern=r"^[0-9a-f]{64}$") | |
| source_root: str | |
| sources: tuple[SourceFileSnapshot, ...] | |
| environment: str | None = None | |
| volume_name: str | None = None | |
| updated_at: datetime | |
| def normalized_json(self) -> str: | |
| return json.dumps( | |
| self.model_dump(mode="json"), | |
| sort_keys=True, | |
| separators=(",", ":"), | |
| ) | |
| def _receipt_path(index_path: str) -> Path: | |
| path = Path(index_path) | |
| return path.with_name(path.name + ".pipeline.json") | |
| def _write_receipt(index_path: str, receipt: DatasetPipelineReceipt) -> None: | |
| destination = _receipt_path(index_path) | |
| with tempfile.NamedTemporaryFile( | |
| mode="w", | |
| encoding="utf-8", | |
| dir=destination.parent, | |
| prefix=destination.name + ".", | |
| suffix=".tmp", | |
| delete=False, | |
| ) as stream: | |
| stream.write(receipt.normalized_json() + "\n") | |
| temporary = Path(stream.name) | |
| temporary.replace(destination) | |
| def _load_receipt(index_path: str) -> DatasetPipelineReceipt: | |
| path = _receipt_path(index_path) | |
| try: | |
| return DatasetPipelineReceipt.model_validate_json( | |
| path.read_text(encoding="utf-8") | |
| ) | |
| except FileNotFoundError as exc: | |
| raise DatasetError( | |
| f"pipeline receipt is missing: {path}; run prepare first" | |
| ) from exc | |
| except Exception as exc: | |
| raise DatasetError(f"pipeline receipt is invalid: {path}: {exc}") from exc | |
| def _source_snapshots( | |
| index: EvaluationIndex, source_root: Path | |
| ) -> tuple[SourceFileSnapshot, ...]: | |
| root = source_root.resolve() | |
| snapshots = [] | |
| logical_paths = sorted({item.logical_source_path for item in index.items}) | |
| for logical_source_path in logical_paths: | |
| parts = PurePosixPath(logical_source_path).parts | |
| if not parts or parts[0] != "inputs": | |
| raise DatasetError( | |
| f"source path must be below inputs/: {logical_source_path}" | |
| ) | |
| path = root.joinpath(*parts[1:]) | |
| try: | |
| stat = path.stat() | |
| except OSError as exc: | |
| raise DatasetError(f"could not stat source file {path}: {exc}") from exc | |
| if not path.is_file(): | |
| raise DatasetError(f"source file is missing: {path}") | |
| snapshots.append( | |
| SourceFileSnapshot( | |
| logical_source_path=logical_source_path, | |
| size_bytes=stat.st_size, | |
| device=stat.st_dev, | |
| inode=stat.st_ino, | |
| modified_ns=stat.st_mtime_ns, | |
| changed_ns=stat.st_ctime_ns, | |
| ) | |
| ) | |
| return tuple(snapshots) | |
| def _require_receipt( | |
| index_path: str, | |
| *, | |
| manifest: CompetitionManifest, | |
| index: EvaluationIndex, | |
| allowed_stages: tuple[str, ...], | |
| required_stage: str, | |
| ) -> DatasetPipelineReceipt: | |
| receipt = _load_receipt(index_path) | |
| if receipt.stage not in allowed_stages: | |
| raise DatasetError( | |
| f"pipeline is at stage {receipt.stage!r}; required prior stage is " | |
| f"{required_stage!r}" | |
| ) | |
| if receipt.manifest_digest != manifest.digest(): | |
| raise DatasetError("pipeline receipt does not match the manifest; rerun prepare") | |
| if receipt.index_digest != index.digest(): | |
| raise DatasetError("pipeline receipt does not match the index; rerun prepare") | |
| return receipt | |
| def _require_unchanged_sources( | |
| receipt: DatasetPipelineReceipt, | |
| index: EvaluationIndex, | |
| source_root: Path, | |
| ) -> tuple[SourceFileSnapshot, ...]: | |
| resolved_root = str(source_root.resolve()) | |
| if receipt.source_root != resolved_root: | |
| raise DatasetError( | |
| "source directory does not match the prepared dataset: " | |
| f"prepared={receipt.source_root} current={resolved_root}" | |
| ) | |
| snapshots = _source_snapshots(index, source_root) | |
| if snapshots != receipt.sources: | |
| raise DatasetError( | |
| "one or more source files changed after preparation; rerun prepare" | |
| ) | |
| return snapshots | |
| def _advance_receipt( | |
| receipt: DatasetPipelineReceipt, | |
| stage: Literal["validated", "uploaded", "sealed"], | |
| *, | |
| environment: str | None = None, | |
| volume_name: str | None = None, | |
| ) -> DatasetPipelineReceipt: | |
| return receipt.model_copy( | |
| update={ | |
| "stage": stage, | |
| "environment": environment, | |
| "volume_name": volume_name, | |
| "updated_at": datetime.now(timezone.utc), | |
| } | |
| ) | |
| def _add_yes_argument(parser: argparse.ArgumentParser) -> None: | |
| parser.add_argument( | |
| "--yes", | |
| action="store_true", | |
| help="disqualify flagged entries without interactive confirmation", | |
| ) | |
| def _prompt_yes_no(question: str, *, assume_yes: bool) -> bool: | |
| if assume_yes: | |
| print(question + "yes", file=sys.stderr) | |
| return True | |
| while True: | |
| try: | |
| response = input(question).strip().lower() | |
| except EOFError: | |
| print("no", file=sys.stderr) | |
| return False | |
| if response == "yes": | |
| return True | |
| if response == "no": | |
| return False | |
| print("Please type yes or no.", file=sys.stderr) | |
| def _review_issues( | |
| index: EvaluationIndex | None, | |
| issues: tuple[DatasetValidationIssue, ...], | |
| *, | |
| assume_yes: bool, | |
| index_path: str, | |
| ) -> EvaluationIndex: | |
| if issues: | |
| print(format_validation_issues(issues), file=sys.stderr) | |
| count = sum(len(issue.evaluation_ids) for issue in issues) | |
| question = ( | |
| f"Disqualify {count} evaluation entry/entries from {len(issues)} " | |
| "source(s) and continue prepare? Type yes or no: " | |
| ) | |
| if not _prompt_yes_no(question, assume_yes=assume_yes): | |
| raise DatasetError( | |
| "operator declined to continue prepare with flagged entries" | |
| ) | |
| if index is None: | |
| raise DatasetError("all evaluation sources are invalid") | |
| if issues: | |
| print( | |
| f"Disqualified {count} evaluation entry/entries; " | |
| f"{len(index.items)} remain.", | |
| file=sys.stderr, | |
| ) | |
| _write_index(index_path, index) | |
| return index | |
| def ensure_manifest_registered( | |
| repository: CompetitionRepository, | |
| manifest: CompetitionManifest, | |
| *, | |
| now: datetime, | |
| actor: str, | |
| ) -> bool: | |
| """Register a missing manifest without silently replacing an existing one.""" | |
| existing = repository.get(manifest.competition_id) | |
| if existing is None: | |
| repository.insert_manifest(manifest, now=now, actor=actor) | |
| return True | |
| if existing.manifest_digest != manifest.digest(): | |
| raise RuntimeError( | |
| "database contains a different manifest revision for " | |
| f"{manifest.competition_id}; use the validator's registered manifest" | |
| ) | |
| return False | |
| def main() -> int: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| subparsers = parser.add_subparsers(dest="command", required=True) | |
| commands = {} | |
| for name in ("prepare", "validate", "upload"): | |
| command = commands[name] = subparsers.add_parser(name) | |
| command.add_argument("--manifest", required=True) | |
| command.add_argument("--source-dir", required=True) | |
| command.add_argument("--index", required=True) | |
| _add_yes_argument(commands["prepare"]) | |
| upload = commands["upload"] | |
| upload.add_argument("--environment", default="main") | |
| seal = subparsers.add_parser("seal") | |
| seal.add_argument("--manifest", required=True) | |
| seal.add_argument("--index", required=True) | |
| seal.add_argument("--environment", default="main") | |
| seal.add_argument("--database-url", default="sqlite:///video_subnet_validator.db") | |
| seal.add_argument("--actor", default="competition-dataset-cli") | |
| args = parser.parse_args() | |
| manifest = load_manifest(args.manifest) | |
| if args.command == "prepare": | |
| evaluation_index, issues = prepare_index_candidates( | |
| manifest, Path(args.source_dir) | |
| ) | |
| evaluation_index = _review_issues( | |
| evaluation_index, | |
| issues, | |
| assume_yes=args.yes, | |
| index_path=args.index, | |
| ) | |
| receipt = DatasetPipelineReceipt( | |
| stage="prepared", | |
| manifest_digest=manifest.digest(), | |
| index_digest=evaluation_index.digest(), | |
| source_root=str(Path(args.source_dir).resolve()), | |
| sources=_source_snapshots( | |
| evaluation_index, | |
| Path(args.source_dir), | |
| ), | |
| updated_at=datetime.now(timezone.utc), | |
| ) | |
| _write_receipt(args.index, receipt) | |
| print( | |
| f"Prepared {len(evaluation_index.items)} item(s); " | |
| f"index checksum={evaluation_index.digest()}; " | |
| f"pipeline receipt={_receipt_path(args.index)}" | |
| ) | |
| return 0 | |
| evaluation_index = _index(args.index) | |
| if args.command == "validate": | |
| receipt = _require_receipt( | |
| args.index, | |
| manifest=manifest, | |
| index=evaluation_index, | |
| allowed_stages=("prepared", "validated"), | |
| required_stage="prepared", | |
| ) | |
| _require_unchanged_sources( | |
| receipt, | |
| evaluation_index, | |
| Path(args.source_dir), | |
| ) | |
| evaluation_index.validate_for_manifest(manifest) | |
| _write_receipt( | |
| args.index, | |
| _advance_receipt(receipt, "validated"), | |
| ) | |
| print( | |
| f"Validated {len(evaluation_index.items)} item(s); " | |
| f"index checksum={evaluation_index.digest()}" | |
| ) | |
| return 0 | |
| if args.command == "upload": | |
| receipt = _require_receipt( | |
| args.index, | |
| manifest=manifest, | |
| index=evaluation_index, | |
| allowed_stages=("validated", "uploaded"), | |
| required_stage="validated", | |
| ) | |
| _require_unchanged_sources( | |
| receipt, | |
| evaluation_index, | |
| Path(args.source_dir), | |
| ) | |
| if receipt.stage == "uploaded" and ( | |
| receipt.environment != args.environment | |
| or receipt.volume_name != manifest.evaluation_input_volume_name | |
| ): | |
| raise DatasetError( | |
| "dataset was already uploaded to a different environment or Volume" | |
| ) | |
| store = ModalVolumeStore(environment_name=args.environment) | |
| store.upload( | |
| manifest, | |
| evaluation_index, | |
| Path(args.source_dir), | |
| validate_local=False, | |
| ) | |
| _write_receipt( | |
| args.index, | |
| _advance_receipt( | |
| receipt, | |
| "uploaded", | |
| environment=args.environment, | |
| volume_name=manifest.evaluation_input_volume_name, | |
| ), | |
| ) | |
| print( | |
| f"Uploaded and read-back verified {len(evaluation_index.items)} item(s) " | |
| f"to {manifest.evaluation_input_volume_name}" | |
| ) | |
| return 0 | |
| receipt = _require_receipt( | |
| args.index, | |
| manifest=manifest, | |
| index=evaluation_index, | |
| allowed_stages=("uploaded", "sealed"), | |
| required_stage="uploaded", | |
| ) | |
| if ( | |
| receipt.environment != args.environment | |
| or receipt.volume_name != manifest.evaluation_input_volume_name | |
| ): | |
| raise DatasetError( | |
| "upload receipt does not match the requested environment and Volume" | |
| ) | |
| evaluation_index.validate_for_manifest(manifest) | |
| store = ModalVolumeStore(environment_name=args.environment) | |
| remote_index = store.load_index(manifest, validate_for_manifest=False) | |
| if remote_index.digest() != evaluation_index.digest(): | |
| raise RuntimeError("remote and local evaluation indexes differ") | |
| repository = CompetitionRepository(args.database_url) | |
| now = datetime.now(timezone.utc) | |
| registered = ensure_manifest_registered( | |
| repository, | |
| manifest, | |
| now=now, | |
| actor=args.actor, | |
| ) | |
| digest = repository.seal_evaluation_dataset( | |
| manifest.competition_id, | |
| evaluation_index, | |
| now=now, | |
| actor=args.actor, | |
| ) | |
| _write_receipt( | |
| args.index, | |
| _advance_receipt( | |
| receipt, | |
| "sealed", | |
| environment=args.environment, | |
| volume_name=manifest.evaluation_input_volume_name, | |
| ), | |
| ) | |
| if registered: | |
| print( | |
| f"Registered {manifest.competition_id} as SCHEDULED in {args.database_url}" | |
| ) | |
| print( | |
| f"Sealed {len(evaluation_index.items)} item(s) for " | |
| f"{manifest.competition_id}; index checksum={digest}" | |
| ) | |
| return 0 | |
| if __name__ == "__main__": | |
| try: | |
| raise SystemExit(main()) | |
| except DatasetError as exc: | |
| print(f"Dataset validation failed:\n{exc}", file=sys.stderr) | |
| raise SystemExit(2) from None | |