eyad-silx commited on
Commit
70d484a
Β·
verified Β·
1 Parent(s): 3c5fad3

push artifacts/scripts/hf_push.py

Browse files
Files changed (1) hide show
  1. artifacts/scripts/hf_push.py +47 -13
artifacts/scripts/hf_push.py CHANGED
@@ -2,19 +2,23 @@
2
  """Push Ingot (SN97) build-pod artifacts to HF repo eyad-silx/progress.
3
 
4
  Modes: --small (docs/logs/scripts), --big (GGUFs + QAT states), default = both.
5
- Only uploads new/changed files (size+mtime_ns fingerprint), records sha256 in
6
- state for the MANIFEST. State: /root/.hf_push_state.json. Log: /root/hf_push.log.
 
 
 
7
  """
8
- import argparse, glob, hashlib, json, os, sys
9
  from datetime import datetime, timezone
10
  from pathlib import Path
11
  from huggingface_hub import HfApi
12
 
13
  REPO = "eyad-silx/progress"
14
- # Free-plan private-storage cap (~20 GiB) on eyad-silx fills up with 3 GGUFs.
15
  # Files that hit the cap overflow to this private org repo (token has repo.write):
16
  OVERFLOW_REPO = "silx-ai/Progress"
17
  OVERFLOW_PREFIX = "ingot-backup/"
 
18
  TOKEN = Path("/root/.hf_token").read_text().strip()
19
  STATE_P = Path("/root/.hf_push_state.json")
20
  LOG_P = Path("/root/hf_push.log")
@@ -30,10 +34,14 @@ Pushed by `hf_push.py` β€” checks hourly and uploads any new/changed artifact.
30
  - `states/` β€” QAT state checkpoints (`t0..tN.safetensors`)
31
  - `artifacts/` β€” `HANDOFF.md`, build/train/eval scripts, run logs, sample draws,
32
  measurement results (`measure*.jsonl`, `pick*.jsonl`, `drawn.jsonl`)
33
- - `MANIFEST.md` β€” size + sha256 + upload time of every artifact (regenerated each push)
 
 
34
  - `PROGRESS.md` β€” push history
35
 
36
  The GGUFs are ~6.8 GB each (PQ2_0 quantized 27B linear-attention models).
 
 
37
  """
38
 
39
  def now():
@@ -97,7 +105,15 @@ def main():
97
  else:
98
  tracks = small_tracks() + big_tracks()
99
 
 
 
 
 
 
 
 
100
  state = load_state()
 
101
  api = HfApi(token=TOKEN)
102
  results = []
103
  for local, repo in tracks:
@@ -106,9 +122,10 @@ def main():
106
  st = os.stat(local)
107
  fp = f"{st.st_size}:{st.st_mtime_ns}"
108
  prev = state.get(repo)
109
- if prev and prev["fp"] == fp:
110
  results.append((repo, "unchanged"))
111
  continue
 
112
  try:
113
  d = sha256(local)
114
  dest = REPO
@@ -116,8 +133,7 @@ def main():
116
  api.upload_file(path_or_fileobj=local, path_in_repo=repo, repo_id=REPO,
117
  repo_type="model", commit_message=f"push {repo}")
118
  except Exception as e1:
119
- msg = str(e1)
120
- if "storage limit" not in msg.lower():
121
  raise
122
  api.upload_file(path_or_fileobj=local, path_in_repo=OVERFLOW_PREFIX + repo,
123
  repo_id=OVERFLOW_REPO, repo_type="model",
@@ -125,24 +141,41 @@ def main():
125
  dest = OVERFLOW_REPO
126
  log("overflowed", local=local, repo=repo, to=OVERFLOW_REPO, sha256=d, size=st.st_size)
127
  state[repo] = {"fp": fp, "sha256": d, "size": st.st_size, "uploaded_at": now(), "dest": dest}
 
 
128
  save_state(state)
129
  if dest == REPO:
130
  log("uploaded", local=local, repo=repo, sha256=d, size=st.st_size)
131
  results.append((repo, "uploaded"))
132
  except Exception as e:
133
- log("error", local=local, repo=repo, err=repr(e)[:500])
 
 
 
 
 
 
134
  results.append((repo, "FAILED"))
135
 
136
  # regenerate docs (manifest / progress / readme) from state
137
  try:
138
  manifest = ["# Manifest β€” eyad-silx/progress (+ overflow repo silx-ai/Progress)", "",
139
- "Files with `stored in` = silx-ai/Progress live at `ingot-backup/<repo path>` in that repo",
140
- "(eyad-silx free-plan private-storage cap ~20 GiB; overflow keeps everything private).", "",
 
 
141
  "| repo path | size (bytes) | sha256 | uploaded (UTC) | stored in |",
142
  "|---|---|---|---|---|"]
143
- for repo in sorted(state):
144
  v = state[repo]
145
  manifest.append(f"| `{repo}` | {v['size']:,} | `{v['sha256']}` | {v['uploaded_at']} | {v.get('dest', REPO)} |")
 
 
 
 
 
 
 
146
  Path("/tmp/MANIFEST.md").write_text("\n".join(manifest) + "\n")
147
  api.upload_file(path_or_fileobj="/tmp/MANIFEST.md", path_in_repo="MANIFEST.md",
148
  repo_id=REPO, repo_type="model", commit_message="update MANIFEST.md")
@@ -163,7 +196,8 @@ def main():
163
  n_up = sum(1 for r in results if r[1] == "uploaded")
164
  n_un = sum(1 for r in results if r[1] == "unchanged")
165
  n_fail = sum(1 for r in results if r[1] == "FAILED")
166
- log("run_done", uploaded=n_up, unchanged=n_un, failed=n_fail)
 
167
  sys.exit(1 if n_fail else 0)
168
 
169
  if __name__ == "__main__":
 
2
  """Push Ingot (SN97) build-pod artifacts to HF repo eyad-silx/progress.
