File size: 7,208 Bytes
2b0bffa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
"""Push CERNenv artefacts to the Hugging Face Hub.



Two subcommands:



* ``model`` — push trained LoRA adapters (output of ``training_unsloth.py``)

  to a model repo. Generates a model card describing the run.



* ``space`` — push a directory as a Hugging Face Space

  (e.g. ``space/training`` for the trainer Space, or the project root

  to publish the env Space). Front-matter is taken from the README.md

  inside the directory.



Usage:

    python -m scripts.push_to_hub model \\

        --adapter_dir runs/unsloth-grpo \\

        --repo_id YOUR_HF_USERNAME/cernenv-grpo-qwen2.5-3b \\

        --base_model unsloth/Qwen2.5-3B-Instruct



    python -m scripts.push_to_hub space \\

        --space_dir space/training \\

        --repo_id YOUR_HF_USERNAME/cernenv-trainer \\

        --hardware a100-large



    python -m scripts.push_to_hub space \\

        --space_dir . \\

        --repo_id YOUR_HF_USERNAME/cernenv \\

        --include "models.py" "server/**" "openenv.yaml" "pyproject.toml" "client.py" "README.md"

"""

from __future__ import annotations

import argparse
import logging
import os
import sys
from pathlib import Path
from typing import Iterable, List, Optional


logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)


DEFAULT_SPACE_EXCLUDES: List[str] = [
    ".venv/**",
    "__pycache__/**",
    "**/__pycache__/**",
    "*.pyc",
    ".cursor/**",
    ".git/**",
    ".DS_Store",
    "**/.DS_Store",
    "runs/**",
    "training/runs/**",
    "training/plots/**",
    "wandb/**",
    "*.zip",
    "*.apk",
    "*.png",
    "*.jpg",
    "*.jpeg",
    "[External]*.txt",
    "Hackathon FAQs*.txt",
    "*.log",
]


def _hf_login() -> None:
    from huggingface_hub import login

    token = os.environ.get("HF_TOKEN")
    if not token:
        raise SystemExit(
            "HF_TOKEN environment variable is required (write-scoped Hugging Face token)."
        )
    login(token=token)


def _model_card(*, repo_id: str, base_model: str, run_dir: Path) -> str:
    return f"""---

license: bsd-3-clause

library_name: peft

base_model: {base_model}

tags:

  - cernenv

  - openenv

  - reinforcement-learning

  - grpo

  - unsloth

  - lora

  - particle-physics

---



# {repo_id}



LoRA (Low-Rank Adaptation) adapters trained with **GRPO** (Group-Relative

Policy Optimization) inside the **CERNenv** OpenEnv environment — an

LHC (Large Hadron Collider) particle-discovery POMDP (Partially Observable

Markov Decision Process).



The agent (this model) plays the role of a high-energy physicist running an

analysis: it configures the beam, allocates luminosity, picks decay

channels and triggers, reconstructs events, fits resonances, estimates

significance, and finally submits a structured discovery claim that is

graded against a hidden ground-truth particle.



* Base model: `{base_model}`

* RL framework: TRL (Transformer Reinforcement Learning) GRPO

* Acceleration: Unsloth + 4-bit + LoRA

* Environment: [CERNenv](https://huggingface.co/spaces/{repo_id.split('/')[0]}/cernenv)



## Usage



```python

from peft import PeftModel

from transformers import AutoModelForCausalLM, AutoTokenizer



base = "{base_model}"

adapter = "{repo_id}"



tokenizer = AutoTokenizer.from_pretrained(base)

model = AutoModelForCausalLM.from_pretrained(base, device_map="auto")

model = PeftModel.from_pretrained(model, adapter)

```



See the CERNenv repo for full evaluation, plots, and the `LLMAgent` wrapper.

"""


def push_model(

    *,

    adapter_dir: str,

    repo_id: str,

    base_model: str,

    private: bool,

) -> None:
    from huggingface_hub import HfApi, create_repo

    _hf_login()
    api = HfApi()

    run_dir = Path(adapter_dir)
    if not run_dir.exists():
        raise SystemExit(f"adapter_dir not found: {run_dir}")

    create_repo(repo_id=repo_id, repo_type="model", private=private, exist_ok=True)

    card_path = run_dir / "README.md"
    card_path.write_text(_model_card(repo_id=repo_id, base_model=base_model, run_dir=run_dir))

    logger.info("uploading %s → %s", run_dir, repo_id)
    api.upload_folder(
        folder_path=str(run_dir),
        repo_id=repo_id,
        repo_type="model",
        commit_message="Upload CERNenv GRPO LoRA adapters",
    )
    logger.info("done: https://huggingface.co/%s", repo_id)


def push_space(

    *,

    space_dir: str,

    repo_id: str,

    hardware: Optional[str],

    private: bool,

    include: Optional[List[str]],

    exclude: Optional[List[str]],

) -> None:
    from huggingface_hub import HfApi, create_repo

    _hf_login()
    api = HfApi()

    src = Path(space_dir).resolve()
    if not src.exists():
        raise SystemExit(f"space_dir not found: {src}")

    create_repo(
        repo_id=repo_id,
        repo_type="space",
        space_sdk="docker",
        space_hardware=hardware,
        private=private,
        exist_ok=True,
    )

    effective_exclude = list(DEFAULT_SPACE_EXCLUDES)
    if exclude:
        effective_exclude.extend(exclude)

    logger.info("uploading %s → space:%s", src, repo_id)
    logger.info("ignore patterns: %s", effective_exclude)
    api.upload_folder(
        folder_path=str(src),
        repo_id=repo_id,
        repo_type="space",
        commit_message="Update CERNenv Space",
        allow_patterns=include,
        ignore_patterns=effective_exclude,
    )
    logger.info("done: https://huggingface.co/spaces/%s", repo_id)


def main() -> None:  # pragma: no cover
    parser = argparse.ArgumentParser()
    sub = parser.add_subparsers(dest="cmd", required=True)

    m = sub.add_parser("model", help="push trained LoRA adapters to the Hub")
    m.add_argument("--adapter_dir", required=True)
    m.add_argument("--repo_id", required=True)
    m.add_argument("--base_model", required=True)
    m.add_argument("--private", action="store_true")

    s = sub.add_parser("space", help="push a directory as an HF Space")
    s.add_argument("--space_dir", required=True)
    s.add_argument("--repo_id", required=True)
    s.add_argument("--hardware", default=None,
                   help="e.g. a100-large, t4-small, l4-medium")
    s.add_argument("--private", action="store_true")
    s.add_argument("--include", nargs="*", default=None,
                   help="glob patterns to include")
    s.add_argument("--exclude", nargs="*", default=None,
                   help="glob patterns to exclude")

    args = parser.parse_args()

    if args.cmd == "model":
        push_model(
            adapter_dir=args.adapter_dir,
            repo_id=args.repo_id,
            base_model=args.base_model,
            private=args.private,
        )
    elif args.cmd == "space":
        push_space(
            space_dir=args.space_dir,
            repo_id=args.repo_id,
            hardware=args.hardware,
            private=args.private,
            include=args.include,
            exclude=args.exclude,
        )


if __name__ == "__main__":  # pragma: no cover
    main()