aetherscan 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,206 @@
1
+ """
2
+ Fail-early save-tag dedup guards.
3
+
4
+ Reusing a save_tag silently mixes a new run's artifacts, config JSON, and DB rows with a
5
+ previous run's — the stale-artifact confusion the cluster runbook used to work around by
6
+ manually incrementing test_vNN tags. These guards hard-stop a run at startup (before any
7
+ expensive work or writes) when its resolved save-tag collides with a completed run's state,
8
+ while staying out of the way of the two legitimate same-tag flows:
9
+
10
+ - Training retries/relaunches: a run-state manifest (run_state_{tag}.json) marks the tag as
11
+ an in-progress resumable run, so the guard is skipped entirely (PR-04's supersede
12
+ semantics are what make same-tag retries safe).
13
+ - Inference resume: manifest rows in the inference_cadences DB table mark an in-progress
14
+ streaming run. Only a completed run — evidenced by its saved config_{tag}.json, written at
15
+ the very end of a successful pass — or stale legacy-path DB rows count as collisions.
16
+
17
+ Because every resolved save-tag carries a fresh second-resolution {command}_{datetime} stamp,
18
+ a fresh run can't collide by construction; the guards bite only if a completed run's tag is
19
+ deliberately reused, and the resumable-run manifests above exempt the legitimate retry/resume
20
+ flows. --force-tag consciously overrides every guard.
21
+
22
+ enforce_tag_guards() is called from main() immediately before command dispatch
23
+ (post-validation, post-DB-init, pre-any-work).
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import argparse
29
+ import logging
30
+ import os
31
+ import sys
32
+
33
+ from aetherscan.config import get_config
34
+ from aetherscan.db import get_db
35
+ from aetherscan.run_state import STAGE_FINAL_SAVE, load_run_state, run_state_path
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ def find_train_tag_collisions(tag: str) -> list[str]:
41
+ """
42
+ Human-readable descriptions of existing training state that would collide with `tag`:
43
+ the final encoder artifact under model_path, the saved config JSON under output_path,
44
+ and non-superseded training_stats DB rows. Empty list = no collisions.
45
+ """
46
+ config = get_config()
47
+ if config is None:
48
+ raise ValueError("get_config() returned None")
49
+ db = get_db()
50
+ collisions: list[str] = []
51
+
52
+ encoder_path = os.path.join(config.model_path, f"vae_encoder_{tag}.keras")
53
+ if os.path.exists(encoder_path):
54
+ collisions.append(f"model artifact exists: {encoder_path}")
55
+
56
+ config_path = os.path.join(config.output_path, f"config_{tag}.json")
57
+ if os.path.exists(config_path):
58
+ collisions.append(f"saved run config exists: {config_path}")
59
+
60
+ if db is not None:
61
+ rows = db.query_training_stat(tag=tag, columns=["tag"])
62
+ if rows:
63
+ collisions.append(
64
+ f"{len(rows)} non-superseded training_stats DB row(s) carry tag '{tag}'"
65
+ )
66
+
67
+ return collisions
68
+
69
+
70
+ def find_inference_tag_collisions(tag: str) -> list[str]:
71
+ """
72
+ Collisions scoped to what inference writes. A saved config_{tag}.json marks a *completed*
73
+ run under this tag (it is written at the very end of a successful pass) — reusing its tag
74
+ is always a collision. DB rows alone are only a collision on the legacy --test-files path
75
+ (no inference_cadences manifest rows): with manifest rows present, same-tag DB state is
76
+ exactly what the streaming path's resume flow consumes, so it must not be flagged.
77
+ """
78
+ config = get_config()
79
+ if config is None:
80
+ raise ValueError("get_config() returned None")
81
+ db = get_db()
82
+ collisions: list[str] = []
83
+
84
+ config_path = os.path.join(config.output_path, f"config_{tag}.json")
85
+ if os.path.exists(config_path):
86
+ collisions.append(f"saved run config exists (completed run marker): {config_path}")
87
+
88
+ if db is not None and not db.query_inference_cadences(tag=tag, columns=["tag"]):
89
+ rows = db.query_inference_result(tag=tag, columns=["tag"])
90
+ if rows:
91
+ collisions.append(
92
+ f"{len(rows)} non-superseded inference_results DB row(s) carry tag '{tag}' "
93
+ f"(no resumable run manifest)"
94
+ )
95
+
96
+ return collisions
97
+
98
+
99
+ def _exit_on_collisions(tag: str, collisions: list[str], resume_hint: str) -> None:
100
+ """Log the collision list plus the user's options, then hard-exit."""
101
+ logger.error("=" * 60)
102
+ logger.error(f"--save-tag '{tag}' collides with existing state from a previous run:")
103
+ for collision in collisions:
104
+ logger.error(f" - {collision}")
105
+ logger.error("Options:")
106
+ logger.error(" - pick a new --save-tag")
107
+ logger.error(f" - {resume_hint}")
108
+ logger.error(" - pass --force-tag to consciously override this guard")
109
+ logger.error("=" * 60)
110
+ sys.exit(1)
111
+
112
+
113
+ def _guard_hf_tag(tag: str, force: bool) -> None:
114
+ """
115
+ HF-side dedup guard, run at startup when --hf-upload is enabled so a tag collision on the
116
+ Hub surfaces now, not after ~30 h of training. A failed check (network/auth) only warns:
117
+ the upload stage itself is non-critical, so its guard must not block training either.
118
+ """
119
+ config = get_config()
120
+ if config is None:
121
+ raise ValueError("get_config() returned None")
122
+ repo_id = config.hf.repo_id
123
+ from aetherscan.hf_hub import hf_tag_exists # noqa: PLC0415
124
+
125
+ try:
126
+ exists = hf_tag_exists(repo_id, tag)
127
+ except Exception as e:
128
+ logger.warning(f"Could not check HF repo '{repo_id}' for tag collisions ({e}) — proceeding")
129
+ return
130
+ if not exists:
131
+ return
132
+ if force:
133
+ logger.warning(
134
+ f"Tag '{tag}' already exists on HF repo '{repo_id}' — --force-tag set, the "
135
+ f"upload stage will move it to the new commit"
136
+ )
137
+ return
138
+ logger.error("=" * 60)
139
+ logger.error(f"--hf-upload is enabled but tag '{tag}' already exists on HF repo '{repo_id}'.")
140
+ logger.error("Options: pick a new --save-tag, or pass --force-tag to move the HF tag.")
141
+ logger.error("=" * 60)
142
+ sys.exit(1)
143
+
144
+
145
+ def enforce_tag_guards(args: argparse.Namespace) -> None:
146
+ """
147
+ Fail-early save-tag dedup guards, dispatched by mode (see module docstring). Reads the
148
+ post-apply config singleton for the effective tag/force values and `args` only to learn
149
+ whether --save-tag was explicitly provided (default datetime tags are immune by
150
+ construction, so the local guards are skipped for them).
151
+ """
152
+ config = get_config()
153
+ if config is None:
154
+ raise ValueError("get_config() returned None")
155
+ tag = config.checkpoint.save_tag
156
+ force = bool(config.checkpoint.force_tag)
157
+ explicit = getattr(args, "save_tag", None) is not None
158
+ command = getattr(args, "command", None)
159
+
160
+ if command == "train":
161
+ if explicit:
162
+ manifest_path = run_state_path(config.output_path, tag)
163
+ # Only an UNFINISHED run's manifest exempts the collision guard. The manifest
164
+ # persists on success, so a completed run's manifest must NOT keep disabling dedup
165
+ # for that tag — otherwise a reused --save-tag would silently overwrite a finished
166
+ # model with no warning (TG-1). "Unfinished" = final_save not yet done, or a
167
+ # recorded non-critical stage failure still pending retry.
168
+ state = load_run_state(manifest_path)
169
+ resumable = state is not None and (
170
+ not state.is_stage_done(STAGE_FINAL_SAVE) or bool(state.stages_failed)
171
+ )
172
+ if resumable:
173
+ logger.info(
174
+ f"Run-state manifest at {manifest_path} marks an unfinished run — tag "
175
+ f"'{tag}' is resumable, skipping the collision guard"
176
+ )
177
+ else:
178
+ collisions = find_train_tag_collisions(tag)
179
+ if collisions and force:
180
+ logger.warning(
181
+ f"--force-tag set: proceeding despite {len(collisions)} "
182
+ f"collision(s) on tag '{tag}'"
183
+ )
184
+ elif collisions:
185
+ _exit_on_collisions(
186
+ tag,
187
+ collisions,
188
+ "to resume the previous run, re-run its identical command "
189
+ "(resume requires its run-state manifest)",
190
+ )
191
+ if config.hf.upload_after_training:
192
+ _guard_hf_tag(tag, force)
193
+
194
+ elif command == "inference" and explicit:
195
+ collisions = find_inference_tag_collisions(tag)
196
+ if collisions and force:
197
+ logger.warning(
198
+ f"--force-tag set: proceeding despite {len(collisions)} collision(s) on tag '{tag}'"
199
+ )
200
+ elif collisions:
201
+ _exit_on_collisions(
202
+ tag,
203
+ collisions,
204
+ "an in-progress run (manifest rows, no saved config JSON) resumes by "
205
+ "re-running its identical command",
206
+ )