3
 
4
  Modes: --small (docs/logs/scripts), --big (GGUFs + QAT states), default = both.
5
+ Only uploads new/changed files (size+mtime_ns fingerprint). Files rejected by the
6
+ free-plan private-storage cap are retried on the overflow repo (silx-ai/Progress,
7
+ org quota) and, if that also fails, recorded as pending with sha256 in the
8
+ MANIFEST (bytes stay on the pod + local rescue cache).
9
+ State: /root/.hf_push_state.json. Log: /root/hf_push.log.
10
  """
11
+ import argparse, fcntl, glob, hashlib, json, os, sys
12
  from datetime import datetime, timezone
13
  from pathlib import Path
14
  from huggingface_hub import HfApi
15
 
16
  REPO = "eyad-silx/progress"
17
+ # Free-plan private-storage caps (~20 GiB user eyad-silx, org silx-ai also full).
18
  # Files that hit the cap overflow to this private org repo (token has repo.write):
19
  OVERFLOW_REPO = "silx-ai/Progress"
20
  OVERFLOW_PREFIX = "ingot-backup/"
21
+
22
  TOKEN = Path("/root/.hf_token").read_text().strip()
23
  STATE_P = Path("/root/.hf_push_state.json")
24
  LOG_P = Path("/root/hf_push.log")
 
34
  - `states/` β€” QAT state checkpoints (`t0..tN.safetensors`)
35
  - `artifacts/` β€” `HANDOFF.md`, build/train/eval scripts, run logs, sample draws,
36
  measurement results (`measure*.jsonl`, `pick*.jsonl`, `drawn.jsonl`)
37
+ - `MANIFEST.md` β€” size + sha256 + upload time of every artifact (regenerated each push),
38
+ including a "Pending" section for files blocked by the free-plan private-storage cap
39
+ (their bytes are preserved on the build pod and in a local rescue cache)
40
  - `PROGRESS.md` β€” push history
41
 
42
  The GGUFs are ~6.8 GB each (PQ2_0 quantized 27B linear-attention models).
43
+ Note: chal-identity's GGUF is byte-identical to king-ix's (sha256 in MANIFEST) β€” the
44
+ identity refit test round-trips the king exactly.
45
  """
46
 
47
  def now():
 
105
  else:
106
  tracks = small_tracks() + big_tracks()
107
 
108
+ lockf = open("/root/.hf_push.lock", "w")
109
+ try:
110
+ fcntl.flock(lockf, fcntl.LOCK_EX | fcntl.LOCK_NB)
111
+ except BlockingIOError:
112
+ log("skipped", reason="another push run in progress")
113
+ sys.exit(0)
114
+
115
  state = load_state()
