File size: 6,869 Bytes
15c6b87 9f37a2c 15c6b87 9f37a2c 15c6b87 9f37a2c 15c6b87 9f37a2c 15c6b87 e8c3967 15c6b87 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | # Hub sync for the mix build: the instance is scratch, so nothing that took an hour to produce may live
# only there (§3.13 "never let the instance be the only copy", applied to a CPU job rather than a
# checkpoint).
#
# Granularity is one *source*: staging a source takes 5-40 minutes, so publishing its shards the moment
# it finishes bounds the loss from any interruption to the one source in flight, and a resume can skip
# every source already on the Hub without re-reading it.
#
# The stage repo is private-in-practice but public by project rule; it holds no credentials and no
# benchmark material -- only token ids from the approved mix.
import json
import os
def repo_id_from_env(default=None):
return os.environ.get("OUNCE100M_STAGE_REPO") or default
def api():
import os
import ounce100m_credentials
ounce100m_credentials.install()
from huggingface_hub import HfApi
return HfApi(token=os.environ["HF_TOKEN"])
def ensure_repo(repo):
# Must install() first: the module-level create_repo() resolves its token from the environment, and
# this is the FIRST hub call a build makes -- before any api() has run. Calling it unauthenticated
# is what produced the misleading 401 in the cold-resume rehearsal (E-017), and the diagnosis in
# these notes was wrong about the cause until p2-token-scope proved dataset writes are permitted.
import os
api()
from huggingface_hub import create_repo
# hub 1.11 (the image's version) takes the owner inside repo_id and has no `namespace` kwarg (E-013)
create_repo(repo_id=repo, repo_type="dataset", private=False, exist_ok=True,
token=os.environ["HF_TOKEN"])
return repo
class StageListingError(RuntimeError):
"""Raised when the stage repo's contents cannot be determined.
Returning an empty set here would be the single most dangerous shortcut in this pipeline: an empty
listing is indistinguishable from "nothing staged yet", so a *broken* listing makes a resume
silently re-stage everything (wasting hours) or, worse, makes a merge believe there is nothing to
merge while still looking consistent. Callers must see the difference between empty and unknown."""
def remote_stage_files(repo):
"""Relative paths under stage/ that exist on the Hub. Raises rather than returning {} on failure."""
import json
import urllib.request
errors = []
a = api()
try:
files = a.list_repo_files(repo_id=repo, repo_type="dataset", revision="main")
return {f for f in files if f.startswith("stage/")}
except Exception as e:
errors.append(f"list_repo_files: {type(e).__name__}: {str(e)[:160]}")
# A freshly created repo has no commits, so "main" does not resolve and list_repo_files raises
# for a reason that genuinely means empty. Distinguish it by asking whether the repo exists at
# all -- NOT by swallowing the exception, which is what made the first version ambiguous.
try:
info = a.repo_info(repo_id=repo, repo_type="dataset")
if not getattr(info, "sha", None):
return set()
except Exception as e2:
errors.append(f"repo_info: {type(e2).__name__}: {str(e2)[:120]}")
# REST fallback. Note the path goes in the query string, not the URL body -- the first version put
# it in the path, got a 404, and the bare `except` turned that into a silent empty listing (E-018).
for url in (f"https://huggingface.co/api/datasets/{repo}/tree/main?recursive=true",
f"https://huggingface.co/api/datasets/{repo}/tree/main"):
try:
with urllib.request.urlopen(url, timeout=90) as r:
entries = json.loads(r.read().decode())
paths = set()
stack = [(e, "") for e in entries]
while stack:
e, prefix = stack.pop()
p = f"{prefix}/{e['path']}".lstrip("/") if prefix else e.get("path", "")
if e.get("type") == "dir":
stack.extend((c, p) for c in (e.get("children") or []))
elif p.startswith("stage/"):
paths.add(p)
return paths
except Exception as e:
errors.append(f"tree {url.split('/api/')[1]}: {type(e).__name__}: {str(e)[:140]}")
raise StageListingError(f"cannot list {repo}: " + " | ".join(errors))
def upload_source(root, key, repo):
"""Publish stage/<key>/* in one commit. Returns the file names pushed."""
a = api()
local = os.path.join(root, "stage", key)
files = sorted(f for f in os.listdir(local) if f.endswith((".bin", ".json")))
if not files:
return []
a.upload_folder(folder_path=local, path_in_repo=f"stage/{key}", repo_id=repo,
repo_type="dataset", commit_message=f"stage {key}: {len(files)} shard(s)",
allow_patterns=["*.bin", "*.json"])
return files
def download_missing(root, keys_wanted, repo, remote=None):
"""Fetch stage/<key>/*.bin for every wanted key not already on disk. Returns {key: [filenames]}."""
from huggingface_hub import hf_hub_download
remote = remote if remote is not None else remote_stage_files(repo)
got = {}
for key in keys_wanted:
# .json must come down too: record.json is what tells the restore the shard list, token count
# and drop stats. A .bin-only filter made every restored source look unknown and was the actual
# cause of "restored 0 staged source(s)" in rehearsal v3, with the files plainly present.
want = sorted(p for p in remote
if p.startswith(f"stage/{key}/") and p.endswith((".bin", ".json")))
if not want:
got[key] = []
continue
local_dir = os.path.join(root, "stage", key)
os.makedirs(local_dir, exist_ok=True)
have = {f"stage/{key}/{f}" for f in os.listdir(local_dir)}
for rp in want:
if rp in have:
continue
hf_hub_download(repo_id=repo, repo_type="dataset", filename=rp,
local_dir=root, force_download=False)
got[key] = sorted(os.listdir(local_dir))
return got
def status(repo):
import urllib.request
url = f"https://huggingface.co/api/datasets/{repo}"
try:
with urllib.request.urlopen(url, timeout=60) as r:
d = json.loads(r.read().decode())
return {"exists": True, "gated": d.get("gated"), "private": d.get("private"),
"lastModified": d.get("lastModified"), "id": d.get("id")}
except Exception as e:
return {"exists": False, "error": f"{type(e).__name__}: {str(e)[:120]}"}
|