File size: 5,309 Bytes
f065e53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""HF ckpt watcher โ€” rate-limit ์นœํ™” ๋ฒ„์ „ (HF ์ปค๋ฐ‹ 128/hour ์ œํ•œ ๋Œ€์‘).

ํ•ต์‹ฌ: ์ปค๋ฐ‹ ์ˆ˜ ์ตœ์†Œํ™”.
- ๋ฌด๊ฑฐ์šด ๋™๊ธฐํ™”(ckpt+logs+plot)๋Š” '์ƒˆ 10k ckpt๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ๋งŒ' ์ˆ˜ํ–‰.
- ์ƒ˜ํ”Œ/ckpt๋Š” upload_folder๋กœ 'ํด๋”๋‹น 1์ปค๋ฐ‹' (ํŒŒ์ผ๋ณ„ ์ปค๋ฐ‹ ๊ธˆ์ง€ โ€” ์ด์ „ ๋ฒ„์ „์ด 429 ์œ ๋ฐœ).
- ckpt: ์ตœ์‹  KEEP๊ฐœ๋งŒ ์œ ์ง€ (๋กœํ…Œ์ด์…˜). ์ƒ˜ํ”Œ: ์ „๋ถ€ ์œ ์ง€.
usage: python hf_ckpt_watcher.py   (setsid nohup / harness background)
"""
import os, re, time
from huggingface_hub import HfApi

TOKEN = os.environ.get("HF_TOKEN", "<SET_HF_TOKEN_ENV>")
REPO = "a12s12/spatial-semanticist-L-migration"
BASE = "/NHNHOME/WORKSPACE/0226010398_A/sr_diffusion/clevr_sudoku/semanticist/output/tokenizer/models_l_spatial"
MODELS_DIR = os.path.join(BASE, "models")
IMAGES_DIR = os.path.join(BASE, "images")
LOGDIR = os.path.join(BASE, "logs/semanticist")
KEEP = 2
POLL_SEC = 300           # 5๋ถ„ ํด๋ง (์ปค๋ฐ‹ ์ ˆ์•ฝ)
REQUIRED = ["model.safetensors", "optimizer.bin"]

api = HfApi(token=TOKEN)


def local_complete_steps():
    out = []
    if not os.path.isdir(MODELS_DIR):
        return out
    for name in os.listdir(MODELS_DIR):
        m = re.fullmatch(r"step(\d+)", name)
        if m:
            d = os.path.join(MODELS_DIR, name)
            if all(os.path.exists(os.path.join(d, r)) for r in REQUIRED):
                out.append(int(m.group(1)))
    return sorted(out)


def hf_steps():
    steps = set()
    for f in api.list_repo_files(REPO):
        m = re.match(r"step(\d+)/", f)
        if m:
            steps.add(int(m.group(1)))
    return sorted(steps)


def sync_samples():
    """IMAGES_DIR ์ „์ฒด๋ฅผ samples_all_steps/ ๋กœ upload_folder (๋ณ€๊ฒฝ๋ถ„๋งŒ 1์ปค๋ฐ‹)."""
    if os.path.isdir(IMAGES_DIR):
        api.upload_folder(folder_path=IMAGES_DIR, path_in_repo="samples_all_steps",
                          repo_id=REPO, repo_type="model",
                          allow_patterns=["*.jpg"],
                          commit_message="sync recon samples")


def sync_logs():
    """loss ๊ทธ๋ž˜ํ”„ ๊ฐฑ์‹  + tensorboard event (์ƒˆ ckpt milestone์—์„œ๋งŒ ํ˜ธ์ถœ)."""
    try:
        import make_loss_plot
        make_loss_plot.main()
        png = os.path.join(BASE, "loss_curves.png")
        if os.path.exists(png):
            api.upload_file(path_or_fileobj=png, path_in_repo="loss_curves.png",
                            repo_id=REPO, repo_type="model",
                            commit_message="update loss curves")
    except Exception as e:
        print(f"[watcher] plot skip: {e}", flush=True)
    if os.path.isdir(LOGDIR):
        api.upload_folder(folder_path=LOGDIR, path_in_repo="logs",
                          repo_id=REPO, repo_type="model",
                          allow_patterns=["events*"],
                          commit_message="update tb logs")


def sync_ckpts():
    """์ตœ์‹  KEEP๊ฐœ ๋กœ์ปฌ ckpt๋งŒ HF์— ์œ ์ง€. ๋ฐ˜ํ™˜: ์ตœ์‹  step (์—†์œผ๋ฉด None)."""
    local = local_complete_steps()
    if not local:
        return None
    on_hf = set(hf_steps())
    target = set(local[-KEEP:])
    for s in sorted(target - on_hf):
        print(f"[watcher] uploading step{s} ...", flush=True)
        api.upload_folder(folder_path=os.path.join(MODELS_DIR, f"step{s}"),
                          path_in_repo=f"step{s}", repo_id=REPO, repo_type="model",
                          commit_message=f"ckpt step{s}")
        print(f"[watcher] step{s} uploaded", flush=True)
    pruned = sorted(on_hf - target)
    for old in pruned:
        print(f"[watcher] pruning HF step{old}", flush=True)
        api.delete_folder(path_in_repo=f"step{old}", repo_id=REPO, repo_type="model",
                          commit_message=f"prune step{old} (keep newest {KEEP})")
    # โ˜… private repo ์ €์žฅํ•œ๋„ ๋Œ€์‘: ๋กœํ…Œ์ด์…˜์œผ๋กœ ์ง€์šด ckpt๋Š” git history์— LFS blob์ด
    #   ๋‚จ์•„ ์šฉ๋Ÿ‰์„ ์žก์•„๋จน๋Š”๋‹ค. prune์ด ์žˆ์—ˆ์œผ๋ฉด history๋ฅผ squashํ•ด์„œ ์‹ค์ œ๋กœ ํšŒ์ˆ˜.
    if pruned:
        try:
            api.super_squash_history(repo_id=REPO, repo_type="model")
            print(f"[watcher] squashed history (freed rotated ckpt LFS)", flush=True)
        except Exception as e:
            print(f"[watcher] squash skip: {str(e)[:120]}", flush=True)
    return local[-1]


def main():
    print(f"[watcher] start (rate-safe). keep={KEEP} poll={POLL_SEC}s repo={REPO}", flush=True)
    last_milestone = None
    while True:
        try:
            local = local_complete_steps()
            newest = local[-1] if local else None
            # ์ƒˆ 10k ckpt๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ๋งŒ ๋ฌด๊ฑฐ์šด ์ „์ฒด ๋™๊ธฐํ™” (์ปค๋ฐ‹ ์ ˆ์•ฝ)
            if newest is not None and newest != last_milestone:
                print(f"[watcher] new milestone step{newest} -> full sync", flush=True)
                new_top = sync_ckpts()
                sync_samples()
                sync_logs()
                last_milestone = new_top
                print(f"[watcher] synced. HF ckpt newest {KEEP} up to step{new_top}", flush=True)
            else:
                # milestone ์‚ฌ์ด์—” ์ƒ˜ํ”Œ๋งŒ ๊ฐ€๋ณ๊ฒŒ (upload_folder=๋ณ€๊ฒฝ๋ถ„ 1์ปค๋ฐ‹)
                sync_samples()
        except Exception as e:
            print(f"[watcher] error (๋‹ค์Œ ํด๋ง์— ์žฌ์‹œ๋„): {str(e)[:200]}", flush=True)
        time.sleep(POLL_SEC)


if __name__ == "__main__":
    main()