116
+ pending = state.get("_pending", {})
117
  api = HfApi(token=TOKEN)
118
  results = []
119
  for local, repo in tracks:
 
122
  st = os.stat(local)
123
  fp = f"{st.st_size}:{st.st_mtime_ns}"
124
  prev = state.get(repo)
125
+ if prev and prev.get("fp") == fp:
126
  results.append((repo, "unchanged"))
127
  continue
128
+ d = None
129
  try:
130
  d = sha256(local)
131
  dest = REPO
 
133
  api.upload_file(path_or_fileobj=local, path_in_repo=repo, repo_id=REPO,
134
  repo_type="model", commit_message=f"push {repo}")
135
  except Exception as e1:
136
+ if "storage limit" not in str(e1).lower():
 
137
  raise
138
  api.upload_file(path_or_fileobj=local, path_in_repo=OVERFLOW_PREFIX + repo,
139
  repo_id=OVERFLOW_REPO, repo_type="model",
 
141
  dest = OVERFLOW_REPO
142
  log("overflowed", local=local, repo=repo, to=OVERFLOW_REPO, sha256=d, size=st.st_size)
143
  state[repo] = {"fp": fp, "sha256": d, "size": st.st_size, "uploaded_at": now(), "dest": dest}
144
+ pending.pop(repo, None)
145
+ state["_pending"] = pending
146
  save_state(state)
147
  if dest == REPO:
148
  log("uploaded", local=local, repo=repo, sha256=d, size=st.st_size)
149
  results.append((repo, "uploaded"))
150
  except Exception as e:
151
+ entry = {"fp": fp, "size": st.st_size, "blocked_at": now()}
152
+ if d:
153
+ entry["sha256"] = d
154
+ pending[repo] = entry
155
+ state["_pending"] = pending
156
+ save_state(state)
157
+ log("error", local=local, repo=repo, err=repr(e)[:400])
158
  results.append((repo, "FAILED"))
159
 
160
  # regenerate docs (manifest / progress / readme) from state
161
  try:
162
  manifest = ["# Manifest β€” eyad-silx/progress (+ overflow repo silx-ai/Progress)", "",
163
+ "Files with `stored in` = silx-ai/Progress live at `ingot-backup/<repo path>` in that repo.",
164
+ "Free-plan private-storage caps (~20 GiB user eyad-silx; org silx-ai also at cap) block",
165
+ "the rest β€” see the Pending section; those bytes live on the build pod and in a local",
166
+ "rescue cache, and push automatically once a cap is lifted (plan upgrade / space freed).", "",
167
  "| repo path | size (bytes) | sha256 | uploaded (UTC) | stored in |",
168
  "|---|---|---|---|---|"]
169
+ for repo in sorted(k for k in state if not k.startswith("_")):
170
  v = state[repo]
171
  manifest.append(f"| `{repo}` | {v['size']:,} | `{v['sha256']}` | {v['uploaded_at']} | {v.get('dest', REPO)} |")
172
+ if pending:
173
+ manifest += ["", "## Pending β€” blocked by private-storage quota", "",
174
+ "| repo path | size (bytes) | sha256 | blocked (UTC) |",
175
+ "|---|---|---|---|"]
176
+ for repo in sorted(pending):
177
+ v = pending[repo]
178
+ manifest.append(f"| `{repo}` | {v['size']:,} | `{v.get('sha256', '(hash pending)')}` | {v['blocked_at']} |")
179
  Path("/tmp/MANIFEST.md").write_text("\n".join(manifest) + "\n")
180
  api.upload_file(path_or_fileobj="/tmp/MANIFEST.md", path_in_repo="MANIFEST.md",
181
  repo_id=REPO, repo_type="model", commit_message="update MANIFEST.md")
 
196
  n_up = sum(1 for r in results if r[1] == "uploaded")
197
  n_un = sum(1 for r in results if r[1] == "unchanged")
198
  n_fail = sum(1 for r in results if r[1] == "FAILED")
199
+ log("run_done", uploaded=n_up, unchanged=n_un, failed=n_fail,
200
+ pending=len(pending))
201
  sys.exit(1 if n_fail else 0)
202
 
203
  if __name__ == "__main__":