Skip to content

Commit a48bc44

Browse files
authored
Merge pull request #168 from BitMind-AI/fix/parallel-reader-legacy-resume
Support audited checkpoint-reader upgrades for existing runs
2 parents 5219956 + c7bab88 commit a48bc44

4 files changed

Lines changed: 57 additions & 0 deletions

File tree

‎pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ where = ["src"]
6363
"" = "src"
6464

6565
[tool.setuptools.package-data]
66+
"gasbench" = ["checkpoint_compatibility.json"]
6667
"gasbench.dataset" = ["configs/*.yaml"]
6768

6869
[tool.black]

‎src/gasbench/benchmarks/common.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,20 @@ def fingerprint_files(paths, root):
275275
return digest.hexdigest()
276276

277277

278+
def compatible_evaluator_identity(evaluator_dir, current, previous):
279+
"""Allow only explicitly audited, exact-build checkpoint reader upgrades."""
280+
if current == previous:
281+
return True
282+
registry = Path(evaluator_dir) / "checkpoint_compatibility.json"
283+
if not registry.is_file():
284+
return False
285+
try:
286+
pairs = json.loads(registry.read_text())
287+
except (OSError, ValueError):
288+
return False
289+
return isinstance(pairs, dict) and isinstance(pairs.get(current), list) and previous in pairs[current]
290+
291+
278292
def runtime_versions():
279293
versions = {
280294
"python": platform.python_version(),
@@ -461,6 +475,12 @@ def create_tracker(
461475
context = json.loads(json.dumps(context))
462476
if saved is not None:
463477
previous = saved.get("benchmark", {})
478+
if compatible_evaluator_identity(
479+
evaluator_dir, context["evaluator_sha256"], previous.get("evaluator_sha256")
480+
):
481+
# Preserve the original manifest identity for both restored and new batches.
482+
# Every other context field is still compared below without exceptions.
483+
context["evaluator_sha256"] = previous["evaluator_sha256"]
464484
if {k: v for k, v in previous.items() if k != "samples"} != context:
465485
raise CheckpointError(
466486
"Checkpoint configuration or model differs from this run"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"cefcbe4740db068e9e34d3f4184cbde9d65b441482bc2ecdbcfeb98584634288": [
3+
"ec5643b53236c144c9ed296f653a1dd39b852ee37ce4feaefa2179650b1ad2cb"
4+
]
5+
}

‎tests/unit/test_benchmark_resume.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,34 @@ def test_augmentation_cache_cannot_change_across_attempts(benchmark, tmp_path):
366366
path.write_bytes(b"changed")
367367
with pytest.raises(CheckpointError, match="augmentation cache"):
368368
b.run(Session(b.model_dir), n_aug_per_dataset=3, aug_cache_dir=str(aug_dir))
369+
370+
371+
@pytest.mark.parametrize("change", ["reader", "unapproved-evaluator", "model"])
372+
def test_audited_reader_upgrade_preserves_resume_guards(benchmark, monkeypatch, tmp_path, change):
373+
b = benchmark
374+
original_fingerprint = common.fingerprint_files
375+
evaluator_root = Path(common.__file__).resolve().parents[1]
376+
identity = ["old"]
377+
378+
def fingerprint(paths, root):
379+
return identity[0] if root == evaluator_root else original_fingerprint(paths, root)
380+
381+
monkeypatch.setattr(common, "fingerprint_files", fingerprint)
382+
(tmp_path / "checkpoint_compatibility.json").write_text(json.dumps({"new": ["old"]}))
383+
compatible = common.compatible_evaluator_identity
384+
monkeypatch.setattr(common, "compatible_evaluator_identity", lambda root, current, previous: compatible(tmp_path, current, previous))
385+
with pytest.raises(Interrupted):
386+
b.run(Session(b.model_dir, stop_after=2))
387+
identity[0] = "unapproved" if change == "unapproved-evaluator" else "new"
388+
if change == "model":
389+
(b.model_dir / "model.py").write_text("changed inference implementation")
390+
resumed = Session(b.model_dir)
391+
if change != "reader":
392+
with pytest.raises(CheckpointError, match="differs"):
393+
b.run(resumed)
394+
assert not resumed.calls
395+
else:
396+
b.run(resumed)
397+
assert len(resumed.calls) == 3
398+
assert RecorderCheckpoint.read_manifest(b.checkpoint)["benchmark"]["evaluator_sha256"] == "old"
399+
assert not compatible(tmp_path, "new", "unrelated-old")

0 commit comments

Comments
 (0)