FlowMo-WM / verify.py
cccat6's picture
Complete FlowMo reproducibility materials and record input provenance
eb4fc81 verified
Raw History Blame Contribute Delete
4.07 kB
"""Check a downloaded release without training or evaluating a model."""
from pathlib import Path
import argparse
import ast
import hashlib
import importlib
import json
import sys
import numpy as np
def sha256(path):
h = hashlib.sha256()
with path.open('rb') as f:
for block in iter(lambda: f.read(4194304), b''):
h.update(block)
return h.hexdigest()
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--package', type=Path, default=Path(__file__).resolve().parent)
p.add_argument('--models', action='store_true', help='Also load every checkpoint on CPU and check its interface and tensors')
p.add_argument('--output', type=Path, help='Optional external JSON receipt')
a = p.parse_args()
root = a.package.resolve()
manifest = json.loads((root / 'MANIFEST.json').read_text())
errors = []
for row in manifest['files']:
path = root / row['path']
if root not in path.resolve().parents:
errors.append('Path escapes release: ' + row['path']); continue
if not path.is_file():
errors.append('Missing: ' + row['path']); continue
if path.stat().st_size != row['bytes'] or sha256(path) != row['sha256']:
errors.append('Release content differs: ' + row['path'])
if path.suffix == '.py':
try:
ast.parse(path.read_text())
except SyntaxError as error:
errors.append(f'{row["path"]}: {error}')
data = json.loads((root / 'data/manifest.json').read_text())
statuses = {}
for row in data['datasets']:
statuses[row['status']] = statuses.get(row['status'], 0) + 1
with np.load(root / row['path'], allow_pickle=False) as arrays:
schema = {key: {'shape': list(arrays[key].shape), 'dtype': str(arrays[key].dtype)} for key in arrays.files if key != 'metadata'}
if schema != row['arrays']:
errors.append('Dataset schema differs: ' + row['path'])
for key in arrays.files:
value = arrays[key]
if np.issubdtype(value.dtype, np.number) and not np.isfinite(value).all():
errors.append('Non-finite dataset array: ' + row['path'] + ':' + key)
model_count = 0
if a.models:
import torch
torch.set_num_threads(1)
sys.path.insert(0, str(root))
for row in manifest['files']:
path = root / row['path']
if path.suffix != '.pt':
continue
method = Path(row['path']).parts[1]
config = importlib.import_module(f'experiments.{method}.src.config').default_config()
model = importlib.import_module(f'experiments.{method}.src.model').build_model(config)
state = torch.load(path, map_location='cpu', weights_only=True)
try:
model.load_state_dict(state, strict=True)
except RuntimeError as error:
errors.append(f'Checkpoint interface: {row["path"]}: {error}')
if not all(torch.isfinite(t).all() for t in state.values()):
errors.append('Non-finite checkpoint: ' + row['path'])
model_count += 1
result = {'passed': not errors, 'files_checked': len(manifest['files']),
'datasets_checked': len(data['datasets']), 'dataset_provenance_counts': statuses,
'checkpoints_loaded_on_cpu': model_count, 'models_executed': False,
'release_integrity_only': True, 'errors': errors}
if a.output:
output = a.output.resolve()
supplied = {str((root / r['path']).resolve()) for r in manifest['files']}
if str(output) in supplied or output == root / 'MANIFEST.json':
raise ValueError('Receipt must not overwrite a supplied file')
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(result, indent=2) + '\n')
print(json.dumps(result, indent=2))
if errors:
raise SystemExit(1)
if __name__ == '__main__':
main()